From 6dc68ac92c8efe81c86d0827e23ae6b9ed978ef9 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Thu, 2 Jul 2026 07:45:03 -0500 Subject: [PATCH] feat(ui): summary generation + action-item confirmation in the meetings store (T7.1) Streams tokens for whichever meeting is generating (gated by summarizingId so a stale stream can't clobber a summary if the user switches selection mid-generation), and refreshes the selected meeting once llm://done or confirm_action_items lands. --- src/lib/stores/meetings.svelte.ts | 41 ++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/lib/stores/meetings.svelte.ts b/src/lib/stores/meetings.svelte.ts index aa6c266..37d1a54 100644 --- a/src/lib/stores/meetings.svelte.ts +++ b/src/lib/stores/meetings.svelte.ts @@ -1,7 +1,15 @@ // Meetings list + selected-meeting detail (Phase 2, FR-STORE-5). // Svelte 5 runes store, same shape as recording.svelte.ts. -import { api, events, type Meeting, type MeetingId, type MeetingListItem } from "../api"; +import { + api, + errorMessage, + events, + type ActionItem, + type Meeting, + type MeetingId, + type MeetingListItem, +} from "../api"; class MeetingsStore { list = $state([]); @@ -9,6 +17,13 @@ class MeetingsStore { selected = $state(null); loading = $state(false); + // Summary generation (T5.4/T5.5, FR-LLM-2/4) — streamed tokens for whichever + // meeting is generating, so a stale stream can't overwrite a different + // meeting's summary if the user switches selection mid-generation. + summarizingId = $state(null); + summaryStream = $state(""); + summaryError = $state(null); + async init() { await this.load(); // A meeting finishing (live stop, or a recovered meeting resuming @@ -17,6 +32,30 @@ class MeetingsStore { await this.load(); if (this.selectedId === meetingId) await this.select(meetingId); }); + await events.onLlmToken(({ meetingId, text }) => { + if (this.summarizingId === meetingId) this.summaryStream += text; + }); + await events.onLlmDone(async ({ meetingId }) => { + if (this.summarizingId === meetingId) this.summarizingId = null; + if (this.selectedId === meetingId) await this.select(meetingId); + }); + } + + async generateSummary(id: MeetingId, templateId?: string) { + this.summarizingId = id; + this.summaryStream = ""; + this.summaryError = null; + try { + await api.generateSummary(id, templateId); + } catch (e) { + this.summaryError = errorMessage(e); + this.summarizingId = null; + } + } + + async confirmActionItems(id: MeetingId, items: ActionItem[]) { + await api.confirmActionItems(id, items); + if (this.selectedId === id) await this.select(id); } async load(query?: string) {