Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions internal/adminapi/adminapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,12 +418,21 @@ func TestTGAdminRecommendedModelsAppendFreeCandidatesUnrecommended(t *testing.T)
for _, model := range payload.Models {
if model.Recommended && !model.Free {
sawRecommended = true
if model.PrimaryReason != "Pareto frontier for simple" {
t.Fatalf("recommended primary reason = %q, want role-specific Pareto explanation: %+v", model.PrimaryReason, model)
}
if strings.EqualFold(model.PolicyLabel, "recommended") {
t.Fatalf("recommended policy label should be suppressed, got %+v", model)
}
}
if model.Free {
sawFreeCandidate = true
if model.Recommended || model.Policy != "free_unverified" || model.Source != "free" {
t.Fatalf("unexpected free candidate flags: %+v", model)
}
if model.StatusLabel != "Untested" || model.PolicyLabel != "Free unverified" {
t.Fatalf("unexpected free candidate display labels: %+v", model)
}
}
}
if !sawRecommended || !sawFreeCandidate {
Expand Down
60 changes: 33 additions & 27 deletions internal/adminapi/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,37 @@ type uiSlot struct {
}

type uiModel struct {
ID string
PromptPrice float64
CompletionPrice float64
ContextLength int
Vision bool
Tools bool
Reasoning bool
Free bool
Score float64 // AA Intelligence Index
CodingIndex float64 // AA Coding Index
AgenticIndex float64 // AA Agentic Index
SpeedTPS float64 // median output tokens/sec
TTFT float64 // median time-to-first-token, seconds
ThinkTime float64 // TTFA - TTFT — time spent thinking before answer starts (reasoners only)
AAPriceBlended float64 // AA's reference blended 3:1 input/output price (USD / 1M)
MarkupPct float64 // (OR blended - AA blended) / AA blended × 100 — positive means OR charges more
EffectivePrompt float64 // 0.9 × prompt + 0.1 × multimodal_slot.prompt for non-vision candidates under roles that route images elsewhere
ValuePerDollar float64 // quality / prompt price (role-specific in preset path; agent/$ in browse path)
Recommended bool
Source string
Policy string
Section string
OverrideNote string
Reasons []string
Warnings []string
Telemetry modelTelemetry
Market llm.OpenRouterMarketSignal
ID string
PromptPrice float64
CompletionPrice float64
ContextLength int
Vision bool
Tools bool
Reasoning bool
Free bool
Score float64 // AA Intelligence Index
CodingIndex float64 // AA Coding Index
AgenticIndex float64 // AA Agentic Index
SpeedTPS float64 // median output tokens/sec
TTFT float64 // median time-to-first-token, seconds
ThinkTime float64 // TTFA - TTFT — time spent thinking before answer starts (reasoners only)
AAPriceBlended float64 // AA's reference blended 3:1 input/output price (USD / 1M)
MarkupPct float64 // (OR blended - AA blended) / AA blended × 100 — positive means OR charges more
EffectivePrompt float64 // 0.9 × prompt + 0.1 × multimodal_slot.prompt for non-vision candidates under roles that route images elsewhere
ValuePerDollar float64 // quality / prompt price (role-specific in preset path; agent/$ in browse path)
Recommended bool
Source string
Policy string
Section string
OverrideNote string
Reasons []string
Warnings []string
Telemetry modelTelemetry
Market llm.OpenRouterMarketSignal
StatusLabel string
PolicyLabel string
PrimaryReason string
SecondaryReasons []string
}

type modelTelemetry struct {
Expand Down Expand Up @@ -405,6 +409,7 @@ func (s *Server) buildIndexData(r *http.Request) indexData {
models = filterModelOverrides(models, catalogProv, overrides, true)
attachMarketSignals(models, marketSignals)
attachModelTelemetry(models, s.loadModelTelemetry(ctx5, catalogProv))
decorateModelDisplay(models, preset)
sections := buildModelSections(models, true)
filters := uiFilters{
ActivePreset: preset,
Expand Down Expand Up @@ -516,6 +521,7 @@ func (s *Server) buildIndexData(r *http.Request) indexData {
models = filterModelOverrides(models, catalogProv, overrides, f.Search == "")
attachMarketSignals(models, marketSignals)
attachModelTelemetry(models, s.loadModelTelemetry(ctx5, catalogProv))
decorateModelDisplay(models, "")
asc := sortDir == "asc"
sort.Slice(models, func(i, j int) bool {
var less bool
Expand Down
127 changes: 127 additions & 0 deletions internal/adminapi/model_display.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package adminapi

import "strings"

type modelDisplay struct {
StatusLabel string
PolicyLabel string
PrimaryReason string
SecondaryReasons []string
}

func decorateModelDisplay(models []uiModel, role string) {
for i := range models {
applyModelDisplay(&models[i], role, false)
}
}

func applyModelDisplay(m *uiModel, role string, current bool) {
d := modelDisplayFor(*m, role, current)
m.StatusLabel = d.StatusLabel
m.PolicyLabel = d.PolicyLabel
m.PrimaryReason = d.PrimaryReason
m.SecondaryReasons = d.SecondaryReasons
}

func modelDisplayFor(m uiModel, role string, current bool) modelDisplay {
status := modelStatusLabel(m, current)
policy := modelPolicyLabel(m, status)
primary := modelPrimaryReason(m, role, status)
secondary := modelSecondaryReasons(m.Reasons, primary)
return modelDisplay{
StatusLabel: status,
PolicyLabel: policy,
PrimaryReason: primary,
SecondaryReasons: secondary,
}
}

func modelStatusLabel(m uiModel, current bool) string {
if current {
return "Current"
}
switch {
case m.Policy == "manual_deny" || m.Policy == "free_blocked":
return "Blocked"
case m.Section == "recommended" || m.Recommended:
return "Recommended"
case m.Section == "untested" || m.Source == "untested" || m.Policy == "free_unverified":
return "Untested"
case m.Section == "interesting" || m.Source == "near_frontier" || m.Source == "manual" || m.Policy == "manual_allow":
return "Interesting"
}
return "Candidate"
}

func modelPolicyLabel(m uiModel, status string) string {
switch m.Policy {
case "", "candidate", "recommended":
return ""
case "manual_allow":
return "Manual allow"
case "manual_deny":
return "Manual deny"
case "free_unverified":
return "Free unverified"
case "free_verified":
return "Free verified"
case "free_degraded":
return "Free degraded"
case "free_blocked":
return "Free blocked"
default:
label := strings.ReplaceAll(m.Policy, "_", " ")
return strings.ToUpper(label[:1]) + label[1:]
}
}

func modelPrimaryReason(m uiModel, role, status string) string {
switch {
case m.Recommended:
if role != "" {
return "Pareto frontier for " + role
}
return "Pareto frontier"
case m.Policy == "manual_allow":
return firstNonEmpty("Manual allow", m.OverrideNote)
case m.Policy == "manual_deny":
return firstNonEmpty("Manual deny", m.OverrideNote)
case m.Policy == "free_blocked":
return "Free endpoint is blocked"
case m.Policy == "free_degraded":
return "Free endpoint degraded"
case m.Policy == "free_verified":
return "Free endpoint verified"
case m.Policy == "free_unverified":
return "Free endpoint needs a check"
case m.Source == "near_frontier":
if role != "" {
return "Near frontier for " + role
}
return "Near recommendation frontier"
case status == "Untested":
return "Compatible but missing role evidence"
}
if len(m.Reasons) > 0 {
return m.Reasons[0]
}
return ""
}

func modelSecondaryReasons(reasons []string, primary string) []string {
out := make([]string, 0, len(reasons))
for _, reason := range uniqueStrings(reasons) {
if reason == "" || reason == primary {
continue
}
out = append(out, reason)
}
return out
}

func firstNonEmpty(fallback, value string) string {
if strings.TrimSpace(value) != "" {
return fallback + ": " + strings.TrimSpace(value)
}
return fallback
}
53 changes: 53 additions & 0 deletions internal/adminapi/recommend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,50 @@ func TestModelSectionsSplitRecommendationBuckets(t *testing.T) {
}
}

func TestModelDisplayDedupesRecommendedInternals(t *testing.T) {
m := uiModel{
ID: "deepseek/deepseek-v3.2",
Recommended: true,
Section: "recommended",
Policy: "recommended",
Reasons: []string{"tool calling", "AA coding 35", "ctx 128k"},
}

got := modelDisplayFor(m, "default", false)
if got.StatusLabel != "Recommended" {
t.Fatalf("status = %q, want Recommended", got.StatusLabel)
}
if got.PolicyLabel != "" {
t.Fatalf("policy label = %q, want empty duplicate-suppressed label", got.PolicyLabel)
}
if got.PrimaryReason != "Pareto frontier for default" {
t.Fatalf("primary reason = %q", got.PrimaryReason)
}
if containsString(got.SecondaryReasons, "recommended") || containsString(got.SecondaryReasons, "Pareto frontier for default") {
t.Fatalf("secondary reasons contain duplicated recommendation internals: %+v", got.SecondaryReasons)
}
}

func TestModelDisplayKeepsCatalogCandidateStatus(t *testing.T) {
m := uiModel{
ID: "qwen/qwen3.5-plus",
Source: "catalog",
Policy: "candidate",
Reasons: []string{"tool calling", "AA intelligence 90"},
}

got := modelDisplayFor(m, "default", false)
if got.StatusLabel != "Candidate" {
t.Fatalf("status = %q, want Candidate for plain catalog result", got.StatusLabel)
}
if got.PolicyLabel != "" {
t.Fatalf("policy label = %q, want empty candidate label", got.PolicyLabel)
}
if got.PrimaryReason != "tool calling" {
t.Fatalf("primary reason = %q, want first catalog reason", got.PrimaryReason)
}
}

func TestSectionDiversityCapsModelFamilies(t *testing.T) {
models := []uiModel{
{ID: "google/gemini-2.5-pro"},
Expand All @@ -247,6 +291,15 @@ func TestSectionDiversityCapsModelFamilies(t *testing.T) {
}
}

func containsString(values []string, want string) bool {
for _, value := range values {
if value == want {
return true
}
}
return false
}

func TestDefaultPresetUsesCodingWhenAgenticIsMissing(t *testing.T) {
caps := map[string]llm.Capabilities{
"x-ai/grok-4.3": {
Expand Down
7 changes: 4 additions & 3 deletions internal/adminapi/templates/partials_models_row.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@
<td>
<div class="model-id">{{.ID}}</div>
<div style="margin-top:4px;color:var(--text-secondary);font-size:0.78rem;">
{{if .Section}}<span class="badge badge--neutral">{{.Section}}</span>{{end}}
{{if .Policy}}<span class="badge badge--neutral">{{.Policy}}</span>{{end}}
{{range .Reasons}}<span>{{.}}</span> {{end}}
{{if .StatusLabel}}<span class="badge badge--neutral">{{.StatusLabel}}</span>{{end}}
{{if .PolicyLabel}}<span class="badge badge--neutral">{{.PolicyLabel}}</span>{{end}}
{{if .PrimaryReason}}<span>Why: {{.PrimaryReason}}</span>{{end}}
{{range .SecondaryReasons}}<span>{{.}}</span> {{end}}
</div>
{{if .Warnings}}
<div style="margin-top:4px;color:var(--warning, #9a5b00);font-size:0.78rem;">
Expand Down
19 changes: 12 additions & 7 deletions internal/adminapi/templates/tg_admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -586,17 +586,17 @@ <h2 id="sheet-title">Choose model</h2>
var chips = document.createElement("div");
chips.className = "chips";
[
model.current ? "current" : "",
model.recommended ? "recommended" : "",
model.section || "",
model.policy || "",
model.status_label || (model.current ? "Current" : ""),
model.policy_label || "",
model.tools ? "tools" : "",
model.vision ? "vision" : "",
model.reasoning ? "reasoning" : "",
model.market && model.market.rank ? "market #" + model.market.rank : "",
model.market && !model.market.rank && (model.market.share || model.market.score) ? "market" : "",
model.agentic_index ? "agentic " + Math.round(model.agentic_index) : ""
].filter(Boolean).forEach(function (v) {
].filter(Boolean).filter(function (v, i, arr) {
return arr.indexOf(v) === i;
}).forEach(function (v) {
var chip = document.createElement("span");
chip.className = "chip";
chip.textContent = v;
Expand All @@ -605,10 +605,15 @@ <h2 id="sheet-title">Choose model</h2>
body.appendChild(id);
body.appendChild(meta);
body.appendChild(chips);
if (model.reasons && model.reasons.length) {
if (model.primary_reason || (model.secondary_reasons && model.secondary_reasons.length)) {
var reasons = document.createElement("div");
reasons.className = "model-card__note";
reasons.textContent = model.reasons.slice(0, 4).join(" · ");
var why = [];
if (model.primary_reason) why.push("Why: " + model.primary_reason);
(model.secondary_reasons || []).slice(0, 3).forEach(function (reason) {
why.push(reason);
});
reasons.textContent = why.join(" · ");
body.appendChild(reasons);
}
if (model.warnings && model.warnings.length) {
Expand Down
Loading
Loading