Browse Source

Refactor ClineAsk

Saoud Rizwan 1 year ago
parent
commit
d5a998a23a

+ 8 - 8
src/core/Cline.ts

@@ -24,14 +24,14 @@ import { combineCommandSequences } from "../shared/combineCommandSequences"
 import {
 import {
 	ClaudeApiReqCancelReason,
 	ClaudeApiReqCancelReason,
 	ClaudeApiReqInfo,
 	ClaudeApiReqInfo,
-	ClaudeAsk,
+	ClineAsk,
 	ClineMessage,
 	ClineMessage,
 	ClaudeSay,
 	ClaudeSay,
 	ClaudeSayTool,
 	ClaudeSayTool,
 } from "../shared/ExtensionMessage"
 } from "../shared/ExtensionMessage"
 import { getApiMetrics } from "../shared/getApiMetrics"
 import { getApiMetrics } from "../shared/getApiMetrics"
 import { HistoryItem } from "../shared/HistoryItem"
 import { HistoryItem } from "../shared/HistoryItem"
-import { ClaudeAskResponse } from "../shared/WebviewMessage"
+import { ClineAskResponse } from "../shared/WebviewMessage"
 import { calculateApiCost } from "../utils/cost"
 import { calculateApiCost } from "../utils/cost"
 import { fileExistsAtPath } from "../utils/fs"
 import { fileExistsAtPath } from "../utils/fs"
 import { arePathsEqual, getReadablePath } from "../utils/path"
 import { arePathsEqual, getReadablePath } from "../utils/path"
@@ -60,7 +60,7 @@ export class Cline {
 	alwaysAllowReadOnly: boolean
 	alwaysAllowReadOnly: boolean
 	apiConversationHistory: Anthropic.MessageParam[] = []
 	apiConversationHistory: Anthropic.MessageParam[] = []
 	claudeMessages: ClineMessage[] = []
 	claudeMessages: ClineMessage[] = []
-	private askResponse?: ClaudeAskResponse
+	private askResponse?: ClineAskResponse
 	private askResponseText?: string
 	private askResponseText?: string
 	private askResponseImages?: string[]
 	private askResponseImages?: string[]
 	private lastMessageTs?: number
 	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)
 	// partial has three valid states true (partial message), false (completion of partial message), undefined (individual complete message)
 	async ask(
 	async ask(
-		type: ClaudeAsk,
+		type: ClineAsk,
 		text?: string,
 		text?: string,
 		partial?: boolean
 		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 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) {
 		if (this.abort) {
 			throw new Error("Cline instance aborted")
 			throw new Error("Cline instance aborted")
@@ -302,7 +302,7 @@ export class Cline {
 		return result
 		return result
 	}
 	}
 
 
-	async handleWebviewAskResponse(askResponse: ClaudeAskResponse, text?: string, images?: string[]) {
+	async handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) {
 		this.askResponse = askResponse
 		this.askResponse = askResponse
 		this.askResponseText = text
 		this.askResponseText = text
 		this.askResponseImages = images
 		this.askResponseImages = images
@@ -442,7 +442,7 @@ export class Cline {
 		// 	)
 		// 	)
 		// (lastClaudeMessage?.ask === "command" && secondLastClaudeMessage?.ask === "completion_result")
 		// (lastClaudeMessage?.ask === "command" && secondLastClaudeMessage?.ask === "completion_result")
 
 
-		let askType: ClaudeAsk
+		let askType: ClineAsk
 		if (lastClaudeMessage?.ask === "completion_result") {
 		if (lastClaudeMessage?.ask === "completion_result") {
 			askType = "resume_completed_task"
 			askType = "resume_completed_task"
 		} else {
 		} 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)
 					const { response, text, images } = await this.ask(type, partialMessage, false)
 					if (response !== "yesButtonTapped") {
 					if (response !== "yesButtonTapped") {
 						if (response === "messageResponse") {
 						if (response === "messageResponse") {

+ 2 - 2
src/shared/ExtensionMessage.ts

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

+ 2 - 2
src/shared/WebviewMessage.ts

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

+ 28 - 28
webview-ui/src/components/chat/ChatView.tsx

@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
 import { useDeepCompareEffect, useEvent, useMount } from "react-use"
 import { useDeepCompareEffect, useEvent, useMount } from "react-use"
 import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
 import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
 import styled from "styled-components"
 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 { findLast } from "../../../../src/shared/array"
 import { combineApiRequests } from "../../../../src/shared/combineApiRequests"
 import { combineApiRequests } from "../../../../src/shared/combineApiRequests"
 import { combineCommandSequences } from "../../../../src/shared/combineCommandSequences"
 import { combineCommandSequences } from "../../../../src/shared/combineCommandSequences"
@@ -42,7 +42,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 	const [selectedImages, setSelectedImages] = useState<string[]>([])
 	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)
 	// 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 [enableButtons, setEnableButtons] = useState<boolean>(false)
 	const [primaryButtonText, setPrimaryButtonText] = useState<string | undefined>(undefined)
 	const [primaryButtonText, setPrimaryButtonText] = useState<string | undefined>(undefined)
 	const [secondaryButtonText, setSecondaryButtonText] = useState<string | undefined>(undefined)
 	const [secondaryButtonText, setSecondaryButtonText] = useState<string | undefined>(undefined)
@@ -69,28 +69,28 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 					switch (lastMessage.ask) {
 					switch (lastMessage.ask) {
 						case "api_req_failed":
 						case "api_req_failed":
 							setTextAreaDisabled(true)
 							setTextAreaDisabled(true)
-							setClaudeAsk("api_req_failed")
+							setClineAsk("api_req_failed")
 							setEnableButtons(true)
 							setEnableButtons(true)
 							setPrimaryButtonText("Retry")
 							setPrimaryButtonText("Retry")
 							setSecondaryButtonText("Start New Task")
 							setSecondaryButtonText("Start New Task")
 							break
 							break
 						case "mistake_limit_reached":
 						case "mistake_limit_reached":
 							setTextAreaDisabled(false)
 							setTextAreaDisabled(false)
-							setClaudeAsk("mistake_limit_reached")
+							setClineAsk("mistake_limit_reached")
 							setEnableButtons(true)
 							setEnableButtons(true)
 							setPrimaryButtonText("Proceed Anyways")
 							setPrimaryButtonText("Proceed Anyways")
 							setSecondaryButtonText("Start New Task")
 							setSecondaryButtonText("Start New Task")
 							break
 							break
 						case "followup":
 						case "followup":
 							setTextAreaDisabled(isPartial)
 							setTextAreaDisabled(isPartial)
-							setClaudeAsk("followup")
+							setClineAsk("followup")
 							setEnableButtons(isPartial)
 							setEnableButtons(isPartial)
 							// setPrimaryButtonText(undefined)
 							// setPrimaryButtonText(undefined)
 							// setSecondaryButtonText(undefined)
 							// setSecondaryButtonText(undefined)
 							break
 							break
 						case "tool":
 						case "tool":
 							setTextAreaDisabled(isPartial)
 							setTextAreaDisabled(isPartial)
-							setClaudeAsk("tool")
+							setClineAsk("tool")
 							setEnableButtons(!isPartial)
 							setEnableButtons(!isPartial)
 							const tool = JSON.parse(lastMessage.text || "{}") as ClaudeSayTool
 							const tool = JSON.parse(lastMessage.text || "{}") as ClaudeSayTool
 							switch (tool.tool) {
 							switch (tool.tool) {
@@ -107,14 +107,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 							break
 							break
 						case "command":
 						case "command":
 							setTextAreaDisabled(isPartial)
 							setTextAreaDisabled(isPartial)
-							setClaudeAsk("command")
+							setClineAsk("command")
 							setEnableButtons(!isPartial)
 							setEnableButtons(!isPartial)
 							setPrimaryButtonText("Run Command")
 							setPrimaryButtonText("Run Command")
 							setSecondaryButtonText("Reject")
 							setSecondaryButtonText("Reject")
 							break
 							break
 						case "command_output":
 						case "command_output":
 							setTextAreaDisabled(false)
 							setTextAreaDisabled(false)
-							setClaudeAsk("command_output")
+							setClineAsk("command_output")
 							setEnableButtons(true)
 							setEnableButtons(true)
 							setPrimaryButtonText("Proceed While Running")
 							setPrimaryButtonText("Proceed While Running")
 							setSecondaryButtonText(undefined)
 							setSecondaryButtonText(undefined)
@@ -122,14 +122,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 						case "completion_result":
 						case "completion_result":
 							// extension waiting for feedback. but we can just present a new task button
 							// extension waiting for feedback. but we can just present a new task button
 							setTextAreaDisabled(isPartial)
 							setTextAreaDisabled(isPartial)
-							setClaudeAsk("completion_result")
+							setClineAsk("completion_result")
 							setEnableButtons(!isPartial)
 							setEnableButtons(!isPartial)
 							setPrimaryButtonText("Start New Task")
 							setPrimaryButtonText("Start New Task")
 							setSecondaryButtonText(undefined)
 							setSecondaryButtonText(undefined)
 							break
 							break
 						case "resume_task":
 						case "resume_task":
 							setTextAreaDisabled(false)
 							setTextAreaDisabled(false)
-							setClaudeAsk("resume_task")
+							setClineAsk("resume_task")
 							setEnableButtons(true)
 							setEnableButtons(true)
 							setPrimaryButtonText("Resume Task")
 							setPrimaryButtonText("Resume Task")
 							setSecondaryButtonText(undefined)
 							setSecondaryButtonText(undefined)
@@ -137,7 +137,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 							break
 							break
 						case "resume_completed_task":
 						case "resume_completed_task":
 							setTextAreaDisabled(false)
 							setTextAreaDisabled(false)
-							setClaudeAsk("resume_completed_task")
+							setClineAsk("resume_completed_task")
 							setEnableButtons(true)
 							setEnableButtons(true)
 							setPrimaryButtonText("Start New Task")
 							setPrimaryButtonText("Start New Task")
 							setSecondaryButtonText(undefined)
 							setSecondaryButtonText(undefined)
@@ -154,7 +154,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 								setInputValue("")
 								setInputValue("")
 								setTextAreaDisabled(true)
 								setTextAreaDisabled(true)
 								setSelectedImages([])
 								setSelectedImages([])
-								setClaudeAsk(undefined)
+								setClineAsk(undefined)
 								setEnableButtons(false)
 								setEnableButtons(false)
 							}
 							}
 							break
 							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
 			// 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
 			// No messages, so user has to submit a task
 			// setTextAreaDisabled(false)
 			// setTextAreaDisabled(false)
-			// setClaudeAsk(undefined)
+			// setClineAsk(undefined)
 			// setPrimaryButtonText(undefined)
 			// setPrimaryButtonText(undefined)
 			// setSecondaryButtonText(undefined)
 			// setSecondaryButtonText(undefined)
 		}
 		}
@@ -183,7 +183,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 	useEffect(() => {
 	useEffect(() => {
 		if (messages.length === 0) {
 		if (messages.length === 0) {
 			setTextAreaDisabled(false)
 			setTextAreaDisabled(false)
-			setClaudeAsk(undefined)
+			setClineAsk(undefined)
 			setEnableButtons(false)
 			setEnableButtons(false)
 			setPrimaryButtonText(undefined)
 			setPrimaryButtonText(undefined)
 			setSecondaryButtonText(undefined)
 			setSecondaryButtonText(undefined)
@@ -191,9 +191,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 	}, [messages.length])
 	}, [messages.length])
 
 
 	const isStreaming = useMemo(() => {
 	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 =
 		const isToolCurrentlyAsking =
-			isLastAsk && claudeAsk !== undefined && enableButtons && primaryButtonText !== undefined
+			isLastAsk && clineAsk !== undefined && enableButtons && primaryButtonText !== undefined
 		if (isToolCurrentlyAsking) {
 		if (isToolCurrentlyAsking) {
 			return false
 			return false
 		}
 		}
@@ -213,7 +213,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 		}
 		}
 
 
 		return false
 		return false
-	}, [modifiedMessages, claudeAsk, enableButtons, primaryButtonText])
+	}, [modifiedMessages, clineAsk, enableButtons, primaryButtonText])
 
 
 	const handleSendMessage = useCallback(
 	const handleSendMessage = useCallback(
 		(text: string, images: string[]) => {
 		(text: string, images: string[]) => {
@@ -221,8 +221,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 			if (text || images.length > 0) {
 			if (text || images.length > 0) {
 				if (messages.length === 0) {
 				if (messages.length === 0) {
 					vscode.postMessage({ type: "newTask", text, images })
 					vscode.postMessage({ type: "newTask", text, images })
-				} else if (claudeAsk) {
-					switch (claudeAsk) {
+				} else if (clineAsk) {
+					switch (clineAsk) {
 						case "followup":
 						case "followup":
 						case "tool":
 						case "tool":
 						case "command": // user can provide feedback to a tool or command use
 						case "command": // user can provide feedback to a tool or command use
@@ -244,14 +244,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 				setInputValue("")
 				setInputValue("")
 				setTextAreaDisabled(true)
 				setTextAreaDisabled(true)
 				setSelectedImages([])
 				setSelectedImages([])
-				setClaudeAsk(undefined)
+				setClineAsk(undefined)
 				setEnableButtons(false)
 				setEnableButtons(false)
 				// setPrimaryButtonText(undefined)
 				// setPrimaryButtonText(undefined)
 				// setSecondaryButtonText(undefined)
 				// setSecondaryButtonText(undefined)
 				disableAutoScrollRef.current = false
 				disableAutoScrollRef.current = false
 			}
 			}
 		},
 		},
-		[messages.length, claudeAsk]
+		[messages.length, clineAsk]
 	)
 	)
 
 
 	const startNewTask = useCallback(() => {
 	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(() => {
 	const handlePrimaryButtonClick = useCallback(() => {
-		switch (claudeAsk) {
+		switch (clineAsk) {
 			case "api_req_failed":
 			case "api_req_failed":
 			case "command":
 			case "command":
 			case "command_output":
 			case "command_output":
@@ -278,11 +278,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 				break
 				break
 		}
 		}
 		setTextAreaDisabled(true)
 		setTextAreaDisabled(true)
-		setClaudeAsk(undefined)
+		setClineAsk(undefined)
 		setEnableButtons(false)
 		setEnableButtons(false)
 		// setPrimaryButtonText(undefined)
 		// setPrimaryButtonText(undefined)
 		// setSecondaryButtonText(undefined)
 		// setSecondaryButtonText(undefined)
-	}, [claudeAsk, startNewTask])
+	}, [clineAsk, startNewTask])
 
 
 	const handleSecondaryButtonClick = useCallback(() => {
 	const handleSecondaryButtonClick = useCallback(() => {
 		if (isStreaming) {
 		if (isStreaming) {
@@ -291,7 +291,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 			return
 			return
 		}
 		}
 
 
-		switch (claudeAsk) {
+		switch (clineAsk) {
 			case "api_req_failed":
 			case "api_req_failed":
 			case "mistake_limit_reached":
 			case "mistake_limit_reached":
 				startNewTask()
 				startNewTask()
@@ -303,11 +303,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 				break
 				break
 		}
 		}
 		setTextAreaDisabled(true)
 		setTextAreaDisabled(true)
-		setClaudeAsk(undefined)
+		setClineAsk(undefined)
 		setEnableButtons(false)
 		setEnableButtons(false)
 		// setPrimaryButtonText(undefined)
 		// setPrimaryButtonText(undefined)
 		// setSecondaryButtonText(undefined)
 		// setSecondaryButtonText(undefined)
-	}, [claudeAsk, startNewTask, isStreaming])
+	}, [clineAsk, startNewTask, isStreaming])
 
 
 	const handleTaskCloseButtonClick = useCallback(() => {
 	const handleTaskCloseButtonClick = useCallback(() => {
 		startNewTask()
 		startNewTask()