From f677db3501b844c733586f9b2876e96af605a1ce Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 30 Jul 2025 03:20:12 +0000 Subject: [PATCH] feat: implement enhanced chat history management with favorites and custom naming - Add isFavorite and customName fields to HistoryItem type with backward compatibility - Create FavoriteButton component with star icon and toggle functionality - Create RenameButton component with inline editing capability - Update TaskItemHeader to include favorite and rename buttons - Add favorites filtering toggle to HistoryView - Enhance useTaskSearch hook with favorites filtering and custom name search - Update webview message handlers for toggleTaskFavorite and renameTask operations - Add comprehensive test coverage for new components and functionality - Maintain backward compatibility with existing history data Resolves #6410 --- packages/types/src/history.ts | 2 + src/core/task-persistence/taskMetadata.ts | 2 + src/core/webview/webviewMessageHandler.ts | 52 ++++++++ src/i18n/locales/en/common.json | 2 + src/shared/WebviewMessage.ts | 4 + .../src/components/history/FavoriteButton.tsx | 28 ++++ .../src/components/history/HistoryView.tsx | 27 ++++ .../src/components/history/RenameButton.tsx | 80 ++++++++++++ .../src/components/history/TaskItem.tsx | 14 +- .../src/components/history/TaskItemHeader.tsx | 26 +++- .../history/__tests__/FavoriteButton.spec.tsx | 81 ++++++++++++ .../history/__tests__/RenameButton.spec.tsx | 106 +++++++++++++++ .../history/__tests__/useTaskSearch.spec.tsx | 122 ++++++++++++++++++ .../src/components/history/useTaskSearch.ts | 33 ++++- 14 files changed, 572 insertions(+), 7 deletions(-) create mode 100644 webview-ui/src/components/history/FavoriteButton.tsx create mode 100644 webview-ui/src/components/history/RenameButton.tsx create mode 100644 webview-ui/src/components/history/__tests__/FavoriteButton.spec.tsx create mode 100644 webview-ui/src/components/history/__tests__/RenameButton.spec.tsx diff --git a/packages/types/src/history.ts b/packages/types/src/history.ts index ace134566e..5696bb8bd0 100644 --- a/packages/types/src/history.ts +++ b/packages/types/src/history.ts @@ -17,6 +17,8 @@ export const historyItemSchema = z.object({ size: z.number().optional(), workspace: z.string().optional(), mode: z.string().optional(), + isFavorite: z.boolean().optional(), + customName: z.string().optional(), }) export type HistoryItem = z.infer diff --git a/src/core/task-persistence/taskMetadata.ts b/src/core/task-persistence/taskMetadata.ts index 7b93b5c14a..a77724c57d 100644 --- a/src/core/task-persistence/taskMetadata.ts +++ b/src/core/task-persistence/taskMetadata.ts @@ -95,6 +95,8 @@ export async function taskMetadata({ size: taskDirSize, workspace, mode, + isFavorite: false, // Initialize as not favorited + customName: undefined, // Initialize with no custom name } return { historyItem, tokenUsage } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 763e118125..5da08ed26a 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2558,5 +2558,57 @@ export const webviewMessageHandler = async ( } break } + case "toggleTaskFavorite": { + if (message.taskId) { + try { + // Get the task and update its favorite status + const { historyItem } = await provider.getTaskWithId(message.taskId) + if (historyItem) { + // Toggle the favorite status + const updatedHistoryItem = { + ...historyItem, + isFavorite: !historyItem.isFavorite, + } + + // Update the task metadata + await provider.updateTaskHistory(updatedHistoryItem) + + // Refresh the webview state to reflect the change + await provider.postStateToWebview() + } + } catch (error) { + provider.log( + `Error toggling task favorite: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) + vscode.window.showErrorMessage(t("common:errors.toggle_favorite_failed")) + } + } + break + } + case "renameTask": { + if (message.taskId && message.newName !== undefined) { + try { + // Get the task and update its custom name + const { historyItem } = await provider.getTaskWithId(message.taskId) + if (historyItem) { + // Update the custom name (empty string means remove custom name) + const updatedHistoryItem = { + ...historyItem, + customName: message.newName.trim() || undefined, + } + + // Update the task metadata + await provider.updateTaskHistory(updatedHistoryItem) + + // Refresh the webview state to reflect the change + await provider.postStateToWebview() + } + } catch (error) { + provider.log(`Error renaming task: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) + vscode.window.showErrorMessage(t("common:errors.rename_task_failed")) + } + } + break + } } } diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 114e129f45..fde416585e 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -78,6 +78,8 @@ "command_already_exists": "Command \"{{commandName}}\" already exists", "create_command_failed": "Failed to create command", "command_template_content": "---\ndescription: \"Brief description of what this command does\"\n---\n\nThis is a new slash command. Edit this file to customize the command behavior.", + "toggle_favorite_failed": "Failed to toggle task favorite status", + "rename_task_failed": "Failed to rename task", "claudeCode": { "processExited": "Claude Code process exited with code {{exitCode}}.", "errorOutput": "Error output: {{output}}", diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index a91d1af7ba..f2872e36c2 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -208,6 +208,8 @@ export interface WebviewMessage { | "deleteCommand" | "createCommand" | "insertTextIntoTextarea" + | "toggleTaskFavorite" + | "renameTask" text?: string editedMessageContent?: string tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account" @@ -270,6 +272,8 @@ export interface WebviewMessage { codebaseIndexGeminiApiKey?: string codebaseIndexMistralApiKey?: string } + taskId?: string + newName?: string } export const checkoutDiffPayloadSchema = z.object({ diff --git a/webview-ui/src/components/history/FavoriteButton.tsx b/webview-ui/src/components/history/FavoriteButton.tsx new file mode 100644 index 0000000000..ac7795ca2d --- /dev/null +++ b/webview-ui/src/components/history/FavoriteButton.tsx @@ -0,0 +1,28 @@ +import React from "react" +import { StandardTooltip } from "@/components/ui" + +interface FavoriteButtonProps { + isFavorite: boolean + onToggleFavorite: () => void + className?: string +} + +export const FavoriteButton: React.FC = ({ isFavorite, onToggleFavorite, className = "" }) => { + return ( + + + + ) +} diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index 2f156d0418..8fbbe7e01b 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -37,6 +37,10 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { setLastNonRelevantSort, showAllWorkspaces, setShowAllWorkspaces, + showFavoritesOnly, + setShowFavoritesOnly, + handleToggleFavorite, + handleRename, } = useTaskSearch() const { t } = useAppTranslation() @@ -152,6 +156,27 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { +