Restore local provider logos and add model-family brand marks

This commit is contained in:
ochenstarik-ui 2026-08-31 18:14:34 +07:00
parent 17b368a155
commit 1fbbeacd13
26 changed files with 99 additions and 9 deletions

View file

@ -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 не заменены.

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View file

@ -787,6 +787,7 @@ function renderAccountCard(profile) {
</div>
</div>
<div class="account-models">${(profile.preferred_models || []).map(modelBrandLabel).join('')}</div>
${quotaGridHtml}
</div>
`;
@ -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 `<span class="model-brand"><img class="brand-logo" src="/static/${getModelIcon(model)}" alt=""><span>${escapeHtml(model || 'Модель: Н/Д')}</span></span>`;
}
function renderRoutingView() { renderAccountRouting(); }

View file

@ -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.

View file

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1 @@
<!doctype html><html lang="ru"><meta charset="utf-8"><title>Hermes Hub — логотипы моделей и провайдеров</title><style>body{background:#07120f;color:#e7dcc1;font:14px system-ui;margin:32px}h1{font-size:22px}section{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}article{border:1px solid #51452c;padding:20px;display:flex;align-items:center;gap:16px}img{width:48px;height:48px;padding:6px;background:white;border-radius:6px;object-fit:contain}</style><h1>Логотипы моделей и провайдеров</h1><p>Локальные файлы. Первые три — существующий набор; остальные — масштабируемые SVG. Последний значок используется только для неизвестной модели.</p><section><article><img src="openai.png" alt="openai.png"><span>openai.png</span></article><article><img src="antigravity.png" alt="antigravity.png"><span>antigravity.png</span></article><article><img src="opencode.png" alt="opencode.png"><span>opencode.png</span></article><article><img src="gemini-color.svg" alt="gemini-color.svg"><span>gemini-color.svg</span></article><article><img src="claude-color.svg" alt="claude-color.svg"><span>claude-color.svg</span></article><article><img src="grok.svg" alt="grok.svg"><span>grok.svg</span></article><article><img src="ollama.svg" alt="ollama.svg"><span>ollama.svg</span></article><article><img src="nvidia-color.svg" alt="nvidia-color.svg"><span>nvidia-color.svg</span></article><article><img src="openrouter.svg" alt="openrouter.svg"><span>openrouter.svg</span></article><article><img src="deepseek-color.svg" alt="deepseek-color.svg"><span>deepseek-color.svg</span></article><article><img src="qwen-color.svg" alt="qwen-color.svg"><span>qwen-color.svg</span></article><article><img src="meta-color.svg" alt="meta-color.svg"><span>meta-color.svg</span></article><article><img src="mistral-color.svg" alt="mistral-color.svg"><span>mistral-color.svg</span></article><article><img src="vllm-color.svg" alt="vllm-color.svg"><span>vllm-color.svg</span></article><article><img src="lmstudio.svg" alt="lmstudio.svg"><span>lmstudio.svg</span></article><article><img src="unknown.svg" alt="unknown.svg"><span>unknown.svg</span></article></section></html>

View file

@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-0-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-1-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-2-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-0-_R_0_" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-1-_R_0_" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-2-_R_0_" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>

After

Width:  |  Height:  |  Size: 756 B

View file

@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>LM Studio</title><path d="M2.84 2a1.273 1.273 0 100 2.547h14.107a1.273 1.273 0 100-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H22.04a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h14.106a1.274 1.274 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H15.38a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h14.106a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h9.698a1.273 1.273 0 100-2.547h-9.698z" fill-opacity=".3"></path><path d="M2.84 2a1.273 1.273 0 100 2.547h10.287a1.274 1.274 0 000-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H18.22a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H11.56a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h5.78a1.273 1.273 0 100-2.547h-5.78z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Mistral</title><path d="M3.428 3.4h3.429v3.428H3.428V3.4zm13.714 0h3.43v3.428h-3.43V3.4z" fill="gold"></path><path d="M3.428 6.828h6.857v3.429H3.429V6.828zm10.286 0h6.857v3.429h-6.857V6.828z" fill="#FFAF00"></path><path d="M3.428 10.258h17.144v3.428H3.428v-3.428z" fill="#FF8205"></path><path d="M3.428 13.686h3.429v3.428H3.428v-3.428zm6.858 0h3.429v3.428h-3.429v-3.428zm6.856 0h3.43v3.428h-3.43v-3.428z" fill="#FA500F"></path><path d="M0 17.114h10.286v3.429H0v-3.429zm13.714 0H24v3.429H13.714v-3.429z" fill="#E10500"></path></svg>

After

Width:  |  Height:  |  Size: 655 B

View file

@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Nvidia</title><path d="M10.212 8.976V7.62c.127-.01.256-.017.388-.021 3.596-.117 5.957 3.184 5.957 3.184s-2.548 3.647-5.282 3.647a3.227 3.227 0 01-1.063-.175v-4.109c1.4.174 1.681.812 2.523 2.258l1.873-1.627a4.905 4.905 0 00-3.67-1.846 6.594 6.594 0 00-.729.044m0-4.476v2.025c.13-.01.259-.019.388-.024 5.002-.174 8.261 4.226 8.261 4.226s-3.743 4.69-7.643 4.69c-.338 0-.675-.031-1.007-.092v1.25c.278.038.558.057.838.057 3.629 0 6.253-1.91 8.794-4.169.421.347 2.146 1.193 2.501 1.564-2.416 2.083-8.048 3.763-11.24 3.763-.308 0-.603-.02-.894-.048V19.5H24v-15H10.21zm0 9.756v1.068c-3.356-.616-4.287-4.21-4.287-4.21a7.173 7.173 0 014.287-2.138v1.172h-.005a3.182 3.182 0 00-2.502 1.178s.615 2.276 2.507 2.931m-5.961-3.3c1.436-1.935 3.604-3.148 5.961-3.336V6.523C5.81 6.887 2 10.723 2 10.723s2.158 6.427 8.21 7.015v-1.166C5.77 16 4.25 10.958 4.25 10.958h-.002z" fill="#74B71B" fill-rule="nonzero"></path></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Ollama</title><path d="M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042zm4.116 9.09c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643zm.662 1.21l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051zm-5.04-1.919c.478 0 .867.39.867.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zm8.706 0c.48 0 .868.39.868.871a.87.87 0 01-.868.871.87.87 0 01-.867-.87.87.87 0 01.867-.872zM7.44 2.3l-.003.002a.659.659 0 00-.285.238l-.005.006c-.138.189-.258.467-.348.832-.17.692-.216 1.631-.124 2.782.43-.128.899-.208 1.404-.237l.01-.001.019-.034c.046-.082.095-.161.148-.239.123-.771.022-1.692-.253-2.444-.134-.364-.297-.65-.453-.813a.628.628 0 00-.107-.09L7.44 2.3zm9.174.04l-.002.001a.628.628 0 00-.107.09c-.156.163-.32.45-.453.814-.29.794-.387 1.776-.23 2.572l.058.097.008.014h.03a5.184 5.184 0 011.466.212c.086-1.124.038-2.043-.128-2.722-.09-.365-.21-.643-.349-.832l-.004-.006a.659.659 0 00-.285-.239h-.004z"></path></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M18.654 3.87a5.087 5.087 0 110 10.174L23.7 19.09c.64.641.187 1.737-.72 1.737H8.48a8.479 8.479 0 010-16.958h10.175zM8.479 7.26a5.087 5.087 0 100 10.176 5.087 5.087 0 000-10.175z"></path></svg>

After

Width:  |  Height:  |  Size: 382 B

View file

@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#lobe-icons-qwen-_R_0_)" fill-rule="nonzero"></path><defs><linearGradient id="lobe-icons-qwen-_R_0_" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"></stop><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"></stop></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="#746b54" stroke-width="1.5" d="M8 3h8v4h5v10h-5v4H8v-4H3V7h5z"/><circle cx="12" cy="12" r="3" fill="none" stroke="#746b54" stroke-width="1.5"/></svg>

After

Width:  |  Height:  |  Size: 235 B

View file

@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>vLLM</title><path d="M0 4.973h9.324V23L0 4.973z" fill="#FDB515"></path><path d="M13.986 4.351L22.378 0l-6.216 23H9.324l4.662-18.649z" fill="#30A2FF"></path></svg>

After

Width:  |  Height:  |  Size: 286 B

View file

@ -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; }

View file

@ -212,7 +212,7 @@ function renderWorkflowNodes(workflow) {
const assignment = `${cfg.model || 'Модель: Н/Д'}${cfg.account || 'Аккаунт: Н/Д'}`;
return `<article class="workflow-node ${wfEscape(agent.runtime_state)} ${workflowUi.selectedAgentId === agent.id ? 'selected' : ''}" data-agent-id="${wfEscape(agent.id)}" style="left:${Number(pos.x) || 0}px;top:${Number(pos.y) || 0}px">
<button class="workflow-port in" aria-label="Вход"></button><button class="workflow-port out" aria-label="Создать связь"></button>
<span class="agent-node-icon" aria-hidden="true"></span><h3>${wfEscape(agent.name)}</h3><p title="${wfEscape(agent.agent_file)}">${wfEscape(agent.agent_file?.split('/').pop() || 'Agent File: Н/Д')}</p><p class="node-assignment" title="${wfEscape(cfg.unavailable_reason || assignment)}">${wfEscape(assignment)}</p>
<span class="agent-node-icon" aria-hidden="true"><img class="brand-logo" src="/static/${getModelIcon(cfg.model)}" alt=""></span><h3>${wfEscape(agent.name)}</h3><p title="${wfEscape(agent.agent_file)}">${wfEscape(agent.agent_file?.split('/').pop() || 'Agent File: Н/Д')}</p><p class="node-assignment" title="${wfEscape(cfg.unavailable_reason || assignment)}">${wfEscape(assignment)}</p>
<div class="workflow-node-status"><i></i><span>${wfEscape(runtimeStateLabel(agent.runtime_state))}</span></div>
</article>`;
}).join('');

View file

@ -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 `<div class="account-row draggable-item" draggable="true" data-pid="${escapeHtml(node.profile_id)}" data-role="${escapeHtml(roleId)}"><div class="drag-handle">⠿</div><div class="route-priority">${index ? 'Резерв '+index : 'Основной'}</div><div><strong>${escapeHtml(profile.display_name || node.profile_id)}</strong><div class="text-muted">${escapeHtml(node.account_identity || profile.account_identity || node.provider)}</div></div><div><select data-route-model="${escapeHtml(node.profile_id)}" data-role="${escapeHtml(roleId)}" aria-label="Модель ${escapeHtml(node.profile_id)}">${modelOptions(snapshot,profile,node.model)}</select></div><div>${routeQuota(profile,snapshot)}</div><div class="text-muted">${escapeHtml(reset || 'Н/Д: нет времени сброса')}</div><div><span class="status-dot ${healthDotClass(node.status)}"></span> ${escapeHtml(node.status_label_ru || 'Не проверялся')}${node.is_active ? '<br>Используется' : ''}</div><button class="route-remove" data-remove-profile="${escapeHtml(node.profile_id)}" data-role="${escapeHtml(roleId)}" aria-label="Удалить из маршрута">×</button></div>`;
return `<div class="account-row draggable-item" draggable="true" data-pid="${escapeHtml(node.profile_id)}" data-role="${escapeHtml(roleId)}"><div class="drag-handle">⠿</div><div class="route-priority">${index ? 'Резерв '+index : 'Основной'}</div><div><strong>${escapeHtml(profile.display_name || node.profile_id)}</strong><div class="text-muted">${escapeHtml(node.account_identity || profile.account_identity || node.provider)}</div></div><div class="route-model-with-logo"><img class="brand-logo" src="/static/${getModelIcon(node.model)}" alt=""><select data-route-model="${escapeHtml(node.profile_id)}" data-role="${escapeHtml(roleId)}" aria-label="Модель ${escapeHtml(node.profile_id)}">${modelOptions(snapshot,profile,node.model)}</select></div><div>${routeQuota(profile,snapshot)}</div><div class="text-muted">${escapeHtml(reset || 'Н/Д: нет времени сброса')}</div><div><span class="status-dot ${healthDotClass(node.status)}"></span> ${escapeHtml(node.status_label_ru || 'Не проверялся')}${node.is_active ? '<br>Используется' : ''}</div><button class="route-remove" data-remove-profile="${escapeHtml(node.profile_id)}" data-role="${escapeHtml(roleId)}" aria-label="Удалить из маршрута">×</button></div>`;
}).join('');
return `<section class="role-section"><header class="role-header"><div><h4>${escapeHtml(agent?.name || pipeline.role_name_ru || roleId)}</h4><p class="text-muted">${escapeHtml(agent?.description || role?.role_description_ru || '')}</p></div><button class="btn btn-secondary btn-sm" data-add-role="${escapeHtml(roleId)}">+ Добавить аккаунт</button></header><div class="grid-header"><span></span><span>Приоритет</span><span>Аккаунт</span><span>Модель</span><span>Квота · остаток</span><span>Сброс</span><span>Статус</span><span></span></div><div class="role-chain-list">${rows || '<p class="inspector-value">Аккаунты не назначены. Добавьте подключённый аккаунт.</p>'}</div><div class="drop-zone" data-role="${escapeHtml(roleId)}">+ Перетащите аккаунт в конец маршрута</div><p class="inspector-value">Session Affinity: ${pipeline.session_affinity ? 'включена' : 'отключена'} · Порядок применяется к следующим назначениям</p></section>`;
}).join('') || '<p class="view-header-note">Агенты ещё не созданы. Добавьте агента на «Обзоре».</p>';

13
tests/test_brand_icons.js Normal file
View file

@ -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');