mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: add useApiSettings hook for managing API settings, including providers, endpoints, cache, and model pools
- Implemented hooks for CRUD operations on providers and endpoints. - Added cache management hooks for cache stats and settings. - Introduced model pool management hooks for high availability and load balancing. - Created localization files for English and Chinese translations of API settings.
This commit is contained in:
@@ -105,8 +105,12 @@ export function useActiveCliExecutions(
|
||||
refetchInterval: number = 5000
|
||||
) {
|
||||
const upsertExecution = useCliStreamStore(state => state.upsertExecution);
|
||||
const removeExecution = useCliStreamStore(state => state.removeExecution);
|
||||
const executions = useCliStreamStore(state => state.executions);
|
||||
const setCurrentExecution = useCliStreamStore(state => state.setCurrentExecution);
|
||||
const markExecutionClosedByUser = useCliStreamStore(state => state.markExecutionClosedByUser);
|
||||
const isExecutionClosedByUser = useCliStreamStore(state => state.isExecutionClosedByUser);
|
||||
const cleanupUserClosedExecutions = useCliStreamStore(state => state.cleanupUserClosedExecutions);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ACTIVE_CLI_EXECUTIONS_QUERY_KEY,
|
||||
@@ -117,11 +121,33 @@ export function useActiveCliExecutions(
|
||||
}
|
||||
const data: ActiveCliExecutionsResponse = await response.json();
|
||||
|
||||
// Get server execution IDs
|
||||
const serverIds = new Set(data.executions.map(e => e.id));
|
||||
|
||||
// Clean up userClosedExecutions - remove those no longer on server
|
||||
cleanupUserClosedExecutions(serverIds);
|
||||
|
||||
// Remove executions that are no longer on server and were closed by user
|
||||
for (const [id, exec] of Object.entries(executions)) {
|
||||
if (isExecutionClosedByUser(id)) {
|
||||
// User closed this execution, remove from local state
|
||||
removeExecution(id);
|
||||
} else if (exec.status !== 'running' && !serverIds.has(id) && exec.recovered) {
|
||||
// Not running, not on server, and was recovered (not user-created)
|
||||
removeExecution(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Process executions and sync to store
|
||||
let hasNewExecution = false;
|
||||
const now = Date.now();
|
||||
|
||||
for (const exec of data.executions) {
|
||||
// Skip if user closed this execution
|
||||
if (isExecutionClosedByUser(exec.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = executions[exec.id];
|
||||
const historicalOutput = parseHistoricalOutput(exec.output || '', exec.startTime);
|
||||
|
||||
@@ -175,7 +201,7 @@ export function useActiveCliExecutions(
|
||||
|
||||
// Set current execution to first running execution if none selected
|
||||
if (hasNewExecution) {
|
||||
const runningExec = data.executions.find(e => e.status === 'running');
|
||||
const runningExec = data.executions.find(e => e.status === 'running' && !isExecutionClosedByUser(e.id));
|
||||
if (runningExec && !executions[runningExec.id]) {
|
||||
setCurrentExecution(runningExec.id);
|
||||
}
|
||||
|
||||
623
ccw/frontend/src/hooks/useApiSettings.ts
Normal file
623
ccw/frontend/src/hooks/useApiSettings.ts
Normal file
@@ -0,0 +1,623 @@
|
||||
// ========================================
|
||||
// useApiSettings Hook
|
||||
// ========================================
|
||||
// TanStack Query hooks for API Settings management
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
fetchProviders,
|
||||
createProvider,
|
||||
updateProvider,
|
||||
deleteProvider,
|
||||
testProvider,
|
||||
testProviderKey,
|
||||
getProviderHealthStatus,
|
||||
triggerProviderHealthCheck,
|
||||
fetchEndpoints,
|
||||
createEndpoint,
|
||||
updateEndpoint,
|
||||
deleteEndpoint,
|
||||
fetchCacheStats,
|
||||
clearCache,
|
||||
updateCacheSettings,
|
||||
fetchModelPools,
|
||||
fetchModelPool,
|
||||
createModelPool,
|
||||
updateModelPool,
|
||||
deleteModelPool,
|
||||
getAvailableModelsForPool,
|
||||
discoverModelsForPool,
|
||||
fetchApiConfig,
|
||||
syncApiConfig,
|
||||
previewYamlConfig,
|
||||
checkCcwLitellmStatus,
|
||||
installCcwLitellm,
|
||||
uninstallCcwLitellm,
|
||||
type ProviderCredential,
|
||||
type CustomEndpoint,
|
||||
type CacheStats,
|
||||
type GlobalCacheSettings,
|
||||
type ModelPoolConfig,
|
||||
type ModelPoolType,
|
||||
type DiscoveredProvider,
|
||||
} from '../lib/api';
|
||||
|
||||
// Query key factory
|
||||
export const apiSettingsKeys = {
|
||||
all: ['apiSettings'] as const,
|
||||
providers: () => [...apiSettingsKeys.all, 'providers'] as const,
|
||||
provider: (id: string) => [...apiSettingsKeys.providers(), id] as const,
|
||||
endpoints: () => [...apiSettingsKeys.all, 'endpoints'] as const,
|
||||
endpoint: (id: string) => [...apiSettingsKeys.endpoints(), id] as const,
|
||||
cache: () => [...apiSettingsKeys.all, 'cache'] as const,
|
||||
modelPools: () => [...apiSettingsKeys.all, 'modelPools'] as const,
|
||||
modelPool: (id: string) => [...apiSettingsKeys.modelPools(), id] as const,
|
||||
ccwLitellm: () => [...apiSettingsKeys.all, 'ccwLitellm'] as const,
|
||||
};
|
||||
|
||||
const STALE_TIME = 2 * 60 * 1000;
|
||||
|
||||
// ========================================
|
||||
// Provider Hooks
|
||||
// ========================================
|
||||
|
||||
export interface UseProvidersOptions {
|
||||
staleTime?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseProvidersReturn {
|
||||
providers: ProviderCredential[];
|
||||
totalCount: number;
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
invalidate: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useProviders(options: UseProvidersOptions = {}): UseProvidersReturn {
|
||||
const { staleTime = STALE_TIME, enabled = true } = options;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: apiSettingsKeys.providers(),
|
||||
queryFn: fetchProviders,
|
||||
staleTime,
|
||||
enabled,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
const providers = query.data?.providers ?? [];
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
const invalidate = async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: apiSettingsKeys.providers() });
|
||||
};
|
||||
|
||||
return {
|
||||
providers,
|
||||
totalCount: providers.length,
|
||||
isLoading: query.isLoading,
|
||||
isFetching: query.isFetching,
|
||||
error: query.error,
|
||||
refetch,
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreateProvider() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (provider: Omit<ProviderCredential, 'id' | 'createdAt' | 'updatedAt'>) =>
|
||||
createProvider(provider),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.providers() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createProvider: mutation.mutateAsync,
|
||||
isCreating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useUpdateProvider() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ providerId, updates }: { providerId: string; updates: Partial<Omit<ProviderCredential, 'id' | 'createdAt' | 'updatedAt'>> }) =>
|
||||
updateProvider(providerId, updates),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.providers() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updateProvider: (providerId: string, updates: Partial<Omit<ProviderCredential, 'id' | 'createdAt' | 'updatedAt'>>) =>
|
||||
mutation.mutateAsync({ providerId, updates }),
|
||||
isUpdating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDeleteProvider() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (providerId: string) => deleteProvider(providerId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.providers() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
deleteProvider: mutation.mutateAsync,
|
||||
isDeleting: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useTestProvider() {
|
||||
const mutation = useMutation({
|
||||
mutationFn: (providerId: string) => testProvider(providerId),
|
||||
});
|
||||
|
||||
return {
|
||||
testProvider: mutation.mutateAsync,
|
||||
isTesting: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useTestProviderKey() {
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ providerId, keyId }: { providerId: string; keyId: string }) =>
|
||||
testProviderKey(providerId, keyId),
|
||||
});
|
||||
|
||||
return {
|
||||
testProviderKey: mutation.mutateAsync,
|
||||
isTesting: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useProviderHealthStatus(providerId: string) {
|
||||
return useQuery({
|
||||
queryKey: [...apiSettingsKeys.provider(providerId), 'health'],
|
||||
queryFn: () => getProviderHealthStatus(providerId),
|
||||
enabled: !!providerId,
|
||||
staleTime: 30000, // 30 seconds
|
||||
refetchInterval: 60000, // Refetch every minute
|
||||
});
|
||||
}
|
||||
|
||||
export function useTriggerProviderHealthCheck() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (providerId: string) => triggerProviderHealthCheck(providerId),
|
||||
onSuccess: (_, providerId) => {
|
||||
queryClient.invalidateQueries({ queryKey: [...apiSettingsKeys.provider(providerId), 'health'] });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
triggerHealthCheck: mutation.mutateAsync,
|
||||
isChecking: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Endpoint Hooks
|
||||
// ========================================
|
||||
|
||||
export interface UseEndpointsOptions {
|
||||
staleTime?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseEndpointsReturn {
|
||||
endpoints: CustomEndpoint[];
|
||||
totalCount: number;
|
||||
cachedCount: number;
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
invalidate: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useEndpoints(options: UseEndpointsOptions = {}): UseEndpointsReturn {
|
||||
const { staleTime = STALE_TIME, enabled = true } = options;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: apiSettingsKeys.endpoints(),
|
||||
queryFn: fetchEndpoints,
|
||||
staleTime,
|
||||
enabled,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
const endpoints = query.data?.endpoints ?? [];
|
||||
const cachedEndpoints = endpoints.filter((e) => e.cacheStrategy.enabled);
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
const invalidate = async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: apiSettingsKeys.endpoints() });
|
||||
};
|
||||
|
||||
return {
|
||||
endpoints,
|
||||
totalCount: endpoints.length,
|
||||
cachedCount: cachedEndpoints.length,
|
||||
isLoading: query.isLoading,
|
||||
isFetching: query.isFetching,
|
||||
error: query.error,
|
||||
refetch,
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreateEndpoint() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (endpoint: Omit<CustomEndpoint, 'createdAt' | 'updatedAt'>) =>
|
||||
createEndpoint(endpoint),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.endpoints() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createEndpoint: mutation.mutateAsync,
|
||||
isCreating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useUpdateEndpoint() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ endpointId, updates }: { endpointId: string; updates: Partial<Omit<CustomEndpoint, 'id' | 'createdAt' | 'updatedAt'>> }) =>
|
||||
updateEndpoint(endpointId, updates),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.endpoints() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updateEndpoint: (endpointId: string, updates: Partial<Omit<CustomEndpoint, 'id' | 'createdAt' | 'updatedAt'>>) =>
|
||||
mutation.mutateAsync({ endpointId, updates }),
|
||||
isUpdating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDeleteEndpoint() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (endpointId: string) => deleteEndpoint(endpointId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.endpoints() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
deleteEndpoint: mutation.mutateAsync,
|
||||
isDeleting: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Cache Hooks
|
||||
// ========================================
|
||||
|
||||
export interface UseCacheStatsOptions {
|
||||
staleTime?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseCacheStatsReturn {
|
||||
stats: CacheStats | null;
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useCacheStats(options: UseCacheStatsOptions = {}): UseCacheStatsReturn {
|
||||
const { staleTime = 30000, enabled = true } = options; // 30 seconds stale time for cache stats
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: apiSettingsKeys.cache(),
|
||||
queryFn: fetchCacheStats,
|
||||
staleTime,
|
||||
enabled,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
return {
|
||||
stats: query.data ?? null,
|
||||
isLoading: query.isLoading,
|
||||
isFetching: query.isFetching,
|
||||
error: query.error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
export function useClearCache() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => clearCache(),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.cache() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
clearCache: mutation.mutateAsync,
|
||||
isClearing: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useUpdateCacheSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (settings: Partial<{ enabled: boolean; cacheDir: string; maxTotalSizeMB: number }>) =>
|
||||
updateCacheSettings(settings),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.cache() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updateCacheSettings: mutation.mutateAsync,
|
||||
isUpdating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Model Pool Hooks
|
||||
// ========================================
|
||||
|
||||
export interface UseModelPoolsOptions {
|
||||
staleTime?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseModelPoolsReturn {
|
||||
pools: ModelPoolConfig[];
|
||||
totalCount: number;
|
||||
enabledCount: number;
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
invalidate: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useModelPools(options: UseModelPoolsOptions = {}): UseModelPoolsReturn {
|
||||
const { staleTime = STALE_TIME, enabled = true } = options;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: apiSettingsKeys.modelPools(),
|
||||
queryFn: fetchModelPools,
|
||||
staleTime,
|
||||
enabled,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
const pools = query.data?.pools ?? [];
|
||||
const enabledPools = pools.filter((p) => p.enabled);
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
const invalidate = async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: apiSettingsKeys.modelPools() });
|
||||
};
|
||||
|
||||
return {
|
||||
pools,
|
||||
totalCount: pools.length,
|
||||
enabledCount: enabledPools.length,
|
||||
isLoading: query.isLoading,
|
||||
isFetching: query.isFetching,
|
||||
error: query.error,
|
||||
refetch,
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
export function useModelPool(poolId: string) {
|
||||
return useQuery({
|
||||
queryKey: apiSettingsKeys.modelPool(poolId),
|
||||
queryFn: () => fetchModelPool(poolId),
|
||||
enabled: !!poolId,
|
||||
staleTime: STALE_TIME,
|
||||
retry: 2,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateModelPool() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (pool: Omit<ModelPoolConfig, 'id'>) => createModelPool(pool),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.modelPools() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createModelPool: mutation.mutateAsync,
|
||||
isCreating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useUpdateModelPool() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ poolId, updates }: { poolId: string; updates: Partial<ModelPoolConfig> }) =>
|
||||
updateModelPool(poolId, updates),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.modelPools() });
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.modelPool(variables.poolId) });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updateModelPool: (poolId: string, updates: Partial<ModelPoolConfig>) =>
|
||||
mutation.mutateAsync({ poolId, updates }),
|
||||
isUpdating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDeleteModelPool() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (poolId: string) => deleteModelPool(poolId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.modelPools() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
deleteModelPool: mutation.mutateAsync,
|
||||
isDeleting: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAvailableModelsForPool(modelType: ModelPoolType) {
|
||||
return useQuery({
|
||||
queryKey: [...apiSettingsKeys.modelPools(), 'available', modelType],
|
||||
queryFn: () => getAvailableModelsForPool(modelType),
|
||||
enabled: !!modelType,
|
||||
staleTime: STALE_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDiscoverModelsForPool() {
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ modelType, targetModel }: { modelType: ModelPoolType; targetModel: string }) =>
|
||||
discoverModelsForPool(modelType, targetModel),
|
||||
});
|
||||
|
||||
return {
|
||||
discoverModels: (modelType: ModelPoolType, targetModel: string) =>
|
||||
mutation.mutateAsync({ modelType, targetModel }),
|
||||
isDiscovering: mutation.isPending,
|
||||
error: mutation.error,
|
||||
data: mutation.data,
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Config Hooks
|
||||
// ========================================
|
||||
|
||||
export function useApiConfig() {
|
||||
return useQuery({
|
||||
queryKey: [...apiSettingsKeys.all, 'config'],
|
||||
queryFn: fetchApiConfig,
|
||||
staleTime: STALE_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSyncApiConfig() {
|
||||
return useMutation({
|
||||
mutationFn: () => syncApiConfig(),
|
||||
});
|
||||
}
|
||||
|
||||
export function usePreviewYamlConfig() {
|
||||
return useMutation({
|
||||
mutationFn: () => previewYamlConfig(),
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CCW-LiteLLM Package Hooks
|
||||
// ========================================
|
||||
|
||||
export interface UseCcwLitellmStatusOptions {
|
||||
staleTime?: number;
|
||||
enabled?: boolean;
|
||||
refresh?: boolean;
|
||||
}
|
||||
|
||||
export function useCcwLitellmStatus(options: UseCcwLitellmStatusOptions = {}) {
|
||||
const { staleTime = 5 * 60 * 1000, enabled = true, refresh = false } = options;
|
||||
|
||||
return useQuery({
|
||||
queryKey: [...apiSettingsKeys.ccwLitellm(), 'status', refresh],
|
||||
queryFn: () => checkCcwLitellmStatus(refresh),
|
||||
staleTime,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstallCcwLitellm() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => installCcwLitellm(),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.ccwLitellm() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
install: mutation.mutateAsync,
|
||||
isInstalling: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useUninstallCcwLitellm() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => uninstallCcwLitellm(),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.ccwLitellm() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
uninstall: mutation.mutateAsync,
|
||||
isUninstalling: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
@@ -26,6 +26,14 @@ import {
|
||||
resetCodexLensGpu,
|
||||
fetchCodexLensIgnorePatterns,
|
||||
updateCodexLensIgnorePatterns,
|
||||
searchCodexLens,
|
||||
searchFilesCodexLens,
|
||||
searchSymbolCodexLens,
|
||||
fetchCodexLensIndexes,
|
||||
rebuildCodexLensIndex,
|
||||
updateCodexLensIndex,
|
||||
cancelCodexLensIndexing,
|
||||
checkCodexLensIndexingStatus,
|
||||
type CodexLensDashboardInitResponse,
|
||||
type CodexLensVenvStatus,
|
||||
type CodexLensConfig,
|
||||
@@ -39,6 +47,11 @@ import {
|
||||
type CodexLensUpdateEnvRequest,
|
||||
type CodexLensUpdateIgnorePatternsRequest,
|
||||
type CodexLensWorkspaceStatus,
|
||||
type CodexLensSearchParams,
|
||||
type CodexLensSearchResponse,
|
||||
type CodexLensSymbolSearchResponse,
|
||||
type CodexLensIndexesResponse,
|
||||
type CodexLensIndexingStatusResponse,
|
||||
} from '../lib/api';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
|
||||
@@ -56,6 +69,11 @@ export const codexLensKeys = {
|
||||
gpuList: () => [...codexLensKeys.gpu(), 'list'] as const,
|
||||
gpuDetect: () => [...codexLensKeys.gpu(), 'detect'] as const,
|
||||
ignorePatterns: () => [...codexLensKeys.all, 'ignorePatterns'] as const,
|
||||
indexes: () => [...codexLensKeys.all, 'indexes'] as const,
|
||||
indexingStatus: () => [...codexLensKeys.all, 'indexingStatus'] as const,
|
||||
search: (params: CodexLensSearchParams) => [...codexLensKeys.all, 'search', params] as const,
|
||||
filesSearch: (params: CodexLensSearchParams) => [...codexLensKeys.all, 'filesSearch', params] as const,
|
||||
symbolSearch: (params: Pick<CodexLensSearchParams, 'query' | 'limit'>) => [...codexLensKeys.all, 'symbolSearch', params] as const,
|
||||
};
|
||||
|
||||
// Default stale times
|
||||
@@ -715,6 +733,189 @@ export function useUpdateIgnorePatterns(): UseUpdateIgnorePatternsReturn {
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Index Management Hooks ==========
|
||||
|
||||
export interface UseCodexLensIndexesOptions {
|
||||
enabled?: boolean;
|
||||
staleTime?: number;
|
||||
}
|
||||
|
||||
export interface UseCodexLensIndexesReturn {
|
||||
data: CodexLensIndexesResponse | undefined;
|
||||
indexes: CodexLensIndexesResponse['indexes'] | undefined;
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for fetching CodexLens indexes
|
||||
*/
|
||||
export function useCodexLensIndexes(options: UseCodexLensIndexesOptions = {}): UseCodexLensIndexesReturn {
|
||||
const { enabled = true, staleTime = STALE_TIME_MEDIUM } = options;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: codexLensKeys.indexes(),
|
||||
queryFn: fetchCodexLensIndexes,
|
||||
staleTime,
|
||||
enabled,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
return {
|
||||
data: query.data,
|
||||
indexes: query.data?.indexes,
|
||||
isLoading: query.isLoading,
|
||||
error: query.error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseCodexLensIndexingStatusReturn {
|
||||
data: CodexLensIndexingStatusResponse | undefined;
|
||||
inProgress: boolean;
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for checking CodexLens indexing status
|
||||
*/
|
||||
export function useCodexLensIndexingStatus(): UseCodexLensIndexingStatusReturn {
|
||||
const query = useQuery({
|
||||
queryKey: codexLensKeys.indexingStatus(),
|
||||
queryFn: checkCodexLensIndexingStatus,
|
||||
staleTime: STALE_TIME_SHORT,
|
||||
refetchInterval: (data) => (data?.inProgress ? 2000 : false), // Poll every 2s when indexing
|
||||
retry: false,
|
||||
});
|
||||
|
||||
return {
|
||||
data: query.data,
|
||||
inProgress: query.data?.inProgress ?? false,
|
||||
isLoading: query.isLoading,
|
||||
error: query.error,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseRebuildIndexReturn {
|
||||
rebuildIndex: (projectPath: string, options?: {
|
||||
indexType?: 'normal' | 'vector';
|
||||
embeddingModel?: string;
|
||||
embeddingBackend?: 'fastembed' | 'litellm';
|
||||
maxWorkers?: number;
|
||||
}) => Promise<{ success: boolean; message?: string; error?: string }>;
|
||||
isRebuilding: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for rebuilding CodexLens index (full rebuild)
|
||||
*/
|
||||
export function useRebuildIndex(): UseRebuildIndexReturn {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async ({
|
||||
projectPath,
|
||||
options = {},
|
||||
}: {
|
||||
projectPath: string;
|
||||
options?: {
|
||||
indexType?: 'normal' | 'vector';
|
||||
embeddingModel?: string;
|
||||
embeddingBackend?: 'fastembed' | 'litellm';
|
||||
maxWorkers?: number;
|
||||
};
|
||||
}) => rebuildCodexLensIndex(projectPath, options),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: codexLensKeys.indexes() });
|
||||
queryClient.invalidateQueries({ queryKey: codexLensKeys.dashboard() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
rebuildIndex: (projectPath, options) =>
|
||||
mutation.mutateAsync({ projectPath, options }),
|
||||
isRebuilding: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseUpdateIndexReturn {
|
||||
updateIndex: (projectPath: string, options?: {
|
||||
indexType?: 'normal' | 'vector';
|
||||
embeddingModel?: string;
|
||||
embeddingBackend?: 'fastembed' | 'litellm';
|
||||
maxWorkers?: number;
|
||||
}) => Promise<{ success: boolean; message?: string; error?: string }>;
|
||||
isUpdating: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for updating CodexLens index (incremental update)
|
||||
*/
|
||||
export function useUpdateIndex(): UseUpdateIndexReturn {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async ({
|
||||
projectPath,
|
||||
options = {},
|
||||
}: {
|
||||
projectPath: string;
|
||||
options?: {
|
||||
indexType?: 'normal' | 'vector';
|
||||
embeddingModel?: string;
|
||||
embeddingBackend?: 'fastembed' | 'litellm';
|
||||
maxWorkers?: number;
|
||||
};
|
||||
}) => updateCodexLensIndex(projectPath, options),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: codexLensKeys.indexes() });
|
||||
queryClient.invalidateQueries({ queryKey: codexLensKeys.dashboard() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updateIndex: (projectPath, options) =>
|
||||
mutation.mutateAsync({ projectPath, options }),
|
||||
isUpdating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseCancelIndexingReturn {
|
||||
cancelIndexing: () => Promise<{ success: boolean; error?: string }>;
|
||||
isCancelling: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for canceling CodexLens indexing
|
||||
*/
|
||||
export function useCancelIndexing(): UseCancelIndexingReturn {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: cancelCodexLensIndexing,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: codexLensKeys.indexingStatus() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
cancelIndexing: mutation.mutateAsync,
|
||||
isCancelling: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined hook for all CodexLens mutations
|
||||
*/
|
||||
@@ -727,6 +928,9 @@ export function useCodexLensMutations() {
|
||||
const updateEnv = useUpdateCodexLensEnv();
|
||||
const gpu = useSelectGpu();
|
||||
const updatePatterns = useUpdateIgnorePatterns();
|
||||
const rebuildIndex = useRebuildIndex();
|
||||
const updateIndex = useUpdateIndex();
|
||||
const cancelIndexing = useCancelIndexing();
|
||||
|
||||
return {
|
||||
updateConfig: updateConfig.updateConfig,
|
||||
@@ -748,6 +952,12 @@ export function useCodexLensMutations() {
|
||||
isSelectingGpu: gpu.isSelecting || gpu.isResetting,
|
||||
updatePatterns: updatePatterns.updatePatterns,
|
||||
isUpdatingPatterns: updatePatterns.isUpdating,
|
||||
rebuildIndex: rebuildIndex.rebuildIndex,
|
||||
isRebuildingIndex: rebuildIndex.isRebuilding,
|
||||
updateIndex: updateIndex.updateIndex,
|
||||
isUpdatingIndex: updateIndex.isUpdating,
|
||||
cancelIndexing: cancelIndexing.cancelIndexing,
|
||||
isCancellingIndexing: cancelIndexing.isCancelling,
|
||||
isMutating:
|
||||
updateConfig.isUpdating ||
|
||||
bootstrap.isBootstrapping ||
|
||||
@@ -757,6 +967,119 @@ export function useCodexLensMutations() {
|
||||
updateEnv.isUpdating ||
|
||||
gpu.isSelecting ||
|
||||
gpu.isResetting ||
|
||||
updatePatterns.isUpdating,
|
||||
updatePatterns.isUpdating ||
|
||||
rebuildIndex.isRebuilding ||
|
||||
updateIndex.isUpdating ||
|
||||
cancelIndexing.isCancelling,
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Search Hooks ==========
|
||||
|
||||
export interface UseCodexLensSearchOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseCodexLensSearchReturn {
|
||||
data: CodexLensSearchResponse | undefined;
|
||||
results: CodexLensSearchResponse['results'] | undefined;
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for content search using CodexLens
|
||||
*/
|
||||
export function useCodexLensSearch(params: CodexLensSearchParams, options: UseCodexLensSearchOptions = {}): UseCodexLensSearchReturn {
|
||||
const { enabled = false } = options;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: codexLensKeys.search(params),
|
||||
queryFn: () => searchCodexLens(params),
|
||||
enabled,
|
||||
staleTime: STALE_TIME_SHORT,
|
||||
retry: 1,
|
||||
});
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
return {
|
||||
data: query.data,
|
||||
results: query.data?.results,
|
||||
isLoading: query.isLoading,
|
||||
error: query.error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for file search using CodexLens
|
||||
*/
|
||||
export function useCodexLensFilesSearch(params: CodexLensSearchParams, options: UseCodexLensSearchOptions = {}): UseCodexLensSearchReturn {
|
||||
const { enabled = false } = options;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: codexLensKeys.filesSearch(params),
|
||||
queryFn: () => searchFilesCodexLens(params),
|
||||
enabled,
|
||||
staleTime: STALE_TIME_SHORT,
|
||||
retry: 1,
|
||||
});
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
return {
|
||||
data: query.data,
|
||||
results: query.data?.results,
|
||||
isLoading: query.isLoading,
|
||||
error: query.error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseCodexLensSymbolSearchOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseCodexLensSymbolSearchReturn {
|
||||
data: CodexLensSymbolSearchResponse | undefined;
|
||||
symbols: CodexLensSymbolSearchResponse['symbols'] | undefined;
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for symbol search using CodexLens
|
||||
*/
|
||||
export function useCodexLensSymbolSearch(
|
||||
params: Pick<CodexLensSearchParams, 'query' | 'limit'>,
|
||||
options: UseCodexLensSymbolSearchOptions = {}
|
||||
): UseCodexLensSymbolSearchReturn {
|
||||
const { enabled = false } = options;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: codexLensKeys.symbolSearch(params),
|
||||
queryFn: () => searchSymbolCodexLens(params),
|
||||
enabled,
|
||||
staleTime: STALE_TIME_SHORT,
|
||||
retry: 1,
|
||||
});
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
return {
|
||||
data: query.data,
|
||||
symbols: query.data?.symbols,
|
||||
isLoading: query.isLoading,
|
||||
error: query.error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
// ========================================
|
||||
// TanStack Query hook for project overview data
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchProjectOverview } from '../lib/api';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { fetchProjectOverview, updateProjectGuidelines, type ProjectGuidelines } from '../lib/api';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
|
||||
// Query key factory
|
||||
@@ -53,3 +53,39 @@ export function useProjectOverview(options: UseProjectOverviewOptions = {}) {
|
||||
refetch: query.refetch,
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Mutations ==========
|
||||
|
||||
export interface UseUpdateGuidelinesReturn {
|
||||
updateGuidelines: (guidelines: ProjectGuidelines) => Promise<{ success: boolean; guidelines?: ProjectGuidelines; error?: string }>;
|
||||
isUpdating: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for updating project guidelines
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { updateGuidelines, isUpdating } = useUpdateGuidelines();
|
||||
* await updateGuidelines({ conventions: { ... }, constraints: { ... } });
|
||||
* ```
|
||||
*/
|
||||
export function useUpdateGuidelines(): UseUpdateGuidelinesReturn {
|
||||
const queryClient = useQueryClient();
|
||||
const projectPath = useWorkflowStore(selectProjectPath);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (guidelines: ProjectGuidelines) => updateProjectGuidelines(guidelines, projectPath),
|
||||
onSuccess: () => {
|
||||
// Invalidate project overview cache to trigger refetch
|
||||
queryClient.invalidateQueries({ queryKey: projectOverviewKeys.detail() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updateGuidelines: mutation.mutateAsync,
|
||||
isUpdating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user