Refactor ClineAsk

This commit is contained in:
Saoud Rizwan
2024-10-06 04:24:23 -04:00
parent 7ee0a58f9b
commit d5a998a23a
4 changed files with 40 additions and 40 deletions

View File

@@ -24,14 +24,14 @@ import { combineCommandSequences } from "../shared/combineCommandSequences"
import {
ClaudeApiReqCancelReason,
ClaudeApiReqInfo,
ClaudeAsk,
ClineAsk,
ClineMessage,
ClaudeSay,
ClaudeSayTool,
} from "../shared/ExtensionMessage"
import { getApiMetrics } from "../shared/getApiMetrics"
import { HistoryItem } from "../shared/HistoryItem"
import { ClaudeAskResponse } from "../shared/WebviewMessage"
import { ClineAskResponse } from "../shared/WebviewMessage"
import { calculateApiCost } from "../utils/cost"
import { fileExistsAtPath } from "../utils/fs"
import { arePathsEqual, getReadablePath } from "../utils/path"
@@ -60,7 +60,7 @@ export class Cline {
alwaysAllowReadOnly: boolean
apiConversationHistory: Anthropic.MessageParam[] = []
claudeMessages: ClineMessage[] = []
private askResponse?: ClaudeAskResponse
private askResponse?: ClineAskResponse
private askResponseText?: string
private askResponseImages?: string[]
private lastMessageTs?: number
@@ -208,10 +208,10 @@ export class Cline {
// partial has three valid states true (partial message), false (completion of partial message), undefined (individual complete message)
async ask(
type: ClaudeAsk,
type: ClineAsk,
text?: string,
partial?: boolean
): Promise<{ response: ClaudeAskResponse; text?: string; images?: string[] }> {
): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> {
// If this Cline instance was aborted by the provider, then the only thing keeping us alive is a promise still running in the background, in which case we don't want to send its result to the webview as it is attached to a new instance of Cline now. So we can safely ignore the result of any active promises, and this class will be deallocated. (Although we set Cline = undefined in provider, that simply removes the reference to this instance, but the instance is still alive until this promise resolves or rejects.)
if (this.abort) {
throw new Error("Cline instance aborted")
@@ -302,7 +302,7 @@ export class Cline {
return result
}
async handleWebviewAskResponse(askResponse: ClaudeAskResponse, text?: string, images?: string[]) {
async handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) {
this.askResponse = askResponse
this.askResponseText = text
this.askResponseImages = images
@@ -442,7 +442,7 @@ export class Cline {
// )
// (lastClaudeMessage?.ask === "command" && secondLastClaudeMessage?.ask === "completion_result")
let askType: ClaudeAsk
let askType: ClineAsk
if (lastClaudeMessage?.ask === "completion_result") {
askType = "resume_completed_task"
} else {
@@ -875,7 +875,7 @@ export class Cline {
}
}
const askApproval = async (type: ClaudeAsk, partialMessage?: string) => {
const askApproval = async (type: ClineAsk, partialMessage?: string) => {
const { response, text, images } = await this.ask(type, partialMessage, false)
if (response !== "yesButtonTapped") {
if (response === "messageResponse") {

View File

@@ -40,14 +40,14 @@ export interface ExtensionState {
export interface ClineMessage {
ts: number
type: "ask" | "say"
ask?: ClaudeAsk
ask?: ClineAsk
say?: ClaudeSay
text?: string
images?: string[]
partial?: boolean
}
export type ClaudeAsk =
export type ClineAsk =
| "followup"
| "command"
| "command_output"

View File

@@ -23,10 +23,10 @@ export interface WebviewMessage {
| "cancelTask"
| "refreshOpenRouterModels"
text?: string
askResponse?: ClaudeAskResponse
askResponse?: ClineAskResponse
apiConfiguration?: ApiConfiguration
images?: string[]
bool?: boolean
}
export type ClaudeAskResponse = "yesButtonTapped" | "noButtonTapped" | "messageResponse"
export type ClineAskResponse = "yesButtonTapped" | "noButtonTapped" | "messageResponse"

View File

@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useDeepCompareEffect, useEvent, useMount } from "react-use"
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
import styled from "styled-components"
import { ClaudeAsk, ClaudeSayTool, ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
import { ClineAsk, ClaudeSayTool, ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
import { findLast } from "../../../../src/shared/array"
import { combineApiRequests } from "../../../../src/shared/combineApiRequests"
import { combineCommandSequences } from "../../../../src/shared/combineCommandSequences"
@@ -42,7 +42,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
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)
const [claudeAsk, setClaudeAsk] = useState<ClaudeAsk | undefined>(undefined)
const [clineAsk, setClineAsk] = useState<ClineAsk | undefined>(undefined)
const [enableButtons, setEnableButtons] = useState<boolean>(false)
const [primaryButtonText, setPrimaryButtonText] = useState<string | undefined>(undefined)
const [secondaryButtonText, setSecondaryButtonText] = useState<string | undefined>(undefined)
@@ -69,28 +69,28 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
switch (lastMessage.ask) {
case "api_req_failed":
setTextAreaDisabled(true)
setClaudeAsk("api_req_failed")
setClineAsk("api_req_failed")
setEnableButtons(true)
setPrimaryButtonText("Retry")
setSecondaryButtonText("Start New Task")
break
case "mistake_limit_reached":
setTextAreaDisabled(false)
setClaudeAsk("mistake_limit_reached")
setClineAsk("mistake_limit_reached")
setEnableButtons(true)
setPrimaryButtonText("Proceed Anyways")
setSecondaryButtonText("Start New Task")
break
case "followup":
setTextAreaDisabled(isPartial)
setClaudeAsk("followup")
setClineAsk("followup")
setEnableButtons(isPartial)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
break
case "tool":
setTextAreaDisabled(isPartial)
setClaudeAsk("tool")
setClineAsk("tool")
setEnableButtons(!isPartial)
const tool = JSON.parse(lastMessage.text || "{}") as ClaudeSayTool
switch (tool.tool) {
@@ -107,14 +107,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
break
case "command":
setTextAreaDisabled(isPartial)
setClaudeAsk("command")
setClineAsk("command")
setEnableButtons(!isPartial)
setPrimaryButtonText("Run Command")
setSecondaryButtonText("Reject")
break
case "command_output":
setTextAreaDisabled(false)
setClaudeAsk("command_output")
setClineAsk("command_output")
setEnableButtons(true)
setPrimaryButtonText("Proceed While Running")
setSecondaryButtonText(undefined)
@@ -122,14 +122,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "completion_result":
// extension waiting for feedback. but we can just present a new task button
setTextAreaDisabled(isPartial)
setClaudeAsk("completion_result")
setClineAsk("completion_result")
setEnableButtons(!isPartial)
setPrimaryButtonText("Start New Task")
setSecondaryButtonText(undefined)
break
case "resume_task":
setTextAreaDisabled(false)
setClaudeAsk("resume_task")
setClineAsk("resume_task")
setEnableButtons(true)
setPrimaryButtonText("Resume Task")
setSecondaryButtonText(undefined)
@@ -137,7 +137,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
break
case "resume_completed_task":
setTextAreaDisabled(false)
setClaudeAsk("resume_completed_task")
setClineAsk("resume_completed_task")
setEnableButtons(true)
setPrimaryButtonText("Start New Task")
setSecondaryButtonText(undefined)
@@ -154,7 +154,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setInputValue("")
setTextAreaDisabled(true)
setSelectedImages([])
setClaudeAsk(undefined)
setClineAsk(undefined)
setEnableButtons(false)
}
break
@@ -174,7 +174,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
// this would get called after sending the first message, so we have to watch messages.length instead
// No messages, so user has to submit a task
// setTextAreaDisabled(false)
// setClaudeAsk(undefined)
// setClineAsk(undefined)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
}
@@ -183,7 +183,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
useEffect(() => {
if (messages.length === 0) {
setTextAreaDisabled(false)
setClaudeAsk(undefined)
setClineAsk(undefined)
setEnableButtons(false)
setPrimaryButtonText(undefined)
setSecondaryButtonText(undefined)
@@ -191,9 +191,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}, [messages.length])
const isStreaming = useMemo(() => {
const isLastAsk = !!modifiedMessages.at(-1)?.ask // checking claudeAsk isn't enough since messages effect may be called again for a tool for example, set claudeAsk to its value, and if the next message is not an ask then it doesn't reset. This is likely due to how much more often we're updating messages as compared to before, and should be resolved with optimizations as it's likely a rendering bug. but as a final guard for now, the cancel button will show if the last message is not an ask
const isLastAsk = !!modifiedMessages.at(-1)?.ask // checking clineAsk isn't enough since messages effect may be called again for a tool for example, set clineAsk to its value, and if the next message is not an ask then it doesn't reset. This is likely due to how much more often we're updating messages as compared to before, and should be resolved with optimizations as it's likely a rendering bug. but as a final guard for now, the cancel button will show if the last message is not an ask
const isToolCurrentlyAsking =
isLastAsk && claudeAsk !== undefined && enableButtons && primaryButtonText !== undefined
isLastAsk && clineAsk !== undefined && enableButtons && primaryButtonText !== undefined
if (isToolCurrentlyAsking) {
return false
}
@@ -213,7 +213,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}
return false
}, [modifiedMessages, claudeAsk, enableButtons, primaryButtonText])
}, [modifiedMessages, clineAsk, enableButtons, primaryButtonText])
const handleSendMessage = useCallback(
(text: string, images: string[]) => {
@@ -221,8 +221,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
if (text || images.length > 0) {
if (messages.length === 0) {
vscode.postMessage({ type: "newTask", text, images })
} else if (claudeAsk) {
switch (claudeAsk) {
} else if (clineAsk) {
switch (clineAsk) {
case "followup":
case "tool":
case "command": // user can provide feedback to a tool or command use
@@ -244,14 +244,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setInputValue("")
setTextAreaDisabled(true)
setSelectedImages([])
setClaudeAsk(undefined)
setClineAsk(undefined)
setEnableButtons(false)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
disableAutoScrollRef.current = false
}
},
[messages.length, claudeAsk]
[messages.length, clineAsk]
)
const startNewTask = useCallback(() => {
@@ -259,10 +259,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}, [])
/*
This logic depends on the useEffect[messages] above to set claudeAsk, after which buttons are shown and we then send an askResponse to the extension.
This logic depends on the useEffect[messages] above to set clineAsk, after which buttons are shown and we then send an askResponse to the extension.
*/
const handlePrimaryButtonClick = useCallback(() => {
switch (claudeAsk) {
switch (clineAsk) {
case "api_req_failed":
case "command":
case "command_output":
@@ -278,11 +278,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
break
}
setTextAreaDisabled(true)
setClaudeAsk(undefined)
setClineAsk(undefined)
setEnableButtons(false)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
}, [claudeAsk, startNewTask])
}, [clineAsk, startNewTask])
const handleSecondaryButtonClick = useCallback(() => {
if (isStreaming) {
@@ -291,7 +291,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
return
}
switch (claudeAsk) {
switch (clineAsk) {
case "api_req_failed":
case "mistake_limit_reached":
startNewTask()
@@ -303,11 +303,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
break
}
setTextAreaDisabled(true)
setClaudeAsk(undefined)
setClineAsk(undefined)
setEnableButtons(false)
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
}, [claudeAsk, startNewTask, isStreaming])
}, [clineAsk, startNewTask, isStreaming])
const handleTaskCloseButtonClick = useCallback(() => {
startNewTask()