146 lines
3.8 KiB
TypeScript
146 lines
3.8 KiB
TypeScript
import type {
|
|
LegacyTranscriptionSegment,
|
|
MeetingSummaryV2,
|
|
NormalizedTranscriptSegment,
|
|
TranscriptionLegacyProjection,
|
|
TranscriptionPayloadV2,
|
|
} from './types';
|
|
|
|
export type RawTranscriptSegment = {
|
|
sliceIndex: number;
|
|
speaker: string;
|
|
startSec: number;
|
|
endSec: number;
|
|
text: string;
|
|
};
|
|
|
|
function formatSection(title: string, items: string[]) {
|
|
if (!items.length) {
|
|
return [];
|
|
}
|
|
|
|
return [`## ${title}`, ...items.map(item => `- ${item}`)];
|
|
}
|
|
|
|
export function formatTranscriptTime(time: number) {
|
|
const safeTime = Math.max(0, time);
|
|
const totalSeconds = Math.floor(safeTime);
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = totalSeconds % 60;
|
|
|
|
return [hours, minutes, seconds]
|
|
.map(part => String(part).padStart(2, '0'))
|
|
.join(':');
|
|
}
|
|
|
|
export function normalizeTranscriptSegments(
|
|
rawSegments: RawTranscriptSegment[]
|
|
): NormalizedTranscriptSegment[] {
|
|
const normalized: NormalizedTranscriptSegment[] = [];
|
|
const dedupe = new Set<string>();
|
|
const sorted = [...rawSegments].sort((left, right) => {
|
|
return (
|
|
left.startSec - right.startSec ||
|
|
left.endSec - right.endSec ||
|
|
left.sliceIndex - right.sliceIndex
|
|
);
|
|
});
|
|
|
|
for (const segment of sorted) {
|
|
const text = segment.text.trim();
|
|
if (!text) continue;
|
|
|
|
const startSec = Math.max(
|
|
normalized.at(-1)?.endSec ?? 0,
|
|
segment.startSec,
|
|
0
|
|
);
|
|
const endSec = Math.max(segment.endSec, startSec);
|
|
if (endSec <= startSec) continue;
|
|
|
|
const speaker = segment.speaker.trim() || 'Speaker';
|
|
const key = `${speaker}|${startSec}|${endSec}|${text}`;
|
|
if (dedupe.has(key)) continue;
|
|
|
|
dedupe.add(key);
|
|
normalized.push({
|
|
speaker,
|
|
startSec,
|
|
endSec,
|
|
start: formatTranscriptTime(startSec),
|
|
end: formatTranscriptTime(endSec),
|
|
text,
|
|
});
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
export function buildNormalizedTranscript(
|
|
segments: NormalizedTranscriptSegment[]
|
|
) {
|
|
return segments
|
|
.map(segment => `${segment.start} ${segment.speaker}: ${segment.text}`)
|
|
.join('\n')
|
|
.trim();
|
|
}
|
|
|
|
export function toLegacyTranscriptionSegments(
|
|
segments: NormalizedTranscriptSegment[]
|
|
): LegacyTranscriptionSegment[] {
|
|
return segments.map(segment => ({
|
|
speaker: segment.speaker,
|
|
start: segment.start,
|
|
end: segment.end,
|
|
transcription: segment.text,
|
|
}));
|
|
}
|
|
|
|
export function summaryToMarkdown(summaryJson?: MeetingSummaryV2 | null) {
|
|
if (!summaryJson) {
|
|
return null;
|
|
}
|
|
|
|
const lines = [
|
|
...formatSection('Key Points', summaryJson.keyPoints),
|
|
...formatSection('Decisions', summaryJson.decisions),
|
|
...formatSection('Open Questions', summaryJson.openQuestions),
|
|
...formatSection('Blockers', summaryJson.blockers),
|
|
].filter(Boolean);
|
|
|
|
const markdown = lines.join('\n').trim();
|
|
return markdown.length ? markdown : null;
|
|
}
|
|
|
|
export function actionItemsToMarkdown(summaryJson?: MeetingSummaryV2 | null) {
|
|
if (!summaryJson?.actionItems.length) {
|
|
return null;
|
|
}
|
|
|
|
const markdown = summaryJson.actionItems
|
|
.map(item => {
|
|
const suffix = [item.owner, item.deadline].filter(Boolean).join(' · ');
|
|
return `- [ ] ${item.description}${suffix ? ` (${suffix})` : ''}`;
|
|
})
|
|
.join('\n')
|
|
.trim();
|
|
|
|
return markdown.length ? markdown : null;
|
|
}
|
|
|
|
export function buildLegacyProjection(
|
|
payload: Pick<TranscriptionPayloadV2, 'normalizedSegments' | 'summaryJson'>
|
|
): TranscriptionLegacyProjection {
|
|
const normalizedSegments = payload.normalizedSegments ?? [];
|
|
|
|
return {
|
|
title: payload.summaryJson?.title ?? null,
|
|
summary: summaryToMarkdown(payload.summaryJson),
|
|
actions: actionItemsToMarkdown(payload.summaryJson),
|
|
transcription: normalizedSegments.length
|
|
? toLegacyTranscriptionSegments(normalizedSegments)
|
|
: null,
|
|
};
|
|
}
|