Skip to content

Commit c2bbae9

Browse files
author
Eric Wheeler
committed
ui: move token and cost info from header to dedicated footer
Create a new TaskItemFooter component that displays token and cost information with different styles for compact and full views. Move the CopyButton and ExportButton from header to footer in full view. Adjust file size display positioning in the header for better visual alignment. Signed-off-by: Eric Wheeler <[email protected]>
1 parent 23b2a8f commit c2bbae9

File tree

3 files changed

+102
-27
lines changed

3 files changed

+102
-27
lines changed

webview-ui/src/components/history/TaskItem.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Checkbox } from "@/components/ui/checkbox"
77
import { useAppTranslation } from "@/i18n/TranslationContext"
88

99
import TaskItemHeader from "./TaskItemHeader"
10+
import TaskItemFooter from "./TaskItemFooter"
1011

1112
interface TaskItemProps {
1213
item: HistoryItem
@@ -106,6 +107,9 @@ const TaskItem = ({
106107
{isCompact ? item.task : undefined}
107108
</div>
108109

110+
{/* Task Item Footer */}
111+
<TaskItemFooter item={item} variant={variant} isSelectionMode={isSelectionMode} />
112+
109113
{/* Workspace info */}
110114
{showWorkspace && item.workspace && (
111115
<div
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React from "react"
2+
import type { HistoryItem } from "@roo-code/types"
3+
import { Coins } from "lucide-react"
4+
import { formatLargeNumber } from "@/utils/format"
5+
import { cn } from "@/lib/utils"
6+
import { useAppTranslation } from "@/i18n/TranslationContext"
7+
import { CopyButton } from "./CopyButton"
8+
import { ExportButton } from "./ExportButton"
9+
10+
export interface TaskItemFooterProps {
11+
item: HistoryItem
12+
variant: "compact" | "full"
13+
isSelectionMode?: boolean
14+
}
15+
16+
const TaskItemFooter: React.FC<TaskItemFooterProps> = ({ item, variant, isSelectionMode = false }) => {
17+
const { t } = useAppTranslation()
18+
const isCompact = variant === "compact"
19+
20+
const metadataIconWithTextAdjustStyle: React.CSSProperties = {
21+
fontSize: "12px",
22+
color: "var(--vscode-descriptionForeground)",
23+
verticalAlign: "middle",
24+
marginBottom: "-2px",
25+
fontWeight: "bold",
26+
}
27+
28+
return (
29+
<div
30+
className={cn("text-xs text-vscode-descriptionForeground", {
31+
"mt-2 flex items-center flex-wrap gap-x-2": isCompact,
32+
"mt-1 flex justify-between items-end": !isCompact,
33+
})}>
34+
{isCompact ? (
35+
<>
36+
{/* Compact Tokens */}
37+
{(item.tokensIn || item.tokensOut) && (
38+
<>
39+
<span data-testid="tokens-in-footer-compact">
40+
{formatLargeNumber(item.tokensIn || 0)}
41+
</span>
42+
<span data-testid="tokens-out-footer-compact">
43+
{formatLargeNumber(item.tokensOut || 0)}
44+
</span>
45+
</>
46+
)}
47+
{/* Compact Cost */}
48+
{!!item.totalCost && (
49+
<span className="flex items-center">
50+
<Coins className="inline-block size-[1em] mr-1" />
51+
<span data-testid="cost-footer-compact">{"$" + item.totalCost.toFixed(2)}</span>
52+
</span>
53+
)}
54+
</>
55+
) : (
56+
<>
57+
<div className="flex flex-col gap-1">
58+
{/* Full Tokens */}
59+
{(item.tokensIn || item.tokensOut) && (
60+
<div className="flex items-center flex-wrap gap-x-1">
61+
<span className="font-medium">{t("history:tokensLabel")}</span>
62+
<span className="flex items-center gap-px" data-testid="tokens-in-footer-full">
63+
<i className="codicon codicon-arrow-up" style={metadataIconWithTextAdjustStyle} />
64+
<span className="font-medium">{formatLargeNumber(item.tokensIn || 0)}</span>
65+
</span>
66+
<span className="flex items-center gap-px" data-testid="tokens-out-footer-full">
67+
<i className="codicon codicon-arrow-down" style={metadataIconWithTextAdjustStyle} />
68+
<span className="font-medium">{formatLargeNumber(item.tokensOut || 0)}</span>
69+
</span>
70+
</div>
71+
)}
72+
{/* Full Cost */}
73+
{!!item.totalCost && (
74+
<div className="flex items-center flex-wrap gap-x-1">
75+
<span className="font-medium">{t("history:apiCostLabel")}</span>
76+
<span data-testid="cost-footer-full">{"$" + item.totalCost.toFixed(4)}</span>
77+
</div>
78+
)}
79+
</div>
80+
{/* Action Buttons for non-compact view */}
81+
{!isSelectionMode && (
82+
<div className="flex flex-row gap-0 items-center opacity-50 hover:opacity-100">
83+
<CopyButton itemTask={item.task} />
84+
<ExportButton itemId={item.id} />
85+
</div>
86+
)}
87+
</>
88+
)}
89+
</div>
90+
)
91+
}
92+
93+
export default TaskItemFooter

webview-ui/src/components/history/TaskItemHeader.tsx

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import React from "react"
22
import type { HistoryItem } from "@roo-code/types"
33
import prettyBytes from "pretty-bytes"
4-
import { DollarSign } from "lucide-react"
54
import { vscode } from "@/utils/vscode"
65
import { formatLargeNumber, formatDate } from "@/utils/format"
76
import { Button } from "@/components/ui"
87
import { CopyButton } from "./CopyButton"
9-
import { ExportButton } from "./ExportButton"
108

119
export interface TaskItemHeaderProps {
1210
item: HistoryItem
@@ -53,24 +51,6 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, variant, isSelect
5351
{formatDate(item.ts)}
5452
</span>
5553

56-
{/* Tokens Info */}
57-
{(item.tokensIn || item.tokensOut) && (
58-
<span className="text-vscode-descriptionForeground flex items-center gap-px">
59-
<i className="codicon codicon-arrow-up" style={metadataIconWithTextAdjustStyle} />
60-
<span data-testid="tokens-in">{formatLargeNumber(item.tokensIn || 0)}</span>
61-
<i className="codicon codicon-arrow-down" style={metadataIconWithTextAdjustStyle} />
62-
<span data-testid="tokens-out">{formatLargeNumber(item.tokensOut || 0)}</span>
63-
</span>
64-
)}
65-
66-
{/* Cost Info */}
67-
{!!item.totalCost && (
68-
<span className="text-vscode-descriptionForeground flex items-center gap-px">
69-
<DollarSign className="inline-block size-[1em]" />
70-
{isCompact ? item.totalCost.toFixed(2) : item.totalCost.toFixed(4)}
71-
</span>
72-
)}
73-
7454
{/* Cache Info */}
7555
{!!item.cacheWrites && (
7656
<span className="text-vscode-descriptionForeground flex items-center gap-px">
@@ -80,11 +60,6 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, variant, isSelect
8060
<span data-testid="cache-reads">{formatLargeNumber(item.cacheReads || 0)}</span>
8161
</span>
8262
)}
83-
84-
{/* Size Info - only in full variant */}
85-
{!isCompact && item.size && (
86-
<span className="text-vscode-descriptionForeground">{prettyBytes(item.size)}</span>
87-
)}
8863
</div>
8964

9065
{/* Action Buttons */}
@@ -94,8 +69,6 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, variant, isSelect
9469
<CopyButton itemTask={item.task} />
9570
) : (
9671
<>
97-
<CopyButton itemTask={item.task} />
98-
<ExportButton itemId={item.id} />
9972
{onDelete && (
10073
<Button
10174
variant="ghost"
@@ -106,6 +79,11 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, variant, isSelect
10679
<span className="codicon codicon-trash" style={actionIconStyle} />
10780
</Button>
10881
)}
82+
{!isCompact && item.size && (
83+
<span className="text-vscode-descriptionForeground ml-1 text-sm">
84+
{prettyBytes(item.size)}
85+
</span>
86+
)}
10987
</>
11088
)}
11189
</div>

0 commit comments

Comments
 (0)