feat(ark): add Code Plan mode preset and guided setup (#617)
This commit is contained in:
committed by
GitHub
Unverified
parent
f16e8062e1
commit
d39982bad8
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "別プロバイダーへのフォールバック",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 回退",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user