mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
- Added A2UITypes for defining question structures and answers. - Created A2UIWebSocketHandler for managing WebSocket connections and message handling. - Developed ask-question tool for interactive user questions via A2UI. - Introduced platformUtils for platform detection and shell command handling. - Centralized TypeScript types in index.ts for better organization. - Implemented compatibility checks for hook templates based on platform requirements.
513 lines
13 KiB
TypeScript
513 lines
13 KiB
TypeScript
// ========================================
|
|
// useCliEndpoints Hook
|
|
// ========================================
|
|
// TanStack Query hooks for CLI endpoint management
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import {
|
|
fetchCliEndpoints,
|
|
toggleCliEndpoint,
|
|
type CliEndpoint,
|
|
type CliEndpointsResponse,
|
|
} from '../lib/api';
|
|
|
|
// Query key factory
|
|
export const cliEndpointsKeys = {
|
|
all: ['cliEndpoints'] as const,
|
|
lists: () => [...cliEndpointsKeys.all, 'list'] as const,
|
|
};
|
|
|
|
const STALE_TIME = 2 * 60 * 1000;
|
|
|
|
export interface UseCliEndpointsOptions {
|
|
staleTime?: number;
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface UseCliEndpointsReturn {
|
|
endpoints: CliEndpoint[];
|
|
litellmEndpoints: CliEndpoint[];
|
|
customEndpoints: CliEndpoint[];
|
|
wrapperEndpoints: CliEndpoint[];
|
|
totalCount: number;
|
|
enabledCount: number;
|
|
isLoading: boolean;
|
|
isFetching: boolean;
|
|
error: Error | null;
|
|
refetch: () => Promise<void>;
|
|
invalidate: () => Promise<void>;
|
|
}
|
|
|
|
export function useCliEndpoints(options: UseCliEndpointsOptions = {}): UseCliEndpointsReturn {
|
|
const { staleTime = STALE_TIME, enabled = true } = options;
|
|
const queryClient = useQueryClient();
|
|
|
|
const query = useQuery({
|
|
queryKey: cliEndpointsKeys.lists(),
|
|
queryFn: fetchCliEndpoints,
|
|
staleTime,
|
|
enabled,
|
|
retry: 2,
|
|
});
|
|
|
|
const endpoints = query.data?.endpoints ?? [];
|
|
const enabledEndpoints = endpoints.filter((e) => e.enabled);
|
|
|
|
const litellmEndpoints = endpoints.filter((e) => e.type === 'litellm');
|
|
const customEndpoints = endpoints.filter((e) => e.type === 'custom');
|
|
const wrapperEndpoints = endpoints.filter((e) => e.type === 'wrapper');
|
|
|
|
const refetch = async () => {
|
|
await query.refetch();
|
|
};
|
|
|
|
const invalidate = async () => {
|
|
await queryClient.invalidateQueries({ queryKey: cliEndpointsKeys.all });
|
|
};
|
|
|
|
return {
|
|
endpoints,
|
|
litellmEndpoints,
|
|
customEndpoints,
|
|
wrapperEndpoints,
|
|
totalCount: endpoints.length,
|
|
enabledCount: enabledEndpoints.length,
|
|
isLoading: query.isLoading,
|
|
isFetching: query.isFetching,
|
|
error: query.error,
|
|
refetch,
|
|
invalidate,
|
|
};
|
|
}
|
|
|
|
export function useToggleCliEndpoint() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: ({ endpointId, enabled }: { endpointId: string; enabled: boolean }) =>
|
|
toggleCliEndpoint(endpointId, enabled),
|
|
onMutate: async ({ endpointId, enabled }) => {
|
|
await queryClient.cancelQueries({ queryKey: cliEndpointsKeys.all });
|
|
const previousEndpoints = queryClient.getQueryData<CliEndpointsResponse>(cliEndpointsKeys.lists());
|
|
|
|
queryClient.setQueryData<CliEndpointsResponse>(cliEndpointsKeys.lists(), (old) => {
|
|
if (!old) return old;
|
|
return {
|
|
endpoints: old.endpoints.map((e) => (e.id === endpointId ? { ...e, enabled } : e)),
|
|
};
|
|
});
|
|
|
|
return { previousEndpoints };
|
|
},
|
|
onError: (_error, _vars, context) => {
|
|
if (context?.previousEndpoints) {
|
|
queryClient.setQueryData(cliEndpointsKeys.lists(), context.previousEndpoints);
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: cliEndpointsKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
toggleEndpoint: (endpointId: string, enabled: boolean) => mutation.mutateAsync({ endpointId, enabled }),
|
|
isToggling: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|
|
|
|
// ========================================
|
|
// useCliInstallations Hook
|
|
// ========================================
|
|
|
|
import {
|
|
fetchCliInstallations,
|
|
installCliTool,
|
|
uninstallCliTool,
|
|
upgradeCliTool,
|
|
type CliInstallation,
|
|
} from '../lib/api';
|
|
|
|
export const cliInstallationsKeys = {
|
|
all: ['cliInstallations'] as const,
|
|
lists: () => [...cliInstallationsKeys.all, 'list'] as const,
|
|
};
|
|
|
|
export interface UseCliInstallationsOptions {
|
|
staleTime?: number;
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface UseCliInstallationsReturn {
|
|
installations: CliInstallation[];
|
|
installedTools: CliInstallation[];
|
|
totalCount: number;
|
|
installedCount: number;
|
|
isLoading: boolean;
|
|
isFetching: boolean;
|
|
error: Error | null;
|
|
refetch: () => Promise<void>;
|
|
invalidate: () => Promise<void>;
|
|
}
|
|
|
|
export function useCliInstallations(options: UseCliInstallationsOptions = {}): UseCliInstallationsReturn {
|
|
const { staleTime = STALE_TIME, enabled = true } = options;
|
|
const queryClient = useQueryClient();
|
|
|
|
const query = useQuery({
|
|
queryKey: cliInstallationsKeys.lists(),
|
|
queryFn: fetchCliInstallations,
|
|
staleTime,
|
|
enabled,
|
|
retry: 2,
|
|
});
|
|
|
|
const installations = query.data?.tools ?? [];
|
|
const installedTools = installations.filter((t) => t.installed);
|
|
|
|
const refetch = async () => {
|
|
await query.refetch();
|
|
};
|
|
|
|
const invalidate = async () => {
|
|
await queryClient.invalidateQueries({ queryKey: cliInstallationsKeys.all });
|
|
};
|
|
|
|
return {
|
|
installations,
|
|
installedTools,
|
|
totalCount: installations.length,
|
|
installedCount: installedTools.length,
|
|
isLoading: query.isLoading,
|
|
isFetching: query.isFetching,
|
|
error: query.error,
|
|
refetch,
|
|
invalidate,
|
|
};
|
|
}
|
|
|
|
export function useInstallCliTool() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (toolName: string) => installCliTool(toolName),
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: cliInstallationsKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
installTool: mutation.mutateAsync,
|
|
isInstalling: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|
|
|
|
export function useUninstallCliTool() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (toolName: string) => uninstallCliTool(toolName),
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: cliInstallationsKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
uninstallTool: mutation.mutateAsync,
|
|
isUninstalling: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|
|
|
|
export function useUpgradeCliTool() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (toolName: string) => upgradeCliTool(toolName),
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: cliInstallationsKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
upgradeTool: mutation.mutateAsync,
|
|
isUpgrading: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|
|
|
|
// ========================================
|
|
// useHooks Hook
|
|
// ========================================
|
|
|
|
import {
|
|
fetchHooks,
|
|
toggleHook,
|
|
type Hook,
|
|
type HooksResponse,
|
|
} from '../lib/api';
|
|
|
|
export const hooksKeys = {
|
|
all: ['hooks'] as const,
|
|
lists: () => [...hooksKeys.all, 'list'] as const,
|
|
};
|
|
|
|
export interface UseHooksOptions {
|
|
staleTime?: number;
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface UseHooksReturn {
|
|
hooks: Hook[];
|
|
enabledHooks: Hook[];
|
|
totalCount: number;
|
|
enabledCount: number;
|
|
isLoading: boolean;
|
|
isFetching: boolean;
|
|
error: Error | null;
|
|
refetch: () => Promise<void>;
|
|
invalidate: () => Promise<void>;
|
|
}
|
|
|
|
export function useHooks(options: UseHooksOptions = {}): UseHooksReturn {
|
|
const { staleTime = STALE_TIME, enabled = true } = options;
|
|
const queryClient = useQueryClient();
|
|
|
|
const query = useQuery({
|
|
queryKey: hooksKeys.lists(),
|
|
queryFn: fetchHooks,
|
|
staleTime,
|
|
enabled,
|
|
retry: 2,
|
|
});
|
|
|
|
const hooks = query.data?.hooks ?? [];
|
|
const enabledHooks = hooks.filter((h) => h.enabled);
|
|
|
|
const refetch = async () => {
|
|
await query.refetch();
|
|
};
|
|
|
|
const invalidate = async () => {
|
|
await queryClient.invalidateQueries({ queryKey: hooksKeys.all });
|
|
};
|
|
|
|
return {
|
|
hooks,
|
|
enabledHooks,
|
|
totalCount: hooks.length,
|
|
enabledCount: enabledHooks.length,
|
|
isLoading: query.isLoading,
|
|
isFetching: query.isFetching,
|
|
error: query.error,
|
|
refetch,
|
|
invalidate,
|
|
};
|
|
}
|
|
|
|
export function useToggleHook() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: ({ hookName, enabled }: { hookName: string; enabled: boolean }) =>
|
|
toggleHook(hookName, enabled),
|
|
onMutate: async ({ hookName, enabled }) => {
|
|
await queryClient.cancelQueries({ queryKey: hooksKeys.all });
|
|
const previousHooks = queryClient.getQueryData<HooksResponse>(hooksKeys.lists());
|
|
|
|
queryClient.setQueryData<HooksResponse>(hooksKeys.lists(), (old) => {
|
|
if (!old) return old;
|
|
return {
|
|
hooks: old.hooks.map((h) => (h.name === hookName ? { ...h, enabled } : h)),
|
|
};
|
|
});
|
|
|
|
return { previousHooks };
|
|
},
|
|
onError: (_error, _vars, context) => {
|
|
if (context?.previousHooks) {
|
|
queryClient.setQueryData(hooksKeys.lists(), context.previousHooks);
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: hooksKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
toggleHook: (hookName: string, enabled: boolean) => mutation.mutateAsync({ hookName, enabled }),
|
|
isToggling: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|
|
|
|
// ========================================
|
|
// useRules Hook
|
|
// ========================================
|
|
|
|
import {
|
|
fetchRules,
|
|
toggleRule,
|
|
createRule as createRuleApi,
|
|
deleteRule as deleteRuleApi,
|
|
type Rule,
|
|
type RulesResponse,
|
|
type RuleCreateInput,
|
|
} from '../lib/api';
|
|
|
|
export const rulesKeys = {
|
|
all: ['rules'] as const,
|
|
lists: () => [...rulesKeys.all, 'list'] as const,
|
|
};
|
|
|
|
export interface UseRulesOptions {
|
|
staleTime?: number;
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface UseRulesReturn {
|
|
rules: Rule[];
|
|
enabledRules: Rule[];
|
|
totalCount: number;
|
|
enabledCount: number;
|
|
isLoading: boolean;
|
|
isFetching: boolean;
|
|
error: Error | null;
|
|
refetch: () => Promise<void>;
|
|
invalidate: () => Promise<void>;
|
|
}
|
|
|
|
export function useRules(options: UseRulesOptions = {}): UseRulesReturn {
|
|
const { staleTime = STALE_TIME, enabled = true } = options;
|
|
const queryClient = useQueryClient();
|
|
|
|
const query = useQuery({
|
|
queryKey: rulesKeys.lists(),
|
|
queryFn: fetchRules,
|
|
staleTime,
|
|
enabled,
|
|
retry: 2,
|
|
});
|
|
|
|
const rules = query.data?.rules ?? [];
|
|
const enabledRules = rules.filter((r) => r.enabled);
|
|
|
|
const refetch = async () => {
|
|
await query.refetch();
|
|
};
|
|
|
|
const invalidate = async () => {
|
|
await queryClient.invalidateQueries({ queryKey: rulesKeys.all });
|
|
};
|
|
|
|
return {
|
|
rules,
|
|
enabledRules,
|
|
totalCount: rules.length,
|
|
enabledCount: enabledRules.length,
|
|
isLoading: query.isLoading,
|
|
isFetching: query.isFetching,
|
|
error: query.error,
|
|
refetch,
|
|
invalidate,
|
|
};
|
|
}
|
|
|
|
export function useToggleRule() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: ({ ruleId, enabled }: { ruleId: string; enabled: boolean }) =>
|
|
toggleRule(ruleId, enabled),
|
|
onMutate: async ({ ruleId, enabled }) => {
|
|
await queryClient.cancelQueries({ queryKey: rulesKeys.all });
|
|
const previousRules = queryClient.getQueryData<RulesResponse>(rulesKeys.lists());
|
|
|
|
queryClient.setQueryData<RulesResponse>(rulesKeys.lists(), (old) => {
|
|
if (!old) return old;
|
|
return {
|
|
rules: old.rules.map((r) => (r.id === ruleId ? { ...r, enabled } : r)),
|
|
};
|
|
});
|
|
|
|
return { previousRules };
|
|
},
|
|
onError: (_error, _vars, context) => {
|
|
if (context?.previousRules) {
|
|
queryClient.setQueryData(rulesKeys.lists(), context.previousRules);
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: rulesKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
toggleRule: (ruleId: string, enabled: boolean) => mutation.mutateAsync({ ruleId, enabled }),
|
|
isToggling: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|
|
|
|
export function useCreateRule() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (input: RuleCreateInput) => createRuleApi(input),
|
|
onSuccess: (newRule) => {
|
|
queryClient.setQueryData<RulesResponse>(rulesKeys.lists(), (old) => {
|
|
if (!old) return { rules: [newRule] };
|
|
return {
|
|
rules: [newRule, ...old.rules],
|
|
};
|
|
});
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: rulesKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
createRule: mutation.mutateAsync,
|
|
isCreating: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|
|
|
|
export function useDeleteRule() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: ({ ruleId, location }: { ruleId: string; location?: string }) =>
|
|
deleteRuleApi(ruleId, location),
|
|
onMutate: async ({ ruleId }) => {
|
|
await queryClient.cancelQueries({ queryKey: rulesKeys.all });
|
|
const previousRules = queryClient.getQueryData<RulesResponse>(rulesKeys.lists());
|
|
|
|
queryClient.setQueryData<RulesResponse>(rulesKeys.lists(), (old) => {
|
|
if (!old) return old;
|
|
return {
|
|
rules: old.rules.filter((r) => r.id !== ruleId),
|
|
};
|
|
});
|
|
|
|
return { previousRules };
|
|
},
|
|
onError: (_error, _vars, context) => {
|
|
if (context?.previousRules) {
|
|
queryClient.setQueryData(rulesKeys.lists(), context.previousRules);
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: rulesKeys.all });
|
|
},
|
|
});
|
|
|
|
return {
|
|
deleteRule: (ruleId: string, location?: string) => mutation.mutateAsync({ ruleId, location }),
|
|
isDeleting: mutation.isPending,
|
|
error: mutation.error,
|
|
};
|
|
}
|