mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Redesigned Task Header (#6561)
* Reorganizes the task header for cleanliness and in preparation for Cloud link * More task ehader visual tweaks * Translations for new task header * Fixes TaskHeader color * Removes stray string * Fixes tests * Iterates on visual details * More visual tweaks * Missing localization call * Fixes tests --------- Co-authored-by: Bruno Bergher <me@brunobergher.comexport>
This commit is contained in:
parent
d90bab71ff
commit
ea79dfeb66
27 changed files with 384 additions and 273 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// npm run test ContextWindowProgress.spec.tsx
|
||||
|
||||
import { render, screen } from "@/utils/test-utils"
|
||||
import { render, screen, fireEvent } from "@/utils/test-utils"
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
|
||||
import TaskHeader from "@src/components/chat/TaskHeader"
|
||||
|
|
@ -70,6 +70,10 @@ describe("ContextWindowProgress", () => {
|
|||
it("renders correctly with valid inputs", () => {
|
||||
renderComponent({ contextTokens: 1000, contextWindow: 4000 })
|
||||
|
||||
// First expand the TaskHeader to access ContextWindowProgress
|
||||
const taskHeader = screen.getByText("Test task")
|
||||
fireEvent.click(taskHeader)
|
||||
|
||||
// Check for basic elements
|
||||
// The context-window-label is not part of the ContextWindowProgress component
|
||||
// but rather part of the parent TaskHeader component in expanded state
|
||||
|
|
@ -83,6 +87,10 @@ describe("ContextWindowProgress", () => {
|
|||
it("handles zero context window gracefully", () => {
|
||||
renderComponent({ contextTokens: 0, contextWindow: 0 })
|
||||
|
||||
// First expand the TaskHeader to access ContextWindowProgress
|
||||
const taskHeader = screen.getByText("Test task")
|
||||
fireEvent.click(taskHeader)
|
||||
|
||||
// In the current implementation, the component is still displayed with zero values
|
||||
// rather than being hidden completely
|
||||
// The context-window-label is not part of the ContextWindowProgress component
|
||||
|
|
@ -93,6 +101,10 @@ describe("ContextWindowProgress", () => {
|
|||
it("handles edge cases with negative values", () => {
|
||||
renderComponent({ contextTokens: -100, contextWindow: 4000 })
|
||||
|
||||
// First expand the TaskHeader to access ContextWindowProgress
|
||||
const taskHeader = screen.getByText("Test task")
|
||||
fireEvent.click(taskHeader)
|
||||
|
||||
// Should show 0 instead of -100
|
||||
expect(screen.getByTestId("context-tokens-count")).toHaveTextContent("0")
|
||||
// The actual context window might be different than what we pass in
|
||||
|
|
@ -102,6 +114,10 @@ describe("ContextWindowProgress", () => {
|
|||
it("calculates percentages correctly", () => {
|
||||
renderComponent({ contextTokens: 1000, contextWindow: 4000 })
|
||||
|
||||
// First expand the TaskHeader to access ContextWindowProgress
|
||||
const taskHeader = screen.getByText("Test task")
|
||||
fireEvent.click(taskHeader)
|
||||
|
||||
// Verify that the token count and window size are displayed correctly
|
||||
const tokenCount = screen.getByTestId("context-tokens-count")
|
||||
const windowSize = screen.getByTestId("context-window-size")
|
||||
|
|
|
|||
|
|
@ -805,8 +805,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
[clineAsk, startNewTask, isStreaming],
|
||||
)
|
||||
|
||||
const handleTaskCloseButtonClick = useCallback(() => startNewTask(), [startNewTask])
|
||||
|
||||
const { info: model } = useSelectedModel(apiConfiguration)
|
||||
|
||||
const selectImages = useCallback(() => vscode.postMessage({ type: "selectImages" }), [])
|
||||
|
|
@ -1765,7 +1763,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
contextTokens={apiMetrics.contextTokens}
|
||||
buttonsDisabled={sendingDisabled}
|
||||
handleCondenseContext={handleCondenseContext}
|
||||
onClose={handleTaskCloseButtonClick}
|
||||
todos={latestTodos}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 flex-1 whitespace-nowrap px-2">
|
||||
<div className="flex items-center gap-2 flex-1 whitespace-nowrap">
|
||||
<div data-testid="context-tokens-count">{formatLargeNumber(safeContextTokens)}</div>
|
||||
<StandardTooltip content={tooltipContent} side="top" sideOffset={8}>
|
||||
<div className="flex-1 relative">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useEffect, useRef } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { SquareArrowOutUpRightIcon } from "lucide-react"
|
||||
|
||||
import type { HistoryItem, ShareVisibility } from "@roo-code/types"
|
||||
import { TelemetryEventName } from "@roo-code/types"
|
||||
|
|
@ -26,9 +27,10 @@ import {
|
|||
interface ShareButtonProps {
|
||||
item?: HistoryItem
|
||||
disabled?: boolean
|
||||
showLabel?: boolean
|
||||
}
|
||||
|
||||
export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
|
||||
export const ShareButton = ({ item, disabled = false, showLabel = false }: ShareButtonProps) => {
|
||||
const [shareDropdownOpen, setShareDropdownOpen] = useState(false)
|
||||
const [connectModalOpen, setConnectModalOpen] = useState(false)
|
||||
const [shareSuccess, setShareSuccess] = useState<{ visibility: ShareVisibility; url: string } | null>(null)
|
||||
|
|
@ -155,14 +157,21 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
|
|||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
size={showLabel ? "sm" : "icon"}
|
||||
disabled={disabled || shareButtonState.disabled}
|
||||
className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
|
||||
onClick={handleShareButtonClick}>
|
||||
<span className="codicon codicon-link"></span>
|
||||
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">
|
||||
<SquareArrowOutUpRightIcon />
|
||||
{showLabel && <span className="ml-0">{t("chat:task.share")}</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</StandardTooltip>
|
||||
|
||||
<PopoverContent className="w-56 p-0" align="start">
|
||||
{shareSuccess ? (
|
||||
<div className="p-3">
|
||||
|
|
@ -217,11 +226,17 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
|
|||
<StandardTooltip content={shareButtonState.title}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
size={showLabel ? "sm" : "icon"}
|
||||
disabled={disabled || shareButtonState.disabled}
|
||||
className="h-7 w-7 p-1.5 hover:bg-vscode-toolbar-hoverBackground"
|
||||
onClick={handleShareButtonClick}>
|
||||
<span className="codicon codicon-link"></span>
|
||||
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">
|
||||
<SquareArrowOutUpRightIcon />
|
||||
{showLabel && <span className="ml-1">{t("chat:task.share")}</span>}
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState } from "react"
|
||||
import prettyBytes from "pretty-bytes"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import type { HistoryItem } from "@roo-code/types"
|
||||
|
|
@ -22,8 +21,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
|
|||
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-1">
|
||||
<ShareButton item={item} disabled={false} />
|
||||
<div className="flex flex-row items-center">
|
||||
<IconButton
|
||||
iconClass="codicon-desktop-download"
|
||||
title={t("chat:task.export")}
|
||||
|
|
@ -53,7 +51,6 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
|
|||
}
|
||||
}}
|
||||
/>
|
||||
<span className="ml-1 text-xs text-vscode-foreground opacity-85">{prettyBytes(item.size)}</span>
|
||||
</div>
|
||||
{deleteTaskId && (
|
||||
<DeleteTaskDialog
|
||||
|
|
@ -64,6 +61,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
<ShareButton item={item} disabled={false} showLabel={false} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { memo, useRef, useState } from "react"
|
||||
import { useWindowSize } from "react-use"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
|
||||
import { CloudUpload, CloudDownload, FoldVertical } from "lucide-react"
|
||||
import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react"
|
||||
import prettyBytes from "pretty-bytes"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
|
|
@ -10,14 +9,13 @@ import { getModelMaxOutputTokens } from "@roo/api"
|
|||
|
||||
import { formatLargeNumber } from "@src/utils/format"
|
||||
import { cn } from "@src/lib/utils"
|
||||
import { Button, StandardTooltip } from "@src/components/ui"
|
||||
import { StandardTooltip } from "@src/components/ui"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel"
|
||||
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
|
||||
import { TaskActions } from "./TaskActions"
|
||||
import { ShareButton } from "./ShareButton"
|
||||
import { ContextWindowProgress } from "./ContextWindowProgress"
|
||||
import { Mention } from "./Mention"
|
||||
import { TodoListDisplay } from "./TodoListDisplay"
|
||||
|
|
@ -32,7 +30,6 @@ export interface TaskHeaderProps {
|
|||
contextTokens: number
|
||||
buttonsDisabled: boolean
|
||||
handleCondenseContext: (taskId: string) => void
|
||||
onClose: () => void
|
||||
todos?: any[]
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +43,6 @@ const TaskHeader = ({
|
|||
contextTokens,
|
||||
buttonsDisabled,
|
||||
handleCondenseContext,
|
||||
onClose,
|
||||
todos,
|
||||
}: TaskHeaderProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -58,8 +54,6 @@ const TaskHeader = ({
|
|||
const textRef = useRef<HTMLDivElement>(null)
|
||||
const contextWindow = model?.contextWindow || 1
|
||||
|
||||
const { width: windowWidth } = useWindowSize()
|
||||
|
||||
const condenseButton = (
|
||||
<StandardTooltip content={t("chat:task.condenseContext")}>
|
||||
<button
|
||||
|
|
@ -74,55 +68,104 @@ const TaskHeader = ({
|
|||
const hasTodos = todos && Array.isArray(todos) && todos.length > 0
|
||||
|
||||
return (
|
||||
<div className="py-2 px-3">
|
||||
<div className="pt-2 pb-0 px-3">
|
||||
<div
|
||||
className={cn(
|
||||
"p-2.5 flex flex-col gap-1.5 relative z-1 border",
|
||||
"px-2.5 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",
|
||||
hasTodos ? "rounded-t-xs border-b-0" : "rounded-xs",
|
||||
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
|
||||
className="flex items-center cursor-pointer -ml-0.5 select-none grow min-w-0"
|
||||
onClick={() => setIsTaskExpanded(!isTaskExpanded)}>
|
||||
<div className="flex items-center shrink-0">
|
||||
<span className={`codicon codicon-chevron-${isTaskExpanded ? "down" : "right"}`}></span>
|
||||
</div>
|
||||
<div className="ml-1.5 whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
|
||||
<span className="font-bold">
|
||||
{t("chat:task.title")}
|
||||
{!isTaskExpanded && ":"}
|
||||
</span>
|
||||
)}
|
||||
onClick={(e) => {
|
||||
// Don't expand if clicking on buttons or interactive elements
|
||||
if (
|
||||
e.target instanceof Element &&
|
||||
(e.target.closest("button") ||
|
||||
e.target.closest('[role="button"]') ||
|
||||
e.target.closest(".share-button") ||
|
||||
e.target.closest("[data-radix-popper-content-wrapper]") ||
|
||||
e.target.closest("img") ||
|
||||
e.target.tagName === "IMG")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't expand/collapse if user is selecting text
|
||||
const selection = window.getSelection()
|
||||
if (selection && selection.toString().length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsTaskExpanded(!isTaskExpanded)
|
||||
}}>
|
||||
<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">
|
||||
{isTaskExpanded && <span className="font-bold">{t("chat:task.title")}</span>}
|
||||
{!isTaskExpanded && (
|
||||
<span className="ml-1">
|
||||
<div>
|
||||
<span className="font-bold mr-1">{t("chat:task.title")}</span>
|
||||
<Mention text={task.text} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center shrink-0 ml-2" onClick={(e) => e.stopPropagation()}>
|
||||
<StandardTooltip content={isTaskExpanded ? t("chat:task.collapse") : t("chat:task.expand")}>
|
||||
<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} />}
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<StandardTooltip content={t("chat:task.closeAndStart")}>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="shrink-0 w-5 h-5">
|
||||
<span className="codicon codicon-close" />
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
{/* Collapsed state: Track context and cost if we have any */}
|
||||
{!isTaskExpanded && contextWindow > 0 && (
|
||||
<div className={`w-full flex flex-row items-center gap-1 h-auto`}>
|
||||
<ContextWindowProgress
|
||||
contextWindow={contextWindow}
|
||||
contextTokens={contextTokens || 0}
|
||||
maxTokens={
|
||||
model
|
||||
? getModelMaxOutputTokens({ modelId, model, settings: apiConfiguration })
|
||||
: undefined
|
||||
<div className="flex items-center gap-2 text-sm" onClick={(e) => e.stopPropagation()}>
|
||||
<StandardTooltip
|
||||
content={
|
||||
<div className="space-y-1">
|
||||
<div>
|
||||
{t("chat:tokenProgress.tokensUsed", {
|
||||
used: formatLargeNumber(contextTokens || 0),
|
||||
total: formatLargeNumber(contextWindow),
|
||||
})}
|
||||
</div>
|
||||
{(() => {
|
||||
const maxTokens = model
|
||||
? getModelMaxOutputTokens({ modelId, model, settings: apiConfiguration })
|
||||
: 0
|
||||
const reservedForOutput = maxTokens || 0
|
||||
const availableSpace = contextWindow - (contextTokens || 0) - reservedForOutput
|
||||
|
||||
return (
|
||||
<>
|
||||
{reservedForOutput > 0 && (
|
||||
<div>
|
||||
{t("chat:tokenProgress.reservedForResponse", {
|
||||
amount: formatLargeNumber(reservedForOutput),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{availableSpace > 0 && (
|
||||
<div>
|
||||
{t("chat:tokenProgress.availableSpace", {
|
||||
amount: formatLargeNumber(availableSpace),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{condenseButton}
|
||||
<ShareButton item={currentTaskItem} disabled={buttonsDisabled} />
|
||||
{!!totalCost && <VSCodeBadge>${totalCost.toFixed(2)}</VSCodeBadge>}
|
||||
side="top"
|
||||
sideOffset={8}>
|
||||
<span className="mr-1">
|
||||
{formatLargeNumber(contextTokens || 0)} / {formatLargeNumber(contextWindow)}
|
||||
</span>
|
||||
</StandardTooltip>
|
||||
{!!totalCost && <span>${totalCost.toFixed(2)}</span>}
|
||||
</div>
|
||||
)}
|
||||
{/* Expanded state: Show task text and images */}
|
||||
|
|
@ -130,10 +173,10 @@ const TaskHeader = ({
|
|||
<>
|
||||
<div
|
||||
ref={textContainerRef}
|
||||
className="-mt-0.5 text-vscode-font-size overflow-y-auto break-words break-anywhere relative">
|
||||
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"
|
||||
className="overflow-auto max-h-80 whitespace-pre-wrap break-words break-anywhere cursor-text"
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: "unset",
|
||||
|
|
@ -144,78 +187,112 @@ const TaskHeader = ({
|
|||
</div>
|
||||
{task.images && task.images.length > 0 && <Thumbnails images={task.images} />}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
{isTaskExpanded && contextWindow > 0 && (
|
||||
<div
|
||||
className={`w-full flex ${windowWidth < 400 ? "flex-col" : "flex-row"} gap-1 h-auto`}>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
<span className="font-bold" data-testid="context-window-label">
|
||||
{t("chat:task.contextWindow")}
|
||||
</span>
|
||||
</div>
|
||||
<ContextWindowProgress
|
||||
contextWindow={contextWindow}
|
||||
contextTokens={contextTokens || 0}
|
||||
maxTokens={
|
||||
model
|
||||
? getModelMaxOutputTokens({
|
||||
modelId,
|
||||
model,
|
||||
settings: apiConfiguration,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{condenseButton}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center h-[20px]">
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
<span className="font-bold">{t("chat:task.tokens")}</span>
|
||||
{typeof tokensIn === "number" && tokensIn > 0 && (
|
||||
<span className="flex items-center gap-0.5">
|
||||
<i className="codicon codicon-arrow-up text-xs font-bold" />
|
||||
{formatLargeNumber(tokensIn)}
|
||||
</span>
|
||||
<div className="border-t border-b border-vscode-panel-border/50 py-4 mt-2 mb-1">
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
{contextWindow > 0 && (
|
||||
<tr>
|
||||
<th
|
||||
className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 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`}>
|
||||
<ContextWindowProgress
|
||||
contextWindow={contextWindow}
|
||||
contextTokens={contextTokens || 0}
|
||||
maxTokens={
|
||||
model
|
||||
? getModelMaxOutputTokens({
|
||||
modelId,
|
||||
model,
|
||||
settings: apiConfiguration,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{condenseButton}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{typeof tokensOut === "number" && tokensOut > 0 && (
|
||||
<span className="flex items-center gap-0.5">
|
||||
<i className="codicon codicon-arrow-down text-xs font-bold" />
|
||||
{formatLargeNumber(tokensOut)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!totalCost && <TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />}
|
||||
</div>
|
||||
|
||||
{((typeof cacheReads === "number" && cacheReads > 0) ||
|
||||
(typeof cacheWrites === "number" && cacheWrites > 0)) && (
|
||||
<div className="flex items-center gap-1 flex-wrap h-[20px]">
|
||||
<span className="font-bold">{t("chat:task.cache")}</span>
|
||||
{typeof cacheWrites === "number" && cacheWrites > 0 && (
|
||||
<span className="flex items-center gap-0.5">
|
||||
<CloudUpload size={16} />
|
||||
{formatLargeNumber(cacheWrites)}
|
||||
</span>
|
||||
)}
|
||||
{typeof cacheReads === "number" && cacheReads > 0 && (
|
||||
<span className="flex items-center gap-0.5">
|
||||
<CloudDownload size={16} />
|
||||
{formatLargeNumber(cacheReads)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<tr>
|
||||
<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
|
||||
{t("chat:task.tokens")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
{typeof tokensIn === "number" && tokensIn > 0 && (
|
||||
<span>↑ {formatLargeNumber(tokensIn)}</span>
|
||||
)}
|
||||
{typeof tokensOut === "number" && tokensOut > 0 && (
|
||||
<span>↓ {formatLargeNumber(tokensOut)}</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{!!totalCost && (
|
||||
<div className="flex justify-between items-center h-[20px]">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-bold">{t("chat:task.apiCost")}</span>
|
||||
<span>${totalCost?.toFixed(2)}</span>
|
||||
</div>
|
||||
<TaskActions item={currentTaskItem} buttonsDisabled={buttonsDisabled} />
|
||||
</div>
|
||||
)}
|
||||
{((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]">
|
||||
{t("chat:task.cache")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
{typeof cacheWrites === "number" && cacheWrites > 0 && (
|
||||
<span>↑ {formatLargeNumber(cacheWrites)}</span>
|
||||
)}
|
||||
{typeof cacheReads === "number" && cacheReads > 0 && (
|
||||
<span>↓ {formatLargeNumber(cacheReads)}</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{!!totalCost && (
|
||||
<tr>
|
||||
<th className="font-bold text-left align-top w-1 whitespace-nowrap pl-1 pr-3 h-[24px]">
|
||||
{t("chat:task.apiCost")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
<span>${totalCost?.toFixed(2)}</span>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{/* Cache size display */}
|
||||
{((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]">
|
||||
{t("chat:task.cache")}
|
||||
</th>
|
||||
<td className="align-top">
|
||||
{prettyBytes(((cacheReads || 0) + (cacheWrites || 0)) * 4)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{/* 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]">
|
||||
{t("chat:task.size")}
|
||||
</th>
|
||||
<td className="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>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -89,19 +89,17 @@ describe("TaskActions", () => {
|
|||
it("renders share button when item has id", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
// ShareButton now uses data-testid for reliable testing
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
expect(shareButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does not render share button when item has no id", () => {
|
||||
render(<TaskActions item={undefined} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.queryAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).not.toBeDefined()
|
||||
// ShareButton returns null when no item ID
|
||||
const shareButton = screen.queryByTestId("share-button")
|
||||
expect(shareButton).toBeNull()
|
||||
})
|
||||
|
||||
it("renders share button even when not authenticated", () => {
|
||||
|
|
@ -112,9 +110,8 @@ describe("TaskActions", () => {
|
|||
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
// ShareButton should still render when not authenticated
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
expect(shareButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
@ -123,11 +120,9 @@ describe("TaskActions", () => {
|
|||
it("shows organization and public share options when authenticated and sharing enabled", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
// Find share button by its test ID and click it
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
expect(screen.getByText("Share with Organization")).toBeInTheDocument()
|
||||
expect(screen.getByText("Share Publicly")).toBeInTheDocument()
|
||||
|
|
@ -136,11 +131,9 @@ describe("TaskActions", () => {
|
|||
it("sends shareCurrentTask message when organization option is selected", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
// Find share button by its test ID and click it
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
const orgOption = screen.getByText("Share with Organization")
|
||||
fireEvent.click(orgOption)
|
||||
|
|
@ -154,11 +147,9 @@ describe("TaskActions", () => {
|
|||
it("sends shareCurrentTask message when public option is selected", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
// Find share button by its test ID and click it
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
const publicOption = screen.getByText("Share Publicly")
|
||||
fireEvent.click(publicOption)
|
||||
|
|
@ -180,11 +171,9 @@ describe("TaskActions", () => {
|
|||
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
// Find share button by its test ID and click it
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
|
||||
expect(screen.getByText("Share Publicly")).toBeInTheDocument()
|
||||
|
|
@ -202,11 +191,9 @@ describe("TaskActions", () => {
|
|||
it("shows connect to cloud option when not authenticated", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
// Find share button by its test ID and click it
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
expect(screen.getByText("Connect to Roo Code Cloud")).toBeInTheDocument()
|
||||
expect(screen.getByText("Sign in to Roo Code Cloud to share tasks")).toBeInTheDocument()
|
||||
|
|
@ -216,11 +203,9 @@ describe("TaskActions", () => {
|
|||
it("does not show organization and public options when not authenticated", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
// Find share button by its test ID and click it
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
expect(screen.queryByText("Share with Organization")).not.toBeInTheDocument()
|
||||
expect(screen.queryByText("Share Publicly")).not.toBeInTheDocument()
|
||||
|
|
@ -229,11 +214,9 @@ describe("TaskActions", () => {
|
|||
it("sends rooCloudSignIn message when connect to cloud is selected", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
// Find share button by its test ID and click it
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
const connectOption = screen.getByText("Connect")
|
||||
fireEvent.click(connectOption)
|
||||
|
|
@ -253,9 +236,8 @@ describe("TaskActions", () => {
|
|||
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Find button by its icon class
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
// Find share button by its test ID
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
expect(shareButton).toBeInTheDocument()
|
||||
expect(shareButton).toBeDisabled()
|
||||
|
||||
|
|
@ -303,10 +285,8 @@ describe("TaskActions", () => {
|
|||
const { rerender } = render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
// Click share button to open connect modal
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
expect(shareButton).toBeDefined()
|
||||
fireEvent.click(shareButton!)
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
fireEvent.click(shareButton)
|
||||
|
||||
// Click connect button to initiate authentication
|
||||
const connectButton = screen.getByText("Connect")
|
||||
|
|
@ -353,12 +333,11 @@ describe("TaskActions", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("renders delete button and file size when item has size", () => {
|
||||
it("renders delete button when item has size", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
const deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
|
||||
expect(deleteButton).toBeInTheDocument()
|
||||
expect(screen.getByText("1024 B")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("does not render delete button when item has no size", () => {
|
||||
|
|
@ -374,11 +353,10 @@ describe("TaskActions", () => {
|
|||
it("keeps share, export, and copy buttons enabled but disables delete button when buttonsDisabled is true", () => {
|
||||
render(<TaskActions item={mockItem} buttonsDisabled={true} />)
|
||||
|
||||
// Find buttons by their labels/icons
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
// Find buttons by their labels/test IDs
|
||||
const shareButton = screen.getByTestId("share-button")
|
||||
const exportButton = screen.getByLabelText("Export task history")
|
||||
const copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy"))
|
||||
const copyButton = screen.getByLabelText("history:copyPrompt")
|
||||
const deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
|
||||
|
||||
// Share, export, and copy buttons should be enabled regardless of buttonsDisabled
|
||||
|
|
@ -393,10 +371,9 @@ describe("TaskActions", () => {
|
|||
// Test with buttonsDisabled = false
|
||||
const { rerender } = render(<TaskActions item={mockItem} buttonsDisabled={false} />)
|
||||
|
||||
let buttons = screen.getAllByRole("button")
|
||||
let shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
let shareButton = screen.getByTestId("share-button")
|
||||
let exportButton = screen.getByLabelText("Export task history")
|
||||
let copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy"))
|
||||
let copyButton = screen.getByLabelText("history:copyPrompt")
|
||||
let deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
|
||||
|
||||
expect(shareButton).not.toBeDisabled()
|
||||
|
|
@ -407,10 +384,9 @@ describe("TaskActions", () => {
|
|||
// Test with buttonsDisabled = true
|
||||
rerender(<TaskActions item={mockItem} buttonsDisabled={true} />)
|
||||
|
||||
buttons = screen.getAllByRole("button")
|
||||
shareButton = buttons.find((btn) => btn.querySelector(".codicon-link"))
|
||||
shareButton = screen.getByTestId("share-button")
|
||||
exportButton = screen.getByLabelText("Export task history")
|
||||
copyButton = buttons.find((btn) => btn.querySelector(".codicon-copy"))
|
||||
copyButton = screen.getByLabelText("history:copyPrompt")
|
||||
deleteButton = screen.getByLabelText("Delete Task (Shift + Click to skip confirmation)")
|
||||
|
||||
// Share, export, and copy remain enabled
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ describe("TaskHeader", () => {
|
|||
contextTokens: 200,
|
||||
buttonsDisabled: false,
|
||||
handleCondenseContext: vi.fn(),
|
||||
onClose: vi.fn(),
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
|
@ -91,9 +90,13 @@ describe("TaskHeader", () => {
|
|||
expect(screen.queryByText(/\$/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should render the condense context button", () => {
|
||||
it("should render the condense context button when expanded", () => {
|
||||
renderTaskHeader()
|
||||
// Find the button that contains the FoldVertical icon
|
||||
// First click to expand the task header
|
||||
const taskHeader = screen.getByText("Test task")
|
||||
fireEvent.click(taskHeader)
|
||||
|
||||
// Now find the condense button in the expanded state
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
|
||||
expect(condenseButton).toBeDefined()
|
||||
|
|
@ -103,6 +106,11 @@ describe("TaskHeader", () => {
|
|||
it("should call handleCondenseContext when condense context button is clicked", () => {
|
||||
const handleCondenseContext = vi.fn()
|
||||
renderTaskHeader({ handleCondenseContext })
|
||||
|
||||
// First click to expand the task header
|
||||
const taskHeader = screen.getByText("Test task")
|
||||
fireEvent.click(taskHeader)
|
||||
|
||||
// Find the button that contains the FoldVertical icon
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
|
||||
|
|
@ -114,6 +122,11 @@ describe("TaskHeader", () => {
|
|||
it("should disable the condense context button when buttonsDisabled is true", () => {
|
||||
const handleCondenseContext = vi.fn()
|
||||
renderTaskHeader({ buttonsDisabled: true, handleCondenseContext })
|
||||
|
||||
// First click to expand the task header
|
||||
const taskHeader = screen.getByText("Test task")
|
||||
fireEvent.click(taskHeader)
|
||||
|
||||
// Find the button that contains the FoldVertical icon
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const condenseButton = buttons.find((button) => button.querySelector("svg.lucide-fold-vertical"))
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp
|
|||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="py-1"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ca/chat.json
generated
9
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Tasca",
|
||||
"seeMore": "Veure més",
|
||||
"seeLess": "Veure menys",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Caché:",
|
||||
"apiCost": "Cost d'API:",
|
||||
"contextWindow": "Finestra de context:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Caché",
|
||||
"apiCost": "Cost d'API",
|
||||
"size": "Mida",
|
||||
"contextWindow": "Finestra de context",
|
||||
"closeAndStart": "Tancar tasca i iniciar-ne una de nova",
|
||||
"export": "Exportar historial de tasques",
|
||||
"delete": "Eliminar tasca (Shift + Clic per ometre confirmació)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/de/chat.json
generated
9
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Aufgabe",
|
||||
"seeMore": "Mehr anzeigen",
|
||||
"seeLess": "Weniger anzeigen",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Cache:",
|
||||
"apiCost": "API-Kosten:",
|
||||
"contextWindow": "Kontextfenster:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Cache",
|
||||
"apiCost": "API-Kosten",
|
||||
"size": "Größe",
|
||||
"contextWindow": "Kontextfenster",
|
||||
"closeAndStart": "Aufgabe schließen und neue starten",
|
||||
"export": "Aufgabenverlauf exportieren",
|
||||
"delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)",
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@
|
|||
"title": "Task",
|
||||
"seeMore": "See more",
|
||||
"seeLess": "See less",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Cache:",
|
||||
"apiCost": "API Cost:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Cache",
|
||||
"apiCost": "API Cost",
|
||||
"size": "Size",
|
||||
"condenseContext": "Intelligently condense context",
|
||||
"contextWindow": "Context Length:",
|
||||
"contextWindow": "Context Length",
|
||||
"closeAndStart": "Close task and start a new one",
|
||||
"export": "Export task history",
|
||||
"share": "Share task",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/es/chat.json
generated
9
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Tarea",
|
||||
"seeMore": "Ver más",
|
||||
"seeLess": "Ver menos",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Caché:",
|
||||
"apiCost": "Costo de API:",
|
||||
"contextWindow": "Longitud del contexto:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Caché",
|
||||
"apiCost": "Costo de API",
|
||||
"size": "Tamaño",
|
||||
"contextWindow": "Longitud del contexto",
|
||||
"closeAndStart": "Cerrar tarea e iniciar una nueva",
|
||||
"export": "Exportar historial de tareas",
|
||||
"delete": "Eliminar tarea (Shift + Clic para omitir confirmación)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/fr/chat.json
generated
9
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Tâche",
|
||||
"seeMore": "Voir plus",
|
||||
"seeLess": "Voir moins",
|
||||
"tokens": "Tokens :",
|
||||
"cache": "Cache :",
|
||||
"apiCost": "Coût API :",
|
||||
"contextWindow": "Durée du contexte :",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Cache",
|
||||
"apiCost": "Coût API",
|
||||
"size": "Taille",
|
||||
"contextWindow": "Durée du contexte",
|
||||
"closeAndStart": "Fermer la tâche et en commencer une nouvelle",
|
||||
"export": "Exporter l'historique des tâches",
|
||||
"delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/hi/chat.json
generated
9
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "कार्य",
|
||||
"seeMore": "अधिक देखें",
|
||||
"seeLess": "कम देखें",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "कैश:",
|
||||
"apiCost": "API लागत:",
|
||||
"contextWindow": "संदर्भ लंबाई:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "कैश",
|
||||
"apiCost": "API लागत",
|
||||
"size": "आकार",
|
||||
"contextWindow": "संदर्भ लंबाई",
|
||||
"closeAndStart": "कार्य बंद करें और नया शुरू करें",
|
||||
"export": "कार्य इतिहास निर्यात करें",
|
||||
"delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/id/chat.json
generated
9
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -4,11 +4,12 @@
|
|||
"title": "Tugas",
|
||||
"seeMore": "Lihat lebih banyak",
|
||||
"seeLess": "Lihat lebih sedikit",
|
||||
"tokens": "Token:",
|
||||
"cache": "Cache:",
|
||||
"apiCost": "Biaya API:",
|
||||
"tokens": "Token",
|
||||
"cache": "Cache",
|
||||
"apiCost": "Biaya API",
|
||||
"size": "Ukuran",
|
||||
"condenseContext": "Kondensasi konteks secara cerdas",
|
||||
"contextWindow": "Panjang Konteks:",
|
||||
"contextWindow": "Panjang Konteks",
|
||||
"closeAndStart": "Tutup tugas dan mulai yang baru",
|
||||
"export": "Ekspor riwayat tugas",
|
||||
"share": "Bagikan tugas",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/it/chat.json
generated
9
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Attività",
|
||||
"seeMore": "Vedi altro",
|
||||
"seeLess": "Vedi meno",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Cache:",
|
||||
"apiCost": "Costo API:",
|
||||
"contextWindow": "Lunghezza del contesto:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Cache",
|
||||
"apiCost": "Costo API",
|
||||
"size": "Dimensione",
|
||||
"contextWindow": "Lunghezza del contesto",
|
||||
"closeAndStart": "Chiudi attività e iniziane una nuova",
|
||||
"export": "Esporta cronologia attività",
|
||||
"delete": "Elimina attività (Shift + Clic per saltare la conferma)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ja/chat.json
generated
9
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "タスク",
|
||||
"seeMore": "もっと見る",
|
||||
"seeLess": "表示を減らす",
|
||||
"tokens": "トークン:",
|
||||
"cache": "キャッシュ:",
|
||||
"apiCost": "APIコスト:",
|
||||
"contextWindow": "コンテキストウィンドウ:",
|
||||
"tokens": "トークン",
|
||||
"cache": "キャッシュ",
|
||||
"apiCost": "APIコスト",
|
||||
"size": "サイズ",
|
||||
"contextWindow": "コンテキストウィンドウ",
|
||||
"closeAndStart": "タスクを閉じて新しいタスクを開始",
|
||||
"export": "タスク履歴をエクスポート",
|
||||
"delete": "タスクを削除(Shift + クリックで確認をスキップ)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ko/chat.json
generated
9
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "작업",
|
||||
"seeMore": "더 보기",
|
||||
"seeLess": "줄여보기",
|
||||
"tokens": "토큰:",
|
||||
"cache": "캐시:",
|
||||
"apiCost": "API 비용:",
|
||||
"contextWindow": "컨텍스트 창:",
|
||||
"tokens": "토큰",
|
||||
"cache": "캐시",
|
||||
"apiCost": "API 비용",
|
||||
"size": "크기",
|
||||
"contextWindow": "컨텍스트 창",
|
||||
"closeAndStart": "작업 닫고 새 작업 시작",
|
||||
"export": "작업 기록 내보내기",
|
||||
"delete": "작업 삭제 (Shift + 클릭으로 확인 생략)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/nl/chat.json
generated
9
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Taak",
|
||||
"seeMore": "Meer weergeven",
|
||||
"seeLess": "Minder weergeven",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Cache:",
|
||||
"apiCost": "API-kosten:",
|
||||
"contextWindow": "Contextlengte:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Cache",
|
||||
"apiCost": "API-kosten",
|
||||
"size": "Grootte",
|
||||
"contextWindow": "Contextlengte",
|
||||
"closeAndStart": "Taak sluiten en een nieuwe starten",
|
||||
"export": "Taakgeschiedenis exporteren",
|
||||
"delete": "Taak verwijderen (Shift + Klik om bevestiging over te slaan)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/pl/chat.json
generated
9
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Zadanie",
|
||||
"seeMore": "Zobacz więcej",
|
||||
"seeLess": "Zobacz mniej",
|
||||
"tokens": "Tokeny:",
|
||||
"cache": "Pamięć podręczna:",
|
||||
"apiCost": "Koszt API:",
|
||||
"contextWindow": "Okno kontekstu:",
|
||||
"tokens": "Tokeny",
|
||||
"cache": "Pamięć podręczna",
|
||||
"apiCost": "Koszt API",
|
||||
"size": "Rozmiar",
|
||||
"contextWindow": "Okno kontekstu",
|
||||
"closeAndStart": "Zamknij zadanie i rozpocznij nowe",
|
||||
"export": "Eksportuj historię zadań",
|
||||
"delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
9
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Tarefa",
|
||||
"seeMore": "Ver mais",
|
||||
"seeLess": "Ver menos",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Cache:",
|
||||
"apiCost": "Custo da API:",
|
||||
"contextWindow": "Janela de contexto:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Cache",
|
||||
"apiCost": "Custo da API",
|
||||
"size": "Tamanho",
|
||||
"contextWindow": "Janela de contexto",
|
||||
"closeAndStart": "Fechar tarefa e iniciar nova",
|
||||
"export": "Exportar histórico de tarefas",
|
||||
"delete": "Excluir tarefa (Shift + Clique para pular confirmação)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/ru/chat.json
generated
9
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Задача",
|
||||
"seeMore": "Показать больше",
|
||||
"seeLess": "Показать меньше",
|
||||
"tokens": "Токенов:",
|
||||
"cache": "Кэш:",
|
||||
"apiCost": "Стоимость API:",
|
||||
"contextWindow": "Длина контекста:",
|
||||
"tokens": "Токенов",
|
||||
"cache": "Кэш",
|
||||
"apiCost": "Стоимость API",
|
||||
"size": "Размер",
|
||||
"contextWindow": "Длина контекста",
|
||||
"closeAndStart": "Закрыть задачу и начать новую",
|
||||
"export": "Экспортировать историю задач",
|
||||
"delete": "Удалить задачу (Shift + клик для пропуска подтверждения)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/tr/chat.json
generated
9
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Görev",
|
||||
"seeMore": "Daha fazla gör",
|
||||
"seeLess": "Daha az gör",
|
||||
"tokens": "Tokenlar:",
|
||||
"cache": "Önbellek:",
|
||||
"apiCost": "API Maliyeti:",
|
||||
"contextWindow": "Bağlam Uzunluğu:",
|
||||
"tokens": "Tokenlar",
|
||||
"cache": "Önbellek",
|
||||
"apiCost": "API Maliyeti",
|
||||
"size": "Boyut",
|
||||
"contextWindow": "Bağlam Uzunluğu",
|
||||
"closeAndStart": "Görevi kapat ve yeni bir görev başlat",
|
||||
"export": "Görev geçmişini dışa aktar",
|
||||
"delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/vi/chat.json
generated
9
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Nhiệm vụ",
|
||||
"seeMore": "Xem thêm",
|
||||
"seeLess": "Thu gọn",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "Bộ nhớ đệm:",
|
||||
"apiCost": "Chi phí API:",
|
||||
"contextWindow": "Chiều dài bối cảnh:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "Bộ nhớ đệm",
|
||||
"apiCost": "Chi phí API",
|
||||
"size": "Kích thước",
|
||||
"contextWindow": "Chiều dài bối cảnh",
|
||||
"closeAndStart": "Đóng nhiệm vụ và bắt đầu nhiệm vụ mới",
|
||||
"export": "Xuất lịch sử nhiệm vụ",
|
||||
"delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)",
|
||||
|
|
|
|||
9
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
9
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -4,10 +4,11 @@
|
|||
"title": "任务",
|
||||
"seeMore": "展开",
|
||||
"seeLess": "收起",
|
||||
"tokens": "Token 用量:",
|
||||
"cache": "缓存:",
|
||||
"apiCost": "API 费用:",
|
||||
"contextWindow": "上下文长度:",
|
||||
"tokens": "Token 用量",
|
||||
"cache": "缓存",
|
||||
"apiCost": "API 费用",
|
||||
"size": "大小",
|
||||
"contextWindow": "上下文长度",
|
||||
"closeAndStart": "关闭任务并开始新任务",
|
||||
"export": "导出任务历史",
|
||||
"delete": "删除任务(Shift + 点击跳过确认)",
|
||||
|
|
|
|||
5
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
5
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -4,9 +4,10 @@
|
|||
"title": "工作",
|
||||
"seeMore": "顯示更多",
|
||||
"seeLess": "顯示較少",
|
||||
"tokens": "Tokens:",
|
||||
"cache": "快取:",
|
||||
"tokens": "Tokens",
|
||||
"cache": "快取",
|
||||
"apiCost": "API 費用:",
|
||||
"size": "大小",
|
||||
"contextWindow": "上下文長度:",
|
||||
"closeAndStart": "關閉現有工作並開始一項新的工作",
|
||||
"export": "匯出工作紀錄",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue