Skip to content

Chat UI refactor#1532

Closed
duckduckhero wants to merge 21 commits intomainfrom
chat-ui-refactor
Closed

Chat UI refactor#1532
duckduckhero wants to merge 21 commits intomainfrom
chat-ui-refactor

Conversation

@duckduckhero
Copy link
Contributor

No description provided.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 4, 2025

📝 Walkthrough

Walkthrough

Introduces a new useChatInput hook and rewires the desktop ChatView to use it. Extracts EmptyChatState UI into the UI package. Refactors chat input, message rendering, and markdown handling across UI components. Adds types/utilities, updates ChatModelInfoModal API, and propagates sessionTitle in selection data. Updates Tailwind content and UI package dependencies.

Changes

Cohort / File(s) Summary
Desktop Right Panel — Hook & Integration
apps/desktop/src/components/right-panel/hooks/useChatInput.ts, apps/desktop/src/components/right-panel/chat-view.tsx
Adds useChatInput (queries LLM state, entity data, mention search, selection processing, modal state). ChatView integrates hook, wires convertMarkdownToHtml, and handles model selection navigation.
Desktop Right Panel — Chat Exports & Empty State
apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx, apps/desktop/src/components/right-panel/components/chat/index.ts
EmptyChatState now a plain function delegating UI to UI package. Index stops re-exporting multiple chat modules; explicitly exports EmptyChatState.
UI Package — Chat Input & Rendering
packages/ui/src/components/chat/chat-input.tsx, packages/ui/src/components/chat/ui-message.tsx, packages/ui/src/components/chat/chat-messages-view.tsx, packages/ui/src/components/chat/markdown-card.tsx, packages/ui/src/components/chat/chat-model-info-modal.tsx, packages/ui/src/components/chat/empty-chat-state.tsx, packages/ui/src/components/chat/utils/markdown-parser.ts, packages/ui/src/components/chat/thinking-indicator.tsx
Refactors ChatInput to be editor-centric and prop-driven (selection processing, mention search, model modal). UIMessage supports markdown parsing/blocks and optional conversion. ChatMessagesView passes markdown utilities and uses shared ThinkingIndicator. MarkdownCard accepts optional converter. ChatModelInfoModal adds onChooseModel callback. New EmptyChatStateUI, markdown parser utility, and ThinkingIndicator.
UI Package — Types
packages/ui/src/components/chat/types.ts, packages/ui/src/components/chat/chat-types.ts
Adds public types for MessagePart/Message/ChatSession and ChatState/ActiveEntityInfo/BadgeType, with re-exports.
UI Package — Misc
packages/ui/src/components/chat/chat-history-view.tsx, packages/ui/tailwind.config.ts, packages/ui/package.json
Switches ChatHistoryItem import to package path. Adds chat components to Tailwind content. Adds dependencies: @hypr/tiptap and @hypr/utils.
Desktop Editor & Utils
apps/desktop/src/components/editor-area/text-selection-popover.tsx, packages/utils/src/contexts/right-panel.tsx
Adds sessionTitle to selection/AI payloads (via useSession). Extends SelectionData with optional sessionTitle.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ChatInput
  participant useChatInput
  participant Editor
  participant ModelModal

  rect rgb(245,248,255)
  note over useChatInput: Initialize queries and state
  useChatInput->>useChatInput: Fetch LLM connection / model / HyprCloud
  useChatInput->>useChatInput: Fetch entity data (note/human/org)
  end

  User->>ChatInput: Type / Mention / Submit
  ChatInput->>useChatInput: handleMentionSearch(query, editor)
  useChatInput-->>ChatInput: results

  ChatInput->>useChatInput: processSelection(pendingSelection)
  useChatInput-->>ChatInput: {id, html, text}

  ChatInput->>useChatInput: handleSubmit(mentionedContent, selectionData, htmlContent)
  useChatInput->>useChatInput: clearPendingSelection()

  User->>ModelModal: Choose model
  ModelModal->>ChatInput: onChooseModel()
  ChatInput-->>ChatView: onChooseModel()
  note over ChatView: Open settings and navigate to AI-LLM
Loading
sequenceDiagram
  autonumber
  participant MessagesView
  participant UIMessage
  participant MarkdownParser
  participant MarkdownCard
  participant Converter

  MessagesView->>UIMessage: Render message parts
  alt text part
    UIMessage->>MarkdownParser: parseMarkdownBlocks(text)
    MarkdownParser-->>UIMessage: [parts]
    loop each parsed part
      alt markdown block
        UIMessage->>MarkdownCard: render(content, isComplete, convertMarkdownToHtml?)
        opt converter provided
          MarkdownCard->>Converter: convertMarkdownToHtml(markdown)
          Converter-->>MarkdownCard: html
        end
      else plain text
        UIMessage-->>MessagesView: render TextContent (with optional converter)
      end
    end
  else non-text part
    UIMessage-->>MessagesView: render accordingly
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • yujonglee

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The pull request description is currently empty and does not provide any context or summary of the extensive refactoring work performed. Please add a concise description outlining the main objectives of this refactor, such as the introduction of the new useChatInput hook, enhancements to markdown rendering in chat messages, and updates to chat input and empty state components, so reviewers can quickly understand the scope and intent of the changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title “Chat UI refactor” succinctly captures the primary purpose of the extensive updates to chat-related components, hooks, and data flow across both the desktop application and UI packages, clearly signaling a broad refactoring effort.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chat-ui-refactor

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24bb1eb and c356c6a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/desktop/src/components/editor-area/text-selection-popover.tsx (3 hunks)
  • apps/desktop/src/components/right-panel/chat-view.tsx (5 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1 hunks)
  • packages/ui/src/components/chat/chat-input.tsx (10 hunks)
  • packages/ui/tailwind.config.ts (1 hunks)
  • packages/utils/src/contexts/right-panel.tsx (1 hunks)
🧰 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:

  • packages/utils/src/contexts/right-panel.tsx
  • packages/ui/src/components/chat/chat-input.tsx
  • apps/desktop/src/components/right-panel/chat-view.tsx
  • packages/ui/tailwind.config.ts
  • apps/desktop/src/components/editor-area/text-selection-popover.tsx
  • apps/desktop/src/components/right-panel/hooks/useChatInput.ts
🧬 Code graph analysis (4)
packages/ui/src/components/chat/chat-input.tsx (1)
packages/utils/src/contexts/right-panel.tsx (1)
  • SelectionData (4-11)
apps/desktop/src/components/right-panel/chat-view.tsx (2)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1)
  • useChatInput (19-196)
packages/ui/src/components/chat/chat-input.tsx (1)
  • ChatInput (39-392)
apps/desktop/src/components/editor-area/text-selection-popover.tsx (1)
packages/utils/src/contexts/sessions.tsx (1)
  • useSession (49-74)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (3)
apps/desktop/src/contexts/hypr.tsx (1)
  • useHypr (63-69)
packages/utils/src/contexts/right-panel.tsx (1)
  • useRightPanel (113-119)
packages/db/src/schema.ts (2)
  • sessions (34-42)
  • humans (13-18)
🪛 ast-grep (0.39.5)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts

[warning] 151-151: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 151-151: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

⏰ 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 (windows, windows-latest)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (1)
packages/ui/tailwind.config.ts (1)

150-150: LGTM!

The addition of the chat components path to Tailwind's content array is correct and follows the existing pattern used for UI and block components. This change ensures Tailwind will scan and generate utility classes for the new chat components introduced in this refactor.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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/components/chat/chat-input.tsx (1)

205-223: Restore parent onKeyDown propagation

The refactor now captures editor keydown events but never forwards them to the onKeyDown callback, so any parent logic (shortcuts, analytics, etc.) tied to that prop no longer runs. Please propagate the event after the internal handling.

       const handleKeyDown = (event: KeyboardEvent) => {
         if (event.metaKey || event.ctrlKey) {
           if (["b", "i", "u", "k"].includes(event.key.toLowerCase())) {
             event.preventDefault();
             return;
           }
         }
 
         if (event.key === "Enter" && !event.shiftKey) {
           event.preventDefault();
 
           if (inputValue.trim()) {
             handleSubmit();
           }
         }
+
+        onKeyDown?.(event as unknown as React.KeyboardEvent<HTMLTextAreaElement>);
       };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f78c2c and 06d4aa2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (6 hunks)
  • apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (2 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (1 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/components/chat/index.ts (1 hunks)
  • packages/ui/src/components/chat/ui-message.tsx (1 hunks)
🧰 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:

  • packages/ui/src/components/chat/index.ts
  • packages/ui/src/components/chat/ui-message.tsx
  • apps/desktop/src/components/right-panel/hooks/useChatInput.ts
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx
  • apps/desktop/src/components/right-panel/components/chat/ui-message.tsx
🧬 Code graph analysis (4)
packages/ui/src/components/chat/ui-message.tsx (3)
apps/desktop/src/components/right-panel/utils/markdown-parser.ts (2)
  • ParsedPart (2-6)
  • parseMarkdownBlocks (8-67)
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (1)
  • UIMessageComponent (15-30)
apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)
  • MarkdownCard (15-140)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (3)
apps/desktop/src/contexts/hypr.tsx (1)
  • useHypr (63-69)
apps/desktop/src/contexts/right-panel.tsx (1)
  • useRightPanel (230-236)
packages/db/src/schema.ts (2)
  • sessions (27-34)
  • humans (13-18)
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (2)
apps/desktop/src/contexts/right-panel.tsx (1)
  • useRightPanel (230-236)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1)
  • useChatInput (18-194)
apps/desktop/src/components/right-panel/components/chat/ui-message.tsx (3)
packages/ui/src/components/chat/ui-message.tsx (1)
  • UIMessageComponent (177-509)
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)
🪛 ast-grep (0.39.5)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts

[warning] 149-149: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 149-149: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

⏰ 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)

Comment on lines 49 to 68
let html = await convertMarkdownToHtml(content);

// Clean up empty paragraphs
html = html
.replace(/<p>\s*<\/p>/g, "")
.replace(/<p>\u00A0<\/p>/g, "")
.replace(/<p>&nbsp;<\/p>/g, "")
.replace(/<p>\s+<\/p>/g, "")
.replace(/<p> <\/p>/g, "")
.trim();

setDisplayHtml(html);
} catch (error) {
console.error("Failed to convert markdown:", error);
setDisplayHtml(content);
}
} else {
// Fallback: display as plain text in a paragraph
setDisplayHtml(`<p>${content}</p>`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sanitize TextContent fallbacks to avoid XSS.

When convertMarkdownToHtml throws or isn't supplied we drop raw content into Renderer, which will render unsanitized HTML and allows script injection. Escape the fallback path before handing it to Renderer.

+const ESCAPE_HTML_MAP: Record<string, string> = {
+  "&": "&amp;",
+  "<": "&lt;",
+  ">": "&gt;",
+  '"': "&quot;",
+  "'": "&#39;",
+};
+
+const escapeHtml = (value: string) =>
+  value.replace(/[&<>"']/g, (char) => ESCAPE_HTML_MAP[char] ?? char);
+
 const TextContent: FC<{
   content: string;
   isHtml?: boolean;
   convertMarkdownToHtml?: (markdown: string) => Promise<string>;
 }> = ({ content, isHtml, convertMarkdownToHtml }) => {
   const [displayHtml, setDisplayHtml] = useState<string>("");
@@
       if (convertMarkdownToHtml) {
         try {
           let html = await convertMarkdownToHtml(content);
@@
           setDisplayHtml(html);
         } catch (error) {
           console.error("Failed to convert markdown:", error);
-          setDisplayHtml(content);
+          setDisplayHtml(`<p>${escapeHtml(content)}</p>`);
         }
       } else {
         // Fallback: display as plain text in a paragraph
-        setDisplayHtml(`<p>${content}</p>`);
+        setDisplayHtml(`<p>${escapeHtml(content)}</p>`);
       }
     };
📝 Committable suggestion

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

Suggested change
let html = await convertMarkdownToHtml(content);
// Clean up empty paragraphs
html = html
.replace(/<p>\s*<\/p>/g, "")
.replace(/<p>\u00A0<\/p>/g, "")
.replace(/<p>&nbsp;<\/p>/g, "")
.replace(/<p>\s+<\/p>/g, "")
.replace(/<p> <\/p>/g, "")
.trim();
setDisplayHtml(html);
} catch (error) {
console.error("Failed to convert markdown:", error);
setDisplayHtml(content);
}
} else {
// Fallback: display as plain text in a paragraph
setDisplayHtml(`<p>${content}</p>`);
}
const ESCAPE_HTML_MAP: Record<string, string> = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
};
const escapeHtml = (value: string) =>
value.replace(/[&<>"']/g, (char) => ESCAPE_HTML_MAP[char] ?? char);
const TextContent: FC<{
content: string;
isHtml?: boolean;
convertMarkdownToHtml?: (markdown: string) => Promise<string>;
}> = ({ content, isHtml, convertMarkdownToHtml }) => {
const [displayHtml, setDisplayHtml] = useState<string>("");
useEffect(() => {
const renderContent = async () => {
if (convertMarkdownToHtml) {
try {
let html = await convertMarkdownToHtml(content);
// Clean up empty paragraphs
html = html
.replace(/<p>\s*<\/p>/g, "")
.replace(/<p>\u00A0<\/p>/g, "")
.replace(/<p>&nbsp;<\/p>/g, "")
.replace(/<p>\s+<\/p>/g, "")
.replace(/<p> <\/p>/g, "")
.trim();
setDisplayHtml(html);
} catch (error) {
console.error("Failed to convert markdown:", error);
setDisplayHtml(`<p>${escapeHtml(content)}</p>`);
}
} else {
// Fallback: display as plain text in a paragraph
setDisplayHtml(`<p>${escapeHtml(content)}</p>`);
}
};
renderContent();
}, [content, convertMarkdownToHtml]);
return <Renderer html={displayHtml} />;
};
🤖 Prompt for AI Agents
In packages/ui/src/components/chat/ui-message.tsx around lines 49 to 68, the
fallback paths setDisplayHtml(content) and setDisplayHtml(`<p>${content}</p>`)
inject raw user content into the renderer and allow XSS; sanitize or escape the
content before calling setDisplayHtml. Replace those fallbacks to either run the
content through the same HTML sanitizer used elsewhere (e.g.,
DOMPurify.sanitize) or escape HTML entities (convert <, >, &, ", ' to entities)
and then wrap in a safe paragraph, and update imports to use the project's
sanitizer utility or add a small escapeHtml helper; ensure you sanitize both the
error catch fallback and the plain-text fallback so no raw HTML is ever passed
to setDisplayHtml.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

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

28-37: TS compile issue: React.MouseEvent type used without import; also unused param.

The returned handler types React.MouseEvent but React isn’t imported as a type. Since the event isn’t used, drop it to avoid the import and keep types tight.

-  const handleButtonClick = useCallback((prompt: string, analyticsEvent: string) => (e: React.MouseEvent) => {
+  const handleButtonClick = useCallback((prompt: string, analyticsEvent: string) => () => {
     if (userId) {
       analyticsCommands.event({
         event: analyticsEvent,
         distinct_id: userId,
       });
     }

     onQuickAction(prompt);
   }, [onQuickAction, userId]);
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (1)

204-241: Duplicate Enter handling; possible double submit and dropdown bypass.

You handle Enter in both TipTap’s handleKeyDown (Lines 174-202) and a DOM keydown listener here. The DOM listener:

  • May double-trigger submit.
  • Ignores the mention dropdown check present in the TipTap handler.

Remove this effect and rely on the TipTap handler.

-  useEffect(() => {
-    const editor = editorRef.current?.editor;
-    if (editor) {
-      const handleKeyDown = (event: KeyboardEvent) => {
-        if (event.metaKey || event.ctrlKey) {
-          if (["b", "i", "u", "k"].includes(event.key.toLowerCase())) {
-            event.preventDefault();
-            return;
-          }
-        }
-
-        if (event.key === "Enter" && !event.shiftKey) {
-          event.preventDefault();
-
-          if (inputValue.trim()) {
-            handleSubmit();
-          }
-        }
-      };
-
-      const handleClick = (event: MouseEvent) => {
-        const target = event.target as HTMLElement;
-        if (target && (target.classList.contains("mention") || target.closest(".mention"))) {
-          event.preventDefault();
-          event.stopPropagation();
-          return false;
-        }
-      };
-
-      editor.view.dom.addEventListener("keydown", handleKeyDown);
-      editor.view.dom.addEventListener("click", handleClick);
-
-      return () => {
-        editor.view.dom.removeEventListener("keydown", handleKeyDown);
-        editor.view.dom.removeEventListener("click", handleClick);
-      };
-    }
-  }, [editorRef.current?.editor, onKeyDown, handleSubmit, inputValue]);
+  // (Removed duplicate DOM keydown/click listeners; TipTap handleKeyDown covers Enter + dropdown cases.)
🧹 Nitpick comments (4)
apps/desktop/src/components/right-panel/components/chat/index.ts (1)

6-6: Redundant re-export; prefer a single pattern.

Line 6 re-exports EmptyChatState that is already covered by export * from "./empty-chat-state" on Line 5. Keep one to avoid duplication noise.

Apply:

-export { EmptyChatState } from "./empty-chat-state";
packages/ui/src/components/chat/empty-chat-state.tsx (2)

19-21: Don’t destructure unused props to satisfy “no unused vars”.

onQuickAction, onFocusInput, sessionId, userId aren’t used in this UI-only component.

-    { onQuickAction, onFocusInput, sessionId, userId, handleButtonClick, handleContainerClick, quickActions }:
+    { handleButtonClick, handleContainerClick, quickActions }:
       EmptyChatStateProps,

110-118: Prefer stable keys over array index (optional).

If possible, derive a stable key (e.g., eventName or a composite) to avoid re-mounts on reorders.

-          {quickActions.map((action, index) => (
+          {quickActions.map((action) => (
             <button
-              key={index}
+              key={`${action.eventName}:${action.actualPrompt}`}
               onClick={handleButtonClick(action.actualPrompt, action.eventName)}
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1)

25-45: Polling frequency consideration (optional).

Multiple queries poll every 5s. Consider adding staleTime and/or increasing intervals to reduce load.

Example:

-  const llmConnectionQuery = useQuery({ ..., refetchInterval: 5000 });
+  const llmConnectionQuery = useQuery({ ..., staleTime: 10_000, refetchInterval: 15_000 });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06d4aa2 and c3f6c96.

⛔ Files ignored due to path filters (3)
  • apps/desktop/src/locales/en/messages.po is excluded by !**/*.po
  • apps/desktop/src/locales/ko/messages.po is excluded by !**/*.po
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (6 hunks)
  • apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx (4 hunks)
  • apps/desktop/src/components/right-panel/components/chat/index.ts (1 hunks)
  • apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (1 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/components/chat/empty-chat-state.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
  • packages/ui/package.json
🧰 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
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx
  • apps/desktop/src/components/right-panel/hooks/useChatInput.ts
  • packages/ui/src/components/chat/empty-chat-state.tsx
  • apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (2)
packages/utils/src/contexts/right-panel.tsx (1)
  • useRightPanel (112-118)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1)
  • useChatInput (19-195)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (3)
apps/desktop/src/contexts/hypr.tsx (1)
  • useHypr (63-69)
packages/utils/src/contexts/right-panel.tsx (1)
  • useRightPanel (112-118)
packages/db/src/schema.ts (2)
  • sessions (34-42)
  • humans (13-18)
apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx (2)
apps/desktop/src/components/right-panel/utils/dynamic-quickactions.ts (1)
  • getDynamicQuickActions (33-95)
packages/ui/src/components/chat/empty-chat-state.tsx (1)
  • EmptyChatStateUI (17-123)
🪛 ast-grep (0.39.5)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts

[warning] 150-150: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 150-150: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

⏰ 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 (2)
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (1)

174-202: Do we still need the onKeyDown prop?

onKeyDown is no longer invoked. Confirm if deprecation is intended. If not, call it inside the TipTap handleKeyDown path before custom logic.

Option:

           handleKeyDown: (view, event) => {
+            // Bubble to external handler if provided
+            if (typeof onKeyDown === "function") {
+              // Cast to satisfy TS; external consumers expect React event-like shape.
+              onKeyDown(event as unknown as React.KeyboardEvent<HTMLTextAreaElement>);
+            }
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1)

73-77: full_name property validated plugin-db’s Human type includes full_name (string | null) and DB schema/migrations/seeds all use full_name, so humanData?.full_name is correct.

Comment on lines 141 to 172
const processSelection = useCallback((selection: any) => {
if (!selection) {
return null;
}

const noteName = noteData?.title || humanData?.full_name || organizationData?.name || "Note";
const selectedHtml = selection.text || "";

// Strip HTML tags
const temp = document.createElement("div");
temp.innerHTML = selectedHtml;
const selectedText = (temp.textContent || temp.innerText || "").trim();

const textPreview = selectedText.length > 0
? (selectedText.length > 6
? `'${selectedText.slice(0, 6)}...'`
: `'${selectedText}'`)
: "NO_TEXT";

const selectionRef = textPreview !== "NO_TEXT"
? `[${noteName} - ${textPreview}(${selection.startOffset}:${selection.endOffset})]`
: `[${noteName} - ${selection.startOffset}:${selection.endOffset}]`;

const escapedSelectionRef = selectionRef.replace(/"/g, "&quot;");

return {
html:
`<a class="mention selection-ref" data-mention="true" data-id="selection-${selection.startOffset}-${selection.endOffset}" data-type="selection" data-label="${escapedSelectionRef}" contenteditable="false">${selectionRef}</a> `,
text: selectionRef,
id: `${selection.startOffset}-${selection.endOffset}-${selection.timestamp}`,
};
}, [noteData?.title, humanData?.full_name, organizationData?.name]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

XSS/sanitization: building HTML with unescaped text and innerHTML usage.

  • Using innerHTML to parse user-provided HTML is flagged; also, selectionRef is injected as inner HTML without escaping, allowing malformed markup injection.

Apply safer parsing and escaping for both attribute and inner text:

-  const processSelection = useCallback((selection: any) => {
+  const processSelection = useCallback((selection: any) => {
     if (!selection) {
       return null;
     }

     const noteName = noteData?.title || humanData?.full_name || organizationData?.name || "Note";
     const selectedHtml = selection.text || "";

-    // Strip HTML tags
-    const temp = document.createElement("div");
-    temp.innerHTML = selectedHtml;
-    const selectedText = (temp.textContent || temp.innerText || "").trim();
+    // Extract plain text safely (avoid innerHTML on unsanitized content)
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(selectedHtml, "text/html");
+    const selectedText = (doc.body.textContent || "").trim();

     const textPreview = selectedText.length > 0
       ? (selectedText.length > 6
         ? `'${selectedText.slice(0, 6)}...'`
         : `'${selectedText}'`)
       : "NO_TEXT";

     const selectionRef = textPreview !== "NO_TEXT"
       ? `[${noteName} - ${textPreview}(${selection.startOffset}:${selection.endOffset})]`
       : `[${noteName} - ${selection.startOffset}:${selection.endOffset}]`;

-    const escapedSelectionRef = selectionRef.replace(/"/g, "&quot;");
+    const escapeAttr = (s: string) =>
+      s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+    const escapeText = (s: string) =>
+      s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+    const escapedLabel = escapeAttr(selectionRef);
+    const escapedInner = escapeText(selectionRef);

     return {
-      html:
-        `<a class="mention selection-ref" data-mention="true" data-id="selection-${selection.startOffset}-${selection.endOffset}" data-type="selection" data-label="${escapedSelectionRef}" contenteditable="false">${selectionRef}</a> `,
+      html:
+        `<a class="mention selection-ref" data-mention="true" data-id="selection-${selection.startOffset}-${selection.endOffset}" data-type="selection" data-label="${escapedLabel}" contenteditable="false">${escapedInner}</a> `,
       text: selectionRef,
       id: `${selection.startOffset}-${selection.endOffset}-${selection.timestamp}`,
     };
-  }, [noteData?.title, humanData?.full_name, organizationData?.name]);
+  }, [noteData?.title, humanData?.full_name, organizationData?.name]);

Based on static analysis hints

📝 Committable suggestion

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

Suggested change
const processSelection = useCallback((selection: any) => {
if (!selection) {
return null;
}
const noteName = noteData?.title || humanData?.full_name || organizationData?.name || "Note";
const selectedHtml = selection.text || "";
// Strip HTML tags
const temp = document.createElement("div");
temp.innerHTML = selectedHtml;
const selectedText = (temp.textContent || temp.innerText || "").trim();
const textPreview = selectedText.length > 0
? (selectedText.length > 6
? `'${selectedText.slice(0, 6)}...'`
: `'${selectedText}'`)
: "NO_TEXT";
const selectionRef = textPreview !== "NO_TEXT"
? `[${noteName} - ${textPreview}(${selection.startOffset}:${selection.endOffset})]`
: `[${noteName} - ${selection.startOffset}:${selection.endOffset}]`;
const escapedSelectionRef = selectionRef.replace(/"/g, "&quot;");
return {
html:
`<a class="mention selection-ref" data-mention="true" data-id="selection-${selection.startOffset}-${selection.endOffset}" data-type="selection" data-label="${escapedSelectionRef}" contenteditable="false">${selectionRef}</a> `,
text: selectionRef,
id: `${selection.startOffset}-${selection.endOffset}-${selection.timestamp}`,
};
}, [noteData?.title, humanData?.full_name, organizationData?.name]);
const processSelection = useCallback((selection: any) => {
if (!selection) {
return null;
}
const noteName = noteData?.title || humanData?.full_name || organizationData?.name || "Note";
const selectedHtml = selection.text || "";
// Extract plain text safely (avoid innerHTML on unsanitized content)
const parser = new DOMParser();
const doc = parser.parseFromString(selectedHtml, "text/html");
const selectedText = (doc.body.textContent || "").trim();
const textPreview = selectedText.length > 0
? (selectedText.length > 6
? `'${selectedText.slice(0, 6)}...'`
: `'${selectedText}'`)
: "NO_TEXT";
const selectionRef = textPreview !== "NO_TEXT"
? `[${noteName} - ${textPreview}(${selection.startOffset}:${selection.endOffset})]`
: `[${noteName} - ${selection.startOffset}:${selection.endOffset}]`;
const escapeAttr = (s: string) =>
s.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const escapeText = (s: string) =>
s.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const escapedLabel = escapeAttr(selectionRef);
const escapedInner = escapeText(selectionRef);
return {
html:
`<a class="mention selection-ref"` +
` data-mention="true"` +
` data-id="selection-${selection.startOffset}-${selection.endOffset}"` +
` data-type="selection"` +
` data-label="${escapedLabel}"` +
` contenteditable="false">${escapedInner}</a> `,
text: selectionRef,
id: `${selection.startOffset}-${selection.endOffset}-${selection.timestamp}`,
};
}, [noteData?.title, humanData?.full_name, organizationData?.name]);
🧰 Tools
🪛 ast-grep (0.39.5)

[warning] 150-150: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 150-150: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: temp.innerHTML = selectedHtml
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

Comment on lines +3 to +15
interface EmptyChatStateProps {
onQuickAction: (prompt: string) => void;
onFocusInput: () => void;
sessionId?: string | null;
userId?: string | null;
handleButtonClick: (prompt: string, analyticsEvent: string) => (e: React.MouseEvent) => void;
handleContainerClick: () => void;
quickActions: Array<{
shownTitle: string;
actualPrompt: string;
eventName: string;
}>;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid React.MouseEvent type dependency and keep prop surface minimal.

  • Remove React.MouseEvent reference to prevent missing type import issues.
  • Keep props interface unchanged for compatibility, but type the click factory to return () => void.
 interface EmptyChatStateProps {
   onQuickAction: (prompt: string) => void;
   onFocusInput: () => void;
   sessionId?: string | null;
   userId?: string | null;
-  handleButtonClick: (prompt: string, analyticsEvent: string) => (e: React.MouseEvent) => void;
+  handleButtonClick: (prompt: string, analyticsEvent: string) => () => void;
   handleContainerClick: () => void;
   quickActions: Array<{
     shownTitle: string;
     actualPrompt: string;
     eventName: string;
   }>;
 }
📝 Committable suggestion

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

Suggested change
interface EmptyChatStateProps {
onQuickAction: (prompt: string) => void;
onFocusInput: () => void;
sessionId?: string | null;
userId?: string | null;
handleButtonClick: (prompt: string, analyticsEvent: string) => (e: React.MouseEvent) => void;
handleContainerClick: () => void;
quickActions: Array<{
shownTitle: string;
actualPrompt: string;
eventName: string;
}>;
}
interface EmptyChatStateProps {
onQuickAction: (prompt: string) => void;
onFocusInput: () => void;
sessionId?: string | null;
userId?: string | null;
handleButtonClick: (prompt: string, analyticsEvent: string) => () => void;
handleContainerClick: () => void;
quickActions: Array<{
shownTitle: string;
actualPrompt: string;
eventName: string;
}>;
}
🤖 Prompt for AI Agents
In packages/ui/src/components/chat/empty-chat-state.tsx around lines 3 to 15,
the handleButtonClick prop currently types its return as (e: React.MouseEvent)
=> void which forces a React type import and expands the prop surface; change
the signature to (prompt: string, analyticsEvent: string) => () => void to
remove the React.MouseEvent dependency and avoid needing to import React types,
and update any local implementations/call sites to accept/return a no-arg
function (or ignore the event parameter) so behavior stays the same while the
prop type is simplified.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (3)
apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx (1)

4-23: Wire up the new onChooseModel prop.

ChatModelInfoModalProps now requires onChooseModel, but the component never invokes it. Any consumer passing analytics/telemetry (or alternative navigation) logic will be ignored, so the new API surface is effectively broken.

Call the provided callback inside handleChooseModel before (or after) the built-in settings navigation so external code can piggyback on the event without losing the existing behavior.

-export function ChatModelInfoModal({ isOpen, onClose }: ChatModelInfoModalProps) {
-  const handleChooseModel = () => {
+export function ChatModelInfoModal({ isOpen, onClose, onChooseModel }: ChatModelInfoModalProps) {
+  const handleChooseModel = () => {
+    onChooseModel?.();
     windowsCommands.windowShow({ type: "settings" }).then(() => {
       setTimeout(() => {
         windowsCommands.windowEmitNavigate({ type: "settings" }, {
           path: "/app/settings",
           search: { tab: "ai-llm" },
         });
       }, 800);
     });
     onClose();
   };
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (2)

174-202: Remove ref from dependency array.

editorRef.current?.editor should not be in the dependency array because refs are not reactive. Changes to ref.current do not trigger re-renders, so the effect won't re-run when the editor instance changes. This can lead to stale closures.

Apply this diff:

     }
-  }, [editorRef.current?.editor, inputValue, handleSubmit]);
+  }, [inputValue, handleSubmit]);

If you need to react to editor initialization, consider using a state variable instead of a ref, or move the setup logic to where the editor is created.


204-241: Remove ref from dependency array.

Same issue as above: editorRef.current?.editor is not reactive and should not be in the dependency array.

Apply this diff:

       };
     }
-  }, [editorRef.current?.editor, onKeyDown, handleSubmit, inputValue]);
+  }, [onKeyDown, handleSubmit, inputValue]);
🧹 Nitpick comments (5)
apps/desktop/src/components/right-panel/views/chat-view.tsx (1)

19-24: Use JSX to render ChatViewInner.

We’re invoking ChatViewInner() as a plain function. React technically tolerates it, but you lose a real component boundary (DevTools tree readability, error stacks, potential hook linting/glitch scenarios). Prefer <ChatViewInner /> (or inline the body) so React continues to treat it as a component.

packages/ui/src/components/chat/chat-messages-view.tsx (1)

49-112: Avoid NodeJS.Timeout in browser UI code.

NodeJS.Timeout ties this component to Node’s ambient types. If the UI package ever drops @types/node, this will stop compiling. Stick with ReturnType<typeof setTimeout> to keep it environment-agnostic.

-  const thinkingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const thinkingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
packages/ui/src/components/chat/types.ts (1)

14-14: Prefer unknown over any for toolDetails.

Using any bypasses type checking. Consider using unknown or defining a specific type for toolDetails to maintain type safety.

Apply this diff:

-  toolDetails?: any;
+  toolDetails?: unknown;

If toolDetails has a known structure, define a proper interface instead.

packages/ui/src/components/chat/markdown-card.tsx (2)

11-11: Remove unused parameter.

The hasEnhancedNote parameter is defined but never used in the component. Consider removing it to reduce confusion.

Apply this diff:

 interface MarkdownCardProps {
   content: string;
   isComplete: boolean;
   sessionTitle?: string;
   onApplyMarkdown?: (markdownContent: string) => void;
-  hasEnhancedNote?: boolean;
   convertMarkdownToHtml?: (markdown: string) => Promise<string>;
 }
 
 export function MarkdownCard(
-  { content, isComplete, sessionTitle, onApplyMarkdown, hasEnhancedNote = false, convertMarkdownToHtml }:
+  { content, isComplete, sessionTitle, onApplyMarkdown, convertMarkdownToHtml }:
     MarkdownCardProps,
 ) {

Also applies to: 16-16


55-111: Consider externalizing styles.

The extensive inline styles with !important declarations suggest potential conflicts with existing CSS. Consider moving these styles to a separate CSS module or using a CSS-in-JS solution to improve maintainability.

This is a minor maintainability concern. If the !important overrides are necessary for TipTap integration, document why they're required.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3f6c96 and 06f75d6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx (2 hunks)
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (7 hunks)
  • apps/desktop/src/components/right-panel/components/chat/index.ts (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (2 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (1 hunks)
  • packages/ui/src/components/chat/chat-history-view.tsx (1 hunks)
  • packages/ui/src/components/chat/chat-messages-view.tsx (1 hunks)
  • packages/ui/src/components/chat/chat-model-info-modal.tsx (1 hunks)
  • packages/ui/src/components/chat/empty-chat-state.tsx (1 hunks)
  • packages/ui/src/components/chat/markdown-card.tsx (1 hunks)
  • packages/ui/src/components/chat/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/components/chat/empty-chat-state.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:

  • packages/ui/src/components/chat/chat-messages-view.tsx
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx
  • packages/ui/src/components/chat/chat-model-info-modal.tsx
  • packages/ui/src/components/chat/types.ts
  • packages/ui/src/components/chat/markdown-card.tsx
  • apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx
  • packages/ui/src/components/chat/chat-history-view.tsx
  • apps/desktop/src/components/right-panel/components/chat/index.ts
  • apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
🧬 Code graph analysis (4)
packages/ui/src/components/chat/chat-messages-view.tsx (1)
packages/utils/src/ai.ts (1)
  • UIMessage (25-25)
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (2)
packages/utils/src/contexts/right-panel.tsx (1)
  • useRightPanel (112-118)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1)
  • useChatInput (19-195)
packages/ui/src/components/chat/chat-model-info-modal.tsx (3)
apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx (1)
  • ChatModelInfoModal (10-24)
packages/ui/src/components/ui/modal.tsx (4)
  • Modal (23-83)
  • ModalBody (99-105)
  • ModalTitle (130-136)
  • ModalDescription (143-148)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
packages/ui/src/components/chat/markdown-card.tsx (2)
apps/desktop/src/components/right-panel/components/chat/markdown-card.tsx (1)
  • MarkdownCard (13-30)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
🔇 Additional comments (1)
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (1)

48-62: LGTM! Clean refactoring to use hook.

The refactoring successfully consolidates data fetching and business logic into the useChatInput hook, making this component more focused on presentation and editor management. The hook provides a clean interface with computed values (entityTitle, currentModelName) and handlers (handleMentionSearch, processSelection, hookSubmit).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06f75d6 and 8b798da.

📒 Files selected for processing (7)
  • apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat/index.ts (1 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (4 hunks)
  • packages/ui/src/components/chat/chat-input.tsx (9 hunks)
  • packages/ui/src/components/chat/chat-model-info-modal.tsx (1 hunks)
  • packages/ui/src/components/chat/chat-types.ts (1 hunks)
  • packages/ui/src/components/chat/thinking-indicator.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/components/right-panel/components/chat/index.ts
🧰 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:

  • packages/ui/src/components/chat/thinking-indicator.tsx
  • packages/ui/src/components/chat/chat-model-info-modal.tsx
  • packages/ui/src/components/chat/chat-types.ts
  • apps/desktop/src/components/right-panel/components/chat/chat-messages-view.tsx
  • packages/ui/src/components/chat/chat-input.tsx
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
🧬 Code graph analysis (2)
packages/ui/src/components/chat/chat-input.tsx (1)
packages/utils/src/contexts/right-panel.tsx (1)
  • SelectionData (4-10)
apps/desktop/src/components/right-panel/views/chat-view.tsx (1)
apps/desktop/src/components/right-panel/hooks/useChatInput.ts (1)
  • useChatInput (19-195)
🪛 GitHub Actions: .github/workflows/desktop_ci.yaml
packages/ui/src/components/chat/chat-input.tsx

[error] 4-4: Cannot find module '@hypr/ui/components/chat/chat-types' or its corresponding type declarations. (tsc --noEmit during 'apps/desktop' typecheck)

🔇 Additional comments (1)
packages/ui/src/components/chat/chat-types.ts (1)

1-20: LGTM!

The type definitions are clear and well-structured. The re-export pattern effectively centralizes public type access for chat components, making the API surface cleaner for consumers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/ui/src/components/chat/ui-message.tsx (1)

48-69: XSS risk in fallback paths requires escaping.

The fallback paths at lines 64 and 68 inject raw content into Renderer without HTML escaping. This allows script injection if the content contains malicious HTML. This concern was previously flagged and remains unaddressed.

Apply this diff to add HTML escaping:

+const ESCAPE_HTML_MAP: Record<string, string> = {
+  "&": "&amp;",
+  "<": "&lt;",
+  ">": "&gt;",
+  '"': "&quot;",
+  "'": "&#39;",
+};
+
+const escapeHtml = (value: string) =>
+  value.replace(/[&<>"']/g, (char) => ESCAPE_HTML_MAP[char] ?? char);
+
 const TextContent: FC<{
   content: string;
   isHtml?: boolean;
   convertMarkdownToHtml?: (markdown: string) => Promise<string>;
 }> = ({ content, isHtml, convertMarkdownToHtml }) => {
   const [displayHtml, setDisplayHtml] = useState<string>("");

   useEffect(() => {
     const processContent = async () => {
       if (isHtml) {
         setDisplayHtml(content);
         return;
       }

       // Convert markdown to HTML
       if (convertMarkdownToHtml) {
         try {
           let html = await convertMarkdownToHtml(content);

           // Clean up empty paragraphs
           html = html
             .replace(/<p>\s*<\/p>/g, "")
             .replace(/<p>\u00A0<\/p>/g, "")
             .replace(/<p>&nbsp;<\/p>/g, "")
             .replace(/<p>\s+<\/p>/g, "")
             .replace(/<p> <\/p>/g, "")
             .trim();

           setDisplayHtml(html);
         } catch (error) {
           console.error("Failed to convert markdown:", error);
-          setDisplayHtml(content);
+          setDisplayHtml(`<p>${escapeHtml(content)}</p>`);
         }
       } else {
         // Fallback: display as plain text in a paragraph
-        setDisplayHtml(`<p>${content}</p>`);
+        setDisplayHtml(`<p>${escapeHtml(content)}</p>`);
       }
     };
packages/ui/src/components/chat/chat-input.tsx (1)

7-7: Verify chat-types module is properly exported.

The import path for BadgeType causes a module resolution failure in the desktop CI. While the relative import ./chat-types is syntactically correct, the error suggests the module isn't being exported from the UI package's public API.

Please verify that chat-types.ts is included in the package's barrel exports (likely in packages/ui/src/components/chat/index.ts or similar). Run the following to check:

#!/bin/bash
# Check if chat-types is exported from the UI package
rg -n "export.*chat-types" packages/ui/
🧹 Nitpick comments (2)
apps/desktop/src/components/right-panel/components/chat/index.ts (1)

2-2: Redundant export: already covered by wildcard.

Line 1's export * from "./empty-chat-state" already re-exports all named exports from that module, including EmptyChatState. The explicit export on Line 2 is unnecessary unless EmptyChatState is a default export being renamed (which would require export { default as EmptyChatState }).

As per coding guidelines.

Apply this diff to remove the redundant export:

 export * from "./empty-chat-state";
-export { EmptyChatState } from "./empty-chat-state";
 export * from "./types";
packages/ui/src/components/chat/ui-message.tsx (1)

196-198: Refactor to use proper types instead of 'any'.

Using any types at lines 196 and 237 bypasses TypeScript's type safety. Consider using the proper UIMessage types or creating a more specific type for message parts.

Also applies to: 237-237

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b798da and 24bb1eb.

📒 Files selected for processing (8)
  • apps/desktop/src/components/right-panel/components/chat/index.ts (1 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (5 hunks)
  • packages/ui/src/components/chat/chat-input.tsx (10 hunks)
  • packages/ui/src/components/chat/chat-messages-view.tsx (3 hunks)
  • packages/ui/src/components/chat/chat-types.ts (1 hunks)
  • packages/ui/src/components/chat/markdown-card.tsx (3 hunks)
  • packages/ui/src/components/chat/ui-message.tsx (7 hunks)
  • packages/ui/src/components/chat/utils/markdown-parser.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/components/right-panel/views/chat-view.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:

  • packages/ui/src/components/chat/utils/markdown-parser.ts
  • apps/desktop/src/components/right-panel/components/chat/index.ts
  • packages/ui/src/components/chat/markdown-card.tsx
  • packages/ui/src/components/chat/ui-message.tsx
  • packages/ui/src/components/chat/chat-types.ts
  • packages/ui/src/components/chat/chat-input.tsx
  • packages/ui/src/components/chat/chat-messages-view.tsx
🧬 Code graph analysis (3)
packages/ui/src/components/chat/ui-message.tsx (2)
packages/ui/src/components/chat/utils/markdown-parser.ts (2)
  • ParsedPart (1-5)
  • parseMarkdownBlocks (7-66)
packages/ui/src/components/chat/markdown-card.tsx (1)
  • MarkdownCard (15-141)
packages/ui/src/components/chat/chat-input.tsx (1)
packages/utils/src/contexts/right-panel.tsx (1)
  • SelectionData (4-10)
packages/ui/src/components/chat/chat-messages-view.tsx (2)
packages/ui/src/components/chat/utils/markdown-parser.ts (1)
  • parseMarkdownBlocks (7-66)
packages/ui/src/components/chat/markdown-card.tsx (1)
  • MarkdownCard (15-141)
🔇 Additional comments (5)
packages/ui/src/components/chat/utils/markdown-parser.ts (1)

7-66: LGTM! Solid markdown block parser implementation.

The parsing logic correctly handles markdown delimiters, incomplete blocks, and edge cases like consecutive delimiters. The loop boundary condition i < text.length - 2 is appropriate for checking three-character delimiters safely.

packages/ui/src/components/chat/markdown-card.tsx (1)

51-51: LGTM! Dependency array is now complete.

The addition of convertMarkdownToHtml to the dependency array correctly addresses the past review comment about missing dependencies.

packages/ui/src/components/chat/chat-types.ts (1)

1-20: LGTM! Well-structured type definitions.

The public types are clearly defined and properly re-exported from internal modules. The type definitions support the chat UI refactor objectives.

packages/ui/src/components/chat/chat-messages-view.tsx (1)

1-123: LGTM! Props correctly threaded through component hierarchy.

The addition of convertMarkdownToHtml and its propagation to child components (UIMessageComponent along with parseMarkdownBlocks and MarkdownCard) properly enables the markdown rendering pipeline.

packages/ui/src/components/chat/chat-input.tsx (1)

159-175: processSelection is already memoized

processSelection is defined with useCallback in apps/desktop/src/components/right-panel/hooks/useChatInput.ts:141; no action required.

Likely an incorrect or invalid review comment.

Comment on lines +9 to +13
interface ParsedPart {
type: "text" | "markdown";
content: string;
isComplete?: boolean;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Import ParsedPart instead of redefining it.

ParsedPart is already defined and exported from ./utils/markdown-parser. Redefining it here creates unnecessary duplication and potential type mismatches.

Apply this diff:

-// Import these from the same package structure
-// Assuming these exist in the UI package - if not, they need to be moved/created
-interface ParsedPart {
-  type: "text" | "markdown";
-  content: string;
-  isComplete?: boolean;
-}
+import type { ParsedPart } from "./utils/markdown-parser";
📝 Committable suggestion

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

Suggested change
interface ParsedPart {
type: "text" | "markdown";
content: string;
isComplete?: boolean;
}
// Remove the local ParsedPart definition and import the shared type instead
import type { ParsedPart } from "./utils/markdown-parser";
🤖 Prompt for AI Agents
In packages/ui/src/components/chat/ui-message.tsx around lines 9 to 13, the file
redefines the ParsedPart interface which is already exported from
./utils/markdown-parser; remove the local interface definition and import the
existing type instead (use an import type { ParsedPart } from
'./utils/markdown-parser'); update any local references to use the imported
ParsedPart and ensure TypeScript-only import if appropriate to avoid runtime
import cost.

@yujonglee yujonglee closed this Oct 10, 2025
@yujonglee yujonglee deleted the chat-ui-refactor branch October 10, 2025 01:07
@yujonglee yujonglee restored the chat-ui-refactor branch October 10, 2025 23:43
@ComputelessComputer ComputelessComputer deleted the chat-ui-refactor branch December 14, 2025 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants