fix(assistant): длинные формулы не обрезаются + лимиты моделей в админке

Рендер ответа: display-формулы KaTeX прокручиваются по горизонтали
(overflow-x:auto), пузырь ассистента во всю ширину, панель шире (380px) —
длинные выражения больше не режутся по правому краю.

Админка: к моделям Kilo добавлены ctx/out (из /models); на карточке Kilo
показывается «контекст N · ответ до M токенов · бесплатно».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-04 21:18:50 +03:00
parent 78a9eca9c0
commit f1f79335ec
3 changed files with 24 additions and 12 deletions
+9 -8
View File
@@ -887,15 +887,16 @@ const ASSISTANT_PRESETS = [
]; ];
// Проверенные бесплатные модели Kilo (чистый русский) — для выпадающего списка // Проверенные бесплатные модели Kilo (чистый русский) — для выпадающего списка
// Проверенные бесплатные модели шлюза Kilo (отдают чистый русский). Порядок — от мощных к лёгким. // Проверенные бесплатные модели шлюза Kilo (отдают чистый русский). Порядок — от мощных к лёгким.
// ctx — окно контекста, out — макс. токенов в ответе (данные из /api/openrouter/models). Все бесплатные ($0).
const KILO_MODELS = [ const KILO_MODELS = [
{ id: 'nvidia/nemotron-3-ultra-550b-a55b:free', label: 'Nemotron 550B — флагман (1M)' }, { id: 'nvidia/nemotron-3-ultra-550b-a55b:free', label: 'Nemotron 550B — флагман (1M)', ctx: 1000000, out: 65536 },
{ id: 'nvidia/nemotron-3-super-120b-a12b:free', label: 'Nemotron 120B — баланс (1M)' }, { id: 'nvidia/nemotron-3-super-120b-a12b:free', label: 'Nemotron 120B — баланс (1M)', ctx: 1000000, out: 262144 },
{ id: 'qwen/qwen3.7-plus:free', label: 'Qwen3.7 Plus — умная, медленная (1M)' }, { id: 'qwen/qwen3.7-plus:free', label: 'Qwen3.7 Plus — умная, медленная (1M)', ctx: 1000000, out: 65536 },
{ id: 'openrouter/owl-alpha', label: 'Owl Alpha — чистый русский (1M)' }, { id: 'openrouter/owl-alpha', label: 'Owl Alpha — чистый русский (1M)', ctx: 1048756, out: 262144 },
{ id: 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free', label: 'Nemotron Nano 30B — быстрая (256K)' }, { id: 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free', label: 'Nemotron Nano 30B — быстрая (256K)', ctx: 256000, out: 65536 },
{ id: 'poolside/laguna-m.1:free', label: 'Laguna M.1 — быстрая (262K)' }, { id: 'poolside/laguna-m.1:free', label: 'Laguna M.1 — быстрая (262K)', ctx: 262144, out: 32768 },
{ id: 'poolside/laguna-xs.2:free', label: 'Laguna XS — лёгкая (262K)' }, { id: 'poolside/laguna-xs.2:free', label: 'Laguna XS — лёгкая (262K)', ctx: 262144, out: 32768 },
{ id: 'openrouter/free', label: 'Free Router — авто-выбор (быстро)' }, { id: 'openrouter/free', label: 'Free Router — авто-выбор (быстро)', ctx: 200000, out: null },
]; ];
function _aset(k) { const r = db.prepare('SELECT value FROM app_settings WHERE key = ?').get(k); return r && r.value != null ? r.value : null; } function _aset(k) { const r = db.prepare('SELECT value FROM app_settings WHERE key = ?').get(k); return r && r.value != null ? r.value : null; }
+7 -2
View File
@@ -7,6 +7,7 @@
var esc = (window.LS && LS.escapeHtml) ? LS.escapeHtml : function (s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]; }); }; var esc = (window.LS && LS.escapeHtml) ? LS.escapeHtml : function (s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]; }); };
var IN = 'padding:8px 11px;border:1px solid var(--border,#e2e8f0);border-radius:9px;font:inherit;font-size:.85rem;width:100%;box-sizing:border-box;background:var(--surface,#fff);color:var(--text,#0f172a)'; var IN = 'padding:8px 11px;border:1px solid var(--border,#e2e8f0);border-radius:9px;font:inherit;font-size:.85rem;width:100%;box-sizing:border-box;background:var(--surface,#fff);color:var(--text,#0f172a)';
var SPARK = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M18.4 5.6l-2.8 2.8M8.4 15.6l-2.8 2.8"/></svg>'; var SPARK = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M18.4 5.6l-2.8 2.8M8.4 15.6l-2.8 2.8"/></svg>';
function fmtTok(n) { if (!n) return '—'; if (n >= 1000000) { var m = n / 1000000; return (m >= 10 ? Math.round(m) : m.toFixed(1).replace(/\.0$/, '')) + 'M'; } if (n >= 1000) return Math.round(n / 1000) + 'K'; return String(n); }
function ensureStyle() { function ensureStyle() {
if (document.getElementById('asst-adm-style')) return; if (document.getElementById('asst-adm-style')) return;
@@ -20,6 +21,8 @@
'.asst-pcb{flex:1;min-width:0;}', '.asst-pcb{flex:1;min-width:0;}',
'.asst-pcn{font-weight:800;font-size:.92rem;color:var(--text,#0f172a);display:flex;align-items:center;gap:7px;flex-wrap:wrap;}', '.asst-pcn{font-weight:800;font-size:.92rem;color:var(--text,#0f172a);display:flex;align-items:center;gap:7px;flex-wrap:wrap;}',
'.asst-pcs{font-size:.76rem;color:#8a94a6;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}', '.asst-pcs{font-size:.76rem;color:#8a94a6;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
'.asst-pclim{font-size:.7rem;color:#8a94a6;margin-top:5px;display:flex;align-items:center;gap:5px;}',
'.asst-pclim b{color:var(--text-2,#475569);font-weight:700;}',
'.asst-bdg{font-size:.6rem;font-weight:800;text-transform:uppercase;letter-spacing:.03em;padding:2px 8px;border-radius:99px;}', '.asst-bdg{font-size:.6rem;font-weight:800;text-transform:uppercase;letter-spacing:.03em;padding:2px 8px;border-radius:99px;}',
'.asst-bdg.act{background:#9B5DE5;color:#fff;}', '.asst-bdg.act{background:#9B5DE5;color:#fff;}',
'.asst-bdg.key{background:rgba(5,150,82,.12);color:#059652;}', '.asst-bdg.key{background:rgba(5,150,82,.12);color:#059652;}',
@@ -124,18 +127,20 @@
else listEl.innerHTML = providers.map(function (p) { else listEl.innerHTML = providers.map(function (p) {
var isKilo = /kilocode\.ai/.test(p.url || ''); var isKilo = /kilocode\.ai/.test(p.url || '');
var act = p.id === activeId; var act = p.id === activeId;
var ksel = ''; var ksel = '', lim = '';
if (isKilo) { if (isKilo) {
var opts = kiloModels.slice(); var opts = kiloModels.slice();
if (!opts.some(function (m) { return m.id === p.model; })) opts = [{ id: p.model, label: p.model }].concat(opts); if (!opts.some(function (m) { return m.id === p.model; })) opts = [{ id: p.model, label: p.model }].concat(opts);
ksel = '<select class="asst-ksel" data-ksel="' + p.id + '">' + opts.map(function (m) { return '<option value="' + esc(m.id) + '"' + (m.id === p.model ? ' selected' : '') + '>' + esc(m.label) + '</option>'; }).join('') + '</select>'; ksel = '<select class="asst-ksel" data-ksel="' + p.id + '">' + opts.map(function (m) { return '<option value="' + esc(m.id) + '"' + (m.id === p.model ? ' selected' : '') + '>' + esc(m.label) + '</option>'; }).join('') + '</select>';
var km = kiloModels.find(function (m) { return m.id === p.model; });
if (km) lim = '<div class="asst-pclim">контекст <b>' + fmtTok(km.ctx) + '</b> · ответ до <b>' + fmtTok(km.out) + '</b> токенов · <b>бесплатно</b></div>';
} }
return '<div class="asst-pcard' + (act ? ' active' : '') + '">' + return '<div class="asst-pcard' + (act ? ' active' : '') + '">' +
'<div class="asst-pcic">' + SPARK + '</div>' + '<div class="asst-pcic">' + SPARK + '</div>' +
'<div class="asst-pcb"><div class="asst-pcn">' + esc(p.name || 'Провайдер') + '<div class="asst-pcb"><div class="asst-pcn">' + esc(p.name || 'Провайдер') +
(act ? '<span class="asst-bdg act">активен</span>' : '') + (act ? '<span class="asst-bdg act">активен</span>' : '') +
'<span class="asst-bdg ' + (p.hasKey ? 'key' : 'nokey') + '">' + (p.hasKey ? 'ключ есть' : 'нет ключа') + '</span></div>' + '<span class="asst-bdg ' + (p.hasKey ? 'key' : 'nokey') + '">' + (p.hasKey ? 'ключ есть' : 'нет ключа') + '</span></div>' +
'<div class="asst-pcs">' + esc(p.model || '') + '</div>' + ksel + '</div>' + '<div class="asst-pcs">' + esc(p.model || '') + '</div>' + ksel + lim + '</div>' +
'<div class="asst-pca">' + '<div class="asst-pca">' +
(act ? '' : '<button class="asst-ib primary" data-act="activate" data-id="' + p.id + '">Сделать активным</button>') + (act ? '' : '<button class="asst-ib primary" data-act="activate" data-id="' + p.id + '">Сделать активным</button>') +
'<button class="asst-ib" data-act="test" data-id="' + p.id + '">Тест</button>' + '<button class="asst-ib" data-act="test" data-id="' + p.id + '">Тест</button>' +
+8 -2
View File
@@ -280,7 +280,7 @@
'.asst-dot{position:absolute;top:0;right:0;width:13px;height:13px;border-radius:50%;background:#F15BB5;border:2px solid #fff;}', '.asst-dot{position:absolute;top:0;right:0;width:13px;height:13px;border-radius:50%;background:#F15BB5;border:2px solid #fff;}',
reduceMotion ? '' : '.asst-fab.pulse{animation:asstPulse 2.2s ease-in-out infinite;}', reduceMotion ? '' : '.asst-fab.pulse{animation:asstPulse 2.2s ease-in-out infinite;}',
'@keyframes asstPulse{0%,100%{box-shadow:0 8px 24px rgba(139,92,246,.32);}50%{box-shadow:0 8px 30px rgba(241,91,181,.5);}}', '@keyframes asstPulse{0%,100%{box-shadow:0 8px 24px rgba(139,92,246,.32);}50%{box-shadow:0 8px 30px rgba(241,91,181,.5);}}',
'.asst-bubble{position:absolute;left:0;bottom:66px;width:330px;max-width:88vw;background:#fff;border-radius:18px;', '.asst-bubble{position:absolute;left:0;bottom:66px;width:380px;max-width:92vw;background:#fff;border-radius:18px;',
' box-shadow:0 20px 56px rgba(15,23,42,.24);padding:15px 17px;border:1px solid rgba(15,23,42,.07);', ' box-shadow:0 20px 56px rgba(15,23,42,.24);padding:15px 17px;border:1px solid rgba(15,23,42,.07);',
' opacity:0;transform:translateY(8px) scale(.98);pointer-events:none;transition:opacity .18s,transform .18s;transform-origin:bottom left;}', ' opacity:0;transform:translateY(8px) scale(.98);pointer-events:none;transition:opacity .18s,transform .18s;transform-origin:bottom left;}',
'.asst-name-face{display:inline-block;width:20px;height:20px;vertical-align:-4px;margin-right:7px;}', '.asst-name-face{display:inline-block;width:20px;height:20px;vertical-align:-4px;margin-right:7px;}',
@@ -314,12 +314,18 @@
'.asst-rich ul,.asst-rich ol{margin:4px 0 4px 18px;padding:0;}', '.asst-rich ul,.asst-rich ol{margin:4px 0 4px 18px;padding:0;}',
'.asst-rich li{margin:2px 0;}', '.asst-rich li{margin:2px 0;}',
'.asst-rich code{background:rgba(15,23,42,.06);border-radius:4px;padding:1px 4px;}', '.asst-rich code{background:rgba(15,23,42,.06);border-radius:4px;padding:1px 4px;}',
// длинные формулы не помещаются в узкий блок → горизонтальная прокрутка, ничего не обрезается
'.asst-rich{overflow-wrap:anywhere;}',
'.asst-rich .katex-display{margin:6px 0;overflow-x:auto;overflow-y:hidden;padding-bottom:4px;max-width:100%;}',
'.asst-rich .katex-display::-webkit-scrollbar{height:6px;}',
'.asst-rich .katex-display::-webkit-scrollbar-thumb{background:rgba(15,23,42,.18);border-radius:99px;}',
'.asst-rich .katex{max-width:100%;}',
'.asst-md-h{font-weight:800;color:#0F172A;margin:6px 0 2px;}', '.asst-md-h{font-weight:800;color:#0F172A;margin:6px 0 2px;}',
'.asst-chat{max-height:46vh;overflow:auto;display:flex;flex-direction:column;gap:8px;margin-bottom:8px;}', '.asst-chat{max-height:46vh;overflow:auto;display:flex;flex-direction:column;gap:8px;margin-bottom:8px;}',
'.asst-chat:empty{display:none;}', '.asst-chat:empty{display:none;}',
'.asst-msg{font-size:.84rem;line-height:1.5;border-radius:12px;padding:8px 11px;max-width:92%;word-break:break-word;}', '.asst-msg{font-size:.84rem;line-height:1.5;border-radius:12px;padding:8px 11px;max-width:92%;word-break:break-word;}',
'.asst-msg-user{align-self:flex-end;background:#9B5DE5;color:#fff;}', '.asst-msg-user{align-self:flex-end;background:#9B5DE5;color:#fff;}',
'.asst-msg-assistant{align-self:flex-start;background:rgba(15,23,42,.05);}', '.asst-msg-assistant{align-self:flex-start;background:rgba(15,23,42,.05);max-width:100%;}',
'.asst-msg-assistant .asst-rich{color:#28324a;}', '.asst-msg-assistant .asst-rich{color:#28324a;}',
'.asst-msg-ph{opacity:.6;}', '.asst-msg-ph{opacity:.6;}',
'.asst-msg-links{align-self:flex-start;font-size:.74rem;}', '.asst-msg-links{align-self:flex-start;font-size:.74rem;}',