diff --git a/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx b/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx index 2a2696e7..69a2db9b 100644 --- a/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx +++ b/ccw/frontend/src/components/deepwiki/DocumentViewer.tsx @@ -3,12 +3,11 @@ // ======================================== // Displays DeepWiki documentation content with table of contents -import { useMemo, useState } from 'react'; +import { useMemo, useState, useCallback } from 'react'; import { useIntl } from 'react-intl'; import { FileText, Hash, Clock, Sparkles, AlertCircle, Link2, Check } from 'lucide-react'; import { Card } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; -import { Button } from '@/components/ui/Button'; import { cn } from '@/lib/utils'; import type { DeepWikiSymbol, DeepWikiDoc } from '@/hooks/useDeepWiki'; @@ -276,20 +275,41 @@ export function DocumentViewer({ diff --git a/ccw/frontend/src/components/hook/HookCard.tsx b/ccw/frontend/src/components/hook/HookCard.tsx index b10d69d9..27aa6666 100644 --- a/ccw/frontend/src/components/hook/HookCard.tsx +++ b/ccw/frontend/src/components/hook/HookCard.tsx @@ -30,6 +30,8 @@ export interface HookCardData { matcher?: string; command?: string; script?: string; + scope?: 'global' | 'project'; + index?: number; } export interface HookCardProps { diff --git a/ccw/frontend/src/components/layout/Sidebar.tsx b/ccw/frontend/src/components/layout/Sidebar.tsx index ba37f41d..d0c1f61f 100644 --- a/ccw/frontend/src/components/layout/Sidebar.tsx +++ b/ccw/frontend/src/components/layout/Sidebar.tsx @@ -28,6 +28,7 @@ import { FileSearch, ScrollText, Clock, + BookOpen, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/Button'; @@ -92,6 +93,7 @@ const navGroupDefinitions: NavGroupDef[] = [ icon: Brain, items: [ { path: '/memory', labelKey: 'navigation.main.memory', icon: Brain }, + { path: '/deepwiki', labelKey: 'navigation.main.deepwiki', icon: BookOpen }, { path: '/skills', labelKey: 'navigation.main.skills', icon: Sparkles }, { path: '/commands', labelKey: 'navigation.main.commands', icon: Terminal }, { path: '/settings/rules', labelKey: 'navigation.main.rules', icon: Shield }, diff --git a/ccw/frontend/src/hooks/useCli.ts b/ccw/frontend/src/hooks/useCli.ts index 534f1c96..062dc776 100644 --- a/ccw/frontend/src/hooks/useCli.ts +++ b/ccw/frontend/src/hooks/useCli.ts @@ -514,23 +514,33 @@ export function useToggleHook() { export function useDeleteHook() { const queryClient = useQueryClient(); + const projectPath = useWorkflowStore(selectProjectPath); const mutation = useMutation({ - mutationFn: (hookName: string) => deleteHook(hookName), - onMutate: async (hookName) => { + mutationFn: (hook: { name: string; scope?: 'global' | 'project'; trigger: string; index?: number }) => { + const scope = hook.scope || 'project'; + const hookIndex = hook.index ?? 0; + return deleteHook({ + projectPath: projectPath || undefined, + scope, + event: hook.trigger, + hookIndex, + }); + }, + onMutate: async (hook) => { await queryClient.cancelQueries({ queryKey: hooksKeys.all }); const previousHooks = queryClient.getQueryData(hooksKeys.lists()); queryClient.setQueryData(hooksKeys.lists(), (old) => { if (!old) return old; return { - hooks: old.hooks.filter((h) => h.name !== hookName), + hooks: old.hooks.filter((h) => h.name !== hook.name), }; }); return { previousHooks }; }, - onError: (_error, _hookName, context) => { + onError: (_error, _hook, context) => { if (context?.previousHooks) { queryClient.setQueryData(hooksKeys.lists(), context.previousHooks); } diff --git a/ccw/frontend/src/lib/api.ts b/ccw/frontend/src/lib/api.ts index 3407492d..24cc6045 100644 --- a/ccw/frontend/src/lib/api.ts +++ b/ccw/frontend/src/lib/api.ts @@ -4407,9 +4407,15 @@ export async function updateHookConfig( /** * Delete a hook */ -export async function deleteHook(hookName: string): Promise { - return fetchApi(`/api/hooks/delete/${encodeURIComponent(hookName)}`, { +export async function deleteHook(params: { + projectPath?: string; + scope: 'global' | 'project'; + event: string; + hookIndex: number; +}): Promise<{ success: boolean }> { + return fetchApi<{ success: boolean }>('/api/hooks', { method: 'DELETE', + body: JSON.stringify(params), }); } diff --git a/ccw/frontend/src/locales/en/navigation.json b/ccw/frontend/src/locales/en/navigation.json index ea8accad..eaa14fb9 100644 --- a/ccw/frontend/src/locales/en/navigation.json +++ b/ccw/frontend/src/locales/en/navigation.json @@ -23,6 +23,7 @@ "skills": "Skills", "commands": "Commands", "memory": "Memory", + "deepwiki": "DeepWiki", "prompts": "Prompt History", "settings": "Settings", "mcp": "MCP Servers", diff --git a/ccw/frontend/src/locales/zh/navigation.json b/ccw/frontend/src/locales/zh/navigation.json index e09fb9da..f0692750 100644 --- a/ccw/frontend/src/locales/zh/navigation.json +++ b/ccw/frontend/src/locales/zh/navigation.json @@ -23,6 +23,7 @@ "skills": "技能", "commands": "命令", "memory": "记忆", + "deepwiki": "DeepWiki", "prompts": "提示历史", "settings": "设置", "mcp": "MCP 服务器", diff --git a/ccw/frontend/src/pages/DeepWikiPage.tsx b/ccw/frontend/src/pages/DeepWikiPage.tsx index abd2f8d6..4e3056e5 100644 --- a/ccw/frontend/src/pages/DeepWikiPage.tsx +++ b/ccw/frontend/src/pages/DeepWikiPage.tsx @@ -177,6 +177,7 @@ export function DeepWikiPage() { symbols={symbols} isLoading={docLoading} error={docError} + filePath={selectedFile ?? undefined} /> )} diff --git a/ccw/frontend/src/pages/HookManagerPage.tsx b/ccw/frontend/src/pages/HookManagerPage.tsx index aaa603e3..782b4482 100644 --- a/ccw/frontend/src/pages/HookManagerPage.tsx +++ b/ccw/frontend/src/pages/HookManagerPage.tsx @@ -53,7 +53,7 @@ function isHookTriggerType(value: string): value is HookTriggerType { return ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop', 'Notification', 'SubagentStart', 'SubagentStop', 'PreCompact', 'SessionEnd', 'PostToolUseFailure', 'PermissionRequest'].includes(value); } -function toHookCardData(hook: { name: string; description?: string; enabled: boolean; trigger: string; matcher?: string; command?: string; script?: string }): HookCardData | null { +function toHookCardData(hook: { name: string; description?: string; enabled: boolean; trigger: string; matcher?: string; command?: string; script?: string; scope?: 'global' | 'project'; index?: number }): HookCardData | null { if (!isHookTriggerType(hook.trigger)) { return null; } @@ -64,6 +64,8 @@ function toHookCardData(hook: { name: string; description?: string; enabled: boo trigger: hook.trigger, matcher: hook.matcher, command: hook.command || hook.script, + scope: hook.scope, + index: hook.index, }; } @@ -200,8 +202,19 @@ export function HookManagerPage() { }; const handleDeleteClick = async (hookName: string) => { + // Find the hook in filteredHooks to get scope and index + const hook = filteredHooks.find(h => h.name === hookName); + if (!hook) { + console.error('Hook not found:', hookName); + return; + } try { - await deleteHook(hookName); + await deleteHook({ + name: hook.name, + scope: hook.scope, + trigger: hook.trigger, + index: hook.index, + }); } catch (error) { console.error('Failed to delete hook:', error); }