mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 20:17:45 +00:00
fix: clarify CLIProxy setup actions
Make account setup the primary CLIProxy CTA, keep advanced variant creation secondary, and contain setup dialogs across narrow viewports.
This commit is contained in:
@@ -378,13 +378,13 @@ export function AddAccountDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent
|
||||
className="sm:max-w-md"
|
||||
className="grid max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] max-w-md grid-rows-[auto_minmax(0,1fr)] overflow-hidden p-0 sm:max-w-md"
|
||||
onInteractOutside={(e) => {
|
||||
// Prevent accidental close by clicking outside during auth
|
||||
if (showAuthUI) e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogHeader className="min-w-0 px-6 pt-6 pr-12">
|
||||
<DialogTitle>{t('addAccountDialog.title', { displayName })}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isKiro
|
||||
@@ -395,7 +395,7 @@ export function AddAccountDialog({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="min-h-0 space-y-4 overflow-y-auto px-6 py-4">
|
||||
{requiresAgyResponsibilityFlow && !showAuthUI && (
|
||||
<AntigravityResponsibilityChecklist
|
||||
value={agyRiskChecklist}
|
||||
@@ -748,12 +748,17 @@ export function AddAccountDialog({
|
||||
)}
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex items-center justify-end gap-2 pt-2">
|
||||
<Button variant="ghost" onClick={handleCancel}>
|
||||
<div className="flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<Button variant="ghost" onClick={handleCancel} className="w-full sm:w-auto">
|
||||
{t('addAccountDialog.cancel')}
|
||||
</Button>
|
||||
{isKiro && !showAuthUI && (
|
||||
<Button variant="outline" onClick={handleKiroImport} disabled={isPending}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleKiroImport}
|
||||
disabled={isPending}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{kiroImportMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
@@ -770,6 +775,7 @@ export function AddAccountDialog({
|
||||
{!showAuthUI && (
|
||||
<Button
|
||||
onClick={handleAuthenticate}
|
||||
className="w-full sm:w-auto"
|
||||
disabled={
|
||||
isPending ||
|
||||
isPowerUserModePending ||
|
||||
|
||||
@@ -142,8 +142,7 @@ export function AccountsSection({
|
||||
/>
|
||||
)}
|
||||
<User className="w-4 h-4" />
|
||||
{/* TODO i18n: missing key for "Accounts" */}
|
||||
Accounts
|
||||
{t('providerEditor.accounts')}
|
||||
{accounts.length > 0 && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{accounts.length}
|
||||
@@ -152,7 +151,7 @@ export function AccountsSection({
|
||||
</h3>
|
||||
<Button variant="default" size="sm" className="h-7 text-xs gap-1" onClick={onAddAccount}>
|
||||
<Plus className="w-3 h-3" />
|
||||
Add
|
||||
{t('providerEditor.addAccount')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent
|
||||
className="sm:max-w-lg"
|
||||
className="grid max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] max-w-lg grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden p-0 sm:max-w-lg"
|
||||
onPointerDownOutside={(e) => {
|
||||
if (step !== 'success' && step !== 'provider') {
|
||||
e.preventDefault();
|
||||
@@ -189,8 +189,8 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<DialogHeader className="min-w-0 px-6 pt-6 pr-12">
|
||||
<DialogTitle className="flex min-w-0 items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-primary" />
|
||||
{i18n.t('setupWizard.title')}
|
||||
</DialogTitle>
|
||||
@@ -203,7 +203,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="min-h-0 space-y-4 overflow-y-auto px-6 py-4">
|
||||
{step === 'provider' && (
|
||||
<ProviderStep providers={PROVIDERS} onSelect={handleProviderSelect} />
|
||||
)}
|
||||
@@ -254,7 +254,9 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
{step === 'success' && <SuccessStep variantName={variantName} onClose={onClose} />}
|
||||
</div>
|
||||
|
||||
<ProgressIndicator currentProgress={currentProgress} allSteps={ALL_STEPS} />
|
||||
<div className="px-6 pb-6">
|
||||
<ProgressIndicator currentProgress={currentProgress} allSteps={ALL_STEPS} />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -73,12 +73,12 @@ export function VariantStep({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{selectedAccount && (
|
||||
<div className="flex items-start gap-2 p-2 bg-muted/50 rounded-md text-sm">
|
||||
<User className="w-4 h-4" />
|
||||
<div className="space-y-1">
|
||||
<div className="flex min-w-0 items-start gap-2 rounded-md bg-muted/50 p-2 text-sm">
|
||||
<User className="w-4 h-4 shrink-0" />
|
||||
<div className="min-w-0 space-y-1">
|
||||
<span>
|
||||
{t('setupVariant.using')}{' '}
|
||||
<span className={cn(privacyMode && PRIVACY_BLUR_CLASS)}>
|
||||
<span className={cn('break-all', privacyMode && PRIVACY_BLUR_CLASS)}>
|
||||
{selectedAccountIdentity?.email}
|
||||
</span>
|
||||
</span>
|
||||
@@ -167,16 +167,18 @@ export function VariantStep({
|
||||
value={catalogModels.some((m) => m.id === modelName) ? modelName : ''}
|
||||
onValueChange={handleModelSelect}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="min-w-0">
|
||||
<SelectValue placeholder={t('setupVariant.selectModel')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent className="max-w-[calc(100vw-2rem)]">
|
||||
{catalogModels.map((m) => (
|
||||
<SelectItem key={m.id} value={m.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{m.name}</span>
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate">{m.name}</span>
|
||||
{m.description && (
|
||||
<span className="text-xs text-muted-foreground">- {m.description}</span>
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
- {m.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
@@ -196,12 +198,12 @@ export function VariantStep({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<Button variant="ghost" onClick={onBack}>
|
||||
<div className="flex flex-col gap-2 pt-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<Button variant="ghost" onClick={onBack} className="justify-start">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
{t('setupVariant.back')}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:items-center">
|
||||
<Button variant="ghost" onClick={onSkip}>
|
||||
{t('setupVariant.skip')}
|
||||
</Button>
|
||||
|
||||
+47
-24
@@ -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}} で認証してアカウントを追加',
|
||||
|
||||
+85
-16
@@ -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 (
|
||||
<div className="flex-1 flex items-center justify-center bg-muted/20">
|
||||
@@ -200,10 +206,16 @@ function EmptyProviderState({ onSetup }: { onSetup: () => void }) {
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<Button onClick={onSetup} className="gap-2">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
{t('cliproxyPage.quickSetup')}
|
||||
</Button>
|
||||
<div className="flex flex-col justify-center gap-2 sm:flex-row">
|
||||
<Button onClick={onAddAccount} className="gap-2">
|
||||
<Plus className="w-4 h-4" />
|
||||
{t('cliproxyPage.addAccount')}
|
||||
</Button>
|
||||
<Button onClick={onCreateVariant} variant="outline" className="gap-2">
|
||||
<GitBranch className="w-4 h-4" />
|
||||
{t('cliproxyPage.advancedVariant')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -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 (
|
||||
<div className="flex h-full min-h-0 overflow-hidden">
|
||||
{/* Left Sidebar */}
|
||||
@@ -377,15 +429,29 @@ export function CliproxyPage() {
|
||||
{t('cliproxyPage.accountManagement')}
|
||||
</p>
|
||||
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="w-full gap-2"
|
||||
onClick={() => setWizardOpen(true)}
|
||||
>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
{t('cliproxyPage.quickSetup')}
|
||||
</Button>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="w-full gap-2"
|
||||
onClick={handleAddAccountForSelectedProvider}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
{t('cliproxyPage.addAccount')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full gap-2"
|
||||
onClick={() => setWizardOpen(true)}
|
||||
>
|
||||
<GitBranch className="w-4 h-4" />
|
||||
{t('cliproxyPage.advancedVariant')}
|
||||
</Button>
|
||||
<p className="text-[11px] leading-relaxed text-muted-foreground">
|
||||
{t('cliproxyPage.setupActionsHint')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Providers List */}
|
||||
@@ -577,7 +643,10 @@ export function CliproxyPage() {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<EmptyProviderState onSetup={() => setWizardOpen(true)} />
|
||||
<EmptyProviderState
|
||||
onAddAccount={handleAddAccountForSelectedProvider}
|
||||
onCreateVariant={() => setWizardOpen(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ const hookState = vi.hoisted(() => ({
|
||||
>;
|
||||
}
|
||||
| undefined,
|
||||
startAuthData: {} as Record<string, unknown>,
|
||||
}));
|
||||
|
||||
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<string, unknown>) => 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: () => <div>variant-step</div>,
|
||||
VariantStep: ({
|
||||
variantName,
|
||||
onVariantNameChange,
|
||||
}: {
|
||||
variantName: string;
|
||||
onVariantNameChange: (value: string) => void;
|
||||
}) => (
|
||||
<label>
|
||||
Runtime Variant Name
|
||||
<input
|
||||
aria-label="Runtime Variant Name"
|
||||
value={variantName}
|
||||
onChange={(event) => onVariantNameChange(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
),
|
||||
}));
|
||||
|
||||
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(<QuickSetupWizard open onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.getByRole('dialog')).toHaveTextContent('Advanced Variant Setup');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 ? <div data-testid="advanced-variant-wizard" /> : 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 ? (
|
||||
<div data-testid="add-account-dialog" data-catalog={catalog?.defaultModel ?? 'missing'} />
|
||||
<div
|
||||
data-testid="add-account-dialog"
|
||||
data-provider={provider}
|
||||
data-catalog={catalog?.defaultModel ?? 'missing'}
|
||||
/>
|
||||
) : 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(<CliproxyPage />);
|
||||
|
||||
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(<CliproxyPage />);
|
||||
|
||||
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(<CliproxyPage />);
|
||||
|
||||
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(<CliproxyPage />);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user