feat(chat): redesign chat UI with modular parts architecture#22
feat(chat): redesign chat UI with modular parts architecture#22Youhai020616 wants to merge 2 commits intomainfrom
Conversation
Refactor monolithic ChatMessage (548→167 lines) into independent part components inspired by better-chatbot: UserMessagePart, AssistMessagePart, ReasoningPart, ToolMessagePart, WordByWordFadeIn. Add multi-layer glass input, ScrollToBottom button, expandable error bar, time-based welcome screen with typewriter animation, specialized tool renderers (search/code/browser), and keyboard shortcuts (Cmd+E thinking, Cmd+Shift+K copy, Cmd+Shift+D delete). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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 |
🤖 Augment PR SummarySummary: This PR redesigns the Chat UI by splitting message rendering into modular “parts” and adding richer, animated interaction patterns. Changes:
Technical Notes: Uses framer-motion for entrance/expand/collapse animations and introduces memoized subcomponents for message rendering to reduce re-renders. 🤖 Was this summary useful? React with 👍 or 👎 |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
||
| useEffect(() => { | ||
| // Delay typewriter start to sync with fade-in animation | ||
| const delay = setTimeout(runTypewriter, 400); |
There was a problem hiding this comment.
runTypewriter creates an interval and returns a cleanup, but the effect that calls it only clears the timeout, not the interval. If the component unmounts mid-typewriter (or fullGreeting changes quickly), the interval can keep running and call state setters after unmount.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| const [copied, setCopied] = useState(false); | ||
|
|
||
| const code = useMemo(() => extractCode(input), [input]); | ||
| const language = useMemo(() => extractLanguage(input, ''), [input]); |
There was a problem hiding this comment.
extractLanguage is called with an empty tool name (extractLanguage(input, '')), so the tool-name-based language inference path never runs. This can make the language badge misleading when the input doesn’t explicitly include a language.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| </Tooltip> | ||
| <Tooltip> | ||
| <TooltipTrigger asChild> | ||
| <Button variant="ghost" size="icon" className="h-7 w-7" onClick={handleEdit}> |
There was a problem hiding this comment.
The Edit action is shown even when onEdit is undefined, so users can enter edit mode and hit Save with no actual persistence/update happening. Consider gating the edit affordance on onEdit being provided (similar to how Delete is gated).
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| const images = extractImages(message); | ||
| const tools = extractToolUse(message); | ||
| const visibleThinking = showThinking ? thinking : null; | ||
| const visibleTools = tools; |
There was a problem hiding this comment.
Tool cards are now rendered regardless of showThinking (visibleTools = tools). If the “thinking” toggle is intended to hide tool call details/status as well (as it previously did), this change may be unintentionally exposing tool inputs when thinking is turned off.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
|
|
||
| // Build a map of toolCallId → tool result content from toolresult messages | ||
| // so ToolCard renderers can display the output of each tool invocation. | ||
| const toolResultsMap = useMemo(() => { |
There was a problem hiding this comment.
toolResultsMap is built from messages entries with role toolresult, but the chat store’s history load path filters tool-result-role messages out of messages. That means tool outputs may be unavailable for previously loaded conversations (e.g., after refresh) even though ToolCards are rendered.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| <TooltipContent className="flex items-center gap-2"> | ||
| <span>{showThinking ? t('toolbar.hideThinking') : t('toolbar.showThinking')}</span> | ||
| <kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground"> | ||
| <span className="text-xs">{navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'}</span>E |
There was a problem hiding this comment.
| const [copied, setCopied] = useState(false); | ||
|
|
||
| const copyContent = useCallback(() => { | ||
| navigator.clipboard.writeText(text); |
There was a problem hiding this comment.
navigator.clipboard.writeText(...) returns a Promise; if it rejects (permissions/unavailable clipboard), this can surface as an unhandled promise rejection. Handling/catching failures would avoid noisy runtime errors.
Severity: low
Other Locations
src/pages/Chat/message-parts/UserMessagePart.tsx:70
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Summary
ChatMessage(548→167 lines) into modular parts —UserMessagePart,AssistMessagePart,ReasoningPart,ToolMessagePart,WordByWordFadeInScrollToBottomButtonwith framer-motion,Cmd+Ethinking toggle shortcutCmd+Shift+Kcopy,Cmd+Shift+Ddelete), tool output visualization with Input/Output tabsChanges
message-parts/tool-renderers/Test plan
🤖 Generated with Claude Code