- Updated the client submodule to the latest commit for improved features and bug fixes. - Added detailed documentation for the post-processing pipeline, including implementation checklists, testing strategies, and identified gaps in the current workflow. - Introduced new files for tracking the implementation of automatic post-processing features, enhancing user experience and system efficiency.
9.1 KiB
NoteFlow Client Processing Pipeline After Recording Stops
Executive Summary
The client-side processing pipeline is NOT fully automated. After recording stops (stopRecording), the system:
- Stops the audio stream and saves audio to disk ✓
- Calls
stopMeeting()on the gRPC server ✓ - THEN NAVIGATES AWAY to the MeetingDetail page
- BUT does NOT automatically trigger summarization or entity extraction
Call Flow After Recording Stops
Recording.tsx: stopRecording() [Lines 313-346]
const stopRecording = async () => {
setRecordingState('stopping');
try {
streamRef.current?.close(); // Closes TauriTranscriptionStream
const stoppedMeeting = await api.stopMeeting(meeting.id); // Calls server
setMeeting(stoppedMeeting);
// IMMEDIATELY NAVIGATE AWAY - no processing triggered
navigate(projectId ? `/projects/${projectId}/meetings/${meeting.id}` : '/projects');
}
}
What stopMeeting() Does
Tauri Adapter (tauri-adapter.ts, line 483-489):
async stopMeeting(meetingId: string): Promise<Meeting> {
const meeting = await invoke<Meeting>(TauriCommands.STOP_MEETING, {
meeting_id: meetingId,
});
meetingCache.cacheMeeting(meeting);
return meeting;
}
Rust Command (commands/meeting.rs, lines 80-83):
pub async fn stop_meeting(state: State<'_, Arc<AppState>>, meeting_id: String) -> Result<Meeting> {
state.grpc_client.stop_meeting(&meeting_id).await
}
This is a pure state change - it marks the meeting as completed on the server, but does NOT:
- Generate a summary
- Extract entities
- Refine speakers (diarization)
What Happens After Navigation to MeetingDetail
MeetingDetail.tsx: Initial Load [Lines 79-98]
useEffect(() => {
const loadMeeting = async () => {
const data = await getAPI().getMeeting({
meeting_id: id,
include_segments: true,
include_summary: true, // Fetches existing summary if present
});
setMeeting(data);
};
}, [id]);
This only fetches the meeting state - it does NOT trigger any processing.
Manual Triggering Points
1. Summarization (MeetingDetail.tsx, Lines 199-210)
Trigger: User clicks "Summary" button or calls handleGenerateSummary()
const handleGenerateSummary = async () => {
if (!meeting) return;
const summary = await getAPI().generateSummary(meeting.id, true);
setMeeting({ ...meeting, summary });
};
Tauri Adapter (Lines 507-526):
async generateSummary(meetingId: string, forceRegenerate?: boolean): Promise<Summary> {
// Get user preferences for tone/format/verbosity
const prefs = await invoke<UserPreferences>(TauriCommands.GET_PREFERENCES);
const options = prefs?.ai_template ? { tone, format, verbosity } : undefined;
return invoke<Summary>(TauriCommands.GENERATE_SUMMARY, {
meeting_id: meetingId,
force_regenerate: forceRegenerate ?? false,
options,
});
}
Rust Command (commands/summary.rs, Lines 89-134):
- Emits progress events ("started" -> "running" -> "completed"/"failed")
- Polls with exponential backoff (1s interval, max 90% progress)
- Returns Summary on completion
2. Entity Extraction (MeetingDetail.tsx, Lines 357-366)
Trigger: User clicks "Entities" button
const extractEntities = (forceRefresh = false) => {
getAPI().extractEntities(meetingId, forceRefresh)
.then(response => setEntitiesFromExtraction(response.entities))
.catch(error => toast error)
};
Tauri Adapter (Lines 687-695):
async extractEntities(meetingId: string, forceRefresh?: boolean): Promise<ExtractEntitiesResponse> {
return invoke<ExtractEntitiesResponse>(TauriCommands.EXTRACT_ENTITIES, {
meeting_id: meetingId,
force_refresh: forceRefresh ?? false,
});
}
Rust Command (commands/entities.rs, Lines 15-24):
- Direct pass-through to gRPC client
- Returns result immediately (no progress polling)
- No event emission
3. Diarization (use-diarization.ts Hook)
Note: The hook provides a start() method for refinement, but it is NOT automatically called when recording stops.
Hook capabilities:
start(meetingId, numSpeakers)- Request diarizationpoll()- Poll job status with exponential backoffrecover()- Recover active jobs after reconnection (Sprint GAP-004)
Not found: Auto-trigger on meeting completion
Entity Extraction Auto-Extraction Feature
Hook feature (use-entity-extraction.ts, Lines 116-121):
useEffect(() => {
if (autoExtract && meetingId && meetingState === 'completed') {
extract(false); // Auto-extract on mount when meeting is completed
}
}, [autoExtract, meetingId, meetingState, extract]);
Problem: This feature exists in the hook, but is NOT USED anywhere in the codebase.
In MeetingDetail.tsx (Lines 72-76):
const { extract: extractEntities } = useEntityExtraction({
meetingId: id,
meetingTitle: meeting?.title,
meetingState: meeting?.state,
// autoExtract is NOT set, defaults to false
});
Backend Processing Pipeline (For Comparison)
The backend automatically triggers some processing in the StopMeeting RPC (server.py gRPC mixin):
-
Always triggered:
- Mark meeting as
completed - Flush any pending segments
- Close streaming connections
- Mark meeting as
-
Conditionally triggered (needs investigation):
- Diarization job submission?
- Entity extraction?
- Summary generation?
Unknown: Whether the backend auto-starts these or waits for explicit client requests.
Processing Pipeline Gaps
GAP-001: No Auto-Summarization After Recording Stops
Current behavior: User must manually click "Summary" button on MeetingDetail page
Expected behavior (?): Auto-generate summary when meeting enters completed state
Impact: User might forget to generate summary; requires multiple interactions
GAP-002: No Auto-Entity Extraction Despite Hook Support
Current behavior: Entity extraction requires manual button click Expected behavior (?): Could auto-extract when meeting is completed (hook supports it) Impact: Entity extraction is "hidden" feature; users may not know it exists
GAP-003: No Auto-Diarization Refinement
Current behavior: User must manually initiate diarization via Tauri UI (not found in React UI) Expected behavior (?): Could auto-refine speakers after recording Impact: Speaker identification may not be optimal without refinement
GAP-004: No Polling for Processing Status
Current behavior: Client fires requests but does NOT poll for completion Expected behavior (?): Show progress while processing happens server-side Impact: User sees no feedback while server processes transcript
Server-Side API Endpoints (From tauri-adapter.ts)
These are available but not automatically triggered:
| RPC | Purpose | Auto-triggered? |
|---|---|---|
startTranscription() |
Begin audio streaming | During recording start |
stopMeeting() |
Mark meeting completed | On stop button |
generateSummary() |
Create AI summary | Manual only |
extractEntities() |
NER extraction | Manual only |
refineSpeakers() |
Start diarization job | Manual only |
getDiarizationJobStatus() |
Poll job progress | Manual poll |
Recommendation: Automatic Processing Pipeline
To close these gaps, consider:
-
On Recording Stop:
- After
stopMeeting()succeeds, auto-trigger:generateSummary()with user preferencesextractEntities()if NER is enabledrefineSpeakers()if diarization is enabled
- After
-
Polling for Progress:
- Show progress indicators while server processes
- Use same polling strategy as diarization hook (exponential backoff)
-
UI Flow:
- Show "Processing..." state instead of immediately navigating
- Display progress: "Generating summary... 45%"
- Auto-navigate when all processing completes
-
Error Handling:
- Toast notification if processing fails
- Allow manual retry from MeetingDetail page
Key Files for Implementation
| File | Purpose | Lines |
|---|---|---|
client/src/pages/Recording.tsx |
Recording page with stop button | 313-346 |
client/src/api/tauri-adapter.ts |
API adapter with processing methods | 507-695 |
client/src/pages/MeetingDetail.tsx |
Detail page with manual triggers | 199-210, 357-366 |
client/src/hooks/use-entity-extraction.ts |
Entity extraction hook with auto-extract | 116-121 |
client/src/hooks/use-diarization.ts |
Diarization with polling (reusable pattern) | 124-253 |
client/src-tauri/src/commands/summary.rs |
Progress event emission | 14-40, 96-134 |
client/src-tauri/src/commands/recording/mod.rs |
Recording lifecycle | 313-346 |
Current State Summary
What works:
- Recording and transcription streaming ✓
- Manual summarization ✓
- Manual entity extraction ✓
- Manual diarization refinement ✓
What's missing:
- Auto-trigger processing pipeline ✗
- Progress feedback during processing ✗
- Coordinated processing after recording stops ✗
- Automatic entity extraction despite hook support ✗