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) => { +