From d5a998a23ad494bf3e4cd86d220009e3f8d9bf79 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 6 Oct 2024 04:24:23 -0400 Subject: [PATCH] Refactor ClineAsk --- src/core/Cline.ts | 16 +++--- src/shared/ExtensionMessage.ts | 4 +- src/shared/WebviewMessage.ts | 4 +- webview-ui/src/components/chat/ChatView.tsx | 56 ++++++++++----------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index d444f55..2de636d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -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") { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 6d57824..14867ae 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -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" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 0c72775..f37c520 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -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" diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index a976b13..a2be7d7 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/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 { 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([]) // 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(undefined) + const [clineAsk, setClineAsk] = useState(undefined) const [enableButtons, setEnableButtons] = useState(false) const [primaryButtonText, setPrimaryButtonText] = useState(undefined) const [secondaryButtonText, setSecondaryButtonText] = useState(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()