update at 2025-10-15 20:00:59
This commit is contained in:
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']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
ProviderForm: typeof import('./src/components/ProviderForm.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
|
||||
@@ -291,6 +291,13 @@ const themeOverrides = computed(() => {
|
||||
primaryColorHover: primaryColor + 'CC',
|
||||
primaryColorPressed: primaryColor + '99',
|
||||
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 }}
|
||||
</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)">
|
||||
<n-icon :component="CopyIcon" size="14" />
|
||||
<!-- 用户消息操作按钮 -->
|
||||
<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-button>
|
||||
</template>
|
||||
复制
|
||||
</n-button>
|
||||
<n-button text size="tiny" @click="handleRegenerateMessage(msg.id)">
|
||||
<n-icon :component="RefreshIcon" size="14" />
|
||||
重新生成
|
||||
</n-button>
|
||||
<n-button text size="tiny" @click="handleDeleteMessage(msg.id)">
|
||||
<n-icon :component="TrashIcon" size="14" />
|
||||
</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-button>
|
||||
</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>
|
||||
|
||||
@@ -491,10 +491,10 @@ class ChatService {
|
||||
throw new Error('消息不存在')
|
||||
}
|
||||
|
||||
// 删除该消息之后的所有消息
|
||||
// 删除该消息之后的所有消息(包括当前要重新生成的助手消息)
|
||||
conversation.messages.splice(messageIndex)
|
||||
|
||||
// 获取最后一条用户消息
|
||||
// 获取最后一条用户消息(应该就是刚才删除的助手消息的前一条)
|
||||
let lastUserMessage: Message | undefined
|
||||
for (let i = conversation.messages.length - 1; i >= 0; i--) {
|
||||
if (conversation.messages[i].role === 'user') {
|
||||
@@ -507,12 +507,53 @@ class ChatService {
|
||||
throw new Error('没有找到用户消息')
|
||||
}
|
||||
|
||||
// 重新发送
|
||||
return await this.sendMessage({
|
||||
topicId,
|
||||
content: lastUserMessage.content,
|
||||
// 创建新的助手消息(不要重复添加用户消息!)
|
||||
const assistantMessage: Message = {
|
||||
id: this.generateId(),
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
status: 'sending',
|
||||
timestamp: new Date(),
|
||||
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()
|
||||
console.log('⏱️ [callModel] 开始处理', { model, 对话消息数: conversation.messages.length })
|
||||
|
||||
// 准备消息历史
|
||||
// 准备消息历史(包含 success 和 paused 状态的消息,paused 的消息也包含有效内容)
|
||||
const beforePrepare = performance.now()
|
||||
const messages = conversation.messages
|
||||
.filter(m => m.status === 'success')
|
||||
let messages = conversation.messages
|
||||
.filter(m => m.status === 'success' || m.status === 'paused')
|
||||
.map(m => ({
|
||||
role: m.role,
|
||||
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()
|
||||
console.log('⏱️ [callModel] 准备消息耗时:', (afterPrepare - beforePrepare).toFixed(2), 'ms', '处理后消息数:', messages.length)
|
||||
|
||||
@@ -634,14 +682,21 @@ class ChatService {
|
||||
console.log('⚠️ [callModelStream] 未选择 MCP 服务器,不注入工具')
|
||||
}
|
||||
|
||||
// 准备消息历史
|
||||
// 准备消息历史(包含 success 和 paused 状态的消息,paused 的消息也包含有效内容)
|
||||
let messages = conversation.messages
|
||||
.filter(m => m.status === 'success')
|
||||
.filter(m => m.status === 'success' || m.status === 'paused')
|
||||
.map(m => ({
|
||||
role: m.role,
|
||||
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 使用工具
|
||||
if (tools.length > 0 && messages.length > 0 && messages[0].role !== 'system') {
|
||||
const systemPrompt = this.createSystemPromptWithTools(tools, mcpServerName)
|
||||
|
||||
Reference in New Issue
Block a user