Improve the conversation process

This commit is contained in:
Rafi
2023-04-04 19:16:07 +08:00
parent 16c9b0e230
commit 3e3283029d
14 changed files with 152 additions and 99 deletions

View File

@@ -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
EXPOSE 80
FROM nginx:alpine ENTRYPOINT ["node", ".output/server/index.mjs"]
WORKDIR /app
COPY --from=builder /app/.output/public .
COPY nginx.conf /etc/nginx/templates/default.conf.template
EXPOSE 80

View File

@@ -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>

View File

@@ -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
View 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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
View 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

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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,
} // }
//
} // }
}, // },
}) })

View File

@@ -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>

View 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
)
}
})

View File

@@ -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) => {