Improve the conversation process
This commit is contained in:
15
Dockerfile
15
Dockerfile
@@ -1,5 +1,7 @@
|
|||||||
FROM node:18-alpine3.16 as builder
|
FROM node:18-alpine3.16 as builder
|
||||||
|
|
||||||
|
ENV NITRO_PORT=80
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
@@ -8,15 +10,8 @@ RUN yarn install
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN yarn generate
|
RUN yarn build
|
||||||
|
|
||||||
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=builder /app/.output/public .
|
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/templates/default.conf.template
|
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["node", ".output/server/index.mjs"]
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
||||||
import {addConversation} from "../utils/helper";
|
|
||||||
|
|
||||||
const { $i18n } = useNuxtApp()
|
const { $i18n } = useNuxtApp()
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
@@ -26,6 +25,8 @@ const processMessageQueue = () => {
|
|||||||
}
|
}
|
||||||
isProcessingQueue = true
|
isProcessingQueue = true
|
||||||
const nextMessage = messageQueue.shift()
|
const nextMessage = messageQueue.shift()
|
||||||
|
console.log(runtimeConfig.public.typewriter)
|
||||||
|
console.log(process.evn.NUXT_PUBLIC_TYPEWRITER)
|
||||||
if (runtimeConfig.public.typewriter) {
|
if (runtimeConfig.public.typewriter) {
|
||||||
let wordIndex = 0;
|
let wordIndex = 0;
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
@@ -108,12 +109,12 @@ const fetchReply = async (message) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event === 'done') {
|
if (event === 'done') {
|
||||||
if (props.conversation.id === null) {
|
abortFetch()
|
||||||
|
props.conversation.messages[props.conversation.messages.length - 1].id = data.messageId
|
||||||
|
if (!props.conversation.topic || props.conversation.topic === '') {
|
||||||
props.conversation.id = data.conversationId
|
props.conversation.id = data.conversationId
|
||||||
genTitle(props.conversation.id)
|
genTitle(props.conversation.id)
|
||||||
}
|
}
|
||||||
props.conversation.messages[props.conversation.messages.length - 1].id = data.messageId
|
|
||||||
abortFetch()
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,53 +191,54 @@ watchEffect(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="conversation">
|
||||||
v-if="conversation.loadingMessages"
|
|
||||||
class="text-center"
|
|
||||||
>
|
|
||||||
<v-progress-circular
|
|
||||||
indeterminate
|
|
||||||
color="primary"
|
|
||||||
></v-progress-circular>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div
|
<div
|
||||||
v-if="conversation.messages.length > 0"
|
v-if="conversation.loadingMessages"
|
||||||
ref="chatWindow"
|
class="text-center"
|
||||||
>
|
>
|
||||||
<v-container>
|
<v-progress-circular
|
||||||
<v-row>
|
indeterminate
|
||||||
<v-col
|
color="primary"
|
||||||
v-for="(message, index) in conversation.messages" :key="index"
|
></v-progress-circular>
|
||||||
cols="12"
|
</div>
|
||||||
>
|
<div v-else>
|
||||||
<div
|
<div
|
||||||
class="d-flex align-center"
|
v-if="conversation.messages"
|
||||||
:class="message.is_bot ? 'justify-start' : 'justify-end'"
|
ref="chatWindow"
|
||||||
>
|
>
|
||||||
<MessageActions
|
<v-container>
|
||||||
v-if="!message.is_bot"
|
<v-row>
|
||||||
:message="message"
|
<v-col
|
||||||
:message-index="index"
|
v-for="(message, index) in conversation.messages" :key="index"
|
||||||
:use-prompt="usePrompt"
|
cols="12"
|
||||||
:delete-message="deleteMessage"
|
>
|
||||||
/>
|
<div
|
||||||
<MsgContent :message="message" />
|
class="d-flex align-center"
|
||||||
<MessageActions
|
:class="message.is_bot ? 'justify-start' : 'justify-end'"
|
||||||
v-if="message.is_bot"
|
>
|
||||||
:message="message"
|
<MessageActions
|
||||||
:message-index="index"
|
v-if="!message.is_bot"
|
||||||
:use-prompt="usePrompt"
|
:message="message"
|
||||||
:delete-message="deleteMessage"
|
:message-index="index"
|
||||||
/>
|
:use-prompt="usePrompt"
|
||||||
</div>
|
:delete-message="deleteMessage"
|
||||||
</v-col>
|
/>
|
||||||
</v-row>
|
<MsgContent :message="message" />
|
||||||
</v-container>
|
<MessageActions
|
||||||
|
v-if="message.is_bot"
|
||||||
<div ref="grab" class="w-100" style="height: 200px;"></div>
|
:message="message"
|
||||||
|
:message-index="index"
|
||||||
|
:use-prompt="usePrompt"
|
||||||
|
:delete-message="deleteMessage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<div ref="grab" class="w-100" style="height: 200px;"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Welcome v-if="conversation.id === null && conversation.messages.length === 0" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h2 class="text-h2">{{ $t('welcomeTo') }} <span class="text-primary">{{ runtimeConfig.public.appName }}</span></h2>
|
<h2 class="text-h2">{{ $t('welcomeTo') }} <span class="text-primary">{{ runtimeConfig.public.appName }}</span></h2>
|
||||||
<p class="text-caption mt-5">
|
<p class="text-caption my-5">
|
||||||
{{ runtimeConfig.public.appName }} {{ $t('welcomeScreen.introduction1') }}
|
{{ runtimeConfig.public.appName }} {{ $t('welcomeScreen.introduction1') }}
|
||||||
<br>
|
<br>
|
||||||
{{ $t('welcomeScreen.introduction2') }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|||||||
18
composables/fetch.js
Normal file
18
composables/fetch.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export const useMyFetch = (url, options = {}) => {
|
||||||
|
let defaultOptions = {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (process.server) {
|
||||||
|
defaultOptions.baseURL = process.env.SERVER_DOMAIN
|
||||||
|
}
|
||||||
|
return useFetch(url, Object.assign(defaultOptions, options))
|
||||||
|
}
|
||||||
|
export const useAuthFetch = async (url, options = {}) => {
|
||||||
|
const res = await useMyFetch(url, options)
|
||||||
|
if (res.error.value && res.error.value.status === 401) {
|
||||||
|
await logout()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
@@ -9,6 +9,6 @@ export const useConversation = () => useState('conversation', () => getDefaultCo
|
|||||||
|
|
||||||
export const useConversations = () => useState('conversations', () => [])
|
export const useConversations = () => useState('conversations', () => [])
|
||||||
|
|
||||||
export const useSettings = () => useState('settings', () => {})
|
export const useSettings = () => useState('settings', () => getSystemSettings())
|
||||||
|
|
||||||
export const useUser = () => useState('user', () => null)
|
export const useUser = () => useState('user', () => null)
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
export const useAuthFetch = async (url, options = {}) => {
|
|
||||||
const res = await useFetch(url, options)
|
|
||||||
if (res.error.value && res.error.value.status === 401) {
|
|
||||||
await logout()
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,13 @@ services:
|
|||||||
platform: linux/x86_64
|
platform: linux/x86_64
|
||||||
build: .
|
build: .
|
||||||
environment:
|
environment:
|
||||||
SERVER_DOMAIN: http://web-server
|
SERVER_DOMAIN: ${SERVER_DOMAIN:-http://web-server}
|
||||||
ports:
|
ports:
|
||||||
- '${CLIENT_PORT:-8080}:80'
|
- '${CLIENT_PORT:-80}:80'
|
||||||
networks:
|
networks:
|
||||||
- chatgpt_network
|
- chatgpt_network
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
chatgpt_network:
|
chatgpt_network:
|
||||||
external: True
|
driver: bridge
|
||||||
|
|||||||
16
docker-compose.test.yml
Normal file
16
docker-compose.test.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
client:
|
||||||
|
platform: linux/x86_64
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
SERVER_DOMAIN: ${SERVER_DOMAIN:-http://web-server}
|
||||||
|
ports:
|
||||||
|
- '${CLIENT_PORT:-80}:80'
|
||||||
|
networks:
|
||||||
|
- chatgpt_network
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
chatgpt_network:
|
||||||
|
driver: bridge
|
||||||
@@ -110,9 +110,15 @@ watchEffect(() => {
|
|||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
onMounted(async () => {
|
const navTitle = computed(() => {
|
||||||
|
if (currentConversation.value && currentConversation.value.topic !== null) {
|
||||||
|
return currentConversation.value.topic === '' ? $i18n.t('defaultConversationTitle') : currentConversation.value.topic
|
||||||
|
}
|
||||||
|
return runtimeConfig.public.appName
|
||||||
|
})
|
||||||
|
|
||||||
|
onNuxtReady(async () => {
|
||||||
loadConversations()
|
loadConversations()
|
||||||
loadSettings()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -213,7 +219,7 @@ onMounted(async () => {
|
|||||||
:to="conversation.id ? `/${conversation.id}` : undefined"
|
:to="conversation.id ? `/${conversation.id}` : undefined"
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
>
|
>
|
||||||
<v-list-item-title>{{ conversation.topic !== "" ? conversation.topic : $t('defaultConversationTitle') }}</v-list-item-title>
|
<v-list-item-title>{{ (conversation.topic && conversation.topic !== '') ? conversation.topic : $t('defaultConversationTitle') }}</v-list-item-title>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<div
|
<div
|
||||||
v-show="isHovering && conversation.id"
|
v-show="isHovering && conversation.id"
|
||||||
@@ -334,7 +340,7 @@ onMounted(async () => {
|
|||||||
>
|
>
|
||||||
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
|
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
|
||||||
|
|
||||||
<v-toolbar-title>{{ currentConversation.id ? currentConversation.topic : runtimeConfig.public.appName }}</v-toolbar-title>
|
<v-toolbar-title>{{ navTitle }}</v-toolbar-title>
|
||||||
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||||
// skip middleware on server
|
|
||||||
if (process.server) return
|
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const signInPath = '/account/signin'
|
const signInPath = '/account/signin'
|
||||||
if (!user.value && to.path !== signInPath) {
|
if (!user.value && to.path !== signInPath) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default defineNuxtConfig({
|
|||||||
modules: [
|
modules: [
|
||||||
'@kevinmarrec/nuxt-pwa',
|
'@kevinmarrec/nuxt-pwa',
|
||||||
'@nuxtjs/color-mode',
|
'@nuxtjs/color-mode',
|
||||||
'@nuxtjs/i18n',
|
'@nuxtjs/i18n'
|
||||||
],
|
],
|
||||||
pwa: {
|
pwa: {
|
||||||
manifest: {
|
manifest: {
|
||||||
@@ -69,13 +69,13 @@ export default defineNuxtConfig({
|
|||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nitro: {
|
// nitro: {
|
||||||
devProxy: {
|
// devProxy: {
|
||||||
"/api": {
|
// "/api": {
|
||||||
target: process.env.NUXT_DEV_SERVER ?? 'http://localhost:8000/api',
|
// target: process.env.NUXT_DEV_SERVER ?? 'http://localhost:8000/api',
|
||||||
changeOrigin: true,
|
// changeOrigin: true,
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,14 +22,18 @@ const loadMessage = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateCurrentConversation = () => {
|
||||||
|
currentConversation.value = Object.assign({}, conversation.value)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (route.params.id) {
|
if (route.params.id) {
|
||||||
conversation.value.loadingMessages = true
|
conversation.value.loadingMessages = true
|
||||||
await loadConversation()
|
await loadConversation()
|
||||||
await loadMessage()
|
await loadMessage()
|
||||||
conversation.value.loadingMessages = false
|
conversation.value.loadingMessages = false
|
||||||
|
updateCurrentConversation()
|
||||||
} else {
|
} else {
|
||||||
conversation.value = getDefaultConversationData()
|
|
||||||
watch(currentConversation, (val) => {
|
watch(currentConversation, (val) => {
|
||||||
conversation.value = Object.assign({}, val)
|
conversation.value = Object.assign({}, val)
|
||||||
})
|
})
|
||||||
@@ -37,11 +41,12 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onActivated(async () => {
|
onActivated(async () => {
|
||||||
currentConversation.value = Object.assign({}, conversation.value)
|
updateCurrentConversation()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Conversation :conversation="conversation" />
|
<Welcome v-if="!route.params.id && conversation.messages.length === 0" />
|
||||||
|
<Conversation :conversation="conversation" />
|
||||||
</template>
|
</template>
|
||||||
10
server/middleware/apiProxy.ts
Normal file
10
server/middleware/apiProxy.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { createProxyMiddleware, Filter, Options, RequestHandler } from 'http-proxy-middleware'
|
||||||
|
export default defineEventHandler((event) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (event.node.req.url.startsWith('/api/')) {
|
||||||
|
return proxyRequest(
|
||||||
|
event,
|
||||||
|
(process.env.SERVER_DOMAIN || 'http://localhost:8000') + event.node.req.url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -3,7 +3,7 @@ export const getDefaultConversationData = () => {
|
|||||||
const { $i18n } = useNuxtApp()
|
const { $i18n } = useNuxtApp()
|
||||||
return {
|
return {
|
||||||
id: null,
|
id: null,
|
||||||
topic: $i18n.t('defaultConversationTitle'),
|
topic: null,
|
||||||
messages: [],
|
messages: [],
|
||||||
loadingMessages: false,
|
loadingMessages: false,
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,15 @@ export const getConversations = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createNewConversation = () => {
|
export const createNewConversation = () => {
|
||||||
navigateTo('/')
|
const route = useRoute()
|
||||||
|
const { $i18n } = useNuxtApp()
|
||||||
|
const currentConversation = useConversation()
|
||||||
|
currentConversation.value = Object.assign(getDefaultConversationData(), {
|
||||||
|
topic: $i18n.t('newConversation')
|
||||||
|
})
|
||||||
|
if (route.path !== '/') {
|
||||||
|
return navigateTo('/')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -38,12 +46,17 @@ export const genTitle = async (conversationId) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!error.value) {
|
if (!error.value) {
|
||||||
|
const route = useRoute()
|
||||||
const conversations = useConversations()
|
const conversations = useConversations()
|
||||||
|
const currentConversation = useConversation()
|
||||||
let index = conversations.value.findIndex(item => item.id === conversationId)
|
let index = conversations.value.findIndex(item => item.id === conversationId)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
index = 0
|
index = 0
|
||||||
}
|
}
|
||||||
conversations.value[index].topic = data.value.title
|
conversations.value[index].topic = data.value.title
|
||||||
|
if (route.path === '/') {
|
||||||
|
currentConversation.value.topic = data.value.title
|
||||||
|
}
|
||||||
return data.value.title
|
return data.value.title
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@@ -58,18 +71,18 @@ const transformData = (list) => {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadSettings = async () => {
|
export const getSystemSettings = async () => {
|
||||||
const settings = useSettings()
|
|
||||||
const { data, error } = await useAuthFetch('/api/chat/settings/', {
|
const { data, error } = await useAuthFetch('/api/chat/settings/', {
|
||||||
method: 'GET'
|
method: 'GET',
|
||||||
})
|
})
|
||||||
if (!error.value) {
|
if (!error.value) {
|
||||||
settings.value = transformData(data.value)
|
return transformData(data.value)
|
||||||
}
|
}
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchUser = async () => {
|
export const fetchUser = async () => {
|
||||||
return useFetch('/api/account/user/')
|
return useMyFetch('/api/account/user/')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setUser = (userData) => {
|
export const setUser = (userData) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user