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
};
}
}
|