Skip to content
Closed
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
188 changes: 141 additions & 47 deletions webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
import { vscode } from "@src/utils/vscode"
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@src/components/ui/dropdown-menu"

import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
Expand Down Expand Up @@ -1002,29 +1008,63 @@ export const ChatRowContent = ({

return (
<>
<div
className={`group text-sm transition-opacity ${
isApiRequestInProgress ? "opacity-100" : "opacity-40 hover:opacity-100"
}`}
style={{
...headerStyle,
marginBottom:
((cost === null || cost === undefined) && apiRequestFailedMessage) ||
apiReqStreamingFailedMessage
? 10
: 0,
justifyContent: "space-between",
}}>
<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
{icon}
{title}
</div>
<div
className="text-xs text-vscode-dropdown-foreground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
style={{ opacity: cost !== null && cost !== undefined && cost > 0 ? 1 : 0 }}>
${Number(cost || 0)?.toFixed(4)}
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div
className={`group text-sm transition-opacity ${
isApiRequestInProgress ? "opacity-100" : "opacity-40 hover:opacity-100"
}`}
style={{
...headerStyle,
marginBottom:
((cost === null || cost === undefined) && apiRequestFailedMessage) ||
apiReqStreamingFailedMessage
? 10
: 0,
justifyContent: "space-between",
}}
onContextMenu={(e) => e.preventDefault()}>
Copy link
Author

Choose a reason for hiding this comment

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

The right-click context menu functionality described in the PR doesn't actually work. The onContextMenu handler prevents the browser's default menu but doesn't open the DropdownMenu on right-click. Radix UI's DropdownMenuTrigger only responds to left-clicks by default. Right-clicking these elements will show no menu at all, breaking expected user interaction.

To enable right-click opening, you need controlled state:

const [open, setOpen] = useState(false)

<DropdownMenu open={open} onOpenChange={setOpen}>
  <DropdownMenuTrigger asChild>
    <div onContextMenu={(e) => {
      e.preventDefault()
      setOpen(true)
    }}>

Alternatively, if right-click isn't required, remove the onContextMenu handlers to restore the browser's default menu.

Fix it with Roo Code or mention @roomote and request a fix.

<div
style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
{icon}
{title}
</div>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<div
className="text-xs text-vscode-dropdown-foreground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
style={{
opacity: cost !== null && cost !== undefined && cost > 0 ? 1 : 0,
}}>
${Number(cost || 0)?.toFixed(4)}
</div>
<div
className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-30 hover:!opacity-100 transition-opacity"
style={{ visibility: isApiRequestInProgress ? "hidden" : "visible" }}
onClick={(e) => {
e.stopPropagation()
vscode.postMessage({ type: "deleteMessage", value: message.ts })
}}
title={t("chat:deleteMessageAndSubsequent")}>
<Trash2 className="w-4 shrink-0" aria-label="Delete message icon" />
</div>
</div>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => vscode.postMessage({ type: "deleteMessage", value: message.ts })}
className="flex items-center gap-2"
disabled={isApiRequestInProgress}>
<Trash2 className="w-4 h-4" />
<span>
{t(
"chat:deleteMessageAndSubsequent",
"Delete message and subsequent nodes",
)}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{(((cost === null || cost === undefined) && apiRequestFailedMessage) ||
apiReqStreamingFailedMessage) && (
<ErrorRow
Expand Down Expand Up @@ -1053,22 +1093,49 @@ export const ChatRowContent = ({
return null // we should never see this message type
case "text":
return (
<div>
<div style={headerStyle}>
<MessageCircle className="w-4 shrink-0" aria-label="Speech bubble icon" />
<span style={{ fontWeight: "bold" }}>{t("chat:text.rooSaid")}</span>
</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} />
))}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="group" onContextMenu={(e) => e.preventDefault()}>
<div style={{ ...headerStyle, justifyContent: "space-between" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<MessageCircle className="w-4 shrink-0" aria-label="Speech bubble icon" />
<span style={{ fontWeight: "bold" }}>{t("chat:text.rooSaid")}</span>
</div>
<div
className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-30 hover:!opacity-100 transition-opacity"
style={{ visibility: isStreaming ? "hidden" : "visible" }}
onClick={(e) => {
e.stopPropagation()
vscode.postMessage({ type: "deleteMessage", value: message.ts })
}}
title={t("chat:deleteMessageAndSubsequent")}>
<Trash2 className="w-4 shrink-0" aria-label="Delete message icon" />
</div>
</div>
)}
</div>
</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>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => vscode.postMessage({ type: "deleteMessage", value: message.ts })}
className="flex items-center gap-2"
disabled={isStreaming}>
<Trash2 className="w-4 h-4" />
<span>
{t("chat:deleteMessageAndSubsequent", "Delete message and subsequent nodes")}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
case "user_feedback":
return (
Expand Down Expand Up @@ -1162,15 +1229,42 @@ export const ChatRowContent = ({
return <ErrorRow type="error" message={message.text || ""} />
case "completion_result":
return (
<>
<div style={headerStyle}>
{icon}
{title}
</div>
<div className="border-l border-green-600/30 ml-2 pl-4 pb-1">
<Markdown markdown={message.text} />
</div>
</>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="group" onContextMenu={(e) => e.preventDefault()}>
<div style={{ ...headerStyle, justifyContent: "space-between" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
{icon}
{title}
</div>
<div
className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-30 hover:!opacity-100 transition-opacity"
style={{ visibility: isStreaming ? "hidden" : "visible" }}
onClick={(e) => {
e.stopPropagation()
vscode.postMessage({ type: "deleteMessage", value: message.ts })
}}
title={t("chat:deleteMessageAndSubsequent")}>
<Trash2 className="w-4 shrink-0" aria-label="Delete message icon" />
</div>
</div>
<div className="border-l border-green-600/30 ml-2 pl-4 pb-1">
<Markdown markdown={message.text} />
</div>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => vscode.postMessage({ type: "deleteMessage", value: message.ts })}
className="flex items-center gap-2"
disabled={isStreaming}>
<Trash2 className="w-4 h-4" />
<span>
{t("chat:deleteMessageAndSubsequent", "Delete message and subsequent nodes")}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
case "shell_integration_warning":
return <CommandExecutionError />
Expand Down
3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/ca/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/de/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webview-ui/src/i18n/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@
"feedback": {
"youSaid": "You said"
},
"deleteMessageAndSubsequent": "Delete message and subsequent nodes",
"mcp": {
"wantsToUseTool": "Roo wants to use a tool on the {{serverName}} MCP server",
"wantsToAccessResource": "Roo wants to access a resource on the {{serverName}} MCP server"
Expand Down
3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/es/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/fr/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/hi/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/id/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/it/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/ja/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/ko/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/nl/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/pl/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/pt-BR/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/ru/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/tr/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/vi/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/zh-CN/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/zh-TW/chat.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.