Skip to content

Conversation

duckduckhero
Copy link
Collaborator

@duckduckhero duckduckhero commented Sep 5, 2025

@yujonglee (just for preview)

Copy link

coderabbitai bot commented Sep 5, 2025

📝 Walkthrough

Walkthrough

Replaces legacy chat logic with a new conversation-based architecture. Adds CustomChatTransport, new hooks (useChat2, useChatQueries2), UI message renderer, and DB v2 (conversations/messages) with Tauri commands. Removes ChatMessage/MessageContent and old hooks. Updates ChatView integration, message parsing, and exports/dependencies.

Changes

Cohort / File(s) Summary
Chat UI Refactor
apps/desktop/src/components/right-panel/components/chat/chat-message.tsx, .../message-content.tsx, .../ui-message.tsx, .../markdown-card.tsx, .../chat-messages-view.tsx, .../index.ts
Removed ChatMessage and MessageContent; added UIMessageComponent with tool/markdown rendering; adjusted MarkdownCard spacing; ChatMessagesView now uses UIMessage types and new status flags; updated exports.
Chat View Integration
apps/desktop/src/components/right-panel/views/chat-view.tsx
Switched to conversation-driven flow using useChat2/useChatQueries2; rewired submit/selection, status flags, and initial/history loading; removed legacy connector/query usage.
Hooks (New vs Old)
apps/desktop/src/components/right-panel/hooks/useChat2.ts, .../useChatQueries2.ts, .../useChatLogic.ts, .../useChatQueries.ts
Added useChat2 (transported chat + DB persistence) and useChatQueries2 (conversations/messages/session loaders). Deleted useChatLogic and useChatQueries.
Transport & Chat Utils
apps/desktop/src/components/right-panel/utils/chat-transport.ts, .../chat-utils.ts, .../markdown-parser.ts
Introduced CustomChatTransport with MCP tool loading and streaming; replaced prepareMessageHistory with prepareMessagesForAI; added cleanUIMessages; updated markdown parsing API (ParsedPart, isComplete).
AI Utils & Dependency
packages/utils/src/ai.ts, package.json
Re-exported useChat, convertToModelMessages, and AI types; added @ai-sdk/react dependency.
DB: Types, Ops, Migrations
crates/db-user/src/chat_conversations_*, crates/db-user/src/chat_messages_v2_*, crates/db-user/src/lib.rs
Added conversations and messages v2 types/ops; added migrations; re-exported new modules; updated migration list.
DB Plugin Commands
plugins/db/build.rs, plugins/db/src/commands/chats_v2.rs, plugins/db/src/commands/mod.rs, plugins/db/src/lib.rs
Added Tauri commands for conversations and messages v2; registered commands and specta exports.
Repo Meta
.gitignore
Ignored .cursor/ directory; non-functional reorder/touch of CLAUDE.md entry.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant CV as ChatView
  participant Q2 as useChatQueries2
  participant C2 as useChat2
  participant T as CustomChatTransport
  participant DB as @hypr/plugin-db
  participant AI as Model

  U->>CV: Type + Submit message
  CV->>Q2: getOrCreateConversationId()
  alt no current conversation
    Q2->>DB: create_conversation()
    DB-->>Q2: conversation id
  end
  CV->>C2: sendMessage(content, metadata, conversationId)
  C2->>DB: create_message_v2(user)
  C2->>T: sendMessages({messages, options})
  T->>DB: (if needed) load session/mentions
  T->>AI: streamText(preparedMessages, tools)
  AI-->>T: chunks (text/tool events)
  T-->>C2: UIMessageChunk stream
  loop streaming
    C2-->>CV: messages/status update
    opt assistant finished
      C2->>DB: create_message_v2(assistant)
    end
  end
Loading
sequenceDiagram
  autonumber
  participant T as CustomChatTransport
  participant MCP as MCP Servers
  participant HN as Hyprnote MCP
  participant AI as Model

  T->>T: initializeModel()
  T->>T: loadMCPTools()
  alt license/tool-enabled
    T->>HN: connect (hypr MCP)
    HN-->>T: dynamic tools
  end
  T->>MCP: connect enabled servers
  MCP-->>T: dynamic tools
  T->>AI: streamText(..., tools: base + MCP)
  AI-->>T: tool calls/results/errors
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chat-refactor-new-db-2

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:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • 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 or @coderabbit 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.

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

@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: 9

Caution

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

⚠️ Outside diff range comments (2)
apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)

7-17: Remove unused props to satisfy “no unused variables” guideline

isComplete and hasEnhancedNote are never used.

 interface MarkdownCardProps {
   content: string;
-  isComplete: boolean;
   sessionTitle?: string;
   onApplyMarkdown?: (markdownContent: string) => void;
-  hasEnhancedNote?: boolean;
 }
 
 export function MarkdownCard(
-  { content, isComplete, sessionTitle, onApplyMarkdown, hasEnhancedNote = false }: MarkdownCardProps,
+  { content, sessionTitle, onApplyMarkdown }: MarkdownCardProps,
 ) {
packages/utils/src/ai.ts (1)

49-54: Bug: api_base used before null check (can throw).
api_base.includes(...) runs before verifying api_base; if undefined, this throws.

Apply this diff to guard the includes and keep behavior unchanged:

-  const ourCloud = api_base.includes("pro.hyprnote.com");
-  const licenseKey = await getLicenseKey();
-
-  if (!api_base) {
+  if (!api_base) {
     throw new Error("no_api_base");
   }
+  const ourCloud = api_base.includes("pro.hyprnote.com");
+  const licenseKey = await getLicenseKey();
🧹 Nitpick comments (38)
crates/db-user/src/chat_conversations_migration.sql (2)

8-8: Consider cascading FK to avoid blocked deletes or orphans

If sessions can be removed, cascade keeps conversations consistent.

-  FOREIGN KEY (session_id) REFERENCES sessions(id)
+  FOREIGN KEY (session_id) REFERENCES sessions(id) ON UPDATE CASCADE ON DELETE CASCADE

1-9: Add indexes for common lookups

Listing by session_id/user_id will benefit from indexes.

 );
 
+-- Query helpers
+CREATE INDEX IF NOT EXISTS idx_chat_conversations_session_id
+  ON chat_conversations(session_id);
+CREATE INDEX IF NOT EXISTS idx_chat_conversations_user_id
+  ON chat_conversations(user_id);
apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)

26-51: Prevent stale UI when content becomes empty

If content becomes empty, previous HTML persists. Reset it.

   useEffect(() => {
     const convertMarkdown = async () => {
       try {
         let html = await miscCommands.opinionatedMdToHtml(content);
@@
       }
     };
 
-    if (content.trim()) {
-      convertMarkdown();
-    }
+    if (content.trim()) {
+      convertMarkdown();
+    } else {
+      setHtmlContent("");
+    }
   }, [content]);
crates/db-user/src/chat_messages_v2_types.rs (1)

19-28: Consider typed JSON fields

Storing JSON as String pushes parse/validate to call sites. Using serde_json::Value improves safety and avoids double encode/decode (DB layer can serialize when persisting).

     pub role: ChatMessageV2Role,
-    pub parts: String, // JSON string
-    pub metadata: Option<String>, // JSON string
+    pub parts: serde_json::Value,
+    pub metadata: Option<serde_json::Value>,
plugins/db/src/lib.rs (1)

72-76: Add indexes for the new list queries to avoid scans

Since v2 lists order/filter on chat_conversations.updated_at DESC and chat_messages_v2.conversation_id, created_at ASC, ensure migrations add indexes on these columns for scale.

plugins/db/build.rs (1)

50-54: Minor: group v2 chat commands under a section comment

Improves scan-ability next to the legacy chat block.

Apply:

-    "delete_chat_messages",
-    "create_conversation",
+    "delete_chat_messages",
+    // chat v2
+    "create_conversation",
     "list_conversations",
     "create_message_v2",
     "list_messages_v2",
     "update_message_v2_parts",
apps/desktop/src/components/right-panel/components/chat/index.ts (1)

7-7: Consistency: use barrel-style re-export like others in this file

Keeps the pattern uniform and future-proofs if more exports are added from ui-message.

-export { UIMessageComponent } from "./ui-message";
+export * from "./ui-message";
packages/utils/src/ai.ts (1)

61-64: Minor: avoid fetching license key when not needed.
Defer getLicenseKey() behind the ourCloud branch to save an async call.

-    headers: {
-      "origin": "http://localhost:1420",
-      ...((ourCloud && licenseKey) ? { "x-hyprnote-license-key": licenseKey } : {}),
-    },
+    headers: {
+      "origin": "http://localhost:1420",
+      ...(ourCloud
+        ? { "x-hyprnote-license-key": await getLicenseKey() }
+        : {}),
+    },
crates/db-user/src/chat_messages_v2_migration.sql (1)

1-12: Add cascading and (optionally) JSON validity + index.

  • Without ON DELETE CASCADE, deleting a conversation can leave orphans or require manual cleanup.
  • Consider JSON validity checks and an index to match common queries.

If cascading is desired, update as:

-  FOREIGN KEY (conversation_id) REFERENCES chat_conversations(id)
+  FOREIGN KEY (conversation_id) REFERENCES chat_conversations(id) ON DELETE CASCADE

Optional (if JSON1 available) and indexing:

-- Optional JSON validation
CHECK (json_valid(parts)),
-- Optional: metadata may be NULL; validate only when present
CHECK (metadata IS NULL OR json_valid(metadata));

-- Optional index for typical lookups
CREATE INDEX IF NOT EXISTS idx_chat_messages_v2_conversation_id_created_at
  ON chat_messages_v2(conversation_id, created_at);
apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1)

8-67: Verify fenced-block handling: current output drops ``` fences and may include language tags as content.

  • If MarkdownCard expects fenced content for code blocks, dropping fences will render code as plain text.
  • Language token after (e.g.,ts) becomes part of content.

Two options—pick one consistent with MarkdownCard:

Option A: Preserve fences (simplest).

-        const markdownContent = text.slice(markdownStart, i);
+        const markdownContent = text.slice(markdownStart, i);
         parts.push({
           type: "markdown",
-          content: markdownContent,
+          content: "```" + markdownContent + "```",
           isComplete: true,
         });
...
-    const markdownContent = text.slice(markdownStart);
+    const markdownContent = text.slice(markdownStart);
     parts.push({
       type: "markdown",
-      content: markdownContent,
+      content: "```" + markdownContent,
       isComplete: false,
     });

Option B: Strip the language token and keep raw code, but ensure the renderer wraps it.

Additionally, consider not trimming text segments to preserve user spacing:

-          const textContent = text.slice(currentIndex, i).trim();
+          const textContent = text.slice(currentIndex, i);
...
-    const remainingText = text.slice(currentIndex).trim();
+    const remainingText = text.slice(currentIndex);
crates/db-user/src/chat_conversations_ops.rs (1)

33-54: Consider indexes to match this query pattern.
You filter by session_id and order by updated_at; add a composite index in the migration to avoid scans.

Suggested DDL:

CREATE INDEX IF NOT EXISTS idx_chat_conversations_session_updated
  ON chat_conversations(session_id, updated_at DESC);
crates/db-user/src/chat_messages_v2_ops.rs (3)

42-55: Add an index for list performance

This query will hot-path; add a composite index to keep scans fast as data grows.

Consider applying in a migration:

CREATE INDEX IF NOT EXISTS idx_chat_messages_v2_conv_created_at
  ON chat_messages_v2(conversation_id, created_at);

34-37: Minor: accept borrowed str to avoid allocations

conversation_id: &str (or impl AsRef<str>) would avoid a String allocation at call sites.


57-73: updated_at policy: consider centralizing in DB

If you plan to update other columns later, a DB-level trigger/default for updated_at can keep write paths consistent across ops.

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

37-47: Avoid as any by aligning transport option typing

getLicense: getLicense as any signals a typing mismatch. Prefer updating CustomChatTransportOptions to accept the query result type (e.g., { data?: License | null }) to remove the cast.


81-87: Stabilize chat id to avoid cross-session collisions

If sessionId is null, multiple users/sessions share "default". Include userId in the fallback.

Apply:

-    id: sessionId || "default",
+    id: sessionId || (userId ? `default:${userId}` : "default"),

117-145: Trim once and include onError in deps

Compute trim() once and add onError to the dependency array to avoid stale closures.

Apply:

-    async (
-      content: string,
+    async (
+      content: string,
       options?: {
@@
-      // Create metadata for the message
+      // Create metadata for the message
       const metadata = {
         mentions: options?.mentionedContent,
         selectionData: options?.selectionData,
         htmlContent: options?.htmlContent,
       };
 
-      // Use passed conversation ID or the one from props
+      // Use passed conversation ID or the one from props
       const convId = options?.conversationId || conversationId;
-      
-      if (!convId || !content.trim()) {
+      const trimmed = content.trim();
+      if (!convId || !trimmed) {
         console.error("Cannot send message without conversation ID", {
           passedId: options?.conversationId,
           propsId: conversationId,
-          content: content.trim()
+          content: trimmed
         });
         return;
       }
@@
         // Send to AI using the correct method
         sendAIMessage({
           id: userMessageId,
           role: "user",
-          parts: [{ type: "text", text: content }],
+          parts: [{ type: "text", text: trimmed }],
           metadata,
         });
       } catch (error) {
         console.error("Failed to send message:", error);
         onError?.(error as Error);
       }
     },
-    [sendAIMessage, conversationId]
+    [sendAIMessage, conversationId, onError]
   );

154-165: Type the role instead of casting

Rather than "user" as any, make the DB command’s role type a string union ("user" | "assistant") and pass the literal. Reduces unsound casts.


186-199: Don’t gate part updates on conversationId

updateMessageV2Parts targets by id only; gating on conversationId can drop in-flight updates during conversation switches.

Apply:

-    async (messageId: string, parts: any[]) => {
-      if (conversationId) {
-        try {
-          await dbCommands.updateMessageV2Parts(
-            messageId,
-            JSON.stringify(parts),
-          );
-        } catch (error) {
-          console.error("Failed to update message parts:", error);
-        }
-      }
-    },
-    [conversationId]
+    async (messageId: string, parts: any[]) => {
+      try {
+        await dbCommands.updateMessageV2Parts(messageId, JSON.stringify(parts));
+      } catch (error) {
+        console.error("Failed to update message parts:", error);
+      }
+    },
+    []
   );
apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (1)

49-49: Timeout type: use cross-env-safe typing

NodeJS.Timeout can conflict in browser builds. Prefer ReturnType<typeof setTimeout>.

Apply:

-  const thinkingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const thinkingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
plugins/db/src/commands/chats_v2.rs (1)

1-1: Minor: reduce repetition by factoring DB-handle extraction into a helper

A small private helper (e.g., get_db(state) -> Result<Db, String>) will de-duplicate boilerplate across commands. Keeps “why” comments concentrated and future-proof for state changes.

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

38-50: You’re loading messages twice (hook + local effect) — pick one path

useChatQueries2 already queries messages and can push them via setMessages; this local effect duplicates DB reads and JSON parsing, increasing work and race risk. Prefer a single source of truth.

Options:

  • Wire setMessages from useChat2 into useChatQueries2 and delete the local loadMessages effect.
  • Or add a flag to useChatQueries2 to skip messagesQuery when ChatView loads them itself.

Also applies to: 72-103


74-99: Clear messages immediately on conversation switch to avoid cross-thread flash

Stale messages remain visible until the DB read resolves. Clear first, then load.

@@
   useEffect(() => {
     const loadMessages = async () => {
-      if (currentConversationId) {
+      if (currentConversationId) {
+        // Why: prevent stale messages flashing during conversation switch
+        setMessages([]);
         try {
@@
       } else {
         // Clear messages for new conversation
         setMessages([]);
         console.log("Cleared messages for new conversation");
       }
     };
@@
   const handleSelectChatGroup = async (groupId: string) => {
-    setCurrentConversationId(groupId);
+    setCurrentConversationId(groupId);
+    // Why: clear quickly to reflect selection change
+    setMessages([]);
   };

Also applies to: 191-193


80-88: Preserve createdAt on historical messages for timestamp rendering

UIMessageComponent checks createdAt; current mapping omits it.

           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) : {},
+            createdAt: new Date(msg.created_at),
           }));

153-157: Trim “what” comments; keep only “why”

The TODO block can be shorter and explain the blocker (backend support), not the obvious steps.

-  // TODO: Implement proper markdown apply functionality
-  console.log("Apply markdown to note:", content);
-  // This would update the session's enhanced_memo_html field
-  // Need backend support for this
+  // Why: blocked until backend API to persist enhanced_memo_html is available
+  console.log("Apply markdown requested");
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (2)

166-171: Preserve whitespace between text parts

Joining without a separator can collapse word boundaries. Use a space.

-      .map(part => part.text || "")
-      .join("");
+      .map(part => part.text || "")
+      .join(" ");

237-242: Prefer typed tool parts over any

Define a minimal discriminated union for tool parts to avoid any-based access (toolName/state/input/output/errorText). Improves safety without changing runtime.

Example:

type ToolState = "input-streaming" | "input-available" | "output-available" | "output-error";

type DynamicToolPart = {
  type: "dynamic-tool";
  toolName?: string;
  state: ToolState;
  input?: unknown;
  output?: unknown;
  errorText?: string;
};

type SpecificToolPart = {
  type: `tool-${string}`;
  state: ToolState;
  input?: unknown;
  output?: unknown;
  errorText?: string;
};

type ToolPart = DynamicToolPart | SpecificToolPart;

Then narrow with const toolPart = part as ToolPart;.

Also applies to: 245-246, 319-321, 425-427

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

87-95: Harden parsed shape for parts.

After parsing, ensure it’s an array; otherwise provide a minimal text part. Also fix the misleading comment.

Apply this diff:

-        try {
-          parts = JSON.parse(msg.parts);
+        try {
+          const parsed = JSON.parse(msg.parts);
+          parts = Array.isArray(parsed) ? parsed : [{ type: "text", text: String(parsed ?? "") }];
         } catch (error) {
           console.error("Failed to parse message parts:", msg.id, error);
-          // Fallback to text content
-          // parts is already a string, no need to parse again
-          parts = [{ type: "text", text: "" }];
+          // Fallback to empty text to keep UI stable
+          parts = [{ type: "text", text: "" }];
         }
apps/desktop/src/components/right-panel/utils/chat-transport.ts (7)

5-15: Remove unused import tool.

tool isn’t used in this file and shadows the loop variable name later.

Apply this diff:

   streamText,
-  tool,
   type UIMessageChunk,
 } from "@hypr/utils/ai";

136-143: Avoid shadowing “tool” identifier for readability.

Rename the loop variable so it’s not confused with the (removed) import.

Apply this diff:

-        for (const [toolName, tool] of Object.entries(tools as Record<string, any>)) {
+        for (const [toolName, toolDef] of Object.entries(tools as Record<string, any>)) {
           newMcpTools[toolName] = dynamicTool({
-            description: tool.description,
-            inputSchema: tool.inputSchema || z.any(),
-            execute: tool.execute,
+            description: toolDef.description,
+            inputSchema: toolDef.inputSchema || z.any(),
+            execute: toolDef.execute,
           });
         }

74-87: DRY up shouldUseTools logic.

The same predicate appears twice. Extract to a private helper and reuse.

Apply this diff:

+  private async shouldUseTools() {
+    const llmConnection = await connectorCommands.getLlmConnection();
+    const { type } = llmConnection;
+    const apiBase = llmConnection.connection?.api_base;
+    const customModel = await connectorCommands.getCustomLlmModel();
+    const modelId = type === "Custom" && customModel ? customModel : "gpt-4";
+    return (
+      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" ||
+      type === "HyprLocal"
+    );
+  }

Then replace both occurrences with:

-    const shouldUseTools = 
-      modelId === "gpt-4.1" || 
-      ...
-      type === "HyprLocal";
+    const shouldUseTools = await this.shouldUseTools();

Also applies to: 164-173


241-251: Use the existing cleanup() to avoid duplication.

Call this.cleanup() in onFinish instead of re-implementing the loop.

Apply this diff:

         onFinish: () => {
-          // Clean up MCP clients
-          for (const client of this.allMcpClients) {
-            client.close();
-          }
-          if (this.hyprMcpClient) {
-            this.hyprMcpClient.close();
-          }
-          this.allMcpClients = [];
-          this.hyprMcpClient = null;
+          this.cleanup();
         },

229-241: Fixed step cap may truncate multi-tool flows.

stopWhen: stepCountIs(5) could prematurely cut tool-chains. Consider making this configurable or omitting it to let the model complete.

Would you like me to thread an optional maxToolSteps into CustomChatTransportOptions?


208-223: Clean up on abort to prevent MCP client leaks.

Attach an abort listener to call cleanup() so canceled streams don’t leak connections.

Apply this diff:

-      // Get tools
+      // Get tools
       const tools = await this.getTools();
+      options.abortSignal?.addEventListener("abort", () => this.cleanup(), { once: true });

49-55: Reduce noisy logs in production.

Wrap these console.log calls with a dev guard or remove them to avoid cluttering logs.

Also applies to: 63-70

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

138-144: Close the parenthesis in event info string.

Minor formatting fix.

Apply this diff:

-  const eventInfo = calendarEvent
-    ? `${calendarEvent.name} (${calendarEvent.start_date} - ${calendarEvent.end_date}${
-      calendarEvent.note ? ` - ${calendarEvent.note}` : ""
-    }`
+  const eventInfo = calendarEvent
+    ? `${calendarEvent.name} (${calendarEvent.start_date} - ${calendarEvent.end_date}${
+        calendarEvent.note ? ` - ${calendarEvent.note}` : ""
+      })`
     : "";

183-188: Type the prepared messages for stronger guarantees.

If ModelMessage is exported, prefer that over any[] to catch shape regressions at compile time.

Apply this diff:

-import { convertToModelMessages } from "@hypr/utils/ai";
+import { convertToModelMessages, type ModelMessage } from "@hypr/utils/ai";
...
-  const preparedMessages: any[] = [];
+  const preparedMessages: ModelMessage[] = [];

207-226: Potentially heavy DB calls inside mentions loop.

Fetching sessions and participants per mention can add up. Consider capping counts earlier or batching if latency spikes.

Also applies to: 239-266

📜 Review details

Configuration used: Path: .coderabbit.yaml

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 362d9b6 and 0109188.

⛔ Files ignored due to path filters (10)
  • 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/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .gitignore (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 (4 hunks)
  • apps/desktop/src/components/right-panel/utils/markdown-parser.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)
  • package.json (1 hunks)
  • packages/utils/src/ai.ts (2 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)
💤 Files with no reviewable changes (4)
  • apps/desktop/src/components/right-panel/hooks/useChatQueries.ts
  • apps/desktop/src/components/right-panel/components/chat/chat-message.tsx
  • 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/components/chat/index.ts
  • plugins/db/src/lib.rs
  • crates/db-user/src/chat_messages_v2_ops.rs
  • crates/db-user/src/chat_messages_v2_types.rs
  • apps/desktop/src/components/right-panel/components/chat/ui-message.tsx
  • crates/db-user/src/chat_conversations_ops.rs
  • apps/desktop/src/components/right-panel/hooks/useChat2.ts
  • plugins/db/src/commands/mod.rs
  • apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx
  • apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts
  • plugins/db/build.rs
  • crates/db-user/src/chat_conversations_types.rs
  • apps/desktop/src/components/right-panel/utils/markdown-parser.ts
  • apps/desktop/src/components/right-panel/utils/chat-transport.ts
  • crates/db-user/src/lib.rs
  • apps/desktop/src/components/right-panel/utils/chat-utils.ts
  • plugins/db/src/commands/chats_v2.rs
  • apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
  • packages/utils/src/ai.ts
🧬 Code graph analysis (10)
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)
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/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)
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/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 (39-300)
apps/desktop/src/components/right-panel/utils/chat-transport.ts (6)
packages/utils/src/ai.ts (11)
  • ChatTransport (24-24)
  • UIMessage (27-27)
  • modelProvider (80-87)
  • experimental_createMCPClient (14-14)
  • tool (22-22)
  • dynamicTool (13-13)
  • ChatRequestOptions (23-23)
  • UIMessageChunk (26-26)
  • streamText (21-21)
  • stepCountIs (20-20)
  • smoothStream (19-19)
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-306)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)
  • prepareMessagesForAI (81-316)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (2)
packages/utils/src/ai.ts (2)
  • UIMessage (27-27)
  • convertToModelMessages (12-12)
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/components/chat/chat-messages-view.tsx (2)
packages/utils/src/ai.ts (1)
  • UIMessage (27-27)
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (1)
  • UIMessageComponent (154-464)
apps/desktop/src/components/right-panel/views/chat-view.tsx (4)
apps/desktop/src/components/right-panel/hooks/useActiveEntity.ts (1)
  • useActiveEntity (14-80)
apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1)
  • useChatQueries2 (15-189)
apps/desktop/src/components/right-panel/hooks/useChat2.ts (1)
  • useChat2 (19-213)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)
  • focusInput (31-35)
🔇 Additional comments (29)
.gitignore (1)

20-20: LGTM: add .cursor/

Good to ignore local tool metadata.

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

41-44: Verify HTML sanitization on failure path

Fallback sets htmlContent = content (raw). Ensure opinionatedMdToHtml and/or Renderer sanitize to prevent XSS if content contains HTML.


113-113: LGTM: spacing tweak

Removing outer margins makes the card layout-controlled by parent; consistent with compact thread UIs.

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

3-16: No changes needed—serde derives are provided by the macro
The user_common_derives! macro in crates/db-user/src/lib.rs already includes serde::Serialize and serde::Deserialize, so your #[serde(rename = …)] attributes are correctly supported.

package.json (1)

6-8: LGTM: runtime dep belongs in dependencies

@ai-sdk/react used at runtime should live here.

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

3-3: LGTM: exposing Chats V2 module is correct

Placement right after chats keeps discoverability consistent.

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

72-76: LGTM: Specta exports include all v2 chat commands

The five commands are properly added to collect_commands!.


72-76: Commands parity verified
All entries in collect_commands! (plugins/db/src/lib.rs) match the COMMANDS list in plugins/db/build.rs.

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

7-7: Resolved: UIMessageComponent is correctly exported and no stale imports remain.
UIMessageComponent is exported in ui-message.tsx (line 154), and no imports from chat-message or message-content barrels were found.

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

8-10: Re-export of useChat looks good.
Public surface aligns with the new chat flow.


12-28: Type and util re-exports are appropriate.
Type-only re-exports prevent runtime bloat; matches new transport/UI usage.

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

3-4: New chat_conversations modules wired in.
Module declarations are correct.


9-10: New chat_messages_v2 modules wired in.
Module declarations are correct.


33-36: Public re-exports for conversations look good.
Matches ops/types exposure pattern used elsewhere.


45-47: Public re-exports for messages v2 look good.
Consistent with prior modules.


144-171: MIGRATIONS count and order look correct.
26 entries; conversations migration precedes messages_v2 as required. Keep append-only invariant.

Please run the existing migration test suite to confirm no ordering regressions in fresh and existing DBs.

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

4-31: create_conversation query looks solid.
Param binding, RFC3339 timestamps, and RETURNING usage are consistent with the schema.


56-75: get_conversation is straightforward and fine.
Single-row lookup by PK matches expected usage.

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

10-27: INSERT … RETURNING with bound params looks correct

Explicit column list + positional params are good; values map 1:1, and RFC3339 timestamps align with TEXT storage and ORDER BY needs.


29-31: Confirm INSERT … RETURNING row availability to avoid unwrap panic

If INSERT returns zero rows in any deployment mode, unwrap() will panic. Please confirm libsql always returns exactly one row here in your environment.

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

91-113: Persist canonical timestamps if available

You’re using new Date().toISOString() for assistant messages. If message includes a created timestamp, prefer that to avoid skew between UI and DB.

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

34-39: Thinking indicator UI reads well

Concise, non-intrusive indicator with debounced show; good polish.


51-55: Confirm intent: always show indicator after submit

isSubmitted short-circuits to true, so the indicator shows even while streaming. If you only want a pre-stream “gap” indicator, refine this branch.


115-131: Rendering pipeline migration looks solid

Switch to UIMessageComponent with spacing/scrolling behaves as expected; keys and props are correct.

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

31-37: N+1 queries when enriching conversations.

Fetching full messages per conversation won’t scale. Prefer a single query that returns last-message metadata or fetch only the latest user message per conversation (limit 1, desc) if supported by listMessagesV2.

Would you like me to scan the repo to confirm if listMessagesV2 accepts pagination/sorting options?


104-111: Confirm UIMessage shape (content vs parts).

You set both content and parts to the same array. Verify UIMessage actually declares content; if not, this widens the object type.

I can grep packages/utils/src/ai.ts and call sites to confirm the UIMessage interface usage.


155-162: Ensure crypto.randomUUID() availability in Tauri context.

Some WebView runtimes lack crypto.randomUUID. If not guaranteed, inject an ID generator from your utils.

Want me to check current Tauri/WebView target configuration for crypto support?

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

37-74: Good defensive cleaning for SDK v5 tool parts.

The filter keeps text and drops problematic tool states. This aligns with the conversion pipeline.


157-165: System prompt MCP tools payload is stubbed.

You’re sending { name, "", "{}" }. If the template relies on real schemas/descriptions, consider enriching this (or clearly comment why it’s minimal).

Should the template render human-readable tool help, or is the current stub intentional?

CLAUDE.md
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix trailing space: ignore rule for CLAUDE.md currently ineffective

The trailing space means Git won’t match CLAUDE.md. Remove it.

-CLAUDE.md 
+CLAUDE.md
🤖 Prompt for AI Agents
In .gitignore around line 19, the entry "CLAUDE.md " has a trailing space so Git
won't match the file; remove the trailing space so the line reads exactly
"CLAUDE.md" (no extra whitespace) and save the file.

Comment on lines +46 to +47
firstMessage: firstUserMessage ? (JSON.parse(firstUserMessage.parts)[0]?.text || "") : "",
mostRecentTimestamp,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard JSON.parse for firstMessage preview (can crash query).

JSON.parse(firstUserMessage.parts) can throw and reject the whole conversations query. Mirror the safe parsing you already use for message parts below.

Apply this diff:

-            firstMessage: firstUserMessage ? (JSON.parse(firstUserMessage.parts)[0]?.text || "") : "",
+            firstMessage: (() => {
+              if (!firstUserMessage) return "";
+              try {
+                const parsed = JSON.parse(firstUserMessage.parts);
+                return Array.isArray(parsed) ? (parsed[0]?.text ?? "") : "";
+              } catch {
+                return "";
+              }
+            })(),
📝 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
firstMessage: firstUserMessage ? (JSON.parse(firstUserMessage.parts)[0]?.text || "") : "",
mostRecentTimestamp,
firstMessage: (() => {
if (!firstUserMessage) return "";
try {
const parsed = JSON.parse(firstUserMessage.parts);
return Array.isArray(parsed) ? (parsed[0]?.text ?? "") : "";
} catch {
return "";
}
})(),
mostRecentTimestamp,
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts around lines
46-47, the code calls JSON.parse(firstUserMessage.parts) directly which can
throw and crash the conversations query; change the firstMessage computation to
safely parse parts (mirror the pattern used for message parts below) by wrapping
JSON.parse in a try/catch or using a safeParse helper so that on parse error you
fallback to an empty array/string and then extract [0]?.text || "" for
firstMessage; ensure the fallback preserves the existing ternary on
firstUserMessage being truthy.

Comment on lines +57 to +68
useEffect(() => {
if (conversationsQuery.data && conversationsQuery.data.length > 0) {
// Sort by most recent message timestamp
const latestConversation = conversationsQuery.data.sort((a, b) =>
b.mostRecentTimestamp - a.mostRecentTimestamp
)[0];
setCurrentConversationId(latestConversation.id);
} else if (conversationsQuery.data && conversationsQuery.data.length === 0) {
// No conversations exist for this session
setCurrentConversationId(null);
}
}, [conversationsQuery.data, sessionId, setCurrentConversationId]);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid auto-select overriding user choice.

Only auto-select when there’s no current conversation, and include it in deps.

Apply this diff:

-useEffect(() => {
-  if (conversationsQuery.data && conversationsQuery.data.length > 0) {
+useEffect(() => {
+  if (!currentConversationId && conversationsQuery.data && conversationsQuery.data.length > 0) {
     const latestConversation = [...conversationsQuery.data].sort((a, b) =>
       b.mostRecentTimestamp - a.mostRecentTimestamp
     )[0];
     setCurrentConversationId(latestConversation.id);
-  } else if (conversationsQuery.data && conversationsQuery.data.length === 0) {
+  } else if (!currentConversationId && conversationsQuery.data && conversationsQuery.data.length === 0) {
     setCurrentConversationId(null);
   }
-}, [conversationsQuery.data, sessionId, setCurrentConversationId]);
+}, [conversationsQuery.data, sessionId, currentConversationId, setCurrentConversationId]);

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

🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts around lines
57 to 68, the effect always auto-selects the latest conversation and can
overwrite a user-selected conversation; update the logic to only
setCurrentConversationId when there is no currentConversationId (null/undefined)
and add currentConversationId to the effect dependency array; implement a
non-mutating way to pick the latest conversation (e.g., use reduce or
[...data].sort()) and keep the existing branches for empty data unchanged.

Comment on lines +60 to +63
const latestConversation = conversationsQuery.data.sort((a, b) =>
b.mostRecentTimestamp - a.mostRecentTimestamp
)[0];
setCurrentConversationId(latestConversation.id);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t mutate React Query data when sorting.

conversationsQuery.data.sort(...) mutates cached data. Sort a copy.

Apply this diff:

-      const latestConversation = conversationsQuery.data.sort((a, b) =>
+      const latestConversation = [...conversationsQuery.data].sort((a, b) =>
         b.mostRecentTimestamp - a.mostRecentTimestamp
       )[0];
📝 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 latestConversation = conversationsQuery.data.sort((a, b) =>
b.mostRecentTimestamp - a.mostRecentTimestamp
)[0];
setCurrentConversationId(latestConversation.id);
const latestConversation = [...conversationsQuery.data].sort((a, b) =>
b.mostRecentTimestamp - a.mostRecentTimestamp
)[0];
setCurrentConversationId(latestConversation.id);
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts around lines
60 to 63, the code calls conversationsQuery.data.sort(...) which mutates React
Query's cached array; make a shallow copy before sorting (for example using
spread or slice) and sort that copy, and also guard for undefined/empty data
before accessing [0] so you only call setCurrentConversationId when a
latestConversation exists.

Comment on lines +38 to 50
// First load conversations and session data
const {
conversations,
sessionData,
getOrCreateConversationId,
} = useChatQueries2({
sessionId,
userId,
currentChatGroupId,
setCurrentChatGroupId,
setMessages,
setHasChatStarted,
prevIsGenerating,
currentConversationId,
setCurrentConversationId,
setMessages: () => {}, // Managed by useChat2
isGenerating: false
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard user selection from being overridden by auto-select

useChatQueries2 auto-selects latest on every refetch; it can overwrite a manual setCurrentConversationId from ChatView. Add a check in the hook to skip auto-select when currentConversationId is already set.

🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/views/chat-view.tsx around lines 38
to 50, useChatQueries2 currently auto-selects the latest conversation on each
refetch which can overwrite a user-picked currentConversationId; update the hook
so it first checks if currentConversationId is non-null/defined and, if so, skip
the auto-select path (do not call setCurrentConversationId) during refetches;
ensure the guard covers both initial load and subsequent refetches and only
auto-select when no manual selection exists.

Comment on lines +80 to +86
// Convert to UIMessage format
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) : {},
}));
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Validate upstream JSON invariants for parts/metadata parsing

JSON.parse on msg.metadata can throw if any writer stores non-JSON. Confirm all create/update call sites stringify consistently.

Expected: every call site stringifies; if any are missing, fix at the writer to keep readers simple.


🏁 Script executed:

#!/bin/bash
# Verify that all create/update calls stringify message parts/metadata

# 1) All createMessageV2 calls: parts & metadata should be JSON.stringify(...)
rg -nP -C2 --type=ts --type=tsx 'createMessageV2\(\s*{[^}]*\}' \
  | rg -nP 'parts\s*:\s*JSON\.stringify|metadata\s*:\s*JSON\.stringify'

# 2) All updateMessageV2Parts calls: argument should be JSON.stringify(...)
rg -nP -C2 --type=ts --type=tsx 'updateMessageV2Parts\s*\(' \
  | rg -nP 'JSON\.stringify\s*\('

Length of output: 284


🏁 Script executed:

#!/bin/bash
# 1. List all createMessageV2 call sites
echo "=== createMessageV2 call sites ==="
rg -n -C2 -P 'createMessageV2\(' -g '*.ts' -g '*.tsx'

# 2. Show createMessageV2 calls where parts/metadata are JSON.stringified
echo "=== Properly stringified parts/metadata in createMessageV2 ==="
rg -n -C2 -P 'createMessageV2\([^)]*\b(parts\s*:\s*JSON\.stringify|metadata\s*:\s*JSON\.stringify)' -g '*.ts' -g '*.tsx'

# 3. Show createMessageV2 calls missing JSON.stringify on parts or metadata
echo "=== createMessageV2 missing JSON.stringify ==="
rg -n -C2 -P 'createMessageV2\([^)]*\b(parts|metadata)\s*:' -g '*.ts' -g '*.tsx' | grep -v 'JSON.stringify'

# 4. List updateMessageV2Parts call sites
echo "=== updateMessageV2Parts call sites ==="
rg -n -C2 -P 'updateMessageV2Parts\(' -g '*.ts' -g '*.tsx'

# 5. Show updateMessageV2Parts calls with JSON.stringify
echo "=== updateMessageV2Parts with JSON.stringify ==="
rg -n -C2 -P 'updateMessageV2Parts\([^)]*JSON.stringify' -g '*.ts' -g '*.tsx'

# 6. Show updateMessageV2Parts calls missing JSON.stringify
echo "=== updateMessageV2Parts missing JSON.stringify ==="
rg -n -C2 -P 'updateMessageV2Parts\(' -g '*.ts' -g '*.tsx' | grep -v 'JSON.stringify'

Length of output: 2978


Serialize message parts and metadata in createMessageV2 calls

  • In apps/desktop/src/components/right-panel/hooks/useChat2.ts at lines 97 and 157, the createMessageV2 calls omit both parts and metadata. Add:
    parts: JSON.stringify(message.parts),
    metadata: JSON.stringify(message.metadata || {}),
    so downstream JSON.parse won’t throw.
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/hooks/useChat2.ts around lines 97 and
157, the createMessageV2 calls do not persist parts or metadata, which later
causes JSON.parse on db message fields to throw; update both createMessageV2
invocations to include parts: JSON.stringify(message.parts) and metadata:
JSON.stringify(message.metadata || {}), ensuring parts and metadata are stored
as strings and metadata defaults to an empty object when absent.

pub id: String,
pub session_id: String,
pub user_id: String,
pub name: Option<String>,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Align name optionality with DB semantics (None currently becomes empty string)

create_conversation uses conversation.name.unwrap_or_default() (context file: crates/db-user/src/chat_conversations_ops.rs), so None is written as "". Deserialization will yield Some(""), making Option misleading. Either make the column truly nullable or make the field non-optional.

Two viable fixes (pick one):

  • If you want nullable in DB, pass NULL on insert and keep Option<String>.
  • If empty string is the contract, switch the type to String:
-        pub name: Option<String>,
+        pub name: String,
📝 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
pub name: Option<String>,
pub name: String,
🤖 Prompt for AI Agents
In crates/db-user/src/chat_conversations_types.rs around line 8, the field pub
name: Option<String> is misleading because create_conversation currently writes
None as an empty string via unwrap_or_default(), so deserialization yields
Some("") — either make the DB column truly nullable and stop converting None to
"" (ensure inserts pass NULL for absent names and keep Option<String>), or
change the Rust type to pub name: String and remove Option usage so the
empty-string contract is explicit (update insert/deserialize logic accordingly);
pick one approach and make the corresponding change consistently in
chat_conversations_ops.rs and DB schema/migrations.

Comment on lines +6 to 8
"dependencies": {
"@ai-sdk/react": "^2.0.30"
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Check peer compatibility (React, next, vite, etc.)

Ensure @ai-sdk/react@^2.0.30 peers align with workspace React versions to avoid hoisting/dupe issues.


🏁 Script executed:

#!/bin/bash
# Inspect React versions across the monorepo
rg -nP '"react"\s*:\s*"' -g '!**/node_modules/**' -C1
rg -nP '"react-dom"\s*:\s*"' -g '!**/node_modules/**' -C1
# where it's imported
rg -nP '@ai-sdk/react' -n -C2 --type=ts --type=tsx

Length of output: 1685


🏁 Script executed:

#!/bin/bash
# Fetch peerDependencies for @ai-sdk/[email protected] from npm
npm view @ai-sdk/[email protected] peerDependencies --json || true

# Locate all imports of @ai-sdk/react in the monorepo
rg -n -C2 "@ai-sdk/react" -g "!**/node_modules/**"

Length of output: 1285


Add React and React-DOM to root package.json dependencies
Root package.json should explicitly depend on React ^18.3.1 and React-DOM ^18.3.1 alongside @ai-sdk/react to satisfy its peer and prevent pnpm from installing React 19.1.1 under @ai-sdk/react.

 "dependencies": {
+  "react": "^18.3.1",
+  "react-dom": "^18.3.1",
   "@ai-sdk/react": "^2.0.30"
 }
📝 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
"dependencies": {
"@ai-sdk/react": "^2.0.30"
}
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@ai-sdk/react": "^2.0.30"
}
🤖 Prompt for AI Agents
In package.json around lines 6 to 8, the root manifest only lists
"@ai-sdk/react" as a dependency which leaves React and React-DOM to be resolved
from peers and allows pnpm to install React 19.1.1 under @ai-sdk/react; add
explicit dependencies for "react" and "react-dom" pinned to "^18.3.1" in the
dependencies object alongside "@ai-sdk/react" so the project and package manager
use React 18.3.1 instead of pulling React 19.x.

Comment on lines +10 to +21
let guard = state.lock().await;

let db = guard
.db
.as_ref()
.ok_or(crate::Error::NoneDatabase)
.map_err(|e| e.to_string())?;

db.create_conversation(conversation)
.await
.map_err(|e| e.to_string())
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid holding the ManagedState lock across awaits; take/clone the DB handle before awaiting

Lock is retained while awaiting DB ops, risking deadlocks and unnecessary contention. Grab a clone of the DB handle within a short scope, then perform awaits outside the lock. Apply the pattern below to each command.

@@
-pub async fn create_conversation(
+pub async fn create_conversation(
     state: tauri::State<'_, crate::ManagedState>,
     conversation: ChatConversation,
 ) -> Result<ChatConversation, String> {
-    let guard = state.lock().await;
-
-    let db = guard
-        .db
-        .as_ref()
-        .ok_or(crate::Error::NoneDatabase)
-        .map_err(|e| e.to_string())?;
+    // Keep the lock only long enough to obtain the handle
+    let db = {
+        let guard = state.lock().await;
+        guard
+            .db
+            .as_ref()
+            .cloned() // requires the handle to be Clone
+            .ok_or(crate::Error::NoneDatabase)
+            .map_err(|e| e.to_string())?
+    };
 
     db.create_conversation(conversation)
         .await
         .map_err(|e| e.to_string())
 }
@@
 pub async fn list_conversations(
     state: tauri::State<'_, crate::ManagedState>,
     session_id: String,
 ) -> Result<Vec<ChatConversation>, String> {
-    let guard = state.lock().await;
-
-    let db = guard
-        .db
-        .as_ref()
-        .ok_or(crate::Error::NoneDatabase)
-        .map_err(|e| e.to_string())?;
+    let db = {
+        let guard = state.lock().await;
+        guard
+            .db
+            .as_ref()
+            .cloned()
+            .ok_or(crate::Error::NoneDatabase)
+            .map_err(|e| e.to_string())?
+    };
 
     db.list_conversations(session_id)
         .await
         .map_err(|e| e.to_string())
 }
@@
 pub async fn create_message_v2(
     state: tauri::State<'_, crate::ManagedState>,
     message: ChatMessageV2,
 ) -> Result<ChatMessageV2, String> {
-    let guard = state.lock().await;
-
-    let db = guard
-        .db
-        .as_ref()
-        .ok_or(crate::Error::NoneDatabase)
-        .map_err(|e| e.to_string())?;
+    let db = {
+        let guard = state.lock().await;
+        guard
+            .db
+            .as_ref()
+            .cloned()
+            .ok_or(crate::Error::NoneDatabase)
+            .map_err(|e| e.to_string())?
+    };
 
     db.create_message_v2(message)
         .await
         .map_err(|e| e.to_string())
 }
@@
 pub async fn list_messages_v2(
     state: tauri::State<'_, crate::ManagedState>,
     conversation_id: String,
 ) -> Result<Vec<ChatMessageV2>, String> {
-    let guard = state.lock().await;
-
-    let db = guard
-        .db
-        .as_ref()
-        .ok_or(crate::Error::NoneDatabase)
-        .map_err(|e| e.to_string())?;
+    let db = {
+        let guard = state.lock().await;
+        guard
+            .db
+            .as_ref()
+            .cloned()
+            .ok_or(crate::Error::NoneDatabase)
+            .map_err(|e| e.to_string())?
+    };
 
     db.list_messages_v2(conversation_id)
         .await
         .map_err(|e| e.to_string())
 }
@@
 pub async fn update_message_v2_parts(
     state: tauri::State<'_, crate::ManagedState>,
     id: String,
     parts: String,
 ) -> Result<(), String> {
-    let guard = state.lock().await;
-
-    let db = guard
-        .db
-        .as_ref()
-        .ok_or(crate::Error::NoneDatabase)
-        .map_err(|e| e.to_string())?;
+    let db = {
+        let guard = state.lock().await;
+        guard
+            .db
+            .as_ref()
+            .cloned()
+            .ok_or(crate::Error::NoneDatabase)
+            .map_err(|e| e.to_string())?
+    };
 
     db.update_message_v2_parts(id, parts)
         .await
         .map_err(|e| e.to_string())
 }

If the DB handle isn’t Clone, expose a cheap, cloneable handle in ManagedState so commands don’t await under the lock.

Also applies to: 30-41, 50-61, 70-81, 91-102

🤖 Prompt for AI Agents
In plugins/db/src/commands/chats_v2.rs around lines 10-21 (and similarly 30-41,
50-61, 70-81, 91-102), the code currently holds the ManagedState lock across
async DB calls; change it to take a short-lived lock, extract or clone a cheap
DB handle (or add a cloneable handle to ManagedState if the DB type isn’t
Clone), drop the lock, and then call the async DB methods outside the lock so
awaits do not run while the mutex is held.

@yujonglee yujonglee closed this Sep 15, 2025
@yujonglee yujonglee deleted the chat-refactor-new-db-2 branch September 15, 2025 15:34
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