nio-cat 0.5.0 → 0.5.1
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/bin/nio.js +0 -4
- package/package.json +1 -1
- package/src/agents/claude.js +3 -1
- package/src/commands.js +12 -1
- package/src/hub.js +27 -0
- package/web/app/app.js +29 -5
- package/web/app/style.css +10 -4
- package/web/sw.js +1 -1
package/bin/nio.js
CHANGED
|
@@ -187,10 +187,6 @@ async function start() {
|
|
|
187
187
|
if (ip) {
|
|
188
188
|
publicUrl = publicUrl || `http://${ip}:${cfg.port}`;
|
|
189
189
|
console.log(` ${c.dim('Xarxa:')} ${publicUrl} ${c.dim('(només la mateixa wifi)')}`);
|
|
190
|
-
if (!flags.url) {
|
|
191
|
-
// A la xarxa local, l'app es carrega des del mateix daemon (mateix origen).
|
|
192
|
-
flags.app = publicUrl;
|
|
193
|
-
}
|
|
194
190
|
}
|
|
195
191
|
}
|
|
196
192
|
|
package/package.json
CHANGED
package/src/agents/claude.js
CHANGED
|
@@ -77,7 +77,7 @@ function handle(ctx, line) {
|
|
|
77
77
|
}
|
|
78
78
|
case 'assistant':
|
|
79
79
|
for (const b of ev.message?.content || []) {
|
|
80
|
-
if (b.type === 'text' && b.text) ctx.endStream(b.text);
|
|
80
|
+
if (b.type === 'text' && b.text) { ctx.said = true; ctx.endStream(b.text); }
|
|
81
81
|
else if (b.type === 'tool_use') ctx.tool({ id: b.id, name: b.name, input: b.input });
|
|
82
82
|
}
|
|
83
83
|
break;
|
|
@@ -93,6 +93,8 @@ function handle(ctx, line) {
|
|
|
93
93
|
break;
|
|
94
94
|
case 'result':
|
|
95
95
|
if (ev.session_id) ctx.setNative(ev.session_id);
|
|
96
|
+
// Les comandes locals (/cost, /context…) no passen pel model: la resposta només és aquí.
|
|
97
|
+
if (!ctx.said && !ev.is_error && !ev.num_turns) ctx.endStream(typeof ev.result === 'string' && ev.result.trim() ? ev.result : 'Fet.');
|
|
96
98
|
ctx.result({
|
|
97
99
|
cost: ev.total_cost_usd,
|
|
98
100
|
ms: ev.duration_ms,
|
package/src/commands.js
CHANGED
|
@@ -23,8 +23,18 @@ function listDir(dir) {
|
|
|
23
23
|
try { return fs.readdirSync(dir, { withFileTypes: true }); } catch { return []; }
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Comandes internes de Claude Code que també funcionen en mode headless (les altres, com /help o /status, no hi són).
|
|
27
|
+
const CLAUDE_BUILTIN = [
|
|
28
|
+
{ name: 'usage', hint: 'Ús de la subscripció i límits' },
|
|
29
|
+
{ name: 'context', hint: 'Mostra com s’omple el context' },
|
|
30
|
+
{ name: 'compact', hint: 'Resumeix la conversa per alliberar context' },
|
|
31
|
+
{ name: 'init', hint: 'Crea un CLAUDE.md al projecte' },
|
|
32
|
+
{ name: 'review', hint: 'Revisa els canvis de codi' },
|
|
33
|
+
{ name: 'security-review', hint: 'Revisió de seguretat dels canvis' },
|
|
34
|
+
];
|
|
35
|
+
|
|
26
36
|
function claudeCommands(cwd) {
|
|
27
|
-
const out = new Map();
|
|
37
|
+
const out = new Map(CLAUDE_BUILTIN.map((c) => [c.name, { ...c, kind: 'comanda' }]));
|
|
28
38
|
const bases = [path.join(os.homedir(), '.claude'), cwd && path.join(cwd, '.claude')].filter(Boolean);
|
|
29
39
|
for (const base of bases) {
|
|
30
40
|
// Comandes: commands/nom.md i commands/espai/nom.md (→ «espai:nom»)
|
|
@@ -45,6 +55,7 @@ function claudeCommands(cwd) {
|
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
function agentCommands(agent, cwd) {
|
|
58
|
+
if (agent === 'codex') return [{ name: 'status', hint: 'Compte, model i carpeta de la sessió', kind: 'comanda' }];
|
|
48
59
|
return agent === 'claude' ? claudeCommands(cwd) : [];
|
|
49
60
|
}
|
|
50
61
|
|
package/src/hub.js
CHANGED
|
@@ -163,12 +163,38 @@ class Hub extends EventEmitter {
|
|
|
163
163
|
this.emit('out', { type: 'removed', session: sid });
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// Codex no té /status en mode headless: ho resolem aquí amb el que sí que sap dir.
|
|
167
|
+
codexStatus(s, bp) {
|
|
168
|
+
const { execFile } = require('child_process');
|
|
169
|
+
execFile(bp, ['login', 'status'], { timeout: 8000 }, (err, out, errOut) => {
|
|
170
|
+
const rows = String(out || errOut || '').split('\n').map((l) => l.trim()).filter(Boolean), login = rows.find((l) => /logged|log in|login/i.test(l) && !/^warning/i.test(l)) || rows[0] || (err ? 'No he pogut llegir el compte' : '');
|
|
171
|
+
const lines = [`**Compte:** ${login}`, `**Model:** ${s.model || s.modelUsed || 'el de per defecte'}`, `**Carpeta:** \`${s.cwd}\``, `**Permisos:** ${s.mode}`, `**Conversa:** ${s.nativeId ? 'continua la de Codex' : 'encara no ha començat'}`];
|
|
172
|
+
this.push(s, 'text', { text: lines.join('\n\n') });
|
|
173
|
+
this.touch(s);
|
|
174
|
+
});
|
|
175
|
+
return this.summary(s);
|
|
176
|
+
}
|
|
177
|
+
|
|
166
178
|
update(sid, patch) {
|
|
167
179
|
const s = this.get(sid);
|
|
168
180
|
if (!s) throw httpErr(404, 'Aquesta sessió no existeix');
|
|
169
181
|
if (patch.mode && ['llegir', 'preguntar', 'tot'].includes(patch.mode)) s.mode = patch.mode;
|
|
170
182
|
if (typeof patch.title === 'string') s.title = patch.title.slice(0, 120);
|
|
171
183
|
if (typeof patch.model === 'string') s.model = patch.model.slice(0, 80);
|
|
184
|
+
if (typeof patch.cwd === 'string' && path.resolve(patch.cwd) !== s.cwd) {
|
|
185
|
+
const cwd = path.resolve(patch.cwd);
|
|
186
|
+
if (s.state === 'running' || s.state === 'waiting') throw httpErr(409, 'Espera que l’agent acabi o atura’l abans de canviar de carpeta');
|
|
187
|
+
let ok = false;
|
|
188
|
+
try { ok = fs.statSync(cwd).isDirectory(); } catch { /* no existeix */ }
|
|
189
|
+
if (!ok) throw httpErr(400, 'Aquesta carpeta no existeix');
|
|
190
|
+
// Claude Code desa cada conversa dins la carpeta del projecte: la copiem perquè l'agent la recordi.
|
|
191
|
+
if (s.agent === 'claude' && s.nativeId) {
|
|
192
|
+
const enc = (d) => path.join(require('os').homedir(), '.claude', 'projects', d.replace(/[^a-zA-Z0-9]/g, '-'), `${s.nativeId}.jsonl`);
|
|
193
|
+
try { fs.mkdirSync(path.dirname(enc(cwd)), { recursive: true }); fs.copyFileSync(enc(s.cwd), enc(cwd)); } catch { s.nativeId = null; }
|
|
194
|
+
}
|
|
195
|
+
s.cwd = cwd;
|
|
196
|
+
this.push(s, 'info', { text: `Carpeta canviada a ${cwd}` });
|
|
197
|
+
}
|
|
172
198
|
this.touch(s);
|
|
173
199
|
return this.summary(s);
|
|
174
200
|
}
|
|
@@ -201,6 +227,7 @@ class Hub extends EventEmitter {
|
|
|
201
227
|
|
|
202
228
|
if (!s.title) s.title = text.replace(/\s+/g, ' ').slice(0, 60);
|
|
203
229
|
this.push(s, 'user', { text });
|
|
230
|
+
if (s.agent === 'codex' && /^\/status\s*$/i.test(text)) return this.codexStatus(s, bp);
|
|
204
231
|
s.state = 'running';
|
|
205
232
|
this.push(s, 'status', { state: 'running' });
|
|
206
233
|
|
package/web/app/app.js
CHANGED
|
@@ -582,7 +582,7 @@ function viewSessions() {
|
|
|
582
582
|
const many = store.machines.length > 1, mach = machine();
|
|
583
583
|
const switcher = many ? `<button class="chip switch-chip" type="button" data-act="machines"><i class="dot ${state.connected ? 'on' : ''}"></i>${esc(mach.name)}${svg(ICON.chev)}</button>` : '';
|
|
584
584
|
const hint = mobileHint();
|
|
585
|
-
const head = `<div class="top"><div><h1>Sessions</h1><p>Cada sessió és una conversa amb un agent, dins d’una carpeta.</p>${switcher}</div><button class="btn ink" data-act="new" type="button">${svg(ICON.plus)}Nova sessió</button></div>${hint}`;
|
|
585
|
+
const head = `<div class="top"><div><h1>Sessions</h1><p>Cada sessió és una conversa amb un agent, dins d’una carpeta.</p>${switcher}</div><button class="btn ink new-fab" data-act="new" type="button">${svg(ICON.plus)}Nova sessió</button></div>${hint}`;
|
|
586
586
|
if (!state.connected && !list.length) {
|
|
587
587
|
return `${head}<div class="panel empty">${bot(state.lastError ? 'sleep' : 'work')}<h2>${esc(state.lastError || 'Connectant amb l’ordinador…')}</h2>${state.lastError ? `<p>${mach.kind === 'vps' ? 'Potser el servidor s’ha reiniciat i l’adreça ha canviat.' : 'Comprova que nio continua engegat a l’ordinador i que el túnel està obert.'}</p><div class="row" style="justify-content:center"><button class="btn" data-act="retry" type="button">${svg(ICON.refresh)}Torna-ho a provar</button>${mach.kind === 'vps' && mach.via ? `<button class="btn ink" data-reconnect="${store.active}" type="button">Torna a connectar</button>` : ''}<a class="btn ghost" href="#/ordinadors">Ordinadors</a></div>` : ''}</div>`;
|
|
588
588
|
}
|
|
@@ -680,7 +680,7 @@ function renderSessionHead() {
|
|
|
680
680
|
const ag = state.info?.agents?.find((a) => a.id === s.agent);
|
|
681
681
|
bar.innerHTML = `<span class="chip"><span class="agent-tag nosq" data-agent="${esc(s.agent)}">${agentBot(s.agent, sessionMood(s))}${esc(AGENT_NAMES[s.agent] || s.agent)}</span></span>
|
|
682
682
|
${ag?.noModes ? '' : `<button class="chip" data-act="mode" type="button">${esc(MODES[s.mode]?.label || s.mode)}</button>`}
|
|
683
|
-
<
|
|
683
|
+
<button class="chip mono" data-act="folder" type="button" title="${esc(s.cwd)} · canvia de carpeta">${esc(basename(s.cwd))}</button>
|
|
684
684
|
<button class="chip mono" data-act="model" type="button" title="Canvia de model">${esc(s.model || 'Model per defecte')}</button>
|
|
685
685
|
${s.cost ? `<span class="chip mono">${s.cost.toFixed(2)} $</span>` : ''}
|
|
686
686
|
${stateChip(s)}`;
|
|
@@ -757,6 +757,7 @@ const NIO_CMDS = [
|
|
|
757
757
|
{ name: 'model', hint: 'Canvia de model' },
|
|
758
758
|
{ name: 'stop', hint: 'Atura l’agent' },
|
|
759
759
|
{ name: 'nova', hint: 'Nova sessió amb el mateix agent i carpeta' },
|
|
760
|
+
{ name: 'carpeta', hint: 'Canvia la carpeta de la sessió' },
|
|
760
761
|
{ name: 'git', hint: 'Git i GitHub: commit i PR' },
|
|
761
762
|
{ name: 'nom', hint: 'Posa nom a la sessió', free: true },
|
|
762
763
|
{ name: 'opcions', hint: 'Opcions de la sessió' },
|
|
@@ -781,12 +782,33 @@ async function runCmd(s, name, arg) {
|
|
|
781
782
|
case 'model': return arg ? patch({ model: arg }, `Model: ${arg}`) : modelSheet(s);
|
|
782
783
|
case 'stop': try { await api(`/api/sessions/${s.id}/stop`, { method: 'POST' }); toast('Aturat'); } catch (e) { toast(e.message); } return;
|
|
783
784
|
case 'nova': return newSessionSheet({ agent: s.agent, cwd: s.cwd, mode: s.mode });
|
|
785
|
+
case 'carpeta': return folderSheet(s);
|
|
784
786
|
case 'git': return gitSheet(s.id);
|
|
785
787
|
case 'nom': return arg ? patch({ title: arg }, 'Nom desat') : toast('Escriu el nom: /nom Arregla els tests');
|
|
786
788
|
case 'opcions': return sessionOptions(s.id);
|
|
787
789
|
case 'ajuda': return helpCmdsSheet(s);
|
|
788
790
|
}
|
|
789
791
|
}
|
|
792
|
+
function folderSheet(s) {
|
|
793
|
+
let d = null;
|
|
794
|
+
const draw = () => {
|
|
795
|
+
const crumbs = d ? d.path.split(/[\\/]/).filter(Boolean) : [];
|
|
796
|
+
$('#fsBody').innerHTML = !d ? '<p class="muted small">Carregant…</p>' : `<div class="fs-bar"><button class="icon-btn" type="button" data-fs="${esc(d.parent || '')}" ${d.parent ? '' : 'disabled'} aria-label="Carpeta de dalt">${svg(ICON.back)}</button><button class="chip" type="button" data-fs="${esc(d.home)}">Inici</button><span class="fs-path mono small" title="${esc(d.path)}">${esc(crumbs.slice(-2).join(' / ') || '/')}</span></div>
|
|
797
|
+
<div class="proj-list fs-list">${d.dirs.map((x) => `<button type="button" class="proj" data-fs="${esc(x.path)}"><span class="ic">›</span><span class="grow"><div ${x.hidden ? 'class="muted"' : ''}>${esc(x.name)}</div></span></button>`).join('') || '<p class="muted small">No hi ha subcarpetes. Pots triar aquesta.</p>'}</div>`;
|
|
798
|
+
const ok = $('#fsOk'); if (ok) { ok.disabled = !d || d.path === s.cwd; ok.textContent = d && d.path === s.cwd ? 'Ja hi és' : 'Treballa aquí'; }
|
|
799
|
+
};
|
|
800
|
+
const go = async (p) => { try { d = await api(`/api/fs?path=${encodeURIComponent(p || '')}`); } catch (e) { toast(e.message, 4200); } draw(); };
|
|
801
|
+
openSheet(`<h2>Canvia de carpeta</h2><p class="muted small">Ara: <span class="mono">${esc(s.cwd)}</span>. La conversa continua i l’agent la recorda.</p><div id="fsBody"></div><div class="row between sheet-foot"><button class="btn ghost" type="button" data-close>Cancel·la</button><button class="btn ink" id="fsOk" type="button" disabled>Treballa aquí</button></div>`, (sh) => {
|
|
802
|
+
sh.onclick = async (e) => {
|
|
803
|
+
if (e.target.closest('[data-close]')) return closeSheet();
|
|
804
|
+
const f = e.target.closest('[data-fs]'); if (f && !f.disabled) return go(f.dataset.fs);
|
|
805
|
+
if (e.target.closest('#fsOk') && d) {
|
|
806
|
+
try { const sum = await api(`/api/sessions/${s.id}`, { method: 'PATCH', body: { cwd: d.path } }); state.sessions.set(s.id, sum); closeSheet(); renderSessionHead(); cmdCache.delete(s.id); toast(`Carpeta: ${basename(sum.cwd)}`); } catch (er) { toast(er.message, 4200); }
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
draw(); go(s.cwd);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
790
812
|
function helpCmdsSheet(s) {
|
|
791
813
|
const ac = agentCmds(s.id);
|
|
792
814
|
openSheet(`<h2>Comandes</h2><p class="muted small">Escriu <span class="mono">/</span> al missatge per veure-les.</p><div class="list">${NIO_CMDS.map((c) => `<div class="card"><b class="mono">/${c.name}</b>${c.args ? ' <span class="muted small mono">l · p · t</span>' : ''}<div class="muted small">${esc(c.hint)}</div></div>`).join('')}</div>${ac.length ? `<p class="muted small">De ${esc(AGENT_NAMES[s.agent] || s.agent)}: ${ac.length} comandes i skills. Se li envien a l’agent tal qual.</p>` : ''}<div class="row sheet-foot"><button class="btn ghost" type="button" data-close>Tanca</button></div>`, (sh) => { $('[data-close]', sh).onclick = closeSheet; });
|
|
@@ -798,7 +820,7 @@ function slashItems(s, v) {
|
|
|
798
820
|
if (!m) return [];
|
|
799
821
|
const q = m[1].toLowerCase();
|
|
800
822
|
const nio = NIO_CMDS.map((c) => ({ ...c, tag: 'nio' }));
|
|
801
|
-
const ag = agentCmds(s.id).map((c) => ({ name: c.name, hint: c.hint, tag: AGENT_NAMES[s.agent] || s.agent, agent: true }));
|
|
823
|
+
const ag = agentCmds(s.id).map((c) => ({ name: c.name, hint: c.hint, tag: (AGENT_NAMES[s.agent] || s.agent).split(' ')[0], agent: true }));
|
|
802
824
|
const all = [...nio, ...ag], starts = all.filter((c) => c.name.toLowerCase().startsWith(q)), has = all.filter((c) => !c.name.toLowerCase().startsWith(q) && c.name.toLowerCase().includes(q));
|
|
803
825
|
return [...starts, ...has].slice(0, 40).map((c) => ({ ...c, label: `/${c.name}` }));
|
|
804
826
|
}
|
|
@@ -809,7 +831,7 @@ function renderComposer() {
|
|
|
809
831
|
if (box.dataset.busy === String(busy) && $('textarea', box)) return;
|
|
810
832
|
box.dataset.busy = String(busy);
|
|
811
833
|
if (s) loadCmds(s.id).then(() => { if (state.route.id === s.id) drawSlash(); });
|
|
812
|
-
box.innerHTML = `<div class="slash" id="slash" role="listbox" hidden></div><div class="inner"><textarea rows="1" id="msg" placeholder="${busy ? 'L’agent està treballant…' : 'Escriu a l’agent…
|
|
834
|
+
box.innerHTML = `<div class="slash" id="slash" role="listbox" hidden></div><div class="inner"><textarea rows="1" id="msg" placeholder="${busy ? 'L’agent està treballant…' : 'Escriu a l’agent…'}" enterkeyhint="send" aria-label="Missatge" aria-controls="slash"></textarea><button class="mic" id="micBtn" type="button" data-mic aria-label="Dicta amb el micròfon">${svg(ICON.mic)}</button><button class="send ${busy ? 'stop' : ''}" id="sendBtn" type="button" aria-label="${busy ? 'Atura l’agent' : 'Envia'}">${svg(busy ? ICON.stop : ICON.send)}</button></div>`;
|
|
813
835
|
const ta = $('#msg'); ta.value = prev;
|
|
814
836
|
const grow = () => autogrow(ta);
|
|
815
837
|
let items = [], sel = 0;
|
|
@@ -829,7 +851,8 @@ function renderComposer() {
|
|
|
829
851
|
ta.value = ''; grow(); drawSlash(); runCmd(cur, c.name, '');
|
|
830
852
|
};
|
|
831
853
|
ta.addEventListener('input', () => { grow(); sel = 0; drawSlash(); });
|
|
832
|
-
pop.addEventListener('
|
|
854
|
+
pop.addEventListener('mousedown', (e) => e.preventDefault());
|
|
855
|
+
pop.addEventListener('click', (e) => { const b = e.target.closest('[data-i]'); if (b) pick(+b.dataset.i); });
|
|
833
856
|
const send = async () => {
|
|
834
857
|
const text = ta.value.trim(); if (!text || !s) return;
|
|
835
858
|
const c = text.match(/^\/([\w:.-]+)(?:\s+([\s\S]*))?$/), nio = c && NIO_CMDS.find((x) => x.name === c[1].toLowerCase());
|
|
@@ -1433,6 +1456,7 @@ document.addEventListener('click', async (e) => {
|
|
|
1433
1456
|
case 'open-sessions': return go('#/sessions');
|
|
1434
1457
|
case 'help': return go('#/ajuda');
|
|
1435
1458
|
case 'more': return moreSheet();
|
|
1459
|
+
case 'folder': { const s = state.sessions.get(state.route.id); return s && folderSheet(s); }
|
|
1436
1460
|
case 'machines': return machinesSheet();
|
|
1437
1461
|
case 'mobile-qr': return mobileQrSheet();
|
|
1438
1462
|
case 'mobile-hint-off': { const m = machine(); LS.set(`nio.mobHint.${m?.id || m?.url}`, true); return render(); }
|
package/web/app/style.css
CHANGED
|
@@ -482,13 +482,14 @@ a.kbd { text-decoration: none; color: inherit; }
|
|
|
482
482
|
/* Comandes amb «/» */
|
|
483
483
|
.composer { position: relative; }
|
|
484
484
|
.slash { position: absolute; left: 24px; right: 24px; bottom: 100%; max-width: 760px; margin: 0 auto 6px; max-height: min(320px, 45dvh); overflow-y: auto; overscroll-behavior: contain; display: grid; padding: 6px; border: 1px solid var(--border); border-radius: 14px; background: var(--white); box-shadow: 0 10px 30px rgba(39, 34, 36, .12); z-index: 5; }
|
|
485
|
-
.slash button { display: grid; grid-template-columns: auto 1fr auto; gap:
|
|
485
|
+
.slash button { display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto; gap: 2px 12px; align-items: center; text-align: left; padding: 9px 10px; border: 0; border-radius: 9px; background: none; width: 100%; }
|
|
486
486
|
.slash button[aria-selected="true"] { background: var(--bg-2); }
|
|
487
|
-
.slash .n { font-weight: 600; }
|
|
487
|
+
.slash .n { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
488
488
|
.slash .h { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg-muted); font-size: 13px; }
|
|
489
|
-
.slash .t {
|
|
489
|
+
.slash .t { display: inline-flex; align-items: center; justify-content: center; height: 20px; padding: 0 8px; border-radius: 10px; background: var(--bg-2); color: var(--fg-muted); font-size: 11px; font-weight: 600; line-height: 1; white-space: nowrap; }
|
|
490
|
+
.slash button[aria-selected="true"] .t { background: var(--white); }
|
|
490
491
|
.slash .t.ag { background: color-mix(in srgb, var(--lemon) 60%, var(--bg)); color: var(--fg); }
|
|
491
|
-
@media (max-width: 820px) { .slash { left: 12px; right: 12px; } .slash .h { grid-column: 1 / -1; grid-row: 2; } }
|
|
492
|
+
@media (max-width: 820px) { .slash { left: 12px; right: 12px; } .slash button { grid-template-columns: minmax(0, 1fr) auto; } .slash .h { grid-column: 1 / -1; grid-row: 2; } .slash .t { grid-column: 2; grid-row: 1; } }
|
|
492
493
|
/* ---------- Veu ---------- */
|
|
493
494
|
.ev-error b { font-size: 12px; letter-spacing: .02em; text-transform: uppercase; }
|
|
494
495
|
.speak { display: inline-flex; align-items: center; gap: 6px; margin-top: 8px; height: 28px; padding: 0 10px; border: 1px solid var(--border); border-radius: 999px; background: none; color: var(--fg-muted); font-size: 12px; font-weight: 600; }
|
|
@@ -563,3 +564,8 @@ dialog.sheet.wiz.compact .sheet-in, .compact .wiz-body { flex: 0 1 auto; }
|
|
|
563
564
|
.chip .agent-tag { gap: 7px; }
|
|
564
565
|
.sheet .list + .or { margin-top: 18px; }
|
|
565
566
|
.sheet #modelForm { margin-top: 4px; }
|
|
567
|
+
|
|
568
|
+
@media (max-width: 820px) {
|
|
569
|
+
.new-fab { position: fixed; right: 16px; bottom: calc(72px + var(--safe-b)); z-index: 25; height: 48px; padding: 0 18px 0 14px; border-radius: 16px; box-shadow: 0 14px 30px -14px color-mix(in srgb, var(--fg) 70%, transparent); }
|
|
570
|
+
body:not(.no-tabs):not(.kb) .main:has(.new-fab) { padding-bottom: calc(130px + var(--safe-b)); }
|
|
571
|
+
}
|
package/web/sw.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// nio · service worker: obre l'app encara que no hi hagi connexió i mostra els avisos.
|
|
2
|
-
const SHELL = 'nio-shell-
|
|
2
|
+
const SHELL = 'nio-shell-v15';
|
|
3
3
|
const FILES = ['/app', '/app/style.css', '/app/app.js', '/bot.css', '/bot.js', '/board.css', '/board.js', '/favicon.svg', '/manifest.webmanifest'];
|
|
4
4
|
|
|
5
5
|
self.addEventListener('install', (e) => {
|