akhaliq/North-Mini-Code-1.0
0
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="UTF-8">5<meta name="viewport" content="width=device-width, initial-scale=1.0">6<title>North Mini Code 1.0</title>7<meta name="description" content="Terminal-style coding assistant powered by North Mini Code 1.0">8<link rel="preconnect" href="https://fonts.googleapis.com">9<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>10<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet">11<style>12/* ═══════════════════════════════════════════════════════13 RESET & BASE14 ═══════════════════════════════════════════════════════ */15*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }16 17:root {18 --bg-deep: #0a0e14;19 --bg-panel: #0d1117;20 --bg-code: #161b22;21 --border: #1e2a3a;22 --border-focus: #2d4a6a;23 --green: #39ff14;24 --green-dim: #1a7a0a;25 --cyan: #00d4ff;26 --amber: #ffb300;27 --gray-light: #e0e0e0;28 --gray-mid: #8b949e;29 --gray-dim: #484f58;30 --red: #ff5555;31 --success: #50fa7b;32 --code-text: #f8f8f2;33 --glow-green: 0 0 8px rgba(57,255,20,0.3);34 --glow-cyan: 0 0 8px rgba(0,212,255,0.3);35 --glow-amber: 0 0 8px rgba(255,179,0,0.2);36 --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;37 --radius: 4px;38 --transition: 0.2s ease;39}40 41html, body {42 height: 100%;43 background: var(--bg-deep);44 color: var(--gray-light);45 font-family: var(--font-mono);46 font-size: 13px;47 line-height: 1.6;48 overflow: hidden;49}50 51/* CRT scanline overlay */52body::after {53 content: '';54 position: fixed;55 inset: 0;56 pointer-events: none;57 z-index: 9999;58 background: repeating-linear-gradient(59 0deg,60 transparent,61 transparent 2px,62 rgba(0, 0, 0, 0.03) 2px,63 rgba(0, 0, 0, 0.03) 4px64 );65}66 67::selection {68 background: rgba(57, 255, 20, 0.25);69 color: #fff;70}71 72::-webkit-scrollbar { width: 6px; height: 6px; }73::-webkit-scrollbar-track { background: transparent; }74::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }75::-webkit-scrollbar-thumb:hover { background: var(--gray-dim); }76 77a { color: var(--cyan); text-decoration: none; }78a:hover { text-decoration: underline; text-shadow: var(--glow-cyan); }79 80/* ═══════════════════════════════════════════════════════81 APP SHELL82 ═══════════════════════════════════════════════════════ */83#app {84 display: flex;85 flex-direction: column;86 height: 100vh;87 max-height: 100vh;88}89 90/* ═══════════════════════════════════════════════════════91 CONFIG WARNING92 ═══════════════════════════════════════════════════════ */93#config-warning {94 display: none;95 background: linear-gradient(90deg, rgba(255,85,85,0.1), rgba(255,179,0,0.1));96 border-bottom: 1px solid var(--red);97 padding: 8px 16px;98 font-size: 12px;99 color: var(--amber);100 text-align: center;101 text-shadow: var(--glow-amber);102}103#config-warning.visible { display: block; }104 105/* ═══════════════════════════════════════════════════════106 HEADER107 ═══════════════════════════════════════════════════════ */108#header {109 display: flex;110 align-items: center;111 justify-content: space-between;112 padding: 12px 20px;113 background: var(--bg-panel);114 border-bottom: 1px solid var(--border);115 flex-shrink: 0;116 gap: 16px;117}118 119.header-title {120 display: flex;121 flex-direction: column;122 gap: 2px;123}124 125.header-ascii {126 color: var(--green);127 font-size: 11px;128 line-height: 1.3;129 text-shadow: var(--glow-green);130 white-space: pre;131 letter-spacing: 0.5px;132}133 134.header-subtitle {135 color: var(--gray-mid);136 font-size: 11px;137 padding-left: 3px;138}139 140.header-actions {141 display: flex;142 align-items: center;143 gap: 10px;144 flex-shrink: 0;145}146 147.pill {148 display: inline-flex;149 align-items: center;150 gap: 5px;151 padding: 4px 10px;152 border: 1px solid var(--border);153 border-radius: 12px;154 font-size: 11px;155 color: var(--gray-mid);156 text-decoration: none;157 transition: all var(--transition);158}159.pill:hover {160 border-color: var(--cyan);161 color: var(--cyan);162 text-decoration: none;163 text-shadow: var(--glow-cyan);164}165.pill .dot {166 width: 6px;167 height: 6px;168 border-radius: 50%;169 background: var(--success);170 box-shadow: 0 0 6px var(--success);171}172 173#btn-new-chat {174 background: transparent;175 border: 1px solid var(--border);176 color: var(--amber);177 font-family: var(--font-mono);178 font-size: 11px;179 padding: 5px 12px;180 border-radius: var(--radius);181 cursor: pointer;182 transition: all var(--transition);183 letter-spacing: 1px;184}185#btn-new-chat:hover {186 border-color: var(--amber);187 background: rgba(255,179,0,0.08);188 text-shadow: var(--glow-amber);189}190 191/* ═══════════════════════════════════════════════════════192 MAIN LAYOUT193 ═══════════════════════════════════════════════════════ */194#main {195 display: flex;196 flex: 1;197 min-height: 0;198 overflow: hidden;199}200 201/* ═══════════════════════════════════════════════════════202 TERMINAL (LEFT PANEL)203 ═══════════════════════════════════════════════════════ */204#terminal-panel {205 display: flex;206 flex-direction: column;207 flex: 1;208 min-width: 0;209 border-right: 1px solid var(--border);210}211 212.panel-label {213 padding: 6px 16px;214 font-size: 10px;215 letter-spacing: 2px;216 color: var(--gray-dim);217 border-bottom: 1px solid var(--border);218 background: rgba(13,17,23,0.6);219 text-transform: uppercase;220 flex-shrink: 0;221}222 223#chat-messages {224 flex: 1;225 overflow-y: auto;226 padding: 16px;227 scroll-behavior: smooth;228}229 230/* Message styles */231.msg {232 margin-bottom: 14px;233 line-height: 1.65;234 animation: msgFadeIn 0.25s ease;235}236 237@keyframes msgFadeIn {238 from { opacity: 0; transform: translateY(6px); }239 to { opacity: 1; transform: translateY(0); }240}241 242.msg-prefix {243 font-weight: 600;244 margin-right: 4px;245}246 247.msg-user .msg-prefix {248 color: var(--green);249 text-shadow: var(--glow-green);250}251.msg-user .msg-content {252 color: var(--green);253 text-shadow: var(--glow-green);254}255 256.msg-assistant .msg-prefix {257 color: var(--cyan);258 text-shadow: var(--glow-cyan);259}260.msg-assistant .msg-content {261 color: var(--gray-light);262}263 264.msg-system .msg-prefix {265 color: var(--amber);266 text-shadow: var(--glow-amber);267}268.msg-system .msg-content {269 color: var(--amber);270 opacity: 0.85;271}272 273/* Markdown elements */274.msg-content strong { color: #fff; font-weight: 600; }275.msg-content em { font-style: italic; color: var(--gray-mid); }276.msg-content code:not(pre code) {277 background: var(--bg-code);278 color: var(--code-text);279 padding: 1px 5px;280 border-radius: 3px;281 font-size: 12px;282 border: 1px solid var(--border);283}284.msg-content a { color: var(--cyan); }285.msg-content ul, .msg-content ol {286 margin: 6px 0 6px 20px;287}288.msg-content li { margin-bottom: 3px; }289.msg-content h1, .msg-content h2, .msg-content h3 {290 color: var(--cyan);291 margin: 10px 0 6px;292 font-size: 14px;293 text-shadow: var(--glow-cyan);294}295.msg-content h1 { font-size: 16px; }296.msg-content h2 { font-size: 15px; }297.msg-content p { margin: 4px 0; }298 299/* Code blocks */300.code-block-wrap {301 margin: 8px 0;302 border: 1px solid var(--border);303 border-radius: var(--radius);304 overflow: hidden;305 background: var(--bg-code);306}307.code-block-header {308 display: flex;309 align-items: center;310 justify-content: space-between;311 padding: 4px 10px;312 background: rgba(30,42,58,0.5);313 border-bottom: 1px solid var(--border);314 font-size: 11px;315}316.code-lang {317 color: var(--amber);318 text-transform: uppercase;319 letter-spacing: 1px;320}321.btn-copy {322 background: transparent;323 border: 1px solid var(--border);324 color: var(--gray-mid);325 font-family: var(--font-mono);326 font-size: 10px;327 padding: 2px 8px;328 border-radius: 3px;329 cursor: pointer;330 transition: all var(--transition);331}332.btn-copy:hover {333 border-color: var(--green);334 color: var(--green);335}336.btn-copy.copied {337 border-color: var(--success);338 color: var(--success);339}340.code-block-wrap pre {341 margin: 0;342 padding: 10px 12px;343 overflow-x: auto;344 font-size: 12px;345 line-height: 1.5;346 color: var(--code-text);347}348.code-block-wrap pre code {349 font-family: var(--font-mono);350 background: none;351 border: none;352 padding: 0;353}354 355/* Thinking blocks */356.think-block {357 margin: 8px 0;358 border: 1px solid rgba(255,179,0,0.15);359 border-radius: var(--radius);360 background: rgba(255,179,0,0.03);361}362.think-block summary {363 padding: 6px 10px;364 cursor: pointer;365 font-size: 12px;366 color: var(--gray-dim);367 user-select: none;368 transition: color var(--transition);369}370.think-block summary:hover { color: var(--amber); }371.think-block .think-content {372 padding: 6px 12px 10px;373 font-size: 12px;374 color: var(--gray-dim);375 line-height: 1.55;376 border-top: 1px solid rgba(255,179,0,0.1);377}378 379/* Streaming cursor */380.streaming-cursor::after {381 content: '█';382 animation: blink 0.8s step-end infinite;383 color: var(--green);384 margin-left: 2px;385}386@keyframes blink {387 50% { opacity: 0; }388}389 390/* ═══════════════════════════════════════════════════════391 INPUT AREA392 ═══════════════════════════════════════════════════════ */393#input-area {394 flex-shrink: 0;395 border-top: 1px solid var(--border);396 background: var(--bg-panel);397 padding: 10px 16px 8px;398}399 400/* Target toggle */401#target-toggle {402 display: flex;403 gap: 14px;404 margin-bottom: 8px;405 align-items: center;406}407 408.target-label {409 font-size: 10px;410 color: var(--gray-dim);411 letter-spacing: 1px;412 text-transform: uppercase;413}414 415.target-btn {416 background: transparent;417 border: none;418 font-family: var(--font-mono);419 font-size: 12px;420 color: var(--gray-dim);421 cursor: pointer;422 padding: 2px 0;423 border-bottom: 2px solid transparent;424 transition: all var(--transition);425 letter-spacing: 0.5px;426}427.target-btn:hover { color: var(--gray-mid); }428.target-btn.active {429 color: var(--green);430 border-bottom-color: var(--green);431 text-shadow: var(--glow-green);432}433 434#input-row {435 display: flex;436 gap: 8px;437 align-items: flex-end;438}439 440.input-prompt-symbol {441 color: var(--green);442 font-weight: 700;443 font-size: 14px;444 line-height: 36px;445 text-shadow: var(--glow-green);446 flex-shrink: 0;447}448 449#chat-input {450 flex: 1;451 background: var(--bg-deep);452 border: 1px solid var(--border);453 border-radius: var(--radius);454 color: var(--green);455 font-family: var(--font-mono);456 font-size: 13px;457 padding: 8px 12px;458 resize: none;459 outline: none;460 min-height: 36px;461 max-height: 120px;462 line-height: 1.5;463 transition: border-color var(--transition);464 caret-color: var(--green);465 text-shadow: var(--glow-green);466}467#chat-input::placeholder {468 color: var(--gray-dim);469 text-shadow: none;470}471#chat-input:focus {472 border-color: var(--border-focus);473}474 475#btn-send, #btn-stop {476 font-family: var(--font-mono);477 font-size: 12px;478 padding: 8px 14px;479 border-radius: var(--radius);480 cursor: pointer;481 transition: all var(--transition);482 letter-spacing: 1px;483 flex-shrink: 0;484 height: 36px;485 display: flex;486 align-items: center;487 gap: 4px;488}489 490#btn-send {491 background: transparent;492 border: 1px solid var(--green);493 color: var(--green);494}495#btn-send:hover:not(:disabled) {496 background: var(--green);497 color: var(--bg-deep);498 box-shadow: 0 0 12px rgba(57,255,20,0.3);499}500#btn-send:disabled {501 opacity: 0.3;502 cursor: not-allowed;503}504 505#btn-stop {506 background: transparent;507 border: 1px solid var(--red);508 color: var(--red);509 display: none;510}511#btn-stop:hover {512 background: var(--red);513 color: var(--bg-deep);514 box-shadow: 0 0 12px rgba(255,85,85,0.3);515}516 517/* Examples */518#examples-row {519 display: flex;520 align-items: center;521 gap: 8px;522 margin-top: 8px;523 flex-wrap: wrap;524}525.examples-label {526 font-size: 10px;527 color: var(--gray-dim);528 letter-spacing: 1px;529 text-transform: uppercase;530 flex-shrink: 0;531}532.example-chip {533 background: rgba(30,42,58,0.4);534 border: 1px solid var(--border);535 border-radius: 12px;536 padding: 3px 10px;537 font-family: var(--font-mono);538 font-size: 11px;539 color: var(--gray-mid);540 cursor: pointer;541 transition: all var(--transition);542 white-space: nowrap;543}544.example-chip:hover {545 border-color: var(--cyan);546 color: var(--cyan);547 background: rgba(0,212,255,0.05);548 text-shadow: var(--glow-cyan);549}550 551/* ═══════════════════════════════════════════════════════552 OUTPUT PANEL (RIGHT)553 ═══════════════════════════════════════════════════════ */554#output-panel {555 display: flex;556 flex-direction: column;557 width: 45%;558 min-width: 340px;559 max-width: 55%;560 background: var(--bg-panel);561}562 563#output-tabs {564 display: flex;565 border-bottom: 1px solid var(--border);566 background: rgba(13,17,23,0.6);567 flex-shrink: 0;568}569 570.output-tab {571 flex: 1;572 background: transparent;573 border: none;574 border-bottom: 2px solid transparent;575 color: var(--gray-dim);576 font-family: var(--font-mono);577 font-size: 11px;578 padding: 8px 12px;579 cursor: pointer;580 transition: all var(--transition);581 letter-spacing: 1px;582 text-transform: uppercase;583}584.output-tab:hover { color: var(--gray-mid); }585.output-tab.active {586 color: var(--cyan);587 border-bottom-color: var(--cyan);588 text-shadow: var(--glow-cyan);589}590 591#output-content {592 flex: 1;593 overflow: auto;594 position: relative;595}596 597/* Tab panes */598.tab-pane { display: none; height: 100%; }599.tab-pane.active { display: flex; flex-direction: column; }600 601/* Preview tab */602#pane-preview {603 align-items: center;604 justify-content: center;605 position: relative;606}607 608.preview-placeholder {609 text-align: center;610 color: var(--gray-dim);611 padding: 40px 20px;612}613.preview-placeholder .ascii-art {614 font-size: 11px;615 line-height: 1.3;616 margin-bottom: 16px;617 color: var(--border-focus);618}619.preview-placeholder .placeholder-text {620 font-size: 12px;621 letter-spacing: 0.5px;622}623 624#preview-image {625 display: none;626 max-width: 100%;627 max-height: 100%;628 object-fit: contain;629 padding: 12px;630}631 632#preview-iframe {633 display: none;634 width: 100%;635 flex: 1;636 min-height: 680px;637 border: none;638 background: #fff;639}640 641#btn-fullscreen {642 display: none;643 position: absolute;644 top: 8px;645 right: 8px;646 background: rgba(13,17,23,0.8);647 border: 1px solid var(--border);648 color: var(--gray-mid);649 font-family: var(--font-mono);650 font-size: 11px;651 padding: 4px 10px;652 border-radius: var(--radius);653 cursor: pointer;654 z-index: 5;655 transition: all var(--transition);656}657#btn-fullscreen:hover {658 border-color: var(--cyan);659 color: var(--cyan);660}661 662/* Console tab */663#pane-console { padding: 12px 16px; gap: 12px; overflow-y: auto; }664 665.console-section { margin-bottom: 8px; }666.console-label {667 font-size: 10px;668 letter-spacing: 2px;669 color: var(--gray-dim);670 margin-bottom: 4px;671 text-transform: uppercase;672}673.console-output {674 background: var(--bg-deep);675 border: 1px solid var(--border);676 border-radius: var(--radius);677 padding: 10px 12px;678 font-size: 12px;679 line-height: 1.5;680 white-space: pre-wrap;681 word-break: break-word;682 min-height: 40px;683 max-height: 280px;684 overflow-y: auto;685}686#console-stdout { color: var(--success); }687#console-stderr { color: var(--red); }688 689/* Code tab */690#pane-code { padding: 0; }691 692.code-tab-header {693 display: flex;694 align-items: center;695 justify-content: space-between;696 padding: 8px 12px;697 border-bottom: 1px solid var(--border);698 background: rgba(30,42,58,0.3);699 flex-shrink: 0;700}701.code-tab-lang {702 font-size: 11px;703 color: var(--amber);704 letter-spacing: 1px;705 text-transform: uppercase;706}707.code-tab-actions {708 display: flex;709 gap: 8px;710}711.code-tab-btn {712 background: transparent;713 border: 1px solid var(--border);714 color: var(--gray-mid);715 font-family: var(--font-mono);716 font-size: 10px;717 padding: 3px 8px;718 border-radius: 3px;719 cursor: pointer;720 text-decoration: none;721 transition: all var(--transition);722 display: inline-flex;723 align-items: center;724 gap: 4px;725}726.code-tab-btn:hover {727 border-color: var(--cyan);728 color: var(--cyan);729 text-decoration: none;730}731 732#code-display {733 flex: 1;734 overflow: auto;735 padding: 12px;736 background: var(--bg-code);737}738#code-display pre {739 margin: 0;740 font-size: 12px;741 line-height: 1.5;742 color: var(--code-text);743}744 745.code-placeholder {746 display: flex;747 align-items: center;748 justify-content: center;749 height: 100%;750 color: var(--gray-dim);751 font-size: 12px;752}753 754/* ═══════════════════════════════════════════════════════755 STATUS BAR756 ═══════════════════════════════════════════════════════ */757#status-bar {758 display: flex;759 align-items: center;760 gap: 8px;761 padding: 5px 16px;762 border-top: 1px solid var(--border);763 background: var(--bg-panel);764 font-size: 11px;765 flex-shrink: 0;766}767 768.status-indicator {769 display: inline-flex;770 align-items: center;771 gap: 6px;772}773 774.status-dot {775 font-size: 10px;776 line-height: 1;777}778 779#status-text {780 letter-spacing: 1px;781 text-transform: uppercase;782}783 784.status-idle { color: var(--gray-dim); }785.status-working { color: var(--amber); text-shadow: var(--glow-amber); }786.status-success { color: var(--success); text-shadow: 0 0 8px rgba(80,250,123,0.3); }787.status-error { color: var(--red); text-shadow: 0 0 8px rgba(255,85,85,0.3); }788.status-info { color: var(--cyan); text-shadow: var(--glow-cyan); }789 790@keyframes spin {791 to { transform: rotate(360deg); }792}793.status-working .status-dot {794 display: inline-block;795 animation: spin 1s linear infinite;796}797 798/* ═══════════════════════════════════════════════════════799 FULLSCREEN OVERLAY800 ═══════════════════════════════════════════════════════ */801#fullscreen-overlay {802 display: none;803 position: fixed;804 inset: 0;805 z-index: 1000;806 background: var(--bg-deep);807 flex-direction: column;808}809#fullscreen-overlay.active { display: flex; }810 811#fullscreen-bar {812 display: flex;813 align-items: center;814 justify-content: space-between;815 padding: 8px 16px;816 border-bottom: 1px solid var(--border);817 background: var(--bg-panel);818}819#fullscreen-bar span {820 color: var(--cyan);821 font-size: 12px;822 letter-spacing: 1px;823}824#btn-exit-fullscreen {825 background: transparent;826 border: 1px solid var(--border);827 color: var(--gray-mid);828 font-family: var(--font-mono);829 font-size: 11px;830 padding: 4px 12px;831 border-radius: var(--radius);832 cursor: pointer;833 transition: all var(--transition);834}835#btn-exit-fullscreen:hover {836 border-color: var(--red);837 color: var(--red);838}839#fullscreen-iframe {840 flex: 1;841 border: none;842 background: #fff;843}844 845/* ═══════════════════════════════════════════════════════846 RESPONSIVE847 ═══════════════════════════════════════════════════════ */848@media (max-width: 900px) {849 #main {850 flex-direction: column;851 }852 #terminal-panel {853 border-right: none;854 border-bottom: 1px solid var(--border);855 max-height: 55vh;856 }857 #output-panel {858 width: 100%;859 max-width: 100%;860 min-width: 0;861 flex: 1;862 }863 .header-ascii { font-size: 10px; }864 #chat-input { font-size: 12px; }865 #preview-iframe { min-height: 400px; }866}867 868@media (max-width: 600px) {869 #header { padding: 8px 12px; gap: 8px; }870 .header-ascii { display: none; }871 .header-subtitle { display: none; }872 .pill { font-size: 10px; padding: 3px 8px; }873 #chat-messages { padding: 10px; }874 #input-area { padding: 8px 10px 6px; }875 #examples-row { display: none; }876}877</style>878</head>879<body>880<div id="app">881 <!-- Config Warning -->882 <div id="config-warning">⚠ WARNING: COHERE_API_KEY not configured. Set it as a Space secret to enable the model.</div>883 884 <!-- Header -->885 <header id="header">886 <div class="header-title">887 <div class="header-ascii" id="header-ascii-art">╔═══ NORTH MINI CODE 1.0 ═══╗</div>888 <div class="header-subtitle" id="header-subtitle">Terminal Coding Assistant</div>889 </div>890 <div class="header-actions">891 <a class="pill" id="model-pill" href="#" target="_blank" rel="noopener">892 <span class="dot"></span>893 <span id="model-pill-text">north-mini-code-1-0</span>894 </a>895 <a class="pill" id="opencode-pill" href="#" target="_blank" rel="noopener">OpenCode</a>896 <button id="btn-new-chat" onclick="newChat()" title="Start a new chat session">[NEW]</button>897 </div>898 </header>899 900 <!-- Main Layout -->901 <div id="main">902 <!-- Terminal Panel -->903 <div id="terminal-panel">904 <div class="panel-label">Terminal</div>905 <div id="chat-messages"></div>906 <div id="input-area">907 <div id="target-toggle">908 <span class="target-label">Mode:</span>909 <button class="target-btn active" data-target="Python" onclick="setTarget('Python')">[ Python ● ]</button>910 <button class="target-btn" data-target="Web" onclick="setTarget('Web')">[ Web ○ ]</button>911 </div>912 <div id="input-row">913 <span class="input-prompt-symbol">❯</span>914 <textarea id="chat-input" rows="1" placeholder="Ask a coding question or describe what to build…" spellcheck="false"></textarea>915 <button id="btn-send" onclick="handleSend()" title="Send message (Shift+Enter)">➤</button>916 <button id="btn-stop" onclick="stopGeneration()" title="Stop generation">■ STOP</button>917 </div>918 <div id="examples-row"></div>919 </div>920 </div>921 922 <!-- Output Panel -->923 <div id="output-panel">924 <div id="output-tabs">925 <button class="output-tab active" data-tab="preview" onclick="switchTab('preview')">Preview</button>926 <button class="output-tab" data-tab="console" onclick="switchTab('console')">Console</button>927 <button class="output-tab" data-tab="code" onclick="switchTab('code')">Code</button>928 </div>929 <div id="output-content">930 <!-- Preview Pane -->931 <div class="tab-pane active" id="pane-preview">932 <div class="preview-placeholder" id="preview-placeholder">933 <div class="ascii-art">934 ┌──────────────────────┐935 │ ╭━━━╮ │936 │ ┃ ▶ ┃ OUTPUT │937 │ ╰━━━╯ │938 └──────────────────────┘</div>939 <div class="placeholder-text">Run code to see output here</div>940 </div>941 <img id="preview-image" alt="Generated output">942 <iframe id="preview-iframe" sandbox="allow-scripts"></iframe>943 <button id="btn-fullscreen" onclick="openFullscreen()">⤢ FULLSCREEN</button>944 </div>945 946 <!-- Console Pane -->947 <div class="tab-pane" id="pane-console">948 <div class="console-section">949 <div class="console-label">stdout:</div>950 <div class="console-output" id="console-stdout">No output yet.</div>951 </div>952 <div class="console-section">953 <div class="console-label">stderr:</div>954 <div class="console-output" id="console-stderr">No errors.</div>955 </div>956 </div>957 958 <!-- Code Pane -->959 <div class="tab-pane" id="pane-code">960 <div class="code-tab-header">961 <span class="code-tab-lang" id="code-tab-lang">—</span>962 <div class="code-tab-actions">963 <button class="code-tab-btn" id="btn-copy-code" onclick="copyCode()">📋 Copy</button>964 <a class="code-tab-btn" id="btn-download" href="#" style="display:none;">⬇ Download</a>965 </div>966 </div>967 <div id="code-display">968 <div class="code-placeholder">No code generated yet.</div>969 </div>970 </div>971 </div>972 </div>973 </div>974 975 <!-- Status Bar -->976 <div id="status-bar">977 <div class="status-indicator status-idle" id="status-indicator">978 <span class="status-dot">●</span>979 <span id="status-text">IDLE</span>980 </div>981 </div>982</div>983 984<!-- Fullscreen Overlay -->985<div id="fullscreen-overlay">986 <div id="fullscreen-bar">987 <span>WEB PREVIEW</span>988 <button id="btn-exit-fullscreen" onclick="closeFullscreen()">[✕ CLOSE]</button>989 </div>990 <iframe id="fullscreen-iframe" sandbox="allow-scripts"></iframe>991</div>992 993<script>994// ═══════════════════════════════════════════════════════995// CONFIG996// ═══════════════════════════════════════════════════════997const CONFIG = __RUNTIME_CONFIG__;998 999// ═══════════════════════════════════════════════════════1000// STATE1001// ═══════════════════════════════════════════════════════1002const state = {1003 history: [],1004 executionContext: {},1005 targetLanguage: 'Python',1006 isGenerating: false,1007 currentEventSource: null,1008 activeTab: 'preview',1009 lastExecution: null,1010 lastCode: '',1011 lastCodeLang: ''1012};1013 1014// ═══════════════════════════════════════════════════════1015// INITIALIZATION1016// ═══════════════════════════════════════════════════════1017document.addEventListener('DOMContentLoaded', () => {1018 // Apply config1019 document.title = CONFIG.app_title || 'North Mini Code 1.0';1020 1021 if (CONFIG.model_url) {1022 document.getElementById('model-pill').href = CONFIG.model_url;1023 }1024 if (CONFIG.model_id) {1025 document.getElementById('model-pill-text').textContent = CONFIG.model_id;1026 }1027 if (CONFIG.opencode_url) {1028 document.getElementById('opencode-pill').href = CONFIG.opencode_url;1029 }1030 1031 // Config warning1032 if (!CONFIG.api_key_configured) {1033 document.getElementById('config-warning').classList.add('visible');1034 }1035 1036 // Render examples1037 renderExamples();1038 1039 // Welcome message1040 addSystemMessage(`Welcome to ${CONFIG.app_title || 'North Mini Code 1.0'}. Type a coding question or select an example below to get started.`);1041 1042 // Input auto-resize & keybinding1043 const input = document.getElementById('chat-input');1044 input.addEventListener('input', autoResize);1045 input.addEventListener('keydown', (e) => {1046 if (e.key === 'Enter' && e.shiftKey) {1047 e.preventDefault();1048 handleSend();1049 }1050 });1051});1052 1053function autoResize() {1054 const el = document.getElementById('chat-input');1055 el.style.height = 'auto';1056 el.style.height = Math.min(el.scrollHeight, 120) + 'px';1057}1058 1059// ═══════════════════════════════════════════════════════1060// EXAMPLES1061// ═══════════════════════════════════════════════════════1062function renderExamples() {1063 const row = document.getElementById('examples-row');1064 if (!CONFIG.examples || CONFIG.examples.length === 0) {1065 row.style.display = 'none';1066 return;1067 }1068 row.innerHTML = '<span class="examples-label">Try:</span>';1069 CONFIG.examples.forEach((ex) => {1070 const chip = document.createElement('button');1071 chip.className = 'example-chip';1072 chip.textContent = ex.label;1073 chip.title = ex.prompt;1074 chip.addEventListener('click', () => {1075 if (ex.target) setTarget(ex.target);1076 sendMessage(ex.prompt);1077 });1078 row.appendChild(chip);1079 });1080}1081 1082// ═══════════════════════════════════════════════════════1083// TARGET LANGUAGE1084// ═══════════════════════════════════════════════════════1085function setTarget(target) {1086 state.targetLanguage = target;1087 document.querySelectorAll('.target-btn').forEach((btn) => {1088 const isActive = btn.dataset.target === target;1089 btn.classList.toggle('active', isActive);1090 btn.textContent = `[ ${btn.dataset.target} ${isActive ? '●' : '○'} ]`;1091 });1092}1093 1094// ═══════════════════════════════════════════════════════1095// CHAT MESSAGES1096// ═══════════════════════════════════════════════════════1097function addSystemMessage(text) {1098 const container = document.getElementById('chat-messages');1099 const div = document.createElement('div');1100 div.className = 'msg msg-system';1101 div.innerHTML = `<span class="msg-prefix">system></span><span class="msg-content">${escapeHtml(text)}</span>`;1102 container.appendChild(div);1103 scrollToBottom();1104}1105 1106function addUserMessage(text) {1107 const container = document.getElementById('chat-messages');1108 const div = document.createElement('div');1109 div.className = 'msg msg-user';1110 div.innerHTML = `<span class="msg-prefix">user></span><span class="msg-content">${escapeHtml(text)}</span>`;1111 container.appendChild(div);1112 scrollToBottom();1113}1114 1115function addAssistantMessage() {1116 const container = document.getElementById('chat-messages');1117 const div = document.createElement('div');1118 div.className = 'msg msg-assistant';1119 div.id = 'current-assistant-msg';1120 div.innerHTML = `<span class="msg-prefix">north></span><span class="msg-content streaming-cursor"></span>`;1121 container.appendChild(div);1122 scrollToBottom();1123 return div;1124}1125 1126function updateAssistantMessage(content, isStreaming) {1127 const div = document.getElementById('current-assistant-msg');1128 if (!div) return;1129 const contentEl = div.querySelector('.msg-content');1130 contentEl.innerHTML = parseMarkdown(content);1131 if (isStreaming) {1132 contentEl.classList.add('streaming-cursor');1133 } else {1134 contentEl.classList.remove('streaming-cursor');1135 }1136 scrollToBottom();1137}1138 1139function finalizeAssistantMessage() {1140 const div = document.getElementById('current-assistant-msg');1141 if (div) {1142 div.id = '';1143 const contentEl = div.querySelector('.msg-content');1144 if (contentEl) contentEl.classList.remove('streaming-cursor');1145 }1146}1147 1148function scrollToBottom() {1149 const container = document.getElementById('chat-messages');1150 requestAnimationFrame(() => {1151 container.scrollTop = container.scrollHeight;1152 });1153}1154 1155// ═══════════════════════════════════════════════════════1156// MARKDOWN PARSER1157// ═══════════════════════════════════════════════════════1158function parseMarkdown(text) {1159 if (!text) return '';1160 1161 // Handle think blocks1162 text = text.replace(/<think>([\s\S]*?)<\/think>/g, (_, content) => {1163 return `<details class="think-block"><summary>💭 Reasoning (click to expand)</summary><div class="think-content">${escapeHtml(content.trim())}</div></details>`;1164 });1165 // Handle unclosed think blocks (during streaming)1166 text = text.replace(/<think>([\s\S]*)$/g, (_, content) => {1167 return `<details class="think-block"><summary>💭 Reasoning (thinking…)</summary><div class="think-content">${escapeHtml(content.trim())}</div></details>`;1168 });1169 1170 // Extract code blocks first to prevent inner processing1171 const codeBlocks = [];1172 text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {1173 const idx = codeBlocks.length;1174 codeBlocks.push({ lang: lang || 'text', code: code.trimEnd() });1175 return `\x00CODEBLOCK_${idx}\x00`;1176 });1177 1178 // Escape HTML in remaining text1179 text = escapeHtml(text);1180 1181 // Restore code blocks with formatting1182 text = text.replace(/\x00CODEBLOCK_(\d+)\x00/g, (_, idx) => {1183 const block = codeBlocks[parseInt(idx)];1184 const escapedCode = escapeHtml(block.code);1185 const id = `code-${Date.now()}-${idx}`;1186 return `<div class="code-block-wrap"><div class="code-block-header"><span class="code-lang">${escapeHtml(block.lang)}</span><button class="btn-copy" onclick="copyBlock(this, '${id}')">📋 Copy</button></div><pre><code id="${id}">${escapedCode}</code></pre></div>`;1187 });1188 1189 // Inline formatting1190 text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');1191 text = text.replace(/(?<!\*)\*([^*]+?)\*(?!\*)/g, '<em>$1</em>');1192 text = text.replace(/`([^`]+?)`/g, '<code>$1</code>');1193 text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');1194 1195 // Headings1196 text = text.replace(/^### (.+)$/gm, '<h3>$1</h3>');1197 text = text.replace(/^## (.+)$/gm, '<h2>$1</h2>');1198 text = text.replace(/^# (.+)$/gm, '<h1>$1</h1>');1199 1200 // Lists - simple approach