mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-06 16:31:12 +08:00
feat: 增加DeepWiki页面和侧边栏导航支持,更新Hook管理功能以支持作用域和索引
This commit is contained in:
@@ -3,12 +3,11 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// Displays DeepWiki documentation content with table of contents
|
// 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 { useIntl } from 'react-intl';
|
||||||
import { FileText, Hash, Clock, Sparkles, AlertCircle, Link2, Check } from 'lucide-react';
|
import { FileText, Hash, Clock, Sparkles, AlertCircle, Link2, Check } from 'lucide-react';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import { Badge } from '@/components/ui/Badge';
|
import { Badge } from '@/components/ui/Badge';
|
||||||
import { Button } from '@/components/ui/Button';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import type { DeepWikiSymbol, DeepWikiDoc } from '@/hooks/useDeepWiki';
|
import type { DeepWikiSymbol, DeepWikiDoc } from '@/hooks/useDeepWiki';
|
||||||
|
|
||||||
@@ -276,20 +275,41 @@ export function DocumentViewer({
|
|||||||
</h4>
|
</h4>
|
||||||
<nav className="space-y-1">
|
<nav className="space-y-1">
|
||||||
{symbols.map(symbol => (
|
{symbols.map(symbol => (
|
||||||
<a
|
<div
|
||||||
key={symbol.name}
|
key={symbol.name}
|
||||||
href={`#${symbol.anchor.replace('#', '')}`}
|
className="group flex items-center gap-1"
|
||||||
className={cn(
|
|
||||||
'block text-xs py-1.5 px-2 rounded transition-colors',
|
|
||||||
'text-muted-foreground hover:text-foreground hover:bg-muted/50',
|
|
||||||
'font-mono'
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<span className={cn('mr-1', getSymbolTypeColor(symbol.type))}>
|
<a
|
||||||
{getSymbolTypeIcon(symbol.type)}
|
href={`#${symbol.anchor.replace('#', '')}`}
|
||||||
</span>
|
className={cn(
|
||||||
{symbol.name}
|
'flex-1 text-xs py-1.5 px-2 rounded transition-colors',
|
||||||
</a>
|
'text-muted-foreground hover:text-foreground hover:bg-muted/50',
|
||||||
|
'font-mono'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={cn('mr-1', getSymbolTypeColor(symbol.type))}>
|
||||||
|
{getSymbolTypeIcon(symbol.type)}
|
||||||
|
</span>
|
||||||
|
{symbol.name}
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => copyDeepLink(symbol.name, symbol.anchor)}
|
||||||
|
className={cn(
|
||||||
|
'opacity-0 group-hover:opacity-100 p-1 rounded transition-all',
|
||||||
|
'hover:bg-muted/50',
|
||||||
|
copiedSymbol === symbol.name
|
||||||
|
? 'text-green-500'
|
||||||
|
: 'text-muted-foreground hover:text-foreground'
|
||||||
|
)}
|
||||||
|
title={copiedSymbol === symbol.name ? 'Copied!' : 'Copy deep link'}
|
||||||
|
>
|
||||||
|
{copiedSymbol === symbol.name ? (
|
||||||
|
<Check className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<Link2 className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export interface HookCardData {
|
|||||||
matcher?: string;
|
matcher?: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
script?: string;
|
script?: string;
|
||||||
|
scope?: 'global' | 'project';
|
||||||
|
index?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HookCardProps {
|
export interface HookCardProps {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
FileSearch,
|
FileSearch,
|
||||||
ScrollText,
|
ScrollText,
|
||||||
Clock,
|
Clock,
|
||||||
|
BookOpen,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
@@ -92,6 +93,7 @@ const navGroupDefinitions: NavGroupDef[] = [
|
|||||||
icon: Brain,
|
icon: Brain,
|
||||||
items: [
|
items: [
|
||||||
{ path: '/memory', labelKey: 'navigation.main.memory', icon: Brain },
|
{ 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: '/skills', labelKey: 'navigation.main.skills', icon: Sparkles },
|
||||||
{ path: '/commands', labelKey: 'navigation.main.commands', icon: Terminal },
|
{ path: '/commands', labelKey: 'navigation.main.commands', icon: Terminal },
|
||||||
{ path: '/settings/rules', labelKey: 'navigation.main.rules', icon: Shield },
|
{ path: '/settings/rules', labelKey: 'navigation.main.rules', icon: Shield },
|
||||||
|
|||||||
@@ -514,23 +514,33 @@ export function useToggleHook() {
|
|||||||
|
|
||||||
export function useDeleteHook() {
|
export function useDeleteHook() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const projectPath = useWorkflowStore(selectProjectPath);
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (hookName: string) => deleteHook(hookName),
|
mutationFn: (hook: { name: string; scope?: 'global' | 'project'; trigger: string; index?: number }) => {
|
||||||
onMutate: async (hookName) => {
|
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 });
|
await queryClient.cancelQueries({ queryKey: hooksKeys.all });
|
||||||
const previousHooks = queryClient.getQueryData<HooksResponse>(hooksKeys.lists());
|
const previousHooks = queryClient.getQueryData<HooksResponse>(hooksKeys.lists());
|
||||||
|
|
||||||
queryClient.setQueryData<HooksResponse>(hooksKeys.lists(), (old) => {
|
queryClient.setQueryData<HooksResponse>(hooksKeys.lists(), (old) => {
|
||||||
if (!old) return old;
|
if (!old) return old;
|
||||||
return {
|
return {
|
||||||
hooks: old.hooks.filter((h) => h.name !== hookName),
|
hooks: old.hooks.filter((h) => h.name !== hook.name),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return { previousHooks };
|
return { previousHooks };
|
||||||
},
|
},
|
||||||
onError: (_error, _hookName, context) => {
|
onError: (_error, _hook, context) => {
|
||||||
if (context?.previousHooks) {
|
if (context?.previousHooks) {
|
||||||
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
|
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4407,9 +4407,15 @@ export async function updateHookConfig(
|
|||||||
/**
|
/**
|
||||||
* Delete a hook
|
* Delete a hook
|
||||||
*/
|
*/
|
||||||
export async function deleteHook(hookName: string): Promise<void> {
|
export async function deleteHook(params: {
|
||||||
return fetchApi<void>(`/api/hooks/delete/${encodeURIComponent(hookName)}`, {
|
projectPath?: string;
|
||||||
|
scope: 'global' | 'project';
|
||||||
|
event: string;
|
||||||
|
hookIndex: number;
|
||||||
|
}): Promise<{ success: boolean }> {
|
||||||
|
return fetchApi<{ success: boolean }>('/api/hooks', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify(params),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"skills": "Skills",
|
"skills": "Skills",
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"memory": "Memory",
|
"memory": "Memory",
|
||||||
|
"deepwiki": "DeepWiki",
|
||||||
"prompts": "Prompt History",
|
"prompts": "Prompt History",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"mcp": "MCP Servers",
|
"mcp": "MCP Servers",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"skills": "技能",
|
"skills": "技能",
|
||||||
"commands": "命令",
|
"commands": "命令",
|
||||||
"memory": "记忆",
|
"memory": "记忆",
|
||||||
|
"deepwiki": "DeepWiki",
|
||||||
"prompts": "提示历史",
|
"prompts": "提示历史",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"mcp": "MCP 服务器",
|
"mcp": "MCP 服务器",
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ export function DeepWikiPage() {
|
|||||||
symbols={symbols}
|
symbols={symbols}
|
||||||
isLoading={docLoading}
|
isLoading={docLoading}
|
||||||
error={docError}
|
error={docError}
|
||||||
|
filePath={selectedFile ?? undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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);
|
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)) {
|
if (!isHookTriggerType(hook.trigger)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,8 @@ function toHookCardData(hook: { name: string; description?: string; enabled: boo
|
|||||||
trigger: hook.trigger,
|
trigger: hook.trigger,
|
||||||
matcher: hook.matcher,
|
matcher: hook.matcher,
|
||||||
command: hook.command || hook.script,
|
command: hook.command || hook.script,
|
||||||
|
scope: hook.scope,
|
||||||
|
index: hook.index,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,8 +202,19 @@ export function HookManagerPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteClick = async (hookName: string) => {
|
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 {
|
try {
|
||||||
await deleteHook(hookName);
|
await deleteHook({
|
||||||
|
name: hook.name,
|
||||||
|
scope: hook.scope,
|
||||||
|
trigger: hook.trigger,
|
||||||
|
index: hook.index,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete hook:', error);
|
console.error('Failed to delete hook:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user