Skip to content

Commit 4761f8a

Browse files
committed
feat: move task details to top activity bar (#5652)
- Created new TaskActivityBar component to display task details at the top - Simplified TaskHeader to only show context window progress and todo list - Updated ChatView to integrate both components properly - Maintains all existing functionality while improving UI layout - Addresses UX concerns about task details taking up UI space Fixes #5652
1 parent e84dd0a commit 4761f8a

File tree

4 files changed

+95
-201
lines changed

4 files changed

+95
-201
lines changed

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import BrowserSessionRow from "./BrowserSessionRow"
4848
import ChatRow from "./ChatRow"
4949
import ChatTextArea from "./ChatTextArea"
5050
import TaskHeader from "./TaskHeader"
51+
import { TaskActivityBar } from "./TaskActivityBar"
5152
import AutoApproveMenu from "./AutoApproveMenu"
5253
import SystemPromptWarning from "./SystemPromptWarning"
5354
import ProfileViolationWarning from "./ProfileViolationWarning"
@@ -1612,17 +1613,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
16121613
)}
16131614
{task ? (
16141615
<>
1615-
<TaskHeader
1616+
<TaskActivityBar
16161617
task={task}
16171618
tokensIn={apiMetrics.totalTokensIn}
16181619
tokensOut={apiMetrics.totalTokensOut}
1619-
cacheWrites={apiMetrics.totalCacheWrites}
1620-
cacheReads={apiMetrics.totalCacheReads}
1620+
cacheWrites={apiMetrics.totalCacheWrites || 0}
1621+
cacheReads={apiMetrics.totalCacheReads || 0}
16211622
totalCost={apiMetrics.totalCost}
1623+
onClose={handleTaskCloseButtonClick}
1624+
/>
1625+
<TaskHeader
1626+
task={task}
16221627
contextTokens={apiMetrics.contextTokens}
16231628
buttonsDisabled={sendingDisabled}
16241629
handleCondenseContext={handleCondenseContext}
1625-
onClose={handleTaskCloseButtonClick}
16261630
todos={latestTodos}
16271631
/>
16281632

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from "react"
2+
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
3+
import { formatLargeNumber } from "@src/utils/format"
4+
import type { ClineMessage } from "@roo-code/types"
5+
6+
interface TaskActivityBarProps {
7+
task: ClineMessage
8+
tokensIn: number
9+
tokensOut: number
10+
cacheWrites: number
11+
cacheReads: number
12+
totalCost: number
13+
onClose: () => void
14+
}
15+
16+
export const TaskActivityBar: React.FC<TaskActivityBarProps> = ({
17+
task,
18+
tokensIn,
19+
tokensOut,
20+
cacheWrites,
21+
cacheReads,
22+
totalCost,
23+
onClose,
24+
}) => {
25+
if (!task) {
26+
return null
27+
}
28+
29+
const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads
30+
const taskText = task.text || ""
31+
32+
return (
33+
<div className="bg-vscode-editor-background border-b border-vscode-panel-border p-3">
34+
<div className="flex items-center justify-between gap-4">
35+
{/* Task Text */}
36+
<div className="flex-1 min-w-0">
37+
<div className="text-sm text-vscode-foreground font-medium truncate" title={taskText}>
38+
{taskText}
39+
</div>
40+
</div>
41+
42+
{/* Metrics */}
43+
<div className="flex items-center gap-4 text-xs text-vscode-descriptionForeground">
44+
{/* Tokens */}
45+
{totalTokens > 0 && (
46+
<div className="flex items-center gap-2">
47+
<span>Tokens: {formatLargeNumber(totalTokens)}</span>
48+
{(cacheWrites > 0 || cacheReads > 0) && (
49+
<span className="text-vscode-charts-blue">
50+
(Cache: {formatLargeNumber(cacheWrites + cacheReads)})
51+
</span>
52+
)}
53+
</div>
54+
)}
55+
56+
{/* Cost */}
57+
{totalCost !== undefined && totalCost > 0 && (
58+
<div className="text-vscode-charts-green">Cost: ${totalCost.toFixed(2)}</div>
59+
)}
60+
</div>
61+
62+
{/* Actions */}
63+
<div className="flex items-center gap-2">
64+
<VSCodeButton appearance="icon" onClick={onClose} title="Close Task" className="!p-1">
65+
<span className="codicon codicon-close"></span>
66+
</VSCodeButton>
67+
</div>
68+
</div>
69+
</div>
70+
)
71+
}

webview-ui/src/components/chat/TaskHeader.tsx

Lines changed: 15 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,33 @@
1-
import { memo, useRef, useState } from "react"
2-
import { useWindowSize } from "react-use"
1+
import { memo } from "react"
32
import { useTranslation } from "react-i18next"
4-
import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
5-
import { CloudUpload, CloudDownload, FoldVertical } from "lucide-react"
3+
import { FoldVertical } from "lucide-react"
64

75
import type { ClineMessage } from "@roo-code/types"
86

97
import { getModelMaxOutputTokens } from "@roo/api"
108

11-
import { formatLargeNumber } from "@src/utils/format"
12-
import { cn } from "@src/lib/utils"
13-
import { Button, StandardTooltip } from "@src/components/ui"
9+
import { StandardTooltip } from "@src/components/ui"
1410
import { useExtensionState } from "@src/context/ExtensionStateContext"
1511
import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel"
1612

17-
import Thumbnails from "../common/Thumbnails"
18-
19-
import { TaskActions } from "./TaskActions"
20-
import { ShareButton } from "./ShareButton"
2113
import { ContextWindowProgress } from "./ContextWindowProgress"
22-
import { Mention } from "./Mention"
2314
import { TodoListDisplay } from "./TodoListDisplay"
2415

2516
export interface TaskHeaderProps {
2617
task: ClineMessage
27-
tokensIn: number
28-
tokensOut: number
29-
cacheWrites?: number
30-
cacheReads?: number
31-
totalCost: number
3218
contextTokens: number
3319
buttonsDisabled: boolean
3420
handleCondenseContext: (taskId: string) => void
35-
onClose: () => void
3621
todos?: any[]
3722
}
3823

39-
const TaskHeader = ({
40-
task,
41-
tokensIn,
42-
tokensOut,
43-
cacheWrites,
44-
cacheReads,
45-
totalCost,
46-
contextTokens,
47-
buttonsDisabled,
48-
handleCondenseContext,
49-
onClose,
50-
todos,
51-
}: TaskHeaderProps) => {
24+
const TaskHeader = ({ task, contextTokens, buttonsDisabled, handleCondenseContext, todos }: TaskHeaderProps) => {
5225
const { t } = useTranslation()
5326
const { apiConfiguration, currentTaskItem } = useExtensionState()
5427
const { id: modelId, info: model } = useSelectedModel(apiConfiguration)
55-
const [isTaskExpanded, setIsTaskExpanded] = useState(false)
5628

57-
const textContainerRef = useRef<HTMLDivElement>(null)
58-
const textRef = useRef<HTMLDivElement>(null)
5929
const contextWindow = model?.contextWindow || 1
6030

61-
const { width: windowWidth } = useWindowSize()
62-
6331
const condenseButton = (
6432
<StandardTooltip content={t("chat:task.condenseContext")}>
6533
<button
@@ -75,42 +43,15 @@ const TaskHeader = ({
7543

7644
return (
7745
<div className="py-2 px-3">
78-
<div
79-
className={cn(
80-
"p-2.5 flex flex-col gap-1.5 relative z-1 border",
81-
hasTodos ? "rounded-t-xs border-b-0" : "rounded-xs",
82-
isTaskExpanded
83-
? "border-vscode-panel-border text-vscode-foreground"
84-
: "border-vscode-panel-border/80 text-vscode-foreground/80",
85-
)}>
86-
<div className="flex justify-between items-center gap-2">
87-
<div
88-
className="flex items-center cursor-pointer -ml-0.5 select-none grow min-w-0"
89-
onClick={() => setIsTaskExpanded(!isTaskExpanded)}>
90-
<div className="flex items-center shrink-0">
91-
<span className={`codicon codicon-chevron-${isTaskExpanded ? "down" : "right"}`}></span>
92-
</div>
93-
<div className="ml-1.5 whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
94-
<span className="font-bold">
95-
{t("chat:task.title")}
96-
{!isTaskExpanded && ":"}
97-
</span>
98-
{!isTaskExpanded && (
99-
<span className="ml-1">
100-
<Mention text={task.text} />
101-
</span>
102-
)}
103-
</div>
46+
{/* Context Window Progress */}
47+
{contextWindow > 0 && (
48+
<div className="p-2.5 flex flex-col gap-1.5 relative z-1 border border-vscode-panel-border/80 rounded-xs mb-2">
49+
<div className="flex items-center gap-1 flex-shrink-0">
50+
<span className="font-bold text-sm text-vscode-foreground/80">
51+
{t("chat:task.contextWindow")}
52+
</span>
10453
</div>
105-
<StandardTooltip content={t("chat:task.closeAndStart")}>
106-
<Button variant="ghost" size="icon" onClick={onClose} className="shrink-0 w-5 h-5">
107-
<span className="codicon codicon-close" />
108-
</Button>
109-
</StandardTooltip>
110-
</div>
111-
{/* Collapsed state: Track context and cost if we have any */}
112-
{!isTaskExpanded && contextWindow > 0 && (
113-
<div className={`w-full flex flex-row items-center gap-1 h-auto`}>
54+
<div className="w-full flex flex-row items-center gap-1 h-auto">
11455
<ContextWindowProgress
11556
contextWindow={contextWindow}
11657
contextTokens={contextTokens || 0}
@@ -121,105 +62,11 @@ const TaskHeader = ({
12162
}
12263
/>
12364
{condenseButton}
124-
<ShareButton item={currentTaskItem} disabled={buttonsDisabled} />
125-
{!!totalCost && <VSCodeBadge>${totalCost.toFixed(2)}</VSCodeBadge>}
12665
</div>
127-
)}
128-
{/* Expanded state: Show task text and images */}
129-
{isTaskExpanded && (
130-
<>
131-
<div
132-
ref={textContainerRef}
133-
className="-mt-0.5 text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
134-
<div
135-
ref={textRef}
136-
className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere"
137-
style={{
138-
display: "-webkit-box",
139-
WebkitLineClamp: "unset",
140-
WebkitBoxOrient: "vertical",
141-
}}>
142-
<Mention text={task.text} />
143-
</div>
144-
</div>
145-
{task.images && task.images.length > 0 && <Thumbnails images={task.images} />}
146-
147-
<div className="flex flex-col gap-1">
148-
{isTaskExpanded && contextWindow > 0 && (
149-
<div
150-
className={`w-full flex ${windowWidth < 400 ? "flex-col" : "flex-row"} gap-1 h-auto`}>
151-
<div className="flex items-center gap-1 flex-shrink-0">
152-
<span className="font-bold" data-testid="context-window-label">
153-
{t("chat:task.contextWindow")}
154-
</span>
155-
</div>
156-
<ContextWindowProgress
157-
contextWindow={contextWindow}
158-
contextTokens={contextTokens || 0}
159-
maxTokens={
160-
model
161-
? getModelMaxOutputTokens({
162-
modelId,
163-
model,
164-
settings: apiConfiguration,
165-
})
166-
: undefined
167-
}
168-
/>
169-
{condenseButton}
170-
</div>
171-
)}
172-
<div className="flex justify-between items-center h-[20px]">
173-
<div className="flex items-center gap-1 flex-wrap">
174-
<span className="font-bold">{t("chat:task.tokens")}</span>
175-
{typeof tokensIn === "number" && tokensIn > 0 && (
176-
<span className="flex items-center gap-0.5">
177-
<i className="codicon codicon-arrow-up text-xs font-bold" />
178-
{formatLargeNumber(tokensIn)}
179-
</span>
180-
)}
181-
{typeof tokensOut === "number" && tokensOut > 0 && (
182-
<span className="flex items-center gap-0.5">
183-
<i className="codicon codicon-arrow-down text-xs font-bold" />
184-
{formatLargeNumber(tokensOut)}
185-
</span>
186-
)}
187-
</div>
188-
{!totalCost && <TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />}
189-
</div>
190-
191-
{((typeof cacheReads === "number" && cacheReads > 0) ||
192-
(typeof cacheWrites === "number" && cacheWrites > 0)) && (
193-
<div className="flex items-center gap-1 flex-wrap h-[20px]">
194-
<span className="font-bold">{t("chat:task.cache")}</span>
195-
{typeof cacheWrites === "number" && cacheWrites > 0 && (
196-
<span className="flex items-center gap-0.5">
197-
<CloudUpload size={16} />
198-
{formatLargeNumber(cacheWrites)}
199-
</span>
200-
)}
201-
{typeof cacheReads === "number" && cacheReads > 0 && (
202-
<span className="flex items-center gap-0.5">
203-
<CloudDownload size={16} />
204-
{formatLargeNumber(cacheReads)}
205-
</span>
206-
)}
207-
</div>
208-
)}
66+
</div>
67+
)}
20968

210-
{!!totalCost && (
211-
<div className="flex justify-between items-center h-[20px]">
212-
<div className="flex items-center gap-1">
213-
<span className="font-bold">{t("chat:task.apiCost")}</span>
214-
<span>${totalCost?.toFixed(2)}</span>
215-
</div>
216-
<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
217-
</div>
218-
)}
219-
</div>
220-
</>
221-
)}
222-
</div>
69+
{/* Todo List */}
22370
<TodoListDisplay todos={todos ?? (task as any)?.tool?.todos ?? []} />
22471
</div>
22572
)

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

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,10 @@ vi.mock("@src/context/ExtensionStateContext", () => ({
4747
describe("TaskHeader", () => {
4848
const defaultProps: TaskHeaderProps = {
4949
task: { type: "say", ts: Date.now(), text: "Test task", images: [] },
50-
tokensIn: 100,
51-
tokensOut: 50,
52-
totalCost: 0.05,
5350
contextTokens: 200,
5451
buttonsDisabled: false,
5552
handleCondenseContext: vi.fn(),
56-
onClose: vi.fn(),
53+
todos: [],
5754
}
5855

5956
const queryClient = new QueryClient()
@@ -66,31 +63,6 @@ describe("TaskHeader", () => {
6663
)
6764
}
6865

69-
it("should display cost when totalCost is greater than 0", () => {
70-
renderTaskHeader()
71-
expect(screen.getByText("$0.05")).toBeInTheDocument()
72-
})
73-
74-
it("should not display cost when totalCost is 0", () => {
75-
renderTaskHeader({ totalCost: 0 })
76-
expect(screen.queryByText("$0.0000")).not.toBeInTheDocument()
77-
})
78-
79-
it("should not display cost when totalCost is null", () => {
80-
renderTaskHeader({ totalCost: null as any })
81-
expect(screen.queryByText(/\$/)).not.toBeInTheDocument()
82-
})
83-
84-
it("should not display cost when totalCost is undefined", () => {
85-
renderTaskHeader({ totalCost: undefined as any })
86-
expect(screen.queryByText(/\$/)).not.toBeInTheDocument()
87-
})
88-
89-
it("should not display cost when totalCost is NaN", () => {
90-
renderTaskHeader({ totalCost: NaN })
91-
expect(screen.queryByText(/\$/)).not.toBeInTheDocument()
92-
})
93-
9466
it("should render the condense context button", () => {
9567
renderTaskHeader()
9668
// Find the button that contains the FoldVertical icon

0 commit comments

Comments
 (0)