Task header theme fixes (#2721)

This commit is contained in:
Chris Estreich 2025-04-17 15:28:11 -07:00 committed by GitHub
parent b077267b4a
commit 3c937c3827
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 85 additions and 135 deletions

View file

@ -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) {

View file

@ -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" }} />

View file

@ -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

View file

@ -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),
})}

View file

@ -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: {

View file

@ -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",