/** * 회의록 Markdown에서 업무 체크리스트 툴팁용 짧은 요약 추출 (저장 시 summary_text 보조용) */ function stripMd(s) { if (!s) return ""; return s .replace(/\r\n/g, "\n") .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/__([^_]+)__/g, "$1") .replace(/^[-*•]\s+/gm, "") .replace(/^\d+\.\s+/gm, "") .replace(/\s+/g, " ") .trim(); } function truncate(s, maxLen) { const t = (s || "").trim(); if (t.length <= maxLen) return t; return t.slice(0, maxLen - 1).trim() + "…"; } /** 체크리스트/액션 등 툴팁에 부적합한 섹션 제목 */ const SKIP_SECTION = /체크리스트|액션|후속\s*확인|참석|결정\s*사항|action\s*items/i; /** 요약으로 쓰기 좋은 섹션 제목 */ const PREFERRED = /요약|개요|핵심|summary|논의\s*안건|회의\s*내용|discussion/i; /** * @param {string} markdown * @param {number} [maxLen] * @returns {string} */ function extractMeetingSummary(markdown, maxLen = 800) { const md = (markdown || "").trim(); if (!md) return ""; const lines = md.split(/\r?\n/); /** @type {{ h: string, body: string }[]} */ const sections = []; for (let i = 0; i < lines.length; ) { const line = lines[i]; const hm = line.match(/^#{1,6}\s+(.+)$/); if (!hm) { i++; continue; } const heading = hm[1].replace(/\*\*/g, "").trim(); const body = []; let j = i + 1; while (j < lines.length) { const tj = lines[j].trim(); if (/^#{1,6}\s+/.test(tj)) break; body.push(lines[j]); j++; } const joined = body.join("\n").trim(); if (joined.length >= 4) sections.push({ h: heading, body: joined }); i = j; } const preferred = sections.find((s) => PREFERRED.test(s.h) && !SKIP_SECTION.test(s.h)); if (preferred) return truncate(stripMd(preferred.body), maxLen); const firstOk = sections.find((s) => !SKIP_SECTION.test(s.h) && s.body.length >= 12); if (firstOk) return truncate(stripMd(firstOk.body), maxLen); let plain = md.replace(/^#{1,6}\s+.*$/gm, "").trim(); plain = plain.replace(/^[-*•]\s+.*$/gm, "").trim(); if (plain.length < 30) plain = stripMd(md); return truncate(stripMd(plain), maxLen); } module.exports = { extractMeetingSummary, stripMd, truncate, };