@@ -28,6 +28,18 @@ class NexMeetApp {
2828 this . userWelcome = document . getElementById ( 'userWelcome' ) ;
2929 this . logoutBtn = document . getElementById ( 'logoutBtn' ) ;
3030 this . userInfo = document . getElementById ( 'userInfo' ) ;
31+
32+ // Post-meeting chat elements
33+ this . postMeetingSection = document . getElementById ( 'postMeetingSection' ) ;
34+ this . postMeetingMessageInput = document . getElementById ( 'postMeetingMessage' ) ;
35+ this . postMeetingChatBtn = document . getElementById ( 'postMeetingChatBtn' ) ;
36+ this . postMeetingResponseArea = document . getElementById ( 'postMeetingResponse' ) ;
37+
38+ // Saved meetings elements
39+ this . savedMeetingsList = document . getElementById ( 'savedMeetingsList' ) ;
40+ this . refreshSavedMeetingsBtn = document . getElementById ( 'refreshSavedMeetings' ) ;
41+
42+ this . selectedMeetingFile = null ;
3143 }
3244
3345 attachEventListeners ( ) {
@@ -43,6 +55,13 @@ class NexMeetApp {
4355 this . aiMessageInput . addEventListener ( 'input' , ( ) => this . validateAITools ( ) ) ;
4456 this . critiqueQuestionInput . addEventListener ( 'input' , ( ) => this . validateAITools ( ) ) ;
4557 this . logoutBtn . addEventListener ( 'click' , ( ) => this . logout ( ) ) ;
58+
59+ // Post-meeting chat listeners
60+ this . postMeetingChatBtn . addEventListener ( 'click' , ( ) => this . sendPostMeetingMessage ( ) ) ;
61+ this . postMeetingMessageInput . addEventListener ( 'input' , ( ) => this . validatePostMeetingTools ( ) ) ;
62+
63+ // Saved meetings listeners
64+ this . refreshSavedMeetingsBtn . addEventListener ( 'click' , ( ) => this . loadSavedMeetings ( ) ) ;
4665 }
4766
4867 async loadAuthConfig ( ) {
@@ -57,6 +76,9 @@ class NexMeetApp {
5776 // Authentication is disabled, proceed without login
5877 this . updateStatus ( ) ;
5978 }
79+
80+ // Load saved meetings on startup
81+ this . loadSavedMeetings ( ) ;
6082 }
6183 } catch ( error ) {
6284 console . error ( 'Error loading auth config:' , error ) ;
@@ -80,6 +102,7 @@ class NexMeetApp {
80102 this . user = data . user ;
81103 this . updateUserInterface ( ) ;
82104 this . updateStatus ( ) ;
105+ this . loadSavedMeetings ( ) ;
83106 } else {
84107 // Not authenticated, redirect to login
85108 window . location . href = '/login' ;
@@ -131,6 +154,10 @@ class NexMeetApp {
131154 this . critiqueBtn . disabled = ! this . isSessionActive || ! this . critiqueQuestionInput . value . trim ( ) ;
132155 }
133156
157+ validatePostMeetingTools ( ) {
158+ this . postMeetingChatBtn . disabled = ! this . selectedMeetingFile || ! this . postMeetingMessageInput . value . trim ( ) ;
159+ }
160+
134161 async startSession ( ) {
135162 const meetingUrl = this . meetingUrlInput . value . trim ( ) ;
136163 const meetingContext = this . meetingContextInput . value . trim ( ) ;
@@ -192,6 +219,13 @@ class NexMeetApp {
192219 this . updateStatus ( ) ;
193220 this . stopTranscriptPolling ( ) ;
194221 this . clearTranscripts ( ) ;
222+
223+ // Show post-meeting chat section
224+ if ( data . savedFile ) {
225+ this . selectedMeetingFile = data . savedFile ;
226+ this . showPostMeetingChat ( ) ;
227+ this . loadSavedMeetings ( ) ; // Refresh saved meetings list
228+ }
195229 } else {
196230 this . showError ( data . error || 'Failed to stop session' ) ;
197231 }
@@ -390,13 +424,15 @@ class NexMeetApp {
390424 }
391425
392426 setLoading ( isLoading ) {
393- const elements = [ this . testBtn , this . startBtn , this . stopBtn , this . sendMessageBtn , this . critiqueBtn ] ;
427+ const elements = [ this . testBtn , this . startBtn , this . stopBtn , this . sendMessageBtn , this . critiqueBtn , this . postMeetingChatBtn ] ;
394428 elements . forEach ( btn => {
395- btn . disabled = isLoading ;
396- if ( isLoading ) {
397- btn . classList . add ( 'loading' ) ;
398- } else {
399- btn . classList . remove ( 'loading' ) ;
429+ if ( btn ) {
430+ btn . disabled = isLoading ;
431+ if ( isLoading ) {
432+ btn . classList . add ( 'loading' ) ;
433+ } else {
434+ btn . classList . remove ( 'loading' ) ;
435+ }
400436 }
401437 } ) ;
402438 }
@@ -429,6 +465,146 @@ class NexMeetApp {
429465 } , 5000 ) ;
430466 }
431467
468+ showPostMeetingChat ( ) {
469+ this . postMeetingSection . style . display = 'block' ;
470+ this . validatePostMeetingTools ( ) ;
471+ }
472+
473+ hidePostMeetingChat ( ) {
474+ this . postMeetingSection . style . display = 'none' ;
475+ this . postMeetingResponseArea . textContent = '' ;
476+ this . postMeetingMessageInput . value = '' ;
477+ }
478+
479+ async sendPostMeetingMessage ( ) {
480+ const message = this . postMeetingMessageInput . value . trim ( ) ;
481+ if ( ! message || ! this . selectedMeetingFile ) return ;
482+
483+ this . setLoading ( true ) ;
484+ this . postMeetingResponseArea . textContent = 'Generating response...' ;
485+
486+ try {
487+ const response = await fetch ( '/api/post-meeting-chat' , {
488+ method : 'POST' ,
489+ headers : { 'Content-Type' : 'application/json' } ,
490+ credentials : 'include' ,
491+ body : JSON . stringify ( {
492+ message,
493+ meetingFile : this . selectedMeetingFile
494+ } )
495+ } ) ;
496+
497+ const data = await response . json ( ) ;
498+
499+ if ( data . success ) {
500+ this . postMeetingResponseArea . textContent = data . aiResponse || '' ;
501+ this . postMeetingMessageInput . value = '' ;
502+ this . showSuccess ( 'Response generated using meeting context' ) ;
503+ } else {
504+ this . showError ( data . error || 'Failed to generate response' ) ;
505+ this . postMeetingResponseArea . textContent = '' ;
506+ }
507+ } catch ( error ) {
508+ this . showError ( 'Network error: ' + error . message ) ;
509+ this . postMeetingResponseArea . textContent = '' ;
510+ } finally {
511+ this . setLoading ( false ) ;
512+ this . validatePostMeetingTools ( ) ;
513+ }
514+ }
515+
516+ async loadSavedMeetings ( ) {
517+ try {
518+ const response = await fetch ( '/api/saved-meetings' , {
519+ credentials : 'include'
520+ } ) ;
521+
522+ if ( response . ok ) {
523+ const data = await response . json ( ) ;
524+ this . displaySavedMeetings ( data . meetings ) ;
525+ } else {
526+ console . error ( 'Failed to load saved meetings' ) ;
527+ this . savedMeetingsList . textContent = '' ;
528+ const errorMsg = document . createElement ( 'p' ) ;
529+ errorMsg . style . textAlign = 'center' ;
530+ errorMsg . style . color = '#999' ;
531+ errorMsg . textContent = 'Failed to load saved meetings' ;
532+ this . savedMeetingsList . appendChild ( errorMsg ) ;
533+ }
534+ } catch ( error ) {
535+ console . error ( 'Error loading saved meetings:' , error ) ;
536+ }
537+ }
538+
539+ displaySavedMeetings ( meetings ) {
540+ this . savedMeetingsList . textContent = '' ;
541+
542+ if ( ! meetings || meetings . length === 0 ) {
543+ const emptyMessage = document . createElement ( 'p' ) ;
544+ emptyMessage . style . textAlign = 'center' ;
545+ emptyMessage . style . color = '#999' ;
546+ emptyMessage . textContent = 'No saved meetings yet...' ;
547+ this . savedMeetingsList . appendChild ( emptyMessage ) ;
548+ return ;
549+ }
550+
551+ meetings . forEach ( meeting => {
552+ const meetingDiv = document . createElement ( 'div' ) ;
553+ meetingDiv . className = 'meeting-item' ;
554+ if ( meeting . filename === this . selectedMeetingFile ) {
555+ meetingDiv . classList . add ( 'selected' ) ;
556+ }
557+
558+ const headerDiv = document . createElement ( 'div' ) ;
559+ headerDiv . className = 'meeting-header' ;
560+ headerDiv . textContent = meeting . meetingContext || 'Meeting' ;
561+
562+ const metaDiv = document . createElement ( 'div' ) ;
563+ metaDiv . className = 'meeting-meta' ;
564+
565+ const timeDiv = document . createElement ( 'div' ) ;
566+ try {
567+ timeDiv . textContent = new Date ( meeting . startTime ) . toLocaleString ( ) ;
568+ } catch ( e ) {
569+ timeDiv . textContent = 'Invalid date' ;
570+ }
571+
572+ const statsDiv = document . createElement ( 'div' ) ;
573+ statsDiv . className = 'meeting-stats' ;
574+ statsDiv . textContent = `${ meeting . transcriptCount } transcripts, ${ meeting . aiQuestionCount } AI Q&As` ;
575+
576+ metaDiv . appendChild ( timeDiv ) ;
577+ metaDiv . appendChild ( statsDiv ) ;
578+
579+ meetingDiv . appendChild ( headerDiv ) ;
580+ meetingDiv . appendChild ( metaDiv ) ;
581+
582+ meetingDiv . addEventListener ( 'click' , ( ) => {
583+ this . selectMeeting ( meeting . filename ) ;
584+ } ) ;
585+
586+ this . savedMeetingsList . appendChild ( meetingDiv ) ;
587+ } ) ;
588+ }
589+
590+ selectMeeting ( filename ) {
591+ this . selectedMeetingFile = filename ;
592+ this . showPostMeetingChat ( ) ;
593+ this . validatePostMeetingTools ( ) ;
594+
595+ // Update visual selection
596+ const meetingItems = this . savedMeetingsList . querySelectorAll ( '.meeting-item' ) ;
597+ meetingItems . forEach ( item => item . classList . remove ( 'selected' ) ) ;
598+
599+ const selectedItem = Array . from ( meetingItems ) . find ( item =>
600+ item . textContent . includes ( filename ) ||
601+ item . onclick && item . onclick . toString ( ) . includes ( filename )
602+ ) ;
603+
604+ // Re-render to show proper selection
605+ this . loadSavedMeetings ( ) ;
606+ }
607+
432608 escapeHtml ( text ) {
433609 const div = document . createElement ( 'div' ) ;
434610 div . textContent = text ;
0 commit comments