Merge pull request #198 from kaitranntt/feat/add-more-models

feat(api): add Minimax, DeepSeek, Qwen provider presets
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2025-12-24 18:47:33 -05:00
commit 40aa6de72a
5 files changed
+121 -22

No files matched your search

+3
View File
@@ -96,6 +96,9 @@ The dashboard provides visual management for all account types:
| **GLM** | API Key | `ccs glm` | Cost-optimized execution |
| **Kimi** | API Key | `ccs kimi` | Long-context, thinking mode |
| **Azure Foundry** | API Key | `ccs foundry` | Claude via Microsoft Azure |
| **Minimax** | API Key | `ccs minimax` | M2 series, 1M context |
| **DeepSeek** | API Key | `ccs deepseek` | V3.2 and R1 reasoning |
| **Qwen** | API Key | `ccs qwen` | Alibaba Cloud, qwen3-coder |
**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.
+33
View File
@@ -98,6 +98,39 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyHint: 'Create resource at ai.azure.com, get API key from Keys tab',
category: 'alternative',
},
{
id: 'minimax',
name: 'Minimax',
description: 'M2.1/M2.1-lightning/M2 - multilang coding (1M context)',
baseUrl: 'https://api.minimax.io/anthropic',
defaultProfileName: 'minimax',
defaultModel: 'MiniMax-M2.1',
apiKeyPlaceholder: 'YOUR_MINIMAX_API_KEY',
apiKeyHint: 'Get your API key at platform.minimax.io',
category: 'alternative',
},
{
id: 'deepseek',
name: 'DeepSeek',
description: 'V3.2 and R1 reasoning model (128K context)',
baseUrl: 'https://api.deepseek.com/anthropic',
defaultProfileName: 'deepseek',
defaultModel: 'deepseek-chat',
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key at platform.deepseek.com',
category: 'alternative',
},
{
id: 'qwen',
name: 'Qwen',
description: 'Alibaba Cloud - qwen3-coder-plus (256K context)',
baseUrl: 'https://dashscope-intl.aliyuncs.com/apps/anthropic',
defaultProfileName: 'qwen',
defaultModel: 'qwen3-coder-plus',
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio',
category: 'alternative',
},
];
/** Get preset by ID */
+6 -1
View File
@@ -426,7 +426,7 @@ async function showHelp(): Promise<void> {
console.log('');
console.log(subheader('Options'));
console.log(
` ${color('--preset <id>', 'command')} Use provider preset (openrouter, glm, glmt, kimi, foundry)`
` ${color('--preset <id>', 'command')} Use provider preset (openrouter, glm, glmt, kimi, foundry, minimax, deepseek, qwen)`
);
console.log(` ${color('--base-url <url>', 'command')} API base URL (create)`);
console.log(` ${color('--api-key <key>', 'command')} API key (create)`);
@@ -442,6 +442,11 @@ async function showHelp(): Promise<void> {
console.log(` ${color('glmt', 'command')} GLMT - GLM with Thinking mode`);
console.log(` ${color('kimi', 'command')} Kimi - Moonshot AI reasoning model`);
console.log(` ${color('foundry', 'command')} Azure Foundry - Claude via Microsoft Azure`);
console.log(` ${color('minimax', 'command')} Minimax - M2 series with 1M context`);
console.log(` ${color('deepseek', 'command')} DeepSeek - V3.2 and R1 reasoning (128K)`);
console.log(
` ${color('qwen', 'command')} Qwen - Alibaba Cloud qwen3-coder-plus (256K)`
);
console.log('');
console.log(subheader('Examples'));
console.log(` ${dim('# Interactive wizard')}`);
@@ -66,7 +66,12 @@ interface ProfileCreateDialogProps {
// Common URL mistakes to warn about
const PROBLEMATIC_PATHS = ['/chat/completions', '/v1/messages', '/messages', '/completions'];
export function ProfileCreateDialog({ open, onOpenChange, onSuccess }: ProfileCreateDialogProps) {
export function ProfileCreateDialog({
open,
onOpenChange,
onSuccess,
initialMode = 'openrouter',
}: ProfileCreateDialogProps) {
const createMutation = useCreateProfile();
const [activeTab, setActiveTab] = useState('basic');
const [urlWarning, setUrlWarning] = useState<string | null>(null);
@@ -123,18 +128,29 @@ export function ProfileCreateDialog({ open, onOpenChange, onSuccess }: ProfileCr
setActiveTab('basic');
setUrlWarning(null);
setShowApiKey(false);
setSelectedPreset('openrouter');
setModelSearch('');
// Pre-fill with OpenRouter preset
const openrouterPreset = PROVIDER_PRESETS.find((p) => p.id === 'openrouter');
if (openrouterPreset) {
// Set initial preset based on initialMode
if (initialMode === 'normal') {
// Custom mode - clear form
setSelectedPreset('custom');
setTimeout(() => {
setValue('name', openrouterPreset.defaultProfileName);
setValue('baseUrl', openrouterPreset.baseUrl);
setValue('name', '');
setValue('baseUrl', '');
}, 0);
} else {
// OpenRouter mode (default)
setSelectedPreset('openrouter');
const openrouterPreset = PROVIDER_PRESETS.find((p) => p.id === 'openrouter');
if (openrouterPreset) {
setTimeout(() => {
setValue('name', openrouterPreset.defaultProfileName);
setValue('baseUrl', openrouterPreset.baseUrl);
}, 0);
}
}
}
}, [open, reset, setValue]);
}, [open, reset, setValue, initialMode]);
// Handle preset selection
const handlePresetSelect = (presetId: string) => {
@@ -244,14 +260,14 @@ export function ProfileCreateDialog({ open, onOpenChange, onSuccess }: ProfileCr
type="button"
onClick={() => handlePresetSelect('custom')}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md border transition-all text-sm',
'flex items-center gap-2 px-4 py-2 rounded-md border-2 transition-all text-sm font-medium',
selectedPreset === 'custom' ||
getPresetsByCategory('alternative').some((p) => p.id === selectedPreset)
? 'border-primary bg-primary/5 font-medium'
: 'border-muted hover:border-muted-foreground/30'
? 'border-primary bg-primary/10 text-primary dark:bg-primary/20'
: 'border-dashed border-muted-foreground/40 hover:border-primary/50 hover:bg-muted/50 text-muted-foreground hover:text-foreground'
)}
>
<Settings2 className="w-3.5 h-3.5 text-muted-foreground" />
<Settings2 className="w-4 h-4" />
<span>Custom</span>
</button>
</div>
@@ -260,8 +276,8 @@ export function ProfileCreateDialog({ open, onOpenChange, onSuccess }: ProfileCr
{/* Show alternative presets when Custom is selected or an alternative is selected */}
{(selectedPreset === 'custom' ||
getPresetsByCategory('alternative').some((p) => p.id === selectedPreset)) && (
<div className="pt-2 border-t border-dashed">
<Label className="text-xs text-muted-foreground mb-1.5 block">
<div className="pt-3 mt-2 border-t border-dashed border-muted-foreground/30">
<Label className="text-xs font-medium text-foreground/70 mb-2 block">
Quick Templates
</Label>
<div className="flex gap-2 flex-wrap">
@@ -552,24 +568,27 @@ function CompactPresetCard({
type="button"
onClick={onClick}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md border transition-all text-sm',
'flex items-center gap-2 px-3 py-2 rounded-md border transition-all text-sm',
isSelected
? preset.featured
? 'border-accent bg-accent/10 dark:bg-accent/20 font-medium'
: 'border-primary bg-primary/5 font-medium'
: 'border-muted hover:border-muted-foreground/30'
: 'border-primary bg-primary/10 dark:bg-primary/20 font-medium'
: 'border-muted-foreground/20 hover:border-muted-foreground/40 hover:bg-muted/50'
)}
>
{preset.icon ? (
<img src={preset.icon} alt="" className="w-3.5 h-3.5" />
<img src={preset.icon} alt="" className="w-4 h-4" />
) : (
<div className="w-3.5 h-3.5 rounded-full bg-muted flex items-center justify-center text-[8px] font-bold">
<div className="w-4 h-4 rounded-full bg-muted-foreground/20 flex items-center justify-center text-[9px] font-bold text-foreground/70">
{preset.name.charAt(0)}
</div>
)}
<span>{preset.name}</span>
<span className="font-medium">{preset.name}</span>
{preset.badge && (
<Badge variant="secondary" className="text-[9px] px-1 py-0 ml-0.5">
<Badge
variant="secondary"
className="text-[10px] px-1.5 py-0 ml-0.5 bg-muted-foreground/10 text-muted-foreground"
>
{preset.badge}
</Badge>
)}
+39
View File
@@ -93,6 +93,45 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
apiKeyHint: 'Create resource at ai.azure.com, get API key from Keys tab',
category: 'alternative',
},
{
id: 'minimax',
name: 'Minimax',
description: 'M2.1/M2.1-lightning/M2 - multilang coding (1M context)',
baseUrl: 'https://api.minimax.io/anthropic',
defaultProfileName: 'minimax',
badge: '1M context',
defaultModel: 'MiniMax-M2.1',
requiresApiKey: true,
apiKeyPlaceholder: 'YOUR_MINIMAX_API_KEY',
apiKeyHint: 'Get your API key at platform.minimax.io',
category: 'alternative',
},
{
id: 'deepseek',
name: 'DeepSeek',
description: 'V3.2 and R1 reasoning model (128K context)',
baseUrl: 'https://api.deepseek.com/anthropic',
defaultProfileName: 'deepseek',
badge: 'Reasoning',
defaultModel: 'deepseek-chat',
requiresApiKey: true,
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key at platform.deepseek.com',
category: 'alternative',
},
{
id: 'qwen',
name: 'Qwen',
description: 'Alibaba Cloud - qwen3-coder-plus (256K context)',
baseUrl: 'https://dashscope-intl.aliyuncs.com/apps/anthropic',
defaultProfileName: 'qwen',
badge: 'Alibaba',
defaultModel: 'qwen3-coder-plus',
requiresApiKey: true,
apiKeyPlaceholder: 'sk-...',
apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio',
category: 'alternative',
},
];
/** Get presets by category */