diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index fa4d7ec82a6..1e1618f22ef 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -48,6 +48,7 @@ import BrowserSessionRow from "./BrowserSessionRow" import ChatRow from "./ChatRow" import ChatTextArea from "./ChatTextArea" import TaskHeader from "./TaskHeader" +import { TaskActivityBar } from "./TaskActivityBar" import AutoApproveMenu from "./AutoApproveMenu" import SystemPromptWarning from "./SystemPromptWarning" import ProfileViolationWarning from "./ProfileViolationWarning" @@ -1612,17 +1613,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction - + diff --git a/webview-ui/src/components/chat/TaskActivityBar.tsx b/webview-ui/src/components/chat/TaskActivityBar.tsx new file mode 100644 index 00000000000..0cddee8ff6f --- /dev/null +++ b/webview-ui/src/components/chat/TaskActivityBar.tsx @@ -0,0 +1,71 @@ +import React from "react" +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" +import { formatLargeNumber } from "@src/utils/format" +import type { ClineMessage } from "@roo-code/types" + +interface TaskActivityBarProps { + task: ClineMessage + tokensIn: number + tokensOut: number + cacheWrites: number + cacheReads: number + totalCost: number + onClose: () => void +} + +export const TaskActivityBar: React.FC = ({ + task, + tokensIn, + tokensOut, + cacheWrites, + cacheReads, + totalCost, + onClose, +}) => { + if (!task) { + return null + } + + const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads + const taskText = task.text || "" + + return ( +
+
+ {/* Task Text */} +
+
+ {taskText} +
+
+ + {/* Metrics */} +
+ {/* Tokens */} + {totalTokens > 0 && ( +
+ Tokens: {formatLargeNumber(totalTokens)} + {(cacheWrites > 0 || cacheReads > 0) && ( + + (Cache: {formatLargeNumber(cacheWrites + cacheReads)}) + + )} +
+ )} + + {/* Cost */} + {totalCost !== undefined && totalCost > 0 && ( +
Cost: ${totalCost.toFixed(2)}
+ )} +
+ + {/* Actions */} +
+ + + +
+
+
+ ) +} diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 1896df486b3..ba91b7175e4 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -1,65 +1,33 @@ -import { memo, useRef, useState } from "react" -import { useWindowSize } from "react-use" +import { memo } from "react" import { useTranslation } from "react-i18next" -import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react" -import { CloudUpload, CloudDownload, FoldVertical } from "lucide-react" +import { FoldVertical } from "lucide-react" import type { ClineMessage } from "@roo-code/types" import { getModelMaxOutputTokens } from "@roo/api" -import { formatLargeNumber } from "@src/utils/format" -import { cn } from "@src/lib/utils" -import { Button, StandardTooltip } from "@src/components/ui" +import { StandardTooltip } from "@src/components/ui" import { useExtensionState } from "@src/context/ExtensionStateContext" import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel" -import Thumbnails from "../common/Thumbnails" - -import { TaskActions } from "./TaskActions" -import { ShareButton } from "./ShareButton" import { ContextWindowProgress } from "./ContextWindowProgress" -import { Mention } from "./Mention" import { TodoListDisplay } from "./TodoListDisplay" export interface TaskHeaderProps { task: ClineMessage - tokensIn: number - tokensOut: number - cacheWrites?: number - cacheReads?: number - totalCost: number contextTokens: number buttonsDisabled: boolean handleCondenseContext: (taskId: string) => void - onClose: () => void todos?: any[] } -const TaskHeader = ({ - task, - tokensIn, - tokensOut, - cacheWrites, - cacheReads, - totalCost, - contextTokens, - buttonsDisabled, - handleCondenseContext, - onClose, - todos, -}: TaskHeaderProps) => { +const TaskHeader = ({ task, contextTokens, buttonsDisabled, handleCondenseContext, todos }: TaskHeaderProps) => { const { t } = useTranslation() const { apiConfiguration, currentTaskItem } = useExtensionState() const { id: modelId, info: model } = useSelectedModel(apiConfiguration) - const [isTaskExpanded, setIsTaskExpanded] = useState(false) - const textContainerRef = useRef(null) - const textRef = useRef(null) const contextWindow = model?.contextWindow || 1 - const { width: windowWidth } = useWindowSize() - const condenseButton = ( - - - {/* Collapsed state: Track context and cost if we have any */} - {!isTaskExpanded && contextWindow > 0 && ( -
+
{condenseButton} - - {!!totalCost && ${totalCost.toFixed(2)}}
- )} - {/* Expanded state: Show task text and images */} - {isTaskExpanded && ( - <> -
-
- -
-
- {task.images && task.images.length > 0 && } - -
- {isTaskExpanded && contextWindow > 0 && ( -
-
- - {t("chat:task.contextWindow")} - -
- - {condenseButton} -
- )} -
-
- {t("chat:task.tokens")} - {typeof tokensIn === "number" && tokensIn > 0 && ( - - - {formatLargeNumber(tokensIn)} - - )} - {typeof tokensOut === "number" && tokensOut > 0 && ( - - - {formatLargeNumber(tokensOut)} - - )} -
- {!totalCost && } -
- - {((typeof cacheReads === "number" && cacheReads > 0) || - (typeof cacheWrites === "number" && cacheWrites > 0)) && ( -
- {t("chat:task.cache")} - {typeof cacheWrites === "number" && cacheWrites > 0 && ( - - - {formatLargeNumber(cacheWrites)} - - )} - {typeof cacheReads === "number" && cacheReads > 0 && ( - - - {formatLargeNumber(cacheReads)} - - )} -
- )} +
+ )} - {!!totalCost && ( -
-
- {t("chat:task.apiCost")} - ${totalCost?.toFixed(2)} -
- -
- )} -
- - )} - + {/* Todo List */} ) diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx index c04f7e45e5e..2afb11c2192 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx @@ -47,13 +47,10 @@ vi.mock("@src/context/ExtensionStateContext", () => ({ describe("TaskHeader", () => { const defaultProps: TaskHeaderProps = { task: { type: "say", ts: Date.now(), text: "Test task", images: [] }, - tokensIn: 100, - tokensOut: 50, - totalCost: 0.05, contextTokens: 200, buttonsDisabled: false, handleCondenseContext: vi.fn(), - onClose: vi.fn(), + todos: [], } const queryClient = new QueryClient() @@ -66,31 +63,6 @@ describe("TaskHeader", () => { ) } - it("should display cost when totalCost is greater than 0", () => { - renderTaskHeader() - expect(screen.getByText("$0.05")).toBeInTheDocument() - }) - - it("should not display cost when totalCost is 0", () => { - renderTaskHeader({ totalCost: 0 }) - expect(screen.queryByText("$0.0000")).not.toBeInTheDocument() - }) - - it("should not display cost when totalCost is null", () => { - renderTaskHeader({ totalCost: null as any }) - expect(screen.queryByText(/\$/)).not.toBeInTheDocument() - }) - - it("should not display cost when totalCost is undefined", () => { - renderTaskHeader({ totalCost: undefined as any }) - expect(screen.queryByText(/\$/)).not.toBeInTheDocument() - }) - - it("should not display cost when totalCost is NaN", () => { - renderTaskHeader({ totalCost: NaN }) - expect(screen.queryByText(/\$/)).not.toBeInTheDocument() - }) - it("should render the condense context button", () => { renderTaskHeader() // Find the button that contains the FoldVertical icon