Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
91 changes: 69 additions & 22 deletions webview-ui/src/components/chat/ChatTextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
import { useEvent } from "react-use"
import DynamicTextArea from "react-textarea-autosize"
import { VolumeX, Image, WandSparkles, SendHorizontal, MessageSquareX } from "lucide-react"
import { VolumeX, Image, WandSparkles, SendHorizontal, MessageSquareX, Clock } from "lucide-react"

import { mentionRegex, mentionRegexGlobal, commandRegexGlobal, unescapeSpaces } from "@roo/context-mentions"
import { WebviewMessage } from "@roo/WebviewMessage"
Expand All @@ -21,7 +21,13 @@ import {
} from "@src/utils/context-mentions"
import { cn } from "@src/lib/utils"
import { convertToMentionPath } from "@src/utils/path-mentions"
import { StandardTooltip } from "@src/components/ui"
import {
StandardTooltip,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@src/components/ui"

import Thumbnails from "../common/Thumbnails"
import { ModeSelector } from "./ModeSelector"
Expand Down Expand Up @@ -1102,7 +1108,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</StandardTooltip>
</div>

<div className="absolute bottom-1 right-1 z-30">
<div className="absolute bottom-1 right-1 z-30 flex items-center gap-1">
{isEditMode && (
<StandardTooltip content={t("chat:cancel.title")}>
<button
Expand All @@ -1124,25 +1130,66 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</button>
</StandardTooltip>
)}
<StandardTooltip content={t("chat:sendMessage")}>
<button
aria-label={t("chat:sendMessage")}
disabled={false}
onClick={onSend}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
)}>
<SendHorizontal className="w-4 h-4" />
</button>
</StandardTooltip>
<DropdownMenu>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Missing test coverage for this new dropdown functionality. Could we add tests to ensure the right-click behavior and message queueing work as expected?

<DropdownMenuTrigger asChild>
<button
aria-label={t("chat:sendMessage")}
disabled={false}
onContextMenu={(e) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this accessible to keyboard-only users? The right-click context menu might not be discoverable or usable without a mouse. Could we consider adding a keyboard shortcut or making the dropdown accessible via keyboard navigation?

e.preventDefault()
// Trigger dropdown on right-click
const event = new MouseEvent("click", {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This indirect approach of dispatching a synthetic click event feels a bit hacky. Would it be cleaner to show the dropdown directly on right-click using the dropdown's API, or use a proper context menu library?

bubbles: true,
cancelable: true,
view: window,
})
e.currentTarget.dispatchEvent(event)
}}
onClick={(e) => {
// If it's a regular left click, just send the message
if (e.button === 0 && !e.ctrlKey && !e.metaKey) {
e.preventDefault()
e.stopPropagation()
onSend()
}
}}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
)}>
<SendHorizontal className="w-4 h-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuItem onClick={onSend}>
<SendHorizontal className="mr-2 h-4 w-4" />
<span>{t("chat:sendMessage")}</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
// Queue the message for after task completion
if (inputValue.trim() || selectedImages.length > 0) {
vscode.postMessage({
type: "queueMessage",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The message structure with 'text' and 'images' properties doesn't appear to be strongly typed. Could we improve type safety by properly typing the WebviewMessage for queueMessage?

text: inputValue,
images: selectedImages,
})
setInputValue("")
setSelectedImages([])
}
}}>
<Clock className="mr-2 h-4 w-4" />
<span>{t("chat:scheduleForAfterTask")}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>

{!inputValue && (
Expand Down
5 changes: 4 additions & 1 deletion webview-ui/src/components/chat/QueuedMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ export const QueuedMessages = ({ queue, onRemove, onUpdate }: QueuedMessagesProp

return (
<div className="px-[15px] py-[10px] pr-[6px]" data-testid="queued-messages">
<div className="text-vscode-descriptionForeground text-md mb-2">{t("queuedMessages.title")}</div>
<div className="text-vscode-descriptionForeground text-md mb-2">
<span className="codicon codicon-clock mr-2" />
{t("queuedMessages.scheduledTitle")}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this title always accurate? The component shows 'Scheduled for after task completion' regardless of whether messages are actually scheduled or just queued. Should this be conditional based on the actual queue state?

</div>
<div className="flex flex-col gap-2 max-h-[300px] overflow-y-auto pr-2">
{queue.map((message, index) => {
const editState = getEditState(message.id, message.text)
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/i18n/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"enhancePromptDescription": "The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works.",
"addImages": "Add images to message",
"sendMessage": "Send message",
"scheduleForAfterTask": "Schedule for after task completion",
"stopTts": "Stop text-to-speech",
"typeMessage": "Type a message...",
"typeTask": "Type your task here...",
Expand Down Expand Up @@ -387,6 +388,7 @@
},
"queuedMessages": {
"title": "Queued Messages:",
"scheduledTitle": "Scheduled for after task completion",
"clickToEdit": "Click to edit message"
}
}
Loading