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, tasks,
async (task) => { async (task) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // 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() const [newTaskMetrics] = await destDb.insert(schema.taskMetrics).values(newTaskMetricsValues).returning()
if (!newTaskMetrics) { 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 React, { memo, useEffect, useMemo, useRef, useState } from "react"
import { useSize } from "react-use" import { useSize } from "react-use"
import { useCopyToClipboard } from "../../utils/clipboard"
import { useTranslation, Trans } from "react-i18next" 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 { safeJsonParse } from "../../utils/json"
import { import {
ClineApiReqInfo, ClineApiReqInfo,
@ -25,7 +28,7 @@ import McpResourceRow from "../mcp/McpResourceRow"
import McpToolRow from "../mcp/McpToolRow" import McpToolRow from "../mcp/McpToolRow"
import { highlightMentions } from "./TaskHeader" import { highlightMentions } from "./TaskHeader"
import { CheckpointSaved } from "./checkpoints/CheckpointSaved" import { CheckpointSaved } from "./checkpoints/CheckpointSaved"
import FollowUpSuggest from "./FollowUpSuggest" import { FollowUpSuggest } from "./FollowUpSuggest"
interface ChatRowProps { interface ChatRowProps {
message: ClineMessage message: ClineMessage
@ -230,7 +233,8 @@ export const ChatRowContent = ({
return [ return [
<span <span
className="codicon codicon-question" 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>, <span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:questions.hasQuestion")}</span>,
] ]
default: default:
@ -795,39 +799,6 @@ export const ChatRowContent = ({
</> </>
)} )}
</p> </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": case "user_feedback":
return ( return (
<div <div className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap word-break-break-word overflow-wrap-anywhere">
className="outline rounded p-4" <div className="flex justify-between gap-2">
style={{ <div className="flex-grow px-2 py-1">{highlightMentions(message.text)}</div>
color: "var(--vscode-badge-foreground)", <Button
padding: "4px", variant="ghost"
overflow: "hidden", size="icon"
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",
}}
disabled={isStreaming} disabled={isStreaming}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
vscode.postMessage({ vscode.postMessage({ type: "deleteMessage", value: message.ts })
type: "deleteMessage",
value: message.ts,
})
}}> }}>
<span className="codicon codicon-trash"></span> <span className="codicon codicon-trash" />
</VSCodeButton> </Button>
</div> </div>
{message.images && message.images.length > 0 && ( {message.images && message.images.length > 0 && (
<Thumbnails images={message.images} style={{ marginTop: "8px" }} /> <Thumbnails images={message.images} style={{ marginTop: "8px" }} />

View file

@ -1,7 +1,8 @@
import { useCallback } from "react" import { useCallback } from "react"
import { cn } from "../../lib/utils" import { ArrowRight, Edit } from "lucide-react"
import { Button } from "../ui/button"
import { Edit } from "lucide-react" import { Button } from "@/components/ui"
import { useAppTranslation } from "../../i18n/TranslationContext" import { useAppTranslation } from "../../i18n/TranslationContext"
interface FollowUpSuggestProps { interface FollowUpSuggestProps {
@ -10,7 +11,7 @@ interface FollowUpSuggestProps {
ts: number ts: number
} }
const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: FollowUpSuggestProps) => { export const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: FollowUpSuggestProps) => {
const { t } = useAppTranslation() const { t } = useAppTranslation()
const handleSuggestionClick = useCallback( const handleSuggestionClick = useCallback(
(suggestion: string, event: React.MouseEvent) => { (suggestion: string, event: React.MouseEvent) => {
@ -19,45 +20,39 @@ const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: Follow
[onSuggestionClick], [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) { if (!suggestions?.length || !onSuggestionClick) {
return null return null
} }
return ( return (
<div className="h-full"> <div className="flex mb-2 flex-col h-full border rounded-xs">
<div className="h-full scrollbar-thin scrollbar-thumb-vscode-scrollbarSlider-background scrollbar-track-transparent"> {suggestions.map((suggestion) => (
<div className={cn("flex gap-2.5 pb-2 flex-col h-full")}> <div key={`${suggestion}-${ts}`} className="w-full relative group">
{suggestions.map((suggestion) => ( <Button
<div key={`${suggestion}-${ts}`} className="w-full relative group"> variant="ghost"
<Button className="text-left whitespace-normal break-words w-full h-auto py-3 justify-start pr-8"
variant="secondary" onClick={(event) => handleSuggestionClick(suggestion, event)}
className="w-full text-left whitespace-normal break-words h-auto min-h-[28px] py-2 justify-start pr-8" aria-label={suggestion}>
onClick={(event) => handleSuggestionClick(suggestion, event)} <div className="flex flex-row items-center gap-2">
aria-label={suggestion}> <ArrowRight />
<span className="text-left">{suggestion}</span> <div>{suggestion}</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"
className="h-6 w-6 p-1 hover:bg-vscode-button-hoverBackground">
<Edit className="h-4 w-4" />
</Button>
</div>
</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> ))}
</div> </div>
) )
} }
export default FollowUpSuggest

View file

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

View file

@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const badgeVariants = cva( 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: { variants: {
variant: { variant: {

View file

@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const buttonVariants = cva( 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: { variants: {
variant: { variant: {
@ -13,7 +13,7 @@ const buttonVariants = cva(
"border border-vscode-input-border bg-primary text-primary-foreground shadow hover:bg-primary/90", "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", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: 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: secondary:
"border border-vscode-input-border bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", "border border-vscode-input-border bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",