Skip to content

Comments

Feat/new thread mock#1319

Open
deepinfect wants to merge 24 commits intodevfrom
feat/new-thread-mock
Open

Feat/new thread mock#1319
deepinfect wants to merge 24 commits intodevfrom
feat/new-thread-mock

Conversation

@deepinfect
Copy link
Collaborator

@deepinfect deepinfect commented Feb 24, 2026

Summary by CodeRabbit

  • New Features

    • New multi-page UI architecture with Welcome, NewThread, and Chat pages
    • Default model and vision model settings in preferences
    • Enhanced chat sidebar with agents and session grouping
    • Project management and selection workflow
  • Chores

    • Removed deprecated security entitlement
    • Database schema updates for new session management

deepinsect and others added 24 commits February 15, 2026 06:24
Add agent icon sidebar with liquid drop styling and session list
with date-grouped mock data. Main content area gets rounded corner
and shadow separation. Sidebar/appbar use semi-transparent background.
Move browser and settings buttons from AppBar to WindowSideBar bottom
icon column. Remove history button, ThreadView overlay, and all
associated toggle/event handling code.
Add a pure visual mock for the redesigned new thread page using shadcn
primitives. Includes centered logo/heading, textarea input with attach/mic/send
toolbar, and a status bar with model, effort, and permissions selectors.
…rouping toggle

Refactor NewThreadMock into reusable mock components (MockInputBox,
MockInputToolbar, MockStatusBar). Add MockChatPage with sticky top bar
and input area, MockMessageList with sample conversation, and shared
mock view state composable. Wire sidebar session clicks to switch
between new thread and chat views. Add project grouping toggle to
sidebar header.
…at top bar

Add a dropdown project/workdir selector to NewThreadMock with recent
projects and an open folder option. Thread project directory through
mock view state so MockTopBar displays a folder breadcrumb prefix
alongside the session title.
…apse

- Add MockWelcomePage with provider grid and ACP agent setup option
- Add DeepChat as default first agent in sidebar
- Add empty state for session list
- Add sidebar collapse/expand with macOS traffic light accommodation
- Add debug toggle button for welcome page
* docs: add spec for default model setting

* feat: #1174 support default models setting

* fix(settings): sync model selections with ACP mode and improve error handling

- Add ACP mode check when applying default model in NewThread.vue
- Add error handling in DefaultModelSettingsSection.vue syncModelSelections
- Update imageModel i18n keys to remove 'select' verb for display-only fields
* fix(settings): disable auto-fallback in default model selection

Remove automatic model fallback in DefaultModelSettingsSection to prevent
unintended model changes when providers load late. Models now only change
on explicit user selection.

* fix: format
…ideBar

Remove multi-tab/multi-window complexity from presenters, simplify shell
UI by removing SideBar component and streamlining AppBar, and clean up
unused event handlers and shortcut bindings.
… and sidebar integration

Implement Phases 1-4 of the new UI architecture:

- Phase 1: Add 4 Pinia stores (pageRouter, session, agent, project) with
  IPC mapping to presenters, event listeners, and error handling
- Phase 2: Create 3 page components (WelcomePage, NewThreadPage, ChatPage)
  and refactor ChatTabView to route via pageRouter store
- Phase 3: Create 5 chat components (ChatTopBar, MessageList, ChatInputBox,
  ChatInputToolbar, ChatStatusBar) matching mock designs exactly
- Phase 4: Refactor WindowSideBar to use store data instead of mock data
- Add specs and implementation plan documentation
The agentPresenter.sendMessage expects content as JSON-encoded
UserMessageContent ({text, files, links, search, think}), not raw
text. Raw text caused JSON.parse failures in MessageManager and
TypeError when setting properties on non-object content.
ChatPage now uses useChatStore().variantAwareMessages for the message
list and chatStore.sendMessage() for sending. MessageList component
updated to handle real Message types (UserMessageContent for user
messages, AssistantMessageBlock[] for assistant messages) instead of
plain text placeholders.
…hase 6)

- NewThreadPage passes agentStore.selectedAgentId to session creation,
  with providerId/modelId set for ACP agents
- Session creation uses forceNewAndActivate to prevent backend from
  reusing empty conversations with different agent settings
- ChatStatusBar wired to chatStore.chatConfig and modelStore for real
  model/effort selection; shows selected agent context on NewThreadPage
- Effort selector hidden for ACP agents (not applicable)
- MessageList rewritten to use existing MessageItemAssistant and
  MessageItemUser components for full markdown/code/tool rendering
- modelStore.initialize() added to ChatTabView onMounted
- Phase 6 specs and todo tracking added
Implement agent-centric architecture replacing old sessionPresenter pattern:

- Shared types: IAgentImplementation, Agent, Session, ChatMessageRecord, etc.
- DB tables: new_sessions, new_projects, deepchat_sessions, deepchat_messages
- DeepChatAgentPresenter: message persistence, LLM coreStream consumption,
  batched stream flushing (120ms renderer, 600ms DB), crash recovery
- NewAgentPresenter: agent registry, session manager, message routing
- ProjectPresenter: project CRUD with directory picker
- Renderer stores: session, message, agent, project, draft (all new arch)
- ChatPage wired to new message store with streaming block display
- NewThreadPage resolves model via defaultModel/preferredModel/first enabled
- 94 unit + integration tests across 9 test files
- Debug logging throughout processing pipeline
- Add contextBuilder with system prompt injection, conversation history
  assembly, and token-based truncation using approximateTokenSize
- Wire buildContext into processMessage, building context before DB
  persist to avoid duplicate user messages in LLM calls
- Add optimistic user message in renderer message store so user messages
  appear immediately without waiting for stream completion
- Add auto-scroll to ChatPage: scroll to bottom on load, on new messages,
  and during streaming; respect user scroll-up to stop auto-following
- Update v0 tests and add new tests for context builder and multi-turn
  integration (110 tests passing across 10 files)
Add tool calling support to the DeepChat agent via an agent loop that
passes tool definitions to coreStream, accumulates tool calls from the
stream, executes them via ToolPresenter.callTool(), and re-calls the LLM
until it stops requesting tools. Includes block accumulation across loop
iterations, server info enrichment, grouped truncation of tool messages
in context builder, and proper abort/error path delegation between
streamHandler and agentLoop.
DeepSeek Reasoner (and similar models like kimi-k2-thinking, glm-4.7)
requires a reasoning_content field on assistant messages that contain
tool_calls. Without it, the API returns a 400 error. Extract reasoning
blocks separately in both the agent loop and context builder, and
include them on the assistant message when the model requires it.
For interleaved thinking models (DeepSeek Reasoner, kimi-k2-thinking),
reasoning_content is only required on assistant messages in the current
agent loop exchange (after the last user message), not on historical
messages from the DB. Also fix content field to always be a string
(not undefined) on assistant messages with tool_calls, matching the
existing agentLoopHandler behavior.
Replace tangled streamHandler.ts and agentLoop.ts with a clean 5-module
architecture: types.ts (shared state), accumulator.ts (pure event→block
mutations), echo.ts (batched renderer/DB flushing), dispatch.ts (tool
execution and finalization), and process.ts (unified loop). Eliminates
tools-vs-no-tools branching in index.ts — single processStream() call
handles both simple completions and multi-turn tool calling. Extracts
reusable trailing-edge throttle utility to shared/utils/throttle.ts.
Pure refactor with no behavior changes.
New agent sessions live in deepchat_sessions, not the legacy
conversations table. Passing the session ID as conversationId caused
SkillPresenter.getActiveSkills to throw "Conversation not found".
Drop the conversationId param since the new agent doesn't use skills.
Mark v2 spec as complete and superseded by v3 module structure.
Update v3 spec to match final implementation. Clean up tasks to
remove references to deleted files and add v2/v3 task sections
with all items checked off.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

Comprehensive architectural overhaul introducing default model settings, a new multi-page UI system with routing, complete Agent Architecture v0-v3 with multi-turn chat and MCP tool integration, modular stream processing, SQLite schema extensions, removal of legacy tab-switching logic, and extensive internationalization and test coverage.

Changes

Cohort / File(s) Summary
Default Model Settings
src/main/presenter/configPresenter/index.ts, src/renderer/settings/components/common/DefaultModelSettingsSection.vue, src/renderer/src/components/NewThread.vue, src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts
Added getters/setters for defaultModel and defaultVisionModel in config presenter; new Vue component for model settings UI; imageServer now resolves models from global config with fallback handling; NewThread.vue prefers default model on initialization.
Entitlements & Security
build/entitlements.mac.plist, src/preload/index.ts
Removed dyld environment variable entitlement; added external URL validation for openExternal calls with protocol allowlist.
New Agent Architecture (v0-v3)
src/main/presenter/newAgentPresenter/*, src/main/presenter/deepchatAgentPresenter/*, src/main/presenter/projectPresenter/index.ts, src/main/presenter/sqlitePresenter/tables/...
Implemented IAgentImplementation with session/message stores; NewAgentPresenter routing and registration; DeepChatAgentPresenter for single-agent chat; modular stream processing (accumulator, echo, dispatch, process); SQLite tables for new_sessions, new_projects, deepchat_sessions, deepchat_messages.
Stream Processing Architecture
src/main/presenter/deepchatAgentPresenter/types.ts, accumulator.ts, echo.ts, dispatch.ts, process.ts, contextBuilder.ts, messageStore.ts, sessionStore.ts
New modular stream handling: types define StreamState and IoParams; accumulator mutates state from events; echo throttles renderer/DB flushes; dispatch handles tool execution and finalization; process orchestrates the loop; contextBuilder manages history/truncation; stores provide CRUD for messages and sessions.
Presenter Wiring & Configuration
src/main/presenter/index.ts, src/main/presenter/sessionPresenter/index.ts, src/main/presenter/configPresenter/index.ts, src/main/events.ts
Extended Presenter with newAgentPresenter and projectPresenter; SessionPresenter uses configPresenter for title generation fallback; added SESSION_EVENTS (LIST_UPDATED, ACTIVATED, DEACTIVATED, STATUS_CHANGED).
Window & Tab Management Refactor
src/main/presenter/windowPresenter/index.ts, src/main/presenter/shortcutPresenter.ts, src/main/presenter/deeplinkPresenter/index.ts, src/main/presenter/syncPresenter/index.ts
Removed tab-creation/switching logic; tab shortcuts now no-ops; deeplinkPresenter no longer waits for tab readiness; syncPresenter resets shell-window logic to no-op; simplified window focus/restore flow.
New UI Pages & Routing
src/renderer/src/pages/WelcomePage.vue, NewThreadPage.vue, ChatPage.vue, src/renderer/src/stores/ui/pageRouter.ts, src/renderer/src/views/ChatTabView.vue
Three-page architecture with PageRouter store; WelcomePage shows provider grid; NewThreadPage creates sessions with project/model selection; ChatPage displays messages and chat UI; ChatTabView routes between pages based on router state and provider/session configuration.
UI Stores (Session, Message, Agent, Project, Draft)
src/renderer/src/stores/ui/session.ts, message.ts, agent.ts, project.ts, draft.ts, pageRouter.ts
New comprehensive stores for sessions (grouping/filtering), messages (streaming integration), agents (selection), projects (folder picker), drafts (pre-session config), and page routing (initialization/navigation).
Sidebar & Layout Components
src/renderer/src/components/WindowSideBar.vue, AppBar.vue, src/renderer/src/App.vue, src/renderer/shell/components/AppBar.vue, src/renderer/shell/App.vue
New WindowSideBar with agent selection column and session list; AppBar with window controls; AppBar.vue refactored to remove window-type prop; shell App.vue simplified with chrome height reporting; removed SideBar.vue.
Chat Components
src/renderer/src/components/chat/ChatTopBar.vue, ChatInputBox.vue, ChatInputToolbar.vue, ChatStatusBar.vue, MessageList.vue
New modular chat UI: TopBar shows project/title; InputBox for textarea; InputToolbar for attach/send; StatusBar for model/effort selection; MessageList renders messages; all integrate with session/message stores.
Mock Components (Development/Preview)
src/renderer/src/components/mock/Mock*.vue, src/renderer/src/composables/useMockViewState.ts
New mock components for development preview (MockChatPage, MockWelcomePage, MockMessageList, MockTopBar, MockInputBox, MockStatusBar, MockInputToolbar); useMockViewState composable for mock state management.
Icon Components
src/renderer/src/components/icons/CloseIcon.vue, MaximizeIcon.vue, MinimizeIcon.vue, RestoreIcon.vue, ModelIcon.vue
New window control icons; ModelIcon updated with monochrome inversion set-based check instead of keyword-based logic.
MCP & Tool Integration
src/renderer/src/components/mcp-config/mcpServerForm.vue, src/shared/types/core/chat-message.ts
mcpServerForm now displays read-only vision model from config (not args); added reasoning_content field to ChatMessage type.
Internationalization (10 languages)
src/renderer/src/i18n/*/settings.json (en-US, da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW)
Added defaultModel (title, chatModel, visionModel) sections; updated imageModel labels from selection prompts to simple "Vision Model" across all locale files.
Type Definitions & Presenter Interfaces
src/shared/types/agent-interface.d.ts, src/shared/types/presenters/new-agent.presenter.d.ts, src/shared/types/presenters/project.presenter.d.ts, src/shared/types/presenters/index.d.ts, src/shared/types/presenters/legacy.presenters.d.ts
New agent-interface types for sessions, messages, agents, projects; INewAgentPresenter and IProjectPresenter interfaces; extended IPresenter and IConfigPresenter in legacy definitions.
Utility Functions
src/shared/utils/throttle.ts
New createThrottle utility for trailing-edge throttling with explicit flush/cancel controls used in echo flushing.
Database Tables & Initialization
src/main/presenter/sqlitePresenter/tables/newSessions.ts, newProjects.ts, deepchatSessions.ts, deepchatMessages.ts, src/main/presenter/sqlitePresenter/index.ts
Four new SQLite tables with row interfaces, CRUD operations, and migration stubs; sqlitePresenter wired with table initialization and migration flow.
Configuration & Utility Updates
src/main/presenter/agentPresenter/utility/utilityHandler.ts
Title generation now uses preferred model from assistantModel config with fallback to conversation settings and retry logic on failure.
Documentation & Specifications
docs/architecture/new-ui-implementation-plan.md, docs/specs/new-agent/*.md, docs/specs/default-model-settings/*.md, docs/specs/new-ui-*.md
Comprehensive architectural documentation covering new UI implementation, new agent architecture (v0-v3), default model settings, chat components, session stores, sidebar, and page router specs and implementation plans.
Comprehensive Test Suite
test/main/presenter/*/, test/shared/utils/throttle.test.ts
Vitest-based tests for configPresenter, accumulator, contextBuilder, deepchatAgentPresenter, echo, dispatch, messageStore, process, agentRegistry, messageManager, sessionManager, newAgentPresenter, projectPresenter, and throttle utility; includes integration tests and crash recovery scenarios.
CSS & Theme Updates
src/renderer/src/assets/style.css, src/shadcn/components/ui/tooltip/TooltipContent.vue
CSS variables adjusted for theme contrast (input color, background opacities, border weights); TooltipContent restructured with translucent black background and backdrop blur.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

  • #1315 — Implements the same default model and default vision model feature with identical configPresenter getters/setters, DefaultModelSettingsSection component, and imageServer handling.
  • #1241 — Modifies agent/session lifecycle and presenter surface including agent presenters, session-manager types, and message/session handling.
  • #847 — Modifies the shared type surface and presenter d.ts splits affecting agent-interface and type-core definitions.

Suggested labels

codex

Suggested reviewers

  • zerob13

Poem

🐰 hops excitedly
New architecture blooms, pages dance with state machines bright,
Stream and tool calls flow through modular delight,
Default models chosen with wisdom and with grace,
Agent presents sessions in their rightful place! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Feat/new thread mock' is too vague and does not clearly summarize the actual scope and significance of this large, multi-component PR. Revise the title to reflect the major architectural changes, such as 'Refactor: New agent architecture with UI overhaul, stream processing, and default model settings' or similar to convey the breadth of changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 44.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/new-thread-mock

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/renderer/src/components/ModelSelect.vue (1)

72-85: 🛠️ Refactor suggestion | 🟠 Major

Use a typed props interface + withDefaults for defaults.

This file now changes the props block, but it still uses an object literal for defineProps. The guideline requires TypeScript interfaces for props. Consider switching to an interface with defineProps<T>() and withDefaults for default values.

✅ Suggested refactor
-const props = defineProps({
-  type: {
-    type: Array as PropType<ModelType[]>,
-    default: undefined // ←  explicit for clarity
-  },
-  excludeProviders: {
-    type: Array as PropType<string[]>,
-    default: () => []
-  },
-  visionOnly: {
-    type: Boolean,
-    default: false
-  }
-})
+interface ModelSelectProps {
+  type?: ModelType[]
+  excludeProviders?: string[]
+  visionOnly?: boolean
+}
+
+const props = withDefaults(defineProps<ModelSelectProps>(), {
+  excludeProviders: () => [],
+  visionOnly: false
+})

As per coding guidelines, "Define TypeScript interfaces for Vue component props and data structures."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/ModelSelect.vue` around lines 72 - 85, Replace
the object-literal props with a typed props interface and use withDefaults +
defineProps to provide defaults: create an interface (e.g., Props) declaring
type?: ModelType[], excludeProviders?: string[], visionOnly?: boolean; then call
defineProps<Props>() and wrap it with withDefaults to set type to undefined,
excludeProviders to () => [], and visionOnly to false, updating any references
to the existing props variable (const props) accordingly.
src/main/presenter/shortcutPresenter.ts (1)

154-210: ⚠️ Potential issue | 🟠 Major

Registered-but-no-op shortcuts still consume OS-level keybindings.

The four tab-switch methods (lines 200–210) are correctly gutted, but the globalShortcut.register call-sites at lines 154–193 are still active. Electron's globalShortcut module registers shortcuts with the operating system, and they work even when the app does not have keyboard focus. Registering a shortcut with an empty callback means the key combination is captured and silently swallowed — Electron will override shortcuts of other applications; if you try to use this shortcut while Chrome or another app is open, your Electron application will override it.

Concretely: Ctrl+Tab, Ctrl+Shift+Tab, Ctrl+1Ctrl+8, and Ctrl+9 will cease to work in any other application while DeepChat is running, and they'll do nothing inside DeepChat either.

Fix: Remove the now-purposeless registration blocks entirely:

🔧 Proposed fix — drop the four no-op registration blocks
-    // Command+Tab 或 Ctrl+Tab 切换到下一个标签页
-    if (this.shortcutKeys.SwitchNextTab) {
-      globalShortcut.register(this.shortcutKeys.SwitchNextTab, () => {
-        const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-        if (focusedWindow?.isFocused()) {
-          this.switchToNextTab(focusedWindow.id)
-        }
-      })
-    }
-
-    // Ctrl+Shift+Tab 切换到上一个标签页
-    if (this.shortcutKeys.SwitchPrevTab) {
-      globalShortcut.register(this.shortcutKeys.SwitchPrevTab, () => {
-        const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-        if (focusedWindow?.isFocused()) {
-          this.switchToPreviousTab(focusedWindow.id)
-        }
-      })
-    }
-
-    // 注册标签页数字快捷键 (1-8)
-    if (this.shortcutKeys.NumberTabs) {
-      for (let i = 1; i <= 8; i++) {
-        globalShortcut.register(`${CommandKey}+${i}`, () => {
-          const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-          if (focusedWindow?.isFocused()) {
-            this.switchToTabByIndex(focusedWindow.id, i - 1)
-          }
-        })
-      }
-    }
-
-    // Command+9 或 Ctrl+9 切换到最后一个标签页
-    if (this.shortcutKeys.SwtichToLastTab) {
-      globalShortcut.register(this.shortcutKeys.SwtichToLastTab, () => {
-        const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-        if (focusedWindow?.isFocused()) {
-          this.switchToLastTab(focusedWindow.id)
-        }
-      })
-    }
-
     this.showHideWindow()

If the shortcuts are intended to be re-wired to the new page-routing system in a follow-up, consider leaving a // TODO comment explaining the intent rather than silently capturing and discarding the keystrokes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/shortcutPresenter.ts` around lines 154 - 210, The
globalShortcut.register calls for shortcutKeys.SwitchNextTab, SwitchPrevTab,
NumberTabs and SwtichToLastTab are still registering OS-level shortcuts while
the corresponding handlers (switchToNextTab, switchToPreviousTab,
switchToTabByIndex, switchToLastTab) are no-ops; remove those registration
blocks (the globalShortcut.register(…) usages tied to
this.shortcutKeys.SwitchNextTab, .SwitchPrevTab, .NumberTabs, and
.SwtichToLastTab) so the app stops capturing Ctrl/Cmd+Tab, Ctrl/Cmd+Shift+Tab,
Ctrl/Cmd+1–9, etc.; if you plan to rewire them later, replace each removed block
with a brief TODO comment referencing the handler names (switchToNextTab,
switchToPreviousTab, switchToTabByIndex, switchToLastTab) so future work knows
where to re-add registrations.
src/main/presenter/sessionPresenter/managers/conversationManager.ts (1)

177-201: ⚠️ Potential issue | 🔴 Critical

DEFAULT_SETTINGS is mutated in-place — corrupts state across all subsequent calls.

At line 177, defaultSettings is assigned by reference to DEFAULT_SETTINGS. When !latestConversation?.settings is true (precisely the condition required for shouldApplyDefaultModel to be true), the block at lines 178-186 is skipped, so defaultSettings never becomes a copy — it remains the same object reference. Lines 198-199 then directly write to DEFAULT_SETTINGS.modelId and DEFAULT_SETTINGS.providerId, permanently polluting the module-level constant for the remainder of the process lifetime. Every subsequent call to createConversation that lacks a latestConversation?.settings will inherit the wrong model.

🐛 Proposed fix — shallow-copy DEFAULT_SETTINGS at the declaration site
-      let defaultSettings = DEFAULT_SETTINGS
+      let defaultSettings = { ...DEFAULT_SETTINGS }
       if (latestConversation?.settings) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/sessionPresenter/managers/conversationManager.ts` around
lines 177 - 201, defaultSettings is being assigned by reference to
DEFAULT_SETTINGS so later writes (e.g., setting modelId/providerId) mutate the
module-level constant; change the initialization to create a shallow copy (e.g.,
use an object spread) when declaring defaultSettings so mutations only affect
the local variable, i.e., replace the direct assignment to DEFAULT_SETTINGS with
a shallow-copy of DEFAULT_SETTINGS before any modifications in the
createConversation flow (references: DEFAULT_SETTINGS and the local variable
defaultSettings in conversationManager.ts).
src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts (2)

52-88: ⚠️ Potential issue | 🟠 Major

getEffectiveModel()'s live-lookup and error-throw branches are dead code due to constructor hard-coding.

Because the constructor unconditionally assigns this.provider and this.model — falling back to 'openai' / 'gpt-4o' when nothing else is configured — both fields are always truthy after construction. getEffectiveModel() therefore always returns the first branch and never reaches the getDefaultVisionModel() re-fetch or the throw. Two consequences:

  1. If the user updates the default vision model while the server is running, the change is not picked up (stale values baked in at construction).
  2. If no vision model is configured at all, the server silently uses openai/gpt-4o instead of surfacing the "no vision model configured" error that getEffectiveModel() is supposed to throw.
🐛 Proposed fix — remove hard defaults from constructor, rely on `getEffectiveModel()` for live resolution
-  private provider: string
-  private model: string
+  private provider: string | undefined
+  private model: string | undefined

   constructor(provider?: string, model?: string) {
-    const defaultVisionModel = presenter.configPresenter.getDefaultVisionModel()
-    this.provider = provider || defaultVisionModel?.providerId || 'openai'
-    this.model = model || defaultVisionModel?.modelId || 'gpt-4o'
+    this.provider = provider
+    this.model = model
     this.server = new Server(

getEffectiveModel() already handles all resolution paths correctly when this.provider/this.model can be undefined.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts` around lines
52 - 88, The constructor currently hard-sets this.provider and this.model to
defaults, which prevents getEffectiveModel() from performing live lookups or
throwing when no model is configured; change the constructor to assign only the
incoming parameters (this.provider = provider; this.model = model) so they can
be undefined, rely on getEffectiveModel() everywhere that needs the resolved
model (call getEffectiveModel() in request handlers or methods that use
provider/model), and keep the existing error-throw and default-resolution logic
inside getEffectiveModel() intact so updates to
presenter.configPresenter.getDefaultVisionModel() are picked up at runtime.

122-122: ⚠️ Potential issue | 🟠 Major

MIME type hardcoded to image/jpeg for all images.

Both queryImageWithModel and ocrImageWithModel construct data:image/jpeg;base64,... regardless of the file's actual extension. Sending a PNG or WEBP file as image/jpeg can cause the model to misparse or reject it.

🐛 Proposed fix using extension-based lookup
+    const ext = path.extname(filePath).toLowerCase().slice(1)
+    const mimeMap: Record<string, string> = {
+      jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
+      gif: 'image/gif', webp: 'image/webp', bmp: 'image/bmp'
+    }
+    const mime = mimeMap[ext] ?? 'image/jpeg'
-    const dataUrl = `data:image/jpeg;base64,${base64Image}`
+    const dataUrl = `data:${mime};base64,${base64Image}`

Apply the same fix to the equivalent block in ocrImageWithModel (Line 168).

Also applies to: 168-168

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts` at line 122,
The data URL is hardcoded to image/jpeg which breaks non-JPEG inputs; update
both queryImageWithModel and ocrImageWithModel so the MIME type is derived from
the file extension (or actual buffer detection) and used when building the
dataUrl (the line creating `dataUrl =
\`data:image/...;base64,${base64Image}\``). Locate the `dataUrl` and
`base64Image` usage in both functions, map extensions (e.g., .png → image/png,
.webp → image/webp, .gif → image/gif) or use a mime lookup utility, and replace
the constant "image/jpeg" with the resolved MIME type; ensure fallback to
image/octet-stream or image/jpeg if resolution fails and apply the same change
at the analogous line in `ocrImageWithModel`.
src/main/presenter/index.ts (1)

350-362: ⚠️ Potential issue | 🟠 Major

Add cleanup for newAgentPresenter in destroy().

newAgentPresenter holds DeepChatAgentPresenter (which maintains abortControllers and runtimeState maps) and manages sessions/messages. These resources are never cleaned up on app shutdown, causing potential leaks of active abort controllers and in-memory state.

Add this.newAgentPresenter.destroy() to the destroy method (after implementing the method in NewAgentPresenter and DeepChatAgentPresenter to clean up their internal resources).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/index.ts` around lines 350 - 362, The destroy() in
presenter index omits cleanup for newAgentPresenter which leaves
DeepChatAgentPresenter's abortControllers and runtimeState maps and
session/message state leaking; implement a destroy method on NewAgentPresenter
(and ensure DeepChatAgentPresenter exposes/cleans its internal abortControllers
and runtimeState maps) to abort any pending controllers, clear maps and
session/message state, then add this.newAgentPresenter.destroy() into the
index's destroy() sequence (place it alongside other presenters' destroy calls)
so all agent-related resources are released on shutdown.
♻️ Duplicate comments (5)
src/renderer/src/i18n/da-DK/settings.json (1)

62-67: Duplicate: locale completeness for new settings keys.

Same concern as in src/renderer/src/i18n/fa-IR/settings.json Line 62-67—please ensure the new keys exist across all locales and stay consistent.

Based on learnings: Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/da-DK/settings.json` around lines 62 - 67, The new i18n
keys ("fileMaxSizeHint" and the nested "defaultModel" keys: "title",
"chatModel", "visionModel") were added to da-DK but are missing or inconsistent
across locales; update every locale file (en-US, fa-IR, fr-FR, he-IL, ja-JP,
ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW and da-DK) to include these exact keys
with appropriate translations so all locale JSONs have the same key structure
and names ("fileMaxSizeHint", "defaultModel.title", "defaultModel.chatModel",
"defaultModel.visionModel").
src/renderer/src/i18n/pt-BR/settings.json (1)

62-67: Duplicate: locale completeness for new settings keys.

Same concern as in src/renderer/src/i18n/fa-IR/settings.json Line 62-67—please ensure the new keys exist across all locales and stay consistent.

Based on learnings: Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/pt-BR/settings.json` around lines 62 - 67, Add the new
translation keys introduced here—fileMaxSizeHint and the nested defaultModel
keys (title, chatModel, visionModel)—to every locale file so key names are
consistent across all locales (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR,
pt-BR, ru-RU, zh-CN, zh-HK, zh-TW); find the same settings JSON structure in
each locale and add equivalent translated strings for "fileMaxSizeHint" and
defaultModel.{title,chatModel,visionModel} to match the key names used in this
diff.
src/renderer/src/i18n/zh-TW/settings.json (1)

62-67: Duplicate: locale completeness for new settings keys.

Same concern as in src/renderer/src/i18n/fa-IR/settings.json Line 62-67—please ensure the new keys exist across all locales and stay consistent.

Based on learnings: Add new translations to ALL language files (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW) with consistent key names across all locales.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/i18n/zh-TW/settings.json` around lines 62 - 67, The new
translation keys fileMaxSizeHint and the defaultModel object (with nested keys
title, chatModel, visionModel) are present in zh-TW but missing or inconsistent
across other locales; update all locale settings.json files (da-DK, en-US,
fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK) to include these
exact keys with appropriate translations so key names remain consistent across
all locale files.
src/renderer/src/components/icons/MinimizeIcon.vue (1)

1-19: Same Iconify lucide guideline concern as RestoreIcon.vue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/icons/MinimizeIcon.vue` around lines 1 - 19, The
MinimizeIcon.vue SVG does not follow the Iconify/lucide
accessibility/consistency guideline (same issue as RestoreIcon.vue); update the
<svg> in MinimizeIcon.vue to match the standardized icon pattern used in
RestoreIcon.vue by adding the required attributes (e.g., role="img",
focusable="false", aria-hidden="true", keep xmlns and viewBox), keep the
:fill="fill" prop and the withDefaults/defineProps setup for fill, and ensure
the SVG markup otherwise mirrors the RestoreIcon.vue implementation for
accessibility and consistency.
src/renderer/src/components/icons/CloseIcon.vue (1)

1-19: Same Iconify lucide guideline concern as RestoreIcon.vue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/icons/CloseIcon.vue` around lines 1 - 19, The
CloseIcon.vue SVG doesn't follow the same Iconify/lucide guidelines as
RestoreIcon.vue; update the <svg> element (in the template) to use scalable
sizing and accessibility attributes: replace fixed width/height "10" with "1em",
add role="img", aria-hidden="true" and focusable="false" attributes, and keep
using the existing fill prop (defineProps/withDefaults for fill) so color is
inherited via 'currentColor'.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e0591e and 781af72.

📒 Files selected for processing (131)
  • build/entitlements.mac.plist
  • docs/architecture/new-ui-implementation-plan.md
  • docs/specs/default-model-settings/plan.md
  • docs/specs/default-model-settings/spec.md
  • docs/specs/default-model-settings/tasks.md
  • docs/specs/new-agent/plan.md
  • docs/specs/new-agent/spec.md
  • docs/specs/new-agent/tasks.md
  • docs/specs/new-agent/v1-spec.md
  • docs/specs/new-agent/v2-spec.md
  • docs/specs/new-agent/v3-spec.md
  • docs/specs/new-ui-agent-session/spec.md
  • docs/specs/new-ui-agent-store/spec.md
  • docs/specs/new-ui-chat-components/spec.md
  • docs/specs/new-ui-implementation/todo.md
  • docs/specs/new-ui-markdown-rendering/spec.md
  • docs/specs/new-ui-page-state/spec.md
  • docs/specs/new-ui-pages/spec.md
  • docs/specs/new-ui-project-store/spec.md
  • docs/specs/new-ui-session-store/spec.md
  • docs/specs/new-ui-sidebar/spec.md
  • docs/specs/new-ui-status-bar/spec.md
  • src/main/events.ts
  • src/main/presenter/agentPresenter/utility/utilityHandler.ts
  • src/main/presenter/configPresenter/index.ts
  • src/main/presenter/deepchatAgentPresenter/accumulator.ts
  • src/main/presenter/deepchatAgentPresenter/contextBuilder.ts
  • src/main/presenter/deepchatAgentPresenter/dispatch.ts
  • src/main/presenter/deepchatAgentPresenter/echo.ts
  • src/main/presenter/deepchatAgentPresenter/index.ts
  • src/main/presenter/deepchatAgentPresenter/messageStore.ts
  • src/main/presenter/deepchatAgentPresenter/process.ts
  • src/main/presenter/deepchatAgentPresenter/sessionStore.ts
  • src/main/presenter/deepchatAgentPresenter/types.ts
  • src/main/presenter/deeplinkPresenter/index.ts
  • src/main/presenter/index.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/builder.ts
  • src/main/presenter/mcpPresenter/inMemoryServers/imageServer.ts
  • src/main/presenter/newAgentPresenter/agentRegistry.ts
  • src/main/presenter/newAgentPresenter/index.ts
  • src/main/presenter/newAgentPresenter/messageManager.ts
  • src/main/presenter/newAgentPresenter/sessionManager.ts
  • src/main/presenter/projectPresenter/index.ts
  • src/main/presenter/sessionPresenter/index.ts
  • src/main/presenter/sessionPresenter/managers/conversationManager.ts
  • src/main/presenter/shortcutPresenter.ts
  • src/main/presenter/sqlitePresenter/index.ts
  • src/main/presenter/sqlitePresenter/tables/deepchatMessages.ts
  • src/main/presenter/sqlitePresenter/tables/deepchatSessions.ts
  • src/main/presenter/sqlitePresenter/tables/newProjects.ts
  • src/main/presenter/sqlitePresenter/tables/newSessions.ts
  • src/main/presenter/syncPresenter/index.ts
  • src/main/presenter/windowPresenter/index.ts
  • src/preload/index.ts
  • src/renderer/settings/components/CommonSettings.vue
  • src/renderer/settings/components/common/DefaultModelSettingsSection.vue
  • src/renderer/shell/App.vue
  • src/renderer/shell/components/AppBar.vue
  • src/renderer/src/App.vue
  • src/renderer/src/assets/style.css
  • src/renderer/src/components/AppBar.vue
  • src/renderer/src/components/ModelSelect.vue
  • src/renderer/src/components/NewThread.vue
  • src/renderer/src/components/NewThreadMock.vue
  • src/renderer/src/components/SideBar.vue
  • src/renderer/src/components/WindowSideBar.vue
  • src/renderer/src/components/chat/ChatInputBox.vue
  • src/renderer/src/components/chat/ChatInputToolbar.vue
  • src/renderer/src/components/chat/ChatStatusBar.vue
  • src/renderer/src/components/chat/ChatTopBar.vue
  • src/renderer/src/components/chat/MessageList.vue
  • src/renderer/src/components/icons/CloseIcon.vue
  • src/renderer/src/components/icons/MaximizeIcon.vue
  • src/renderer/src/components/icons/MinimizeIcon.vue
  • src/renderer/src/components/icons/ModelIcon.vue
  • src/renderer/src/components/icons/RestoreIcon.vue
  • src/renderer/src/components/mcp-config/mcpServerForm.vue
  • src/renderer/src/components/mock/MockChatPage.vue
  • src/renderer/src/components/mock/MockInputBox.vue
  • src/renderer/src/components/mock/MockInputToolbar.vue
  • src/renderer/src/components/mock/MockMessageList.vue
  • src/renderer/src/components/mock/MockStatusBar.vue
  • src/renderer/src/components/mock/MockTopBar.vue
  • src/renderer/src/components/mock/MockWelcomePage.vue
  • src/renderer/src/composables/useMockViewState.ts
  • src/renderer/src/events.ts
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/pages/ChatPage.vue
  • src/renderer/src/pages/NewThreadPage.vue
  • src/renderer/src/pages/WelcomePage.vue
  • src/renderer/src/stores/ui/agent.ts
  • src/renderer/src/stores/ui/draft.ts
  • src/renderer/src/stores/ui/message.ts
  • src/renderer/src/stores/ui/pageRouter.ts
  • src/renderer/src/stores/ui/project.ts
  • src/renderer/src/stores/ui/session.ts
  • src/renderer/src/views/ChatTabView.vue
  • src/shadcn/components/ui/tooltip/TooltipContent.vue
  • src/shared/types/agent-interface.d.ts
  • src/shared/types/core/chat-message.ts
  • src/shared/types/presenters/index.d.ts
  • src/shared/types/presenters/legacy.presenters.d.ts
  • src/shared/types/presenters/new-agent.presenter.d.ts
  • src/shared/types/presenters/project.presenter.d.ts
  • src/shared/utils/throttle.ts
  • test/main/presenter/configPresenter/defaultModelSettings.test.ts
  • test/main/presenter/deepchatAgentPresenter/accumulator.test.ts
  • test/main/presenter/deepchatAgentPresenter/contextBuilder.test.ts
  • test/main/presenter/deepchatAgentPresenter/deepchatAgentPresenter.test.ts
  • test/main/presenter/deepchatAgentPresenter/dispatch.test.ts
  • test/main/presenter/deepchatAgentPresenter/echo.test.ts
  • test/main/presenter/deepchatAgentPresenter/messageStore.test.ts
  • test/main/presenter/deepchatAgentPresenter/process.test.ts
  • test/main/presenter/newAgentPresenter/agentRegistry.test.ts
  • test/main/presenter/newAgentPresenter/integration.test.ts
  • test/main/presenter/newAgentPresenter/messageManager.test.ts
  • test/main/presenter/newAgentPresenter/newAgentPresenter.test.ts
  • test/main/presenter/newAgentPresenter/sessionManager.test.ts
  • test/main/presenter/projectPresenter/projectPresenter.test.ts
  • test/shared/utils/throttle.test.ts
💤 Files with no reviewable changes (2)
  • build/entitlements.mac.plist
  • src/renderer/src/components/SideBar.vue

Comment on lines +30 to +35
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
emit('submit')
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing e.isComposing guard causes premature submit for CJK IME users.

Each keydown in an IME is responded to by the browser, so when a user hits Enter to confirm an IME selection, the code immediately processes a keydown event — triggering submit before the user has finished composing their text. The Enter key is mis-recognized and sends the chat message instead of ending the composition.

The fix is a single additional guard:

🐛 Proposed fix
 function handleKeydown(e: KeyboardEvent) {
-  if (e.key === 'Enter' && !e.shiftKey) {
+  if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
     e.preventDefault()
     emit('submit')
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/chat/ChatInputBox.vue` around lines 30 - 35, The
handleKeydown function submits on Enter currently without checking IME
composition state; update handleKeydown to skip handling when the event
indicates composition (use the KeyboardEvent.isComposing property) so Enter used
to confirm CJK/IME composition won't trigger emit('submit'); locate the function
named handleKeydown and add a guard that returns early if e.isComposing is true
before the existing Enter/shift checks.

Comment on lines +21 to +44
async function initialize(): Promise<void> {
try {
// 1. Check if any provider is enabled
const enabledProviders = configPresenter.getEnabledProviders()
if (!enabledProviders || enabledProviders.length === 0) {
route.value = { name: 'welcome' }
return
}

// 2. Check for active session on this tab
const tabId = window.api.getWebContentsId()
const activeSession = await sessionPresenter.getActiveSession(tabId)
if (activeSession) {
route.value = { name: 'chat', sessionId: activeSession.sessionId }
return
}

// 3. Default to new thread
route.value = { name: 'newThread' }
} catch (e) {
error.value = String(e)
route.value = { name: 'newThread' }
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find presenter-related files and interfaces
find . -type f \( -name "*.ts" -o -name "*.d.ts" \) | grep -i presenter | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 1266


🏁 Script executed:

# Search for getEnabledProviders definition
rg -n "getEnabledProviders" --type=ts -A 2 -B 2

Repository: ThinkInAIXYZ/deepchat

Length of output: 2848


🏁 Script executed:

# Search for usePresenter implementation
rg -n "usePresenter" --type=ts -l | head -10

Repository: ThinkInAIXYZ/deepchat

Length of output: 469


🏁 Script executed:

# Find usePresenter composable
fd -t f "usePresenter" --extension ts --extension tsx

Repository: ThinkInAIXYZ/deepchat

Length of output: 109


🏁 Script executed:

# Search for usePresenter definition
rg -n "export.*usePresenter|function usePresenter|const usePresenter" --type=ts -A 10

Repository: ThinkInAIXYZ/deepchat

Length of output: 1079


🏁 Script executed:

# Read usePresenter.ts to understand the proxy implementation
cat -n src/renderer/src/composables/usePresenter.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 3993


Add await to getEnabledProviders() calls — the usePresenter proxy wraps all methods as async.

Although getEnabledProviders() is defined as synchronous in the main process, the usePresenter proxy (lines 73–74 of src/renderer/src/composables/usePresenter.ts) wraps every method call as async via ipcRenderer.invoke(). This means at the renderer level, the call returns Promise<LLM_PROVIDER[]> instead of the array itself.

Without await, enabledProviders at lines 24 and 67 becomes a Promise object. The check at line 25 (enabledProviders.length === 0) evaluates to undefined === 0 (false), so the condition fails and the welcome route is never displayed even when all providers are disabled.

Fix by awaiting both calls:

Diff
   async function initialize(): Promise<void> {
     try {
       // 1. Check if any provider is enabled
-      const enabledProviders = configPresenter.getEnabledProviders()
+      const enabledProviders = await configPresenter.getEnabledProviders()
       if (!enabledProviders || enabledProviders.length === 0) {
         route.value = { name: 'welcome' }
         return
       }

The same issue occurs at line 67 in the event listener handler.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/stores/ui/pageRouter.ts` around lines 21 - 44, The renderer
is calling getEnabledProviders() via the usePresenter proxy which returns a
Promise, so update calls in initialize() and the provider-change event handler
to await the result (e.g., const enabledProviders = await
configPresenter.getEnabledProviders()); if the handler is not async, mark it
async so you can await inside it; keep using the enabledProviders variable for
the length check and subsequent routing logic (route.value = { name: 'welcome' }
/ 'newThread' / 'chat') unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants