import type { MeetingVO } from "../../api/business/meeting"; export type AnalysisChapter = { time?: string; title: string; summary: string; }; export type AnalysisSpeakerSummary = { speaker: string; summary: string; }; export type AnalysisKeyPoint = { title: string; summary: string; speaker?: string; time?: string; }; export type MeetingAnalysis = { overview: string; keywords: string[]; chapters: AnalysisChapter[]; speakerSummaries: AnalysisSpeakerSummary[]; keyPoints: AnalysisKeyPoint[]; todos: string[]; }; export const ANALYSIS_EMPTY: MeetingAnalysis = { overview: "", keywords: [], chapters: [], speakerSummaries: [], keyPoints: [], todos: [], }; const splitLines = (value?: string | null) => (value || "") .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean); const parseLooseJson = (raw?: string | null) => { const input = (raw || "").trim(); if (!input) return null; const tryParse = (text: string) => { try { return JSON.parse(text); } catch { return null; } }; const direct = tryParse(input); if (direct && typeof direct === "object") return direct; const fenced = input.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]?.trim(); if (fenced) { const fencedParsed = tryParse(fenced); if (fencedParsed && typeof fencedParsed === "object") return fencedParsed; } const start = input.indexOf("{"); const end = input.lastIndexOf("}"); if (start >= 0 && end > start) { const wrapped = tryParse(input.slice(start, end + 1)); if (wrapped && typeof wrapped === "object") return wrapped; } return null; }; const extractSection = (markdown: string, aliases: string[]) => { const lines = markdown.split(/\r?\n/); const lowerAliases = aliases.map((item) => item.toLowerCase()); const cleanHeading = (line: string) => line.replace(/^#{1,6}\s*/, "").trim().toLowerCase(); let start = -1; for (let index = 0; index < lines.length; index += 1) { const line = lines[index].trim(); if (!line.startsWith("#")) continue; const heading = cleanHeading(line); if (lowerAliases.some((alias) => heading.includes(alias))) { start = index + 1; break; } } if (start < 0) return ""; const buffer: string[] = []; for (let index = start; index < lines.length; index += 1) { const line = lines[index]; if (line.trim().startsWith("#")) break; buffer.push(line); } return buffer.join("\n").trim(); }; const parseBulletList = (content?: string | null) => splitLines(content) .map((line) => line.replace(/^[-*•\s]+/, "").replace(/^\d+[.)]\s*/, "").trim()) .filter(Boolean); const parseOverviewSection = (markdown: string) => extractSection(markdown, ["全文概要", "概要", "摘要", "概览"]) || markdown.replace(/^---[\s\S]*?---/, "").trim(); const parseKeywordsSection = (markdown: string, tags: string) => { const section = extractSection(markdown, ["关键词", "关键字", "标签"]); const fromSection = parseBulletList(section) .flatMap((line) => line.split(/[,,、/]/)) .map((item) => item.trim()) .filter(Boolean); if (fromSection.length) { return Array.from(new Set(fromSection)).slice(0, 12); } return Array.from(new Set((tags || "").split(",").map((item) => item.trim()).filter(Boolean))).slice(0, 12); }; export const buildMeetingAnalysis = ( sourceAnalysis: MeetingVO["analysis"] | undefined, summaryContent: string | undefined, tags: string, ): MeetingAnalysis => { const parseStructured = (parsed: Record): MeetingAnalysis => { const chapters = Array.isArray(parsed.chapters) ? parsed.chapters : []; const speakerSummaries = Array.isArray(parsed.speakerSummaries) ? parsed.speakerSummaries : []; const keyPoints = Array.isArray(parsed.keyPoints) ? parsed.keyPoints : []; const todos = Array.isArray(parsed.todos) ? parsed.todos : Array.isArray(parsed.actionItems) ? parsed.actionItems : []; return { overview: String(parsed.overview || "").trim(), keywords: Array.from( new Set((Array.isArray(parsed.keywords) ? parsed.keywords : []).map((item) => String(item).trim()).filter(Boolean)), ).slice(0, 12), chapters: chapters .map((item: any) => ({ time: item?.time ? String(item.time).trim() : undefined, title: String(item?.title || "").trim(), summary: String(item?.summary || "").trim(), })) .filter((item: AnalysisChapter) => item.title || item.summary), speakerSummaries: speakerSummaries .map((item: any) => ({ speaker: String(item?.speaker || "").trim(), summary: String(item?.summary || "").trim(), })) .filter((item: AnalysisSpeakerSummary) => item.speaker || item.summary), keyPoints: keyPoints .map((item: any) => ({ title: String(item?.title || "").trim(), summary: String(item?.summary || "").trim(), speaker: item?.speaker ? String(item.speaker).trim() : undefined, time: item?.time ? String(item.time).trim() : undefined, })) .filter((item: AnalysisKeyPoint) => item.title || item.summary), todos: todos.map((item: any) => String(item).trim()).filter(Boolean).slice(0, 10), }; }; if (sourceAnalysis) { return parseStructured(sourceAnalysis as Record); } const raw = (summaryContent || "").trim(); if (!raw && !tags) return ANALYSIS_EMPTY; const loose = parseLooseJson(raw); if (loose) { return parseStructured(loose); } return { overview: parseOverviewSection(raw), keywords: parseKeywordsSection(raw, tags), chapters: [], speakerSummaries: [], keyPoints: [], todos: [], }; };