Multiple improvements for conversation

This commit is contained in:
Rafi
2023-03-30 21:45:23 +08:00
parent 97649e4bee
commit f67ed7621c
7 changed files with 95 additions and 61 deletions

View File

@@ -1,5 +1,6 @@
<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, $auth } = useNuxtApp() const { $i18n, $auth } = useNuxtApp()
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
@@ -137,9 +138,17 @@ const scrollChatWindow = () => {
grab.value.scrollIntoView({behavior: 'smooth'}) grab.value.scrollIntoView({behavior: 'smooth'})
} }
const checkOrAddConversation = () => {
if (props.conversation.messages.length === 0) {
props.conversation.messages.push({id: null, is_bot: true, message: ''})
}
}
const send = (message) => { const send = (message) => {
fetchingResponse.value = true fetchingResponse.value = true
if (props.conversation.messages.length === 0) {
addConversation(props.conversation)
}
props.conversation.messages.push({message: message}) props.conversation.messages.push({message: message})
fetchReply(message) fetchReply(message)
scrollChatWindow() scrollChatWindow()
@@ -182,7 +191,17 @@ watchEffect(() => {
<template> <template>
<div <div
v-if="conversation.messages" v-if="conversation.loadingMessages"
class="text-center"
>
<v-progress-circular
indeterminate
color="primary"
></v-progress-circular>
</div>
<div v-else>
<div
v-if="conversation.messages.length > 0"
ref="chatWindow" ref="chatWindow"
> >
<v-container> <v-container>
@@ -217,7 +236,10 @@ watchEffect(() => {
<div ref="grab" class="w-100" style="height: 200px;"></div> <div ref="grab" class="w-100" style="height: 200px;"></div>
</div> </div>
<Welcome v-else /> <Welcome v-if="conversation.id === null && conversation.messages.length === 0" />
</div>
<v-footer app> <v-footer app>
<div class="px-md-16 w-100 d-flex flex-column"> <div class="px-md-16 w-100 d-flex flex-column">
<div class="d-flex align-center"> <div class="d-flex align-center">

View File

@@ -21,6 +21,7 @@
"themeMode": "Theme Mode", "themeMode": "Theme Mode",
"feedback": "Feedback", "feedback": "Feedback",
"newConversation": "New conversation", "newConversation": "New conversation",
"defaultConversationTitle": "Unnamed",
"clearConversations": "Clear conversations", "clearConversations": "Clear conversations",
"modelParameters": "Model Parameters", "modelParameters": "Model Parameters",
"model": "Model", "model": "Model",
@@ -40,6 +41,7 @@
"signOut": "Sign out", "signOut": "Sign out",
"webSearch": "Web Search", "webSearch": "Web Search",
"webSearchDefaultPrompt": "Web search results:\n\n[web_results]\nCurrent date: [current_date]\n\nInstructions: Using the provided web search results, write a comprehensive reply to the given query. Make sure to cite results using [[number](URL)] notation after the reference. If the provided search results refer to multiple subjects with the same name, write separate answers for each subject.\nQuery: [query]", "webSearchDefaultPrompt": "Web search results:\n\n[web_results]\nCurrent date: [current_date]\n\nInstructions: Using the provided web search results, write a comprehensive reply to the given query. Make sure to cite results using [[number](URL)] notation after the reference. If the provided search results refer to multiple subjects with the same name, write separate answers for each subject.\nQuery: [query]",
"genTitlePrompt": "Generate a short title for the following content, no more than 10 words. \n\nContent: ",
"welcomeScreen": { "welcomeScreen": {
"introduction1": "is an unofficial client for ChatGPT, but uses the official OpenAI API.", "introduction1": "is an unofficial client for ChatGPT, but uses the official OpenAI API.",
"introduction2": "You will need an OpenAI API Key before you can use this client.", "introduction2": "You will need an OpenAI API Key before you can use this client.",

View File

@@ -21,6 +21,7 @@
"themeMode": "Тема", "themeMode": "Тема",
"feedback": "Обратная связь", "feedback": "Обратная связь",
"newConversation": "Новый чат", "newConversation": "Новый чат",
"defaultConversationTitle": "Безымянный",
"clearConversations": "Очистить чаты", "clearConversations": "Очистить чаты",
"modelParameters": "Параметры модели", "modelParameters": "Параметры модели",
"model": "Модель", "model": "Модель",
@@ -40,6 +41,7 @@
"signOut": "Выход", "signOut": "Выход",
"webSearch": "Поиск в интернете", "webSearch": "Поиск в интернете",
"webSearchDefaultPrompt": "Результаты веб-поиска:\n\n[web_results]\nТекущая дата: [current_date]\n\nИнструкции: Используя предоставленные результаты веб-поиска, напишите развернутый ответ на заданный запрос. Обязательно цитируйте результаты, используя обозначение [[number](URL)] после ссылки. Если предоставленные результаты поиска относятся к нескольким темам с одинаковым названием, напишите отдельные ответы для каждой темы.\nЗапрос: [query]", "webSearchDefaultPrompt": "Результаты веб-поиска:\n\n[web_results]\nТекущая дата: [current_date]\n\nИнструкции: Используя предоставленные результаты веб-поиска, напишите развернутый ответ на заданный запрос. Обязательно цитируйте результаты, используя обозначение [[number](URL)] после ссылки. Если предоставленные результаты поиска относятся к нескольким темам с одинаковым названием, напишите отдельные ответы для каждой темы.\nЗапрос: [query]",
"genTitlePrompt": "Придумайте короткий заголовок для следующего содержания, не более 10 слов. \n\nСодержание: ",
"welcomeScreen": { "welcomeScreen": {
"introduction1": "является неофициальным клиентом для ChatGPT, но использует официальный API OpenAI.", "introduction1": "является неофициальным клиентом для ChatGPT, но использует официальный API OpenAI.",
"introduction2": "Вам понадобится ключ API OpenAI, прежде чем вы сможете использовать этот клиент.", "introduction2": "Вам понадобится ключ API OpenAI, прежде чем вы сможете использовать этот клиент.",

View File

@@ -21,6 +21,7 @@
"themeMode": "主题模式", "themeMode": "主题模式",
"feedback": "反馈", "feedback": "反馈",
"newConversation": "新的对话", "newConversation": "新的对话",
"defaultConversationTitle": "未命名",
"clearConversations": "清除对话", "clearConversations": "清除对话",
"modelParameters": "模型参数", "modelParameters": "模型参数",
"model": "模型", "model": "模型",
@@ -40,6 +41,7 @@
"signOut": "退出登录", "signOut": "退出登录",
"webSearch": "网页搜索", "webSearch": "网页搜索",
"webSearchDefaultPrompt": "网络搜索结果:\n\n[web_results]\n当前日期[current_date]\n\n说明使用提供的网络搜索结果对给定的查询写出全面的回复。确保在引用参考文献后使用 [[number](URL)] 符号进行引用结果. 如果提供的搜索结果涉及到多个具有相同名称的主题,请针对每个主题编写单独的答案。\n查询[query]", "webSearchDefaultPrompt": "网络搜索结果:\n\n[web_results]\n当前日期[current_date]\n\n说明使用提供的网络搜索结果对给定的查询写出全面的回复。确保在引用参考文献后使用 [[number](URL)] 符号进行引用结果. 如果提供的搜索结果涉及到多个具有相同名称的主题,请针对每个主题编写单独的答案。\n查询[query]",
"genTitlePrompt": "为以下内容生成一个不超过10个字的简短标题。 \n\n内容: ",
"welcomeScreen": { "welcomeScreen": {
"introduction1": "是一个非官方的ChatGPT客户端但使用OpenAI的官方API", "introduction1": "是一个非官方的ChatGPT客户端但使用OpenAI的官方API",
"introduction2": "在使用本客户端之前您需要一个OpenAI API密钥。", "introduction2": "在使用本客户端之前您需要一个OpenAI API密钥。",

View File

@@ -54,6 +54,7 @@ const deleteConversation = async (index) => {
deletingConversationIndex.value = null deletingConversationIndex.value = null
if (!error.value) { if (!error.value) {
if (conversations.value[index].id === currentConversation.value.id) { if (conversations.value[index].id === currentConversation.value.id) {
console.log('delete current conversation')
createNewConversation() createNewConversation()
} }
conversations.value.splice(index, 1) conversations.value.splice(index, 1)
@@ -130,8 +131,8 @@ onMounted(async () => {
block block
variant="outlined" variant="outlined"
prepend-icon="add" prepend-icon="add"
@click="createNewConversation()"
class="text-none" class="text-none"
@click="createNewConversation"
> >
{{ $t('newConversation') }} {{ $t('newConversation') }}
</v-btn> </v-btn>
@@ -172,19 +173,19 @@ onMounted(async () => {
<v-list-item <v-list-item
rounded="xl" rounded="xl"
active-color="primary" active-color="primary"
:to="`/${conversation.id}`" :to="conversation.id ? `/${conversation.id}` : undefined"
v-bind="props" v-bind="props"
> >
<v-list-item-title>{{ conversation.topic }}</v-list-item-title> <v-list-item-title>{{ conversation.topic }}</v-list-item-title>
<template v-slot:append> <template v-slot:append>
<div <div
v-show="isHovering" v-show="isHovering && conversation.id"
> >
<v-btn <v-btn
icon="edit" icon="edit"
size="small" size="small"
variant="text" variant="text"
@click.stop="editConversation(cIdx)" @click.prevent="editConversation(cIdx)"
> >
</v-btn> </v-btn>
<v-btn <v-btn
@@ -192,7 +193,7 @@ onMounted(async () => {
size="small" size="small"
variant="text" variant="text"
:loading="deletingConversationIndex === cIdx" :loading="deletingConversationIndex === cIdx"
@click.stop="deleteConversation(cIdx)" @click.prevent="deleteConversation(cIdx)"
> >
</v-btn> </v-btn>
</div> </div>

View File

@@ -1,26 +1,32 @@
<script setup> <script setup>
import {getDefaultConversationData} from "~/utils/helper";
definePageMeta({ definePageMeta({
middleware: ["auth"], middleware: ["auth"],
path: '/:id?', path: '/:id?',
keepalive: true keepalive: true
}) })
const route = useRoute() const route = useRoute()
const conversation = ref({}) const currentConversation = useConversation()
const conversation = ref(getDefaultConversationData())
watchEffect(() => {
if (!route.params.id) {
conversation.value = getDefaultConversationData()
}
})
const loadMessage = async () => { const loadMessage = async () => {
conversation.value = Object.assign(conversation.value, conversation)
conversation.value.loadingMessages = true conversation.value.loadingMessages = true
const { data, error } = await useAuthFetch('/api/chat/messages/?conversationId=' + route.params.id) const { data, error } = await useAuthFetch('/api/chat/messages/?conversationId=' + route.params.id)
if (!error.value) { if (!error.value) {
conversation.value.messages = data.value conversation.value.messages = data.value
} }
conversation.value.loadingMessages = true conversation.value.loadingMessages = false
} }
onMounted(async () => { onMounted(async () => {
if (route.params.id) { if (route.params.id) {
conversation.value.id = parseInt(route.params.id)
await loadMessage() await loadMessage()
} }
currentConversation.value = Object.assign({}, conversation.value)
}) })
</script> </script>

View File

@@ -1,8 +1,9 @@
export const getDefaultConversationData = () => { export const getDefaultConversationData = () => {
const { $i18n } = useNuxtApp()
return { return {
id: null, id: null,
topic: null, topic: $i18n.t('defaultConversationTitle'),
messages: [], messages: [],
loadingMessages: false, loadingMessages: false,
} }
@@ -19,34 +20,32 @@ export const getConversations = async () => {
export const createNewConversation = () => { export const createNewConversation = () => {
const conversation = useConversation() const conversation = useConversation()
conversation.value = getDefaultConversationData() conversation.value = getDefaultConversationData()
navigateTo('/')
} }
export const openConversationMessages = async (currentConversation) => {
const conversation = useConversation() export const addConversation = (conversation) => {
conversation.value = Object.assign(conversation.value, currentConversation) const conversations = useConversations()
conversation.value.loadingMessages = true conversations.value = [conversation, ...conversations.value]
const { data, error } = await useAuthFetch('/api/chat/messages/?conversationId=' + currentConversation.id)
if (!error.value) {
conversation.value.messages = data.value
}
conversation.value.loadingMessages = true
} }
export const genTitle = async (conversationId) => { export const genTitle = async (conversationId) => {
const { $i18n } = useNuxtApp()
const { data, error } = await useAuthFetch('/api/gen_title/', { const { data, error } = await useAuthFetch('/api/gen_title/', {
method: 'POST', method: 'POST',
body: { body: {
conversationId: conversationId conversationId: conversationId,
prompt: $i18n.t('genTitlePrompt')
} }
}) })
if (!error.value) { if (!error.value) {
const conversation = {
id: conversationId,
topic: data.value.title,
}
const conversations = useConversations() const conversations = useConversations()
// prepend to conversations let index = conversations.value.findIndex(item => item.id === conversationId)
conversations.value = [conversation, ...conversations.value] if (index === -1) {
index = 0
}
conversations.value[index].topic = data.value.title
return data.value.title return data.value.title
} }
return null return null