Chat modes

This commit is contained in:
Matt Rubens
2025-01-03 22:39:52 -08:00
parent ae9a35b7b1
commit 344c796f2e
52 changed files with 6273 additions and 1500 deletions

View File

@@ -14,6 +14,7 @@ import ContextMenu from "./ContextMenu"
import Thumbnails from "../common/Thumbnails"
import { vscode } from "../../utils/vscode"
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
import { Mode } from "../../../../src/core/prompts/types"
interface ChatTextAreaProps {
inputValue: string
@@ -26,6 +27,8 @@ interface ChatTextAreaProps {
onSelectImages: () => void
shouldDisableImages: boolean
onHeightChange?: (height: number) => void
mode: Mode
setMode: (value: Mode) => void
}
const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
@@ -41,6 +44,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onSelectImages,
shouldDisableImages,
onHeightChange,
mode,
setMode,
},
ref,
) => {
@@ -668,56 +673,98 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}}
/>
)}
{(listApiConfigMeta || []).length > 1 && (
<div
<div
style={{
position: "absolute",
left: 25,
bottom: 19,
zIndex: 3,
display: "flex",
gap: 8,
alignItems: "center"
}}
>
<select
value={mode}
disabled={textAreaDisabled}
onChange={(e) => {
const newMode = e.target.value as Mode;
setMode(newMode);
vscode.postMessage({
type: "mode",
text: newMode
});
}}
style={{
position: "absolute",
left: 25,
bottom: 14,
zIndex: 2
fontSize: "11px",
cursor: textAreaDisabled ? "not-allowed" : "pointer",
backgroundColor: "transparent",
border: "none",
color: "var(--vscode-input-foreground)",
opacity: textAreaDisabled ? 0.5 : 0.6,
outline: "none",
paddingLeft: 14,
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
backgroundImage: "url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e\")",
backgroundRepeat: "no-repeat",
backgroundPosition: "left 0px center",
backgroundSize: "10px"
}}>
<option value="code" style={{
backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)"
}}>Code</option>
<option value="architect" style={{
backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)"
}}>Architect</option>
<option value="ask" style={{
backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)"
}}>Ask</option>
</select>
<select
value={currentApiConfigName}
disabled={textAreaDisabled}
onChange={(e) => vscode.postMessage({
type: "loadApiConfiguration",
text: e.target.value
})}
style={{
fontSize: "11px",
cursor: textAreaDisabled ? "not-allowed" : "pointer",
backgroundColor: "transparent",
border: "none",
color: "var(--vscode-input-foreground)",
opacity: textAreaDisabled ? 0.5 : 0.6,
outline: "none",
paddingLeft: 14,
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
backgroundImage: "url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e\")",
backgroundRepeat: "no-repeat",
backgroundPosition: "left 0px center",
backgroundSize: "10px"
}}
>
<select
value={currentApiConfigName}
disabled={textAreaDisabled}
onChange={(e) => vscode.postMessage({
type: "loadApiConfiguration",
text: e.target.value
})}
style={{
fontSize: "11px",
cursor: textAreaDisabled ? "not-allowed" : "pointer",
backgroundColor: "transparent",
border: "none",
color: "var(--vscode-input-foreground)",
opacity: textAreaDisabled ? 0.5 : 0.6,
outline: "none",
paddingLeft: 14,
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
backgroundImage: "url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e\")",
backgroundRepeat: "no-repeat",
backgroundPosition: "left 0px center",
backgroundSize: "10px"
}}
>
{(listApiConfigMeta || [])?.map((config) => (
<option
key={config.name}
value={config.name}
style={{
backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)"
}}
>
{config.name}
</option>
))}
</select>
</div>
)}
<div className="button-row" style={{ position: "absolute", right: 20, display: "flex", alignItems: "center", height: 31, bottom: 8, zIndex: 2, justifyContent: "flex-end" }}>
{(listApiConfigMeta || [])?.map((config) => (
<option
key={config.name}
value={config.name}
style={{
backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)"
}}
>
{config.name}
</option>
))}
</select>
</div>
<div className="button-row" style={{ position: "absolute", right: 12, display: "flex", alignItems: "center", height: 31, bottom: 8, zIndex: 3, padding: "0 8px", justifyContent: "flex-end", backgroundColor: "var(--vscode-input-background)", }}>
<span style={{ display: "flex", alignItems: "center", gap: 12 }}>
{apiConfiguration?.apiProvider === "openrouter" && (
<div style={{ display: "flex", alignItems: "center" }}>

View File

@@ -38,7 +38,7 @@ interface ChatViewProps {
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands, writeDelayMs } = useExtensionState()
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands, writeDelayMs, mode, setMode } = useExtensionState()
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
@@ -297,11 +297,13 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
// there is no other case that a textfield should be enabled
}
}
// Only reset message-specific state, preserving mode
setInputValue("")
setTextAreaDisabled(true)
setSelectedImages([])
setClineAsk(undefined)
setEnableButtons(false)
// Do not reset mode here as it should persist
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
disableAutoScrollRef.current = false
@@ -338,8 +340,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setTextAreaDisabled(true)
setClineAsk(undefined)
setEnableButtons(false)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
disableAutoScrollRef.current = false
}, [clineAsk, startNewTask])
@@ -367,8 +367,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setTextAreaDisabled(true)
setClineAsk(undefined)
setEnableButtons(false)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
disableAutoScrollRef.current = false
}, [clineAsk, startNewTask, isStreaming])
@@ -779,9 +777,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
useEvent("wheel", handleWheel, window, { passive: true }) // passive improves scrolling performance
const placeholderText = useMemo(() => {
const text = task ? "Type a message...\n(@ to add context, hold shift to drag in images)" : "Type your task here...\n(@ to add context, hold shift to drag in images)"
return text
}, [task])
const baseText = task ? "Type a message..." : "Type your task here..."
const contextText = "(@ to add context"
const imageText = shouldDisableImages ? "" : ", hold shift to drag in images"
const helpText = imageText ? `\n${contextText}${imageText})` : `\n${contextText})`
return baseText + helpText
}, [task, shouldDisableImages])
const itemContent = useCallback(
(index: number, messageOrGroup: ClineMessage | ClineMessage[]) => {
@@ -983,6 +984,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
scrollToBottomAuto()
}
}}
mode={mode}
setMode={setMode}
/>
</div>
)

View File

@@ -263,6 +263,7 @@ describe('ChatView - Auto Approval Tests', () => {
// First hydrate state with initial task
mockPostMessage({
alwaysAllowWrite: true,
writeDelayMs: 0,
clineMessages: [
{
type: 'say',
@@ -276,6 +277,7 @@ describe('ChatView - Auto Approval Tests', () => {
// Then send the write tool ask message
mockPostMessage({
alwaysAllowWrite: true,
writeDelayMs: 0,
clineMessages: [
{
type: 'say',