summaryrefslogtreecommitdiff
path: root/modules/ext/ui-chat.js
blob: 32c67e79fa73f3c705cf1d84899ee4a8057b6f0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// UI mode: chat (level 2). Single column, sticky composer, slash chips, raw-terminal drawer.
class UIChatMode {
    constructor() { this.commands = {}; this.terminal = null; }
    requirements() { return { met: true, missing: [] }; }
    getHelpInfo() { return {}; }
    registerCommands() {}
    init(terminal) {
        this.terminal = terminal;
        if (!terminal.uiShell) return;
        terminal.uiShell.registerMode({
            id: 'chat', label: 'Chat', level: 2, icon: '💬',
            tagline: 'Conversation first.',
            mount: (api) => this.mount(api)
        });
    }
    mount(api) {
        api.injectCSS('chat', `
body[data-ui-mode="chat"] #ui-root { display: flex; flex-direction: column; }
#chat-head { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 10px 14px;
  border-bottom: 1px solid var(--border-color); background: var(--bg-secondary); }
#chat-head .title { font-weight: 600; flex: 1 1 auto; }
#chat-head button { min-height: 44px; }
#chat-stream { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 12px 14px;
  display: flex; flex-direction: column; gap: 6px; max-width: 860px; width: 100%; margin: 0 auto; }
#chat-stream .output-line { font-family: inherit; font-size: .95em; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; padding: 10px 12px; border-radius: 14px;
  background: var(--bg-secondary); border: 1px solid var(--border-color); max-width: 88%; }
#chat-stream .output-line.info { background: var(--ai-bg-color); border-color: var(--border-color); color: var(--ai-text-color); }
#chat-stream .output-line.error { border-color: var(--error-color); }
#chat-chips { flex: 0 0 auto; padding: 8px 14px 0; overflow-x: auto; white-space: nowrap;
  max-width: 860px; margin: 0 auto; width: 100%; }
#chat-composer { flex: 0 0 auto; padding: 8px 14px 14px; max-width: 860px; margin: 0 auto; width: 100%; }
#chat-composer #input-area { display: flex; gap: 8px; align-items: flex-end; background: var(--bg-secondary);
  border: 1px solid var(--border-color); border-radius: 16px; padding: 8px; }
#chat-composer .prompt-label { display: none; }
#chat-composer #command-input { width: 100%; font-family: inherit; font-size: 1em; min-height: 44px; background: transparent; border: 0; }
#chat-composer #autocomplete-dropdown { display: none; }
body[data-ui-mode="chat"] #raw-drawer { position: absolute; inset: 0 0 34px 0; z-index: 60;
  background: var(--bg-color); display: none; flex-direction: column; padding: 10px; }
body[data-ui-mode="chat"] #raw-drawer.open { display: flex; }
body[data-ui-mode="chat"] #raw-drawer #terminal-container { flex: 1 1 auto; min-height: 0; overflow: auto;
  font-family: var(--font-primary); font-size: .9em; }
`);

        const root = api.root;
        root.innerHTML = '';
        const head = api.el('div', { id: 'chat-head' });
        head.appendChild(api.el('span', { class: 'title', text: 'newOS4' }));
        const rawBtn = api.el('button', { class: 'wx11-btn', text: '⌨ Plain terminal' });
        const uiBtn = api.el('button', { class: 'wx11-btn', text: '⇄ Style' });
        uiBtn.addEventListener('click', () => api.exec('ui picker'));
        head.appendChild(uiBtn);
        head.appendChild(rawBtn);

        const stream = api.el('div', { id: 'chat-stream' });
        const chips = api.el('div', { id: 'chat-chips' });
        const composer = api.el('div', { id: 'chat-composer' });
        const drawer = api.el('div', { id: 'raw-drawer' });

        const chipMap = { '/help': 'help', '/files': 'ls', '/modules': 'modules', '/sys': 'cat /sys/info' };
        ['/help', '/files', '/modules', '/sys', '/style'].forEach(label => {
            const chip = api.el('span', { class: 'wx11-chip', text: label });
            chip.addEventListener('click', () => {
                if (label === '/style') return void api.exec('ui picker');
                api.exec(chipMap[label]);
            });
            chips.appendChild(chip);
        });

        root.appendChild(head);
        root.appendChild(stream);
        root.appendChild(chips);
        root.appendChild(composer);
        root.appendChild(drawer);

        api.attach(stream, api.stock.output);          // visible stream first
        api.attach(composer, api.stock.input);
        // Do NOT attach the output to the drawer here: it is one node, the last attach wins, and
        // the drawer starts hidden — the stream would render empty. The toggle moves it.

        const field = document.getElementById('command-input');
        if (field) field.placeholder = 'Message newOS4…';

        let open = false;
        rawBtn.addEventListener('click', () => {
            open = !open;
            if (open) { api.attach(drawer, api.stock.output); drawer.classList.add('open'); }
            else { api.attach(stream, api.stock.output); drawer.classList.remove('open'); }
            rawBtn.textContent = open ? '💬 Chat view' : '⌨ Plain terminal';
        });

        return () => {
            open = false;
            drawer.classList.remove('open');
            const f = document.getElementById('command-input');
            if (f) f.placeholder = 'Type a command...';   // leave the next mode a clean prompt
        };
    }
}