diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index a56a00fc355a..ab5cb1dc5d4d 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -148,6 +148,7 @@ export const globalSettingsSchema = z.object({ includeTaskHistoryInEnhance: z.boolean().optional(), historyPreviewCollapsed: z.boolean().optional(), reasoningBlockCollapsed: z.boolean().optional(), + taskTitlesEnabled: z.boolean().optional(), profileThresholds: z.record(z.string(), z.number()).optional(), hasOpenedModeSelector: z.boolean().optional(), lastModeExportPath: z.string().optional(), diff --git a/packages/types/src/history.ts b/packages/types/src/history.ts index 395ec5986f70..49b95093a551 100644 --- a/packages/types/src/history.ts +++ b/packages/types/src/history.ts @@ -10,6 +10,7 @@ export const historyItemSchema = z.object({ parentTaskId: z.string().optional(), number: z.number(), ts: z.number(), + title: z.string().optional(), task: z.string(), tokensIn: z.number(), tokensOut: z.number(), diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 119668d66821..0d393f7507da 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1793,6 +1793,7 @@ export class ClineProvider terminalCompressProgressBar, historyPreviewCollapsed, reasoningBlockCollapsed, + taskTitlesEnabled, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -1866,6 +1867,7 @@ export class ClineProvider taskHistory: (taskHistory || []) .filter((item: HistoryItem) => item.ts && item.task) .sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts), + taskTitlesEnabled: taskTitlesEnabled ?? false, soundEnabled: soundEnabled ?? false, ttsEnabled: ttsEnabled ?? false, ttsSpeed: ttsSpeed ?? 1.0, @@ -2142,6 +2144,7 @@ export class ClineProvider maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, + taskTitlesEnabled: stateValues.taskTitlesEnabled ?? false, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -2203,7 +2206,17 @@ export class ClineProvider const existingItemIndex = history.findIndex((h) => h.id === item.id) if (existingItemIndex !== -1) { - history[existingItemIndex] = item + const existingItem = history[existingItemIndex] + const hasTitleProp = Object.prototype.hasOwnProperty.call(item, "title") + const mergedItem: HistoryItem = { + ...existingItem, + ...item, + } + if (!hasTitleProp) { + mergedItem.title = existingItem.title + } + + history[existingItemIndex] = mergedItem } else { history.push(item) } diff --git a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts index 29aefcaeba43..5d3a0b6b735f 100644 --- a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts @@ -1219,4 +1219,63 @@ describe("ClineProvider - Sticky Mode", () => { }) }) }) + + describe("updateTaskHistory", () => { + beforeEach(async () => { + await provider.resolveWebviewView(mockWebviewView) + }) + + it("preserves existing title when update omits the title property", async () => { + const baseItem: HistoryItem = { + id: "task-with-title", + number: 1, + ts: Date.now(), + task: "Original task", + tokensIn: 10, + tokensOut: 20, + cacheWrites: 0, + cacheReads: 0, + totalCost: 0, + title: "Custom title", + } + + await provider.updateTaskHistory(baseItem) + + const itemWithoutTitle = { ...baseItem } + delete (itemWithoutTitle as any).title + itemWithoutTitle.tokensIn = 42 + + await provider.updateTaskHistory(itemWithoutTitle as HistoryItem) + + const history = mockContext.globalState.get("taskHistory") as HistoryItem[] + expect(history[0]?.title).toBe("Custom title") + }) + + it("allows clearing a title when explicitly set to undefined", async () => { + const baseItem: HistoryItem = { + id: "task-clear-title", + number: 1, + ts: Date.now(), + task: "Another task", + tokensIn: 5, + tokensOut: 15, + cacheWrites: 0, + cacheReads: 0, + totalCost: 0, + title: "Temporary title", + } + + await provider.updateTaskHistory(baseItem) + + const clearedItem: HistoryItem = { + ...baseItem, + title: undefined, + } + + await provider.updateTaskHistory(clearedItem) + + const history = mockContext.globalState.get("taskHistory") as HistoryItem[] + expect(history[0]?.title).toBeUndefined() + }) + }) }) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c3e57c67a20e..a898be24517f 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -9,6 +9,7 @@ import { type Language, type GlobalState, type ClineMessage, + type HistoryItem, type TelemetrySetting, TelemetryEventName, UserSettingsConfig, @@ -673,6 +674,57 @@ export const webviewMessageHandler = async ( vscode.window.showErrorMessage(t("common:errors.share_task_failed")) } break + case "setTaskTitle": { + const ids = Array.isArray(message.ids) + ? Array.from( + new Set( + message.ids.filter((id): id is string => typeof id === "string" && id.trim().length > 0), + ), + ) + : [] + if (ids.length === 0) { + break + } + + const rawTitle = message.text ?? "" + const trimmedTitle = rawTitle.trim() + const normalizedTitle = trimmedTitle.length > 0 ? trimmedTitle : undefined + const { taskHistory } = await provider.getState() + if (!Array.isArray(taskHistory) || taskHistory.length === 0) { + break + } + + let hasUpdates = false + const historyById = new Map(taskHistory.map((item) => [item.id, item] as const)) + + for (const id of ids) { + const existingItem = historyById.get(id) + if (!existingItem) { + console.warn(`[setTaskTitle] Unable to locate task history item with id ${id}`) + continue + } + + const normalizedExistingTitle = + existingItem.title && existingItem.title.trim().length > 0 ? existingItem.title.trim() : undefined + if (normalizedExistingTitle === normalizedTitle) { + continue + } + + const updatedItem: HistoryItem = { + ...existingItem, + title: normalizedTitle, + } + + await provider.updateTaskHistory(updatedItem) + hasUpdates = true + } + + if (hasUpdates) { + await provider.postStateToWebview() + } + + break + } case "showTaskWithId": provider.showTaskWithId(message.text!) break @@ -1621,6 +1673,10 @@ export const webviewMessageHandler = async ( await updateGlobalState("reasoningBlockCollapsed", message.bool ?? true) // No need to call postStateToWebview here as the UI already updated optimistically break + case "setTaskTitlesEnabled": + await updateGlobalState("taskTitlesEnabled", message.bool ?? false) + await provider.postStateToWebview() + break case "toggleApiConfigPin": if (message.text) { const currentPinned = getGlobalState("pinnedApiConfigs") ?? {} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 66f389f81c10..606d693b6688 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -288,6 +288,7 @@ export type ExtensionState = Pick< | "openRouterImageGenerationSelectedModel" | "includeTaskHistoryInEnhance" | "reasoningBlockCollapsed" + | "taskTitlesEnabled" > & { version: string clineMessages: ClineMessage[] @@ -327,6 +328,7 @@ export type ExtensionState = Pick< renderContext: "sidebar" | "editor" settingsImportedAt?: number historyPreviewCollapsed?: boolean + taskTitlesEnabled?: boolean cloudUserInfo: CloudUserInfo | null cloudIsAuthenticated: boolean diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d43a2fce0434..985333ad66b5 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -64,6 +64,7 @@ export interface WebviewMessage { | "importSettings" | "exportSettings" | "resetState" + | "setTaskTitle" | "flushRouterModels" | "requestRouterModels" | "requestOpenAiModels" @@ -195,6 +196,7 @@ export interface WebviewMessage { | "profileThresholds" | "setHistoryPreviewCollapsed" | "setReasoningBlockCollapsed" + | "setTaskTitlesEnabled" | "openExternal" | "filterMarketplaceItems" | "marketplaceButtonClicked" diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index aef0bc5eee93..ca762da7dcf6 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -1,9 +1,10 @@ -import { memo, useEffect, useRef, useState } from "react" +import { memo, useCallback, useEffect, useRef, useState } from "react" +import type { KeyboardEvent as ReactKeyboardEvent } from "react" 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 { FoldVertical, ChevronUp, ChevronDown, Pencil } from "lucide-react" import prettyBytes from "pretty-bytes" import type { ClineMessage } from "@roo-code/types" @@ -16,6 +17,8 @@ import { cn } from "@src/lib/utils" import { StandardTooltip } from "@src/components/ui" import { useExtensionState } from "@src/context/ExtensionStateContext" import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel" +import { vscode } from "@src/utils/vscode" +import { DecoratedVSCodeTextField } from "@src/components/common/DecoratedVSCodeTextField" import Thumbnails from "../common/Thumbnails" @@ -50,7 +53,7 @@ const TaskHeader = ({ todos, }: TaskHeaderProps) => { const { t } = useTranslation() - const { apiConfiguration, currentTaskItem, clineMessages } = useExtensionState() + const { apiConfiguration, currentTaskItem, clineMessages, taskTitlesEnabled = false } = useExtensionState() const { id: modelId, info: model } = useSelectedModel(apiConfiguration) const [isTaskExpanded, setIsTaskExpanded] = useState(false) const [showLongRunningTaskMessage, setShowLongRunningTaskMessage] = useState(false) @@ -82,6 +85,103 @@ const TaskHeader = ({ return () => clearTimeout(timer) }, [currentTaskItem, isTaskComplete]) + const [isEditingTitle, setIsEditingTitle] = useState(false) + const [titleInput, setTitleInput] = useState(currentTaskItem?.title ?? "") + const titleInputRef = useRef(null) + const skipBlurSubmitRef = useRef(false) + const currentTitle = currentTaskItem?.title?.trim() ?? "" + + useEffect(() => { + if (!isEditingTitle) { + setTitleInput(currentTaskItem?.title ?? "") + } + }, [currentTaskItem?.title, isEditingTitle]) + + useEffect(() => { + setIsEditingTitle(false) + }, [currentTaskItem?.id]) + + useEffect(() => { + if (!taskTitlesEnabled) { + setIsEditingTitle(false) + return + } + + if (isEditingTitle) { + skipBlurSubmitRef.current = false + requestAnimationFrame(() => { + titleInputRef.current?.focus() + titleInputRef.current?.select() + }) + } + }, [isEditingTitle, taskTitlesEnabled]) + + const submitTitle = useCallback(() => { + if (!taskTitlesEnabled) { + return + } + + if (!currentTaskItem) { + setIsEditingTitle(false) + return + } + + const trimmed = titleInput.trim() + const existingTrimmed = currentTaskItem.title?.trim() ?? "" + + setIsEditingTitle(false) + + if (trimmed === existingTrimmed) { + setTitleInput(currentTaskItem.title ?? "") + return + } + + vscode.postMessage({ + type: "setTaskTitle", + text: trimmed, + ids: [currentTaskItem.id], + }) + + setTitleInput(trimmed) + }, [currentTaskItem, taskTitlesEnabled, titleInput]) + + useEffect(() => { + if (!isEditingTitle) { + skipBlurSubmitRef.current = false + } + }, [isEditingTitle]) + + const handleTitleBlur = useCallback(() => { + if (!taskTitlesEnabled) { + return + } + if (skipBlurSubmitRef.current) { + skipBlurSubmitRef.current = false + return + } + submitTitle() + }, [submitTitle, taskTitlesEnabled]) + + const handleTitleKeyDown = useCallback( + (event: ReactKeyboardEvent) => { + if (!taskTitlesEnabled) { + return + } + + if (event.key === "Enter") { + event.preventDefault() + skipBlurSubmitRef.current = true + submitTitle() + } else if (event.key === "Escape") { + event.preventDefault() + skipBlurSubmitRef.current = true + setIsEditingTitle(false) + setTitleInput(currentTaskItem?.title ?? "") + } + }, + [currentTaskItem?.title, submitTitle, taskTitlesEnabled], + ) + const textContainerRef = useRef(null) const textRef = useRef(null) const contextWindow = model?.contextWindow || 1 @@ -97,59 +197,128 @@ const TaskHeader = ({ ) + const renderPrimaryValue = () => { + if (!taskTitlesEnabled || !currentTaskItem) { + return ( +
+ +
+ ) + } + + if (isEditingTitle) { + return ( +
event.stopPropagation()} className="w-full" data-testid="task-title-editor"> + setTitleInput(event.target.value)} + onBlur={handleTitleBlur} + onKeyDown={handleTitleKeyDown} + placeholder={t("chat:task.titlePlaceholder")} + data-testid="task-title-input" + /> +
+ ) + } + + const tooltipKey = currentTitle.length > 0 ? "chat:task.editTitle" : "chat:task.addTitle" + const showTitle = currentTitle.length > 0 + const displayNode = showTitle ? ( + + {currentTitle} + + ) : ( +
+ +
+ ) + + return ( +
+
{displayNode}
+ + + +
+ ) + } + + const renderCollapsedSummary = () => ( +
+ {t("chat:task.title")} +
{renderPrimaryValue()}
+
+ ) + const hasTodos = todos && Array.isArray(todos) && todos.length > 0 return ( -
- {showLongRunningTaskMessage && !isTaskComplete && ( - openUpsell()} - dismissOnClick={false} - variant="banner"> - {t("cloud:upsell.longRunningTask")} - - )} -
+
+ {showLongRunningTaskMessage && !isTaskComplete && ( + openUpsell()} + dismissOnClick={false} + variant="banner"> + {t("cloud:upsell.longRunningTask")} + )} - 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) - }}> -
-
-
- {isTaskExpanded && {t("chat:task.title")}} - {!isTaskExpanded && ( -
- {t("chat:task.title")} - -
- )} +
{ + // 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) + }}> +
+
+
+ {isTaskExpanded ? ( +
+ {t("chat:task.title")} +
{renderPrimaryValue()}
+
+ ) : ( + renderCollapsedSummary() + )} +
e.stopPropagation()}> @@ -328,7 +497,7 @@ const TaskHeader = ({
-
+ ) } diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx index 6cdbeaf0c684..43f4b30dfb31 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx @@ -7,6 +7,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import type { ProviderSettings } from "@roo-code/types" import TaskHeader, { TaskHeaderProps } from "../TaskHeader" +import { vscode } from "@/utils/vscode" // Mock i18n vi.mock("react-i18next", () => ({ @@ -27,9 +28,20 @@ vi.mock("@/utils/vscode", () => ({ }, })) -// Mock the VSCodeBadge component +// Mock the VSCodeBadge/TextField components vi.mock("@vscode/webview-ui-toolkit/react", () => ({ VSCodeBadge: ({ children }: { children: React.ReactNode }) =>
{children}
, + VSCodeTextField: React.forwardRef( + ({ onInput, "data-testid": dataTestId, value = "", ...rest }: any, ref) => ( + onInput?.({ target: event.target })} + {...rest} + /> + ), + ), })) // Create a variable to hold the mock state @@ -37,6 +49,7 @@ let mockExtensionState: { apiConfiguration: ProviderSettings currentTaskItem: { id: string } | null clineMessages: any[] + taskTitlesEnabled: boolean } = { apiConfiguration: { apiProvider: "anthropic", @@ -45,6 +58,7 @@ let mockExtensionState: { } as ProviderSettings, currentTaskItem: { id: "test-task-id" }, clineMessages: [], + taskTitlesEnabled: true, } // Mock the ExtensionStateContext @@ -89,6 +103,19 @@ vi.mock("@roo/array", () => ({ })) describe("TaskHeader", () => { + beforeEach(() => { + vi.clearAllMocks() + mockExtensionState = { + apiConfiguration: { + apiProvider: "anthropic", + apiKey: "test-api-key", + apiModelId: "claude-3-opus-20240229", + } as ProviderSettings, + currentTaskItem: { id: "test-task-id" }, + clineMessages: [], + taskTitlesEnabled: true, + } + }) const defaultProps: TaskHeaderProps = { task: { type: "say", ts: Date.now(), text: "Test task", images: [] }, tokensIn: 100, @@ -180,6 +207,31 @@ describe("TaskHeader", () => { expect(handleCondenseContext).not.toHaveBeenCalled() }) + it("posts setTaskTitle message when editing title", () => { + renderTaskHeader() + + const editButton = screen.getByTestId("task-title-edit-button") + fireEvent.click(editButton) + + const input = screen.getByTestId("task-title-input") + fireEvent.change(input, { target: { value: "New task title" } }) + fireEvent.keyDown(input, { key: "Enter", code: "Enter" }) + + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "setTaskTitle", + text: "New task title", + ids: ["test-task-id"], + }) + }) + + it("hides title controls when task titles are disabled", () => { + mockExtensionState.taskTitlesEnabled = false + renderTaskHeader() + + expect(screen.queryByTestId("task-title-edit-button")).not.toBeInTheDocument() + expect(screen.queryByTestId("task-title-display")).not.toBeInTheDocument() + }) + describe("DismissibleUpsell behavior", () => { beforeEach(() => { vi.useFakeTimers() @@ -192,6 +244,7 @@ describe("TaskHeader", () => { } as ProviderSettings, currentTaskItem: { id: "test-task-id" }, clineMessages: [], + taskTitlesEnabled: true, } }) diff --git a/webview-ui/src/components/history/TaskItem.tsx b/webview-ui/src/components/history/TaskItem.tsx index d661d999300d..742990e48f3b 100644 --- a/webview-ui/src/components/history/TaskItem.tsx +++ b/webview-ui/src/components/history/TaskItem.tsx @@ -4,11 +4,13 @@ import type { HistoryItem } from "@roo-code/types" import { vscode } from "@/utils/vscode" import { cn } from "@/lib/utils" import { Checkbox } from "@/components/ui/checkbox" +import { useExtensionState } from "@/context/ExtensionStateContext" import TaskItemFooter from "./TaskItemFooter" interface DisplayHistoryItem extends HistoryItem { highlight?: string + titleHighlight?: string } interface TaskItemProps { @@ -32,6 +34,7 @@ const TaskItem = ({ onDelete, className, }: TaskItemProps) => { + const { taskTitlesEnabled = false } = useExtensionState() const handleClick = () => { if (isSelectionMode && onToggleSelection) { onToggleSelection(item.id, !isSelected) @@ -41,6 +44,9 @@ const TaskItem = ({ } const isCompact = variant === "compact" + const showTitle = taskTitlesEnabled && Boolean(item.title?.trim()) + const displayHighlight = showTitle && item.titleHighlight ? item.titleHighlight : item.highlight + const displayText = showTitle && item.title ? item.title : item.task return (
- {item.highlight ? undefined : item.task} + data-testid="task-content"> + {displayHighlight ? ( + + ) : ( + {displayText} + )}
vi.fn(() => ({ taskTitlesEnabled: true }))) +vi.mock("@/context/ExtensionStateContext", () => ({ + useExtensionState: mockUseExtensionState, +})) vi.mock("@src/i18n/TranslationContext", () => ({ useAppTranslation: () => ({ t: (key: string) => key, @@ -29,6 +33,7 @@ const mockTask = { describe("TaskItem", () => { beforeEach(() => { vi.clearAllMocks() + mockUseExtensionState.mockReturnValue({ taskTitlesEnabled: true }) }) it("renders task information", () => { @@ -80,6 +85,41 @@ describe("TaskItem", () => { expect(screen.getByTestId("export")).toBeInTheDocument() }) + it("renders title instead of task text when provided", () => { + render( + , + ) + + const content = screen.getByTestId("task-content") + expect(content).toHaveTextContent("Important task") + expect(content).not.toHaveTextContent("Test task") + expect(content.querySelector("span")?.className || "").not.toContain("font-semibold") + }) + + it("falls back to task text when feature disabled", () => { + mockUseExtensionState.mockReturnValue({ taskTitlesEnabled: false }) + render( + , + ) + + const content = screen.getByTestId("task-content") + expect(content).toHaveTextContent("Test task") + expect(content).not.toHaveTextContent("Hidden title") + expect(content.querySelector("span")?.className || "").not.toContain("font-semibold") + }) + it("displays time ago information", () => { render( { mockUseExtensionState.mockReturnValue({ taskHistory: mockTaskHistory, cwd: "/workspace/project1", + taskTitlesEnabled: true, } as any) }) @@ -154,6 +157,36 @@ describe("useTaskSearch", () => { expect((result.current.tasks[0] as any).highlight).toBe("Create a React component") }) + it("matches search queries against task titles", () => { + const { result } = renderHook(() => useTaskSearch()) + + act(() => { + result.current.setShowAllWorkspaces(true) + result.current.setSearchQuery("build") + }) + + expect(result.current.tasks).toHaveLength(1) + expect(result.current.tasks[0].id).toBe("task-1") + expect((result.current.tasks[0] as any).titleHighlight).toBe("Build component") + }) + + it("ignores task titles in search when the feature is disabled", () => { + mockUseExtensionState.mockReturnValue({ + taskHistory: mockTaskHistory, + cwd: "/workspace/project1", + taskTitlesEnabled: false, + } as any) + + const { result } = renderHook(() => useTaskSearch()) + + act(() => { + result.current.setShowAllWorkspaces(true) + result.current.setSearchQuery("build") + }) + + expect(result.current.tasks).toHaveLength(0) + }) + it("automatically switches to mostRelevant when searching", () => { const { result } = renderHook(() => useTaskSearch()) diff --git a/webview-ui/src/components/history/useTaskSearch.ts b/webview-ui/src/components/history/useTaskSearch.ts index 3969985b98a9..f8894617e30f 100644 --- a/webview-ui/src/components/history/useTaskSearch.ts +++ b/webview-ui/src/components/history/useTaskSearch.ts @@ -7,7 +7,7 @@ import { useExtensionState } from "@/context/ExtensionStateContext" type SortOption = "newest" | "oldest" | "mostExpensive" | "mostTokens" | "mostRelevant" export const useTaskSearch = () => { - const { taskHistory, cwd } = useExtensionState() + const { taskHistory, cwd, taskTitlesEnabled = false } = useExtensionState() const [searchQuery, setSearchQuery] = useState("") const [sortOption, setSortOption] = useState("newest") const [lastNonRelevantSort, setLastNonRelevantSort] = useState("newest") @@ -33,9 +33,9 @@ export const useTaskSearch = () => { const fzf = useMemo(() => { return new Fzf(presentableTasks, { - selector: (item) => item.task, + selector: (item) => (taskTitlesEnabled && item.title ? `${item.title} ${item.task}` : item.task), }) - }, [presentableTasks]) + }, [presentableTasks, taskTitlesEnabled]) const tasks = useMemo(() => { let results = presentableTasks @@ -44,14 +44,27 @@ export const useTaskSearch = () => { const searchResults = fzf.find(searchQuery) results = searchResults.map((result) => { const positions = Array.from(result.positions) - const taskEndIndex = result.item.task.length + const includeTitles = taskTitlesEnabled && !!result.item.title + const titleLength = includeTitles ? result.item.title!.length : 0 + const separatorLength = includeTitles ? 1 : 0 + const taskOffset = includeTitles ? titleLength + separatorLength : 0 + const titlePositions = includeTitles ? positions.filter((p) => p < titleLength) : [] + const taskPositions = includeTitles + ? positions.filter((p) => p >= taskOffset).map((p) => p - taskOffset) + : positions + + const titleHighlight = + titlePositions.length > 0 && result.item.title + ? highlightFzfMatch(result.item.title, titlePositions) + : undefined + + const taskHighlight = + taskPositions.length > 0 ? highlightFzfMatch(result.item.task, taskPositions) : undefined return { ...result.item, - highlight: highlightFzfMatch( - result.item.task, - positions.filter((p) => p < taskEndIndex), - ), + titleHighlight, + highlight: taskHighlight, workspace: result.item.workspace, } }) @@ -76,7 +89,7 @@ export const useTaskSearch = () => { return (b.ts || 0) - (a.ts || 0) } }) - }, [presentableTasks, searchQuery, fzf, sortOption]) + }, [presentableTasks, searchQuery, fzf, sortOption, taskTitlesEnabled]) return { tasks, diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 39f57613b734..52af86c7316c 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -195,6 +195,7 @@ const SettingsView = forwardRef(({ onDone, t openRouterImageApiKey, openRouterImageGenerationSelectedModel, reasoningBlockCollapsed, + taskTitlesEnabled, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -369,6 +370,7 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} }) vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? true }) vscode.postMessage({ type: "setReasoningBlockCollapsed", bool: reasoningBlockCollapsed ?? true }) + vscode.postMessage({ type: "setTaskTitlesEnabled", bool: taskTitlesEnabled ?? false }) vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) vscode.postMessage({ type: "profileThresholds", values: profileThresholds }) @@ -766,6 +768,7 @@ const SettingsView = forwardRef(({ onDone, t {/* UI Section */} {activeTab === "ui" && ( diff --git a/webview-ui/src/components/settings/UISettings.tsx b/webview-ui/src/components/settings/UISettings.tsx index 2de16e688224..d342694bb098 100644 --- a/webview-ui/src/components/settings/UISettings.tsx +++ b/webview-ui/src/components/settings/UISettings.tsx @@ -10,13 +10,26 @@ import { Section } from "./Section" import { ExtensionStateContextType } from "@/context/ExtensionStateContext" interface UISettingsProps extends HTMLAttributes { + taskTitlesEnabled: boolean reasoningBlockCollapsed: boolean setCachedStateField: SetCachedStateField } -export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...props }: UISettingsProps) => { +export const UISettings = ({ + taskTitlesEnabled, + reasoningBlockCollapsed, + setCachedStateField, + ...props +}: UISettingsProps) => { const { t } = useAppTranslation() + const handleTaskTitlesEnabledChange = (value: boolean) => { + setCachedStateField("taskTitlesEnabled", value) + telemetryClient.capture("ui_settings_task_titles_enabled_changed", { + enabled: value, + }) + } + const handleReasoningBlockCollapsedChange = (value: boolean) => { setCachedStateField("reasoningBlockCollapsed", value) @@ -37,6 +50,18 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr
+
+ handleTaskTitlesEnabledChange(e.target.checked)} + data-testid="enable-task-titles-checkbox"> + {t("settings:ui.taskTitles.label")} + +
+ {t("settings:ui.taskTitles.description")} +
+
+ {/* Collapse Thinking Messages Setting */}
{ const defaultProps = { + taskTitlesEnabled: false, reasoningBlockCollapsed: false, setCachedStateField: vi.fn(), } @@ -14,6 +15,12 @@ describe("UISettings", () => { expect(checkbox).toBeTruthy() }) + it("renders the task titles checkbox", () => { + const { getByTestId } = render() + const checkbox = getByTestId("enable-task-titles-checkbox") + expect(checkbox).toBeTruthy() + }) + it("displays the correct initial state", () => { const { getByTestId } = render() const checkbox = getByTestId("collapse-thinking-checkbox") as HTMLInputElement @@ -40,4 +47,18 @@ describe("UISettings", () => { rerender() expect(checkbox.checked).toBe(true) }) + + it("calls setCachedStateField when task titles checkbox is toggled", async () => { + const setCachedStateField = vi.fn() + const { getByTestId } = render( + , + ) + + const checkbox = getByTestId("enable-task-titles-checkbox") + fireEvent.click(checkbox) + + await waitFor(() => { + expect(setCachedStateField).toHaveBeenCalledWith("taskTitlesEnabled", true) + }) + }) }) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 542b2385c026..18528af56827 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -143,6 +143,8 @@ export interface ExtensionStateContextType extends ExtensionState { togglePinnedApiConfig: (configName: string) => void terminalCompressProgressBar?: boolean setTerminalCompressProgressBar: (value: boolean) => void + taskTitlesEnabled?: boolean + setTaskTitlesEnabled: (value: boolean) => void setHistoryPreviewCollapsed: (value: boolean) => void setReasoningBlockCollapsed: (value: boolean) => void autoCondenseContext: boolean @@ -242,6 +244,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalCompressProgressBar: true, // Default to compress progress bar output historyPreviewCollapsed: false, // Initialize the new state (default to expanded) reasoningBlockCollapsed: true, // Default to collapsed + taskTitlesEnabled: false, cloudUserInfo: null, cloudIsAuthenticated: false, cloudOrganizations: [], @@ -419,6 +422,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const contextValue: ExtensionStateContextType = { ...state, reasoningBlockCollapsed: state.reasoningBlockCollapsed ?? true, + taskTitlesEnabled: state.taskTitlesEnabled ?? false, didHydrateState, showWelcome, theme, @@ -537,6 +541,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })), setReasoningBlockCollapsed: (value) => setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })), + setTaskTitlesEnabled: (value) => setState((prevState) => ({ ...prevState, taskTitlesEnabled: value })), setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })), setAutoCondenseContext: (value) => setState((prevState) => ({ ...prevState, autoCondenseContext: value })), setAutoCondenseContextPercent: (value) => diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 65ffdfa0151e..9c3090ef4954 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -15,6 +15,9 @@ "export": "Exportar historial de tasques", "delete": "Eliminar tasca (Shift + Clic per ometre confirmació)", "condenseContext": "Condensar context de forma intel·ligent", + "addTitle": "Afegeix títol", + "editTitle": "Edita el títol", + "titlePlaceholder": "Afegeix un títol", "share": "Compartir tasca", "shareWithOrganization": "Compartir amb l'organització", "shareWithOrganizationDescription": "Només els membres de la teva organització poden accedir", diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 2aa6b7ad729b..a135960d8bb2 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -882,6 +882,10 @@ } }, "ui": { + "taskTitles": { + "label": "Habilita els títols de tasques editables", + "description": "Mostra i edita títols personalitzats per a les tasques al xat i a l'historial en lloc de només el text original de la tasca" + }, "collapseThinking": { "label": "Replega els missatges de pensament per defecte", "description": "Quan estigui activat, els blocs de pensament es replegaran per defecte fins que interactuïs amb ells" diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index a68bc69b932b..c697cced129a 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -16,6 +16,9 @@ "delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)", "share": "Aufgabe teilen", "condenseContext": "Kontext intelligent komprimieren", + "addTitle": "Titel hinzufügen", + "editTitle": "Titel bearbeiten", + "titlePlaceholder": "Titel hinzufügen", "shareWithOrganization": "Mit Organisation teilen", "shareWithOrganizationDescription": "Nur Mitglieder deiner Organisation können zugreifen", "sharePublicly": "Öffentlich teilen", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index a96e2151851e..a2349e78a75d 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -882,6 +882,10 @@ } }, "ui": { + "taskTitles": { + "label": "Bearbeitbare Aufgabentitel aktivieren", + "description": "Zeige und bearbeite benutzerdefinierte Titel für Aufgaben im Chat und im Verlauf statt nur den ursprünglichen Aufgabentext" + }, "collapseThinking": { "label": "Gedankenblöcke standardmäßig ausblenden", "description": "Wenn aktiviert, werden Gedankenblöcke standardmäßig ausgeblendet, bis du mit ihnen interagierst" diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 7bd0cde9d34d..84afa9810d44 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -11,6 +11,9 @@ "apiCost": "API Cost", "size": "Size", "condenseContext": "Intelligently condense context", + "addTitle": "Add title", + "editTitle": "Edit title", + "titlePlaceholder": "Add a title", "contextWindow": "Context Length", "closeAndStart": "Close task and start a new one", "export": "Export task history", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index aa3199e8e8b9..512c581d968b 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -39,6 +39,10 @@ "description": "Manage your slash commands to quickly execute custom workflows and actions. Learn more" }, "ui": { + "taskTitles": { + "label": "Enable editable task titles", + "description": "Show and edit custom titles for tasks in chat and history instead of only the original task text" + }, "collapseThinking": { "label": "Collapse Thinking messages by default", "description": "When enabled, thinking blocks will be collapsed by default until you interact with them" diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 20de4dcaba6f..546120c3a5d6 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -15,6 +15,9 @@ "export": "Exportar historial de tareas", "delete": "Eliminar tarea (Shift + Clic para omitir confirmación)", "condenseContext": "Condensar contexto de forma inteligente", + "addTitle": "Añadir título", + "editTitle": "Editar título", + "titlePlaceholder": "Añade un título", "share": "Compartir tarea", "shareWithOrganization": "Compartir con organización", "shareWithOrganizationDescription": "Solo los miembros de tu organización pueden acceder", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 44c1b9496d18..223a8cf7b557 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -882,6 +882,10 @@ } }, "ui": { + "taskTitles": { + "label": "Habilitar títulos de tareas editables", + "description": "Mostrar y editar títulos personalizados para las tareas en el chat y el historial en lugar de solo el texto original de la tarea" + }, "collapseThinking": { "label": "Colapsar mensajes de pensamiento por defecto", "description": "Cuando está activado, los bloques de pensamiento se colapsarán por defecto hasta que interactúes con ellos" diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 4d31f328ad3e..ce1649443ac0 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -15,6 +15,9 @@ "export": "Exporter l'historique des tâches", "delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)", "condenseContext": "Condenser intelligemment le contexte", + "addTitle": "Ajouter un titre", + "editTitle": "Modifier le titre", + "titlePlaceholder": "Ajouter un titre", "share": "Partager la tâche", "shareWithOrganization": "Partager avec l'organisation", "shareWithOrganizationDescription": "Seuls les membres de ton organisation peuvent accéder", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index cd2b3bef8769..4c54cfd4377a 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -882,6 +882,10 @@ } }, "ui": { + "taskTitles": { + "label": "Activer les titres de tâche modifiables", + "description": "Afficher et modifier des titres personnalisés pour les tâches dans le chat et l’historique, plutôt que seulement le texte original de la tâche" + }, "collapseThinking": { "label": "Réduire les messages de réflexion par défaut", "description": "Si activé, les blocs de réflexion seront réduits par défaut jusqu'à ce que vous interagissiez avec eux" diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 17b5ceef01df..8620bf5577d4 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -15,6 +15,9 @@ "export": "कार्य इतिहास निर्यात करें", "delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)", "condenseContext": "संदर्भ को बुद्धिमानी से संघनित करें", + "addTitle": "शीर्षक जोड़ें", + "editTitle": "शीर्षक संपादित करें", + "titlePlaceholder": "एक शीर्षक जोड़ें", "share": "कार्य साझा करें", "shareWithOrganization": "संगठन के साथ साझा करें", "shareWithOrganizationDescription": "केवल आपके संगठन के सदस्य पहुंच सकते हैं", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index d9d8184fbbd6..25150a97918b 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "संपादन योग्य कार्य शीर्षक सक्षम करें", + "description": "केवल मूल कार्य पाठ के बजाय चैट और इतिहास में कार्यों के लिए कस्टम शीर्षक दिखाएँ और संपादित करें" + }, "collapseThinking": { "label": "सोच संदेशों को डिफ़ॉल्ट रूप से संक्षिप्त करें", "description": "सक्षम होने पर, सोच ब्लॉक आपके द्वारा उनके साथ इंटरैक्ट करने तक डिफ़ॉल्ट रूप से संक्षिप्त रहेंगे" diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 532ab4130311..65084b0d69b0 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -11,6 +11,9 @@ "apiCost": "Biaya API", "size": "Ukuran", "condenseContext": "Kondensasi konteks secara cerdas", + "addTitle": "Tambahkan judul", + "editTitle": "Edit judul", + "titlePlaceholder": "Tambahkan judul", "contextWindow": "Panjang Konteks", "closeAndStart": "Tutup tugas dan mulai yang baru", "export": "Ekspor riwayat tugas", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 187f42958b69..0f10ed19c3fb 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -912,6 +912,10 @@ } }, "ui": { + "taskTitles": { + "label": "Aktifkan judul tugas yang dapat diedit", + "description": "Tampilkan dan edit judul kustom untuk tugas di chat dan riwayat alih-alih hanya teks tugas asli" + }, "collapseThinking": { "label": "Ciutkan pesan Berpikir secara default", "description": "Jika diaktifkan, blok berpikir akan diciutkan secara default sampai Anda berinteraksi dengannya" diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index fd3bf967d32d..141a72916af0 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -15,6 +15,9 @@ "export": "Esporta cronologia attività", "delete": "Elimina attività (Shift + Clic per saltare la conferma)", "condenseContext": "Condensa contesto in modo intelligente", + "addTitle": "Aggiungi titolo", + "editTitle": "Modifica titolo", + "titlePlaceholder": "Aggiungi un titolo", "share": "Condividi attività", "shareWithOrganization": "Condividi con l'organizzazione", "shareWithOrganizationDescription": "Solo i membri della tua organizzazione possono accedere", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 335877b0a878..73f9138b2625 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "Abilita titoli delle attività modificabili", + "description": "Mostra e modifica titoli personalizzati per le attività in chat e nella cronologia invece del solo testo originale dell’attività" + }, "collapseThinking": { "label": "Comprimi i messaggi di pensiero per impostazione predefinita", "description": "Se abilitato, i blocchi di pensiero verranno compressi per impostazione predefinita finché non interagisci con essi" diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 52e633b3ef56..1fc675b5088e 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -15,6 +15,9 @@ "export": "タスク履歴をエクスポート", "delete": "タスクを削除(Shift + クリックで確認をスキップ)", "condenseContext": "コンテキストをインテリジェントに圧縮", + "addTitle": "タイトルを追加", + "editTitle": "タイトルを編集", + "titlePlaceholder": "タイトルを追加", "share": "タスクを共有", "shareWithOrganization": "組織と共有", "shareWithOrganizationDescription": "組織のメンバーのみがアクセスできます", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index bce95eeab204..46ffc786f77a 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "編集可能なタスクタイトルを有効化", + "description": "元のタスクテキストだけでなく、チャットや履歴でカスタムタイトルを表示・編集できるようにします" + }, "collapseThinking": { "label": "デフォルトで思考メッセージを折りたたむ", "description": "有効にすると、操作するまで思考ブロックがデフォルトで折りたたまれます" diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 991955f1e8a1..954b0cbf5466 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -15,6 +15,9 @@ "export": "작업 기록 내보내기", "delete": "작업 삭제 (Shift + 클릭으로 확인 생략)", "condenseContext": "컨텍스트 지능적으로 압축", + "addTitle": "제목 추가", + "editTitle": "제목 편집", + "titlePlaceholder": "제목을 추가하세요", "share": "작업 공유", "shareWithOrganization": "조직과 공유", "shareWithOrganizationDescription": "조직 구성원만 액세스할 수 있습니다", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index f7aec2f4ced6..c0b3ef18e606 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "편집 가능한 작업 제목 사용", + "description": "채팅과 기록에서 작업의 기본 텍스트만이 아니라 사용자 지정 제목을 표시하고 편집합니다" + }, "collapseThinking": { "label": "기본적으로 생각 메시지 접기", "description": "활성화하면 상호 작용할 때까지 생각 블록이 기본적으로 접힙니다" diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index e5d779f70c2f..e583fe8ebf4d 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -15,6 +15,9 @@ "export": "Taakgeschiedenis exporteren", "delete": "Taak verwijderen (Shift + Klik om bevestiging over te slaan)", "condenseContext": "Context intelligent samenvatten", + "addTitle": "Titel toevoegen", + "editTitle": "Titel bewerken", + "titlePlaceholder": "Voeg een titel toe", "share": "Taak delen", "shareWithOrganization": "Delen met organisatie", "shareWithOrganizationDescription": "Alleen leden van je organisatie kunnen toegang krijgen", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index d5b246e22ae1..e24035a3c22b 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "Bewerkbare taaktitels inschakelen", + "description": "Toon en bewerk aangepaste titels voor taken in chat en geschiedenis in plaats van alleen de oorspronkelijke taaktekst" + }, "collapseThinking": { "label": "Denkberichten standaard samenvouwen", "description": "Indien ingeschakeld, worden denkblokken standaard samengevouwen totdat je ermee interageert" diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 26e8e59e2c6a..514f14424b84 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -15,6 +15,9 @@ "export": "Eksportuj historię zadań", "delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)", "condenseContext": "Inteligentnie skondensuj kontekst", + "addTitle": "Dodaj tytuł", + "editTitle": "Edytuj tytuł", + "titlePlaceholder": "Dodaj tytuł", "share": "Udostępnij zadanie", "shareWithOrganization": "Udostępnij organizacji", "shareWithOrganizationDescription": "Tylko członkowie twojej organizacji mogą uzyskać dostęp", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 385a38fe2c43..e060ec8ce9e8 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "Włącz edytowalne tytuły zadań", + "description": "Wyświetlaj i edytuj niestandardowe tytuły zadań na czacie i w historii zamiast wyłącznie oryginalnego tekstu zadania" + }, "collapseThinking": { "label": "Domyślnie zwijaj komunikaty o myśleniu", "description": "Gdy włączone, bloki myślenia będą domyślnie zwinięte, dopóki nie wejdziesz z nimi w interakcję" diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index b14a1bbaa79c..788726ec20ec 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -15,6 +15,9 @@ "export": "Exportar histórico de tarefas", "delete": "Excluir tarefa (Shift + Clique para pular confirmação)", "condenseContext": "Condensar contexto de forma inteligente", + "addTitle": "Adicionar título", + "editTitle": "Editar título", + "titlePlaceholder": "Adicione um título", "share": "Compartilhar tarefa", "shareWithOrganization": "Compartilhar com organização", "shareWithOrganizationDescription": "Apenas membros da sua organização podem acessar", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index be2ff89ff781..29cbb735185c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "Ativar títulos de tarefas editáveis", + "description": "Mostrar e editar títulos personalizados para tarefas no chat e no histórico em vez de apenas o texto original da tarefa" + }, "collapseThinking": { "label": "Recolher mensagens de pensamento por padrão", "description": "Quando ativado, os blocos de pensamento serão recolhidos por padrão até que você interaja com eles" diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index c5acc9f25a37..a5876fdc2741 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -15,6 +15,9 @@ "export": "Экспортировать историю задач", "delete": "Удалить задачу (Shift + клик для пропуска подтверждения)", "condenseContext": "Интеллектуально сжать контекст", + "addTitle": "Добавить заголовок", + "editTitle": "Редактировать заголовок", + "titlePlaceholder": "Добавьте заголовок", "share": "Поделиться задачей", "shareWithOrganization": "Поделиться с организацией", "shareWithOrganizationDescription": "Только члены вашей организации могут получить доступ", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index b429f01f4e2b..3cb56fb937a7 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "Включить редактируемые заголовки задач", + "description": "Показывать и редактировать пользовательские заголовки задач в чате и истории вместо только исходного текста задачи" + }, "collapseThinking": { "label": "Сворачивать сообщения о размышлениях по умолчанию", "description": "Если включено, блоки с размышлениями будут свернуты по умолчанию, пока вы не начнете с ними взаимодействовать" diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 8e2a1077dfbc..f8e962ee560d 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -15,6 +15,9 @@ "export": "Görev geçmişini dışa aktar", "delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)", "condenseContext": "Bağlamı akıllıca yoğunlaştır", + "addTitle": "Başlık ekle", + "editTitle": "Başlığı düzenle", + "titlePlaceholder": "Bir başlık ekle", "share": "Görevi paylaş", "shareWithOrganization": "Kuruluşla paylaş", "shareWithOrganizationDescription": "Sadece kuruluşunuzun üyeleri erişebilir", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 429599d7ea01..2389d786394d 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "Düzenlenebilir görev başlıklarını etkinleştir", + "description": "Sohbet ve geçmişte görevler için yalnızca özgün görev metni yerine özel başlıkları göster ve düzenle" + }, "collapseThinking": { "label": "Düşünme mesajlarını varsayılan olarak daralt", "description": "Etkinleştirildiğinde, düşünme blokları siz onlarla etkileşime girene kadar varsayılan olarak daraltılır" diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 221ced0377ae..007ef0d033df 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -15,6 +15,9 @@ "export": "Xuất lịch sử nhiệm vụ", "delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)", "condenseContext": "Cô đọng ngữ cảnh thông minh", + "addTitle": "Thêm tiêu đề", + "editTitle": "Sửa tiêu đề", + "titlePlaceholder": "Thêm một tiêu đề", "share": "Chia sẻ nhiệm vụ", "shareWithOrganization": "Chia sẻ với tổ chức", "shareWithOrganizationDescription": "Chỉ thành viên tổ chức của bạn mới có thể truy cập", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 35fd639ba662..00f3e99c6a78 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "Bật tiêu đề nhiệm vụ có thể chỉnh sửa", + "description": "Hiển thị và chỉnh sửa tiêu đề tùy chỉnh cho nhiệm vụ trong cuộc trò chuyện và lịch sử thay vì chỉ văn bản nhiệm vụ gốc" + }, "collapseThinking": { "label": "Thu gọn tin nhắn Suy nghĩ theo mặc định", "description": "Khi được bật, các khối suy nghĩ sẽ được thu gọn theo mặc định cho đến khi bạn tương tác với chúng" diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index cbb0e8633ce7..4687b87a645b 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -16,6 +16,9 @@ "delete": "删除任务(Shift + 点击跳过确认)", "share": "分享任务", "condenseContext": "智能压缩上下文", + "addTitle": "添加标题", + "editTitle": "编辑标题", + "titlePlaceholder": "添加一个标题", "shareWithOrganization": "与组织分享", "shareWithOrganizationDescription": "仅组织成员可访问", "sharePublicly": "公开分享", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index abb3e44637cb..bfb131330a6c 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "启用可编辑的任务标题", + "description": "在聊天和历史记录中显示并编辑任务的自定义标题,而不是仅显示原始任务文本" + }, "collapseThinking": { "label": "默认折叠“思考”消息", "description": "启用后,“思考”块将默认折叠,直到您与其交互" diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index dbc4b3dd9f8c..3173eb34401e 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -11,6 +11,9 @@ "apiCost": "API 費用", "size": "大小", "condenseContext": "智慧壓縮內容", + "addTitle": "新增標題", + "editTitle": "編輯標題", + "titlePlaceholder": "新增一個標題", "contextWindow": "上下文長度", "closeAndStart": "關閉現有工作並開始新工作", "export": "匯出工作記錄", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 91f7c5677a48..a397f99dde39 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -883,6 +883,10 @@ } }, "ui": { + "taskTitles": { + "label": "啟用可編輯的任務標題", + "description": "在聊天與歷史記錄中顯示並編輯任務的自訂標題,而非僅顯示原始任務文字" + }, "collapseThinking": { "label": "預設折疊“思考”訊息", "description": "啟用後,“思考”塊將預設折疊,直到您與其互動"