From 9353a96826dc2b29b516ac59c0c386b02c2cb7a8 Mon Sep 17 00:00:00 2001 From: HCode <71808213+hyf-github-user@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:39:57 +0800 Subject: [PATCH] add chat edit (#104) --- ...-844d84c0-8975-4ee8-a722-0aa345d61351.json | 7 + src/components/chat/Message.vue | 131 ++++++++++++++++-- src/i18n/zh-CN/chat.json | 4 + src/models/chat.ts | 6 +- src/pages/chat/Conversation.vue | 82 +++++++++++ 5 files changed, 213 insertions(+), 17 deletions(-) create mode 100644 change/@acedatacloud-nexior-844d84c0-8975-4ee8-a722-0aa345d61351.json diff --git a/change/@acedatacloud-nexior-844d84c0-8975-4ee8-a722-0aa345d61351.json b/change/@acedatacloud-nexior-844d84c0-8975-4ee8-a722-0aa345d61351.json new file mode 100644 index 00000000..c378e0ec --- /dev/null +++ b/change/@acedatacloud-nexior-844d84c0-8975-4ee8-a722-0aa345d61351.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "add edit chat", + "packageName": "@acedatacloud/nexior", + "email": "1348977728@qq.com", + "dependentChangeType": "patch" +} diff --git a/src/components/chat/Message.vue b/src/components/chat/Message.vue index 3faff506..50210d71 100644 --- a/src/components/chat/Message.vue +++ b/src/components/chat/Message.vue @@ -16,20 +16,45 @@
- -
-
- - +
+ + + +
+
+ +
+
+ + +
+
+
+
+ +
+ 取消 + 发送
+
@@ -45,8 +70,9 @@ import { defineComponent } from 'vue'; import AnsweringMark from './AnsweringMark.vue'; import copy from 'copy-to-clipboard'; -import { ElAlert, ElButton, ElImage } from 'element-plus'; +import { ElAlert, ElButton, ElImage, ElTooltip, ElInput } from 'element-plus'; import MarkdownRenderer from '@/components/common/MarkdownRenderer.vue'; +import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { IApplication, IChatMessage, IChatMessageState } from '@/models'; import CopyToClipboard from '../common/CopyToClipboard.vue'; import { @@ -64,6 +90,8 @@ import { ROUTE_CONSOLE_APPLICATION_BUY } from '@/router'; interface IData { copied: boolean; + isEditing: boolean; + questionValue: string; messageState: typeof IChatMessageState; } @@ -75,7 +103,10 @@ export default defineComponent({ MarkdownRenderer, ElAlert, ElButton, - ElImage + ElImage, + ElTooltip, + FontAwesomeIcon, + ElInput }, props: { message: { @@ -87,10 +118,12 @@ export default defineComponent({ required: true } }, - emits: ['stop'], + emits: ['stop', 'update:messages', 'edit'], data(): IData { return { copied: false, + isEditing: false, + questionValue: this.message.content as string, messageState: IChatMessageState }; }, @@ -123,7 +156,24 @@ export default defineComponent({ return this.message.role === ROLE_ASSISTANT && this.message.error?.code === ERROR_CODE_USED_UP; } }, + watch: {}, methods: { + startEditing() { + this.isEditing = true; + this.questionValue = this.message.content as string; + console.debug('start to get answer', this.message); + }, + cancelEdit() { + this.isEditing = false; + }, + sendEdit() { + // Implement the logic to save the edited content + this.isEditing = false; + this.onSubmit(); + }, + onSubmit() { + this.$emit('edit', this.message, this.questionValue); + }, onCopy() { copy(this.message.content!.toString(), { debug: true @@ -209,7 +259,48 @@ export default defineComponent({ width: fit-content; text-align: left; max-width: 100%; - padding: 8px 15px; + position: relative; + .edit-left { + position: absolute; + left: -25px; /* Adjust as needed */ + top: 50%; + transform: translateY(-50%); + } + .chat-container { + // background-color: var(--el-bg-color-page); + // color: var(--el-text-color-primary); + padding: 10px; + width: 100%; + height: 100%; + .chat-input { + // background-color: var(--el-bg-color-page); + // color: var(--el-text-color-primary); + padding-bottom: 30px; /* 为按钮预留空间 */ + } + + .button-group { + position: absolute; + bottom: 10px; + right: 10px; + .cancel-button { + // background-color: #333; + // color: white; + background-color: var(--el-bg-color-page); + color: var(--el-text-color-primary); + border-radius: 20px; + // border: none; + } + + .send-button { + // background-color: white; + // color: black; + background-color: var(--el-bg-color-page); + color: var(--el-text-color-primary); + border-radius: 20px; + // border: none; + } + } + } } } .content { @@ -223,6 +314,18 @@ export default defineComponent({ margin: 5px 0; border-radius: 10px; } + .edit-area { + width: 100%; + min-height: 100px; + border-radius: 10px; + padding: 8px; + margin-bottom: 10px; + } + .edit-buttons { + display: flex; + justify-content: flex-end; + gap: 10px; + } } .operations { diff --git a/src/i18n/zh-CN/chat.json b/src/i18n/zh-CN/chat.json index 10923ea6..6e50fb28 100644 --- a/src/i18n/zh-CN/chat.json +++ b/src/i18n/zh-CN/chat.json @@ -1,4 +1,8 @@ { + "button.edit": { + "message": "编辑消息", + "description": "编辑发送的消息" + }, "model.35Standard": { "message": "3.5 标准", "description": "服务的模型名称,即 GPT 3.5 基础" diff --git a/src/models/chat.ts b/src/models/chat.ts index 9bacd028..f7e12618 100644 --- a/src/models/chat.ts +++ b/src/models/chat.ts @@ -46,13 +46,13 @@ export interface IChatMessage { } export interface IChatConversation { - id: string; - messages: IChatMessage[]; + id?: string; + messages?: IChatMessage[]; title?: string; deleting?: boolean; editing?: boolean; new?: boolean; - updated_at: number; + updated_at?: number; } export interface IChatConversationOptions { diff --git a/src/pages/chat/Conversation.vue b/src/pages/chat/Conversation.vue index 0827923b..39568af2 100644 --- a/src/pages/chat/Conversation.vue +++ b/src/pages/chat/Conversation.vue @@ -16,8 +16,13 @@ v-for="(message, messageIndex) in messages" :key="messageIndex" :message="message" + :messages="messages" + :question="question" :application="application" class="message" + @update:question="question = $event" + @update:messages="messages = $event" + @edit="onEdit" />
@@ -154,6 +159,80 @@ export default defineComponent({ this.question = question; this.onSubmit(); }, + async onEdit(targetMessage: IChatMessage, questionValue: string) { + // 1. Clear the following message + const targetIndex = this.messages.findIndex((message) => message === targetMessage); + if (targetIndex !== -1) { + this.messages = this.messages.slice(0, targetIndex); + } + this.question = questionValue; + // 2. Update the messages + const token = this.credential?.token; + // reset question and references + if (!token) { + console.error('no token or endpoint or question'); + this.messages.push({ + error: { + code: ERROR_CODE_NOT_APPLIED + }, + role: ROLE_ASSISTANT, + state: IChatMessageState.FAILED + }); + return; + } + let conversationId = this.conversationId; + chatOperator + .updateConversation( + { + id: this.conversationId, + messages: this.messages + }, + { + token + } + ) + .then(async () => { + await this.$store.dispatch('chat/setConversation', { + id: conversationId, + messages: this.messages + }); + // 3. Send edited questions + + this.messages.push({ + content: this.question, + role: ROLE_USER + }); + console.debug('onEdit', this.question); + await this.onFetchAnswer(); + }) + .catch((error) => { + if (this.messages && this.messages.length > 0) { + this.messages[this.messages.length - 1].state = IChatMessageState.FAILED; + } + console.error(error); + if (axios.isCancel(error)) { + this.messages[this.messages.length - 1].error = { + code: ERROR_CODE_CANCELED + }; + } else if (error?.response?.data) { + let data = error?.response?.data; + if (isJSONString(data)) { + data = JSON.parse(data); + } + console.debug('error', data); + if (this.messages && this.messages.length > 0) { + this.messages[this.messages.length - 1].error = data.error; + } + } else { + if (this.messages && this.messages.length > 0) { + this.messages[this.messages.length - 1].error = { + code: ERROR_CODE_UNKNOWN + }; + } + } + this.answering = false; + }); + }, async onCreateNewConversation() { await this.$router.push({ name: ROUTE_CHAT_CONVERSATION_NEW @@ -163,6 +242,7 @@ export default defineComponent({ await this.onCreateNewConversation(); await this.$store.dispatch('chat/getApplication'); }, + // Send a message async onSubmit() { if (this.references.length > 0) { let content = []; @@ -190,6 +270,7 @@ export default defineComponent({ console.debug('onSubmit', this.question, this.references); await this.onFetchAnswer(); }, + // Swipe the message to the bottom async onScrollDown() { setTimeout(() => { const container = document.querySelector('.dialogue') as HTMLDivElement; @@ -199,6 +280,7 @@ export default defineComponent({ container.scrollTop = container?.scrollHeight; }, 0); }, + // Get answers to questions async onFetchAnswer() { const token = this.credential?.token; const question = this.question;