diff --git a/package.json b/package.json index eb242b9..eaf305b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tracememo", - "version": "2.2.1", + "version": "2.2.2", "packageManager": "pnpm@7.33.7", "description": "TraceMemo(迹忆)是一款本地优先、可追溯的 AI 微信知识与分析工作台。 原名 WechatExplorer,支持聊天记录搜索、知识库、AI 总结和 Agent 助手。", "keywords": [ diff --git a/src/main/key-service-win.ts b/src/main/key-service-win.ts index 0772d13..19161c4 100644 --- a/src/main/key-service-win.ts +++ b/src/main/key-service-win.ts @@ -683,7 +683,7 @@ export class KeyService { if (loginRequired) { return { success: false, - error: '微信已启动但尚未完成登录,请先在微信客户端完成登录后再重试自动获取密钥。', + error: '微信可能已经启动并登录,请先在微信客户端保持未登录状态,具体点击上方“查看5分钟上手教程” ', logs } } diff --git a/src/renderer/src/features/settings/ai-model/AIProviderEditor.tsx b/src/renderer/src/features/settings/ai-model/AIProviderEditor.tsx index 8906017..e653a8b 100644 --- a/src/renderer/src/features/settings/ai-model/AIProviderEditor.tsx +++ b/src/renderer/src/features/settings/ai-model/AIProviderEditor.tsx @@ -43,7 +43,7 @@ export function AIProviderEditor({ ) return ( -
+

{editing ? '编辑供应商' : '新增供应商'}

@@ -66,7 +66,11 @@ export function AIProviderEditor({
diff --git a/src/renderer/src/styles/settings.scss b/src/renderer/src/styles/settings.scss index d38b2b7..6c0b489 100644 --- a/src/renderer/src/styles/settings.scss +++ b/src/renderer/src/styles/settings.scss @@ -1777,6 +1777,16 @@ white-space: nowrap; font-weight: 400; } +.ai-model-row .ai-model-identity-field { + display: grid; + min-width: 0; + gap: 5px; + justify-self: stretch; + color: #66706b; + font-family: inherit; + font-size: 11px; + font-weight: 600; +} .ai-provider-advanced, .ai-provider-preview { border: 1px solid #e3e8e5; diff --git a/tests/component/ai-model-settings.test.tsx b/tests/component/ai-model-settings.test.tsx new file mode 100644 index 0000000..abbbdf9 --- /dev/null +++ b/tests/component/ai-model-settings.test.tsx @@ -0,0 +1,65 @@ +import { act, fireEvent, render, screen } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { AIModelPage } from '../../src/renderer/src/features/settings/pages/AIModelPage' +import type { AIRuntimeModelConfig } from '../../src/shared/ai-provider' + +const runtime: AIRuntimeModelConfig = { + providerName: 'Not configured', + model: '', + modelName: 'No model selected', + configured: false, + status: 'untested' +} + +describe('AI model settings', () => { + const scrollIntoView = vi.fn() + + beforeEach(() => { + Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', { + configurable: true, + value: scrollIntoView + }) + window.api = { + listAIProviders: vi.fn().mockResolvedValue({ success: true, providers: [] }), + getAIRuntimeConfig: vi.fn().mockResolvedValue(runtime) + } as typeof window.api + }) + + it('reveals the new provider editor and labels model identity fields', async () => { + render() + + await act(async () => { + await Promise.resolve() + }) + fireEvent.click(screen.getByRole('button', { name: '添加供应商' })) + + expect(scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth', block: 'start' }) + expect(screen.getByLabelText('供应商名称')).toHaveFocus() + expect(screen.getByLabelText('模型名称')).toHaveAttribute('placeholder', '例如:DeepSeek Chat') + expect(screen.getByLabelText('模型 ID')).toHaveAttribute('placeholder', '例如:deepseek-chat') + }) + + it('does not reload providers when the parent callback identity changes', async () => { + const firstRuntimeChange = vi.fn() + const secondRuntimeChange = vi.fn() + const { rerender } = render( + + ) + + await act(async () => { + await Promise.resolve() + }) + expect(window.api.listAIProviders).toHaveBeenCalledOnce() + expect(window.api.getAIRuntimeConfig).toHaveBeenCalledOnce() + expect(firstRuntimeChange).toHaveBeenCalledWith(runtime) + + await act(async () => { + rerender() + await Promise.resolve() + }) + + expect(window.api.listAIProviders).toHaveBeenCalledOnce() + expect(window.api.getAIRuntimeConfig).toHaveBeenCalledOnce() + expect(secondRuntimeChange).not.toHaveBeenCalled() + }) +})