Keep active task transcripts in memory for instant task switching
This commit is contained in:
Vendored
+93
-32
@@ -94,6 +94,12 @@ export async function loadProjects() {
|
||||
export async function loadTasks(projectId) {
|
||||
const tasks = await get("/api/projects/{projectId}/tasks", { path: { projectId } });
|
||||
state.tasks.set(projectId, tasks);
|
||||
for (const t of tasks) {
|
||||
if (t.status === "active") {
|
||||
taskFeed(t.id);
|
||||
}
|
||||
}
|
||||
dropClosedFeeds();
|
||||
await Promise.all(tasks.filter((t) => t.status === "active").map((t) => loadSessions(t.id)));
|
||||
notify("tasks");
|
||||
}
|
||||
@@ -148,31 +154,41 @@ export function focusTab(tab) {
|
||||
class Feed {
|
||||
transcriptTopic;
|
||||
streamTopic;
|
||||
store;
|
||||
readyTopic;
|
||||
shown;
|
||||
events = [];
|
||||
ready = false;
|
||||
last = 0;
|
||||
buffer = [];
|
||||
timer = 0;
|
||||
constructor(transcriptTopic, streamTopic, store) {
|
||||
constructor(transcriptTopic, streamTopic, readyTopic, shown) {
|
||||
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);
|
||||
this.readyTopic = readyTopic;
|
||||
this.shown = shown;
|
||||
}
|
||||
push = (ev) => {
|
||||
if (ev.seq === 0 && (!this.ready || !this.shown())) {
|
||||
return;
|
||||
}
|
||||
this.buffer.push(ev);
|
||||
if (!this.timer) {
|
||||
if (this.ready && !this.timer) {
|
||||
this.timer = setTimeout(() => this.flush(), 0);
|
||||
}
|
||||
};
|
||||
load = (history) => {
|
||||
if (this.ready) {
|
||||
return;
|
||||
}
|
||||
this.ready = true;
|
||||
this.buffer = [...history, ...this.buffer];
|
||||
this.flush();
|
||||
if (this.shown()) {
|
||||
notify(this.readyTopic);
|
||||
}
|
||||
};
|
||||
flush() {
|
||||
clearTimeout(this.timer);
|
||||
const events = this.buffer;
|
||||
this.buffer = [];
|
||||
this.timer = 0;
|
||||
@@ -184,27 +200,75 @@ class Feed {
|
||||
}
|
||||
this.append(stored);
|
||||
stored = [];
|
||||
notify(this.streamTopic, ev);
|
||||
if (this.shown()) {
|
||||
notify(this.streamTopic, ev);
|
||||
}
|
||||
}
|
||||
this.append(stored);
|
||||
}
|
||||
append(events) {
|
||||
const fresh = events.filter((ev) => ev.seq > this.last);
|
||||
const fresh = [];
|
||||
for (const ev of events) {
|
||||
if (ev.seq > this.last) {
|
||||
fresh.push(ev);
|
||||
this.last = ev.seq;
|
||||
}
|
||||
}
|
||||
if (fresh.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.events.push(...fresh);
|
||||
this.last = fresh.at(-1).seq;
|
||||
notify(this.transcriptTopic, fresh);
|
||||
if (this.shown()) {
|
||||
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 historyPage = 1000;
|
||||
const taskFeeds = new Map();
|
||||
const leadFeed = new Feed("lead.transcript", "lead.stream", "lead.ready", () => true);
|
||||
state.leadTranscript = leadFeed.events;
|
||||
async function loadHistory(taskId, feed) {
|
||||
const events = [];
|
||||
for (;;) {
|
||||
const page = await get("/api/tasks/{taskId}/events", { path: { taskId }, query: { after: events.at(-1)?.seq ?? 0, limit: historyPage } });
|
||||
events.push(...page);
|
||||
if (page.length < historyPage) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
feed.load(events);
|
||||
}
|
||||
const historyLoads = new Set();
|
||||
function taskFeed(taskId) {
|
||||
let feed = taskFeeds.get(taskId);
|
||||
if (!feed) {
|
||||
feed = new Feed("transcript", "stream", "ready", () => state.selectedTaskId === taskId);
|
||||
taskFeeds.set(taskId, feed);
|
||||
}
|
||||
if (feed.ready || historyLoads.has(feed)) {
|
||||
return feed;
|
||||
}
|
||||
const loading = feed;
|
||||
historyLoads.add(loading);
|
||||
loadHistory(taskId, loading)
|
||||
.catch((err) => console.error("load task history", taskId, err))
|
||||
.finally(() => historyLoads.delete(loading));
|
||||
return feed;
|
||||
}
|
||||
function dropClosedFeeds() {
|
||||
for (const taskId of taskFeeds.keys()) {
|
||||
const task = findTask(taskId);
|
||||
if (taskId !== state.selectedTaskId && task && task.status !== "active") {
|
||||
taskFeeds.delete(taskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function transcriptLoading() {
|
||||
return state.selectedTaskId !== undefined && taskFeeds.get(state.selectedTaskId)?.ready !== true;
|
||||
}
|
||||
export function leadLoading() {
|
||||
return !leadFeed.ready;
|
||||
}
|
||||
const visitsKey = "byre.visits";
|
||||
export function lastVisits() {
|
||||
return JSON.parse(localStorage.getItem(visitsKey) ?? "{}");
|
||||
@@ -218,23 +282,20 @@ function markVisited(taskId) {
|
||||
export function selectTask(taskId) {
|
||||
markVisited(state.selectedTaskId);
|
||||
markVisited(taskId);
|
||||
closeTaskStream?.();
|
||||
closeTaskStream = undefined;
|
||||
taskFeed.reset();
|
||||
state.selectedTaskId = taskId;
|
||||
state.transcript = taskId ? taskFeed(taskId).events : [];
|
||||
state.pinned = undefined;
|
||||
state.reviewed = undefined;
|
||||
dropClosedFeeds();
|
||||
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);
|
||||
subscribe(0, { lead: true }, leadFeed.push, () => undefined, () => leadFeed.load([]));
|
||||
}
|
||||
export function handleEvent(ev) {
|
||||
if (ev.task_id) {
|
||||
taskFeeds.get(ev.task_id)?.push(ev);
|
||||
}
|
||||
if (ev.seq === 0) {
|
||||
notify("event", ev);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user