Skip to content

Conversation

@zerob13
Copy link
Collaborator

@zerob13 zerob13 commented Sep 17, 2025

Fixes an issue where the correct model may not be selected when there is a pinned conversation. Optimizes new conversation performance.

Summary by CodeRabbit

  • New Features
    • Automatically selects your most recently used or preferred model when starting a new thread, with a graceful fallback to the first available enabled chat or image model.
  • Bug Fixes
    • Keeps the active model valid if models become disabled or change.
    • Reduces incorrect or blank model selection on first load.
    • Prevents unnecessary re-initialization, improving stability when models update.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 17, 2025

Walkthrough

Refactors NewThread.vue’s model-selection logic: adds one-time async initialization on mount, helper functions for choosing an enabled model, restores from recent threads or preferred setting, falls back to first enabled model, and adds a focused watcher on enabledModels to validate or replace the active model post-initialization.

Changes

Cohort / File(s) Summary of Changes
Model selection initialization and validation
src/renderer/src/components/NewThread.vue
Replaced composite watcher with one-time async init (onMounted) plus targeted watch on enabledModels. Added helpers (findEnabledModel, pickFirstEnabledModel, setActiveFromEnabled, initActiveModel). Init tries recent thread model → preferred config → first enabled (Chat/ImageGeneration). Watch validates active model and falls back if missing. Adjusted watch options to immediate: false, deep: true. No public API changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as NewThread.vue
  participant S as ChatStore
  participant E as EnabledModels
  participant P as ConfigPresenter
  participant A as ActiveModel

  rect rgb(240,245,255)
    note over C: onMounted
    C->>C: initActiveModel()
    alt Match from recent threads
      C->>S: Get most recent model (pinned/normal)
      C->>E: Check if model is enabled
      E-->>C: Enabled?
      C->>A: setActiveFromEnabled(model)
    else Restore preferred model
      C->>P: getSetting('preferredModel')
      C->>E: Check if preferred is enabled
      E-->>C: Enabled?
      C->>A: setActiveFromEnabled(model)
    else Fallback
      C->>E: pickFirstEnabledModel(Chat/ImageGeneration)
      C->>A: setActiveFromEnabled(model)
    end
    note over C: mark initialized
  end

  rect rgb(245,255,245)
    note over C,E: Watch enabledModels (immediate: false)
    C->>C: if not initialized → initActiveModel()
    C->>E: Validate current active model exists
    alt Active missing
      C->>E: pickFirstEnabledModel(...)
      C->>A: setActiveFromEnabled(model)
    else Active valid
      note over C: No change
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A twitch of whiskers, choices align,
I sniff out models, one at a time.
From threads to prefs, I hop in queue,
If none are ripe, the first will do.
Initialized now—so calm, so able—
I nibble bugs beneath the table. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description is too brief and does not follow the repository template: it only provides a one-line summary and omits required sections such as a clear problem statement, a detailed solution description, UI/UX changes, platform compatibility notes, and additional context. The raw_summary shows important implementation details (new helpers like findEnabledModel, pickFirstEnabledModel, setActiveFromEnabled, initActiveModel, the onMounted await initActiveModel call, and the watch change to enabledModels with immediate:false) that are not documented in the description. Because the template is explicit about required sections, the current description fails to meet repository standards. Please update the PR description to follow the provided template: add a clear problem statement, a detailed description of the solution and implementation, UI/UX impact (include screenshots/GIFs if applicable), platform compatibility/testing notes, and any additional context or reproduction steps. Explicitly call out the key code changes from the raw summary (the helper functions, initActiveModel flow, async onMounted initialization, and the change to the enabledModels watcher) so reviewers can quickly verify behavior and intent. This will satisfy the template and speed up review.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary change — fixing model selection when a pinned thread exists — and directly matches the PR's intent and code changes in NewThread.vue that target model selection logic. It is concise and actionable for a reviewer. The wording is slightly informal but still clear enough to understand the main change.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/pinned-thread-cause-model-select-failed

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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: 1

🧹 Nitpick comments (10)
src/renderer/src/components/NewThread.vue (10)

248-249: Guard against undefined dtThreads in predicate

g.dtThreads.length can throw if dtThreads is undefined. Use optional chaining.

-const normalGroup = chatStore.threads.find((g) => g.dt !== 'Pinned' && g.dtThreads.length > 0)
+const normalGroup = chatStore.threads.find(
+  (g) => g.dt !== 'Pinned' && (g.dtThreads?.length ?? 0) > 0
+)

207-218: Type the return of findEnabledModel for strictness

Add a precise return type (avoid implicit any).

-const findEnabledModel = (providerId: string, modelId: string) => {
+const findEnabledModel = (
+  providerId: string,
+  modelId: string
+): { model: MODEL_META; providerId: string } | undefined => {

220-226: Prefer Chat models first, then Image; add return type

Aligns with typical UX expectations and adds typing.

-const pickFirstEnabledModel = () => {
-  const found = settingsStore.enabledModels
-    .flatMap((p) => p.models.map((m) => ({ ...m, providerId: p.providerId })))
-    .find((m) => m.type === ModelType.Chat || m.type === ModelType.ImageGeneration)
-  return found
-}
+const pickFirstEnabledModel = ():
+  | (MODEL_META & { providerId: string })
+  | undefined => {
+  const flat = settingsStore.enabledModels.flatMap((p) =>
+    p.models.map((m) => ({ ...m, providerId: p.providerId }))
+  )
+  return flat.find((m) => m.type === ModelType.Chat) ??
+    flat.find((m) => m.type === ModelType.ImageGeneration)
+}

227-240: Preserve model tags in activeModel

UI shows badges from activeModel.tags, but this setter drops them.

-  activeModel.value = {
+  activeModel.value = {
     name: m.name,
     id: m.id,
     providerId: m.providerId,
-    tags: [],
+    tags: (m as any).tags ?? [],
     type: m.type ?? ModelType.Chat
   }

If MODEL_META exposes tags, tighten the param type instead of (m as any).


204-206: Avoid re-entrant init races

initActiveModel can be invoked concurrently (watch + mount). Add an initializing guard with try/finally; skip work in the watcher if initializing.

-const initialized = ref(false)
+const initialized = ref(false)
+const initializing = ref(false)
@@
-const initActiveModel = async () => {
+const initActiveModel = async () => {
   if (initialized.value) return
+  if (initializing.value) return
+  initializing.value = true
+  try {
@@
-    initialized.value = true
+    initialized.value = true
@@
-  }
+  } finally {
+    initializing.value = false
+  }
 }
@@
 watch(
   () => settingsStore.enabledModels,
   async () => {
-    if (!initialized.value) {
+    if (!initialized.value) {
+      if (initializing.value) return
       await initActiveModel()
       return
     }

Also applies to: 242-289, 290-315


244-246: Translate inline comments to English per codebase standards

Comments here are in Chinese; guidelines require English for all comments/logs.

Example:

-// 1) 尝试根据最近会话(区分 pinned/非 pinned)选择
+// 1) Try recent threads (prefer pinned vs. non‑pinned)

Also applies to: 265-266, 282-293


15-16: Add accessible labels and i18n for icon/imagery

  • Image lacks alt text.
  • Icon-only buttons need aria-labels (i18n).
-<img src="@/assets/logo-dark.png" class="w-24 h-24" loading="lazy" />
+<img src="@/assets/logo-dark.png" class="w-24 h-24" loading="lazy" :alt="t('app.logoAlt')" />
@@
-<Button
+<Button
   variant="outline"
   class="flex border-none rounded-none shadow-none items-center gap-1.5 px-2 h-full"
   size="sm"
+  :aria-label="t('a11y.selectModel')"
>
@@
-<Button
+<Button
   class="w-7 h-full rounded-none border-none shadow-none transition-all duration-300"
   :class="{
     'w-0 opacity-0 p-0 overflow-hidden': !showSettingsButton && !isHovering,
     'w-7 opacity-100': showSettingsButton || isHovering
   }"
   size="icon"
   variant="outline"
+  :aria-label="t('a11y.settings')"
>

If these keys don’t exist, I can add them to i18n.

Also applies to: 36-40, 75-86


4-12: Sidebar toggle button: add aria-label (i18n)

Icon-only control; screen readers need a label.

-<Button
+<Button
   class="w-7 h-7 rounded-md"
   size="icon"
   variant="outline"
   @click="onSidebarButtonClick"
+  :aria-label="t(chatStore.isSidebarOpen ? 'a11y.closeSidebar' : 'a11y.openSidebar')"
>

139-143: Prefer type alias over interface (project guideline)

Convert interface PreferredModel to a type alias.

-interface PreferredModel {
-  modelId: string
-  providerId: string
-}
+type PreferredModel = {
+  modelId: string
+  providerId: string
+}

471-475: Avoid as any when creating thread; tighten types

Pass a well-typed options object; avoid any.

-  } as any)
+  } as CreateThreadOptions)

If CreateThreadOptions doesn’t exist, define/export it where createThread is declared and import here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16de806 and b9c8c71.

📒 Files selected for processing (1)
  • src/renderer/src/components/NewThread.vue (2 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
src/renderer/src/**/*

📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)

src/renderer/src/**/*: All user-facing strings must use i18n keys (avoid hardcoded user-visible text in code)
Use the 'vue-i18n' framework for all internationalization in the renderer
Ensure all user-visible text in the renderer uses the translation system

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/**/*.{vue,ts,js,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

渲染进程代码放在 src/renderer

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/src/**/*.{vue,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/vue-best-practices.mdc)

src/renderer/src/**/*.{vue,ts,tsx,js,jsx}: Use the Composition API for better code organization and reusability
Implement proper state management with Pinia
Utilize Vue Router for navigation and route management
Leverage Vue's built-in reactivity system for efficient data handling

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/src/**/*.vue

📄 CodeRabbit inference engine (.cursor/rules/vue-best-practices.mdc)

Use scoped styles to prevent CSS conflicts between components

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

src/renderer/**/*.{ts,tsx,vue}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Use TypeScript for all code; prefer types over interfaces.
Avoid enums; use const objects instead.
Use arrow functions for methods and computed properties.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/**/*.{vue,ts}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

Implement lazy loading for routes and components.

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/**/*.{ts,vue}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

src/renderer/**/*.{ts,vue}: Use useFetch and useAsyncData for data fetching.
Implement SEO best practices using Nuxt's useHead and useSeoMeta.

Use Pinia for frontend state management (do not introduce alternative state libraries)

Files:

  • src/renderer/src/components/NewThread.vue
**/*.{ts,tsx,js,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for all logs and comments

Files:

  • src/renderer/src/components/NewThread.vue
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Enable and adhere to strict TypeScript typing (avoid implicit any, prefer precise types)

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/{src,shell,floating}/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

src/renderer/{src,shell,floating}/**/*.vue: Use Vue 3 Composition API for all components
All user-facing strings must use i18n keys via vue-i18n (no hard-coded UI strings)
Use Tailwind CSS utilities and ensure styles are scoped in Vue components

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/src/components/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Organize UI components by feature within src/renderer/src/

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/src/**

📄 CodeRabbit inference engine (AGENTS.md)

Put application code for the Vue app under src/renderer/src (components, stores, views, i18n, lib)

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/src/**/*.{vue,ts}

📄 CodeRabbit inference engine (AGENTS.md)

All user-facing strings in the renderer must use vue-i18n keys defined in src/renderer/src/i18n

Files:

  • src/renderer/src/components/NewThread.vue
**/*.{js,jsx,ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

Apply Prettier formatting: single quotes, no semicolons, max width 100

Files:

  • src/renderer/src/components/NewThread.vue
src/renderer/**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

Name Vue components in PascalCase (e.g., ChatInput.vue)

Files:

  • src/renderer/src/components/NewThread.vue
🧠 Learnings (1)
📚 Learning: 2025-07-23T00:45:57.322Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-07-23T00:45:57.322Z
Learning: Applies to src/renderer/**/*.{vue} : Use composition API and declarative programming patterns; avoid options API.

Applied to files:

  • src/renderer/src/components/NewThread.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)

Comment on lines +242 to +289
const initActiveModel = async () => {
if (initialized.value) return
// 1) 尝试根据最近会话(区分 pinned/非 pinned)选择
if (chatStore.threads.length > 0) {
const pinnedGroup = chatStore.threads.find((g) => g.dt === 'Pinned')
const pinnedFirst = pinnedGroup?.dtThreads?.[0]
const normalGroup = chatStore.threads.find((g) => g.dt !== 'Pinned' && g.dtThreads.length > 0)
const normalFirst = normalGroup?.dtThreads?.[0]
const candidate = [pinnedFirst, normalFirst]
.filter(Boolean)
.sort((a, b) => (b!.updatedAt || 0) - (a!.updatedAt || 0))[0] as
| typeof pinnedFirst
| undefined
if (candidate?.settings?.modelId && candidate?.settings?.providerId) {
const match = findEnabledModel(candidate.settings.providerId, candidate.settings.modelId)
if (match) {
setActiveFromEnabled({ ...match.model, providerId: match.providerId })
initialized.value = true
return
}
} catch (error) {
console.warn('Failed to get user preferred model:', error)
}
}

// 如果没有偏好模型或偏好模型不可用,使用第一个可用模型
if (settingsStore.enabledModels.length > 0) {
const model = settingsStore.enabledModels
.flatMap((provider) =>
provider.models.map((m) => ({ ...m, providerId: provider.providerId }))
)
.find((m) => m.type === ModelType.Chat || m.type === ModelType.ImageGeneration)
if (model) {
activeModel.value = {
name: model.name,
id: model.id,
providerId: model.providerId,
tags: [],
type: model.type ?? ModelType.Chat
}
// 2) 尝试用户上次选择的偏好模型
try {
const preferredModel = (await configPresenter.getSetting('preferredModel')) as
| PreferredModel
| undefined
if (preferredModel?.modelId && preferredModel?.providerId) {
const match = findEnabledModel(preferredModel.providerId, preferredModel.modelId)
if (match) {
setActiveFromEnabled({ ...match.model, providerId: match.providerId })
initialized.value = true
return
}
}
} catch (error) {
console.warn('Failed to get user preferred model:', error)
}

// 3) 选择第一个可用模型
const first = pickFirstEnabledModel()
if (first) {
setActiveFromEnabled(first)
initialized.value = true
}
}

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

Do not overwrite user/deeplink model selection on mount

If a deeplink (or user action) sets the model before mount, await initActiveModel() can still override it because initialized isn’t flipped in handleModelUpdate. Add an early-return in initActiveModel when a valid activeModel already exists, and mark initialized in handleModelUpdate.

Apply this diff:

@@
-const initActiveModel = async () => {
+const initActiveModel = async () => {
   if (initialized.value) return
+  // Respect an already-selected model (e.g., via deeplink or user action)
+  if (activeModel.value?.id && activeModel.value?.providerId) {
+    const exists = findEnabledModel(activeModel.value.providerId, activeModel.value.id)
+    if (exists) {
+      initialized.value = true
+      return
+    }
+  }
@@
 const handleModelUpdate = (model: MODEL_META, providerId: string) => {
   activeModel.value = {
@@
   }
   chatStore.updateChatConfig({
@@
   })
@@
   configPresenter.setSetting('preferredModel', {
@@
   })
+
+  // Prevent init flow from overriding an explicit selection
+  initialized.value = true
@@
 }
@@
 onMounted(async () => {
@@
-  // 组件激活时初始化一次默认模型
+  // Initialize default model once on component activation (if none selected yet)
   await initActiveModel()

Also applies to: 335-355, 426-427

🤖 Prompt for AI Agents
In src/renderer/src/components/NewThread.vue around lines 242-289 (also apply
same change at 335-355 and 426-427), initActiveModel can override a model set
via deeplink/user action because handleModelUpdate never flips initialized; add
an early return at the start of initActiveModel that returns if activeModel is
already set and valid (has providerId and modelId), and update handleModelUpdate
to set initialized.value = true when it applies a user/deeplink model (use the
same validity check) so initActiveModel won't overwrite it on mount.

@zerob13 zerob13 merged commit 02fe4de into dev Sep 17, 2025
2 checks passed
@zerob13 zerob13 deleted the bugfix/pinned-thread-cause-model-select-failed branch September 21, 2025 15:16
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