Skip to content

Commit ec42aba

Browse files
committed
Added post-meeting chats & meeting logs
1 parent f2bfc2f commit ec42aba

File tree

7 files changed

+506
-13
lines changed

7 files changed

+506
-13
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ vite.config.ts.timestamp-*
138138

139139
# NexMeet specific files
140140
# Database files
141+
transcripts/
141142
data/
142143
*.db
143144
*.sqlite

public/app.js

Lines changed: 182 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

public/index.html

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,45 @@
215215
border-radius: 8px;
216216
margin-bottom: 15px;
217217
}
218+
219+
.meeting-item {
220+
background: white;
221+
border: 2px solid #e1e5e9;
222+
border-radius: 8px;
223+
padding: 15px;
224+
margin-bottom: 10px;
225+
cursor: pointer;
226+
transition: border-color 0.3s, background-color 0.3s;
227+
}
228+
229+
.meeting-item:hover {
230+
border-color: #667eea;
231+
background-color: #f8f9ff;
232+
}
233+
234+
.meeting-item.selected {
235+
border-color: #667eea;
236+
background-color: #e8f0fe;
237+
}
238+
239+
.meeting-header {
240+
font-weight: 600;
241+
color: #333;
242+
margin-bottom: 5px;
243+
}
244+
245+
.meeting-meta {
246+
font-size: 0.9rem;
247+
color: #666;
248+
display: flex;
249+
justify-content: space-between;
250+
align-items: center;
251+
}
252+
253+
.meeting-stats {
254+
font-size: 0.85rem;
255+
color: #999;
256+
}
218257
</style>
219258
</head>
220259
<body>
@@ -288,6 +327,32 @@ <h3>🧠 AI Tools</h3>
288327
</div>
289328
</div>
290329

330+
<!-- Post-Meeting Chat Section -->
331+
<div class="card" id="postMeetingSection" style="display: none;">
332+
<h3>💬 Post-Meeting Chat</h3>
333+
<p style="color: #666; margin-bottom: 15px;">Chat with AI about your completed meeting using full meeting context</p>
334+
335+
<div class="form-group">
336+
<label for="postMeetingMessage">Ask AI about the meeting</label>
337+
<textarea id="postMeetingMessage" placeholder="What were the main action items? Can you summarize the key decisions made?"></textarea>
338+
<button id="postMeetingChatBtn" class="btn">🗣️ Chat with AI</button>
339+
</div>
340+
341+
<div id="postMeetingResponse" class="response-area"></div>
342+
</div>
343+
344+
<!-- Saved Meetings Section -->
345+
<div class="card">
346+
<h3>📚 Saved Meetings</h3>
347+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
348+
<p style="color: #666;">Access transcripts and chat about previous meetings</p>
349+
<button id="refreshSavedMeetings" class="btn">🔄 Refresh</button>
350+
</div>
351+
<div id="savedMeetingsList" class="transcript-container">
352+
<p style="text-align: center; color: #999;">No saved meetings yet...</p>
353+
</div>
354+
</div>
355+
291356
<div class="card">
292357
<h3>ℹ️ How to Use</h3>
293358
<ol style="line-height: 1.6;">
@@ -297,6 +362,9 @@ <h3>ℹ️ How to Use</h3>
297362
<li><strong>Ask Questions:</strong> Once active, ask questions in the chat and the AI will respond with context-aware answers</li>
298363
<li><strong>Deactivate:</strong> Type "Thanks AI" in the chat to return to passive mode</li>
299364
<li><strong>Manual Control:</strong> Use the tools on the right to send messages or generate meeting critiques</li>
365+
<li><strong>End Meeting:</strong> Click "Stop Session" to end the meeting and automatically save all transcripts and AI conversations</li>
366+
<li><strong>Post-Meeting Chat:</strong> After ending a meeting, use the post-meeting chat to ask questions with full context</li>
367+
<li><strong>Saved Meetings:</strong> Access previous meetings from the saved meetings section to review or chat about them</li>
300368
</ol>
301369
</div>
302370
</div>

src/handlers/transcriptHandler.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,16 @@ class TranscriptHandler {
145145
const botManager = require('../services/botManager');
146146
const aiService = require('../services/aiService');
147147
const contextManager = require('../services/contextManager');
148+
const transcriptStorage = require('../services/transcriptStorageService');
148149

149150
contextManager.addQuestion(question, asker);
150151

151152
const context = this.buildContext(appState);
152153
const response = await aiService.generateResponse(question, context);
153154

155+
// Save the AI conversation
156+
transcriptStorage.addAIConversation(question, response, asker);
157+
154158
await botManager.sendMessage(appState.currentBot.id, response);
155159

156160
logger.logInfo(`AI responded to question from ${asker}: ${question}`);

0 commit comments

Comments
 (0)