-
Notifications
You must be signed in to change notification settings - Fork 352
Chat refactor new db 2 #1449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chat refactor new db 2 #1449
Conversation
📝 WalkthroughWalkthroughReplaces 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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this 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
andhasEnhancedNote
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 orphansIf 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 lookupsListing 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 emptyIf
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 fieldsStoring JSON as
String
pushes parse/validate to call sites. Usingserde_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 scansSince v2 lists order/filter on
chat_conversations.updated_at DESC
andchat_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 commentImproves 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 fileKeeps 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 CASCADEOptional (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 performanceThis 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
(orimpl AsRef<str>
) would avoid a String allocation at call sites.
57-73
: updated_at policy: consider centralizing in DBIf 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
: Avoidas any
by aligning transport option typing
getLicense: getLicense as any
signals a typing mismatch. Prefer updatingCustomChatTransportOptions
to accept the query result type (e.g.,{ data?: License | null }
) to remove the cast.
81-87
: Stabilize chat id to avoid cross-session collisionsIf
sessionId
is null, multiple users/sessions share"default"
. IncludeuserId
in the fallback.Apply:
- id: sessionId || "default", + id: sessionId || (userId ? `default:${userId}` : "default"),
117-145
: Trim once and includeonError
in depsCompute
trim()
once and addonError
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 castingRather than
"user" as any
, make the DB command’srole
type a string union ("user" | "assistant"
) and pass the literal. Reduces unsound casts.
186-199
: Don’t gate part updates on conversationId
updateMessageV2Parts
targets byid
only; gating onconversationId
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. PreferReturnType<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 helperA 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 pathuseChatQueries2 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 flashStale 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 renderingUIMessageComponent 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 partsJoining 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 anyDefine 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 importtool
.
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 upshouldUseTools
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()
inonFinish
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
intoCustomChatTransportOptions
?
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 overany[]
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.
⛔ 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 pathFallback sets
htmlContent = content
(raw). EnsureopinionatedMdToHtml
and/orRenderer
sanitize to prevent XSS ifcontent
contains HTML.
113-113
: LGTM: spacing tweakRemoving 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
Theuser_common_derives!
macro in crates/db-user/src/lib.rs already includesserde::Serialize
andserde::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 correctPlacement right after
chats
keeps discoverability consistent.plugins/db/src/lib.rs (2)
72-76
: LGTM: Specta exports include all v2 chat commandsThe five commands are properly added to
collect_commands!
.
72-76
: Commands parity verified
All entries incollect_commands!
(plugins/db/src/lib.rs) match theCOMMANDS
list inplugins/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 fromchat-message
ormessage-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 correctExplicit 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 panicIf 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 availableYou’re using
new Date().toISOString()
for assistant messages. Ifmessage
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 wellConcise, 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 solidSwitch 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
andparts
to the same array. VerifyUIMessage
actually declarescontent
; if not, this widens the object type.I can grep
packages/utils/src/ai.ts
and call sites to confirm theUIMessage
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 |
There was a problem hiding this comment.
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.
firstMessage: firstUserMessage ? (JSON.parse(firstUserMessage.parts)[0]?.text || "") : "", | ||
mostRecentTimestamp, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
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]); |
There was a problem hiding this comment.
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.
const latestConversation = conversationsQuery.data.sort((a, b) => | ||
b.mostRecentTimestamp - a.mostRecentTimestamp | ||
)[0]; | ||
setCurrentConversationId(latestConversation.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
// 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 | ||
}); |
There was a problem hiding this comment.
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.
// 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) : {}, | ||
})); |
There was a problem hiding this comment.
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 bothparts
andmetadata
. Add:so downstreamparts: JSON.stringify(message.parts), metadata: JSON.stringify(message.metadata || {}),
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>, |
There was a problem hiding this comment.
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.
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.
"dependencies": { | ||
"@ai-sdk/react": "^2.0.30" | ||
} |
There was a problem hiding this comment.
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.
"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.
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()) | ||
} |
There was a problem hiding this comment.
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 (just for preview)