Skip to content

Commit

Permalink
add chat edit (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyf-github-user authored Aug 14, 2024
1 parent 3d5b719 commit 9353a96
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "add edit chat",
"packageName": "@acedatacloud/nexior",
"email": "[email protected]",
"dependentChangeType": "patch"
}
131 changes: 117 additions & 14 deletions src/components/chat/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,45 @@
</div>
<div v-if="!errorText" class="main">
<div class="content">
<markdown-renderer v-if="!Array.isArray(message.content)" :content="message?.content" />
<div v-else>
<div v-for="(item, index) in message.content" :key="index">
<img
v-if="item.type === 'image_url'"
:src="typeof item?.image_url === 'string' ? item.image_url : item.image_url?.url"
fit="cover"
class="image"
/>
<markdown-renderer v-if="item.type === 'text'" :key="index" :content="item.text" />
<div class="edit-left">
<el-tooltip
v-if="message.role === 'user' && !isEditing"
effect="dark"
:content="$t('chat.button.edit')"
placement="bottom"
>
<font-awesome-icon icon="fa-solid fa-edit" class="icon icon-edit" @click="startEditing" />
</el-tooltip>
</div>
<div v-if="!isEditing" class="message-content">
<markdown-renderer v-if="!Array.isArray(message.content)" :content="message?.content" />
<div v-else>
<div v-for="(item, index) in message.content" :key="index">
<img
v-if="item.type === 'image_url'"
:src="typeof item?.image_url === 'string' ? item.image_url : item.image_url?.url"
fit="cover"
class="image"
/>
<markdown-renderer v-if="item.type === 'text'" :key="index" :content="item.text" />
</div>
</div>
</div>
<div v-else class="chat-container">
<el-input
v-model="questionValue"
type="textarea"
class="chat-input"
@keydown.enter.exact.prevent="sendEdit"
></el-input>
<div class="button-group">
<el-button size="small" class="cancel-button" @click="cancelEdit">取消</el-button>
<el-button type="primary" size="small" class="send-button" @click="sendEdit">发送</el-button>
</div>
</div>
<answering-mark v-if="message.state === messageState.PENDING" />
</div>

<div class="operations">
<copy-to-clipboard v-if="!Array.isArray(message.content)" :content="message.content!" class="btn-copy" />
</div>
Expand All @@ -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 {
Expand All @@ -64,6 +90,8 @@ import { ROUTE_CONSOLE_APPLICATION_BUY } from '@/router';
interface IData {
copied: boolean;
isEditing: boolean;
questionValue: string;
messageState: typeof IChatMessageState;
}
Expand All @@ -75,7 +103,10 @@ export default defineComponent({
MarkdownRenderer,
ElAlert,
ElButton,
ElImage
ElImage,
ElTooltip,
FontAwesomeIcon,
ElInput
},
props: {
message: {
Expand All @@ -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
};
},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/zh-CN/chat.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"button.edit": {
"message": "编辑消息",
"description": "编辑发送的消息"
},
"model.35Standard": {
"message": "3.5 标准",
"description": "服务的模型名称,即 GPT 3.5 基础"
Expand Down
6 changes: 3 additions & 3 deletions src/models/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
82 changes: 82 additions & 0 deletions src/pages/chat/Conversation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
</div>
</div>
Expand Down Expand Up @@ -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
Expand All @@ -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 = [];
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit 9353a96

Please sign in to comment.