diff --git a/ui/src/components/account/add-account-dialog.tsx b/ui/src/components/account/add-account-dialog.tsx index feb43cfe..12cfda55 100644 --- a/ui/src/components/account/add-account-dialog.tsx +++ b/ui/src/components/account/add-account-dialog.tsx @@ -378,13 +378,13 @@ export function AddAccountDialog({ return ( { // Prevent accidental close by clicking outside during auth if (showAuthUI) e.preventDefault(); }} > - + {t('addAccountDialog.title', { displayName })} {isKiro @@ -395,7 +395,7 @@ export function AddAccountDialog({ -
+
{requiresAgyResponsibilityFlow && !showAuthUI && ( - {isKiro && !showAuthUI && ( -
diff --git a/ui/src/components/setup/wizard/index.tsx b/ui/src/components/setup/wizard/index.tsx index aca2a3fa..f911da32 100644 --- a/ui/src/components/setup/wizard/index.tsx +++ b/ui/src/components/setup/wizard/index.tsx @@ -177,7 +177,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) { return ( { if (step !== 'success' && step !== 'provider') { e.preventDefault(); @@ -189,8 +189,8 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) { } }} > - - + + {i18n.t('setupWizard.title')} @@ -203,7 +203,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) { -
+
{step === 'provider' && ( )} @@ -254,7 +254,9 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) { {step === 'success' && }
- +
+ +
); diff --git a/ui/src/components/setup/wizard/steps/variant-step.tsx b/ui/src/components/setup/wizard/steps/variant-step.tsx index 45a1639f..394212de 100644 --- a/ui/src/components/setup/wizard/steps/variant-step.tsx +++ b/ui/src/components/setup/wizard/steps/variant-step.tsx @@ -73,12 +73,12 @@ export function VariantStep({ return (
{selectedAccount && ( -
- -
+
+ +
{t('setupVariant.using')}{' '} - + {selectedAccountIdentity?.email} @@ -167,16 +167,18 @@ export function VariantStep({ value={catalogModels.some((m) => m.id === modelName) ? modelName : ''} onValueChange={handleModelSelect} > - + - + {catalogModels.map((m) => ( -
- {m.name} +
+ {m.name} {m.description && ( - - {m.description} + + - {m.description} + )}
@@ -196,12 +198,12 @@ export function VariantStep({
-
- -
+
diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts index ec63f655..3541ed4d 100644 --- a/ui/src/lib/i18n.ts +++ b/ui/src/lib/i18n.ts @@ -140,9 +140,9 @@ const resources = { }, setupVariant: { using: 'Using:', - variantNameRequired: 'Variant Name *', + variantNameRequired: 'Runtime Variant Name *', variantNamePlaceholder: 'e.g., my-gemini, g3, flash', - invokeHintPrefix: 'Use this name to invoke:', + invokeHintPrefix: 'Run with this variant:', model: 'Model', modelPlaceholder: 'e.g., gemini-2.5-pro, claude-opus-4-6-thinking', choosePresetInstead: 'Choose from presets instead', @@ -155,7 +155,7 @@ const resources = { skip: 'Skip', creating: 'Creating...', createVariant: 'Create Variant', - skipHint: 'Skip if you just wanted to add an account without creating a variant', + skipHint: 'Skip if account setup is enough and you do not need a separate runtime variant', }, customPresetDialog: { title: 'Custom Preset', @@ -1277,6 +1277,10 @@ const resources = { emptyControlPanelPrefix: 'For live usage stats and real-time monitoring, visit the', controlPanel: 'Control Panel', quickSetup: 'Quick Setup', + addAccount: 'Add Account', + advancedVariant: 'Advanced Variant', + setupActionsHint: + 'Add accounts here. Create variants only when you need a separate runtime/profile.', accountManagement: 'CCS-level account management', providers: 'Providers', variants: 'Variants', @@ -1783,11 +1787,11 @@ const resources = { // Domain 2: Accounts / Auth // ======================================== setupWizard: { - title: 'Quick Setup Wizard', - stepProviderDesc: 'Select a provider to get started', + title: 'Advanced Variant Setup', + stepProviderDesc: 'Select a provider for the runtime variant', stepAuthDesc: 'Authenticate with your provider', stepAccountDesc: 'Select which account to use', - stepVariantDesc: 'Create your custom variant', + stepVariantDesc: 'Optional: create a custom runtime variant', stepSuccessDesc: 'Setup complete!', authStep: { authenticateWith: 'Authenticate with {{provider}} to add an account', @@ -1948,6 +1952,8 @@ const resources = { status: 'Status', loadingSettings: 'Loading settings...', loadingEditor: 'Loading editor...', + accounts: 'Accounts', + addAccount: 'Add Account', noAccountsConnected: 'No accounts connected', addAccountToStart: 'Add an account to get started', gcpProjectIdReadonly: 'GCP Project ID (read-only)', @@ -2796,9 +2802,9 @@ const resources = { }, setupVariant: { using: '当前使用:', - variantNameRequired: '变体名称 *', + variantNameRequired: '运行时变体名称 *', variantNamePlaceholder: '例如:my-gemini、g3、flash', - invokeHintPrefix: '可通过该名称调用:', + invokeHintPrefix: '使用此变体运行:', model: '模型', modelPlaceholder: '例如:gemini-2.5-pro、claude-opus-4-6-thinking', choosePresetInstead: '改为从预设中选择', @@ -2811,7 +2817,7 @@ const resources = { skip: '跳过', creating: '创建中...', createVariant: '创建变体', - skipHint: '如果你只想添加账号而不创建变体,可以直接跳过', + skipHint: '如果账号设置已足够且不需要单独运行时变体,可以跳过', }, customPresetDialog: { title: '自定义预设', @@ -3855,6 +3861,9 @@ const resources = { emptyControlPanelPrefix: '如需查看实时用量和监控,请前往', controlPanel: '控制面板', quickSetup: '快速设置', + addAccount: '添加账号', + advancedVariant: '高级变体', + setupActionsHint: '在这里添加账号。只有需要单独运行时/配置档时才创建变体。', accountManagement: 'CCS 级账号管理', providers: '提供商', variants: '变体', @@ -4327,11 +4336,11 @@ const resources = { failedSave: '保存失败', }, setupWizard: { - title: '快速设置向导', - stepProviderDesc: '选择一个提供商开始', + title: '高级变体设置', + stepProviderDesc: '为运行时变体选择提供商', stepAuthDesc: '完成提供商认证', stepAccountDesc: '选择要使用的账号', - stepVariantDesc: '创建自定义变体', + stepVariantDesc: '可选:创建自定义运行时变体', stepSuccessDesc: '设置完成!', authStep: { authenticateWith: '通过 {{provider}} 认证以添加账号', @@ -4488,6 +4497,8 @@ const resources = { status: '状态', loadingSettings: '加载设置中...', loadingEditor: '加载编辑器中...', + accounts: '账号', + addAccount: '添加账号', noAccountsConnected: '未连接账号', addAccountToStart: '添加账号以开始', gcpProjectIdReadonly: 'GCP 项目 ID(只读)', @@ -5314,9 +5325,9 @@ const resources = { }, setupVariant: { using: 'Sử dụng:', - variantNameRequired: 'Tên biến thể *', + variantNameRequired: 'Tên biến thể runtime *', variantNamePlaceholder: 'ví dụ: my-gemini, g3, flash', - invokeHintPrefix: 'Sử dụng tên này để gọi:', + invokeHintPrefix: 'Chạy bằng biến thể này:', model: 'Mô hình', modelPlaceholder: 'ví dụ: gemini-2.5-pro, claude-opus-4-5', choosePresetInstead: 'Hoặc chọn từ preset có sẵn', @@ -5329,7 +5340,7 @@ const resources = { skip: 'Bỏ qua', creating: 'Đang tạo...', createVariant: 'Tạo biến thể', - skipHint: 'Bỏ qua nếu bạn chỉ muốn thêm tài khoản mà không tạo biến thể', + skipHint: 'Bỏ qua nếu thiết lập tài khoản là đủ và không cần biến thể runtime riêng', }, customPresetDialog: { title: 'Preset tùy chỉnh', @@ -6460,6 +6471,9 @@ const resources = { 'Để biết số liệu thống kê sử dụng trực tiếp và theo dõi thời gian thực, hãy truy cập', controlPanel: 'Bảng điều khiển', quickSetup: 'Thiết lập nhanh', + addAccount: 'Thêm tài khoản', + advancedVariant: 'Biến thể nâng cao', + setupActionsHint: 'Thêm tài khoản tại đây. Chỉ tạo biến thể khi cần runtime/profile riêng.', accountManagement: 'Quản lý tài khoản cấp CCS', providers: 'Nhà cung cấp', variants: 'Biến thể', @@ -6940,11 +6954,11 @@ const resources = { failedSave: 'Không lưu được', }, setupWizard: { - title: 'Trình thiết lập nhanh', - stepProviderDesc: 'Chọn nhà cung cấp để bắt đầu', + title: 'Thiết lập biến thể nâng cao', + stepProviderDesc: 'Chọn nhà cung cấp cho biến thể runtime', stepAuthDesc: 'Xác thực với nhà cung cấp', stepAccountDesc: 'Chọn tài khoản để sử dụng', - stepVariantDesc: 'Tạo biến thể tùy chỉnh', + stepVariantDesc: 'Tùy chọn: tạo biến thể runtime tùy chỉnh', stepSuccessDesc: 'Thiết lập hoàn tất!', authStep: { authenticateWith: 'Xác thực với {{provider}} để thêm tài khoản', @@ -7101,6 +7115,8 @@ const resources = { status: 'Trạng thái', loadingSettings: 'Đang tải cài đặt...', loadingEditor: 'Đang tải trình soạn thảo...', + accounts: 'Tài khoản', + addAccount: 'Thêm tài khoản', noAccountsConnected: 'Chưa kết nối tài khoản nào', addAccountToStart: 'Thêm tài khoản để bắt đầu', gcpProjectIdReadonly: 'GCP Project ID (chỉ đọc)', @@ -7941,9 +7957,9 @@ const resources = { }, setupVariant: { using: '使用中:', - variantNameRequired: 'バリアント名 *', + variantNameRequired: 'ランタイムバリアント名 *', variantNamePlaceholder: '例: my-gemini, g3, flash', - invokeHintPrefix: 'この名前で呼び出せます:', + invokeHintPrefix: 'このバリアントで実行:', model: 'モデル', modelPlaceholder: '例: gemini-2.5-pro, claude-opus-4-6-thinking', choosePresetInstead: '代わりにプリセットから選択', @@ -7956,7 +7972,8 @@ const resources = { skip: 'スキップ', creating: '作成中...', createVariant: 'バリアントを作成', - skipHint: 'アカウント追加だけが目的なら、バリアント作成はスキップできます', + skipHint: + 'アカウント設定だけで十分で、別ランタイムバリアントが不要な場合はスキップできます', }, customPresetDialog: { title: 'カスタムプリセット', @@ -9092,6 +9109,10 @@ const resources = { emptyControlPanelPrefix: 'ライブ利用統計とリアルタイム監視は', controlPanel: 'コントロールパネルで確認できます', quickSetup: 'クイックセットアップ', + addAccount: 'アカウントを追加', + advancedVariant: '高度なバリアント', + setupActionsHint: + 'ここでアカウントを追加します。別ランタイム/プロファイルが必要な場合のみバリアントを作成します。', accountManagement: 'CCSレベルのアカウント管理', providers: 'プロバイダー', variants: 'バリアント', @@ -10008,6 +10029,8 @@ const resources = { status: 'ステータス', loadingSettings: '設定を読み込み中...', loadingEditor: 'エディターを読み込み中...', + accounts: 'アカウント', + addAccount: 'アカウントを追加', noAccountsConnected: '接続されたアカウントなし', addAccountToStart: 'アカウントを追加して開始', gcpProjectIdReadonly: 'GCP プロジェクト ID(読み取り専用)', @@ -10276,11 +10299,11 @@ const resources = { }, }, setupWizard: { - title: 'クイックセットアップウィザード', - stepProviderDesc: 'プロバイダーを選択して開始', + title: '高度なバリアント設定', + stepProviderDesc: 'ランタイムバリアント用のプロバイダーを選択', stepAuthDesc: 'プロバイダーで認証', stepAccountDesc: '使用するアカウントを選択', - stepVariantDesc: 'カスタムバリアントを作成', + stepVariantDesc: '任意: カスタムランタイムバリアントを作成', stepSuccessDesc: 'セットアップ完了!', authStep: { authenticateWith: '{{provider}} で認証してアカウントを追加', diff --git a/ui/src/pages/cliproxy.tsx b/ui/src/pages/cliproxy.tsx index 962591e2..95a8d6e3 100644 --- a/ui/src/pages/cliproxy.tsx +++ b/ui/src/pages/cliproxy.tsx @@ -10,7 +10,7 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Skeleton } from '@/components/ui/skeleton'; -import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2 } from 'lucide-react'; +import { Check, X, RefreshCw, Plus, Zap, GitBranch, Trash2 } from 'lucide-react'; import { QuickSetupWizard } from '@/components/quick-setup-wizard'; import { AddAccountDialog } from '@/components/account/add-account-dialog'; import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card'; @@ -183,7 +183,13 @@ function VariantSidebarItem({ } // Empty state for right panel -function EmptyProviderState({ onSetup }: { onSetup: () => void }) { +function EmptyProviderState({ + onAddAccount, + onCreateVariant, +}: { + onAddAccount: () => void; + onCreateVariant: () => void; +}) { const { t } = useTranslation(); return (
@@ -200,10 +206,16 @@ function EmptyProviderState({ onSetup }: { onSetup: () => void }) { .

- +
+ + +
); @@ -305,6 +317,38 @@ export function CliproxyPage() { const parentAuthForVariant = selectedVariantData ? providers.find((p) => p.provider === selectedVariantData.provider) : undefined; + const selectedAccountTarget = + selectedVariantData && parentAuthForVariant + ? { + provider: selectedVariantData.provider, + displayName: parentAuthForVariant.displayName, + accountCount: parentAuthForVariant.accounts?.length || 0, + } + : selectedStatus + ? { + provider: selectedStatus.provider, + displayName: selectedStatus.displayName, + accountCount: selectedStatus.accounts?.length || 0, + } + : null; + const fallbackAccountTarget = selectedVariantData + ? { + provider: selectedVariantData.provider, + displayName: getProviderDisplayName(selectedVariantData.provider), + accountCount: 0, + } + : selectedProvider && isValidProvider(selectedProvider) + ? { + provider: selectedProvider, + displayName: getProviderDisplayName(selectedProvider), + accountCount: 0, + } + : { + provider: 'gemini', + displayName: getProviderDisplayName('gemini'), + accountCount: 0, + }; + const accountSetupTarget = selectedAccountTarget ?? fallbackAccountTarget; const warningProvider = (selectedVariantData?.provider || selectedStatus?.provider || '') .toLowerCase() .trim(); @@ -352,6 +396,14 @@ export function CliproxyPage() { setSelectedProvider(null); }; + const handleAddAccountForSelectedProvider = () => { + setAddAccountProvider({ + provider: accountSetupTarget.provider, + displayName: accountSetupTarget.displayName, + isFirstAccount: accountSetupTarget.accountCount === 0, + }); + }; + return (
{/* Left Sidebar */} @@ -377,15 +429,29 @@ export function CliproxyPage() { {t('cliproxyPage.accountManagement')}

- +
+ + +

+ {t('cliproxyPage.setupActionsHint')} +

+
{/* Providers List */} @@ -577,7 +643,10 @@ export function CliproxyPage() { /> ) : ( - setWizardOpen(true)} /> + setWizardOpen(true)} + /> )}
diff --git a/ui/tests/unit/components/setup/quick-setup-wizard.test.tsx b/ui/tests/unit/components/setup/quick-setup-wizard.test.tsx index abaa5de8..ead77d69 100644 --- a/ui/tests/unit/components/setup/quick-setup-wizard.test.tsx +++ b/ui/tests/unit/components/setup/quick-setup-wizard.test.tsx @@ -15,6 +15,7 @@ const hookState = vi.hoisted(() => ({ >; } | undefined, + startAuthData: {} as Record, })); const applyDefaultPresetMock = vi.hoisted(() => vi.fn()); @@ -43,7 +44,7 @@ vi.mock('@/hooks/use-cliproxy', () => ({ useStartAuth: () => ({ isPending: false, mutate: (_args: unknown, options?: { onSuccess?: (data: Record) => void }) => { - options?.onSuccess?.({}); + options?.onSuccess?.(hookState.startAuthData); }, }), useCancelAuth: () => ({ @@ -72,7 +73,22 @@ vi.mock('@/components/setup/wizard/steps/account-step', () => ({ })); vi.mock('@/components/setup/wizard/steps/variant-step', () => ({ - VariantStep: () =>
variant-step
, + VariantStep: ({ + variantName, + onVariantNameChange, + }: { + variantName: string; + onVariantNameChange: (value: string) => void; + }) => ( + + ), })); vi.mock('@/components/setup/wizard/steps/success-step', () => ({ @@ -84,6 +100,7 @@ import { QuickSetupWizard } from '@/components/setup/wizard'; describe('QuickSetupWizard preset catalog reuse', () => { beforeEach(() => { hookState.catalogData = undefined; + hookState.startAuthData = {}; applyDefaultPresetMock.mockReset(); applyDefaultPresetMock.mockResolvedValue({ success: true, presetName: 'Gemini Pro' }); }); @@ -126,4 +143,10 @@ describe('QuickSetupWizard preset catalog reuse', () => { ) ); }); + + it('labels the wizard as advanced variant setup', () => { + render(); + + expect(screen.getByRole('dialog')).toHaveTextContent('Advanced Variant Setup'); + }); }); diff --git a/ui/tests/unit/pages/cliproxy-page.test.tsx b/ui/tests/unit/pages/cliproxy-page.test.tsx index 92b57035..c0c422b6 100644 --- a/ui/tests/unit/pages/cliproxy-page.test.tsx +++ b/ui/tests/unit/pages/cliproxy-page.test.tsx @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { render, screen, userEvent } from '@tests/setup/test-utils'; const hookState = vi.hoisted(() => ({ + variantsData: { variants: [] as Array<{ name: string; provider: string; target?: string }> }, authData: { authStatus: [ { @@ -36,7 +37,7 @@ const hookState = vi.hoisted(() => ({ vi.mock('@/hooks/use-cliproxy', () => ({ useCliproxy: () => ({ - data: { variants: [] }, + data: hookState.variantsData, isFetching: false, }), useCliproxyAuth: () => ({ @@ -60,7 +61,8 @@ vi.mock('@/hooks/use-cliproxy', () => ({ })); vi.mock('@/components/quick-setup-wizard', () => ({ - QuickSetupWizard: () => null, + QuickSetupWizard: ({ open }: { open: boolean }) => + open ?
: null, })); vi.mock('@/components/monitoring/proxy-status-widget', () => ({ @@ -82,9 +84,21 @@ vi.mock('@/components/cliproxy/provider-editor', () => ({ })); vi.mock('@/components/account/add-account-dialog', () => ({ - AddAccountDialog: ({ open, catalog }: { open: boolean; catalog?: { defaultModel?: string } }) => + AddAccountDialog: ({ + open, + provider, + catalog, + }: { + open: boolean; + provider?: string; + catalog?: { defaultModel?: string }; + }) => open ? ( -
+
) : null, })); @@ -92,6 +106,7 @@ import { CliproxyPage } from '@/pages/cliproxy'; describe('CliproxyPage add-account catalog gating', () => { beforeEach(() => { + hookState.variantsData = { variants: [] }; hookState.authData = { authStatus: [ { @@ -154,4 +169,51 @@ describe('CliproxyPage add-account catalog gating', () => { 'gemini-3.9-pro-preview' ); }); + + it('uses the sidebar setup CTA for account connection instead of variant creation', async () => { + render(); + + await userEvent.click(screen.getByRole('button', { name: /add account/i })); + + expect(screen.getByTestId('add-account-dialog')).toHaveAttribute('data-provider', 'gemini'); + expect(screen.queryByTestId('advanced-variant-wizard')).not.toBeInTheDocument(); + }); + + it('adds accounts against the base provider when a runtime variant is selected', async () => { + hookState.variantsData = { + variants: [{ name: 'gemini-parallel', provider: 'gemini', target: 'claude' }], + }; + + render(); + + await userEvent.click(screen.getByRole('button', { name: /gemini-parallel/i })); + await userEvent.click(screen.getByRole('button', { name: /add account/i })); + + expect(screen.getByTestId('add-account-dialog')).toHaveAttribute('data-provider', 'gemini'); + expect(screen.queryByTestId('advanced-variant-wizard')).not.toBeInTheDocument(); + }); + + it('surfaces account setup from the empty state before provider data is available', async () => { + hookState.authData = { + authStatus: [], + source: 'local', + }; + + render(); + + const addAccountButtons = screen.getAllByRole('button', { name: /add account/i }); + await userEvent.click(addAccountButtons[addAccountButtons.length - 1]); + + expect(screen.getByTestId('add-account-dialog')).toHaveAttribute('data-provider', 'gemini'); + expect(screen.queryByTestId('advanced-variant-wizard')).not.toBeInTheDocument(); + }); + + it('keeps advanced variant creation available as a secondary action', async () => { + render(); + + await userEvent.click(screen.getByRole('button', { name: /advanced variant/i })); + + expect(screen.getByTestId('advanced-variant-wizard')).toBeInTheDocument(); + expect(screen.queryByTestId('add-account-dialog')).not.toBeInTheDocument(); + }); });