Просмотр исходного кода

Redesigned Task Header (#6561)

* Reorganizes the task header for cleanliness and in preparation for Cloud link

* More task ehader visual tweaks

* Translations for new task header

* Fixes TaskHeader color

* Removes stray string

* Fixes tests

* Iterates on visual details

* More visual tweaks

* Missing localization call

* Fixes tests

---------

Co-authored-by: Bruno Bergher <[email protected]>
Bruno Bergher 4 месяцев назад
Родитель
Сommit
ea79dfeb66
27 измененных файлов с 381 добавлено и 270 удалено
  1. 17 1
      webview-ui/src/__tests__/ContextWindowProgress.spec.tsx
  2. 0 3
      webview-ui/src/components/chat/ChatView.tsx
  3. 1 1
      webview-ui/src/components/chat/ContextWindowProgress.tsx
  4. 24 9
      webview-ui/src/components/chat/ShareButton.tsx
  5. 2 4
      webview-ui/src/components/chat/TaskActions.tsx
  6. 192 115
      webview-ui/src/components/chat/TaskHeader.tsx
  7. 40 64
      webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx
  8. 16 3
      webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx
  9. 1 0
      webview-ui/src/components/common/Thumbnails.tsx
  10. 5 4
      webview-ui/src/i18n/locales/ca/chat.json
  11. 5 4
      webview-ui/src/i18n/locales/de/chat.json
  12. 5 4
      webview-ui/src/i18n/locales/en/chat.json
  13. 5 4
      webview-ui/src/i18n/locales/es/chat.json
  14. 5 4
      webview-ui/src/i18n/locales/fr/chat.json
  15. 5 4
      webview-ui/src/i18n/locales/hi/chat.json
  16. 5 4
      webview-ui/src/i18n/locales/id/chat.json
  17. 5 4
      webview-ui/src/i18n/locales/it/chat.json
  18. 5 4
      webview-ui/src/i18n/locales/ja/chat.json
  19. 5 4
      webview-ui/src/i18n/locales/ko/chat.json
  20. 5 4
      webview-ui/src/i18n/locales/nl/chat.json
  21. 5 4
      webview-ui/src/i18n/locales/pl/chat.json
  22. 5 4
      webview-ui/src/i18n/locales/pt-BR/chat.json
  23. 5 4
      webview-ui/src/i18n/locales/ru/chat.json
  24. 5 4
      webview-ui/src/i18n/locales/tr/chat.json
  25. 5 4
      webview-ui/src/i18n/locales/vi/chat.json
  26. 5 4
      webview-ui/src/i18n/locales/zh-CN/chat.json
  27. 3 2
      webview-ui/src/i18n/locales/zh-TW/chat.json

+ 17 - 1
webview-ui/src/__tests__/ContextWindowProgress.spec.tsx

@@ -1,6 +1,6 @@
 // npm run test ContextWindowProgress.spec.tsx
 
-import { render, screen } from "@/utils/test-utils"
+import { render, screen, fireEvent } from "@/utils/test-utils"
 import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
 
 import TaskHeader from "@src/components/chat/TaskHeader"
@@ -70,6 +70,10 @@ describe("ContextWindowProgress", () => {
 	it("renders correctly with valid inputs", () => {
 		renderComponent({ contextTokens: 1000, contextWindow: 4000 })
 
+		// First expand the TaskHeader to access ContextWindowProgress
+		const taskHeader = screen.getByText("Test task")
+		fireEvent.click(taskHeader)
+
 		// Check for basic elements
 		// The context-window-label is not part of the ContextWindowProgress component
 		// but rather part of the parent TaskHeader component in expanded state
@@ -83,6 +87,10 @@ describe("ContextWindowProgress", () => {
 	it("handles zero context window gracefully", () => {
 		renderComponent({ contextTokens: 0, contextWindow: 0 })
 
+		// First expand the TaskHeader to access ContextWindowProgress
+		const taskHeader = screen.getByText("Test task")
+		fireEvent.click(taskHeader)
+
 		// In the current implementation, the component is still displayed with zero values
 		// rather than being hidden completely
 		// The context-window-label is not part of the ContextWindowProgress component
@@ -93,6 +101,10 @@ describe("ContextWindowProgress", () => {
 	it("handles edge cases with negative values", () => {
 		renderComponent({ contextTokens: -100, contextWindow: 4000 })
 
+		// First expand the TaskHeader to access ContextWindowProgress
+		const taskHeader = screen.getByText("Test task")
+		fireEvent.click(taskHeader)
+
 		// Should show 0 instead of -100
 		expect(screen.getByTestId("context-tokens-count")).toHaveTextContent("0")
 		// The actual context window might be different than what we pass in
@@ -102,6 +114,10 @@ describe("ContextWindowProgress", () => {
 	it("calculates percentages correctly", () => {
 		renderComponent({ contextTokens: 1000, contextWindow: 4000 })
 
+		// First expand the TaskHeader to access ContextWindowProgress
+		const taskHeader = screen.getByText("Test task")
+		fireEvent.click(taskHeader)
+
 		// Verify that the token count and window size are displayed correctly
 		const tokenCount = screen.getByTestId("context-tokens-count")
 		const windowSize = screen.getByTestId("context-window-size")

+ 0 - 3
webview-ui/src/components/chat/ChatView.tsx

@@ -805,8 +805,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 		[clineAsk, startNewTask, isStreaming],
 	)
 
-	const handleTaskCloseButtonClick = useCallback(() => startNewTask(), [startNewTask])
-
 	const { info: model } = useSelectedModel(apiConfiguration)
 
 	const selectImages = useCallback(() => vscode.postMessage({ type: "selectImages" }), [])
@@ -1765,7 +1763,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 						contextTokens={apiMetrics.contextTokens}
 						buttonsDisabled={sendingDisabled}
 						handleCondenseContext={handleCondenseContext}
-						onClose={handleTaskCloseButtonClick}
 						todos={latestTodos}
 					/>
 

+ 1 - 1
webview-ui/src/components/chat/ContextWindowProgress.tsx

@@ -55,7 +55,7 @@ export const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens
 
 	return (
 		<>
-			<div className="flex items-center gap-2 flex-1 whitespace-nowrap px-2">
+			<div className="flex items-center gap-2 flex-1 whitespace-nowrap">
 				<div data-testid="context-tokens-count">{formatLargeNumber(safeContextTokens)}</div>
 				<StandardTooltip content={tooltipContent} side="top" sideOffset={8}>
 					<div className="flex-1 relative">

+ 24 - 9
webview-ui/src/components/chat/ShareButton.tsx

@@ -1,5 +1,6 @@
 import { useState, useEffect, useRef } from "react"
 import { useTranslation } from "react-i18next"
+import { SquareArrowOutUpRightIcon } from "lucide-react"
 
 import type { HistoryItem, ShareVisibility } from "@roo-code/types"
 import { TelemetryEventName } from "@roo-code/types"
@@ -26,9 +27,10 @@ import {
 interface ShareButtonProps {
 	item?: HistoryItem
 	disabled?: boolean
+	showLabel?: boolean
 }
 
-export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
+export const ShareButton = ({ item, disabled = false, showLabel = false }: ShareButtonProps) => {
 	const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
 	const [connectModalOpen, setConnectModalOpen] = useState(false)
 	const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null)
@@ -155,14 +157,21 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
 						<PopoverTrigger asChild>
 							<Button
 								variant="ghost"
-								size="icon"
+								size={showLabel ? "sm" : "icon"}
 								disabled={disabled || shareButtonState.disabled}
-								className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
-								onClick={handleShareButtonClick}>
-								<span className="codicon codicon-link"></span>
+								className={
+									showLabel
+										? "h-7 px-2 hover:bg-vscode-toolbar-hoverBackground"
+										: "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
+								}
+								onClick={handleShareButtonClick}
+								data-testid="share-button">
+								<SquareArrowOutUpRightIcon />
+								{showLabel && <span className="ml-0">{t("chat:task.share")}</span>}
 							</Button>
 						</PopoverTrigger>
 					</StandardTooltip>
+
 					<PopoverContent className="w-56 p-0" align="start">
 						{shareSuccess ? (
 							<div className="p-3">
@@ -217,11 +226,17 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
 				<StandardTooltip content={shareButtonState.title}>
 					<Button
 						variant="ghost"
-						size="icon"
+						size={showLabel ? "sm" : "icon"}
 						disabled={disabled || shareButtonState.disabled}
-						className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
-						onClick={handleShareButtonClick}>
-						<span className="codicon codicon-link"></span>
+						className={
+							showLabel
+								? "h-7 px-2 hover:bg-vscode-toolbar-hoverBackground"
+								: "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
+						}
+						onClick={handleShareButtonClick}
+						data-testid="share-button">
+						<SquareArrowOutUpRightIcon />
+						{showLabel && <span className="ml-1">{t("chat:task.share")}</span>}
 					</Button>
 				</StandardTooltip>
 			)}

+ 2 - 4
webview-ui/src/components/chat/TaskActions.tsx

@@ -1,5 +1,4 @@
 import { useState } from "react"
-import prettyBytes from "pretty-bytes"
 import { useTranslation } from "react-i18next"
 
 import type { HistoryItem } from "@roo-code/types"
@@ -22,8 +21,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
 	const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
 
 	return (
-		<div className="flex flex-row gap-1">
-			<ShareButton item={item} disabled={false} />
+		<div className="flex flex-row items-center">
 			<IconButton
 				iconClass="codicon-desktop-download"
 				title={t("chat:task.export")}
@@ -53,7 +51,6 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
 								}
 							}}
 						/>
-						<span className="ml-1 text-xs text-vscode-foreground opacity-85">{prettyBytes(item.size)}</span>
 					</div>
 					{deleteTaskId && (
 						<DeleteTaskDialog
@@ -64,6 +61,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
 					)}
 				</>
 			)}
+			<ShareButton item={item} disabled={false} showLabel={false} />
 		</div>
 	)
 }

+ 192 - 115
webview-ui/src/components/chat/TaskHeader.tsx

@@ -1,8 +1,7 @@
 import { memo, useRef, useState } from "react"
-import { useWindowSize } from "react-use"
 import { useTranslation } from "react-i18next"
-import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
-import { CloudUpload, CloudDownload, FoldVertical } from "lucide-react"
+import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react"
+import prettyBytes from "pretty-bytes"
 
 import type { ClineMessage } from "@roo-code/types"
 
@@ -10,14 +9,13 @@ 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"
@@ -32,7 +30,6 @@ export interface TaskHeaderProps {
 	contextTokens: number
 	buttonsDisabled: boolean
 	handleCondenseContext: (taskId: string) => void
-	onClose: () => void
 	todos?: any[]
 }
 
@@ -46,7 +43,6 @@ const TaskHeader = ({
 	contextTokens,
 	buttonsDisabled,
 	handleCondenseContext,
-	onClose,
 	todos,
 }: TaskHeaderProps) => {
 	const { t } = useTranslation()
@@ -58,8 +54,6 @@ const TaskHeader = ({
 	const textRef = useRef<HTMLDivElement>(null)
 	const contextWindow = model?.contextWindow || 1
 
-	const { width: windowWidth } = useWindowSize()
-
 	const condenseButton = (
 		<StandardTooltip content={t("chat:task.condenseContext")}>
 			<button
@@ -74,55 +68,104 @@ const TaskHeader = ({
 	const hasTodos = todos && Array.isArray(todos) && todos.length > 0
 
 	return (
-		<div className="py-2 px-3">
+		<div className="pt-2 pb-0 px-3">
 			<div
 				className={cn(
-					"p-2.5 flex flex-col gap-1.5 relative z-1 border",
+					"px-2.5 pt-2.5 pb-2 flex flex-col gap-1.5 relative z-1 cursor-pointer",
+					"bg-vscode-input-background hover:bg-vscode-input-background/90",
+					"text-vscode-foreground/80 hover:text-vscode-foreground",
 					hasTodos ? "rounded-t-xs border-b-0" : "rounded-xs",
-					isTaskExpanded
-						? "border-vscode-panel-border text-vscode-foreground"
-						: "border-vscode-panel-border/80 text-vscode-foreground/80",
-				)}>
-				<div className="flex justify-between items-center gap-2">
-					<div
-						className="flex items-center cursor-pointer -ml-0.5 select-none grow min-w-0"
-						onClick={() => setIsTaskExpanded(!isTaskExpanded)}>
-						<div className="flex items-center shrink-0">
-							<span className={`codicon codicon-chevron-${isTaskExpanded ? "down" : "right"}`}></span>
-						</div>
-						<div className="ml-1.5 whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
-							<span className="font-bold">
-								{t("chat:task.title")}
-								{!isTaskExpanded && ":"}
-							</span>
+				)}
+				onClick={(e) => {
+					// Don't expand if clicking on buttons or interactive elements
+					if (
+						e.target instanceof Element &&
+						(e.target.closest("button") ||
+							e.target.closest('[role="button"]') ||
+							e.target.closest(".share-button") ||
+							e.target.closest("[data-radix-popper-content-wrapper]") ||
+							e.target.closest("img") ||
+							e.target.tagName === "IMG")
+					) {
+						return
+					}
+
+					// Don't expand/collapse if user is selecting text
+					const selection = window.getSelection()
+					if (selection && selection.toString().length > 0) {
+						return
+					}
+
+					setIsTaskExpanded(!isTaskExpanded)
+				}}>
+				<div className="flex justify-between items-center gap-0">
+					<div className="flex items-center select-none grow min-w-0">
+						<div className="whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
+							{isTaskExpanded && <span className="font-bold">{t("chat:task.title")}</span>}
 							{!isTaskExpanded && (
-								<span className="ml-1">
+								<div>
+									<span className="font-bold mr-1">{t("chat:task.title")}</span>
 									<Mention text={task.text} />
-								</span>
+								</div>
 							)}
 						</div>
+						<div className="flex items-center shrink-0 ml-2" onClick={(e) => e.stopPropagation()}>
+							<StandardTooltip content={isTaskExpanded ? t("chat:task.collapse") : t("chat:task.expand")}>
+								<button
+									onClick={() => setIsTaskExpanded(!isTaskExpanded)}
+									className="shrink-0 min-h-[20px] min-w-[20px] p-[2px] cursor-pointer opacity-85 hover:opacity-100 bg-transparent border-none rounded-md">
+									{isTaskExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
+								</button>
+							</StandardTooltip>
+						</div>
 					</div>
-					<StandardTooltip content={t("chat:task.closeAndStart")}>
-						<Button variant="ghost" size="icon" onClick={onClose} className="shrink-0 w-5 h-5">
-							<span className="codicon codicon-close" />
-						</Button>
-					</StandardTooltip>
 				</div>
-				{/* Collapsed state: Track context and cost if we have any */}
 				{!isTaskExpanded && contextWindow > 0 && (
-					<div className={`w-full flex flex-row items-center gap-1 h-auto`}>
-						<ContextWindowProgress
-							contextWindow={contextWindow}
-							contextTokens={contextTokens || 0}
-							maxTokens={
-								model
-									? getModelMaxOutputTokens({ modelId, model, settings: apiConfiguration })
-									: undefined
+					<div className="flex items-center gap-2 text-sm" onClick={(e) => e.stopPropagation()}>
+						<StandardTooltip
+							content={
+								<div className="space-y-1">
+									<div>
+										{t("chat:tokenProgress.tokensUsed", {
+											used: formatLargeNumber(contextTokens || 0),
+											total: formatLargeNumber(contextWindow),
+										})}
+									</div>
+									{(() => {
+										const maxTokens = model
+											? getModelMaxOutputTokens({ modelId, model, settings: apiConfiguration })
+											: 0
+										const reservedForOutput = maxTokens || 0
+										const availableSpace = contextWindow - (contextTokens || 0) - reservedForOutput
+
+										return (
+											<>
+												{reservedForOutput > 0 && (
+													<div>
+														{t("chat:tokenProgress.reservedForResponse", {
+															amount: formatLargeNumber(reservedForOutput),
+														})}
+													</div>
+												)}
+												{availableSpace > 0 && (
+													<div>
+														{t("chat:tokenProgress.availableSpace", {
+															amount: formatLargeNumber(availableSpace),
+														})}
+													</div>
+												)}
+											</>
+										)
+									})()}
+								</div>
 							}
-						/>
-						{condenseButton}
-						<ShareButton item={currentTaskItem} disabled={buttonsDisabled} />
-						{!!totalCost && <VSCodeBadge>${totalCost.toFixed(2)}</VSCodeBadge>}
+							side="top"
+							sideOffset={8}>
+							<span className="mr-1">
+								{formatLargeNumber(contextTokens || 0)} / {formatLargeNumber(contextWindow)}
+							</span>
+						</StandardTooltip>
+						{!!totalCost && <span>${totalCost.toFixed(2)}</span>}
 					</div>
 				)}
 				{/* Expanded state: Show task text and images */}
@@ -130,10 +173,10 @@ const TaskHeader = ({
 					<>
 						<div
 							ref={textContainerRef}
-							className="-mt-0.5 text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
+							className="text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
 							<div
 								ref={textRef}
-								className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere"
+								className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere cursor-text"
 								style={{
 									display: "-webkit-box",
 									WebkitLineClamp: "unset",
@@ -144,78 +187,112 @@ const TaskHeader = ({
 						</div>
 						{task.images && task.images.length > 0 && <Thumbnails images={task.images} />}
 
-						<div className="flex flex-col gap-1">
-							{isTaskExpanded && contextWindow > 0 && (
-								<div
-									className={`w-full flex ${windowWidth < 400 ? "flex-col" : "flex-row"} gap-1 h-auto`}>
-									<div className="flex items-center gap-1 flex-shrink-0">
-										<span className="font-bold" data-testid="context-window-label">
-											{t("chat:task.contextWindow")}
-										</span>
-									</div>
-									<ContextWindowProgress
-										contextWindow={contextWindow}
-										contextTokens={contextTokens || 0}
-										maxTokens={
-											model
-												? getModelMaxOutputTokens({
-														modelId,
-														model,
-														settings: apiConfiguration,
-													})
-												: undefined
-										}
-									/>
-									{condenseButton}
-								</div>
-							)}
-							<div className="flex justify-between items-center h-[20px]">
-								<div className="flex items-center gap-1 flex-wrap">
-									<span className="font-bold">{t("chat:task.tokens")}</span>
-									{typeof tokensIn === "number" && tokensIn > 0 && (
-										<span className="flex items-center gap-0.5">
-											<i className="codicon codicon-arrow-up text-xs font-bold" />
-											{formatLargeNumber(tokensIn)}
-										</span>
+						<div className="border-t border-b border-vscode-panel-border/50 py-4 mt-2 mb-1">
+							<table className="w-full">
+								<tbody>
+									{contextWindow > 0 && (
+										<tr>
+											<th
+												className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]"
+												data-testid="context-window-label">
+												{t("chat:task.contextWindow")}
+											</th>
+											<td className="align-top">
+												<div className={`max-w-80 -mt-0.5 flex flex-nowrap gap-1`}>
+													<ContextWindowProgress
+														contextWindow={contextWindow}
+														contextTokens={contextTokens || 0}
+														maxTokens={
+															model
+																? getModelMaxOutputTokens({
+																		modelId,
+																		model,
+																		settings: apiConfiguration,
+																	})
+																: undefined
+														}
+													/>
+													{condenseButton}
+												</div>
+											</td>
+										</tr>
 									)}
-									{typeof tokensOut === "number" && tokensOut > 0 && (
-										<span className="flex items-center gap-0.5">
-											<i className="codicon codicon-arrow-down text-xs font-bold" />
-											{formatLargeNumber(tokensOut)}
-										</span>
+
+									<tr>
+										<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
+											{t("chat:task.tokens")}
+										</th>
+										<td className="align-top">
+											<div className="flex items-center gap-1 flex-wrap">
+												{typeof tokensIn === "number" && tokensIn > 0 && (
+													<span>↑ {formatLargeNumber(tokensIn)}</span>
+												)}
+												{typeof tokensOut === "number" && tokensOut > 0 && (
+													<span>↓ {formatLargeNumber(tokensOut)}</span>
+												)}
+											</div>
+										</td>
+									</tr>
+
+									{((typeof cacheReads === "number" && cacheReads > 0) ||
+										(typeof cacheWrites === "number" && cacheWrites > 0)) && (
+										<tr>
+											<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
+												{t("chat:task.cache")}
+											</th>
+											<td className="align-top">
+												<div className="flex items-center gap-1 flex-wrap">
+													{typeof cacheWrites === "number" && cacheWrites > 0 && (
+														<span>↑ {formatLargeNumber(cacheWrites)}</span>
+													)}
+													{typeof cacheReads === "number" && cacheReads > 0 && (
+														<span>↓ {formatLargeNumber(cacheReads)}</span>
+													)}
+												</div>
+											</td>
+										</tr>
 									)}
-								</div>
-								{!totalCost && <TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />}
-							</div>
 
-							{((typeof cacheReads === "number" && cacheReads > 0) ||
-								(typeof cacheWrites === "number" && cacheWrites > 0)) && (
-								<div className="flex items-center gap-1 flex-wrap h-[20px]">
-									<span className="font-bold">{t("chat:task.cache")}</span>
-									{typeof cacheWrites === "number" && cacheWrites > 0 && (
-										<span className="flex items-center gap-0.5">
-											<CloudUpload size={16} />
-											{formatLargeNumber(cacheWrites)}
-										</span>
+									{!!totalCost && (
+										<tr>
+											<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
+												{t("chat:task.apiCost")}
+											</th>
+											<td className="align-top">
+												<span>${totalCost?.toFixed(2)}</span>
+											</td>
+										</tr>
 									)}
-									{typeof cacheReads === "number" && cacheReads > 0 && (
-										<span className="flex items-center gap-0.5">
-											<CloudDownload size={16} />
-											{formatLargeNumber(cacheReads)}
-										</span>
+
+									{/* Cache size display */}
+									{((typeof cacheReads === "number" && cacheReads > 0) ||
+										(typeof cacheWrites === "number" && cacheWrites > 0)) && (
+										<tr>
+											<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
+												{t("chat:task.cache")}
+											</th>
+											<td className="align-top">
+												{prettyBytes(((cacheReads || 0) + (cacheWrites || 0)) * 4)}
+											</td>
+										</tr>
 									)}
-								</div>
-							)}
 
-							{!!totalCost && (
-								<div className="flex justify-between items-center h-[20px]">
-									<div className="flex items-center gap-1">
-										<span className="font-bold">{t("chat:task.apiCost")}</span>
-										<span>${totalCost?.toFixed(2)}</span>
-									</div>
-									<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
-								</div>
-							)}
+									{/* Size display */}
+									{!!currentTaskItem?.size && currentTaskItem.size > 0 && (
+										<tr>
+											<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-2  h-[20px]">
+												{t("chat:task.size")}
+											</th>
+											<td className="align-top">{prettyBytes(currentTaskItem.size)}</td>
+										</tr>
+									)}
+								</tbody>
+							</table>
+						</div>
+
+						{/* Footer with task management buttons */}
+						<div onClick={(e) => e.stopPropagation()}>
+							<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
 						</div>
 					</>
 				)}

+ 40 - 64
webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx

@@ -89,19 +89,17 @@ describe("TaskActions", () => {
 		it("renders share button when item has id", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
+			// ShareButton now uses data-testid for reliable testing
+			const shareButton = screen.getByTestId("share-button")
 			expect(shareButton).toBeInTheDocument()
 		})
 
 		it("does not render share button when item has no id", () => {
 			render(<TaskActions item={undefined} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.queryAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).not.toBeDefined()
+			// ShareButton returns null when no item ID
+			const shareButton = screen.queryByTestId("share-button")
+			expect(shareButton).toBeNull()
 		})
 
 		it("renders share button even when not authenticated", () => {
@@ -112,9 +110,8 @@ describe("TaskActions", () => {
 
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
+			// ShareButton should still render when not authenticated
+			const shareButton = screen.getByTestId("share-button")
 			expect(shareButton).toBeInTheDocument()
 		})
 	})
@@ -123,11 +120,9 @@ describe("TaskActions", () => {
 		it("shows organization and public share options when authenticated and sharing enabled", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			// Find share button by its test ID and click it
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			expect(screen.getByText("Share with Organization")).toBeInTheDocument()
 			expect(screen.getByText("Share Publicly")).toBeInTheDocument()
@@ -136,11 +131,9 @@ describe("TaskActions", () => {
 		it("sends shareCurrentTask message when organization option is selected", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			// Find share button by its test ID and click it
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			const orgOption = screen.getByText("Share with Organization")
 			fireEvent.click(orgOption)
@@ -154,11 +147,9 @@ describe("TaskActions", () => {
 		it("sends shareCurrentTask message when public option is selected", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			// Find share button by its test ID and click it
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			const publicOption = screen.getByText("Share Publicly")
 			fireEvent.click(publicOption)
@@ -180,11 +171,9 @@ describe("TaskActions", () => {
 
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			// Find share button by its test ID and click it
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
 			expect(screen.getByText("Share Publicly")).toBeInTheDocument()
@@ -202,11 +191,9 @@ describe("TaskActions", () => {
 		it("shows connect to cloud option when not authenticated", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			// Find share button by its test ID and click it
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			expect(screen.getByText("Connect to Roo Code Cloud")).toBeInTheDocument()
 			expect(screen.getByText("Sign in to Roo Code Cloud to share tasks")).toBeInTheDocument()
@@ -216,11 +203,9 @@ describe("TaskActions", () => {
 		it("does not show organization and public options when not authenticated", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			// Find share button by its test ID and click it
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
 			expect(screen.queryByText("Share Publicly")).not.toBeInTheDocument()
@@ -229,11 +214,9 @@ describe("TaskActions", () => {
 		it("sends rooCloudSignIn message when connect to cloud is selected", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			// Find share button by its test ID and click it
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			const connectOption = screen.getByText("Connect")
 			fireEvent.click(connectOption)
@@ -253,9 +236,8 @@ describe("TaskActions", () => {
 
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			// Find button by its icon class
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
+			// Find share button by its test ID
+			const shareButton = screen.getByTestId("share-button")
 			expect(shareButton).toBeInTheDocument()
 			expect(shareButton).toBeDisabled()
 
@@ -303,10 +285,8 @@ describe("TaskActions", () => {
 			const { rerender } = render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
 			// Click share button to open connect modal
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
-			expect(shareButton).toBeDefined()
-			fireEvent.click(shareButton!)
+			const shareButton = screen.getByTestId("share-button")
+			fireEvent.click(shareButton)
 
 			// Click connect button to initiate authentication
 			const connectButton = screen.getByText("Connect")
@@ -353,12 +333,11 @@ describe("TaskActions", () => {
 			})
 		})
 
-		it("renders delete button and file size when item has size", () => {
+		it("renders delete button when item has size", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
 			const deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
 			expect(deleteButton).toBeInTheDocument()
-			expect(screen.getByText("1024 B")).toBeInTheDocument()
 		})
 
 		it("does not render delete button when item has no size", () => {
@@ -374,11 +353,10 @@ describe("TaskActions", () => {
 		it("keeps share, export, and copy buttons enabled but disables delete button when buttonsDisabled is true", () => {
 			render(<TaskActions item={mockItem} buttonsDisabled={true} />)
 
-			// Find buttons by their labels/icons
-			const buttons = screen.getAllByRole("button")
-			const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
+			// Find buttons by their labels/test IDs
+			const shareButton = screen.getByTestId("share-button")
 			const exportButton = screen.getByLabelText("Export task history")
-			const copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy"))
+			const copyButton = screen.getByLabelText("history:copyPrompt")
 			const deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
 
 			// Share, export, and copy buttons should be enabled regardless of buttonsDisabled
@@ -393,10 +371,9 @@ describe("TaskActions", () => {
 			// Test with buttonsDisabled = false
 			const { rerender } = render(<TaskActions item={mockItem} buttonsDisabled={false} />)
 
-			let buttons = screen.getAllByRole("button")
-			let shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
+			let shareButton = screen.getByTestId("share-button")
 			let exportButton = screen.getByLabelText("Export task history")
-			let copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy"))
+			let copyButton = screen.getByLabelText("history:copyPrompt")
 			let deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
 
 			expect(shareButton).not.toBeDisabled()
@@ -407,10 +384,9 @@ describe("TaskActions", () => {
 			// Test with buttonsDisabled = true
 			rerender(<TaskActions item={mockItem} buttonsDisabled={true} />)
 
-			buttons = screen.getAllByRole("button")
-			shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
+			shareButton = screen.getByTestId("share-button")
 			exportButton = screen.getByLabelText("Export task history")
-			copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy"))
+			copyButton = screen.getByLabelText("history:copyPrompt")
 			deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
 
 			// Share, export, and copy remain enabled

+ 16 - 3
webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx

@@ -53,7 +53,6 @@ describe("TaskHeader", () => {
 		contextTokens: 200,
 		buttonsDisabled: false,
 		handleCondenseContext: vi.fn(),
-		onClose: vi.fn(),
 	}
 
 	const queryClient = new QueryClient()
@@ -91,9 +90,13 @@ describe("TaskHeader", () => {
 		expect(screen.queryByText(/\$/)).not.toBeInTheDocument()
 	})
 
-	it("should render the condense context button", () => {
+	it("should render the condense context button when expanded", () => {
 		renderTaskHeader()
-		// Find the button that contains the FoldVertical icon
+		// First click to expand the task header
+		const taskHeader = screen.getByText("Test task")
+		fireEvent.click(taskHeader)
+
+		// Now find the condense button in the expanded state
 		const buttons = screen.getAllByRole("button")
 		const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
 		expect(condenseButton).toBeDefined()
@@ -103,6 +106,11 @@ describe("TaskHeader", () => {
 	it("should call handleCondenseContext when condense context button is clicked", () => {
 		const handleCondenseContext = vi.fn()
 		renderTaskHeader({ handleCondenseContext })
+
+		// First click to expand the task header
+		const taskHeader = screen.getByText("Test task")
+		fireEvent.click(taskHeader)
+
 		// Find the button that contains the FoldVertical icon
 		const buttons = screen.getAllByRole("button")
 		const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
@@ -114,6 +122,11 @@ describe("TaskHeader", () => {
 	it("should disable the condense context button when buttonsDisabled is true", () => {
 		const handleCondenseContext = vi.fn()
 		renderTaskHeader({ buttonsDisabled: true, handleCondenseContext })
+
+		// First click to expand the task header
+		const taskHeader = screen.getByText("Test task")
+		fireEvent.click(taskHeader)
+
 		// Find the button that contains the FoldVertical icon
 		const buttons = screen.getAllByRole("button")
 		const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))

+ 1 - 0
webview-ui/src/components/common/Thumbnails.tsx

@@ -39,6 +39,7 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp
 	return (
 		<div
 			ref={containerRef}
+			className="py-1"
 			style={{
 				display: "flex",
 				flexWrap: "wrap",

+ 5 - 4
webview-ui/src/i18n/locales/ca/chat.json

@@ -4,10 +4,11 @@
 		"title": "Tasca",
 		"seeMore": "Veure més",
 		"seeLess": "Veure menys",
-		"tokens": "Tokens:",
-		"cache": "Caché:",
-		"apiCost": "Cost d'API:",
-		"contextWindow": "Finestra de context:",
+		"tokens": "Tokens",
+		"cache": "Caché",
+		"apiCost": "Cost d'API",
+		"size": "Mida",
+		"contextWindow": "Finestra de context",
 		"closeAndStart": "Tancar tasca i iniciar-ne una de nova",
 		"export": "Exportar historial de tasques",
 		"delete": "Eliminar tasca (Shift + Clic per ometre confirmació)",

+ 5 - 4
webview-ui/src/i18n/locales/de/chat.json

@@ -4,10 +4,11 @@
 		"title": "Aufgabe",
 		"seeMore": "Mehr anzeigen",
 		"seeLess": "Weniger anzeigen",
-		"tokens": "Tokens:",
-		"cache": "Cache:",
-		"apiCost": "API-Kosten:",
-		"contextWindow": "Kontextfenster:",
+		"tokens": "Tokens",
+		"cache": "Cache",
+		"apiCost": "API-Kosten",
+		"size": "Größe",
+		"contextWindow": "Kontextfenster",
 		"closeAndStart": "Aufgabe schließen und neue starten",
 		"export": "Aufgabenverlauf exportieren",
 		"delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)",

+ 5 - 4
webview-ui/src/i18n/locales/en/chat.json

@@ -4,11 +4,12 @@
 		"title": "Task",
 		"seeMore": "See more",
 		"seeLess": "See less",
-		"tokens": "Tokens:",
-		"cache": "Cache:",
-		"apiCost": "API Cost:",
+		"tokens": "Tokens",
+		"cache": "Cache",
+		"apiCost": "API Cost",
+		"size": "Size",
 		"condenseContext": "Intelligently condense context",
-		"contextWindow": "Context Length:",
+		"contextWindow": "Context Length",
 		"closeAndStart": "Close task and start a new one",
 		"export": "Export task history",
 		"share": "Share task",

+ 5 - 4
webview-ui/src/i18n/locales/es/chat.json

@@ -4,10 +4,11 @@
 		"title": "Tarea",
 		"seeMore": "Ver más",
 		"seeLess": "Ver menos",
-		"tokens": "Tokens:",
-		"cache": "Caché:",
-		"apiCost": "Costo de API:",
-		"contextWindow": "Longitud del contexto:",
+		"tokens": "Tokens",
+		"cache": "Caché",
+		"apiCost": "Costo de API",
+		"size": "Tamaño",
+		"contextWindow": "Longitud del contexto",
 		"closeAndStart": "Cerrar tarea e iniciar una nueva",
 		"export": "Exportar historial de tareas",
 		"delete": "Eliminar tarea (Shift + Clic para omitir confirmación)",

+ 5 - 4
webview-ui/src/i18n/locales/fr/chat.json

@@ -4,10 +4,11 @@
 		"title": "Tâche",
 		"seeMore": "Voir plus",
 		"seeLess": "Voir moins",
-		"tokens": "Tokens :",
-		"cache": "Cache :",
-		"apiCost": "Coût API :",
-		"contextWindow": "Durée du contexte :",
+		"tokens": "Tokens",
+		"cache": "Cache",
+		"apiCost": "Coût API",
+		"size": "Taille",
+		"contextWindow": "Durée du contexte",
 		"closeAndStart": "Fermer la tâche et en commencer une nouvelle",
 		"export": "Exporter l'historique des tâches",
 		"delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)",

+ 5 - 4
webview-ui/src/i18n/locales/hi/chat.json

@@ -4,10 +4,11 @@
 		"title": "कार्य",
 		"seeMore": "अधिक देखें",
 		"seeLess": "कम देखें",
-		"tokens": "Tokens:",
-		"cache": "कैश:",
-		"apiCost": "API लागत:",
-		"contextWindow": "संदर्भ लंबाई:",
+		"tokens": "Tokens",
+		"cache": "कैश",
+		"apiCost": "API लागत",
+		"size": "आकार",
+		"contextWindow": "संदर्भ लंबाई",
 		"closeAndStart": "कार्य बंद करें और नया शुरू करें",
 		"export": "कार्य इतिहास निर्यात करें",
 		"delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)",

+ 5 - 4
webview-ui/src/i18n/locales/id/chat.json

@@ -4,11 +4,12 @@
 		"title": "Tugas",
 		"seeMore": "Lihat lebih banyak",
 		"seeLess": "Lihat lebih sedikit",
-		"tokens": "Token:",
-		"cache": "Cache:",
-		"apiCost": "Biaya API:",
+		"tokens": "Token",
+		"cache": "Cache",
+		"apiCost": "Biaya API",
+		"size": "Ukuran",
 		"condenseContext": "Kondensasi konteks secara cerdas",
-		"contextWindow": "Panjang Konteks:",
+		"contextWindow": "Panjang Konteks",
 		"closeAndStart": "Tutup tugas dan mulai yang baru",
 		"export": "Ekspor riwayat tugas",
 		"share": "Bagikan tugas",

+ 5 - 4
webview-ui/src/i18n/locales/it/chat.json

@@ -4,10 +4,11 @@
 		"title": "Attività",
 		"seeMore": "Vedi altro",
 		"seeLess": "Vedi meno",
-		"tokens": "Tokens:",
-		"cache": "Cache:",
-		"apiCost": "Costo API:",
-		"contextWindow": "Lunghezza del contesto:",
+		"tokens": "Tokens",
+		"cache": "Cache",
+		"apiCost": "Costo API",
+		"size": "Dimensione",
+		"contextWindow": "Lunghezza del contesto",
 		"closeAndStart": "Chiudi attività e iniziane una nuova",
 		"export": "Esporta cronologia attività",
 		"delete": "Elimina attività (Shift + Clic per saltare la conferma)",

+ 5 - 4
webview-ui/src/i18n/locales/ja/chat.json

@@ -4,10 +4,11 @@
 		"title": "タスク",
 		"seeMore": "もっと見る",
 		"seeLess": "表示を減らす",
-		"tokens": "トークン:",
-		"cache": "キャッシュ:",
-		"apiCost": "APIコスト:",
-		"contextWindow": "コンテキストウィンドウ:",
+		"tokens": "トークン",
+		"cache": "キャッシュ",
+		"apiCost": "APIコスト",
+		"size": "サイズ",
+		"contextWindow": "コンテキストウィンドウ",
 		"closeAndStart": "タスクを閉じて新しいタスクを開始",
 		"export": "タスク履歴をエクスポート",
 		"delete": "タスクを削除(Shift + クリックで確認をスキップ)",

+ 5 - 4
webview-ui/src/i18n/locales/ko/chat.json

@@ -4,10 +4,11 @@
 		"title": "작업",
 		"seeMore": "더 보기",
 		"seeLess": "줄여보기",
-		"tokens": "토큰:",
-		"cache": "캐시:",
-		"apiCost": "API 비용:",
-		"contextWindow": "컨텍스트 창:",
+		"tokens": "토큰",
+		"cache": "캐시",
+		"apiCost": "API 비용",
+		"size": "크기",
+		"contextWindow": "컨텍스트 창",
 		"closeAndStart": "작업 닫고 새 작업 시작",
 		"export": "작업 기록 내보내기",
 		"delete": "작업 삭제 (Shift + 클릭으로 확인 생략)",

+ 5 - 4
webview-ui/src/i18n/locales/nl/chat.json

@@ -4,10 +4,11 @@
 		"title": "Taak",
 		"seeMore": "Meer weergeven",
 		"seeLess": "Minder weergeven",
-		"tokens": "Tokens:",
-		"cache": "Cache:",
-		"apiCost": "API-kosten:",
-		"contextWindow": "Contextlengte:",
+		"tokens": "Tokens",
+		"cache": "Cache",
+		"apiCost": "API-kosten",
+		"size": "Grootte",
+		"contextWindow": "Contextlengte",
 		"closeAndStart": "Taak sluiten en een nieuwe starten",
 		"export": "Taakgeschiedenis exporteren",
 		"delete": "Taak verwijderen (Shift + Klik om bevestiging over te slaan)",

+ 5 - 4
webview-ui/src/i18n/locales/pl/chat.json

@@ -4,10 +4,11 @@
 		"title": "Zadanie",
 		"seeMore": "Zobacz więcej",
 		"seeLess": "Zobacz mniej",
-		"tokens": "Tokeny:",
-		"cache": "Pamięć podręczna:",
-		"apiCost": "Koszt API:",
-		"contextWindow": "Okno kontekstu:",
+		"tokens": "Tokeny",
+		"cache": "Pamięć podręczna",
+		"apiCost": "Koszt API",
+		"size": "Rozmiar",
+		"contextWindow": "Okno kontekstu",
 		"closeAndStart": "Zamknij zadanie i rozpocznij nowe",
 		"export": "Eksportuj historię zadań",
 		"delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)",

+ 5 - 4
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -4,10 +4,11 @@
 		"title": "Tarefa",
 		"seeMore": "Ver mais",
 		"seeLess": "Ver menos",
-		"tokens": "Tokens:",
-		"cache": "Cache:",
-		"apiCost": "Custo da API:",
-		"contextWindow": "Janela de contexto:",
+		"tokens": "Tokens",
+		"cache": "Cache",
+		"apiCost": "Custo da API",
+		"size": "Tamanho",
+		"contextWindow": "Janela de contexto",
 		"closeAndStart": "Fechar tarefa e iniciar nova",
 		"export": "Exportar histórico de tarefas",
 		"delete": "Excluir tarefa (Shift + Clique para pular confirmação)",

+ 5 - 4
webview-ui/src/i18n/locales/ru/chat.json

@@ -4,10 +4,11 @@
 		"title": "Задача",
 		"seeMore": "Показать больше",
 		"seeLess": "Показать меньше",
-		"tokens": "Токенов:",
-		"cache": "Кэш:",
-		"apiCost": "Стоимость API:",
-		"contextWindow": "Длина контекста:",
+		"tokens": "Токенов",
+		"cache": "Кэш",
+		"apiCost": "Стоимость API",
+		"size": "Размер",
+		"contextWindow": "Длина контекста",
 		"closeAndStart": "Закрыть задачу и начать новую",
 		"export": "Экспортировать историю задач",
 		"delete": "Удалить задачу (Shift + клик для пропуска подтверждения)",

+ 5 - 4
webview-ui/src/i18n/locales/tr/chat.json

@@ -4,10 +4,11 @@
 		"title": "Görev",
 		"seeMore": "Daha fazla gör",
 		"seeLess": "Daha az gör",
-		"tokens": "Tokenlar:",
-		"cache": "Önbellek:",
-		"apiCost": "API Maliyeti:",
-		"contextWindow": "Bağlam Uzunluğu:",
+		"tokens": "Tokenlar",
+		"cache": "Önbellek",
+		"apiCost": "API Maliyeti",
+		"size": "Boyut",
+		"contextWindow": "Bağlam Uzunluğu",
 		"closeAndStart": "Görevi kapat ve yeni bir görev başlat",
 		"export": "Görev geçmişini dışa aktar",
 		"delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)",

+ 5 - 4
webview-ui/src/i18n/locales/vi/chat.json

@@ -4,10 +4,11 @@
 		"title": "Nhiệm vụ",
 		"seeMore": "Xem thêm",
 		"seeLess": "Thu gọn",
-		"tokens": "Tokens:",
-		"cache": "Bộ nhớ đệm:",
-		"apiCost": "Chi phí API:",
-		"contextWindow": "Chiều dài bối cảnh:",
+		"tokens": "Tokens",
+		"cache": "Bộ nhớ đệm",
+		"apiCost": "Chi phí API",
+		"size": "Kích thước",
+		"contextWindow": "Chiều dài bối cảnh",
 		"closeAndStart": "Đóng nhiệm vụ và bắt đầu nhiệm vụ mới",
 		"export": "Xuất lịch sử nhiệm vụ",
 		"delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)",

+ 5 - 4
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -4,10 +4,11 @@
 		"title": "任务",
 		"seeMore": "展开",
 		"seeLess": "收起",
-		"tokens": "Token 用量:",
-		"cache": "缓存:",
-		"apiCost": "API 费用:",
-		"contextWindow": "上下文长度:",
+		"tokens": "Token 用量",
+		"cache": "缓存",
+		"apiCost": "API 费用",
+		"size": "大小",
+		"contextWindow": "上下文长度",
 		"closeAndStart": "关闭任务并开始新任务",
 		"export": "导出任务历史",
 		"delete": "删除任务(Shift + 点击跳过确认)",

+ 3 - 2
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -4,9 +4,10 @@
 		"title": "工作",
 		"seeMore": "顯示更多",
 		"seeLess": "顯示較少",
-		"tokens": "Tokens:",
-		"cache": "快取",
+		"tokens": "Tokens",
+		"cache": "快取",
 		"apiCost": "API 費用:",
+		"size": "大小",
 		"contextWindow": "上下文長度:",
 		"closeAndStart": "關閉現有工作並開始一項新的工作",
 		"export": "匯出工作紀錄",