diff --git a/electron/main/ai/agent.ts b/electron/main/ai/agent.ts index 59f72f3..d8ff783 100644 --- a/electron/main/ai/agent.ts +++ b/electron/main/ai/agent.ts @@ -147,7 +147,7 @@ function getLockedPromptSection(chatType: 'group' | 'private'): string { ` : `成员查询策略: - 当用户提到特定群成员(如"张三说过什么"、"小明的发言"等)时,应先调用 get_group_members 获取成员列表 -- 群成员有三种名称:accountName(QQ原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名) +- 群成员有三种名称:accountName(原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名) - 通过 get_group_members 的 search 参数可以模糊搜索这三种名称 - 找到成员后,使用其 id 字段作为 search_messages 的 sender_id 参数来获取该成员的发言 ` @@ -553,6 +553,16 @@ export class Agent { let toolParams: Record | undefined try { toolParams = JSON.parse(tc.function.arguments || '{}') + + // 对于消息获取类工具,用用户配置的 limit 覆盖 LLM 传递的值(保持显示一致) + const toolsWithLimit = ['search_messages', 'get_recent_messages', 'get_conversation_between'] + if (this.context.maxMessagesLimit && toolsWithLimit.includes(tc.function.name)) { + toolParams = { + ...toolParams, + limit: this.context.maxMessagesLimit, // 用户配置优先 + } + } + // 对于搜索类工具,添加时间范围信息 if ( this.context.timeFilter && diff --git a/electron/main/ai/tools/registry.ts b/electron/main/ai/tools/registry.ts index fdfecbc..59703a5 100644 --- a/electron/main/ai/tools/registry.ts +++ b/electron/main/ai/tools/registry.ts @@ -54,8 +54,8 @@ async function searchMessagesExecutor( context: ToolContext ): Promise { const { sessionId, timeFilter: contextTimeFilter, maxMessagesLimit } = context - // 优先使用 LLM 指定的 limit,其次使用用户配置,最后使用默认值 200,上限 5000 - const limit = Math.min(params.limit || maxMessagesLimit || 200, 5000) + // 用户配置优先:如果用户设置了 maxMessagesLimit,使用它;否则使用 LLM 指定的值或默认值 200,上限 5000 + const limit = Math.min(maxMessagesLimit || params.limit || 200, 5000) // 构建时间过滤器:优先使用 LLM 指定的年/月,否则使用 context 中的 let effectiveTimeFilter = contextTimeFilter @@ -144,8 +144,15 @@ async function getRecentMessagesExecutor( context: ToolContext ): Promise { const { sessionId, timeFilter: contextTimeFilter, maxMessagesLimit } = context - // 优先使用 LLM 指定的 limit,其次使用用户配置,最后使用默认值 100 - const limit = params.limit || maxMessagesLimit || 100 + // 用户配置优先:如果用户设置了 maxMessagesLimit,使用它;否则使用 LLM 指定的值或默认值 100 + const limit = maxMessagesLimit || params.limit || 100 + + console.log('[getRecentMessages] 参数调试:', { + 'params.limit': params.limit, + 'context.maxMessagesLimit': maxMessagesLimit, + '计算后的 limit': limit, + 'context 完整内容': JSON.stringify(context), + }) // 构建时间过滤器:优先使用 LLM 指定的年/月 let effectiveTimeFilter = contextTimeFilter @@ -495,8 +502,8 @@ async function getConversationBetweenExecutor( context: ToolContext ): Promise { const { sessionId, timeFilter: contextTimeFilter, maxMessagesLimit } = context - // 优先使用 LLM 指定的 limit,其次使用用户配置,最后使用默认值 100 - const limit = params.limit || maxMessagesLimit || 100 + // 用户配置优先:如果用户设置了 maxMessagesLimit,使用它;否则使用 LLM 指定的值或默认值 100 + const limit = maxMessagesLimit || params.limit || 100 // 构建时间过滤器 let effectiveTimeFilter = contextTimeFilter diff --git a/electron/main/ipc/ai.ts b/electron/main/ipc/ai.ts index 4ebb233..1f01013 100644 --- a/electron/main/ipc/ai.ts +++ b/electron/main/ipc/ai.ts @@ -476,11 +476,13 @@ export function registerAIHandlers({ win }: IpcContext): void { if (abortController.signal.aborted) { return } - aiLogger.debug('IPC', `Agent chunk: ${requestId}`, { - type: chunk.type, - contentLength: chunk.content?.length, - toolName: chunk.toolName, - }) + // 减少日志噪音:只在工具调用时记录 + if (chunk.type === 'tool_start' || chunk.type === 'tool_result') { + aiLogger.debug('IPC', `Agent chunk: ${requestId}`, { + type: chunk.type, + toolName: chunk.toolName, + }) + } win.webContents.send('agent:streamChunk', { requestId, chunk }) }) diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 5d8f9b8..cba0d63 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -472,7 +472,15 @@ const aiApi = { dataMessageCount?: number, contentBlocks?: ContentBlock[] ): Promise => { - return ipcRenderer.invoke('ai:addMessage', conversationId, role, content, dataKeywords, dataMessageCount, contentBlocks) + return ipcRenderer.invoke( + 'ai:addMessage', + conversationId, + role, + content, + dataKeywords, + dataMessageCount, + contentBlocks + ) }, /** diff --git a/src/components/common/SettingModal.vue b/src/components/common/SettingModal.vue index ea29f76..7dfcece 100644 --- a/src/components/common/SettingModal.vue +++ b/src/components/common/SettingModal.vue @@ -42,9 +42,9 @@ watch( () => props.open, (newVal) => { if (newVal) { - activeTab.value = 'ai-config' - // 刷新 AI 配置 - aiConfigRef.value?.refresh() + activeTab.value = 'settings' // 默认打开基础设置 Tab + // 刷新缓存管理 + cacheManageRef.value?.refresh() } } ) diff --git a/src/components/common/Sidebar.vue b/src/components/common/Sidebar.vue index 3c7be20..c7dcc91 100644 --- a/src/components/common/Sidebar.vue +++ b/src/components/common/Sidebar.vue @@ -4,10 +4,6 @@ import { storeToRefs } from 'pinia' import { ref, onMounted, nextTick } from 'vue' import { useRouter, useRoute } from 'vue-router' import type { AnalysisSession } from '@/types/chat' -import pkg from '../../../package.json' - -const { version } = pkg - import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import 'dayjs/locale/zh-cn' @@ -331,11 +327,6 @@ function isPrivateChat(session: AnalysisSession): boolean { 设置 - - -
- v{{ version }} -
diff --git a/src/components/common/settings/AIPromptConfigTab.vue b/src/components/common/settings/AIPromptConfigTab.vue index eab2871..df5dbb9 100644 --- a/src/components/common/settings/AIPromptConfigTab.vue +++ b/src/components/common/settings/AIPromptConfigTab.vue @@ -105,9 +105,9 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea

- 群聊预设 + 群聊系统提示词

- + 添加 @@ -141,17 +141,17 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea
{{ preset.name }} - 内置 + 内置
- + {{ preset.isBuiltIn ? '查看' : '编辑' }} - 复制 - + 复制 + 删除
@@ -162,14 +162,14 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea
- +

- 私聊预设 + 私聊系统提示词

- + 添加 @@ -203,33 +203,23 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea
{{ preset.name }} - 内置 + 内置
- + {{ preset.isBuiltIn ? '查看' : '编辑' }} - 复制 - + 复制 + 删除
- - -
-
- -

- 点击预设即可激活。时间信息和工具说明由系统自动注入,你只需编辑角色定义和回答要求。 -

-
-
diff --git a/src/components/common/settings/AIPromptEditModal.vue b/src/components/common/settings/AIPromptEditModal.vue index 65166ef..88c6a81 100644 --- a/src/components/common/settings/AIPromptEditModal.vue +++ b/src/components/common/settings/AIPromptEditModal.vue @@ -32,8 +32,8 @@ const formData = ref({ const isBuiltIn = computed(() => props.preset?.isBuiltIn ?? false) const isEditMode = computed(() => props.mode === 'edit') const modalTitle = computed(() => { - if (isBuiltIn.value) return '查看预设' - return isEditMode.value ? '编辑预设' : '添加预设' + if (isBuiltIn.value) return '查看系统提示词' + return isEditMode.value ? '编辑系统提示词' : '添加系统提示词' }) const canSave = computed(() => { @@ -149,7 +149,7 @@ function getLockedPromptSection(chatType: string): string { - 当用户提到"对方"、"他/她"时,通过 get_group_members 获取另一方信息` : `成员查询策略: - 当用户提到特定群成员(如"张三说过什么"、"小明的发言"等)时,应先调用 get_group_members 获取成员列表 -- 群成员有三种名称:accountName(QQ原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名) +- 群成员有三种名称:accountName(原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名) - 通过 get_group_members 的 search 参数可以模糊搜索这三种名称 - 找到成员后,使用其 id 字段作为 search_messages 的 sender_id 参数来获取该成员的发言` @@ -213,12 +213,15 @@ ${formData.value.responseRules}`
- +
- + 适用于{{ formData.chatType === 'group' ? '群聊' : '私聊' }}
@@ -227,10 +230,10 @@ ${formData.value.responseRules}` @@ -245,7 +248,7 @@ ${formData.value.responseRules}` :rows="5" placeholder="定义 AI 回答的格式和要求..." :disabled="isBuiltIn" - class="font-mono text-sm" + class="font-mono text-sm w-120" /> @@ -256,7 +259,7 @@ ${formData.value.responseRules}` 完整提示词预览
-
{{ previewContent }}
+
{{ previewContent }}
@@ -272,4 +275,3 @@ ${formData.value.responseRules}` - diff --git a/src/composables/useAIChat.ts b/src/composables/useAIChat.ts index 843a4e2..07c566a 100644 --- a/src/composables/useAIChat.ts +++ b/src/composables/useAIChat.ts @@ -243,6 +243,12 @@ export function useAIChat( maxMessagesLimit: aiGlobalSettings.value.maxMessagesPerRequest, } + console.log('[AI] 构建 context:', { + sessionId, + maxMessagesLimit: context.maxMessagesLimit, + aiGlobalSettings: aiGlobalSettings.value, + }) + // 收集历史消息(排除当前用户消息和 AI 占位消息) const historyMessages = messages.value .slice(0, -2) // 排除刚添加的用户消息和 AI 占位消息 @@ -270,7 +276,10 @@ export function useAIChat( return } - console.log('[AI] Agent chunk:', chunk.type, chunk.toolName || chunk.content?.slice(0, 50)) + // 只在工具调用时记录,减少日志噪音 + if (chunk.type === 'tool_start' || chunk.type === 'tool_result') { + console.log('[AI] Agent chunk:', chunk.type, chunk.toolName) + } switch (chunk.type) { case 'content': diff --git a/src/pages/home/components/ImportTutorialModal.vue b/src/pages/home/components/ImportTutorialModal.vue new file mode 100644 index 0000000..3d9443c --- /dev/null +++ b/src/pages/home/components/ImportTutorialModal.vue @@ -0,0 +1,280 @@ + + + diff --git a/src/pages/index.vue b/src/pages/home/index.vue similarity index 53% rename from src/pages/index.vue rename to src/pages/home/index.vue index eb91f52..1fbe625 100644 --- a/src/pages/index.vue +++ b/src/pages/home/index.vue @@ -4,13 +4,13 @@ import { FileDropZone } from '@/components/UI' import { storeToRefs } from 'pinia' import { ref } from 'vue' import { useRouter } from 'vue-router' +import ImportTutorialModal from './components/ImportTutorialModal.vue' const chatStore = useChatStore() const { isImporting, importProgress } = storeToRefs(chatStore) const importError = ref(null) const showTutorialModal = ref(false) -const showFormatModal = ref(false) const features = [ { @@ -85,71 +85,10 @@ async function handleFileDrop({ paths }: { files: File[]; paths: string[] }) { } } -// 教程 Accordion 数据 -const tutorialItems = [ - { - value: 'qq', - label: 'QQ', - icon: 'i-heroicons-chat-bubble-left-right', - steps: [ - '使用 qq-chat-exporter 导出聊天记录(推荐最新版)', - '导出完成后会得到 .json 文件', - '将 .json 文件拖拽到上方导入区域', - ], - link: 'https://github.com/shuakami/qq-chat-exporter', - hasExternalLink: true, - }, - { - value: 'other', - label: '其他平台', - icon: 'i-heroicons-device-phone-mobile', - steps: ['使用任意工具导出聊天记录', '将导出文件转换为 ChatLab 通用格式', '将转换后的 .json 文件拖拽到上方导入区域'], - hasExternalLink: false, - showFormatButton: true, - }, -] - -// 默认展开所有项 -const tutorialDefaultValue = tutorialItems.map((item) => item.value) - function openTutorial() { showTutorialModal.value = true } -// 复制格式示例 -const formatExample = `{ - "chatlab": { - "version": "1.0.0", - "exportedAt": 1732924800, - "generator": "Your Tool Name" - }, - "meta": { - "name": "群聊名称", - "platform": "qq", - "type": "group" - }, - "members": [ - { - "platformId": "123456789", - "accountName": "用户昵称", - "groupNickname": "群昵称(可选)" - } - ], - "messages": [ - { - "sender": "123456789", - "accountName": "发送时昵称", - "timestamp": 1732924800, - "type": 0, - "content": "消息内容" - } - ] -}` - -function copyFormatExample() { - window.electron.copyToClipboard(formatExample) -} - function getProgressText(): string { if (!importProgress.value) return '' switch (importProgress.value.stage) { @@ -324,176 +263,7 @@ function getProgressDetail(): string { - - - - - - - - + diff --git a/src/routes/index.ts b/src/routes/index.ts index dbb11f3..acd79b4 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -5,7 +5,7 @@ export const router = createRouter({ { path: '/', name: 'home', - component: () => import('@/pages/index.vue'), + component: () => import('@/pages/home/index.vue'), }, { path: '/group-chat/:id',