Skip to content

Usechat fix 0924#1494

Merged
duckduckhero merged 27 commits intomainfrom
usechat-fix-0924
Sep 24, 2025
Merged

Usechat fix 0924#1494
duckduckhero merged 27 commits intomainfrom
usechat-fix-0924

Conversation

@duckduckhero
Copy link
Contributor

No description provided.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

📝 Walkthrough

Walkthrough

This PR replaces the right-panel chat stack with a new transport- and UIMessage-based architecture, adds v2 chat DB schema/ops/commands, introduces new hooks (useChat2/useChatQueries2), swaps message renderers, and wires local-LLM progress via events. Legacy hooks/components are removed. Utilities are updated for UIMessage, parsing, and AI preparation.

Changes

Cohort / File(s) Summary
Config
/.gitignore, /package.json
Add .cursor/ ignore. Add dependency @ai-sdk/react@^2.0.30.
Editor area LLM progress
/apps/desktop/src/components/editor-area/index.tsx
Subscribe to @hypr/plugin-local-llm events; update progress on llmEvent with payload.progress; cleanup on unmount.
Chat UI components (new renderer)
/apps/desktop/src/components/right-panel/components/chat/ui-message.tsx, /apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx, /apps/desktop/src/components/right-panel/components/chat/index.ts, /apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx, /apps/desktop/src/components/right-panel/components/chat/chat-input.tsx
Introduce UIMessageComponent rendering user/assistant messages, markdown blocks, and tool parts. ChatMessagesView now accepts UIMessage[] and new status flags; uses UIMessageComponent. Export UIMessageComponent; remove exports for old components. Adjust markdown-card spacing. Guard mentions to exclude selection nodes.
Chat view wiring
/apps/desktop/src/components/right-panel/views/chat-view.tsx
Rewire to new hooks (useChat2, useChatQueries2), conversation-based state, submit/stop handlers, license gating, DB-backed load/submit, markdown apply, derived status flags, history view, and updated props to child components.
Chat hooks (new)
/apps/desktop/src/components/right-panel/hooks/useChat2.ts, /apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts
Add useChat2 integrating CustomChatTransport with useChat, DB persistence, error handling, and parts updates. Add useChatQueries2 for conversations/messages/session data via React Query, with helpers to create/get conversation id.
Chat hooks (removed legacy)
/apps/desktop/src/components/right-panel/hooks/useChatLogic.ts, /apps/desktop/src/components/right-panel/hooks/useChatQueries.ts
Remove legacy chat logic and data-orchestration hooks.
Chat content components (removed legacy)
/apps/desktop/src/components/right-panel/components/chat/chat-message.tsx, /apps/desktop/src/components/right-panel/components/chat/message-content.tsx
Remove legacy ChatMessage and MessageContent implementations.
Transport and utils (new/updated)
/apps/desktop/src/components/right-panel/utils/chat-transport.ts, /apps/desktop/src/components/right-panel/utils/chat-utils.ts, /apps/desktop/src/components/right-panel/utils/markdown-parser.ts, /apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts
Add CustomChatTransport implementing streaming, tools (MCP/Hypr), model init, options, cleanup. Add cleanUIMessages and prepareMessagesForAI using UIMessage and model conversion. Update markdown parser to return ParsedPart. Relax session store requirement in edit_enhanced_note (warn, proceed).
Local LLM plugin: events and progress plumbing
/plugins/local-llm/src/events.rs, /plugins/local-llm/src/ext/plugin.rs, /plugins/local-llm/src/lib.rs, /plugins/local-llm/src/server.rs
Add LLMEvent (Progress). Create emitter, mount events, pass emitter through server/provider. Route progress via events; update function signatures to include progress callbacks and emitter fields.
DB user crate: chat v2 schema and ops
/crates/db-user/src/chat_conversations_migration.sql, /crates/db-user/src/chat_conversations_ops.rs, /crates/db-user/src/chat_conversations_types.rs, /crates/db-user/src/chat_messages_v2_migration.sql, /crates/db-user/src/chat_messages_v2_ops.rs, /crates/db-user/src/chat_messages_v2_types.rs, /crates/db-user/src/lib.rs, /crates/db-user/src/sessions_ops.rs
Add conversations and messages_v2 tables, types, and ops (create/list/get/update parts). Register migrations (25→27). Remove manual cascade deletes in delete_session (rely on FK cascade for new tables).
DB plugin: commands
/plugins/db/build.rs, /plugins/db/src/commands/mod.rs, /plugins/db/src/commands/chats_v2.rs, /plugins/db/src/lib.rs
Expose Tauri/Specta commands: create/list conversations, create/list/update messages_v2. Register module and commands.
Utils package exports
/packages/utils/src/ai.ts
Re-export useChat from @ai-sdk/react and AI types/utilities: convertToModelMessages, ChatRequestOptions, ChatTransport, LanguageModel, UIMessage, UIMessageChunk.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ChatView
  participant useChat2
  participant CustomChatTransport
  participant Model as LanguageModel
  participant DB as DB (plugin)

  User->>ChatView: Submit input
  ChatView->>useChat2: sendMessage({text, mentions, selection, htmlContent})
  useChat2->>DB: create_message_v2(user message)
  useChat2->>CustomChatTransport: sendMessages({messages, options})
  CustomChatTransport->>Model: streamText(messages, tools)
  Model-->>CustomChatTransport: UIMessageChunk(s)
  CustomChatTransport-->>useChat2: stream chunks
  useChat2-->>ChatView: update messages/status
  useChat2->>DB: create_message_v2(assistant message on finish)
  User->>ChatView: Stop (optional)
  ChatView->>useChat2: stop()
Loading
sequenceDiagram
  autonumber
  participant LocalLLM as Local LLM Server
  participant Evt as Event Emitter
  participant App as Desktop App
  participant Editor as Editor Area

  LocalLLM->>Evt: LLMEvent.Progress(p)
  Evt-->>App: emit("LLMEvent", {progress: p})
  App->>Editor: localLlmEvents.llmEvent(payload)
  Editor->>Editor: setProgress(payload.progress)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive No pull request description was provided, so there is insufficient information in the PR body to confirm intent or summarize the extensive chat, transport, and DB migration changes included in the diff. Please add a brief description summarizing the main goals, key areas changed (hooks, transport, UI message rendering, and DB migrations), and any migration or rollout notes so reviewers can quickly assess scope and impact.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Usechat fix 0924" is concise and references "useChat"-related work present in the changeset (hook/transport/UI updates), so it is related to the main changes; however, it is vague and date-based rather than describing the core functional changes or migration scope.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch usechat-fix-0924

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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (1)

48-50: Restore original HTML on undo (capture HTML slice and use DOMSerializer)

Undo currently inserts plain text and can drop formatting; capture HTML from selectionData.html (if present) or serialize the ProseMirror slice and insert that on undo.

-        const originalContent = selectionData?.text || editor.state.doc.textBetween(startOffset, endOffset);
+        const originalHtml =
+          (selectionData as any)?.html ??
+          (() => {
+            const slice = editor.state.doc.slice(startOffset, endOffset);
+            const serializer = DOMSerializer.fromSchema(editor.state.schema);
+            const container = document.createElement("div");
+            container.appendChild(serializer.serializeFragment(slice.content));
+            return container.innerHTML;
+          })();
-                currentEditor.chain()
+                currentEditor.chain()
                   .setTextSelection({ from: startOffset, to: highlightEnd })
                   .deleteSelection()
-                  .insertContent(originalContent)
+                  .insertContent(originalHtml)
                   .run();

Add:

import { DOMSerializer } from "prosemirror-model";
  • SelectionData (apps/desktop/src/contexts/right-panel.tsx — ~lines 10–13) has no html field; add html?: string or keep the (selectionData as any)?.html cast.
  • prosemirror-model appears in the repo (pnpm-lock.yaml / packages/tiptap); ensure apps/desktop can resolve it — add to apps/desktop package.json if not.

File locations: apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (around lines 48–50 and 139–145).

🧹 Nitpick comments (23)
apps/desktop/src/components/editor-area/index.tsx (1)

15-17: Consolidate imports from @hypr/plugin-local-llm

Merge into a single import for clarity and to avoid duplicate import statements.

-import { events as localLlmEvents } from "@hypr/plugin-local-llm";
-import { commands as localLlmCommands } from "@hypr/plugin-local-llm";
+import { commands as localLlmCommands, events as localLlmEvents } from "@hypr/plugin-local-llm";
crates/db-user/src/chat_conversations_migration.sql (1)

1-9: Add an index to speed list_conversations(session_id ORDER BY updated_at DESC).

This query is on the hot path; a composite index prevents full scans.

Apply this diff to append the index:

 CREATE TABLE IF NOT EXISTS chat_conversations (
   id TEXT PRIMARY KEY,
   session_id TEXT NOT NULL,
   user_id TEXT NOT NULL,
   name TEXT,
   created_at TEXT NOT NULL,
   updated_at TEXT NOT NULL,
   FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
 );
+CREATE INDEX IF NOT EXISTS idx_chat_conversations_session_updated
+  ON chat_conversations (session_id, updated_at DESC);
crates/db-user/src/chat_conversations_types.rs (1)

3-12: Optionally tag the SQL table for consistency (enables SqlTable).

If other models implement hypr_db_core::SqlTable, add the sql_table tag.

 user_common_derives! {
-    pub struct ChatConversation {
+    #[sql_table("chat_conversations")]
+    pub struct ChatConversation {
         pub id: String,
         pub session_id: String,
         pub user_id: String,
         pub name: Option<String>,
         pub created_at: chrono::DateTime<chrono::Utc>,
         pub updated_at: chrono::DateTime<chrono::Utc>,
     }
 }
crates/db-user/src/chat_messages_v2_migration.sql (1)

1-12: Add an index to speed list_messages_v2(conversation_id ORDER BY created_at ASC).

Improves retrieval performance for per-conversation message streams.

Apply this diff to append the index:

 CREATE TABLE IF NOT EXISTS chat_messages_v2 (
   id TEXT PRIMARY KEY,
   conversation_id TEXT NOT NULL,
   role TEXT CHECK(role IN ('system', 'user', 'assistant')) NOT NULL,
   parts TEXT NOT NULL,
   -- JSON string of message parts array
   metadata TEXT,
   -- JSON string for mentions, selections, etc.
   created_at TEXT NOT NULL,
   updated_at TEXT NOT NULL,
   FOREIGN KEY (conversation_id) REFERENCES chat_conversations(id) ON DELETE CASCADE
 );
+CREATE INDEX IF NOT EXISTS idx_chat_messages_v2_conv_created
+  ON chat_messages_v2 (conversation_id, created_at ASC);

Optional: if json1 is guaranteed, consider CHECK(json_valid(parts)) and CHECK(metadata IS NULL OR json_valid(metadata)).

crates/db-user/src/chat_messages_v2_types.rs (1)

18-28: Optionally tag the SQL table for ChatMessageV2 as well.

Keeps parity with other models if SqlTable is used elsewhere.

 user_common_derives! {
-    pub struct ChatMessageV2 {
+    #[sql_table("chat_messages_v2")]
+    pub struct ChatMessageV2 {
         pub id: String,
         pub conversation_id: String,
         pub role: ChatMessageV2Role,
         pub parts: String, // JSON string
         pub metadata: Option<String>, // JSON string
         pub created_at: chrono::DateTime<chrono::Utc>,
         pub updated_at: chrono::DateTime<chrono::Utc>,
     }
 }
crates/db-user/src/chat_conversations_ops.rs (2)

28-30: Avoid panic on empty result: replace unwrap() with expect() to document invariant

INSERT ... RETURNING should yield one row, but unwrap() will panic without context if it doesn't. Use expect() with a clear invariant note.

-        let row = rows.next().await?.unwrap();
+        let row = rows.next().await?.expect("INSERT chat_conversations RETURNING should yield one row");

41-44: Add index on chat_conversations(session_id, updated_at DESC); RFC3339 UTC timestamps are already used

  • Confirmed: chat_conversations timestamps are chrono::DateTimechrono::Utc and written with to_rfc3339(), so lexicographic ORDER BY updated_at DESC is correct (see crates/db-user/src/chat_conversations_types.rs and ops).
  • Action: add to migration (crates/db-user/src/chat_conversations_migration.sql):
    CREATE INDEX IF NOT EXISTS idx_chat_conversations_session_updated_at ON chat_conversations(session_id, updated_at DESC);
crates/db-user/src/chat_messages_v2_ops.rs (1)

29-31: Avoid panic on empty result: replace unwrap() with expect()

Mirror the expectation that INSERT ... RETURNING yields one row.

-        let row = rows.next().await?.unwrap();
+        let row = rows.next().await?.expect("INSERT chat_messages_v2 RETURNING should yield one row");
plugins/db/src/commands/chats_v2.rs (1)

10-17: Reduce duplication with a small helper to get DB from state

Five functions repeat the same state/db extraction. Consider a private helper like with_db(state, |db| async move { ... }) to centralize NoneDatabase mapping.

Also applies to: 32-37, 52-57, 72-77, 93-97

apps/desktop/src/components/right-panel/utils/chat-utils.ts (2)

95-114: Add error handling for missing sessionData.

When freshSessionData is still undefined after the fetch attempt (line 114), the code continues execution. Consider adding a fallback or warning to handle this case more explicitly.

   } catch (error) {
     console.error("Error fetching session data:", error);
   }
 }
+
+// Provide a default structure if sessionData is still missing
+if (!freshSessionData && sessionId) {
+  console.warn(`Session data could not be fetched for sessionId: ${sessionId}`);
+  freshSessionData = {
+    title: "",
+    rawContent: "",
+    enhancedContent: "",
+    preMeetingContent: "",
+    words: [],
+  };
+}

262-264: Extract HTML stripping to a utility function.

The HTML stripping logic is duplicated (lines 262 and 264). Consider extracting it to a reusable function.

+const stripHtmlTags = (html: string): string => {
+  return html.replace(/<[^>]*>/g, "");
+};
+
 // Then use it in both places:
-briefContent = session.enhanced_memo_html.replace(/<[^>]*>/g, "").slice(0, 200) + "...";
+briefContent = stripHtmlTags(session.enhanced_memo_html).slice(0, 200) + "...";
package.json (1)

6-8: Move @ai-sdk/react into the package that uses it (packages/utils) or justify keeping it at root

Only usage found: packages/utils/src/ai.ts:8; root package.json lists "@ai-sdk/react": "^2.0.30" (pnpm-lock resolves to 2.0.48). Add the dependency to packages/utils/package.json dependencies or document/justify the root-level placement.

apps/desktop/src/components/right-panel/utils/chat-transport.ts (3)

65-72: DRY the shouldUseTools gate

The identical gating logic appears twice. Centralize it to avoid drift when model IDs change.

Apply these diffs in place:

-    const shouldUseTools = modelId === "gpt-4.1"
-      || modelId === "openai/gpt-4.1"
-      || modelId === "anthropic/claude-sonnet-4"
-      || modelId === "openai/gpt-4o"
-      || modelId === "gpt-4o"
-      || apiBase?.includes("pro.hyprnote.com")
-      || modelId === "openai/gpt-5";
+    const shouldUseTools = this.computeShouldUseTools(modelId, apiBase);
-    const shouldUseTools = modelId === "gpt-4.1"
-      || modelId === "openai/gpt-4.1"
-      || modelId === "anthropic/claude-sonnet-4"
-      || modelId === "openai/gpt-4o"
-      || modelId === "gpt-4o"
-      || apiBase?.includes("pro.hyprnote.com")
-      || modelId === "openai/gpt-5";
+    const shouldUseTools = this.computeShouldUseTools(modelId, apiBase);

Add this helper outside the shown ranges:

private computeShouldUseTools(modelId: string, apiBase?: string) {
  return (
    modelId === "gpt-4.1" ||
    modelId === "openai/gpt-4.1" ||
    modelId === "anthropic/claude-sonnet-4" ||
    modelId === "openai/gpt-4o" ||
    modelId === "gpt-4o" ||
    modelId === "openai/gpt-5" ||
    !!apiBase?.includes("pro.hyprnote.com")
  );
}

Also applies to: 149-156


192-197: Also propagate mentions from message metadata

Today only selectionData is pulled from the last message. Read mentions too to avoid races with option updates.

       const messageMetadata = lastMessage?.metadata as any;
       if (messageMetadata?.selectionData) {
         this.options.selectionData = messageMetadata.selectionData;
       }
+      if (messageMetadata?.mentions) {
+        this.options.mentionedContent = messageMetadata.mentions;
+      }

198-206: Clean up MCP clients on abort

When the request is aborted, onFinish may not fire, leaking clients. Hook the AbortSignal.

-      const preparedMessages = await prepareMessagesForAI(options.messages, {
+      const preparedMessages = await prepareMessagesForAI(options.messages, {
         sessionId: this.options.sessionId,
         userId: this.options.userId,
         sessionData: this.options.sessionData,
         selectionData: this.options.selectionData,
         mentionedContent: this.options.mentionedContent,
       });
+
+      options.abortSignal?.addEventListener(
+        "abort",
+        () => this.cleanup(),
+        { once: true },
+      );
apps/desktop/src/components/right-panel/hooks/useChat2.ts (2)

147-149: Avoid timing hacks by reading mentions in transport

The 10ms delay exists to ensure mentions/selection reach the transport before tool loading. If transport reads mentions from the last message metadata (as suggested), this delay can be removed.

-      // Small delay to ensure options are updated before tools are loaded
-      await new Promise(resolve => setTimeout(resolve, 10));
+      // Avoid delay by letting the transport read metadata from the last message

117-174: Fix useCallback dependencies to avoid stale captures

sendMessage uses sessions and onError but they aren’t in deps. Include them to prevent stale values.

-    [sendAIMessage, conversationId],
+    [sendAIMessage, conversationId, sessions, onError],
apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1)

8-67: Consider handling fenced language headers

Currently, content inside ```lang blocks includes the language token. Strip the optional language spec so downstream renderers get clean code.

-        markdownStart = i + 3;
+        // Skip optional language token until first newline
+        let start = i + 3;
+        while (start < text.length && text[start] !== "\n") start++;
+        markdownStart = text[start] === "\n" ? start + 1 : start;
apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1)

55-58: Avoid mutating React Query cache data when sorting

sort() mutates the cached array. Copy before sort to prevent cache mutation.

-      const latestConversation = conversationsQuery.data.sort((a, b) =>
+      const latestConversation = [...conversationsQuery.data].sort((a, b) =>
         b.mostRecentTimestamp - a.mostRecentTimestamp
       )[0];
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (1)

18-52: Consider adding error boundaries for markdown conversion failures.

While the error handling with console.error and fallback to original content is good, consider wrapping the component with an error boundary to gracefully handle any rendering errors.

apps/desktop/src/components/right-panel/views/chat-view.tsx (1)

127-135: Consider adding user feedback for conversation creation failure.

When conversation creation fails, the user gets no feedback beyond the console error.

Consider showing a toast or alert to inform the user:

       convId = await getOrCreateConversationId();
       if (!convId) {
         console.error("Failed to create conversation");
+        // TODO: Show user-friendly error message
         return;
       }
apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (2)

34-39: Simplify session lookup; avoid O(n) Object.keys and remove the unused local. Also confirm the permissive behavior change is intentional.

This keeps the same runtime behavior while avoiding an extra allocation/scan.

-      if (sessions && Object.keys(sessions).length > 0) {
-        const sessionStore = sessions[sessionId];
-        if (!sessionStore) {
-          console.warn("Session store not found in sessions object, but continuing with editor ref");
-        }
-      }
+      if (sessions && !(sessionId in sessions)) {
+        console.warn("Session store not found; continuing with editor ref");
+      }

Please confirm the relaxed guard (proceeding without a session store) won’t break downstream expectations.


85-89: Target the correct highlight when multiple exist. Avoid global querySelector by anchoring at startOffset.

Prevents picking an unrelated highlight if more than one is present.

-          const highlighted = document.querySelector("[data-ai-highlight=\"true\"]");
+          const { node } = editor.view.domAtPos(startOffset);
+          const highlighted = (node instanceof Element ? node : node.parentElement)?.closest('[data-ai-highlight="true"]');
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f986a98 and 0abe5f6.

⛔ Files ignored due to path filters (11)
  • plugins/db/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/db/permissions/autogenerated/commands/create_conversation.toml is excluded by !plugins/**/permissions/**
  • plugins/db/permissions/autogenerated/commands/create_message_v2.toml is excluded by !plugins/**/permissions/**
  • plugins/db/permissions/autogenerated/commands/list_conversations.toml is excluded by !plugins/**/permissions/**
  • plugins/db/permissions/autogenerated/commands/list_messages_v2.toml is excluded by !plugins/**/permissions/**
  • plugins/db/permissions/autogenerated/commands/update_message_v2_parts.toml is excluded by !plugins/**/permissions/**
  • plugins/db/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/db/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/db/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
  • plugins/local-llm/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • .gitignore (1 hunks)
  • apps/desktop/src/components/editor-area/index.tsx (2 hunks)
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat/chat-message.tsx (0 hunks)
  • apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (4 hunks)
  • apps/desktop/src/components/right-panel/components/chat/index.ts (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat/message-content.tsx (0 hunks)
  • apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChat2.ts (1 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatLogic.ts (0 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatQueries.ts (0 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1 hunks)
  • apps/desktop/src/components/right-panel/utils/chat-transport.ts (1 hunks)
  • apps/desktop/src/components/right-panel/utils/chat-utils.ts (5 hunks)
  • apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1 hunks)
  • apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (1 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (6 hunks)
  • crates/db-user/src/chat_conversations_migration.sql (1 hunks)
  • crates/db-user/src/chat_conversations_ops.rs (1 hunks)
  • crates/db-user/src/chat_conversations_types.rs (1 hunks)
  • crates/db-user/src/chat_messages_v2_migration.sql (1 hunks)
  • crates/db-user/src/chat_messages_v2_ops.rs (1 hunks)
  • crates/db-user/src/chat_messages_v2_types.rs (1 hunks)
  • crates/db-user/src/lib.rs (5 hunks)
  • crates/db-user/src/sessions_ops.rs (0 hunks)
  • package.json (1 hunks)
  • packages/utils/src/ai.ts (1 hunks)
  • plugins/db/build.rs (1 hunks)
  • plugins/db/src/commands/chats_v2.rs (1 hunks)
  • plugins/db/src/commands/mod.rs (1 hunks)
  • plugins/db/src/lib.rs (1 hunks)
  • plugins/local-llm/src/events.rs (1 hunks)
  • plugins/local-llm/src/ext/plugin.rs (2 hunks)
  • plugins/local-llm/src/lib.rs (2 hunks)
  • plugins/local-llm/src/server.rs (10 hunks)
💤 Files with no reviewable changes (5)
  • crates/db-user/src/sessions_ops.rs
  • apps/desktop/src/components/right-panel/components/chat/chat-message.tsx
  • apps/desktop/src/components/right-panel/hooks/useChatQueries.ts
  • apps/desktop/src/components/right-panel/hooks/useChatLogic.ts
  • apps/desktop/src/components/right-panel/components/chat/message-content.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • apps/desktop/src/components/right-panel/utils/markdown-parser.ts
  • plugins/local-llm/src/events.rs
  • plugins/db/build.rs
  • packages/utils/src/ai.ts
  • crates/db-user/src/chat_messages_v2_ops.rs
  • apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts
  • apps/desktop/src/components/right-panel/components/chat/index.ts
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx
  • apps/desktop/src/components/right-panel/components/chat/ui-message.tsx
  • crates/db-user/src/chat_conversations_types.rs
  • apps/desktop/src/components/right-panel/utils/chat-transport.ts
  • plugins/local-llm/src/lib.rs
  • plugins/local-llm/src/ext/plugin.rs
  • crates/db-user/src/chat_conversations_ops.rs
  • apps/desktop/src/components/right-panel/utils/chat-utils.ts
  • plugins/db/src/commands/chats_v2.rs
  • crates/db-user/src/chat_messages_v2_types.rs
  • plugins/db/src/commands/mod.rs
  • apps/desktop/src/components/editor-area/index.tsx
  • plugins/local-llm/src/server.rs
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
  • apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts
  • apps/desktop/src/components/right-panel/hooks/useChat2.ts
  • crates/db-user/src/lib.rs
  • apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx
  • plugins/db/src/lib.rs
  • apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx
🧬 Code graph analysis (14)
plugins/local-llm/src/events.rs (1)
plugins/local-stt/src/lib.rs (1)
  • tauri_specta (34-58)
crates/db-user/src/chat_messages_v2_ops.rs (2)
plugins/db/src/commands/chats_v2.rs (3)
  • create_message_v2 (46-61)
  • list_messages_v2 (66-81)
  • update_message_v2_parts (86-102)
crates/db-core/src/lib.rs (1)
  • conn (17-22)
apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1)
packages/utils/src/ai.ts (1)
  • UIMessage (25-25)
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (2)
apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1)
  • parseMarkdownBlocks (8-67)
apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)
  • MarkdownCard (15-140)
apps/desktop/src/components/right-panel/utils/chat-transport.ts (6)
packages/utils/src/ai.ts (4)
  • UIMessage (25-25)
  • modelProvider (79-86)
  • tool (24-24)
  • dynamicTool (14-14)
apps/desktop/src/components/right-panel/utils/mcp-http-wrapper.ts (1)
  • buildVercelToolsFromMcp (5-31)
apps/desktop/src/components/right-panel/utils/tools/search_session_multi_keywords.ts (1)
  • createSearchSessionTool (5-59)
apps/desktop/src/components/right-panel/utils/tools/search_session_date_range.ts (1)
  • createSearchSessionDateRangeTool (5-82)
apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (1)
  • createEditEnhancedNoteTool (12-310)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)
  • prepareMessagesForAI (82-319)
plugins/local-llm/src/lib.rs (2)
plugins/template/src/lib.rs (1)
  • tauri_specta (12-15)
plugins/local-stt/src/lib.rs (1)
  • tauri_specta (34-58)
plugins/local-llm/src/ext/plugin.rs (1)
plugins/local-llm/src/server.rs (2)
  • new (47-56)
  • new (151-156)
crates/db-user/src/chat_conversations_ops.rs (2)
plugins/db/src/commands/chats_v2.rs (2)
  • create_conversation (6-21)
  • list_conversations (26-41)
crates/db-core/src/lib.rs (1)
  • conn (17-22)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (2)
packages/utils/src/ai.ts (2)
  • UIMessage (25-25)
  • convertToModelMessages (13-13)
apps/desktop/src/contexts/right-panel.tsx (1)
  • SelectionData (10-16)
plugins/db/src/commands/chats_v2.rs (2)
crates/db-user/src/chat_conversations_ops.rs (2)
  • create_conversation (4-31)
  • list_conversations (33-54)
crates/db-user/src/chat_messages_v2_ops.rs (3)
  • create_message_v2 (4-32)
  • list_messages_v2 (34-55)
  • update_message_v2_parts (57-73)
apps/desktop/src/components/right-panel/views/chat-view.tsx (6)
apps/desktop/src/hooks/use-license.ts (1)
  • useLicense (9-105)
apps/desktop/src/components/right-panel/hooks/useActiveEntity.ts (1)
  • useActiveEntity (14-80)
packages/utils/src/contexts/sessions.tsx (1)
  • useSessions (33-47)
apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1)
  • useChatQueries2 (15-175)
apps/desktop/src/components/right-panel/hooks/useChat2.ts (1)
  • useChat2 (17-203)
apps/desktop/src/components/pro-gate-modal/service.ts (1)
  • showProGateModal (6-27)
apps/desktop/src/components/right-panel/hooks/useChat2.ts (2)
apps/desktop/src/hooks/use-license.ts (1)
  • useLicense (9-105)
apps/desktop/src/components/right-panel/utils/chat-transport.ts (1)
  • CustomChatTransport (38-274)
apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (2)
packages/utils/src/ai.ts (1)
  • UIMessage (25-25)
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (1)
  • UIMessageComponent (154-473)
plugins/db/src/lib.rs (3)
crates/db-user/src/chat_conversations_ops.rs (2)
  • create_conversation (4-31)
  • list_conversations (33-54)
plugins/db/src/commands/chats_v2.rs (5)
  • create_conversation (6-21)
  • list_conversations (26-41)
  • create_message_v2 (46-61)
  • list_messages_v2 (66-81)
  • update_message_v2_parts (86-102)
crates/db-user/src/chat_messages_v2_ops.rs (3)
  • create_message_v2 (4-32)
  • list_messages_v2 (34-55)
  • update_message_v2_parts (57-73)
⏰ 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). (2)
  • GitHub Check: ci (macos, macos-14)
  • GitHub Check: ci (windows, windows-latest)
🔇 Additional comments (51)
.gitignore (2)

20-20: Good addition: ignore Cursor workspace

Adding “.cursor/” helps keep local editor artifacts out of VCS.


19-19: Fix trailing space in ignore pattern for CLAUDE.md

The trailing space means this pattern won’t match “CLAUDE.md” and will fail to ignore it. Remove the space.

-CLAUDE.md 
+CLAUDE.md

Optional: if you only want to ignore the root file, anchor it:

-CLAUDE.md
+/CLAUDE.md

Likely an incorrect or invalid review comment.

plugins/db/src/commands/mod.rs (1)

3-3: New chats_v2 module wiring looks good.

Module exposure aligns with added commands and types.

plugins/db/src/lib.rs (1)

73-77: Specta surface updated for chats_v2 — looks correct.

Matches build.rs COMMANDS and new ops.

crates/db-user/src/lib.rs (2)

3-5: New chats_v2 modules and re-exports are well‑integrated.

Public surface aligns with plugin commands.

Also applies to: 9-11, 33-36, 45-48


145-173: Migrations appended correctly.

Count and order look good; new files are last, respecting “append only”.

crates/db-user/src/chat_messages_v2_types.rs (1)

3-16: Enum shape and (de)serialization mapping look solid.

strum + serde names align with DB CHECK(role ...).

plugins/db/build.rs (1)

50-54: Command surface extended correctly — consider adding get_conversation. get_conversation is implemented at crates/db-user/src/chat_conversations_ops.rs (pub async fn get_conversation at ~line 56) but isn’t included in plugins/db/build.rs’s command list; if the UI needs it, add the command here and update specta.

crates/db-user/src/chat_conversations_ops.rs (2)

21-23: Empty string vs NULL semantics for name

You coerce None to "". Confirm the schema defines name as NOT NULL or DEFAULT '' to avoid losing "unknown" vs "empty" semantics.


56-75: LGTM

Parameterized query, row decoding, and Option return are clean.

crates/db-user/src/chat_messages_v2_ops.rs (2)

64-71: Confirm silent no-op on missing id is acceptable

UPDATE result is ignored; callers won’t know if 0 rows were affected. If that’s intentional, ignore; otherwise consider surfacing a signal.


34-55: LGTM

Read path and decoder are consistent; ascending time order matches UI expectations.

plugins/db/src/commands/chats_v2.rs (1)

1-102: LGTM

Command surfaces and DB delegation look consistent with the new ops.

apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (1)

156-156: LGTM! Good defensive check for selection node filtering.

The addition of the guard condition node.attrs.type !== "selection" properly filters out selection-type nodes when extracting mentions, preventing unwanted selection nodes from being processed as regular mentions.

plugins/local-llm/src/events.rs (1)

34-38: LGTM! Well-structured event type for LLM progress tracking.

The LLMEvent enum is properly structured with appropriate derives and serde rename. The Progress(f64) variant appropriately uses floating-point for progress percentage (0.0-1.0 range).

packages/utils/src/ai.ts (2)

8-8: LGTM! Proper re-export of useChat from @ai-sdk/react.

The export is correctly placed and aligns with the PR's objective of introducing new chat architecture.


10-27: Exports validated — types available in 'ai' v5

packages/utils/package.json specifies "ai": "^5.0.34"; ChatRequestOptions, ChatTransport, UIMessage and UIMessageChunk are present in ai v5 SDK docs — no change required.

apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)

37-76: LGTM! Well-implemented message cleaning function.

The cleanUIMessages function properly filters out problematic tool states that cause conversion errors. The implementation is defensive and handles edge cases appropriately.

apps/desktop/src/components/right-panel/utils/chat-transport.ts (5)

47-52: Model initialization looks correct

Provider selection and language model retrieval are consistent with utils.


106-134: Verify inputSchema compatibility for SSE MCP tools

Here you pass tool.inputSchema directly to dynamicTool. In mcp-http-wrapper you convert JSON Schemas via jsonSchema(...). Ensure experimental_createMCPClient.tools() returns Zod schemas; otherwise tool invocation may fail.


208-229: Confirm step limiter is intended

stopWhen(stepCountIs(10)) will cut off long tool loops/streams. Verify this aligns with UX expectations for all providers.


231-244: Error-to-text mapping LGTM

Reasonable normalization for UIMessage stream errors.


264-273: Cleanup logic LGTM

Clients are closed and references cleared.

apps/desktop/src/components/right-panel/hooks/useChat2.ts (3)

34-43: Singleton transport initialization looks good

Stable instance created once with initial options.


45-56: Option refresh on deps change LGTM

Keeps transport in sync with session/user/selection/license.


95-115: Assistant message persistence on finish LGTM

Saves streamed assistant message into V2 schema using the active conversationIdRef.

plugins/local-llm/src/lib.rs (2)

43-43: Specta event registration LGTM

LLMEvent is added to the event catalog for typed IPC.


71-73: Mount events during setup

Mounting events in setup is correct. Ensure LLMEvent derives/implements the specta Event trait.

apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)

113-113: Spacing tweak LGTM

Removing outer margins makes this card composable in dense lists.

plugins/local-llm/src/ext/plugin.rs (3)

5-5: Bring Event trait into scope

Required for event.emit(&handle).


196-203: Emitter wiring LGTM

Emits LLMEvent via tauri_specta Event integration.


201-202: ServerState initialization with emitter LGTM

Matches new constructor signature and propagates events to the server.

apps/desktop/src/components/right-panel/components/chat/index.ts (1)

8-8: Export switch to UIMessageComponent LGTM

Aligns public API with the new UIMessage renderer.

apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (2)

74-106: Message mapping and parse fallbacks LGTM

Consistent with UIMessage shape and tolerant to bad stored JSON.


108-112: DB-to-UI sync gate LGTM

Avoids overwriting in-flight streams.

apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (5)

1-3: LGTM! Clean import updates for the new UIMessage architecture.

The imports correctly reference the new UIMessage type and UIMessageComponent, properly replacing the legacy Message and ChatMessage imports.


6-14: Well-structured prop interface with granular state flags.

The new interface provides better control over streaming states with separate flags (isSubmitted, isStreaming, isReady, isError) instead of the previous generic isGenerating flag. This allows for more precise UI state management.


35-39: Good UI enhancement with the three-dot thinking indicator.

The expansion from two to three dots provides better visual feedback, and the animation timing is well-balanced.


53-89: Excellent transition state handling for multi-part messages.

The logic correctly handles the complex transitions between text and tool parts during streaming. The fallback condition provides good coverage for edge cases.


120-128: Clean component integration with proper prop forwarding.

The UIMessageComponent is correctly integrated with all necessary props passed through.

apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (3)

55-151: Good CSS implementation for text selection and styling.

The comprehensive CSS rules properly handle text selection, typography, and user interaction. The explicit user-select rules ensure text is selectable across browsers.


238-243: Good flexible tool type detection.

The code correctly handles both dynamic tools and specific tool types with proper fallback for tool name extraction.


312-313: Potential XSS vulnerability with JSON.stringify in DOM.

Directly inserting JSON.stringify output into the DOM could pose security risks if the input contains malicious content.

Consider sanitizing the JSON output or using a dedicated JSON viewer component:

-                            {JSON.stringify(toolPart.input, null, 2)}
+                            {JSON.stringify(toolPart.input, null, 2)
+                              .replace(/</g, '&lt;')
+                              .replace(/>/g, '&gt;')}

Likely an incorrect or invalid review comment.

apps/desktop/src/components/right-panel/views/chat-view.tsx (3)

112-120: Good implementation of pro-gate license checking.

The license check properly limits free users to 4 messages and logs analytics events appropriately.


172-197: Good error handling in markdown application.

The function properly checks for session existence and handles errors gracefully with try-catch.


199-202: Clean derivation of UI state flags.

The status flags are cleanly derived from the status value, making the code more maintainable.

plugins/local-llm/src/server.rs (5)

40-56: Good architectural change with event emitter pattern.

The addition of the emitter to ServerState provides a clean way to propagate LLM events without tight coupling.


166-178: Clean event propagation through progress callback.

The progress events are properly routed through the emitter via the LLMEvent::Progress wrapper.


323-323: Good addition of progress callback parameter.

The progress_fn parameter provides a clean abstraction for progress reporting without coupling to specific event systems.


484-487: Clean handling of progress events in streaming mode.

The progress events are properly handled without generating unnecessary stream deltas.


218-218: Verify removal of tool filtering logic.

plugins/local-llm/src/server.rs:218 now uses request.tools.clone(); confirm the tools list is validated and compatible with hypr_llama::LlamaRequest and model.generate_stream_with_callback — reintroduce filtering/validation if unsupported tool entries can cause errors.

Comment on lines +392 to +405
useEffect(() => {
let unlisten: () => void;
localLlmEvents.llmEvent.listen(({ payload }) => {
if (payload.progress) {
setProgress(payload.progress);
}
}).then((fn) => {
unlisten = fn;
});

return () => {
unlisten();
};
}, []);
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

Prevent cleanup crash and handle 0% progress correctly; optionally scope listener to local LLM

  • unlisten is assigned asynchronously; calling it before assignment can throw.
  • if (payload.progress) skips valid 0 updates.
  • Optionally mount the listener only when using the local LLM to avoid unnecessary rerenders.
 useEffect(() => {
-  let unlisten: () => void;
-  localLlmEvents.llmEvent.listen(({ payload }) => {
-    if (payload.progress) {
-      setProgress(payload.progress);
-    }
-  }).then((fn) => {
-    unlisten = fn;
-  });
-
-  return () => {
-    unlisten();
-  };
-}, []);
+  if (!actualIsLocalLlm) return;
+  let unlisten: () => void = () => {};
+  localLlmEvents.llmEvent.listen(({ payload }) => {
+    if (typeof payload?.progress === "number") {
+      setProgress(payload.progress);
+    }
+  }).then((fn) => {
+    unlisten = fn;
+  });
+
+  return () => {
+    unlisten();
+  };
+}, [actualIsLocalLlm]);

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

🤖 Prompt for AI Agents
In apps/desktop/src/components/editor-area/index.tsx around lines 392 to 405,
the effect assigns unlisten asynchronously (risking calling undefined on
cleanup), treats 0 as falsy (skips valid 0 progress), and may register the
listener unnecessarily; fix by only mounting the listener when using the local
LLM (guard with the local-LLM condition if available), update the payload check
to test for progress !== undefined (so 0 is accepted), and make cleanup robust
by checking unlisten is a function before calling it (e.g., if (typeof unlisten
=== 'function') unlisten()) — also consider storing the returned unsubscribe
synchronously or cancelling registration on unmount if your event API supports
it.

Comment on lines +179 to +180
const htmlContent = (message.metadata as any)?.htmlContent;
const textContent = getTextContent();
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

Add null check for message.metadata access.

The type casting (message.metadata as any)?.htmlContent is risky. Consider adding proper type guards or null checks.

-    const htmlContent = (message.metadata as any)?.htmlContent;
+    const htmlContent = message.metadata && typeof message.metadata === 'object' 
+      ? (message.metadata as Record<string, any>).htmlContent 
+      : undefined;
📝 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
const htmlContent = (message.metadata as any)?.htmlContent;
const textContent = getTextContent();
const htmlContent = message.metadata && typeof message.metadata === 'object'
? (message.metadata as Record<string, any>).htmlContent
: undefined;
const textContent = getTextContent();
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/components/chat/ui-message.tsx around
lines 179-180, the current access (message.metadata as any)?.htmlContent assumes
message and metadata are non-null; add a null/type guard before reading
htmlContent — for example verify message and message.metadata exist (or use
optional chaining on message: message?.metadata?.htmlContent), narrow the
metadata type instead of casting to any, and provide a safe fallback (e.g.,
undefined or empty string) so htmlContent is only used when present.

Comment on lines +386 to +387
{JSON.stringify(toolPart.input, null, 2)}
</pre>
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

Apply the same XSS protection to other JSON.stringify instances.

The same security concern applies to these JSON.stringify calls.

Apply the same sanitization as suggested above to all JSON.stringify instances in the component.

Also applies to: 422-423

🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/components/chat/ui-message.tsx around
lines 386-387 (also apply to 422-423), the direct JSON.stringify outputs need
the same XSS sanitization used elsewhere in this component; find each
JSON.stringify instance and wrap its result with the existing sanitize/escape
helper (the same function used earlier in the file) before rendering, and if
those strings are injected via dangerouslySetInnerHTML ensure you pass the
sanitized value into __html; alternatively render the sanitized string as text
inside <pre> to avoid raw HTML injection.

Comment on lines +33 to +50
conversations.map(async (conv) => {
const messages = await dbCommands.listMessagesV2(conv.id);
const firstUserMessage = messages.find(msg => msg.role === "user");

const mostRecentTimestamp = messages.length > 0
? Math.max(...messages.map(msg => new Date(msg.created_at).getTime()))
: new Date(conv.created_at).getTime();

return {
...conv,
firstMessage: firstUserMessage ? (JSON.parse(firstUserMessage.parts)[0]?.text || "") : "",
mostRecentTimestamp,
};
}),
);

return conversationsWithPreview;
},
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

🧩 Analysis chain

Potential crash on unguarded JSON.parse for firstMessage

JSON.parse(firstUserMessage.parts) can throw and fail the entire conversations query. Messages parsing later is guarded; consider consistent parsing here.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the file and show surrounding context
rg -n "useChatQueries2\.ts|listMessagesV2" || true
echo "---- show file content ----"
sed -n '1,240p' apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts || true

echo "---- grep for JSON.parse and .parts usage ----"
rg -n "JSON.parse|\.parts\b" || true

echo "---- search for listMessagesV2 usages ----"
rg -n "listMessagesV2" || true

echo "---- show messages handling elsewhere (try/catch around JSON.parse) ----"
rg -n --hidden "try\s*\{[^}]*JSON.parse" -S || true

Length of output: 31476


Guard JSON.parse for conversation previews (avoid crashing on malformed parts)

  • JSON.parse(firstUserMessage.parts) is unguarded and can throw — fix in apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (line ~43) by wrapping parse in try/catch or using a safe-parse fallback like the messagesQuery implementation.
  • Also guard apps/desktop/src/components/right-panel/views/chat-view.tsx (line ~87) where JSON.parse(msg.parts) is used unguarded.
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts around lines
33-50 and apps/desktop/src/components/right-panel/views/chat-view.tsx around
line ~87, the code calls JSON.parse on message.parts without validation which
can throw on malformed JSON; update both locations to safely parse parts by
implementing a small safeParseParts helper or inline try/catch that returns an
empty array/string fallback on parse error (mirroring messagesQuery behavior),
then use the parsed result (e.g., first element text) only when parse succeeds;
ensure no exceptions propagate and preserve existing fallback values when parts
are invalid or absent.

Comment on lines +231 to 281
if (mention.type === "human") {
const humanData = await dbCommands.getHuman(mention.id);
if (humanData) {
let humanContent = "";
humanContent += "Name: " + humanData?.full_name + "\n";
humanContent += "Email: " + humanData?.email + "\n";
humanContent += "Job Title: " + humanData?.job_title + "\n";
humanContent += "LinkedIn: " + humanData?.linkedin_username + "\n";

// Add recent sessions for this person
if (humanData?.full_name) {
try {
const participantSessions = await dbCommands.listSessions({
type: "search",
query: humanData.full_name,
user_id: userId || "",
limit: 5,
});

if (participantSessions.length > 0) {
humanContent += "\nNotes this person participated in:\n";
for (const session of participantSessions.slice(0, 2)) {
const participants = await dbCommands.sessionListParticipants(session.id);
const isParticipant = participants.some((p: any) =>
p.full_name === humanData.full_name || p.email === humanData.email
);

if (isParticipant) {
let briefContent = "";
if (session.enhanced_memo_html && session.enhanced_memo_html.trim() !== "") {
// Strip HTML tags for brief content
briefContent = session.enhanced_memo_html.replace(/<[^>]*>/g, "").slice(0, 200) + "...";
} else if (session.raw_memo_html && session.raw_memo_html.trim() !== "") {
briefContent = session.raw_memo_html.replace(/<[^>]*>/g, "").slice(0, 200) + "...";
}
humanContent += `- "${session.title || "Untitled"}": ${briefContent}\n`;
}
}
}

humanContent += `- "${session.title || "Untitled"}": ${briefContent}\n`;
} catch (error) {
console.error(`Error fetching notes for person "${humanData.full_name}":`, error);
}
}

processedMentions.push({
type: "human",
label: mention.label,
content: humanContent,
});
}
} catch (error) {
console.error(`Error fetching notes for person "${humanData.full_name}":`, error);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize database queries for human mentions.

The code makes sequential database calls inside loops (lines 243-268). When multiple humans are mentioned, this could lead to N+1 query problems and slow performance.

Consider batching the participant session queries or implementing caching to reduce database round trips when processing multiple human mentions.

Comment on lines +77 to +101
useEffect(() => {
const loadMessages = async () => {
if (currentConversationId) {
try {
const { commands } = await import("@hypr/plugin-db");
const dbMessages = await commands.listMessagesV2(currentConversationId);

const uiMessages = dbMessages.map(msg => ({
id: msg.id,
role: msg.role as "user" | "assistant" | "system",
parts: JSON.parse(msg.parts),
metadata: msg.metadata ? JSON.parse(msg.metadata) : {},
}));

setMessages(uiMessages);
} catch (error) {
console.error("Failed to load messages:", error);
}
} else {
setMessages([]);
}
};

loadMessages();
}, [currentConversationId, setMessages]);
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

Add cleanup for message loading to prevent memory leaks.

The effect should handle component unmounting to prevent state updates after unmount.

   useEffect(() => {
+    let isMounted = true;
     const loadMessages = async () => {
       if (currentConversationId) {
         try {
           const { commands } = await import("@hypr/plugin-db");
           const dbMessages = await commands.listMessagesV2(currentConversationId);

           const uiMessages = dbMessages.map(msg => ({
             id: msg.id,
             role: msg.role as "user" | "assistant" | "system",
             parts: JSON.parse(msg.parts),
             metadata: msg.metadata ? JSON.parse(msg.metadata) : {},
           }));

-          setMessages(uiMessages);
+          if (isMounted) {
+            setMessages(uiMessages);
+          }
         } catch (error) {
           console.error("Failed to load messages:", error);
         }
       } else {
-        setMessages([]);
+        if (isMounted) {
+          setMessages([]);
+        }
       }
     };

     loadMessages();
+    return () => {
+      isMounted = false;
+    };
   }, [currentConversationId, setMessages]);
📝 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
useEffect(() => {
const loadMessages = async () => {
if (currentConversationId) {
try {
const { commands } = await import("@hypr/plugin-db");
const dbMessages = await commands.listMessagesV2(currentConversationId);
const uiMessages = dbMessages.map(msg => ({
id: msg.id,
role: msg.role as "user" | "assistant" | "system",
parts: JSON.parse(msg.parts),
metadata: msg.metadata ? JSON.parse(msg.metadata) : {},
}));
setMessages(uiMessages);
} catch (error) {
console.error("Failed to load messages:", error);
}
} else {
setMessages([]);
}
};
loadMessages();
}, [currentConversationId, setMessages]);
useEffect(() => {
let isMounted = true;
const loadMessages = async () => {
if (currentConversationId) {
try {
const { commands } = await import("@hypr/plugin-db");
const dbMessages = await commands.listMessagesV2(currentConversationId);
const uiMessages = dbMessages.map(msg => ({
id: msg.id,
role: msg.role as "user" | "assistant" | "system",
parts: JSON.parse(msg.parts),
metadata: msg.metadata ? JSON.parse(msg.metadata) : {},
}));
if (isMounted) {
setMessages(uiMessages);
}
} catch (error) {
console.error("Failed to load messages:", error);
}
} else {
if (isMounted) {
setMessages([]);
}
}
};
loadMessages();
return () => {
isMounted = false;
};
}, [currentConversationId, setMessages]);
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/views/chat-view.tsx around lines 77
to 101, the useEffect that loads messages can update state after the component
unmounts; add a cancellation mechanism and cleanup to avoid memory leaks: create
a let cancelled = false (or use an AbortController) at the top of the effect,
check cancelled before calling setMessages (and before any other state updates)
inside loadMessages, and return a cleanup function that sets cancelled = true
(or aborts the controller) so in-flight async work won’t call setMessages after
unmount.

Comment on lines +42 to +45
"SELECT * FROM chat_messages_v2
WHERE conversation_id = ?
ORDER BY created_at ASC",
vec![conversation_id.into()],
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Add index for list ordering/filtering

For WHERE conversation_id = ? ORDER BY created_at ASC, add an index on (conversation_id, created_at).


🏁 Script executed:

#!/bin/bash
# Verify index presence aiding list_messages_v2
rg -n -C2 -e 'CREATE\s+INDEX.*chat_messages_v2' -e 'CREATE\s+INDEX.*(conversation_id|created_at)' --passthru

Length of output: 278443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Searching for occurrences of `chat_messages_v2` =="
rg -n --hidden -S '\bchat_messages_v2\b' || echo "no matches for chat_messages_v2"

echo
echo "== Searching for any CREATE INDEX statements =="
rg -n --hidden -S -P 'CREATE\s+(UNIQUE\s+)?INDEX\b' || echo "no CREATE INDEX statements found"

echo
echo "== Searching for conversation_id and created_at usages =="
rg -n --hidden -S '\bconversation_id\b' -C2 || echo "no matches for conversation_id"
echo
rg -n --hidden -S '\bcreated_at\b' -C2 || echo "no matches for created_at"

echo
echo "== Searching for migration/schema files (common names) =="
rg -n --hidden -S '(^|\b)(migrations|schema\.sql|migration)\b' || echo "no obvious migration/schema filenames found"

Length of output: 51791


Add composite index (conversation_id, created_at) to chat_messages_v2

No CREATE INDEX found in migrations — add to crates/db-user/src/chat_messages_v2_migration.sql (or a new migration):
CREATE INDEX IF NOT EXISTS idx_chat_messages_v2_conversation_created_at ON chat_messages_v2(conversation_id, created_at);

🤖 Prompt for AI Agents
In crates/db-user/src/chat_messages_v2_ops.rs around lines 42 to 45, queries
filter and order by conversation_id and created_at but there is no composite
index; add a migration file (either update
crates/db-user/src/chat_messages_v2_migration.sql or create a new migration in
the migrations directory) that creates a composite index on (conversation_id,
created_at) for chat_messages_v2 (use IF NOT EXISTS to be idempotent), then
run/apply the migrations and update any migration manifest so the index is
applied in all environments.


#[tauri::command]
#[specta::specta]
#[tracing::instrument(skip(state))]
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

Prevent logging PII/content: skip sensitive args in tracing instrumentation

By default, instrument records arguments. Skip user/session/message data to avoid leaking chat content.

-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, conversation))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, session_id))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, message))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, conversation_id))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, id, parts))]

Also applies to: 25-25, 45-45, 65-65, 85-85

🤖 Prompt for AI Agents
In plugins/db/src/commands/chats_v2.rs at lines 5, 25, 45, 65 and 85, the
#[tracing::instrument] annotations currently record function arguments (which
may include PII/chat content); update each macro to skip sensitive parameters by
adding a skip list that includes state plus any user/session/message-related
args (for example: #[tracing::instrument(skip(state, user, user_id, session,
session_id, message, messages, content))]). Ensure you list the actual parameter
names used in each function so tracing will not log chat content or identifiers.

@duckduckhero duckduckhero merged commit 62f781d into main Sep 24, 2025
26 checks passed
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