MythraMind / MythraMind_Bloom_v3.html
StoneFighterStudio's picture
Upload MythraMind_Bloom_v3.html
48457ec verified
Raw
History Blame Contribute Delete
62.1 kB
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>MythraMind Bloom v3</title>
<style>
:root{
--bg:#06060e;--bg2:rgba(11,8,22,.97);--bg3:rgba(16,11,32,.85);
--bdr:rgba(130,80,255,.18);--bdr2:rgba(130,80,255,.42);
--p:#9333ea;--p2:#a855f7;--p3:#c084fc;
--c:#06b6d4;--c2:#22d3ee;
--g:#10b981;--g2:#34d399;
--o:#f59e0b;--r:#ef4444;--r2:#f87171;
--tx:#ede8f6;--td:#7c6f99;--tc:#c4b5fd;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
font-family:'Segoe UI',system-ui,sans-serif;font-size:13px}
body::before{content:'';position:fixed;inset:0;pointer-events:none;
background:radial-gradient(ellipse 80% 60% at 10% 90%,rgba(147,51,234,.07) 0,transparent 60%),
radial-gradient(ellipse 60% 50% at 90% 10%,rgba(6,182,212,.05) 0,transparent 60%)}
.hd{height:52px;display:flex;align-items:center;padding:0 20px;gap:14px;
background:var(--bg2);border-bottom:1px solid var(--bdr);backdrop-filter:blur(14px);
position:relative;z-index:50;flex-shrink:0}
.logo{font-size:18px;font-weight:800;letter-spacing:-.5px;
background:linear-gradient(120deg,#c4b5fd,#06b6d4 55%,#34d399);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.pill{display:flex;align-items:center;gap:5px;padding:3px 9px;border-radius:20px;
font-size:10px;font-weight:700;letter-spacing:.5px}
.pill-g{background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);color:var(--g2)}
.pill-p{background:rgba(147,51,234,.12);border:1px solid rgba(147,51,234,.3);color:var(--p3)}
.dot{width:6px;height:6px;border-radius:50%;animation:blink 2s infinite}
.dot-g{background:var(--g)}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
.hstats{margin-left:auto;display:flex;gap:14px;font-size:11px;color:var(--td)}
.hstat b{color:var(--tc);font-family:'Courier New',monospace}
.tabs-nav{height:40px;display:flex;align-items:stretch;background:rgba(9,6,20,.9);
border-bottom:1px solid var(--bdr);padding:0 16px;gap:2px;flex-shrink:0}
.tb{display:flex;align-items:center;gap:6px;padding:0 18px;background:transparent;
border:none;border-bottom:2px solid transparent;color:var(--td);cursor:pointer;
font-size:12px;font-weight:600;transition:all .2s;font-family:inherit}
.tb:hover{color:var(--tx)}.tb.on{color:var(--p3);border-bottom-color:var(--p2)}
.tabs-body{height:calc(100vh - 92px);overflow:hidden}
.pane{display:none;height:100%;padding:14px;gap:14px;overflow:hidden}
.pane.on{display:flex}
#treino{flex-direction:row}
#chat{flex-direction:row}
#playground{flex-direction:column}
.pnl{background:var(--bg2);border:1px solid var(--bdr);border-radius:10px;
display:flex;flex-direction:column;overflow:hidden}
.pnl-hd{padding:11px 14px;border-bottom:1px solid var(--bdr);font-weight:700;
font-size:11px;letter-spacing:.9px;text-transform:uppercase;color:var(--p3);
display:flex;align-items:center;gap:7px;flex-shrink:0}
.pnl-bd{flex:1;overflow-y:auto;padding:13px;display:flex;flex-direction:column;gap:10px}
::-webkit-scrollbar{width:4px;height:4px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(130,80,255,.25);border-radius:2px}
.fld{display:flex;flex-direction:column;gap:4px}
label{font-size:11px;font-weight:600;color:var(--td)}
input[type=text],input[type=number],textarea,select{
background:rgba(18,12,36,.8);border:1px solid var(--bdr);border-radius:6px;
color:var(--tx);padding:7px 10px;font-size:12px;font-family:'Courier New',monospace;
width:100%;transition:border-color .2s}
input:focus,textarea:focus,select:focus{outline:none;border-color:var(--p2)}
textarea{resize:vertical;min-height:80px;line-height:1.5}
.sr{display:flex;align-items:center;gap:8px}
.sr input[type=range]{flex:1;-webkit-appearance:none;height:4px;border-radius:2px;
background:rgba(130,80,255,.2);border:none;padding:0}
.sr input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;
border-radius:50%;background:var(--p2);cursor:pointer}
.sv{font-size:11px;font-weight:700;color:var(--tc);font-family:'Courier New',monospace;min-width:40px;text-align:right}
.dvd{height:1px;background:var(--bdr);margin:2px 0;flex-shrink:0}
.btn{padding:7px 14px;border-radius:6px;border:none;font-size:11px;font-weight:700;
cursor:pointer;transition:all .2s;font-family:inherit;letter-spacing:.4px}
.btn:active{transform:scale(.97)}
.btn-pu{background:linear-gradient(135deg,#6d28d9,#a855f7);color:#fff}
.btn-pu:hover{background:linear-gradient(135deg,#7c3aed,#c084fc);box-shadow:0 3px 12px rgba(139,92,246,.4)}
.btn-gn{background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.35);color:var(--g2)}
.btn-gn:hover{background:rgba(16,185,129,.22)}
.btn-cy{background:rgba(6,182,212,.1);border:1px solid rgba(6,182,212,.3);color:var(--c2)}
.btn-cy:hover{background:rgba(6,182,212,.2)}
.btn-rd{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:var(--r2)}
.btn-rd:hover{background:rgba(239,68,68,.2)}
.btn-sm{padding:5px 10px;font-size:10px}
.brow{display:flex;gap:7px;flex-wrap:wrap}
.st{display:flex;justify-content:space-between;align-items:center;
padding:5px 9px;background:rgba(130,80,255,.06);border-radius:5px;border-left:2px solid var(--p2);font-size:11px}
.st-l{color:var(--td)}.st-v{color:var(--g2);font-weight:700;font-family:'Courier New',monospace}
.st-v.cyan{color:var(--c2)}.st-v.org{color:var(--o)}.st-v.red{color:var(--r2)}
.pb-item{display:grid;grid-template-columns:46px 1fr 40px;align-items:center;gap:6px;margin:2px 0}
.pb-lbl{font-size:11px;font-family:'Courier New',monospace;color:var(--tc);text-align:right;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pb-track{height:17px;background:rgba(130,80,255,.1);border-radius:3px;overflow:hidden}
.pb-fill{height:100%;display:flex;align-items:center;padding-left:4px;
font-size:9px;font-weight:700;color:rgba(255,255,255,.8);min-width:18px}
.pb-pct{font-size:11px;color:var(--td);font-family:'Courier New',monospace;text-align:right}
.chat-log{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:7px}
.msg{padding:8px 11px;border-radius:8px;font-size:12px;line-height:1.5;max-width:88%;word-break:break-word}
.msg.u{align-self:flex-end;background:rgba(147,51,234,.22);border:1px solid rgba(147,51,234,.3)}
.msg.b{align-self:flex-start;background:rgba(6,182,212,.1);border:1px solid rgba(6,182,212,.2)}
.msg.sys{align-self:center;background:rgba(16,185,129,.07);border:1px solid rgba(16,185,129,.2);
color:var(--td);font-size:11px;max-width:95%}
.chat-in{display:flex;gap:7px;padding-top:11px;border-top:1px solid var(--bdr);flex-shrink:0}
.chat-in input{flex:1}
.tflow{display:flex;flex-wrap:wrap;gap:3px;padding:8px;
background:rgba(18,12,36,.5);border-radius:6px;min-height:36px}
.tok{display:inline-flex;flex-direction:column;align-items:center;padding:3px 5px;
border-radius:5px;font-size:11px;font-family:'Courier New',monospace;
background:rgba(130,80,255,.15);border:1px solid rgba(130,80,255,.3);cursor:pointer;transition:all .15s}
.tok:hover{transform:translateY(-2px)}
.tok-c{font-size:8px;color:var(--td)}
.tok.mask{background:rgba(239,68,68,.18);border-color:rgba(239,68,68,.45);color:var(--r2)}
.tok.reco{background:rgba(16,185,129,.18);border-color:rgba(16,185,129,.45);color:var(--g2)}
.diff-box{background:rgba(18,12,36,.6);border:1px solid var(--bdr);border-radius:6px;
padding:10px;min-height:46px;font-size:13px;line-height:2;word-break:break-all;
font-family:'Courier New',monospace}
.chart-wrap{background:rgba(18,12,36,.6);border:1px solid var(--bdr);border-radius:7px;
overflow:hidden;position:relative;flex-shrink:0}
.chart-wrap canvas{display:block}
.chart-lbl{position:absolute;top:5px;left:9px;font-size:9px;color:var(--td);letter-spacing:.5px}
.vti-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px}
.vti-col{display:flex;flex-direction:column;align-items:center;gap:2px}
.vti-bw{height:50px;width:100%;background:rgba(130,80,255,.1);border-radius:3px;
display:flex;align-items:flex-end;overflow:hidden;border:1px solid var(--bdr)}
.vti-bf{width:100%;border-radius:2px 2px 0 0;transition:height .4s}
.vti-lb{font-size:8px;color:var(--td);text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;width:100%}
.sap-ring{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;
justify-content:center;border:2px solid var(--td);transition:all .5s;position:relative}
.sap-ring::after{content:'SAP';font-size:7px;font-weight:700;position:absolute;
bottom:-14px;left:50%;transform:translateX(-50%);white-space:nowrap;color:var(--td)}
.prog-wrap{background:rgba(130,80,255,.1);border-radius:3px;height:5px;overflow:hidden;flex-shrink:0}
.prog-fill{height:100%;background:linear-gradient(90deg,#6d28d9,#06b6d4);border-radius:3px;transition:width .3s}
.beam-item{padding:7px 10px;border-radius:6px;border:1px solid var(--bdr);
font-size:11px;background:rgba(130,80,255,.05);margin-bottom:5px}
.beam-item.best{border-color:var(--g2);background:rgba(16,185,129,.06)}
.morph-tag{display:inline-block;padding:2px 6px;border-radius:4px;font-size:9px;
font-weight:700;background:rgba(245,158,11,.15);border:1px solid rgba(245,158,11,.3);color:var(--o)}
.sec{font-size:10px;font-weight:700;color:var(--p3);letter-spacing:.7px;text-transform:uppercase}
@keyframes flash{0%{box-shadow:0 0 20px rgba(239,68,68,.8)}100%{box-shadow:none}}
.topic-flash{animation:flash .6s ease-out}
@media(max-width:900px){
#treino,#chat{flex-direction:column}
.hstats{display:none}
}
</style>
</head>
<body>
<div class="hd">
<div class="logo">🌸 MythraMind <strong>Bloom</strong> v3</div>
<div class="pill pill-g"><div class="dot dot-g"></div>ATIVO</div>
<div class="pill pill-p">SEM BPE</div>
<div class="tabs-nav" style="border:none;background:none;height:auto;padding:0;margin-left:4px">
<button class="tb on" onclick="openTab('treino',this)">📚 Treino</button>
<button class="tb" onclick="openTab('chat',this)">💬 Chat</button>
<button class="tb" onclick="openTab('playground',this)">🎮 Playground</button>
</div>
<div class="hstats">
<div class="hstat">Params: <b id="hP">~54k</b></div>
<div class="hstat">Loss: <b id="hL"></b></div>
<div class="hstat">Modo: <b id="hM">Congelado</b></div>
<div class="hstat">Steps: <b id="hS">0</b></div>
</div>
</div>
<div class="tabs-body">
<!-- ══ TAB TREINO ══ -->
<div class="pane on" id="treino">
<div class="pnl" style="width:330px;flex-shrink:0">
<div class="pnl-hd">📝 Dados & Config</div>
<div class="pnl-bd">
<div class="fld">
<label>Texto de Treino (PT-BR)</label>
<textarea id="txtTrain" rows="7">O gato preto dorme no sofá. Maria gosta de animais. O cachorro late alto. A menina corre feliz. Os pássaros cantam de manhã. O céu azul brilha. Pedro come pão todo dia. A casa é grande e bonita. O menino estuda muito.</textarea>
</div>
<div class="fld">
<label>Modo</label>
<div class="brow">
<button class="btn btn-cy btn-sm" onclick="setMode('frozen',this)">❄️ Congelado</button>
<button class="btn btn-gn btn-sm" onclick="setMode('incremental',this)">🌱 Incremental</button>
<button class="btn btn-pu btn-sm" onclick="setMode('zero',this)">🔥 Do Zero</button>
</div>
</div>
<div class="dvd"></div>
<div class="sec">⚙️ Hiperparâmetros</div>
<div class="fld"><label>Learning Rate</label>
<div class="sr"><input type="range" id="slLR" min="0.0001" max="0.05" step="0.0001" value="0.006"
oninput="sv('slLR','vLR')"><div class="sv" id="vLR">0.006</div></div>
</div>
<div class="fld"><label>Épocas</label>
<input type="number" id="inEp" min="1" max="500" value="60"></div>
<div class="fld"><label>Context (chars)</label>
<input type="number" id="inCtx" min="2" max="8" value="4"></div>
<div class="dvd"></div>
<div class="brow">
<button class="btn btn-pu" onclick="doTrain()">▶ Treinar</button>
<button class="btn btn-rd" onclick="doReset()">↺ Reset</button>
</div>
<div class="brow">
<button class="btn btn-gn btn-sm" onclick="doSave()">💾 Salvar</button>
<button class="btn btn-cy btn-sm" onclick="document.getElementById('fi').click()">📂 Carregar</button>
<input type="file" id="fi" accept=".json" onchange="doLoad(event)" style="display:none">
</div>
<div id="trnStatus" style="font-size:11px;color:var(--td)">Pronto.</div>
<div class="prog-wrap"><div class="prog-fill" id="pFill" style="width:0%"></div></div>
<div class="dvd"></div>
<div class="st"><span class="st-l">Modo</span><span class="st-v cyan" id="modeD">Congelado</span></div>
<div class="st"><span class="st-l">Steps</span><span class="st-v" id="stepsD">0</span></div>
<div class="st"><span class="st-l">Loss Atual</span><span class="st-v" id="lossD"></span></div>
<div class="st"><span class="st-l">Melhor Loss</span><span class="st-v" id="bestD"></span></div>
<div class="dvd"></div>
<div class="sec">⚡ Teste Rápido</div>
<input type="text" id="inTest" value="O gato ">
<div class="brow">
<button class="btn btn-cy btn-sm" onclick="doTest()">Continuar</button>
<button class="btn btn-gn btn-sm" onclick="doBeam()">🌿 Beam</button>
</div>
<div id="testOut" style="font-family:'Courier New',monospace;font-size:12px;color:var(--g2);
padding:7px;background:rgba(16,185,129,.05);border-radius:5px;min-height:24px;line-height:1.7"></div>
</div>
</div>
<div class="pnl" style="flex:1">
<div class="pnl-hd">📊 Progresso & Análise</div>
<div class="pnl-bd">
<div class="chart-wrap" style="height:185px">
<canvas id="cLoss" height="185"></canvas>
<div class="chart-lbl">CURVA DE LOSS</div>
</div>
<div class="brow" style="gap:6px">
<div class="st" style="flex:1"><span class="st-l">Época</span><span class="st-v" id="epD">0</span></div>
<div class="st" style="flex:1"><span class="st-l">Loss</span><span class="st-v" id="lD"></span></div>
<div class="st" style="flex:1"><span class="st-l">Melhor</span><span class="st-v" id="bD"></span></div>
</div>
<div class="dvd"></div>
<div class="sec">🌿 HashProj — Palavras no Espaço Latente</div>
<div class="tflow" id="hashViz" style="min-height:36px"></div>
<div class="dvd"></div>
<div class="sec">📜 Log</div>
<div id="trnLog" style="flex:1;overflow-y:auto;background:rgba(18,12,36,.5);border:1px solid var(--bdr);
border-radius:6px;padding:8px;font-size:11px;font-family:'Courier New',monospace;color:var(--td);
min-height:80px;max-height:220px"></div>
</div>
</div>
</div>
<!-- ══ TAB CHAT ══ -->
<div class="pane" id="chat">
<div class="pnl" style="flex:1">
<div class="pnl-hd">💬 Conversa com o Bloom</div>
<div class="pnl-bd" style="height:calc(100% - 46px)">
<div class="chat-log" id="chatLog"></div>
<div class="chat-in">
<input type="text" id="chatIn" placeholder="Digite aqui..." onkeydown="if(event.key==='Enter')doChat()">
<button class="btn btn-pu btn-sm" onclick="doChat()">Enviar</button>
</div>
</div>
</div>
<div class="pnl" style="width:280px;flex-shrink:0">
<div class="pnl-hd">⚙️ Estado Interno</div>
<div class="pnl-bd">
<div class="fld"><label>Temperatura</label>
<div class="sr"><input type="range" id="slCT" min="0.1" max="2.5" step="0.1" value="0.9"
oninput="sv('slCT','vCT')"><div class="sv" id="vCT">0.9</div></div>
</div>
<div class="fld"><label>Top-K</label>
<div class="sr"><input type="range" id="slTK" min="1" max="50" step="1" value="12"
oninput="sv('slTK','vTK')"><div class="sv" id="vTK">12</div></div>
</div>
<div class="fld"><label>Máx Chars</label>
<input type="number" id="inMaxC" min="20" max="400" value="120"></div>
<div class="dvd"></div>
<div class="sec">🌿 Seiva (Sap State)</div>
<div style="display:flex;align-items:center;gap:10px">
<div class="sap-ring" id="sapRing">
<span id="sapVal" style="font-size:9px;font-weight:700;font-family:'Courier New',monospace">0.00</span>
</div>
<div style="flex:1">
<div style="font-size:10px;color:var(--td);margin-bottom:3px">Memória Longa</div>
<div class="prog-wrap"><div class="prog-fill" id="sapBar" style="width:0%"></div></div>
</div>
</div>
<div class="dvd"></div>
<div class="sec">🎵 VTI — Ritmo Gramatical</div>
<div class="vti-grid" id="vtiGrid"></div>
<div class="dvd"></div>
<div class="sec">🎯 Próximas Previsões</div>
<div id="chatPreds"></div>
<div class="dvd"></div>
<div class="st"><span class="st-l">Msgs</span><span class="st-v cyan" id="msgC">0</span></div>
<div class="st"><span class="st-l">Topic Resets</span><span class="st-v org" id="topicR">0</span></div>
<div style="margin-top:auto;padding-top:8px">
<button class="btn btn-rd btn-sm" onclick="doClear()">🗑️ Limpar</button>
</div>
</div>
</div>
</div>
<!-- ══ TAB PLAYGROUND ══ -->
<div class="pane" id="playground">
<div style="display:flex;gap:14px;flex:1;min-height:0;overflow:hidden">
<!-- Difusão -->
<div class="pnl" style="flex:1.1">
<div class="pnl-hd">🔄 Difusão Mask-Predict</div>
<div class="pnl-bd">
<div class="fld"><label>Texto</label>
<textarea id="pgDT" rows="3">O gato preto dorme no sofá vermelho</textarea>
</div>
<div class="fld"><label>Máscara %</label>
<div class="sr"><input type="range" id="slMask" min="0.1" max="0.8" step="0.05" value="0.3"
oninput="sv('slMask','vMask',v=>(v*100|0)+'%')"><div class="sv" id="vMask">30%</div></div>
</div>
<div class="fld"><label>Iterações</label>
<div class="sr"><input type="range" id="slDI" min="1" max="8" step="1" value="3"
oninput="sv('slDI','vDI')"><div class="sv" id="vDI">3</div></div>
</div>
<div class="fld"><label>Temperatura</label>
<div class="sr"><input type="range" id="slDTemp" min="0.1" max="2" step="0.1" value="0.6"
oninput="sv('slDTemp','vDTemp')"><div class="sv" id="vDTemp">0.6</div></div>
</div>
<div class="brow">
<button class="btn btn-pu btn-sm" onclick="pgMask()">🎭 Mascarar</button>
<button class="btn btn-gn btn-sm" onclick="pgReco()">✨ Reconstruir</button>
<button class="btn btn-cy btn-sm" onclick="pgAnim()">▶ Animar</button>
</div>
<div class="dvd"></div>
<div class="sec">Original</div><div class="diff-box" id="pgOrig" style="min-height:36px"></div>
<div class="sec">Com Máscara</div><div class="diff-box" id="pgMaskViz" style="min-height:36px"></div>
<div class="sec">Reconstruído <span id="pgItN" style="color:var(--g2);font-family:'Courier New',monospace"></span></div>
<div class="diff-box" id="pgRec" style="min-height:36px"></div>
</div>
</div>
<!-- Temperatura de Atenção -->
<div class="pnl" style="flex:1">
<div class="pnl-hd">🌡️ Temperatura de Atenção</div>
<div class="pnl-bd">
<div class="fld"><label>Texto</label>
<input type="text" id="pgAT" value="O gato dorme"></div>
<div class="fld"><label>Temperatura Softmax</label>
<div class="sr"><input type="range" id="slAT" min="0.05" max="5" step="0.05" value="1"
oninput="sv('slAT','vAT');pgAttn()"><div class="sv" id="vAT">1.0</div></div>
</div>
<div class="brow" style="gap:5px">
<button class="btn btn-sm btn-rd" onclick="setAT(.1)">0.1</button>
<button class="btn btn-sm btn-gn" onclick="setAT(1)">1.0</button>
<button class="btn btn-sm btn-cy" onclick="setAT(5)">5.0</button>
</div>
<button class="btn btn-gn btn-sm" onclick="pgAttn()">🔍 Analisar</button>
<div class="st"><span class="st-l">Entropia</span><span class="st-v cyan" id="pgEnt"></span></div>
<div class="st"><span class="st-l">Confiança top-1</span><span class="st-v" id="pgConf"></span></div>
<div class="dvd"></div>
<div class="sec">Top-12 Próximos Chars</div>
<div id="pgAttnRes" style="flex:1;overflow-y:auto"></div>
<div style="margin-top:8px;padding:7px;background:rgba(130,80,255,.06);border-radius:6px;
font-size:10px;color:var(--td);line-height:1.7">
💡 <b style="color:var(--tc)">Temp 0.1</b>: focado, greedy<br>
💡 <b style="color:var(--tc)">Temp 1.0</b>: distribuição natural<br>
💡 <b style="color:var(--tc)">Temp 5.0</b>: exploratório, criativo
</div>
</div>
</div>
<!-- Probabilidade + Geração -->
<div class="pnl" style="flex:1">
<div class="pnl-hd">📈 Probabilidade & Geração</div>
<div class="pnl-bd">
<div class="sec">Probabilidade de Sequência</div>
<div class="fld"><label>Frase ①</label><input type="text" id="pgP1" value="O gato dorme"></div>
<div class="fld"><label>Frase ② (comparar)</label><input type="text" id="pgP2" value="O pedra canta"></div>
<button class="btn btn-pu btn-sm" onclick="pgProb()">📊 Calcular</button>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px">
<div class="st"><span class="st-l">LogP ①</span><span class="st-v" id="pgLP1"></span></div>
<div class="st"><span class="st-l">LogP ②</span><span class="st-v" id="pgLP2"></span></div>
<div class="st"><span class="st-l">PPL ①</span><span class="st-v cyan" id="pgPP1"></span></div>
<div class="st"><span class="st-l">PPL ②</span><span class="st-v cyan" id="pgPP2"></span></div>
</div>
<div id="pgProbRes" style="max-height:100px;overflow-y:auto"></div>
<div class="dvd"></div>
<div class="sec">⚡ Geração Livre</div>
<div class="fld"><label>Seed</label><input type="text" id="pgSeed" value="O "></div>
<div class="fld"><label>Temperatura</label>
<div class="sr"><input type="range" id="slGT" min="0.1" max="3" step="0.1" value="1"
oninput="sv('slGT','vGT')"><div class="sv" id="vGT">1.0</div></div>
</div>
<div class="fld"><label>Comprimento</label>
<div class="sr"><input type="range" id="slGL" min="20" max="300" step="10" value="100"
oninput="sv('slGL','vGL')"><div class="sv" id="vGL">100</div></div>
</div>
<div class="fld"><label>Bias Morfológico PT-BR</label>
<select id="selM">
<option value="none">Nenhum</option>
<option value="verb">Verbos (ar/er/ir)</option>
<option value="noun">Substantivos (a/o/ão)</option>
<option value="adj">Adjetivos (oso/vel/al)</option>
</select>
</div>
<button class="btn btn-cy btn-sm" onclick="pgGen()">⚡ Gerar</button>
<div id="pgGenOut" style="flex:1;overflow-y:auto;background:rgba(6,182,212,.05);
border:1px solid rgba(6,182,212,.2);border-radius:6px;padding:9px;
font-family:'Courier New',monospace;font-size:12px;color:var(--c2);
line-height:1.7;min-height:40px;margin-top:5px"></div>
</div>
</div>
</div>
<!-- Beam Sintático -->
<div class="pnl" style="height:170px;flex-shrink:0">
<div class="pnl-hd">🌿 Beam Sintático — Cat-First Routing</div>
<div class="pnl-bd" style="flex-direction:row;gap:12px;overflow:hidden">
<div style="display:flex;flex-direction:column;gap:8px;width:240px;flex-shrink:0">
<div class="fld"><label>Contexto</label>
<input type="text" id="pgBC" value="O gato preto"></div>
<div class="fld"><label>Beams</label>
<input type="number" id="pgBK" min="1" max="5" value="3"></div>
<button class="btn btn-pu btn-sm" onclick="pgBeam()">🌿 Executar Beam</button>
</div>
<div style="flex:1;overflow-y:auto">
<div class="sec" style="margin-bottom:6px">Candidatos por Categoria Prevista</div>
<div id="pgBeamRes"></div>
</div>
</div>
</div>
</div>
</div><!-- tabs-body -->
<script>
'use strict';
// ════════════════════════════════════════════════════════
// CONSTANTS & CATEGORIES
// ════════════════════════════════════════════════════════
const CATS=['substantivo','verbo','adjetivo','advérbio','artigo',
'pronome','preposição','conjunção','interjeição','numeral',
'pontuação','espaço','keyword','operador','identificador',
'especial','desconhecido','pausa'];
const CAT_HUE=[260,180,130,200,290,240,150,310,80,0,320,220,170,40,100,350,250,280];
const catColor=c=>`hsl(${CAT_HUE[c]||260},78%,63%)`;
const ARTS=new Set(['o','a','os','as','um','uma','uns','umas']);
const PRNS=new Set(['eu','tu','ele','ela','nós','eles','elas','você','vocês','me','te','se','nos','lhe']);
const PRPS=new Set(['de','do','da','dos','das','em','no','na','nos','nas','por','pelo','pela','para','pra','com','sobre','entre','até']);
const CONJ=new Set(['e','ou','mas','porém','pois','que','se','como','quando','porque','então','portanto','nem']);
const ADVS=new Set(['não','sim','já','ainda','muito','pouco','bem','mal','mais','menos','também','nunca','sempre','aqui','lá','hoje','agora','antes','depois','só','apenas']);
const INTJ=new Set(['ah','oh','ei','oi','olá','uau','eita','nossa','puxa']);
function classifyCat(w){
const t=(w||'').toLowerCase().trim();
if(!t)return 11;
if(/^\s+$/.test(w))return 11;
if(/^[.,!?;:]$/.test(t))return 9;
if(/^\d+([.,]\d+)?$/.test(t))return 9;
if(ARTS.has(t))return 4;
if(PRNS.has(t))return 5;
if(PRPS.has(t))return 6;
if(CONJ.has(t))return 7;
if(ADVS.has(t))return 3;
if(INTJ.has(t))return 8;
if(/(ar|er|ir|ou|ei|ando|endo|ado|ido)$/i.test(t)&&t.length>2)return 1;
if(/(oso|osa|vel|al|ável|ível|ente|ante)$/i.test(t))return 2;
return 0;
}
// ════════════════════════════════════════════════════════
// BLOOM LM — Char-Level Neural LM with Bloom Enhancements
// Melhorias implementadas:
// • HashProj (n-gram hashing, sem BPE)
// • Bipolar E_cat {-0.08, +0.08}
// • Seiva (Sap State) — memória lenta de longo prazo
// • VTI Topic Reset — flush ao detectar mudança de tópico
// • Backprop Esparsa — só B[cat] da categoria ativa
// • Bias Morfológico PT-BR na geração
// • Beam Sintático — prevê categoria antes da palavra
// ════════════════════════════════════════════════════════
class BloomLM {
constructor(ctxLen=4){
this.ctxLen=ctxLen;
this.ED=24; // char embed dim
this.HD=128; // hidden dim
this.V=128; // ASCII vocab
this.C=18; // categories
this.HS=256; // hash space
this.R=16; // sap/latent dim
this.trainSteps=0;
this.adamT=0;
this._initW();
this._initAdam();
this._initState();
}
// ── RANDOM UTILS ──
_rn(){
let u=0,v=0;
while(!u)u=Math.random();while(!v)v=Math.random();
return Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v);
}
_rm(r,c,s=.05){
return Array.from({length:r},()=>Float32Array.from({length:c},()=>this._rn()*s));
}
_zl(n){return new Float32Array(n);}
// ── WEIGHT INIT ──
_initW(){
const{ED,HD,V,C,HS,R,ctxLen}=this;
const iD=ctxLen*ED+ED; // input dim
this.E_char = this._rm(V,ED,.05); // char embeds
this.E_cat = Array.from({length:C},()=> // bipolar cat embeds
Float32Array.from({length:ED},()=>this._rn()>.0?.08:-.08));
this.HashProj = this._rm(HS,ED,.02); // n-gram→embed (HashProj)
this.W1 = this._rm(HD,iD,.05); // main hidden layer
this.b1 = this._zl(HD);
this.W_out = this._rm(V,HD,.03); // char output head
this.b_out = this._zl(V);
this.W_cat = this._rm(C,HD,.03); // category output head
this.W_sap = this._rm(R,HD,.01); // sap update proj
this.B = Array.from({length:C},()=>this._rm(HD,R,.02)); // category lenses
this.W_vti = this._rm(R,C,.02); // VTI gate
}
// ── ADAM INIT — zero-copy of all weights ──
_initAdam(){
const zc=x=>{
if(x instanceof Float32Array)return new Float32Array(x.length);
if(Array.isArray(x))return x.map(zc);
return null;
};
const keys=['E_char','E_cat','HashProj','W1','b1','W_out','b_out','W_cat','W_sap','B','W_vti'];
this.Am={};this.Av={};
for(const k of keys){this.Am[k]=zc(this[k]);this.Av[k]=zc(this[k]);}
}
// ── SINGLE ADAM STEP on Float32Array ──
_adam(p,m,v,g,lr){
const t=this.adamT;
const bc1=1-Math.pow(.9,t),bc2=1-Math.pow(.999,t);
for(let i=0;i<p.length;i++){
m[i]=.9*m[i]+.1*g[i];
v[i]=.999*v[i]+.001*g[i]*g[i];
p[i]-=lr*(m[i]/bc1)/(Math.sqrt(v[i]/bc2)+1e-8);
}
}
// ── STATE INIT ──
_initState(){
this.state={
sap:this._zl(this.R),
catBuffer:[],
prevVTI:this._zl(this.C),
topicResets:0,
};
}
// ── N-GRAM HASH (FNV-1a) — HashProj input ──
_ngHash(ctx){
const s=ctx.map(c=>String.fromCharCode((c||32)&127)).join('');
const v=new Float32Array(this.HS);
for(let n=2;n<=4;n++){
for(let i=0;i<=s.length-n;i++){
const ng=s.slice(i,i+n);
let h=2166136261>>>0;
for(let j=0;j<ng.length;j++){h^=ng.charCodeAt(j);h=Math.imul(h,16777619)>>>0;}
v[h%this.HS]+=1;
}
}
const nm=Math.sqrt(v.reduce((s,x)=>s+x*x,0))+1e-8;
for(let i=0;i<v.length;i++)v[i]/=nm;
return v;
}
_softmax(l,T=1){
let mx=-Infinity;for(const x of l)if(x>mx)mx=x;
const e=Array.from(l).map(x=>Math.exp((x-mx)/T));
const s=e.reduce((a,b)=>a+b,0);
return e.map(x=>x/s);
}
// ════════════════════════════════════════════════════════
// FORWARD PASS
// ════════════════════════════════════════════════════════
forward(ctx,cat){
const{ED,HD,V,C,HS,R,ctxLen}=this;
// 1. Build input: concat char embeds + cat embed (with HashProj correction)
const inp=new Float32Array(ctxLen*ED+ED);
for(let i=0;i<ctxLen;i++){
const ch=(ctx[i]||32)&127;
const e=this.E_char[ch];
for(let j=0;j<ED;j++)inp[i*ED+j]=e[j];
}
// HashProj: n-gram hashes → dense correction for cat embed slot
const ngH=this._ngHash(ctx);
const ngV=new Float32Array(ED);
for(let h=0;h<HS;h++){
if(Math.abs(ngH[h])<1e-6)continue;
const row=this.HashProj[h];
for(let j=0;j<ED;j++)ngV[j]+=ngH[h]*row[j];
}
// Bipolar cat embed + HashProj correction
const cE=this.E_cat[cat];
for(let j=0;j<ED;j++)inp[ctxLen*ED+j]=cE[j]+ngV[j]*.3;
// 2. Hidden layer: h = tanh(W1 @ inp + b1)
const pre=new Float32Array(HD);
const h=new Float32Array(HD);
for(let i=0;i<HD;i++){
let acc=this.b1[i];
const row=this.W1[i];
for(let j=0;j<inp.length;j++)acc+=row[j]*inp[j];
pre[i]=acc;h[i]=Math.tanh(acc);
}
// 3. VTI — update buffer & compute histogram
const{catBuffer,prevVTI,sap}=this.state;
catBuffer.push(cat);
if(catBuffer.length>24)catBuffer.shift();
const vti=new Float32Array(C);
for(const c of catBuffer)vti[c]+=1/catBuffer.length;
// VTI Topic Reset (melhoria TXT §3)
let topicReset=false;
const nn=Math.sqrt(vti.reduce((s,v)=>s+v*v,0))*Math.sqrt(prevVTI.reduce((s,v)=>s+v*v,0));
const cosDist=nn>.01?1-vti.reduce((s,v,i)=>s+v*prevVTI[i],0)/nn:0;
if(cosDist>.75){
for(let i=0;i<sap.length;i++)sap[i]*=.1; // flush sap 90%
topicReset=true;this.state.topicResets++;
}
for(let i=0;i<C;i++)prevVTI[i]=vti[i];
// 4. Seiva (Sap State) — slow-decay memory (melhoria TXT §2)
const sapUpd=new Float32Array(R);
for(let i=0;i<R;i++){
let acc=0;const row=this.W_sap[i];
for(let j=0;j<HD;j++)acc+=row[j]*h[j];
sapUpd[i]=acc;
}
const surprise=sapUpd.reduce((s,v,i)=>s+(v-sap[i])*(v-sap[i]),0);
if(surprise>.4)for(let i=0;i<R;i++)sap[i]=.999*sap[i]+.001*sapUpd[i];
const sapNorm=Math.sqrt(sap.reduce((s,v)=>s+v*v,0));
// 5. VTI gate: ritmo = sigmoid(W_vti @ vti)
const ritmo=new Float32Array(R);
for(let i=0;i<R;i++){
let acc=0;const row=this.W_vti[i];
for(let j=0;j<C;j++)acc+=row[j]*vti[j];
ritmo[i]=1/(1+Math.exp(-acc));
}
const latente=new Float32Array(R);
for(let i=0;i<R;i++)latente[i]=sap[i]*ritmo[i];
// 6. Bloom Lens: B[cat] @ latente → HD correction (ESPARSO)
const hB=new Float32Array(HD);
const B_c=this.B[cat];
for(let i=0;i<HD;i++){
let acc=0;const row=B_c[i];
for(let j=0;j<R;j++)acc+=row[j]*latente[j];
hB[i]=h[i]+acc*.3; // residual connection
}
// 7. Output heads
const lc=new Float32Array(V);
for(let i=0;i<V;i++){
let acc=this.b_out[i];const row=this.W_out[i];
for(let j=0;j<HD;j++)acc+=row[j]*hB[j];
lc[i]=acc;
}
const lcat=new Float32Array(C);
for(let i=0;i<C;i++){
let acc=0;const row=this.W_cat[i];
for(let j=0;j<HD;j++)acc+=row[j]*hB[j];
lcat[i]=acc;
}
return{logits_char:lc,logits_cat:lcat,h,hB,pre,inp,vti,ritmo,latente,topicReset,sapNorm,ngH};
}
// ════════════════════════════════════════════════════════
// SPARSE BACKPROP — só atualiza pesos do caminho ativo
// ════════════════════════════════════════════════════════
trainStep(ctx,cat,nextChar,nextCat,lr=.005){
this.adamT++;
const{ED,HD,V,C,HS,R,ctxLen}=this;
const res=this.forward(ctx,cat);
const{h,hB,pre,inp,ngH,latente}=res;
// Cross-entropy losses
const pC=this._softmax(res.logits_char);
const pCat=this._softmax(res.logits_cat);
const loss=-Math.log(pC[(nextChar)&127]+1e-10)-.3*Math.log(pCat[nextCat]+1e-10);
// Output gradients
const dLC=Float32Array.from(pC); dLC[(nextChar)&127]-=1;
const dLCat=Float32Array.from(pCat);dLCat[nextCat]-=.3;
// dL/dhB from W_out
const dHB=new Float32Array(HD);
for(let i=0;i<V;i++){
const dl=dLC[i];if(Math.abs(dl)<1e-7)continue;
const row=this.W_out[i];
for(let j=0;j<HD;j++)dHB[j]+=dl*row[j];
}
for(let i=0;i<C;i++){
const dl=dLCat[i];if(Math.abs(dl)<1e-7)continue;
const row=this.W_cat[i];
for(let j=0;j<HD;j++)dHB[j]+=dl*row[j];
}
// ── Update W_out, b_out (dense) ──
const gW=new Float32Array(HD);
for(let i=0;i<V;i++){
const dl=dLC[i];
for(let j=0;j<HD;j++)gW[j]=dl*hB[j];
this._adam(this.W_out[i],this.Am.W_out[i],this.Av.W_out[i],gW,lr);
}
this._adam(this.b_out,this.Am.b_out,this.Av.b_out,dLC,lr);
// ── Update W_cat (dense) ──
for(let i=0;i<C;i++){
const dl=dLCat[i];
for(let j=0;j<HD;j++)gW[j]=dl*hB[j];
this._adam(this.W_cat[i],this.Am.W_cat[i],this.Av.W_cat[i],gW,lr);
}
// ── SPARSE: B[cat] — só a lente da categoria ativa ──
const B_c=this.B[cat];const AmBc=this.Am.B[cat];const AvBc=this.Av.B[cat];
const gBrow=new Float32Array(R);
for(let i=0;i<HD;i++){
const dh=dHB[i]*.3;
for(let j=0;j<R;j++)gBrow[j]=dh*latente[j];
this._adam(B_c[i],AmBc[i],AvBc[i],gBrow,lr);
}
// dL/dh = dHB * (1-tanh²)
const dH=new Float32Array(HD);
for(let i=0;i<HD;i++)dH[i]=dHB[i]*(1-h[i]*h[i]);
// dL/dinp from W1
const dInp=new Float32Array(inp.length);
const gW1row=new Float32Array(inp.length);
for(let i=0;i<HD;i++){
const dp=dH[i];if(Math.abs(dp)<1e-8)continue;
const row=this.W1[i];
for(let j=0;j<inp.length;j++){dInp[j]+=dp*row[j];gW1row[j]=dp*inp[j];}
this._adam(this.W1[i],this.Am.W1[i],this.Av.W1[i],gW1row,lr);
}
this._adam(this.b1,this.Am.b1,this.Av.b1,dH,lr);
// ── SPARSE: E_char — só chars usados no ctx ──
const gE=new Float32Array(ED);
for(let ci=0;ci<ctxLen;ci++){
const ch=(ctx[ci]||32)&127;
for(let j=0;j<ED;j++)gE[j]=dInp[ci*ED+j];
this._adam(this.E_char[ch],this.Am.E_char[ch],this.Av.E_char[ch],gE,lr);
}
// ── SPARSE: E_cat — só categoria ativa (bipolar) ──
for(let j=0;j<ED;j++)gE[j]=dInp[ctxLen*ED+j];
this._adam(this.E_cat[cat],this.Am.E_cat[cat],this.Av.E_cat[cat],gE,lr);
// ── SPARSE: HashProj — só buckets ativados pelos n-gramas ──
const gHP=new Float32Array(ED);
for(let h=0;h<HS;h++){
if(Math.abs(ngH[h])<1e-6)continue;
for(let j=0;j<ED;j++)gHP[j]=dInp[ctxLen*ED+j]*ngH[h]*.3;
this._adam(this.HashProj[h],this.Am.HashProj[h],this.Av.HashProj[h],gHP,lr);
}
this.trainSteps++;
return loss;
}
// ════════════════════════════════════════════════════════
// GERAÇÃO com Bias Morfológico PT-BR
// ════════════════════════════════════════════════════════
generate(seedChars,seedCats,len,temp=1,topK=15,morph='none'){
const MB={
verb:[114,97,101,105,111,117], // r a e i o u
noun:[97,111,115,101,227], // a o s e ã
adj:[111,115,97,101,108] // o s a e l
};
const buf=[...seedChars],catBuf=[...seedCats],out=[];
for(let i=0;i<len;i++){
const ctx=buf.slice(-this.ctxLen);
while(ctx.length<this.ctxLen)ctx.unshift(32);
const cat=catBuf[catBuf.length-1]||0;
const{logits_char}=this.forward(ctx,cat);
const l=Array.from(logits_char);
if(morph!=='none'&&MB[morph]&&!buf.slice(-3).includes(32))
for(const c of MB[morph])if(c<128)l[c]+=1.5;
const probs=this._softmax(l,temp);
const idx=[...probs.keys()].sort((a,b)=>probs[b]-probs[a]).slice(0,topK);
let r=Math.random()*idx.reduce((s,j)=>s+probs[j],0),s=idx[0];
for(const j of idx){r-=probs[j];if(r<0){s=j;break;}}
buf.push(s);catBuf.push(s===32?0:classifyCat(String.fromCharCode(s)));
out.push(s);if(!s)break;
}
return out.map(c=>String.fromCharCode(c)).join('');
}
probSequence(chars){
let logP=0;const perChar=[],buf=new Array(this.ctxLen).fill(32);
for(let i=0;i<chars.length;i++){
const ctx=buf.slice(-this.ctxLen);
const cat=classifyCat(String.fromCharCode(chars[i]));
const{logits_char}=this.forward(ctx,cat);
const probs=this._softmax(logits_char);
const p=probs[chars[i]&127]+1e-10;
logP+=Math.log(p);
perChar.push({ch:String.fromCharCode(chars[i]),p,cat});
buf.push(chars[i]);
}
return{logP,ppl:Math.exp(-logP/Math.max(chars.length,1)),perChar};
}
// ════════════════════════════════════════════════════════
// BEAM SINTÁTICO — Cat-First Routing
// ════════════════════════════════════════════════════════
syntacticBeam(ctxChars,k=3,len=30,temp=.8){
const ctx=[...ctxChars].slice(-this.ctxLen);
while(ctx.length<this.ctxLen)ctx.unshift(32);
const cat=classifyCat(String.fromCharCode(ctx[ctx.length-1]||32));
const{logits_cat}=this.forward(ctx,cat);
const pCat=this._softmax(logits_cat);
const topCats=[...pCat.keys()].sort((a,b)=>pCat[b]-pCat[a]).slice(0,k);
return topCats.map(nc=>{
const cc=[...ctx];let text='',score=Math.log(pCat[nc]+1e-10);
for(let i=0;i<len;i++){
const c=cc.slice(-this.ctxLen);
const{logits_char}=this.forward(c,nc);
const probs=this._softmax(logits_char,temp);
const sorted=[...probs.keys()].sort((a,b)=>probs[b]-probs[a]).slice(0,10);
let r=Math.random(),s=sorted[0];
for(const j of sorted){r-=probs[j];if(r<0){s=j;break;}}
text+=String.fromCharCode(s);score+=Math.log(probs[s]+1e-10);
cc.push(s);if(i>5&&s===32)break;
}
return{cat:nc,catName:CATS[nc],prob:pCat[nc],text:text.trim(),score};
}).sort((a,b)=>b.score-a.score);
}
toJSON(){
const ser=x=>{
if(x instanceof Float32Array)return Array.from(x);
if(Array.isArray(x))return x.map(ser);
return x;
};
return{
ctxLen:this.ctxLen,trainSteps:this.trainSteps,
v:3, // version
weights:{
E_char:ser(this.E_char),E_cat:ser(this.E_cat),
HashProj:ser(this.HashProj),W1:ser(this.W1),b1:ser(this.b1),
W_out:ser(this.W_out),b_out:ser(this.b_out),W_cat:ser(this.W_cat),
W_sap:ser(this.W_sap),B:ser(this.B),W_vti:ser(this.W_vti)
}
};
}
static fromJSON(obj){
const m=new BloomLM(obj.ctxLen||4);
m.trainSteps=obj.trainSteps||0;
const w=obj.weights||obj.W;
if(w){
const toF=x=>Array.isArray(x[0])?x.map(r=>Array.isArray(r[0])?r.map(q=>Float32Array.from(q)):Float32Array.from(r)):Float32Array.from(x);
m.E_char=w.E_char.map(r=>Float32Array.from(r));
m.E_cat=w.E_cat.map(r=>Float32Array.from(r));
m.HashProj=w.HashProj.map(r=>Float32Array.from(r));
m.W1=w.W1.map(r=>Float32Array.from(r));
m.b1=Float32Array.from(w.b1);
m.W_out=w.W_out.map(r=>Float32Array.from(r));
m.b_out=Float32Array.from(w.b_out);
m.W_cat=w.W_cat.map(r=>Float32Array.from(r));
m.W_sap=w.W_sap.map(r=>Float32Array.from(r));
m.B=w.B.map(mat=>mat.map(r=>Float32Array.from(r)));
m.W_vti=w.W_vti.map(r=>Float32Array.from(r));
m._initAdam();
}
return m;
}
}
// ════════════════════════════════════════════════════════
// GLOBAL STATE
// ════════════════════════════════════════════════════════
let MODEL=new BloomLM(4);
let trainMode='frozen';
let lossHist=[];
let isTraining=false;
let chatCtx='';
let msgCount=0;
let diffState={chars:[],maskIdxs:[]};
const toAscii=s=>[...s].map(c=>c.charCodeAt(0)&127);
const escH=s=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
// ── UI HELPERS ──
function sv(id,valId,fmt){
const v=document.getElementById(id).value;
document.getElementById(valId).textContent=fmt?fmt(v):v;
}
function setAT(v){document.getElementById('slAT').value=v;sv('slAT','vAT');pgAttn();}
function openTab(name,el){
document.querySelectorAll('.pane').forEach(p=>p.classList.remove('on'));
document.querySelectorAll('.tb').forEach(b=>b.classList.remove('on'));
document.getElementById(name).classList.add('on');
el.classList.add('on');
if(name==='playground')setTimeout(pgAttn,80);
updateVTI();updateSap();
}
function setMode(m,el){
trainMode=m;
const labels={frozen:'Congelado',incremental:'Incremental',zero:'Do Zero'};
document.getElementById('modeD').textContent=labels[m];
document.getElementById('hM').textContent=labels[m];
}
function tlog(msg){
const b=document.getElementById('trnLog');
b.innerHTML+=`<span style="color:var(--td)">${new Date().toLocaleTimeString('pt-BR')}</span> ${msg}\n`;
b.scrollTop=b.scrollHeight;
}
// ── CANVAS LOSS CHART ──
function drawLoss(){
const canvas=document.getElementById('cLoss');
const P=canvas.parentElement;
canvas.width=P.offsetWidth||400;canvas.height=185;
const ctx=canvas.getContext('2d');
const W=canvas.width,H=canvas.height;
const pad={t:22,r:14,b:26,l:48};
ctx.clearRect(0,0,W,H);
if(lossHist.length<2){
ctx.fillStyle='rgba(130,80,255,.25)';ctx.font='11px Segoe UI';
ctx.textAlign='center';ctx.fillText('Inicie o treino para ver a curva',W/2,H/2);return;
}
const mx=Math.max(...lossHist),mn=Math.min(...lossHist),rng=mx-mn||.001;
const toX=i=>pad.l+(i/(lossHist.length-1))*(W-pad.l-pad.r);
const toY=v=>H-pad.b-(v-mn)/rng*(H-pad.t-pad.b);
ctx.strokeStyle='rgba(130,80,255,.1)';ctx.lineWidth=1;
for(let i=0;i<=4;i++){
const y=pad.t+i*(H-pad.t-pad.b)/4;
ctx.beginPath();ctx.moveTo(pad.l,y);ctx.lineTo(W-pad.r,y);ctx.stroke();
ctx.fillStyle='rgba(180,150,255,.45)';ctx.font='9px Courier New';
ctx.textAlign='right';ctx.fillText((mx-i*rng/4).toFixed(3),pad.l-3,y+3);
}
ctx.beginPath();ctx.moveTo(toX(0),H-pad.b);
lossHist.forEach((v,i)=>ctx.lineTo(toX(i),toY(v)));
ctx.lineTo(toX(lossHist.length-1),H-pad.b);ctx.closePath();
const gr=ctx.createLinearGradient(0,pad.t,0,H-pad.b);
gr.addColorStop(0,'rgba(147,51,234,.35)');gr.addColorStop(1,'rgba(147,51,234,.02)');
ctx.fillStyle=gr;ctx.fill();
ctx.beginPath();ctx.strokeStyle='#a855f7';ctx.lineWidth=2;ctx.lineJoin='round';
lossHist.forEach((v,i)=>i?ctx.lineTo(toX(i),toY(v)):ctx.moveTo(toX(i),toY(v)));ctx.stroke();
const lx=toX(lossHist.length-1),ly=toY(lossHist[lossHist.length-1]);
ctx.beginPath();ctx.arc(lx,ly,4,0,Math.PI*2);ctx.fillStyle='#c4b5fd';ctx.fill();
ctx.fillStyle='rgba(130,80,255,.4)';ctx.font='9px Segoe UI';ctx.textAlign='center';
ctx.fillText(`${lossHist.length} épocas | best: ${Math.min(...lossHist).toFixed(4)}`,W/2,H-5);
}
// ── TRAINING ──
async function doTrain(){
if(isTraining)return;
const text=document.getElementById('txtTrain').value;
if(text.length<5){alert('Texto muito curto!');return;}
const epochs=parseInt(document.getElementById('inEp').value)||60;
const lr=parseFloat(document.getElementById('slLR').value)||.006;
const ctxLen=parseInt(document.getElementById('inCtx').value)||4;
if(trainMode==='zero'||MODEL.ctxLen!==ctxLen){
MODEL=new BloomLM(ctxLen);lossHist=[];tlog('🔥 Modelo reiniciado do zero');
}
isTraining=true;
document.getElementById('trnStatus').textContent='🔄 Treinando...';
const chars=toAscii(text);
const cats=text.split('').map((c,i)=>{
let s=i;while(s>0&&text[s-1]!==' ')s--;
return classifyCat(text.slice(s,i+1).match(/[a-záàâãäéèêíìîóòôõúùûç]+/i)?.[0]||c);
});
let best=Infinity;
for(let ep=0;ep<epochs&&isTraining;ep++){
let epL=0,cnt=0;
for(let i=MODEL.ctxLen;i<chars.length-1;i++){
epL+=MODEL.trainStep(chars.slice(i-MODEL.ctxLen,i),cats[i],chars[i],cats[i+1],lr);cnt++;
}
const avg=epL/Math.max(cnt,1);
lossHist.push(avg);
if(avg<best)best=avg;
const pct=((ep+1)/epochs*100).toFixed(0);
document.getElementById('pFill').style.width=pct+'%';
document.getElementById('epD').textContent=ep+1;
document.getElementById('lD').textContent=avg.toFixed(4);
document.getElementById('bD').textContent=best.toFixed(4);
document.getElementById('lossD').textContent=avg.toFixed(4);
document.getElementById('bestD').textContent=best.toFixed(4);
document.getElementById('stepsD').textContent=MODEL.trainSteps;
document.getElementById('hL').textContent=avg.toFixed(3);
document.getElementById('hS').textContent=MODEL.trainSteps;
if((ep+1)%10===0||ep===0){
tlog(`Época ${ep+1}/${epochs} — Loss: ${avg.toFixed(4)}`);
drawLoss();updateHashViz(text);
}
await new Promise(r=>setTimeout(r,0));
}
isTraining=false;
document.getElementById('trnStatus').textContent='✅ Treino concluído!';
drawLoss();updateHashViz(text);
tlog(`✅ Melhor: ${best.toFixed(4)} | Steps: ${MODEL.trainSteps}`);
}
function doReset(){
if(!confirm('Resetar modelo?'))return;
MODEL=new BloomLM(parseInt(document.getElementById('inCtx').value)||4);
lossHist=[];isTraining=false;
document.getElementById('pFill').style.width='0%';
document.getElementById('trnLog').innerHTML='';
['epD','lD','bD'].forEach(id=>document.getElementById(id).textContent='—');
document.getElementById('stepsD').textContent='0';
document.getElementById('hL').textContent='—';document.getElementById('hS').textContent='0';
drawLoss();tlog('🔄 Modelo reiniciado');
}
function doSave(){
const a=document.createElement('a');
a.href=URL.createObjectURL(new Blob([JSON.stringify(MODEL.toJSON())],{type:'application/json'}));
a.download='mythra_bloom_v3.json';a.click();
}
function doLoad(e){
const f=e.target.files[0];if(!f)return;
const r=new FileReader();
r.onload=ev=>{
try{MODEL=BloomLM.fromJSON(JSON.parse(ev.target.result));
tlog(`✅ Modelo carregado (${MODEL.trainSteps} steps)`);}
catch(er){alert('Erro: '+er.message);}
};r.readAsText(f);
}
function doTest(){
const seed=document.getElementById('inTest').value;
const chars=toAscii(seed),cats=seed.split('').map(c=>classifyCat(c));
const gen=MODEL.generate(chars,cats,80,parseFloat(document.getElementById('slCT').value)||.9,15,'none');
document.getElementById('testOut').textContent=seed+gen;
}
function doBeam(){
const seed=document.getElementById('inTest').value;
const chars=toAscii(seed);
const beams=MODEL.syntacticBeam(chars,3,40,.85);
document.getElementById('testOut').innerHTML=beams.map((b,i)=>{
const col=catColor(b.cat);
return`<div style="margin:2px 0">
<span class="morph-tag" style="background:${col}22;border-color:${col}44;color:${col}">${b.catName}</span>
<span style="color:var(--td);font-size:10px;margin:0 5px">${(b.prob*100).toFixed(1)}%</span>
<span style="color:${i===0?'var(--g2)':'var(--tc)'};font-family:'Courier New',monospace">${escH(seed+b.text)}</span>
</div>`;
}).join('')||'<span style="color:var(--td)">Treine o modelo primeiro</span>';
}
function updateHashViz(text){
const words=[...new Set(text.split(/\s+/).filter(Boolean))].slice(0,18);
document.getElementById('hashViz').innerHTML=words.map(w=>{
const cat=classifyCat(w),col=catColor(cat);
return`<span class="tok" style="border-color:${col}44;background:${col}18">
<span>${escH(w)}</span><span class="tok-c" style="color:${col}">${CATS[cat].slice(0,4)}</span>
</span>`;
}).join('');
}
// ── CHAT ──
function addMsg(text,role){
const d=document.createElement('div');
d.className='msg '+role;d.textContent=text;
document.getElementById('chatLog').appendChild(d);
document.getElementById('chatLog').scrollTop=9999;
if(role!=='sys')msgCount++;
document.getElementById('msgC').textContent=msgCount;
}
function doChat(){
const el=document.getElementById('chatIn');
const text=el.value.trim();if(!text)return;el.value='';
addMsg(text,'u');chatCtx+=text+' ';
const chars=toAscii(chatCtx).slice(-MODEL.ctxLen*6);
const cats=chatCtx.slice(-MODEL.ctxLen*6).split('').map(c=>classifyCat(c));
const temp=parseFloat(document.getElementById('slCT').value)||.9;
const topK=parseInt(document.getElementById('slTK').value)||12;
const maxC=parseInt(document.getElementById('inMaxC').value)||120;
const gen=MODEL.generate(chars,cats,maxC,temp,topK,'none');
chatCtx+=gen+'\n';
addMsg(gen||'(treine o modelo para obter respostas)','b');
updateChatPreds(chars,cats,temp);updateVTI();updateSap();
document.getElementById('topicR').textContent=MODEL.state.topicResets;
}
function updateChatPreds(chars,cats,temp){
const ctx=[...chars].slice(-MODEL.ctxLen);
while(ctx.length<MODEL.ctxLen)ctx.unshift(32);
const cat=cats[cats.length-1]||0;
const{logits_char}=MODEL.forward(ctx,cat);
const probs=MODEL._softmax(logits_char,temp);
const top=[...probs.keys()].sort((a,b)=>probs[b]-probs[a]).slice(0,8);
const maxP=probs[top[0]]||1;
document.getElementById('chatPreds').innerHTML=top.map(ch=>{
const lbl=ch>=32&&ch<127?`"${String.fromCharCode(ch)}"`:String.fromCharCode(ch);
const w=(probs[ch]/maxP*100).toFixed(0);
const col=`hsl(${260+probs[ch]/maxP*80},80%,65%)`;
return`<div class="pb-item">
<div class="pb-lbl" style="color:${col}">${escH(lbl)}</div>
<div class="pb-track"><div class="pb-fill" style="width:${w}%;background:${col}99"></div></div>
<div class="pb-pct">${(probs[ch]*100).toFixed(1)}%</div>
</div>`;
}).join('');
}
function updateVTI(){
const vti=new Float32Array(18);
for(const c of MODEL.state.catBuffer)vti[c]+=1/Math.max(MODEL.state.catBuffer.length,1);
document.getElementById('vtiGrid').innerHTML=CATS.slice(0,6).map((name,i)=>{
const pct=Math.min(vti[i]*200,100);const col=catColor(i);
return`<div class="vti-col">
<div class="vti-bw"><div class="vti-bf" style="height:${pct}%;background:${col}aa;min-height:2px"></div></div>
<div class="vti-lb" style="color:${col}">${name.slice(0,4)}</div>
</div>`;
}).join('');
}
function updateSap(){
const s=MODEL.state.sap;
const n=Math.sqrt(s.reduce((a,v)=>a+v*v,0));
const pct=Math.min(n*60,100);
const hue=280-pct*1.2;const col=`hsl(${hue},80%,60%)`;
document.getElementById('sapVal').textContent=n.toFixed(2);
document.getElementById('sapBar').style.width=pct+'%';
const ring=document.getElementById('sapRing');
ring.style.borderColor=col;ring.style.color=col;
ring.style.boxShadow=pct>40?`0 0 12px ${col}55`:'none';
}
function doClear(){
document.getElementById('chatLog').innerHTML='';
chatCtx='';msgCount=0;
document.getElementById('msgC').textContent='0';
document.getElementById('chatPreds').innerHTML='';
MODEL._initState();updateVTI();updateSap();
addMsg('💬 Chat limpo. Estado do modelo resetado.','sys');
}
// ── PLAYGROUND — DIFUSÃO ──
function pgMask(){
const text=document.getElementById('pgDT').value;
diffState.chars=[...text];
const pct=parseFloat(document.getElementById('slMask').value);
const n=Math.max(1,Math.round(text.length*pct));
const all=[...Array(text.length).keys()];
diffState.maskIdxs=all.sort(()=>Math.random()-.5).slice(0,n).sort((a,b)=>a-b);
document.getElementById('pgOrig').innerHTML=diffState.chars.map(c=>`<span>${escH(c)}</span>`).join('');
document.getElementById('pgMaskViz').innerHTML=diffState.chars.map((c,i)=>
diffState.maskIdxs.includes(i)
?`<span class="tok mask" style="display:inline-block;padding:0 2px">▒</span>`
:`<span>${escH(c)}</span>`).join('');
document.getElementById('pgRec').innerHTML='<span style="color:var(--td)">→ Clique Reconstruir</span>';
document.getElementById('pgItN').textContent='';
}
function _recoStep(rec,temp){
for(const idx of diffState.maskIdxs){
const src=rec.slice(Math.max(0,idx-MODEL.ctxLen),idx);
const ctx=toAscii(src.join(''));
while(ctx.length<MODEL.ctxLen)ctx.unshift(32);
const cat=classifyCat(src.join(''));
const{logits_char}=MODEL.forward(ctx,cat);
const probs=MODEL._softmax(logits_char,temp);
let best=32,bestP=0;
for(let c=32;c<127;c++){if(probs[c]>bestP){bestP=probs[c];best=c;}}
rec[idx]=String.fromCharCode(best);
}
return rec;
}
function pgReco(){
if(!diffState.chars.length){pgMask();return;}
const iters=parseInt(document.getElementById('slDI').value)||3;
const temp=parseFloat(document.getElementById('slDTemp').value)||.6;
let rec=[...diffState.chars];
for(let it=0;it<iters;it++)rec=_recoStep(rec,temp);
document.getElementById('pgItN').textContent=`[${iters} iters]`;
document.getElementById('pgRec').innerHTML=rec.map((c,i)=>
diffState.maskIdxs.includes(i)
?`<span class="tok reco" style="display:inline-block;padding:0 2px">${escH(c)}</span>`
:`<span>${escH(c)}</span>`).join('');
}
async function pgAnim(){
if(!diffState.chars.length)pgMask();
await new Promise(r=>setTimeout(r,300));
const iters=parseInt(document.getElementById('slDI').value)||3;
const temp=parseFloat(document.getElementById('slDTemp').value)||.6;
const rec=[...diffState.chars];
const render=(maxI=-1)=>doc('pgRec').innerHTML=rec.map((c,i)=>
diffState.maskIdxs.includes(i)&&i<=maxI
?`<span class="tok reco" style="display:inline-block;padding:0 2px">${escH(c)}</span>`
:diffState.maskIdxs.includes(i)
?`<span class="tok mask" style="display:inline-block;padding:0 2px">▒</span>`
:`<span>${escH(c)}</span>`).join('');
for(let it=0;it<iters;it++){
document.getElementById('pgItN').textContent=`[iter ${it+1}/${iters}]`;
for(const idx of diffState.maskIdxs){
const src=rec.slice(Math.max(0,idx-MODEL.ctxLen),idx);
const ctx=toAscii(src.join(''));
while(ctx.length<MODEL.ctxLen)ctx.unshift(32);
const{logits_char}=MODEL.forward(ctx,classifyCat(src.join('')));
const probs=MODEL._softmax(logits_char,temp);
let best=32,bestP=0;
for(let c=32;c<127;c++){if(probs[c]>bestP){bestP=probs[c];best=c;}}
rec[idx]=String.fromCharCode(best);
render(idx);
await new Promise(r=>setTimeout(r,35));
}
await new Promise(r=>setTimeout(r,220));
}
}
const doc=id=>document.getElementById(id);
// ── PLAYGROUND — TEMPERATURA ──
function pgAttn(){
const text=document.getElementById('pgAT').value;if(!text)return;
const chars=toAscii(text);
const ctx=chars.slice(-MODEL.ctxLen);
while(ctx.length<MODEL.ctxLen)ctx.unshift(32);
const cat=classifyCat(text.split(' ').pop()||'');
const temp=parseFloat(document.getElementById('slAT').value)||1;
const{logits_char}=MODEL.forward(ctx,cat);
const probs=MODEL._softmax(logits_char,temp);
const top=[...probs.keys()].filter(c=>c>=32&&c<127).sort((a,b)=>probs[b]-probs[a]).slice(0,12);
const maxP=probs[top[0]]||1;
const ent=-probs.reduce((s,p)=>s+(p>1e-12?p*Math.log(p):0),0);
document.getElementById('pgEnt').textContent=ent.toFixed(4)+' nats';
document.getElementById('pgConf').textContent=(probs[top[0]]*100).toFixed(2)+'%';
document.getElementById('pgAttnRes').innerHTML=top.map(ch=>{
const lbl=`"${String.fromCharCode(ch).replace(/ /,'·')}"`;
const w=(probs[ch]/maxP*100).toFixed(0);
const hue=260+(probs[ch]/maxP)*80;
return`<div class="pb-item">
<div class="pb-lbl" style="color:hsl(${hue},82%,66%)">${escH(lbl)}</div>
<div class="pb-track"><div class="pb-fill" style="width:${w}%;background:hsl(${hue},76%,54%)99"></div></div>
<div class="pb-pct">${(probs[ch]*100).toFixed(2)}%</div>
</div>`;
}).join('');
}
// ── PLAYGROUND — PROBABILIDADE ──
function pgProb(){
const t1=document.getElementById('pgP1').value;
const t2=document.getElementById('pgP2').value;
const r1=MODEL.probSequence(toAscii(t1));
document.getElementById('pgLP1').textContent=r1.logP.toFixed(4);
document.getElementById('pgPP1').textContent=r1.ppl.toFixed(3);
if(t2){
const r2=MODEL.probSequence(toAscii(t2));
document.getElementById('pgLP2').textContent=r2.logP.toFixed(4);
document.getElementById('pgPP2').textContent=r2.ppl.toFixed(3);
}
const maxP=Math.max(...r1.perChar.map(x=>x.p));
document.getElementById('pgProbRes').innerHTML=r1.perChar.map(({ch,p})=>{
const col=p>.3?'var(--g2)':p>.06?'var(--c2)':'var(--r2)';
const w=(p/maxP*100).toFixed(0);
return`<div class="pb-item">
<div class="pb-lbl" style="color:${col}">"${escH(ch.replace(/ /,'·').replace(/\n/,'↵'))}"</div>
<div class="pb-track"><div class="pb-fill" style="width:${w}%;background:${col}88"></div></div>
<div class="pb-pct" style="color:${col}">${(p*100).toFixed(1)}%</div>
</div>`;
}).join('');
}
// ── PLAYGROUND — GERAÇÃO LIVRE ──
function pgGen(){
const seed=document.getElementById('pgSeed').value;
const temp=parseFloat(document.getElementById('slGT').value)||1;
const len=parseInt(document.getElementById('slGL').value)||100;
const morph=document.getElementById('selM').value;
const chars=toAscii(seed),cats=seed.split('').map(c=>classifyCat(c));
const gen=MODEL.generate(chars,cats,len,temp,20,morph);
document.getElementById('pgGenOut').textContent=seed+gen;
}
// ── PLAYGROUND — BEAM ──
function pgBeam(){
const ctx=document.getElementById('pgBC').value;
const k=parseInt(document.getElementById('pgBK').value)||3;
const beams=MODEL.syntacticBeam(toAscii(ctx),k,40,.85);
document.getElementById('pgBeamRes').innerHTML=beams.map((b,i)=>{
const col=catColor(b.cat);
return`<div class="beam-item ${i===0?'best':''}">
<span class="morph-tag" style="background:${col}22;border-color:${col}44;color:${col}">${b.catName}</span>
<span style="color:var(--td);margin:0 6px;font-size:10px">${(b.prob*100).toFixed(1)}% | score:${b.score.toFixed(1)}</span>
<span style="color:${i===0?'var(--g2)':'var(--tc)'};font-family:'Courier New',monospace;font-size:12px">${escH(ctx+b.text)}</span>
</div>`;
}).join('')||'<span style="color:var(--td);font-size:11px">Treine o modelo primeiro</span>';
}
// ── INIT ──
window.addEventListener('resize',()=>drawLoss());
updateVTI();updateSap();
setTimeout(drawLoss,200);
setTimeout(()=>{
addMsg('🌸 MythraMind Bloom v3 — IA sem BPE com HashProj, Seiva, VTI Reset e Backprop Esparsa. Treine na aba 📚 e converse aqui!','sys');
},150);
</script>
</body>
</html>