+ ${!hasAnyContent
+ ? html`
No insights yet...
`
+ : html`
+
Current Summary
+ ${data.summary.length > 0
+ ? data.summary
+ .slice(0, 5)
+ .map(
+ (bullet, index) => html`
+
this.handleMarkdownClick(bullet)}
+ >
+ ${bullet}
+
+ `
+ )
+ : html`
No content yet...
`}
+ ${data.topic.header
+ ? html`
+
${data.topic.header}
+ ${data.topic.bullets
+ .slice(0, 3)
+ .map(
+ (bullet, index) => html`
+
this.handleMarkdownClick(bullet)}
+ >
+ ${bullet}
+
+ `
+ )}
+ `
+ : ''}
+ ${data.actions.length > 0
+ ? html`
+
Actions
+ ${data.actions
+ .slice(0, 5)
+ .map(
+ (action, index) => html`
+
this.handleMarkdownClick(action)}
+ >
+ ${action}
+
+ `
+ )}
+ `
+ : ''}
+ ${this.hasCompletedRecording && data.followUps && data.followUps.length > 0
+ ? html`
+
Follow-Ups
+ ${data.followUps.map(
+ (followUp, index) => html`
+
this.handleMarkdownClick(followUp)}
+ >
+ ${followUp}
+
+ `
+ )}
+ `
+ : ''}
+ `}
+
+ `;
+ }
+}
+
+customElements.define('summary-view', SummaryView);
\ No newline at end of file
diff --git a/src/features/listen/summary/repositories/index.js b/src/features/listen/summary/repositories/index.js
new file mode 100644
index 0000000..d5bd3b3
--- /dev/null
+++ b/src/features/listen/summary/repositories/index.js
@@ -0,0 +1,5 @@
+const summaryRepository = require('./sqlite.repository');
+
+module.exports = {
+ ...summaryRepository,
+};
\ No newline at end of file
diff --git a/src/features/listen/summary/repositories/sqlite.repository.js b/src/features/listen/summary/repositories/sqlite.repository.js
new file mode 100644
index 0000000..d7a2266
--- /dev/null
+++ b/src/features/listen/summary/repositories/sqlite.repository.js
@@ -0,0 +1,47 @@
+const sqliteClient = require('../../../../common/services/sqliteClient');
+
+function saveSummary({ sessionId, tldr, text, bullet_json, action_json, model = 'gpt-4.1' }) {
+ const db = sqliteClient.getDb();
+ return new Promise((resolve, reject) => {
+ const now = Math.floor(Date.now() / 1000);
+ const query = `
+ INSERT INTO summaries (session_id, generated_at, model, text, tldr, bullet_json, action_json, updated_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ ON CONFLICT(session_id) DO UPDATE SET
+ generated_at=excluded.generated_at,
+ model=excluded.model,
+ text=excluded.text,
+ tldr=excluded.tldr,
+ bullet_json=excluded.bullet_json,
+ action_json=excluded.action_json,
+ updated_at=excluded.updated_at
+ `;
+ db.run(query, [sessionId, now, model, text, tldr, bullet_json, action_json, now], function(err) {
+ if (err) {
+ console.error('Error saving summary:', err);
+ reject(err);
+ } else {
+ resolve({ changes: this.changes });
+ }
+ });
+ });
+}
+
+function getSummaryBySessionId(sessionId) {
+ const db = sqliteClient.getDb();
+ return new Promise((resolve, reject) => {
+ const query = "SELECT * FROM summaries WHERE session_id = ?";
+ db.get(query, [sessionId], (err, row) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(row || null);
+ }
+ });
+ });
+}
+
+module.exports = {
+ saveSummary,
+ getSummaryBySessionId,
+};
\ No newline at end of file
diff --git a/src/features/listen/summary/summaryService.js b/src/features/listen/summary/summaryService.js
new file mode 100644
index 0000000..860fa35
--- /dev/null
+++ b/src/features/listen/summary/summaryService.js
@@ -0,0 +1,356 @@
+const { BrowserWindow } = require('electron');
+const { getSystemPrompt } = require('../../../common/prompts/promptBuilder.js');
+const { createLLM } = require('../../../common/ai/factory');
+const authService = require('../../../common/services/authService');
+const sessionRepository = require('../../../common/repositories/session');
+const summaryRepository = require('./repositories');
+const { getStoredApiKey, getStoredProvider } = require('../../../electron/windowManager');
+
+class SummaryService {
+ constructor() {
+ this.previousAnalysisResult = null;
+ this.analysisHistory = [];
+ this.conversationHistory = [];
+ this.currentSessionId = null;
+
+ // Callbacks
+ this.onAnalysisComplete = null;
+ this.onStatusUpdate = null;
+ }
+
+ setCallbacks({ onAnalysisComplete, onStatusUpdate }) {
+ this.onAnalysisComplete = onAnalysisComplete;
+ this.onStatusUpdate = onStatusUpdate;
+ }
+
+ setSessionId(sessionId) {
+ this.currentSessionId = sessionId;
+ }
+
+ async getApiKey() {
+ const storedKey = await getStoredApiKey();
+ if (storedKey) {
+ console.log('[SummaryService] Using stored API key');
+ return storedKey;
+ }
+
+ const envKey = process.env.OPENAI_API_KEY;
+ if (envKey) {
+ console.log('[SummaryService] Using environment API key');
+ return envKey;
+ }
+
+ console.error('[SummaryService] No API key found in storage or environment');
+ return null;
+ }
+
+ sendToRenderer(channel, data) {
+ BrowserWindow.getAllWindows().forEach(win => {
+ if (!win.isDestroyed()) {
+ win.webContents.send(channel, data);
+ }
+ });
+ }
+
+ addConversationTurn(speaker, text) {
+ const conversationText = `${speaker.toLowerCase()}: ${text.trim()}`;
+ this.conversationHistory.push(conversationText);
+ console.log(`💬 Added conversation text: ${conversationText}`);
+ console.log(`📈 Total conversation history: ${this.conversationHistory.length} texts`);
+
+ // Trigger analysis if needed
+ this.triggerAnalysisIfNeeded();
+ }
+
+ getConversationHistory() {
+ return this.conversationHistory;
+ }
+
+ resetConversationHistory() {
+ this.conversationHistory = [];
+ this.previousAnalysisResult = null;
+ this.analysisHistory = [];
+ console.log('🔄 Conversation history and analysis state reset');
+ }
+
+ /**
+ * Converts conversation history into text to include in the prompt.
+ * @param {Array