mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: Add support for message queueing (#6167)
* feat: Add support for message queueing * fix: address PR review feedback for message queueing feature - Restore original ChatView tests from main branch - Fix broken test by updating ChatTextArea mock - Add comprehensive tests for message queueing (simplified due to mocking constraints) - Fix race condition using useRef and setTimeout in queue processing - Extract QueuedMessage interface to shared types.ts file - Replace inline styles with Tailwind classes in QueuedMessages - Add i18n support for 'Queued Messages:' text - Add keyboard navigation for removing queued messages - Add JSDoc for fromQueue parameter in handleSendMessage * refactor: move QueuedMessage interface to packages/types - Move QueuedMessage interface from local types.ts to packages/types/src/message.ts - Update imports in ChatView.tsx and QueuedMessages.tsx to use @roo-code/types - Remove local types.ts file to follow codebase conventions * fix: add id field when creating queued messages - Generate unique id using timestamp when adding messages to queue - Fixes TypeScript error after moving QueuedMessage interface * Stop disabling sending * Translations * Fix tests * Improved styling * Remove unused string * Test cleanup * fix: address message queueing issues - Fix race condition in queue processing by re-checking queue state inside setTimeout - Add error handling for queue operations with retry mechanism - Replace array index with stable message.id for React keys in QueuedMessages - Generate more unique IDs using timestamp + random component * feat: add inline editing for queued messages - Add ability to edit queued messages by clicking on them - Support Enter to save and Escape to cancel edits - Add textarea that auto-resizes based on content - Add hover effect to indicate messages are editable - Add translation for click to edit tooltip * feat: add scrollbar and fix height for queued messages - Add max-height of 300px with scrollbar to queue container - Add flex-shrink-0 to prevent message items from being squished - Ensure consistent height for message items when multiple messages are queued * feat: add translations for queued message edit tooltip - Add 'queuedMessages.clickToEdit' translation key to all 18 language files - Provides localized tooltip text for the click-to-edit functionality * fix: improve message queue processing reliability - Fix race condition by removing nested setState and setTimeout - Add retry limit (3 attempts) to prevent infinite loops - Add proper cleanup on component unmount - Clear queue when starting new task - Prevent queue processing during API errors - Fix ESLint warnings for React hooks dependencies --------- Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com> Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
parent
b73c17a375
commit
f546e6ed1d
24 changed files with 811 additions and 518 deletions
|
|
@ -174,3 +174,19 @@ export const tokenUsageSchema = z.object({
|
|||
})
|
||||
|
||||
export type TokenUsage = z.infer<typeof tokenUsageSchema>
|
||||
|
||||
/**
|
||||
* QueuedMessage
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a message that is queued to be sent when sending is enabled
|
||||
*/
|
||||
export interface QueuedMessage {
|
||||
/** Unique identifier for the queued message */
|
||||
id: string
|
||||
/** The text content of the message */
|
||||
text: string
|
||||
/** Array of image data URLs attached to the message */
|
||||
images: string[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,10 +204,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
}, [selectedType, searchQuery])
|
||||
|
||||
const handleEnhancePrompt = useCallback(() => {
|
||||
if (sendingDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const trimmedInput = inputValue.trim()
|
||||
|
||||
if (trimmedInput) {
|
||||
|
|
@ -216,7 +212,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
} else {
|
||||
setInputValue(t("chat:enhancePromptDescription"))
|
||||
}
|
||||
}, [inputValue, sendingDisabled, setInputValue, t])
|
||||
}, [inputValue, setInputValue, t])
|
||||
|
||||
const allModes = useMemo(() => getAllModes(customModes), [customModes])
|
||||
|
||||
|
|
@ -436,11 +432,9 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
|
||||
event.preventDefault()
|
||||
|
||||
if (!sendingDisabled) {
|
||||
// Reset history navigation state when sending
|
||||
resetHistoryNavigation()
|
||||
onSend()
|
||||
}
|
||||
// Always call onSend - let ChatView handle queueing when disabled
|
||||
resetHistoryNavigation()
|
||||
onSend()
|
||||
}
|
||||
|
||||
if (event.key === "Backspace" && !isComposing) {
|
||||
|
|
@ -488,7 +482,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
}
|
||||
},
|
||||
[
|
||||
sendingDisabled,
|
||||
onSend,
|
||||
showContextMenu,
|
||||
searchQuery,
|
||||
|
|
@ -1034,8 +1027,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
<StandardTooltip content={t("chat:enhancePrompt")}>
|
||||
<button
|
||||
aria-label={t("chat:enhancePrompt")}
|
||||
disabled={sendingDisabled}
|
||||
onClick={!sendingDisabled ? handleEnhancePrompt : undefined}
|
||||
disabled={false}
|
||||
onClick={handleEnhancePrompt}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
|
|
@ -1045,9 +1038,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
"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)]",
|
||||
!sendingDisabled && "cursor-pointer",
|
||||
sendingDisabled &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
|
||||
</button>
|
||||
|
|
@ -1059,8 +1050,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
<StandardTooltip content={t("chat:sendMessage")}>
|
||||
<button
|
||||
aria-label={t("chat:sendMessage")}
|
||||
disabled={sendingDisabled}
|
||||
onClick={!sendingDisabled ? onSend : undefined}
|
||||
disabled={false}
|
||||
onClick={onSend}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
|
|
@ -1070,9 +1061,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
"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)]",
|
||||
!sendingDisabled && "cursor-pointer",
|
||||
sendingDisabled &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<SendHorizontal className="w-4 h-4" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@ import AutoApproveMenu from "./AutoApproveMenu"
|
|||
import SystemPromptWarning from "./SystemPromptWarning"
|
||||
import ProfileViolationWarning from "./ProfileViolationWarning"
|
||||
import { CheckpointWarning } from "./CheckpointWarning"
|
||||
import QueuedMessages from "./QueuedMessages"
|
||||
import { getLatestTodo } from "@roo/todo"
|
||||
import { QueuedMessage } from "@roo-code/types"
|
||||
|
||||
export interface ChatViewProps {
|
||||
isHidden: boolean
|
||||
|
|
@ -154,6 +156,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
||||
const [sendingDisabled, setSendingDisabled] = useState(false)
|
||||
const [selectedImages, setSelectedImages] = useState<string[]>([])
|
||||
const [messageQueue, setMessageQueue] = useState<QueuedMessage[]>([])
|
||||
const isProcessingQueueRef = useRef(false)
|
||||
const retryCountRef = useRef<Map<string, number>>(new Map())
|
||||
const MAX_RETRY_ATTEMPTS = 3
|
||||
|
||||
// we need to hold on to the ask because useEffect > lastMessage will always let us know when an ask comes in and handle it, but by the time handleMessage is called, the last message might not be the ask anymore (it could be a say that followed)
|
||||
const [clineAsk, setClineAsk] = useState<ClineAsk | undefined>(undefined)
|
||||
|
|
@ -439,6 +445,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
}
|
||||
// Reset user response flag for new task
|
||||
userRespondedRef.current = false
|
||||
|
||||
// Clear message queue when starting a new task
|
||||
setMessageQueue([])
|
||||
// Clear retry counts
|
||||
retryCountRef.current.clear()
|
||||
}, [task?.ts])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -538,47 +549,133 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
disableAutoScrollRef.current = false
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Handles sending messages to the extension
|
||||
* @param text - The message text to send
|
||||
* @param images - Array of image data URLs to send with the message
|
||||
* @param fromQueue - Internal flag indicating if this message is being sent from the queue (prevents re-queueing)
|
||||
*/
|
||||
const handleSendMessage = useCallback(
|
||||
(text: string, images: string[]) => {
|
||||
text = text.trim()
|
||||
(text: string, images: string[], fromQueue = false) => {
|
||||
try {
|
||||
text = text.trim()
|
||||
|
||||
if (text || images.length > 0) {
|
||||
// Mark that user has responded - this prevents any pending auto-approvals
|
||||
userRespondedRef.current = true
|
||||
if (text || images.length > 0) {
|
||||
if (sendingDisabled && !fromQueue) {
|
||||
// Generate a more unique ID using timestamp + random component
|
||||
const messageId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
setMessageQueue((prev) => [...prev, { id: messageId, text, images }])
|
||||
setInputValue("")
|
||||
setSelectedImages([])
|
||||
return
|
||||
}
|
||||
// Mark that user has responded - this prevents any pending auto-approvals
|
||||
userRespondedRef.current = true
|
||||
|
||||
if (messagesRef.current.length === 0) {
|
||||
vscode.postMessage({ type: "newTask", text, images })
|
||||
} else if (clineAskRef.current) {
|
||||
if (clineAskRef.current === "followup") {
|
||||
markFollowUpAsAnswered()
|
||||
if (messagesRef.current.length === 0) {
|
||||
vscode.postMessage({ type: "newTask", text, images })
|
||||
} else if (clineAskRef.current) {
|
||||
if (clineAskRef.current === "followup") {
|
||||
markFollowUpAsAnswered()
|
||||
}
|
||||
|
||||
// Use clineAskRef.current
|
||||
switch (
|
||||
clineAskRef.current // Use clineAskRef.current
|
||||
) {
|
||||
case "followup":
|
||||
case "tool":
|
||||
case "browser_action_launch":
|
||||
case "command": // User can provide feedback to a tool or command use.
|
||||
case "command_output": // User can send input to command stdin.
|
||||
case "use_mcp_server":
|
||||
case "completion_result": // If this happens then the user has feedback for the completion result.
|
||||
case "resume_task":
|
||||
case "resume_completed_task":
|
||||
case "mistake_limit_reached":
|
||||
vscode.postMessage({
|
||||
type: "askResponse",
|
||||
askResponse: "messageResponse",
|
||||
text,
|
||||
images,
|
||||
})
|
||||
break
|
||||
// There is no other case that a textfield should be enabled.
|
||||
}
|
||||
} else {
|
||||
// This is a new message in an ongoing task.
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "messageResponse", text, images })
|
||||
}
|
||||
|
||||
// Use clineAskRef.current
|
||||
switch (
|
||||
clineAskRef.current // Use clineAskRef.current
|
||||
) {
|
||||
case "followup":
|
||||
case "tool":
|
||||
case "browser_action_launch":
|
||||
case "command": // User can provide feedback to a tool or command use.
|
||||
case "command_output": // User can send input to command stdin.
|
||||
case "use_mcp_server":
|
||||
case "completion_result": // If this happens then the user has feedback for the completion result.
|
||||
case "resume_task":
|
||||
case "resume_completed_task":
|
||||
case "mistake_limit_reached":
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "messageResponse", text, images })
|
||||
break
|
||||
// There is no other case that a textfield should be enabled.
|
||||
}
|
||||
handleChatReset()
|
||||
}
|
||||
|
||||
handleChatReset()
|
||||
} catch (error) {
|
||||
console.error("Error in handleSendMessage:", error)
|
||||
// If this was a queued message, we should handle it differently
|
||||
if (fromQueue) {
|
||||
throw error // Re-throw to be caught by the queue processor
|
||||
}
|
||||
// For direct sends, we could show an error to the user
|
||||
// but for now we'll just log it
|
||||
}
|
||||
},
|
||||
[handleChatReset, markFollowUpAsAnswered], // messagesRef and clineAskRef are stable
|
||||
[handleChatReset, markFollowUpAsAnswered, sendingDisabled], // messagesRef and clineAskRef are stable
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// Early return if conditions aren't met
|
||||
// Also don't process queue if there's an API error (clineAsk === "api_req_failed")
|
||||
if (
|
||||
sendingDisabled ||
|
||||
messageQueue.length === 0 ||
|
||||
isProcessingQueueRef.current ||
|
||||
clineAsk === "api_req_failed"
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Mark as processing immediately to prevent race conditions
|
||||
isProcessingQueueRef.current = true
|
||||
|
||||
// Process the first message in the queue
|
||||
const [nextMessage, ...remaining] = messageQueue
|
||||
|
||||
// Update queue immediately to prevent duplicate processing
|
||||
setMessageQueue(remaining)
|
||||
|
||||
// Process the message
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
handleSendMessage(nextMessage.text, nextMessage.images, true)
|
||||
// Clear retry count on success
|
||||
retryCountRef.current.delete(nextMessage.id)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to send queued message:", error)
|
||||
|
||||
// Get current retry count
|
||||
const retryCount = retryCountRef.current.get(nextMessage.id) || 0
|
||||
|
||||
// Only re-add if under retry limit
|
||||
if (retryCount < MAX_RETRY_ATTEMPTS) {
|
||||
retryCountRef.current.set(nextMessage.id, retryCount + 1)
|
||||
// Re-add the message to the end of the queue
|
||||
setMessageQueue((current) => [...current, nextMessage])
|
||||
} else {
|
||||
console.error(`Message ${nextMessage.id} failed after ${MAX_RETRY_ATTEMPTS} attempts, discarding`)
|
||||
retryCountRef.current.delete(nextMessage.id)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
isProcessingQueueRef.current = false
|
||||
})
|
||||
|
||||
// Cleanup function to handle component unmount
|
||||
return () => {
|
||||
isProcessingQueueRef.current = false
|
||||
}
|
||||
}, [sendingDisabled, messageQueue, handleSendMessage, clineAsk])
|
||||
|
||||
const handleSetChatBoxMessage = useCallback(
|
||||
(text: string, images: string[]) => {
|
||||
// Avoid nested template literals by breaking down the logic
|
||||
|
|
@ -594,6 +691,18 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
[inputValue, selectedImages],
|
||||
)
|
||||
|
||||
// Cleanup retry count map on unmount
|
||||
useEffect(() => {
|
||||
// Store refs in variables to avoid stale closure issues
|
||||
const retryCountMap = retryCountRef.current
|
||||
const isProcessingRef = isProcessingQueueRef
|
||||
|
||||
return () => {
|
||||
retryCountMap.clear()
|
||||
isProcessingRef.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const startNewTask = useCallback(() => vscode.postMessage({ type: "clearTask" }), [])
|
||||
|
||||
// This logic depends on the useEffect[messages] above to set clineAsk,
|
||||
|
|
@ -1630,7 +1739,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
const areButtonsVisible = showScrollToBottom || primaryButtonText || secondaryButtonText || isStreaming
|
||||
|
||||
return (
|
||||
<div className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}>
|
||||
<div
|
||||
data-testid="chat-view"
|
||||
className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}>
|
||||
{(showAnnouncement || showAnnouncementModal) && (
|
||||
<Announcement
|
||||
hideAnnouncement={() => {
|
||||
|
|
@ -1836,6 +1947,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
</>
|
||||
)}
|
||||
|
||||
<QueuedMessages
|
||||
queue={messageQueue}
|
||||
onRemove={(index) => setMessageQueue((prev) => prev.filter((_, i) => i !== index))}
|
||||
onUpdate={(index, newText) => {
|
||||
setMessageQueue((prev) => prev.map((msg, i) => (i === index ? { ...msg, text: newText } : msg)))
|
||||
}}
|
||||
/>
|
||||
<ChatTextArea
|
||||
ref={textAreaRef}
|
||||
inputValue={inputValue}
|
||||
|
|
|
|||
112
webview-ui/src/components/chat/QueuedMessages.tsx
Normal file
112
webview-ui/src/components/chat/QueuedMessages.tsx
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import React, { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
import { QueuedMessage } from "@roo-code/types"
|
||||
import { Mention } from "./Mention"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
interface QueuedMessagesProps {
|
||||
queue: QueuedMessage[]
|
||||
onRemove: (index: number) => void
|
||||
onUpdate: (index: number, newText: string) => void
|
||||
}
|
||||
|
||||
const QueuedMessages: React.FC<QueuedMessagesProps> = ({ queue, onRemove, onUpdate }) => {
|
||||
const { t } = useTranslation("chat")
|
||||
const [editingStates, setEditingStates] = useState<Record<string, { isEditing: boolean; value: string }>>({})
|
||||
|
||||
if (queue.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const getEditState = (messageId: string, currentText: string) => {
|
||||
return editingStates[messageId] || { isEditing: false, value: currentText }
|
||||
}
|
||||
|
||||
const setEditState = (messageId: string, isEditing: boolean, value?: string) => {
|
||||
setEditingStates((prev) => ({
|
||||
...prev,
|
||||
[messageId]: { isEditing, value: value ?? prev[messageId]?.value ?? "" },
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSaveEdit = (index: number, messageId: string, newValue: string) => {
|
||||
onUpdate(index, newValue)
|
||||
setEditState(messageId, false)
|
||||
}
|
||||
|
||||
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="flex flex-col gap-2 max-h-[300px] overflow-y-auto pr-2">
|
||||
{queue.map((message, index) => {
|
||||
const editState = getEditState(message.id, message.text)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={message.id}
|
||||
className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap flex-shrink-0">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-grow px-2 py-1 wrap-anywhere">
|
||||
{editState.isEditing ? (
|
||||
<textarea
|
||||
ref={(textarea) => {
|
||||
if (textarea) {
|
||||
// Set cursor at the end
|
||||
textarea.setSelectionRange(
|
||||
textarea.value.length,
|
||||
textarea.value.length,
|
||||
)
|
||||
}
|
||||
}}
|
||||
value={editState.value}
|
||||
onChange={(e) => setEditState(message.id, true, e.target.value)}
|
||||
onBlur={() => handleSaveEdit(index, message.id, editState.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
handleSaveEdit(index, message.id, editState.value)
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setEditState(message.id, false, message.text)
|
||||
}
|
||||
}}
|
||||
className="w-full bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded px-2 py-1 resize-none focus:outline-0 focus:ring-1 focus:ring-vscode-focusBorder"
|
||||
placeholder={t("chat:editMessage.placeholder")}
|
||||
autoFocus
|
||||
rows={Math.min(editState.value.split("\n").length, 10)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
onClick={() => setEditState(message.id, true, message.text)}
|
||||
className="cursor-pointer hover:bg-vscode-list-hoverBackground px-1 py-0.5 -mx-1 -my-0.5 rounded transition-colors"
|
||||
title={t("chat:queuedMessages.clickToEdit")}>
|
||||
<Mention text={message.text} withShadow />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onRemove(index)
|
||||
}}>
|
||||
<span className="codicon codicon-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{message.images && message.images.length > 0 && (
|
||||
<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QueuedMessages
|
||||
|
|
@ -77,7 +77,7 @@ describe("ChatTextArea", () => {
|
|||
})
|
||||
|
||||
describe("enhance prompt button", () => {
|
||||
it("should be disabled when sendingDisabled is true", () => {
|
||||
it("should be enabled even when sendingDisabled is true (for message queueing)", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
|
|
@ -86,7 +86,7 @@ describe("ChatTextArea", () => {
|
|||
})
|
||||
render(<ChatTextArea {...defaultProps} sendingDisabled={true} />)
|
||||
const enhanceButton = getEnhancePromptButton()
|
||||
expect(enhanceButton).toHaveClass("cursor-not-allowed")
|
||||
expect(enhanceButton).toHaveClass("cursor-pointer")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
4
webview-ui/src/i18n/locales/ca/chat.json
generated
4
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Activa la comanda {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Missatges en cua:",
|
||||
"clickToEdit": "Feu clic per editar el missatge"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/de/chat.json
generated
4
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"editMessage": {
|
||||
"placeholder": "Bearbeite deine Nachricht..."
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Warteschlange Nachrichten:",
|
||||
"clickToEdit": "Klicken zum Bearbeiten der Nachricht"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Trigger the {{name}} command"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Queued Messages:",
|
||||
"clickToEdit": "Click to edit message"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/es/chat.json
generated
4
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Activar el comando {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Mensajes en cola:",
|
||||
"clickToEdit": "Haz clic para editar el mensaje"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/fr/chat.json
generated
4
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Déclencher la commande {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Messages en file d'attente :",
|
||||
"clickToEdit": "Cliquez pour modifier le message"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/hi/chat.json
generated
4
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "{{name}} कमांड को ट्रिगर करें"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "कतार में संदेश:",
|
||||
"clickToEdit": "संदेश संपादित करने के लिए क्लिक करें"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/id/chat.json
generated
4
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -357,5 +357,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Jalankan perintah {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Pesan Antrian:",
|
||||
"clickToEdit": "Klik untuk mengedit pesan"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/it/chat.json
generated
4
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Attiva il comando {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Messaggi in coda:",
|
||||
"clickToEdit": "Clicca per modificare il messaggio"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ja/chat.json
generated
4
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "{{name}}コマンドをトリガー"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "キューメッセージ:",
|
||||
"clickToEdit": "クリックしてメッセージを編集"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ko/chat.json
generated
4
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "{{name}} 명령 트리거"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "대기열 메시지:",
|
||||
"clickToEdit": "클릭하여 메시지 편집"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/nl/chat.json
generated
4
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Activeer de {{name}} opdracht"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Berichten in wachtrij:",
|
||||
"clickToEdit": "Klik om bericht te bewerken"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/pl/chat.json
generated
4
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Uruchom polecenie {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Wiadomości w kolejce:",
|
||||
"clickToEdit": "Kliknij, aby edytować wiadomość"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
4
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Acionar o comando {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Mensagens na fila:",
|
||||
"clickToEdit": "Clique para editar a mensagem"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ru/chat.json
generated
4
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Запустить команду {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Сообщения в очереди:",
|
||||
"clickToEdit": "Нажмите, чтобы редактировать сообщение"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/tr/chat.json
generated
4
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "{{name}} komutunu tetikle"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Sıradaki Mesajlar:",
|
||||
"clickToEdit": "Mesajı düzenlemek için tıkla"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/vi/chat.json
generated
4
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "Kích hoạt lệnh {{name}}"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "Tin nhắn trong hàng đợi:",
|
||||
"clickToEdit": "Nhấp để chỉnh sửa tin nhắn"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
4
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"editMessage": {
|
||||
"placeholder": "编辑消息..."
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "队列消息:",
|
||||
"clickToEdit": "点击编辑消息"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
4
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -351,5 +351,9 @@
|
|||
},
|
||||
"command": {
|
||||
"triggerDescription": "觸發 {{name}} 命令"
|
||||
},
|
||||
"queuedMessages": {
|
||||
"title": "佇列訊息:",
|
||||
"clickToEdit": "點擊編輯訊息"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue