Răsfoiți Sursa

Task header theme fixes (#2721)

Chris Estreich 10 luni în urmă
părinte
comite
3c937c3827

+ 11 - 1
evals/packages/db/scripts/copy-run.mts

@@ -57,7 +57,17 @@ const copyRun = async (runId: number) => {
 		tasks,
 		async (task) => {
 			// eslint-disable-next-line @typescript-eslint/no-unused-vars
-			const { id: _, ...newTaskMetricsValues } = task.taskMetrics!
+			const { id: _, ...newTaskMetricsValues } = task.taskMetrics || {
+				duration: 0,
+				tokensIn: 0,
+				tokensOut: 0,
+				tokensContext: 0,
+				cacheWrites: 0,
+				cacheReads: 0,
+				cost: 0,
+				createdAt: new Date(),
+			}
+
 			const [newTaskMetrics] = await destDb.insert(schema.taskMetrics).values(newTaskMetricsValues).returning()
 
 			if (!newTaskMetrics) {

+ 18 - 74
webview-ui/src/components/chat/ChatRow.tsx

@@ -1,9 +1,12 @@
-import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
-import deepEqual from "fast-deep-equal"
 import React, { memo, useEffect, useMemo, useRef, useState } from "react"
 import { useSize } from "react-use"
-import { useCopyToClipboard } from "../../utils/clipboard"
 import { useTranslation, Trans } from "react-i18next"
+import deepEqual from "fast-deep-equal"
+import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
+
+import { Button } from "@/components/ui"
+
+import { useCopyToClipboard } from "../../utils/clipboard"
 import { safeJsonParse } from "../../utils/json"
 import {
 	ClineApiReqInfo,
@@ -25,7 +28,7 @@ import McpResourceRow from "../mcp/McpResourceRow"
 import McpToolRow from "../mcp/McpToolRow"
 import { highlightMentions } from "./TaskHeader"
 import { CheckpointSaved } from "./checkpoints/CheckpointSaved"
-import FollowUpSuggest from "./FollowUpSuggest"
+import { FollowUpSuggest } from "./FollowUpSuggest"
 
 interface ChatRowProps {
 	message: ClineMessage
@@ -230,7 +233,8 @@ export const ChatRowContent = ({
 				return [
 					<span
 						className="codicon codicon-question"
-						style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
+						style={{ color: normalColor, marginBottom: "-1.5px" }}
+					/>,
 					<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:questions.hasQuestion")}</span>,
 				]
 			default:
@@ -795,39 +799,6 @@ export const ChatRowContent = ({
 											</>
 										)}
 									</p>
-
-									{/* {apiProvider === "" && (
-											<div
-												style={{
-													display: "flex",
-													alignItems: "center",
-													backgroundColor:
-														"color-mix(in srgb, var(--vscode-errorForeground) 20%, transparent)",
-													color: "var(--vscode-editor-foreground)",
-													padding: "6px 8px",
-													borderRadius: "3px",
-													margin: "10px 0 0 0",
-													fontSize: "12px",
-												}}>
-												<i
-													className="codicon codicon-warning"
-													style={{
-														marginRight: 6,
-														fontSize: 16,
-														color: "var(--vscode-errorForeground)",
-													}}></i>
-												<span>
-													Uh-oh, this could be a problem on end. We've been alerted and
-													will resolve this ASAP. You can also{" "}
-													<a
-														href=""
-														style={{ color: "inherit", textDecoration: "underline" }}>
-														contact us
-													</a>
-													.
-												</span>
-											</div>
-										)} */}
 								</>
 							)}
 
@@ -853,46 +824,19 @@ export const ChatRowContent = ({
 					)
 				case "user_feedback":
 					return (
-						<div
-							className="outline rounded p-4"
-							style={{
-								color: "var(--vscode-badge-foreground)",
-								padding: "4px",
-								overflow: "hidden",
-								whiteSpace: "pre-wrap",
-								wordBreak: "break-word",
-								overflowWrap: "anywhere",
-							}}>
-							<div
-								style={{
-									display: "flex",
-									justifyContent: "space-between",
-									alignItems: "flex-start",
-									gap: "10px",
-								}}>
-								<span style={{ display: "block", flexGrow: 1, padding: "4px" }}>
-									{highlightMentions(message.text)}
-								</span>
-								<VSCodeButton
-									appearance="icon"
-									style={{
-										padding: "3px",
-										flexShrink: 0,
-										height: "24px",
-										marginTop: "-3px",
-										marginBottom: "-3px",
-										marginRight: "-6px",
-									}}
+						<div className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap word-break-break-word overflow-wrap-anywhere">
+							<div className="flex justify-between gap-2">
+								<div className="flex-grow px-2 py-1">{highlightMentions(message.text)}</div>
+								<Button
+									variant="ghost"
+									size="icon"
 									disabled={isStreaming}
 									onClick={(e) => {
 										e.stopPropagation()
-										vscode.postMessage({
-											type: "deleteMessage",
-											value: message.ts,
-										})
+										vscode.postMessage({ type: "deleteMessage", value: message.ts })
 									}}>
-									<span className="codicon codicon-trash"></span>
-								</VSCodeButton>
+									<span className="codicon codicon-trash" />
+								</Button>
 							</div>
 							{message.images && message.images.length > 0 && (
 								<Thumbnails images={message.images} style={{ marginTop: "8px" }} />

+ 31 - 36
webview-ui/src/components/chat/FollowUpSuggest.tsx

@@ -1,7 +1,8 @@
 import { useCallback } from "react"
-import { cn } from "../../lib/utils"
-import { Button } from "../ui/button"
-import { Edit } from "lucide-react"
+import { ArrowRight, Edit } from "lucide-react"
+
+import { Button } from "@/components/ui"
+
 import { useAppTranslation } from "../../i18n/TranslationContext"
 
 interface FollowUpSuggestProps {
@@ -10,7 +11,7 @@ interface FollowUpSuggestProps {
 	ts: number
 }
 
-const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: FollowUpSuggestProps) => {
+export const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: FollowUpSuggestProps) => {
 	const { t } = useAppTranslation()
 	const handleSuggestionClick = useCallback(
 		(suggestion: string, event: React.MouseEvent) => {
@@ -19,45 +20,39 @@ const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: Follow
 		[onSuggestionClick],
 	)
 
-	// Don't render if there are no suggestions or no click handler
+	// Don't render if there are no suggestions or no click handler.
 	if (!suggestions?.length || !onSuggestionClick) {
 		return null
 	}
 
 	return (
-		<div className="h-full">
-			<div className="h-full scrollbar-thin scrollbar-thumb-vscode-scrollbarSlider-background scrollbar-track-transparent">
-				<div className={cn("flex gap-2.5 pb-2 flex-col h-full")}>
-					{suggestions.map((suggestion) => (
-						<div key={`${suggestion}-${ts}`} className="w-full relative group">
-							<Button
-								variant="secondary"
-								className="w-full text-left whitespace-normal break-words h-auto min-h-[28px] py-2 justify-start pr-8"
-								onClick={(event) => handleSuggestionClick(suggestion, event)}
-								aria-label={suggestion}>
-								<span className="text-left">{suggestion}</span>
-							</Button>
-							<div
-								className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity"
-								onClick={(e) => {
-									e.stopPropagation()
-									// Simulate shift-click by directly calling the handler with shiftKey=true
-									onSuggestionClick?.(suggestion, { ...e, shiftKey: true })
-								}}
-								title={t("chat:followUpSuggest.copyToInput")}>
-								<Button
-									variant="ghost"
-									size="icon"
-									className="h-6 w-6 p-1 hover:bg-vscode-button-hoverBackground">
-									<Edit className="h-4 w-4" />
-								</Button>
-							</div>
+		<div className="flex mb-2 flex-col h-full border rounded-xs">
+			{suggestions.map((suggestion) => (
+				<div key={`${suggestion}-${ts}`} className="w-full relative group">
+					<Button
+						variant="ghost"
+						className="text-left whitespace-normal break-words w-full h-auto py-3 justify-start pr-8"
+						onClick={(event) => handleSuggestionClick(suggestion, event)}
+						aria-label={suggestion}>
+						<div className="flex flex-row items-center gap-2">
+							<ArrowRight />
+							<div>{suggestion}</div>
 						</div>
-					))}
+					</Button>
+					<div
+						className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity"
+						onClick={(e) => {
+							e.stopPropagation()
+							// Simulate shift-click by directly calling the handler with shiftKey=true.
+							onSuggestionClick?.(suggestion, { ...e, shiftKey: true })
+						}}
+						title={t("chat:followUpSuggest.copyToInput")}>
+						<Button variant="ghost" size="icon">
+							<Edit />
+						</Button>
+					</div>
 				</div>
-			</div>
+			))}
 		</div>
 	)
 }
-
-export default FollowUpSuggest

+ 22 - 21
webview-ui/src/components/chat/TaskHeader.tsx

@@ -1,13 +1,12 @@
 import React, { memo, useMemo, useRef, useState } from "react"
 import { useWindowSize } from "react-use"
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 import prettyBytes from "pretty-bytes"
 import { useTranslation } from "react-i18next"
 
 import { vscode } from "@/utils/vscode"
 import { formatLargeNumber } from "@/utils/format"
 import { calculateTokenDistribution, getMaxTokensForModel } from "@/utils/model-utils"
-import { Button } from "@/components/ui"
+import { Button, Badge } from "@/components/ui"
 
 import { ClineMessage } from "../../../../src/shared/ExtensionMessage"
 import { mentionRegexGlobal } from "../../../../src/shared/context-mentions"
@@ -17,6 +16,7 @@ import { useExtensionState } from "../../context/ExtensionStateContext"
 import Thumbnails from "../common/Thumbnails"
 import { normalizeApiConfiguration } from "../settings/ApiOptions"
 import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
+import { cn } from "@/lib/utils"
 
 interface TaskHeaderProps {
 	task: ClineMessage
@@ -55,10 +55,15 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
 	const shouldShowPromptCacheInfo = doesModelSupportPromptCache && apiConfiguration?.apiProvider !== "openrouter"
 
 	return (
-		<div className="py-[10px] px-[13px]">
+		<div className="py-2 px-3">
 			<div
-				className={`rounded p-[10px] flex flex-col gap-[6px] relative z-1 outline hover:outline-vscode-badge-foreground hover:text-vscode-badge-foreground transition-color duration-500 ${!!isTaskExpanded ? "outline-vscode-badge-foreground text-vscode-badge-foreground" : "outline-vscode-badge-foreground/80 text-vscode-badge-foreground/80"}`}>
-				<div className="flex justify-between items-center">
+				className={cn(
+					"rounded-xs p-2.5 flex flex-col gap-1.5 relative z-1 border",
+					!!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)}>
@@ -73,14 +78,14 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
 							{!isTaskExpanded && <span className="ml-1">{highlightMentions(task.text, false)}</span>}
 						</div>
 					</div>
-
-					<VSCodeButton
-						appearance="icon"
+					<Button
+						variant="ghost"
+						size="icon"
 						onClick={onClose}
-						className="ml-1.5 shrink-0 text-vscode-badge-foreground"
-						title={t("chat:task.closeAndStart")}>
-						<span className="codicon codicon-close"></span>
-					</VSCodeButton>
+						title={t("chat:task.closeAndStart")}
+						className="shrink-0 w-5 h-5">
+						<span className="codicon codicon-close" />
+					</Button>
 				</div>
 				{/* Collapsed state: Track context and cost if we have any */}
 				{!isTaskExpanded && contextWindow > 0 && (
@@ -90,11 +95,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
 							contextTokens={contextTokens || 0}
 							maxTokens={getMaxTokensForModel(selectedModelInfo, apiConfiguration)}
 						/>
-						{!!totalCost && (
-							<div className="ml-2.5 bg-vscode-editor-foreground text-vscode-editor-background py-0.5 px-1 rounded-full text-[11px] font-medium inline-block shrink-0">
-								${totalCost?.toFixed(2)}
-							</div>
-						)}
+						{!!totalCost && <Badge>${totalCost.toFixed(2)}</Badge>}
 					</div>
 				)}
 				{/* Expanded state: Show task text and images */}
@@ -271,7 +272,7 @@ const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens }: Cont
 				<div className="flex-1 relative">
 					{/* Invisible overlay for hover area */}
 					<div
-						className="absolute w-full cursor-pointer h-4 -top-[7px] z-5"
+						className="absolute w-full h-4 -top-[7px] z-5"
 						title={t("chat:tokenProgress.availableSpace", { amount: formatLargeNumber(availableSize) })}
 						data-testid="context-available-space"
 					/>
@@ -282,7 +283,7 @@ const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens }: Cont
 						<div className="relative h-full" style={{ width: `${currentPercent}%` }}>
 							{/* Invisible overlay for current tokens section */}
 							<div
-								className="absolute cursor-pointer h-4 -top-[7px] w-full z-6"
+								className="absolute h-4 -top-[7px] w-full z-6"
 								title={t("chat:tokenProgress.tokensUsed", {
 									used: formatLargeNumber(safeContextTokens),
 									total: formatLargeNumber(safeContextWindow),
@@ -297,7 +298,7 @@ const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens }: Cont
 						<div className="relative h-full" style={{ width: `${reservedPercent}%` }}>
 							{/* Invisible overlay for reserved section */}
 							<div
-								className="absolute cursor-pointer h-4 -top-[7px] w-full z-6"
+								className="absolute h-4 -top-[7px] w-full z-6"
 								title={t("chat:tokenProgress.reservedForResponse", {
 									amount: formatLargeNumber(reservedForOutput),
 								})}
@@ -312,7 +313,7 @@ const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens }: Cont
 							<div className="relative h-full" style={{ width: `${availablePercent}%` }}>
 								{/* Invisible overlay for available space */}
 								<div
-									className="absolute cursor-pointer h-4 -top-[7px] w-full z-6"
+									className="absolute h-4 -top-[7px] w-full z-6"
 									title={t("chat:tokenProgress.availableSpace", {
 										amount: formatLargeNumber(availableSize),
 									})}

+ 1 - 1
webview-ui/src/components/ui/badge.tsx

@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
 import { cn } from "@/lib/utils"
 
 const badgeVariants = cva(
-	"inline-flex items-center rounded-xs border border-transparent shadow px-2 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+	"inline-flex items-center rounded-full border border-transparent px-2 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
 	{
 		variants: {
 			variant: {

+ 2 - 2
webview-ui/src/components/ui/button.tsx

@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
 import { cn } from "@/lib/utils"
 
 const buttonVariants = cva(
-	"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer",
+	"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer active:opacity-80",
 	{
 		variants: {
 			variant: {
@@ -13,7 +13,7 @@ const buttonVariants = cva(
 					"border border-vscode-input-border bg-primary text-primary-foreground shadow hover:bg-primary/90",
 				destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
 				outline:
-					"border border-vscode-input-border bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+					"border border-vscode-input-border bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
 				secondary:
 					"border border-vscode-input-border bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
 				ghost: "hover:bg-accent hover:text-accent-foreground",