feat: add China LLM providers guided login flow
Add a guided login experience for 4 domestic (China) LLM providers in the /login command: DeepSeek, Zhipu GLM, Tongyi Qianwen, and MiMo Xiaomi. Each provider includes model presets with pricing, context windows, and optional Coding Plan integration. - New file: src/utils/chinaLlmProviders.ts — provider preset configs - Modified: src/components/ConsoleOAuthFlow.tsx — 4-step guided flow (select provider → select mode → select model → enter API key) All providers are OpenAI-compatible; credentials saved as OPENAI_BASE_URL + OPENAI_API_KEY under modelType: 'openai'. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
dc188f3210
commit
dc90f0d214
|
|
@ -19,6 +19,7 @@ import { getOauthAccountInfo, validateForceLoginOrg } from '../utils/auth.js';
|
||||||
import { openBrowser } from '../utils/browser.js';
|
import { openBrowser } from '../utils/browser.js';
|
||||||
import { logError } from '../utils/log.js';
|
import { logError } from '../utils/log.js';
|
||||||
import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js';
|
import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js';
|
||||||
|
import { CHINA_LLM_PROVIDERS, type ProviderPreset, resolveChinaProviderBaseURL } from '../utils/chinaLlmProviders.js';
|
||||||
import { Select } from './CustomSelect/select.js';
|
import { Select } from './CustomSelect/select.js';
|
||||||
import { Spinner } from './Spinner.js';
|
import { Spinner } from './Spinner.js';
|
||||||
import TextInput from './TextInput.js';
|
import TextInput from './TextInput.js';
|
||||||
|
|
@ -65,6 +66,10 @@ type OAuthStatus =
|
||||||
opusModel: string;
|
opusModel: string;
|
||||||
activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model';
|
activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model';
|
||||||
} // Gemini Generate Content API platform
|
} // Gemini Generate Content API platform
|
||||||
|
| { state: 'china_provider_select'; activeIndex: number } // China LLM: pick provider
|
||||||
|
| { state: 'china_mode_select'; provider: ProviderPreset; activeIndex: number } // China LLM: pick access mode
|
||||||
|
| { state: 'china_model_select'; provider: ProviderPreset; mode: 'api' | 'coding-plan'; activeIndex: number } // China LLM: pick model
|
||||||
|
| { state: 'china_apikey'; provider: ProviderPreset; mode: 'api' | 'coding-plan'; modelId: string; apiKey: string } // China LLM: enter API key
|
||||||
| { state: 'ready_to_start' } // Flow started, waiting for browser to open
|
| { state: 'ready_to_start' } // Flow started, waiting for browser to open
|
||||||
| { state: 'waiting_for_login'; url: string } // Browser opened, waiting for user to login
|
| { state: 'waiting_for_login'; url: string } // Browser opened, waiting for user to login
|
||||||
| { state: 'creating_api_key' } // Got access token, creating API key
|
| { state: 'creating_api_key' } // Got access token, creating API key
|
||||||
|
|
@ -455,6 +460,15 @@ function OAuthStatusMessage({
|
||||||
),
|
),
|
||||||
value: 'openai_chat_api',
|
value: 'openai_chat_api',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<Text>
|
||||||
|
China LLM Providers · <Text dimColor>DeepSeek, Zhipu GLM, Qwen, MiMo</Text>
|
||||||
|
{'\n'}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
value: 'china_providers',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Text>
|
<Text>
|
||||||
|
|
@ -534,6 +548,9 @@ function OAuthStatusMessage({
|
||||||
opusModel: process.env.OPENAI_DEFAULT_OPUS_MODEL ?? '',
|
opusModel: process.env.OPENAI_DEFAULT_OPUS_MODEL ?? '',
|
||||||
activeField: 'base_url',
|
activeField: 'base_url',
|
||||||
});
|
});
|
||||||
|
} else if (value === 'china_providers') {
|
||||||
|
logEvent('tengu_china_providers_selected', {});
|
||||||
|
setOAuthStatus({ state: 'china_provider_select', activeIndex: 0 });
|
||||||
} else if (value === 'chatgpt_subscription') {
|
} else if (value === 'chatgpt_subscription') {
|
||||||
logEvent('tengu_chatgpt_subscription_selected', {});
|
logEvent('tengu_chatgpt_subscription_selected', {});
|
||||||
setOAuthStatus({
|
setOAuthStatus({
|
||||||
|
|
@ -1272,6 +1289,197 @@ function OAuthStatusMessage({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'china_provider_select': {
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" gap={1} marginTop={1}>
|
||||||
|
<Text bold>Select China LLM Provider</Text>
|
||||||
|
<Text dimColor>Direct connection, no proxy needed. All providers are OpenAI-compatible.</Text>
|
||||||
|
<Box>
|
||||||
|
<Select
|
||||||
|
options={CHINA_LLM_PROVIDERS.map(p => ({
|
||||||
|
label: (
|
||||||
|
<Text>
|
||||||
|
{p.icon} {p.label} · <Text dimColor>{p.description}</Text>
|
||||||
|
{'\n'}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
value: p.id,
|
||||||
|
}))}
|
||||||
|
onChange={value => {
|
||||||
|
const provider = CHINA_LLM_PROVIDERS.find(p => p.id === value);
|
||||||
|
if (!provider) return;
|
||||||
|
logEvent('tengu_china_provider_selected', {});
|
||||||
|
if (provider.codingPlan) {
|
||||||
|
setOAuthStatus({ state: 'china_mode_select', provider, activeIndex: 0 });
|
||||||
|
} else {
|
||||||
|
setOAuthStatus({ state: 'china_model_select', provider, mode: 'api', activeIndex: 0 });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'china_mode_select': {
|
||||||
|
const { provider } = oauthStatus;
|
||||||
|
const modeOptions = [
|
||||||
|
{ id: 'api' as const, label: 'Pay-as-you-go (API)', desc: 'Top up freely, pay per use' },
|
||||||
|
{ id: 'coding-plan' as const, label: 'Coding Plan', desc: 'Fixed monthly fee, high usage' },
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" gap={1} marginTop={1}>
|
||||||
|
<Text bold>
|
||||||
|
{provider.icon} {provider.label} — Select Access Mode
|
||||||
|
</Text>
|
||||||
|
<Box>
|
||||||
|
<Select
|
||||||
|
options={modeOptions.map(m => ({
|
||||||
|
label: (
|
||||||
|
<Text>
|
||||||
|
{m.label} · <Text dimColor>{m.desc}</Text>
|
||||||
|
{'\n'}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
value: m.id,
|
||||||
|
}))}
|
||||||
|
onChange={value => {
|
||||||
|
logEvent('tengu_china_mode_selected', {});
|
||||||
|
setOAuthStatus({
|
||||||
|
state: 'china_model_select',
|
||||||
|
provider,
|
||||||
|
mode: value as 'api' | 'coding-plan',
|
||||||
|
activeIndex: 0,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Text dimColor>
|
||||||
|
No plan? Select "Pay-as-you-go"
|
||||||
|
{provider.id === 'zhipu' ? ' · GLM-4.7-Flash is free forever' : ''}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'china_model_select': {
|
||||||
|
const { provider, mode: accessMode } = oauthStatus;
|
||||||
|
const models = provider.models;
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" gap={1} marginTop={1}>
|
||||||
|
<Text bold>
|
||||||
|
{provider.icon} {provider.label} — Select Model
|
||||||
|
</Text>
|
||||||
|
<Box>
|
||||||
|
<Select
|
||||||
|
options={models.map(m => {
|
||||||
|
const priceLabel =
|
||||||
|
m.inputPricePerMTok === 0 && m.outputPricePerMTok === 0
|
||||||
|
? 'Free'
|
||||||
|
: `¥${m.inputPricePerMTok}/¥${m.outputPricePerMTok}`;
|
||||||
|
const tagLabel = m.tags?.length ? ` [${m.tags.join(', ')}]` : '';
|
||||||
|
return {
|
||||||
|
label: (
|
||||||
|
<Text>
|
||||||
|
{m.label} ·{' '}
|
||||||
|
<Text dimColor>
|
||||||
|
{priceLabel} · {m.contextWindow}
|
||||||
|
{tagLabel}
|
||||||
|
</Text>
|
||||||
|
{'\n'}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
value: m.id,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
onChange={value => {
|
||||||
|
logEvent('tengu_china_model_selected', {});
|
||||||
|
setOAuthStatus({ state: 'china_apikey', provider, mode: accessMode, modelId: value, apiKey: '' });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'china_apikey': {
|
||||||
|
const { provider, mode: accessMode, modelId } = oauthStatus;
|
||||||
|
|
||||||
|
const [chinaKeyValue, setChinaKeyValue] = useState('');
|
||||||
|
const [chinaKeyCursor, setChinaKeyCursor] = useState(0);
|
||||||
|
const [chinaKeyError, setChinaKeyError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const doChinaSave = useCallback(() => {
|
||||||
|
if (!chinaKeyValue.trim()) {
|
||||||
|
setChinaKeyError('Please enter an API key');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const baseUrl = resolveChinaProviderBaseURL(provider.id, accessMode);
|
||||||
|
const env: Record<string, string> = {
|
||||||
|
OPENAI_BASE_URL: baseUrl,
|
||||||
|
OPENAI_API_KEY: chinaKeyValue.trim(),
|
||||||
|
OPENAI_DEFAULT_SONNET_MODEL: modelId,
|
||||||
|
OPENAI_DEFAULT_HAIKU_MODEL: modelId,
|
||||||
|
OPENAI_DEFAULT_OPUS_MODEL: modelId,
|
||||||
|
};
|
||||||
|
const { error } = updateSettingsForSource('userSettings', {
|
||||||
|
modelType: 'openai' as any,
|
||||||
|
env,
|
||||||
|
} as any);
|
||||||
|
if (error) {
|
||||||
|
setOAuthStatus({
|
||||||
|
state: 'error',
|
||||||
|
message: 'Failed to save settings. Please try again.',
|
||||||
|
toRetry: { state: 'china_apikey', provider, mode: accessMode, modelId, apiKey: chinaKeyValue },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (const [k, v] of Object.entries(env)) process.env[k] = v;
|
||||||
|
logEvent('tengu_china_login_success', {});
|
||||||
|
setOAuthStatus({ state: 'success' });
|
||||||
|
void onDone();
|
||||||
|
}
|
||||||
|
}, [chinaKeyValue, provider, accessMode, modelId, onDone, setOAuthStatus]);
|
||||||
|
|
||||||
|
useKeybinding(
|
||||||
|
'confirm:no',
|
||||||
|
() => {
|
||||||
|
setOAuthStatus({ state: 'china_model_select', provider, mode: accessMode, activeIndex: 0 });
|
||||||
|
},
|
||||||
|
{ context: 'Confirmation' },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" gap={1} marginTop={1}>
|
||||||
|
<Text bold>
|
||||||
|
{provider.icon} {provider.label} API Key
|
||||||
|
</Text>
|
||||||
|
<Box flexDirection="column" gap={0}>
|
||||||
|
<Text dimColor> Get your key: {provider.apiKeyPage}</Text>
|
||||||
|
<Text dimColor> {provider.freeTier}</Text>
|
||||||
|
<Text dimColor> Key format: {provider.keyFormat}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text>API Key: </Text>
|
||||||
|
<TextInput
|
||||||
|
value={chinaKeyValue}
|
||||||
|
onChange={v => {
|
||||||
|
setChinaKeyValue(v);
|
||||||
|
setChinaKeyError(null);
|
||||||
|
}}
|
||||||
|
onSubmit={doChinaSave}
|
||||||
|
cursorOffset={chinaKeyCursor}
|
||||||
|
onChangeCursorOffset={setChinaKeyCursor}
|
||||||
|
columns={useTerminalSize().columns - 12}
|
||||||
|
mask="*"
|
||||||
|
focus={true}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{chinaKeyError ? <Text color="error">{chinaKeyError}</Text> : null}
|
||||||
|
<Text dimColor>Enter to confirm · Esc to go back</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
case 'platform_setup':
|
case 'platform_setup':
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" gap={1} marginTop={1}>
|
<Box flexDirection="column" gap={1} marginTop={1}>
|
||||||
|
|
|
||||||
274
src/utils/chinaLlmProviders.ts
Normal file
274
src/utils/chinaLlmProviders.ts
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
/**
|
||||||
|
* Domestic (China) LLM provider presets with URLs, pricing, and model data.
|
||||||
|
* All providers are OpenAI-compatible — just swap baseURL + apiKey.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ProviderModel = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
inputPricePerMTok: number
|
||||||
|
outputPricePerMTok: number
|
||||||
|
contextWindow: string
|
||||||
|
free?: boolean
|
||||||
|
tags?: string[]
|
||||||
|
deprecated?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CodingPlanTier = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
price: string
|
||||||
|
credits: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProviderPreset = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
description: string
|
||||||
|
icon: string
|
||||||
|
baseURL: string
|
||||||
|
apiKeyPage: string
|
||||||
|
freeTier: string
|
||||||
|
keyFormat: string
|
||||||
|
codingPlan?: {
|
||||||
|
baseURL: string
|
||||||
|
keyFormat: string
|
||||||
|
purchasePage: string
|
||||||
|
tiers: CodingPlanTier[]
|
||||||
|
}
|
||||||
|
models: ProviderModel[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHINA_LLM_PROVIDERS: ProviderPreset[] = [
|
||||||
|
{
|
||||||
|
id: 'deepseek',
|
||||||
|
label: 'DeepSeek',
|
||||||
|
description: 'Cheapest pricing, best code, 5M free tokens',
|
||||||
|
icon: '\u{1F525}',
|
||||||
|
baseURL: 'https://api.deepseek.com',
|
||||||
|
apiKeyPage: 'https://platform.deepseek.com/api_keys',
|
||||||
|
freeTier: '5M tokens on signup (30 days), min top-up ¥10',
|
||||||
|
keyFormat: 'sk-...',
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: 'deepseek-v4-pro',
|
||||||
|
label: 'DeepSeek V4 Pro',
|
||||||
|
inputPricePerMTok: 3,
|
||||||
|
outputPricePerMTok: 6,
|
||||||
|
contextWindow: '1M',
|
||||||
|
tags: ['Recommended', 'Best code'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deepseek-v4-flash',
|
||||||
|
label: 'DeepSeek V4 Flash',
|
||||||
|
inputPricePerMTok: 1,
|
||||||
|
outputPricePerMTok: 2,
|
||||||
|
contextWindow: '1M',
|
||||||
|
tags: ['Fast'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zhipu',
|
||||||
|
label: 'Zhipu GLM',
|
||||||
|
description: 'Free models, Coding Plan, strong reasoning',
|
||||||
|
icon: '\u{1F9E0}',
|
||||||
|
baseURL: 'https://open.bigmodel.cn/api/paas/v4',
|
||||||
|
apiKeyPage: 'https://open.bigmodel.cn/user/apiKeys',
|
||||||
|
freeTier: 'GLM-4.7-Flash / GLM-Z1-Flash free forever',
|
||||||
|
keyFormat: '{id}.{secret}',
|
||||||
|
codingPlan: {
|
||||||
|
baseURL: 'https://open.bigmodel.cn/api/coding/paas/v4',
|
||||||
|
keyFormat: '{id}.{secret}',
|
||||||
|
purchasePage: 'https://bigmodel.cn/claude-code',
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
id: 'lite',
|
||||||
|
label: 'Lite',
|
||||||
|
price: '¥72/mo ($30/quarter)',
|
||||||
|
credits: '~400 prompts/week',
|
||||||
|
description: 'GLM-5.1/5-Turbo/4.7/4.5-Air, MCP tools',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pro',
|
||||||
|
label: 'Pro',
|
||||||
|
price: '¥216/mo ($90/quarter)',
|
||||||
|
credits: '~2000 prompts/week',
|
||||||
|
description: 'Lite + GLM-5, 5x quota',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'max',
|
||||||
|
label: 'Max',
|
||||||
|
price: '¥576/mo ($240/quarter)',
|
||||||
|
credits: '~8000 prompts/week',
|
||||||
|
description: '4x Pro quota for heavy use',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: 'glm-5.1',
|
||||||
|
label: 'GLM-5.1',
|
||||||
|
inputPricePerMTok: 10.1,
|
||||||
|
outputPricePerMTok: 31.7,
|
||||||
|
contextWindow: '203K',
|
||||||
|
tags: ['Flagship'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'glm-4.7',
|
||||||
|
label: 'GLM-4.7',
|
||||||
|
inputPricePerMTok: 4.3,
|
||||||
|
outputPricePerMTok: 15.8,
|
||||||
|
contextWindow: '205K',
|
||||||
|
tags: ['Recommended'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'glm-4.7-flash',
|
||||||
|
label: 'GLM-4.7 Flash',
|
||||||
|
inputPricePerMTok: 0,
|
||||||
|
outputPricePerMTok: 0,
|
||||||
|
contextWindow: '203K',
|
||||||
|
free: true,
|
||||||
|
tags: ['Free forever'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qwen',
|
||||||
|
label: 'Tongyi Qianwen',
|
||||||
|
description: 'Alibaba Cloud, Coding Plan, 90-day free tier',
|
||||||
|
icon: '☁️',
|
||||||
|
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||||
|
apiKeyPage: 'https://bailian.console.aliyun.com',
|
||||||
|
freeTier: '90-day free tier for all models after activation',
|
||||||
|
keyFormat: 'sk-...',
|
||||||
|
codingPlan: {
|
||||||
|
baseURL: 'https://coding.dashscope.aliyuncs.com/v1',
|
||||||
|
keyFormat: 'sk-sp-...',
|
||||||
|
purchasePage: 'https://bailian.console.aliyun.com',
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
id: 'pro',
|
||||||
|
label: 'Pro',
|
||||||
|
price: '¥200/mo',
|
||||||
|
credits: 'Includes Qwen/GLM/Kimi/MiniMax models',
|
||||||
|
description: 'Entry tier (Lite discontinued 2026/03)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: 'qwen3-max',
|
||||||
|
label: 'Qwen3 Max',
|
||||||
|
inputPricePerMTok: 2.5,
|
||||||
|
outputPricePerMTok: 10,
|
||||||
|
contextWindow: '262K',
|
||||||
|
tags: ['Flagship'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qwen3.5-plus',
|
||||||
|
label: 'Qwen3.5 Plus',
|
||||||
|
inputPricePerMTok: 0.8,
|
||||||
|
outputPricePerMTok: 4.8,
|
||||||
|
contextWindow: '1M',
|
||||||
|
tags: ['Recommended', 'Value'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qwen3.5-flash',
|
||||||
|
label: 'Qwen3.5 Flash',
|
||||||
|
inputPricePerMTok: 0.2,
|
||||||
|
outputPricePerMTok: 2,
|
||||||
|
contextWindow: '1M',
|
||||||
|
tags: ['Fast'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mimo',
|
||||||
|
label: 'MiMo Xiaomi',
|
||||||
|
description: '1M context, 128K output, Token Plan, open source',
|
||||||
|
icon: '\u{1F4F1}',
|
||||||
|
baseURL: 'https://api.xiaomimimo.com/v1',
|
||||||
|
apiKeyPage: 'https://platform.xiaomimimo.com/api-keys',
|
||||||
|
freeTier: 'Credits for new users, mimo-v2-flash low cost',
|
||||||
|
keyFormat: 'sk-...',
|
||||||
|
codingPlan: {
|
||||||
|
baseURL: 'https://token-plan-cn.xiaomimimo.com/v1',
|
||||||
|
keyFormat: 'tp-...',
|
||||||
|
purchasePage: 'https://platform.xiaomimimo.com/token-plan',
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
id: 'lite',
|
||||||
|
label: 'Lite',
|
||||||
|
price: '¥39/mo ($6/mo)',
|
||||||
|
credits: '4.1B Credits/mo',
|
||||||
|
description: 'Light use, all MiMo models',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'standard',
|
||||||
|
label: 'Standard',
|
||||||
|
price: '¥99/mo ($16/mo)',
|
||||||
|
credits: '11B Credits/mo',
|
||||||
|
description: '2.7x Lite, daily coding',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pro',
|
||||||
|
label: 'Pro',
|
||||||
|
price: '¥329/mo ($50/mo)',
|
||||||
|
credits: '38B Credits/mo',
|
||||||
|
description: '9x Lite, heavy complex projects',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'max',
|
||||||
|
label: 'Max',
|
||||||
|
price: '¥659/mo ($100/mo)',
|
||||||
|
credits: '82B Credits/mo',
|
||||||
|
description: '20x Lite, team-level usage',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: 'mimo-v2.5-pro',
|
||||||
|
label: 'MiMo V2.5 Pro',
|
||||||
|
inputPricePerMTok: 3,
|
||||||
|
outputPricePerMTok: 6,
|
||||||
|
contextWindow: '1M',
|
||||||
|
tags: ['Recommended', 'Flagship'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mimo-v2.5',
|
||||||
|
label: 'MiMo V2.5',
|
||||||
|
inputPricePerMTok: 1,
|
||||||
|
outputPricePerMTok: 2,
|
||||||
|
contextWindow: '1M',
|
||||||
|
tags: ['Multimodal'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mimo-v2-flash',
|
||||||
|
label: 'MiMo V2 Flash',
|
||||||
|
inputPricePerMTok: 0.7,
|
||||||
|
outputPricePerMTok: 2.1,
|
||||||
|
contextWindow: '256K',
|
||||||
|
tags: ['Fast'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function findChinaProviderById(id: string): ProviderPreset | undefined {
|
||||||
|
return CHINA_LLM_PROVIDERS.find(p => p.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveChinaProviderBaseURL(
|
||||||
|
providerId: string,
|
||||||
|
mode: 'api' | 'coding-plan',
|
||||||
|
): string {
|
||||||
|
const provider = findChinaProviderById(providerId)
|
||||||
|
if (!provider) return ''
|
||||||
|
if (mode === 'coding-plan' && provider.codingPlan) {
|
||||||
|
return provider.codingPlan.baseURL
|
||||||
|
}
|
||||||
|
return provider.baseURL
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user