CxWebApp/static/js/lib/api.js
CxAI Agent d057e09fa2
Some checks are pending
build-and-push / image (push) Waiting to run
feat: add new panes for demand, diffusion, inbox, items, lang, mac, slack, system, tools, and websocket
- 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.
2026-05-16 19:23:30 -05:00

113 lines
3.9 KiB
JavaScript

// lib/api.js — fetch helpers, MCP base resolver, service catalogue.
export const MCP_BASE = (() => {
const fromAttr = document.body?.dataset?.mcpBase;
return (fromAttr || 'http://127.0.0.1:8082').replace(/\/+$/, '');
})();
class HttpError extends Error {
constructor(status, body) {
super(`HTTP ${status}`);
this.status = status;
this.body = body;
}
}
async function parseResp(res) {
const text = await res.text();
let body;
try { body = text ? JSON.parse(text) : null; } catch { body = text; }
if (!res.ok) throw new HttpError(res.status, body);
return body;
}
export async function jget(url, opts = {}) {
const res = await fetch(url, { ...opts, headers: { Accept: 'application/json', ...(opts.headers || {}) } });
return parseResp(res);
}
export async function jpost(url, body, opts = {}) {
const res = await fetch(url, {
method: 'POST',
...opts,
headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...(opts.headers || {}) },
body: body == null ? undefined : (typeof body === 'string' ? body : JSON.stringify(body)),
});
return parseResp(res);
}
export async function jdelete(url, opts = {}) {
const res = await fetch(url, { method: 'DELETE', ...opts });
return parseResp(res);
}
export async function jraw(method, url, body) {
const init = { method };
if (body && body !== '') {
init.headers = { 'Content-Type': 'application/json' };
init.body = body;
}
const res = await fetch(url, init);
const text = await res.text();
let parsed;
try { parsed = text ? JSON.parse(text) : null; } catch { parsed = text; }
return { status: res.status, ok: res.ok, body: parsed, raw: text };
}
// ---------- MCP shortcuts (used by agent/inbox/tools panes) ----------
export const mcp = {
status: () => jget(`${MCP_BASE}/status`),
events: (limit = 150) => jget(`${MCP_BASE}/events?limit=${limit}`),
tools: () => jget(`${MCP_BASE}/tools`),
call: (name, args = {}) => jpost(`${MCP_BASE}/tools/${name}`, args),
};
// ---------- Service registry (used by dashboard health pills) ----------
export const services = [
{ id: 'crow', label: 'Crow API', health: '/api/health' },
{ id: 'mcp', label: 'MCP', health: `${MCP_BASE}/status` },
{ id: 'diffusion', label: 'Diffusion', health: '/api/diffusion/v1/health' },
{ id: 'demand', label: 'Demand', health: '/api/demand/healthz' },
{ id: 'lang', label: 'Lang', health: '/api/lang/healthz' },
{ id: 'slack', label: 'Slack', health: '/api/slack/healthz' },
];
export async function probeService(svc) {
const t0 = performance.now();
try {
await jget(svc.health);
return { ok: true, ms: Math.round(performance.now() - t0) };
} catch (err) {
return { ok: false, ms: Math.round(performance.now() - t0), error: err.status || err.message };
}
}
export function escapeHtml(s) {
return String(s ?? '').replace(/[&<>"']/g, c => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[c]));
}
export function formatUptime(seconds) {
if (seconds == null || isNaN(seconds)) return '—';
seconds = Math.max(0, Math.floor(seconds));
const d = Math.floor(seconds / 86400); seconds %= 86400;
const h = Math.floor(seconds / 3600); seconds %= 3600;
const m = Math.floor(seconds / 60); const s = seconds % 60;
if (d) return `${d}d ${h}h ${m}m`;
if (h) return `${h}h ${m}m ${s}s`;
if (m) return `${m}m ${s}s`;
return `${s}s`;
}
export function statusClass(status) {
if (!status) return 'muted';
const s = String(status).toLowerCase();
if (s.includes('success') || s === 'ok' || s === 'ready' || s === 'healthy') return 'ok';
if (s.includes('error') || s.includes('fail')) return 'err';
if (s.includes('warn') || s.includes('pending') || s.includes('queued') || s.includes('running')) return 'warn';
return 'info';
}
export { HttpError };