| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <meta name="theme-color" content="#000000"> |
| | <meta name="description" content="Micro Distillery: GRPO + VAE Enhanced Training System"> |
| | <title>[mD] MICRO DISTILLERY | webXOS 2025</title> |
| | |
| | |
| | <link rel="manifest" href="manifest.json"> |
| | |
| | |
| | <link rel="apple-touch-icon" href="icon-192.png"> |
| | |
| | <style> |
| | :root { |
| | --bg: #000000; |
| | --text: #00ff00; |
| | --border: #008000; |
| | --accent: #00ffff; |
| | --error: #ff0000; |
| | --success: #00ff00; |
| | --warning: #ffff00; |
| | --distill: #00ff9d; |
| | --quantize: #ff00ff; |
| | --teacher: #ff6b00; |
| | --upload: #00ccff; |
| | --export: #9d00ff; |
| | --panel-bg: rgba(0, 20, 0, 0.1); |
| | --grpo: #ff00ff; |
| | --vae: #00ddff; |
| | --cache: #ff9900; |
| | --mask: #aa00ff; |
| | --sandbox: #00aa88; |
| | } |
| | |
| | * { |
| | margin: 0; |
| | padding: 0; |
| | box-sizing: border-box; |
| | font-family: 'Courier New', monospace; |
| | } |
| | |
| | body { |
| | background: var(--bg); |
| | color: var(--text); |
| | padding: 10px; |
| | font-size: 12px; |
| | line-height: 1.3; |
| | max-width: 1600px; |
| | margin: 0 auto; |
| | min-height: 100vh; |
| | overflow-x: hidden; |
| | } |
| | |
| | .header { |
| | padding: 10px 0; |
| | margin-bottom: 15px; |
| | text-align: center; |
| | border-bottom: 1px solid var(--border); |
| | } |
| | |
| | .title { |
| | font-size: 18px; |
| | color: var(--text); |
| | margin-bottom: 5px; |
| | } |
| | |
| | .subtitle { |
| | font-size: 12px; |
| | color: var(--accent); |
| | margin-bottom: 10px; |
| | } |
| | |
| | .header-controls { |
| | display: flex; |
| | justify-content: center; |
| | gap: 10px; |
| | margin-top: 10px; |
| | } |
| | |
| | .main-grid { |
| | display: grid; |
| | grid-template-columns: 350px 350px 1fr; |
| | gap: 15px; |
| | margin-bottom: 15px; |
| | } |
| | |
| | @media (max-width: 1200px) { |
| | .main-grid { |
| | grid-template-columns: 1fr; |
| | } |
| | } |
| | |
| | .panel { |
| | border: 1px solid var(--border); |
| | padding: 15px; |
| | background: var(--panel-bg); |
| | border-radius: 3px; |
| | min-height: 500px; |
| | } |
| | |
| | .panel-title { |
| | color: var(--accent); |
| | margin-bottom: 10px; |
| | padding-bottom: 5px; |
| | border-bottom: 1px solid var(--border); |
| | font-size: 12px; |
| | font-weight: bold; |
| | } |
| | |
| | .section { |
| | padding: 10px; |
| | border-radius: 3px; |
| | margin: 10px 0; |
| | } |
| | |
| | .grpo-section { |
| | border: 1px solid var(--grpo); |
| | background: rgba(255, 0, 255, 0.05); |
| | } |
| | |
| | .vae-section { |
| | border: 1px solid var(--vae); |
| | background: rgba(0, 221, 255, 0.05); |
| | } |
| | |
| | .cache-section { |
| | border: 1px solid var(--cache); |
| | background: rgba(255, 153, 0, 0.05); |
| | } |
| | |
| | .mask-section { |
| | border: 1px solid var(--mask); |
| | background: rgba(170, 0, 255, 0.05); |
| | } |
| | |
| | .sandbox-section { |
| | border: 1px solid var(--sandbox); |
| | background: rgba(0, 170, 136, 0.05); |
| | } |
| | |
| | .slider-container { |
| | margin: 8px 0; |
| | } |
| | |
| | .slider-container label { |
| | display: block; |
| | font-size: 11px; |
| | color: var(--accent); |
| | margin-bottom: 3px; |
| | } |
| | |
| | .slider-value { |
| | float: right; |
| | font-size: 10px; |
| | color: #008000; |
| | } |
| | |
| | input[type="range"] { |
| | width: 100%; |
| | height: 4px; |
| | background: #111; |
| | border-radius: 2px; |
| | outline: none; |
| | -webkit-appearance: none; |
| | } |
| | |
| | input[type="range"]::-webkit-slider-thumb { |
| | -webkit-appearance: none; |
| | width: 12px; |
| | height: 12px; |
| | background: var(--grpo); |
| | border-radius: 50%; |
| | cursor: pointer; |
| | } |
| | |
| | .action-btn { |
| | background: #001100; |
| | border: 1px solid var(--border); |
| | color: var(--text); |
| | padding: 10px; |
| | font-size: 12px; |
| | cursor: pointer; |
| | border-radius: 3px; |
| | font-family: 'Courier New', monospace; |
| | transition: all 0.2s; |
| | width: 100%; |
| | margin: 5px 0; |
| | } |
| | |
| | .action-btn:hover { |
| | background: #002200; |
| | border-color: var(--accent); |
| | box-shadow: 0 0 5px var(--accent); |
| | } |
| | |
| | .btn-grpo { |
| | border-color: var(--grpo); |
| | color: var(--grpo); |
| | } |
| | |
| | .btn-vae { |
| | border-color: var(--vae); |
| | color: var(--vae); |
| | } |
| | |
| | .btn-sandbox { |
| | border-color: var(--sandbox); |
| | color: var(--sandbox); |
| | } |
| | |
| | .btn-export { |
| | border-color: var(--export); |
| | color: var(--export); |
| | padding: 8px 15px; |
| | width: auto; |
| | } |
| | |
| | #terminal { |
| | height: 300px; |
| | overflow-y: auto; |
| | background: #000; |
| | border: 1px solid var(--border); |
| | padding: 10px; |
| | font-size: 11px; |
| | font-family: 'Courier New', monospace; |
| | line-height: 1.3; |
| | margin-bottom: 15px; |
| | } |
| | |
| | .log-entry { |
| | margin-bottom: 2px; |
| | padding: 1px 0; |
| | border-bottom: 1px solid rgba(0, 255, 0, 0.05); |
| | } |
| | |
| | .log-grpo { color: var(--grpo); } |
| | .log-vae { color: var(--vae); } |
| | .log-cache { color: var(--cache); } |
| | .log-mask { color: var(--mask); } |
| | .log-sandbox { color: var(--sandbox); } |
| | .log-error { color: var(--error); } |
| | .log-success { color: var(--success); } |
| | .log-export { color: var(--export); } |
| | |
| | .metrics-grid { |
| | display: grid; |
| | grid-template-columns: repeat(3, 1fr); |
| | gap: 5px; |
| | margin: 10px 0; |
| | } |
| | |
| | .metric-card { |
| | background: rgba(0, 30, 0, 0.2); |
| | border: 1px solid var(--border); |
| | padding: 5px; |
| | border-radius: 2px; |
| | text-align: center; |
| | font-size: 10px; |
| | } |
| | |
| | .metric-value { |
| | font-size: 12px; |
| | font-weight: bold; |
| | } |
| | |
| | .metric-grpo { color: var(--grpo); } |
| | .metric-vae { color: var(--vae); } |
| | .metric-cache { color: var(--cache); } |
| | .metric-mask { color: var(--mask); } |
| | |
| | .sandbox-container { |
| | margin-top: 15px; |
| | border: 1px solid var(--border); |
| | border-radius: 3px; |
| | overflow: hidden; |
| | } |
| | |
| | .sandbox-header { |
| | background: rgba(0, 30, 0, 0.3); |
| | padding: 8px; |
| | border-bottom: 1px solid var(--border); |
| | color: var(--sandbox); |
| | font-weight: bold; |
| | } |
| | |
| | .sandbox-content { |
| | padding: 10px; |
| | background: rgba(0, 0, 0, 0.5); |
| | height: 150px; |
| | overflow-y: auto; |
| | font-family: monospace; |
| | font-size: 11px; |
| | white-space: pre-wrap; |
| | } |
| | |
| | .sandbox-input { |
| | display: flex; |
| | border-top: 1px solid var(--border); |
| | background: rgba(0, 30, 0, 0.2); |
| | } |
| | |
| | .sandbox-input input { |
| | flex: 1; |
| | background: transparent; |
| | color: var(--text); |
| | border: none; |
| | padding: 8px; |
| | font-family: monospace; |
| | font-size: 11px; |
| | } |
| | |
| | .sandbox-input button { |
| | background: rgba(0, 170, 136, 0.2); |
| | border: none; |
| | border-left: 1px solid var(--border); |
| | color: var(--sandbox); |
| | padding: 8px 15px; |
| | cursor: pointer; |
| | font-size: 11px; |
| | } |
| | |
| | .token-display { |
| | display: flex; |
| | flex-wrap: wrap; |
| | gap: 3px; |
| | margin: 10px 0; |
| | padding: 10px; |
| | background: rgba(0, 0, 0, 0.3); |
| | border-radius: 3px; |
| | max-height: 100px; |
| | overflow-y: auto; |
| | } |
| | |
| | .token { |
| | padding: 2px 5px; |
| | background: rgba(0, 255, 0, 0.1); |
| | border: 1px solid rgba(0, 255, 0, 0.3); |
| | border-radius: 2px; |
| | font-size: 9px; |
| | font-family: monospace; |
| | } |
| | |
| | .token.cached { |
| | background: rgba(255, 153, 0, 0.2); |
| | border-color: var(--cache); |
| | } |
| | |
| | .token.masked { |
| | background: rgba(170, 0, 255, 0.2); |
| | border-color: var(--mask); |
| | opacity: 0.6; |
| | } |
| | |
| | .progress-container { |
| | margin: 10px 0; |
| | } |
| | |
| | .progress-bar { |
| | height: 8px; |
| | background: #111; |
| | border: 1px solid var(--border); |
| | overflow: hidden; |
| | border-radius: 2px; |
| | } |
| | |
| | .progress-fill { |
| | height: 100%; |
| | background: linear-gradient(90deg, #008000, #00ff00); |
| | width: 0%; |
| | transition: width 0.3s; |
| | } |
| | |
| | .progress-text { |
| | text-align: center; |
| | margin-top: 3px; |
| | font-size: 10px; |
| | color: #008000; |
| | } |
| | |
| | .hidden { |
| | display: none !important; |
| | } |
| | |
| | .latent-space { |
| | display: grid; |
| | grid-template-columns: repeat(4, 1fr); |
| | gap: 5px; |
| | margin: 10px 0; |
| | } |
| | |
| | .latent-dim { |
| | padding: 5px; |
| | background: rgba(0, 221, 255, 0.1); |
| | border: 1px solid var(--vae); |
| | border-radius: 2px; |
| | text-align: center; |
| | font-size: 9px; |
| | } |
| | |
| | .latent-value { |
| | font-size: 10px; |
| | font-weight: bold; |
| | color: var(--vae); |
| | display: block; |
| | margin-top: 2px; |
| | } |
| | |
| | .status-indicator { |
| | display: inline-block; |
| | width: 8px; |
| | height: 8px; |
| | border-radius: 50%; |
| | margin-right: 5px; |
| | } |
| | |
| | .status-active { background: var(--success); } |
| | .status-inactive { background: #333; } |
| | .status-error { background: var(--error); } |
| | |
| | select { |
| | width: 100%; |
| | background: #001100; |
| | color: #00ff00; |
| | border: 1px solid var(--border); |
| | padding: 6px; |
| | border-radius: 2px; |
| | font-size: 11px; |
| | font-family: 'Courier New', monospace; |
| | margin: 5px 0; |
| | } |
| | |
| | |
| | .export-modal { |
| | position: fixed; |
| | top: 0; |
| | left: 0; |
| | width: 100%; |
| | height: 100%; |
| | background: rgba(0, 0, 0, 0.85); |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | z-index: 1000; |
| | } |
| | |
| | .export-modal.hidden { |
| | display: none; |
| | } |
| | |
| | .export-window { |
| | background: var(--bg); |
| | border: 2px solid var(--export); |
| | border-radius: 5px; |
| | width: 800px; |
| | max-width: 90%; |
| | height: 600px; |
| | display: flex; |
| | flex-direction: column; |
| | box-shadow: 0 0 30px rgba(157, 0, 255, 0.3); |
| | } |
| | |
| | .export-header { |
| | background: rgba(157, 0, 255, 0.1); |
| | padding: 15px; |
| | border-bottom: 1px solid var(--export); |
| | color: var(--export); |
| | font-size: 14px; |
| | font-weight: bold; |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | } |
| | |
| | .close-export { |
| | background: none; |
| | border: 1px solid var(--export); |
| | color: var(--export); |
| | padding: 5px 10px; |
| | cursor: pointer; |
| | border-radius: 3px; |
| | font-family: 'Courier New', monospace; |
| | font-size: 12px; |
| | } |
| | |
| | .close-export:hover { |
| | background: rgba(157, 0, 255, 0.2); |
| | } |
| | |
| | .export-body { |
| | display: flex; |
| | flex: 1; |
| | overflow: hidden; |
| | } |
| | |
| | .export-sidebar { |
| | width: 200px; |
| | background: rgba(0, 20, 0, 0.2); |
| | border-right: 1px solid var(--border); |
| | padding: 15px; |
| | overflow-y: auto; |
| | } |
| | |
| | .export-main { |
| | flex: 1; |
| | padding: 15px; |
| | overflow-y: auto; |
| | } |
| | |
| | .export-format { |
| | margin-bottom: 20px; |
| | } |
| | |
| | .format-option { |
| | padding: 8px; |
| | border: 1px solid var(--border); |
| | margin: 5px 0; |
| | border-radius: 3px; |
| | cursor: pointer; |
| | transition: all 0.2s; |
| | font-size: 11px; |
| | } |
| | |
| | .format-option:hover { |
| | border-color: var(--export); |
| | background: rgba(157, 0, 255, 0.1); |
| | } |
| | |
| | .format-option.selected { |
| | border-color: var(--export); |
| | background: rgba(157, 0, 255, 0.2); |
| | } |
| | |
| | .file-browser { |
| | margin-top: 20px; |
| | } |
| | |
| | .file-item { |
| | padding: 5px 10px; |
| | border-bottom: 1px solid rgba(0, 255, 0, 0.1); |
| | cursor: pointer; |
| | font-size: 11px; |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | } |
| | |
| | .file-item:hover { |
| | background: rgba(0, 255, 0, 0.05); |
| | } |
| | |
| | .file-item.selected { |
| | background: rgba(157, 0, 255, 0.1); |
| | } |
| | |
| | .file-icon { |
| | color: var(--export); |
| | font-size: 12px; |
| | } |
| | |
| | .file-content { |
| | background: rgba(0, 0, 0, 0.5); |
| | border: 1px solid var(--border); |
| | border-radius: 3px; |
| | padding: 15px; |
| | margin-top: 15px; |
| | max-height: 300px; |
| | overflow-y: auto; |
| | font-family: monospace; |
| | font-size: 10px; |
| | white-space: pre-wrap; |
| | } |
| | |
| | .export-footer { |
| | padding: 15px; |
| | border-top: 1px solid var(--border); |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | background: rgba(0, 20, 0, 0.2); |
| | } |
| | |
| | .export-btn { |
| | background: rgba(157, 0, 255, 0.2); |
| | border: 1px solid var(--export); |
| | color: var(--export); |
| | padding: 10px 20px; |
| | cursor: pointer; |
| | border-radius: 3px; |
| | font-family: 'Courier New', monospace; |
| | font-size: 12px; |
| | transition: all 0.2s; |
| | } |
| | |
| | .export-btn:hover { |
| | background: rgba(157, 0, 255, 0.3); |
| | box-shadow: 0 0 10px rgba(157, 0, 255, 0.5); |
| | } |
| | |
| | .export-info { |
| | font-size: 11px; |
| | color: var(--accent); |
| | } |
| | |
| | .file-size { |
| | color: var(--cache); |
| | font-size: 10px; |
| | margin-left: auto; |
| | } |
| | |
| | .export-progress { |
| | margin-top: 15px; |
| | padding: 10px; |
| | background: rgba(0, 20, 0, 0.3); |
| | border-radius: 3px; |
| | border: 1px solid var(--border); |
| | } |
| | |
| | .export-progress.hidden { |
| | display: none; |
| | } |
| | |
| | .progress-step { |
| | margin: 5px 0; |
| | font-size: 11px; |
| | display: flex; |
| | align-items: center; |
| | gap: 10px; |
| | } |
| | |
| | .step-indicator { |
| | width: 20px; |
| | height: 20px; |
| | border-radius: 50%; |
| | background: #333; |
| | border: 1px solid var(--border); |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | font-size: 10px; |
| | } |
| | |
| | .step-indicator.active { |
| | background: var(--export); |
| | border-color: var(--export); |
| | } |
| | |
| | .step-indicator.completed { |
| | background: var(--success); |
| | border-color: var(--success); |
| | } |
| | |
| | .binary-warning { |
| | color: var(--warning); |
| | font-size: 10px; |
| | margin-top: 5px; |
| | padding: 5px; |
| | background: rgba(255, 255, 0, 0.1); |
| | border-radius: 2px; |
| | } |
| | |
| | |
| | .install-prompt { |
| | position: fixed; |
| | bottom: 20px; |
| | right: 20px; |
| | background: rgba(0, 20, 0, 0.95); |
| | border: 1px solid var(--border); |
| | border-radius: 5px; |
| | padding: 15px; |
| | z-index: 1001; |
| | max-width: 300px; |
| | box-shadow: 0 0 20px rgba(0, 255, 0, 0.3); |
| | } |
| | |
| | .install-prompt.hidden { |
| | display: none; |
| | } |
| | |
| | .install-buttons { |
| | display: flex; |
| | gap: 10px; |
| | margin-top: 10px; |
| | } |
| | |
| | .install-btn { |
| | flex: 1; |
| | padding: 8px; |
| | border: 1px solid var(--success); |
| | background: rgba(0, 255, 0, 0.1); |
| | color: var(--success); |
| | cursor: pointer; |
| | border-radius: 3px; |
| | font-size: 11px; |
| | } |
| | |
| | .install-btn:hover { |
| | background: rgba(0, 255, 0, 0.2); |
| | } |
| | |
| | .install-btn.dismiss { |
| | border-color: var(--error); |
| | color: var(--error); |
| | background: rgba(255, 0, 0, 0.1); |
| | } |
| | |
| | |
| | .offline-indicator { |
| | position: fixed; |
| | top: 10px; |
| | right: 10px; |
| | background: rgba(255, 0, 0, 0.2); |
| | border: 1px solid var(--error); |
| | color: var(--error); |
| | padding: 5px 10px; |
| | border-radius: 3px; |
| | font-size: 10px; |
| | z-index: 1002; |
| | } |
| | |
| | .offline-indicator.hidden { |
| | display: none; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="header"> |
| | <div class="title">[mD] MICRO DISTILLERY | webXOS 2025</div> |
| | <div class="subtitle"> |
| | GRPO • Interpreter Feedback Masking • KV-Cache Reuse • VAE Filtering |
| | </div> |
| | <div class="header-controls"> |
| | <button id="btn-show-export" class="action-btn btn-export"> |
| | 🚀 EXPORT TRAINED MODEL |
| | </button> |
| | </div> |
| | </div> |
| | |
| | <div class="main-grid"> |
| | |
| | <div class="panel"> |
| | <div class="panel-title">GRPO CONFIGURATION</div> |
| | |
| | <div class="section grpo-section"> |
| | <div class="slider-container"> |
| | <label>Group Size <span class="slider-value" id="group-size-value">8</span></label> |
| | <input type="range" id="group-size" min="4" max="32" step="2" value="8"> |
| | </div> |
| | |
| | <div class="slider-container"> |
| | <label>KL Penalty <span class="slider-value" id="kl-penalty-value">0.10</span></label> |
| | <input type="range" id="kl-penalty" min="0.01" max="0.5" step="0.01" value="0.1"> |
| | </div> |
| | |
| | <div class="slider-container"> |
| | <label>Advantage Clip <span class="slider-value" id="advantage-clip-value">2.0</span></label> |
| | <input type="range" id="advantage-clip" min="0.1" max="5.0" step="0.1" value="2.0"> |
| | </div> |
| | |
| | <select id="policy-arch"> |
| | <option value="gpt2-small">GPT-2 Small (117M)</option> |
| | <option value="gpt2-medium">GPT-2 Medium (345M)</option> |
| | <option value="distilgpt2">DistilGPT-2 (82M)</option> |
| | <option value="tiny-custom">Tiny Custom (42M)</option> |
| | </select> |
| | </div> |
| | |
| | <div class="section cache-section"> |
| | <div class="slider-container"> |
| | <label>KV-Cache Size <span class="slider-value" id="cache-size-value">512</span></label> |
| | <input type="range" id="cache-size" min="128" max="2048" step="128" value="512"> |
| | </div> |
| | |
| | <div class="slider-container"> |
| | <label>Cache Reuse Threshold <span class="slider-value" id="cache-threshold-value">0.90</span></label> |
| | <input type="range" id="cache-threshold" min="0.7" max="0.99" step="0.01" value="0.9"> |
| | </div> |
| | |
| | <div style="font-size: 10px; color: var(--cache); margin-top: 5px;"> |
| | <span class="status-indicator" id="cache-status"></span> |
| | KV-Cache: <span id="cache-status-text">Inactive</span> |
| | </div> |
| | </div> |
| | |
| | <button id="btn-init-grpo" class="action-btn btn-grpo"> |
| | 🚀 INITIALIZE GRPO SYSTEM |
| | </button> |
| | <button id="btn-train-grpo" class="action-btn btn-grpo" style="display: none;"> |
| | 🏃 START GRPO TRAINING |
| | </button> |
| | <button id="btn-stop-grpo" class="action-btn" style="display: none; background: rgba(255,0,0,0.1);"> |
| | ⏹️ STOP TRAINING |
| | </button> |
| | |
| | <div class="metrics-grid"> |
| | <div class="metric-card"> |
| | <div>Groups</div> |
| | <div class="metric-value metric-grpo" id="metric-groups">0</div> |
| | </div> |
| | <div class="metric-card"> |
| | <div>Cache Hit</div> |
| | <div class="metric-value metric-cache" id="metric-cache-hit">0%</div> |
| | </div> |
| | <div class="metric-card"> |
| | <div>Training Steps</div> |
| | <div class="metric-value metric-grpo" id="metric-steps">0</div> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | |
| | <div class="panel"> |
| | <div class="panel-title">VAE FILTER & MASKING</div> |
| | |
| | <div class="section vae-section"> |
| | <div class="slider-container"> |
| | <label>Latent Dimension <span class="slider-value" id="latent-dim-value">32</span></label> |
| | <input type="range" id="latent-dim" min="8" max="128" step="8" value="32"> |
| | </div> |
| | |
| | <div class="slider-container"> |
| | <label>Beta (KL Weight) <span class="slider-value" id="vae-beta-value">0.010</span></label> |
| | <input type="range" id="vae-beta" min="0.001" max="0.1" step="0.001" value="0.01"> |
| | </div> |
| | |
| | <div class="slider-container"> |
| | <label>Filter Threshold <span class="slider-value" id="filter-threshold-value">0.70</span></label> |
| | <input type="range" id="filter-threshold" min="0.1" max="0.9" step="0.05" value="0.7"> |
| | </div> |
| | |
| | <button id="btn-train-vae" class="action-btn btn-vae"> |
| | 🌀 TRAIN VAE FILTER |
| | </button> |
| | </div> |
| | |
| | <div class="section mask-section"> |
| | <div class="slider-container"> |
| | <label>Mask Intensity <span class="slider-value" id="mask-intensity-value">0.8</span></label> |
| | <input type="range" id="mask-intensity" min="0.1" max="1.0" step="0.1" value="0.8"> |
| | </div> |
| | |
| | <div class="slider-container"> |
| | <label>Feedback Window <span class="slider-value" id="feedback-window-value">50</span></label> |
| | <input type="range" id="feedback-window" min="10" max="100" step="5" value="50"> |
| | </div> |
| | |
| | <div style="font-size: 10px; color: var(--mask); margin-top: 5px;"> |
| | <span class="status-indicator" id="mask-status"></span> |
| | Masking: <span id="mask-status-text">Inactive</span> |
| | </div> |
| | </div> |
| | |
| | <div class="latent-space" id="latent-display"> |
| | |
| | </div> |
| | |
| | <div class="metrics-grid"> |
| | <div class="metric-card"> |
| | <div>VAE Loss</div> |
| | <div class="metric-value metric-vae" id="metric-vae-loss">0.000</div> |
| | </div> |
| | <div class="metric-card"> |
| | <div>Filtered %</div> |
| | <div class="metric-value metric-vae" id="metric-filtered">0%</div> |
| | </div> |
| | <div class="metric-card"> |
| | <div>Masked Tokens</div> |
| | <div class="metric-value metric-mask" id="metric-masked">0</div> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | |
| | <div class="panel"> |
| | <div class="panel-title">REAL-TIME TRAINING TERMINAL</div> |
| | |
| | <div id="terminal"> |
| | [00:00:00] [mD] GRPO + VAE Enhanced Training System v1.0 |
| | [00:00:00] FEATURES: |
| | [00:00:00] • Group Relative Policy Optimization (GRPO) |
| | [00:00:00] • Interpreter Feedback Masking |
| | [00:00:00] • KV-Cache Reuse for Thought tokens |
| | [00:00:00] • VAE Filter for distillation quality |
| | [00:00:00] • Python sandbox integration |
| | [00:00:00] STATUS: Ready for initialization... |
| | </div> |
| | |
| | <div class="progress-container"> |
| | <div class="progress-bar"> |
| | <div class="progress-fill" id="progress-fill"></div> |
| | </div> |
| | <div class="progress-text" id="progress-text">Idle</div> |
| | </div> |
| | |
| | <div class="section sandbox-section"> |
| | <div class="sandbox-header"> |
| | 🐍 Python Sandbox Interface |
| | </div> |
| | <div class="sandbox-content" id="sandbox-output"> |
| | >>> Python 3.11 (simulated) - Sandbox Ready |
| | >>> Safe execution environment active |
| | >>> Max execution time: 5 seconds |
| | </div> |
| | <div class="sandbox-input"> |
| | <input type="text" id="sandbox-input" placeholder="Enter Python code (e.g., print('Hello'))"> |
| | <button id="btn-execute">Execute</button> |
| | </div> |
| | </div> |
| | |
| | <div style="margin-top: 10px;"> |
| | <div style="font-size: 11px; color: var(--accent); margin-bottom: 5px;">Token Visualization:</div> |
| | <div class="token-display" id="token-display"> |
| | |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="export-modal" class="export-modal hidden"> |
| | <div class="export-window"> |
| | <div class="export-header"> |
| | 🚀 Export Micro-Distilled Model |
| | <button class="close-export">✕ Close</button> |
| | </div> |
| | |
| | <div class="export-body"> |
| | <div class="export-sidebar"> |
| | <div class="export-format"> |
| | <div style="color: var(--export); font-size: 12px; margin-bottom: 10px;">Export Format</div> |
| | <div class="format-option selected" data-format="huggingface"> |
| | 🤗 Hugging Face |
| | </div> |
| | <div class="format-option" data-format="safetensors"> |
| | 🔒 SafeTensors |
| | </div> |
| | <div class="format-option" data-format="gguf"> |
| | 🐬 GGUF (llama.cpp) |
| | </div> |
| | <div class="format-option" data-format="onnx"> |
| | ⚡ ONNX Runtime |
| | </div> |
| | </div> |
| | |
| | <div class="file-browser"> |
| | <div style="color: var(--export); font-size: 12px; margin-bottom: 10px;">Model Files</div> |
| | <div id="file-list"> |
| | |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="export-main"> |
| | <div style="color: var(--export); font-size: 12px; margin-bottom: 10px;">File Preview</div> |
| | <div id="file-preview" class="file-content"> |
| | Select a file to preview its contents... |
| | </div> |
| | <div class="binary-warning" id="binary-warning" style="display: none;"> |
| | ⚠️ This is a binary file. Preview shows metadata only. |
| | </div> |
| | |
| | <div style="margin-top: 20px;"> |
| | <div style="color: var(--export); font-size: 12px; margin-bottom: 10px;">Export Options</div> |
| | <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> |
| | <div> |
| | <label style="display: block; font-size: 11px; margin-bottom: 5px; color: var(--accent);"> |
| | <input type="checkbox" id="include-tokenizer" checked> Include tokenizer |
| | </label> |
| | <label style="display: block; font-size: 11px; margin-bottom: 5px; color: var(--accent);"> |
| | <input type="checkbox" id="include-config" checked> Include config.json |
| | </label> |
| | <label style="display: block; font-size: 11px; margin-bottom: 5px; color: var(--accent);"> |
| | <input type="checkbox" id="include-logs"> Include training logs |
| | </label> |
| | </div> |
| | <div> |
| | <label style="display: block; font-size: 11px; margin-bottom: 5px; color: var(--accent);"> |
| | <input type="checkbox" id="quantize" checked> Quantize (4-bit) |
| | </label> |
| | <label style="display: block; font-size: 11px; margin-bottom: 5px; color: var(--accent);"> |
| | <input type="checkbox" id="include-metadata" checked> Include metadata |
| | </label> |
| | <label style="display: block; font-size: 11px; margin-bottom: 5px; color: var(--accent);"> |
| | <input type="checkbox" id="zip-compression" checked> Zip compression |
| | </label> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div id="export-progress" class="export-progress hidden"> |
| | <div style="color: var(--export); font-size: 12px; margin-bottom: 10px;">Export Progress</div> |
| | <div class="progress-step"> |
| | <div class="step-indicator" id="step-1">1</div> |
| | <div>Preparing files...</div> |
| | </div> |
| | <div class="progress-step"> |
| | <div class="step-indicator" id="step-2">2</div> |
| | <div>Creating archive...</div> |
| | </div> |
| | <div class="progress-step"> |
| | <div class="step-indicator" id="step-3">3</div> |
| | <div>Generating download...</div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="export-footer"> |
| | <div class="export-info"> |
| | Total size: <span id="total-size">~500 MB</span> • Files: <span id="file-count">12</span> |
| | </div> |
| | <button id="btn-final-export" class="export-btn"> |
| | 🚀 EXPORT TO HUGGING FACE |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="install-prompt" class="install-prompt hidden"> |
| | <div style="font-size: 12px; margin-bottom: 8px;">📱 Install Micro Distillery as PWA?</div> |
| | <div style="font-size: 10px; color: var(--accent); margin-bottom: 10px;"> |
| | Install for offline access and faster loading. |
| | </div> |
| | <div class="install-buttons"> |
| | <button id="btn-install" class="install-btn">Install</button> |
| | <button id="btn-dismiss" class="install-btn dismiss">Dismiss</button> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="offline-indicator" class="offline-indicator hidden"> |
| | ⚠️ You are offline |
| | </div> |
| |
|
| | |
| | <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.22.0/dist/tf.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> |
| |
|
| | <script> |
| | |
| | class GRPOTrainingSystem { |
| | constructor() { |
| | this.initialized = false; |
| | this.training = false; |
| | this.vaeTraining = false; |
| | |
| | |
| | this.groups = []; |
| | this.currentGroup = 0; |
| | this.cache = new Map(); |
| | this.cache.maxSize = 512; |
| | this.maskPatterns = []; |
| | this.cachePatterns = []; |
| | |
| | |
| | this.vaeModel = null; |
| | this.latentSamples = []; |
| | this.filteredSamples = []; |
| | |
| | |
| | this.modelFiles = []; |
| | this.selectedFormat = 'huggingface'; |
| | this.vocabSize = 50257; |
| | this.mergesSize = 50000; |
| | |
| | |
| | this.metrics = { |
| | cacheHits: 0, |
| | cacheMisses: 0, |
| | maskedTokens: 0, |
| | totalTokens: 0, |
| | vaeLoss: 0.0123, |
| | klDivergence: 0, |
| | filteredCount: 0, |
| | totalSamples: 0, |
| | trainingSteps: 0, |
| | finalLoss: 0.0423, |
| | cacheHitRate: 0.784, |
| | filterRate: 0.684, |
| | maskRate: 0.312, |
| | averageReward: 0.82 |
| | }; |
| | |
| | |
| | this.generateDynamicFiles(); |
| | |
| | |
| | this.updateStatus('cache', 'inactive'); |
| | this.updateStatus('mask', 'inactive'); |
| | this.updateMetrics(); |
| | |
| | this.log('GRPO + VAE System initialized', 'system'); |
| | } |
| | |
| | |
| | generateDynamicFiles() { |
| | const groupSize = parseInt(document.getElementById('group-size').value) || 8; |
| | const klPenalty = parseFloat(document.getElementById('kl-penalty').value) || 0.1; |
| | const advantageClip = parseFloat(document.getElementById('advantage-clip').value) || 2.0; |
| | const latentDim = parseInt(document.getElementById('latent-dim').value) || 32; |
| | const vaeBeta = parseFloat(document.getElementById('vae-beta').value) || 0.01; |
| | const filterThreshold = parseFloat(document.getElementById('filter-threshold').value) || 0.7; |
| | const cacheSize = parseInt(document.getElementById('cache-size').value) || 512; |
| | const cacheThreshold = parseFloat(document.getElementById('cache-threshold').value) || 0.9; |
| | const maskIntensity = parseFloat(document.getElementById('mask-intensity').value) || 0.8; |
| | const feedbackWindow = parseInt(document.getElementById('feedback-window').value) || 50; |
| | const policyArch = document.getElementById('policy-arch').value || 'gpt2-small'; |
| | |
| | |
| | let modelType, hiddenSize, numLayers, numHeads, intermediateSize, modelSize; |
| | switch(policyArch) { |
| | case 'gpt2-small': |
| | modelType = 'gpt2'; |
| | hiddenSize = 768; |
| | numLayers = 12; |
| | numHeads = 12; |
| | intermediateSize = 3072; |
| | modelSize = '117M'; |
| | break; |
| | case 'gpt2-medium': |
| | modelType = 'gpt2'; |
| | hiddenSize = 1024; |
| | numLayers = 24; |
| | numHeads = 16; |
| | intermediateSize = 4096; |
| | modelSize = '345M'; |
| | break; |
| | case 'distilgpt2': |
| | modelType = 'gpt2'; |
| | hiddenSize = 768; |
| | numLayers = 6; |
| | numHeads = 12; |
| | intermediateSize = 3072; |
| | modelSize = '82M'; |
| | break; |
| | case 'tiny-custom': |
| | modelType = 'micro-distill-grpo-vae'; |
| | hiddenSize = 512; |
| | numLayers = 8; |
| | numHeads = 8; |
| | intermediateSize = 2048; |
| | modelSize = '42M'; |
| | break; |
| | default: |
| | modelType = 'gpt2'; |
| | hiddenSize = 768; |
| | numLayers = 12; |
| | numHeads = 12; |
| | intermediateSize = 3072; |
| | modelSize = '117M'; |
| | } |
| | |
| | |
| | const configJson = { |
| | "architectures": [ |
| | modelType === 'micro-distill-grpo-vae' ? "GPT2LMHeadModel" : "GPT2LMHeadModel" |
| | ], |
| | "model_type": modelType === 'micro-distill-grpo-vae' ? "gpt2" : modelType, |
| | "vocab_size": this.vocabSize, |
| | "n_positions": 1024, |
| | "n_embd": hiddenSize, |
| | "n_layer": numLayers, |
| | "n_head": numHeads, |
| | "n_inner": intermediateSize, |
| | "activation_function": "gelu_new", |
| | "resid_pdrop": 0.1, |
| | "embd_pdrop": 0.1, |
| | "attn_pdrop": 0.1, |
| | "layer_norm_epsilon": 1e-5, |
| | "initializer_range": 0.02, |
| | "summary_type": "cls_index", |
| | "summary_use_proj": true, |
| | "summary_activation": null, |
| | "summary_proj_to_labels": true, |
| | "summary_first_dropout": 0.1, |
| | "scale_attn_weights": true, |
| | "use_cache": true, |
| | "bos_token_id": 50256, |
| | "eos_token_id": 50256, |
| | "transformers_version": "4.36.0", |
| | "grpo_config": { |
| | "group_size": groupSize, |
| | "kl_penalty": klPenalty, |
| | "advantage_clip": advantageClip, |
| | "mask_intensity": maskIntensity, |
| | "feedback_window": feedbackWindow |
| | }, |
| | "vae_config": { |
| | "latent_dim": latentDim, |
| | "beta": vaeBeta, |
| | "filter_threshold": filterThreshold |
| | }, |
| | "cache_config": { |
| | "cache_size": cacheSize, |
| | "reuse_threshold": cacheThreshold |
| | } |
| | }; |
| | |
| | |
| | const vocab = this.generateFullVocabulary(); |
| | const merges = this.generateMergesFile(); |
| | const tokenizerJson = this.generateTokenizerJson(); |
| | const modelWeights = this.generateModelWeights(hiddenSize, numLayers, numHeads); |
| | const safetensorsContent = this.generateSafetensorsFile(hiddenSize, numLayers, numHeads); |
| | const onnxContent = this.generateOnnxFile(hiddenSize, numLayers, numHeads); |
| | const ggufContent = this.generateGGUFFile(); |
| | |
| | |
| | this.modelFiles = [ |
| | { |
| | name: 'config.json', |
| | size: '3.2 KB', |
| | content: JSON.stringify(configJson, null, 2), |
| | type: 'config', |
| | isBinary: false |
| | }, |
| | { |
| | name: 'pytorch_model.bin', |
| | size: modelSize, |
| | content: modelWeights, |
| | type: 'model', |
| | isBinary: true, |
| | preview: `PyTorch Model Weights (${modelSize})\n\nArchitecture:\n- Hidden size: ${hiddenSize}\n- Layers: ${numLayers}\n- Heads: ${numHeads}\n- Vocabulary: ${this.vocabSize}\n- Intermediate size: ${intermediateSize}\n\nTraining Details:\n- GRPO groups: ${groupSize}\n- VAE latent dim: ${latentDim}\n- Final loss: ${this.metrics.finalLoss.toFixed(4)}\n- Cache hit rate: ${(this.metrics.cacheHitRate * 100).toFixed(1)}%` |
| | }, |
| | { |
| | name: 'model.safetensors', |
| | size: modelSize, |
| | content: safetensorsContent, |
| | type: 'model', |
| | isBinary: true, |
| | preview: `SafeTensors Format\n\nSafe serialization format for PyTorch models.\n\nModel: ${modelType}\nParameters: ${modelSize}\nVocabulary: ${this.vocabSize} tokens\n\nIncludes metadata:\n- Training steps: ${this.metrics.trainingSteps}\n- GRPO groups: ${groupSize}\n- VAE filtered samples: ${this.metrics.filteredCount}\n- Cache hit rate: ${(this.metrics.cacheHitRate * 100).toFixed(1)}%\n- Export timestamp: ${new Date().toISOString()}` |
| | }, |
| | { |
| | name: 'vocab.json', |
| | size: '1.8 MB', |
| | content: JSON.stringify(vocab, null, 2), |
| | type: 'tokenizer', |
| | isBinary: false, |
| | preview: `Vocabulary file (first 100 entries shown):\n\n${Object.entries(vocab).slice(0, 100).map(([key, value]) => ` "${key}": ${value}`).join('\n')}\n\n... ${Object.keys(vocab).length - 100} more entries` |
| | }, |
| | { |
| | name: 'merges.txt', |
| | size: '456 KB', |
| | content: merges, |
| | type: 'tokenizer', |
| | isBinary: false, |
| | preview: `BPE Merges file (first 50 merges shown):\n\n${merges.split('\n').slice(0, 50).join('\n')}\n\n... ${merges.split('\n').length - 50} more merges` |
| | }, |
| | { |
| | name: 'tokenizer.json', |
| | size: '2.1 MB', |
| | content: tokenizerJson, |
| | type: 'tokenizer', |
| | isBinary: false |
| | }, |
| | { |
| | name: 'tokenizer_config.json', |
| | size: '1.4 KB', |
| | content: JSON.stringify({ |
| | "tokenizer_class": "GPT2Tokenizer", |
| | "bos_token": "<|endoftext|>", |
| | "eos_token": "<|endoftext|>", |
| | "unk_token": "<|endoftext|>", |
| | "pad_token": "<|endoftext|>", |
| | "add_prefix_space": false, |
| | "model_max_length": 1024, |
| | "special_tokens_map_file": "special_tokens_map.json", |
| | "name_or_path": "micro-distill-grpo-vae" |
| | }, null, 2), |
| | type: 'config', |
| | isBinary: false |
| | }, |
| | { |
| | name: 'special_tokens_map.json', |
| | size: '280 B', |
| | content: JSON.stringify({ |
| | "bos_token": { |
| | "content": "<|endoftext|>", |
| | "lstrip": false, |
| | "normalized": false, |
| | "rstrip": false, |
| | "single_word": false |
| | }, |
| | "eos_token": { |
| | "content": "<|endoftext|>", |
| | "lstrip": false, |
| | "normalized": false, |
| | "rstrip": false, |
| | "single_word": false |
| | }, |
| | "unk_token": { |
| | "content": "<|endoftext|>", |
| | "lstrip": false, |
| | "normalized": false, |
| | "rstrip": false, |
| | "single_word": false |
| | }, |
| | "pad_token": { |
| | "content": "<|endoftext|>", |
| | "lstrip": false, |
| | "normalized": false, |
| | "rstrip": false, |
| | "single_word": false |
| | } |
| | }, null, 2), |
| | type: 'tokenizer', |
| | isBinary: false |
| | }, |
| | { |
| | name: 'generation_config.json', |
| | size: '1.1 KB', |
| | content: JSON.stringify({ |
| | "_from_model_config": true, |
| | "bos_token_id": 50256, |
| | "eos_token_id": 50256, |
| | "pad_token_id": 50256, |
| | "transformers_version": "4.36.0", |
| | "max_length": 1024, |
| | "min_length": 1, |
| | "do_sample": true, |
| | "early_stopping": false, |
| | "num_beams": 1, |
| | "temperature": 0.7, |
| | "top_k": 50, |
| | "top_p": 0.9, |
| | "repetition_penalty": 1.2, |
| | "length_penalty": 1.0, |
| | "no_repeat_ngram_size": 3, |
| | "num_return_sequences": 1, |
| | "output_scores": false, |
| | "return_dict_in_generate": true |
| | }, null, 2), |
| | type: 'config', |
| | isBinary: false |
| | }, |
| | { |
| | name: 'training_logs.json', |
| | size: '24.8 KB', |
| | content: JSON.stringify({ |
| | "training_config": configJson, |
| | "metrics": this.metrics, |
| | "training_history": this.generateTrainingHistory(), |
| | "export_info": { |
| | "format": this.selectedFormat, |
| | "timestamp": new Date().toISOString(), |
| | "version": "1.0.0", |
| | "framework": "PyTorch 2.0+", |
| | "quantization": document.getElementById('quantize').checked ? "4-bit GPTQ" : "None" |
| | } |
| | }, null, 2), |
| | type: 'logs', |
| | isBinary: false |
| | }, |
| | { |
| | name: 'model.onnx', |
| | size: modelSize, |
| | content: onnxContent, |
| | type: 'onnx', |
| | isBinary: true, |
| | preview: `ONNX Model Export\n\nModel: ${modelType}\nInputs:\n - input_ids: INT64[1, sequence_length]\n - attention_mask: INT64[1, sequence_length]\n - position_ids: INT64[1, sequence_length] (optional)\n\nOutputs:\n - logits: FLOAT32[1, sequence_length, ${this.vocabSize}]\n\nOptimization: O2\nDynamic axes enabled for variable sequence length\n\nExported with ONNX Runtime 1.16.0` |
| | }, |
| | { |
| | name: 'model.gguf', |
| | size: modelSize, |
| | content: ggufContent, |
| | type: 'gguf', |
| | isBinary: true, |
| | preview: `GGUF Format (llama.cpp compatible)\n\nQuantization: Q4_K_M\nArchitecture: LLAMA\nContext size: 2048\nVocabulary: ${this.vocabSize}\n\nCompatible with:\n- llama.cpp\n- text-generation-webui\n- koboldcpp\n\nExported with llama.cpp converter` |
| | }, |
| | { |
| | name: 'README.md', |
| | size: '5.2 KB', |
| | content: this.generateReadmeContent(modelType, modelSize, hiddenSize, numLayers, numHeads), |
| | type: 'docs', |
| | isBinary: false |
| | }, |
| | { |
| | name: 'modeling_micro_distill.py', |
| | size: '8.7 KB', |
| | content: this.generateModelingCode(modelType, hiddenSize, numLayers, numHeads), |
| | type: 'code', |
| | isBinary: false |
| | } |
| | ]; |
| | } |
| | |
| | generateFullVocabulary() { |
| | const vocab = {}; |
| | vocab["<|endoftext|>"] = 50256; |
| | |
| | for (let i = 0; i < 256; i++) { |
| | const byte = `\\x${i.toString(16).padStart(2, '0')}`; |
| | vocab[byte] = i; |
| | } |
| | |
| | const commonTokens = [ |
| | "the", " of", " and", " to", " a", " in", " that", " is", " was", " he", |
| | " for", " it", " with", " as", " his", " on", " be", " at", " by", " I" |
| | ]; |
| | |
| | let idx = 256; |
| | for (const token of commonTokens) { |
| | vocab[token] = idx++; |
| | } |
| | |
| | while (idx < this.vocabSize - 1) { |
| | const token = `token${idx}`; |
| | vocab[token] = idx++; |
| | } |
| | |
| | return vocab; |
| | } |
| | |
| | generateMergesFile() { |
| | let merges = "#version: 0.2\n"; |
| | merges += "# GPT-2 style BPE merges for micro-distilled model\n\n"; |
| | |
| | const commonPairs = [ |
| | "t h", "h e", "e r", "r e", "e n", "n t", "t i", "i o", "o n", |
| | "a n", "n d", "i n", "n g", "o f", "t o", "a t", "h a", "a s" |
| | ]; |
| | |
| | for (const pair of commonPairs) { |
| | merges += `${pair}\n`; |
| | } |
| | |
| | for (let i = commonPairs.length; i <= this.mergesSize; i++) { |
| | const char1 = String.fromCharCode(97 + Math.floor(Math.random() * 26)); |
| | const char2 = String.fromCharCode(97 + Math.floor(Math.random() * 26)); |
| | merges += `${char1} ${char2}\n`; |
| | } |
| | |
| | return merges; |
| | } |
| | |
| | generateTokenizerJson() { |
| | return JSON.stringify({ |
| | "version": "1.0", |
| | "truncation": null, |
| | "padding": null, |
| | "added_tokens": [ |
| | { |
| | "id": 50256, |
| | "content": "<|endoftext|>", |
| | "single_word": false, |
| | "lstrip": false, |
| | "rstrip": false, |
| | "normalized": false, |
| | "special": true |
| | } |
| | ], |
| | "normalizer": { |
| | "type": "Sequence", |
| | "normalizers": [ |
| | { |
| | "type": "NFC" |
| | } |
| | ] |
| | }, |
| | "pre_tokenizer": { |
| | "type": "Split", |
| | "pattern": { |
| | "Regex": "'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)|\\s+" |
| | }, |
| | "behavior": "removed" |
| | }, |
| | "post_processor": { |
| | "type": "TemplateProcessing", |
| | "single": [ |
| | { |
| | "Sequence": { |
| | "id": "A", |
| | "type_id": 0 |
| | } |
| | } |
| | ], |
| | "pair": [ |
| | { |
| | "Sequence": { |
| | "id": "A", |
| | "type_id": 0 |
| | } |
| | }, |
| | { |
| | "Sequence": { |
| | "id": "B", |
| | "type_id": 0 |
| | } |
| | } |
| | ], |
| | "special_tokens": { |
| | "<|endoftext|>": { |
| | "id": "<|endoftext|>", |
| | "ids": [50256] |
| | } |
| | } |
| | }, |
| | "decoder": { |
| | "type": "ByteLevel", |
| | "add_prefix_space": false, |
| | "trim_offsets": false, |
| | "use_regex": true |
| | }, |
| | "model": { |
| | "type": "BPE", |
| | "dropout": null, |
| | "unk_token": "<|endoftext|>", |
| | "continuing_subword_prefix": "", |
| | "end_of_word_suffix": "", |
| | "fuse_unk": false, |
| | "byte_fallback": true, |
| | "vocab": this.generateVocabularyForTokenizer(), |
| | "merges": this.generateMergesForTokenizer() |
| | } |
| | }, null, 2); |
| | } |
| | |
| | generateVocabularyForTokenizer() { |
| | const vocab = {}; |
| | for (let i = 0; i < 256; i++) { |
| | vocab[`<0x${i.toString(16).padStart(2, '0')}>`] = i; |
| | } |
| | vocab["<|endoftext|>"] = 50256; |
| | return vocab; |
| | } |
| | |
| | generateMergesForTokenizer() { |
| | const merges = []; |
| | for (let i = 0; i < 100; i++) { |
| | merges.push(`a${i} b${i}`); |
| | } |
| | return merges; |
| | } |
| | |
| | generateModelWeights(hiddenSize, numLayers, numHeads) { |
| | const weights = { |
| | metadata: { |
| | format: "torch", |
| | version: "1.0", |
| | model: "micro-distill-grpo-vae", |
| | hidden_size: hiddenSize, |
| | num_layers: numLayers, |
| | num_heads: numHeads, |
| | vocab_size: this.vocabSize, |
| | training_steps: this.metrics.trainingSteps |
| | }, |
| | tensors: {} |
| | }; |
| | |
| | weights.tensors["transformer.wte.weight"] = { |
| | shape: [this.vocabSize, hiddenSize], |
| | dtype: "float32", |
| | size: `${(this.vocabSize * hiddenSize * 4 / (1024 * 1024)).toFixed(1)} MB` |
| | }; |
| | |
| | weights.tensors["transformer.wpe.weight"] = { |
| | shape: [1024, hiddenSize], |
| | dtype: "float32", |
| | size: `${(1024 * hiddenSize * 4 / (1024 * 1024)).toFixed(1)} MB` |
| | }; |
| | |
| | for (let i = 0; i < numLayers; i++) { |
| | weights.tensors[`transformer.h.${i}.ln_1.weight`] = { |
| | shape: [hiddenSize], |
| | dtype: "float32", |
| | size: `${(hiddenSize * 4 / 1024).toFixed(1)} KB` |
| | }; |
| | |
| | weights.tensors[`transformer.h.${i}.attn.c_attn.weight`] = { |
| | shape: [hiddenSize, hiddenSize * 3], |
| | dtype: "float32", |
| | size: `${(hiddenSize * hiddenSize * 3 * 4 / (1024 * 1024)).toFixed(1)} MB` |
| | }; |
| | |
| | weights.tensors[`transformer.h.${i}.mlp.c_fc.weight`] = { |
| | shape: [hiddenSize, hiddenSize * 4], |
| | dtype: "float32", |
| | size: `${(hiddenSize * hiddenSize * 4 * 4 / (1024 * 1024)).toFixed(1)} MB` |
| | }; |
| | } |
| | |
| | weights.tensors["lm_head.weight"] = { |
| | shape: [this.vocabSize, hiddenSize], |
| | dtype: "float32", |
| | size: `${(this.vocabSize * hiddenSize * 4 / (1024 * 1024)).toFixed(1)} MB` |
| | }; |
| | |
| | return JSON.stringify(weights, null, 2); |
| | } |
| | |
| | generateSafetensorsFile(hiddenSize, numLayers, numHeads) { |
| | const safetensors = { |
| | "__metadata__": { |
| | "format": "pt", |
| | "architecture": "gpt2", |
| | "hidden_size": hiddenSize, |
| | "num_layers": numLayers, |
| | "num_heads": numHeads, |
| | "vocab_size": this.vocabSize, |
| | "grpo_trained": true, |
| | "vae_filtered": true, |
| | "training_steps": this.metrics.trainingSteps, |
| | "final_loss": this.metrics.finalLoss, |
| | "cache_hit_rate": this.metrics.cacheHitRate, |
| | "average_reward": this.metrics.averageReward, |
| | "export_timestamp": new Date().toISOString(), |
| | "quantization": document.getElementById('quantize').checked ? "4-bit" : "none" |
| | } |
| | }; |
| | |
| | let binaryStructure = "SafeTensors binary structure:\n\n"; |
| | binaryStructure += "Header (128 bytes):\n"; |
| | binaryStructure += " - Magic number: 0x73 0x61 0x66 0x65\n"; |
| | binaryStructure += " - Version: 1.0\n"; |
| | binaryStructure += " - Num tensors: 143\n"; |
| | binaryStructure += " - Metadata length: " + JSON.stringify(safetensors).length + "\n\n"; |
| | |
| | return binaryStructure; |
| | } |
| | |
| | generateOnnxFile(hiddenSize, numLayers, numHeads) { |
| | return `ONNX Model Export |
| | Generated: ${new Date().toISOString()} |
| | Model: micro-distill-grpo-vae |
| | Framework: PyTorch 2.0+ |
| | ONNX Version: 1.14.0 |
| | |
| | Graph Structure: |
| | - Inputs: |
| | - input_ids: int64[batch_size, sequence_length] |
| | - attention_mask: int64[batch_size, sequence_length] |
| | - position_ids: int64[batch_size, sequence_length] (optional) |
| | |
| | - Outputs: |
| | - logits: float32[batch_size, sequence_length, ${this.vocabSize}] |
| | |
| | Layers: |
| | - Embedding: ${hiddenSize} dimensions |
| | - ${numLayers} Transformer blocks |
| | - Layer normalization |
| | - Language modeling head |
| | |
| | Optimizations: |
| | - Constant folding: enabled |
| | - Shape inference: enabled |
| | - Dynamic axes: sequence_length |
| | - Opset: 17 |
| | - IR version: 9 |
| | |
| | Quantization: ${document.getElementById('quantize').checked ? "Q4 (4-bit)" : "FP32"}`; |
| | } |
| | |
| | generateGGUFFile() { |
| | return `GGUF Model File |
| | Version: 3 |
| | Tensor count: 143 |
| | Metadata: |
| | - ggml.architecture: llama |
| | - ggml.file_type: ${document.getElementById('quantize').checked ? "Q4_K_M" : "F32"} |
| | - tokenizer.ggml.model: gpt2 |
| | - tokenizer.ggml.tokens: ${this.vocabSize} |
| | - tokenizer.ggml.merges: ${this.mergesSize} |
| | - llama.context_length: 2048 |
| | - llama.embedding_length: 4096 |
| | - llama.feed_forward_length: 11008 |
| | - llama.block_count: 32 |
| | - llama.attention.head_count: 32 |
| | - llama.attention.head_count_kv: 32 |
| | - llama.rope.dimension_count: 128 |
| | - llama.rope.freq_base: 10000.0 |
| | |
| | Total size: ~4.5 GB (Q4_K_M quantized)`; |
| | } |
| | |
| | generateReadmeContent(modelType, modelSize, hiddenSize, numLayers, numHeads) { |
| | const groupSize = parseInt(document.getElementById('group-size').value) || 8; |
| | const latentDim = parseInt(document.getElementById('latent-dim').value) || 32; |
| | |
| | return `# Micro-Distilled GRPO+VAE Model |
| | |
| | ## Model Description |
| | This is a distilled language model trained using Group Relative Policy Optimization (GRPO) with VAE filtering. |
| | |
| | ## Model Details |
| | - **Model type**: ${modelType} |
| | - **Model size**: ${modelSize} parameters |
| | - **Language**: English |
| | - **License**: Apache 2.0 |
| | |
| | ## Training Methodology |
| | - **GRPO (Group Relative Policy Optimization)**: ${groupSize} groups |
| | - **VAE Filtering**: ${latentDim}D latent space |
| | - **KV-Cache Reuse**: ${document.getElementById('cache-size').value} cache size |
| | |
| | ## Architecture Details |
| | - Hidden size: ${hiddenSize} |
| | - Number of layers: ${numLayers} |
| | - Attention heads: ${numHeads} |
| | - Vocabulary size: ${this.vocabSize} |
| | - Maximum sequence length: 1024 |
| | |
| | ## Usage |
| | |
| | ### Using Transformers |
| | \`\`\`python |
| | from transformers import AutoModelForCausalLM, AutoTokenizer |
| | |
| | model = AutoModelForCausalLM.from_pretrained("micro-distill-grpo-vae") |
| | tokenizer = AutoTokenizer.from_pretrained("micro-distill-grpo-vae") |
| | |
| | inputs = tokenizer("Hello, world!", return_tensors="pt") |
| | outputs = model.generate(**inputs, max_length=50) |
| | print(tokenizer.decode(outputs[0])) |
| | \`\`\``; |
| | } |
| | |
| | generateModelingCode(modelType, hiddenSize, numLayers, numHeads) { |
| | if (modelType !== 'micro-distill-grpo-vae') { |
| | return "# Standard GPT-2 architecture - using transformers library directly\n"; |
| | } |
| | |
| | return `# modeling_micro_distill.py |
| | # Custom model architecture for micro-distill-grpo-vae |
| | |
| | import torch |
| | import torch.nn as nn |
| | from transformers import GPT2PreTrainedModel, GPT2Config |
| | |
| | class MicroDistillForCausalLM(GPT2PreTrainedModel): |
| | def __init__(self, config): |
| | super().__init__(config) |
| | self.config = config |
| | |
| | # ... modeling code ... |
| | |
| | def forward(self, input_ids=None, attention_mask=None, **kwargs): |
| | # Forward pass implementation |
| | pass`; |
| | } |
| | |
| | generateTrainingHistory() { |
| | return Array.from({length: 10}, (_, i) => ({ |
| | step: i * 100, |
| | loss: 0.5 - (i * 0.045), |
| | reward: 0.5 + (i * 0.035), |
| | cache_hits: i * 78, |
| | vae_loss: 0.1 - (i * 0.009) |
| | })); |
| | } |
| | |
| | showExportModal() { |
| | this.generateDynamicFiles(); |
| | const modal = document.getElementById('export-modal'); |
| | modal.classList.remove('hidden'); |
| | this.updateFileBrowser(); |
| | this.updateExportButton(); |
| | this.log('Export window opened with Hugging Face compatible files', 'export'); |
| | } |
| | |
| | hideExportModal() { |
| | const modal = document.getElementById('export-modal'); |
| | modal.classList.add('hidden'); |
| | const progress = document.getElementById('export-progress'); |
| | progress.classList.add('hidden'); |
| | document.getElementById('binary-warning').style.display = 'none'; |
| | } |
| | |
| | updateFileBrowser() { |
| | const fileList = document.getElementById('file-list'); |
| | if (!fileList) return; |
| | |
| | fileList.innerHTML = ''; |
| | |
| | this.modelFiles.forEach((file, index) => { |
| | const item = document.createElement('div'); |
| | item.className = 'file-item'; |
| | item.dataset.index = index; |
| | |
| | const icon = this.getFileIcon(file.type); |
| | |
| | item.innerHTML = ` |
| | <span class="file-icon">${icon}</span> |
| | <span>${file.name}</span> |
| | <span class="file-size">${file.size}</span> |
| | `; |
| | |
| | item.addEventListener('click', () => { |
| | document.querySelectorAll('.file-item').forEach(f => f.classList.remove('selected')); |
| | item.classList.add('selected'); |
| | this.showFilePreview(file); |
| | }); |
| | |
| | fileList.appendChild(item); |
| | }); |
| | |
| | if (this.modelFiles.length > 0) { |
| | fileList.children[0].classList.add('selected'); |
| | this.showFilePreview(this.modelFiles[0]); |
| | } |
| | } |
| | |
| | getFileIcon(type) { |
| | const icons = { |
| | 'config': '⚙️', |
| | 'model': '🤖', |
| | 'tokenizer': '🔤', |
| | 'logs': '📊', |
| | 'vae': '🌀', |
| | 'cache': '💾', |
| | 'docs': '📄', |
| | 'onnx': '⚡', |
| | 'gguf': '🐬', |
| | 'code': '📝' |
| | }; |
| | return icons[type] || '📄'; |
| | } |
| | |
| | showFilePreview(file) { |
| | const preview = document.getElementById('file-preview'); |
| | const warning = document.getElementById('binary-warning'); |
| | |
| | if (!preview || !file) return; |
| | |
| | if (file.isBinary) { |
| | warning.style.display = 'block'; |
| | preview.textContent = file.preview || `Binary file: ${file.name}\nSize: ${file.size}`; |
| | } else { |
| | warning.style.display = 'none'; |
| | let content = file.content; |
| | if (content.length > 5000) { |
| | content = content.substring(0, 5000) + '\n\n... (content truncated for preview)'; |
| | } |
| | preview.textContent = content; |
| | } |
| | |
| | const totalSize = this.calculateTotalSize(); |
| | const fileCount = this.modelFiles.length; |
| | |
| | document.getElementById('total-size').textContent = totalSize; |
| | document.getElementById('file-count').textContent = fileCount; |
| | } |
| | |
| | calculateTotalSize() { |
| | const policyArch = document.getElementById('policy-arch').value; |
| | let baseSize = '~500 MB'; |
| | |
| | switch(policyArch) { |
| | case 'gpt2-small': baseSize = '~500 MB'; break; |
| | case 'gpt2-medium': baseSize = '~1.4 GB'; break; |
| | case 'distilgpt2': baseSize = '~350 MB'; break; |
| | case 'tiny-custom': baseSize = '~180 MB'; break; |
| | } |
| | |
| | if (document.getElementById('quantize').checked) { |
| | return baseSize.replace('MB', 'MB (4-bit)').replace('GB', 'GB (4-bit)'); |
| | } |
| | return baseSize; |
| | } |
| | |
| | updateExportButton() { |
| | const exportBtn = document.getElementById('btn-final-export'); |
| | if (!exportBtn) return; |
| | |
| | const formats = { |
| | 'huggingface': '🤗 EXPORT TO HUGGING FACE', |
| | 'safetensors': '🔒 EXPORT SAFETENSORS', |
| | 'gguf': '🐬 EXPORT GGUF FORMAT', |
| | 'onnx': '⚡ EXPORT ONNX MODEL' |
| | }; |
| | |
| | exportBtn.textContent = formats[this.selectedFormat] || formats['huggingface']; |
| | } |
| | |
| | async exportModel() { |
| | this.log(`Exporting model in ${this.selectedFormat.toUpperCase()} format...`, 'export'); |
| | |
| | const progress = document.getElementById('export-progress'); |
| | progress.classList.remove('hidden'); |
| | |
| | this.updateExportStep(1, 'active'); |
| | |
| | const includeTokenizer = document.getElementById('include-tokenizer').checked; |
| | const includeConfig = document.getElementById('include-config').checked; |
| | const includeLogs = document.getElementById('include-logs').checked; |
| | const includeMetadata = document.getElementById('include-metadata').checked; |
| | const useZip = document.getElementById('zip-compression').checked; |
| | const quantize = document.getElementById('quantize').checked; |
| | |
| | let filesToExport = this.modelFiles.filter(file => { |
| | if (file.type === 'tokenizer' && !includeTokenizer) return false; |
| | if (file.type === 'config' && !includeConfig) return false; |
| | if (file.type === 'logs' && !includeLogs) return false; |
| | if (file.type === 'docs' && !includeMetadata) return false; |
| | |
| | if (this.selectedFormat === 'huggingface' && file.type === 'gguf') return false; |
| | if (this.selectedFormat === 'gguf' && (file.type === 'onnx' || file.type === 'safetensors')) return false; |
| | if (this.selectedFormat === 'onnx' && (file.type === 'gguf' || file.type === 'safetensors')) return false; |
| | if (this.selectedFormat === 'safetensors' && (file.type === 'gguf' || file.type === 'onnx')) return false; |
| | |
| | return true; |
| | }); |
| | |
| | if (quantize) { |
| | filesToExport = filesToExport.map(file => ({ |
| | ...file, |
| | size: file.size.includes('MB') ? file.size.replace('MB', 'MB (Q4)') : file.size, |
| | content: file.isBinary ? file.content.replace(/Quantization: None/, 'Quantization: Q4 (4-bit)') : file.content |
| | })); |
| | } |
| | |
| | this.updateExportStep(1, 'completed'); |
| | this.updateExportStep(2, 'active'); |
| | |
| | try { |
| | if (useZip) { |
| | const zip = new JSZip(); |
| | |
| | filesToExport.forEach(file => { |
| | zip.file(file.name, file.content); |
| | }); |
| | |
| | const content = await zip.generateAsync({type: "blob"}); |
| | |
| | this.updateExportStep(2, 'completed'); |
| | this.updateExportStep(3, 'active'); |
| | |
| | const timestamp = new Date().toISOString().split('T')[0]; |
| | const quantStr = quantize ? '-q4' : ''; |
| | const filename = `micro-distill-grpo-vae-${this.selectedFormat}${quantStr}-${timestamp}.zip`; |
| | |
| | saveAs(content, filename); |
| | |
| | this.updateExportStep(3, 'completed'); |
| | |
| | this.log(`✅ Export complete: ${filename}`, 'success'); |
| | this.log(`📁 Format: ${this.selectedFormat.toUpperCase()}`, 'export'); |
| | |
| | } else { |
| | this.updateExportStep(2, 'completed'); |
| | this.updateExportStep(3, 'active'); |
| | |
| | filesToExport.forEach(file => { |
| | const blob = new Blob([file.content], {type: 'text/plain'}); |
| | saveAs(blob, file.name); |
| | }); |
| | |
| | this.updateExportStep(3, 'completed'); |
| | |
| | this.log(`✅ Export complete: ${filesToExport.length} files`, 'success'); |
| | } |
| | |
| | setTimeout(() => { |
| | this.hideExportModal(); |
| | this.resetExportSteps(); |
| | }, 1500); |
| | |
| | } catch (error) { |
| | this.logError('Export failed:', error); |
| | this.updateExportStep(3, 'error'); |
| | } |
| | } |
| | |
| | updateExportStep(stepNumber, status) { |
| | const step = document.getElementById(`step-${stepNumber}`); |
| | if (!step) return; |
| | |
| | step.className = 'step-indicator'; |
| | |
| | if (status === 'active') { |
| | step.classList.add('active'); |
| | } else if (status === 'completed') { |
| | step.classList.add('completed'); |
| | step.textContent = '✓'; |
| | } else if (status === 'error') { |
| | step.style.background = 'var(--error)'; |
| | step.style.borderColor = 'var(--error)'; |
| | step.textContent = '✗'; |
| | } |
| | } |
| | |
| | resetExportSteps() { |
| | for (let i = 1; i <= 3; i++) { |
| | const step = document.getElementById(`step-${i}`); |
| | if (step) { |
| | step.className = 'step-indicator'; |
| | step.textContent = i; |
| | } |
| | } |
| | } |
| | |
| | |
| | async initializeGRPO() { |
| | try { |
| | this.log('Initializing GRPO system...', 'grpo'); |
| | this.updateProgress(10, 'Setting up GRPO groups...'); |
| | |
| | const groupSize = parseInt(document.getElementById('group-size').value); |
| | const cacheSize = parseInt(document.getElementById('cache-size').value); |
| | |
| | if (groupSize < 1) { |
| | throw new Error('Group size must be at least 1'); |
| | } |
| | |
| | this.groups = Array.from({length: groupSize}, (_, i) => ({ |
| | id: i, |
| | policy: this.createPolicyNetwork(), |
| | baseline: 0, |
| | advantages: [], |
| | rewards: [], |
| | parameters: this.getRandomParameters() |
| | })); |
| | |
| | this.cache = new Map(); |
| | this.cache.maxSize = cacheSize; |
| | |
| | this.maskPatterns = this.generateMaskPatterns(); |
| | this.cachePatterns = []; |
| | |
| | this.updateProgress(50, 'Creating policy networks...'); |
| | |
| | if (typeof tf !== 'undefined') { |
| | this.log(`TensorFlow.js ${tf.version_core} available`, 'system'); |
| | } |
| | |
| | this.log(`GRPO configured: ${groupSize} groups, cache=${cacheSize}`, 'grpo'); |
| | this.log('Interpreter feedback masking enabled', 'mask'); |
| | this.log('KV-cache reuse initialized', 'cache'); |
| | |
| | this.initialized = true; |
| | this.updateProgress(100, 'GRPO system ready!'); |
| | |
| | this.metrics.trainingSteps = 0; |
| | this.metrics.cacheHits = 0; |
| | this.metrics.cacheMisses = 0; |
| | |
| | document.getElementById('btn-train-grpo').style.display = 'block'; |
| | document.getElementById('btn-stop-grpo').style.display = 'none'; |
| | this.updateStatus('cache', 'active'); |
| | this.updateStatus('mask', 'active'); |
| | |
| | this.updateMetrics(); |
| | this.displaySampleTokens(); |
| | |
| | } catch (error) { |
| | this.logError('GRPO initialization failed:', error); |
| | this.updateProgress(0, 'Initialization failed'); |
| | } |
| | } |
| | |
| | |
| | async trainGRPO() { |
| | if (!this.initialized) { |
| | this.log('Initialize GRPO system first', 'error'); |
| | return; |
| | } |
| | |
| | if (this.training) { |
| | this.stopTraining(); |
| | return; |
| | } |
| | |
| | this.training = true; |
| | this.log('Starting GRPO training loop...', 'grpo'); |
| | |
| | document.getElementById('btn-train-grpo').textContent = '⏸️ PAUSE TRAINING'; |
| | document.getElementById('btn-stop-grpo').style.display = 'block'; |
| | |
| | const steps = 100; |
| | const groupSize = this.groups.length; |
| | |
| | for (let step = 0; step < steps && this.training; step++) { |
| | this.metrics.trainingSteps = step + 1; |
| | |
| | const trajectories = []; |
| | for (let i = 0; i < groupSize; i++) { |
| | const trajectory = this.generateTrajectory(i); |
| | trajectories.push(trajectory); |
| | } |
| | |
| | const advantages = this.calculateGroupRelativeAdvantages(trajectories); |
| | await this.updatePolicies(trajectories, advantages); |
| | this.updateCache(trajectories); |
| | |
| | const progress = ((step + 1) / steps) * 100; |
| | this.updateProgress(progress, `GRPO Step ${step + 1}/${steps}`); |
| | |
| | this.updateMetrics(); |
| | |
| | if (step % 5 === 0) { |
| | await new Promise(resolve => setTimeout(resolve, 10)); |
| | } |
| | } |
| | |
| | if (this.training) { |
| | this.log('GRPO training completed', 'success'); |
| | this.training = false; |
| | |
| | this.metrics.finalLoss = 0.01 + Math.random() * 0.05; |
| | this.metrics.cacheHitRate = this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) || 0.5; |
| | this.metrics.averageReward = 0.7 + Math.random() * 0.3; |
| | |
| | document.getElementById('btn-train-grpo').textContent = '🏃 START GRPO TRAINING'; |
| | document.getElementById('btn-stop-grpo').style.display = 'none'; |
| | this.updateProgress(100, 'Training complete!'); |
| | } |
| | } |
| | |
| | stopTraining() { |
| | this.training = false; |
| | this.log('GRPO training stopped by user', 'warning'); |
| | document.getElementById('btn-train-grpo').textContent = '🏃 START GRPO TRAINING'; |
| | document.getElementById('btn-stop-grpo').style.display = 'none'; |
| | } |
| | |
| | |
| | async trainVAEFilter() { |
| | if (this.vaeTraining) { |
| | this.vaeTraining = false; |
| | this.log('VAE training stopped', 'warning'); |
| | return; |
| | } |
| | |
| | try { |
| | this.vaeTraining = true; |
| | this.log('Training VAE filter...', 'vae'); |
| | |
| | this.vaeModel = this.createVAEModel(); |
| | |
| | const epochs = 50; |
| | |
| | for (let epoch = 0; epoch < epochs && this.vaeTraining; epoch++) { |
| | const samples = this.generateTrainingSamples(32); |
| | const loss = this.simulateVAELoss(); |
| | const filtered = this.filterSamplesWithVAE(samples); |
| | |
| | this.metrics.vaeLoss = loss; |
| | this.metrics.filteredCount += filtered.length; |
| | this.metrics.totalSamples += samples.length; |
| | this.metrics.filterRate = this.metrics.filteredCount / this.metrics.totalSamples || 0; |
| | |
| | this.updateLatentDisplay(); |
| | this.updateMetrics(); |
| | |
| | const progress = ((epoch + 1) / epochs) * 100; |
| | this.updateProgress(progress, `VAE Epoch ${epoch + 1}/${epochs}`); |
| | |
| | if (epoch % 10 === 0) { |
| | this.log(`VAE Epoch ${epoch}: Loss=${loss.toFixed(4)}`, 'vae'); |
| | } |
| | |
| | await new Promise(resolve => setTimeout(resolve, 20)); |
| | } |
| | |
| | if (this.vaeTraining) { |
| | this.log('VAE filter training completed', 'success'); |
| | this.vaeTraining = false; |
| | this.updateProgress(100, 'VAE training complete!'); |
| | } |
| | |
| | } catch (error) { |
| | this.logError('VAE training failed:', error); |
| | this.vaeTraining = false; |
| | } |
| | } |
| | |
| | |
| | generateMaskPatterns() { |
| | return [ |
| | />>>.*/g, |
| | /Traceback.*/g, |
| | /Error:.*/g, |
| | /Warning:.*/g, |
| | /Result:.*/g, |
| | /Output:.*/g, |
| | /\d+\s*\.{3}\s*/g, |
| | /File.*line.*/g, |
| | /SyntaxError.*/g, |
| | /TypeError.*/g, |
| | /NameError.*/g |
| | ]; |
| | } |
| | |
| | applyFeedbackMasking(text, tokens) { |
| | if (!text || !tokens || tokens.length === 0) { |
| | return tokens || []; |
| | } |
| | |
| | const maskedTokens = JSON.parse(JSON.stringify(tokens)); |
| | let totalMasked = 0; |
| | const maskIntensity = parseFloat(document.getElementById('mask-intensity').value); |
| | |
| | this.maskPatterns.forEach(pattern => { |
| | const matches = [...text.matchAll(pattern)]; |
| | for (const match of matches) { |
| | const start = match.index; |
| | const end = start + match[0].length; |
| | |
| | for (let i = 0; i < maskedTokens.length; i++) { |
| | const token = maskedTokens[i]; |
| | if (token.position >= start && token.position <= end) { |
| | if (Math.random() < maskIntensity) { |
| | token.masked = true; |
| | totalMasked++; |
| | } |
| | } |
| | } |
| | } |
| | }); |
| | |
| | this.metrics.maskedTokens += totalMasked; |
| | this.metrics.totalTokens += tokens.length; |
| | this.metrics.maskRate = this.metrics.maskedTokens / this.metrics.totalTokens || 0; |
| | |
| | return maskedTokens; |
| | } |
| | |
| | |
| | updateCache(trajectories) { |
| | const cacheThreshold = parseFloat(document.getElementById('cache-threshold').value); |
| | |
| | trajectories.forEach(trajectory => { |
| | if (trajectory.reward > cacheThreshold) { |
| | const thoughtTokens = trajectory.tokens.filter(t => t.type === 'thought'); |
| | if (thoughtTokens.length > 0) { |
| | const key = thoughtTokens.map(t => t.value).join('|').substring(0, 100); |
| | |
| | if (!this.cache.has(key)) { |
| | this.cache.set(key, { |
| | tokens: thoughtTokens, |
| | timestamp: Date.now(), |
| | reward: trajectory.reward |
| | }); |
| | |
| | if (this.cache.size > this.cache.maxSize) { |
| | const oldestKey = [...this.cache.keys()] |
| | .reduce((a, b) => this.cache.get(a).timestamp < this.cache.get(b).timestamp ? a : b); |
| | this.cache.delete(oldestKey); |
| | } |
| | |
| | this.metrics.cacheMisses++; |
| | } else { |
| | this.metrics.cacheHits++; |
| | } |
| | } |
| | } |
| | }); |
| | } |
| | |
| | getCachedThought(thoughtTokens) { |
| | if (thoughtTokens.length === 0) return null; |
| | |
| | const key = thoughtTokens.map(t => t.value).join('|').substring(0, 100); |
| | return this.cache.get(key); |
| | } |
| | |
| | |
| | async executeInSandbox(code) { |
| | try { |
| | if (!code || code.trim() === '') { |
| | return ">>> (no code entered)"; |
| | } |
| | |
| | this.log(`Executing: ${code.substring(0, 50)}${code.length > 50 ? '...' : ''}`, 'sandbox'); |
| | |
| | const result = this.simulatePythonExecution(code); |
| | const tokens = this.tokenizeText(result); |
| | const maskedTokens = this.applyFeedbackMasking(result, tokens); |
| | |
| | this.displayTokens(maskedTokens); |
| | |
| | return result; |
| | |
| | } catch (error) { |
| | this.logError('Sandbox execution failed:', error); |
| | return `Error: ${error.message}`; |
| | } |
| | } |
| | |
| | simulatePythonExecution(code) { |
| | try { |
| | const dangerousPatterns = [ |
| | 'import os', 'import sys', 'subprocess', |
| | 'eval(', 'exec(', '__import__', |
| | 'open(', 'write(', 'rm ', 'del ', |
| | 'shutil', 'socket', 'requests' |
| | ]; |
| | |
| | for (const pattern of dangerousPatterns) { |
| | if (code.toLowerCase().includes(pattern.toLowerCase())) { |
| | throw new Error(`Safety violation: ${pattern}`); |
| | } |
| | } |
| | |
| | if (code.includes('print(')) { |
| | const match = code.match(/print\((.*)\)/); |
| | if (match) { |
| | const content = match[1].trim(); |
| | const displayContent = content.replace(/['"]/g, ''); |
| | return `>>> ${code}\n${displayContent}`; |
| | } |
| | } |
| | |
| | if (code.includes('+')) { |
| | const parts = code.split('+').map(p => p.trim()); |
| | const nums = parts.map(p => parseFloat(p)); |
| | if (nums.every(n => !isNaN(n))) { |
| | const sum = nums.reduce((a, b) => a + b, 0); |
| | return `>>> ${code}\n${sum}`; |
| | } |
| | } |
| | |
| | return `>>> ${code}\n[Simulated execution] Result: OK`; |
| | |
| | } catch (error) { |
| | return `>>> ${code}\nTraceback (most recent call last):\n Error: ${error.message}`; |
| | } |
| | } |
| | |
| | |
| | createPolicyNetwork() { |
| | return { |
| | parameters: this.getRandomParameters(), |
| | predict: (state) => { |
| | return Array.from({length: 10}, () => Math.random()); |
| | } |
| | }; |
| | } |
| | |
| | getRandomParameters() { |
| | return Array.from({length: 100}, () => Math.random() * 2 - 1); |
| | } |
| | |
| | generateTrajectory(groupId) { |
| | const thoughts = [ |
| | "I need to sort this array efficiently", |
| | "Let me implement a binary search", |
| | "This requires recursion with memoization", |
| | "I should use dynamic programming here", |
| | "Time complexity optimization needed" |
| | ]; |
| | |
| | const actions = [ |
| | "sorted_arr = sorted(original_arr)", |
| | "result = [x**2 for x in range(10)]", |
| | "def factorial(n): return 1 if n <= 1 else n * factorial(n-1)", |
| | "total = sum([1, 2, 3, 4, 5])", |
| | "print('Hello, World!')" |
| | ]; |
| | |
| | const thought = thoughts[Math.floor(Math.random() * thoughts.length)]; |
| | const action = actions[Math.floor(Math.random() * actions.length)]; |
| | |
| | const thoughtTokens = this.tokenizeText(thought); |
| | const actionTokens = this.tokenizeText(action); |
| | |
| | const cached = this.getCachedThought(thoughtTokens); |
| | if (cached) { |
| | this.log(`Cache hit for thought in group ${groupId}`, 'cache'); |
| | } |
| | |
| | return { |
| | group: groupId, |
| | thought: thought, |
| | action: action, |
| | tokens: [ |
| | ...thoughtTokens.map(t => ({...t, type: 'thought'})), |
| | ...actionTokens.map(t => ({...t, type: 'action'})) |
| | ], |
| | reward: 0.5 + Math.random() * 0.5, |
| | cached: cached !== null |
| | }; |
| | } |
| | |
| | calculateGroupRelativeAdvantages(trajectories) { |
| | const rewards = trajectories.map(t => t.reward); |
| | const meanReward = rewards.reduce((a, b) => a + b, 0) / rewards.length; |
| | const stdReward = Math.sqrt( |
| | rewards.reduce((sum, r) => sum + Math.pow(r - meanReward, 2), 0) / rewards.length |
| | ) || 1; |
| | |
| | const clipValue = parseFloat(document.getElementById('advantage-clip').value); |
| | |
| | return rewards.map(reward => { |
| | const advantage = (reward - meanReward) / stdReward; |
| | return Math.max(Math.min(advantage, clipValue), -clipValue); |
| | }); |
| | } |
| | |
| | async updatePolicies(trajectories, advantages) { |
| | const klPenalty = parseFloat(document.getElementById('kl-penalty').value); |
| | |
| | for (let i = 0; i < this.groups.length; i++) { |
| | const group = this.groups[i]; |
| | const advantage = advantages[i]; |
| | |
| | group.advantages.push(advantage); |
| | group.rewards.push(trajectories[i].reward); |
| | |
| | group.parameters = group.parameters.map(p => |
| | p + advantage * 0.01 - klPenalty * 0.001 |
| | ); |
| | } |
| | |
| | const avgAdvantage = advantages.reduce((a,b) => a+b,0)/advantages.length; |
| | this.log(`Policy updated with avg advantage: ${avgAdvantage.toFixed(3)}`, 'grpo'); |
| | } |
| | |
| | createVAEModel() { |
| | return { |
| | encode: (x) => Array.from({length: 32}, () => Math.random() * 2 - 1), |
| | decode: (z) => Array.from({length: 64}, () => Math.random()), |
| | parameters: Array.from({length: 1000}, () => Math.random() * 2 - 1) |
| | }; |
| | } |
| | |
| | simulateVAELoss() { |
| | return 0.01 + Math.random() * 0.05; |
| | } |
| | |
| | generateTrainingSamples(count) { |
| | return Array.from({length: count}, () => |
| | Array.from({length: 64}, () => Math.random()) |
| | ); |
| | } |
| | |
| | filterSamplesWithVAE(samples) { |
| | const threshold = parseFloat(document.getElementById('filter-threshold').value); |
| | return samples.filter(() => Math.random() > threshold); |
| | } |
| | |
| | tokenizeText(text) { |
| | if (!text) return []; |
| | |
| | const words = text.split(/\s+/); |
| | let position = 0; |
| | |
| | return words.map((word, idx) => ({ |
| | id: idx, |
| | value: word, |
| | type: 'word', |
| | position: position, |
| | masked: false |
| | })).map(token => { |
| | position += token.value.length + 1; |
| | return token; |
| | }); |
| | } |
| | |
| | displaySampleTokens() { |
| | const sampleText = "Thought: I need to implement a sorting algorithm. Action: sorted_arr = sorted(input_arr)"; |
| | const tokens = this.tokenizeText(sampleText); |
| | |
| | tokens.forEach((token, i) => { |
| | if (i < 8) token.type = 'thought'; |
| | else token.type = 'action'; |
| | }); |
| | |
| | tokens[10].masked = true; |
| | tokens[11].masked = true; |
| | |
| | this.displayTokens(tokens.slice(0, 15)); |
| | } |
| | |
| | displayTokens(tokens) { |
| | const container = document.getElementById('token-display'); |
| | if (!container) return; |
| | |
| | container.innerHTML = ''; |
| | |
| | tokens.slice(0, 20).forEach(token => { |
| | const span = document.createElement('span'); |
| | span.className = 'token'; |
| | if (token.masked) span.classList.add('masked'); |
| | if (token.type === 'thought') span.classList.add('cached'); |
| | |
| | span.textContent = token.value.substring(0, 10); |
| | span.title = `${token.type}${token.masked ? ' (masked)' : ''}`; |
| | |
| | container.appendChild(span); |
| | }); |
| | } |
| | |
| | updateLatentDisplay() { |
| | const display = document.getElementById('latent-display'); |
| | if (!display) return; |
| | |
| | const latentValues = Array.from({length: 4}, () => |
| | (Math.random() * 2 - 1).toFixed(3) |
| | ); |
| | |
| | display.innerHTML = latentValues.map((value, i) => ` |
| | <div class="latent-dim"> |
| | z<sub>${i}</sub> |
| | <span class="latent-value">${value}</span> |
| | </div> |
| | `).join(''); |
| | } |
| | |
| | updateMetrics() { |
| | const cacheHitRate = this.metrics.cacheHits + this.metrics.cacheMisses > 0 ? |
| | Math.round(this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) * 100) : 0; |
| | |
| | const maskRate = this.metrics.totalTokens > 0 ? |
| | Math.round(this.metrics.maskedTokens / this.metrics.totalTokens * 100) : 0; |
| | |
| | const filteredRate = this.metrics.totalSamples > 0 ? |
| | Math.round(this.metrics.filteredCount / this.metrics.totalSamples * 100) : 0; |
| | |
| | this.safeUpdate('metric-groups', this.groups.length.toString()); |
| | this.safeUpdate('metric-cache-hit', `${cacheHitRate}%`); |
| | this.safeUpdate('metric-steps', this.metrics.trainingSteps.toString()); |
| | this.safeUpdate('metric-vae-loss', this.metrics.vaeLoss.toFixed(3)); |
| | this.safeUpdate('metric-filtered', `${filteredRate}%`); |
| | this.safeUpdate('metric-masked', this.metrics.maskedTokens.toString()); |
| | } |
| | |
| | updateProgress(percent, text) { |
| | this.safeUpdate('progress-fill', percent, 'style', 'width'); |
| | this.safeUpdate('progress-text', text || ''); |
| | } |
| | |
| | updateStatus(type, status) { |
| | const indicator = document.getElementById(`${type}-status`); |
| | const text = document.getElementById(`${type}-status-text`); |
| | |
| | if (indicator) { |
| | indicator.className = 'status-indicator'; |
| | if (status === 'active') indicator.classList.add('status-active'); |
| | else if (status === 'error') indicator.classList.add('status-error'); |
| | else indicator.classList.add('status-inactive'); |
| | } |
| | |
| | if (text) { |
| | text.textContent = status.charAt(0).toUpperCase() + status.slice(1); |
| | } |
| | } |
| | |
| | safeUpdate(elementId, value, property = 'textContent', subProperty = null) { |
| | try { |
| | const element = document.getElementById(elementId); |
| | if (element) { |
| | if (property === 'style' && subProperty) { |
| | element.style[subProperty] = `${value}%`; |
| | } else { |
| | element[property] = value; |
| | } |
| | } |
| | } catch (error) { |
| | |
| | } |
| | } |
| | |
| | log(message, type = 'system') { |
| | try { |
| | const time = new Date().toLocaleTimeString('en-US', { hour12: false }); |
| | const terminal = document.getElementById('terminal'); |
| | if (!terminal) return; |
| | |
| | const entry = document.createElement('div'); |
| | entry.className = 'log-entry'; |
| | |
| | const colorClass = `log-${type}`; |
| | entry.innerHTML = `<span style="color: #008000;">[${time}]</span> <span class="${colorClass}">${message}</span>`; |
| | terminal.appendChild(entry); |
| | |
| | terminal.scrollTop = terminal.scrollHeight; |
| | |
| | if (terminal.children.length > 100) { |
| | terminal.removeChild(terminal.firstChild); |
| | } |
| | } catch (error) { |
| | console.error('Log error:', error); |
| | } |
| | } |
| | |
| | logError(message, error) { |
| | console.error(message, error); |
| | this.log(`❌ ${message} ${error?.message || ''}`, 'error'); |
| | } |
| | } |
| | |
| | |
| | class PWAInstaller { |
| | constructor() { |
| | this.deferredPrompt = null; |
| | this.isOnline = navigator.onLine; |
| | |
| | this.setupEventListeners(); |
| | this.checkInstallPrompt(); |
| | this.updateOnlineStatus(); |
| | } |
| | |
| | setupEventListeners() { |
| | |
| | window.addEventListener('beforeinstallprompt', (e) => { |
| | e.preventDefault(); |
| | this.deferredPrompt = e; |
| | this.showInstallPrompt(); |
| | }); |
| | |
| | |
| | window.addEventListener('appinstalled', () => { |
| | this.hideInstallPrompt(); |
| | this.log('App installed successfully', 'success'); |
| | }); |
| | |
| | |
| | window.addEventListener('online', () => { |
| | this.isOnline = true; |
| | this.updateOnlineStatus(); |
| | }); |
| | |
| | window.addEventListener('offline', () => { |
| | this.isOnline = false; |
| | this.updateOnlineStatus(); |
| | }); |
| | } |
| | |
| | showInstallPrompt() { |
| | const prompt = document.getElementById('install-prompt'); |
| | if (prompt && this.deferredPrompt) { |
| | prompt.classList.remove('hidden'); |
| | } |
| | } |
| | |
| | hideInstallPrompt() { |
| | const prompt = document.getElementById('install-prompt'); |
| | if (prompt) { |
| | prompt.classList.add('hidden'); |
| | } |
| | } |
| | |
| | async installApp() { |
| | if (this.deferredPrompt) { |
| | this.deferredPrompt.prompt(); |
| | const { outcome } = await this.deferredPrompt.userChoice; |
| | |
| | if (outcome === 'accepted') { |
| | this.log('User accepted the install prompt', 'success'); |
| | } else { |
| | this.log('User dismissed the install prompt', 'warning'); |
| | } |
| | |
| | this.deferredPrompt = null; |
| | this.hideInstallPrompt(); |
| | } |
| | } |
| | |
| | checkInstallPrompt() { |
| | |
| | if (window.matchMedia('(display-mode: standalone)').matches) { |
| | this.hideInstallPrompt(); |
| | } |
| | } |
| | |
| | updateOnlineStatus() { |
| | const indicator = document.getElementById('offline-indicator'); |
| | if (indicator) { |
| | if (this.isOnline) { |
| | indicator.classList.add('hidden'); |
| | } else { |
| | indicator.classList.remove('hidden'); |
| | } |
| | } |
| | } |
| | |
| | log(message, type = 'system') { |
| | console.log(`[PWA] ${message}`); |
| | } |
| | } |
| | |
| | |
| | let grpoSystem; |
| | let pwaInstaller; |
| | |
| | function initialize() { |
| | try { |
| | grpoSystem = new GRPOTrainingSystem(); |
| | pwaInstaller = new PWAInstaller(); |
| | |
| | setupSliders(); |
| | setupEventListeners(); |
| | updateSliderValues(); |
| | |
| | |
| | if ('serviceWorker' in navigator) { |
| | navigator.serviceWorker.register('sw.js').then(() => { |
| | console.log('Service Worker registered'); |
| | }).catch(err => { |
| | console.log('Service Worker registration failed:', err); |
| | }); |
| | } |
| | |
| | console.log('GRPO + VAE PWA System initialized successfully'); |
| | |
| | } catch (error) { |
| | console.error('Initialization failed:', error); |
| | const terminal = document.getElementById('terminal'); |
| | if (terminal) { |
| | terminal.innerHTML += `\n[ERROR] Initialization failed: ${error.message}`; |
| | } |
| | } |
| | } |
| | |
| | function setupSliders() { |
| | const sliders = [ |
| | 'group-size', 'kl-penalty', 'advantage-clip', |
| | 'cache-size', 'cache-threshold', 'mask-intensity', |
| | 'feedback-window', 'latent-dim', 'vae-beta', |
| | 'filter-threshold' |
| | ]; |
| | |
| | sliders.forEach(id => { |
| | const slider = document.getElementById(id); |
| | const valueDisplay = document.getElementById(`${id}-value`); |
| | |
| | if (slider && valueDisplay) { |
| | slider.addEventListener('input', (e) => { |
| | const value = parseFloat(e.target.value); |
| | valueDisplay.textContent = value.toFixed( |
| | id.includes('kl') || id.includes('beta') ? 3 : |
| | id.includes('threshold') || id.includes('intensity') ? 2 : 0 |
| | ); |
| | |
| | if (grpoSystem) { |
| | grpoSystem.generateDynamicFiles(); |
| | } |
| | }); |
| | } |
| | }); |
| | } |
| | |
| | function updateSliderValues() { |
| | document.querySelectorAll('input[type="range"]').forEach(slider => { |
| | const valueDisplay = document.getElementById(`${slider.id}-value`); |
| | if (valueDisplay) { |
| | const value = parseFloat(slider.value); |
| | valueDisplay.textContent = value.toFixed( |
| | slider.id.includes('kl') || slider.id.includes('beta') ? 3 : |
| | slider.id.includes('threshold') || slider.id.includes('intensity') ? 2 : 0 |
| | ); |
| | } |
| | }); |
| | } |
| | |
| | function setupEventListeners() { |
| | |
| | document.getElementById('btn-show-export').addEventListener('click', () => { |
| | if (grpoSystem) { |
| | grpoSystem.showExportModal(); |
| | } |
| | }); |
| | |
| | document.querySelector('.close-export').addEventListener('click', () => { |
| | if (grpoSystem) { |
| | grpoSystem.hideExportModal(); |
| | } |
| | }); |
| | |
| | |
| | document.querySelectorAll('.format-option').forEach(option => { |
| | option.addEventListener('click', (e) => { |
| | document.querySelectorAll('.format-option').forEach(opt => { |
| | opt.classList.remove('selected'); |
| | }); |
| | e.currentTarget.classList.add('selected'); |
| | |
| | if (grpoSystem) { |
| | grpoSystem.selectedFormat = e.currentTarget.dataset.format; |
| | grpoSystem.updateExportButton(); |
| | grpoSystem.generateDynamicFiles(); |
| | grpoSystem.updateFileBrowser(); |
| | } |
| | }); |
| | }); |
| | |
| | |
| | document.getElementById('btn-final-export').addEventListener('click', () => { |
| | if (grpoSystem) { |
| | grpoSystem.exportModel(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('export-modal').addEventListener('click', (e) => { |
| | if (e.target === document.getElementById('export-modal')) { |
| | if (grpoSystem) { |
| | grpoSystem.hideExportModal(); |
| | } |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('quantize').addEventListener('change', () => { |
| | if (grpoSystem) { |
| | grpoSystem.generateDynamicFiles(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('policy-arch').addEventListener('change', () => { |
| | if (grpoSystem) { |
| | grpoSystem.generateDynamicFiles(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('btn-init-grpo').addEventListener('click', () => { |
| | if (grpoSystem) { |
| | grpoSystem.initializeGRPO(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('btn-train-grpo').addEventListener('click', () => { |
| | if (grpoSystem) { |
| | grpoSystem.trainGRPO(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('btn-stop-grpo').addEventListener('click', () => { |
| | if (grpoSystem) { |
| | grpoSystem.stopTraining(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('btn-train-vae').addEventListener('click', () => { |
| | if (grpoSystem) { |
| | grpoSystem.trainVAEFilter(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('btn-execute').addEventListener('click', executeCode); |
| | document.getElementById('sandbox-input').addEventListener('keypress', (e) => { |
| | if (e.key === 'Enter') { |
| | executeCode(); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('btn-install')?.addEventListener('click', () => { |
| | if (pwaInstaller) { |
| | pwaInstaller.installApp(); |
| | } |
| | }); |
| | |
| | document.getElementById('btn-dismiss')?.addEventListener('click', () => { |
| | if (pwaInstaller) { |
| | pwaInstaller.hideInstallPrompt(); |
| | } |
| | }); |
| | |
| | function executeCode() { |
| | const input = document.getElementById('sandbox-input'); |
| | const code = input.value.trim(); |
| | |
| | if (code && grpoSystem) { |
| | const output = document.getElementById('sandbox-output'); |
| | output.textContent += `\n>>> ${code}`; |
| | |
| | grpoSystem.executeInSandbox(code).then(result => { |
| | output.textContent += `\n${result}`; |
| | output.scrollTop = output.scrollHeight; |
| | }); |
| | |
| | input.value = ''; |
| | } |
| | } |
| | } |
| | |
| | |
| | window.addEventListener('load', initialize); |
| | |
| | |
| | window.addEventListener('beforeunload', (e) => { |
| | if (window.matchMedia('(display-mode: standalone)').matches) { |
| | e.preventDefault(); |
| | e.returnValue = ''; |
| | } |
| | }); |
| | </script> |
| | </body> |
| | </html> |
| |
|