Conversation
📝 WalkthroughWalkthroughThis PR replaces the right-panel chat stack with a new transport- and UIMessage-based architecture, adds v2 chat DB schema/ops/commands, introduces new hooks (useChat2/useChatQueries2), swaps message renderers, and wires local-LLM progress via events. Legacy hooks/components are removed. Utilities are updated for UIMessage, parsing, and AI preparation. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant ChatView
participant useChat2
participant CustomChatTransport
participant Model as LanguageModel
participant DB as DB (plugin)
User->>ChatView: Submit input
ChatView->>useChat2: sendMessage({text, mentions, selection, htmlContent})
useChat2->>DB: create_message_v2(user message)
useChat2->>CustomChatTransport: sendMessages({messages, options})
CustomChatTransport->>Model: streamText(messages, tools)
Model-->>CustomChatTransport: UIMessageChunk(s)
CustomChatTransport-->>useChat2: stream chunks
useChat2-->>ChatView: update messages/status
useChat2->>DB: create_message_v2(assistant message on finish)
User->>ChatView: Stop (optional)
ChatView->>useChat2: stop()
sequenceDiagram
autonumber
participant LocalLLM as Local LLM Server
participant Evt as Event Emitter
participant App as Desktop App
participant Editor as Editor Area
LocalLLM->>Evt: LLMEvent.Progress(p)
Evt-->>App: emit("LLMEvent", {progress: p})
App->>Editor: localLlmEvents.llmEvent(payload)
Editor->>Editor: setProgress(payload.progress)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (1)
48-50: Restore original HTML on undo (capture HTML slice and use DOMSerializer)Undo currently inserts plain text and can drop formatting; capture HTML from selectionData.html (if present) or serialize the ProseMirror slice and insert that on undo.
- const originalContent = selectionData?.text || editor.state.doc.textBetween(startOffset, endOffset); + const originalHtml = + (selectionData as any)?.html ?? + (() => { + const slice = editor.state.doc.slice(startOffset, endOffset); + const serializer = DOMSerializer.fromSchema(editor.state.schema); + const container = document.createElement("div"); + container.appendChild(serializer.serializeFragment(slice.content)); + return container.innerHTML; + })();- currentEditor.chain() + currentEditor.chain() .setTextSelection({ from: startOffset, to: highlightEnd }) .deleteSelection() - .insertContent(originalContent) + .insertContent(originalHtml) .run();Add:
import { DOMSerializer } from "prosemirror-model";
- SelectionData (apps/desktop/src/contexts/right-panel.tsx — ~lines 10–13) has no
htmlfield; addhtml?: stringor keep the(selectionData as any)?.htmlcast.- prosemirror-model appears in the repo (pnpm-lock.yaml / packages/tiptap); ensure apps/desktop can resolve it — add to apps/desktop package.json if not.
File locations: apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (around lines 48–50 and 139–145).
🧹 Nitpick comments (23)
apps/desktop/src/components/editor-area/index.tsx (1)
15-17: Consolidate imports from @hypr/plugin-local-llmMerge into a single import for clarity and to avoid duplicate import statements.
-import { events as localLlmEvents } from "@hypr/plugin-local-llm"; -import { commands as localLlmCommands } from "@hypr/plugin-local-llm"; +import { commands as localLlmCommands, events as localLlmEvents } from "@hypr/plugin-local-llm";crates/db-user/src/chat_conversations_migration.sql (1)
1-9: Add an index to speed list_conversations(session_id ORDER BY updated_at DESC).This query is on the hot path; a composite index prevents full scans.
Apply this diff to append the index:
CREATE TABLE IF NOT EXISTS chat_conversations ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE ); +CREATE INDEX IF NOT EXISTS idx_chat_conversations_session_updated + ON chat_conversations (session_id, updated_at DESC);crates/db-user/src/chat_conversations_types.rs (1)
3-12: Optionally tag the SQL table for consistency (enables SqlTable).If other models implement hypr_db_core::SqlTable, add the sql_table tag.
user_common_derives! { - pub struct ChatConversation { + #[sql_table("chat_conversations")] + pub struct ChatConversation { pub id: String, pub session_id: String, pub user_id: String, pub name: Option<String>, pub created_at: chrono::DateTime<chrono::Utc>, pub updated_at: chrono::DateTime<chrono::Utc>, } }crates/db-user/src/chat_messages_v2_migration.sql (1)
1-12: Add an index to speed list_messages_v2(conversation_id ORDER BY created_at ASC).Improves retrieval performance for per-conversation message streams.
Apply this diff to append the index:
CREATE TABLE IF NOT EXISTS chat_messages_v2 ( id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, role TEXT CHECK(role IN ('system', 'user', 'assistant')) NOT NULL, parts TEXT NOT NULL, -- JSON string of message parts array metadata TEXT, -- JSON string for mentions, selections, etc. created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (conversation_id) REFERENCES chat_conversations(id) ON DELETE CASCADE ); +CREATE INDEX IF NOT EXISTS idx_chat_messages_v2_conv_created + ON chat_messages_v2 (conversation_id, created_at ASC);Optional: if json1 is guaranteed, consider CHECK(json_valid(parts)) and CHECK(metadata IS NULL OR json_valid(metadata)).
crates/db-user/src/chat_messages_v2_types.rs (1)
18-28: Optionally tag the SQL table for ChatMessageV2 as well.Keeps parity with other models if SqlTable is used elsewhere.
user_common_derives! { - pub struct ChatMessageV2 { + #[sql_table("chat_messages_v2")] + pub struct ChatMessageV2 { pub id: String, pub conversation_id: String, pub role: ChatMessageV2Role, pub parts: String, // JSON string pub metadata: Option<String>, // JSON string pub created_at: chrono::DateTime<chrono::Utc>, pub updated_at: chrono::DateTime<chrono::Utc>, } }crates/db-user/src/chat_conversations_ops.rs (2)
28-30: Avoid panic on empty result: replace unwrap() with expect() to document invariant
INSERT ... RETURNINGshould yield one row, butunwrap()will panic without context if it doesn't. Useexpect()with a clear invariant note.- let row = rows.next().await?.unwrap(); + let row = rows.next().await?.expect("INSERT chat_conversations RETURNING should yield one row");
41-44: Add index on chat_conversations(session_id, updated_at DESC); RFC3339 UTC timestamps are already used
- Confirmed: chat_conversations timestamps are chrono::DateTimechrono::Utc and written with to_rfc3339(), so lexicographic ORDER BY updated_at DESC is correct (see crates/db-user/src/chat_conversations_types.rs and ops).
- Action: add to migration (crates/db-user/src/chat_conversations_migration.sql):
CREATE INDEX IF NOT EXISTS idx_chat_conversations_session_updated_at ON chat_conversations(session_id, updated_at DESC);crates/db-user/src/chat_messages_v2_ops.rs (1)
29-31: Avoid panic on empty result: replace unwrap() with expect()Mirror the expectation that
INSERT ... RETURNINGyields one row.- let row = rows.next().await?.unwrap(); + let row = rows.next().await?.expect("INSERT chat_messages_v2 RETURNING should yield one row");plugins/db/src/commands/chats_v2.rs (1)
10-17: Reduce duplication with a small helper to get DB from stateFive functions repeat the same state/db extraction. Consider a private helper like
with_db(state, |db| async move { ... })to centralize NoneDatabase mapping.Also applies to: 32-37, 52-57, 72-77, 93-97
apps/desktop/src/components/right-panel/utils/chat-utils.ts (2)
95-114: Add error handling for missing sessionData.When
freshSessionDatais still undefined after the fetch attempt (line 114), the code continues execution. Consider adding a fallback or warning to handle this case more explicitly.} catch (error) { console.error("Error fetching session data:", error); } } + +// Provide a default structure if sessionData is still missing +if (!freshSessionData && sessionId) { + console.warn(`Session data could not be fetched for sessionId: ${sessionId}`); + freshSessionData = { + title: "", + rawContent: "", + enhancedContent: "", + preMeetingContent: "", + words: [], + }; +}
262-264: Extract HTML stripping to a utility function.The HTML stripping logic is duplicated (lines 262 and 264). Consider extracting it to a reusable function.
+const stripHtmlTags = (html: string): string => { + return html.replace(/<[^>]*>/g, ""); +}; + // Then use it in both places: -briefContent = session.enhanced_memo_html.replace(/<[^>]*>/g, "").slice(0, 200) + "..."; +briefContent = stripHtmlTags(session.enhanced_memo_html).slice(0, 200) + "...";package.json (1)
6-8: Move @ai-sdk/react into the package that uses it (packages/utils) or justify keeping it at rootOnly usage found: packages/utils/src/ai.ts:8; root package.json lists "@ai-sdk/react": "^2.0.30" (pnpm-lock resolves to 2.0.48). Add the dependency to packages/utils/package.json dependencies or document/justify the root-level placement.
apps/desktop/src/components/right-panel/utils/chat-transport.ts (3)
65-72: DRY the shouldUseTools gateThe identical gating logic appears twice. Centralize it to avoid drift when model IDs change.
Apply these diffs in place:
- const shouldUseTools = modelId === "gpt-4.1" - || modelId === "openai/gpt-4.1" - || modelId === "anthropic/claude-sonnet-4" - || modelId === "openai/gpt-4o" - || modelId === "gpt-4o" - || apiBase?.includes("pro.hyprnote.com") - || modelId === "openai/gpt-5"; + const shouldUseTools = this.computeShouldUseTools(modelId, apiBase);- const shouldUseTools = modelId === "gpt-4.1" - || modelId === "openai/gpt-4.1" - || modelId === "anthropic/claude-sonnet-4" - || modelId === "openai/gpt-4o" - || modelId === "gpt-4o" - || apiBase?.includes("pro.hyprnote.com") - || modelId === "openai/gpt-5"; + const shouldUseTools = this.computeShouldUseTools(modelId, apiBase);Add this helper outside the shown ranges:
private computeShouldUseTools(modelId: string, apiBase?: string) { return ( modelId === "gpt-4.1" || modelId === "openai/gpt-4.1" || modelId === "anthropic/claude-sonnet-4" || modelId === "openai/gpt-4o" || modelId === "gpt-4o" || modelId === "openai/gpt-5" || !!apiBase?.includes("pro.hyprnote.com") ); }Also applies to: 149-156
192-197: Also propagate mentions from message metadataToday only selectionData is pulled from the last message. Read mentions too to avoid races with option updates.
const messageMetadata = lastMessage?.metadata as any; if (messageMetadata?.selectionData) { this.options.selectionData = messageMetadata.selectionData; } + if (messageMetadata?.mentions) { + this.options.mentionedContent = messageMetadata.mentions; + }
198-206: Clean up MCP clients on abortWhen the request is aborted, onFinish may not fire, leaking clients. Hook the AbortSignal.
- const preparedMessages = await prepareMessagesForAI(options.messages, { + const preparedMessages = await prepareMessagesForAI(options.messages, { sessionId: this.options.sessionId, userId: this.options.userId, sessionData: this.options.sessionData, selectionData: this.options.selectionData, mentionedContent: this.options.mentionedContent, }); + + options.abortSignal?.addEventListener( + "abort", + () => this.cleanup(), + { once: true }, + );apps/desktop/src/components/right-panel/hooks/useChat2.ts (2)
147-149: Avoid timing hacks by reading mentions in transportThe 10ms delay exists to ensure mentions/selection reach the transport before tool loading. If transport reads mentions from the last message metadata (as suggested), this delay can be removed.
- // Small delay to ensure options are updated before tools are loaded - await new Promise(resolve => setTimeout(resolve, 10)); + // Avoid delay by letting the transport read metadata from the last message
117-174: Fix useCallback dependencies to avoid stale capturessendMessage uses sessions and onError but they aren’t in deps. Include them to prevent stale values.
- [sendAIMessage, conversationId], + [sendAIMessage, conversationId, sessions, onError],apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1)
8-67: Consider handling fenced language headersCurrently, content inside ```lang blocks includes the language token. Strip the optional language spec so downstream renderers get clean code.
- markdownStart = i + 3; + // Skip optional language token until first newline + let start = i + 3; + while (start < text.length && text[start] !== "\n") start++; + markdownStart = text[start] === "\n" ? start + 1 : start;apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1)
55-58: Avoid mutating React Query cache data when sortingsort() mutates the cached array. Copy before sort to prevent cache mutation.
- const latestConversation = conversationsQuery.data.sort((a, b) => + const latestConversation = [...conversationsQuery.data].sort((a, b) => b.mostRecentTimestamp - a.mostRecentTimestamp )[0];apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (1)
18-52: Consider adding error boundaries for markdown conversion failures.While the error handling with console.error and fallback to original content is good, consider wrapping the component with an error boundary to gracefully handle any rendering errors.
apps/desktop/src/components/right-panel/views/chat-view.tsx (1)
127-135: Consider adding user feedback for conversation creation failure.When conversation creation fails, the user gets no feedback beyond the console error.
Consider showing a toast or alert to inform the user:
convId = await getOrCreateConversationId(); if (!convId) { console.error("Failed to create conversation"); + // TODO: Show user-friendly error message return; }apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (2)
34-39: Simplify session lookup; avoid O(n) Object.keys and remove the unused local. Also confirm the permissive behavior change is intentional.This keeps the same runtime behavior while avoiding an extra allocation/scan.
- if (sessions && Object.keys(sessions).length > 0) { - const sessionStore = sessions[sessionId]; - if (!sessionStore) { - console.warn("Session store not found in sessions object, but continuing with editor ref"); - } - } + if (sessions && !(sessionId in sessions)) { + console.warn("Session store not found; continuing with editor ref"); + }Please confirm the relaxed guard (proceeding without a session store) won’t break downstream expectations.
85-89: Target the correct highlight when multiple exist. Avoid global querySelector by anchoring at startOffset.Prevents picking an unrelated highlight if more than one is present.
- const highlighted = document.querySelector("[data-ai-highlight=\"true\"]"); + const { node } = editor.view.domAtPos(startOffset); + const highlighted = (node instanceof Element ? node : node.parentElement)?.closest('[data-ai-highlight="true"]');
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (11)
plugins/db/js/bindings.gen.tsis excluded by!**/*.gen.tsplugins/db/permissions/autogenerated/commands/create_conversation.tomlis excluded by!plugins/**/permissions/**plugins/db/permissions/autogenerated/commands/create_message_v2.tomlis excluded by!plugins/**/permissions/**plugins/db/permissions/autogenerated/commands/list_conversations.tomlis excluded by!plugins/**/permissions/**plugins/db/permissions/autogenerated/commands/list_messages_v2.tomlis excluded by!plugins/**/permissions/**plugins/db/permissions/autogenerated/commands/update_message_v2_parts.tomlis excluded by!plugins/**/permissions/**plugins/db/permissions/autogenerated/reference.mdis excluded by!plugins/**/permissions/**plugins/db/permissions/default.tomlis excluded by!plugins/**/permissions/**plugins/db/permissions/schemas/schema.jsonis excluded by!plugins/**/permissions/**plugins/local-llm/js/bindings.gen.tsis excluded by!**/*.gen.tspnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (36)
.gitignore(1 hunks)apps/desktop/src/components/editor-area/index.tsx(2 hunks)apps/desktop/src/components/right-panel/components/chat/chat-input.tsx(1 hunks)apps/desktop/src/components/right-panel/components/chat/chat-message.tsx(0 hunks)apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx(4 hunks)apps/desktop/src/components/right-panel/components/chat/index.ts(1 hunks)apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx(1 hunks)apps/desktop/src/components/right-panel/components/chat/message-content.tsx(0 hunks)apps/desktop/src/components/right-panel/components/chat/ui-message.tsx(1 hunks)apps/desktop/src/components/right-panel/hooks/useChat2.ts(1 hunks)apps/desktop/src/components/right-panel/hooks/useChatLogic.ts(0 hunks)apps/desktop/src/components/right-panel/hooks/useChatQueries.ts(0 hunks)apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts(1 hunks)apps/desktop/src/components/right-panel/utils/chat-transport.ts(1 hunks)apps/desktop/src/components/right-panel/utils/chat-utils.ts(5 hunks)apps/desktop/src/components/right-panel/utils/markdown-parser.ts(1 hunks)apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts(1 hunks)apps/desktop/src/components/right-panel/views/chat-view.tsx(6 hunks)crates/db-user/src/chat_conversations_migration.sql(1 hunks)crates/db-user/src/chat_conversations_ops.rs(1 hunks)crates/db-user/src/chat_conversations_types.rs(1 hunks)crates/db-user/src/chat_messages_v2_migration.sql(1 hunks)crates/db-user/src/chat_messages_v2_ops.rs(1 hunks)crates/db-user/src/chat_messages_v2_types.rs(1 hunks)crates/db-user/src/lib.rs(5 hunks)crates/db-user/src/sessions_ops.rs(0 hunks)package.json(1 hunks)packages/utils/src/ai.ts(1 hunks)plugins/db/build.rs(1 hunks)plugins/db/src/commands/chats_v2.rs(1 hunks)plugins/db/src/commands/mod.rs(1 hunks)plugins/db/src/lib.rs(1 hunks)plugins/local-llm/src/events.rs(1 hunks)plugins/local-llm/src/ext/plugin.rs(2 hunks)plugins/local-llm/src/lib.rs(2 hunks)plugins/local-llm/src/server.rs(10 hunks)
💤 Files with no reviewable changes (5)
- crates/db-user/src/sessions_ops.rs
- apps/desktop/src/components/right-panel/components/chat/chat-message.tsx
- apps/desktop/src/components/right-panel/hooks/useChatQueries.ts
- apps/desktop/src/components/right-panel/hooks/useChatLogic.ts
- apps/desktop/src/components/right-panel/components/chat/message-content.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}
⚙️ CodeRabbit configuration file
**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".
Files:
apps/desktop/src/components/right-panel/utils/markdown-parser.tsplugins/local-llm/src/events.rsplugins/db/build.rspackages/utils/src/ai.tscrates/db-user/src/chat_messages_v2_ops.rsapps/desktop/src/components/right-panel/hooks/useChatQueries2.tsapps/desktop/src/components/right-panel/components/chat/index.tsapps/desktop/src/components/right-panel/components/chat/chat-input.tsxapps/desktop/src/components/right-panel/components/chat/ui-message.tsxcrates/db-user/src/chat_conversations_types.rsapps/desktop/src/components/right-panel/utils/chat-transport.tsplugins/local-llm/src/lib.rsplugins/local-llm/src/ext/plugin.rscrates/db-user/src/chat_conversations_ops.rsapps/desktop/src/components/right-panel/utils/chat-utils.tsplugins/db/src/commands/chats_v2.rscrates/db-user/src/chat_messages_v2_types.rsplugins/db/src/commands/mod.rsapps/desktop/src/components/editor-area/index.tsxplugins/local-llm/src/server.rsapps/desktop/src/components/right-panel/views/chat-view.tsxapps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.tsapps/desktop/src/components/right-panel/hooks/useChat2.tscrates/db-user/src/lib.rsapps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsxplugins/db/src/lib.rsapps/desktop/src/components/right-panel/components/chat/markdown-card.tsx
🧬 Code graph analysis (14)
plugins/local-llm/src/events.rs (1)
plugins/local-stt/src/lib.rs (1)
tauri_specta(34-58)
crates/db-user/src/chat_messages_v2_ops.rs (2)
plugins/db/src/commands/chats_v2.rs (3)
create_message_v2(46-61)list_messages_v2(66-81)update_message_v2_parts(86-102)crates/db-core/src/lib.rs (1)
conn(17-22)
apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1)
packages/utils/src/ai.ts (1)
UIMessage(25-25)
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (2)
apps/desktop/src/components/right-panel/utils/markdown-parser.ts (1)
parseMarkdownBlocks(8-67)apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)
MarkdownCard(15-140)
apps/desktop/src/components/right-panel/utils/chat-transport.ts (6)
packages/utils/src/ai.ts (4)
UIMessage(25-25)modelProvider(79-86)tool(24-24)dynamicTool(14-14)apps/desktop/src/components/right-panel/utils/mcp-http-wrapper.ts (1)
buildVercelToolsFromMcp(5-31)apps/desktop/src/components/right-panel/utils/tools/search_session_multi_keywords.ts (1)
createSearchSessionTool(5-59)apps/desktop/src/components/right-panel/utils/tools/search_session_date_range.ts (1)
createSearchSessionDateRangeTool(5-82)apps/desktop/src/components/right-panel/utils/tools/edit_enhanced_note.ts (1)
createEditEnhancedNoteTool(12-310)apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)
prepareMessagesForAI(82-319)
plugins/local-llm/src/lib.rs (2)
plugins/template/src/lib.rs (1)
tauri_specta(12-15)plugins/local-stt/src/lib.rs (1)
tauri_specta(34-58)
plugins/local-llm/src/ext/plugin.rs (1)
plugins/local-llm/src/server.rs (2)
new(47-56)new(151-156)
crates/db-user/src/chat_conversations_ops.rs (2)
plugins/db/src/commands/chats_v2.rs (2)
create_conversation(6-21)list_conversations(26-41)crates/db-core/src/lib.rs (1)
conn(17-22)
apps/desktop/src/components/right-panel/utils/chat-utils.ts (2)
packages/utils/src/ai.ts (2)
UIMessage(25-25)convertToModelMessages(13-13)apps/desktop/src/contexts/right-panel.tsx (1)
SelectionData(10-16)
plugins/db/src/commands/chats_v2.rs (2)
crates/db-user/src/chat_conversations_ops.rs (2)
create_conversation(4-31)list_conversations(33-54)crates/db-user/src/chat_messages_v2_ops.rs (3)
create_message_v2(4-32)list_messages_v2(34-55)update_message_v2_parts(57-73)
apps/desktop/src/components/right-panel/views/chat-view.tsx (6)
apps/desktop/src/hooks/use-license.ts (1)
useLicense(9-105)apps/desktop/src/components/right-panel/hooks/useActiveEntity.ts (1)
useActiveEntity(14-80)packages/utils/src/contexts/sessions.tsx (1)
useSessions(33-47)apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (1)
useChatQueries2(15-175)apps/desktop/src/components/right-panel/hooks/useChat2.ts (1)
useChat2(17-203)apps/desktop/src/components/pro-gate-modal/service.ts (1)
showProGateModal(6-27)
apps/desktop/src/components/right-panel/hooks/useChat2.ts (2)
apps/desktop/src/hooks/use-license.ts (1)
useLicense(9-105)apps/desktop/src/components/right-panel/utils/chat-transport.ts (1)
CustomChatTransport(38-274)
apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (2)
packages/utils/src/ai.ts (1)
UIMessage(25-25)apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (1)
UIMessageComponent(154-473)
plugins/db/src/lib.rs (3)
crates/db-user/src/chat_conversations_ops.rs (2)
create_conversation(4-31)list_conversations(33-54)plugins/db/src/commands/chats_v2.rs (5)
create_conversation(6-21)list_conversations(26-41)create_message_v2(46-61)list_messages_v2(66-81)update_message_v2_parts(86-102)crates/db-user/src/chat_messages_v2_ops.rs (3)
create_message_v2(4-32)list_messages_v2(34-55)update_message_v2_parts(57-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: ci (macos, macos-14)
- GitHub Check: ci (windows, windows-latest)
🔇 Additional comments (51)
.gitignore (2)
20-20: Good addition: ignore Cursor workspaceAdding “.cursor/” helps keep local editor artifacts out of VCS.
19-19: Fix trailing space in ignore pattern for CLAUDE.mdThe trailing space means this pattern won’t match “CLAUDE.md” and will fail to ignore it. Remove the space.
-CLAUDE.md +CLAUDE.mdOptional: if you only want to ignore the root file, anchor it:
-CLAUDE.md +/CLAUDE.mdLikely an incorrect or invalid review comment.
plugins/db/src/commands/mod.rs (1)
3-3: New chats_v2 module wiring looks good.Module exposure aligns with added commands and types.
plugins/db/src/lib.rs (1)
73-77: Specta surface updated for chats_v2 — looks correct.Matches build.rs COMMANDS and new ops.
crates/db-user/src/lib.rs (2)
3-5: New chats_v2 modules and re-exports are well‑integrated.Public surface aligns with plugin commands.
Also applies to: 9-11, 33-36, 45-48
145-173: Migrations appended correctly.Count and order look good; new files are last, respecting “append only”.
crates/db-user/src/chat_messages_v2_types.rs (1)
3-16: Enum shape and (de)serialization mapping look solid.strum + serde names align with DB CHECK(role ...).
plugins/db/build.rs (1)
50-54: Command surface extended correctly — consider addingget_conversation.get_conversationis implemented at crates/db-user/src/chat_conversations_ops.rs (pub async fn get_conversation at ~line 56) but isn’t included in plugins/db/build.rs’s command list; if the UI needs it, add the command here and update specta.crates/db-user/src/chat_conversations_ops.rs (2)
21-23: Empty string vs NULL semantics for nameYou coerce None to "". Confirm the schema defines name as NOT NULL or DEFAULT '' to avoid losing "unknown" vs "empty" semantics.
56-75: LGTMParameterized query, row decoding, and Option return are clean.
crates/db-user/src/chat_messages_v2_ops.rs (2)
64-71: Confirm silent no-op on missing id is acceptable
UPDATEresult is ignored; callers won’t know if 0 rows were affected. If that’s intentional, ignore; otherwise consider surfacing a signal.
34-55: LGTMRead path and decoder are consistent; ascending time order matches UI expectations.
plugins/db/src/commands/chats_v2.rs (1)
1-102: LGTMCommand surfaces and DB delegation look consistent with the new ops.
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (1)
156-156: LGTM! Good defensive check for selection node filtering.The addition of the guard condition
node.attrs.type !== "selection"properly filters out selection-type nodes when extracting mentions, preventing unwanted selection nodes from being processed as regular mentions.plugins/local-llm/src/events.rs (1)
34-38: LGTM! Well-structured event type for LLM progress tracking.The
LLMEventenum is properly structured with appropriate derives and serde rename. TheProgress(f64)variant appropriately uses floating-point for progress percentage (0.0-1.0 range).packages/utils/src/ai.ts (2)
8-8: LGTM! Proper re-export of useChat from @ai-sdk/react.The export is correctly placed and aligns with the PR's objective of introducing new chat architecture.
10-27: Exports validated — types available in 'ai' v5packages/utils/package.json specifies "ai": "^5.0.34"; ChatRequestOptions, ChatTransport, UIMessage and UIMessageChunk are present in ai v5 SDK docs — no change required.
apps/desktop/src/components/right-panel/utils/chat-utils.ts (1)
37-76: LGTM! Well-implemented message cleaning function.The
cleanUIMessagesfunction properly filters out problematic tool states that cause conversion errors. The implementation is defensive and handles edge cases appropriately.apps/desktop/src/components/right-panel/utils/chat-transport.ts (5)
47-52: Model initialization looks correctProvider selection and language model retrieval are consistent with utils.
106-134: Verify inputSchema compatibility for SSE MCP toolsHere you pass tool.inputSchema directly to dynamicTool. In mcp-http-wrapper you convert JSON Schemas via jsonSchema(...). Ensure experimental_createMCPClient.tools() returns Zod schemas; otherwise tool invocation may fail.
208-229: Confirm step limiter is intendedstopWhen(stepCountIs(10)) will cut off long tool loops/streams. Verify this aligns with UX expectations for all providers.
231-244: Error-to-text mapping LGTMReasonable normalization for UIMessage stream errors.
264-273: Cleanup logic LGTMClients are closed and references cleared.
apps/desktop/src/components/right-panel/hooks/useChat2.ts (3)
34-43: Singleton transport initialization looks goodStable instance created once with initial options.
45-56: Option refresh on deps change LGTMKeeps transport in sync with session/user/selection/license.
95-115: Assistant message persistence on finish LGTMSaves streamed assistant message into V2 schema using the active conversationIdRef.
plugins/local-llm/src/lib.rs (2)
43-43: Specta event registration LGTMLLMEvent is added to the event catalog for typed IPC.
71-73: Mount events during setupMounting events in setup is correct. Ensure LLMEvent derives/implements the specta Event trait.
apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)
113-113: Spacing tweak LGTMRemoving outer margins makes this card composable in dense lists.
plugins/local-llm/src/ext/plugin.rs (3)
5-5: Bring Event trait into scopeRequired for event.emit(&handle).
196-203: Emitter wiring LGTMEmits LLMEvent via tauri_specta Event integration.
201-202: ServerState initialization with emitter LGTMMatches new constructor signature and propagates events to the server.
apps/desktop/src/components/right-panel/components/chat/index.ts (1)
8-8: Export switch to UIMessageComponent LGTMAligns public API with the new UIMessage renderer.
apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (2)
74-106: Message mapping and parse fallbacks LGTMConsistent with UIMessage shape and tolerant to bad stored JSON.
108-112: DB-to-UI sync gate LGTMAvoids overwriting in-flight streams.
apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (5)
1-3: LGTM! Clean import updates for the new UIMessage architecture.The imports correctly reference the new UIMessage type and UIMessageComponent, properly replacing the legacy Message and ChatMessage imports.
6-14: Well-structured prop interface with granular state flags.The new interface provides better control over streaming states with separate flags (
isSubmitted,isStreaming,isReady,isError) instead of the previous genericisGeneratingflag. This allows for more precise UI state management.
35-39: Good UI enhancement with the three-dot thinking indicator.The expansion from two to three dots provides better visual feedback, and the animation timing is well-balanced.
53-89: Excellent transition state handling for multi-part messages.The logic correctly handles the complex transitions between text and tool parts during streaming. The fallback condition provides good coverage for edge cases.
120-128: Clean component integration with proper prop forwarding.The UIMessageComponent is correctly integrated with all necessary props passed through.
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (3)
55-151: Good CSS implementation for text selection and styling.The comprehensive CSS rules properly handle text selection, typography, and user interaction. The explicit user-select rules ensure text is selectable across browsers.
238-243: Good flexible tool type detection.The code correctly handles both dynamic tools and specific tool types with proper fallback for tool name extraction.
312-313: Potential XSS vulnerability with JSON.stringify in DOM.Directly inserting JSON.stringify output into the DOM could pose security risks if the input contains malicious content.
Consider sanitizing the JSON output or using a dedicated JSON viewer component:
- {JSON.stringify(toolPart.input, null, 2)} + {JSON.stringify(toolPart.input, null, 2) + .replace(/</g, '<') + .replace(/>/g, '>')}Likely an incorrect or invalid review comment.
apps/desktop/src/components/right-panel/views/chat-view.tsx (3)
112-120: Good implementation of pro-gate license checking.The license check properly limits free users to 4 messages and logs analytics events appropriately.
172-197: Good error handling in markdown application.The function properly checks for session existence and handles errors gracefully with try-catch.
199-202: Clean derivation of UI state flags.The status flags are cleanly derived from the status value, making the code more maintainable.
plugins/local-llm/src/server.rs (5)
40-56: Good architectural change with event emitter pattern.The addition of the emitter to ServerState provides a clean way to propagate LLM events without tight coupling.
166-178: Clean event propagation through progress callback.The progress events are properly routed through the emitter via the LLMEvent::Progress wrapper.
323-323: Good addition of progress callback parameter.The progress_fn parameter provides a clean abstraction for progress reporting without coupling to specific event systems.
484-487: Clean handling of progress events in streaming mode.The progress events are properly handled without generating unnecessary stream deltas.
218-218: Verify removal of tool filtering logic.plugins/local-llm/src/server.rs:218 now uses
request.tools.clone(); confirm the tools list is validated and compatible withhypr_llama::LlamaRequestandmodel.generate_stream_with_callback— reintroduce filtering/validation if unsupported tool entries can cause errors.
| useEffect(() => { | ||
| let unlisten: () => void; | ||
| localLlmEvents.llmEvent.listen(({ payload }) => { | ||
| if (payload.progress) { | ||
| setProgress(payload.progress); | ||
| } | ||
| }).then((fn) => { | ||
| unlisten = fn; | ||
| }); | ||
|
|
||
| return () => { | ||
| unlisten(); | ||
| }; | ||
| }, []); |
There was a problem hiding this comment.
Prevent cleanup crash and handle 0% progress correctly; optionally scope listener to local LLM
- unlisten is assigned asynchronously; calling it before assignment can throw.
if (payload.progress)skips valid 0 updates.- Optionally mount the listener only when using the local LLM to avoid unnecessary rerenders.
useEffect(() => {
- let unlisten: () => void;
- localLlmEvents.llmEvent.listen(({ payload }) => {
- if (payload.progress) {
- setProgress(payload.progress);
- }
- }).then((fn) => {
- unlisten = fn;
- });
-
- return () => {
- unlisten();
- };
-}, []);
+ if (!actualIsLocalLlm) return;
+ let unlisten: () => void = () => {};
+ localLlmEvents.llmEvent.listen(({ payload }) => {
+ if (typeof payload?.progress === "number") {
+ setProgress(payload.progress);
+ }
+ }).then((fn) => {
+ unlisten = fn;
+ });
+
+ return () => {
+ unlisten();
+ };
+}, [actualIsLocalLlm]);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/desktop/src/components/editor-area/index.tsx around lines 392 to 405,
the effect assigns unlisten asynchronously (risking calling undefined on
cleanup), treats 0 as falsy (skips valid 0 progress), and may register the
listener unnecessarily; fix by only mounting the listener when using the local
LLM (guard with the local-LLM condition if available), update the payload check
to test for progress !== undefined (so 0 is accepted), and make cleanup robust
by checking unlisten is a function before calling it (e.g., if (typeof unlisten
=== 'function') unlisten()) — also consider storing the returned unsubscribe
synchronously or cancelling registration on unmount if your event API supports
it.
| const htmlContent = (message.metadata as any)?.htmlContent; | ||
| const textContent = getTextContent(); |
There was a problem hiding this comment.
Add null check for message.metadata access.
The type casting (message.metadata as any)?.htmlContent is risky. Consider adding proper type guards or null checks.
- const htmlContent = (message.metadata as any)?.htmlContent;
+ const htmlContent = message.metadata && typeof message.metadata === 'object'
+ ? (message.metadata as Record<string, any>).htmlContent
+ : undefined;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const htmlContent = (message.metadata as any)?.htmlContent; | |
| const textContent = getTextContent(); | |
| const htmlContent = message.metadata && typeof message.metadata === 'object' | |
| ? (message.metadata as Record<string, any>).htmlContent | |
| : undefined; | |
| const textContent = getTextContent(); |
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/components/chat/ui-message.tsx around
lines 179-180, the current access (message.metadata as any)?.htmlContent assumes
message and metadata are non-null; add a null/type guard before reading
htmlContent — for example verify message and message.metadata exist (or use
optional chaining on message: message?.metadata?.htmlContent), narrow the
metadata type instead of casting to any, and provide a safe fallback (e.g.,
undefined or empty string) so htmlContent is only used when present.
| {JSON.stringify(toolPart.input, null, 2)} | ||
| </pre> |
There was a problem hiding this comment.
Apply the same XSS protection to other JSON.stringify instances.
The same security concern applies to these JSON.stringify calls.
Apply the same sanitization as suggested above to all JSON.stringify instances in the component.
Also applies to: 422-423
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/components/chat/ui-message.tsx around
lines 386-387 (also apply to 422-423), the direct JSON.stringify outputs need
the same XSS sanitization used elsewhere in this component; find each
JSON.stringify instance and wrap its result with the existing sanitize/escape
helper (the same function used earlier in the file) before rendering, and if
those strings are injected via dangerouslySetInnerHTML ensure you pass the
sanitized value into __html; alternatively render the sanitized string as text
inside <pre> to avoid raw HTML injection.
| conversations.map(async (conv) => { | ||
| const messages = await dbCommands.listMessagesV2(conv.id); | ||
| const firstUserMessage = messages.find(msg => msg.role === "user"); | ||
|
|
||
| const mostRecentTimestamp = messages.length > 0 | ||
| ? Math.max(...messages.map(msg => new Date(msg.created_at).getTime())) | ||
| : new Date(conv.created_at).getTime(); | ||
|
|
||
| return { | ||
| ...conv, | ||
| firstMessage: firstUserMessage ? (JSON.parse(firstUserMessage.parts)[0]?.text || "") : "", | ||
| mostRecentTimestamp, | ||
| }; | ||
| }), | ||
| ); | ||
|
|
||
| return conversationsWithPreview; | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
Potential crash on unguarded JSON.parse for firstMessage
JSON.parse(firstUserMessage.parts) can throw and fail the entire conversations query. Messages parsing later is guarded; consider consistent parsing here.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the file and show surrounding context
rg -n "useChatQueries2\.ts|listMessagesV2" || true
echo "---- show file content ----"
sed -n '1,240p' apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts || true
echo "---- grep for JSON.parse and .parts usage ----"
rg -n "JSON.parse|\.parts\b" || true
echo "---- search for listMessagesV2 usages ----"
rg -n "listMessagesV2" || true
echo "---- show messages handling elsewhere (try/catch around JSON.parse) ----"
rg -n --hidden "try\s*\{[^}]*JSON.parse" -S || trueLength of output: 31476
Guard JSON.parse for conversation previews (avoid crashing on malformed parts)
- JSON.parse(firstUserMessage.parts) is unguarded and can throw — fix in apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts (line ~43) by wrapping parse in try/catch or using a safe-parse fallback like the messagesQuery implementation.
- Also guard apps/desktop/src/components/right-panel/views/chat-view.tsx (line ~87) where JSON.parse(msg.parts) is used unguarded.
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/hooks/useChatQueries2.ts around lines
33-50 and apps/desktop/src/components/right-panel/views/chat-view.tsx around
line ~87, the code calls JSON.parse on message.parts without validation which
can throw on malformed JSON; update both locations to safely parse parts by
implementing a small safeParseParts helper or inline try/catch that returns an
empty array/string fallback on parse error (mirroring messagesQuery behavior),
then use the parsed result (e.g., first element text) only when parse succeeds;
ensure no exceptions propagate and preserve existing fallback values when parts
are invalid or absent.
| if (mention.type === "human") { | ||
| const humanData = await dbCommands.getHuman(mention.id); | ||
| if (humanData) { | ||
| let humanContent = ""; | ||
| humanContent += "Name: " + humanData?.full_name + "\n"; | ||
| humanContent += "Email: " + humanData?.email + "\n"; | ||
| humanContent += "Job Title: " + humanData?.job_title + "\n"; | ||
| humanContent += "LinkedIn: " + humanData?.linkedin_username + "\n"; | ||
|
|
||
| // Add recent sessions for this person | ||
| if (humanData?.full_name) { | ||
| try { | ||
| const participantSessions = await dbCommands.listSessions({ | ||
| type: "search", | ||
| query: humanData.full_name, | ||
| user_id: userId || "", | ||
| limit: 5, | ||
| }); | ||
|
|
||
| if (participantSessions.length > 0) { | ||
| humanContent += "\nNotes this person participated in:\n"; | ||
| for (const session of participantSessions.slice(0, 2)) { | ||
| const participants = await dbCommands.sessionListParticipants(session.id); | ||
| const isParticipant = participants.some((p: any) => | ||
| p.full_name === humanData.full_name || p.email === humanData.email | ||
| ); | ||
|
|
||
| if (isParticipant) { | ||
| let briefContent = ""; | ||
| if (session.enhanced_memo_html && session.enhanced_memo_html.trim() !== "") { | ||
| // Strip HTML tags for brief content | ||
| briefContent = session.enhanced_memo_html.replace(/<[^>]*>/g, "").slice(0, 200) + "..."; | ||
| } else if (session.raw_memo_html && session.raw_memo_html.trim() !== "") { | ||
| briefContent = session.raw_memo_html.replace(/<[^>]*>/g, "").slice(0, 200) + "..."; | ||
| } | ||
| humanContent += `- "${session.title || "Untitled"}": ${briefContent}\n`; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| humanContent += `- "${session.title || "Untitled"}": ${briefContent}\n`; | ||
| } catch (error) { | ||
| console.error(`Error fetching notes for person "${humanData.full_name}":`, error); | ||
| } | ||
| } | ||
|
|
||
| processedMentions.push({ | ||
| type: "human", | ||
| label: mention.label, | ||
| content: humanContent, | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| console.error(`Error fetching notes for person "${humanData.full_name}":`, error); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Optimize database queries for human mentions.
The code makes sequential database calls inside loops (lines 243-268). When multiple humans are mentioned, this could lead to N+1 query problems and slow performance.
Consider batching the participant session queries or implementing caching to reduce database round trips when processing multiple human mentions.
| useEffect(() => { | ||
| const loadMessages = async () => { | ||
| if (currentConversationId) { | ||
| try { | ||
| const { commands } = await import("@hypr/plugin-db"); | ||
| const dbMessages = await commands.listMessagesV2(currentConversationId); | ||
|
|
||
| const uiMessages = dbMessages.map(msg => ({ | ||
| id: msg.id, | ||
| role: msg.role as "user" | "assistant" | "system", | ||
| parts: JSON.parse(msg.parts), | ||
| metadata: msg.metadata ? JSON.parse(msg.metadata) : {}, | ||
| })); | ||
|
|
||
| setMessages(uiMessages); | ||
| } catch (error) { | ||
| console.error("Failed to load messages:", error); | ||
| } | ||
| } else { | ||
| setMessages([]); | ||
| } | ||
| }; | ||
|
|
||
| loadMessages(); | ||
| }, [currentConversationId, setMessages]); |
There was a problem hiding this comment.
Add cleanup for message loading to prevent memory leaks.
The effect should handle component unmounting to prevent state updates after unmount.
useEffect(() => {
+ let isMounted = true;
const loadMessages = async () => {
if (currentConversationId) {
try {
const { commands } = await import("@hypr/plugin-db");
const dbMessages = await commands.listMessagesV2(currentConversationId);
const uiMessages = dbMessages.map(msg => ({
id: msg.id,
role: msg.role as "user" | "assistant" | "system",
parts: JSON.parse(msg.parts),
metadata: msg.metadata ? JSON.parse(msg.metadata) : {},
}));
- setMessages(uiMessages);
+ if (isMounted) {
+ setMessages(uiMessages);
+ }
} catch (error) {
console.error("Failed to load messages:", error);
}
} else {
- setMessages([]);
+ if (isMounted) {
+ setMessages([]);
+ }
}
};
loadMessages();
+ return () => {
+ isMounted = false;
+ };
}, [currentConversationId, setMessages]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| const loadMessages = async () => { | |
| if (currentConversationId) { | |
| try { | |
| const { commands } = await import("@hypr/plugin-db"); | |
| const dbMessages = await commands.listMessagesV2(currentConversationId); | |
| const uiMessages = dbMessages.map(msg => ({ | |
| id: msg.id, | |
| role: msg.role as "user" | "assistant" | "system", | |
| parts: JSON.parse(msg.parts), | |
| metadata: msg.metadata ? JSON.parse(msg.metadata) : {}, | |
| })); | |
| setMessages(uiMessages); | |
| } catch (error) { | |
| console.error("Failed to load messages:", error); | |
| } | |
| } else { | |
| setMessages([]); | |
| } | |
| }; | |
| loadMessages(); | |
| }, [currentConversationId, setMessages]); | |
| useEffect(() => { | |
| let isMounted = true; | |
| const loadMessages = async () => { | |
| if (currentConversationId) { | |
| try { | |
| const { commands } = await import("@hypr/plugin-db"); | |
| const dbMessages = await commands.listMessagesV2(currentConversationId); | |
| const uiMessages = dbMessages.map(msg => ({ | |
| id: msg.id, | |
| role: msg.role as "user" | "assistant" | "system", | |
| parts: JSON.parse(msg.parts), | |
| metadata: msg.metadata ? JSON.parse(msg.metadata) : {}, | |
| })); | |
| if (isMounted) { | |
| setMessages(uiMessages); | |
| } | |
| } catch (error) { | |
| console.error("Failed to load messages:", error); | |
| } | |
| } else { | |
| if (isMounted) { | |
| setMessages([]); | |
| } | |
| } | |
| }; | |
| loadMessages(); | |
| return () => { | |
| isMounted = false; | |
| }; | |
| }, [currentConversationId, setMessages]); |
🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/views/chat-view.tsx around lines 77
to 101, the useEffect that loads messages can update state after the component
unmounts; add a cancellation mechanism and cleanup to avoid memory leaks: create
a let cancelled = false (or use an AbortController) at the top of the effect,
check cancelled before calling setMessages (and before any other state updates)
inside loadMessages, and return a cleanup function that sets cancelled = true
(or aborts the controller) so in-flight async work won’t call setMessages after
unmount.
| "SELECT * FROM chat_messages_v2 | ||
| WHERE conversation_id = ? | ||
| ORDER BY created_at ASC", | ||
| vec![conversation_id.into()], |
There was a problem hiding this comment.
🛠️ Refactor suggestion
🧩 Analysis chain
Add index for list ordering/filtering
For WHERE conversation_id = ? ORDER BY created_at ASC, add an index on (conversation_id, created_at).
🏁 Script executed:
#!/bin/bash
# Verify index presence aiding list_messages_v2
rg -n -C2 -e 'CREATE\s+INDEX.*chat_messages_v2' -e 'CREATE\s+INDEX.*(conversation_id|created_at)' --passthruLength of output: 278443
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Searching for occurrences of `chat_messages_v2` =="
rg -n --hidden -S '\bchat_messages_v2\b' || echo "no matches for chat_messages_v2"
echo
echo "== Searching for any CREATE INDEX statements =="
rg -n --hidden -S -P 'CREATE\s+(UNIQUE\s+)?INDEX\b' || echo "no CREATE INDEX statements found"
echo
echo "== Searching for conversation_id and created_at usages =="
rg -n --hidden -S '\bconversation_id\b' -C2 || echo "no matches for conversation_id"
echo
rg -n --hidden -S '\bcreated_at\b' -C2 || echo "no matches for created_at"
echo
echo "== Searching for migration/schema files (common names) =="
rg -n --hidden -S '(^|\b)(migrations|schema\.sql|migration)\b' || echo "no obvious migration/schema filenames found"Length of output: 51791
Add composite index (conversation_id, created_at) to chat_messages_v2
No CREATE INDEX found in migrations — add to crates/db-user/src/chat_messages_v2_migration.sql (or a new migration):
CREATE INDEX IF NOT EXISTS idx_chat_messages_v2_conversation_created_at ON chat_messages_v2(conversation_id, created_at);
🤖 Prompt for AI Agents
In crates/db-user/src/chat_messages_v2_ops.rs around lines 42 to 45, queries
filter and order by conversation_id and created_at but there is no composite
index; add a migration file (either update
crates/db-user/src/chat_messages_v2_migration.sql or create a new migration in
the migrations directory) that creates a composite index on (conversation_id,
created_at) for chat_messages_v2 (use IF NOT EXISTS to be idempotent), then
run/apply the migrations and update any migration manifest so the index is
applied in all environments.
|
|
||
| #[tauri::command] | ||
| #[specta::specta] | ||
| #[tracing::instrument(skip(state))] |
There was a problem hiding this comment.
Prevent logging PII/content: skip sensitive args in tracing instrumentation
By default, instrument records arguments. Skip user/session/message data to avoid leaking chat content.
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, conversation))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, session_id))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, message))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, conversation_id))]
@@
-#[tracing::instrument(skip(state))]
+#[tracing::instrument(skip(state, id, parts))]Also applies to: 25-25, 45-45, 65-65, 85-85
🤖 Prompt for AI Agents
In plugins/db/src/commands/chats_v2.rs at lines 5, 25, 45, 65 and 85, the
#[tracing::instrument] annotations currently record function arguments (which
may include PII/chat content); update each macro to skip sensitive parameters by
adding a skip list that includes state plus any user/session/message-related
args (for example: #[tracing::instrument(skip(state, user, user_id, session,
session_id, message, messages, content))]). Ensure you list the actual parameter
names used in each function so tracing will not log chat content or identifiers.
No description provided.