koneck 2.25.76 → 2.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/ui.js ADDED
@@ -0,0 +1,586 @@
1
+ /**
2
+ * The single page, as a string.
3
+ *
4
+ * No build step and no dependencies: one HTML document with its styles and script inline, served
5
+ * from memory. A web UI that needs bundling is a web UI that rots — and KONECK is installed with
6
+ * `npm i -g koneck`, so anything requiring a second toolchain would be broken for most people on
7
+ * the day they first tried it.
8
+ *
9
+ * Nothing is fetched from the network either. A page that pulls a font or a script from a CDN does
10
+ * not work on a machine that is offline, which is the machine most likely to be running a local
11
+ * model in the first place.
12
+ */
13
+ /** The mark, drawn as the terminal draws it: a K facing its own reflection. */
14
+ const MARK = `<svg viewBox="0 0 34 12" class="mark" aria-hidden="true">
15
+ <g fill="currentColor">
16
+ <rect x="0" y="0" width="5" height="12"/><rect x="29" y="0" width="5" height="12"/>
17
+ <path d="M5 6 L11 0 L14 0 L7 7 Z"/><path d="M5 6 L11 12 L14 12 L7 5 Z"/>
18
+ <path d="M29 6 L23 0 L20 0 L27 7 Z"/><path d="M29 6 L23 12 L20 12 L27 5 Z"/>
19
+ </g></svg>`;
20
+ /** The mirrored K, as a character reference so no source file needs the glyph itself. */
21
+ const KK = '<span class="kk">K&#42200;</span>';
22
+ export function page() {
23
+ return `<!doctype html>
24
+ <html lang="en"><head>
25
+ <meta charset="utf-8">
26
+ <meta name="viewport" content="width=device-width,initial-scale=1">
27
+ <meta name="referrer" content="no-referrer">
28
+ <title>KONECK</title>
29
+ <style>
30
+ :root{
31
+ --bg:#0b1013; --panel:#111a1f; --panel-2:#16232a; --line:#1e3038;
32
+ --ink:#d7e6ec; --muted:#7f9aa6; --dim:#4f6874;
33
+ --cyan:#4dd6e8; --amber:#e8b04d; --green:#57d99a; --red:#ef6d72; --violet:#a78bfa;
34
+ --radius:10px; --mono:ui-monospace,'SF Mono',Menlo,Consolas,'DejaVu Sans Mono',monospace;
35
+ }
36
+ *{box-sizing:border-box}
37
+ html,body{height:100%}
38
+ body{margin:0;background:var(--bg);color:var(--ink);
39
+ font:14px/1.55 ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
40
+ display:flex;overflow:hidden}
41
+ button,input,textarea,select{font:inherit;color:inherit}
42
+ ::-webkit-scrollbar{width:9px;height:9px}
43
+ ::-webkit-scrollbar-thumb{background:#24373f;border-radius:9px}
44
+ ::-webkit-scrollbar-track{background:transparent}
45
+ .kk{color:var(--cyan);font-family:var(--mono)}
46
+
47
+ aside{width:264px;flex:0 0 264px;background:var(--panel);border-right:1px solid var(--line);
48
+ display:flex;flex-direction:column;min-height:0}
49
+ .brand{display:flex;align-items:center;gap:9px;padding:16px 16px 13px;color:var(--cyan)}
50
+ .mark{width:30px;height:11px;display:block}
51
+ .brand b{font-size:13px;letter-spacing:.16em;font-weight:600;color:var(--ink)}
52
+ .brand span{font-size:9px;letter-spacing:.14em;color:var(--dim);
53
+ border:1px solid var(--line);border-radius:4px;padding:2px 5px}
54
+ .newbtn{margin:0 12px 14px;padding:9px;border:1px solid var(--line);background:var(--panel-2);
55
+ border-radius:var(--radius);color:var(--ink);cursor:pointer;display:flex;align-items:center;
56
+ justify-content:center;gap:7px;transition:.14s}
57
+ .newbtn:hover{border-color:var(--cyan);color:var(--cyan)}
58
+ .shead{padding:6px 16px;font-size:10px;letter-spacing:.13em;color:var(--dim);text-transform:uppercase}
59
+ .slist{overflow-y:auto;flex:1;min-height:0;padding:0 8px 10px}
60
+ .srow{padding:8px 10px;border-radius:8px;cursor:pointer;margin-bottom:2px;border:1px solid transparent}
61
+ .srow:hover{background:var(--panel-2)}
62
+ .srow.on{background:var(--panel-2);border-color:var(--line)}
63
+ .srow .t{font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
64
+ .srow .m{font-size:10.5px;color:var(--dim);margin-top:2px;display:flex;gap:7px;align-items:center}
65
+ .dot{width:6px;height:6px;border-radius:50%;background:var(--green);flex:0 0 6px}
66
+ .dot.busy{background:var(--amber);animation:pulse 1.1s infinite}
67
+ @keyframes pulse{50%{opacity:.28}}
68
+ .foot{border-top:1px solid var(--line);padding:10px 14px;font-size:10.5px;color:var(--dim);
69
+ display:flex;justify-content:space-between;align-items:center}
70
+
71
+ main{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0}
72
+ header{height:46px;flex:0 0 46px;border-bottom:1px solid var(--line);display:flex;
73
+ align-items:center;gap:10px;padding:0 16px;background:var(--panel);font-size:12px}
74
+ .pill{border:1px solid var(--line);background:var(--panel-2);border-radius:999px;
75
+ padding:4px 11px;color:var(--muted);display:flex;gap:6px;align-items:center;cursor:pointer}
76
+ .pill:hover{border-color:var(--cyan);color:var(--ink)}
77
+ .pill b{color:var(--ink);font-weight:500}
78
+ .spacer{flex:1}
79
+ .stat{color:var(--dim);font-variant-numeric:tabular-nums}
80
+ .stat b{color:var(--muted);font-weight:500}
81
+
82
+ #feed{flex:1;overflow-y:auto;padding:26px 0 8px;min-height:0}
83
+ .wrap{max-width:820px;margin:0 auto;padding:0 24px}
84
+ .hero{text-align:center;padding:13vh 0 0;color:var(--muted)}
85
+ .hero .big{display:flex;align-items:center;justify-content:center;gap:12px;color:var(--cyan);
86
+ margin-bottom:8px}
87
+ .hero .big .mark{width:52px;height:19px}
88
+ .hero h1{font-size:21px;margin:0;color:var(--ink);font-weight:600;letter-spacing:.01em}
89
+ .hero p{font-size:13px;margin:7px 0 0}
90
+
91
+ .msg{margin-bottom:20px}
92
+ .you{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--radius);
93
+ padding:11px 14px;white-space:pre-wrap;word-break:break-word}
94
+ .who{display:flex;align-items:center;gap:8px;font-size:10.5px;letter-spacing:.12em;
95
+ color:var(--dim);text-transform:uppercase;margin-bottom:7px}
96
+ .reply{white-space:pre-wrap;word-break:break-word}
97
+ .reply code{background:var(--panel-2);border:1px solid var(--line);border-radius:4px;
98
+ padding:1px 5px;font-family:var(--mono);font-size:12.5px}
99
+ .reply pre{background:var(--panel);border:1px solid var(--line);border-radius:8px;
100
+ padding:12px 14px;overflow-x:auto;margin:10px 0;white-space:pre}
101
+ .reply pre code{background:none;border:0;padding:0;font-size:12.5px;line-height:1.5}
102
+ .reply h3{font-size:14px;margin:16px 0 6px}
103
+ .reply strong{color:#eaf4f8}
104
+ .reply table{border-collapse:collapse;margin:10px 0;font-size:12.5px;display:block;overflow-x:auto}
105
+ .reply th,.reply td{border:1px solid var(--line);padding:5px 9px;text-align:left}
106
+ .reply th{background:var(--panel-2);color:var(--muted)}
107
+
108
+ .tool{border:1px solid var(--line);border-left:2px solid var(--dim);border-radius:8px;
109
+ background:var(--panel);margin:8px 0;overflow:hidden}
110
+ .tool.ok{border-left-color:var(--green)} .tool.bad{border-left-color:var(--red)}
111
+ .tool.run{border-left-color:var(--amber)}
112
+ .tool .th{display:flex;align-items:center;gap:9px;padding:8px 12px;cursor:pointer;font-size:12.5px}
113
+ .tool .nm{font-family:var(--mono);color:var(--cyan)}
114
+ .tool .dt{color:var(--muted);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
115
+ .tool .ms{color:var(--dim);font-variant-numeric:tabular-nums;font-size:11px}
116
+ .tool .body{border-top:1px solid var(--line);padding:9px 12px;font-family:var(--mono);
117
+ font-size:11.5px;color:var(--muted);white-space:pre-wrap;max-height:280px;overflow:auto}
118
+ .think{border:1px dashed var(--line);border-radius:8px;padding:9px 12px;margin:8px 0;
119
+ color:var(--violet);font-size:12.5px;white-space:pre-wrap;max-height:200px;overflow:auto}
120
+ .notice{border:1px solid var(--line);border-left:2px solid var(--amber);border-radius:8px;
121
+ padding:10px 13px;margin:9px 0;font-size:12.5px;color:var(--muted);white-space:pre-wrap}
122
+ .notice.info{border-left-color:var(--cyan)}
123
+ .err{border:1px solid #4a2226;border-left:2px solid var(--red);background:#180f11;
124
+ border-radius:8px;padding:10px 13px;margin:9px 0;font-size:12.5px;color:#f4b8ba;white-space:pre-wrap}
125
+
126
+ .plan{border:1px solid var(--line);border-radius:8px;background:var(--panel);margin:9px 0;
127
+ padding:10px 13px;font-size:12.5px}
128
+ .plan h4{margin:0 0 7px;font-size:10.5px;letter-spacing:.12em;color:var(--dim);
129
+ text-transform:uppercase;display:flex;gap:8px}
130
+ .step{display:flex;gap:9px;padding:2px 0;color:var(--muted)}
131
+ .step .bx{width:13px;color:var(--dim);flex:0 0 13px}
132
+ .step.done{color:var(--dim)} .step.done .bx{color:var(--green)}
133
+ .step.doing{color:var(--ink)} .step.doing .bx{color:var(--amber)}
134
+ .step.blocked .bx{color:var(--red)}
135
+
136
+ .ask{border:1px solid var(--amber);border-radius:var(--radius);background:#1a1508;
137
+ padding:12px 14px;margin:11px 0}
138
+ .ask .q{font-size:13px;margin-bottom:4px}
139
+ .ask .a{font-family:var(--mono);font-size:11.5px;color:var(--muted);margin-bottom:10px;
140
+ white-space:pre-wrap;max-height:130px;overflow:auto}
141
+ .ask .btns{display:flex;gap:8px}
142
+ .ask button{padding:6px 14px;border-radius:7px;border:1px solid var(--line);cursor:pointer;
143
+ background:var(--panel-2)}
144
+ .ask .yes{border-color:var(--green);color:var(--green)}
145
+ .ask .no{border-color:var(--red);color:var(--red)}
146
+
147
+ .busy{display:flex;align-items:center;gap:10px;color:var(--amber);font-size:12.5px;margin:8px 0}
148
+ .sp{width:11px;height:11px;border:2px solid var(--amber);border-right-color:transparent;
149
+ border-radius:50%;animation:spin .8s linear infinite}
150
+ @keyframes spin{to{transform:rotate(360deg)}}
151
+
152
+ footer{flex:0 0 auto;border-top:1px solid var(--line);background:var(--panel);padding:12px 0 14px}
153
+ .cbox{max-width:820px;margin:0 auto;padding:0 24px}
154
+ .cin{display:flex;align-items:flex-end;gap:10px;border:1px solid var(--line);
155
+ background:var(--panel-2);border-radius:var(--radius);padding:9px 11px;transition:.14s}
156
+ .cin:focus-within{border-color:var(--cyan)}
157
+ textarea{flex:1;background:none;border:0;outline:0;resize:none;max-height:190px;
158
+ line-height:1.5;font-size:14px}
159
+ textarea::placeholder{color:var(--dim)}
160
+ .send{width:31px;height:31px;flex:0 0 31px;border-radius:50%;border:0;cursor:pointer;
161
+ background:var(--cyan);color:#04181c;font-size:15px;display:grid;place-items:center}
162
+ .send:disabled{background:var(--line);color:var(--dim);cursor:not-allowed}
163
+ .chint{display:flex;gap:12px;margin-top:7px;font-size:10.5px;color:var(--dim)}
164
+ .chint kbd{border:1px solid var(--line);border-radius:4px;padding:0 4px;font-family:var(--mono)}
165
+
166
+ dialog{border:1px solid var(--line);background:var(--panel);color:var(--ink);
167
+ border-radius:var(--radius);padding:0;max-width:440px;width:92%}
168
+ dialog::backdrop{background:rgba(0,0,0,.66)}
169
+ .dlg{padding:18px}
170
+ .dlg h3{margin:0 0 4px;font-size:15px}
171
+ .dlg p{margin:0 0 14px;color:var(--muted);font-size:12.5px}
172
+ .dlg input,.dlg select{width:100%;padding:8px 10px;border-radius:8px;border:1px solid var(--line);
173
+ background:var(--panel-2);margin-bottom:10px}
174
+ .dlg .row{display:flex;gap:8px;justify-content:flex-end}
175
+ .dlg button{padding:7px 15px;border-radius:8px;border:1px solid var(--line);cursor:pointer;
176
+ background:var(--panel-2)}
177
+ .dlg .go{border-color:var(--cyan);color:var(--cyan)}
178
+ </style>
179
+ </head><body>
180
+
181
+ <aside>
182
+ <div class="brand">${MARK}<b>KONECK</b><span>web</span></div>
183
+ <button class="newbtn" id="new">+ New session</button>
184
+ <div class="shead">Sessions</div>
185
+ <div class="slist" id="slist"></div>
186
+ <div class="foot"><span id="wsname">&mdash;</span><span id="conn">offline</span></div>
187
+ </aside>
188
+
189
+ <main>
190
+ <header>
191
+ <div class="pill" id="pprov">provider <b>&mdash;</b></div>
192
+ <div class="pill" id="pmodel">model <b>&mdash;</b></div>
193
+ <div class="spacer"></div>
194
+ <div class="stat" id="stok">&mdash;</div>
195
+ <div class="pill" id="pabort" style="display:none;border-color:var(--red);color:var(--red)">stop</div>
196
+ </header>
197
+
198
+ <div id="feed"><div class="wrap" id="stream">
199
+ <div class="hero">
200
+ <div class="big">${MARK}<h1>Into the workspace</h1></div>
201
+ <p>Describe what you want built. Everything the CLI does, from here.</p>
202
+ </div>
203
+ </div></div>
204
+
205
+ <footer><div class="cbox">
206
+ <div class="cin">
207
+ <textarea id="ta" rows="1" placeholder="Describe what you want to build..."></textarea>
208
+ <button class="send" id="send" disabled>&uarr;</button>
209
+ </div>
210
+ <div class="chint">
211
+ <span><kbd>Enter</kbd> send</span><span><kbd>Shift</kbd>+<kbd>Enter</kbd> newline</span>
212
+ <span id="hintstate"></span>
213
+ </div>
214
+ </div></footer>
215
+ </main>
216
+
217
+ <dialog id="tokdlg"><form class="dlg" method="dialog">
218
+ <h3>Token needed</h3>
219
+ <p>Anything that starts a run needs the token KONECK printed when it started. It is in the
220
+ terminal you launched it from.</p>
221
+ <input id="tokin" placeholder="paste the token" autocomplete="off">
222
+ <div class="row"><button class="go" id="toksave">Use it</button></div>
223
+ </form></dialog>
224
+
225
+ <dialog id="mdlg"><form class="dlg" method="dialog">
226
+ <h3>Model</h3><p>Applies from your next message.</p>
227
+ <select id="mprov"></select>
228
+ <input id="mmodel" placeholder="model name" autocomplete="off">
229
+ <div class="row"><button>Cancel</button><button class="go" id="msave">Switch</button></div>
230
+ </form></dialog>
231
+
232
+ <script>
233
+ ${clientScript()}
234
+ </script>
235
+ </body></html>`;
236
+ }
237
+ /**
238
+ * The client.
239
+ *
240
+ * Plain DOM. A framework would be more code than the page it renders, and this has one job: turn a
241
+ * stream of typed events into a transcript that reads well.
242
+ */
243
+ function clientScript() {
244
+ return String.raw `
245
+ const $ = (id) => document.getElementById(id);
246
+ const state = {
247
+ token: new URLSearchParams(location.search).get('token') || localStorage.getItem('koneck.token') || '',
248
+ session: null, provider: '-', model: '-', busy: false, es: null,
249
+ // The element currently being appended to, so streamed text lands in one bubble rather than
250
+ // producing a new one per chunk.
251
+ replyEl: null, thinkEl: null, toolEls: new Map(), planEl: null, lastTool: null,
252
+ };
253
+ if (state.token) localStorage.setItem('koneck.token', state.token);
254
+ // The token does not belong in the address bar, where it survives in history and in a screenshot.
255
+ if (location.search.indexOf('token=') !== -1) history.replaceState({}, '', location.pathname);
256
+
257
+ const api = async (path, opts) => {
258
+ opts = opts || {};
259
+ const sep = path.indexOf('?') === -1 ? '?' : '&';
260
+ const res = await fetch(path + sep + 'token=' + encodeURIComponent(state.token), {
261
+ method: opts.method || 'GET',
262
+ body: opts.body,
263
+ headers: { 'content-type': 'application/json' },
264
+ });
265
+ if (res.status === 401) { $('tokdlg').showModal(); throw new Error('token required'); }
266
+ if (!res.ok) {
267
+ let msg = res.statusText;
268
+ try { msg = (await res.json()).error || msg; } catch (e) {}
269
+ throw new Error(msg);
270
+ }
271
+ return res.status === 204 ? null : res.json();
272
+ };
273
+
274
+ const stream = () => $('stream');
275
+ const atBottom = () => {
276
+ const f = $('feed');
277
+ return f.scrollHeight - f.scrollTop - f.clientHeight < 90;
278
+ };
279
+ const stick = (was) => { if (was) $('feed').scrollTop = $('feed').scrollHeight; };
280
+ const el = (tag, cls, text) => {
281
+ const n = document.createElement(tag);
282
+ if (cls) n.className = cls;
283
+ if (text !== undefined) n.textContent = text;
284
+ return n;
285
+ };
286
+ const add = (node) => { const was = atBottom(); stream().appendChild(node); stick(was); return node; };
287
+ const clearHero = () => { const h = stream().querySelector('.hero'); if (h) h.remove(); };
288
+
289
+ /**
290
+ * Enough markdown to read a reply: fences, inline code, bold, headings, tables.
291
+ *
292
+ * Everything is escaped before any tag is inserted, and fenced blocks are lifted out first so
293
+ * their contents can never be interpreted. Model output is untrusted text — it routinely contains
294
+ * HTML, because it is often writing HTML.
295
+ */
296
+ function md(src) {
297
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
298
+ const blocks = [];
299
+ let text = src.replace(/` + '```' + String.raw `([a-zA-Z0-9_+-]*)\n([\s\S]*?)` + '```' + String.raw `/g, function (m, lang, code) {
300
+ blocks.push('<pre><code>' + esc(code.replace(/\n$/, '')) + '</code></pre>');
301
+ return '' + (blocks.length - 1) + '';
302
+ });
303
+ text = esc(text);
304
+ text = text.replace(/(^\|.+\|\n\|[ :|-]+\|\n(?:\|.*\|\n?)*)/gm, function (t) {
305
+ const rows = t.trim().split('\n').filter(function (r) { return !/^\|[ :|-]+\|$/.test(r.trim()); });
306
+ const cells = (r, tag) => '<tr>' + r.split('|').slice(1, -1)
307
+ .map((c) => '<' + tag + '>' + c.trim() + '</' + tag + '>').join('') + '</tr>';
308
+ return '<table>' + cells(rows[0], 'th') +
309
+ rows.slice(1).map((r) => cells(r, 'td')).join('') + '</table>';
310
+ });
311
+ text = text
312
+ .replace(/^#{2,3} (.*)$/gm, '<h3>$1</h3>')
313
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
314
+ .replace(/` + '`' + String.raw `([^` + '`' + String.raw `\n]+)` + '`' + String.raw `/g, '<code>$1</code>');
315
+ return text.replace(/(\d+)/g, (m, i) => blocks[Number(i)]);
316
+ }
317
+
318
+ const ms = (n) => n >= 1000 ? (n / 1000).toFixed(n >= 10000 ? 0 : 1) + 's' : Math.round(n) + 'ms';
319
+ const num = (n) => n.toLocaleString();
320
+
321
+ function renderTool(e) {
322
+ const box = el('div', 'tool run');
323
+ const th = el('div', 'th');
324
+ th.appendChild(el('span', 'nm', e.name));
325
+ th.appendChild(el('span', 'dt', e.detail || ''));
326
+ th.appendChild(el('span', 'ms', '...'));
327
+ const body = el('div', 'body');
328
+ body.style.display = 'none';
329
+ th.onclick = () => { body.style.display = body.style.display === 'none' ? 'block' : 'none'; };
330
+ box.appendChild(th); box.appendChild(body);
331
+ const entry = { box: box, th: th, body: body };
332
+ state.toolEls.set(e.callId, entry);
333
+ state.lastTool = entry;
334
+ return add(box);
335
+ }
336
+
337
+ function finishTool(e) {
338
+ const found = state.toolEls.get(e.callId) || state.lastTool;
339
+ if (!found) return;
340
+ found.box.className = 'tool ' + (e.ok ? 'ok' : 'bad');
341
+ found.th.children[2].textContent = ms(e.ms);
342
+ if (e.detail) found.th.children[1].textContent = e.detail;
343
+ }
344
+
345
+ function renderPlan(steps) {
346
+ const done = steps.filter((s) => s.state === 'done').length;
347
+ const box = el('div', 'plan');
348
+ const h = el('h4');
349
+ h.appendChild(el('span', null, 'Plan'));
350
+ h.appendChild(el('span', null, done + '/' + steps.length));
351
+ box.appendChild(h);
352
+ for (const s of steps) {
353
+ const row = el('div', 'step ' + s.state);
354
+ const mark = s.state === 'done' ? 'v' : s.state === 'doing' ? '>' : s.state === 'blocked' ? 'x' : '.';
355
+ row.appendChild(el('span', 'bx', mark));
356
+ row.appendChild(el('span', null, s.text));
357
+ box.appendChild(row);
358
+ }
359
+ if (state.planEl && state.planEl.parentNode) {
360
+ state.planEl.replaceWith(box); state.planEl = box; return box;
361
+ }
362
+ state.planEl = box;
363
+ return add(box);
364
+ }
365
+
366
+ function renderAsk(e) {
367
+ const box = el('div', 'ask');
368
+ box.appendChild(el('div', 'q', 'Allow ' + e.tool + '?'));
369
+ box.appendChild(el('div', 'a', e.args));
370
+ const btns = el('div', 'btns');
371
+ const answer = async (allow) => {
372
+ const all = btns.querySelectorAll('button');
373
+ for (let i = 0; i < all.length; i++) all[i].disabled = true;
374
+ await api('/api/answer', { method: 'POST',
375
+ body: JSON.stringify({ session: state.session, ask: e.askId, allow: allow }) });
376
+ box.appendChild(el('div', 'a', allow ? 'allowed' : 'refused'));
377
+ };
378
+ const yes = el('button', 'yes', 'Allow'); yes.onclick = () => answer(true);
379
+ const no = el('button', 'no', 'Refuse'); no.onclick = () => answer(false);
380
+ btns.appendChild(yes); btns.appendChild(no);
381
+ box.appendChild(btns);
382
+ return add(box);
383
+ }
384
+
385
+ let spinner = null;
386
+ function setBusy(on) {
387
+ state.busy = on;
388
+ $('send').disabled = on || !$('ta').value.trim();
389
+ $('pabort').style.display = on ? 'flex' : 'none';
390
+ $('hintstate').textContent = on ? 'working...' : '';
391
+ if (on && !spinner) {
392
+ spinner = el('div', 'busy');
393
+ spinner.appendChild(el('div', 'sp'));
394
+ spinner.appendChild(el('span', null, 'working'));
395
+ add(spinner);
396
+ } else if (!on && spinner) { spinner.remove(); spinner = null; }
397
+ }
398
+
399
+ function onEvent(e) {
400
+ switch (e.t) {
401
+ case 'session':
402
+ state.provider = e.provider; state.model = e.model;
403
+ setPills(e.provider, e.model);
404
+ $('wsname').textContent = e.cwd.split('/').pop() || e.cwd;
405
+ break;
406
+ case 'user': {
407
+ clearHero();
408
+ const box = el('div', 'msg');
409
+ box.appendChild(el('div', 'who', 'you'));
410
+ box.appendChild(el('div', 'you', e.text));
411
+ add(box); state.replyEl = null; state.thinkEl = null;
412
+ setBusy(true);
413
+ break;
414
+ }
415
+ case 'chunk': {
416
+ if (!state.replyEl) {
417
+ const box = el('div', 'msg');
418
+ const who = el('div', 'who');
419
+ who.appendChild(el('span', 'kk', 'koneck'));
420
+ const body = el('div', 'reply');
421
+ box.appendChild(who); box.appendChild(body); add(box);
422
+ state.replyEl = body; state.replyEl.dataset.raw = '';
423
+ }
424
+ state.replyEl.dataset.raw += e.text;
425
+ const was = atBottom();
426
+ state.replyEl.innerHTML = md(state.replyEl.dataset.raw);
427
+ stick(was);
428
+ break;
429
+ }
430
+ case 'thinking':
431
+ if (!state.thinkEl) state.thinkEl = add(el('div', 'think', ''));
432
+ state.thinkEl.textContent = (state.thinkEl.textContent + e.text).slice(-1400);
433
+ break;
434
+ case 'tool': clearHero(); renderTool(e); break;
435
+ case 'tool-output': {
436
+ const any = state.lastTool;
437
+ if (any) any.body.textContent = (any.body.textContent + e.line + '\n').slice(-4000);
438
+ break;
439
+ }
440
+ case 'tool-done': finishTool(e); break;
441
+ case 'plan': renderPlan(e.steps); break;
442
+ case 'tokens':
443
+ $('stok').innerHTML = '<b>' + num(e.total) + '</b> tokens' +
444
+ (e.cost > 0 ? ' &middot; $' + e.cost.toFixed(4) : '');
445
+ break;
446
+ case 'ask': renderAsk(e); break;
447
+ case 'notice': add(el('div', 'notice ' + (e.level === 'info' ? 'info' : ''), e.text)); break;
448
+ case 'error': add(el('div', 'err', e.text)); setBusy(false); break;
449
+ case 'turn-end':
450
+ setBusy(false);
451
+ state.replyEl = null; state.thinkEl = null;
452
+ break;
453
+ }
454
+ }
455
+
456
+ function setPills(provider, model) {
457
+ $('pprov').innerHTML = 'provider <b>' + escHtml(provider) + '</b>';
458
+ $('pmodel').innerHTML = 'model <b>' + escHtml(model) + '</b>';
459
+ }
460
+ function escHtml(s) {
461
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
462
+ }
463
+
464
+ function listen() {
465
+ if (state.es) state.es.close();
466
+ const es = new EventSource('/api/events?session=' + encodeURIComponent(state.session) +
467
+ '&token=' + encodeURIComponent(state.token));
468
+ state.es = es;
469
+ es.onopen = () => { $('conn').textContent = 'live'; $('conn').style.color = 'var(--green)'; };
470
+ es.onerror = () => { $('conn').textContent = 'reconnecting'; $('conn').style.color = 'var(--amber)'; };
471
+ es.onmessage = (m) => { try { onEvent(JSON.parse(m.data)); } catch (err) {} };
472
+ }
473
+
474
+ async function refreshSessions() {
475
+ let data = null;
476
+ try { data = await api('/api/sessions'); } catch (e) { return; }
477
+ const list = $('slist');
478
+ list.innerHTML = '';
479
+ for (const s of data.live) {
480
+ const row = el('div', 'srow' + (s.id === state.session ? ' on' : ''));
481
+ row.appendChild(el('div', 't', s.title));
482
+ const meta = el('div', 'm');
483
+ meta.appendChild(el('span', 'dot' + (s.busy ? ' busy' : '')));
484
+ meta.appendChild(el('span', null, s.model));
485
+ meta.appendChild(el('span', null, s.turns + ' turns'));
486
+ row.appendChild(meta);
487
+ row.onclick = () => { openSession(s.id); };
488
+ list.appendChild(row);
489
+ }
490
+ if (!data.live.length) list.appendChild(el('div', 'shead', 'none yet'));
491
+ }
492
+
493
+ function resetView() {
494
+ stream().innerHTML = '';
495
+ state.planEl = null; state.replyEl = null; state.thinkEl = null; state.lastTool = null;
496
+ state.toolEls = new Map();
497
+ }
498
+
499
+ function openSession(id) {
500
+ state.session = id;
501
+ resetView();
502
+ listen();
503
+ refreshSessions();
504
+ }
505
+
506
+ async function newSession() {
507
+ const s = await api('/api/sessions', { method: 'POST', body: JSON.stringify({}) });
508
+ openSession(s.id);
509
+ }
510
+
511
+ async function send() {
512
+ const ta = $('ta');
513
+ const text = ta.value.trim();
514
+ if (!text || state.busy) return;
515
+ if (!state.session) await newSession();
516
+ ta.value = ''; ta.style.height = 'auto';
517
+ $('send').disabled = true;
518
+ try {
519
+ await api('/api/send', { method: 'POST',
520
+ body: JSON.stringify({ session: state.session, text: text }) });
521
+ } catch (e) { add(el('div', 'err', e.message)); setBusy(false); }
522
+ }
523
+
524
+ $('ta').addEventListener('input', (ev) => {
525
+ const ta = ev.target;
526
+ ta.style.height = 'auto';
527
+ ta.style.height = Math.min(ta.scrollHeight, 190) + 'px';
528
+ $('send').disabled = state.busy || !ta.value.trim();
529
+ });
530
+ $('ta').addEventListener('keydown', (ev) => {
531
+ if (ev.key === 'Enter' && !ev.shiftKey) { ev.preventDefault(); send(); }
532
+ });
533
+ $('send').onclick = send;
534
+ $('new').onclick = newSession;
535
+ $('pabort').onclick = () => {
536
+ api('/api/abort', { method: 'POST', body: JSON.stringify({ session: state.session }) })
537
+ .catch(() => {});
538
+ };
539
+ $('toksave').onclick = () => {
540
+ const v = $('tokin').value.trim();
541
+ if (v) { localStorage.setItem('koneck.token', v); location.reload(); }
542
+ };
543
+ $('pmodel').onclick = async () => {
544
+ let info = null;
545
+ try { info = await api('/api/workspaces'); } catch (e) { return; }
546
+ const sel = $('mprov');
547
+ sel.innerHTML = '';
548
+ for (const p of info.providers) {
549
+ const o = document.createElement('option');
550
+ o.value = p.name;
551
+ o.textContent = p.label + (p.keyless ? ' - local' : '');
552
+ if (p.name === state.provider) o.selected = true;
553
+ sel.appendChild(o);
554
+ }
555
+ $('mmodel').value = state.model === '-' ? info.model : state.model;
556
+ $('mdlg').showModal();
557
+ };
558
+ $('msave').onclick = async () => {
559
+ const provider = $('mprov').value, model = $('mmodel').value.trim();
560
+ if (!state.session) await newSession();
561
+ let info = null;
562
+ try {
563
+ info = await api('/api/model', { method: 'POST',
564
+ body: JSON.stringify({ session: state.session, provider: provider, model: model }) });
565
+ } catch (e) { return; }
566
+ state.provider = info.provider; state.model = info.model;
567
+ setPills(info.provider, info.model);
568
+ };
569
+
570
+ (async function start() {
571
+ let hello = null;
572
+ try { hello = await fetch('/api/hello').then((r) => r.json()); } catch (e) {}
573
+ if (hello && hello.needsToken && !state.token) $('tokdlg').showModal();
574
+ let info = null;
575
+ try { info = await api('/api/workspaces'); } catch (e) {}
576
+ if (info) {
577
+ state.provider = info.provider; state.model = info.model;
578
+ setPills(info.provider, info.model);
579
+ $('wsname').textContent = info.cwd.split('/').pop() || info.cwd;
580
+ }
581
+ await refreshSessions();
582
+ setInterval(refreshSessions, 4000);
583
+ })();
584
+ `;
585
+ }
586
+ //# sourceMappingURL=ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,+EAA+E;AAC/E,MAAM,IAAI,GAAG;;;;;aAKA,CAAC;AAEd,yFAAyF;AACzF,MAAM,EAAE,GAAG,mCAAmC,CAAC;AAE/C,MAAM,UAAU,IAAI;IAClB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBA+Jc,IAAI;;;;;;;;;;;;;;;;;;yBAkBF,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiC3B,YAAY,EAAE;;eAED,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY;IACnB,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAuDQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA,+BAA+B,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;eAerF,GAAG,GAAG,GAAG,MAAM,CAAC,GAAG,CAAA,KAAK,GAAG,GAAG,GAAG,MAAM,CAAC,GAAG,CAAA,OAAO,GAAG,GAAG,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8QnF,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koneck",
3
- "version": "2.25.76",
3
+ "version": "2.26.0",
4
4
  "description": "Kinetically Orchestrated Neural Execution Engine for Code",
5
5
  "type": "module",
6
6
  "bin": {