维护 Agent 对话
This commit is contained in:
parent
cde6b98336
commit
58df6a599e
@ -79,20 +79,11 @@
|
||||
v-for="message in conv.messages"
|
||||
:message="message"
|
||||
:key="message.id"
|
||||
:role="message.role"
|
||||
:content="message.text"
|
||||
:content-html="message.text.length > 0 ? renderMarkdown(message) : ''"
|
||||
:reasoning-content="message.reasoning_content"
|
||||
:reasoning-header="message.status=='reasoning' ? '正在思考...' : '推理过程'"
|
||||
:status="message.status"
|
||||
:is-processing="isStreaming"
|
||||
:error-message="message.message"
|
||||
:show-refs="true"
|
||||
@retry="retryMessage(message.id)"
|
||||
@retryStoppedMessage="retryStoppedMessage(message.id)"
|
||||
>
|
||||
<template #refs v-if="message.role=='received' && message.status=='finished'">
|
||||
<RefsComponent :message="message" :conv="conv" @retry="retryMessage(message.id)" />
|
||||
</template>
|
||||
</MessageComponent>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
@ -178,13 +169,8 @@ import {
|
||||
CopyOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { Marked } from 'marked';
|
||||
import { markedHighlight } from 'marked-highlight';
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { message } from 'ant-design-vue'
|
||||
import RefsComponent from '@/components/RefsComponent.vue'
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github.css';
|
||||
import MessageInputComponent from '@/components/MessageInputComponent.vue'
|
||||
import MessageComponent from '@/components/MessageComponent.vue'
|
||||
|
||||
@ -232,39 +218,18 @@ const meta = reactive(JSON.parse(localStorage.getItem('meta')) || {
|
||||
wideScreen: false,
|
||||
})
|
||||
|
||||
const marked = new Marked(
|
||||
{
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
tables: true,
|
||||
},
|
||||
markedHighlight({
|
||||
langPrefix: 'hljs language-',
|
||||
highlight(code) {
|
||||
return hljs.highlightAuto(code).value;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const consoleMsg = (msg) => console.log(msg)
|
||||
onClickOutside(panel, () => setTimeout(() => opts.showPanel = false, 30))
|
||||
onClickOutside(modelCard, () => setTimeout(() => opts.showModelCard = false, 30))
|
||||
|
||||
const renderMarkdown = (msg) => {
|
||||
if (msg.status === 'loading') {
|
||||
return marked.parse(msg.text + '🟢')
|
||||
} else {
|
||||
return marked.parse(msg.text)
|
||||
}
|
||||
}
|
||||
|
||||
// 从 message 中获取 history 信息,每个消息都是 {role, content} 的格式
|
||||
const getHistory = () => {
|
||||
const history = conv.value.messages.map((msg) => {
|
||||
if (msg.text) {
|
||||
if (msg.content) {
|
||||
return {
|
||||
role: msg.role === 'sent' ? 'user' : 'assistant',
|
||||
content: msg.text
|
||||
content: msg.content
|
||||
}
|
||||
}
|
||||
}).reduce((acc, cur) => {
|
||||
@ -309,15 +274,15 @@ const handleKeyDown = (e) => {
|
||||
const renameTitle = () => {
|
||||
if (meta.summary_title) {
|
||||
const prompt = '请用一个很短的句子关于下面的对话内容的主题起一个名字,不要带标点符号:'
|
||||
const firstUserMessage = conv.value.messages[0].text
|
||||
const firstAiMessage = conv.value.messages[1].text
|
||||
const firstUserMessage = conv.value.messages[0].content
|
||||
const firstAiMessage = conv.value.messages[1].content
|
||||
const context = `${prompt}\n\n问题: ${firstUserMessage}\n\n回复: ${firstAiMessage},主题是(一句话):`
|
||||
simpleCall(context).then((data) => {
|
||||
const response = data.response.split(":")[0].replace(/^["'"']/g, '').replace(/["'"']$/g, '')
|
||||
emit('rename-title', response)
|
||||
})
|
||||
} else {
|
||||
emit('rename-title', conv.value.messages[0].text)
|
||||
emit('rename-title', conv.value.messages[0].content)
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,16 +318,16 @@ const appendUserMessage = (msg) => {
|
||||
conv.value.messages.push({
|
||||
id: generateRandomHash(16),
|
||||
role: 'sent',
|
||||
text: msg
|
||||
content: msg
|
||||
})
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
const appendAiMessage = (text, refs=null) => {
|
||||
const appendAiMessage = (content, refs=null) => {
|
||||
conv.value.messages.push({
|
||||
id: generateRandomHash(16),
|
||||
role: 'received',
|
||||
text: text,
|
||||
content: content,
|
||||
reasoning_content: '',
|
||||
refs,
|
||||
status: "init",
|
||||
@ -377,8 +342,8 @@ const updateMessage = (info) => {
|
||||
if (msg) {
|
||||
try {
|
||||
// 只有在 text 不为空时更新
|
||||
if (info.text !== null && info.text !== undefined && info.text !== '') {
|
||||
msg.text += info.text;
|
||||
if (info.content !== null && info.content !== undefined && info.content !== '') {
|
||||
msg.content += info.content;
|
||||
}
|
||||
|
||||
if (info.reasoning_content !== null && info.reasoning_content !== undefined && info.reasoning_content !== '') {
|
||||
@ -415,7 +380,7 @@ const updateMessage = (info) => {
|
||||
} catch (error) {
|
||||
console.error('Error updating message:', error);
|
||||
msg.status = 'error';
|
||||
msg.text = '消息更新失败';
|
||||
msg.content = '消息更新失败';
|
||||
}
|
||||
} else {
|
||||
console.error('Message not found:', info.id);
|
||||
@ -512,13 +477,13 @@ const fetchChatResponse = (user_input, cur_res_id) => {
|
||||
const data = JSON.parse(line);
|
||||
updateMessage({
|
||||
id: cur_res_id,
|
||||
text: data.response,
|
||||
content: data.response,
|
||||
reasoning_content: data.reasoning_content,
|
||||
status: data.status,
|
||||
meta: data.meta,
|
||||
...data,
|
||||
});
|
||||
// console.log("Last message", conv.value.messages[conv.value.messages.length - 1].text)
|
||||
// console.log("Last message", conv.value.messages[conv.value.messages.length - 1].content)
|
||||
// console.log("Last message", conv.value.messages[conv.value.messages.length - 1].status)
|
||||
|
||||
if (data.history) {
|
||||
@ -588,7 +553,7 @@ const retryMessage = (id) => {
|
||||
const index = conv.value.messages.findIndex(msg => msg.id === id);
|
||||
const pastMessage = conv.value.messages[index-1]
|
||||
console.log("retryMessage", id, pastMessage)
|
||||
conv.value.inputText = pastMessage.text
|
||||
conv.value.inputText = pastMessage.content
|
||||
if (index !== -1) {
|
||||
conv.value.messages = conv.value.messages.slice(0, index-1);
|
||||
}
|
||||
@ -606,7 +571,7 @@ onMounted(() => {
|
||||
// 检查现有消息中是否有内容为空的情况
|
||||
if (conv.value.messages && conv.value.messages.length > 0) {
|
||||
conv.value.messages.forEach(msg => {
|
||||
if (msg.role === 'received' && (!msg.text || msg.text.trim() === '')) {
|
||||
if (msg.role === 'received' && (!msg.content || msg.content.trim() === '')) {
|
||||
msg.status = 'error';
|
||||
msg.message = '内容加载失败';
|
||||
}
|
||||
@ -669,7 +634,7 @@ const retryStoppedMessage = (id) => {
|
||||
if (messageIndex > 0) {
|
||||
const userMessage = conv.value.messages[messageIndex - 1];
|
||||
if (userMessage && userMessage.role === 'sent') {
|
||||
conv.value.inputText = userMessage.text;
|
||||
conv.value.inputText = userMessage.content;
|
||||
// 删除被停止的消息,以及所有后面的消息
|
||||
conv.value.messages = conv.value.messages.slice(0, messageIndex-1);
|
||||
// sendMessage();
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="message-box" :class="[role, customClasses]">
|
||||
<div class="message-box" :class="[message.role, customClasses]">
|
||||
<!-- 用户消息 -->
|
||||
<p v-if="role === 'user' || role === 'sent'" class="message-text">{{ content }}</p>
|
||||
<p v-if="message.role === 'user' || message.role === 'sent'" class="message-text">{{ message.content }}</p>
|
||||
|
||||
<!-- 助手消息 -->
|
||||
<div v-else-if="role === 'assistant' || role === 'received'" class="assistant-message">
|
||||
<div v-else-if="message.role === 'assistant' || message.role === 'received'" class="assistant-message">
|
||||
<!-- 推理过程 (ChatComponent特有) -->
|
||||
<div v-if="reasoningContent" class="reasoning-box">
|
||||
<div v-if="message.reasoning_content" class="reasoning-box">
|
||||
<a-collapse v-model:activeKey="reasoningActiveKey" :bordered="false">
|
||||
<template #expandIcon="{ isActive }">
|
||||
<caret-right-outlined :rotate="isActive ? 90 : 0" />
|
||||
</template>
|
||||
<a-collapse-panel key="show" :header="reasoningHeader" class="reasoning-header">
|
||||
<p class="reasoning-content">{{ reasoningContent }}</p>
|
||||
<a-collapse-panel key="show" :header="message.status=='reasoning' ? '正在思考...' : '推理过程'" class="reasoning-header">
|
||||
<p class="reasoning-content">{{ message.reasoning_content }}</p>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
@ -25,21 +25,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 检索中状态 (ChatComponent特有) -->
|
||||
<div v-else-if="status === 'searching' && isProcessing" class="searching-msg">
|
||||
<div v-else-if="message.status === 'searching' && isProcessing" class="searching-msg">
|
||||
<i>正在检索……</i>
|
||||
</div>
|
||||
|
||||
<!-- 生成中状态 (ChatComponent特有) -->
|
||||
<div v-else-if="status === 'generating' && isProcessing" class="searching-msg">
|
||||
<div v-else-if="message.status === 'generating' && isProcessing" class="searching-msg">
|
||||
<i>正在生成……</i>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status === 'error'" class="err-msg" @click="$emit('retry')">
|
||||
请求错误,请重试。{{ errorMessage }}
|
||||
<div v-else-if="message.status === 'error'" class="err-msg" @click="$emit('retry')">
|
||||
请求错误,请重试。{{ message.message }}
|
||||
</div>
|
||||
|
||||
<!-- 消息内容 -->
|
||||
<div v-else-if="contentHtml" v-html="contentHtml" class="message-md"></div>
|
||||
<div v-else-if="message.content" v-html="renderMarkdown(message)" class="message-md"></div>
|
||||
|
||||
<div v-if="message.isStoppedByUser" class="retry-hint">
|
||||
你停止生成了本次回答
|
||||
@ -49,8 +49,9 @@
|
||||
<!-- 工具调用 (AgentView特有) -->
|
||||
<slot name="tool-calls"></slot>
|
||||
|
||||
<!-- 引用组件 (ChatComponent特有) -->
|
||||
<slot name="refs"></slot>
|
||||
<div v-if="(message.role=='received' || message.role=='assistant') && message.status=='finished' && showRefs">
|
||||
<RefsComponent :message="message" @retry="emit('retry')" />
|
||||
</div>
|
||||
<!-- 错误消息 -->
|
||||
</div>
|
||||
|
||||
@ -62,6 +63,13 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { CaretRightOutlined } from '@ant-design/icons-vue';
|
||||
import RefsComponent from '@/components/RefsComponent.vue'
|
||||
|
||||
import { Marked } from 'marked';
|
||||
import { markedHighlight } from 'marked-highlight';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github.css';
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
// 消息角色:'user'|'assistant'|'sent'|'received'
|
||||
@ -69,49 +77,25 @@ const props = defineProps({
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 消息内容
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 已渲染的HTML内容
|
||||
contentHtml: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 推理内容 (ChatComponent使用)
|
||||
reasoningContent: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 消息状态
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否正在处理中
|
||||
isProcessing: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 错误信息
|
||||
errorMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义类
|
||||
customClasses: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 推理框标题
|
||||
reasoningHeader: {
|
||||
type: String,
|
||||
default: '推理过程'
|
||||
// 是否显示推理过程
|
||||
showRefs: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
@ -128,10 +112,36 @@ const emit = defineEmits(['retry', 'retryStoppedMessage']);
|
||||
// 推理面板展开状态
|
||||
const reasoningActiveKey = ref(['show']);
|
||||
|
||||
|
||||
const renderMarkdown = (msg) => {
|
||||
if (!msg.content) return '';
|
||||
|
||||
if (msg.status === 'loading') {
|
||||
return marked.parse(msg.content + '🟢')
|
||||
} else {
|
||||
return marked.parse(msg.content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const marked = new Marked(
|
||||
{
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
tables: true,
|
||||
},
|
||||
markedHighlight({
|
||||
langPrefix: 'hljs language-',
|
||||
highlight(code) {
|
||||
return hljs.highlightAuto(code).value;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 计算属性:内容为空且正在加载
|
||||
const isEmptyAndLoading = computed(() => {
|
||||
const isEmpty = !props.content || props.content.length === 0;
|
||||
const isLoading = props.status === 'init'
|
||||
const isEmpty = !props.message.content || props.message.content.length === 0;
|
||||
const isLoading = props.message.status === 'init'
|
||||
return isEmpty && isLoading;
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
<div class="tags">
|
||||
<!-- <span class="item btn" @click="likeThisResponse(msg)"><LikeOutlined /></span> -->
|
||||
<!-- <span class="item btn" @click="dislikeThisResponse(msg)"><DislikeOutlined /></span> -->
|
||||
<span class="item"><BulbOutlined /> {{ msg.meta.server_model_name }}</span>
|
||||
<span class="item btn" @click="copyText(msg.text)" title="复制"><CopyOutlined /></span>
|
||||
<span v-if="msg.meta?.server_model_name" class="item"><BulbOutlined /> {{ msg.meta.server_model_name }}</span>
|
||||
<span class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined /></span>
|
||||
<span class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined /></span>
|
||||
<span
|
||||
class="item btn"
|
||||
@ -120,11 +120,9 @@ import GraphContainer from './GraphContainer.vue' // 导入 GraphContainer 组
|
||||
const emit = defineEmits(['retry']);
|
||||
const props = defineProps({
|
||||
message: Object,
|
||||
conv: Object,
|
||||
})
|
||||
|
||||
const msg = ref(props.message)
|
||||
const conv = ref(props.conv)
|
||||
|
||||
// 使用 useClipboard 实现复制功能
|
||||
const { copy, isSupported } = useClipboard()
|
||||
@ -166,7 +164,7 @@ const toggleDrawer = (filename) => {
|
||||
openDetail[filename] = !openDetail[filename]
|
||||
}
|
||||
|
||||
const showRefs = computed(() => msg.value.role=='received' && msg.value.status=='finished')
|
||||
const showRefs = computed(() => (msg.value.role=='received' || msg.value.role=='assistant') && msg.value.status=='finished')
|
||||
|
||||
const subGraphVisible = ref(false)
|
||||
const subGraphData = ref(null)
|
||||
|
||||
@ -38,13 +38,9 @@
|
||||
<div class="chat-box" ref="messagesContainer" :class="{ 'is-debug': options.debug_mode }">
|
||||
<MessageComponent
|
||||
v-for="(message, index) in messages"
|
||||
:message="message"
|
||||
:key="index"
|
||||
:role="message.role"
|
||||
:content="message.content"
|
||||
:content-html="message.content ? renderMarkdown(message.content) : ''"
|
||||
:status="message.status"
|
||||
:is-processing="isProcessing"
|
||||
:error-message="message.message"
|
||||
@retry="retryMessage(index)"
|
||||
>
|
||||
<div v-if="options.debug_mode" class="status-info">{{ message }}</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user