diff --git a/.github/workflows/prcheck.yml b/.github/workflows/prcheck.yml index 6f88f91c86..b77d37ad52 100644 --- a/.github/workflows/prcheck.yml +++ b/.github/workflows/prcheck.yml @@ -74,6 +74,9 @@ jobs: - name: format:check run: pnpm run format:check + - name: Check renderer architecture baseline + run: pnpm run architecture:renderer-baseline:check + - name: Configure pnpm workspace for Linux ${{ matrix.arch }} run: pnpm run install:sharp env: diff --git a/AGENTS.md b/AGENTS.md index 391710c8e6..035031b1ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ ## Project Structure & Module Organization - `src/main/`: Electron main process; presenters in `presenter/` (Window/Tab/Thread/Mcp/Config/LLMProvider), `eventbus.ts` for app events. - `src/preload/`: Secure IPC bridge (contextIsolation on). -- `src/renderer/`: Vue 3 app. App code in `src/renderer/src` (`components/`, `stores`, `views`, `i18n`, `lib`). Secondary renderers live in `src/renderer/browser`, `src/renderer/settings`, `src/renderer/floating`, and `src/renderer/splash`. +- `src/renderer/`: Vue 3 app. Main-window composition lives in `src/renderer/src/apps/chat-main`; shared main renderer code remains in `components/`, `stores`, `views`, `i18n`, and `lib`. Secondary renderers live in `src/renderer/browser-overlay`, `src/renderer/settings`, `src/renderer/floating`, and `src/renderer/splash`. - `src/shared/`: Shared TS types/utilities. - `test/`: Vitest suites (`test/main`, `test/renderer`) with setup files. - `scripts/`: Build/signing/runtime installers, commit checks. diff --git a/docs/architecture/baselines/renderer-application-boundaries-baseline.json b/docs/architecture/baselines/renderer-application-boundaries-baseline.json new file mode 100644 index 0000000000..3e5cfcd4df --- /dev/null +++ b/docs/architecture/baselines/renderer-application-boundaries-baseline.json @@ -0,0 +1,727 @@ +{ + "schemaVersion": 1, + "apps": [ + { + "id": "chat-main", + "html": "src/renderer/index.html", + "entry": "src/renderer/src/main.ts", + "htmlExists": true, + "entryExists": true + }, + { + "id": "browser-overlay", + "html": "src/renderer/browser-overlay/index.html", + "entry": "src/renderer/browser-overlay/main.ts", + "htmlExists": true, + "entryExists": true + }, + { + "id": "floating", + "html": "src/renderer/floating/index.html", + "entry": "src/renderer/floating/main.ts", + "htmlExists": true, + "entryExists": true + }, + { + "id": "splash", + "html": "src/renderer/splash/index.html", + "entry": "src/renderer/splash/main.ts", + "htmlExists": true, + "entryExists": true + }, + { + "id": "settings", + "html": "src/renderer/settings/index.html", + "entry": "src/renderer/settings/main.ts", + "htmlExists": true, + "entryExists": true + } + ], + "browser": { + "legacyDirectoryExists": false, + "activeOverlayDirectory": "src/renderer/browser-overlay" + }, + "settingsToChatAppImports": [ + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/composables/useDeviceVersion" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/composables/useFontManager" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/lib/iconLoader" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/lib/startupDeferred" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/lib/storeInitializer" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/stores/language" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/stores/modelCheck" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "../src/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/components/settings/ModelCheckDialog.vue" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/stores/ollamaStore" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/stores/providerDeeplinkImport" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/stores/startupWorkloadStore" + }, + { + "file": "src/renderer/settings/App.vue", + "specifier": "@/stores/theme" + }, + { + "file": "src/renderer/settings/components/AboutUsSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/AboutUsSettings.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/AboutUsSettings.vue", + "specifier": "@/stores/theme" + }, + { + "file": "src/renderer/settings/components/AboutUsSettings.vue", + "specifier": "@/stores/upgrade" + }, + { + "file": "src/renderer/settings/components/AcpDebugDialog.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/AcpDebugDialog.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/AcpDependencyDialog.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/AcpProfileDialog.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/AcpSettings.vue", + "specifier": "@/components/agent/AgentTransferDialog.vue" + }, + { + "file": "src/renderer/settings/components/AcpSettings.vue", + "specifier": "@/components/icons/AcpAgentIcon.vue" + }, + { + "file": "src/renderer/settings/components/AcpSettings.vue", + "specifier": "@/components/mcp-config/AgentMcpSelector.vue" + }, + { + "file": "src/renderer/settings/components/AcpSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/AcpTerminalDialog.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/AddCustomModelButton.vue", + "specifier": "@/components/settings/ModelConfigDialog.vue" + }, + { + "file": "src/renderer/settings/components/AddCustomProviderDialog.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/BedrockProviderSettingsDetail.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/BedrockProviderSettingsDetail.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue", + "specifier": "@/components/icons/ModelIcon.vue" + }, + { + "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue", + "specifier": "@/components/ModelSelect.vue" + }, + { + "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue", + "specifier": "@/stores/mcp" + }, + { + "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/BuiltinKnowledgeSettings.vue", + "specifier": "@/stores/theme" + }, + { + "file": "src/renderer/settings/components/common/AutoCompactionSettingsSection.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue", + "specifier": "@/components/icons/ModelIcon.vue" + }, + { + "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue", + "specifier": "@/components/ModelSelect.vue" + }, + { + "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/common/DefaultModelSettingsSection.vue", + "specifier": "@/stores/theme" + }, + { + "file": "src/renderer/settings/components/common/LoggingSettingsSection.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/common/LoggingSettingsSection.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/common/PrivacySettingsSection.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/common/PrivacySettingsSection.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/common/ProxySettingsSection.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/common/SettingToggleRow.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/CommonSettings.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/CronJobsSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/DataSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/DataSettings.vue", + "specifier": "@/lib/cloudSyncForm" + }, + { + "file": "src/renderer/settings/components/DataSettings.vue", + "specifier": "@/lib/utils" + }, + { + "file": "src/renderer/settings/components/DataSettings.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/DataSettings.vue", + "specifier": "@/stores/sync" + }, + { + "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue", + "specifier": "@/components/agent/AgentTransferDialog.vue" + }, + { + "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue", + "specifier": "@/components/icons/AgentAvatar.vue" + }, + { + "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue", + "specifier": "@/components/icons/ModelIcon.vue" + }, + { + "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue", + "specifier": "@/components/ModelSelect.vue" + }, + { + "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/DeepChatAgentsSettings.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/DifyKnowledgeSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/DifyKnowledgeSettings.vue", + "specifier": "@/stores/mcp" + }, + { + "file": "src/renderer/settings/components/display/FontSettingsSection.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/display/FontSettingsSection.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/DisplaySettings.vue", + "specifier": "@/stores/floatingButton" + }, + { + "file": "src/renderer/settings/components/DisplaySettings.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/DisplaySettings.vue", + "specifier": "@/stores/theme" + }, + { + "file": "src/renderer/settings/components/DisplaySettings.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/EnvironmentsSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/EnvironmentsSettings.vue", + "specifier": "@/stores/ui/project" + }, + { + "file": "src/renderer/settings/components/FastGptKnowledgeSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/FastGptKnowledgeSettings.vue", + "specifier": "@/stores/mcp" + }, + { + "file": "src/renderer/settings/components/GeminiSafetyConfig.vue", + "specifier": "@/lib/gemini" + }, + { + "file": "src/renderer/settings/components/GitHubCopilotOAuth.vue", + "specifier": "@/stores/modelCheck" + }, + { + "file": "src/renderer/settings/components/GitHubCopilotOAuth.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/GrokOAuth.vue", + "specifier": "@/stores/modelCheck" + }, + { + "file": "src/renderer/settings/components/KnowledgeFile.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/KnowledgeFileItem.vue", + "specifier": "@/lib/utils" + }, + { + "file": "src/renderer/settings/components/McpBuiltinMarket.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/components/mcp-config/components/McpServers.vue" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/components/onboarding/GuidedOnboardingOverlay.vue" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/composables/useGuidedOnboardingStep" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/stores/mcp" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/stores/ui/agent" + }, + { + "file": "src/renderer/settings/components/McpSettings.vue", + "specifier": "@/stores/ui/session" + }, + { + "file": "src/renderer/settings/components/MemoryConfigInlinePanel.vue", + "specifier": "@/components/icons/ModelIcon.vue" + }, + { + "file": "src/renderer/settings/components/MemoryConfigInlinePanel.vue", + "specifier": "@/components/ModelSelect.vue" + }, + { + "file": "src/renderer/settings/components/MemoryConfigInlinePanel.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/MemoryConfigInlinePanel.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/MemoryDiagnosticsPanel.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/MemoryInboxBar.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/MemoryInlinePanel.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/MemoryListView.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/MemoryPersonaPanel.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/components/icons/ModelIcon.vue" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/components/onboarding/GuidedOnboardingOverlay.vue" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/composables/useGuidedOnboardingStep" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/stores/startupWorkloadStore" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettings.vue", + "specifier": "@/stores/theme" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue", + "specifier": "@/lib/gemini" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue", + "specifier": "@/lib/gemini" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue", + "specifier": "@/stores/modelCheck" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/ModelProviderSettingsDetail.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/NotificationsHooksSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/NowledgeMemSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue", + "specifier": "@/components/settings/ModelConfigItem.vue" + }, + { + "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue", + "specifier": "@/stores/modelCheck" + }, + { + "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue", + "specifier": "@/stores/ollamaStore" + }, + { + "file": "src/renderer/settings/components/OllamaProviderSettingsDetail.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/OpenAICodexOAuth.vue", + "specifier": "@/stores/modelCheck" + }, + { + "file": "src/renderer/settings/components/prompt/CustomPromptSettingsSection.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/prompt/CustomPromptSettingsSection.vue", + "specifier": "@/lib/download" + }, + { + "file": "src/renderer/settings/components/prompt/CustomPromptSettingsSection.vue", + "specifier": "@/stores/prompts" + }, + { + "file": "src/renderer/settings/components/prompt/PromptEditorSheet.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/prompt/PromptEditorSheet.vue", + "specifier": "@/lib/utils" + }, + { + "file": "src/renderer/settings/components/prompt/SystemPromptSettingsSection.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/prompt/SystemPromptSettingsSection.vue", + "specifier": "@/stores/systemPromptStore" + }, + { + "file": "src/renderer/settings/components/ProviderApiConfig.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/ProviderApiConfig.vue", + "specifier": "@/stores/modelCheck" + }, + { + "file": "src/renderer/settings/components/ProviderConfigImportDialog.vue", + "specifier": "@/lib/utils" + }, + { + "file": "src/renderer/settings/components/ProviderDeeplinkImportDialog.vue", + "specifier": "@/components/icons/ModelIcon.vue" + }, + { + "file": "src/renderer/settings/components/ProviderDeeplinkImportDialog.vue", + "specifier": "@/stores/theme" + }, + { + "file": "src/renderer/settings/components/ProviderModelList.vue", + "specifier": "@/components/settings/ModelConfigItem.vue" + }, + { + "file": "src/renderer/settings/components/ProviderModelList.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/ProviderModelList.vue", + "specifier": "@/stores/uiSettingsStore" + }, + { + "file": "src/renderer/settings/components/ProviderRateLimitConfig.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/RagflowKnowledgeSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/RagflowKnowledgeSettings.vue", + "specifier": "@/stores/mcp" + }, + { + "file": "src/renderer/settings/components/RemoteSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/SettingsOverview.vue", + "specifier": "@/stores/modelStore" + }, + { + "file": "src/renderer/settings/components/SettingsOverview.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/SettingsOverview.vue", + "specifier": "@/stores/sync" + }, + { + "file": "src/renderer/settings/components/SettingsOverview.vue", + "specifier": "@/stores/ui/agent" + }, + { + "file": "src/renderer/settings/components/ShortcutSettings.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/ShortcutSettings.vue", + "specifier": "@/stores/shortcutKey" + }, + { + "file": "src/renderer/settings/components/skills/InstallFromGitDialog.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/skills/InstallSkillToAgentDialog.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/skills/SkillAgentsTab.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/skills/SkillAgentsTab.vue", + "specifier": "@/stores/skillsStore" + }, + { + "file": "src/renderer/settings/components/skills/SkillDetailDialog.vue", + "specifier": "@/components/markdown/MarkdownRenderer.vue" + }, + { + "file": "src/renderer/settings/components/skills/SkillFolderTree.vue", + "specifier": "@/stores/skillsStore" + }, + { + "file": "src/renderer/settings/components/skills/SkillImportExportTab.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/skills/SkillInstallDialog.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/skills/SkillInstallDialog.vue", + "specifier": "@/stores/skillsStore" + }, + { + "file": "src/renderer/settings/components/skills/SkillsHeader.vue", + "specifier": "@/stores/language" + }, + { + "file": "src/renderer/settings/components/skills/SkillsSettings.vue", + "specifier": "@/components/onboarding/GuidedOnboardingOverlay.vue" + }, + { + "file": "src/renderer/settings/components/skills/SkillsSettings.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/skills/SkillsSettings.vue", + "specifier": "@/composables/useGuidedOnboardingStep" + }, + { + "file": "src/renderer/settings/components/skills/SkillsSettings.vue", + "specifier": "@/stores/skillsStore" + }, + { + "file": "src/renderer/settings/components/skills/SkillsSettings.vue", + "specifier": "@/stores/ui/agent" + }, + { + "file": "src/renderer/settings/components/skills/SkillsSettings.vue", + "specifier": "@/stores/ui/session" + }, + { + "file": "src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/skills/SkillSyncDialog/ExportWizard.vue", + "specifier": "@/stores/skillsStore" + }, + { + "file": "src/renderer/settings/components/skills/SkillSyncDialog/ImportWizard.vue", + "specifier": "@/components/use-toast" + }, + { + "file": "src/renderer/settings/components/VertexProviderSettingsDetail.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/components/VoiceAIProviderConfig.vue", + "specifier": "@/stores/providerStore" + }, + { + "file": "src/renderer/settings/lib/guidedOnboardingSettings.ts", + "specifier": "@/lib/onboardingResume" + }, + { + "file": "src/renderer/settings/main.ts", + "specifier": "../src/lib/iconLoader" + }, + { + "file": "src/renderer/settings/main.ts", + "specifier": "@/i18n" + } + ], + "settingsToChatAppImportCount": 170 +} diff --git a/docs/architecture/chat-display-model-boundary/plan.md b/docs/architecture/chat-display-model-boundary/plan.md new file mode 100644 index 0000000000..bce0141698 --- /dev/null +++ b/docs/architecture/chat-display-model-boundary/plan.md @@ -0,0 +1,34 @@ +# 实施计划 + +## 1. 建立 feature model + +在 `src/renderer/src/features/chat-page/model/displayMessage.ts` 原样迁移 +`messageListItems.ts` 的所有 type 和 pure helper: + +- `DisplayMessage` 家族与 `MessageListItem`; +- assistant block 的 internal tool、filter、presence 策略; +- compaction 判定。 + +新模块只保留对 `@shared/types/*` 的 type/value 依赖,因此可以被 feature、component、store 和 +纯 UI helper 使用而不引入运行时分层循环。 + +## 2. 原子切换消费者 + +把 renderer source、renderer tests 和 fixtures 的 import 改为 +`@/features/chat-page/model/displayMessage`。特别保留 component 对 feature model 的单向读取: +组件不会导入 ChatPage、feature composable 或 store。 + +删除旧的 `components/chat/messageListItems.ts`,不建立过渡 re-export;这样新增引用无法继续落在 +错误的 component 目录。 + +## 3. 验证 + +1. 搜索确认旧模块 import 为零。 +2. 执行 formatter;随后执行 i18n、lint 与 renderer type check。 +3. 运行直接覆盖消息 list/window、message block 和 chat feature composable 的 targeted suites;如 + repository runner 支持,再执行完整 renderer test suite。 + +## 兼容性与回滚 + +模块导出名和实现不变,调用方数据格式与运行时行为不变。若出现解析或架构问题,单次提交即可将 +imports 和文件位置恢复,无需迁移数据或 IPC contract。 diff --git a/docs/architecture/chat-display-model-boundary/spec.md b/docs/architecture/chat-display-model-boundary/spec.md new file mode 100644 index 0000000000..91fb983ab0 --- /dev/null +++ b/docs/architecture/chat-display-model-boundary/spec.md @@ -0,0 +1,35 @@ +# Chat 展示模型边界收敛 + +## 背景 + +`DisplayMessage`、其 block/usage 契约,以及 assistant block 的可渲染性策略目前定义在 +`src/renderer/src/components/chat/messageListItems.ts`。但记录到展示消息的转换、流式尾部拼装、 +是否显示空 assistant 消息的决策都由 `features/chat-page` 持有。feature 因而反向依赖 UI 目录以 +取得自己的领域展示模型,模型所有权与实际业务所有权不一致。 + +这些契约同时被 chat list、message block 组件、message store、chat-only composable 与测试使用。 +它们不是可复用通用 UI 的模型,而是 chat-page feature 的输入/输出契约。 + +## 目标 + +1. 将 `DisplayMessage` 家族、`MessageListItem` 与 assistant block 可渲染性策略迁入 + `src/renderer/src/features/chat-page/model/`。 +2. 所有消费者直接引用 feature model,不保留 `components/chat/messageListItems.ts` 作为第二个 + 定义或兼容 re-export。 +3. 保持类型形状、过滤条件与 compaction 判定完全不变;这是纯模块边界调整。 +4. 记录 chat 组件可以依赖该 feature 的**纯 model contract**:model 只依赖 shared types, + 不依赖 Vue、Pinia、IPC、component 或 composable,且不得反向导入消费者。 + +## 非目标 + +- 不调整消息转换、流式缓存、虚拟化或 Markdown 渲染逻辑。 +- 不变更 `MessageList` / `MessageListRow` 的 props、events 或 template。 +- 不迁移通用 renderer foundation,也不改变 IPC/store 数据格式。 +- 不在本切片拆分 `ChatStatusBar` 或继续提取 ChatPage viewport 生命周期。 + +## 验收标准 + +1. `components/chat/messageListItems.ts` 不再存在,且 renderer 与测试中没有对其的 import。 +2. 所有原有导出的 type 和 helper 在 feature model 有同名导出,运行行为保持一致。 +3. feature model 不导入 Vue、Pinia、renderer store、renderer API 或 component。 +4. `pnpm run format`、`pnpm run i18n`、`pnpm run lint` 和相关 renderer type/test 校验通过。 diff --git a/docs/architecture/chat-display-model-boundary/tasks.md b/docs/architecture/chat-display-model-boundary/tasks.md new file mode 100644 index 0000000000..4c91772991 --- /dev/null +++ b/docs/architecture/chat-display-model-boundary/tasks.md @@ -0,0 +1,7 @@ +# 任务清单 + +- [x] 审计展示模型、renderability policy 与全部 source/test 消费者。 +- [x] 在 `features/chat-page/model/` 建立纯展示模型模块。 +- [x] 原子更新 renderer source、tests 与 fixtures 的 import。 +- [x] 删除旧 component-local 模块并确认无残留引用。 +- [x] 执行 format、i18n、lint、typecheck 与 targeted renderer tests。 diff --git a/docs/architecture/chatpage-decomposition/spec.md b/docs/architecture/chatpage-decomposition/spec.md index b4d9f444e1..59fe8759aa 100644 --- a/docs/architecture/chatpage-decomposition/spec.md +++ b/docs/architecture/chatpage-decomposition/spec.md @@ -2,7 +2,8 @@ ## 背景 -`src/renderer/src/pages/ChatPage.vue` 已膨胀到 3050 行,单个 ` diff --git a/src/renderer/src/apps/chat-main/ChatMainApp.vue b/src/renderer/src/apps/chat-main/ChatMainApp.vue new file mode 100644 index 0000000000..eee16723d3 --- /dev/null +++ b/src/renderer/src/apps/chat-main/ChatMainApp.vue @@ -0,0 +1,646 @@ + + + diff --git a/src/renderer/src/components/chat/ChatToolInteractionOverlay.vue b/src/renderer/src/components/chat/ChatToolInteractionOverlay.vue index 2548204813..0b0d3b4f2a 100644 --- a/src/renderer/src/components/chat/ChatToolInteractionOverlay.vue +++ b/src/renderer/src/components/chat/ChatToolInteractionOverlay.vue @@ -98,7 +98,7 @@ import { useI18n } from 'vue-i18n' import { Button } from '@shadcn/components/ui/button' import { Icon } from '@iconify/vue' import type { ToolInteractionResponse } from '@shared/types/agent-interface' -import type { DisplayAssistantMessageBlock } from '@/components/chat/messageListItems' +import type { DisplayAssistantMessageBlock } from '@/features/chat-page/model/displayMessage' type PendingInteractionView = { messageId: string diff --git a/src/renderer/src/components/chat/MessageList.vue b/src/renderer/src/components/chat/MessageList.vue index 77fc5939ed..87067c4358 100644 --- a/src/renderer/src/components/chat/MessageList.vue +++ b/src/renderer/src/components/chat/MessageList.vue @@ -51,7 +51,7 @@ import { type DisplayAssistantMessageBlock, type DisplayMessage, type MessageListItem -} from './messageListItems' +} from '@/features/chat-page/model/displayMessage' import MessageListRow from './MessageListRow.vue' const props = withDefaults( diff --git a/src/renderer/src/components/chat/MessageListRow.vue b/src/renderer/src/components/chat/MessageListRow.vue index b1b5aedcf8..49b854fa40 100644 --- a/src/renderer/src/components/chat/MessageListRow.vue +++ b/src/renderer/src/components/chat/MessageListRow.vue @@ -59,7 +59,7 @@ import { isCompactionMessageItem, type DisplayUserMessage, type MessageListItem -} from './messageListItems' +} from '@/features/chat-page/model/displayMessage' const props = withDefaults( defineProps<{ diff --git a/src/renderer/src/components/message/MessageBlockAction.vue b/src/renderer/src/components/message/MessageBlockAction.vue index 9fd3705b98..365ba02cea 100644 --- a/src/renderer/src/components/message/MessageBlockAction.vue +++ b/src/renderer/src/components/message/MessageBlockAction.vue @@ -48,7 +48,7 @@ import { useI18n } from 'vue-i18n' import { Icon } from '@iconify/vue' import { Button } from '@shadcn/components/ui/button' import { computed, ref, onMounted, onUnmounted } from 'vue' -import type { DisplayAssistantMessageBlock } from '@/components/chat/messageListItems' +import type { DisplayAssistantMessageBlock } from '@/features/chat-page/model/displayMessage' const { t } = useI18n() diff --git a/src/renderer/src/components/message/MessageBlockActivityGroup.vue b/src/renderer/src/components/message/MessageBlockActivityGroup.vue index 1754396209..bb9d4f8604 100644 --- a/src/renderer/src/components/message/MessageBlockActivityGroup.vue +++ b/src/renderer/src/components/message/MessageBlockActivityGroup.vue @@ -63,7 +63,7 @@ import { useI18n } from 'vue-i18n' import type { DisplayAssistantMessageBlock, DisplayMessageUsage -} from '@/components/chat/messageListItems' +} from '@/features/chat-page/model/displayMessage' import { formatActivityDuration } from './messageActivityGroups' import MessageBlockThink from './MessageBlockThink.vue' import MessageBlockToolCall from './MessageBlockToolCall.vue' diff --git a/src/renderer/src/components/message/MessageBlockAudio.vue b/src/renderer/src/components/message/MessageBlockAudio.vue index fa6405fcb0..19c9886751 100644 --- a/src/renderer/src/components/message/MessageBlockAudio.vue +++ b/src/renderer/src/components/message/MessageBlockAudio.vue @@ -35,7 +35,7 @@ import { computed, ref } from 'vue' import { Icon } from '@iconify/vue' import { useI18n } from 'vue-i18n' import { Spinner } from '@shadcn/components/ui/spinner' -import type { DisplayAssistantMessageBlock } from '@/components/chat/messageListItems' +import type { DisplayAssistantMessageBlock } from '@/features/chat-page/model/displayMessage' const keyMap = { 'mcp.sampling.contentType.audio': 'Audio', diff --git a/src/renderer/src/components/message/MessageBlockContent.vue b/src/renderer/src/components/message/MessageBlockContent.vue index f68920164c..4180c3761b 100644 --- a/src/renderer/src/components/message/MessageBlockContent.vue +++ b/src/renderer/src/components/message/MessageBlockContent.vue @@ -47,7 +47,7 @@ import ToolCallPreview from '../artifacts/ToolCallPreview.vue' import { useBlockContent, type ProcessedPart } from '@/composables/useArtifacts' import { useArtifactStore } from '@/stores/artifact' import MarkdownRenderer from '@/components/markdown/MarkdownRenderer.vue' -import type { DisplayAssistantMessageBlock } from '@/components/chat/messageListItems' +import type { DisplayAssistantMessageBlock } from '@/features/chat-page/model/displayMessage' const artifactStore = useArtifactStore() const props = defineProps<{ diff --git a/src/renderer/src/components/message/MessageBlockError.vue b/src/renderer/src/components/message/MessageBlockError.vue index 6131759034..66ae84cd9f 100644 --- a/src/renderer/src/components/message/MessageBlockError.vue +++ b/src/renderer/src/components/message/MessageBlockError.vue @@ -47,7 +47,7 @@ import { useI18n } from 'vue-i18n' import { Icon } from '@iconify/vue' import { computed, ref, useId } from 'vue' -import type { DisplayAssistantMessageBlock } from '@/components/chat/messageListItems' +import type { DisplayAssistantMessageBlock } from '@/features/chat-page/model/displayMessage' const { t } = useI18n() const props = defineProps<{ diff --git a/src/renderer/src/components/message/MessageBlockImage.vue b/src/renderer/src/components/message/MessageBlockImage.vue index fbdebad37b..b02a6517be 100644 --- a/src/renderer/src/components/message/MessageBlockImage.vue +++ b/src/renderer/src/components/message/MessageBlockImage.vue @@ -76,7 +76,7 @@ import { Button } from '@shadcn/components/ui/button' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@shadcn/components/ui/dialog' import { Spinner } from '@shadcn/components/ui/spinner' import { Tooltip, TooltipContent, TooltipTrigger } from '@shadcn/components/ui/tooltip' -import type { DisplayAssistantMessageBlock } from '@/components/chat/messageListItems' +import type { DisplayAssistantMessageBlock } from '@/features/chat-page/model/displayMessage' import ImageActionContextMenu from './ImageActionContextMenu.vue' import { useImageActions } from '@/composables/useImageActions' diff --git a/src/renderer/src/components/message/MessageBlockQuestionRequest.vue b/src/renderer/src/components/message/MessageBlockQuestionRequest.vue index 6468dd3a53..2b2d988faa 100644 --- a/src/renderer/src/components/message/MessageBlockQuestionRequest.vue +++ b/src/renderer/src/components/message/MessageBlockQuestionRequest.vue @@ -28,7 +28,7 @@