mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(models): restore disabled custom providers
This commit is contained in:
parent
aecc619bef
commit
281ea53551
@ -175,7 +175,11 @@ public class ModelProviderService {
|
|||||||
throw new MateClawException("err.llm.provider_id_invalid",
|
throw new MateClawException("err.llm.provider_id_invalid",
|
||||||
"Provider id 仅允许字母/数字及 . _ -(不允许斜杠或空格),首字符必须是字母或数字,长度 1-64: " + request.getId());
|
"Provider id 仅允许字母/数字及 . _ -(不允许斜杠或空格),首字符必须是字母或数字,长度 1-64: " + request.getId());
|
||||||
}
|
}
|
||||||
if (modelProviderMapper.selectById(request.getId()) != null) {
|
ModelProviderEntity existing = modelProviderMapper.selectById(request.getId());
|
||||||
|
if (existing != null && canRestoreCustomProvider(existing)) {
|
||||||
|
return restoreCustomProvider(existing, request);
|
||||||
|
}
|
||||||
|
if (existing != null) {
|
||||||
throw new MateClawException("err.llm.provider_exists", "Provider 已存在: " + request.getId());
|
throw new MateClawException("err.llm.provider_exists", "Provider 已存在: " + request.getId());
|
||||||
}
|
}
|
||||||
ModelProtocol protocol = ModelProtocol.resolve(request.getProtocol(), request.getChatModel());
|
ModelProtocol protocol = ModelProtocol.resolve(request.getProtocol(), request.getChatModel());
|
||||||
@ -211,6 +215,32 @@ public class ModelProviderService {
|
|||||||
return toProviderInfo(provider, modelConfigService.listModelsByProvider(request.getId()));
|
return toProviderInfo(provider, modelConfigService.listModelsByProvider(request.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean canRestoreCustomProvider(ModelProviderEntity provider) {
|
||||||
|
return Boolean.TRUE.equals(provider.getIsCustom()) && !Boolean.TRUE.equals(provider.getEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProviderInfoDTO restoreCustomProvider(ModelProviderEntity provider, CreateCustomProviderRequest request) {
|
||||||
|
ModelProtocol protocol = ModelProtocol.resolve(request.getProtocol(), request.getChatModel());
|
||||||
|
provider.setName(request.getName());
|
||||||
|
provider.setApiKeyPrefix(request.getApiKeyPrefix());
|
||||||
|
provider.setChatModel(protocol.getChatModelClass());
|
||||||
|
provider.setBaseUrl(request.getDefaultBaseUrl());
|
||||||
|
provider.setIsCustom(true);
|
||||||
|
provider.setIsLocal(false);
|
||||||
|
provider.setEnabled(true);
|
||||||
|
provider.setSupportModelDiscovery(protocol.supportsSelfConfiguredDiscovery());
|
||||||
|
provider.setRequireApiKey(request.getRequireApiKey() == null || Boolean.TRUE.equals(request.getRequireApiKey()));
|
||||||
|
modelProviderMapper.updateById(provider);
|
||||||
|
if (request.getModels() != null) {
|
||||||
|
for (ModelInfoDTO model : request.getModels()) {
|
||||||
|
modelConfigService.addModelToProvider(request.getId(), model.getId(), model.getName(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tryAutoActivateModel(request.getId(), provider);
|
||||||
|
eventPublisher.publishEvent(new ModelConfigChangedEvent("provider-enabled"));
|
||||||
|
return toProviderInfo(provider, modelConfigService.listModelsByProvider(request.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteCustomProvider(String providerId) {
|
public void deleteCustomProvider(String providerId) {
|
||||||
ModelProviderEntity provider = getProvider(providerId);
|
ModelProviderEntity provider = getProvider(providerId);
|
||||||
if (!Boolean.TRUE.equals(provider.getIsCustom())) {
|
if (!Boolean.TRUE.equals(provider.getIsCustom())) {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import vip.mate.llm.failover.AvailableProviderPool;
|
|||||||
import vip.mate.llm.failover.ProviderHealthProperties;
|
import vip.mate.llm.failover.ProviderHealthProperties;
|
||||||
import vip.mate.llm.failover.ProviderHealthTracker;
|
import vip.mate.llm.failover.ProviderHealthTracker;
|
||||||
import vip.mate.llm.failover.ProviderInitProbe;
|
import vip.mate.llm.failover.ProviderInitProbe;
|
||||||
|
import vip.mate.llm.model.CreateCustomProviderRequest;
|
||||||
import vip.mate.llm.model.EnableResult;
|
import vip.mate.llm.model.EnableResult;
|
||||||
import vip.mate.llm.model.ModelConfigEntity;
|
import vip.mate.llm.model.ModelConfigEntity;
|
||||||
import vip.mate.llm.model.ModelProviderEntity;
|
import vip.mate.llm.model.ModelProviderEntity;
|
||||||
@ -112,6 +113,36 @@ class ModelProviderServiceEnableTest {
|
|||||||
verify(eventPublisher, never()).publishEvent(any());
|
verify(eventPublisher, never()).publishEvent(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("createCustomProvider with an existing disabled custom row re-enables and updates it")
|
||||||
|
void createCustomProviderReEnablesDisabledCustomProvider() {
|
||||||
|
ModelProviderEntity existing = providerEntity("custom-openai", false);
|
||||||
|
existing.setIsCustom(true);
|
||||||
|
existing.setName("Old name");
|
||||||
|
when(providerMapper.selectById("custom-openai")).thenReturn(existing);
|
||||||
|
when(modelConfigService.listModelsByProvider("custom-openai")).thenReturn(new ArrayList<>());
|
||||||
|
|
||||||
|
CreateCustomProviderRequest request = new CreateCustomProviderRequest();
|
||||||
|
request.setId("custom-openai");
|
||||||
|
request.setName("New name");
|
||||||
|
request.setDefaultBaseUrl("https://new.example.com/v1");
|
||||||
|
request.setApiKeyPrefix("mk-");
|
||||||
|
request.setProtocol("openai-compatible");
|
||||||
|
request.setRequireApiKey(true);
|
||||||
|
|
||||||
|
service.createCustomProvider(request);
|
||||||
|
|
||||||
|
assertTrue(existing.getEnabled(), "disabled custom provider should be visible again");
|
||||||
|
assertEquals("New name", existing.getName());
|
||||||
|
assertEquals("https://new.example.com/v1", existing.getBaseUrl());
|
||||||
|
assertEquals("mk-", existing.getApiKeyPrefix());
|
||||||
|
verify(providerMapper).updateById(existing);
|
||||||
|
verify(providerMapper, never()).insert(any(ModelProviderEntity.class));
|
||||||
|
ArgumentCaptor<ModelConfigChangedEvent> evtCap = ArgumentCaptor.forClass(ModelConfigChangedEvent.class);
|
||||||
|
verify(eventPublisher).publishEvent(evtCap.capture());
|
||||||
|
assertEquals("provider-enabled", evtCap.getValue().reason());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("setEnabled(false) when provider's model is current default: auto-switches and reports new")
|
@DisplayName("setEnabled(false) when provider's model is current default: auto-switches and reports new")
|
||||||
void disableSwitchesDefault() {
|
void disableSwitchesDefault() {
|
||||||
|
|||||||
@ -1137,15 +1137,15 @@ export default {
|
|||||||
livenessUnprobedTooltip: 'Checking availability after startup; will refresh shortly',
|
livenessUnprobedTooltip: 'Checking availability after startup; will refresh shortly',
|
||||||
livenessUnconfigured: 'Needs setup',
|
livenessUnconfigured: 'Needs setup',
|
||||||
// RFC-074 PR-2: enable / disable / drawer
|
// RFC-074 PR-2: enable / disable / drawer
|
||||||
enableProviderCta: '+ Enable Provider',
|
enableProviderCta: '+ Enable / Restore Provider',
|
||||||
addCustomProvider: '+ Custom',
|
addCustomProvider: '+ Custom',
|
||||||
enable: 'Enable',
|
enable: 'Enable',
|
||||||
disable: 'Disable',
|
disable: 'Disable',
|
||||||
alreadyEnabled: 'Enabled',
|
alreadyEnabled: 'Enabled',
|
||||||
disableConfirm: 'Disable provider "{name}"? It will disappear from the dropdown but its config is kept — you can re-enable any time.',
|
disableConfirm: 'Disable provider "{name}"? It will disappear from the dropdown but its config is kept — you can re-enable any time.',
|
||||||
defaultSwitchedToast: 'Default model switched to {provider} / {model}',
|
defaultSwitchedToast: 'Default model switched to {provider} / {model}',
|
||||||
addProviderDrawerTitle: 'Add Provider',
|
addProviderDrawerTitle: 'Enable / Restore Provider',
|
||||||
addProviderDrawerSubtitle: 'Once enabled the provider appears in the model dropdown. Cloud providers still need an API key after enabling.',
|
addProviderDrawerSubtitle: 'Disabled providers can be restored here. Once enabled, the provider appears in the model dropdown. Cloud providers may still need an API key.',
|
||||||
catalogEmpty: 'Catalog is empty',
|
catalogEmpty: 'Catalog is empty',
|
||||||
catalogSearchPlaceholder: 'Search providers…',
|
catalogSearchPlaceholder: 'Search providers…',
|
||||||
catalogSearchEmpty: 'No providers match “{query}”',
|
catalogSearchEmpty: 'No providers match “{query}”',
|
||||||
|
|||||||
@ -982,15 +982,15 @@ export default {
|
|||||||
livenessUnprobedTooltip: '启动后正在检测可用性,稍候自动更新',
|
livenessUnprobedTooltip: '启动后正在检测可用性,稍候自动更新',
|
||||||
livenessUnconfigured: '需要配置',
|
livenessUnconfigured: '需要配置',
|
||||||
// RFC-074 PR-2: enable / disable / drawer
|
// RFC-074 PR-2: enable / disable / drawer
|
||||||
enableProviderCta: '+ 启用提供商',
|
enableProviderCta: '+ 启用/恢复提供商',
|
||||||
addCustomProvider: '+ 自定义提供商',
|
addCustomProvider: '+ 自定义提供商',
|
||||||
enable: '启用',
|
enable: '启用',
|
||||||
disable: '禁用',
|
disable: '禁用',
|
||||||
alreadyEnabled: '已启用',
|
alreadyEnabled: '已启用',
|
||||||
disableConfirm: '确定禁用提供商"{name}"?它将从下拉中消失,但配置保留,可随时再启用。',
|
disableConfirm: '确定禁用提供商"{name}"?它将从下拉中消失,但配置保留,可随时再启用。',
|
||||||
defaultSwitchedToast: '已将默认模型切换到 {provider} / {model}',
|
defaultSwitchedToast: '已将默认模型切换到 {provider} / {model}',
|
||||||
addProviderDrawerTitle: '添加提供商',
|
addProviderDrawerTitle: '启用/恢复提供商',
|
||||||
addProviderDrawerSubtitle: '启用后会出现在模型下拉中。如果是云端提供商,启用后还需要填 API Key。',
|
addProviderDrawerSubtitle: '禁用后的提供商可在这里恢复;启用后会出现在模型下拉中。云端提供商可能还需要填 API Key。',
|
||||||
catalogEmpty: '目录为空',
|
catalogEmpty: '目录为空',
|
||||||
catalogSearchPlaceholder: '搜索提供商…',
|
catalogSearchPlaceholder: '搜索提供商…',
|
||||||
catalogSearchEmpty: '没有匹配“{query}”的提供商',
|
catalogSearchEmpty: '没有匹配“{query}”的提供商',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user