feat: connect chat and runs, cancellable chat messages, unified frontend runs module
This commit is contained in:
1 parent
d7e92f38f3
commit
bc71fa4d59
7 files changed
+244
-119
No files matched your search
@@ -0,0 +1,142 @@
|
||||
import * as backend from './backend';
|
||||
|
||||
// State
|
||||
let runs = [];
|
||||
let unsubscribeRunEvents = null;
|
||||
|
||||
// Event listeners for UI components
|
||||
const listeners = {
|
||||
runs: [],
|
||||
chats: {},
|
||||
};
|
||||
|
||||
class RunListener {
|
||||
constructor() {
|
||||
this.runIds = new Set();
|
||||
this.callbacks = [];
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
setIdList(runIds) {
|
||||
this.runIds = new Set(runIds);
|
||||
}
|
||||
|
||||
addRunId(runId) {
|
||||
this.runIds.add(runId);
|
||||
}
|
||||
|
||||
addCallback(callback) {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.cancelled = true;
|
||||
listeners.runs = listeners.runs.filter(l => l !== this);
|
||||
}
|
||||
|
||||
hasRunId(runId) {
|
||||
return this.runIds.has(runId);
|
||||
}
|
||||
|
||||
emitChange(data) {
|
||||
if (this.cancelled) return;
|
||||
for (let cb of this.callbacks) cb(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Update a run based on an incoming event
|
||||
function updateRun(event) {
|
||||
const { name, runId, data } = event.data;
|
||||
|
||||
// Find the run by ID
|
||||
const runIndex = runs.findIndex(r => r.id === runId);
|
||||
let run = runIndex >= 0 ? runs[runIndex] : null;
|
||||
|
||||
if (name == 'created') {
|
||||
if (!run) {
|
||||
run = Object.assign({}, data, {id: runId, events: []});
|
||||
runs.push(run);
|
||||
} else {
|
||||
console.warn("Created but existing?", run, data);
|
||||
}
|
||||
} else if (name == 'done') {
|
||||
run.done = true;
|
||||
run.endTime = data.endTime;
|
||||
run.output = data.output;
|
||||
} else if (name == 'chat-message') {
|
||||
let chatListeners = listeners.chats[data.chatId] || [];
|
||||
for (let f of chatListeners) {
|
||||
f({runId: runId, ...data});
|
||||
}
|
||||
}
|
||||
|
||||
if (run) {
|
||||
run.events.push(event);
|
||||
for (let listener of listeners.runs) {
|
||||
if (listener.hasRunId(runId)) {
|
||||
listener.emitChange(run);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('Bad run event (non-existing run)', event);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the runs module
|
||||
async function init() {
|
||||
// Listen for connection establishment
|
||||
const res = await backend.sendAction("get-agent-runs");
|
||||
runs = res.runs || [];
|
||||
|
||||
// Listen for run events
|
||||
unsubscribeRunEvents = backend.onAction('run-event', updateRun);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
// Get all runs
|
||||
export function getRuns() {
|
||||
return runs;
|
||||
}
|
||||
|
||||
// Get a specific run by ID
|
||||
export function getRun(runId) {
|
||||
return runs.find(r => r.id === runId);
|
||||
}
|
||||
|
||||
|
||||
// Cancel a run
|
||||
export async function cancelRun(runId) {
|
||||
try {
|
||||
const res = await backend.sendAction("cancel-agent-run", { runId });
|
||||
console.log(`Cancelled run ${runId}`, res);
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.error(`Failed to cancel run ${runId}:`, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to runs changes
|
||||
export function addRunListener(callback) {
|
||||
const listener = new RunListener();
|
||||
listener.addCallback(callback);
|
||||
listeners.runs.push(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
// Subscribe to run chat message events
|
||||
export function onChatMessage(chatId, callback) {
|
||||
let chatListeners = listeners.chats[chatId];
|
||||
if (!chatListeners) {
|
||||
chatListeners = [];
|
||||
listeners.chats[chatId] = chatListeners;
|
||||
}
|
||||
chatListeners.push(callback);
|
||||
return () => {
|
||||
const index = chatListeners.indexOf(callback);
|
||||
if (index > -1) {
|
||||
chatListeners.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user