diff --git a/backend/src/controllers/adminController.js b/backend/src/controllers/adminController.js index 6fec4ab..47ba6a5 100644 --- a/backend/src/controllers/adminController.js +++ b/backend/src/controllers/adminController.js @@ -887,15 +887,16 @@ const ASSISTANT_PRESETS = [ ]; // Проверенные бесплатные модели Kilo (чистый русский) — для выпадающего списка // Проверенные бесплатные модели шлюза Kilo (отдают чистый русский). Порядок — от мощных к лёгким. +// ctx — окно контекста, out — макс. токенов в ответе (данные из /api/openrouter/models). Все бесплатные ($0). const KILO_MODELS = [ - { id: 'nvidia/nemotron-3-ultra-550b-a55b:free', label: 'Nemotron 550B — флагман (1M)' }, - { id: 'nvidia/nemotron-3-super-120b-a12b:free', label: 'Nemotron 120B — баланс (1M)' }, - { id: 'qwen/qwen3.7-plus:free', label: 'Qwen3.7 Plus — умная, медленная (1M)' }, - { id: 'openrouter/owl-alpha', label: 'Owl Alpha — чистый русский (1M)' }, - { id: 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free', label: 'Nemotron Nano 30B — быстрая (256K)' }, - { id: 'poolside/laguna-m.1:free', label: 'Laguna M.1 — быстрая (262K)' }, - { id: 'poolside/laguna-xs.2:free', label: 'Laguna XS — лёгкая (262K)' }, - { id: 'openrouter/free', label: 'Free Router — авто-выбор (быстро)' }, + { 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)', ctx: 1000000, out: 262144 }, + { id: 'qwen/qwen3.7-plus:free', label: 'Qwen3.7 Plus — умная, медленная (1M)', ctx: 1000000, out: 65536 }, + { 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)', ctx: 256000, out: 65536 }, + { 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)', ctx: 262144, out: 32768 }, + { 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; } diff --git a/frontend/js/admin/sections/assistant.js b/frontend/js/admin/sections/assistant.js index 548b726..12c0893 100644 --- a/frontend/js/admin/sections/assistant.js +++ b/frontend/js/admin/sections/assistant.js @@ -7,6 +7,7 @@ var esc = (window.LS && LS.escapeHtml) ? LS.escapeHtml : function (s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&', '<': '<', '>': '>', '"': '"' })[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 SPARK = ''; + 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() { if (document.getElementById('asst-adm-style')) return; @@ -20,6 +21,8 @@ '.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-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.act{background:#9B5DE5;color:#fff;}', '.asst-bdg.key{background:rgba(5,150,82,.12);color:#059652;}', @@ -124,18 +127,20 @@ else listEl.innerHTML = providers.map(function (p) { var isKilo = /kilocode\.ai/.test(p.url || ''); var act = p.id === activeId; - var ksel = ''; + var ksel = '', lim = ''; if (isKilo) { var opts = kiloModels.slice(); if (!opts.some(function (m) { return m.id === p.model; })) opts = [{ id: p.model, label: p.model }].concat(opts); ksel = ''; + var km = kiloModels.find(function (m) { return m.id === p.model; }); + if (km) lim = '