mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-08 03:07:53 +00:00
feat(webview): add edit toggle for todo updates
Extract SubtaskDetail into a shared type and remove the unused SubtaskCostList component.
This commit is contained in:
parent
188a1c6e8e
commit
788a21a192
7 changed files with 39 additions and 139 deletions
|
|
@ -48,19 +48,7 @@ import { QueuedMessages } from "./QueuedMessages"
|
|||
import DismissibleUpsell from "../common/DismissibleUpsell"
|
||||
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
|
||||
import { Cloud } from "lucide-react"
|
||||
|
||||
/**
|
||||
* Detailed information about a subtask for UI display.
|
||||
* Matches the SubtaskDetail interface from backend aggregateTaskCosts.ts
|
||||
*/
|
||||
interface SubtaskDetail {
|
||||
id: string // Task ID
|
||||
name: string // First 50 chars of task description
|
||||
tokens: number // tokensIn + tokensOut
|
||||
cost: number // Aggregated total cost
|
||||
status: "active" | "completed" | "delegated"
|
||||
hasNestedChildren: boolean // Has its own subtasks
|
||||
}
|
||||
import type { SubtaskDetail } from "@src/types/subtasks"
|
||||
|
||||
export interface ChatViewProps {
|
||||
isHidden: boolean
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
import { memo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ChevronRight, ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { formatLargeNumber } from "@/utils/format"
|
||||
|
||||
export interface SubtaskDetail {
|
||||
id: string
|
||||
name: string
|
||||
tokens: number
|
||||
cost: number
|
||||
status: "active" | "completed" | "delegated"
|
||||
hasNestedChildren: boolean
|
||||
}
|
||||
|
||||
export interface SubtaskCostListProps {
|
||||
subtasks: SubtaskDetail[]
|
||||
onSubtaskClick?: (subtaskId: string) => void
|
||||
}
|
||||
|
||||
interface SubtaskRowProps {
|
||||
subtask: SubtaskDetail
|
||||
isLast: boolean
|
||||
onClick?: () => void
|
||||
t: (key: string, options?: Record<string, unknown>) => string
|
||||
}
|
||||
|
||||
const statusColors: Record<SubtaskDetail["status"], string> = {
|
||||
active: "bg-vscode-testing-iconQueued",
|
||||
completed: "bg-vscode-testing-iconPassed",
|
||||
delegated: "bg-vscode-testing-iconSkipped",
|
||||
}
|
||||
|
||||
const SubtaskRow = memo(({ subtask, isLast, onClick, t }: SubtaskRowProps) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"flex items-center gap-2 w-full text-left py-1 px-2 rounded",
|
||||
"text-xs text-vscode-descriptionForeground",
|
||||
"hover:bg-vscode-list-hoverBackground transition-colors",
|
||||
)}>
|
||||
{/* Tree indicator */}
|
||||
<span className="text-vscode-descriptionForeground opacity-50 font-mono">{isLast ? "└─" : "├─"}</span>
|
||||
|
||||
{/* Status indicator */}
|
||||
<span
|
||||
className={cn("size-2 rounded-full shrink-0", statusColors[subtask.status])}
|
||||
title={t(`subtasks.status.${subtask.status}`)}
|
||||
/>
|
||||
|
||||
{/* Subtask name */}
|
||||
<span className="flex-1 truncate" title={subtask.name}>
|
||||
"{subtask.name}"
|
||||
</span>
|
||||
|
||||
{/* Nested indicator */}
|
||||
{subtask.hasNestedChildren && (
|
||||
<span className="text-vscode-descriptionForeground opacity-50" title={t("subtasks.hasNestedChildren")}>
|
||||
+
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Token count */}
|
||||
<span className="text-vscode-descriptionForeground opacity-70 tabular-nums">
|
||||
{formatLargeNumber(subtask.tokens)}
|
||||
</span>
|
||||
|
||||
{/* Cost */}
|
||||
<span className="min-w-[60px] text-right tabular-nums">${subtask.cost.toFixed(2)}</span>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
SubtaskRow.displayName = "SubtaskRow"
|
||||
|
||||
export const SubtaskCostList = memo(({ subtasks, onSubtaskClick }: SubtaskCostListProps) => {
|
||||
const { t } = useTranslation("chat")
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
if (!subtasks || subtasks.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-2 border-t border-vscode-sideBar-background pt-2">
|
||||
{/* Collapsible Header */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className={cn(
|
||||
"flex items-center gap-1 w-full text-left",
|
||||
"text-sm text-vscode-descriptionForeground",
|
||||
"hover:text-vscode-foreground transition-colors",
|
||||
)}
|
||||
aria-expanded={isExpanded}>
|
||||
{isExpanded ? <ChevronDown className="size-4" /> : <ChevronRight className="size-4" />}
|
||||
<span>{t("subtasks.count", { count: subtasks.length })}</span>
|
||||
</button>
|
||||
|
||||
{/* Expanded Subtask List */}
|
||||
{isExpanded && (
|
||||
<div className="mt-2 ml-2 space-y-1">
|
||||
{subtasks.map((subtask, index) => (
|
||||
<SubtaskRow
|
||||
key={subtask.id}
|
||||
subtask={subtask}
|
||||
isLast={index === subtasks.length - 1}
|
||||
onClick={() => onSubtaskClick?.(subtask.id)}
|
||||
t={t}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
SubtaskCostList.displayName = "SubtaskCostList"
|
||||
|
||||
export default SubtaskCostList
|
||||
|
|
@ -35,7 +35,7 @@ import { ContextWindowProgress } from "./ContextWindowProgress"
|
|||
import { Mention } from "./Mention"
|
||||
import { TodoListDisplay } from "./TodoListDisplay"
|
||||
import { LucideIconButton } from "./LucideIconButton"
|
||||
import type { SubtaskDetail } from "./SubtaskCostList"
|
||||
import type { SubtaskDetail } from "@src/types/subtasks"
|
||||
|
||||
export interface TaskHeaderProps {
|
||||
task: ClineMessage
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useState, useRef, useMemo, useEffect } from "react"
|
|||
|
||||
import { formatLargeNumber } from "@src/utils/format"
|
||||
|
||||
import type { SubtaskDetail } from "./SubtaskCostList"
|
||||
import type { SubtaskDetail } from "@src/types/subtasks"
|
||||
|
||||
type TodoStatus = "completed" | "in_progress" | "pending"
|
||||
|
||||
|
|
|
|||
|
|
@ -179,6 +179,28 @@ const UpdateTodoListToolBlock: React.FC<UpdateTodoListToolBlockProps> = ({
|
|||
Todo List Updated
|
||||
</span>
|
||||
<div className="flex-grow" />
|
||||
{editable && (
|
||||
<button
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
style={{
|
||||
border: isEditing
|
||||
? "1px solid var(--vscode-button-border)"
|
||||
: "1px solid var(--vscode-button-secondaryBorder)",
|
||||
background: isEditing
|
||||
? "var(--vscode-button-background)"
|
||||
: "var(--vscode-button-secondaryBackground)",
|
||||
color: isEditing
|
||||
? "var(--vscode-button-foreground)"
|
||||
: "var(--vscode-button-secondaryForeground)",
|
||||
borderRadius: 4,
|
||||
padding: "2px 8px",
|
||||
cursor: "pointer",
|
||||
fontSize: 13,
|
||||
marginLeft: 8,
|
||||
}}>
|
||||
{isEditing ? "Done" : "Edit"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</ToolUseBlockHeader>
|
||||
<div className="overflow-x-auto max-w-full" style={{ padding: "6px 0 2px 0" }}>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest"
|
|||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
|
||||
import { TodoListDisplay } from "../TodoListDisplay"
|
||||
import type { SubtaskDetail } from "../SubtaskCostList"
|
||||
import type { SubtaskDetail } from "@src/types/subtasks"
|
||||
|
||||
// Mock i18next
|
||||
vi.mock("i18next", () => ({
|
||||
|
|
|
|||
13
webview-ui/src/types/subtasks.ts
Normal file
13
webview-ui/src/types/subtasks.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export type SubtaskDetail = {
|
||||
/** Task ID */
|
||||
id: string
|
||||
/** First 50 chars of task description */
|
||||
name: string
|
||||
/** tokensIn + tokensOut */
|
||||
tokens: number
|
||||
/** Aggregated total cost */
|
||||
cost: number
|
||||
status: "active" | "completed" | "delegated"
|
||||
/** Has its own subtasks */
|
||||
hasNestedChildren: boolean
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue