mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-24 18:50:15 +00:00
fix: remove PRIMARY chip badge + add Claude Opus 4.7 label
The composer-model-badge ("PRIMARY"/"FALLBACK N") added in #1287 was
projected onto the model chip itself, which added ~71px (≈30% of the
chip's width) of redundant signal: the model name is already right
there, so a pill announcing "this is your configured primary" is just
clutter that competes with real affordances. The badges still render in
the dropdown rows (model-opt-badge), where they actually distinguish
picker rows. This PR strips the chip projection only.
Separately, the new claude-opus-4-7 model had no label entry, so the
generic "split on dashes, replace with spaces" fallback formatter was
turning it into "Claude Opus 4 7" (no dot between the major and minor
versions). Every other Claude model in this hardcoded label table is
explicitly listed, so adding the missing entry is the right fix.
Changes:
- static/index.html: drop <span id="composerModelBadge"> from chip
- static/ui.js: drop badgeEl/badge logic from syncModelChip()
- static/style.css: drop .composer-model-badge* rules (4 lines)
- api/config.py: add 3 label entries for claude-opus-4.7 — the
anthropic/claude-opus-4.7 form (in _FALLBACK_MODELS), the bare
claude-opus-4.7 form (in _PROVIDER_MODELS["anthropic"]), and the
dashed claude-opus-4-7 form (in the bare-id static labels block,
which is what live-fetched Anthropic API responses return)
- tests/test_model_picker_badges.py: flip the chip-badge presence
assertions into absence assertions, plus a symmetric guard that
syncModelChip() never references composerModelBadge again
Backend `_build_configured_model_badges()` and the
`configured_model_badges` payload in `/api/models` are intentionally
preserved — the dropdown rows still consume them.
Tests: 3252 passed, 2 skipped, 3 xpassed (full suite).
Visual verification: chip width 235px → 164px (saves 71px) when the
model is the configured primary. Label correctly renders "Claude Opus
4.7" instead of "Claude Opus 4 7".
Closes nothing — direct user feedback from Nathan.
This commit is contained in:
@@ -503,6 +503,7 @@ _FALLBACK_MODELS = [
|
||||
{"provider": "OpenAI", "id": "openai/gpt-5.4-mini", "label": "GPT-5.4 Mini"},
|
||||
{"provider": "OpenAI", "id": "openai/gpt-5.4", "label": "GPT-5.4"},
|
||||
# Anthropic — 4.6 flagship + 4.5 generation
|
||||
{"provider": "Anthropic", "id": "anthropic/claude-opus-4.7", "label": "Claude Opus 4.7"},
|
||||
{"provider": "Anthropic", "id": "anthropic/claude-opus-4.6", "label": "Claude Opus 4.6"},
|
||||
{"provider": "Anthropic", "id": "anthropic/claude-sonnet-4.6", "label": "Claude Sonnet 4.6"},
|
||||
{"provider": "Anthropic", "id": "anthropic/claude-sonnet-4-5", "label": "Claude Sonnet 4.5"},
|
||||
@@ -641,6 +642,7 @@ def _resolve_provider_alias(name: str) -> str:
|
||||
# Well-known models per provider (used to populate dropdown for direct API providers)
|
||||
_PROVIDER_MODELS = {
|
||||
"anthropic": [
|
||||
{"id": "claude-opus-4.7", "label": "Claude Opus 4.7"},
|
||||
{"id": "claude-opus-4.6", "label": "Claude Opus 4.6"},
|
||||
{"id": "claude-sonnet-4.6", "label": "Claude Sonnet 4.6"},
|
||||
{"id": "claude-sonnet-4-5", "label": "Claude Sonnet 4.5"},
|
||||
@@ -738,6 +740,7 @@ _PROVIDER_MODELS = {
|
||||
{"id": "gpt-5", "label": "GPT-5"},
|
||||
{"id": "gpt-5-codex", "label": "GPT-5 Codex"},
|
||||
{"id": "gpt-5-nano", "label": "GPT-5 Nano"},
|
||||
{"id": "claude-opus-4-7", "label": "Claude Opus 4.7"},
|
||||
{"id": "claude-opus-4-6", "label": "Claude Opus 4.6"},
|
||||
{"id": "claude-opus-4-5", "label": "Claude Opus 4.5"},
|
||||
{"id": "claude-opus-4-1", "label": "Claude Opus 4.1"},
|
||||
|
||||
@@ -401,7 +401,6 @@
|
||||
<button class="composer-model-chip" id="composerModelChip" type="button" onclick="toggleModelDropdown()" title="Conversation model">
|
||||
<span class="composer-model-icon" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg></span>
|
||||
<span class="composer-model-label" id="composerModelLabel"></span>
|
||||
<span class="composer-model-badge" id="composerModelBadge" hidden></span>
|
||||
<span class="composer-model-chevron" aria-hidden="true"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
|
||||
</button>
|
||||
<select id="modelSelect" class="composer-model-select" title="Conversation model" aria-hidden="true" tabindex="-1">
|
||||
|
||||
@@ -824,10 +824,6 @@
|
||||
.composer-model-chip:hover{color:var(--text);background-color:var(--hover-bg);}
|
||||
.composer-model-chip.active{color:var(--text);background:var(--accent-bg);border-color:var(--accent-bg);}
|
||||
.composer-model-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.composer-model-badge{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;padding:2px 7px;border-radius:999px;font-size:10px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;border:1px solid transparent;}
|
||||
.composer-model-badge[hidden]{display:none;}
|
||||
.composer-model-badge--primary{background:rgba(50,184,198,.16);border-color:rgba(50,184,198,.32);color:#8fe7ef;}
|
||||
.composer-model-badge--fallback{background:rgba(255,184,77,.14);border-color:rgba(255,184,77,.28);color:#ffd18a;}
|
||||
.composer-model-icon,.composer-model-chevron{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;line-height:1;}
|
||||
.composer-model-select{position:absolute!important;left:-9999px!important;width:1px!important;height:1px!important;opacity:0!important;pointer-events:none!important;}
|
||||
.composer-right{display:flex;gap:8px;align-items:center;flex-shrink:0;}
|
||||
|
||||
@@ -425,28 +425,16 @@ function syncModelChip(){
|
||||
const sel=$('modelSelect');
|
||||
const chip=$('composerModelChip');
|
||||
const label=$('composerModelLabel');
|
||||
const badgeEl=$('composerModelBadge');
|
||||
const dd=$('composerModelDropdown');
|
||||
if(!sel||!chip||!label) return;
|
||||
// Don't show a model label until boot has finished loading to prevent flash of wrong default
|
||||
if(!S._bootReady){
|
||||
label.textContent='';
|
||||
chip.title='Conversation model';
|
||||
if(badgeEl){
|
||||
badgeEl.textContent='';
|
||||
badgeEl.hidden=true;
|
||||
badgeEl.className='composer-model-badge';
|
||||
}
|
||||
return;
|
||||
}
|
||||
const opt=_selectedModelOption();
|
||||
label.textContent=opt?opt.textContent:getModelLabel(sel.value||'');
|
||||
const badge=_getConfiguredModelBadge(sel.value||'',window._configuredModelBadges||{});
|
||||
if(badgeEl){
|
||||
badgeEl.textContent=badge&&badge.label?badge.label:'';
|
||||
badgeEl.hidden=!badgeEl.textContent;
|
||||
badgeEl.className='composer-model-badge'+(badge&&badge.role?` composer-model-badge--${badge.role}`:'');
|
||||
}
|
||||
chip.title=sel.value||'Conversation model';
|
||||
chip.classList.toggle('active',!!(dd&&dd.classList.contains('open')));
|
||||
}
|
||||
|
||||
@@ -106,18 +106,17 @@ def test_ui_renders_model_badges_from_api_payload():
|
||||
"A UI precisa de um helper de matching resiliente para religar badges mesmo quando "
|
||||
"o update do catálogo mudar prefixos/formas do model ID."
|
||||
)
|
||||
assert 'id="composerModelBadge"' in html, (
|
||||
"O chip principal do modelo precisa de um container dedicado para exibir o badge "
|
||||
"do modelo selecionado fora do dropdown."
|
||||
# Chip-projected badge was removed in v0.50.243 (added too much width to the
|
||||
# composer chip; signal value low since the model name is right next to it).
|
||||
# Badges remain in the dropdown rows (model-opt-badge) for picker rows.
|
||||
assert 'id="composerModelBadge"' not in html, (
|
||||
"composer-model-badge chip projection was intentionally removed — "
|
||||
"do not re-add it to the composer chip."
|
||||
)
|
||||
assert "composer-model-badge" in css, (
|
||||
"O badge do chip principal precisa de estilo próprio para ficar visível ao lado "
|
||||
"do nome do modelo selecionado."
|
||||
assert "composer-model-badge" not in css, (
|
||||
"composer-model-badge CSS was intentionally removed alongside the chip span."
|
||||
)
|
||||
assert "const badge=_getConfiguredModelBadge(sel.value||'',window._configuredModelBadges||{});" in js, (
|
||||
"syncModelChip() deve buscar o badge configurado do modelo selecionado e projetá-lo "
|
||||
"no chip principal da composer."
|
||||
)
|
||||
assert "badgeEl.textContent=badge&&badge.label?badge.label:'';" in js, (
|
||||
"syncModelChip() deve preencher o texto do badge visível no chip principal quando houver metadata configurada."
|
||||
assert "composerModelBadge" not in js, (
|
||||
"syncModelChip() must not reference composerModelBadge — the chip-projected "
|
||||
"badge was removed because it added too much width to the composer chip."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user