Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions webview-ui/playwright/gallery/stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export const stories: Record<string, Story> = {
const { ChatTextAreaStory } = await import("@/components/chat/__tests__/ChatTextArea.visual.fixture")
return <ChatTextAreaStory />
},
"chat-row-conversation": async () => {
const { ChatRowConversationStory } = await import("@/components/chat/__tests__/ChatRow.visual.fixture")
return <ChatRowConversationStory />
},
"history-empty": async () => {
const [{ AppProviders }, { default: HistoryView }] = await Promise.all([
import("../AppProviders"),
Expand Down
128 changes: 70 additions & 58 deletions webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
Eye,
FileDiff,
ListTree,
User,
Edit,
Trash2,
MessageCircleQuestionMark,
Expand All @@ -67,7 +66,6 @@
PocketKnife,
FolderTree,
SquareTerminal,
MessageCircle,
Repeat2,
Split,
ArrowRight,
Expand Down Expand Up @@ -1207,38 +1205,36 @@
return null // we should never see this message type
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">
<Markdown markdown={message.text} partial={message.partial} />
{message.images && message.images.length > 0 && (
<div style={{ marginTop: "10px" }}>
{message.images.map((image, index) => (
<ImageBlock key={index} imageData={image} />
))}
</div>
)}
<div className="group" role="group" aria-label={t("chat:text.rooSaid")}>

Check warning on line 1208 in webview-ui/src/components/chat/ChatRow.tsx

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

webview-ui/src/components/chat/ChatRow.tsx:1208: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
<div className="pl-6 relative">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<div className="absolute top-0 right-0 z-10">
<OpenMarkdownPreviewButton markdown={message.text} />
</div>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<div className="pr-10">
<Markdown markdown={message.text} partial={message.partial} />
{message.images && message.images.length > 0 && (

Check warning on line 1215 in webview-ui/src/components/chat/ChatRow.tsx

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

webview-ui/src/components/chat/ChatRow.tsx:1215: 5 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.
<div style={{ marginTop: "10px" }}>

Check warning on line 1216 in webview-ui/src/components/chat/ChatRow.tsx

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

webview-ui/src/components/chat/ChatRow.tsx:1216: 2 mutation test gaps; example: NoCoverage ObjectLiteral mutant (replacement: {}). See the job summary for the complete list and resolution guidance.
{message.images.map((image, index) => (

Check warning on line 1217 in webview-ui/src/components/chat/ChatRow.tsx

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

webview-ui/src/components/chat/ChatRow.tsx:1217: NoCoverage ArrowFunction mutant (replacement: () => undefined). See the job summary for the complete list and resolution guidance.
<ImageBlock key={index} imageData={image} />
))}
</div>
)}
</div>
</div>
</div>
)
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"
role="group"
aria-label={t("chat:feedback.youSaid")}>

Check warning on line 1231 in webview-ui/src/components/chat/ChatRow.tsx

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

webview-ui/src/components/chat/ChatRow.tsx:1231: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
<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">
Expand All @@ -1261,50 +1257,66 @@
/>
</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
className="px-2 py-1 wrap-anywhere rounded-lg transition-colors"
role="button"
tabIndex={isStreaming ? -1 : 0}
aria-disabled={isStreaming || undefined}

Check warning on line 1264 in webview-ui/src/components/chat/ChatRow.tsx

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

webview-ui/src/components/chat/ChatRow.tsx:1264: Survived ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.
onClick={(e) => {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
e.stopPropagation()

Check warning on line 1266 in webview-ui/src/components/chat/ChatRow.tsx

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

webview-ui/src/components/chat/ChatRow.tsx:1266: Survived CallExpression mutant (replacement: ;). See the job summary for the complete list and resolution guidance.
if (!isStreaming) {
handleEditClick()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}}
onKeyDown={(e) => {
if (isStreaming) {
return
}
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
handleEditClick()
}
}}
title={t("chat:queuedMessages.clickToEdit")}>
<Mention text={message.text} withShadow />
</div>
)}
{!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"
className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity rounded-sm focus-visible:outline focus-visible:outline-1 focus-visible:outline-vscode-focusBorder"
style={{ visibility: isStreaming ? "hidden" : "visible" }}
aria-label={t("common:confirmation.editMessage")}
onClick={(e) => {
e.stopPropagation()
handleEditClick()
}}>
<Edit className="w-4 shrink-0" aria-hidden="true" />
</button>
<button
type="button"
className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity rounded-sm focus-visible:outline focus-visible:outline-1 focus-visible:outline-vscode-focusBorder"
style={{ visibility: isStreaming ? "hidden" : "visible" }}
aria-label={t("common:confirmation.deleteMessage")}
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"
Expand Down
7 changes: 6 additions & 1 deletion webview-ui/src/components/chat/Mention.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ export const Mention = ({ text, withShadow = false }: MentionProps) => {
<span
key={index}
className={`${withShadow ? "mention-context-highlight-with-shadow" : "mention-context-highlight"} text-[0.9em] cursor-pointer`}
onClick={() => vscode.postMessage({ type: "openMention", text: part })}>
onClick={(event) => {
// Keep a mention click from bubbling into the surrounding chat row, where it
// would otherwise also trigger the row's click-to-edit handler.
event.stopPropagation()
vscode.postMessage({ type: "openMention", text: part })
}}>
@{part}
</span>
)
Expand Down
Loading
Loading