Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add chat edit #104

Merged
merged 3 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "add edit chat",
"packageName": "@acedatacloud/nexior",
"email": "1348977728@qq.com",
"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
Loading