From 81196b0ff14a2b119cdd55d49f8a7eeb63abc8f6 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Fri, 19 Dec 2025 19:41:51 -0500 Subject: [PATCH] refactor(ui): split quick-setup-wizard into setup/wizard/ directory - extract 541-line component into step modules - wrap accounts in useMemo for stable deps - add barrel export in setup/index.ts --- ui/src/components/quick-setup-wizard.tsx | 563 +----------------- ui/src/components/setup/index.ts | 17 + ui/src/components/setup/wizard/constants.ts | 20 + ui/src/components/setup/wizard/index.tsx | 259 ++++++++ .../setup/wizard/progress-indicator.tsx | 21 + .../setup/wizard/steps/account-step.tsx | 84 +++ .../setup/wizard/steps/auth-step.tsx | 97 +++ .../setup/wizard/steps/provider-step.tsx | 26 + .../setup/wizard/steps/success-step.tsx | 35 ++ .../setup/wizard/steps/variant-step.tsx | 104 ++++ ui/src/components/setup/wizard/types.ts | 66 ++ 11 files changed, 755 insertions(+), 537 deletions(-) create mode 100644 ui/src/components/setup/index.ts create mode 100644 ui/src/components/setup/wizard/constants.ts create mode 100644 ui/src/components/setup/wizard/index.tsx create mode 100644 ui/src/components/setup/wizard/progress-indicator.tsx create mode 100644 ui/src/components/setup/wizard/steps/account-step.tsx create mode 100644 ui/src/components/setup/wizard/steps/auth-step.tsx create mode 100644 ui/src/components/setup/wizard/steps/provider-step.tsx create mode 100644 ui/src/components/setup/wizard/steps/success-step.tsx create mode 100644 ui/src/components/setup/wizard/steps/variant-step.tsx create mode 100644 ui/src/components/setup/wizard/types.ts diff --git a/ui/src/components/quick-setup-wizard.tsx b/ui/src/components/quick-setup-wizard.tsx index 930446ab..420786e4 100644 --- a/ui/src/components/quick-setup-wizard.tsx +++ b/ui/src/components/quick-setup-wizard.tsx @@ -1,541 +1,30 @@ /** - * Quick Setup Wizard Component - * Phase 03: Multi-account dashboard support - * - * Step-by-step wizard: Provider -> Auth -> Account -> Variant -> Success + * Quick Setup Wizard - Re-export from modular structure + * @deprecated Import from '@/components/setup/wizard' directly */ -import { useState, useEffect } from 'react'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Card, CardContent } from '@/components/ui/card'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - Copy, - Check, - RefreshCw, - ChevronRight, - ArrowLeft, - Terminal, - User, - Sparkles, - ExternalLink, - Loader2, -} from 'lucide-react'; -import { useCliproxyAuth, useCreateVariant, useStartAuth } from '@/hooks/use-cliproxy'; -import type { AuthStatus, OAuthAccount } from '@/lib/api-client'; -import { MODEL_CATALOGS } from '@/lib/model-catalogs'; -import { applyDefaultPreset } from '@/lib/preset-utils'; -import { cn } from '@/lib/utils'; -import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; -import { toast } from 'sonner'; +/* eslint-disable react-refresh/only-export-components */ +export { + QuickSetupWizard, + ProgressIndicator, + ProviderStep, + AuthStep, + AccountStep, + VariantStep, + SuccessStep, + PROVIDERS, + ALL_STEPS, + getStepProgress, +} from './setup/wizard'; -interface QuickSetupWizardProps { - open: boolean; - onClose: () => void; -} - -type WizardStep = 'provider' | 'auth' | 'account' | 'variant' | 'success'; - -const providers = [ - { id: 'gemini', name: 'Google Gemini', description: 'Gemini Pro/Flash models' }, - { id: 'codex', name: 'OpenAI Codex', description: 'GPT-4 and codex models' }, - { id: 'agy', name: 'Antigravity', description: 'Antigravity AI models' }, - { id: 'qwen', name: 'Alibaba Qwen', description: 'Qwen Code models' }, - { id: 'iflow', name: 'iFlow', description: 'iFlow AI models' }, -]; - -export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) { - const [step, setStep] = useState('provider'); - const [selectedProvider, setSelectedProvider] = useState(''); - const [selectedAccount, setSelectedAccount] = useState(null); - const [variantName, setVariantName] = useState(''); - const [modelName, setModelName] = useState(''); - const [copied, setCopied] = useState(false); - const [isRefreshing, setIsRefreshing] = useState(false); - const [isAddingNewAccount, setIsAddingNewAccount] = useState(false); // Track explicit "add account" action - - const { data: authData, refetch } = useCliproxyAuth(); - const createMutation = useCreateVariant(); - const startAuthMutation = useStartAuth(); - const { privacyMode } = usePrivacy(); - - // Get auth status for selected provider - const providerAuth = authData?.authStatus.find( - (s: AuthStatus) => s.provider === selectedProvider - ); - const accounts = providerAuth?.accounts || []; - - // Reset on close - use timeout to avoid synchronous setState in effect - useEffect(() => { - if (!open) { - const timer = setTimeout(() => { - setStep('provider'); - setSelectedProvider(''); - setSelectedAccount(null); - setVariantName(''); - setModelName(''); - setCopied(false); - setIsAddingNewAccount(false); - }, 0); - return () => clearTimeout(timer); - } - }, [open]); - - // Auto-advance from auth step when account detected - // BUT only if user didn't explicitly click "Add new account" - useEffect(() => { - if (step === 'auth' && accounts.length > 0 && !isAddingNewAccount) { - const timer = setTimeout(() => { - setStep('account'); - }, 0); - return () => clearTimeout(timer); - } - }, [step, accounts, isAddingNewAccount]); - - const copyCommand = async (cmd: string) => { - await navigator.clipboard.writeText(cmd); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - const handleRefresh = async () => { - setIsRefreshing(true); - await refetch(); - setIsRefreshing(false); - }; - - const handleStartAuth = () => { - // Check if this is the first account before auth - const isFirstAccount = (providerAuth?.accounts?.length || 0) === 0; - - startAuthMutation.mutate( - { provider: selectedProvider }, - { - onSuccess: async (data) => { - // Apply default preset if this was the first account - if (isFirstAccount) { - const result = await applyDefaultPreset(selectedProvider); - if (result.success && result.presetName) { - toast.success(`Applied "${result.presetName}" preset`); - } else if (!result.success) { - toast.warning('Account added, but failed to apply default preset'); - } - } - - // Account created, select it and advance to variant step - if (data.account) { - setSelectedAccount(data.account as OAuthAccount); - setStep('variant'); - } - refetch(); // Refresh auth status - }, - } - ); - }; - - const handleProviderSelect = (providerId: string) => { - setSelectedProvider(providerId); - const auth = authData?.authStatus.find((s: AuthStatus) => s.provider === providerId); - const provAccounts = auth?.accounts || []; - - if (provAccounts.length === 0) { - // No accounts - go to auth step to add first account - setStep('auth'); - } else { - // Has accounts - always show account selection (includes "Add new account" option) - setStep('account'); - } - }; - - const handleAccountSelect = (account: OAuthAccount) => { - setSelectedAccount(account); - setStep('variant'); - }; - - const handleCreateVariant = async () => { - if (!variantName || !selectedProvider) return; - - try { - await createMutation.mutateAsync({ - name: variantName, - provider: selectedProvider as 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow', - model: modelName || undefined, - account: selectedAccount?.id, - }); - setStep('success'); - } catch (error) { - console.error('Failed to create variant:', error); - } - }; - - const authCommand = `ccs ${selectedProvider} --auth --add`; - - // Progress steps for indicator - const allSteps = ['provider', 'auth', 'variant', 'success']; - const getStepProgress = (s: WizardStep) => { - if (s === 'account') return 1; // Same as auth - return allSteps.indexOf(s); - }; - const currentProgress = getStepProgress(step); - - // Prevent accidental close when user has made progress - const handleOpenChange = (isOpen: boolean) => { - if (!isOpen) { - // Allow closing from success step or provider step (no progress yet) - if (step === 'success' || step === 'provider') { - onClose(); - return; - } - // For other steps, require explicit close via Cancel/Back - // The X button still works, but clicking outside doesn't close - } - }; - - return ( - - { - // Prevent closing on outside click when user has made progress - if (step !== 'success' && step !== 'provider') { - e.preventDefault(); - } - }} - onEscapeKeyDown={(e) => { - // Prevent ESC close during auth or variant creation - if (startAuthMutation.isPending || createMutation.isPending) { - e.preventDefault(); - } - }} - > - - - - Quick Setup Wizard - - - {step === 'provider' && 'Select a provider to get started'} - {step === 'auth' && 'Authenticate with your provider'} - {step === 'account' && 'Select which account to use'} - {step === 'variant' && 'Create your custom variant'} - {step === 'success' && 'Setup complete!'} - - - -
- {/* Step: Provider Selection */} - {step === 'provider' && ( -
- {providers.map((p) => ( - - ))} -
- )} - - {/* Step: Authentication */} - {step === 'auth' && ( -
- {/* Primary: OAuth Button */} -
-

- Authenticate with {providers.find((p) => p.id === selectedProvider)?.name} to add - an account -

- - {startAuthMutation.isPending && ( -

- Complete the OAuth flow in your browser... -

- )} -
- - {/* Divider */} -
-
- -
-
- Or use terminal -
-
- - {/* Secondary: CLI Command */} - - -
- - Run this command in your terminal: -
-
- - {authCommand} - - -
-
-
- -
- - -
-
- )} - - {/* Step: Account Selection */} - {step === 'account' && ( -
- {/* Existing accounts header */} -
- Select an account ({accounts.length}) -
- -
- {accounts.map((acc: OAuthAccount) => ( - - ))} -
- - {/* Divider */} -
-
- -
-
- Or -
-
- - {/* Add new account button - more prominent */} - - -
- -
-
- )} - - {/* Step: Create Variant */} - {step === 'variant' && ( -
- {selectedAccount && ( -
- - - Using:{' '} - - {selectedAccount.email || selectedAccount.id} - - -
- )} - -
- - setVariantName(e.target.value)} - placeholder="e.g., my-gemini, g3, flash" - /> -
- Use this name to invoke: ccs {variantName || ''} "prompt" -
-
- -
- - -
- Default: {MODEL_CATALOGS[selectedProvider]?.defaultModel || 'provider default'} -
-
- -
- -
- - -
-
-

- Skip if you just wanted to add an account without creating a variant -

-
- )} - - {/* Step: Success */} - {step === 'success' && ( -
-
-
- -
-
-
-
Variant Created!
-
- Your custom variant is ready to use -
-
- - -
Usage:
- - ccs {variantName} "your prompt here" - -
-
- -
- )} -
- - {/* Progress indicator */} -
- {allSteps.map((s, i) => ( -
= i ? 'bg-primary' : 'bg-muted' - }`} - /> - ))} -
- -
- ); -} +export type { + WizardStep, + QuickSetupWizardProps, + ProviderOption, + ProviderStepProps, + AuthStepProps, + AccountStepProps, + VariantStepProps, + SuccessStepProps, + ProgressIndicatorProps, +} from './setup/wizard'; diff --git a/ui/src/components/setup/index.ts b/ui/src/components/setup/index.ts new file mode 100644 index 00000000..763a70e8 --- /dev/null +++ b/ui/src/components/setup/index.ts @@ -0,0 +1,17 @@ +/** + * Setup Components Barrel Export + */ + +// Quick setup wizard (from subdirectory) +export { QuickSetupWizard } from './wizard'; +export type { + WizardStep, + QuickSetupWizardProps, + ProviderOption, + ProviderStepProps, + AuthStepProps, + AccountStepProps, + VariantStepProps, + SuccessStepProps, + ProgressIndicatorProps, +} from './wizard'; diff --git a/ui/src/components/setup/wizard/constants.ts b/ui/src/components/setup/wizard/constants.ts new file mode 100644 index 00000000..6ac80cff --- /dev/null +++ b/ui/src/components/setup/wizard/constants.ts @@ -0,0 +1,20 @@ +/** + * Constants for Quick Setup Wizard + */ + +import type { ProviderOption } from './types'; + +export const PROVIDERS: ProviderOption[] = [ + { id: 'gemini', name: 'Google Gemini', description: 'Gemini Pro/Flash models' }, + { id: 'codex', name: 'OpenAI Codex', description: 'GPT-4 and codex models' }, + { id: 'agy', name: 'Antigravity', description: 'Antigravity AI models' }, + { id: 'qwen', name: 'Alibaba Qwen', description: 'Qwen Code models' }, + { id: 'iflow', name: 'iFlow', description: 'iFlow AI models' }, +]; + +export const ALL_STEPS = ['provider', 'auth', 'variant', 'success']; + +export function getStepProgress(step: string): number { + if (step === 'account') return 1; // Same as auth + return ALL_STEPS.indexOf(step); +} diff --git a/ui/src/components/setup/wizard/index.tsx b/ui/src/components/setup/wizard/index.tsx new file mode 100644 index 00000000..cea897c4 --- /dev/null +++ b/ui/src/components/setup/wizard/index.tsx @@ -0,0 +1,259 @@ +/** + * Quick Setup Wizard Component + * Phase 03: Multi-account dashboard support + * + * Step-by-step wizard: Provider -> Auth -> Account -> Variant -> Success + */ + +/* eslint-disable react-refresh/only-export-components */ +import { useState, useEffect, useMemo } from 'react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from '@/components/ui/dialog'; +import { Sparkles } from 'lucide-react'; +import { useCliproxyAuth, useCreateVariant, useStartAuth } from '@/hooks/use-cliproxy'; +import type { AuthStatus, OAuthAccount } from '@/lib/api-client'; +import { applyDefaultPreset } from '@/lib/preset-utils'; +import { usePrivacy } from '@/contexts/privacy-context'; +import { toast } from 'sonner'; + +import { PROVIDERS, ALL_STEPS, getStepProgress } from './constants'; +import { ProgressIndicator } from './progress-indicator'; +import { ProviderStep } from './steps/provider-step'; +import { AuthStep } from './steps/auth-step'; +import { AccountStep } from './steps/account-step'; +import { VariantStep } from './steps/variant-step'; +import { SuccessStep } from './steps/success-step'; +import type { WizardStep, QuickSetupWizardProps } from './types'; + +export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) { + const [step, setStep] = useState('provider'); + const [selectedProvider, setSelectedProvider] = useState(''); + const [selectedAccount, setSelectedAccount] = useState(null); + const [variantName, setVariantName] = useState(''); + const [modelName, setModelName] = useState(''); + const [isRefreshing, setIsRefreshing] = useState(false); + const [isAddingNewAccount, setIsAddingNewAccount] = useState(false); + + const { data: authData, refetch } = useCliproxyAuth(); + const createMutation = useCreateVariant(); + const startAuthMutation = useStartAuth(); + const { privacyMode } = usePrivacy(); + + // Get auth status for selected provider + const providerAuth = authData?.authStatus.find( + (s: AuthStatus) => s.provider === selectedProvider + ); + const accounts = useMemo(() => providerAuth?.accounts || [], [providerAuth?.accounts]); + + // Reset on close + useEffect(() => { + if (!open) { + const timer = setTimeout(() => { + setStep('provider'); + setSelectedProvider(''); + setSelectedAccount(null); + setVariantName(''); + setModelName(''); + setIsAddingNewAccount(false); + }, 0); + return () => clearTimeout(timer); + } + }, [open]); + + // Auto-advance from auth step when account detected + useEffect(() => { + if (step === 'auth' && accounts.length > 0 && !isAddingNewAccount) { + const timer = setTimeout(() => { + setStep('account'); + }, 0); + return () => clearTimeout(timer); + } + }, [step, accounts, isAddingNewAccount]); + + const handleRefresh = async () => { + setIsRefreshing(true); + await refetch(); + setIsRefreshing(false); + }; + + const handleStartAuth = () => { + const isFirstAccount = (providerAuth?.accounts?.length || 0) === 0; + + startAuthMutation.mutate( + { provider: selectedProvider }, + { + onSuccess: async (data) => { + if (isFirstAccount) { + const result = await applyDefaultPreset(selectedProvider); + if (result.success && result.presetName) { + toast.success(`Applied "${result.presetName}" preset`); + } else if (!result.success) { + toast.warning('Account added, but failed to apply default preset'); + } + } + + if (data.account) { + setSelectedAccount(data.account as OAuthAccount); + setStep('variant'); + } + refetch(); + }, + } + ); + }; + + const handleProviderSelect = (providerId: string) => { + setSelectedProvider(providerId); + const auth = authData?.authStatus.find((s: AuthStatus) => s.provider === providerId); + const provAccounts = auth?.accounts || []; + + if (provAccounts.length === 0) { + setStep('auth'); + } else { + setStep('account'); + } + }; + + const handleAccountSelect = (account: OAuthAccount) => { + setSelectedAccount(account); + setStep('variant'); + }; + + const handleCreateVariant = async () => { + if (!variantName || !selectedProvider) return; + + try { + await createMutation.mutateAsync({ + name: variantName, + provider: selectedProvider as 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow', + model: modelName || undefined, + account: selectedAccount?.id, + }); + setStep('success'); + } catch (error) { + console.error('Failed to create variant:', error); + } + }; + + const authCommand = `ccs ${selectedProvider} --auth --add`; + const currentProgress = getStepProgress(step); + + // Prevent accidental close when user has made progress + const handleOpenChange = (isOpen: boolean) => { + if (!isOpen) { + if (step === 'success' || step === 'provider') { + onClose(); + return; + } + } + }; + + return ( + + { + if (step !== 'success' && step !== 'provider') { + e.preventDefault(); + } + }} + onEscapeKeyDown={(e) => { + if (startAuthMutation.isPending || createMutation.isPending) { + e.preventDefault(); + } + }} + > + + + + Quick Setup Wizard + + + {step === 'provider' && 'Select a provider to get started'} + {step === 'auth' && 'Authenticate with your provider'} + {step === 'account' && 'Select which account to use'} + {step === 'variant' && 'Create your custom variant'} + {step === 'success' && 'Setup complete!'} + + + +
+ {step === 'provider' && ( + + )} + + {step === 'auth' && ( + setStep('provider')} + onStartAuth={handleStartAuth} + onRefresh={handleRefresh} + /> + )} + + {step === 'account' && ( + { + setIsAddingNewAccount(true); + setStep('auth'); + }} + onBack={() => setStep('provider')} + /> + )} + + {step === 'variant' && ( + (accounts.length > 0 ? setStep('account') : setStep('provider'))} + onSkip={onClose} + onCreate={handleCreateVariant} + /> + )} + + {step === 'success' && } +
+ + +
+
+ ); +} + +// Re-exports +export { ProgressIndicator } from './progress-indicator'; +export { ProviderStep } from './steps/provider-step'; +export { AuthStep } from './steps/auth-step'; +export { AccountStep } from './steps/account-step'; +export { VariantStep } from './steps/variant-step'; +export { SuccessStep } from './steps/success-step'; +export { PROVIDERS, ALL_STEPS, getStepProgress } from './constants'; +export type { + WizardStep, + QuickSetupWizardProps, + ProviderOption, + ProviderStepProps, + AuthStepProps, + AccountStepProps, + VariantStepProps, + SuccessStepProps, + ProgressIndicatorProps, +} from './types'; diff --git a/ui/src/components/setup/wizard/progress-indicator.tsx b/ui/src/components/setup/wizard/progress-indicator.tsx new file mode 100644 index 00000000..cd643a51 --- /dev/null +++ b/ui/src/components/setup/wizard/progress-indicator.tsx @@ -0,0 +1,21 @@ +/** + * Progress Indicator Component + * Shows step dots for wizard progress + */ + +import type { ProgressIndicatorProps } from './types'; + +export function ProgressIndicator({ currentProgress, allSteps }: ProgressIndicatorProps) { + return ( +
+ {allSteps.map((s, i) => ( +
= i ? 'bg-primary' : 'bg-muted' + }`} + /> + ))} +
+ ); +} diff --git a/ui/src/components/setup/wizard/steps/account-step.tsx b/ui/src/components/setup/wizard/steps/account-step.tsx new file mode 100644 index 00000000..3914db7e --- /dev/null +++ b/ui/src/components/setup/wizard/steps/account-step.tsx @@ -0,0 +1,84 @@ +/** + * Account Selection Step + */ + +import { Button } from '@/components/ui/button'; +import { ChevronRight, ArrowLeft, User, ExternalLink } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; +import type { AccountStepProps } from '../types'; + +export function AccountStep({ + accounts, + privacyMode, + onSelect, + onAddNew, + onBack, +}: AccountStepProps) { + return ( +
+ {/* Existing accounts header */} +
+ Select an account ({accounts.length}) +
+ +
+ {accounts.map((acc) => ( + + ))} +
+ + {/* Divider */} +
+
+ +
+
+ Or +
+
+ + {/* Add new account button - more prominent */} + + +
+ +
+
+ ); +} diff --git a/ui/src/components/setup/wizard/steps/auth-step.tsx b/ui/src/components/setup/wizard/steps/auth-step.tsx new file mode 100644 index 00000000..6b200c83 --- /dev/null +++ b/ui/src/components/setup/wizard/steps/auth-step.tsx @@ -0,0 +1,97 @@ +/** + * Authentication Step + */ + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Copy, Check, RefreshCw, ArrowLeft, Terminal, ExternalLink, Loader2 } from 'lucide-react'; +import type { AuthStepProps } from '../types'; + +export function AuthStep({ + selectedProvider, + providers, + authCommand, + isRefreshing, + isPending, + onBack, + onStartAuth, + onRefresh, +}: AuthStepProps) { + const [copied, setCopied] = useState(false); + + const copyCommand = async (cmd: string) => { + await navigator.clipboard.writeText(cmd); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+ {/* Primary: OAuth Button */} +
+

+ Authenticate with {providers.find((p) => p.id === selectedProvider)?.name} to add an + account +

+ + {isPending && ( +

+ Complete the OAuth flow in your browser... +

+ )} +
+ + {/* Divider */} +
+
+ +
+
+ Or use terminal +
+
+ + {/* Secondary: CLI Command */} + + +
+ + Run this command in your terminal: +
+
+ + {authCommand} + + +
+
+
+ +
+ + +
+
+ ); +} diff --git a/ui/src/components/setup/wizard/steps/provider-step.tsx b/ui/src/components/setup/wizard/steps/provider-step.tsx new file mode 100644 index 00000000..2f8e0901 --- /dev/null +++ b/ui/src/components/setup/wizard/steps/provider-step.tsx @@ -0,0 +1,26 @@ +/** + * Provider Selection Step + */ + +import { ChevronRight } from 'lucide-react'; +import type { ProviderStepProps } from '../types'; + +export function ProviderStep({ providers, onSelect }: ProviderStepProps) { + return ( +
+ {providers.map((p) => ( + + ))} +
+ ); +} diff --git a/ui/src/components/setup/wizard/steps/success-step.tsx b/ui/src/components/setup/wizard/steps/success-step.tsx new file mode 100644 index 00000000..56bf23e9 --- /dev/null +++ b/ui/src/components/setup/wizard/steps/success-step.tsx @@ -0,0 +1,35 @@ +/** + * Success Step + */ + +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Check } from 'lucide-react'; +import type { SuccessStepProps } from '../types'; + +export function SuccessStep({ variantName, onClose }: SuccessStepProps) { + return ( +
+
+
+ +
+
+
+
Variant Created!
+
Your custom variant is ready to use
+
+ + +
Usage:
+ + ccs {variantName} "your prompt here" + +
+
+ +
+ ); +} diff --git a/ui/src/components/setup/wizard/steps/variant-step.tsx b/ui/src/components/setup/wizard/steps/variant-step.tsx new file mode 100644 index 00000000..7eafe601 --- /dev/null +++ b/ui/src/components/setup/wizard/steps/variant-step.tsx @@ -0,0 +1,104 @@ +/** + * Variant Creation Step + */ + +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { ArrowLeft, User } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; +import { MODEL_CATALOGS } from '@/lib/model-catalogs'; +import type { VariantStepProps } from '../types'; + +export function VariantStep({ + selectedProvider, + selectedAccount, + variantName, + modelName, + isPending, + privacyMode, + onVariantNameChange, + onModelChange, + onBack, + onSkip, + onCreate, +}: VariantStepProps) { + return ( +
+ {selectedAccount && ( +
+ + + Using:{' '} + + {selectedAccount.email || selectedAccount.id} + + +
+ )} + +
+ + onVariantNameChange(e.target.value)} + placeholder="e.g., my-gemini, g3, flash" + /> +
+ Use this name to invoke: ccs {variantName || ''} "prompt" +
+
+ +
+ + +
+ Default: {MODEL_CATALOGS[selectedProvider]?.defaultModel || 'provider default'} +
+
+ +
+ +
+ + +
+
+

+ Skip if you just wanted to add an account without creating a variant +

+
+ ); +} diff --git a/ui/src/components/setup/wizard/types.ts b/ui/src/components/setup/wizard/types.ts new file mode 100644 index 00000000..cccbf332 --- /dev/null +++ b/ui/src/components/setup/wizard/types.ts @@ -0,0 +1,66 @@ +/** + * Types for Quick Setup Wizard + */ + +import type { OAuthAccount } from '@/lib/api-client'; + +export type WizardStep = 'provider' | 'auth' | 'account' | 'variant' | 'success'; + +export interface QuickSetupWizardProps { + open: boolean; + onClose: () => void; +} + +export interface ProviderOption { + id: string; + name: string; + description: string; +} + +export interface ProviderStepProps { + providers: ProviderOption[]; + onSelect: (providerId: string) => void; +} + +export interface AuthStepProps { + selectedProvider: string; + providers: ProviderOption[]; + authCommand: string; + isRefreshing: boolean; + isPending: boolean; + onBack: () => void; + onStartAuth: () => void; + onRefresh: () => void; +} + +export interface AccountStepProps { + accounts: OAuthAccount[]; + privacyMode: boolean; + onSelect: (account: OAuthAccount) => void; + onAddNew: () => void; + onBack: () => void; +} + +export interface VariantStepProps { + selectedProvider: string; + selectedAccount: OAuthAccount | null; + variantName: string; + modelName: string; + isPending: boolean; + privacyMode: boolean; + onVariantNameChange: (value: string) => void; + onModelChange: (value: string) => void; + onBack: () => void; + onSkip: () => void; + onCreate: () => void; +} + +export interface SuccessStepProps { + variantName: string; + onClose: () => void; +} + +export interface ProgressIndicatorProps { + currentProgress: number; + allSteps: string[]; +}