mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat(frontend): implement comprehensive API Settings Management Interface
Implement a complete API Management Interface for React frontend with split- panel layout, migrating all features from legacy JS frontend. New Features: - API Settings page with 5 tabs: Providers, Endpoints, Cache, Model Pools, CLI Settings - Provider Management: CRUD operations, multi-key rotation, health checks, test connection - Endpoint Management: CRUD operations, cache strategy configuration, enable/disable toggle - Cache Settings: Global configuration, statistics display, clear cache functionality - Model Pool Management: CRUD operations, auto-discovery feature, provider exclusion - CLI Settings Management: Provider-based and Direct modes, full CRUD support - Multi-Key Settings Modal: Manage API keys with rotation strategies and weights - Manage Models Modal: View and manage models per provider (LLM and Embedding) - Sync to CodexLens: Integration handler for provider configuration sync Technical Implementation: - Created 12 new React components in components/api-settings/ - Extended lib/api.ts with 460+ lines of API client functions - Created hooks/useApiSettings.ts with 772 lines of TanStack Query hooks - Added RadioGroup UI component for form selections - Implemented unified error handling with useNotifications across all operations - Complete i18n support (500+ keys in English and Chinese) - Route integration (/api-settings) and sidebar navigation Code Quality: - All acceptance criteria from plan.json verified - Code review passed with Gemini (all 7 IMPL tasks complete) - Follows existing patterns: Shadcn UI, TanStack Query, react-intl, Lucide icons
This commit is contained in:
@@ -220,6 +220,11 @@ export {
|
||||
useUpdateIgnorePatterns,
|
||||
useCodexLensMutations,
|
||||
codexLensKeys,
|
||||
useCodexLensIndexes,
|
||||
useCodexLensIndexingStatus,
|
||||
useRebuildIndex,
|
||||
useUpdateIndex,
|
||||
useCancelIndexing,
|
||||
} from './useCodexLens';
|
||||
export type {
|
||||
UseCodexLensDashboardOptions,
|
||||
@@ -248,4 +253,10 @@ export type {
|
||||
UseUpdateCodexLensEnvReturn,
|
||||
UseSelectGpuReturn,
|
||||
UseUpdateIgnorePatternsReturn,
|
||||
UseCodexLensIndexesOptions,
|
||||
UseCodexLensIndexesReturn,
|
||||
UseCodexLensIndexingStatusReturn,
|
||||
UseRebuildIndexReturn,
|
||||
UseUpdateIndexReturn,
|
||||
UseCancelIndexingReturn,
|
||||
} from './useCodexLens';
|
||||
@@ -33,6 +33,11 @@ import {
|
||||
checkCcwLitellmStatus,
|
||||
installCcwLitellm,
|
||||
uninstallCcwLitellm,
|
||||
fetchCliSettings,
|
||||
createCliSettings,
|
||||
updateCliSettings,
|
||||
deleteCliSettings,
|
||||
toggleCliSettingsEnabled,
|
||||
type ProviderCredential,
|
||||
type CustomEndpoint,
|
||||
type CacheStats,
|
||||
@@ -40,6 +45,8 @@ import {
|
||||
type ModelPoolConfig,
|
||||
type ModelPoolType,
|
||||
type DiscoveredProvider,
|
||||
type CliSettingsEndpoint,
|
||||
type SaveCliSettingsRequest,
|
||||
} from '../lib/api';
|
||||
|
||||
// Query key factory
|
||||
@@ -53,6 +60,8 @@ export const apiSettingsKeys = {
|
||||
modelPools: () => [...apiSettingsKeys.all, 'modelPools'] as const,
|
||||
modelPool: (id: string) => [...apiSettingsKeys.modelPools(), id] as const,
|
||||
ccwLitellm: () => [...apiSettingsKeys.all, 'ccwLitellm'] as const,
|
||||
cliSettings: () => [...apiSettingsKeys.all, 'cliSettings'] as const,
|
||||
cliSetting: (id: string) => [...apiSettingsKeys.cliSettings(), id] as const,
|
||||
};
|
||||
|
||||
const STALE_TIME = 2 * 60 * 1000;
|
||||
@@ -621,3 +630,142 @@ export function useUninstallCcwLitellm() {
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLI Settings Hooks
|
||||
// ========================================
|
||||
|
||||
export interface UseCliSettingsOptions {
|
||||
staleTime?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseCliSettingsReturn {
|
||||
cliSettings: CliSettingsEndpoint[];
|
||||
totalCount: number;
|
||||
enabledCount: number;
|
||||
providerBasedCount: number;
|
||||
directCount: number;
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
invalidate: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useCliSettings(options: UseCliSettingsOptions = {}): UseCliSettingsReturn {
|
||||
const { staleTime = STALE_TIME, enabled = true } = options;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: apiSettingsKeys.cliSettings(),
|
||||
queryFn: fetchCliSettings,
|
||||
staleTime,
|
||||
enabled,
|
||||
retry: 2,
|
||||
});
|
||||
|
||||
const cliSettings = query.data?.endpoints ?? [];
|
||||
const enabledCliSettings = cliSettings.filter((s) => s.enabled);
|
||||
|
||||
// Determine mode based on whether settings have providerId in description or env vars
|
||||
const providerBasedCount = cliSettings.filter((s) => {
|
||||
// Provider-based: has ANTHROPIC_BASE_URL set to provider's apiBase
|
||||
return s.settings.env.ANTHROPIC_BASE_URL && !s.settings.env.ANTHROPIC_BASE_URL.includes('api.anthropic.com');
|
||||
}).length;
|
||||
|
||||
const directCount = cliSettings.length - providerBasedCount;
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
const invalidate = async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: apiSettingsKeys.cliSettings() });
|
||||
};
|
||||
|
||||
return {
|
||||
cliSettings,
|
||||
totalCount: cliSettings.length,
|
||||
enabledCount: enabledCliSettings.length,
|
||||
providerBasedCount,
|
||||
directCount,
|
||||
isLoading: query.isLoading,
|
||||
isFetching: query.isFetching,
|
||||
error: query.error,
|
||||
refetch,
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreateCliSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (request: SaveCliSettingsRequest) => createCliSettings(request),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.cliSettings() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createCliSettings: mutation.mutateAsync,
|
||||
isCreating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useUpdateCliSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ endpointId, request }: { endpointId: string; request: Partial<SaveCliSettingsRequest> }) =>
|
||||
updateCliSettings(endpointId, request),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.cliSettings() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updateCliSettings: (endpointId: string, request: Partial<SaveCliSettingsRequest>) =>
|
||||
mutation.mutateAsync({ endpointId, request }),
|
||||
isUpdating: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDeleteCliSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (endpointId: string) => deleteCliSettings(endpointId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.cliSettings() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
deleteCliSettings: mutation.mutateAsync,
|
||||
isDeleting: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export function useToggleCliSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ endpointId, enabled }: { endpointId: string; enabled: boolean }) =>
|
||||
toggleCliSettingsEnabled(endpointId, enabled),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: apiSettingsKeys.cliSettings() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
toggleCliSettings: (endpointId: string, enabled: boolean) =>
|
||||
mutation.mutateAsync({ endpointId, enabled }),
|
||||
isToggling: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
fetchCodexLensConfig,
|
||||
updateCodexLensConfig,
|
||||
bootstrapCodexLens,
|
||||
installCodexLensSemantic,
|
||||
uninstallCodexLens,
|
||||
fetchCodexLensModels,
|
||||
fetchCodexLensModelInfo,
|
||||
@@ -52,6 +53,7 @@ import {
|
||||
type CodexLensSymbolSearchResponse,
|
||||
type CodexLensIndexesResponse,
|
||||
type CodexLensIndexingStatusResponse,
|
||||
type CodexLensSemanticInstallResponse,
|
||||
} from '../lib/api';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
|
||||
@@ -553,6 +555,33 @@ export function useBootstrapCodexLens(): UseBootstrapCodexLensReturn {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseInstallSemanticReturn {
|
||||
installSemantic: (gpuMode: 'cpu' | 'cuda' | 'directml') => Promise<{ success: boolean; message?: string; gpuMode?: string }>;
|
||||
isInstalling: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for installing CodexLens semantic dependencies
|
||||
*/
|
||||
export function useInstallSemantic(): UseInstallSemanticReturn {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: installCodexLensSemantic,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: codexLensKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: codexLensKeys.dashboard() });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
installSemantic: mutation.mutateAsync,
|
||||
isInstalling: mutation.isPending,
|
||||
error: mutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseUninstallCodexLensReturn {
|
||||
uninstall: () => Promise<{ success: boolean; message?: string }>;
|
||||
isUninstalling: boolean;
|
||||
@@ -922,6 +951,7 @@ export function useCancelIndexing(): UseCancelIndexingReturn {
|
||||
export function useCodexLensMutations() {
|
||||
const updateConfig = useUpdateCodexLensConfig();
|
||||
const bootstrap = useBootstrapCodexLens();
|
||||
const installSemantic = useInstallSemantic();
|
||||
const uninstall = useUninstallCodexLens();
|
||||
const download = useDownloadModel();
|
||||
const deleteModel = useDeleteModel();
|
||||
@@ -937,6 +967,8 @@ export function useCodexLensMutations() {
|
||||
isUpdatingConfig: updateConfig.isUpdating,
|
||||
bootstrap: bootstrap.bootstrap,
|
||||
isBootstrapping: bootstrap.isBootstrapping,
|
||||
installSemantic: installSemantic.installSemantic,
|
||||
isInstallingSemantic: installSemantic.isInstalling,
|
||||
uninstall: uninstall.uninstall,
|
||||
isUninstalling: uninstall.isUninstalling,
|
||||
downloadModel: download.downloadModel,
|
||||
@@ -961,6 +993,7 @@ export function useCodexLensMutations() {
|
||||
isMutating:
|
||||
updateConfig.isUpdating ||
|
||||
bootstrap.isBootstrapping ||
|
||||
installSemantic.isInstalling ||
|
||||
uninstall.isUninstalling ||
|
||||
download.isDownloading ||
|
||||
deleteModel.isDeleting ||
|
||||
|
||||
Reference in New Issue
Block a user