feat(ark): add Code Plan mode preset and guided setup (#617)

This commit is contained in:
Lingxuan Zuo
2026-03-22 16:26:12 +08:00
committed by GitHub
Unverified
parent f16e8062e1
commit d39982bad8
12 changed files with 299 additions and 10 deletions

View File

@@ -95,6 +95,9 @@ export const PROVIDER_DEFINITIONS: ProviderDefinition[] = [
modelIdPlaceholder: 'ep-20260228000000-xxxxx',
category: 'official',
envVar: 'ARK_API_KEY',
codePlanPresetBaseUrl: 'https://ark.cn-beijing.volces.com/api/coding/v3',
codePlanPresetModelId: 'ark-code-latest',
codePlanDocsUrl: 'https://www.volcengine.com/docs/82379/1928261?lang=zh',
supportedAuthModes: ['api_key'],
defaultAuthMode: 'api_key',
supportsMultipleAccounts: true,

View File

@@ -84,6 +84,9 @@ export interface ProviderTypeInfo {
isOAuth?: boolean;
supportsApiKey?: boolean;
apiKeyUrl?: string;
codePlanPresetBaseUrl?: string;
codePlanPresetModelId?: string;
codePlanDocsUrl?: string;
}
export interface ProviderModelEntry extends Record<string, unknown> {

View File

@@ -55,6 +55,7 @@ import { subscribeHostEvent } from '@/lib/host-events';
const inputClasses = 'h-[44px] rounded-xl font-mono text-[13px] bg-[#eeece3] dark:bg-muted border-black/10 dark:border-white/10 focus-visible:ring-2 focus-visible:ring-blue-500/50 focus-visible:border-blue-500 shadow-sm transition-all text-foreground placeholder:text-foreground/40';
const labelClasses = 'text-[14px] text-foreground/80 font-bold';
type ArkMode = 'apikey' | 'codeplan';
function normalizeFallbackProviderIds(ids?: string[]): string[] {
return Array.from(new Set((ids ?? []).filter(Boolean)));
@@ -85,6 +86,17 @@ function fallbackModelsEqual(a?: string[], b?: string[]): boolean {
return left.length === right.length && left.every((model, index) => model === right[index]);
}
function isArkCodePlanMode(
vendorId: string,
baseUrl: string | undefined,
modelId: string | undefined,
codePlanPresetBaseUrl?: string,
codePlanPresetModelId?: string,
): boolean {
if (vendorId !== 'ark' || !codePlanPresetBaseUrl || !codePlanPresetModelId) return false;
return (baseUrl || '').trim() === codePlanPresetBaseUrl && (modelId || '').trim() === codePlanPresetModelId;
}
function getAuthModeLabel(
authMode: ProviderAccount['authMode'],
t: (key: string) => string
@@ -317,10 +329,20 @@ function ProviderCard({
const [showFallback, setShowFallback] = useState(false);
const [validating, setValidating] = useState(false);
const [saving, setSaving] = useState(false);
const [arkMode, setArkMode] = useState<ArkMode>('apikey');
const typeInfo = PROVIDER_TYPE_INFO.find((t) => t.id === account.vendorId);
const providerDocsUrl = getProviderDocsUrl(typeInfo, i18n.language);
const showModelIdField = shouldShowProviderModelId(typeInfo, devModeUnlocked);
const codePlanPreset = typeInfo?.codePlanPresetBaseUrl && typeInfo?.codePlanPresetModelId
? {
baseUrl: typeInfo.codePlanPresetBaseUrl,
modelId: typeInfo.codePlanPresetModelId,
}
: null;
const effectiveDocsUrl = account.vendorId === 'ark' && arkMode === 'codeplan'
? (typeInfo?.codePlanDocsUrl || providerDocsUrl)
: providerDocsUrl;
const canEditModelConfig = Boolean(typeInfo?.showBaseUrl || showModelIdField);
useEffect(() => {
@@ -332,8 +354,17 @@ function ProviderCard({
setModelId(account.model || '');
setFallbackModelsText(normalizeFallbackModels(account.fallbackModels).join('\n'));
setFallbackProviderIds(normalizeFallbackProviderIds(account.fallbackAccountIds));
setArkMode(
isArkCodePlanMode(
account.vendorId,
account.baseUrl,
account.model,
typeInfo?.codePlanPresetBaseUrl,
typeInfo?.codePlanPresetModelId,
) ? 'codeplan' : 'apikey'
);
}
}, [isEditing, account.baseUrl, account.fallbackModels, account.fallbackAccountIds, account.model, account.apiProtocol]);
}, [isEditing, account.baseUrl, account.fallbackModels, account.fallbackAccountIds, account.model, account.apiProtocol, account.vendorId, typeInfo?.codePlanPresetBaseUrl, typeInfo?.codePlanPresetModelId]);
const fallbackOptions = allProviders.filter((candidate) => candidate.account.id !== account.id);
@@ -524,10 +555,10 @@ function ProviderCard({
{isEditing && (
<div className="space-y-6 mt-4 pt-4 border-t border-black/5 dark:border-white/5">
{providerDocsUrl && (
{effectiveDocsUrl && (
<div className="flex justify-end -mt-2 mb-2">
<a
href={providerDocsUrl}
href={effectiveDocsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[12px] text-blue-500 hover:text-blue-600 font-medium inline-flex items-center gap-1"
@@ -562,6 +593,55 @@ function ProviderCard({
/>
</div>
)}
{account.vendorId === 'ark' && codePlanPreset && (
<div className="space-y-1.5 pt-2">
<div className="flex items-center justify-between gap-2">
<Label className={currentLabelClasses}>{t('aiProviders.dialog.codePlanPreset')}</Label>
{typeInfo?.codePlanDocsUrl && (
<a
href={typeInfo.codePlanDocsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[12px] text-blue-500 hover:text-blue-600 font-medium inline-flex items-center gap-1"
>
{t('aiProviders.dialog.codePlanDoc')}
<ExternalLink className="h-3 w-3" />
</a>
)}
</div>
<div className="flex gap-2 text-[13px]">
<button
type="button"
onClick={() => {
setArkMode('apikey');
setBaseUrl(typeInfo?.defaultBaseUrl || '');
if (modelId.trim() === codePlanPreset.modelId) {
setModelId(typeInfo?.defaultModelId || '');
}
}}
className={cn("flex-1 py-1.5 px-3 rounded-lg border transition-colors", arkMode === 'apikey' ? "bg-white dark:bg-card border-black/20 dark:border-white/20 shadow-sm font-medium" : "border-transparent bg-black/5 dark:bg-white/5 text-muted-foreground hover:bg-black/10 dark:hover:bg-white/10")}
>
{t('aiProviders.authModes.apiKey')}
</button>
<button
type="button"
onClick={() => {
setArkMode('codeplan');
setBaseUrl(codePlanPreset.baseUrl);
setModelId(codePlanPreset.modelId);
}}
className={cn("flex-1 py-1.5 px-3 rounded-lg border transition-colors", arkMode === 'codeplan' ? "bg-white dark:bg-card border-black/20 dark:border-white/20 shadow-sm font-medium" : "border-transparent bg-black/5 dark:bg-white/5 text-muted-foreground hover:bg-black/10 dark:hover:bg-white/10")}
>
{t('aiProviders.dialog.codePlanMode')}
</button>
</div>
{arkMode === 'codeplan' && (
<p className="text-[12px] text-muted-foreground">
{t('aiProviders.dialog.codePlanPresetDesc')}
</p>
)}
</div>
)}
{account.vendorId === 'custom' && (
<div className="space-y-1.5 pt-2">
<Label className={currentLabelClasses}>{t('aiProviders.dialog.protocol', 'Protocol')}</Label>
@@ -776,6 +856,7 @@ function AddProviderDialog({
const [baseUrl, setBaseUrl] = useState('');
const [modelId, setModelId] = useState('');
const [apiProtocol, setApiProtocol] = useState<ProviderAccount['apiProtocol']>('openai-completions');
const [arkMode, setArkMode] = useState<ArkMode>('apikey');
const [showKey, setShowKey] = useState(false);
const [saving, setSaving] = useState(false);
const [validationError, setValidationError] = useState<string | null>(null);
@@ -801,6 +882,15 @@ function AddProviderDialog({
const typeInfo = PROVIDER_TYPE_INFO.find((t) => t.id === selectedType);
const providerDocsUrl = getProviderDocsUrl(typeInfo, i18n.language);
const showModelIdField = shouldShowProviderModelId(typeInfo, devModeUnlocked);
const codePlanPreset = typeInfo?.codePlanPresetBaseUrl && typeInfo?.codePlanPresetModelId
? {
baseUrl: typeInfo.codePlanPresetBaseUrl,
modelId: typeInfo.codePlanPresetModelId,
}
: null;
const effectiveDocsUrl = selectedType === 'ark' && arkMode === 'codeplan'
? (typeInfo?.codePlanDocsUrl || providerDocsUrl)
: providerDocsUrl;
const isOAuth = typeInfo?.isOAuth ?? false;
const supportsApiKey = typeInfo?.supportsApiKey ?? false;
const vendorMap = new Map(vendors.map((vendor) => [vendor.id, vendor]));
@@ -820,6 +910,23 @@ function AddProviderDialog({
setAuthMode(selectedVendor.defaultAuthMode === 'api_key' ? 'apikey' : 'oauth');
}, [selectedVendor, isOAuth, supportsApiKey]);
useEffect(() => {
if (selectedType !== 'ark') {
setArkMode('apikey');
return;
}
setArkMode(
isArkCodePlanMode(
'ark',
baseUrl,
modelId,
typeInfo?.codePlanPresetBaseUrl,
typeInfo?.codePlanPresetModelId,
) ? 'codeplan' : 'apikey'
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedType]);
// Keep refs to the latest values so event handlers see the current dialog state.
const latestRef = React.useRef({ selectedType, typeInfo, onAdd, onClose, t });
const pendingOAuthRef = React.useRef<{ accountId: string; label: string } | null>(null);
@@ -1056,6 +1163,7 @@ function AddProviderDialog({
setName(type.id === 'custom' ? t('aiProviders.custom') : type.name);
setBaseUrl(type.defaultBaseUrl || '');
setModelId(type.defaultModelId || '');
setArkMode('apikey');
}}
className="p-4 rounded-2xl border border-black/5 dark:border-white/5 hover:bg-black/5 dark:hover:bg-white/5 transition-colors text-center group"
>
@@ -1088,16 +1196,17 @@ function AddProviderDialog({
setValidationError(null);
setBaseUrl('');
setModelId('');
setArkMode('apikey');
}}
className="text-[13px] text-blue-500 hover:text-blue-600 font-medium"
>
{t('aiProviders.dialog.change')}
</button>
{providerDocsUrl && (
{effectiveDocsUrl && (
<>
<span className="mx-2 text-foreground/20">|</span>
<a
href={providerDocsUrl}
href={effectiveDocsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[13px] text-blue-500 hover:text-blue-600 font-medium inline-flex items-center gap-1"
@@ -1220,6 +1329,58 @@ function AddProviderDialog({
/>
</div>
)}
{selectedType === 'ark' && codePlanPreset && (
<div className="space-y-2.5">
<div className="flex items-center justify-between gap-2">
<Label className={labelClasses}>{t('aiProviders.dialog.codePlanPreset')}</Label>
{typeInfo?.codePlanDocsUrl && (
<a
href={typeInfo.codePlanDocsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[13px] text-blue-500 hover:text-blue-600 font-medium inline-flex items-center gap-1"
tabIndex={-1}
>
{t('aiProviders.dialog.codePlanDoc')}
<ExternalLink className="h-3 w-3" />
</a>
)}
</div>
<div className="flex gap-2 text-[13px]">
<button
type="button"
onClick={() => {
setArkMode('apikey');
setBaseUrl(typeInfo?.defaultBaseUrl || '');
if (modelId.trim() === codePlanPreset.modelId) {
setModelId(typeInfo?.defaultModelId || '');
}
setValidationError(null);
}}
className={cn("flex-1 py-1.5 px-3 rounded-lg border transition-colors", arkMode === 'apikey' ? "bg-white dark:bg-card border-black/20 dark:border-white/20 shadow-sm font-medium" : "border-transparent bg-black/5 dark:bg-white/5 text-muted-foreground hover:bg-black/10 dark:hover:bg-white/10")}
>
{t('aiProviders.authModes.apiKey')}
</button>
<button
type="button"
onClick={() => {
setArkMode('codeplan');
setBaseUrl(codePlanPreset.baseUrl);
setModelId(codePlanPreset.modelId);
setValidationError(null);
}}
className={cn("flex-1 py-1.5 px-3 rounded-lg border transition-colors", arkMode === 'codeplan' ? "bg-white dark:bg-card border-black/20 dark:border-white/20 shadow-sm font-medium" : "border-transparent bg-black/5 dark:bg-white/5 text-muted-foreground hover:bg-black/10 dark:hover:bg-white/10")}
>
{t('aiProviders.dialog.codePlanMode')}
</button>
</div>
{arkMode === 'codeplan' && (
<p className="text-[12px] text-muted-foreground">
{t('aiProviders.dialog.codePlanPresetDesc')}
</p>
)}
</div>
)}
{selectedType === 'custom' && (
<div className="space-y-2.5">
<Label className={labelClasses}>{t('aiProviders.dialog.protocol', 'Protocol')}</Label>

View File

@@ -52,6 +52,11 @@
"replaceApiKeyHelp": "Leave this field empty if you want to keep the currently stored API key.",
"baseUrl": "Base URL",
"modelId": "Model ID",
"codePlanPreset": "Code Plan Preset",
"codePlanMode": "Code Plan",
"useCodePlanPreset": "Use Ark Code Plan Preset",
"codePlanPresetDesc": "Code Plan uses https://ark.cn-beijing.volces.com/api/coding/v3 and model ark-code-latest. Do not use /api/v3 for Code Plan traffic.",
"codePlanDoc": "Code Plan docs",
"protocol": "Protocol",
"fallbackModels": "Fallback Models",
"fallbackProviders": "Fallback Providers",

View File

@@ -67,6 +67,11 @@
"baseUrl": "Base URL",
"modelId": "Model ID",
"modelIdDesc": "The model identifier from your provider (e.g. deepseek-ai/DeepSeek-V3)",
"codePlanPreset": "Code Plan Preset",
"codePlanMode": "Code Plan",
"useCodePlanPreset": "Use Ark Code Plan Preset",
"codePlanPresetDesc": "Code Plan uses https://ark.cn-beijing.volces.com/api/coding/v3 and model ark-code-latest. Do not use /api/v3 for Code Plan traffic.",
"codePlanDoc": "Code Plan docs",
"protocol": "Protocol",
"protocols": {
"openaiCompletions": "OpenAI Completions",

View File

@@ -52,6 +52,11 @@
"replaceApiKeyHelp": "現在保存されている API キーをそのまま使う場合は、この欄を空のままにしてください。",
"baseUrl": "ベース URL",
"modelId": "モデル ID",
"codePlanPreset": "Code Plan プリセット",
"codePlanMode": "Code Plan",
"useCodePlanPreset": "Ark Code Plan プリセットを使用",
"codePlanPresetDesc": "Code Plan は https://ark.cn-beijing.volces.com/api/coding/v3 と model ark-code-latest を使います。Code Plan 通信に /api/v3 を使わないでください。",
"codePlanDoc": "Code Plan ドキュメント",
"protocol": "プロトコル",
"fallbackModels": "フォールバックモデル",
"fallbackProviders": "別プロバイダーへのフォールバック",

View File

@@ -67,6 +67,11 @@
"baseUrl": "ベース URL",
"modelId": "モデル ID",
"modelIdDesc": "プロバイダーのモデル識別子deepseek-ai/DeepSeek-V3",
"codePlanPreset": "Code Plan プリセット",
"codePlanMode": "Code Plan",
"useCodePlanPreset": "Ark Code Plan プリセットを使用",
"codePlanPresetDesc": "Code Plan は https://ark.cn-beijing.volces.com/api/coding/v3 と model ark-code-latest を使います。Code Plan 通信に /api/v3 を使わないでください。",
"codePlanDoc": "Code Plan ドキュメント",
"protocol": "プロトコル",
"protocols": {
"openaiCompletions": "OpenAI Completions",

View File

@@ -52,6 +52,11 @@
"replaceApiKeyHelp": "如果想保留当前已保存的 API key这里留空即可。",
"baseUrl": "基础 URL",
"modelId": "模型 ID",
"codePlanPreset": "Code Plan 预设",
"codePlanMode": "Code Plan",
"useCodePlanPreset": "使用 Ark Code Plan 预设",
"codePlanPresetDesc": "Code Plan 使用 https://ark.cn-beijing.volces.com/api/coding/v3 与模型 ark-code-latest。请勿把 /api/v3 用于 Code Plan 流量。",
"codePlanDoc": "Code Plan 文档",
"protocol": "协议",
"fallbackModels": "回退模型",
"fallbackProviders": "跨 Provider 回退",

View File

@@ -67,6 +67,11 @@
"baseUrl": "基础 URL",
"modelId": "模型 ID",
"modelIdDesc": "提供商的模型标识符(例如 deepseek-ai/DeepSeek-V3",
"codePlanPreset": "Code Plan 预设",
"codePlanMode": "Code Plan",
"useCodePlanPreset": "使用 Ark Code Plan 预设",
"codePlanPresetDesc": "Code Plan 使用 https://ark.cn-beijing.volces.com/api/coding/v3 与模型 ark-code-latest。请勿把 /api/v3 用于 Code Plan 流量。",
"codePlanDoc": "Code Plan 文档",
"protocol": "协议",
"protocols": {
"openaiCompletions": "OpenAI Completions",

View File

@@ -75,6 +75,9 @@ export interface ProviderTypeInfo {
apiKeyUrl?: string;
docsUrl?: string;
docsUrlZh?: string;
codePlanPresetBaseUrl?: string;
codePlanPresetModelId?: string;
codePlanDocsUrl?: string;
}
export type ProviderAuthMode =
@@ -161,7 +164,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [
{ id: 'siliconflow', name: 'SiliconFlow (CN)', icon: '🌊', placeholder: 'sk-...', model: 'Multi-Model', requiresApiKey: true, defaultBaseUrl: 'https://api.siliconflow.cn/v1', showModelId: true, modelIdPlaceholder: 'deepseek-ai/DeepSeek-V3', defaultModelId: 'deepseek-ai/DeepSeek-V3', docsUrl: 'https://docs.siliconflow.cn/cn/userguide/introduction' },
{ id: 'minimax-portal', name: 'MiniMax (Global)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultModelId: 'MiniMax-M2.5', apiKeyUrl: 'https://intl.minimaxi.com/' },
{ id: 'qwen-portal', name: 'Qwen (Global)', icon: '☁️', placeholder: 'sk-...', model: 'Qwen', requiresApiKey: false, isOAuth: true, defaultModelId: 'coder-model' },
{ id: 'ark', name: 'ByteDance Ark', icon: 'A', placeholder: 'your-ark-api-key', model: 'Doubao', requiresApiKey: true, defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'ep-20260228000000-xxxxx', docsUrl: 'https://www.volcengine.com/' },
{ id: 'ark', name: 'ByteDance Ark', icon: 'A', placeholder: 'your-ark-api-key', model: 'Doubao', requiresApiKey: true, defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'ep-20260228000000-xxxxx', docsUrl: 'https://www.volcengine.com/', codePlanPresetBaseUrl: 'https://ark.cn-beijing.volces.com/api/coding/v3', codePlanPresetModelId: 'ark-code-latest', codePlanDocsUrl: 'https://www.volcengine.com/docs/82379/1928261?lang=zh' },
{ id: 'ollama', name: 'Ollama', icon: '🦙', placeholder: 'Not required', requiresApiKey: false, defaultBaseUrl: 'http://localhost:11434/v1', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'qwen3:latest' },
{
id: 'custom',

View File

@@ -727,6 +727,7 @@ function ProviderContent({
const providerMenuRef = useRef<HTMLDivElement | null>(null);
const [authMode, setAuthMode] = useState<'oauth' | 'apikey'>('oauth');
const [arkMode, setArkMode] = useState<'apikey' | 'codeplan'>('apikey');
// OAuth Flow State
const [oauthFlowing, setOauthFlowing] = useState(false);
@@ -939,9 +940,22 @@ function ProviderContent({
onApiKeyChange(storedKey || '');
const info = providers.find((p) => p.id === selectedProvider);
setBaseUrl(savedProvider?.baseUrl || info?.defaultBaseUrl || '');
setModelId(savedProvider?.model || info?.defaultModelId || '');
const nextBaseUrl = savedProvider?.baseUrl || info?.defaultBaseUrl || '';
const nextModelId = savedProvider?.model || info?.defaultModelId || '';
setBaseUrl(nextBaseUrl);
setModelId(nextModelId);
setApiProtocol(savedProvider?.apiProtocol || 'openai-completions');
if (
selectedProvider === 'ark'
&& info?.codePlanPresetBaseUrl
&& info?.codePlanPresetModelId
&& nextBaseUrl.trim() === info.codePlanPresetBaseUrl
&& nextModelId.trim() === info.codePlanPresetModelId
) {
setArkMode('codeplan');
} else {
setArkMode('apikey');
}
}
} catch (error) {
if (!cancelled) {
@@ -977,11 +991,20 @@ function ProviderContent({
const selectedProviderData = providers.find((p) => p.id === selectedProvider);
const providerDocsUrl = getProviderDocsUrl(selectedProviderData, i18n.language);
const effectiveProviderDocsUrl = selectedProvider === 'ark' && arkMode === 'codeplan'
? (selectedProviderData?.codePlanDocsUrl || providerDocsUrl)
: providerDocsUrl;
const selectedProviderIconUrl = selectedProviderData
? getProviderIconUrl(selectedProviderData.id)
: undefined;
const showBaseUrlField = selectedProviderData?.showBaseUrl ?? false;
const showModelIdField = shouldShowProviderModelId(selectedProviderData, devModeUnlocked);
const codePlanPreset = selectedProviderData?.codePlanPresetBaseUrl && selectedProviderData?.codePlanPresetModelId
? {
baseUrl: selectedProviderData.codePlanPresetBaseUrl,
modelId: selectedProviderData.codePlanPresetModelId,
}
: null;
const requiresKey = selectedProviderData?.requiresApiKey ?? false;
const isOAuth = selectedProviderData?.isOAuth ?? false;
const supportsApiKey = selectedProviderData?.supportsApiKey ?? false;
@@ -1135,6 +1158,7 @@ function ProviderContent({
setKeyValid(null);
setProviderMenuOpen(false);
setAuthMode('oauth');
setArkMode('apikey');
};
return (
@@ -1143,9 +1167,9 @@ function ProviderContent({
<div className="space-y-2">
<div className="flex items-center justify-between gap-3">
<Label>{t('provider.label')}</Label>
{selectedProvider && providerDocsUrl && (
{selectedProvider && effectiveProviderDocsUrl && (
<a
href={providerDocsUrl}
href={effectiveProviderDocsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[13px] text-blue-500 hover:text-blue-600 font-medium inline-flex items-center gap-1"
@@ -1241,6 +1265,68 @@ function ProviderContent({
animate={{ opacity: 1, y: 0 }}
className="space-y-4"
>
{codePlanPreset && (
<div className="space-y-2">
<div className="flex items-center justify-between gap-3">
<Label>{t('provider.codePlanPreset')}</Label>
{selectedProviderData?.codePlanDocsUrl && (
<a
href={selectedProviderData.codePlanDocsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[13px] text-blue-500 hover:text-blue-600 font-medium inline-flex items-center gap-1"
>
{t('provider.codePlanDoc')}
<ExternalLink className="h-3 w-3" />
</a>
)}
</div>
<div className="flex gap-2 text-sm">
<button
type="button"
onClick={() => {
setArkMode('apikey');
setBaseUrl(selectedProviderData?.defaultBaseUrl || '');
if (modelId.trim() === codePlanPreset.modelId) {
setModelId(selectedProviderData?.defaultModelId || '');
}
onConfiguredChange(false);
}}
className={cn(
'flex-1 py-2 px-3 rounded-lg border transition-colors',
arkMode === 'apikey'
? 'bg-primary/10 border-primary/30 font-medium'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-muted'
)}
>
{t('settings:aiProviders.authModes.apiKey')}
</button>
<button
type="button"
onClick={() => {
setArkMode('codeplan');
setBaseUrl(codePlanPreset.baseUrl);
setModelId(codePlanPreset.modelId);
onConfiguredChange(false);
}}
className={cn(
'flex-1 py-2 px-3 rounded-lg border transition-colors',
arkMode === 'codeplan'
? 'bg-primary/10 border-primary/30 font-medium'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-muted'
)}
>
{t('provider.codePlanMode')}
</button>
</div>
{arkMode === 'codeplan' && (
<p className="text-xs text-muted-foreground">
{t('provider.codePlanPresetDesc')}
</p>
)}
</div>
)}
{/* Base URL field (for siliconflow, ollama, custom) */}
{showBaseUrlField && (
<div className="space-y-2">

View File

@@ -27,6 +27,9 @@ describe('provider metadata', () => {
defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
showBaseUrl: true,
showModelId: true,
codePlanPresetBaseUrl: 'https://ark.cn-beijing.volces.com/api/coding/v3',
codePlanPresetModelId: 'ark-code-latest',
codePlanDocsUrl: 'https://www.volcengine.com/docs/82379/1928261?lang=zh',
}),
])
);