+
+
+ {
+ setValue(e.target.value);
+ setShowSuggestions(true);
+ }}
+ onFocus={() => setShowSuggestions(true)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleAdd();
+ }
+ }}
+ />
+
+
+
+ {showSuggestions && availableSuggestions.length > 0 && (
+
+
+ {availableSuggestions.map((model) => (
+
+ ))}
+
+
+ )}
+
+ );
+}
+
// Sidebar tab types
type SidebarTab =
| 'cloud'
@@ -137,6 +239,7 @@ export default function SettingModels() {
apiHost: p.apiHost,
is_valid: p.is_valid ?? false,
model_type: p.model_type ?? '',
+ model_types: [] as string[],
externalConfig: p.externalConfig
? p.externalConfig.map((ec) => ({ ...ec }))
: undefined,
@@ -285,11 +388,13 @@ export default function SettingModels() {
...fi,
provider_id: found.id,
apiKey: found.api_key || '',
- // Fall back to provider's default API host if endpoint_url is empty
apiHost: found.endpoint_url || item.apiHost,
- is_valid: !!found?.is_valid,
+ is_valid: found?.is_vaild === 2,
prefer: found.prefer ?? false,
model_type: found.model_type ?? '',
+ model_types:
+ found.model_types ??
+ (found.model_type ? [found.model_type] : []),
externalConfig: fi.externalConfig
? fi.externalConfig.map((ec) => {
if (
@@ -352,19 +457,6 @@ export default function SettingModels() {
setLocalTypes(types);
setLocalProviderIds(providerIds);
}
- if (modelType === 'cloud') {
- setCloudPrefer(true);
- setForm((f) => f.map((fi) => ({ ...fi, prefer: false })));
- setLocalPrefer(false);
- } else if (modelType === 'local') {
- setLocalEnabled(true);
- setForm((f) => f.map((fi) => ({ ...fi, prefer: false })));
- setLocalPrefer(true);
- setCloudPrefer(false);
- } else {
- setLocalPrefer(false);
- setCloudPrefer(false);
- }
} catch (e) {
console.error('Error fetching providers:', e);
// ignore error
@@ -375,7 +467,21 @@ export default function SettingModels() {
fetchSubscription();
updateCredits();
}
- }, [items, modelType, fetchModelsForPlatform]);
+ }, [items, fetchModelsForPlatform]);
+
+ // Sync prefer UI flags when modelType changes
+ useEffect(() => {
+ if (modelType === 'cloud') {
+ setCloudPrefer(true);
+ setForm((f) => f.map((fi) => ({ ...fi, prefer: false })));
+ setLocalPrefer(false);
+ } else if (modelType === 'local') {
+ setLocalEnabled(true);
+ setForm((f) => f.map((fi) => ({ ...fi, prefer: false })));
+ setLocalPrefer(true);
+ setCloudPrefer(false);
+ }
+ }, [modelType]);
// Get current default model display text
const getDefaultModelDisplayText = (): string => {
@@ -391,12 +497,11 @@ export default function SettingModels() {
return `${t('setting.eigent-cloud')} / ${modelName}`;
}
- // Check for custom model preference
const preferredIdx = form.findIndex((f) => f.prefer);
if (preferredIdx !== -1) {
const item = items[preferredIdx];
- const modelType = form[preferredIdx].model_type || '';
- return `${t('setting.custom-model')} / ${item.name}${modelType ? ` (${modelType})` : ''}`;
+ const activeModel = form[preferredIdx].model_type || '';
+ return `${t('setting.custom-model')} / ${item.name}${activeModel ? ` (${activeModel})` : ''}`;
}
// Check for local model preference
@@ -525,7 +630,10 @@ export default function SettingModels() {
} else {
newErrors[idx].apiHost = '';
}
- if (!model_type || model_type.trim() === '') {
+ if (
+ (!model_type || model_type.trim() === '') &&
+ (!form[idx].model_types || form[idx].model_types.length === 0)
+ ) {
newErrors[idx].model_type = t('setting.model-type-can-not-be-empty');
hasError = true;
} else {
@@ -590,8 +698,9 @@ export default function SettingModels() {
provider_name: item.id,
api_key: form[idx].apiKey,
endpoint_url: form[idx].apiHost,
- is_valid: form[idx].is_valid,
+ is_vaild: 2,
model_type: form[idx].model_type,
+ model_types: form[idx].model_types,
};
if (externalConfig) {
data.encrypted_config = {};
@@ -599,30 +708,49 @@ export default function SettingModels() {
data.encrypted_config[ec.key] = ec.value;
});
}
+ const savedModelType = form[idx].model_type;
+ const savedModelTypes = form[idx].model_types;
try {
if (provider_id) {
- await proxyFetchPut(`/api/v1/provider/${provider_id}`, data);
- } else {
- await proxyFetchPost('/api/v1/provider', data);
+ // DELETE + POST to work around remote server's _UPDATABLE_FIELDS
+ // not including model_type / model_types in PUT
+ await proxyFetchDelete(`/api/v1/provider/${provider_id}`);
}
- // add: refresh provider list after saving, update form and switch editable status
+ const created = await proxyFetchPost('/api/v1/provider', data);
+ const newProviderId = created?.id;
+
+ // Always set this provider as preferred using the correct new ID
+ if (newProviderId) {
+ await proxyFetchPost('/api/v1/provider/prefer', {
+ provider_id: newProviderId,
+ });
+ }
+
+ // Refresh provider list after saving
const res = await proxyFetchGet('/api/v1/providers');
const providerList = Array.isArray(res) ? res : res.items || [];
+
setForm((f) =>
f.map((fi, i) => {
- const item = items[i];
+ const curItem = items[i];
const found = providerList.find(
- (p: any) => p.provider_name === item.id
+ (p: any) => p.provider_name === curItem.id
);
if (found) {
+ const isCurrentProvider = i === idx;
return {
...fi,
provider_id: found.id,
apiKey: found.api_key || '',
- // Fall back to provider's default API host if endpoint_url is empty
- apiHost: found.endpoint_url || item.apiHost,
- is_valid: !!found.is_valid,
- prefer: found.prefer ?? false,
+ apiHost: found.endpoint_url || curItem.apiHost,
+ is_valid: found?.is_vaild === 2,
+ prefer: isCurrentProvider ? true : false,
+ model_type: isCurrentProvider
+ ? savedModelType
+ : (found.model_type ?? fi.model_type),
+ model_types: isCurrentProvider
+ ? savedModelTypes
+ : (found.model_types ?? fi.model_types),
externalConfig: fi.externalConfig
? fi.externalConfig.map((ec) => {
if (
@@ -640,16 +768,19 @@ export default function SettingModels() {
})
);
- // Check if this was a pending default model selection
+ // Update UI state directly (avoid handleSwitch which reads stale form state)
+ setModelType('custom');
+ setActiveModelIdx(idx);
+ setLocalEnabled(false);
+ setCloudPrefer(false);
+ setLocalPrefer(false);
+
if (
pendingDefaultModel &&
pendingDefaultModel.category === 'custom' &&
pendingDefaultModel.modelId === item.id
) {
- await handleSwitch(idx, true);
setPendingDefaultModel(null);
- } else {
- handleSwitch(idx, true);
}
} finally {
setLoading(null);
@@ -794,9 +925,10 @@ export default function SettingModels() {
},
};
- // Update or create provider
+ // DELETE + POST to work around remote server ignoring model_type in PUT
if (currentProviderId) {
- await proxyFetchPut(`/api/v1/provider/${currentProviderId}`, data);
+ await proxyFetchDelete(`/api/v1/provider/${currentProviderId}`);
+ await proxyFetchPost('/api/v1/provider', data);
} else {
await proxyFetchPost('/api/v1/provider', data);
}
@@ -974,10 +1106,10 @@ export default function SettingModels() {
const item = items[i];
return {
apiKey: '',
- // Restore provider's default API host instead of clearing it
apiHost: item.apiHost,
is_valid: false,
model_type: '',
+ model_types: [],
externalConfig: item.externalConfig
? item.externalConfig.map((ec) => ({ ...ec, value: '' }))
: undefined,
@@ -1410,29 +1542,98 @@ export default function SettingModels() {
);
}}
/>
- {/* Model Type Setting */}
-
+
+ {t('setting.model-type-setting')}
+
+ {errors[idx]?.model_type && (
+
+ {errors[idx].model_type}
+
+ )}
+ {/* Active model indicator + model chips */}
+ {form[idx].model_types.length > 0 && (
+
+ {form[idx].model_types.map((m) => (
+
+ {m === form[idx].model_type && (
+
+ )}
+
+
+
+ ))}
+
+ )}
+ {/* Add model input with suggestions dropdown */}
+
{
+ setForm((f) =>
+ f.map((fi, i) => {
+ if (i !== idx) return fi;
+ return {
+ ...fi,
+ model_types: [model],
+ model_type: model,
+ };
+ })
+ );
+ setErrors((errs) =>
+ errs.map((er, i) =>
+ i === idx ? { ...er, model_type: '' } : er
+ )
+ );
+ }}
+ />
+
{/* externalConfig render */}
{item.externalConfig &&
form[idx].externalConfig &&
diff --git a/src/types/index.ts b/src/types/index.ts
index 82eeea590..41bca650c 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -35,6 +35,8 @@ export type Provider = {
externalConfig?: externalConfig[];
is_valid?: boolean;
model_type?: string;
+ model_types?: string[];
+ suggestedModels?: string[];
prefer?: boolean;
azure_deployment?: string;
};