/** * Action creators for Meeting Intelligence feature. */ import { IStore } from '../app/types'; import { CLEAR_SEARCH, CLEAR_SELECTED_MEETING, EXPORT_FAILURE, EXPORT_REQUEST, EXPORT_SUCCESS, FETCH_MEETINGS_FAILURE, FETCH_MEETINGS_REQUEST, FETCH_MEETINGS_SUCCESS, FETCH_SPEAKER_STATS_SUCCESS, FETCH_SUMMARY_FAILURE, FETCH_SUMMARY_REQUEST, FETCH_SUMMARY_SUCCESS, FETCH_TRANSCRIPT_FAILURE, FETCH_TRANSCRIPT_REQUEST, FETCH_TRANSCRIPT_SUCCESS, GENERATE_SUMMARY_FAILURE, GENERATE_SUMMARY_REQUEST, GENERATE_SUMMARY_SUCCESS, SEARCH_FAILURE, SEARCH_REQUEST, SEARCH_SUCCESS, SELECT_MEETING, SET_ACTIVE_TAB, SET_EXPORT_FORMAT, SET_SEARCH_QUERY, TOGGLE_MEETING_INTELLIGENCE, UPDATE_MEETING_STATUS } from './actionTypes'; import { API_BASE_URL } from './constants'; import { IMeeting, IMeetingSummary, ISearchResult, ISpeakerStats, ITranscriptSegment } from './types'; /** * Toggle the meeting intelligence dashboard. * * @returns {Object} The action object. */ export function toggleMeetingIntelligence() { return { type: TOGGLE_MEETING_INTELLIGENCE }; } /** * Set the active tab. * * @param {string} tab - The tab to set active. * @returns {Object} The action object. */ export function setActiveTab(tab: 'recordings' | 'transcript' | 'summary' | 'search') { return { type: SET_ACTIVE_TAB, tab }; } /** * Select a meeting for detailed view. * * @param {string} meetingId - The meeting ID to select. * @returns {Function} Async thunk action. */ export function selectMeeting(meetingId: string) { return async (dispatch: IStore['dispatch']) => { dispatch({ type: SELECT_MEETING, meetingId }); // Fetch transcript and summary for the selected meeting dispatch(fetchTranscript(meetingId)); dispatch(fetchSummary(meetingId)); dispatch(fetchSpeakerStats(meetingId)); }; } /** * Clear the selected meeting. * * @returns {Object} The action object. */ export function clearSelectedMeeting() { return { type: CLEAR_SELECTED_MEETING }; } /** * Fetch the list of meetings. * * @returns {Function} Async thunk action. */ export function fetchMeetings() { return async (dispatch: IStore['dispatch']) => { dispatch({ type: FETCH_MEETINGS_REQUEST }); try { const response = await fetch(`${API_BASE_URL}/meetings`); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } const data = await response.json(); dispatch({ type: FETCH_MEETINGS_SUCCESS, meetings: data.meetings as IMeeting[] }); } catch (error) { dispatch({ type: FETCH_MEETINGS_FAILURE, error: error instanceof Error ? error.message : 'Failed to fetch meetings' }); } }; } /** * Fetch transcript for a meeting. * * @param {string} meetingId - The meeting ID. * @returns {Function} Async thunk action. */ export function fetchTranscript(meetingId: string) { return async (dispatch: IStore['dispatch']) => { dispatch({ type: FETCH_TRANSCRIPT_REQUEST }); try { const response = await fetch(`${API_BASE_URL}/meetings/${meetingId}/transcript`); if (!response.ok) { if (response.status === 404) { dispatch({ type: FETCH_TRANSCRIPT_SUCCESS, transcript: [] }); return; } throw new Error(`HTTP error: ${response.status}`); } const data = await response.json(); dispatch({ type: FETCH_TRANSCRIPT_SUCCESS, transcript: data.segments as ITranscriptSegment[] }); } catch (error) { dispatch({ type: FETCH_TRANSCRIPT_FAILURE, error: error instanceof Error ? error.message : 'Failed to fetch transcript' }); } }; } /** * Fetch summary for a meeting. * * @param {string} meetingId - The meeting ID. * @returns {Function} Async thunk action. */ export function fetchSummary(meetingId: string) { return async (dispatch: IStore['dispatch']) => { dispatch({ type: FETCH_SUMMARY_REQUEST }); try { const response = await fetch(`${API_BASE_URL}/meetings/${meetingId}/summary`); if (!response.ok) { if (response.status === 404) { dispatch({ type: FETCH_SUMMARY_SUCCESS, summary: null }); return; } throw new Error(`HTTP error: ${response.status}`); } const data = await response.json(); dispatch({ type: FETCH_SUMMARY_SUCCESS, summary: data as IMeetingSummary }); } catch (error) { dispatch({ type: FETCH_SUMMARY_FAILURE, error: error instanceof Error ? error.message : 'Failed to fetch summary' }); } }; } /** * Generate AI summary for a meeting. * * @param {string} meetingId - The meeting ID. * @returns {Function} Async thunk action. */ export function generateSummary(meetingId: string) { return async (dispatch: IStore['dispatch']) => { dispatch({ type: GENERATE_SUMMARY_REQUEST }); try { const response = await fetch(`${API_BASE_URL}/meetings/${meetingId}/summary`, { method: 'POST' }); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } const data = await response.json(); dispatch({ type: GENERATE_SUMMARY_SUCCESS, summary: data as IMeetingSummary }); } catch (error) { dispatch({ type: GENERATE_SUMMARY_FAILURE, error: error instanceof Error ? error.message : 'Failed to generate summary' }); } }; } /** * Fetch speaker statistics for a meeting. * * @param {string} meetingId - The meeting ID. * @returns {Function} Async thunk action. */ export function fetchSpeakerStats(meetingId: string) { return async (dispatch: IStore['dispatch']) => { try { const response = await fetch(`${API_BASE_URL}/meetings/${meetingId}/speakers`); if (!response.ok) { return; } const data = await response.json(); dispatch({ type: FETCH_SPEAKER_STATS_SUCCESS, speakerStats: data.speakers as ISpeakerStats[] }); } catch { // Silently ignore speaker stats errors } }; } /** * Search transcripts. * * @param {string} query - The search query. * @returns {Function} Async thunk action. */ export function searchTranscripts(query: string) { return async (dispatch: IStore['dispatch']) => { dispatch({ type: SET_SEARCH_QUERY, query }); if (!query.trim()) { dispatch({ type: CLEAR_SEARCH }); return; } dispatch({ type: SEARCH_REQUEST }); try { const response = await fetch(`${API_BASE_URL}/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, limit: 50 }) }); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } const data = await response.json(); dispatch({ type: SEARCH_SUCCESS, results: data.results as ISearchResult[] }); } catch (error) { dispatch({ type: SEARCH_FAILURE, error: error instanceof Error ? error.message : 'Search failed' }); } }; } /** * Clear search results. * * @returns {Object} The action object. */ export function clearSearch() { return { type: CLEAR_SEARCH }; } /** * Set export format. * * @param {string} format - The export format. * @returns {Object} The action object. */ export function setExportFormat(format: 'pdf' | 'markdown' | 'json') { return { type: SET_EXPORT_FORMAT, format }; } /** * Export a meeting. * * @param {string} meetingId - The meeting ID. * @param {string} format - The export format. * @returns {Function} Async thunk action. */ export function exportMeeting(meetingId: string, format: string) { return async (dispatch: IStore['dispatch']) => { dispatch({ type: EXPORT_REQUEST }); try { const response = await fetch( `${API_BASE_URL}/meetings/${meetingId}/export?format=${format}` ); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } // Get filename from content-disposition header const contentDisposition = response.headers.get('content-disposition'); let filename = `meeting-${meetingId}.${format === 'markdown' ? 'md' : format}`; if (contentDisposition) { const match = contentDisposition.match(/filename="(.+)"/); if (match) { filename = match[1]; } } // Download the file const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); dispatch({ type: EXPORT_SUCCESS }); } catch (error) { dispatch({ type: EXPORT_FAILURE, error: error instanceof Error ? error.message : 'Export failed' }); } }; } /** * Update meeting status (from polling). * * @param {string} meetingId - The meeting ID. * @param {string} status - The new status. * @returns {Object} The action object. */ export function updateMeetingStatus(meetingId: string, status: string) { return { type: UPDATE_MEETING_STATUS, meetingId, status }; }