mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
ux: Improvements to to-do lists and task headers (#9096)
Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
parent
fc2147ab4f
commit
fff5cf804f
28 changed files with 537 additions and 448 deletions
|
|
@ -18,6 +18,7 @@ import { formatPathTooltip } from "@src/utils/formatPathTooltip"
|
|||
|
||||
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
|
||||
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
|
||||
import { TodoChangeDisplay } from "./TodoChangeDisplay"
|
||||
import CodeAccordian from "../common/CodeAccordian"
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import { ReasoningBlock } from "./ReasoningBlock"
|
||||
|
|
@ -62,6 +63,39 @@ import {
|
|||
import { cn } from "@/lib/utils"
|
||||
import { PathTooltip } from "../ui/PathTooltip"
|
||||
|
||||
// Helper function to get previous todos before a specific message
|
||||
function getPreviousTodos(messages: ClineMessage[], currentMessageTs: number): any[] {
|
||||
// Find the previous updateTodoList message before the current one
|
||||
const previousUpdateIndex = messages
|
||||
.slice()
|
||||
.reverse()
|
||||
.findIndex((msg) => {
|
||||
if (msg.ts >= currentMessageTs) return false
|
||||
if (msg.type === "ask" && msg.ask === "tool") {
|
||||
try {
|
||||
const tool = JSON.parse(msg.text || "{}")
|
||||
return tool.tool === "updateTodoList"
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (previousUpdateIndex !== -1) {
|
||||
const previousMessage = messages.slice().reverse()[previousUpdateIndex]
|
||||
try {
|
||||
const tool = JSON.parse(previousMessage.text || "{}")
|
||||
return tool.todos || []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// If no previous updateTodoList message, return empty array
|
||||
return []
|
||||
}
|
||||
|
||||
interface ChatRowProps {
|
||||
message: ClineMessage
|
||||
lastModifiedMessage?: ClineMessage
|
||||
|
|
@ -127,11 +161,10 @@ export const ChatRowContent = ({
|
|||
onFollowUpUnmount,
|
||||
onBatchFileResponse,
|
||||
isFollowUpAnswered,
|
||||
editable,
|
||||
}: ChatRowContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState()
|
||||
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration, clineMessages } = useExtensionState()
|
||||
const { info: model } = useSelectedModel(apiConfiguration)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editedContent, setEditedContent] = useState("")
|
||||
|
|
@ -503,18 +536,11 @@ export const ChatRowContent = ({
|
|||
}
|
||||
case "updateTodoList" as any: {
|
||||
const todos = (tool as any).todos || []
|
||||
return (
|
||||
<UpdateTodoListToolBlock
|
||||
todos={todos}
|
||||
content={tool.content}
|
||||
onChange={(updatedTodos) => {
|
||||
if (typeof vscode !== "undefined" && vscode?.postMessage) {
|
||||
vscode.postMessage({ type: "updateTodoList", payload: { todos: updatedTodos } })
|
||||
}
|
||||
}}
|
||||
editable={editable && isLast}
|
||||
/>
|
||||
)
|
||||
|
||||
// Get previous todos from the latest todos in the task context
|
||||
const previousTodos = getPreviousTodos(clineMessages, message.ts)
|
||||
|
||||
return <TodoChangeDisplay previousTodos={previousTodos} newTodos={todos} />
|
||||
}
|
||||
case "newFileCreated":
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { useState, useEffect, useCallback } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { CloudUpload, Copy, Check } from "lucide-react"
|
||||
import { Copy, Check, CloudUploadIcon } from "lucide-react"
|
||||
import QRCode from "qrcode"
|
||||
|
||||
import type { HistoryItem } from "@roo-code/types"
|
||||
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useCopyToClipboard } from "@/utils/clipboard"
|
||||
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, StandardTooltip } from "@/components/ui"
|
||||
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input } from "@/components/ui"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { LucideIconButton } from "./LucideIconButton"
|
||||
|
||||
interface CloudTaskButtonProps {
|
||||
item?: HistoryItem
|
||||
|
|
@ -83,18 +84,12 @@ export const CloudTaskButton = ({ item, disabled = false }: CloudTaskButtonProps
|
|||
|
||||
return (
|
||||
<>
|
||||
<StandardTooltip content={t("chat:task.openInCloud")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={disabled}
|
||||
className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
data-testid="cloud-task-button"
|
||||
aria-label={t("chat:task.openInCloud")}>
|
||||
<CloudUpload className="h-4 w-4" />
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
<LucideIconButton
|
||||
icon={CloudUploadIcon}
|
||||
disabled={disabled}
|
||||
data-testid="cloud-task-button"
|
||||
title={t("chat:task.openInCloud")}
|
||||
onClick={() => setDialogOpen(true)}></LucideIconButton>
|
||||
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent className="max-w-100">
|
||||
|
|
|
|||
50
webview-ui/src/components/chat/LucideIconButton.tsx
Normal file
50
webview-ui/src/components/chat/LucideIconButton.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { cn } from "@src/lib/utils"
|
||||
import { Button, StandardTooltip } from "@src/components/ui"
|
||||
import { Loader2, LucideIcon } from "lucide-react"
|
||||
|
||||
interface LucideIconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
icon: LucideIcon
|
||||
title: string
|
||||
disabled?: boolean
|
||||
tooltip?: boolean
|
||||
isLoading?: boolean
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export const LucideIconButton: React.FC<LucideIconButtonProps> = ({
|
||||
icon,
|
||||
title,
|
||||
className,
|
||||
disabled,
|
||||
tooltip = true,
|
||||
isLoading,
|
||||
onClick,
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
const Icon = icon
|
||||
return (
|
||||
<StandardTooltip content={tooltip ? title : undefined}>
|
||||
<Button
|
||||
aria-label={title}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-full",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"transition-all duration-150",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
!disabled && "cursor-pointer hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
|
||||
disabled && "cursor-not-allowed opacity-40 hover:bg-transparent active:bg-transparent",
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
onClick={!disabled ? onClick : undefined}
|
||||
style={{ fontSize: 16.5, ...style }}
|
||||
{...props}>
|
||||
{isLoading ? <Loader2 className="size-2.5 animate-spin" /> : <Icon className="size-2.5" />}
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
)
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ export const Mention = ({ text, withShadow = false }: MentionProps) => {
|
|||
return (
|
||||
<span
|
||||
key={index}
|
||||
className={`${withShadow ? "mention-context-highlight-with-shadow" : "mention-context-highlight"} cursor-pointer`}
|
||||
className={`${withShadow ? "mention-context-highlight-with-shadow" : "mention-context-highlight"} text-[0.9em] cursor-pointer`}
|
||||
onClick={() => vscode.postMessage({ type: "openMention", text: part })}>
|
||||
@{part}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Share2 } from "lucide-react"
|
||||
import { Share2Icon } from "lucide-react"
|
||||
|
||||
import { type HistoryItem, type ShareVisibility, TelemetryEventName } from "@roo-code/types"
|
||||
|
||||
|
|
@ -10,7 +10,6 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
|
|||
import { useCloudUpsell } from "@/hooks/useCloudUpsell"
|
||||
import { CloudUpsellDialog } from "@/components/cloud/CloudUpsellDialog"
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
|
|
@ -20,14 +19,14 @@ import {
|
|||
CommandGroup,
|
||||
StandardTooltip,
|
||||
} from "@/components/ui"
|
||||
import { LucideIconButton } from "./LucideIconButton"
|
||||
|
||||
interface ShareButtonProps {
|
||||
item?: HistoryItem
|
||||
disabled?: boolean
|
||||
showLabel?: boolean
|
||||
}
|
||||
|
||||
export const ShareButton = ({ item, disabled = false, showLabel = false }: ShareButtonProps) => {
|
||||
export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
|
||||
const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
|
||||
const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null)
|
||||
const [wasConnectInitiatedFromShare, setWasConnectInitiatedFromShare] = useState(false)
|
||||
|
|
@ -153,20 +152,13 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share
|
|||
<Popover open={shareDropdownOpen} onOpenChange={setShareDropdownOpen}>
|
||||
<StandardTooltip content={shareButtonState.title}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size={showLabel ? "sm" : "icon"}
|
||||
<LucideIconButton
|
||||
icon={Share2Icon}
|
||||
disabled={disabled || shareButtonState.disabled}
|
||||
className={
|
||||
showLabel
|
||||
? "h-7 px-2 hover:bg-vscode-toolbar-hoverBackground"
|
||||
: "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
|
||||
}
|
||||
tooltip={false}
|
||||
onClick={handleShareButtonClick}
|
||||
data-testid="share-button">
|
||||
<Share2 />
|
||||
{showLabel && <span className="ml-0">{t("chat:task.share")}</span>}
|
||||
</Button>
|
||||
data-testid="share-button"
|
||||
title={t("chat:task.share")}></LucideIconButton>
|
||||
</PopoverTrigger>
|
||||
</StandardTooltip>
|
||||
|
||||
|
|
@ -221,22 +213,12 @@ export const ShareButton = ({ item, disabled = false, showLabel = false }: Share
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
<StandardTooltip content={shareButtonState.title}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size={showLabel ? "sm" : "icon"}
|
||||
disabled={disabled || shareButtonState.disabled}
|
||||
className={
|
||||
showLabel
|
||||
? "h-7 px-2 hover:bg-vscode-toolbar-hoverBackground"
|
||||
: "h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
|
||||
}
|
||||
onClick={handleShareButtonClick}
|
||||
data-testid="share-button">
|
||||
<Share2 />
|
||||
{showLabel && <span className="ml-1">{t("chat:task.share")}</span>}
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
<LucideIconButton
|
||||
icon={Share2Icon}
|
||||
disabled={disabled || shareButtonState.disabled}
|
||||
title={shareButtonState.title}
|
||||
onClick={handleShareButtonClick}
|
||||
data-testid="share-button"></LucideIconButton>
|
||||
)}
|
||||
|
||||
{/* Connect to Cloud Modal */}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import { vscode } from "@/utils/vscode"
|
|||
import { useCopyToClipboard } from "@/utils/clipboard"
|
||||
|
||||
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
|
||||
import { IconButton } from "./IconButton"
|
||||
import { ShareButton } from "./ShareButton"
|
||||
import { CloudTaskButton } from "./CloudTaskButton"
|
||||
import { CopyIcon, DownloadIcon, Trash2Icon } from "lucide-react"
|
||||
import { LucideIconButton } from "./LucideIconButton"
|
||||
|
||||
interface TaskActionsProps {
|
||||
item?: HistoryItem
|
||||
|
|
@ -19,40 +20,38 @@ interface TaskActionsProps {
|
|||
export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
|
||||
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
|
||||
const { t } = useTranslation()
|
||||
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
|
||||
const { copyWithFeedback } = useCopyToClipboard()
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center">
|
||||
<IconButton
|
||||
iconClass="codicon-desktop-download"
|
||||
<div className="flex flex-row items-center -ml-0.5 mt-1 gap-1">
|
||||
<LucideIconButton
|
||||
icon={DownloadIcon}
|
||||
title={t("chat:task.export")}
|
||||
onClick={() => vscode.postMessage({ type: "exportCurrentTask" })}
|
||||
/>
|
||||
|
||||
{item?.task && (
|
||||
<IconButton
|
||||
iconClass={showCopyFeedback ? "codicon-check" : "codicon-copy"}
|
||||
<LucideIconButton
|
||||
icon={CopyIcon}
|
||||
title={t("history:copyPrompt")}
|
||||
onClick={(e) => copyWithFeedback(item.task, e)}
|
||||
/>
|
||||
)}
|
||||
{!!item?.size && item.size > 0 && (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<IconButton
|
||||
iconClass="codicon-trash"
|
||||
title={t("chat:task.delete")}
|
||||
disabled={buttonsDisabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
if (e.shiftKey) {
|
||||
vscode.postMessage({ type: "deleteTaskWithId", text: item.id })
|
||||
} else {
|
||||
setDeleteTaskId(item.id)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<LucideIconButton
|
||||
icon={Trash2Icon}
|
||||
title={t("chat:task.delete")}
|
||||
disabled={buttonsDisabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (e.shiftKey) {
|
||||
vscode.postMessage({ type: "deleteTaskWithId", text: item.id })
|
||||
} else {
|
||||
setDeleteTaskId(item.id)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{deleteTaskId && (
|
||||
<DeleteTaskDialog
|
||||
taskId={deleteTaskId}
|
||||
|
|
@ -62,7 +61,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
<ShareButton item={item} disabled={false} showLabel={false} />
|
||||
<ShareButton item={item} disabled={false} />
|
||||
<CloudTaskButton item={item} disabled={buttonsDisabled} />
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,15 @@ import { useTranslation } from "react-i18next"
|
|||
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
|
||||
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
|
||||
import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
|
||||
import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react"
|
||||
import {
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
SquarePen,
|
||||
Coins,
|
||||
HardDriveDownload,
|
||||
HardDriveUpload,
|
||||
FoldVerticalIcon,
|
||||
} from "lucide-react"
|
||||
import prettyBytes from "pretty-bytes"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
|
@ -23,6 +31,7 @@ import { TaskActions } from "./TaskActions"
|
|||
import { ContextWindowProgress } from "./ContextWindowProgress"
|
||||
import { Mention } from "./Mention"
|
||||
import { TodoListDisplay } from "./TodoListDisplay"
|
||||
import { LucideIconButton } from "./LucideIconButton"
|
||||
|
||||
export interface TaskHeaderProps {
|
||||
task: ClineMessage
|
||||
|
|
@ -87,20 +96,18 @@ const TaskHeader = ({
|
|||
const contextWindow = model?.contextWindow || 1
|
||||
|
||||
const condenseButton = (
|
||||
<StandardTooltip content={t("chat:task.condenseContext")}>
|
||||
<button
|
||||
disabled={buttonsDisabled}
|
||||
onClick={() => currentTaskItem && handleCondenseContext(currentTaskItem.id)}
|
||||
className="shrink-0 min-h-[20px] min-w-[20px] p-[2px] cursor-pointer disabled:cursor-not-allowed opacity-85 hover:opacity-100 bg-transparent border-none rounded-md">
|
||||
<FoldVertical size={16} />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
<LucideIconButton
|
||||
title={t("chat:task.condenseContext")}
|
||||
icon={FoldVerticalIcon}
|
||||
disabled={buttonsDisabled}
|
||||
onClick={() => currentTaskItem && handleCondenseContext(currentTaskItem.id)}
|
||||
/>
|
||||
)
|
||||
|
||||
const hasTodos = todos && Array.isArray(todos) && todos.length > 0
|
||||
|
||||
return (
|
||||
<div className="pt-2 pb-0 px-3">
|
||||
<div className="group pt-2 pb-0 px-3">
|
||||
{showLongRunningTaskMessage && !isTaskComplete && (
|
||||
<DismissibleUpsell
|
||||
upsellId="longRunningTask"
|
||||
|
|
@ -115,10 +122,15 @@ const TaskHeader = ({
|
|||
"px-3 pt-2.5 pb-2 flex flex-col gap-1.5 relative z-1 cursor-pointer",
|
||||
"bg-vscode-input-background hover:bg-vscode-input-background/90",
|
||||
"text-vscode-foreground/80 hover:text-vscode-foreground",
|
||||
"shadow-sm shadow-black/30 rounded-xl",
|
||||
"shadow-lg shadow-vscode-sideBar-background/50 rounded-xl",
|
||||
hasTodos && "border-b-0",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
// Don't expand if clicking on todos section
|
||||
if (e.target instanceof Element && e.target.closest("[data-todo-list]")) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't expand if clicking on buttons or interactive elements
|
||||
if (
|
||||
e.target instanceof Element &&
|
||||
|
|
@ -142,12 +154,14 @@ const TaskHeader = ({
|
|||
}}>
|
||||
<div className="flex justify-between items-center gap-0">
|
||||
<div className="flex items-center select-none grow min-w-0">
|
||||
<div className="whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
|
||||
<div className="grow min-w-0">
|
||||
{isTaskExpanded && <span className="font-bold">{t("chat:task.title")}</span>}
|
||||
{!isTaskExpanded && (
|
||||
<div>
|
||||
<span className="font-bold mr-1">{t("chat:task.title")}</span>
|
||||
<Mention text={task.text} />
|
||||
<div className="flex items-center gap-2">
|
||||
<SquarePen className="size-3 shrink-0" />
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
<Mention text={task.text} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -156,7 +170,11 @@ const TaskHeader = ({
|
|||
<button
|
||||
onClick={() => setIsTaskExpanded(!isTaskExpanded)}
|
||||
className="shrink-0 min-h-[20px] min-w-[20px] p-[2px] cursor-pointer opacity-85 hover:opacity-100 bg-transparent border-none rounded-md">
|
||||
{isTaskExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
{isTaskExpanded ? (
|
||||
<ChevronUp size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} className="opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
|
|
@ -166,6 +184,7 @@ const TaskHeader = ({
|
|||
<div
|
||||
className="flex items-center gap-2 text-sm text-muted-foreground/70"
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<Coins className="size-3 shrink-0" />
|
||||
<StandardTooltip
|
||||
content={
|
||||
<div className="space-y-1">
|
||||
|
|
@ -220,7 +239,7 @@ const TaskHeader = ({
|
|||
className="text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
|
||||
<div
|
||||
ref={textRef}
|
||||
className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere cursor-text"
|
||||
className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere cursor-text py-0.5"
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: "unset",
|
||||
|
|
@ -231,18 +250,22 @@ const TaskHeader = ({
|
|||
</div>
|
||||
{task.images && task.images.length > 0 && <Thumbnails images={task.images} />}
|
||||
|
||||
<div className="border-t border-b border-vscode-panel-border/50 py-4 mt-2 mb-1">
|
||||
<table className="w-full">
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
|
||||
</div>
|
||||
|
||||
<div className="pt-3 mt-2 -mx-2.5 px-2.5 border-t border-vscode-sideBar-background">
|
||||
<table className="w-full text-sm">
|
||||
<tbody>
|
||||
{contextWindow > 0 && (
|
||||
<tr>
|
||||
<th
|
||||
className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]"
|
||||
className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]"
|
||||
data-testid="context-window-label">
|
||||
{t("chat:task.contextWindow")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
<div className={`max-w-80 -mt-0.5 flex flex-nowrap gap-1`}>
|
||||
<td className="font-light align-top">
|
||||
<div className={`max-w-md -mt-1.5 flex flex-nowrap gap-1`}>
|
||||
<ContextWindowProgress
|
||||
contextWindow={contextWindow}
|
||||
contextTokens={contextTokens || 0}
|
||||
|
|
@ -263,10 +286,10 @@ const TaskHeader = ({
|
|||
)}
|
||||
|
||||
<tr>
|
||||
<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
|
||||
<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]">
|
||||
{t("chat:task.tokens")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
<td className="font-light align-top">
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
{typeof tokensIn === "number" && tokensIn > 0 && (
|
||||
<span>↑ {formatLargeNumber(tokensIn)}</span>
|
||||
|
|
@ -281,16 +304,22 @@ const TaskHeader = ({
|
|||
{((typeof cacheReads === "number" && cacheReads > 0) ||
|
||||
(typeof cacheWrites === "number" && cacheWrites > 0)) && (
|
||||
<tr>
|
||||
<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
|
||||
<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]">
|
||||
{t("chat:task.cache")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
<td className="font-light align-top">
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
{typeof cacheWrites === "number" && cacheWrites > 0 && (
|
||||
<span>↑ {formatLargeNumber(cacheWrites)}</span>
|
||||
<>
|
||||
<HardDriveDownload className="size-2.5" />
|
||||
<span>{formatLargeNumber(cacheWrites)}</span>
|
||||
</>
|
||||
)}
|
||||
{typeof cacheReads === "number" && cacheReads > 0 && (
|
||||
<span>↓ {formatLargeNumber(cacheReads)}</span>
|
||||
<>
|
||||
<HardDriveUpload className="size-2.5" />
|
||||
<span>{formatLargeNumber(cacheReads)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -299,10 +328,10 @@ const TaskHeader = ({
|
|||
|
||||
{!!totalCost && (
|
||||
<tr>
|
||||
<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
|
||||
<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-3 h-[24px]">
|
||||
{t("chat:task.apiCost")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
<td className="font-light align-top">
|
||||
<span>${totalCost?.toFixed(2)}</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -311,24 +340,22 @@ const TaskHeader = ({
|
|||
{/* Size display */}
|
||||
{!!currentTaskItem?.size && currentTaskItem.size > 0 && (
|
||||
<tr>
|
||||
<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-2 h-[20px]">
|
||||
<th className="font-medium text-left align-top w-1 whitespace-nowrap pr-2 h-[20px]">
|
||||
{t("chat:task.size")}
|
||||
</th>
|
||||
<td className="align-top">{prettyBytes(currentTaskItem.size)}</td>
|
||||
<td className="font-light align-top">
|
||||
{prettyBytes(currentTaskItem.size)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Footer with task management buttons */}
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* Todo list - always shown at bottom when todos exist */}
|
||||
{hasTodos && <TodoListDisplay todos={todos ?? (task as any)?.tool?.todos ?? []} />}
|
||||
</div>
|
||||
<TodoListDisplay todos={todos ?? (task as any)?.tool?.todos ?? []} />
|
||||
<CloudUpsellDialog open={isOpen} onOpenChange={closeUpsell} onConnect={handleConnect} />
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
131
webview-ui/src/components/chat/TodoChangeDisplay.tsx
Normal file
131
webview-ui/src/components/chat/TodoChangeDisplay.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { t } from "i18next"
|
||||
import { ArrowRight, Check, ListChecks, SquareDashed } from "lucide-react"
|
||||
|
||||
type TodoStatus = "completed" | "in_progress" | "pending"
|
||||
|
||||
interface TodoItem {
|
||||
id?: string
|
||||
content: string
|
||||
status?: TodoStatus | string
|
||||
}
|
||||
|
||||
interface TodoChangeDisplayProps {
|
||||
previousTodos: TodoItem[]
|
||||
newTodos: TodoItem[]
|
||||
}
|
||||
|
||||
interface TodoGroup {
|
||||
todos: TodoItem[]
|
||||
status: TodoStatus | null
|
||||
keyPrefix: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
function getTodoIcon(status: TodoStatus | null) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return <Check className="size-3 mt-1 shrink-0" />
|
||||
case "in_progress":
|
||||
return <ArrowRight className="size-3 mt-1 shrink-0" />
|
||||
default:
|
||||
return <SquareDashed className="size-3 mt-1 shrink-0" />
|
||||
}
|
||||
}
|
||||
|
||||
function TodoList({ todos, status, keyPrefix, className }: TodoGroup) {
|
||||
if (todos.length === 0) return null
|
||||
|
||||
return (
|
||||
<ul className="list-none space-y-1 my-1">
|
||||
{todos.map((todo) => {
|
||||
const icon = getTodoIcon(status)
|
||||
return (
|
||||
<li
|
||||
key={`${keyPrefix}-${todo.id || todo.content}`}
|
||||
className={`flex flex-row gap-2 items-start ${className || ""}`}>
|
||||
{icon}
|
||||
<span>{todo.content}</span>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export function TodoChangeDisplay({ previousTodos, newTodos }: TodoChangeDisplayProps) {
|
||||
const isInitialState = previousTodos.length === 0
|
||||
|
||||
// Determine which todos to display
|
||||
let todoGroups: TodoGroup[]
|
||||
|
||||
if (isInitialState && newTodos.length > 0) {
|
||||
// For initial state, show all todos grouped by status
|
||||
todoGroups = [
|
||||
{
|
||||
todos: newTodos.filter((todo) => !todo.status || todo.status === "pending"),
|
||||
status: null,
|
||||
keyPrefix: "pending",
|
||||
},
|
||||
{
|
||||
todos: newTodos.filter((todo) => todo.status === "in_progress"),
|
||||
status: "in_progress",
|
||||
keyPrefix: "in-progress",
|
||||
className: "text-vscode-charts-yellow",
|
||||
},
|
||||
{
|
||||
todos: newTodos.filter((todo) => todo.status === "completed"),
|
||||
status: "completed",
|
||||
keyPrefix: "completed",
|
||||
},
|
||||
]
|
||||
} else {
|
||||
// For updates, only show changes
|
||||
const completedTodos = newTodos.filter((newTodo) => {
|
||||
if (newTodo.status !== "completed") return false
|
||||
const previousTodo = previousTodos.find((p) => p.id === newTodo.id || p.content === newTodo.content)
|
||||
return !previousTodo || previousTodo.status !== "completed"
|
||||
})
|
||||
|
||||
const startedTodos = newTodos.filter((newTodo) => {
|
||||
if (newTodo.status !== "in_progress") return false
|
||||
const previousTodo = previousTodos.find((p) => p.id === newTodo.id || p.content === newTodo.content)
|
||||
return !previousTodo || previousTodo.status !== "in_progress"
|
||||
})
|
||||
|
||||
todoGroups = [
|
||||
{
|
||||
todos: completedTodos,
|
||||
status: "completed",
|
||||
keyPrefix: "completed",
|
||||
},
|
||||
{
|
||||
todos: startedTodos,
|
||||
status: "in_progress",
|
||||
keyPrefix: "started",
|
||||
className: "text-vscode-charts-yellow",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// If no todos to display, don't render anything
|
||||
if (todoGroups.every((group) => group.todos.length === 0)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-todo-changes className="overflow-hidden">
|
||||
<div className="flex items-center gap-2">
|
||||
<ListChecks className="size-4 shrink-0" />
|
||||
<span className="flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-base font-semibold">
|
||||
{t("chat:todo.updated")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="pl-1 pr-1 pt-1 font-light leading-normal">
|
||||
{todoGroups.map((group, index) => (
|
||||
<TodoList key={index} {...group} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,21 @@
|
|||
import { cn } from "@/lib/utils"
|
||||
import { t } from "i18next"
|
||||
import { ArrowRight, Check, ListChecks, SquareDashed } from "lucide-react"
|
||||
import { useState, useRef, useMemo, useEffect } from "react"
|
||||
|
||||
type TodoStatus = "completed" | "in_progress" | "pending"
|
||||
|
||||
function getTodoIcon(status: TodoStatus | null) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return <Check className={`size-3 mt-1 shrink-0`} />
|
||||
case "in_progress":
|
||||
return <ArrowRight className="size-3 mt-1 shrink-0" />
|
||||
default:
|
||||
return <SquareDashed className="size-3 mt-1 shrink-0" />
|
||||
}
|
||||
}
|
||||
|
||||
export function TodoListDisplay({ todos }: { todos: any[] }) {
|
||||
const [isCollapsed, setIsCollapsed] = useState(true)
|
||||
const ulRef = useRef<HTMLUListElement>(null)
|
||||
|
|
@ -37,317 +53,50 @@ export function TodoListDisplay({ todos }: { todos: any[] }) {
|
|||
|
||||
const allCompleted = completedCount === totalCount && totalCount > 0
|
||||
|
||||
// Create the status icon for the most important todo
|
||||
const getMostImportantTodoIcon = () => {
|
||||
if (allCompleted) {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "var(--vscode-charts-green)",
|
||||
marginRight: 8,
|
||||
marginLeft: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (!mostImportantTodo) {
|
||||
return (
|
||||
<span
|
||||
className="codicon codicon-checklist"
|
||||
style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
marginRight: 8,
|
||||
marginLeft: 2,
|
||||
flexShrink: 0,
|
||||
fontSize: 14,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (mostImportantTodo.status === "completed") {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "var(--vscode-charts-green)",
|
||||
marginRight: 8,
|
||||
marginLeft: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (mostImportantTodo.status === "in_progress") {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "var(--vscode-charts-yellow)",
|
||||
marginRight: 8,
|
||||
marginLeft: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Default not-started todo
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
border: "1px solid var(--vscode-descriptionForeground)",
|
||||
background: "transparent",
|
||||
marginRight: 8,
|
||||
marginLeft: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="border border-t-0 rounded-b-xs relative"
|
||||
style={{
|
||||
margin: "0",
|
||||
padding: "6px 10px",
|
||||
background: "var(--vscode-editor-background,transparent)",
|
||||
borderColor: "var(--vscode-panel-border)",
|
||||
}}>
|
||||
<div data-todo-list className="mt-1 -mx-2.5 border-t border-vscode-sideBar-background overflow-hidden">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 2,
|
||||
marginBottom: 0,
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-2 pt-2 px-2.5 cursor-pointer select-none",
|
||||
mostImportantTodo?.status === "in_progress" && isCollapsed
|
||||
? "text-vscode-charts-yellow"
|
||||
: "text-vscode-foreground",
|
||||
)}
|
||||
onClick={() => setIsCollapsed((v) => !v)}>
|
||||
{getMostImportantTodoIcon()}
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: allCompleted
|
||||
? "var(--vscode-charts-green)"
|
||||
: mostImportantTodo?.status === "in_progress"
|
||||
? "var(--vscode-charts-yellow)"
|
||||
: "var(--vscode-foreground)",
|
||||
flex: 1,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
{allCompleted ? "All tasks completed!" : mostImportantTodo?.content || "No pending tasks"}
|
||||
<ListChecks className="size-3 shrink-0" />
|
||||
<span className="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{isCollapsed
|
||||
? allCompleted
|
||||
? t("chat:todo.complete", { total: completedCount })
|
||||
: mostImportantTodo?.content // show current todo while not done
|
||||
: t("chat:todo.partial", { completed: completedCount, total: totalCount })}
|
||||
</span>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
|
||||
<span
|
||||
className="codicon codicon-checklist"
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
{isCollapsed && completedCount < totalCount && (
|
||||
<div className="shrink-0 text-vscode-descriptionForeground text-xs">
|
||||
{completedCount}/{totalCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Floating panel for expanded state */}
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: "rgba(0, 0, 0, 0.1)",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
onClick={() => setIsCollapsed(true)}
|
||||
/>
|
||||
{/* Floating panel */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "100%",
|
||||
left: 0,
|
||||
right: 0,
|
||||
marginTop: 4,
|
||||
background: "var(--vscode-editor-background)",
|
||||
border: "1px solid var(--vscode-panel-border)",
|
||||
borderRadius: 6,
|
||||
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
||||
zIndex: 1001,
|
||||
maxHeight: "400px",
|
||||
minHeight: "200px",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
{/* Panel header */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "12px 16px",
|
||||
borderBottom: "1px solid var(--vscode-panel-border)",
|
||||
background: "var(--vscode-editor-background)",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<span
|
||||
className="codicon codicon-checklist"
|
||||
style={{ color: "var(--vscode-foreground)" }}
|
||||
/>
|
||||
<span style={{ fontWeight: "bold", fontSize: 14 }}>Todo List</span>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
{completedCount}/{totalCount}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
className="codicon codicon-chevron-up"
|
||||
style={{
|
||||
fontSize: 14,
|
||||
opacity: 0.7,
|
||||
cursor: "pointer",
|
||||
padding: "4px",
|
||||
borderRadius: "2px",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsCollapsed(true)
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.opacity = "1"
|
||||
e.currentTarget.style.background = "var(--vscode-toolbar-hoverBackground)"
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.opacity = "0.7"
|
||||
e.currentTarget.style.background = "transparent"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Todo list */}
|
||||
<ul
|
||||
ref={ulRef}
|
||||
style={{
|
||||
margin: 0,
|
||||
paddingLeft: 0,
|
||||
listStyle: "none",
|
||||
maxHeight: "340px",
|
||||
overflowY: "auto",
|
||||
padding: "12px 16px",
|
||||
}}>
|
||||
{todos.map((todo: any, idx: number) => {
|
||||
let icon
|
||||
if (todo.status === "completed") {
|
||||
icon = (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "var(--vscode-charts-green)",
|
||||
marginRight: 8,
|
||||
marginTop: 7,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
} else if (todo.status === "in_progress") {
|
||||
icon = (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "var(--vscode-charts-yellow)",
|
||||
marginRight: 8,
|
||||
marginTop: 7,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
icon = (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
border: "1px solid var(--vscode-descriptionForeground)",
|
||||
background: "transparent",
|
||||
marginRight: 8,
|
||||
marginTop: 7,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<li
|
||||
key={todo.id || todo.content}
|
||||
ref={(el) => (itemRefs.current[idx] = el)}
|
||||
style={{
|
||||
marginBottom: 8,
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
minHeight: 20,
|
||||
lineHeight: "1.4",
|
||||
}}>
|
||||
{icon}
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color:
|
||||
todo.status === "completed"
|
||||
? "var(--vscode-charts-green)"
|
||||
: todo.status === "in_progress"
|
||||
? "var(--vscode-charts-yellow)"
|
||||
: "var(--vscode-foreground)",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
{todo.content}
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* Inline expanded list */}
|
||||
{!isCollapsed && (
|
||||
<ul ref={ulRef} className="list-none max-h-[300px] overflow-y-auto mt-2 -mb-1 pb-0 px-2 cursor-default">
|
||||
{todos.map((todo: any, idx: number) => {
|
||||
const icon = getTodoIcon(todo.status as TodoStatus)
|
||||
return (
|
||||
<li
|
||||
key={todo.id || todo.content}
|
||||
ref={(el) => (itemRefs.current[idx] = el)}
|
||||
className={cn(
|
||||
"font-light flex flex-row gap-2 items-start min-h-[20px] leading-normal mb-2",
|
||||
todo.status === "in_progress" && "text-vscode-charts-yellow",
|
||||
todo.status !== "in_progress" && todo.status !== "completed" && "opacity-60",
|
||||
)}>
|
||||
{icon}
|
||||
<span>{todo.content}</span>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ca/chat.json
generated
9
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"terminal": "Terminal",
|
||||
"url": "Enganxa la URL per obtenir-ne el contingut"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} de {{total}} tasques pendents fetes",
|
||||
"complete": "{{total}} tasques pendents fetes",
|
||||
"updated": "S'ha actualitzat la llista de tasques pendents",
|
||||
"completed": "Completat",
|
||||
"started": "Iniciat"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/de/chat.json
generated
9
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo möchte einen Slash-Befehl ausführen",
|
||||
"didRun": "Roo hat einen Slash-Befehl ausgeführt"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} von {{total}} To-Dos erledigt",
|
||||
"complete": "{{total}} To-Dos erledigt",
|
||||
"updated": "Die To-Do-Liste wurde aktualisiert",
|
||||
"completed": "Abgeschlossen",
|
||||
"started": "Gestartet"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -414,5 +414,12 @@
|
|||
"problems": "Problems",
|
||||
"terminal": "Terminal",
|
||||
"url": "Paste URL to fetch contents"
|
||||
},
|
||||
"todo": {
|
||||
"partial": "{{completed}} of {{total}} to-dos done",
|
||||
"complete": "{{total}} to-dos done",
|
||||
"updated": "Updated the to-do list",
|
||||
"completed": "Completed",
|
||||
"started": "Started"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/es/chat.json
generated
9
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo quiere ejecutar un comando slash",
|
||||
"didRun": "Roo ejecutó un comando slash"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} de {{total}} tareas pendientes realizadas",
|
||||
"complete": "{{total}} tareas pendientes realizadas",
|
||||
"updated": "Se actualizó la lista de tareas pendientes",
|
||||
"completed": "Completado",
|
||||
"started": "Iniciado"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/fr/chat.json
generated
9
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo veut exécuter une commande slash",
|
||||
"didRun": "Roo a exécuté une commande slash"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} sur {{total}} tâches terminées",
|
||||
"complete": "{{total}} tâches terminées",
|
||||
"updated": "La liste des tâches a été mise à jour",
|
||||
"completed": "Terminé",
|
||||
"started": "Commencé"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/hi/chat.json
generated
9
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo एक स्लैश कमांड चलाना चाहता है",
|
||||
"didRun": "Roo ने एक स्लैश कमांड चलाया"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{total}} में से {{completed}} टू-डू हो गए",
|
||||
"complete": "{{total}} टू-डू हो गए",
|
||||
"updated": "टू-डू सूची अपडेट की गई",
|
||||
"completed": "पूरा हुआ",
|
||||
"started": "शुरू हुआ"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/id/chat.json
generated
9
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -429,5 +429,12 @@
|
|||
"wantsToRun": "Roo ingin menjalankan perintah slash",
|
||||
"didRun": "Roo telah menjalankan perintah slash"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} dari {{total}} to-do selesai",
|
||||
"complete": "{{total}} to-do selesai",
|
||||
"updated": "Memperbarui daftar to-do",
|
||||
"completed": "Selesai",
|
||||
"started": "Dimulai"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/it/chat.json
generated
9
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo vuole eseguire un comando slash",
|
||||
"didRun": "Roo ha eseguito un comando slash"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} di {{total}} cose da fare completate",
|
||||
"complete": "{{total}} cose da fare completate",
|
||||
"updated": "Aggiornata la lista delle cose da fare",
|
||||
"completed": "Completato",
|
||||
"started": "Iniziato"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ja/chat.json
generated
9
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Rooはスラッシュコマンドを実行したい",
|
||||
"didRun": "Rooはスラッシュコマンドを実行しました"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{total}}件中{{completed}}件のTo-Doが完了",
|
||||
"complete": "{{total}}件のTo-Doが完了",
|
||||
"updated": "To-Doリストを更新しました",
|
||||
"completed": "完了",
|
||||
"started": "開始"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ko/chat.json
generated
9
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo가 슬래시 명령어를 실행하려고 합니다",
|
||||
"didRun": "Roo가 슬래시 명령어를 실행했습니다"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{total}}개의 할 일 중 {{completed}}개 완료",
|
||||
"complete": "{{total}}개의 할 일 완료",
|
||||
"updated": "할 일 목록을 업데이트했습니다",
|
||||
"completed": "완료됨",
|
||||
"started": "시작됨"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/nl/chat.json
generated
9
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo wil een slash commando uitvoeren",
|
||||
"didRun": "Roo heeft een slash commando uitgevoerd"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} van {{total}} to-do's voltooid",
|
||||
"complete": "{{total}} to-do's voltooid",
|
||||
"updated": "De to-do-lijst is bijgewerkt",
|
||||
"completed": "Voltooid",
|
||||
"started": "Gestart"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/pl/chat.json
generated
9
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo chce uruchomić komendę slash",
|
||||
"didRun": "Roo uruchomił komendę slash"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "Ukończono {{completed}} z {{total}} zadań do wykonania",
|
||||
"complete": "Ukończono {{total}} zadań do wykonania",
|
||||
"updated": "Zaktualizowano listę zadań do wykonania",
|
||||
"completed": "Ukończono",
|
||||
"started": "Rozpoczęto"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
9
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -423,5 +423,12 @@
|
|||
"wantsToRun": "Roo quer executar um comando slash",
|
||||
"didRun": "Roo executou um comando slash"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} de {{total}} tarefas concluídas",
|
||||
"complete": "{{total}} tarefas concluídas",
|
||||
"updated": "A lista de tarefas foi atualizada",
|
||||
"completed": "Concluído",
|
||||
"started": "Iniciado"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ru/chat.json
generated
9
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -424,5 +424,12 @@
|
|||
"wantsToRun": "Roo хочет выполнить слеш-команду",
|
||||
"didRun": "Roo выполнил слеш-команду"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} из {{total}} задач выполнено",
|
||||
"complete": "{{total}} задач выполнено",
|
||||
"updated": "Список задач обновлен",
|
||||
"completed": "Завершено",
|
||||
"started": "Начато"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/tr/chat.json
generated
9
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -424,5 +424,12 @@
|
|||
"wantsToRun": "Roo bir slash komutu çalıştırmak istiyor",
|
||||
"didRun": "Roo bir slash komutu çalıştırdı"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{total}} yapılacaklar listesinden {{completed}} tanesi tamamlandı",
|
||||
"complete": "{{total}} yapılacaklar listesi tamamlandı",
|
||||
"updated": "Yapılacaklar listesi güncellendi",
|
||||
"completed": "Tamamlandı",
|
||||
"started": "Başladı"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/vi/chat.json
generated
9
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -424,5 +424,12 @@
|
|||
"wantsToRun": "Roo muốn chạy lệnh slash",
|
||||
"didRun": "Roo đã chạy lệnh slash"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "{{completed}} trong tổng số {{total}} công việc đã hoàn thành",
|
||||
"complete": "{{total}} công việc đã hoàn thành",
|
||||
"updated": "Đã cập nhật danh sách công việc",
|
||||
"completed": "Đã hoàn thành",
|
||||
"started": "Đã bắt đầu"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
9
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -424,5 +424,12 @@
|
|||
"wantsToRun": "Roo 想要运行斜杠命令",
|
||||
"didRun": "Roo 运行了斜杠命令"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "已完成 {{completed}} / {{total}} 个待办事项",
|
||||
"complete": "已完成 {{total}} 个待办事项",
|
||||
"updated": "已更新待办事项列表",
|
||||
"completed": "已完成",
|
||||
"started": "已开始"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
9
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -424,5 +424,12 @@
|
|||
"wantsToRun": "Roo 想要執行斜線指令",
|
||||
"didRun": "Roo 執行了斜線指令"
|
||||
},
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more."
|
||||
"docs": "Check our <DocsLink>docs</DocsLink> to learn more.",
|
||||
"todo": {
|
||||
"partial": "已完成 {{completed}} / {{total}} 個待辦事項",
|
||||
"complete": "已完成 {{total}} 個待辦事項",
|
||||
"updated": "已更新待辦事項列表",
|
||||
"completed": "已完成",
|
||||
"started": "已開始"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -388,14 +388,18 @@ vscode-dropdown::part(listbox) {
|
|||
}
|
||||
|
||||
.mention-context-highlight {
|
||||
background-color: color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
|
||||
border-radius: 3px;
|
||||
background-color: color-mix(in srgb, var(--vscode-badge-foreground) 15%, transparent);
|
||||
border-radius: 6px;
|
||||
padding: 1px 4px;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.mention-context-highlight-with-shadow {
|
||||
background-color: color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
|
||||
background-color: color-mix(in srgb, var(--vscode-badge-foreground) 15%, transparent);
|
||||
border-radius: 3px;
|
||||
padding: 1px 4px;
|
||||
box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue