Skip to content

Commit 0aef09b

Browse files
committed
feat: extract and display meeting participants
1 parent 78e17ff commit 0aef09b

File tree

2 files changed

+72
-39
lines changed

2 files changed

+72
-39
lines changed

src/renderer/features/notetaker/components/RecordingView.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,27 @@ export function RecordingView({ recordingItem }: RecordingViewProps) {
4747
const isPastRecording = recordingItem.type === "past";
4848
const hasSegments = segments.length > 0;
4949

50+
const participants =
51+
recordingItem.type === "past"
52+
? (recordingItem.recording.participants as string[] | undefined)
53+
: [
54+
...new Set(
55+
segments
56+
.map((s) => s.speaker)
57+
.filter((s): s is string => s !== null && s !== undefined),
58+
),
59+
];
60+
5061
return (
5162
<Box
5263
p="4"
5364
className="flex flex-1 flex-col gap-4 overflow-y-auto overflow-x-hidden"
5465
>
55-
{/* Meeting Header */}
5666
<Flex direction="column" gap="2">
5767
<Text size="4" weight="bold">
5868
{recordingItem.recording.meeting_title || "Untitled meeting"}
5969
</Text>
60-
<Flex gap="2">
70+
<Flex gap="2" wrap="wrap">
6171
<Badge color="gray" variant="soft">
6272
{recordingItem.recording.platform}
6373
</Badge>
@@ -66,10 +76,29 @@ export function RecordingView({ recordingItem }: RecordingViewProps) {
6676
recordingItem.recording.created_at || new Date(),
6777
).toLocaleString()}
6878
</Text>
79+
{participants && participants.length > 0 && (
80+
<>
81+
<Text size="2" color="gray">
82+
83+
</Text>
84+
<Text size="2" color="gray">
85+
{participants.length} participant
86+
{participants.length !== 1 ? "s" : ""}
87+
</Text>
88+
</>
89+
)}
6990
</Flex>
91+
{participants && participants.length > 0 && (
92+
<Flex gap="1" wrap="wrap">
93+
{participants.map((participant) => (
94+
<Badge key={participant} color="blue" variant="soft" size="1">
95+
{participant}
96+
</Badge>
97+
))}
98+
</Flex>
99+
)}
70100
</Flex>
71101

72-
{/* Summary - only for past recordings */}
73102
{isPastRecording && (
74103
<Flex direction="column" gap="2">
75104
<Text size="2" weight="bold">
@@ -85,7 +114,6 @@ export function RecordingView({ recordingItem }: RecordingViewProps) {
85114
</Flex>
86115
)}
87116

88-
{/* Action items - only for past recordings */}
89117
{isPastRecording && (
90118
<Flex direction="column" gap="2">
91119
<Text size="2" weight="bold">
@@ -101,7 +129,6 @@ export function RecordingView({ recordingItem }: RecordingViewProps) {
101129
</Flex>
102130
)}
103131

104-
{/* Notes - only for past recordings */}
105132
{isPastRecording && (
106133
<Flex direction="column" gap="2">
107134
<Text size="2" weight="bold">
@@ -117,7 +144,6 @@ export function RecordingView({ recordingItem }: RecordingViewProps) {
117144
</Flex>
118145
)}
119146

120-
{/* Transcript */}
121147
<Flex direction="column" gap="2">
122148
<Flex justify="between" align="center">
123149
<Text size="2" weight="bold">

src/renderer/services/recordingService.ts

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ interface UploadBatch {
1313

1414
const uploadBatches = new Map<string, UploadBatch>();
1515

16-
// Track initialization to prevent double-initialization
1716
let isInitialized = false;
1817

1918
/**
@@ -22,7 +21,6 @@ let isInitialized = false;
2221
* Call this once when the app starts (outside React component lifecycle)
2322
*/
2423
export function initializeRecordingService() {
25-
// Prevent double-initialization
2624
if (isInitialized) {
2725
console.warn("[RecordingService] Already initialized, skipping");
2826
return;
@@ -31,8 +29,6 @@ export function initializeRecordingService() {
3129
console.log("[RecordingService] Initializing...");
3230
isInitialized = true;
3331

34-
// Handle crash recovery after checking auth client is ready
35-
// This prevents the race condition where recovery tries to upload before client exists
3632
const authStore = useAuthStore.getState();
3733
if (authStore.client) {
3834
handleCrashRecovery();
@@ -42,27 +38,22 @@ export function initializeRecordingService() {
4238
);
4339
}
4440

45-
// Listen for recording started events
4641
window.electronAPI.onRecallRecordingStarted((recording) => {
4742
console.log("[RecordingService] Recording started:", recording);
4843

4944
const store = useActiveRecordingStore.getState();
50-
// Pass full DesktopRecording object to store
5145
store.addRecording(recording);
5246

53-
// Initialize upload batch tracker
5447
uploadBatches.set(recording.id, {
5548
recordingId: recording.id,
5649
timer: null,
5750
segmentCount: 0,
5851
});
5952
});
6053

61-
// Listen for transcript segments
6254
window.electronAPI.onRecallTranscriptSegment((data) => {
6355
const store = useActiveRecordingStore.getState();
6456

65-
// Add segment to store
6657
store.addSegment(data.posthog_recording_id, {
6758
timestamp: data.timestamp,
6859
speaker: data.speaker,
@@ -71,19 +62,16 @@ export function initializeRecordingService() {
7162
is_final: data.is_final,
7263
});
7364

74-
// Track batch for upload
7565
const batch = uploadBatches.get(data.posthog_recording_id);
7666
if (batch) {
7767
batch.segmentCount++;
7868

79-
// Start timer if not already running
8069
if (!batch.timer) {
8170
batch.timer = setTimeout(() => {
8271
uploadPendingSegments(data.posthog_recording_id);
8372
}, BATCH_TIMEOUT_MS);
8473
}
8574

86-
// Upload if batch size reached
8775
if (batch.segmentCount >= BATCH_SIZE) {
8876
if (batch.timer) {
8977
clearTimeout(batch.timer);
@@ -94,7 +82,6 @@ export function initializeRecordingService() {
9482
}
9583
});
9684

97-
// Listen for meeting ended events
9885
window.electronAPI.onRecallMeetingEnded((data) => {
9986
console.log("[RecordingService] Meeting ended:", data);
10087

@@ -104,14 +91,50 @@ export function initializeRecordingService() {
10491
}
10592
uploadBatches.delete(data.posthog_recording_id);
10693

107-
// Upload any pending segments, then update status to uploading
108-
uploadPendingSegments(data.posthog_recording_id).then(() => {
94+
uploadPendingSegments(data.posthog_recording_id).then(async () => {
10995
const store = useActiveRecordingStore.getState();
96+
97+
const recording = store.getRecording(data.posthog_recording_id);
98+
if (recording) {
99+
const participants = [
100+
...new Set(
101+
recording.segments
102+
.map((s) => s.speaker)
103+
.filter((s): s is string => s !== null && s !== undefined),
104+
),
105+
];
106+
107+
if (participants.length > 0) {
108+
console.log(
109+
`[RecordingService] Extracted ${participants.length} participants:`,
110+
participants,
111+
);
112+
113+
try {
114+
const authStore = useAuthStore.getState();
115+
const client = authStore.client;
116+
117+
if (client) {
118+
await client.updateDesktopRecording(data.posthog_recording_id, {
119+
participants,
120+
});
121+
console.log(
122+
`[RecordingService] Updated recording with participants`,
123+
);
124+
}
125+
} catch (error) {
126+
console.error(
127+
"[RecordingService] Failed to update participants:",
128+
error,
129+
);
130+
}
131+
}
132+
}
133+
110134
store.updateStatus(data.posthog_recording_id, "uploading");
111135
});
112136
});
113137

114-
// Listen for recording ready events
115138
window.electronAPI.onRecallRecordingReady((data) => {
116139
console.log("[RecordingService] Recording ready:", data);
117140

@@ -123,9 +146,6 @@ export function initializeRecordingService() {
123146
console.log("[RecordingService] Initialized successfully");
124147
}
125148

126-
/**
127-
* Upload pending transcript segments to backend
128-
*/
129149
async function uploadPendingSegments(recordingId: string): Promise<void> {
130150
const store = useActiveRecordingStore.getState();
131151
const recording = store.getRecording(recordingId);
@@ -153,7 +173,6 @@ async function uploadPendingSegments(recordingId: string): Promise<void> {
153173
throw new Error("PostHog client not initialized");
154174
}
155175

156-
// Upload segments to backend
157176
await client.updateDesktopRecordingTranscript(recordingId, {
158177
segments: pendingSegments.map((seg) => ({
159178
timestamp_ms: seg.timestamp,
@@ -164,7 +183,6 @@ async function uploadPendingSegments(recordingId: string): Promise<void> {
164183
})),
165184
});
166185

167-
// Update last uploaded index
168186
const newIndex =
169187
recording.lastUploadedSegmentIndex + pendingSegments.length;
170188
store.updateLastUploadedIndex(recordingId, newIndex);
@@ -173,7 +191,6 @@ async function uploadPendingSegments(recordingId: string): Promise<void> {
173191
`[RecordingService] Successfully uploaded ${pendingSegments.length} segments`,
174192
);
175193

176-
// Reset batch tracker
177194
const batch = uploadBatches.get(recordingId);
178195
if (batch) {
179196
batch.segmentCount = 0;
@@ -196,11 +213,9 @@ async function uploadPendingSegments(recordingId: string): Promise<void> {
196213

197214
/**
198215
* Handle crash recovery - upload any pending segments and clear from IDB
199-
* Called on app startup. Keeps things simple: save what we have and move on.
200216
*
201217
* Tradeoff: Might lose last ~10 segments if upload fails during crash recovery.
202-
* Acceptable because: (1) Backend already has 90%+ from batched uploads during meeting
203-
* (2) Crashes are rare, (3) Crash + upload failure is even rarer
218+
* Acceptable because backend already has 90%+ from batched uploads during meeting.
204219
*/
205220
function handleCrashRecovery() {
206221
const store = useActiveRecordingStore.getState();
@@ -220,36 +235,28 @@ function handleCrashRecovery() {
220235
`[RecordingService] Uploading pending segments for ${recording.id} (best effort)`,
221236
);
222237

223-
// Upload pending segments in background (best effort, don't block startup)
224238
uploadPendingSegments(recording.id).catch((error) => {
225239
console.error(
226240
`[RecordingService] Failed to upload segments during recovery (acceptable):`,
227241
error,
228242
);
229243
});
230244

231-
// Clear from IDB immediately - recording is already in backend
232245
store.clearRecording(recording.id);
233246
console.log(`[RecordingService] Cleared ${recording.id} from IDB`);
234247
}
235248
}
236249

237-
/**
238-
* Clean up the recording service
239-
* Call this when the app shuts down
240-
*/
241250
export function shutdownRecordingService() {
242251
console.log("[RecordingService] Shutting down...");
243252

244-
// Clear all upload batch timers
245253
for (const batch of uploadBatches.values()) {
246254
if (batch.timer) {
247255
clearTimeout(batch.timer);
248256
}
249257
}
250258
uploadBatches.clear();
251259

252-
// Reset initialization flag to allow re-initialization after logout/login
253260
isInitialized = false;
254261

255262
console.log("[RecordingService] Shutdown complete");

0 commit comments

Comments
 (0)