Skip to content

Conversation

@yyhhyyyyyy
Copy link
Collaborator

@yyhhyyyyyy yyhhyyyyyy commented Aug 25, 2025

add message navigation sidebar with search functionality
CleanShot 2025-08-25 at 16 40 26

Summary by CodeRabbit

  • New Features

    • Added a message navigation sidebar with search, result counts, previews, and icons.
    • Supports desktop (fixed panel) and mobile (overlay) with a header toggle button.
    • Clicking a result scrolls to the message and briefly highlights it.
    • Sidebar auto-closes after creating/selecting threads on small screens; layout adapts when open.
  • Chores

    • Added navigation UI translations for en-US, fa-IR, fr-FR, ja-JP, ko-KR, ru-RU, zh-CN, zh-HK, and zh-TW.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Adds a message navigation sidebar with search and jump-to-message capabilities, integrates toggle and close behaviors across views, exposes message scrolling APIs, and introduces new i18n strings. Updates the chat store with UI state and a getter. Minor layout/interaction tweaks in ChatTabView, TitleView, ThreadsView, ChatView, and MessageList.

Changes

Cohort / File(s) Summary of changes
Message Navigation UI
src/renderer/src/components/MessageNavigationSidebar.vue, src/renderer/src/views/ChatTabView.vue, src/renderer/src/components/TitleView.vue, src/renderer/src/components/ThreadsView.vue
New sidebar component with search, highlighting, and jump actions; integrated in ChatTabView with desktop/mobile modes and outside-click handling; header toggle button; auto-close navigation on new/narrow thread selection.
Message Scrolling API
src/renderer/src/components/message/MessageList.vue, src/renderer/src/components/ChatView.vue
Adds and exposes scrollToMessage(messageId) with smooth scroll and temporary highlight; exposes messageList from ChatView via defineExpose.
Store Additions
src/renderer/src/stores/chat.ts
Adds isMessageNavigationOpen UI state and getCurrentThreadMessages() getter; both exposed via store API.
i18n Additions
src/renderer/src/i18n/*/chat.json (en-US, fa-IR, fr-FR, ja-JP, ko-KR, ru-RU, zh-CN, zh-HK, zh-TW)
Adds top-level navigation object with keys for title, placeholders, counts, and labels; structural brace/comma fixes where needed; no changes to existing keys.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant TitleView
  participant ChatTabView
  participant Store as ChatStore
  participant Nav as MessageNavigationSidebar
  participant ChatView
  participant MsgList as MessageList

  User->>TitleView: Click navigation toggle
  TitleView->>Store: Toggle isMessageNavigationOpen
  Store-->>ChatTabView: isMessageNavigationOpen changed
  ChatTabView->>Nav: Mount/show with messages, activeMessageId

  User->>Nav: Search / click message
  Nav-->>ChatTabView: emit scroll-to-message(messageId)
  ChatTabView->>ChatView: access exposed messageList
  ChatView->>MsgList: call scrollToMessage(messageId)
  MsgList-->>User: Smooth scroll + temporary highlight

  alt Small screen
    ChatTabView->>Store: Close navigation after scroll (delayed)
  end

  User-->>ChatTabView: Click outside overlay (mobile)
  ChatTabView->>Store: Close navigation (if outside Nav)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • zerob13

Poem

A rabbit hops through threads so wide,
With lantern-search by sidebar’s side.
It finds a note, then whoosh—highlight! ✨
Smooth scrolly burrow, snug and bright.
Click, close, and nibble bits of lore—
Messages mapped, then hop for more! 🐇📜

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/message-nav-sidebar

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

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

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

84-91: Localize hardcoded thread title "新会话"

Renderer user-visible strings must use i18n. Replace the literal with a translation key (e.g., chat.threads.newThread).

Example change (outside this hunk):

-const threadId = await chatStore.createThread('新会话', {
+const threadId = await chatStore.createThread(t('chat.threads.newThread'), {

If chat.threads.newThread doesn't exist yet, add it across locales in chat.json.

Also applies to: 99-106

🧹 Nitpick comments (25)
src/renderer/src/stores/chat.ts (3)

42-42: UI state addition is fine; consider persisting the toggle across sessions

isMessageNavigationOpen is a good fit for Pinia state. Optional: persist this UI preference (e.g., per-tab or globally) so the sidebar opens in the user’s last-chosen state after app reload.

I can wire this via a small Pinia plugin/localStorage bridge if desired.


86-90: Simplify getter; avoid redundant activeThreadId guard and annotate return type

getMessages() already returns [] when no active thread. You can simplify and make the return type explicit to improve readability.

Apply:

-const getCurrentThreadMessages = () => {
-  const activeThreadId = getActiveThreadId()
-  if (!activeThreadId) return []
-  return getMessages()
-}
+const getCurrentThreadMessages = (): (AssistantMessage | UserMessage)[] => getMessages()

1188-1196: Export additions look good; avoid API duplication confusion

Exposing isMessageNavigationOpen and getCurrentThreadMessages is reasonable. Minor: since getMessages already returns the active tab’s messages, consider documenting that getCurrentThreadMessages is an alias intended for navigation context, or export an alias to reduce cognitive overhead in consumers.

Optionally, in the future, replace getCurrentThreadMessages calls with getMessages and deprecate the alias to keep a single source of truth.

Also applies to: 1226-1231

src/renderer/src/components/ThreadsView.vue (1)

218-219: Close both side panels — DRY the repeated logic

You close both panels in two places. Extracting a small helper keeps this consistent and easier to change.

Apply within these ranges:

-    chatStore.isSidebarOpen = false
-    chatStore.isMessageNavigationOpen = false
+    closeSidePanels()
-      chatStore.isSidebarOpen = false
-      chatStore.isMessageNavigationOpen = false
+      closeSidePanels()

Add this helper near existing consts (outside the selected ranges):

const closeSidePanels = () => {
  chatStore.isSidebarOpen = false
  chatStore.isMessageNavigationOpen = false
}

Also applies to: 230-231

src/renderer/src/components/message/MessageList.vue (2)

278-297: Scope the selector to this list and escape messageId; also make the API awaitable and signal success

Using document.querySelector risks matching elements from another list (e.g., multiple tabs) and breaks if messageId contains special CSS characters. Scope the query to messagesContainer and use CSS.escape when available. Returning a Promise makes the public API more reliable for callers.

Apply this diff:

-const scrollToMessage = (messageId: string) => {
-  nextTick(() => {
-    const messageElement = document.querySelector(`[data-message-id="${messageId}"]`)
-    if (messageElement) {
-      messageElement.scrollIntoView({
-        behavior: 'smooth',
-        block: 'center'
-      })
-
-      // 添加高亮效果
-      messageElement.classList.add('message-highlight')
-      setTimeout(() => {
-        messageElement.classList.remove('message-highlight')
-      }, 2000)
-    }
-  })
-}
+const scrollToMessage = async (messageId: string): Promise<boolean> => {
+  await nextTick()
+  const container = messagesContainer.value
+  if (!container) return false
+  const safeId =
+    (window.CSS && 'escape' in window.CSS) ? window.CSS.escape(messageId) : messageId
+  const messageElement = container.querySelector<HTMLElement>(
+    `[data-message-id="${safeId}"]`
+  )
+  if (!messageElement) return false
+
+  messageElement.scrollIntoView({
+    behavior: 'smooth',
+    block: 'center',
+    inline: 'nearest'
+  })
+
+  // 添加高亮效果
+  messageElement.classList.add('message-highlight')
+  window.setTimeout(() => {
+    messageElement.classList.remove('message-highlight')
+  }, 2000)
+  return true
+}

391-395: Type the exposed API and reflect the awaitable scrollToMessage

Expose explicit types so downstream components get correct intellisense and can await the operation.

-defineExpose({
-  scrollToBottom,
-  scrollToMessage,
-  aboveThreshold
-})
+defineExpose<{
+  scrollToBottom: () => void
+  scrollToMessage: (id: string) => Promise<boolean>
+  aboveThreshold: typeof aboveThreshold
+}>({
+  scrollToBottom,
+  scrollToMessage,
+  aboveThreshold
+})
src/renderer/src/i18n/ru-RU/chat.json (1)

58-67: Use ICU pluralization for Russian counts

Russian requires plural rules; fixed strings like “Всего {count} сообщений” will be grammatically wrong for count=1. Use vue-i18n ICU plurals for both totalMessages and searchResults.

Suggested replacements:

-    "totalMessages": "Всего {count} сообщений",
-    "searchResults": "Найдено {count} результатов из {total} сообщений",
+    "totalMessages": "Всего {count} {count, plural, one {сообщение} few {сообщения} many {сообщений} other {сообщения}}",
+    "searchResults": "Найден{count, plural, one {# результат} few {# результата} many {# результатов} other {# результата}} из {total} {total, plural, one {сообщения} few {сообщений} many {сообщений} other {сообщений}}"

If your i18n setup uses list-based formatting instead of ICU, I can adapt to that format.

src/renderer/src/i18n/fr-FR/chat.json (1)

63-64: Optional: add pluralization for count-dependent strings

Consider using vue-i18n pluralization for better French grammar, e.g., define two forms:

  • totalMessages: "{count} message au total | {count} messages au total"
  • searchResults: "{count} résultat trouvé sur {total} messages | {count} résultats trouvés sur {total} messages"

Note: switching to pluralization requires using tc() (or equivalent) at call sites.

src/renderer/src/components/ChatView.vue (1)

116-118: Expose a minimal, typed public API instead of the raw child ref

Exposing the entire messageList ref couples parents to internals and weakens type safety. Prefer exposing just the method(s) parents need (e.g., scrollToMessage) and type the ref.

Apply this diff locally to the exposed API:

-defineExpose({
-  messageList
-})
+defineExpose({
+  scrollToMessage: (id: string) => messageList.value?.scrollToMessage?.(id)
+})

And (outside this hunk) tighten the ref type near Line 35:

// near Line 35
-type MessageListPublicApi = { scrollToBottom: (smooth?: boolean) => void; scrollToMessage?: (id: string) => void }
-const messageList = ref<MessageListPublicApi | null>(null)
+type MessageListPublicApi = {
+  scrollToBottom: (smooth?: boolean) => void
+  scrollToMessage: (id: string) => void
+}
+const messageList = ref<MessageListPublicApi | null>(null)
src/renderer/src/components/TitleView.vue (1)

46-59: Add accessible labels and i18n to the navigation toggle; convert comment to English

Icon-only buttons should expose an aria-label (and optionally title) and reflect pressed state. Also, comments in code should be English per guidelines.

-      <!-- 消息导航按钮 -->
+      <!-- Message navigation toggle -->
       <Button
         class="w-7 h-7 rounded-md relative !p-0"
         size="icon"
         variant="outline"
         :class="{ 'bg-accent': chatStore.isMessageNavigationOpen }"
+        :aria-pressed="chatStore.isMessageNavigationOpen"
+        :aria-label="t('chat.navigation.title')"
+        :title="t('chat.navigation.title')"
         @click="chatStore.isMessageNavigationOpen = !chatStore.isMessageNavigationOpen"
       >
         <Icon
           icon="lucide:list"
           class="w-4 h-4 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
         />
       </Button>
src/renderer/src/i18n/zh-TW/chat.json (1)

61-62: Nit: 用語更貼近繁體中文

考慮將「匹配」改為「符合」以更貼近慣用語。

-    "noResults": "沒有找到匹配的訊息",
+    "noResults": "沒有找到符合的訊息",
src/renderer/src/components/MessageNavigationSidebar.vue (4)

199-215: Localize date/time format to the active UI locale

Hardcoding 'zh-CN' breaks formatting for other locales. Use useI18n().locale.

-const { t } = useI18n()
+const { t, locale } = useI18n()
-  if (diff < 24 * 60 * 60 * 1000 && date.getDate() === now.getDate()) {
-    return date.toLocaleTimeString('zh-CN', {
+  if (diff < 24 * 60 * 60 * 1000 && date.getDate() === now.getDate()) {
+    return date.toLocaleTimeString(locale.value, {
       hour: '2-digit',
       minute: '2-digit'
     })
   }
-  return date.toLocaleDateString('zh-CN', {
+  return date.toLocaleDateString(locale.value, {
     month: 'short',
     day: 'numeric',
     hour: '2-digit',
     minute: '2-digit'
   })

Also applies to: 134-136


121-128: Remove unused prop isOpen or use it for UI state

isOpen is declared but unused. Either remove it to avoid API noise or wire it to classes/transitions.

-interface Props {
-  messages: Message[]
-  isOpen: boolean
-  activeMessageId?: string
-}
+interface Props {
+  messages: Message[]
+  activeMessageId?: string
+}

7-11: Add accessible label to the close button (i18n)

Provide aria-label/title for the close icon; renderer strings must be localized.

-      <Button variant="ghost" size="icon" class="h-6 w-6" @click="$emit('close')">
+      <Button
+        variant="ghost"
+        size="icon"
+        class="h-6 w-6"
+        :aria-label="t('chat.navigation.title')"
+        :title="t('chat.navigation.title')"
+        @click="$emit('close')"
+      >

139-178: Content extraction assumptions may miss non-text blocks

getFullMessageContent ignores assistant blocks not typed as "content" and assumes user content lives in text. If your schema includes code/tool blocks or attachments, consider including their textual previews to improve search coverage.

src/renderer/src/views/ChatTabView.vue (10)

102-108: Lazy-load MessageNavigationSidebar; add type-only import for ChatView instance

  • MessageNavigationSidebar is always imported eagerly while all other heavy components are lazy-loaded. Load it async to reduce initial bundle.
  • Also import ChatView as a type for stronger typing on the ref.

Apply this diff within the selected lines:

-import MessageNavigationSidebar from '@/components/MessageNavigationSidebar.vue'
 const ThreadsView = defineAsyncComponent(() => import('@/components/ThreadsView.vue'))
 const TitleView = defineAsyncComponent(() => import('@/components/TitleView.vue'))
 const ChatView = defineAsyncComponent(() => import('@/components/ChatView.vue'))
 const NewThread = defineAsyncComponent(() => import('@/components/NewThread.vue'))
+const MessageNavigationSidebar = defineAsyncComponent(() => import('@/components/MessageNavigationSidebar.vue'))

Additionally, add a type-only import outside the selected lines to enable accurate typing of the template ref (see a related suggestion on Line 115):

import type ChatView from '@/components/ChatView.vue'

6-8: Keep sidebar width and content spacing in sync

You’re coupling the desktop sidebar width (w-80) with a separate content margin (mr-80). If one changes, the other can drift. Consider centralizing the width in a single source of truth (CSS var or a small computed that returns both classes) so updates stay consistent.


50-54: v-show + hidden/lg:block is redundant

v-show="... && isLargeScreen" already gates rendering visibility by viewport. The extra hidden lg:block is unnecessary and makes the state harder to reason about.

-  class="fixed right-0 top-0 w-80 max-w-80 h-full z-10 hidden lg:block"
+  class="fixed right-0 top-0 w-80 max-w-80 h-full z-10"

63-85: Duplicate close paths on mobile overlay

The overlay backdrop click closes the nav, and the global onClickOutside handler also tries to close it. Keeping both makes behavior drift-prone. Prefer one path (backdrop click is enough for mobile) and simplify the outside-click handler accordingly.


151-160: Simplify onClickOutside: limit it to the threads sidebar

Let the mobile overlay handle closing the message nav. This reduces branching and removes the need to special-case clicks inside messageNavigationRef.

-onClickOutside(sidebarRef, (event) => {
-  const isClickInMessageNavigation = messageNavigationRef.value?.contains(event.target as Node)
-
-  if (chatStore.isSidebarOpen && !isLargeScreen.value) {
-    chatStore.isSidebarOpen = false
-  }
-  if (chatStore.isMessageNavigationOpen && !isLargeScreen.value && !isClickInMessageNavigation) {
-    chatStore.isMessageNavigationOpen = false
-  }
-})
+onClickOutside(sidebarRef, () => {
+  if (chatStore.isSidebarOpen && !isLargeScreen.value) {
+    chatStore.isSidebarOpen = false
+  }
+})

76-76: Remove messageNavigationRef if not needed

If you adopt the previous simplification, messageNavigationRef becomes unused. Drop the template ref and the variable to avoid dead code.

-          <div ref="messageNavigationRef" class="w-80 max-w-80">
+          <div class="w-80 max-w-80">
-const messageNavigationRef = ref<HTMLElement>()

Also applies to: 148-149


115-115: Type the chatViewRef for safer calls

chatViewRef is untyped; calls like chatViewRef.value.messageList.scrollToMessage(...) won’t be checked. Type the ref to catch regressions at compile time.

-const chatViewRef = ref()
+const chatViewRef = ref<InstanceType<typeof ChatView> | null>(null)

Note: This assumes you add a type-only import:
import type ChatView from '@/components/ChatView.vue'.
If possible, expose a dedicated API from ChatView (e.g., defineExpose({ scrollToMessage })) so callers don’t reach into messageList.


216-227: Avoid hard-coded 1s delay; cancel on unmount

Closing the nav after a fixed 1s timeout is brittle and can fire after the component is torn down or the panel is re-opened. Either (a) expose a scrollToMessage(...): Promise<void> from ChatView and close when it resolves, or (b) at minimum, store and clear the timeout on unmount.

Minimal mitigation:

-    if (!isLargeScreen.value && chatStore.isMessageNavigationOpen) {
-      setTimeout(() => {
-        chatStore.isMessageNavigationOpen = false
-      }, 1000) // 延迟1秒关闭,让用户看到滚动效果
-    }
+    if (!isLargeScreen.value && chatStore.isMessageNavigationOpen) {
+      if (mobileNavCloseTimer) clearTimeout(mobileNavCloseTimer)
+      mobileNavCloseTimer = window.setTimeout(() => {
+        // Guard in case the panel was re-opened
+        if (chatStore.isMessageNavigationOpen) {
+          chatStore.isMessageNavigationOpen = false
+        }
+      }, 600) // shorter delay still shows motion while feeling snappier
+    }

And outside this block, add:

// at top-level in <script setup>
let mobileNavCloseTimer: number | undefined
onBeforeUnmount(() => mobileNavCloseTimer && clearTimeout(mobileNavCloseTimer))

If you want, I can sketch an exposed scrollToMessage Promise-based API for ChatView.


56-57: Pass a computed messages reference to avoid churn

If chatStore.getMessages() creates a new array each call, this will cause needless updates to MessageNavigationSidebar. Cache via a computed and bind that instead.

-        :messages="chatStore.getMessages()"
+        :messages="messages"

Add this (outside the selected lines):

const messages = computed(() => chatStore.getMessages())

Also applies to: 78-79


50-50: Use English for comments (repo guideline)

Several new comments are in Chinese. The repo requires English for logs/comments in .vue/.ts files. Please translate these to keep consistency.

-    <!-- 消息导航侧边栏 (大屏幕) -->
+    <!-- Message navigation sidebar (desktop) -->
-    <!-- 小屏幕模式下的消息导航侧边栏 -->
+    <!-- Message navigation sidebar (mobile) -->
-          <!-- 背景遮罩 -->
+          <!-- Backdrop -->
-          <!-- 侧边栏 -->
+          <!-- Sidebar -->
-/**
- * 处理滚动到指定消息
- */
+/**
+ * Scroll to target message
+ */

Also applies to: 63-63, 72-72, 75-75, 212-215

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ee226ce and 704c858.

📒 Files selected for processing (16)
  • src/renderer/src/components/ChatView.vue (1 hunks)
  • src/renderer/src/components/MessageNavigationSidebar.vue (1 hunks)
  • src/renderer/src/components/ThreadsView.vue (2 hunks)
  • src/renderer/src/components/TitleView.vue (1 hunks)
  • src/renderer/src/components/message/MessageList.vue (2 hunks)
  • src/renderer/src/i18n/en-US/chat.json (1 hunks)
  • src/renderer/src/i18n/fa-IR/chat.json (1 hunks)
  • src/renderer/src/i18n/fr-FR/chat.json (1 hunks)
  • src/renderer/src/i18n/ja-JP/chat.json (1 hunks)
  • src/renderer/src/i18n/ko-KR/chat.json (1 hunks)
  • src/renderer/src/i18n/ru-RU/chat.json (1 hunks)
  • src/renderer/src/i18n/zh-CN/chat.json (1 hunks)
  • src/renderer/src/i18n/zh-HK/chat.json (1 hunks)
  • src/renderer/src/i18n/zh-TW/chat.json (1 hunks)
  • src/renderer/src/stores/chat.ts (4 hunks)
  • src/renderer/src/views/ChatTabView.vue (6 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for logs and comments

Files:

  • src/renderer/src/components/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.vue
src/renderer/src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

src/renderer/src/**/*.vue: Use Composition API for all Vue 3 components
Use Tailwind CSS with scoped styles for styling
Organize components by feature in src/renderer/src/
Follow existing component patterns in src/renderer/src/ when creating new UI components
Use Composition API with proper TypeScript typing for new UI components
Implement responsive design with Tailwind CSS for new UI components
Add proper error handling and loading states for new UI components

Use scoped styles to prevent CSS conflicts between components

Files:

  • src/renderer/src/components/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/views/ChatTabView.vue
src/renderer/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

src/renderer/src/**/*.{ts,tsx,vue}: Use Pinia for frontend state management
Renderer to Main: Use usePresenter.ts composable for direct presenter method calls

Files:

  • src/renderer/src/components/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.vue
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/TitleView.vue
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.vue
src/renderer/**/*.{vue,ts,js,tsx,jsx}

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

渲染进程代码放在 src/renderer

Files:

  • src/renderer/src/components/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.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/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.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/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.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/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.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.

Files:

  • src/renderer/src/components/TitleView.vue
  • src/renderer/src/components/ThreadsView.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/components/MessageNavigationSidebar.vue
  • src/renderer/src/stores/chat.ts
  • src/renderer/src/views/ChatTabView.vue
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Strict type checking enabled for TypeScript

**/*.{ts,tsx}: 始终使用 try-catch 处理可能的错误
提供有意义的错误信息
记录详细的错误日志
优雅降级处理
日志应包含时间戳、日志级别、错误代码、错误描述、堆栈跟踪(如适用)、相关上下文信息
日志级别应包括 ERROR、WARN、INFO、DEBUG
不要吞掉错误
提供用户友好的错误信息
实现错误重试机制
避免记录敏感信息
使用结构化日志
设置适当的日志级别

Files:

  • src/renderer/src/stores/chat.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

**/*.{js,jsx,ts,tsx}: 使用 OxLint 进行代码检查
Log和注释使用英文书写

Files:

  • src/renderer/src/stores/chat.ts
src/{main,renderer}/**/*.ts

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

src/{main,renderer}/**/*.ts: Use context isolation for improved security
Implement proper inter-process communication (IPC) patterns
Optimize application startup time with lazy loading
Implement proper error handling and logging for debugging

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/stores/**/*.{vue,ts,tsx,js,jsx}

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

src/renderer/src/stores/**/*.{vue,ts,tsx,js,jsx}: Use modules to organize related state and actions
Implement proper state persistence for maintaining data across sessions
Use getters for computed state properties
Utilize actions for side effects and asynchronous operations
Keep the store focused on global state, not component-specific data

Files:

  • src/renderer/src/stores/chat.ts
🧠 Learnings (1)
📚 Learning: 2025-07-21T01:45:54.229Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/electron-best-practices.mdc:0-0
Timestamp: 2025-07-21T01:45:54.229Z
Learning: Applies to src/main/presenter/index.ts : The IPC messages from the main process to notify the view mainly rely on the EventBus index.ts to listen for events that need to be notified and then send them to the renderer through the mainWindow

Applied to files:

  • src/renderer/src/components/ChatView.vue
🔇 Additional comments (10)
src/renderer/src/i18n/ko-KR/chat.json (1)

57-68: All chat.navigation keys verified across locales — resolved
All chat.json files under src/renderer/src/i18n contain the full set of navigation keys (title, searchPlaceholder, noResults, noMessages, totalMessages, searchResults, userMessage, assistantMessage, unknownMessage). No discrepancies were found, so the navigation block is consistent across all locales.

src/renderer/src/i18n/zh-HK/chat.json (1)

57-68: Cantonese translations read well; placeholders consistent

Good addition. Same request as other locales: ensure key parity across all chat.json files to prevent fallback to en-US at runtime.

Use the verification script shared in ko-KR comment to validate this file too. It will flag any missing/mismatched keys.

src/renderer/src/i18n/fa-IR/chat.json (1)

57-68: Farsi navigation strings added correctly; RTL-friendly phrasing

Looks good. Confirm parity with other locales to avoid missing translations.

Use the same shell script from the ko-KR comment; it validates presence and exact key set for chat.navigation across locales.

src/renderer/src/i18n/zh-CN/chat.json (1)

58-67: LGTM: added navigation keys are complete and consistent

The new navigation block is structurally sound, placeholders look correct ({count}, {total}), and the tone fits the locale.

src/renderer/src/i18n/en-US/chat.json (1)

58-67: Navigation keys consistent across locales

Verified via the provided script that every chat.json under src/renderer/src/i18n/**/ contains the exact set of navigation keys:
assistantMessage, noMessages, noResults, searchPlaceholder, searchResults, title, totalMessages, unknownMessage, userMessage. No discrepancies were detected—ready to merge.

src/renderer/src/i18n/ja-JP/chat.json (1)

58-67: LGTM: natural Japanese phrasing and correct placeholders

The navigation block reads well; counts are expressed idiomatically with 件 and ordering fits Japanese UI norms.

src/renderer/src/i18n/fr-FR/chat.json (2)

58-67: LGTM: navigation i18n block is well-structured and consistent

Keys and placeholders look correct and align with the new UI.


58-67: All chat.json navigation keys are consistent across locales
The cross-locale verification script confirmed that every navigation.* key in src/renderer/src/i18n/en-US/chat.json is present (and no extras) in all other i18n/*/chat.json files. No further action required.

src/renderer/src/i18n/zh-TW/chat.json (1)

58-67: LGTM: 新增的 navigation 區塊鍵值與佈局合理

字詞與語意清楚,佔位符使用正確。

src/renderer/src/components/MessageNavigationSidebar.vue (1)

68-71: Fields model_provider and timestamp match the shared Message type

I’ve confirmed in src/shared/chat.d.ts that the Message type defines both:

  • model_provider: string
  • timestamp: number

These align exactly with how they’re used in MessageNavigationSidebar.vue, so no renaming or mapping is needed.

Likely an incorrect or invalid review comment.

Comment on lines +398 to +408
<style scoped>
.message-highlight {
background-color: rgba(59, 130, 246, 0.1);
border-left: 3px solid rgb(59, 130, 246);
transition: all 0.3s ease;
}
.dark .message-highlight {
background-color: rgba(59, 130, 246, 0.15);
}
</style>
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

Scoped style won’t reach child component DOM; use :deep to apply highlight

Because this component’s styles are scoped, .message-highlight won’t match elements rendered inside MessageItemUser/Assistant. Use :deep() (scoped piercing) and optionally scope under the container to avoid global bleed.

-<style scoped>
-.message-highlight {
-  background-color: rgba(59, 130, 246, 0.1);
-  border-left: 3px solid rgb(59, 130, 246);
-  transition: all 0.3s ease;
-}
-
-.dark .message-highlight {
-  background-color: rgba(59, 130, 246, 0.15);
-}
-</style>
+<style scoped>
+.message-list-container :deep(.message-highlight) {
+  background-color: rgba(59, 130, 246, 0.1);
+  border-left: 3px solid rgb(59, 130, 246);
+  transition: all 0.3s ease;
+}
+.dark .message-list-container :deep(.message-highlight) {
+  background-color: rgba(59, 130, 246, 0.15);
+}
+</style>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<style scoped>
.message-highlight {
background-color: rgba(59, 130, 246, 0.1);
border-left: 3px solid rgb(59, 130, 246);
transition: all 0.3s ease;
}
.dark .message-highlight {
background-color: rgba(59, 130, 246, 0.15);
}
</style>
<style scoped>
.message-list-container :deep(.message-highlight) {
background-color: rgba(59, 130, 246, 0.1);
border-left: 3px solid rgb(59, 130, 246);
transition: all 0.3s ease;
}
.dark .message-list-container :deep(.message-highlight) {
background-color: rgba(59, 130, 246, 0.15);
}
</style>
🤖 Prompt for AI Agents
In src/renderer/src/components/message/MessageList.vue around lines 398 to 408,
the scoped CSS selectors (.message-highlight and .dark .message-highlight) won't
match elements rendered inside child components (MessageItemUser/Assistant);
update the selectors to use Vue's scoped piercing syntax (e.g.
:deep(.message-highlight)) or scope it under the parent container (e.g.
.message-list :deep(.message-highlight)) and mirror the same change for the dark
variant so the highlight styles apply to child component DOM without leaking
globally.

Comment on lines +86 to +88
<div class="text-sm text-foreground/80 line-clamp-2">
<span v-html="highlightSearchQuery(getMessagePreview(message))"></span>
</div>
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

XSS risk: v-html on untrusted message text

Using v-html with user/assistant content enables HTML injection. Replace string-based highlighting with node-safe rendering (no v-html).

Apply this diff to render safely:

-          <div class="text-sm text-foreground/80 line-clamp-2">
-            <span v-html="highlightSearchQuery(getMessagePreview(message))"></span>
-          </div>
+          <div class="text-sm text-foreground/80 line-clamp-2">
+            <span>
+              <template
+                v-for="(seg, i) in getHighlightedSegments(getMessagePreview(message))"
+                :key="i"
+              >
+                <mark
+                  v-if="seg.m"
+                  class="bg-yellow-200 dark:bg-yellow-800 px-1 rounded"
+                  >{{ seg.t }}</mark
+                >
+                <span v-else>{{ seg.t }}</span>
+              </template>
+            </span>
+          </div>

And replace the highlighter function near Lines 217-229:

-// 高亮搜索关键词
-const highlightSearchQuery = (text: string): string => {
-  if (!searchQuery.value.trim()) {
-    return text
-  }
-
-  const query = searchQuery.value.trim()
-  const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')
-  return text.replace(
-    regex,
-    '<mark class="bg-yellow-200 dark:bg-yellow-800 px-1 rounded">$1</mark>'
-  )
-}
+// 安全高亮(不使用 v-html)
+const getHighlightedSegments = (
+  text: string
+): Array<{ t: string; m: boolean }> => {
+  const q = searchQuery.value.trim()
+  if (!q) return [{ t: text, m: false }]
+  const escaped = q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+  const regex = new RegExp(`(${escaped})`, 'gi')
+  const parts = text.split(regex).filter(Boolean)
+  const qLower = q.toLowerCase()
+  return parts.map((p) => ({ t: p, m: p.toLowerCase() === qLower }))
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/renderer/src/components/MessageNavigationSidebar.vue around lines 86-88,
remove the v-html usage to eliminate XSS risk and instead render highlighted
text as safe DOM nodes: change the template to iterate over an array of text
segments (using v-for) and render each segment with v-text or plain text binding
and conditional classes for highlights rather than using v-html; update the
highlighter function referenced near lines 217-229 to return a safe array of
{text: string, match: boolean} segments (no HTML), splitting the preview on the
search query in a case-insensitive manner and preserving the original text, and
ensure any input is treated as plain text (no innerHTML, no sanitization library
necessary if you never inject HTML). Also update any computed/props usage so the
template consumes the segments array (e.g.,
highlightSearchQuery(getMessagePreview(message)) returns segments) and adjust
types accordingly.

<!-- 消息导航侧边栏 (大屏幕) -->
<div
v-show="chatStore.isMessageNavigationOpen && isLargeScreen"
class="fixed right-0 top-0 w-80 max-w-80 h-full z-10 hidden lg:block"
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify Tailwind max-w-60/max-w-80 availability

max-w-60/max-w-80 aren’t in Tailwind’s default maxWidth scale. If you haven’t extended theme.maxWidth to include spacing values, these classes won’t work.

Run this to verify your Tailwind config and usages:

Also applies to: 76-76


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find Tailwind config
fd -a 'tailwind.config.*' | sed -n '1p' | xargs -I{} sh -c 'echo "==> {}"; cat "{}"'

echo
echo "==> Searching for max-w-60/max-w-80 usages and potential custom definitions"
rg -n --no-heading -C1 'max-w-(60|80)' || true
rg -n --no-heading -C2 'maxWidth|spacing' tailwind.config.* || true

Length of output: 5389


Add custom maxWidth entries for max-w-60/max-w-80 in Tailwind config

Your Tailwind setup does not include max-w-60 or max-w-80 in its theme.maxWidth scale, so those classes won’t generate any CSS. You have two options: add these keys to your config or switch to default max-width utilities.

• In tailwind.config.js, under theme.extend, add:

module.exports = {
  // …
  theme: {
    extend: {
      maxWidth: {
        '60': '15rem', // for max-w-60
        '80': '20rem', // for max-w-80
      },
      // …other extensions
    }
  },
  // …
}

• Or replace max-w-60/max-w-80 in your components with built-ins (e.g., max-w-7xl, max-w-xl, max-w-screen-md, etc.).

Affected usages in ChatTabView.vue (update these or your config accordingly):

  • Line 26: w-60 max-w-60 h-full fixed z-20 lg:relative
  • Line 53: class="fixed right-0 top-0 w-80 max-w-80 h-full z-10 hidden lg:block"
  • Line 76: <div ref="messageNavigationRef" class="w-80 max-w-80">

Please add the custom maxWidth entries or swap to default classes so your layouts behave as expected.

🤖 Prompt for AI Agents
In src/renderer/src/views/ChatTabView.vue around lines 26, 53 and 76, the
classes use non-existent Tailwind utilities `max-w-60` and `max-w-80` so no CSS
is generated; either add matching entries to your Tailwind config under
theme.extend.maxWidth (`'60': '15rem'` and `'80': '20rem'`) or replace those
classes in this file with built-in max-w utilities (e.g., `max-w-xl`,
`max-w-screen-md`, or another existing scale) so the width constraints take
effect.

@zerob13
Copy link
Collaborator

zerob13 commented Aug 25, 2025

LGTM

@zerob13 zerob13 merged commit 0073b7c into dev Aug 25, 2025
2 checks passed
@zerob13 zerob13 mentioned this pull request Aug 26, 2025
@zerob13 zerob13 deleted the feat/message-nav-sidebar branch January 6, 2026 12: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.

3 participants