update at 2025-10-15 20:00:59
This commit is contained in:
@@ -28,4 +28,7 @@ sk-2546da09b6d9471894aeb95278f96c11
|
|||||||
4. MCP 功能叠加。
|
4. MCP 功能叠加。
|
||||||
✅
|
✅
|
||||||
|
|
||||||
|
5. 优化消息交互。比如标题\内容超长怎么处理❓
|
||||||
|
|
||||||
|
6. 上下文的问题?上下文由谁维护❓以及如何维护❓
|
||||||
|
|
||||||
|
|||||||
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
@@ -28,6 +28,7 @@ declare module 'vue' {
|
|||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
NStatistic: typeof import('naive-ui')['NStatistic']
|
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||||
|
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
ProviderForm: typeof import('./src/components/ProviderForm.vue')['default']
|
ProviderForm: typeof import('./src/components/ProviderForm.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
|||||||
@@ -291,6 +291,13 @@ const themeOverrides = computed(() => {
|
|||||||
primaryColorHover: primaryColor + 'CC',
|
primaryColorHover: primaryColor + 'CC',
|
||||||
primaryColorPressed: primaryColor + '99',
|
primaryColorPressed: primaryColor + '99',
|
||||||
primaryColorSuppl: primaryColor
|
primaryColorSuppl: primaryColor
|
||||||
|
},
|
||||||
|
Tooltip: {
|
||||||
|
color: primaryColor + 'F2', // 95% 透明度
|
||||||
|
textColor: '#ffffff',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '6px 12px'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -78,19 +78,51 @@
|
|||||||
{{ msg.error }}
|
{{ msg.error }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="msg.role === 'assistant' && (msg.status === 'success' || msg.status === 'paused')" class="message-actions">
|
<!-- 用户消息操作按钮 -->
|
||||||
<n-button text size="tiny" @click="handleCopyMessage(msg.content)">
|
<div v-if="msg.role === 'user'" class="message-actions">
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button text size="tiny" type="primary" @click="handleCopyMessage(msg.content)">
|
||||||
<n-icon :component="CopyIcon" size="14" />
|
<n-icon :component="CopyIcon" size="14" />
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
复制
|
复制
|
||||||
</n-button>
|
</n-tooltip>
|
||||||
<n-button text size="tiny" @click="handleRegenerateMessage(msg.id)">
|
<n-tooltip trigger="hover">
|
||||||
<n-icon :component="RefreshIcon" size="14" />
|
<template #trigger>
|
||||||
重新生成
|
<n-button text size="tiny" type="error" @click="handleDeleteMessage(msg.id)">
|
||||||
</n-button>
|
|
||||||
<n-button text size="tiny" @click="handleDeleteMessage(msg.id)">
|
|
||||||
<n-icon :component="TrashIcon" size="14" />
|
<n-icon :component="TrashIcon" size="14" />
|
||||||
删除
|
|
||||||
</n-button>
|
</n-button>
|
||||||
|
</template>
|
||||||
|
删除
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
|
<!-- AI 消息操作按钮 -->
|
||||||
|
<div v-if="msg.role === 'assistant' && (msg.status === 'success' || msg.status === 'paused')" class="message-actions">
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button text size="tiny" type="primary" @click="handleCopyMessage(msg.content)">
|
||||||
|
<n-icon :component="CopyIcon" size="14" />
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
复制
|
||||||
|
</n-tooltip>
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button text size="tiny" type="info" @click="handleRegenerateMessage(msg.id)">
|
||||||
|
<n-icon :component="RefreshIcon" size="14" />
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
重新生成
|
||||||
|
</n-tooltip>
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button text size="tiny" type="error" @click="handleDeleteMessage(msg.id)">
|
||||||
|
<n-icon :component="TrashIcon" size="14" />
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
删除
|
||||||
|
</n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -491,10 +491,10 @@ class ChatService {
|
|||||||
throw new Error('消息不存在')
|
throw new Error('消息不存在')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除该消息之后的所有消息
|
// 删除该消息之后的所有消息(包括当前要重新生成的助手消息)
|
||||||
conversation.messages.splice(messageIndex)
|
conversation.messages.splice(messageIndex)
|
||||||
|
|
||||||
// 获取最后一条用户消息
|
// 获取最后一条用户消息(应该就是刚才删除的助手消息的前一条)
|
||||||
let lastUserMessage: Message | undefined
|
let lastUserMessage: Message | undefined
|
||||||
for (let i = conversation.messages.length - 1; i >= 0; i--) {
|
for (let i = conversation.messages.length - 1; i >= 0; i--) {
|
||||||
if (conversation.messages[i].role === 'user') {
|
if (conversation.messages[i].role === 'user') {
|
||||||
@@ -507,12 +507,53 @@ class ChatService {
|
|||||||
throw new Error('没有找到用户消息')
|
throw new Error('没有找到用户消息')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新发送
|
// 创建新的助手消息(不要重复添加用户消息!)
|
||||||
return await this.sendMessage({
|
const assistantMessage: Message = {
|
||||||
topicId,
|
id: this.generateId(),
|
||||||
content: lastUserMessage.content,
|
role: 'assistant',
|
||||||
|
content: '',
|
||||||
|
status: 'sending',
|
||||||
|
timestamp: new Date(),
|
||||||
model: conversation.metadata?.model
|
model: conversation.metadata?.model
|
||||||
})
|
}
|
||||||
|
|
||||||
|
conversation.messages.push(assistantMessage)
|
||||||
|
conversation.updatedAt = new Date()
|
||||||
|
this.conversations.set(conversation.id, conversation)
|
||||||
|
this.saveConversations()
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用 AI 模型
|
||||||
|
const response = await this.callModel(conversation, conversation.metadata?.model)
|
||||||
|
|
||||||
|
// 更新助手消息
|
||||||
|
assistantMessage.content = response.content
|
||||||
|
assistantMessage.status = 'success'
|
||||||
|
assistantMessage.tokens = response.tokens
|
||||||
|
|
||||||
|
conversation.updatedAt = new Date()
|
||||||
|
this.conversations.set(conversation.id, conversation)
|
||||||
|
this.saveConversations()
|
||||||
|
|
||||||
|
// 更新话题
|
||||||
|
const topic = this.topics.get(topicId)
|
||||||
|
if (topic) {
|
||||||
|
topic.messageCount = conversation.messages.length
|
||||||
|
topic.lastMessage = this.getMessagePreview(response.content)
|
||||||
|
topic.updatedAt = new Date()
|
||||||
|
this.topics.set(topicId, topic)
|
||||||
|
this.saveTopics()
|
||||||
|
}
|
||||||
|
|
||||||
|
return assistantMessage
|
||||||
|
} catch (error) {
|
||||||
|
assistantMessage.status = 'error'
|
||||||
|
assistantMessage.error = error instanceof Error ? error.message : '重新生成失败'
|
||||||
|
conversation.updatedAt = new Date()
|
||||||
|
this.conversations.set(conversation.id, conversation)
|
||||||
|
this.saveConversations()
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 私有方法 ====================
|
// ==================== 私有方法 ====================
|
||||||
@@ -527,15 +568,22 @@ class ChatService {
|
|||||||
const callModelStartTime = performance.now()
|
const callModelStartTime = performance.now()
|
||||||
console.log('⏱️ [callModel] 开始处理', { model, 对话消息数: conversation.messages.length })
|
console.log('⏱️ [callModel] 开始处理', { model, 对话消息数: conversation.messages.length })
|
||||||
|
|
||||||
// 准备消息历史
|
// 准备消息历史(包含 success 和 paused 状态的消息,paused 的消息也包含有效内容)
|
||||||
const beforePrepare = performance.now()
|
const beforePrepare = performance.now()
|
||||||
const messages = conversation.messages
|
let messages = conversation.messages
|
||||||
.filter(m => m.status === 'success')
|
.filter(m => m.status === 'success' || m.status === 'paused')
|
||||||
.map(m => ({
|
.map(m => ({
|
||||||
role: m.role,
|
role: m.role,
|
||||||
content: m.content
|
content: m.content
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// 限制上下文:只保留最近 20 条消息(约 10 轮对话)
|
||||||
|
const MAX_CONTEXT_MESSAGES = 20
|
||||||
|
if (messages.length > MAX_CONTEXT_MESSAGES) {
|
||||||
|
console.log(`📊 [callModel] 限制上下文: ${messages.length} 条 → ${MAX_CONTEXT_MESSAGES} 条`)
|
||||||
|
messages = messages.slice(-MAX_CONTEXT_MESSAGES)
|
||||||
|
}
|
||||||
|
|
||||||
const afterPrepare = performance.now()
|
const afterPrepare = performance.now()
|
||||||
console.log('⏱️ [callModel] 准备消息耗时:', (afterPrepare - beforePrepare).toFixed(2), 'ms', '处理后消息数:', messages.length)
|
console.log('⏱️ [callModel] 准备消息耗时:', (afterPrepare - beforePrepare).toFixed(2), 'ms', '处理后消息数:', messages.length)
|
||||||
|
|
||||||
@@ -634,14 +682,21 @@ class ChatService {
|
|||||||
console.log('⚠️ [callModelStream] 未选择 MCP 服务器,不注入工具')
|
console.log('⚠️ [callModelStream] 未选择 MCP 服务器,不注入工具')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 准备消息历史
|
// 准备消息历史(包含 success 和 paused 状态的消息,paused 的消息也包含有效内容)
|
||||||
let messages = conversation.messages
|
let messages = conversation.messages
|
||||||
.filter(m => m.status === 'success')
|
.filter(m => m.status === 'success' || m.status === 'paused')
|
||||||
.map(m => ({
|
.map(m => ({
|
||||||
role: m.role,
|
role: m.role,
|
||||||
content: m.content
|
content: m.content
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// 限制上下文:只保留最近 20 条消息(约 10 轮对话)
|
||||||
|
const MAX_CONTEXT_MESSAGES = 20
|
||||||
|
if (messages.length > MAX_CONTEXT_MESSAGES) {
|
||||||
|
console.log(`📊 [callModelStream] 限制上下文: ${messages.length} 条 → ${MAX_CONTEXT_MESSAGES} 条`)
|
||||||
|
messages = messages.slice(-MAX_CONTEXT_MESSAGES)
|
||||||
|
}
|
||||||
|
|
||||||
// 如果有工具,添加系统提示词指导 AI 使用工具
|
// 如果有工具,添加系统提示词指导 AI 使用工具
|
||||||
if (tools.length > 0 && messages.length > 0 && messages[0].role !== 'system') {
|
if (tools.length > 0 && messages.length > 0 && messages[0].role !== 'system') {
|
||||||
const systemPrompt = this.createSystemPromptWithTools(tools, mcpServerName)
|
const systemPrompt = this.createSystemPromptWithTools(tools, mcpServerName)
|
||||||
|
|||||||
Reference in New Issue
Block a user