import { get, put, subscribe } from "./api.js"; export const tabs = ["transcript", "changes", "files", "inbox", "project"]; export const state = { me: undefined, projects: [], tasks: new Map(), sessions: new Map(), inbox: [], selectedTaskId: undefined, transcript: [], pinned: undefined, connected: false, focus: { tabs: ["transcript"], tab: "transcript" }, leadSessions: [], leadTranscript: [], }; const listeners = new Map(); export function on(topic, fn) { let set = listeners.get(topic); if (!set) { set = new Set(); listeners.set(topic, set); } set.add(fn); return () => set.delete(fn); } export function onEach(topics, fn) { const offs = topics.map((t) => on(t, fn)); return () => { for (const off of offs) { off(); } }; } export function activeTasks() { return state.projects.flatMap((project) => (state.tasks.get(project.id) ?? []).filter((t) => t.status === "active").map((task) => ({ project, task }))); } export function notify(topic, detail) { for (const fn of listeners.get(topic) ?? []) { fn(detail); } } export function findTask(id) { for (const tasks of state.tasks.values()) { const task = tasks.find((t) => t.id === id); if (task) { return task; } } return undefined; } export function currentSession(taskId) { return state.sessions.get(taskId)?.findLast((s) => s.state !== "completed") ?? state.sessions.get(taskId)?.at(-1); } export function currentLead() { return state.leadSessions.findLast((s) => s.state !== "completed") ?? state.leadSessions.at(-1); } export function snapshots() { return state.transcript.filter((e) => e.kind === "snapshot.created").map((e) => e.payload); } export function pinSnapshot(n) { state.pinned = n; notify("pin"); } export async function loadMe() { state.me = await get("/api/me"); notify("me"); } export async function loadProjects() { state.projects = await get("/api/projects"); await Promise.all(state.projects.map((p) => loadTasks(p.id))); notify("projects"); } export async function loadTasks(projectId) { const tasks = await get("/api/projects/{projectId}/tasks", { path: { projectId } }); state.tasks.set(projectId, tasks); await Promise.all(tasks.filter((t) => t.status === "active").map((t) => loadSessions(t.id))); notify("tasks"); } export async function loadSessions(taskId) { state.sessions.set(taskId, await get("/api/tasks/{taskId}/sessions", { path: { taskId } })); notify("sessions", taskId); } export async function loadInbox() { state.inbox = await get("/api/inbox"); notify("inbox"); } export async function loadLeadSessions() { state.leadSessions = await get("/api/me/lead/sessions"); notify("lead.sessions"); } export async function loadFocus() { applyFocus(await get("/api/me/focus")); } export function applyFocus(f) { state.focus = f; const hash = f.task_id ? `#/task/${f.task_id}` : f.project_id ? `#/project/${f.project_id}` : "#/"; if (location.hash !== hash && !(hash === "#/" && location.hash === "")) { location.hash = hash; } notify("focus"); } export function focusedProject() { return state.projects.find((p) => p.id === state.focus.project_id); } export async function setFocus(change) { const taskId = change.task_id === undefined ? state.focus.task_id : (change.task_id ?? undefined); const projectId = taskId ? findTask(taskId)?.project_id : change.project_id === undefined ? state.focus.project_id : (change.project_id ?? undefined); const next = { tabs: state.focus.tabs, tab: change.tab ?? state.focus.tab }; if (taskId) { next.task_id = taskId; } if (projectId) { next.project_id = projectId; } applyFocus(next); applyFocus(await put("/api/me/focus", { body: next })); } export function focusTask(taskId, tab = "transcript") { setFocus({ task_id: taskId, tab }).catch((err) => console.error("set focus", err)); } export function focusProject(projectId) { setFocus({ task_id: null, project_id: projectId, tab: "project" }).catch((err) => console.error("set focus", err)); } export function focusTab(tab) { setFocus({ tab }).catch((err) => console.error("set focus", err)); } class Feed { transcriptTopic; streamTopic; store; events = []; last = 0; buffer = []; timer = 0; constructor(transcriptTopic, streamTopic, store) { this.transcriptTopic = transcriptTopic; this.streamTopic = streamTopic; this.store = store; } reset() { clearTimeout(this.timer); this.timer = 0; this.buffer = []; this.events = []; this.last = 0; this.store(this.events); } push = (ev) => { this.buffer.push(ev); if (!this.timer) { this.timer = setTimeout(() => this.flush(), 0); } }; flush() { const events = this.buffer; this.buffer = []; this.timer = 0; let stored = []; for (const ev of events) { if (ev.seq !== 0) { stored.push(ev); continue; } this.append(stored); stored = []; notify(this.streamTopic, ev); } this.append(stored); } append(events) { const fresh = events.filter((ev) => ev.seq > this.last); if (fresh.length === 0) { return; } this.events.push(...fresh); this.last = fresh.at(-1).seq; notify(this.transcriptTopic, fresh); } } const taskFeed = new Feed("transcript", "stream", (events) => { state.transcript = events; }); const leadFeed = new Feed("lead.transcript", "lead.stream", (events) => { state.leadTranscript = events; }); let closeTaskStream; const visitsKey = "byre.visits"; export function lastVisits() { return JSON.parse(localStorage.getItem(visitsKey) ?? "{}"); } function markVisited(taskId) { if (!taskId) { return; } localStorage.setItem(visitsKey, JSON.stringify({ ...lastVisits(), [taskId]: Date.now() })); } export function selectTask(taskId) { markVisited(state.selectedTaskId); markVisited(taskId); closeTaskStream?.(); closeTaskStream = undefined; taskFeed.reset(); state.selectedTaskId = taskId; state.pinned = undefined; notify("selection"); if (!taskId) { return; } closeTaskStream = subscribe(0, { task_id: taskId }, taskFeed.push, () => undefined); } export function startLeadStream() { leadFeed.reset(); subscribe(0, { lead: true }, leadFeed.push, () => undefined); } export function handleEvent(ev) { if (ev.seq === 0) { notify("event", ev); return; } switch (ev.kind) { case "user.updated": if (ev.payload["login"] === state.me?.login) { void loadMe(); } break; case "project.created": void loadProjects(); break; case "task.created": case "task.merge_title": case "task.merged": case "task.discarded": if (ev.project_id) { void loadTasks(ev.project_id); } break; case "session.created": case "session.state": case "session.model": if (ev.owner) { void loadLeadSessions(); } else if (ev.task_id) { void loadSessions(ev.task_id); } break; case "inbox.created": case "inbox.updated": void loadInbox(); break; case "focus.updated": if (ev.actor !== state.me?.login) { applyFocus(ev.payload); } break; } notify("event", ev); } //# sourceMappingURL=state.js.map