/**
* This file will automatically be loaded by webpack and run in the "renderer" context.
* To learn more about the differences between the "main" and the "renderer" context in
* Electron, visit:
*
* https://electronjs.org/docs/tutorial/process-model
*/
import './index.css';
// Create empty meetings data structure to be filled from the file
const meetingsData = {
upcomingMeetings: [],
pastMeetings: []
};
// Create empty arrays that will be filled from file
const upcomingMeetings = [];
const pastMeetings = [];
// Group past meetings by date
let pastMeetingsByDate = {};
// Global recording state variables
window.isRecording = false;
window.currentRecordingId = null;
// Function to check if there's an active recording for the current note
async function checkActiveRecordingState() {
if (!currentEditingMeetingId) return;
try {
console.log('Checking active recording state for note:', currentEditingMeetingId);
const result = await window.electronAPI.getActiveRecordingId(currentEditingMeetingId);
if (result.success && result.data) {
console.log('Found active recording for current note:', result.data);
updateRecordingButtonUI(true, result.data.recordingId);
} else {
console.log('No active recording found for note');
updateRecordingButtonUI(false, null);
}
} catch (error) {
console.error('Error checking recording state:', error);
}
}
// Function to update the recording button UI
function updateRecordingButtonUI(isActive, recordingId) {
const recordButton = document.getElementById('recordButton');
if (!recordButton) return;
// Get the elements inside the button
const recordIcon = recordButton.querySelector('.record-icon');
const stopIcon = recordButton.querySelector('.stop-icon');
if (isActive) {
// Recording is active
console.log('Updating UI for active recording:', recordingId);
window.isRecording = true;
window.currentRecordingId = recordingId;
// Update button UI
recordButton.classList.add('recording');
recordIcon.style.display = 'none';
stopIcon.style.display = 'block';
} else {
// No active recording
console.log('Updating UI for inactive recording');
window.isRecording = false;
window.currentRecordingId = null;
// Update button UI
recordButton.classList.remove('recording');
recordIcon.style.display = 'block';
stopIcon.style.display = 'none';
}
}
// Function to format date for section headers
function formatDateHeader(dateString) {
const date = new Date(dateString);
const now = new Date();
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
// Check if date is today, yesterday, or earlier
if (date.toDateString() === now.toDateString()) {
return 'Today';
} else if (date.toDateString() === yesterday.toDateString()) {
return 'Yesterday';
} else {
// Format as "Fri, Apr 25" or similar
const options = { weekday: 'short', month: 'short', day: 'numeric' };
return date.toLocaleDateString('en-US', options);
}
}
// We'll initialize pastMeetings and pastMeetingsByDate when we load data from file
// Save meetings data back to file
async function saveMeetingsData() {
// Save to localStorage as a backup
localStorage.setItem('meetingsData', JSON.stringify(meetingsData));
// Save to the actual file using IPC
try {
console.log('Saving meetings data to file...');
const result = await window.electronAPI.saveMeetingsData(meetingsData);
if (result.success) {
console.log('Meetings data saved successfully to file');
} else {
console.error('Failed to save meetings data to file:', result.error);
}
} catch (error) {
console.error('Error saving meetings data to file:', error);
}
}
// Keep track of which meeting is being edited
let currentEditingMeetingId = null;
// Function to save the current note
async function saveCurrentNote() {
const editorElement = document.getElementById('simple-editor');
const noteTitleElement = document.getElementById('noteTitle');
// Early exit if elements aren't available
if (!editorElement || !noteTitleElement) {
console.warn('Cannot save note: Editor elements not found');
return;
}
// Early exit if no current meeting ID
if (!currentEditingMeetingId) {
console.warn('Cannot save note: No active meeting ID');
return;
}
// Get title text, defaulting to "New Note" if empty
const noteTitle = noteTitleElement.textContent.trim() || 'New Note';
// Set title back to element in case it was empty
if (!noteTitleElement.textContent.trim()) {
noteTitleElement.textContent = noteTitle;
}
// Find which meeting is currently active by ID
const activeMeeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === currentEditingMeetingId);
if (activeMeeting) {
console.log(`Saving note with ID: ${currentEditingMeetingId}, Title: ${noteTitle}`);
// Get the current content from the editor
const content = editorElement.value;
console.log(`Note content length: ${content.length} characters`);
// Update the title and content in the meeting object
activeMeeting.title = noteTitle;
activeMeeting.content = content;
// Update the data arrays directly to make sure they stay in sync
const pastIndex = meetingsData.pastMeetings.findIndex(m => m.id === currentEditingMeetingId);
if (pastIndex !== -1) {
meetingsData.pastMeetings[pastIndex].title = noteTitle;
meetingsData.pastMeetings[pastIndex].content = content;
console.log('Updated meeting in pastMeetings array');
}
const upcomingIndex = meetingsData.upcomingMeetings.findIndex(m => m.id === currentEditingMeetingId);
if (upcomingIndex !== -1) {
meetingsData.upcomingMeetings[upcomingIndex].title = noteTitle;
meetingsData.upcomingMeetings[upcomingIndex].content = content;
console.log('Updated meeting in upcomingMeetings array');
}
// Also update the subtitle if it's a date-based one
const dateObj = new Date(activeMeeting.date);
if (dateObj) {
document.getElementById('noteDate').textContent = formatDate(dateObj);
}
try {
// Save the data to file
await saveMeetingsData();
console.log('Note saved successfully:', noteTitle);
} catch (error) {
console.error('Error saving note:', error);
}
} else {
console.error(`Cannot save note: Meeting not found with ID: ${currentEditingMeetingId}`);
// Log all available meetings for debugging
console.log('Available meeting IDs:', [...upcomingMeetings, ...pastMeetings].map(m => m.id).join(', '));
}
}
// Format date for display in the note header
function formatDate(date) {
const options = { month: 'short', day: 'numeric' };
return date.toLocaleDateString('en-US', options);
}
// Simple debounce function
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
// Function to create meeting card elements
function createMeetingCard(meeting) {
const card = document.createElement('div');
card.className = 'meeting-card';
card.dataset.id = meeting.id;
let iconHtml = '';
if (meeting.type === 'profile') {
iconHtml = `
`;
return card;
}
// Function to show home view
function showHomeView() {
document.getElementById('homeView').style.display = 'block';
document.getElementById('editorView').style.display = 'none';
document.getElementById('backButton').style.display = 'none';
document.getElementById('newNoteBtn').style.display = 'block';
document.getElementById('toggleSidebar').style.display = 'none';
// Show Record Meeting button and set its state based on meeting detection
const joinMeetingBtn = document.getElementById('joinMeetingBtn');
if (joinMeetingBtn) {
// Always show the button
joinMeetingBtn.style.display = 'block';
joinMeetingBtn.innerHTML = 'Record Meeting';
// Enable/disable based on meeting detection
if (window.meetingDetected) {
joinMeetingBtn.disabled = false;
} else {
joinMeetingBtn.disabled = true;
}
}
}
// Function to show editor view
function showEditorView(meetingId) {
console.log(`Showing editor view for meeting ID: ${meetingId}`);
// Make the views visible/hidden
document.getElementById('homeView').style.display = 'none';
document.getElementById('editorView').style.display = 'block';
document.getElementById('backButton').style.display = 'block';
document.getElementById('newNoteBtn').style.display = 'none';
document.getElementById('toggleSidebar').style.display = 'none'; // Hide the sidebar toggle
// Always hide the join meeting button when in editor view
const joinMeetingBtn = document.getElementById('joinMeetingBtn');
if (joinMeetingBtn) {
joinMeetingBtn.style.display = 'none';
}
// Find the meeting in either upcoming or past meetings
let meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (!meeting) {
console.error(`Meeting not found: ${meetingId}`);
return;
}
// Set the current editing meeting ID
currentEditingMeetingId = meetingId;
console.log(`Now editing meeting: ${meetingId} - ${meeting.title}`);
// Set the meeting title
document.getElementById('noteTitle').textContent = meeting.title;
// Set the date display
const dateObj = new Date(meeting.date);
document.getElementById('noteDate').textContent = formatDate(dateObj);
// Show/hide the Recall recording link
const recallLinkEl = document.getElementById('recallLink');
if (recallLinkEl) {
if (meeting.recallRecordingId) {
recallLinkEl.style.display = 'inline-flex';
recallLinkEl.onclick = async (e) => {
e.preventDefault();
recallLinkEl.textContent = 'Loading...';
try {
const result = await window.electronAPI.getRecordingVideoUrl(meeting.recallRecordingId);
if (result.success && result.videoUrl) {
window.electronAPI.openExternal(result.videoUrl);
} else {
alert('Recording not available yet: ' + (result.error || 'Please try again later.'));
}
} catch (err) {
alert('Error fetching recording: ' + err.message);
} finally {
recallLinkEl.innerHTML = `
View Recording
`;
}
};
} else {
recallLinkEl.style.display = 'none';
}
}
// Get the editor element
const editorElement = document.getElementById('simple-editor');
// Important: Reset the editor content completely
if (editorElement) {
editorElement.value = '';
}
// Add a small delay to ensure the DOM has updated before setting content
setTimeout(() => {
if (meeting.content) {
editorElement.value = meeting.content;
console.log(`Loaded content for meeting: ${meetingId}, length: ${meeting.content.length} characters`);
} else {
// If content is missing, create template
const now = new Date();
const template = `# Meeting Title\n• ${meeting.title}\n\n# Meeting Date and Time\n• ${now.toLocaleString()}\n\n# Participants\n• \n\n# Description\n• \n\nChat with meeting transcript: `;
editorElement.value = template;
// Save this template to the meeting
meeting.content = template;
saveMeetingsData();
console.log(`Created new template for meeting: ${meetingId}`);
}
// Set up auto-save handler for this specific note
setupAutoSaveHandler();
// Add event listener to the title
setupTitleEditing();
// Check if this note has an active recording and update the record button
checkActiveRecordingState();
// Update debug panel with any available data if it's open
const debugPanel = document.getElementById('debugPanel');
if (debugPanel && !debugPanel.classList.contains('hidden')) {
// Update transcript if available
if (meeting.transcript && meeting.transcript.length > 0) {
updateDebugTranscript(meeting.transcript);
} else {
// Clear transcript area if no transcript
const transcriptContent = document.getElementById('transcriptContent');
if (transcriptContent) {
transcriptContent.innerHTML = `
No transcript available yet
`;
}
}
// Update participants if available
if (meeting.participants && meeting.participants.length > 0) {
updateDebugParticipants(meeting.participants);
} else {
// Clear participants area if no participants
const participantsContent = document.getElementById('participantsContent');
if (participantsContent) {
participantsContent.innerHTML = `
No participants detected yet
`;
}
}
// Reset video preview when changing notes
const videoContent = document.getElementById('videoContent');
if (videoContent) {
videoContent.innerHTML = `
Video preview will appear here
`;
}
}
}, 50);
}
// Setup the title editing and save function
function setupTitleEditing() {
const titleElement = document.getElementById('noteTitle');
// Remove existing event listeners if any
titleElement.removeEventListener('blur', titleBlurHandler);
titleElement.removeEventListener('keydown', titleKeydownHandler);
// Add event listeners
titleElement.addEventListener('blur', titleBlurHandler);
titleElement.addEventListener('keydown', titleKeydownHandler);
}
// Event handler for title blur
async function titleBlurHandler() {
await saveCurrentNote();
}
// Event handler for title keydown
function titleKeydownHandler(e) {
if (e.key === 'Enter') {
e.preventDefault(); // Prevent new line
e.target.blur(); // Remove focus to trigger save
}
}
// Create a single reference to the auto-save handler to ensure we can remove it properly
let currentAutoSaveHandler = null;
// Function to set up auto-save handler
function setupAutoSaveHandler() {
// Create a debounced auto-save handler
const autoSaveHandler = debounce(async () => {
console.log('Auto-saving note due to content change');
if (currentEditingMeetingId) {
console.log(`Auto-save triggered for meeting: ${currentEditingMeetingId}`);
await saveCurrentNote();
} else {
console.warn('Cannot auto-save: No active meeting ID');
}
}, 1000);
// First remove any existing handler
if (currentAutoSaveHandler) {
const editorElement = document.getElementById('simple-editor');
if (editorElement) {
console.log('Removing existing auto-save handler');
editorElement.removeEventListener('input', currentAutoSaveHandler);
}
}
// Store the reference for future cleanup
currentAutoSaveHandler = autoSaveHandler;
// Get the editor element and attach the new handler
const editorElement = document.getElementById('simple-editor');
if (editorElement) {
editorElement.addEventListener('input', autoSaveHandler);
console.log(`Set up editor auto-save handler for meeting: ${currentEditingMeetingId || 'none'}`);
// Manually trigger a save once to ensure the content is saved
setTimeout(() => {
console.log('Triggering initial save after setup');
editorElement.dispatchEvent(new Event('input'));
}, 500);
} else {
console.warn('Editor element not found for auto-save setup');
}
}
// Function to create a new meeting
async function createNewMeeting() {
console.log('Creating new note...');
// Save any existing note before creating a new one
if (currentEditingMeetingId) {
await saveCurrentNote();
console.log('Saved current note before creating new one');
}
// Reset the current editing ID to ensure we start fresh
currentEditingMeetingId = null;
// Generate a unique ID
const id = 'meeting-' + Date.now();
console.log('Generated new meeting ID:', id);
// Current date and time
const now = new Date();
// Generate the template for the content
const template = `# Meeting Title\n• New Note\n\n# Meeting Date and Time\n• ${now.toLocaleString()}\n\n# Participants\n• \n\n# Description\n• \n\nChat with meeting transcript: `;
// Create a new meeting object - ensure it's of type document
const newMeeting = {
id: id,
type: 'document', // Explicitly set as document type, not calendar
title: 'New Note',
subtitle: now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
hasDemo: false,
date: now.toISOString(),
participants: [],
content: template // Set the content directly
};
// Log what we're adding
console.log(`Adding new meeting: id=${id}, title=${newMeeting.title}, content.length=${template.length}`);
// Add to pastMeetings - make sure to push to both arrays
pastMeetings.unshift(newMeeting);
meetingsData.pastMeetings.unshift(newMeeting);
// Update the grouped meetings
const dateKey = formatDateHeader(newMeeting.date);
if (!pastMeetingsByDate[dateKey]) {
pastMeetingsByDate[dateKey] = [];
}
pastMeetingsByDate[dateKey].unshift(newMeeting);
// Save the data to file
try {
await saveMeetingsData();
console.log('New meeting created and saved:', newMeeting.title);
} catch (error) {
console.error('Error saving new meeting:', error);
}
// Set current editing ID to the new meeting ID BEFORE showing the editor
currentEditingMeetingId = id;
console.log('Set currentEditingMeetingId to:', id);
// Force a reset of the editor before showing the new meeting
const editorElement = document.getElementById('simple-editor');
if (editorElement) {
editorElement.value = '';
}
// Now show the editor view with the new meeting
showEditorView(id);
// Automatically start recording for the new note
try {
console.log('Auto-starting recording for new note');
// Start manual recording for the new note
window.electronAPI.startManualRecording(id)
.then(result => {
if (result.success) {
console.log('Auto-started recording for new note with ID:', result.recordingId);
// Update recording button UI
window.isRecording = true;
window.currentRecordingId = result.recordingId;
// Update recording button UI
const recordButton = document.getElementById('recordButton');
if (recordButton) {
const recordIcon = recordButton.querySelector('.record-icon');
const stopIcon = recordButton.querySelector('.stop-icon');
recordButton.classList.add('recording');
recordIcon.style.display = 'none';
stopIcon.style.display = 'block';
}
} else {
console.error('Failed to auto-start recording:', result.error);
}
})
.catch(error => {
console.error('Error auto-starting recording:', error);
});
} catch (error) {
console.error('Exception auto-starting recording:', error);
}
return id;
}
// Function to render meetings to the page
function renderMeetings() {
// Clear previous content
const mainContent = document.querySelector('.main-content .content-container');
mainContent.innerHTML = '';
// Create all notes section (replaces both upcoming and date-grouped sections)
const notesSection = document.createElement('section');
notesSection.className = 'meetings-section';
notesSection.innerHTML = `
Notes
`;
mainContent.appendChild(notesSection);
// Get the notes container
const notesContainer = notesSection.querySelector('#notes-list');
// Add all meetings to the notes section (both upcoming and past)
const allMeetings = [...upcomingMeetings, ...pastMeetings];
// Sort by date, newest first
allMeetings.sort((a, b) => {
return new Date(b.date) - new Date(a.date);
});
// Filter out calendar entries and add only document type meetings to the container
allMeetings
.filter(meeting => meeting.type !== 'calendar') // Skip calendar entries
.forEach(meeting => {
notesContainer.appendChild(createMeetingCard(meeting));
});
}
// Load meetings data from file
async function loadMeetingsDataFromFile() {
console.log("Loading meetings data from file...");
try {
const result = await window.electronAPI.loadMeetingsData();
console.log("Load result success:", result.success);
if (result.success) {
console.log(`Got data with ${result.data.pastMeetings?.length || 0} past meetings`);
if (result.data.pastMeetings && result.data.pastMeetings.length > 0) {
console.log("Most recent meeting:", result.data.pastMeetings[0].id, result.data.pastMeetings[0].title);
}
// Initialize arrays if they don't exist in the loaded data
if (!result.data.upcomingMeetings) {
result.data.upcomingMeetings = [];
}
if (!result.data.pastMeetings) {
result.data.pastMeetings = [];
}
// Update the meetings data objects
Object.assign(meetingsData, result.data);
// Clear and reassign the references
upcomingMeetings.length = 0;
pastMeetings.length = 0;
console.log("Before updating arrays, pastMeetings count:", pastMeetings.length);
// Filter out calendar entries when loading data
meetingsData.upcomingMeetings
.filter(meeting => meeting.type !== 'calendar')
.forEach(meeting => upcomingMeetings.push(meeting));
meetingsData.pastMeetings
.filter(meeting => meeting.type !== 'calendar')
.forEach(meeting => pastMeetings.push(meeting));
console.log("After updating arrays, pastMeetings count:", pastMeetings.length);
if (pastMeetings.length > 0) {
console.log("First past meeting:", pastMeetings[0].id, pastMeetings[0].title);
}
// Regroup past meetings by date
pastMeetingsByDate = {};
meetingsData.pastMeetings.forEach(meeting => {
const dateKey = formatDateHeader(meeting.date);
if (!pastMeetingsByDate[dateKey]) {
pastMeetingsByDate[dateKey] = [];
}
pastMeetingsByDate[dateKey].push(meeting);
});
console.log('Meetings data loaded from file');
// Re-render the meetings
renderMeetings();
} else {
console.error('Failed to load meetings data from file:', result.error);
}
} catch (error) {
console.error('Error loading meetings data from file:', error);
}
}
// Function to update the transcript section in the debug panel
function updateDebugTranscript(transcript) {
const transcriptContent = document.getElementById('transcriptContent');
if (!transcriptContent) return;
// Check if user was at bottom before clearing content
const wasAtBottom = transcriptContent.scrollTop + transcriptContent.clientHeight >= transcriptContent.scrollHeight - 5;
// Clear previous content
transcriptContent.innerHTML = '';
if (!transcript || transcript.length === 0) {
// Show placeholder if no transcript is available
transcriptContent.innerHTML = `
`;
// Add a highlight class for the newest entry
if (index === transcript.length - 1) {
entryDiv.classList.add('newest-entry');
}
transcriptDiv.appendChild(entryDiv);
});
transcriptContent.appendChild(transcriptDiv);
// Only auto-scroll to bottom if user was at the bottom before the update
if (wasAtBottom) {
// Use setTimeout to ensure DOM has updated
setTimeout(() => {
transcriptContent.scrollTop = transcriptContent.scrollHeight;
}, 0);
}
}
// Function to update the video preview in the debug panel
function updateDebugVideoPreview(frameData) {
// Get the image data from the frame
const { buffer, participantId, participantName, frameType } = frameData;
// Determine if this is a screenshare or participant video
const isScreenshare = frameType !== 'webcam';
if (isScreenshare) {
updateScreensharePreview(frameData);
} else {
updateParticipantVideoPreview(frameData);
}
// Make sure debug panel toggle shows new content notification if panel is closed
const debugPanel = document.getElementById('debugPanel');
if (debugPanel && debugPanel.classList.contains('hidden')) {
const debugPanelToggle = document.getElementById('debugPanelToggle');
if (debugPanelToggle && !debugPanelToggle.classList.contains('has-new-content')) {
debugPanelToggle.classList.add('has-new-content');
}
}
}
// Function to update participant video preview
function updateParticipantVideoPreview(frameData) {
const videoContent = document.getElementById('videoContent');
if (!videoContent) return;
const { buffer, participantId, participantName, frameType } = frameData;
// Check if we already have a container for this participant
let participantVideoContainer = document.getElementById(`video-participant-${participantId}`);
// If no container exists, create one
if (!participantVideoContainer) {
// Clear the placeholder content if this is the first frame
if (videoContent.querySelector('.placeholder-content')) {
videoContent.innerHTML = '';
}
// Create a container for this participant's video
participantVideoContainer = document.createElement('div');
participantVideoContainer.id = `video-participant-${participantId}`;
participantVideoContainer.className = 'video-participant-container';
// Add the name label
const nameLabel = document.createElement('div');
nameLabel.className = 'video-participant-name';
nameLabel.textContent = participantName;
participantVideoContainer.appendChild(nameLabel);
// Create an image element for the video frame
const videoImg = document.createElement('img');
videoImg.className = 'video-frame';
videoImg.id = `video-frame-${participantId}`;
participantVideoContainer.appendChild(videoImg);
// Add the frame type label
const typeLabel = document.createElement('div');
typeLabel.className = 'video-frame-type';
typeLabel.textContent = 'Camera';
participantVideoContainer.appendChild(typeLabel);
// Add to the video content area
videoContent.appendChild(participantVideoContainer);
}
// Update the image with the new frame
const videoImg = document.getElementById(`video-frame-${participantId}`);
if (videoImg) {
videoImg.src = `data:image/png;base64,${buffer}`;
}
}
// Function to update screenshare preview
function updateScreensharePreview(frameData) {
const screenshareContent = document.getElementById('screenshareContent');
if (!screenshareContent) return;
const { buffer, participantId, participantName, frameType } = frameData;
// Check if we already have a container for this screenshare
let screenshareContainer = document.getElementById(`screenshare-participant-${participantId}`);
// If no container exists, create one
if (!screenshareContainer) {
// Clear the placeholder content if this is the first frame
if (screenshareContent.querySelector('.placeholder-content')) {
screenshareContent.innerHTML = '';
}
// Create a container for this participant's screenshare
screenshareContainer = document.createElement('div');
screenshareContainer.id = `screenshare-participant-${participantId}`;
screenshareContainer.className = 'video-participant-container';
// Create an image element for the screenshare frame
const screenshareImg = document.createElement('img');
screenshareImg.className = 'video-frame';
screenshareImg.id = `screenshare-frame-${participantId}`;
screenshareContainer.appendChild(screenshareImg);
// Add the frame type label
const typeLabel = document.createElement('div');
typeLabel.className = 'video-frame-type';
typeLabel.textContent = 'Screen';
screenshareContainer.appendChild(typeLabel);
// Add to the screenshare content area
screenshareContent.appendChild(screenshareContainer);
}
// Update the image with the new frame
const screenshareImg = document.getElementById(`screenshare-frame-${participantId}`);
if (screenshareImg) {
screenshareImg.src = `data:image/png;base64,${buffer}`;
}
}
// Function to update the participants section in the debug panel
function updateDebugParticipants(participants) {
const participantsContent = document.getElementById('participantsContent');
if (!participantsContent) return;
// Clear previous content
participantsContent.innerHTML = '';
if (!participants || participants.length === 0) {
// Show placeholder if no participants are available
participantsContent.innerHTML = `
`;
participantsList.appendChild(participantDiv);
});
participantsContent.appendChild(participantsList);
}
// Function to initialize the debug panel
function initDebugPanel() {
const debugPanelToggle = document.getElementById('debugPanelToggle');
const debugPanel = document.getElementById('debugPanel');
const closeDebugPanelBtn = document.getElementById('closeDebugPanelBtn');
// Set up toggle button for the debug panel
if (debugPanelToggle && debugPanel) {
debugPanelToggle.addEventListener('click', () => {
// Toggle the debug panel visibility
if (debugPanel.classList.contains('hidden')) {
debugPanel.classList.remove('hidden');
document.querySelector('.app-container').classList.add('debug-panel-open');
// Update the toggle button position and remove any notification indicators
debugPanelToggle.style.right = '50%';
debugPanelToggle.classList.remove('has-new-content');
debugPanelToggle.innerHTML = `
`;
// If there's an active meeting, refresh the debug panels with latest data
if (currentEditingMeetingId) {
const meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === currentEditingMeetingId);
if (meeting) {
// Update transcript if available
if (meeting.transcript && meeting.transcript.length > 0) {
updateDebugTranscript(meeting.transcript);
} else {
// Clear transcript area if no transcript
const transcriptContent = document.getElementById('transcriptContent');
if (transcriptContent) {
transcriptContent.innerHTML = `
No transcript available yet
`;
}
}
// Update participants if available
if (meeting.participants && meeting.participants.length > 0) {
updateDebugParticipants(meeting.participants);
} else {
// Clear participants area if no participants
const participantsContent = document.getElementById('participantsContent');
if (participantsContent) {
participantsContent.innerHTML = `
No participants detected yet
`;
}
}
// Reset video preview when opening debug panel
const videoContent = document.getElementById('videoContent');
if (videoContent) {
videoContent.innerHTML = `
Video preview will appear here
`;
}
}
}
} else {
debugPanel.classList.add('hidden');
document.querySelector('.app-container').classList.remove('debug-panel-open');
// Reset the toggle button position
debugPanelToggle.style.right = '0';
debugPanelToggle.innerHTML = `
`;
}
});
}
// Set up close button for the debug panel
if (closeDebugPanelBtn && debugPanel) {
closeDebugPanelBtn.addEventListener('click', () => {
debugPanel.classList.add('hidden');
// Restore the editorView to full width
document.querySelector('.app-container').classList.remove('debug-panel-open');
// Reset the toggle button position and icon
const debugPanelToggle = document.getElementById('debugPanelToggle');
if (debugPanelToggle) {
debugPanelToggle.style.right = '0';
debugPanelToggle.innerHTML = `
`;
}
});
}
// Set up clear button for the logger section
const clearLoggerBtn = document.getElementById('clearLoggerBtn');
if (clearLoggerBtn) {
clearLoggerBtn.addEventListener('click', () => {
sdkLogger.clear();
});
}
}
// SDK Logger functions
const sdkLogger = {
logs: [],
maxLogs: 100,
// Initialize the logger
init() {
// Listen for logs from the main process
window.sdkLoggerBridge?.onSdkLog(logEntry => {
// Add an origin flag to logs created in this renderer to prevent duplicates
if (!logEntry.originatedFromRenderer) {
this.addLogEntry(logEntry);
}
});
// Log initialization
this.log('SDK Logger initialized', 'info');
},
// Log an API call
logApiCall(method, params = {}) {
const logEntry = {
type: 'api-call',
method,
params,
timestamp: new Date()
};
// Send to main process
this._sendToMainProcess(logEntry);
// Add to local logs
this.addLogEntry(logEntry);
},
// Log an event
logEvent(eventType, data = {}) {
const logEntry = {
type: 'event',
eventType,
data,
timestamp: new Date()
};
// Send to main process
this._sendToMainProcess(logEntry);
// Add to local logs
this.addLogEntry(logEntry);
},
// Log an error
logError(errorType, message) {
const logEntry = {
type: 'error',
errorType,
message,
timestamp: new Date()
};
// Send to main process
this._sendToMainProcess(logEntry);
// Add to local logs
this.addLogEntry(logEntry);
},
// Log a generic message
log(message, level = 'info') {
const logEntry = {
type: level,
message,
timestamp: new Date()
};
// Send to main process
this._sendToMainProcess(logEntry);
// Add to local logs
this.addLogEntry(logEntry);
},
// Helper to send logs to main process
_sendToMainProcess(logEntry) {
if (window.sdkLoggerBridge?.sendSdkLog) {
// Mark this log entry as originating from this renderer to prevent duplicates
const markedLogEntry = { ...logEntry, originatedFromRenderer: true };
window.sdkLoggerBridge.sendSdkLog(markedLogEntry);
}
},
// Add a log entry to the UI and internal array
addLogEntry(entry) {
// Add to internal logs array
this.logs.push(entry);
// Trim logs if we have too many
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}
// Add to UI
const loggerContent = document.getElementById('sdkLoggerContent');
if (loggerContent) {
const logElement = document.createElement('div');
logElement.className = `sdk-log-entry ${entry.type}`;
const timestamp = document.createElement('div');
timestamp.className = 'timestamp';
timestamp.textContent = this.formatTimestamp(entry.timestamp instanceof Date ? entry.timestamp : new Date(entry.timestamp));
logElement.appendChild(timestamp);
// Format content based on log type
let content = '';
switch (entry.type) {
case 'api-call':
content = `RecallAiSdk.${entry.method}()`;
if (entry.params && Object.keys(entry.params).length > 0) {
content += `
${this.formatParams(entry.params)}
`;
}
break;
case 'event':
content = `Event: ${entry.eventType}`;
if (entry.data && Object.keys(entry.data).length > 0) {
content += `
${this.formatParams(entry.data)}
`;
}
break;
case 'error':
content = `Error: ${entry.errorType}`;
if (entry.message) {
content += `
${entry.message}
`;
}
break;
default:
content = entry.message;
}
logElement.innerHTML += content;
// Add to the top of the log
loggerContent.insertBefore(logElement, loggerContent.firstChild);
// Only auto-scroll to top if user is already at the top
const isAtTop = loggerContent.scrollTop <= 5;
if (isAtTop) {
loggerContent.scrollTop = 0;
}
}
},
// Clear all logs
clear() {
this.logs = [];
const loggerContent = document.getElementById('sdkLoggerContent');
if (loggerContent) {
loggerContent.innerHTML = '';
}
},
// Format timestamp to readable string
formatTimestamp(date) {
return date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3
});
},
// Format parameters object to JSON string
formatParams(params) {
try {
return JSON.stringify(params, null, 2)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\n/g, ' ')
.replace(/ /g, ' ');
} catch (e) {
return String(params);
}
}
};
// Initialize the app when the DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
console.log('DOM content loaded, loading data from file...');
// Initialize the SDK Logger
sdkLogger.init();
// Initialize the debug panel
initDebugPanel();
// Try to load the latest data from file - this is the only data source
await loadMeetingsDataFromFile();
// Render meetings only after loading from file
console.log('Data loaded, rendering meetings...');
renderMeetings();
// Initially show home view
showHomeView();
// Listen for meeting detection status updates
window.electronAPI.onMeetingDetectionStatus((data) => {
console.log('Meeting detection status update:', data);
const joinMeetingBtn = document.getElementById('joinMeetingBtn');
// Store the meeting detection state globally
window.meetingDetected = data.detected;
if (joinMeetingBtn) {
// Only update button state if we're in the home view
const inHomeView = document.getElementById('homeView').style.display !== 'none';
if (inHomeView) {
// Always show the button, but enable/disable based on meeting detection
joinMeetingBtn.style.display = 'block';
joinMeetingBtn.disabled = !data.detected;
}
}
});
// Listen for requests to open a meeting note (from notification click)
window.electronAPI.onOpenMeetingNote((meetingId) => {
console.log('Received request to open meeting note:', meetingId);
// Ensure we have the latest data before showing the note
loadMeetingsDataFromFile().then(() => {
console.log('Data refreshed, checking for meeting ID:', meetingId);
// Log the list of available meeting IDs to help with debugging
console.log('Available meeting IDs:', pastMeetings.map(m => m.id));
// Verify the meeting exists in our data
const meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (meeting) {
console.log('Found meeting to open:', meeting.title);
setTimeout(() => {
showEditorView(meetingId);
}, 200); // Add a small delay to ensure UI is ready
} else {
console.error('Meeting not found with ID:', meetingId);
// Attempt to reload data again after a delay
setTimeout(() => {
console.log('Retrying data load after delay...');
loadMeetingsDataFromFile().then(() => {
const retryMeeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (retryMeeting) {
console.log('Found meeting on second attempt:', retryMeeting.title);
showEditorView(meetingId);
} else {
console.error('Meeting still not found after retry. Available meetings:',
pastMeetings.map(m => `${m.id}: ${m.title}`));
}
});
}, 1500);
}
});
});
// Listen for recording completed events
window.electronAPI.onRecordingCompleted((meetingId) => {
console.log('Recording completed for meeting:', meetingId);
// If this note is currently being edited, reload its content
if (currentEditingMeetingId === meetingId) {
loadMeetingsDataFromFile().then(() => {
// Refresh the editor with the updated content
const meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (meeting) {
document.getElementById('simple-editor').value = meeting.content;
}
});
}
});
// Listen for video frame events
window.electronAPI.onVideoFrame((data) => {
// Only handle video frames for the currently open meeting
if (data.noteId === currentEditingMeetingId) {
console.log(`Video frame received for participant: ${data.participantName}`);
// Update the video preview in the debug panel
updateDebugVideoPreview(data);
}
});
// Listen for participants update events
window.electronAPI.onParticipantsUpdated((meetingId) => {
console.log('Participants updated for meeting:', meetingId);
// If this note is currently being edited, refresh the data
// and update the debug panel's participants section
if (currentEditingMeetingId === meetingId) {
loadMeetingsDataFromFile().then(() => {
const meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (meeting && meeting.participants && meeting.participants.length > 0) {
// Log the latest participant
const latestParticipant = meeting.participants[meeting.participants.length - 1];
console.log(`Participant updated: ${latestParticipant.name}`);
// Update the participants area in the debug panel
updateDebugParticipants(meeting.participants);
// Show notification about new participant if debug panel is closed
const debugPanel = document.getElementById('debugPanel');
if (debugPanel && debugPanel.classList.contains('hidden')) {
const debugPanelToggle = document.getElementById('debugPanelToggle');
if (debugPanelToggle) {
// Add pulse effect to show there's new content
debugPanelToggle.classList.add('has-new-content');
// Create a mini notification for participant join
const miniNotification = document.createElement('div');
miniNotification.className = 'debug-notification participant-notification';
miniNotification.innerHTML = `
New Participant:${latestParticipant.name || 'Unknown'}
`;
// Add to document
document.body.appendChild(miniNotification);
// Remove after a short time
setTimeout(() => {
miniNotification.classList.add('fade-out');
setTimeout(() => {
document.body.removeChild(miniNotification);
}, 500);
}, 5000);
}
}
}
});
}
});
// Listen for transcript update events
window.electronAPI.onTranscriptUpdated((meetingId) => {
console.log('Transcript updated for meeting:', meetingId);
// If this note is currently being edited, we can refresh the data
// and update the debug panel's transcript section
if (currentEditingMeetingId === meetingId) {
loadMeetingsDataFromFile().then(() => {
const meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (meeting && meeting.transcript && meeting.transcript.length > 0) {
const latestEntry = meeting.transcript[meeting.transcript.length - 1];
const latestSpeaker = latestEntry.participant?.name || latestEntry.speaker || 'Unknown';
const latestText = latestEntry.words
? latestEntry.words.map(word => word.text).join(' ')
: latestEntry.text || '';
console.log(`Latest transcript: ${latestSpeaker}: "${latestText}"`);
updateDebugTranscript(meeting.transcript);
const debugPanel = document.getElementById('debugPanel');
if (debugPanel && debugPanel.classList.contains('hidden')) {
const debugPanelToggle = document.getElementById('debugPanelToggle');
if (debugPanelToggle) {
debugPanelToggle.classList.add('has-new-content');
if (window.isRecording) {
const miniNotification = document.createElement('div');
miniNotification.className = 'debug-notification transcript-notification';
miniNotification.innerHTML = `
${latestSpeaker}:
${latestText.slice(0, 40)}${latestText.length > 40 ? '...' : ''}
`;
// Add to document
document.body.appendChild(miniNotification);
// Remove after a short time
setTimeout(() => {
miniNotification.classList.add('fade-out');
setTimeout(() => {
document.body.removeChild(miniNotification);
}, 500);
}, 5000);
}
}
}
}
});
}
});
// Listen for meeting title updates
window.electronAPI.onMeetingTitleUpdated((data) => {
console.log('Meeting title updated:', data);
const { meetingId, newTitle } = data;
// Reload the meetings data
loadMeetingsDataFromFile().then(() => {
// Re-render the meetings list to show the updated title
renderMeetings();
// If this is the currently open meeting, update the editor title too
if (currentEditingMeetingId === meetingId) {
const noteTitleElement = document.getElementById('noteTitle');
if (noteTitleElement) {
noteTitleElement.textContent = newTitle;
console.log('Updated editor title to:', newTitle);
}
}
});
});
// Listen for summary generation events
window.electronAPI.onSummaryGenerated((meetingId) => {
console.log('Summary generated for meeting:', meetingId);
// If this note is currently being edited, refresh the content
if (currentEditingMeetingId === meetingId) {
loadMeetingsDataFromFile().then(() => {
const meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (meeting) {
// Update the editor with the new content containing the summary
document.getElementById('simple-editor').value = meeting.content;
}
});
}
});
// Listen for streaming summary updates
window.electronAPI.onSummaryUpdate((data) => {
const { meetingId, content, timestamp } = data;
// If this note is currently being edited, update the content immediately
if (currentEditingMeetingId === meetingId) {
// Get the editor element
const editorElement = document.getElementById('simple-editor');
// Update the editor with the latest streamed content
// Use requestAnimationFrame for smoother updates that don't block the main thread
requestAnimationFrame(() => {
editorElement.value = content;
// Force the editor to scroll to the bottom to follow the new text
// This creates a better experience of watching text appear
editorElement.scrollTop = editorElement.scrollHeight;
});
}
});
// Add event listeners for buttons
document.querySelector('.new-note-btn').addEventListener('click', async () => {
console.log('New note button clicked');
await createNewMeeting();
});
// Join Meeting button handler
document.getElementById('joinMeetingBtn').addEventListener('click', async () => {
console.log('Join Meeting button clicked');
// Get the button element
const joinButton = document.getElementById('joinMeetingBtn');
// Show loading state
const originalText = joinButton.textContent;
joinButton.disabled = true;
joinButton.innerHTML = `
Joining...
`;
// First check if there's a detected meeting
if (window.electronAPI.checkForDetectedMeeting) {
try {
const hasDetectedMeeting = await window.electronAPI.checkForDetectedMeeting();
if (hasDetectedMeeting) {
console.log('Found detected meeting, joining...');
await window.electronAPI.joinDetectedMeeting();
// Keep button disabled as we're navigating to a different view
} else {
console.log('No active meeting detected');
// Reset button state
joinButton.disabled = false;
joinButton.textContent = originalText;
// Show a little toast message
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = 'No active meeting detected';
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
}
} catch (error) {
console.error('Error joining meeting:', error);
// Reset button state
joinButton.disabled = false;
joinButton.textContent = originalText;
// Show error toast
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = 'Error joining meeting';
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
}
} else {
// Fallback for direct call
try {
await window.electronAPI.joinDetectedMeeting();
// Keep button disabled as we're navigating to a different view
} catch (error) {
console.error('Error joining meeting:', error);
// Reset button state
joinButton.disabled = false;
joinButton.textContent = originalText;
}
}
});
document.querySelector('.search-input').addEventListener('input', (e) => {
console.log('Search query:', e.target.value);
// TODO: Implement search functionality
});
// Add click event delegation for meeting cards and their actions
document.querySelector('.main-content').addEventListener('click', (e) => {
// Check if delete button was clicked
if (e.target.closest('.delete-meeting-btn')) {
e.stopPropagation(); // Prevent opening the note
const deleteBtn = e.target.closest('.delete-meeting-btn');
const meetingId = deleteBtn.dataset.id;
if (confirm('Are you sure you want to delete this note? This cannot be undone.')) {
console.log('Deleting meeting:', meetingId);
// Show loading state
deleteBtn.disabled = true;
deleteBtn.innerHTML = ``;
// Use the main process deletion via IPC
window.electronAPI.deleteMeeting(meetingId)
.then(result => {
if (result.success) {
console.log('Meeting deleted successfully on server');
// After successful server deletion, update local data
// Remove from local pastMeetings array
const pastMeetingIndex = pastMeetings.findIndex(meeting => meeting.id === meetingId);
if (pastMeetingIndex !== -1) {
pastMeetings.splice(pastMeetingIndex, 1);
}
// Remove from meetingsData as well
const pastDataIndex = meetingsData.pastMeetings.findIndex(meeting => meeting.id === meetingId);
if (pastDataIndex !== -1) {
meetingsData.pastMeetings.splice(pastDataIndex, 1);
}
// Also check upcomingMeetings
const upcomingMeetingIndex = upcomingMeetings.findIndex(meeting => meeting.id === meetingId);
if (upcomingMeetingIndex !== -1) {
upcomingMeetings.splice(upcomingMeetingIndex, 1);
}
const upcomingDataIndex = meetingsData.upcomingMeetings.findIndex(meeting => meeting.id === meetingId);
if (upcomingDataIndex !== -1) {
meetingsData.upcomingMeetings.splice(upcomingDataIndex, 1);
}
// Update the grouped meetings
pastMeetingsByDate = {};
meetingsData.pastMeetings.forEach(meeting => {
const dateKey = formatDateHeader(meeting.date);
if (!pastMeetingsByDate[dateKey]) {
pastMeetingsByDate[dateKey] = [];
}
pastMeetingsByDate[dateKey].push(meeting);
});
// Re-render the meetings list
renderMeetings();
} else {
// Server side deletion failed
console.error('Server deletion failed:', result.error);
alert('Failed to delete note: ' + (result.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error deleting meeting:', error);
alert('Failed to delete note: ' + (error.message || 'Unknown error'));
})
.finally(() => {
// Reset button state whether success or failure
deleteBtn.disabled = false;
deleteBtn.innerHTML = ``;
});
}
return;
}
// Find the meeting card that was clicked (for opening)
const card = e.target.closest('.meeting-card');
if (card) {
const meetingId = card.dataset.id;
showEditorView(meetingId);
}
});
// Back button event listener
document.getElementById('backButton').addEventListener('click', async () => {
// Save content before going back to home
await saveCurrentNote();
showHomeView();
renderMeetings(); // Refresh the meeting list
});
// Set up the initial auto-save handler
setupAutoSaveHandler();
// Toggle sidebar button with initial state
const toggleSidebarBtn = document.getElementById('toggleSidebar');
const sidebar = document.getElementById('sidebar');
const editorContent = document.querySelector('.editor-content');
const chatInputContainer = document.querySelector('.chat-input-container');
// Start with sidebar hidden
sidebar.classList.add('hidden');
editorContent.classList.add('full-width');
chatInputContainer.style.display = 'none';
toggleSidebarBtn.addEventListener('click', () => {
sidebar.classList.toggle('hidden');
editorContent.classList.toggle('full-width');
// Show/hide chat input with sidebar
if (sidebar.classList.contains('hidden')) {
chatInputContainer.style.display = 'none';
} else {
chatInputContainer.style.display = 'block';
}
});
// Chat input handling
const chatInput = document.getElementById('chatInput');
const sendButton = document.getElementById('sendButton');
// When send button is clicked
sendButton.addEventListener('click', () => {
const message = chatInput.value.trim();
if (message) {
console.log('Sending message:', message);
// Here you would handle the AI chat functionality
// For now, just clear the input
chatInput.value = '';
}
});
// Send message on Enter key
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendButton.click();
}
});
// Handle share buttons
const shareButtons = document.querySelectorAll('.share-btn');
shareButtons.forEach(button => {
button.addEventListener('click', () => {
const action = button.textContent.trim();
console.log(`Share action: ${action}`);
// Implement actual sharing functionality here
});
});
// Handle AI option buttons
const aiButtons = document.querySelectorAll('.ai-btn');
aiButtons.forEach(button => {
button.addEventListener('click', async () => {
const action = button.textContent.trim();
console.log(`AI action: ${action}`);
// Handle different AI actions
if (action === 'Generate meeting summary') {
if (!currentEditingMeetingId) {
alert('No meeting is currently open');
return;
}
// Show loading state
const originalText = button.textContent;
button.textContent = 'Generating summary...';
button.disabled = true;
try {
// Use streaming version instead of standard version
console.log('Starting streaming summary generation');
// Log the summary generation request to the SDK logger
sdkLogger.log('Requesting AI summary generation for meeting: ' + currentEditingMeetingId);
window.electronAPI.generateMeetingSummaryStreaming(currentEditingMeetingId)
.then(result => {
if (result.success) {
console.log('Summary generated successfully (streaming)');
} else {
console.error('Failed to generate summary:', result.error);
alert('Failed to generate summary: ' + result.error);
}
})
.catch(error => {
console.error('Error generating summary:', error);
alert('Error generating summary: ' + (error.message || error));
})
.finally(() => {
// Reset button state
button.textContent = originalText;
button.disabled = false;
});
} catch (error) {
console.error('Error starting streaming summary generation:', error);
alert('Error starting summary generation: ' + (error.message || error));
// Reset button state
button.textContent = originalText;
button.disabled = false;
}
} else if (action === 'List action items') {
alert('List action items functionality coming soon');
} else if (action === 'Write follow-up email') {
alert('Write follow-up email functionality coming soon');
} else if (action === 'List Q&A') {
alert('List Q&A functionality coming soon');
}
});
});
// UI variables will be initialized when the recording button is set up
// Listen for recording state change events
window.electronAPI.onRecordingStateChange((data) => {
console.log('Recording state change received:', data);
// If this state change is for the current note, update the UI
if (data.noteId === currentEditingMeetingId) {
console.log('Updating recording button for current note');
const isActive = data.state === 'recording' || data.state === 'paused';
updateRecordingButtonUI(isActive, isActive ? data.recordingId : null);
}
});
// Setup record/stop button toggle
const recordButton = document.getElementById('recordButton');
if (recordButton) {
recordButton.addEventListener('click', async () => {
// Only allow recording if we're in a note
if (!currentEditingMeetingId) {
alert('You need to be in a note to start recording');
return;
}
window.isRecording = !window.isRecording;
// Get the elements inside the button
const recordIcon = recordButton.querySelector('.record-icon');
const stopIcon = recordButton.querySelector('.stop-icon');
if (window.isRecording) {
try {
// Start recording
console.log('Starting manual recording for meeting:', currentEditingMeetingId);
recordButton.disabled = true; // Temporarily disable to prevent double-clicks
// Change to stop mode immediately for better feedback
recordButton.classList.add('recording');
recordIcon.style.display = 'none';
stopIcon.style.display = 'block';
// Call the API to start recording
const result = await window.electronAPI.startManualRecording(currentEditingMeetingId);
recordButton.disabled = false;
if (result.success) {
console.log('Manual recording started with ID:', result.recordingId);
window.currentRecordingId = result.recordingId;
// Show a little toast message
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = 'Recording started...';
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
} else {
// If starting failed, revert UI
console.error('Failed to start recording:', result.error);
alert('Failed to start recording: ' + result.error);
window.isRecording = false;
recordButton.classList.remove('recording');
recordIcon.style.display = 'block';
stopIcon.style.display = 'none';
}
} catch (error) {
// Handle errors
console.error('Error starting recording:', error);
alert('Error starting recording: ' + (error.message || error));
// Reset UI state
window.isRecording = false;
recordButton.classList.remove('recording');
recordIcon.style.display = 'block';
stopIcon.style.display = 'none';
recordButton.disabled = false;
}
} else {
// Stop recording
if (window.currentRecordingId) {
try {
console.log('Stopping manual recording:', window.currentRecordingId);
recordButton.disabled = true; // Temporarily disable
// Call the API to stop recording
const result = await window.electronAPI.stopManualRecording(window.currentRecordingId);
// Change to record mode
recordButton.classList.remove('recording');
recordIcon.style.display = 'block';
stopIcon.style.display = 'none';
recordButton.disabled = false;
if (result.success) {
console.log('Manual recording stopped successfully');
// Show a little toast message
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = 'Recording stopped. Generating summary...';
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
// The recording-completed event handler will take care of refreshing the content
// and generating the summary when the recording finishes processing
} else {
console.error('Failed to stop recording:', result.error);
alert('Failed to stop recording: ' + result.error);
}
// Reset recording ID
window.currentRecordingId = null;
} catch (error) {
console.error('Error stopping recording:', error);
alert('Error stopping recording: ' + (error.message || error));
recordButton.disabled = false;
}
} else {
console.warn('No active recording ID found');
// Reset UI anyway
recordButton.classList.remove('recording');
recordIcon.style.display = 'block';
stopIcon.style.display = 'none';
}
}
});
}
// Handle generate notes button (Auto button)
const generateButton = document.querySelector('.generate-btn');
if (generateButton) {
generateButton.addEventListener('click', async () => {
console.log('Generating AI summary from transcript...');
// Check if we have an active meeting
if (!currentEditingMeetingId) {
alert('No meeting is currently open');
return;
}
// Store the original HTML content (including the sparkle icon)
const originalHTML = generateButton.innerHTML;
// Show loading state - but keep the same structure
generateButton.innerHTML = `
Generating...
`;
generateButton.disabled = true;
try {
// Use streaming version for better user experience
console.log('Starting streaming summary generation');
// Log the Auto button summary generation to the SDK logger
sdkLogger.log('Auto button: Requesting AI summary generation for meeting: ' + currentEditingMeetingId);
const result = await window.electronAPI.generateMeetingSummaryStreaming(currentEditingMeetingId);
if (result.success) {
console.log('Summary generated successfully (streaming)');
// Show a little toast message
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = 'Summary generated successfully!';
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
} else {
console.error('Failed to generate summary:', result.error);
alert('Failed to generate summary: ' + result.error);
}
} catch (error) {
console.error('Error generating summary:', error);
alert('Error generating summary: ' + (error.message || error));
} finally {
// Reset button state with the original HTML (including sparkle icon)
generateButton.innerHTML = originalHTML;
generateButton.disabled = false;
}
});
}
// Listen for recording completed events
window.electronAPI.onRecordingCompleted((meetingId) => {
console.log('Recording completed for meeting:', meetingId);
if (currentEditingMeetingId === meetingId) {
// Reload the meeting data first
loadMeetingsDataFromFile().then(() => {
// Refresh the editor with the updated content
const meeting = [...upcomingMeetings, ...pastMeetings].find(m => m.id === meetingId);
if (meeting) {
document.getElementById('simple-editor').value = meeting.content;
}
});
}
});
});