mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 00:14:15 +00:00
refactor(ui): centralize provider list in provider-config.ts (DRY)
- Add CLIPROXY_PROVIDERS array as single source of truth for UI - Add CLIProxyProvider type and isValidProvider() helper - Update api-client.ts to use CLIProxyProvider type - Update cliproxy-dialog.tsx to import from provider-config - Update cliproxy-header.tsx to use CLIPROXY_PROVIDERS - Update use-cliproxy-auth-flow.ts to use isValidProvider() - Update wizard constants to use typed provider info Adding a new provider now only requires updating: - Backend: src/auth/profile-detector.ts (CLIPROXY_PROFILES) - UI: ui/src/lib/provider-config.ts (CLIPROXY_PROVIDERS) - UI wizard: ui/src/components/setup/wizard/constants.ts (PROVIDER_INFO)
This commit is contained in:
1 parent
4a2abc74ca
commit
5a4c8e009c
7 files changed
+79
-49
No files matched your search
@@ -13,15 +13,14 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { useCreateVariant, useCliproxyAuth } from '@/hooks/use-cliproxy';
|
import { useCreateVariant, useCliproxyAuth } from '@/hooks/use-cliproxy';
|
||||||
import { usePrivacy } from '@/contexts/privacy-context';
|
import { usePrivacy } from '@/contexts/privacy-context';
|
||||||
|
import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
|
||||||
const providers = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp', 'claude'] as const;
|
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, 'Name is required')
|
.min(1, 'Name is required')
|
||||||
.regex(/^[a-zA-Z][a-zA-Z0-9._-]*$/, 'Invalid variant name'),
|
.regex(/^[a-zA-Z][a-zA-Z0-9._-]*$/, 'Invalid variant name'),
|
||||||
provider: z.enum(providers, { message: 'Provider is required' }),
|
provider: z.enum(CLIPROXY_PROVIDERS, { message: 'Provider is required' }),
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
account: z.string().optional(),
|
account: z.string().optional(),
|
||||||
});
|
});
|
||||||
@@ -33,16 +32,10 @@ interface CliproxyDialogProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerOptions = [
|
const providerOptions = CLIPROXY_PROVIDERS.map((id) => ({
|
||||||
{ value: 'gemini', label: 'Google Gemini' },
|
value: id,
|
||||||
{ value: 'codex', label: 'OpenAI Codex' },
|
label: getProviderDisplayName(id),
|
||||||
{ value: 'agy', label: 'Antigravity' },
|
}));
|
||||||
{ value: 'qwen', label: 'Alibaba Qwen' },
|
|
||||||
{ value: 'iflow', label: 'iFlow' },
|
|
||||||
{ value: 'kiro', label: 'Kiro (AWS)' },
|
|
||||||
{ value: 'ghcp', label: 'GitHub Copilot (OAuth)' },
|
|
||||||
{ value: 'claude', label: 'Claude (Anthropic)' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
|
export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
|
||||||
const createMutation = useCreateVariant();
|
const createMutation = useCreateVariant();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { RefreshCw, Loader2, AlertTriangle } from 'lucide-react';
|
|||||||
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
|
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
|
||||||
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
|
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
|
||||||
|
|
||||||
interface VersionInfo {
|
interface VersionInfo {
|
||||||
currentVersion: string;
|
currentVersion: string;
|
||||||
@@ -135,16 +136,10 @@ export function CliproxyHeader({
|
|||||||
.catch(() => {}); // Silently fail
|
.catch(() => {}); // Silently fail
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const providers = [
|
const providers = CLIPROXY_PROVIDERS.map((id) => ({
|
||||||
{ id: 'claude', displayName: 'Claude' },
|
id,
|
||||||
{ id: 'gemini', displayName: 'Gemini' },
|
displayName: getProviderDisplayName(id),
|
||||||
{ id: 'codex', displayName: 'Codex' },
|
}));
|
||||||
{ id: 'agy', displayName: 'Agy' },
|
|
||||||
{ id: 'qwen', displayName: 'Qwen' },
|
|
||||||
{ id: 'iflow', displayName: 'iFlow' },
|
|
||||||
{ id: 'kiro', displayName: 'Kiro' },
|
|
||||||
{ id: 'ghcp', displayName: 'GitHub Copilot' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const getProviderStatus = (providerId: string) => {
|
const getProviderStatus = (providerId: string) => {
|
||||||
const status = authData?.authStatus.find((s) => s.provider === providerId);
|
const status = authData?.authStatus.find((s) => s.provider === providerId);
|
||||||
|
|||||||
@@ -1,20 +1,42 @@
|
|||||||
/**
|
/**
|
||||||
* Constants for Quick Setup Wizard
|
* Constants for Quick Setup Wizard
|
||||||
|
* Provider display info with custom ordering for wizard UI.
|
||||||
|
* Provider IDs must match CLIPROXY_PROVIDERS from provider-config.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ProviderOption } from './types';
|
import type { ProviderOption } from './types';
|
||||||
|
import type { CLIProxyProvider } from '@/lib/provider-config';
|
||||||
|
|
||||||
export const PROVIDERS: ProviderOption[] = [
|
/** Provider display info for wizard - ordered by recommendation */
|
||||||
{ id: 'agy', name: 'Antigravity', description: 'Antigravity AI models' },
|
const PROVIDER_INFO: Record<CLIProxyProvider, { name: string; description: string }> = {
|
||||||
{ id: 'claude', name: 'Claude (Anthropic)', description: 'Claude Opus/Sonnet models' },
|
agy: { name: 'Antigravity', description: 'Antigravity AI models' },
|
||||||
{ id: 'gemini', name: 'Google Gemini', description: 'Gemini Pro/Flash models' },
|
claude: { name: 'Claude (Anthropic)', description: 'Claude Opus/Sonnet models' },
|
||||||
{ id: 'codex', name: 'OpenAI Codex', description: 'GPT-4 and codex models' },
|
gemini: { name: 'Google Gemini', description: 'Gemini Pro/Flash models' },
|
||||||
{ id: 'qwen', name: 'Alibaba Qwen', description: 'Qwen Code models' },
|
codex: { name: 'OpenAI Codex', description: 'GPT-4 and codex models' },
|
||||||
{ id: 'iflow', name: 'iFlow', description: 'iFlow AI models' },
|
qwen: { name: 'Alibaba Qwen', description: 'Qwen Code models' },
|
||||||
{ id: 'kiro', name: 'Kiro (AWS)', description: 'AWS CodeWhisperer models' },
|
iflow: { name: 'iFlow', description: 'iFlow AI models' },
|
||||||
{ id: 'ghcp', name: 'GitHub Copilot (OAuth)', description: 'GitHub Copilot via OAuth' },
|
kiro: { name: 'Kiro (AWS)', description: 'AWS CodeWhisperer models' },
|
||||||
|
ghcp: { name: 'GitHub Copilot (OAuth)', description: 'GitHub Copilot via OAuth' },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Wizard display order - most recommended first */
|
||||||
|
const WIZARD_PROVIDER_ORDER: CLIProxyProvider[] = [
|
||||||
|
'agy',
|
||||||
|
'claude',
|
||||||
|
'gemini',
|
||||||
|
'codex',
|
||||||
|
'qwen',
|
||||||
|
'iflow',
|
||||||
|
'kiro',
|
||||||
|
'ghcp',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const PROVIDERS: ProviderOption[] = WIZARD_PROVIDER_ORDER.map((id) => ({
|
||||||
|
id,
|
||||||
|
name: PROVIDER_INFO[id].name,
|
||||||
|
description: PROVIDER_INFO[id].description,
|
||||||
|
}));
|
||||||
|
|
||||||
export const ALL_STEPS = ['provider', 'auth', 'variant', 'success'];
|
export const ALL_STEPS = ['provider', 'auth', 'variant', 'success'];
|
||||||
|
|
||||||
export function getStepProgress(step: string): number {
|
export function getStepProgress(step: string): number {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
useCancelAuth,
|
useCancelAuth,
|
||||||
} from '@/hooks/use-cliproxy';
|
} from '@/hooks/use-cliproxy';
|
||||||
import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
|
import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
|
||||||
|
import type { CLIProxyProvider } from '@/lib/provider-config';
|
||||||
import { applyDefaultPreset } from '@/lib/preset-utils';
|
import { applyDefaultPreset } from '@/lib/preset-utils';
|
||||||
import { usePrivacy } from '@/contexts/privacy-context';
|
import { usePrivacy } from '@/contexts/privacy-context';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -136,15 +137,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
|||||||
try {
|
try {
|
||||||
await createMutation.mutateAsync({
|
await createMutation.mutateAsync({
|
||||||
name: variantName,
|
name: variantName,
|
||||||
provider: selectedProvider as
|
provider: selectedProvider as CLIProxyProvider,
|
||||||
| 'gemini'
|
|
||||||
| 'codex'
|
|
||||||
| 'agy'
|
|
||||||
| 'qwen'
|
|
||||||
| 'iflow'
|
|
||||||
| 'kiro'
|
|
||||||
| 'ghcp'
|
|
||||||
| 'claude',
|
|
||||||
model: modelName || undefined,
|
model: modelName || undefined,
|
||||||
account: selectedAccount?.id,
|
account: selectedAccount?.id,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { api } from '@/lib/api-client';
|
import { api } from '@/lib/api-client';
|
||||||
|
import { isValidProvider } from '@/lib/provider-config';
|
||||||
|
|
||||||
interface AuthFlowState {
|
interface AuthFlowState {
|
||||||
provider: string | null;
|
provider: string | null;
|
||||||
@@ -14,8 +15,6 @@ interface AuthFlowState {
|
|||||||
error: string | null;
|
error: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VALID_PROVIDERS = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp', 'claude'];
|
|
||||||
|
|
||||||
export function useCliproxyAuthFlow() {
|
export function useCliproxyAuthFlow() {
|
||||||
const [state, setState] = useState<AuthFlowState>({
|
const [state, setState] = useState<AuthFlowState>({
|
||||||
provider: null,
|
provider: null,
|
||||||
@@ -35,7 +34,7 @@ export function useCliproxyAuthFlow() {
|
|||||||
|
|
||||||
const startAuth = useCallback(
|
const startAuth = useCallback(
|
||||||
async (provider: string) => {
|
async (provider: string) => {
|
||||||
if (!VALID_PROVIDERS.includes(provider)) {
|
if (!isValidProvider(provider)) {
|
||||||
setState({
|
setState({
|
||||||
provider: null,
|
provider: null,
|
||||||
isAuthenticating: false,
|
isAuthenticating: false,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* Phase 03: REST API Routes & CRUD
|
* Phase 03: REST API Routes & CRUD
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { CLIProxyProvider } from './provider-config';
|
||||||
|
|
||||||
const BASE_URL = '/api';
|
const BASE_URL = '/api';
|
||||||
|
|
||||||
async function request<T>(url: string, options?: RequestInit): Promise<T> {
|
async function request<T>(url: string, options?: RequestInit): Promise<T> {
|
||||||
@@ -47,7 +49,7 @@ export interface UpdateProfile {
|
|||||||
|
|
||||||
export interface Variant {
|
export interface Variant {
|
||||||
name: string;
|
name: string;
|
||||||
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude';
|
provider: CLIProxyProvider;
|
||||||
settings: string;
|
settings: string;
|
||||||
account?: string;
|
account?: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
@@ -56,13 +58,13 @@ export interface Variant {
|
|||||||
|
|
||||||
export interface CreateVariant {
|
export interface CreateVariant {
|
||||||
name: string;
|
name: string;
|
||||||
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude';
|
provider: CLIProxyProvider;
|
||||||
model?: string;
|
model?: string;
|
||||||
account?: string;
|
account?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateVariant {
|
export interface UpdateVariant {
|
||||||
provider?: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude';
|
provider?: CLIProxyProvider;
|
||||||
model?: string;
|
model?: string;
|
||||||
account?: string;
|
account?: string;
|
||||||
}
|
}
|
||||||
@@ -72,7 +74,7 @@ export interface OAuthAccount {
|
|||||||
id: string;
|
id: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude';
|
provider: CLIProxyProvider;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
tokenFile: string;
|
tokenFile: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|||||||
@@ -1,8 +1,34 @@
|
|||||||
/**
|
/**
|
||||||
* Provider Configuration
|
* Provider Configuration
|
||||||
* Shared constants for provider branding and assets
|
* Shared constants for CLIProxy providers - SINGLE SOURCE OF TRUTH for UI
|
||||||
|
*
|
||||||
|
* When adding a new provider, update CLIPROXY_PROVIDERS array and related mappings.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonical list of CLIProxy provider IDs
|
||||||
|
* This is the UI's single source of truth for valid providers.
|
||||||
|
* Must stay in sync with backend's CLIPROXY_PROFILES in src/auth/profile-detector.ts
|
||||||
|
*/
|
||||||
|
export const CLIPROXY_PROVIDERS = [
|
||||||
|
'gemini',
|
||||||
|
'codex',
|
||||||
|
'agy',
|
||||||
|
'qwen',
|
||||||
|
'iflow',
|
||||||
|
'kiro',
|
||||||
|
'ghcp',
|
||||||
|
'claude',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/** Union type for CLIProxy provider IDs */
|
||||||
|
export type CLIProxyProvider = (typeof CLIPROXY_PROVIDERS)[number];
|
||||||
|
|
||||||
|
/** Check if a string is a valid CLIProxy provider */
|
||||||
|
export function isValidProvider(provider: string): provider is CLIProxyProvider {
|
||||||
|
return CLIPROXY_PROVIDERS.includes(provider as CLIProxyProvider);
|
||||||
|
}
|
||||||
|
|
||||||
// Map provider names to asset filenames (only providers with actual logos)
|
// Map provider names to asset filenames (only providers with actual logos)
|
||||||
export const PROVIDER_ASSETS: Record<string, string> = {
|
export const PROVIDER_ASSETS: Record<string, string> = {
|
||||||
gemini: '/assets/providers/gemini-color.svg',
|
gemini: '/assets/providers/gemini-color.svg',
|
||||||
|
|||||||
Reference in new issue
Block a user