| <!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"> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <div class="pane" id="playground"> |
| <div style="display:flex;gap:14px;flex:1;min-height:0;overflow:hidden"> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| <script> |
| 'use strict'; |
| |
| |
| |
| |
| 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; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class BloomLM { |
| constructor(ctxLen=4){ |
| this.ctxLen=ctxLen; |
| this.ED=24; |
| this.HD=128; |
| this.V=128; |
| this.C=18; |
| this.HS=256; |
| this.R=16; |
| this.trainSteps=0; |
| this.adamT=0; |
| this._initW(); |
| this._initAdam(); |
| this._initState(); |
| } |
| |
| |
| _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);} |
| |
| |
| _initW(){ |
| const{ED,HD,V,C,HS,R,ctxLen}=this; |
| const iD=ctxLen*ED+ED; |
| |
| this.E_char = this._rm(V,ED,.05); |
| this.E_cat = Array.from({length:C},()=> |
| Float32Array.from({length:ED},()=>this._rn()>.0?.08:-.08)); |
| this.HashProj = this._rm(HS,ED,.02); |
| this.W1 = this._rm(HD,iD,.05); |
| this.b1 = this._zl(HD); |
| this.W_out = this._rm(V,HD,.03); |
| this.b_out = this._zl(V); |
| this.W_cat = this._rm(C,HD,.03); |
| this.W_sap = this._rm(R,HD,.01); |
| this.B = Array.from({length:C},()=>this._rm(HD,R,.02)); |
| this.W_vti = this._rm(R,C,.02); |
| } |
| |
| |
| _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]);} |
| } |
| |
| |
| _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); |
| } |
| } |
| |
| |
| _initState(){ |
| this.state={ |
| sap:this._zl(this.R), |
| catBuffer:[], |
| prevVTI:this._zl(this.C), |
| topicResets:0, |
| }; |
| } |
| |
| |
| _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(ctx,cat){ |
| const{ED,HD,V,C,HS,R,ctxLen}=this; |
| |
| |
| 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]; |
| } |
| |
| 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]; |
| } |
| |
| const cE=this.E_cat[cat]; |
| for(let j=0;j<ED;j++)inp[ctxLen*ED+j]=cE[j]+ngV[j]*.3; |
| |
| |
| 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); |
| } |
| |
| |
| 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; |
| |
| |
| 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; |
| topicReset=true;this.state.topicResets++; |
| } |
| for(let i=0;i<C;i++)prevVTI[i]=vti[i]; |
| |
| |
| 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)); |
| |
| |
| 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]; |
| |
| |
| 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; |
| } |
| |
| |
| 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}; |
| } |
| |
| |
| |
| |
| 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; |
| |
| |
| 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); |
| |
| |
| const dLC=Float32Array.from(pC); dLC[(nextChar)&127]-=1; |
| const dLCat=Float32Array.from(pCat);dLCat[nextCat]-=.3; |
| |
| |
| 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]; |
| } |
| |
| |
| 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); |
| |
| |
| 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); |
| } |
| |
| |
| 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); |
| } |
| |
| |
| const dH=new Float32Array(HD); |
| for(let i=0;i<HD;i++)dH[i]=dHB[i]*(1-h[i]*h[i]); |
| |
| |
| 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); |
| |
| |
| 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); |
| } |
| |
| |
| 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); |
| |
| |
| 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; |
| } |
| |
| |
| |
| |
| generate(seedChars,seedCats,len,temp=1,topK=15,morph='none'){ |
| const MB={ |
| verb:[114,97,101,105,111,117], |
| noun:[97,111,115,101,227], |
| adj:[111,115,97,101,108] |
| }; |
| 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}; |
| } |
| |
| |
| |
| |
| 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, |
| 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; |
| } |
| } |
| |
| |
| |
| |
| 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,'&').replace(/</g,'<').replace(/>/g,'>'); |
| |
| |
| 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; |
| } |
| |
| |
| 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); |
| } |
| |
| |
| 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(''); |
| } |
| |
| |
| 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'); |
| } |
| |
| |
| 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); |
| |
| |
| 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(''); |
| } |
| |
| |
| 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(''); |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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>'; |
| } |
| |
| |
| 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> |
|
|