mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 22:14:22 +00:00
Merge pull request #198 from kaitranntt/feat/add-more-models
feat(api): add Minimax, DeepSeek, Qwen provider presets
This commit is contained in:
5 files changed
+121
-22
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in new issue
Block a user