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