diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 9a17834ced7..a56a125d08c 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -193,6 +193,7 @@ export const globalSettingsSchema = z.object({ * @default "send" */ enterBehavior: z.enum(["send", "newline"]).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 b4d84cb9a51..93ac9bdf4c1 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().max(255).optional(), task: z.string(), tokensIn: z.number(), tokensOut: z.number(), diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 86d8b2ddbbe..44cdd60b0ae 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -262,6 +262,7 @@ export type ExtensionState = Pick< | "includeCurrentCost" | "maxGitStatusFiles" | "requestDelaySeconds" + | "taskTitlesEnabled" > & { version: string clineMessages: ClineMessage[] @@ -302,6 +303,7 @@ export type ExtensionState = Pick< renderContext: "sidebar" | "editor" settingsImportedAt?: number historyPreviewCollapsed?: boolean + taskTitlesEnabled?: boolean cloudUserInfo: CloudUserInfo | null cloudIsAuthenticated: boolean @@ -389,6 +391,7 @@ export interface WebviewMessage { | "importSettings" | "exportSettings" | "resetState" + | "setTaskTitle" | "flushRouterModels" | "requestRouterModels" | "requestOpenAiModels" diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 33fa12ca78c..e80c0afbdeb 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2003,6 +2003,7 @@ export class ClineProvider historyPreviewCollapsed, reasoningBlockCollapsed, enterBehavior, + taskTitlesEnabled, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -2093,6 +2094,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, @@ -2409,6 +2411,7 @@ export class ClineProvider historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, enterBehavior: stateValues.enterBehavior ?? "send", + taskTitlesEnabled: stateValues.taskTitlesEnabled ?? false, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -2479,13 +2482,19 @@ export class ClineProvider const existingItemIndex = history.findIndex((h) => h.id === item.id) if (existingItemIndex !== -1) { - // Preserve existing metadata (e.g., delegation fields) unless explicitly overwritten. - // This prevents loss of status/awaitingChildId/delegatedToId when tasks are reopened, - // terminated, or when routine message persistence occurs. - history[existingItemIndex] = { - ...history[existingItemIndex], + const existingItem = history[existingItemIndex] + const hasTitleProp = Object.prototype.hasOwnProperty.call(item, "title") + // Preserve existing metadata unless explicitly overwritten. + // Title is only cleared when explicitly provided (including undefined). + 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 3f820aace15..8c76ba08a2c 100644 --- a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts @@ -1230,4 +1230,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/taskTitleHandler.ts b/src/core/webview/taskTitleHandler.ts new file mode 100644 index 00000000000..a3ded490813 --- /dev/null +++ b/src/core/webview/taskTitleHandler.ts @@ -0,0 +1,102 @@ +import type { HistoryItem } from "@roo-code/types" + +import type { ClineProvider } from "./ClineProvider" +import type { WebviewMessage } from "../../shared/WebviewMessage" + +const MAX_TITLE_LENGTH = 255 + +/** + * Sanitizes and normalizes a title string: + * - Removes control characters + * - Normalizes whitespace + * - Trims leading/trailing whitespace + * - Truncates if too long + * - Returns undefined for empty strings + */ +function normalizeTitle(title: string | undefined | null, ids?: string[]): string | undefined { + const rawTitle = title ?? "" + + // Sanitize: remove control characters and normalize whitespace + const sanitized = rawTitle + // eslint-disable-next-line no-control-regex + .replace(/[\x00-\x1F\x7F-\x9F]/g, "") // Remove control characters + .replace(/\s+/g, " ") // Normalize whitespace + .trim() + + // Clear empty titles + if (sanitized.length === 0) { + return undefined + } + + // Truncate if too long + if (sanitized.length > MAX_TITLE_LENGTH) { + const truncated = sanitized.slice(0, MAX_TITLE_LENGTH).trim() + console.warn( + `[setTaskTitle] Title truncated from ${sanitized.length} to ${MAX_TITLE_LENGTH} chars for task(s): ${ids?.join(", ") ?? "unknown"}`, + ) + return truncated + } + + return sanitized +} + +/** + * Handles the setTaskTitle webview message. + * Updates task titles for one or more history items, with deduplication and no-op detection. + */ +export async function handleSetTaskTitle(provider: ClineProvider, message: WebviewMessage): Promise { + // 1. Validate and deduplicate incoming task IDs + 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) { + return + } + + // 2. Normalize the incoming title (with sanitization and truncation) + const normalizedTitle = normalizeTitle(message.text, ids) + + // 3. Get task history from state + const { taskHistory } = await provider.getState() + if (!Array.isArray(taskHistory) || taskHistory.length === 0) { + return + } + + // 4. Create a map for O(1) lookups + const historyById = new Map(taskHistory.map((item) => [item.id, item] as const)) + + // 5. Process each ID, skipping no-ops + let hasUpdates = false + + 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 + } + + // Normalize existing title for comparison + const normalizedExistingTitle = + existingItem.title && existingItem.title.trim().length > 0 ? existingItem.title.trim() : undefined + + // Skip if title is unchanged + if (normalizedExistingTitle === normalizedTitle) { + continue + } + + // Update the history item + const updatedItem: HistoryItem = { + ...existingItem, + title: normalizedTitle, + } + + await provider.updateTaskHistory(updatedItem) + hasUpdates = true + } + + // 6. Sync webview state if there were changes + if (hasUpdates) { + await provider.postStateToWebview() + } +} diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index dbeb380d162..cfa96fa0cd2 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -32,6 +32,7 @@ import { ClineProvider } from "./ClineProvider" import { BrowserSessionPanelManager } from "./BrowserSessionPanelManager" import { handleCheckpointRestoreOperation } from "./checkpointRestoreHandler" import { generateErrorDiagnostics } from "./diagnosticsHandler" +import { handleSetTaskTitle } from "./taskTitleHandler" import { changeLanguage, t } from "../../i18n" import { Package } from "../../shared/package" import { type RouterName, toRouterName } from "../../shared/api" @@ -728,6 +729,9 @@ export const webviewMessageHandler = async ( vscode.window.showErrorMessage(t("common:errors.share_task_failed")) } break + case "setTaskTitle": + await handleSetTaskTitle(provider, message) + break case "showTaskWithId": provider.showTaskWithId(message.text!) break @@ -1616,7 +1620,6 @@ export const webviewMessageHandler = async ( await updateGlobalState("hasOpenedModeSelector", message.bool ?? true) await provider.postStateToWebview() break - case "toggleApiConfigPin": if (message.text) { const currentPinned = getGlobalState("pinnedApiConfigs") ?? {} diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 5dca11b9634..be88c50e236 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -1,4 +1,5 @@ -import { memo, useEffect, useRef, useState, useMemo } from "react" +import { memo, useCallback, useEffect, useMemo, 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" @@ -12,6 +13,7 @@ import { HardDriveUpload, FoldVertical, Globe, + Pencil, } from "lucide-react" import prettyBytes from "pretty-bytes" @@ -26,6 +28,7 @@ import { StandardTooltip, Button } 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" @@ -67,7 +70,13 @@ const TaskHeader = ({ todos, }: TaskHeaderProps) => { const { t } = useTranslation() - const { apiConfiguration, currentTaskItem, clineMessages, isBrowserSessionActive } = useExtensionState() + const { + apiConfiguration, + currentTaskItem, + clineMessages, + isBrowserSessionActive, + taskTitlesEnabled = false, + } = useExtensionState() const { id: modelId, info: model } = useSelectedModel(apiConfiguration) const [isTaskExpanded, setIsTaskExpanded] = useState(false) const [showLongRunningTaskMessage, setShowLongRunningTaskMessage] = useState(false) @@ -99,6 +108,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 @@ -124,7 +230,96 @@ const TaskHeader = ({ /> ) + const renderTitleEditor = () => ( +
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 renderTitleAction = () => { + if (!taskTitlesEnabled || !currentTaskItem || isEditingTitle) { + return null + } + + const tooltipKey = currentTitle.length > 0 ? "chat:task.editTitle" : "chat:task.addTitle" + + return ( + + + + ) + } + + const renderCollapsedTitleContent = () => { + if (!taskTitlesEnabled || !currentTaskItem) { + return ( + + + + ) + } + + if (isEditingTitle) { + return renderTitleEditor() + } + + if (currentTitle.length > 0) { + return ( + + {currentTitle} + + ) + } + + return ( + + + + ) + } + + const renderExpandedTitleContent = () => { + if (!taskTitlesEnabled || !currentTaskItem) { + return null + } + + if (isEditingTitle) { + return renderTitleEditor() + } + + if (currentTitle.length > 0) { + return ( + + {currentTitle} + + ) + } + + return null + } + const hasTodos = todos && Array.isArray(todos) && todos.length > 0 + const expandedTitleContent = renderExpandedTitleContent() return (
@@ -175,13 +370,21 @@ const TaskHeader = ({
- {isTaskExpanded && {t("chat:task.title")}} - {!isTaskExpanded && ( -
+ {isTaskExpanded ? ( +
+
+ {t("chat:task.title")} + {renderTitleAction()} +
+ {expandedTitleContent ? ( +
{expandedTitleContent}
+ ) : null} +
+ ) : ( +
- - - +
{renderCollapsedTitleContent()}
+ {renderTitleAction()}
)}
@@ -352,7 +555,7 @@ const TaskHeader = ({ {t("chat:task.contextWindow")} -
+
({ @@ -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,8 @@ let mockExtensionState: { apiConfiguration: ProviderSettings currentTaskItem: { id: string } | null clineMessages: any[] + taskTitlesEnabled: boolean + isBrowserSessionActive: boolean } = { apiConfiguration: { apiProvider: "anthropic", @@ -45,6 +59,8 @@ let mockExtensionState: { } as ProviderSettings, currentTaskItem: { id: "test-task-id" }, clineMessages: [], + taskTitlesEnabled: true, + isBrowserSessionActive: false, } // Mock the ExtensionStateContext @@ -89,6 +105,20 @@ 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, + isBrowserSessionActive: false, + } + }) const defaultProps: TaskHeaderProps = { task: { type: "say", ts: Date.now(), text: "Test task", images: [] }, tokensIn: 100, @@ -180,6 +210,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-input")).not.toBeInTheDocument() + }) + describe("DismissibleUpsell behavior", () => { beforeEach(() => { vi.useFakeTimers() @@ -192,6 +247,8 @@ describe("TaskHeader", () => { } as ProviderSettings, currentTaskItem: { id: "test-task-id" }, clineMessages: [], + taskTitlesEnabled: true, + isBrowserSessionActive: false, } }) diff --git a/webview-ui/src/components/history/TaskItem.tsx b/webview-ui/src/components/history/TaskItem.tsx index 087a790013b..fdfea3983a3 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 3969985b98a..f8894617e30 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 d86007e80fe..6ae051fdb56 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -214,6 +214,7 @@ const SettingsView = forwardRef(({ onDone, t includeCurrentTime, includeCurrentCost, maxGitStatusFiles, + taskTitlesEnabled, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -423,6 +424,7 @@ const SettingsView = forwardRef(({ onDone, t condensingApiConfigId: condensingApiConfigId || "", includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + taskTitlesEnabled: taskTitlesEnabled ?? false, enterBehavior: enterBehavior ?? "send", includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, @@ -910,6 +912,7 @@ const SettingsView = forwardRef(({ onDone, t {/* UI Section */} {renderTab === "ui" && ( { + taskTitlesEnabled: boolean reasoningBlockCollapsed: boolean enterBehavior: "send" | "newline" setCachedStateField: SetCachedStateField } export const UISettings = ({ + taskTitlesEnabled, reasoningBlockCollapsed, enterBehavior, setCachedStateField, @@ -28,6 +30,12 @@ export const UISettings = ({ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0 return isMac ? "⌘" : "Ctrl" }, []) + const handleTaskTitlesEnabledChange = (value: boolean) => { + setCachedStateField("taskTitlesEnabled", value) + telemetryClient.capture("ui_settings_task_titles_enabled_changed", { + enabled: value, + }) + } const handleReasoningBlockCollapsedChange = (value: boolean) => { setCachedStateField("reasoningBlockCollapsed", value) @@ -54,6 +62,18 @@ export const UISettings = ({
+
+ 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, enterBehavior: "send" as const, setCachedStateField: vi.fn(), @@ -15,6 +16,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 @@ -41,4 +48,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 d2ff79a8e02..ed4bb8eb777 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -148,6 +148,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 enterBehavior?: "send" | "newline" @@ -253,6 +255,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode historyPreviewCollapsed: false, // Initialize the new state (default to expanded) reasoningBlockCollapsed: true, // Default to collapsed enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline + taskTitlesEnabled: false, cloudUserInfo: null, cloudIsAuthenticated: false, cloudOrganizations: [], @@ -454,6 +457,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const contextValue: ExtensionStateContextType = { ...state, reasoningBlockCollapsed: state.reasoningBlockCollapsed ?? true, + taskTitlesEnabled: state.taskTitlesEnabled ?? false, didHydrateState, showWelcome, theme, @@ -574,6 +578,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })), enterBehavior: state.enterBehavior ?? "send", setEnterBehavior: (value) => setState((prevState) => ({ ...prevState, enterBehavior: 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 c989cc030bd..9fb0a0922ae 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 8a0e94d2859..f3396ade15e 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -971,6 +971,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 254052278bd..e38f3eef98a 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 a559a18593f..c875164ca7a 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -971,6 +971,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 d92957916b3..c2d67af2708 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 fc64ad18510..56456f9b935 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -70,6 +70,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 ae0d02232f6..029fe4d90d2 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 590fbcae20e..e5bc71493b3 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -971,6 +971,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 56eec51e96b..cfee98cf827 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 8ad9f1791fc..1630aaa7bd9 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -971,6 +971,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 a58e3abff44..e6760a83230 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 8260e9c24b1..421612cff90 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -972,6 +972,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 c00a6d5ab3b..0dc0d2fd366 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 50850b74cca..abbf19880d3 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -1001,6 +1001,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 3fe64691356..075929725ea 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 d307ef1aee3..c0571629b5e 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -972,6 +972,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 68f3597396c..0d5cc932fb0 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 8c8707caaa8..86737ba6aba 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -972,6 +972,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 a9934a4fd3d..3fc3a10da60 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 4ce757bfcbc..c24286755de 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -972,6 +972,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 d1747ed529d..8d63e35271e 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 d2988b8e04e..e34d0596a0b 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -972,6 +972,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 acdd23f06be..0056d493780 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 380c7a895ce..756f9677902 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -972,6 +972,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 30a86559fa4..c275ea98d1e 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 387891910fd..f2be96a8349 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -972,6 +972,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 008de4397d9..41467acb73d 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 430e0969a84..67452f42da7 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -972,6 +972,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 f88ed480c0b..48e32df242c 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 3feb8e2a1ee..87aede959c4 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -972,6 +972,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 c5724152f88..a3db7a98411 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 141ef12b87f..ebd2b9563d0 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -972,6 +972,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 e13124379e7..aff3924cbcf 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 f301f17e08c..95b6b5bac31 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -972,6 +972,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 5d20352c573..57387b99777 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 d57b05d2f49..a5d65986ec7 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -972,6 +972,10 @@ } }, "ui": { + "taskTitles": { + "label": "啟用可編輯的任務標題", + "description": "在聊天與歷史記錄中顯示並編輯任務的自訂標題,而非僅顯示原始任務文字" + }, "collapseThinking": { "label": "預設折疊「思考」訊息", "description": "啟用後,「思考」塊將預設折疊,直到您與其互動"