feat(api): add Alibaba Coding Plan preset and providers promotion

This commit is contained in:
Tam Nhu Tran
2026-03-04 00:39:02 +07:00
parent 08eb46a5de
commit 8811e5320f
13 changed files with 226 additions and 47 deletions
+7 -1
View File
@@ -109,10 +109,14 @@ The dashboard provides visual management for all account types:
| **Azure Foundry** | API Key | `ccs foundry` | Claude via Microsoft Azure |
| **Minimax** | API Key | `ccs mm` | M2 series, 1M context |
| **DeepSeek** | API Key | `ccs deepseek` | V3.2 and R1 reasoning |
| **Qwen** | API Key | `ccs qwen` | Alibaba Cloud, qwen3-coder |
| **Qwen (OAuth)** | OAuth | `ccs qwen` | Qwen Code via CLIProxy |
| **Qwen API** | API Key | `ccs api create --preset qwen` | DashScope Anthropic-compatible API |
| **Alibaba Coding Plan** | API Key | `ccs api create --preset alibaba-coding-plan` | Model Studio Coding Plan endpoint |
**OpenRouter Integration** (v7.0.0): CCS v7.0.0 adds OpenRouter with interactive model picker, dynamic discovery, and tier mapping (opus/sonnet/haiku). Create via `ccs api create --preset openrouter` or dashboard.
**Alibaba Coding Plan Integration**: Configure via `ccs api create --preset alibaba-coding-plan` (or preset alias `alibaba`) with Coding Plan keys (`sk-sp-...`) and endpoint `https://coding-intl.dashscope.aliyuncs.com/apps/anthropic`.
**Ollama Integration**: Run local open-source models (qwen3-coder, gpt-oss:20b) with full privacy. Use `ccs api create --preset ollama` - requires [Ollama v0.14.0+](https://ollama.com) installed. For cloud models, use `ccs api create --preset ollama-cloud`.
**Azure Foundry**: Use `ccs api create --preset foundry` to set up Claude via Microsoft Azure AI Foundry. Requires Azure resource and API key from [ai.azure.com](https://ai.azure.com).
@@ -143,9 +147,11 @@ ccs cursor # Cursor IDE integration (token import + local daemon)
ccs kiro # Kiro/AWS CodeWhisperer (OAuth)
ccs ghcp # GitHub Copilot (OAuth device flow)
ccs agy # Antigravity (OAuth)
ccs qwen # Qwen Code (OAuth via CLIProxy)
ccs ollama # Local Ollama (no API key needed)
ccs glm # GLM (API key)
ccs km # Kimi API profile (API key)
ccs api create --preset alibaba-coding-plan # Alibaba Coding Plan profile
```
### Droid Alias (`argv[0]` pattern)
+2
View File
@@ -659,6 +659,8 @@ async function showHelp(): Promise<void> {
console.log('');
console.log(` ${dim('# Quick setup with preset')}`);
console.log(` ${color('ccs api create --preset openrouter', 'command')}`);
console.log(` ${color('ccs api create --preset alibaba-coding-plan', 'command')}`);
console.log(` ${color('ccs api create --preset alibaba', 'command')} ${dim('# alias')}`);
console.log(` ${color('ccs api create --preset glm', 'command')}`);
console.log('');
console.log(` ${dim('# Create with name')}`);
+5 -1
View File
@@ -133,6 +133,10 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
['ccs glm', 'GLM 5 (API key required)'],
['ccs glmt', 'GLM with thinking mode'],
['ccs km', 'Kimi for Coding (API key)'],
[
'ccs api create --preset alibaba-coding-plan',
'Alibaba Coding Plan (Anthropic-compatible API key)',
],
['ccs ollama', 'Local Ollama (http://localhost:11434)'],
['ccs ollama-cloud', 'Ollama Cloud (API key required)'],
['', ''], // Spacer
@@ -184,7 +188,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
['ccs gemini', 'Google Gemini (gemini-2.5-pro or 3-pro)'],
['ccs codex', 'OpenAI Codex (supports -medium/-high/-xhigh model suffixes)'],
['ccs agy', 'Antigravity (Claude/Gemini models)'],
['ccs qwen', 'Qwen Code (qwen3-coder)'],
['ccs qwen', 'Qwen Code OAuth (CLIProxy)'],
['ccs kimi', 'Kimi (Moonshot AI K2/K2.5 models)'],
['ccs kiro', 'Kiro (AWS CodeWhisperer Claude models)'],
['ccs ghcp', 'GitHub Copilot (OAuth via CLIProxy Plus)'],
+19 -1
View File
@@ -9,6 +9,7 @@ export type PresetCategory = 'recommended' | 'alternative';
export const PROVIDER_PRESET_IDS = [
'openrouter',
'alibaba-coding-plan',
'ollama',
'glm',
'glmt',
@@ -51,6 +52,8 @@ export const OPENROUTER_BASE_URL = 'https://openrouter.ai/api';
*/
export const PROVIDER_PRESET_ALIASES: Readonly<Record<string, ProviderPresetId>> = Object.freeze({
kimi: 'km',
alibaba: 'alibaba-coding-plan',
acp: 'alibaba-coding-plan',
});
const RAW_PROVIDER_PRESET_DEFINITIONS: readonly ProviderPresetDefinition[] = [
@@ -69,6 +72,21 @@ const RAW_PROVIDER_PRESET_DEFINITIONS: readonly ProviderPresetDefinition[] = [
featured: true,
icon: '/icons/openrouter.svg',
},
{
id: 'alibaba-coding-plan',
name: 'Alibaba Coding Plan',
description: 'Alibaba Cloud Coding Plan via Anthropic-compatible endpoint',
baseUrl: 'https://coding-intl.dashscope.aliyuncs.com/apps/anthropic',
defaultProfileName: 'alibaba-plan',
defaultModel: 'qwen3-coder-plus',
apiKeyPlaceholder: 'sk-sp-...',
apiKeyHint: 'Get your Coding Plan key from Alibaba Cloud Model Studio',
category: 'recommended',
requiresApiKey: true,
badge: 'Coding Plan',
featured: true,
icon: '/assets/providers/alibabacloud-color.svg',
},
{
id: 'ollama',
name: 'Ollama (Local)',
@@ -183,7 +201,7 @@ const RAW_PROVIDER_PRESET_DEFINITIONS: readonly ProviderPresetDefinition[] = [
name: 'Qwen',
description: 'Alibaba Cloud - Qwen3 models (256K-1M context, thinking support)',
baseUrl: 'https://dashscope-intl.aliyuncs.com/apps/anthropic',
defaultProfileName: 'qwen',
defaultProfileName: 'qwen-api',
defaultModel: 'qwen3-coder-plus',
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio',
+21
View File
@@ -2,6 +2,22 @@ import { describe, expect, it } from 'bun:test';
import { getPresetById, isValidPresetId } from '../../../src/api/services/provider-presets';
describe('provider-presets', () => {
it('resolves Alibaba Coding Plan preset id', () => {
const preset = getPresetById('alibaba-coding-plan');
expect(preset?.id).toBe('alibaba-coding-plan');
expect(preset?.baseUrl).toBe('https://coding-intl.dashscope.aliyuncs.com/apps/anthropic');
expect(preset?.defaultProfileName).toBe('alibaba-plan');
});
it('resolves alibaba alias to Alibaba Coding Plan preset', () => {
const preset = getPresetById('alibaba');
expect(preset?.id).toBe('alibaba-coding-plan');
});
it('treats alibaba alias as a valid preset id', () => {
expect(isValidPresetId('alibaba')).toBe(true);
});
it('resolves canonical km preset id', () => {
const preset = getPresetById('km');
expect(preset?.id).toBe('km');
@@ -25,4 +41,9 @@ describe('provider-presets', () => {
it('treats legacy kimi alias as a valid preset id', () => {
expect(isValidPresetId('kimi')).toBe(true);
});
it('uses non-reserved default profile name for qwen API preset', () => {
const preset = getPresetById('qwen');
expect(preset?.defaultProfileName).toBe('qwen-api');
});
});
@@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>AlibabaCloud</title><path d="M14.752 4.64h5.274C22.242 4.64 24 6.475 24 8.691V15.8a3.947 3.947 0 01-3.974 3.975h-5.274l1.299-1.835 3.822-1.222c.688-.23 1.146-.918 1.146-1.605v-5.81c0-.687-.458-1.375-1.146-1.605L16.05 6.475l-1.3-1.835zM2.98 15.111c0 .688.46 1.376 1.147 1.606l3.822 1.146 1.3 1.835H3.974A3.947 3.947 0 010 15.723V8.69c0-2.216 1.758-4.05 3.975-4.05h5.273L7.95 6.474 4.127 7.697c-.688.23-1.146.918-1.146 1.606v5.808z" fill="#FF6A00"></path><path d="M16.051 11.213H8.025v1.835h8.026v-1.835z" fill="#FF6A00"></path></svg>

After

Width:  |  Height:  |  Size: 656 B

+1 -1
View File
@@ -79,7 +79,7 @@ function buildNavGroups(t: (key: string) => string): SidebarGroupDef[] {
path: '/providers',
icon: Key,
label: t('nav.apiProfiles'),
badge: { text: 'OpenRouter', icon: '/icons/openrouter.svg' },
badge: { text: 'Featured', icon: '/icons/openrouter.svg' },
},
{
path: '/cliproxy',
@@ -0,0 +1,43 @@
/**
* Alibaba Coding Plan Promo Card
* Permanent promotional card for Alibaba Coding Plan in providers sidebar.
*/
import { Button } from '@/components/ui/button';
import { CloudCog } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface AlibabaCodingPlanPromoCardProps {
onCreateClick: () => void;
}
export function AlibabaCodingPlanPromoCard({ onCreateClick }: AlibabaCodingPlanPromoCardProps) {
const { t } = useTranslation();
return (
<div className="p-3 border-t bg-gradient-to-r from-orange-500/5 to-orange-500/10 dark:from-orange-500/10 dark:to-orange-500/15">
<div className="flex items-center gap-2">
<div className="p-1.5 bg-orange-500/10 dark:bg-orange-500/20 rounded shrink-0">
<img src="/assets/providers/alibabacloud-color.svg" alt="" className="w-4 h-4" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-orange-700 dark:text-orange-300">
{t('alibabaCodingPlanPromo.title')}
</p>
<p className="text-[10px] text-muted-foreground truncate">
{t('alibabaCodingPlanPromo.subtitle')}
</p>
</div>
<Button
size="sm"
variant="ghost"
onClick={onCreateClick}
className="h-7 px-2 text-orange-700 dark:text-orange-300 hover:text-orange-700 hover:bg-orange-500/10 dark:hover:bg-orange-500/20"
>
<CloudCog className="w-3 h-3 mr-1" />
<span className="text-xs">{t('alibabaCodingPlanPromo.add')}</span>
</Button>
</div>
</div>
);
}
+1
View File
@@ -19,5 +19,6 @@ export { OpenRouterBanner } from './openrouter-banner';
export { OpenRouterModelPicker } from './openrouter-model-picker';
export { OpenRouterPromoCard } from './openrouter-promo-card';
export { OpenRouterQuickStart } from './openrouter-quick-start';
export { AlibabaCodingPlanPromoCard } from './alibaba-coding-plan-promo-card';
export { ModelTierMapping } from './model-tier-mapping';
export type { TierMapping } from './model-tier-mapping';
@@ -8,16 +8,18 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { useOpenRouterReady } from '@/hooks/use-openrouter-models';
import { Sparkles, ExternalLink, ArrowRight, Zap } from 'lucide-react';
import { Sparkles, ExternalLink, ArrowRight, Zap, CloudCog, KeyRound } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface OpenRouterQuickStartProps {
onOpenRouterClick: () => void;
onAlibabaCodingPlanClick: () => void;
onCustomClick: () => void;
}
export function OpenRouterQuickStart({
onOpenRouterClick,
onAlibabaCodingPlanClick,
onCustomClick,
}: OpenRouterQuickStartProps) {
const { t } = useTranslation();
@@ -84,6 +86,65 @@ export function OpenRouterQuickStart({
</CardContent>
</Card>
{/* Alibaba Coding Plan Card */}
<Card className="border-orange-500/30 dark:border-orange-500/40 bg-gradient-to-br from-orange-500/5 to-background dark:from-orange-500/10">
<CardHeader className="pb-3">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 rounded-lg bg-orange-500/10 dark:bg-orange-500/20">
<img
src="/assets/providers/alibabacloud-color.svg"
alt="Alibaba Coding Plan"
className="w-6 h-6"
/>
</div>
<Badge
variant="secondary"
className="bg-orange-500/10 text-orange-700 dark:bg-orange-500/20 dark:text-orange-200"
>
{t('alibabaCodingPlanQuickStart.recommended')}
</Badge>
</div>
<CardTitle className="text-xl">{t('alibabaCodingPlanQuickStart.title')}</CardTitle>
<CardDescription className="text-base">
{t('alibabaCodingPlanQuickStart.description')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="flex items-center gap-2 text-muted-foreground">
<CloudCog className="w-4 h-4 text-orange-600" />
<span>{t('alibabaCodingPlanQuickStart.featureEndpoint')}</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<KeyRound className="w-4 h-4 text-orange-600" />
<span>{t('alibabaCodingPlanQuickStart.featureKeyFormat')}</span>
</div>
</div>
<Button
onClick={onAlibabaCodingPlanClick}
className="w-full bg-orange-600 hover:bg-orange-600/90 text-white"
size="lg"
>
{t('alibabaCodingPlanQuickStart.createAlibabaProfile')}
<ArrowRight className="w-4 h-4 ml-2" />
</Button>
<p className="text-xs text-center text-muted-foreground">
{t('alibabaCodingPlanQuickStart.readGuideAt')}{' '}
<a
href="https://www.alibabacloud.com/help/en/model-studio/coding-plan"
target="_blank"
rel="noopener noreferrer"
className="text-orange-700 dark:text-orange-400 hover:underline inline-flex items-center gap-1"
>
Alibaba Cloud Model Studio
<ExternalLink className="w-3 h-3" />
</a>
</p>
</CardContent>
</Card>
{/* Divider */}
<div className="flex items-center gap-4">
<Separator className="flex-1" />
@@ -72,7 +72,7 @@ interface ProfileCreateDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess: (name: string) => void;
initialMode?: 'normal' | 'openrouter';
initialMode?: 'normal' | 'openrouter' | 'alibaba-coding-plan';
}
// Common URL mistakes to warn about
@@ -180,7 +180,8 @@ export function ProfileCreateDialog({
setSelectedPreset(CUSTOM_PRESET_ID);
applyPresetToForm(null);
} else {
const defaultPreset = getPresetById(DEFAULT_PRESET_ID);
const presetId = initialMode === 'openrouter' ? DEFAULT_PRESET_ID : initialMode;
const defaultPreset = getPresetById(presetId);
if (defaultPreset) {
setSelectedPreset(defaultPreset.id);
applyPresetToForm(defaultPreset);
+47 -3
View File
@@ -32,7 +32,7 @@ const resources = {
system: 'System',
health: 'Health',
settings: 'Settings',
openrouterTooltip: '349+ models via OpenRouter',
openrouterTooltip: 'Featured: OpenRouter + Alibaba Coding Plan',
},
home: {
profiles: 'Profiles',
@@ -240,6 +240,21 @@ const resources = {
or: 'or',
createCustomProfile: 'Create Custom API Profile',
},
alibabaCodingPlanQuickStart: {
recommended: 'Recommended',
title: 'Try Alibaba Coding Plan',
description:
'Use Alibaba Cloud Coding Plan through an Anthropic-compatible endpoint for coding workloads.',
featureEndpoint: 'Anthropic-compatible endpoint',
featureKeyFormat: 'Dedicated sk-sp API keys',
createAlibabaProfile: 'Create Alibaba Coding Plan Profile',
readGuideAt: 'Read setup guide at',
},
alibabaCodingPlanPromo: {
title: 'Alibaba Coding Plan',
subtitle: 'Model Studio Coding Plan via Anthropic endpoint',
add: 'Add',
},
credentialHealth: {
title: 'Credential Health',
ready: 'Ready',
@@ -1164,7 +1179,7 @@ const resources = {
system: '系统',
health: '健康',
settings: '设置',
openrouterTooltip: '通过 OpenRouter 提供 349+ 模型',
openrouterTooltip: '精选:OpenRouter + Alibaba Coding Plan',
},
home: {
profiles: '配置',
@@ -1368,6 +1383,20 @@ const resources = {
or: '或',
createCustomProfile: '创建自定义 API 配置',
},
alibabaCodingPlanQuickStart: {
recommended: '推荐',
title: '试用阿里云 Coding Plan',
description: '通过 Anthropic 兼容端点接入阿里云 Model Studio Coding Plan。',
featureEndpoint: 'Anthropic 兼容端点',
featureKeyFormat: '专用 sk-sp API Key',
createAlibabaProfile: '创建 Alibaba Coding Plan 配置',
readGuideAt: '查看配置文档:',
},
alibabaCodingPlanPromo: {
title: 'Alibaba Coding Plan',
subtitle: '通过 Anthropic 端点接入 Model Studio Coding Plan',
add: '添加',
},
credentialHealth: {
title: '凭据健康状态',
ready: '就绪',
@@ -2254,7 +2283,7 @@ const resources = {
system: 'Hệ thống',
health: 'Sức khỏe',
settings: 'Cài đặt',
openrouterTooltip: 'Hơn 349 mô hình qua OpenRouter',
openrouterTooltip: 'Nổi bật: OpenRouter + Alibaba Coding Plan',
},
home: {
profiles: 'Hồ sơ',
@@ -2464,6 +2493,21 @@ const resources = {
or: 'hoặc',
createCustomProfile: 'Tạo hồ sơ API tùy chỉnh',
},
alibabaCodingPlanQuickStart: {
recommended: 'Đề xuất',
title: 'Dùng thử Alibaba Coding Plan',
description:
'Sử dụng Alibaba Cloud Coding Plan qua endpoint tương thích Anthropic cho tác vụ lập trình.',
featureEndpoint: 'Endpoint tương thích Anthropic',
featureKeyFormat: 'Khóa API sk-sp chuyên dụng',
createAlibabaProfile: 'Tạo hồ sơ Alibaba Coding Plan',
readGuideAt: 'Xem hướng dẫn tại',
},
alibabaCodingPlanPromo: {
title: 'Alibaba Coding Plan',
subtitle: 'Model Studio Coding Plan qua endpoint Anthropic',
add: 'Thêm',
},
credentialHealth: {
title: 'Tình trạng thông tin xác thực',
ready: 'Sẵn sàng',
+14 -37
View File
@@ -1,8 +1,3 @@
/**
* API Profiles Page - Master-Detail Layout
* Comprehensive profile management with inline editing
*/
import { useState, useMemo } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -23,6 +18,7 @@ import { ProfileCreateDialog } from '@/components/profiles/profile-create-dialog
import { OpenRouterBanner } from '@/components/profiles/openrouter-banner';
import { OpenRouterQuickStart } from '@/components/profiles/openrouter-quick-start';
import { OpenRouterPromoCard } from '@/components/profiles/openrouter-promo-card';
import { AlibabaCodingPlanPromoCard } from '@/components/profiles/alibaba-coding-plan-promo-card';
import { useProfiles, useDeleteProfile } from '@/hooks/use-profiles';
import { useOpenRouterModels } from '@/hooks/use-openrouter-models';
import { ConfirmDialog } from '@/components/shared/confirm-dialog';
@@ -38,30 +34,22 @@ export function ApiPage() {
const [selectedProfile, setSelectedProfile] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [isCreateDialogOpen, setCreateDialogOpen] = useState(false);
const [createMode, setCreateMode] = useState<'normal' | 'openrouter'>('normal');
const [createMode, setCreateMode] = useState<'normal' | 'openrouter' | 'alibaba-coding-plan'>(
'normal'
);
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
const [editorHasChanges, setEditorHasChanges] = useState(false);
const [pendingSwitch, setPendingSwitch] = useState<string | null>(null);
// Prefetch OpenRouter models when page loads (lazy - won't block render)
useOpenRouterModels();
// Memoize profiles to maintain stable reference
const profiles = useMemo(() => data?.profiles || [], [data?.profiles]);
// Filter profiles by search
const filteredProfiles = useMemo(
() => profiles.filter((p) => p.name.toLowerCase().includes(searchQuery.toLowerCase())),
[profiles, searchQuery]
);
// selectedProfile is null by default - user must click to select
// This allows OpenRouterQuickStart to show as the default right panel
const selectedProfileData = selectedProfile
? profiles.find((p) => p.name === selectedProfile)
: null;
// Handle profile deletion
const handleDelete = (name: string) => {
deleteMutation.mutate(name, {
onSuccess: () => {
@@ -73,18 +61,14 @@ export function ApiPage() {
});
};
// Handle create success
const handleCreateSuccess = (name: string) => {
setCreateDialogOpen(false);
// Use the same unsaved changes check as profile selection
if (editorHasChanges && selectedProfile !== null) {
setPendingSwitch(name);
} else {
setSelectedProfile(name);
}
};
// Handle profile selection with unsaved changes check
const handleProfileSelect = (name: string) => {
if (editorHasChanges && selectedProfile !== name) {
setPendingSwitch(name);
@@ -95,14 +79,9 @@ export function ApiPage() {
return (
<div className="h-[calc(100vh-100px)] flex flex-col">
{/* OpenRouter Announcement Banner */}
<OpenRouterBanner onCreateClick={() => setCreateDialogOpen(true)} />
{/* Main Content */}
<div className="flex-1 flex min-h-0">
{/* Left Panel - Profiles List */}
<div className="w-80 border-r flex flex-col bg-muted/30">
{/* Header */}
<div className="p-4 border-b bg-background">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
@@ -120,7 +99,6 @@ export function ApiPage() {
</Button>
</div>
{/* Search */}
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
@@ -132,7 +110,6 @@ export function ApiPage() {
</div>
</div>
{/* Profile List */}
<ScrollArea className="flex-1">
{isLoading ? (
<div className="p-4 text-sm text-muted-foreground">
@@ -197,7 +174,6 @@ export function ApiPage() {
)}
</ScrollArea>
{/* Footer Stats */}
{profiles.length > 0 && (
<div className="p-3 border-t bg-background text-xs text-muted-foreground">
<div className="flex items-center justify-between">
@@ -212,16 +188,20 @@ export function ApiPage() {
</div>
)}
{/* OpenRouter Promo - always visible */}
<OpenRouterPromoCard
onCreateClick={() => {
setCreateMode('openrouter');
setCreateDialogOpen(true);
}}
/>
<AlibabaCodingPlanPromoCard
onCreateClick={() => {
setCreateMode('alibaba-coding-plan');
setCreateDialogOpen(true);
}}
/>
</div>
{/* Right Panel - Editor or QuickStart */}
<div className="flex-1 flex flex-col min-w-0">
{selectedProfileData ? (
<ProfileEditor
@@ -237,6 +217,10 @@ export function ApiPage() {
setCreateMode('openrouter');
setCreateDialogOpen(true);
}}
onAlibabaCodingPlanClick={() => {
setCreateMode('alibaba-coding-plan');
setCreateDialogOpen(true);
}}
onCustomClick={() => {
setCreateMode('normal');
setCreateDialogOpen(true);
@@ -246,7 +230,6 @@ export function ApiPage() {
</div>
</div>
{/* Create Dialog */}
<ProfileCreateDialog
open={isCreateDialogOpen}
onOpenChange={setCreateDialogOpen}
@@ -254,7 +237,6 @@ export function ApiPage() {
initialMode={createMode}
/>
{/* Delete Confirmation */}
<ConfirmDialog
open={!!deleteConfirm}
title={t('apiProfiles.deleteProfileTitle')}
@@ -265,7 +247,6 @@ export function ApiPage() {
onCancel={() => setDeleteConfirm(null)}
/>
{/* Unsaved Changes Confirmation */}
<ConfirmDialog
open={!!pendingSwitch}
title={t('apiProfiles.unsavedChangesTitle')}
@@ -286,7 +267,6 @@ export function ApiPage() {
);
}
/** Profile list item component */
function ProfileListItem({
profile,
isSelected,
@@ -308,14 +288,12 @@ function ProfileListItem({
)}
onClick={onSelect}
>
{/* Status indicator */}
{profile.configured ? (
<CheckCircle2 className="w-4 h-4 text-green-600 shrink-0" />
) : (
<AlertCircle className="w-4 h-4 text-yellow-600 shrink-0" />
)}
{/* Profile info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 min-w-0">
<div className="font-medium text-sm truncate">{profile.name}</div>
@@ -335,7 +313,6 @@ function ProfileListItem({
</div>
</div>
{/* Actions */}
<Button
variant="ghost"
size="icon"