diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f50607..2eb06e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## [Unreleased] +## [v0.51.438] — 2026-06-15 — Release OY (model cost/health comparison table on Insights) + +### Added + +- **The Insights panel has an opt-in "Model health comparison" reference table.** A collapsible (default-collapsed) table listing common models with their provider, input/output cost per 1M tokens, and a plain-English replacement/when-to-use guide — a quick at-a-glance cost reference alongside the usage analytics. Every cell is HTML-escaped; the data is static (no new API calls), and the four new i18n keys are present across all locales. (#691) + ## [v0.51.437] — 2026-06-15 — Release OX (Escape blurs the composer for keyboard nav) ### Fixed diff --git a/static/i18n.js b/static/i18n.js index 5903f4af..07e8bf62 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -858,6 +858,10 @@ const LOCALES = { insights_model_tokens: 'Tokens', insights_model_cost: 'Cost', insights_model_share: 'Share', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: 'No usage data yet', insights_footer: 'Showing data from the last {days} days', insights_skill_usage_title: 'Skill Usage', @@ -2275,6 +2279,10 @@ const LOCALES = { insights_model_tokens: 'Token', insights_model_cost: 'Costo', insights_model_share: 'Quota', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: 'Nessun dato di utilizzo', insights_footer: 'Dati mostrati dagli ultimi {days} giorni', insights_skill_usage_title: 'Utilizzo Skill', @@ -3693,6 +3701,10 @@ const LOCALES = { insights_model_tokens: 'トークン', insights_model_cost: 'コスト', insights_model_share: 'シェア', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: '使用データはまだありません', insights_footer: '直近 {days} 日間のデータを表示', insights_skill_usage_title: 'スキル使用状況', @@ -5579,6 +5591,10 @@ const LOCALES = { insights_model_tokens: 'Tokens', insights_model_cost: 'Cost', insights_model_share: 'Share', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: 'No usage data yet', insights_footer: 'Showing data from the last {days} days', // TODO: translate insights_skill_usage_title: 'Skill Usage', // TODO: translate @@ -6916,6 +6932,10 @@ const LOCALES = { insights_model_tokens: 'Tokens', insights_model_cost: 'Cost', insights_model_share: 'Share', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: 'No usage data yet', insights_footer: 'Showing data from the last {days} days', // TODO: translate insights_skill_usage_title: 'Skill Usage', // TODO: translate @@ -8269,6 +8289,10 @@ const LOCALES = { insights_model_tokens: 'Tokens', insights_model_cost: 'Cost', insights_model_share: 'Share', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: 'No usage data yet', insights_footer: 'Showing data from the last {days} days', // TODO: translate insights_skill_usage_title: 'Skill Usage', // TODO: translate @@ -9615,6 +9639,10 @@ const LOCALES = { insights_model_tokens: '令牌', insights_model_cost: '费用', insights_model_share: '占比', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: '暂无使用数据', insights_footer: '显示最近 {days} 天的数据', insights_skill_usage_title: '技能使用统计', @@ -10507,6 +10535,10 @@ const LOCALES = { insights_model_tokens: 'Token', insights_model_cost: '成本', insights_model_share: '占比', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: '尚無用量資料', insights_footer: '顯示最近 {days} 天的資料', insights_skill_usage_title: '技能使用情況', @@ -12281,6 +12313,10 @@ const LOCALES = { insights_token_breakdown: 'Token Breakdown', // TODO: translate insights_tokens: 'Tokens', // TODO: translate insights_total: 'Total', // TODO: translate + insights_model_health_title: 'Model health comparison', // TODO: translate + insights_model_health_provider: 'Provider', // TODO: translate + insights_model_health_replacement: 'Replacement guide', // TODO: translate + insights_model_health_cost_per_m: 'Cost / 1M', // TODO: translate settings_desc_api_redact: 'Self-hosted users can disable for transparency (not recommended for shared instances).', // TODO: translate settings_label_api_redact: 'Redact sensitive data in API responses', // TODO: translate voice_error: 'Voice not supported in this browser', // TODO: translate @@ -13662,6 +13698,10 @@ const LOCALES = { insights_model_tokens: '토큰', insights_model_cost: '비용', insights_model_share: '비율', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: '아직 사용 데이터가 없습니다', insights_footer: 'Showing data from the last {days} days', // TODO: translate insights_skill_usage_title: 'Skill Usage', // TODO: translate @@ -14423,6 +14463,10 @@ const LOCALES = { insights_model_tokens: 'Jetons', insights_model_cost: 'Coût', insights_model_share: 'Partager', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: 'Aucune donnée d\'utilisation pour l\'instant', insights_footer: 'Affichage des données des {days} derniers jours', insights_skill_usage_title: 'Utilisation des Skills', @@ -16423,6 +16467,10 @@ const LOCALES = { insights_model_tokens: 'Jetonlar', insights_model_cost: 'Maliyet', insights_model_share: 'Paylaşmak', + insights_model_health_title: 'Model health comparison', + insights_model_health_provider: 'Provider', + insights_model_health_replacement: 'Replacement guide', + insights_model_health_cost_per_m: 'Cost / 1M', insights_no_usage_data: 'Henüz kullanım verisi yok', insights_footer: 'Son {days} günün verileri gösteriliyor', insights_skill_usage_title: 'Skill Usage', // TODO: translate @@ -16754,6 +16802,10 @@ const LOCALES = { fork_from_here: 'Rozgałęź stąd', forked_from: 'Rozgałęzione z', subagent_children: 'Sesje subagentów', + insights_model_health_title: 'Porównanie kondycji modeli', + insights_model_health_provider: 'Dostawca', + insights_model_health_replacement: 'Przewodnik zastąpienia', + insights_model_health_cost_per_m: 'Koszt / 1 mln', btw_asking: 'Zadawanie pytania pobocznego…', btw_label: 'Pytanie poboczne — poza historią', btw_done: 'Odpowiedziano na pytanie poboczne', diff --git a/static/panels.js b/static/panels.js index 210fb593..d4d6010f 100644 --- a/static/panels.js +++ b/static/panels.js @@ -3441,6 +3441,30 @@ async function copyLogsAll() { } // ── Insights panel ── +const STATIC_MODEL_HEALTH_ROWS = [ + {id:'openai/gpt-5.4-mini', provider:'OpenAI', inputCostPerM:0.25, outputCostPerM:2.00, replacement:'Default economical general-purpose model'}, + {id:'openai/gpt-5.4', provider:'OpenAI', inputCostPerM:2.00, outputCostPerM:10.00, replacement:'Use for complex synthesis; fall back to Mini for routine turns'}, + {id:'anthropic/claude-sonnet-4.5', provider:'Anthropic', inputCostPerM:3.00, outputCostPerM:15.00, replacement:'Strong coding and analysis option; use Mini for low-risk chat'}, + {id:'google/gemini-2.5-pro', provider:'Google', inputCostPerM:1.25, outputCostPerM:10.00, replacement:'Long-context research option; use Flash for speed-sensitive work'}, + {id:'google/gemini-2.5-flash', provider:'Google', inputCostPerM:0.30, outputCostPerM:2.50, replacement:'Low-latency replacement for lighter multimodal or research turns'}, +]; + +function _renderModelHealthCost(row) { + const input = Number(row.inputCostPerM || 0); + const output = Number(row.outputCostPerM || 0); + return `$${input.toFixed(2)} / $${output.toFixed(2)}`; +} + +function _renderStaticModelHealthTable() { + const rows = STATIC_MODEL_HEALTH_ROWS.map(row => `
: {" entries) and
+ # assert EVERY locale carries all four keys — a `count >= N` check silently
+ # tolerates a locale missing the keys (English fallback). #3634 pt gap.
+ import re
+
+ locale_starts = [
+ (m.group(1), m.start())
+ for m in re.finditer(r"""^ ['"]?([A-Za-z_-]+)['"]?: \{$""", I18N_JS, re.MULTILINE)
+ ]
+ assert len(locale_starts) >= 13, f"expected 13+ locale blocks, found {len(locale_starts)}"
+ bounds = locale_starts + [("__end__", len(I18N_JS))]
+ for i, (code, start) in enumerate(locale_starts):
+ block = I18N_JS[start : bounds[i + 1][1]]
+ for key in keys:
+ assert f"{key}:" in block, f"locale '{code}' is missing {key}"
+
+
+def test_model_health_table_css_is_responsive_and_contained():
+ assert ".insights-model-health-table" in STYLE_CSS
+ assert ".insights-model-health-table .insights-table-head" in STYLE_CSS
+ assert ".insights-model-health-card>summary" in STYLE_CSS
+ assert "minmax(130px,1.5fr)" in STYLE_CSS
+ assert "overflow-x:auto" in STYLE_CSS
+ assert ".insights-model-health-replacement" in STYLE_CSS