Compare commits

...

3 Commits

Author SHA1 Message Date
Elephant Lumps 49dc0fc516 add sendingDisabled to dependency array 2025-05-07 01:29:03 -05:00
Elephant Lumps 2fb97c872e changeset 2025-05-07 01:20:25 -05:00
Elephant Lumps 4bca0fb927 enable text area while cline is doing stuff 2025-05-07 01:19:51 -05:00
3 changed files with 45 additions and 41 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Allow the user to form their next message while Cline is taking action
+13 -13
View File
@@ -44,7 +44,7 @@ interface ChatTextAreaProps {
inputValue: string
activeQuote: string | null
setInputValue: (value: string) => void
textAreaDisabled: boolean
sendingDisabled: boolean
placeholderText: string
selectedImages: string[]
setSelectedImages: React.Dispatch<React.SetStateAction<string[]>>
@@ -229,7 +229,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
inputValue,
activeQuote,
setInputValue,
textAreaDisabled,
sendingDisabled,
placeholderText,
selectedImages,
setSelectedImages,
@@ -519,8 +519,11 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
const isComposing = event.nativeEvent?.isComposing ?? false
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
event.preventDefault()
setIsTextAreaFocused(false)
onSend()
if (!sendingDisabled) {
setIsTextAreaFocused(false)
onSend()
}
}
if (event.key === "Backspace" && !isComposing) {
@@ -604,6 +607,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
selectedSlashCommandsIndex,
slashCommandsQuery,
handleSlashCommandsSelect,
sendingDisabled,
],
)
@@ -916,8 +920,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
useShortcut("Meta+Shift+a", onModeToggle, { disableTextInputs: false }) // important that we don't disable the text input here
const handleContextButtonClick = useCallback(() => {
if (textAreaDisabled) return
// Focus the textarea first
textAreaRef.current?.focus()
@@ -956,7 +958,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
} as React.ChangeEvent<HTMLTextAreaElement>
handleInputChange(event)
updateHighlights()
}, [inputValue, textAreaDisabled, handleInputChange, updateHighlights])
}, [inputValue, handleInputChange, updateHighlights])
// Use an effect to detect menu close
useEffect(() => {
@@ -1248,7 +1250,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
<div
style={{
padding: "10px 15px",
opacity: textAreaDisabled ? 0.5 : 1,
opacity: 1,
position: "relative",
display: "flex",
// Drag-over styles moved to DynamicTextArea
@@ -1358,7 +1360,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
textAreaRef.current = el
}}
value={inputValue}
disabled={textAreaDisabled}
onChange={(e) => {
handleInputChange(e)
updateHighlights()
@@ -1408,7 +1409,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
// Instead of using boxShadow, we use a div with a border to better replicate the behavior when the textarea is focused
// boxShadow: "0px 0px 0px 1px var(--vscode-input-border)",
padding: "9px 28px 3px 9px",
cursor: textAreaDisabled ? "not-allowed" : undefined,
cursor: "text",
flex: 1,
zIndex: 1,
outline:
@@ -1466,9 +1467,9 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
/> */}
<div
data-testid="send-button"
className={`input-icon-button ${textAreaDisabled ? "disabled" : ""} codicon codicon-send`}
className={`input-icon-button ${sendingDisabled ? "disabled" : ""} codicon codicon-send`}
onClick={() => {
if (!textAreaDisabled) {
if (!sendingDisabled) {
setIsTextAreaFocused(false)
onSend()
}
@@ -1504,7 +1505,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
data-testid="context-button"
appearance="icon"
aria-label="Add Context"
disabled={textAreaDisabled}
onClick={handleContextButtonClick}
style={{ padding: "0px 0px", height: "20px" }}>
<ButtonContainer>
+27 -28
View File
@@ -90,7 +90,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
const [activeQuote, setActiveQuote] = useState<string | null>(null)
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
const textAreaRef = useRef<HTMLTextAreaElement>(null)
const [textAreaDisabled, setTextAreaDisabled] = useState(false)
const [sendingDisabled, setSendingDisabled] = useState(false)
const [selectedImages, setSelectedImages] = useState<string[]>([])
// 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)
@@ -147,42 +147,42 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
const isPartial = lastMessage.partial === true
switch (lastMessage.ask) {
case "api_req_failed":
setTextAreaDisabled(true)
setSendingDisabled(true)
setClineAsk("api_req_failed")
setEnableButtons(true)
setPrimaryButtonText("Retry")
setSecondaryButtonText("Start New Task")
break
case "mistake_limit_reached":
setTextAreaDisabled(false)
setSendingDisabled(false)
setClineAsk("mistake_limit_reached")
setEnableButtons(true)
setPrimaryButtonText("Proceed Anyways")
setSecondaryButtonText("Start New Task")
break
case "auto_approval_max_req_reached":
setTextAreaDisabled(true)
setSendingDisabled(true)
setClineAsk("auto_approval_max_req_reached")
setEnableButtons(true)
setPrimaryButtonText("Proceed")
setSecondaryButtonText("Start New Task")
break
case "followup":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("followup")
setEnableButtons(false)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
break
case "plan_mode_respond":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("plan_mode_respond")
setEnableButtons(false)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
break
case "tool":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("tool")
setEnableButtons(!isPartial)
const tool = JSON.parse(lastMessage.text || "{}") as ClineSayTool
@@ -199,28 +199,28 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}
break
case "browser_action_launch":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("browser_action_launch")
setEnableButtons(!isPartial)
setPrimaryButtonText("Approve")
setSecondaryButtonText("Reject")
break
case "command":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("command")
setEnableButtons(!isPartial)
setPrimaryButtonText("Run Command")
setSecondaryButtonText("Reject")
break
case "command_output":
setTextAreaDisabled(false)
setSendingDisabled(false)
setClineAsk("command_output")
setEnableButtons(true)
setPrimaryButtonText("Proceed While Running")
setSecondaryButtonText(undefined)
break
case "use_mcp_server":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("use_mcp_server")
setEnableButtons(!isPartial)
setPrimaryButtonText("Approve")
@@ -228,14 +228,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
break
case "completion_result":
// extension waiting for feedback. but we can just present a new task button
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("completion_result")
setEnableButtons(!isPartial)
setPrimaryButtonText("Start New Task")
setSecondaryButtonText(undefined)
break
case "resume_task":
setTextAreaDisabled(false)
setSendingDisabled(false)
setClineAsk("resume_task")
setEnableButtons(true)
setPrimaryButtonText("Resume Task")
@@ -243,7 +243,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setDidClickCancel(false) // special case where we reset the cancel button state
break
case "resume_completed_task":
setTextAreaDisabled(false)
setSendingDisabled(false)
setClineAsk("resume_completed_task")
setEnableButtons(true)
setPrimaryButtonText("Start New Task")
@@ -251,14 +251,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setDidClickCancel(false)
break
case "new_task":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("new_task")
setEnableButtons(!isPartial)
setPrimaryButtonText("Start New Task with Context")
setSecondaryButtonText(undefined)
break
case "condense":
setTextAreaDisabled(isPartial)
setSendingDisabled(isPartial)
setClineAsk("condense")
setEnableButtons(!isPartial)
setPrimaryButtonText("Condense Conversation")
@@ -273,7 +273,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
if (secondLastMessage?.ask === "command_output") {
// if the last ask is a command_output, and we receive an api_req_started, then that means the command has finished and we don't need input from the user anymore (in every other case, the user has to interact with input field or buttons to continue, which does the following automatically)
setInputValue("")
setTextAreaDisabled(true)
setSendingDisabled(true)
setSelectedImages([])
setClineAsk(undefined)
setEnableButtons(false)
@@ -310,7 +310,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
useEffect(() => {
if (messages.length === 0) {
setTextAreaDisabled(false)
setSendingDisabled(false)
setClineAsk(undefined)
setEnableButtons(false)
setPrimaryButtonText("Approve")
@@ -397,7 +397,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}
setInputValue("")
setActiveQuote(null) // Clear quote when sending message
setTextAreaDisabled(true)
setSendingDisabled(true)
setSelectedImages([])
setClineAsk(undefined)
setEnableButtons(false)
@@ -467,7 +467,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
})
break
}
setTextAreaDisabled(true)
setSendingDisabled(true)
setClineAsk(undefined)
setEnableButtons(false)
// setPrimaryButtonText(undefined)
@@ -516,7 +516,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setSelectedImages([])
break
}
setTextAreaDisabled(true)
setSendingDisabled(true)
setClineAsk(undefined)
setEnableButtons(false)
// setPrimaryButtonText(undefined)
@@ -542,8 +542,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
vscode.postMessage({ type: "selectImages" })
}, [])
const shouldDisableImages =
!selectedModelInfo.supportsImages || textAreaDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
const shouldDisableImages = !selectedModelInfo.supportsImages || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
const handleMessage = useCallback(
(e: MessageEvent) => {
@@ -552,7 +551,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "action":
switch (message.action!) {
case "didBecomeVisible":
if (!isHidden && !textAreaDisabled && !enableButtons) {
if (!isHidden && !sendingDisabled && !enableButtons) {
textAreaRef.current?.focus()
}
break
@@ -601,7 +600,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}
// textAreaRef.current is not explicitly required here since react guarantees that ref will be stable across re-renders, and we're not using its value but its reference.
},
[isHidden, textAreaDisabled, enableButtons, handleSendMessage, handlePrimaryButtonClick, handleSecondaryButtonClick],
[isHidden, sendingDisabled, enableButtons, handleSendMessage, handlePrimaryButtonClick, handleSecondaryButtonClick],
)
useEvent("message", handleMessage)
@@ -613,14 +612,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
useEffect(() => {
const timer = setTimeout(() => {
if (!isHidden && !textAreaDisabled && !enableButtons) {
if (!isHidden && !sendingDisabled && !enableButtons) {
textAreaRef.current?.focus()
}
}, 50)
return () => {
clearTimeout(timer)
}
}, [isHidden, textAreaDisabled, enableButtons])
}, [isHidden, sendingDisabled, enableButtons])
const visibleMessages = useMemo(() => {
return modifiedMessages.filter((message) => {
@@ -1096,7 +1095,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
activeQuote={activeQuote}
inputValue={inputValue}
setInputValue={setInputValue}
textAreaDisabled={textAreaDisabled}
sendingDisabled={sendingDisabled}
placeholderText={placeholderText}
selectedImages={selectedImages}
setSelectedImages={setSelectedImages}