mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-23 19:00:14 +00:00
fix: show config-managed custom providers
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- **PR #2634** by @Michaelyklam (closes #2632) — Show `custom_providers` entries created by `hermes model` in Settings → Providers as read-only config-managed provider cards, including their configured models and key status, instead of filtering them out because they are not WebUI-editable API-key providers.
|
||||
|
||||
|
||||
## [v0.51.95] — 2026-05-20 — Release BS (stage-388 — 5-PR batch — live tool callback event dedup + browser-only dashboard links + messaging transcript merge alignment + Geist Contrast skin + SSE runtime diagnostics)
|
||||
|
||||
|
||||
@@ -1984,8 +1984,10 @@ def get_providers() -> dict[str, Any]:
|
||||
"display_name": cp_name,
|
||||
"has_key": cp_has_key,
|
||||
"configurable": False, # custom providers managed via config.yaml
|
||||
"is_custom": True,
|
||||
"key_source": "config_yaml" if cp_has_key else "none",
|
||||
"models": cp_models,
|
||||
"models_total": len(cp_models),
|
||||
})
|
||||
|
||||
# Determine active provider
|
||||
|
||||
+56
-43
@@ -5770,7 +5770,7 @@ async function loadProvidersPanel(){
|
||||
try{
|
||||
const data=await api('/api/providers');
|
||||
const quota=await _fetchProviderQuotaStatus(false).catch(e=>({ok:false,status:'unavailable',quota:null,message:e.message||t('provider_quota_unavailable'),client_fetched_at:new Date().toISOString()}));
|
||||
const providers=(data.providers||[]).filter(p=>p.configurable||p.is_oauth);
|
||||
const providers=(data.providers||[]).filter(p=>p.configurable||p.is_oauth||p.is_custom);
|
||||
list.innerHTML='';
|
||||
_providerCardEls.clear();
|
||||
const quotaCard=_buildProviderQuotaCard(quota);
|
||||
@@ -6097,48 +6097,59 @@ function _buildProviderCard(p){
|
||||
return card;
|
||||
}
|
||||
|
||||
const field=document.createElement('div');
|
||||
field.className='provider-card-field';
|
||||
const label=document.createElement('label');
|
||||
label.className='provider-card-label';
|
||||
label.textContent=t('providers_status_api_key');
|
||||
field.appendChild(label);
|
||||
let input=null;
|
||||
let saveBtn=null;
|
||||
if(p.configurable){
|
||||
const field=document.createElement('div');
|
||||
field.className='provider-card-field';
|
||||
const label=document.createElement('label');
|
||||
label.className='provider-card-label';
|
||||
label.textContent=t('providers_status_api_key');
|
||||
field.appendChild(label);
|
||||
|
||||
const row=document.createElement('div');
|
||||
row.className='provider-card-row';
|
||||
const input=document.createElement('input');
|
||||
input.type='password';
|
||||
input.className='provider-card-input';
|
||||
input.placeholder=p.has_key?t('providers_key_placeholder_replace'):t('providers_key_placeholder_new');
|
||||
input.autocomplete='off';
|
||||
const toggleBtn=document.createElement('button');
|
||||
toggleBtn.type='button';
|
||||
toggleBtn.className='provider-card-btn provider-card-btn-ghost';
|
||||
toggleBtn.textContent='Show';
|
||||
toggleBtn.onclick=()=>{
|
||||
const revealed=input.type==='text';
|
||||
input.type=revealed?'password':'text';
|
||||
toggleBtn.textContent=revealed?'Show':'Hide';
|
||||
};
|
||||
const saveBtn=document.createElement('button');
|
||||
saveBtn.type='button';
|
||||
saveBtn.className='provider-card-btn provider-card-btn-primary';
|
||||
saveBtn.textContent=t('providers_save');
|
||||
saveBtn.onclick=()=>_saveProviderKey(p.id);
|
||||
saveBtn.disabled=true;
|
||||
row.appendChild(input);
|
||||
row.appendChild(toggleBtn);
|
||||
row.appendChild(saveBtn);
|
||||
if(p.has_key){
|
||||
const removeBtn=document.createElement('button');
|
||||
removeBtn.type='button';
|
||||
removeBtn.className='provider-card-btn provider-card-btn-danger';
|
||||
removeBtn.textContent=t('providers_remove');
|
||||
removeBtn.onclick=()=>_removeProviderKey(p.id);
|
||||
row.appendChild(removeBtn);
|
||||
const row=document.createElement('div');
|
||||
row.className='provider-card-row';
|
||||
input=document.createElement('input');
|
||||
input.type='password';
|
||||
input.className='provider-card-input';
|
||||
input.placeholder=p.has_key?t('providers_key_placeholder_replace'):t('providers_key_placeholder_new');
|
||||
input.autocomplete='off';
|
||||
const toggleBtn=document.createElement('button');
|
||||
toggleBtn.type='button';
|
||||
toggleBtn.className='provider-card-btn provider-card-btn-ghost';
|
||||
toggleBtn.textContent='Show';
|
||||
toggleBtn.onclick=()=>{
|
||||
const revealed=input.type==='text';
|
||||
input.type=revealed?'password':'text';
|
||||
toggleBtn.textContent=revealed?'Show':'Hide';
|
||||
};
|
||||
saveBtn=document.createElement('button');
|
||||
saveBtn.type='button';
|
||||
saveBtn.className='provider-card-btn provider-card-btn-primary';
|
||||
saveBtn.textContent=t('providers_save');
|
||||
saveBtn.onclick=()=>_saveProviderKey(p.id);
|
||||
saveBtn.disabled=true;
|
||||
row.appendChild(input);
|
||||
row.appendChild(toggleBtn);
|
||||
row.appendChild(saveBtn);
|
||||
if(p.has_key){
|
||||
const removeBtn=document.createElement('button');
|
||||
removeBtn.type='button';
|
||||
removeBtn.className='provider-card-btn provider-card-btn-danger';
|
||||
removeBtn.textContent=t('providers_remove');
|
||||
removeBtn.onclick=()=>_removeProviderKey(p.id);
|
||||
row.appendChild(removeBtn);
|
||||
}
|
||||
field.appendChild(row);
|
||||
body.appendChild(field);
|
||||
}else{
|
||||
const hint=document.createElement('div');
|
||||
hint.className='provider-card-hint';
|
||||
hint.textContent=p.is_custom
|
||||
? 'Custom provider loaded from config.yaml / hermes model. Edit it from the CLI or config file.'
|
||||
: 'Provider is managed outside the WebUI.';
|
||||
body.appendChild(hint);
|
||||
}
|
||||
field.appendChild(row);
|
||||
body.appendChild(field);
|
||||
|
||||
// Model list — show when provider has known models
|
||||
if(modelCount>0){
|
||||
@@ -6192,8 +6203,10 @@ function _buildProviderCard(p){
|
||||
body.appendChild(refreshRow);
|
||||
card.appendChild(body);
|
||||
|
||||
_providerCardEls.set(p.id,{card,input,saveBtn,hasKey:p.has_key});
|
||||
input.addEventListener('input',()=>{saveBtn.disabled=!input.value.trim();});
|
||||
if(input&&saveBtn){
|
||||
_providerCardEls.set(p.id,{card,input,saveBtn,hasKey:p.has_key});
|
||||
input.addEventListener('input',()=>{saveBtn.disabled=!input.value.trim();});
|
||||
}
|
||||
header.addEventListener('click',e=>{
|
||||
// Don't toggle when clicking inside body (defensive; body isn't inside header)
|
||||
if(e.target.closest('.provider-card-body')) return;
|
||||
|
||||
@@ -91,6 +91,7 @@ class TestCustomProvidersInGetProviders:
|
||||
assert glmcode["configurable"] is False, (
|
||||
"custom providers should not be configurable via WebUI"
|
||||
)
|
||||
assert glmcode["is_custom"] is True
|
||||
assert glmcode["key_source"] == "config_yaml"
|
||||
assert glmcode["display_name"] == "glmcode"
|
||||
|
||||
@@ -99,9 +100,17 @@ class TestCustomProvidersInGetProviders:
|
||||
assert "glm-5.1" in model_ids, (
|
||||
f"Expected glm-5.1 in models, got: {model_ids}"
|
||||
)
|
||||
assert glmcode["models_total"] == 1
|
||||
finally:
|
||||
self._restore_cfg(old_cfg, old_mtime)
|
||||
|
||||
def test_providers_panel_renders_config_yaml_custom_providers(self):
|
||||
"""Settings → Providers must not filter out read-only custom providers."""
|
||||
src = open("static/panels.js", encoding="utf-8").read()
|
||||
assert "filter(p=>p.configurable||p.is_oauth||p.is_custom)" in src
|
||||
assert "Custom provider loaded from config.yaml / hermes model" in src
|
||||
assert "if(p.configurable){" in src
|
||||
|
||||
def test_custom_provider_with_multi_models(self, monkeypatch, tmp_path):
|
||||
"""Custom provider with `models` list should expose all entries."""
|
||||
_install_fake_hermes_cli(monkeypatch)
|
||||
|
||||
Reference in New Issue
Block a user