meeting summary notes

This commit is contained in:
Arjun 2026-03-17 17:10:16 +05:30
parent 537ca08fe5
commit 49c004b53e
5 changed files with 80 additions and 2 deletions

View file

@ -41,6 +41,7 @@ import { search } from '@x/core/dist/search/search.js';
import { versionHistory, voice } from '@x/core';
import { classifySchedule } from '@x/core/dist/knowledge/inline_tasks.js';
import { getBillingInfo } from '@x/core/dist/billing/billing.js';
import { summarizeMeeting } from '@x/core/dist/knowledge/summarize_meeting.js';
/**
* Convert markdown to a styled HTML document for PDF/DOCX export.
@ -701,6 +702,10 @@ export function setupIpcHandlers() {
return { success: false, error: 'Unknown format' };
},
'meeting:summarize': async (_event, args) => {
const notes = await summarizeMeeting(args.transcript);
return { notes };
},
'inline-task:classifySchedule': async (_event, args) => {
const schedule = await classifySchedule(args.instruction);
return { schedule };

View file

@ -3343,12 +3343,46 @@ function App() {
navigateToFile(notePath)
}, [loadDirectory, navigateToFile, fileTabs])
const meetingNotePathRef = useRef<string | null>(null)
const handleToggleMeeting = useCallback(async () => {
if (meetingTranscription.state === 'recording') {
await meetingTranscription.stop()
// Read the final transcript and generate meeting notes via LLM
const notePath = meetingNotePathRef.current
if (notePath) {
try {
const result = await window.ipc.invoke('workspace:readFile', { path: notePath, encoding: 'utf8' })
const fileContent = result.data
if (fileContent && fileContent.trim()) {
// Call LLM to summarize the transcript
const { notes } = await window.ipc.invoke('meeting:summarize', { transcript: fileContent })
if (notes) {
// Prepend meeting notes below the title but above the transcript
const { raw: fm, body: transcriptBody } = splitFrontmatter(fileContent)
// Strip the "# Meeting note" title from transcript body — we'll put it first
const bodyWithoutTitle = transcriptBody.replace(/^#\s+Meeting note\s*\n*/, '')
const newBody = '# Meeting note\n\n' + notes + '\n\n---\n\n## Raw transcript\n\n' + bodyWithoutTitle
const newContent = fm ? `${fm}\n${newBody}` : newBody
await window.ipc.invoke('workspace:writeFile', {
path: notePath,
data: newContent,
opts: { encoding: 'utf8' },
})
// Refresh the file view
await handleVoiceNoteCreated(notePath)
}
}
} catch (err) {
console.error('[meeting] Failed to generate meeting notes:', err)
}
meetingNotePathRef.current = null
}
} else if (meetingTranscription.state === 'idle') {
const notePath = await meetingTranscription.start()
if (notePath) {
meetingNotePathRef.current = notePath
await handleVoiceNoteCreated(notePath)
}
}

View file

@ -48,11 +48,11 @@ function formatTranscript(entries: TranscriptEntry[], date: string): string {
'---',
'type: meeting',
'source: rowboat',
'title: Meeting Transcription',
'title: Meeting note',
`date: "${date}"`,
'---',
'',
'# Meeting Transcription',
'# Meeting note',
'',
];
for (let i = 0; i < entries.length; i++) {

View file

@ -0,0 +1,31 @@
import { generateText } from 'ai';
import container from '../di/container.js';
import type { IModelConfigRepo } from '../models/repo.js';
import { createProvider } from '../models/models.js';
const SYSTEM_PROMPT = `You are a meeting notes assistant. Given a raw meeting transcript, create concise, well-organized meeting notes.
Format rules:
- Use ### for section headers that group related discussion topics
- Section headers should be in sentence case (e.g. "### Onboarding flow status"), NOT Title Case
- Use bullet points with sub-bullets for details
- Include a "### Action items" section at the end if any were discussed
- Focus on decisions, key discussions, and takeaways not verbatim quotes
- Attribute statements to speakers when relevant (use their names/labels from the transcript)
- Keep it concise the notes should be much shorter than the transcript
- Output markdown only, no preamble or explanation`;
export async function summarizeMeeting(transcript: string): Promise<string> {
const repo = container.resolve<IModelConfigRepo>('modelConfigRepo');
const config = await repo.getConfig();
const provider = createProvider(config.provider);
const model = provider.languageModel(config.model);
const result = await generateText({
model,
system: SYSTEM_PROMPT,
prompt: transcript,
});
return result.text.trim();
}

View file

@ -498,6 +498,14 @@ const ipcSchemas = {
token: z.string(),
}).nullable(),
},
'meeting:summarize': {
req: z.object({
transcript: z.string(),
}),
res: z.object({
notes: z.string(),
}),
},
// Inline task schedule classification
'export:note': {
req: z.object({