feat: comprehensive dashboard finalization and AI panel enhancements#5
Merged
Conversation
This commit represents the completion of Phase 2, 3, and 4 of the Minutely dashboard overhaul. Key changes include: 1. Dashboard Frontend & Backend Integration: - Implemented useDashboardData hook for parallelized, authenticated data fetching. - Refactored StatsRow, RecentMeetingsGrid, and ActionItemsList to use real backend data with skeleton loaders and empty states. - Fixed GetDashboardStats to use real SQL calculations via Supabase RPC. - Integrated real SSE progress tracking in UploadZoneCard. - Enhanced Sidebar with mobile-friendly navigation drawer and user profile integration. 2. Meeting Detail & Navigation: - Refactored MeetingDetailPage for parallel API fetching (metadata, transcript, insights). - Added source badges (Live vs. Upload) to meeting cards and headers. - Implemented search/filter bar and "Load More" pagination in the All Meetings tab. 3. AI Panel (TranscriptPanel) Redesign: - Added auto-fetching of AI insights on tab activation. - Implemented AISkeletonLoader for AI insights. - Added a "Start Live Transcription" CTA to the Live tab's empty state. - Improved copy button discoverability and added tooltips to confidence indicators. - Unified checkbox and toggle switch styles in the settings view for a consistent UI. 4. Infrastructure & Fixes: - Added migration for get_dashboard_stats SQL function. - Optimized API endpoints and domain models to support rich meeting summaries. - Resolved UI inconsistencies in the Jitsi settings interop.
There was a problem hiding this comment.
Pull request overview
Replaces the default web welcome page with the Minutely dashboard experience and wires up new dashboard + meeting-detail UI to backend APIs, while expanding the transcription/AI pipeline (frontend live transcription, backend job processing, action items, and dashboard stats).
Changes:
- Swap WelcomePage to render the new
DashboardPageUI (stats, meetings, uploads, settings, meeting detail). - Enhance transcription UX: AudioWorklet-based Deepgram streaming, revamped TranscriptPanel + upload modal.
- Extend backend: meeting summaries + stats endpoints, action-item persistence/handlers, worker pool job claiming tweaks, and Supabase SQL additions.
Reviewed changes
Copilot reviewed 31 out of 33 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| react/features/welcome/components/WelcomePage.web.tsx | Replaces legacy Jitsi welcome page UI with DashboardPage. |
| react/features/transcription/middleware.ts | Updates live transcription pipeline (Deepgram WS, AudioWorklet capture, segment forwarding). |
| react/features/transcription/components/UploadRecordingModal.tsx | Reworks upload modal UI/state machine with progress/processing/success/error states. |
| react/features/transcription/components/TranscriptPanel.tsx | TranscriptPanel redesign (live/AI tabs, skeleton loader, copy affordances, expand/collapse). |
| react/features/dashboard/hooks/useDashboardData.ts | Adds consolidated dashboard data hook with authenticated parallel fetches. |
| react/features/dashboard/components/UploadZoneCard.tsx | Adds dashboard upload card with SSE progress tracking. |
| react/features/dashboard/components/StatsRow.tsx | Adds stats row with skeleton state and animated counters. |
| react/features/dashboard/components/Sidebar.tsx | Adds responsive sidebar + mobile drawer and profile fetch/sign-out. |
| react/features/dashboard/components/SettingsView.tsx | Adds settings wrapper integrating existing Jitsi settings tabs + unified checkbox/toggle styles. |
| react/features/dashboard/components/RecentMeetingsGrid.tsx | Adds meetings list/grid with search/filter + load-more pagination + empty/skeleton states. |
| react/features/dashboard/components/QuickStartCard.tsx | Adds quick-start meeting join card (room name or random). |
| react/features/dashboard/components/MeetingDetailPage.tsx | Adds meeting detail view fetching metadata/transcript/insights in parallel. |
| react/features/dashboard/components/DashboardPage.web.tsx | Adds dashboard page composition and tabbed navigation among dashboard sections. |
| react/features/dashboard/components/ActionItemsList.tsx | Adds action items list UI with optimistic status toggling. |
| minutely-api/supabase/migrations/000003_dashboard_stats.sql | Adds get_dashboard_stats RPC + user_action_items_view. |
| minutely-api/internal/workers/pool.go | Changes worker job selection/claiming flow (prioritized job types + optimistic claim). |
| minutely-api/internal/workers/file_processor.go | Refactors processor to support “claimed” jobs + expands MIME→ext mapping and ffmpeg preprocessing notes. |
| minutely-api/internal/transport/http/handlers.go | Adds meeting summaries and dashboard stats handlers. |
| minutely-api/internal/transport/http/action_item_handler.go | Adds action-item list/update-status HTTP handlers. |
| minutely-api/internal/core/services/meeting_service.go | Exposes meeting summaries + dashboard stats service methods. |
| minutely-api/internal/core/domain/meeting.go | Adds MeetingSummary model + repo/service method additions. |
| minutely-api/internal/core/domain/action_item.go | Introduces action item domain model + repository interface + dashboard stats struct. |
| minutely-api/internal/adapters/pythonai/ai_processor.go | Extends AI processor to persist extracted action items. |
| minutely-api/internal/adapters/postgres/supabase_repo.go | Adds meeting summaries query + dashboard stats RPC call. |
| minutely-api/internal/adapters/postgres/schema/02_action_items.sql | Adds action-items DDL + meeting summaries view (non-Supabase migration path). |
| minutely-api/internal/adapters/postgres/dummy_repo.go | Extends dummy meeting repo with stats + summaries methods. |
| minutely-api/internal/adapters/postgres/action_item_repo.go | Adds Supabase-backed action-item repository implementation. |
| minutely-api/internal/adapters/audio/ffmpeg.go | Adds ffmpeg preprocessing chain (highpass/loudnorm/silence removal) to WAV extraction. |
| minutely-api/cmd/api/main.go | Wires action item repo/handler routes + meeting summaries + stats route. |
| custom.d.ts | Adds module declarations for common image formats. |
| config.js | Enables transcription config section and auto-captioning behavior. |
| ai-service/modal_app.py | Improves chunking/map-reduce summarization for long transcripts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+331
to
336
| // ── ScriptProcessor fallback (legacy) ──────────────────────────── | ||
| console.warn('[Minutely Transcription] Using deprecated ScriptProcessorNode (legacy browser)'); | ||
| const scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1); | ||
| source.connect(scriptProcessor); | ||
| scriptProcessor.connect(audioContext.destination); | ||
|
|
||
| scriptProcessor.onaudioprocess = (e) => { |
Comment on lines
+51
to
+60
| item, err := h.repo.GetByID(r.Context(), id) | ||
| if err != nil { | ||
| jsonError(w, "action item not found", http.StatusNotFound) | ||
| return | ||
| } | ||
|
|
||
| item.Status = domain.ActionItemStatus(payload.Status) | ||
| if err := h.repo.Update(r.Context(), item); err != nil { | ||
| jsonError(w, "failed to update action item", http.StatusInternalServerError) | ||
| return |
Comment on lines
+71
to
+96
| // Also extract and persist action items | ||
| if actionItemsInterface, ok := result["action_items"]; ok { | ||
| if actionItemsList, ok := actionItemsInterface.([]interface{}); ok { | ||
| for _, itemInterface := range actionItemsList { | ||
| if itemMap, ok := itemInterface.(map[string]interface{}); ok { | ||
| task, _ := itemMap["task"].(string) | ||
| assignee, _ := itemMap["assignee"].(string) | ||
| // deadline, _ := itemMap["deadline"].(string) | ||
|
|
||
| if task != "" { | ||
| actionItem := &domain.ActionItem{ | ||
| MeetingID: event.MeetingID, | ||
| TranscriptID: &event.TranscriptID, | ||
| Task: task, | ||
| Status: domain.ActionItemStatusOpen, | ||
| } | ||
|
|
||
| if assignee != "" { | ||
| actionItem.AssigneeName = &assignee | ||
| } | ||
|
|
||
| if err := p.actionItemRepo.Create(ctx, actionItem); err != nil { | ||
| log.Printf("[AIProcessor] Warning: failed to save action item '%s': %v", task, err) | ||
| } | ||
| } | ||
| } |
Comment on lines
+13
to
+29
| ActionItemStatusOpen ActionItemStatus = "open" | ||
| ActionItemStatusDone ActionItemStatus = "done" | ||
| ActionItemStatusDismissed ActionItemStatus = "dismissed" | ||
| ) | ||
|
|
||
| type ActionItem struct { | ||
| ID uuid.UUID `json:"id"` | ||
| MeetingID uuid.UUID `json:"meeting_id"` | ||
| TranscriptID *uuid.UUID `json:"transcript_id,omitempty"` | ||
| Task string `json:"task"` | ||
| AssigneeName *string `json:"assignee_name,omitempty"` | ||
| AssigneeID *uuid.UUID `json:"assignee_id,omitempty"` | ||
| Status ActionItemStatus `json:"status"` | ||
| DueDate *time.Time `json:"due_date,omitempty"` | ||
| SegmentRef *uuid.UUID `json:"segment_ref,omitempty"` | ||
| CreatedAt time.Time `json:"created_at"` | ||
| UpdatedAt time.Time `json:"updated_at"` |
Comment on lines
+138
to
+141
| async function connectToDeepgram(store: any, token: string, meetingId: string) { | ||
| console.log(`[Deepgram] Connecting with token (len: ${token?.length}, prefix: ${token?.substring(0, 4)}...)`); | ||
| deepgramWs = new WebSocket(DEEPGRAM_WS_URL, [ 'token', token ]); | ||
|
|
Comment on lines
+25
to
+37
| try { | ||
| const { data: { session } } = await supabase.auth.getSession(); | ||
| if (!session) return; | ||
|
|
||
| await fetch(`/api/v1/action-items/${id}/status`, { | ||
| method: 'PATCH', | ||
| headers: { | ||
| 'Authorization': `Bearer ${session.access_token}`, | ||
| 'Content-Type': 'application/json' | ||
| }, | ||
| body: JSON.stringify({ status: newStatus }) | ||
| }); | ||
| } catch (err) { |
Comment on lines
+5
to
+15
| SELECT json_build_object( | ||
| 'total_meetings', COUNT(DISTINCT m.id), | ||
| 'hours_transcribed', COALESCE(SUM(t.duration_secs) / 3600.0, 0), | ||
| 'open_action_items', COUNT(DISTINCT ai.id) FILTER (WHERE ai.status = 'open'), | ||
| 'people_met', COUNT(DISTINCT ts.speaker_email) | ||
| ) | ||
| FROM public.meetings m | ||
| LEFT JOIN public.transcripts t ON t.meeting_id = m.id | ||
| LEFT JOIN public.action_items ai ON ai.meeting_id = m.id | ||
| LEFT JOIN public.transcript_segments ts ON ts.transcript_id = t.id | ||
| WHERE m.user_id = p_user_id; |
Comment on lines
+64
to
+68
| const { data: { session } } = await supabase.auth.getSession(); | ||
| const headers: Record<string, string> = {}; | ||
| if (session) { | ||
| headers['Authorization'] = `Bearer ${session.access_token}`; | ||
| } |
Comment on lines
64
to
+82
| try { | ||
| // Fake upload progress for UX | ||
| const progressInterval = setInterval(() => { | ||
| setProgress(prev => { | ||
| if (prev >= 90) { | ||
| clearInterval(progressInterval); | ||
| return 90; | ||
| } | ||
| return prev + 10; | ||
| }); | ||
| }, 500); | ||
|
|
||
| const res = await fetch(`/api/v1/meetings/${meetingId}/recordings/upload`, { | ||
| method: 'POST', | ||
| body: formData, | ||
| }); | ||
|
|
||
| clearInterval(progressInterval); | ||
|
|
Comment on lines
+15
to
+31
| // ───────────────────────────────────────────────────────────────────────────── | ||
| // Deepgram Nova-3 Live Transcription URL | ||
| // Improvements over previous config: | ||
| // • model=nova-3 — most accurate Deepgram model (~5% WER vs ~7% for Nova-2) | ||
| // • smart_format=true — adds punctuation, capitalization, and number formatting | ||
| // • filler_words=false — strips "um", "uh", "like" from output | ||
| // • utterance_end_ms — flushes a final transcript after 1500ms of silence | ||
| // • vad_events=true — fires SpeechStarted/UtteranceEnd events for better UX | ||
| // • diarize=true — speaker separation (per-participant streams already give identity) | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
| const DEEPGRAM_WS_URL = [ | ||
| 'wss://api.deepgram.com/v1/listen', | ||
| '?model=nova-2', | ||
| '&encoding=linear16', | ||
| '&sample_rate=16000', | ||
| '&smart_format=true', | ||
| ].join(''); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit represents the completion of Phase 2, 3, and 4 of the Minutely dashboard overhaul.
Key changes include:
Dashboard Frontend & Backend Integration:
Meeting Detail & Navigation:
AI Panel (TranscriptPanel) Redesign:
Infrastructure & Fixes: