| @@ -71,7 +71,7 @@ · 70 unmodified lines |
| 71 | 71 | let S = null; |
| 72 | 72 | const ui = { |
| 73 | 73 | page: 'home', tab: {}, search: {}, searchOpen: {}, sort: {}, settings: null, |
| 74 | | − note: null, meeting: null, dirty: false, side: true, |
| 74 | + note: null, meeting: null, dirty: false, side: true, cmdOpen: {}, |
| 75 | 75 | }; |
| 76 | 76 | |
| 77 | 77 | const toast = (msg, ms = 2600) => { |
| @@ -181,13 +181,13 @@ · 103 unmodified lines |
| 181 | 181 | applyTheme(); |
| 182 | 182 | $('#app').classList.toggle('side-off', !ui.side); |
| 183 | 183 | renderSide(); |
| 184 | | − const pages = { home, notetaker, insights, dictionary, snippets, style, transforms, scratchpad }; |
| 184 | + const pages = { home, notetaker, assistant: assistantPage, insights, dictionary, snippets, style, transforms, scratchpad }; |
| 185 | 185 | $('#main').innerHTML = (pages[ui.page] || home)(); |
| 186 | 186 | if (ui.settings) renderSettings(); |
| 187 | 187 | } |
| 188 | 188 | |
| 189 | 189 | const NAV = [ |
| 190 | | − ['home', 'mic', 'Dictation'], ['notetaker', 'rec', 'Notetaker'], ['insights', 'chart', 'Insights'], |
| 190 | + ['home', 'mic', 'Dictation'], ['notetaker', 'rec', 'Notetaker'], ['assistant', 'bot', 'Assistant'], ['insights', 'chart', 'Insights'], |
| 191 | 191 | ['dictionary', 'book', 'Dictionary'], ['snippets', 'scissors', 'Snippets'], ['style', 'type', 'Style'], |
| 192 | 192 | ['transforms', 'wand', 'Transforms'], ['scratchpad', 'pad', 'Scratchpad'], |
| 193 | 193 | ]; |
| @@ -486,6 +486,79 @@ · 292 unmodified lines |
| 486 | 486 | <button class="tcard" data-act="tf-edit"><span class="plus-dot">${ic('plus')}</span><h4>Create your own</h4><p>Write your own prompt</p></button></div></div>`; |
| 487 | 487 | } |
| 488 | 488 | |
| 489 | + // ---------------------------------------------------------------- assistant |
| 490 | + const modelName = id => (S.runtime.assistantModels || []).find(m => m.id === id)?.name || id || ''; |
| 491 | + const secs = ms => ms >= 60000 ? fmtDur(ms) : `${(ms / 1000).toFixed(1)} s`; |
| 492 | + |
| 493 | + function assistantPage() { |
| 494 | + const a = S.settings.assistant || {}, rt = S.runtime, running = rt.assistant === 'running'; |
| 495 | + const all = [...(S.commands || [])].sort((x, y) => y.ts - x.ts); |
| 496 | + const live = running && all[0] && !all[0].reply ? all[0] : null; |
| 497 | + const items = all.filter(c => matches('asst', c.text, c.reply)); |
| 498 | + const groups = []; |
| 499 | + items.forEach(c => { const d = fmtDay(c.ts); if (!groups.length || groups.at(-1).d !== d) groups.push({ d, xs: [] }); groups.at(-1).xs.push(c); }); |
| 500 | + const log = groups.map(g => `<div class="day"><span>${g.d}</span></div><div class="list asst-log">${g.xs.map(c => cmdRow(c, live?.id)).join('')}</div>`).join(''); |
| 501 | + const m = (rt.assistantModels || []).find(x => x.id === a.model); |
| 502 | + const key = esc(a.hotkey?.label || 'Left Shift'); |
| 503 | + return `<div class="page narrow asst"> |
| 504 | + <div class="page-head"><h1>Assistant</h1><div class="head-actions"> |
| 505 | + <button class="icon-btn" data-go="settings/assistant" title="Assistant settings">${ic('gear')}</button></div></div> |
| 506 | + <div class="nt-card asst-card"><div class="label"><span>COMMAND</span> |
| 507 | + <span class="asst-model"><span class="status-dot ${!m || !m.available ? 'error' : running ? 'loading' : a.enabled === false ? '' : 'ready'}"></span>${esc(m?.name || 'No agent selected')}</span></div> |
| 508 | + ${a.enabled === false ? `<p class="muted asst-hint">The assistant key is off. Typed commands still run.</p>` : `<p class="muted asst-hint">Hold <b>${key}</b> and say a command, or type one here.</p>`} |
| 509 | + <div class="asst-cmd"><input class="field" id="asst-in" placeholder="e.g. open my notes folder"> |
| 510 | + ${running ? '<button class="btn ghost" data-act="asst-stop">Stop</button>' : ''}<button class="btn" data-act="asst-run">Run</button></div> |
| 511 | + ${running ? `<div class="live"><span class="rec"></span><span class="grow"><b style="font-weight:500">Running</b> <span class="muted">${esc(live?.text || '')}</span></span> |
| 512 | + ${live ? `<span data-elapsed="${live.ts}">${secs(Date.now() - live.ts)}</span>` : ''}</div>` : ''} |
| 513 | + </div> |
| 514 | + <div class="recents"><h3>System prompt</h3></div> |
| 515 | + <div class="asst-sp"> |
| 516 | + <p class="muted">Added to every command, after the pointer to <span class="code">~/.config/agents/AGENTS.md</span>.</p> |
| 517 | + <textarea class="field" id="asst-sp" placeholder="e.g. Prefer dwm keybindings over xdotool. Answer in one sentence.">${esc(a.systemPrompt || '')}</textarea> |
| 518 | + <div class="asst-sp-foot"><span id="asst-sp-state">${ui.spState || ''}</span><span style="flex:1"></span><button class="btn sm" data-act="asst-sp-save">Save</button></div> |
| 519 | + </div> |
| 520 | + ${tabs('asst', [['log', `Commands <span class="muted">${all.length}</span>`]], `<span class="tools"><button data-act="search" data-key="asst" title="Search">${ic('search')}</button></span>`)} |
| 521 | + ${rt.assistantLog ? `<div class="asst-log-path"><span class="grow">Every command is also appended to <span class="code">${esc(rt.assistantLog)}</span></span><button class="btn ghost sm" data-act="asst-log">Open</button></div>` : ''} |
| 522 | + ${searchBox('asst')} |
| 523 | + ${log || `<div class="empty">${ui.search.asst ? 'No matches' : 'No commands yet'}</div>`}</div>`; |
| 524 | + } |
| 525 | + |
| 526 | + function cmdRow(c, liveId) { |
| 527 | + const open = ui.cmdOpen[c.id], isLive = c.id === liveId, id = esc(c.id); |
| 528 | + const err = !isLive && c.ok === false; |
| 529 | + const reply = isLive ? '<span class="status-dot loading"></span> Running…' : c.reply ? esc(c.reply) : err ? 'No reply' : 'Done'; |
| 530 | + const name = modelName(c.model); |
| 531 | + const meta = [name && esc(name), isLive ? `<span data-elapsed="${c.ts}">${secs(Date.now() - c.ts)}</span>` : c.ms ? secs(c.ms) : ''].filter(Boolean).join(' · '); |
| 532 | + const win = [c.app, c.title].filter(Boolean).map(esc).join(' · '); |
| 533 | + return `<div class="row cmd${open ? ' open' : ''}"> |
| 534 | + <div class="cmd-head" data-act="cmd-toggle" data-id="${id}" title="${open ? 'Hide details' : 'Show details'}"> |
| 535 | + <span class="time">${fmtTime(c.ts)}</span> |
| 536 | + <span class="grow"><div class="cmd-text">${esc(c.text || '')}${c.followUp ? '<span class="cmd-tag">Follow-up</span>' : ''}</div> |
| 537 | + <div class="cmd-reply${err ? ' err' : ''}">${reply}</div></span> |
| 538 | + <span class="cmd-meta">${meta}</span></div> |
| 539 | + ${open ? `<div class="cmd-more"> |
| 540 | + <div class="cap">Prompt sent</div>${c.prompt ? `<pre class="cmd-pre">${esc(c.prompt)}</pre>` : '<div class="muted">Not recorded for this command.</div>'} |
| 541 | + <div class="cap">Focused window</div><div class="${win ? '' : 'muted'}">${win || 'Not recorded'}</div> |
| 542 | + <div class="cmd-acts"> |
| 543 | + <button class="btn ghost sm" data-act="cmd-copy" data-field="reply" data-id="${id}">${ic('copy')}Copy reply</button> |
| 544 | + ${c.prompt ? `<button class="btn ghost sm" data-act="cmd-copy" data-field="prompt" data-id="${id}">${ic('copy')}Copy prompt</button>` : ''} |
| 545 | + <button class="btn ghost sm" data-act="cmd-rerun" data-id="${id}">${ic('refresh')}Run again</button> |
| 546 | + <button class="btn danger sm" data-act="remove" data-coll="commands" data-id="${id}">${ic('trash')}Delete</button></div></div>` : ''} |
| 547 | + </div>`; |
| 548 | + } |
| 549 | + |
| 550 | + // The system prompt saves on blur and on Save, never per keystroke. |
| 551 | + async function saveSysPrompt(loud) { |
| 552 | + const ta = $('#asst-sp'); if (!ta) return; |
| 553 | + const v = ta.value, a = S.settings.assistant = S.settings.assistant || {}; |
| 554 | + if (v !== (a.systemPrompt || '')) { |
| 555 | + a.systemPrompt = v; ui.spState = 'Saved'; |
| 556 | + const st = $('#asst-sp-state'); if (st) st.textContent = 'Saved'; |
| 557 | + await lilt.set('assistant.systemPrompt', v); |
| 558 | + } |
| 559 | + if (loud) toast('Saved', 1400); |
| 560 | + } |
| 561 | + |
| 489 | 562 | // ---------------------------------------------------------------- scratchpad |
| 490 | 563 | function scratchpad() { |
| 491 | 564 | if (ui.note) return noteEditor(); |
| @@ -591,13 +664,7 @@ · 99 unmodified lines |
| 591 | 664 | : `<button class="btn ghost sm" data-act="set" data-path="assistant.model" data-val="${esc(m.id)}">Use</button>`; |
| 592 | 665 | return `<div class="srow"><div class="grow"><div class="t">${esc(m.name)}</div><div class="d">${esc(m.desc || m.cli || '')}</div></div>${right}</div>`; |
| 593 | 666 | }).join(''); |
| 594 | | − const when = ts => dayKey(ts) === dayKey(Date.now()) ? fmtTime(ts) : new Date(ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' + fmtTime(ts); |
| 595 | | − const cmds = (S.commands || []).slice(0, 20).map(c => `<div class="srow"><div class="grow"> |
| 596 | | − <div class="d">${when(c.ts)}${c.model ? ` · ${esc((rt.assistantModels || []).find(m => m.id === c.model)?.name || c.model)}` : ''}${c.ms ? ` · ${(c.ms / 1000).toFixed(1)} s` : ''}</div> |
| 597 | | − <div class="t" style="font-weight:400">${esc(c.text || '')}</div> |
| 598 | | − <div class="d" style="white-space:pre-wrap;max-height:84px;overflow:hidden${c.ok === false ? ';color:var(--red)' : ''}">${esc(c.reply || (c.ok === false ? 'Failed' : ''))}</div></div> |
| 599 | | − <button class="icon-btn" data-act="remove" data-coll="commands" data-id="${esc(c.id)}" title="Delete">${ic('trash')}</button></div>`).join(''); |
| 600 | | − return `<h2>Assistant</h2><p>Say a command and an agent carries it out on this machine. Replies are listed under Recent commands.</p> |
| 667 | + return `<h2>Assistant</h2><p>Say a command and an agent carries it out on this machine. The system prompt, typed commands and every reply are in the <button class="link-btn" data-act="asst-panel">Assistant panel →</button></p> |
| 601 | 668 | <div class="group"> |
| 602 | 669 | ${tog('assistant.enabled', 'Assistant key', 'Off leaves only the dictation key active.')} |
| 603 | 670 | <div class="srow"><div class="grow"><div class="t">Shortcut</div><div class="d">Hold <b>${esc(key)}</b> and say a command. Double-tap for hands-free.</div></div><button class="btn ghost" data-act="asst-hotkey">${ui.capturingAsst ? 'Press a key…' : 'Change'}</button></div> |
| @@ -605,12 +672,7 @@ · 1 unmodified line |
| 605 | 672 | ${rt.assistantDirOk === false ? '<div class="d" style="color:var(--red)">No AGENTS.md there yet</div>' : ''}</div><button class="btn ghost" data-act="asst-open">Open</button></div> |
| 606 | 673 | </div> |
| 607 | 674 | <h5>Agent <span class="muted" style="font-weight:400;font-size:13.5px">· runs headless with full tool access</span></h5> |
| 608 | | − <div class="group models">${models || '<div class="srow muted">No agents found</div>'}</div> |
| 609 | | − <h5>Type a command</h5> |
| 610 | | − <div class="group"><div class="srow"><input class="field grow" id="asst-in" placeholder="e.g. open my notes folder"> |
| 611 | | − ${running ? '<button class="btn ghost sm" style="min-width:0" data-act="asst-stop">Stop</button>' : ''}<button class="btn sm" data-act="asst-run">Run</button></div></div> |
| 612 | | − <h5>Recent commands</h5> |
| 613 | | − <div class="group">${cmds || '<div class="srow muted">No commands yet</div>'}</div>`; |
| 675 | + <div class="group models">${models || '<div class="srow muted">No agents found</div>'}</div>`; |
| 614 | 676 | }, |
| 615 | 677 | system() { |
| 616 | 678 | return `<h2>System</h2><h5>App settings</h5><div class="group"> |
| @@ -919,6 +981,12 @@ · 302 unmodified lines |
| 919 | 981 | case 'asst-open': return lilt.open('file://' + (S.runtime.assistantDir || '')); |
| 920 | 982 | case 'asst-run': return runAssistant(); |
| 921 | 983 | case 'asst-stop': await lilt.stopAssistant(); return toast('Stopped', 1400); |
| 984 | + case 'asst-panel': ui.settings = null; $('#overlay').innerHTML = ''; return go('assistant'); |
| 985 | + case 'asst-sp-save': return saveSysPrompt(true); |
| 986 | + case 'asst-log': return lilt.open('file://' + (S.runtime.assistantLog || '')); |
| 987 | + case 'cmd-toggle': if (String(getSelection())) return; ui.cmdOpen[id] = !ui.cmdOpen[id]; return render(); |
| 988 | + case 'cmd-copy': { const c = (S.commands || []).find(x => x.id === id); lilt.copy(c?.[t.dataset.field] || ''); return toast('Copied to clipboard', 1400); } |
| 989 | + case 'cmd-rerun': { const c = (S.commands || []).find(x => x.id === id); return c?.text && runAssistant(c.text); } |
| 922 | 990 | case 'mic': return micModal(); |
| 923 | 991 | case 'lang': return langModal(); |
| 924 | 992 | case 'llm': return modal(`<h3>Language model</h3><p class="sub">Any Ollama model tag. Small instruct models (3–8B) keep cleanup under a second on your GPU.</p><input class="field" id="m-llm" value="${esc(S.settings.llmModel)}"><div class="foot"><button class="btn line" data-close>Cancel</button><button class="btn" data-ok>Save</button></div>`, el => { |
| @@ -950,7 +1018,9 @@ · 25 unmodified lines |
| 950 | 1018 | const n = $(`[data-search=${t.dataset.search}]`); if (n) { n.focus(); n.setSelectionRange(pos, pos); } |
| 951 | 1019 | } |
| 952 | 1020 | if (t.dataset.noteField) saveNote(); |
| 1021 | + if (t.id === 'asst-sp') { ui.spState = 'Not saved'; const st = $('#asst-sp-state'); if (st) st.textContent = 'Not saved'; } |
| 953 | 1022 | }); |
| 1023 | + document.addEventListener('focusout', e => { if (e.target.id === 'asst-sp') saveSysPrompt(false); }); |
| 954 | 1024 | document.addEventListener('change', e => { |
| 955 | 1025 | const t = e.target; |
| 956 | 1026 | if (t.dataset.set) setSetting(t.dataset.set, /^\d+$/.test(t.value) ? Number(t.value) : t.value); |
| @@ -964,18 +1034,13 @@ · 7 unmodified lines |
| 964 | 1034 | |
| 965 | 1035 | document.addEventListener('keydown', e => { if (e.key === 'Enter' && e.target.id === 'asst-in') runAssistant(); }); |
| 966 | 1036 | |
| 967 | | − // Typed assistant command. The reply also lands in S.commands via the state push. |
| 968 | | − async function runAssistant() { |
| 969 | | − const inp = $('#asst-in'), text = inp?.value.trim(); |
| 1037 | + // Typed assistant command. The record shows up in S.commands (and the panel log) via the state push. |
| 1038 | + async function runAssistant(again) { |
| 1039 | + const inp = $('#asst-in'), text = (again || inp?.value || '').trim(); |
| 970 | 1040 | if (!text) return; |
| 971 | | − inp.value = ''; inp.blur(); |
| 972 | | − toast('Running…', 60000); |
| 973 | | − try { |
| 974 | | − const r = await lilt.assistant(text); |
| 975 | | − const ok = typeof r === 'object' && r ? r.ok !== false : true; |
| 976 | | − const msg = typeof r === 'string' ? r : r?.reply || (ok ? 'Done' : 'Failed'); |
| 977 | | − toast((ok ? '' : 'Failed: ') + (msg.length > 220 ? msg.slice(0, 220) + '…' : msg), 6000); |
| 978 | | − } catch (err) { toast(`Failed: ${err.message || err}`, 6000); } |
| 1041 | + if (inp && !again) { inp.value = ''; inp.blur(); } |
| 1042 | + if (ui.page !== 'assistant') toast('Running…', 4000); |
| 1043 | + try { await lilt.assistant(text); } catch (err) { toast(`Failed: ${err.message || err}`, 6000); } |
| 979 | 1044 | } |
| 980 | 1045 | |
| 981 | 1046 | async function loadMeetingAudio() { |
| @@ -983,7 +1048,10 @@ · 1 unmodified line |
| 983 | 1048 | const url = await lilt.audioUrl(el.dataset.audio); if (url) el.src = url; else el.remove(); |
| 984 | 1049 | } |
| 985 | 1050 | |
| 986 | | − setInterval(() => { const t = $('#nt-timer'); if (t) t.textContent = fmtDur(Date.now() - Number(t.dataset.start)); }, 1000); |
| 1051 | + setInterval(() => { |
| 1052 | + const t = $('#nt-timer'); if (t) t.textContent = fmtDur(Date.now() - Number(t.dataset.start)); |
| 1053 | + document.querySelectorAll('[data-elapsed]').forEach(el => { el.textContent = secs(Date.now() - Number(el.dataset.elapsed)); }); |
| 1054 | + }, 1000); |
| 987 | 1055 | |
| 988 | 1056 | const lilt = window.lilt || mockLilt(); |
| 989 | 1057 | lilt.onState(onState); |
| @@ -995,9 +1063,10 @@ · 5 unmodified lines |
| 995 | 1063 | function mockLilt() { |
| 996 | 1064 | const now = Date.now(), H = 36e5; |
| 997 | 1065 | const st = { |
| 998 | | − settings: { theme: 'system', firstName: 'Ada', lastName: 'Lovelace', email: 'ada@example.com', hotkey: { keycode: 108, label: 'Right Alt' }, micId: '', language: 'en', model: 'large-v3-turbo', llmModel: 'qwen3.5:4b', cleanup: 'light', styles: { personal: 'casual', work: 'formal', email: 'formal', other: 'casual' }, transformsOptIn: true, autoTransform: { enabled: false, id: 'polish' }, showBarAlways: true, showInDock: true, sounds: true, privacy: { storeAudio: true, contextAwareness: true }, notetaker: { maxMinutes: 120, systemAudio: true }, vibe: { fileTagging: true }, dismissed: {}, assistant: { model: 'claude-sonnet', hotkey: { keycode: 50, label: 'Left Shift' }, enabled: true } }, |
| 1066 | + settings: { theme: 'system', firstName: 'Ada', lastName: 'Lovelace', email: 'ada@example.com', hotkey: { keycode: 108, label: 'Right Alt' }, micId: '', language: 'en', model: 'large-v3-turbo', llmModel: 'qwen3.5:4b', cleanup: 'light', styles: { personal: 'casual', work: 'formal', email: 'formal', other: 'casual' }, transformsOptIn: true, autoTransform: { enabled: false, id: 'polish' }, showBarAlways: true, showInDock: true, sounds: true, privacy: { storeAudio: true, contextAwareness: true }, notetaker: { maxMinutes: 120, systemAudio: true }, vibe: { fileTagging: true }, dismissed: {}, assistant: { model: 'claude-sonnet', hotkey: { keycode: 50, label: 'Left Shift' }, enabled: true, systemPrompt: 'Prefer dwm keybindings over xdotool when moving windows. Answer in one sentence.' } }, |
| 999 | 1067 | commands: [ |
| 1000 | | − { id: 'c1', ts: now - H / 2, text: 'Open my notes folder in the file manager', reply: 'Opened ~/lit/notes in pcmanfm.', model: 'claude-sonnet', ms: 8200, ok: true }, |
| 1068 | + { id: 'c3', ts: now - H / 4, text: 'Now move it to tag 3', prompt: 'Read ~/.config/agents/AGENTS.md first.\n\nPrefer dwm keybindings over xdotool when moving windows. Answer in one sentence.\n\nCommand: Now move it to tag 3', reply: 'Moved the pcmanfm window to tag 3.', model: 'claude-sonnet', ms: 5400, ok: true, app: 'St', title: 'fish ~/src/lilt', followUp: true }, |
| 1069 | + { id: 'c1', ts: now - H / 2, text: 'Open my notes folder in the file manager', prompt: 'Read ~/.config/agents/AGENTS.md first.\n\nPrefer dwm keybindings over xdotool when moving windows. Answer in one sentence.\n\nCommand: Open my notes folder in the file manager', reply: 'Opened ~/lit/notes in pcmanfm.', model: 'claude-sonnet', ms: 8200, ok: true, app: 'St', title: 'fish ~/src/lilt', followUp: false }, |
| 1001 | 1070 | { id: 'c2', ts: now - 25 * H, text: 'Mute the speakers', reply: 'pactl: connection refused', model: 'codex', ms: 3100, ok: false }, |
| 1002 | 1071 | ], |
| 1003 | 1072 | history: [ |
| @@ -1010,7 +1079,7 @@ · 6 unmodified lines |
| 1010 | 1079 | snippets: [{ id: 's1', trigger: 'my email address', text: 'ada@example.com' }], |
| 1011 | 1080 | transforms: DEFAULT_TRANSFORMS.map(x => ({ ...x })), notes: [], meetings: [], |
| 1012 | 1081 | runtime: { version: '1.0.0', engine: 'ready', llm: 'ready', hotkeyLabel: 'Right Alt', mcpUrl: 'http://127.0.0.1:7865/mcp', dataDir: '~/.local/share/lilt', models: [{ id: 'large-v3-turbo', name: 'Whisper large-v3-turbo', engine: 'whisper.cpp', size: '1.6 GB', langs: '99 languages', installed: true }, { id: 'parakeet-v3', name: 'Parakeet TDT 0.6B v3', engine: 'parakeet', size: '1.3 GB', langs: '25 languages', installed: true }, { id: 'large-v3', name: 'Whisper large-v3', engine: 'whisper.cpp', size: '3.1 GB', langs: '99 languages', installed: false }], |
| 1013 | | − assistant: false, recordingKind: 'dictate', assistantDir: '/home/ada/.config/agents', assistantDirOk: false, |
| 1082 | + assistant: false, recordingKind: 'dictate', assistantDir: '/home/ada/.config/agents', assistantDirOk: false, assistantLog: '/home/ada/.local/share/lilt/commands.jsonl', |
| 1014 | 1083 | assistantModels: [{ id: 'claude-sonnet', name: 'Claude Sonnet', cli: 'claude', desc: 'Claude Code CLI · uses your Claude sign-in', available: true }, { id: 'codex', name: 'GPT-5.6', cli: 'codex', desc: 'Codex CLI · uses your ChatGPT sign-in', available: true }, { id: 'grok', name: 'Grok', cli: 'grok', desc: 'Grok CLI', available: false }] }, |
| 1015 | 1084 | }; |
| 1016 | 1085 | let cb = () => {}; |
| @@ -1025,10 +1094,12 @@ · 8 unmodified lines |
| 1025 | 1094 | copy: t => navigator.clipboard?.writeText(t), audioUrl: async () => null, notetaker: async () => {}, downloadModel: () => {}, |
| 1026 | 1095 | captureHotkey: async target => target === 'assistant' ? { keycode: 50, label: 'Left Shift' } : { keycode: 108, label: 'Right Alt' }, |
| 1027 | 1096 | assistant: async text => { |
| 1028 | | − st.runtime.assistant = 'running'; push(); |
| 1029 | | − await new Promise(r => setTimeout(r, 800)); |
| 1030 | | − const c = { id: `c${++n}`, ts: Date.now(), text, reply: `Stub reply to: ${text}`, model: st.settings.assistant.model, ms: 800, ok: true }; |
| 1031 | | − st.commands = [c, ...st.commands]; st.runtime.assistant = false; push(); return c.reply; |
| 1097 | + const prompt = `Read ~/.config/agents/AGENTS.md first.\n\n${st.settings.assistant.systemPrompt || ''}\n\nCommand: ${text}`; |
| 1098 | + const c = { id: `cmd${Date.now()}`, ts: Date.now(), text, prompt, reply: '', model: st.settings.assistant.model, ms: 0, ok: false, app: 'St', title: 'fish ~/src/lilt', followUp: Date.now() - (st.commands[0]?.ts || 0) < 3e5 }; |
| 1099 | + st.commands = [c, ...st.commands]; st.runtime.assistant = 'running'; push(); |
| 1100 | + await new Promise(r => setTimeout(r, 3000)); |
| 1101 | + Object.assign(c, { reply: `Stub reply to: ${text}`, ms: Date.now() - c.ts, ok: true }); |
| 1102 | + st.runtime.assistant = false; push(); return c; |
| 1032 | 1103 | }, |
| 1033 | 1104 | stopAssistant: async () => { st.runtime.assistant = false; push(); }, transform: async (id, t) => t, mcpAdd: async () => 'stub', open: () => {}, |
| 1034 | 1105 | exportData: async () => '~/dl/lilt-export.json', deleteData: async () => {}, win: () => {}, |