-
Notifications
You must be signed in to change notification settings - Fork 301
feat(chat): chat input UX — inline model selector, working-state effects, reasoning shimmer, table striping, unlabeled turns #1813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4aac287
74b6690
a71e7b3
95afc7f
874e7f5
78b2a8d
7e41416
29f3eb8
830bd43
8fc5b3b
4f7443e
aaa6500
9b92eb1
0b85558
4b03b68
90eba09
e53906d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,7 +58,6 @@ import { | |
| Eye, | ||
| FileDiff, | ||
| ListTree, | ||
| User, | ||
| Edit, | ||
| Trash2, | ||
| MessageCircleQuestionMark, | ||
|
|
@@ -67,7 +66,6 @@ import { | |
| PocketKnife, | ||
| FolderTree, | ||
| SquareTerminal, | ||
| MessageCircle, | ||
| Repeat2, | ||
| Split, | ||
| ArrowRight, | ||
|
|
@@ -1208,13 +1206,10 @@ export const ChatRowContent = ({ | |
| case "text": | ||
| return ( | ||
| <div className="group"> | ||
| <div style={headerStyle}> | ||
| <MessageCircle className="w-4 shrink-0" aria-label="Speech bubble icon" /> | ||
| <span style={{ fontWeight: "bold" }}>{t("chat:text.rooSaid")}</span> | ||
| <div style={{ flexGrow: 1 }} /> | ||
| <OpenMarkdownPreviewButton markdown={message.text} /> | ||
| </div> | ||
| <div className="pl-6"> | ||
| <div className="pl-6 relative"> | ||
| <div className="absolute top-0 right-0 z-10"> | ||
| <OpenMarkdownPreviewButton markdown={message.text} /> | ||
| </div> | ||
| <Markdown markdown={message.text} partial={message.partial} /> | ||
| {message.images && message.images.length > 0 && ( | ||
| <div style={{ marginTop: "10px" }}> | ||
|
|
@@ -1228,17 +1223,13 @@ export const ChatRowContent = ({ | |
| ) | ||
| case "user_feedback": | ||
| return ( | ||
| <div className="group"> | ||
| <div style={headerStyle}> | ||
| <User className="w-4 shrink-0" aria-label="User icon" /> | ||
| <span style={{ fontWeight: "bold" }}>{t("chat:feedback.youSaid")}</span> | ||
| </div> | ||
| <div className="group w-fit max-w-[70%] ml-auto flex flex-col items-end gap-1"> | ||
| <div | ||
| className={cn( | ||
| "ml-6 border rounded-sm overflow-hidden whitespace-pre-wrap", | ||
| "border rounded-sm overflow-hidden whitespace-pre-wrap", | ||
| isEditing | ||
| ? "bg-vscode-editor-background text-vscode-editor-foreground" | ||
| : "cursor-text p-1 bg-vscode-editor-foreground/70 text-vscode-editor-background", | ||
| : "cursor-text p-1 bg-vscode-list-hoverBackground text-vscode-foreground", | ||
| )}> | ||
| {isEditing ? ( | ||
| <div className="flex flex-col gap-2"> | ||
|
|
@@ -1261,50 +1252,61 @@ export const ChatRowContent = ({ | |
| /> | ||
| </div> | ||
| ) : ( | ||
| <div className="flex justify-between"> | ||
| <div | ||
| className="flex-grow px-2 py-1 wrap-anywhere rounded-lg transition-colors" | ||
| onClick={(e) => { | ||
| e.stopPropagation() | ||
| if (!isStreaming) { | ||
| handleEditClick() | ||
| } | ||
| }} | ||
| title={t("chat:queuedMessages.clickToEdit")}> | ||
| <Mention text={message.text} withShadow /> | ||
| </div> | ||
| <div className="flex gap-2 pr-1"> | ||
| <div | ||
| className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity" | ||
| style={{ visibility: isStreaming ? "hidden" : "visible" }} | ||
| onClick={(e) => { | ||
| e.stopPropagation() | ||
| handleEditClick() | ||
| }}> | ||
| <Edit className="w-4 shrink-0" aria-label="Edit message icon" /> | ||
| </div> | ||
| <div | ||
| className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity" | ||
| style={{ visibility: isStreaming ? "hidden" : "visible" }} | ||
| onClick={(e) => { | ||
| e.stopPropagation() | ||
| vscode.postMessage({ type: "deleteMessage", value: message.ts }) | ||
| }}> | ||
| <Trash2 className="w-4 shrink-0" aria-label="Delete message icon" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <button | ||
| type="button" | ||
| className="text-left px-2 py-1 wrap-anywhere rounded-lg transition-colors bg-transparent border-0 font-[inherit] text-[inherit] cursor-text focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder" | ||
| disabled={isStreaming} | ||
| onClick={(e) => { | ||
| e.stopPropagation() | ||
| // Clicks on an inline mention open its own target and must not | ||
| // also enter edit mode for the surrounding message bubble. | ||
| if ((e.target as HTMLElement).closest("[data-mention]")) return | ||
| if (!isStreaming) { | ||
| handleEditClick() | ||
| } | ||
|
Comment on lines
+1259
to
+1266
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Keep mention clicks from opening the message editor. For a non-streaming message containing a mention, 🤖 Prompt for AI Agents |
||
| }} | ||
| title={t("chat:queuedMessages.clickToEdit")}> | ||
| <Mention text={message.text} withShadow /> | ||
| </button> | ||
| )} | ||
| {!isEditing && message.images && message.images.length > 0 && ( | ||
| <Thumbnails images={message.images} style={{ marginTop: "8px" }} /> | ||
| )} | ||
| </div> | ||
| {!isEditing && ( | ||
| <div className="flex gap-2 pr-1"> | ||
| <button | ||
| type="button" | ||
| aria-label={t("chat:edit")} | ||
| className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 transition-opacity bg-transparent border-0 p-0 text-inherit focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder rounded-sm" | ||
| style={{ visibility: isStreaming ? "hidden" : "visible" }} | ||
| disabled={isStreaming} | ||
| onClick={(e) => { | ||
| e.stopPropagation() | ||
| handleEditClick() | ||
| }}> | ||
| <Edit className="w-4 shrink-0" aria-hidden="true" /> | ||
| </button> | ||
| <button | ||
| type="button" | ||
| aria-label={t("common:confirmation.deleteMessage")} | ||
| className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 transition-opacity bg-transparent border-0 p-0 text-inherit focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder rounded-sm" | ||
| style={{ visibility: isStreaming ? "hidden" : "visible" }} | ||
| disabled={isStreaming} | ||
| onClick={(e) => { | ||
| e.stopPropagation() | ||
| vscode.postMessage({ type: "deleteMessage", value: message.ts }) | ||
| }}> | ||
| <Trash2 className="w-4 shrink-0" aria-hidden="true" /> | ||
| </button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ) | ||
| case "user_feedback_diff": | ||
| const tool = safeJsonParse<ClineSayTool>(message.text) | ||
| return ( | ||
| <div style={{ marginTop: -10, width: "100%" }}> | ||
| <div className="max-w-[70%] ml-auto" style={{ marginTop: -10, width: "100%" }}> | ||
| <CodeAccordion | ||
| code={tool?.diff} | ||
| language="diff" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 14802
🏁 Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 43280
🤖 get_repo_knowledge executed:
get_repo_knowledge Zoo-Code-Org/Zoo-Code /tmp/coderabbit-repo-knowledge/zoo-code-org-zoo-code-6665e61c/learnings /tmp/coderabbit-repo-knowledge/zoo-code-org-zoo-code-6665e61c/conventionsLength of output: 19518
🏁 Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 41876
🏁 Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 42222
Add focused persistence and boundary tests for both settings.
The schema, cached state, save payload, and restored webview state include both fields. The tests do not cover either field in the schema or save payload. Add tests for true, false, and unset values, including the shared defaults (
"marquee"andfalse), and assert thatupdateSettingspreserves the selected values.🤖 Prompt for AI Agents
Source: Path instructions