{"id":"d61b0c67-aa4d-4463-a93f-c24c3895aeaa","scriptName":"DevTools by 404NotFound","findRegex":"$","replaceString":"<style>\n.tvmf-cbtn,.tvmf-copy{display:block;width:100%;margin-top:8px;cursor:pointer;\n  font-family:'Courier New',monospace !important;font-weight:bold;letter-spacing:1px;\n  color:#ffd6ea;background:#1a0010;border:2px solid #ff2e8b;border-radius:4px;\n  text-shadow:0 0 4px #ff2e8b;image-rendering:pixelated;pointer-events:auto !important;\n  box-shadow:0 0 0 2px #0a0008,0 0 10px rgba(255,46,139,.5);}\n.tvmf-cbtn{font-size:13px;padding:9px 0;}\n.tvmf-copy{font-size:11px;padding:6px 0;}\n.tvmf-cbtn:active,.tvmf-copy:active{transform:translateY(1px);}\n.tvmf-pane{display:none;margin-top:8px;}\n.tvmf-pane.open{display:block;}\n.tvmf-subh{display:block;width:100%;text-align:left;margin-top:6px;cursor:pointer;\n  font-family:'Courier New',monospace !important;font-weight:bold;font-size:11px;\n  color:#ff8fc1;background:#140009;border:1px solid #ff2e8b;border-radius:4px;padding:6px 8px;\n  text-shadow:0 0 3px #ff2e8b;pointer-events:auto !important;}\n.tvmf-dc-code{width:100%;box-sizing:border-box;height:130px;resize:vertical;margin-top:8px;\n  font-family:'Courier New',monospace !important;font-size:11px;line-height:1.5;\n  color:#ffd6ea;background:#0a0008;border:2px solid #ff2e8b;border-radius:4px;padding:10px 12px;\n  text-shadow:0 0 2px #ff2e8b;outline:none;\n  box-shadow:0 0 0 2px #1a0010,0 0 12px rgba(255,46,139,.4);}\n.tvmf-dc-code::placeholder{color:#ff5fa2;opacity:.55;}\n.tvmf-box{font-family:'Courier New',monospace !important;font-style:normal !important;\n  white-space:pre-wrap;font-size:10px;line-height:1.5;color:#ff5fa2;text-shadow:0 0 2px #ff2e8b;\n  background:#0a0008;border:2px solid #ff2e8b;border-radius:4px;padding:10px 12px;margin-top:4px;\n  max-height:300px;overflow:auto;user-select:text;image-rendering:pixelated;\n  box-shadow:0 0 0 2px #1a0010,0 0 12px rgba(255,46,139,.4);}\n</style>\n\n<div class=\"tvmf-d1-wrap\">\n  <button class=\"tvmf-cbtn tvmf-d1-open\" type=\"button\">🍒 Сообщение 🍒</button>\n  <div class=\"tvmf-d1-panel tvmf-pane\">\n    <button class=\"tvmf-copy tvmf-d1-copy\" type=\"button\">🍒 Копировать 🍒</button>\n    <div class=\"tvmf-box tvmf-d1-box\">🍒 загрузка…</div>\n  </div>\n</div>\n\n<div class=\"tvmf-d2-wrap\">\n  <button class=\"tvmf-cbtn tvmf-d2-open\" type=\"button\">🍒 Контекст 🍒</button>\n  <div class=\"tvmf-d2-panel tvmf-pane\">\n    <button class=\"tvmf-copy tvmf-d2-copy\" type=\"button\">🍒 Копировать всё 🍒</button>\n    <div class=\"tvmf-d2-blocks\"></div>\n  </div>\n</div>\n\n<div class=\"tvmf-d3-wrap\">\n  <button class=\"tvmf-cbtn tvmf-d3-open\" type=\"button\">🍒 Ошибки 🍒</button>\n  <div class=\"tvmf-d3-panel tvmf-pane\">\n    <button class=\"tvmf-copy tvmf-d3-copy\" type=\"button\">🍒 Копировать 🍒</button>\n    <button class=\"tvmf-copy tvmf-d3-clear\" type=\"button\">🍒 Очистить 🍒</button>\n    <div class=\"tvmf-box tvmf-d3-box\">🍒 загрузка…</div>\n  </div>\n</div>\n\n<div class=\"tvmf-dc-wrap\">\n  <button class=\"tvmf-cbtn tvmf-dc-open\" type=\"button\">🍒 Dev Console 🍒</button>\n  <div class=\"tvmf-dc-panel tvmf-pane\">\n    <textarea class=\"tvmf-dc-code\" spellcheck=\"false\" placeholder=\"// JS: const m = await tavo.message.find(-1); log('last', m);&#10;// или вставь HTML-панель целиком (начинается с <div>)\"></textarea>\n    <button class=\"tvmf-cbtn tvmf-dc-run\" type=\"button\">▶ 🍒 Применить 🍒</button>\n    <button class=\"tvmf-copy tvmf-dc-clearcode\" type=\"button\">🍒 Очистить ввод 🍒</button>\n    <button class=\"tvmf-copy tvmf-dc-clear\" type=\"button\">🍒 Очистить вывод 🍒</button>\n    <button class=\"tvmf-copy tvmf-dc-copy\" type=\"button\">🍒 Копировать вывод 🍒</button>\n    <div class=\"tvmf-box tvmf-dc-box\">🍒 готов к работе…</div>\n  </div>\n</div>\n\n<div class=\"tvmf-tk-wrap\">\n  <button class=\"tvmf-cbtn tvmf-tk-open\" type=\"button\">🍒 Инструменты 🍒</button>\n  <div class=\"tvmf-tk-panel tvmf-pane\">\n    <button class=\"tvmf-subh tvmf-tk-rxlist\" type=\"button\">🍒 Список регексов чата</button>\n    <button class=\"tvmf-subh tvmf-tk-chat\"   type=\"button\">🍒 Инфо о чате</button>\n    <button class=\"tvmf-copy tvmf-tk-copy\" type=\"button\">🍒 Копировать вывод 🍒</button>\n    <div class=\"tvmf-box tvmf-tk-box\">🍒 нажми кнопку выше…</div>\n  </div>\n</div>\n\n<script>\nwindow.tvmfCopy = window.tvmfCopy || async function(btn, text){\n  let ok=false;\n  try{ await navigator.clipboard.writeText(text); ok=true; }\n  catch(_){ try{ const ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.opacity='0'; document.body.appendChild(ta); ta.focus(); ta.select(); ok=document.execCommand('copy'); document.body.removeChild(ta);}catch(_){} }\n  const old=btn.textContent; btn.textContent= ok?'🍒 Скопировано ⋆˚':'💔 Не вышло'; setTimeout(()=>btn.textContent=old,2000);\n};\n\n// ── ОШИБКИ: хук ставится один раз ──\n(async () => {\n  if (!window.__tvmfErrHook) {\n    window.__tvmfErrHook = true;\n    const push = async (type, m) => {\n      const rec = { t: new Date().toLocaleTimeString('ru'), type, msg: String(m).slice(0, 300) };\n      try { let s = await tavo.get('tvmf_errors'); if(!Array.isArray(s)) s=[]; s.push(rec); if(s.length>100)s=s.slice(-100); await tavo.set('tvmf_errors', s); } catch(_){}\n    };\n    window.addEventListener('error', (e) => {\n      if (e.target && (e.target.src || e.target.href)) push('РЕСУРС', (e.target.tagName||'?')+': '+(e.target.src||e.target.href));\n      else push('JS', (e.message||'')+' @'+(e.filename||'')+':'+(e.lineno||''));\n    }, true);\n    window.addEventListener('unhandledrejection', (e) => push('ПРОМИС', e.reason?.message || e.reason || 'rejection'));\n    const oe = console.error, ow = console.warn;\n    console.error = function(){ push('console.error', [...arguments].join(' ')); return oe.apply(this, arguments); };\n    console.warn  = function(){ push('console.warn',  [...arguments].join(' ')); return ow.apply(this, arguments); };\n    const of = window.fetch;\n    if (of) window.fetch = function(){ return of.apply(this, arguments).then(r=>{ if(!r.ok) push('FETCH', r.status+' '+r.url); return r; }).catch(err=>{ push('FETCH', 'fail '+err); throw err; }); };\n  }\n})();\n\n// ── 1. СООБЩЕНИЕ ──\n(async () => {\n  const w = document.querySelector('.tvmf-d1-wrap:not([data-done])');\n  if (!w) return; w.dataset.done = '1';\n  const btn = w.querySelector('.tvmf-d1-open');\n  const panel = w.querySelector('.tvmf-d1-panel');\n  const box = w.querySelector('.tvmf-d1-box');\n  const copy = w.querySelector('.tvmf-d1-copy');\n  const out = [];\n  const log = (l, v) => out.push(`🍒 ${l}:\\n${typeof v === 'string' ? v : JSON.stringify(v, null, 1)}`);\n  try {\n    const msg = await tavo.message.current();\n    log('MSG keys', msg ? Object.keys(msg) : 'нет');\n    log('MSG full', msg);\n    const chat = await tavo.chat.current();\n    log('CHAT keys', chat ? Object.keys(chat) : 'нет');\n    log('count', await tavo.message.count());\n    const last = (await tavo.message.find(-1))[0];\n    log('last msg keys', last ? Object.keys(last) : 'нет');\n  } catch (e) { out.push('💔 ОШИБКА: ' + (e?.message || e)); }\n  const text = out.join('\\n\\n');\n  box.textContent = text;\n  btn.addEventListener('click', () => {\n    const o = panel.classList.toggle('open');\n    btn.textContent = o ? '🍒 Свернуть 🍒' : '🍒 Сообщение 🍒';\n  });\n  copy.addEventListener('click', () => tvmfCopy(copy, text));\n})();\n\n// ── 2. КОНТЕКСТ ──\n(async () => {\n  const w = document.querySelector('.tvmf-d2-wrap:not([data-done])');\n  if (!w) return; w.dataset.done = '1';\n  const btn = w.querySelector('.tvmf-d2-open');\n  const panel = w.querySelector('.tvmf-d2-panel');\n  const blocks = w.querySelector('.tvmf-d2-blocks');\n  const copy = w.querySelector('.tvmf-d2-copy');\n\n  // ── токены: латиница/цифры/знаки 0.25, кириллица 0.9, эмодзи 2.5 ──\n  const TOK = (t) => {\n    if (!t) return 0;\n    let n = 0;\n    for (const c of String(t)) {\n      const p = c.codePointAt(0);\n      if (p > 0x1F000)                      n += 2.5;   // эмодзи: 2–3 токена\n      else if (p >= 0x0400 && p <= 0x04FF)  n += 0.9;   // кириллица ≈ 1 токен/буква\n      else                                  n += 0.25;  // латиница/цифры/знаки/пробелы ≈ 4 симв/токен\n    }\n    return Math.round(n);\n  };\n  const all = [];\n  const add = (title, body) => {\n    const txt = typeof body==='string'?body:JSON.stringify(body,null,1);\n    all.push(`🍒 ${title}\\n${txt}`);\n    const sec = document.createElement('div');\n    const h = document.createElement('button'); h.type='button'; h.className='tvmf-subh'; h.textContent='🍒 '+title;\n    const b = document.createElement('div'); b.className='tvmf-box'; b.style.display='none'; b.textContent=txt;\n    h.addEventListener('click', ()=>{ b.style.display = b.style.display==='none'?'block':'none'; });\n    sec.appendChild(h); sec.appendChild(b); blocks.appendChild(sec);\n  };\n  const safe = async (t, fn) => { try { add(t, await fn()); } catch(e){ add(t, '💔 '+(e?.message||e)); } };\n\n  let chat=null;\n  await safe('Сообщение', async()=>{ const m=await tavo.message.current(); return m?{id:m.id,role:m.role,len:(m.content||'').length,tokens:TOK(m.content),hasReasoning:!!m.reasoning}:'нет'; });\n  await safe('Чат', async()=>{ chat=await tavo.chat.current(); return chat?{id:chat.id,name:chat.name,preset:chat.preset?.id,lorebooks:(chat.lorebooks||[]).map(l=>l.id),regexes:(chat.regexes||[]).length}:'нет'; });\n  await safe('Кол-во сообщений', async()=> await tavo.message.count());\n  await safe('Персонаж', async()=>{ const id=chat?.characters?.[0]?.id; const c=await tavo.character.get(id); if(!c) return 'нет';\n    return {name:c.name, description:c.description, personality:c.personality, scenario:c.scenario, systemPrompt:c.system_prompt||c.systemPrompt,\n      tokens:TOK([c.description,c.personality,c.scenario,c.system_prompt||c.systemPrompt,c.mes_example].filter(Boolean).join('\\n'))}; });\n  await safe('Персона', async()=>{ if(chat?.persona?.id==null) return 'нет'; const p=await tavo.persona.get(chat.persona.id); return p?{name:p.name,description:p.description,tokens:TOK(p.description)}:'нет'; });\n  await safe('Пресет (активные тогглы)', async()=>{ if(chat?.preset?.id==null) return 'нет';\n    const pr=await tavo.preset.get(chat.preset.id); const act=(pr?.entries||[]).filter(e=>e.enabled!==false&&e.active!==false);\n    return {total:(pr?.entries||[]).length, active:act.length, tokens:TOK(act.map(e=>e.content||'').join('\\n')),\n      list:act.map((e,i)=>({name:e.name||('#'+i), role:e.role||'', content:(e.content||'').trim()}))}; });\n  await safe('Лорбуки', async()=>{ const res=[];\n    for(const lb of (chat?.lorebooks||[])){ const f=await tavo.lorebook.get(lb.id); const list=Array.isArray(f?.entries)?f.entries:Object.values(f?.entries||{});\n      res.push({id:lb.id, total:list.length, entries:list.map((e,i)=>({name:e.comment||e.name||('#'+i),\n        type:(e.strategy==='constant'||e.constant===true)?'constant':'keyed', keywords:e.keywords||e.key||e.keys||[], len:(e.content||'').length, on:e.enabled!==false&&e.disable!==true}))}); }\n    return res.length?res:'нет'; });\n  await safe('Регексы', async()=>{ const res=[];\n    for(const rx of (chat?.regexes||[])){ const r=await tavo.regex?.get?.(rx.id);\n      (r?.entries||[]).forEach((e,i)=>res.push({name:e.name||('#'+i), timing:e.timing, placement:e.placement||e.placements, find:(e.findRegex||'').slice(0,40)})); }\n    return res.length?res:'нет'; });\n  await safe('Память', async()=>{ const m=await tavo.memory.current(); return m?{enabled:m.enabled, count:(m.memories||[]).length, memories:m.memories||[]}:'нет'; });\n  await safe('Поле ввода', async()=>{ try{ return await tavo.input.get(); }catch(_){ return 'недоступно'; } });\n  await safe('Переменные', async()=>{ const names=['ui_ai_meta_tokens_final','ui_ai_meta_cache_final','tvmf_user_stamps_'+(chat?.id??'x'),'tvmf_errors'];\n    const r={}; for(const n of names){ const v=await tavo.get(n); r[n]=v==null?null:(typeof v==='object'?(Array.isArray(v)?('массив '+v.length):Object.keys(v)):v); } return r; });\n\n  const text = all.join('\\n\\n──────────\\n\\n');\n  btn.addEventListener('click', ()=>{ const o=panel.classList.toggle('open'); btn.textContent=o?'🍒 Свернуть 🍒':'🍒 Контекст 🍒'; });\n  copy.addEventListener('click', ()=> tvmfCopy(copy, text));\n})();\n\n// ── 3. ОШИБКИ ──\n(async () => {\n  const w = document.querySelector('.tvmf-d3-wrap:not([data-done])');\n  if (!w) return; w.dataset.done = '1';\n  const btn = w.querySelector('.tvmf-d3-open');\n  const panel = w.querySelector('.tvmf-d3-panel');\n  const box = w.querySelector('.tvmf-d3-box');\n  const copy = w.querySelector('.tvmf-d3-copy');\n  const clr = w.querySelector('.tvmf-d3-clear');\n\n  const render = async () => {\n    let saved = []; try { saved = await tavo.get('tvmf_errors'); } catch(_){}\n    const list = Array.isArray(saved) ? saved : [];\n    box.textContent = list.length\n      ? list.map(r => `🍒 [${r.t}] ${r.type}\\n${r.msg}`).join('\\n\\n')\n      : '🍒 ошибок пока нет — всё чисто';\n    return list.map(r => `[${r.t}] ${r.type}: ${r.msg}`).join('\\n');\n  };\n  let text = await render();\n\n  btn.addEventListener('click', async () => {\n    const o = panel.classList.toggle('open');\n    btn.textContent = o ? '🍒 Свернуть 🍒' : '🍒 Ошибки 🍒';\n    if (o) text = await render();\n  });\n  copy.addEventListener('click', () => tvmfCopy(copy, text));\n  clr.addEventListener('click', async () => { try { await tavo.set('tvmf_errors', []); } catch(_){} text = await render(); });\n})();\n\n// ── 4. DEV CONSOLE ──\n(async () => {\n  const w = document.querySelector('.tvmf-dc-wrap:not([data-done])');\n  if (!w) return; w.dataset.done = '1';\n  const btn   = w.querySelector('.tvmf-dc-open');\n  const panel = w.querySelector('.tvmf-dc-panel');\n  const code  = w.querySelector('.tvmf-dc-code');\n  const box   = w.querySelector('.tvmf-dc-box');\n  const runBtn= w.querySelector('.tvmf-dc-run');\n  const clrCodeBtn = w.querySelector('.tvmf-dc-clearcode');\n  const clrBtn= w.querySelector('.tvmf-dc-clear');\n  const copy  = w.querySelector('.tvmf-dc-copy');\n  const SAVE_VAR = 'tvmf_devconsole_code';\n\n  try { const saved = await tavo.get(SAVE_VAR); if (saved) code.value = saved; } catch (_) {}\n\n  let saveT = null;\n  code.addEventListener('input', () => {\n    clearTimeout(saveT);\n    saveT = setTimeout(() => tavo.set(SAVE_VAR, code.value).catch(()=>{}), 300);\n  });\n\n  const out = [];\n  const render = () => { box.textContent = out.length ? out.join('\\n') : '🍒 (пусто)'; };\n  const log = (l, v) => {\n    if (v === undefined) out.push('🍒 ' + (typeof l === 'string' ? l : JSON.stringify(l, null, 1)));\n    else out.push(`🍒 ${l}:\\n${typeof v === 'string' ? v : JSON.stringify(v, null, 1)}`);\n    render();\n  };\n\n  const injectHTML = (text) => {\n    const prev = document.getElementById('tvmf-dc-inject');\n    if (prev) prev.remove();\n    const host = document.createElement('div');\n    host.id = 'tvmf-dc-inject';\n    host.style.cssText = 'position:fixed;left:50%;bottom:14px;transform:translateX(-50%);' +\n      'z-index:2147483647;width:300px;max-width:92vw;max-height:80vh;overflow:auto;';\n    const content = document.createElement('div');\n    content.innerHTML = text;\n    const scripts = [...content.querySelectorAll('script')];\n    scripts.forEach(s => s.remove());\n    const removeBtn = document.createElement('button');\n    removeBtn.type = 'button'; removeBtn.className = 'tvmf-copy'; removeBtn.textContent = '💔 Убрать 💔';\n    removeBtn.addEventListener('click', () => host.remove());\n    host.appendChild(content); host.appendChild(removeBtn);\n    document.body.appendChild(host);\n    scripts.forEach(old => {\n      const s = document.createElement('script');\n      if (old.src) s.src = old.src; else s.textContent = old.textContent;\n      document.body.appendChild(s);\n    });\n  };\n\n  btn.addEventListener('click', () => {\n    const o = panel.classList.toggle('open');\n    btn.textContent = o ? '🍒 Свернуть 🍒' : '🍒 Dev Console 🍒';\n  });\n\n  clrCodeBtn.addEventListener('click', async () => {\n    code.value = '';\n    try { await tavo.set(SAVE_VAR, undefined); } catch (_) {}\n    out.length = 0; out.push('🍒 ввод очищен'); render();\n    code.focus();\n  });\n\n  clrBtn.addEventListener('click', () => { out.length = 0; render(); });\n\n  runBtn.addEventListener('click', async () => {\n    out.length = 0; render();\n    const text = (code.value || '').trim();\n    if (!text) { out.push('💔 пусто'); render(); return; }\n    if (text[0] === '<') {\n      try {\n        injectHTML(text);\n        out.push('🍒 HTML вставлен — панель внизу по центру');\n        out.push('⚠ живёт до перезагрузки страницы (или жми «Убрать»)');\n        render();\n      } catch (e) { out.push('💔 HTML ОШИБКА: ' + (e?.message || e)); if (e?.stack) out.push(e.stack); render(); }\n      return;\n    }\n    try {\n      const fn = new Function('tavo', 'log', '\"use strict\"; return (async () => {\\n' + text + '\\n})();');\n      const result = await fn(tavo, log);\n      if (result !== undefined) log('⟹ результат', result);\n      out.push('✅ Готово'); render();\n    } catch (e) { out.push('💔 ОШИБКА: ' + (e?.message || e)); if (e?.stack) out.push(e.stack); render(); }\n  });\n\n  copy.addEventListener('click', () => window.tvmfCopy(copy, box.textContent));\n})();\n\n// ── 5. ИНСТРУМЕНТЫ ──\n(async () => {\n  const w = document.querySelector('.tvmf-tk-wrap:not([data-done])');\n  if (!w) return; w.dataset.done = '1';\n  const $ = s => w.querySelector(s);\n  const btn = $('.tvmf-tk-open'), panel = $('.tvmf-tk-panel'), box = $('.tvmf-tk-box');\n  const out = [];\n  const render = () => { box.textContent = out.length ? out.join('\\n') : '🍒 (пусто)'; };\n  const log = (l, v) => {\n    if (v === undefined) out.push('🍒 ' + (typeof l === 'string' ? l : JSON.stringify(l, null, 1)));\n    else out.push(`🍒 ${l}:\\n${typeof v === 'string' ? v : JSON.stringify(v, null, 1)}`);\n    render();\n  };\n  const reset = () => { out.length = 0; };\n\n  btn.addEventListener('click', () => {\n    const o = panel.classList.toggle('open');\n    btn.textContent = o ? '🍒 Свернуть 🍒' : '🍒 Инструменты 🍒';\n  });\n\n  $('.tvmf-tk-rxlist').addEventListener('click', async () => {\n    reset();\n    try {\n      const chat = await tavo.chat.current();\n      const refs = chat?.regexes || [];\n      log('подключено регексов', refs.length);\n      for (const r of refs) {\n        let full = null;\n        try { full = await tavo.regex.get(r.id); } catch (_) {}\n        if (full) {\n          out.push(`  • ${full.scriptName || r.id}`);\n          out.push(`     timing: ${full.timing || '—'} | placement: ${full.placement ?? '—'} | disabled: ${full.disabled ?? false}`);\n          out.push(`     find: ${String(full.findRegex || '').slice(0, 80)}`);\n        } else { out.push(`  • ${r.id} (не удалось получить)`); }\n      }\n      render();\n    } catch (e) { log('💔 ОШИБКА', e?.message || e); }\n  });\n\n  $('.tvmf-tk-chat').addEventListener('click', async () => {\n    reset();\n    try {\n      const chat  = await tavo.chat.current();\n      const total = await tavo.message.count();\n      let ai = 0;\n      try { ai = (await tavo.message.find(undefined, { role: 'assistant', hidden: false }) || []).length; } catch (_) {}\n      log('id чата', chat?.id ?? '—');\n      log('всего сообщений', total);\n      log('от ИИ', ai);\n      log('лорбуков привязано', (chat?.lorebooks || []).length);\n      log('регексов привязано', (chat?.regexes || []).length);\n    } catch (e) { log('💔 ОШИБКА', e?.message || e); }\n  });\n\n  $('.tvmf-tk-copy').addEventListener('click', () => window.tvmfCopy($('.tvmf-tk-copy'), box.textContent));\n})();\n</script>\n","trimStrings":[],"placement":[2],"disabled":false,"markdownOnly":true,"promptOnly":false,"runOnEdit":false,"substituteRegex":0,"minDepth":null,"maxDepth":null}