aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/ui-chat.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/ext/ui-chat.js')
-rw-r--r--modules/ext/ui-chat.js99
1 files changed, 99 insertions, 0 deletions
diff --git a/modules/ext/ui-chat.js b/modules/ext/ui-chat.js
new file mode 100644
index 0000000..32c67e7
--- /dev/null
+++ b/modules/ext/ui-chat.js
@@ -0,0 +1,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
+ };
+ }
+}