diff --git a/docs/reports/model-provider-logos.md b/docs/reports/model-provider-logos.md new file mode 100644 index 0000000..013879b --- /dev/null +++ b/docs/reports/model-provider-logos.md @@ -0,0 +1,11 @@ +# Логотипы моделей и провайдеров + +2026-08-31, предварительная часть перед A50. База `17b368a`. + +Причина отсутствия изображений: getProviderIcon ссылался на codex.png, claude.png и другие файлы, отсутствовавшие в web/static; реальные provider ids antigravity/claude также не совпадали с алиасами в таблице. Неизвестным провайдерам ошибочно назначалась llama.png. + +Существующие OpenAI/Antigravity/OpenCode PNG скопированы без изменения из assets/providers. Набор дополнен локальными SVG Gemini, Claude, Grok, Ollama, NVIDIA, OpenRouter, DeepSeek, Qwen, Meta, Mistral, vLLM, LM Studio из [Lobe Icons](https://github.com/lobehub/lobe-icons). Лицензия MIT и точная ревизия сохранены рядом с файлами; загрузки с CDN при работе Hub нет. + +Провайдер определяется по provider id, логотип модели — по её семейству. Название модели не изменяется; неизвестная модель получает нейтральный символ. Нет выдачи Ollama за бренд любой размещённой на нём модели. Добавлены значки назначенных моделей в карточки аккаунтов, рядом с выбором модели в маршрутизации и вместо общего символа на узлах графа. Единственное изменение workflow.js — отображение изображения; топология и настройки не затронуты. + +Проверки: test_brand_icons.js проверяет алиасы, семейства, fallback и существование файлов; прежние два JS-набора проходят. Браузер подтвердил complete/naturalWidth для 16/16 изображений. Снимок: docs/screenshots/a50/brand-catalog.png. Это каталог, не доказательство сквозной работы A50 или авторизованных аккаунтов. Новый клиент находится в отдельном worktree A50; установленный Hub и preview A48 не заменены. diff --git a/docs/screenshots/a50/brand-catalog.png b/docs/screenshots/a50/brand-catalog.png new file mode 100644 index 0000000..7397cb0 Binary files /dev/null and b/docs/screenshots/a50/brand-catalog.png differ diff --git a/src/antigravity_provider/router/web/static/app.js b/src/antigravity_provider/router/web/static/app.js index ae65cdf..12f1f8b 100644 --- a/src/antigravity_provider/router/web/static/app.js +++ b/src/antigravity_provider/router/web/static/app.js @@ -787,6 +787,7 @@ function renderAccountCard(profile) { +
${(profile.preferred_models || []).map(modelBrandLabel).join('')}
${quotaGridHtml} `; @@ -991,14 +992,32 @@ function renderOverviewView() { function getProviderIcon(provider) { const map = { - 'openai-codex': 'codex.png', - 'google-antigravity': 'антигравити.png', - 'opencode-go': 'opencode.png', - 'anthropic-claude': 'claude.png', - 'deepseek': 'deepseek.png', - 'grok': 'grok.jfif' + 'openai-codex':'openai.png', openai:'openai.png', codex:'openai.png', + antigravity:'antigravity.png', 'google-antigravity':'antigravity.png', + 'opencode-go':'opencode.png', opencode:'opencode.png', + claude:'claude-color.svg', 'anthropic-claude':'claude-color.svg', anthropic:'claude-color.svg', + grok:'grok.svg', xai:'grok.svg', ollama:'ollama.svg', + nvidia:'nvidia-color.svg', openrouter:'openrouter.svg', + deepseek:'deepseek-color.svg', vllm:'vllm-color.svg', lmstudio:'lmstudio.svg', + local:'unknown.svg', 'llama.cpp':'unknown.svg', }; - return map[provider] || 'llama.png'; + return 'brands/'+(map[String(provider || '').toLowerCase()] || 'unknown.svg'); +} + +function getModelIcon(model) { + const name=String(model || '').toLowerCase().split('/').at(-1); + const family=[ + [/^gemini(?:[-.:]|$)/,'gemini-color.svg'], + [/^claude(?:[-.:]|$)/,'claude-color.svg'],[/^(?:gpt|chatgpt|codex|o[134])(?:[-.:0-9]|$)/,'openai.png'], + [/^grok(?:[-.:]|$)/,'grok.svg'],[/^deepseek(?:[-.:]|$)/,'deepseek-color.svg'], + [/^qwen(?:[-.:0-9]|$)/,'qwen-color.svg'],[/^(?:llama|meta-llama)(?:[-.:0-9]|$)/,'meta-color.svg'], + [/^(?:mistral|mixtral|codestral|devstral|magistral)(?:[-.:0-9]|$)/,'mistral-color.svg'], + ].find(([pattern])=>pattern.test(name)); + return 'brands/'+(family?.[1] || 'unknown.svg'); +} + +function modelBrandLabel(model) { + return `${escapeHtml(model || 'Модель: Н/Д')}`; } function renderRoutingView() { renderAccountRouting(); } diff --git a/src/antigravity_provider/router/web/static/brands/LICENSE-lobe-icons.txt b/src/antigravity_provider/router/web/static/brands/LICENSE-lobe-icons.txt new file mode 100644 index 0000000..1dd53d2 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/LICENSE-lobe-icons.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 LobeHub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/antigravity_provider/router/web/static/brands/README.md b/src/antigravity_provider/router/web/static/brands/README.md new file mode 100644 index 0000000..b1c0b3d --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/README.md @@ -0,0 +1,3 @@ +# Brand assets + +Existing PNGs copied unchanged from assets/providers. Other SVG marks: https://github.com/lobehub/lobe-icons (MIT), revision 4aaf4ee1fb2678a7f989ea570f0f6ce14a9abf75; license bundled. Served locally without CDN. SVG geometry unchanged. Trademarks belong to their owners. diff --git a/src/antigravity_provider/router/web/static/brands/antigravity.png b/src/antigravity_provider/router/web/static/brands/antigravity.png new file mode 100644 index 0000000..d53a1b8 Binary files /dev/null and b/src/antigravity_provider/router/web/static/brands/antigravity.png differ diff --git a/src/antigravity_provider/router/web/static/brands/catalog.html b/src/antigravity_provider/router/web/static/brands/catalog.html new file mode 100644 index 0000000..b29d0f3 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/catalog.html @@ -0,0 +1 @@ +Hermes Hub — логотипы моделей и провайдеров

Логотипы моделей и провайдеров

Локальные файлы. Первые три — существующий набор; остальные — масштабируемые SVG. Последний значок используется только для неизвестной модели.

openai.pngopenai.png
antigravity.pngantigravity.png
opencode.pngopencode.png
gemini-color.svggemini-color.svg
claude-color.svgclaude-color.svg
grok.svggrok.svg
ollama.svgollama.svg
nvidia-color.svgnvidia-color.svg
openrouter.svgopenrouter.svg
deepseek-color.svgdeepseek-color.svg
qwen-color.svgqwen-color.svg
meta-color.svgmeta-color.svg
mistral-color.svgmistral-color.svg
vllm-color.svgvllm-color.svg
lmstudio.svglmstudio.svg
unknown.svgunknown.svg
\ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/claude-color.svg b/src/antigravity_provider/router/web/static/brands/claude-color.svg new file mode 100644 index 0000000..62dc0db --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/claude-color.svg @@ -0,0 +1 @@ +Claude \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/deepseek-color.svg b/src/antigravity_provider/router/web/static/brands/deepseek-color.svg new file mode 100644 index 0000000..3fc2302 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/deepseek-color.svg @@ -0,0 +1 @@ +DeepSeek \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/gemini-color.svg b/src/antigravity_provider/router/web/static/brands/gemini-color.svg new file mode 100644 index 0000000..62681df --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/gemini-color.svg @@ -0,0 +1 @@ +Gemini \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/grok.svg b/src/antigravity_provider/router/web/static/brands/grok.svg new file mode 100644 index 0000000..efb1a61 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/grok.svg @@ -0,0 +1 @@ +Grok \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/lmstudio.svg b/src/antigravity_provider/router/web/static/brands/lmstudio.svg new file mode 100644 index 0000000..ea0816b --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/lmstudio.svg @@ -0,0 +1 @@ +LM Studio \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/meta-color.svg b/src/antigravity_provider/router/web/static/brands/meta-color.svg new file mode 100644 index 0000000..a8a8d94 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/meta-color.svg @@ -0,0 +1 @@ +Meta \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/mistral-color.svg b/src/antigravity_provider/router/web/static/brands/mistral-color.svg new file mode 100644 index 0000000..8e03e24 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/mistral-color.svg @@ -0,0 +1 @@ +Mistral \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/nvidia-color.svg b/src/antigravity_provider/router/web/static/brands/nvidia-color.svg new file mode 100644 index 0000000..a9683c2 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/nvidia-color.svg @@ -0,0 +1 @@ +Nvidia \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/ollama.svg b/src/antigravity_provider/router/web/static/brands/ollama.svg new file mode 100644 index 0000000..cc887e3 --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/ollama.svg @@ -0,0 +1 @@ +Ollama \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/openai.png b/src/antigravity_provider/router/web/static/brands/openai.png new file mode 100644 index 0000000..7bfb26c Binary files /dev/null and b/src/antigravity_provider/router/web/static/brands/openai.png differ diff --git a/src/antigravity_provider/router/web/static/brands/opencode.png b/src/antigravity_provider/router/web/static/brands/opencode.png new file mode 100644 index 0000000..145cae7 Binary files /dev/null and b/src/antigravity_provider/router/web/static/brands/opencode.png differ diff --git a/src/antigravity_provider/router/web/static/brands/openrouter.svg b/src/antigravity_provider/router/web/static/brands/openrouter.svg new file mode 100644 index 0000000..029070c --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/openrouter.svg @@ -0,0 +1 @@ +OpenRouter \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/qwen-color.svg b/src/antigravity_provider/router/web/static/brands/qwen-color.svg new file mode 100644 index 0000000..f2d0ada --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/qwen-color.svg @@ -0,0 +1 @@ +Qwen \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/unknown.svg b/src/antigravity_provider/router/web/static/brands/unknown.svg new file mode 100644 index 0000000..38f80ad --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/unknown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/brands/vllm-color.svg b/src/antigravity_provider/router/web/static/brands/vllm-color.svg new file mode 100644 index 0000000..54acc3d --- /dev/null +++ b/src/antigravity_provider/router/web/static/brands/vllm-color.svg @@ -0,0 +1 @@ +vLLM \ No newline at end of file diff --git a/src/antigravity_provider/router/web/static/style.css b/src/antigravity_provider/router/web/static/style.css index b677047..0f593bb 100644 --- a/src/antigravity_provider/router/web/static/style.css +++ b/src/antigravity_provider/router/web/static/style.css @@ -1915,3 +1915,12 @@ body[data-theme="medium"] .nav-item.active .nav-icon { stroke:var(--text-accent) .status-dot.not-ready { background:var(--status-error); } .status-dot.ready { background:var(--status-healthy); } .resource-value { font-size:clamp(18px,1.7vw,26px); } +/* Brand marks are local assets, separate for a provider and the model it hosts. */ +.brand-logo,.account-provider-tag img,.available-account-card>img { width:24px; height:24px; object-fit:contain; padding:3px; border-radius:4px; background:#fff; flex:none; } +.model-brand { display:inline-flex; align-items:center; gap:6px; min-width:0; } +.model-brand>span { overflow-wrap:anywhere; } +.account-models { display:flex; gap:8px; flex-wrap:wrap; margin:10px 0; font-size:11px; } +.account-models:empty { display:none; } +.route-model-with-logo { display:flex; align-items:center; gap:6px; min-width:0; } +.route-model-with-logo select { min-width:0; } +.agent-node-icon .brand-logo { width:27px; height:27px; } diff --git a/src/antigravity_provider/router/web/static/workflow.js b/src/antigravity_provider/router/web/static/workflow.js index 9495d01..fdb750d 100644 --- a/src/antigravity_provider/router/web/static/workflow.js +++ b/src/antigravity_provider/router/web/static/workflow.js @@ -212,7 +212,7 @@ function renderWorkflowNodes(workflow) { const assignment = `${cfg.model || 'Модель: Н/Д'} • ${cfg.account || 'Аккаунт: Н/Д'}`; return `
-

${wfEscape(agent.name)}

${wfEscape(agent.agent_file?.split('/').pop() || 'Agent File: Н/Д')}

${wfEscape(assignment)}

+

${wfEscape(agent.name)}

${wfEscape(agent.agent_file?.split('/').pop() || 'Agent File: Н/Д')}

${wfEscape(assignment)}

${wfEscape(runtimeStateLabel(agent.runtime_state))}
`; }).join(''); diff --git a/src/antigravity_provider/router/web/static/workspace.js b/src/antigravity_provider/router/web/static/workspace.js index edb881e..0e8514e 100644 --- a/src/antigravity_provider/router/web/static/workspace.js +++ b/src/antigravity_provider/router/web/static/workspace.js @@ -135,7 +135,7 @@ function renderAccountRouting() { const profile = profiles[node.profile_id] || {profile_id:node.profile_id,provider:node.provider}; const quota = profile.quota_snapshot || snapshot.quotas?.[node.profile_id] || {}; const reset = (quota.buckets || []).map(b => b.reset_time_formatted || b.reset_after_formatted).filter(Boolean).join('; '); - return `
${index ? 'Резерв '+index : 'Основной'}
${escapeHtml(profile.display_name || node.profile_id)}
${escapeHtml(node.account_identity || profile.account_identity || node.provider)}
${routeQuota(profile,snapshot)}
${escapeHtml(reset || 'Н/Д: нет времени сброса')}
${escapeHtml(node.status_label_ru || 'Не проверялся')}${node.is_active ? '
Используется' : ''}
`; + return `
${index ? 'Резерв '+index : 'Основной'}
${escapeHtml(profile.display_name || node.profile_id)}
${escapeHtml(node.account_identity || profile.account_identity || node.provider)}
${routeQuota(profile,snapshot)}
${escapeHtml(reset || 'Н/Д: нет времени сброса')}
${escapeHtml(node.status_label_ru || 'Не проверялся')}${node.is_active ? '
Используется' : ''}
`; }).join(''); return `

${escapeHtml(agent?.name || pipeline.role_name_ru || roleId)}

${escapeHtml(agent?.description || role?.role_description_ru || '')}

ПриоритетАккаунтМодельКвота · остатокСбросСтатус
${rows || '

Аккаунты не назначены. Добавьте подключённый аккаунт.

'}
+ Перетащите аккаунт в конец маршрута

Session Affinity: ${pipeline.session_affinity ? 'включена' : 'отключена'} · Порядок применяется к следующим назначениям

`; }).join('') || '

Агенты ещё не созданы. Добавьте агента на «Обзоре».

'; diff --git a/tests/test_brand_icons.js b/tests/test_brand_icons.js new file mode 100644 index 0000000..c74bb62 --- /dev/null +++ b/tests/test_brand_icons.js @@ -0,0 +1,13 @@ +const assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm'); +const root='src/antigravity_provider/router/web/static/'; +const source=fs.readFileSync(root+'app.js','utf8'); +const context=vm.createContext({}); +vm.runInContext(source.slice(source.indexOf('function getProviderIcon('),source.indexOf('function modelBrandLabel(')),context); +for(const provider of ['openai-codex','antigravity','google-antigravity','opencode-go','claude','anthropic-claude','grok','ollama','local','vllm','openrouter','nvidia','unknown'])assert(fs.existsSync(root+context.getProviderIcon(provider)),provider); +for(const model of ['gemini-3.7-flash','claude-opus-4-6','gpt-oss:20b','qwen3:32b','org/deepseek-r1','meta-llama/llama-3.3','grok-4','mistral-small','unknown',''])assert(fs.existsSync(root+context.getModelIcon(model)),model); +assert(context.getModelIcon('qwen3:32b').includes('qwen')); +assert(context.getModelIcon('gemini-3.7-flash').includes('gemini')); +assert(!context.getModelIcon('gemini-3.7-flash').includes('ollama')); +assert(context.getModelIcon('custom-model').includes('unknown')); +assert(context.getProviderIcon('custom-provider').includes('unknown')); +console.log('Brand assets: provider aliases, model families, unknown fallback and all referenced files verified');