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.
This commit is contained in:
iamdoubz
2026-07-02 07:45:03 -05:00
parent 075eec6a95
commit 6dc68ac92c
+40 -1
View File
@@ -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<MeetingListItem[]>([]);
@@ -9,6 +17,13 @@ class MeetingsStore {
selected = $state<Meeting | null>(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<MeetingId | null>(null);
summaryStream = $state("");
summaryError = $state<string | null>(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) {