Initial commit
This commit is contained in:
133
dashboard/js/api.js
Normal file
133
dashboard/js/api.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* API Client — all fetch calls + EventSource wrapper
|
||||
*/
|
||||
|
||||
const BASE = '/api/v1';
|
||||
|
||||
class ApiClient {
|
||||
constructor() {
|
||||
this._eventSources = new Map();
|
||||
this._bus = new EventTarget();
|
||||
}
|
||||
|
||||
// --- Event bus ---
|
||||
on(event, fn) { this._bus.addEventListener(event, fn); }
|
||||
off(event, fn) { this._bus.removeEventListener(event, fn); }
|
||||
_emit(event, detail) {
|
||||
this._bus.dispatchEvent(new CustomEvent(event, { detail }));
|
||||
}
|
||||
|
||||
// --- HTTP helpers ---
|
||||
async _fetch(path, opts = {}) {
|
||||
try {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
headers: { 'Content-Type': 'application/json', ...opts.headers },
|
||||
...opts,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
throw new Error(err.detail || `HTTP ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
} catch (e) {
|
||||
this._emit('error', { message: e.message, path });
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Health ---
|
||||
async checkHealth() {
|
||||
try {
|
||||
const data = await fetch('/health').then(r => r.json());
|
||||
this._emit('health', data);
|
||||
return data;
|
||||
} catch {
|
||||
this._emit('health', { status: 'disconnected' });
|
||||
return { status: 'disconnected' };
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sessions ---
|
||||
async createSession(body) {
|
||||
const data = await this._fetch('/sessions', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
this._emit('session:created', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async getSession(sessionId) {
|
||||
const data = await this._fetch(`/sessions/${sessionId}`);
|
||||
this._emit('session:state', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async deleteSession(sessionId) {
|
||||
const data = await this._fetch(`/sessions/${sessionId}`, { method: 'DELETE' });
|
||||
this._emit('session:deleted', { session_id: sessionId });
|
||||
return data;
|
||||
}
|
||||
|
||||
async getEvents(sessionId) {
|
||||
return this._fetch(`/sessions/${sessionId}/events`);
|
||||
}
|
||||
|
||||
// --- Messages ---
|
||||
async sendMessage(sessionId, message, stream = false) {
|
||||
return this._fetch(`/sessions/${sessionId}/messages`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ message, stream }),
|
||||
});
|
||||
}
|
||||
|
||||
// --- SSE ---
|
||||
subscribeSSE(sessionId) {
|
||||
this.unsubscribeSSE(sessionId);
|
||||
|
||||
const url = `${BASE}/sessions/${sessionId}/stream`;
|
||||
const es = new EventSource(url);
|
||||
|
||||
const eventTypes = [
|
||||
'session.created', 'execution.started', 'agent.delta',
|
||||
'tool.started', 'tool.completed', 'subagent.assigned',
|
||||
'execution.completed', 'error', 'keepalive',
|
||||
];
|
||||
|
||||
for (const type of eventTypes) {
|
||||
es.addEventListener(type, (e) => {
|
||||
try {
|
||||
const payload = JSON.parse(e.data);
|
||||
this._emit('sse', { type, ...payload });
|
||||
this._emit(`sse:${type}`, payload);
|
||||
} catch {
|
||||
this._emit('sse', { type, raw: e.data });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
es.onerror = () => {
|
||||
this._emit('sse:error', { sessionId });
|
||||
};
|
||||
|
||||
this._eventSources.set(sessionId, es);
|
||||
return es;
|
||||
}
|
||||
|
||||
unsubscribeSSE(sessionId) {
|
||||
const es = this._eventSources.get(sessionId);
|
||||
if (es) {
|
||||
es.close();
|
||||
this._eventSources.delete(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribeAll() {
|
||||
for (const [id, es] of this._eventSources) {
|
||||
es.close();
|
||||
}
|
||||
this._eventSources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export const api = new ApiClient();
|
||||
Reference in New Issue
Block a user