mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-30 13:11:02 +00:00
Move meeting notes into core transcripts, remove the bundled meeting-notes plugin/API, and require explicit transcripts.enabled before exposing the recording-capable tool.
97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
import { normalizeStringEntries } from "../shared/string-normalization.js";
|
|
import type { TranscriptSessionDescriptor, TranscriptUtterance } from "./provider-types.js";
|
|
|
|
export type TranscriptsSummary = {
|
|
sessionId: string;
|
|
title: string;
|
|
generatedAt: string;
|
|
overview: string;
|
|
transcript: string[];
|
|
decisions: string[];
|
|
actionItems: string[];
|
|
risks: string[];
|
|
utteranceCount: number;
|
|
};
|
|
|
|
const ACTION_PATTERNS =
|
|
/\b(todo|action|follow up|follow-up|assign|owner|next step|ship|fix|send|schedule)\b/i;
|
|
const DECISION_PATTERNS = /\b(decided|decision|we will|we'll|agreed|approved|go with|ship it)\b/i;
|
|
const RISK_PATTERNS =
|
|
/\b(risk|blocked|blocker|concern|issue|problem|unknown|deadline|privacy|security)\b/i;
|
|
|
|
function firstSentences(utterances: TranscriptUtterance[], limit: number): string {
|
|
const text = normalizeStringEntries(utterances.map((utterance) => utterance.text)).join(" ");
|
|
const sentences = text.match(/[^.!?]+[.!?]?/g) ?? [];
|
|
return normalizeStringEntries(sentences.slice(0, limit)).join(" ");
|
|
}
|
|
|
|
function collectMatches(utterances: TranscriptUtterance[], pattern: RegExp): string[] {
|
|
return utterances
|
|
.filter((utterance) => pattern.test(utterance.text))
|
|
.map(formatSpeakerLine)
|
|
.filter(Boolean)
|
|
.slice(0, 12);
|
|
}
|
|
|
|
function formatSpeakerLine(utterance: TranscriptUtterance): string {
|
|
const text = utterance.text.trim();
|
|
if (!text) {
|
|
return "";
|
|
}
|
|
const speaker = utterance.speaker?.label?.trim();
|
|
return speaker ? `${speaker}: ${text}` : text;
|
|
}
|
|
|
|
function formatTranscript(utterances: TranscriptUtterance[]): string[] {
|
|
return utterances.map(formatSpeakerLine).filter(Boolean);
|
|
}
|
|
|
|
export function summarizeTranscripts(params: {
|
|
session: TranscriptSessionDescriptor;
|
|
utterances: TranscriptUtterance[];
|
|
}): TranscriptsSummary {
|
|
const title = params.session.title?.trim() || "Transcripts";
|
|
const overview = firstSentences(params.utterances, 4) || "No transcript captured yet.";
|
|
return {
|
|
sessionId: params.session.sessionId,
|
|
title,
|
|
generatedAt: new Date().toISOString(),
|
|
overview,
|
|
transcript: formatTranscript(params.utterances),
|
|
decisions: collectMatches(params.utterances, DECISION_PATTERNS),
|
|
actionItems: collectMatches(params.utterances, ACTION_PATTERNS),
|
|
risks: collectMatches(params.utterances, RISK_PATTERNS),
|
|
utteranceCount: params.utterances.length,
|
|
};
|
|
}
|
|
|
|
function renderList(items: string[]): string {
|
|
return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : "- None captured";
|
|
}
|
|
|
|
export function renderTranscriptsMarkdown(summary: TranscriptsSummary): string {
|
|
return [
|
|
`# ${summary.title}`,
|
|
"",
|
|
`Generated: ${summary.generatedAt}`,
|
|
`Session: ${summary.sessionId}`,
|
|
"",
|
|
"## Overview",
|
|
summary.overview,
|
|
"",
|
|
"## Transcript",
|
|
renderList(summary.transcript),
|
|
"",
|
|
"## Decisions",
|
|
renderList(summary.decisions),
|
|
"",
|
|
"## Action Items",
|
|
renderList(summary.actionItems),
|
|
"",
|
|
"## Risks",
|
|
renderList(summary.risks),
|
|
"",
|
|
`Transcript utterances: ${summary.utteranceCount}`,
|
|
].join("\n");
|
|
}
|