Some checks are pending
build-and-push / image (push) Waiting to run
- Implemented demand pane for managing trend-driven design jobs. - Created diffusion pane for generating images via Stable Diffusion. - Added inbox pane for sweeping and routing artifacts through the CxAI inbox classifier. - Developed items pane for CRUD operations against /api/items. - Introduced lang pane for running language pipelines. - Established mac pane for macOS app distribution information. - Integrated slack pane for sending messages and displaying diagnostics. - Built system pane for process introspection and version information. - Launched tools pane for browsing and invoking MCP tools. - Set up websocket pane for connecting to the /ws/echo service.
102 lines
4.5 KiB
JavaScript
102 lines
4.5 KiB
JavaScript
// panes/inbox.js — sweep & route CxAI/_inbox via MCP tools.
|
|
import { registerPane } from '../app.js';
|
|
import { mcp, escapeHtml } from '../lib/api.js';
|
|
import { ok, err, fmtJSON } from '../lib/ui.js';
|
|
|
|
const TPL = `
|
|
<div class="pane-head">
|
|
<div>
|
|
<div class="title">Inbox</div>
|
|
<div class="sub">Route artifacts through the CxAI inbox classifier.</div>
|
|
</div>
|
|
<div class="grow"></div>
|
|
<button class="btn btn-secondary" id="inbox-refresh">Refresh</button>
|
|
</div>
|
|
|
|
<div class="grid cols-2">
|
|
<div class="card">
|
|
<div class="card-title"><h2>Sweep</h2><span class="muted mono" id="inbox-sweep-meta">idle</span></div>
|
|
<form id="inbox-sweep-form" class="grid cols-2 gap-3">
|
|
<label class="field">
|
|
<span class="lbl">Mode</span>
|
|
<select class="input" id="inbox-mode">
|
|
<option value="all">all (inbox + needs-review)</option>
|
|
<option value="inbox">inbox only</option>
|
|
<option value="needs_review">needs-review only</option>
|
|
</select>
|
|
</label>
|
|
<label class="field">
|
|
<span class="lbl">Threshold</span>
|
|
<input class="input" id="inbox-threshold" type="number" min="0" max="1" step="0.05" value="0.7" />
|
|
</label>
|
|
<label class="check" style="grid-column: span 2"><input type="checkbox" id="inbox-dry" /> dry-run (don't move files)</label>
|
|
<button class="btn btn-primary" type="submit" style="grid-column: span 2">Run sweep</button>
|
|
</form>
|
|
<div class="mt-3">
|
|
<h3 class="mb-2">Last result</h3>
|
|
<pre id="inbox-result" class="muted">no sweep yet</pre>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title"><h2>Route a single file</h2><span class="muted" id="inbox-route-meta"></span></div>
|
|
<form id="inbox-route-form" class="flex gap-2 mb-3">
|
|
<input class="input" id="inbox-route-path" placeholder="/absolute/path/to/file.md" />
|
|
<label class="check"><input type="checkbox" id="inbox-route-dry" /> dry</label>
|
|
<button class="btn btn-primary" type="submit">Route</button>
|
|
</form>
|
|
<pre id="inbox-route-result" class="muted">no route yet</pre>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
async function runSweep(host) {
|
|
const mode = host.querySelector('#inbox-mode').value;
|
|
const threshold = parseFloat(host.querySelector('#inbox-threshold').value);
|
|
const dry_run = host.querySelector('#inbox-dry').checked;
|
|
host.querySelector('#inbox-sweep-meta').textContent = 'running…';
|
|
host.querySelector('#inbox-result').textContent = 'running…';
|
|
try {
|
|
const r = await mcp.call('inbox_sweep_tool', { mode, threshold, dry_run });
|
|
host.querySelector('#inbox-result').textContent = fmtJSON(r);
|
|
const total = (r?.routed?.length ?? r?.routed ?? 0) || (r?.summary?.routed ?? 0);
|
|
host.querySelector('#inbox-sweep-meta').textContent =
|
|
dry_run ? `dry-run · ${JSON.stringify(r).length}b` : `routed ${total}`;
|
|
const pill = document.getElementById('nav-inbox-pill');
|
|
if (pill && !dry_run) pill.textContent = String(total || '');
|
|
ok(`Sweep complete${dry_run ? ' (dry)' : ''}`);
|
|
} catch (e) {
|
|
host.querySelector('#inbox-result').textContent = fmtJSON(e.body ?? { error: e.message });
|
|
host.querySelector('#inbox-sweep-meta').textContent = `error ${e.status ?? ''}`;
|
|
err(`sweep: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
async function routeOne(host) {
|
|
const path = host.querySelector('#inbox-route-path').value.trim();
|
|
if (!path) return;
|
|
const dry_run = host.querySelector('#inbox-route-dry').checked;
|
|
host.querySelector('#inbox-route-meta').textContent = 'routing…';
|
|
host.querySelector('#inbox-route-result').textContent = 'routing…';
|
|
try {
|
|
const r = await mcp.call('route_file_tool', { path, dry_run });
|
|
host.querySelector('#inbox-route-result').textContent = fmtJSON(r);
|
|
host.querySelector('#inbox-route-meta').textContent = r?.target || r?.destination || 'ok';
|
|
ok(`Routed ${path.split('/').pop()}`);
|
|
} catch (e) {
|
|
host.querySelector('#inbox-route-result').textContent = fmtJSON(e.body ?? { error: e.message });
|
|
host.querySelector('#inbox-route-meta').textContent = `error ${e.status ?? ''}`;
|
|
err(`route: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
registerPane('inbox', {
|
|
label: 'Inbox',
|
|
init(host) {
|
|
host.innerHTML = TPL;
|
|
host.querySelector('#inbox-sweep-form').addEventListener('submit', (e) => { e.preventDefault(); runSweep(host); });
|
|
host.querySelector('#inbox-route-form').addEventListener('submit', (e) => { e.preventDefault(); routeOne(host); });
|
|
host.querySelector('#inbox-refresh').addEventListener('click', () => runSweep(host));
|
|
},
|
|
});
|