회의록: include_checklist 꺼짐일 때 회의 체크리스트 섹션 후처리 제거

Made-with: Cursor
This commit is contained in:
2026-04-15 17:23:04 +09:00
parent 046366599d
commit 27a6a2b122
3 changed files with 72 additions and 7 deletions

View File

@@ -140,6 +140,12 @@ function buildMeetingMinutesSystemPrompt(settings) {
lines.push("사용자 추가 지시:");
lines.push(custom.trim());
}
if (!includeChecklist) {
lines.push("");
lines.push(
"【출력에서 제외(최종)】`## 회의 체크리스트`, `## 후속 확인 체크리스트` 같은 체크리스트 전용 제목, 그 아래 `- [ ]`·불릿 목록, 회의 본문 끝의 괄호 메타 문장(예: 체크리스트를 마지막으로 작성)은 넣지 마세요."
);
}
return lines.join("\n");
}
@@ -368,10 +374,23 @@ function isMeetingChecklistSectionTitle(title) {
return false;
}
/**
* 모델이 덧붙이는 메타 문장(체크리스트 섹션 직후 등)
* @param {string} t
*/
function isChecklistMetaParentheticalLine(t) {
const s = String(t || "").trim();
if (!s || s.length > 200) return false;
if (!/^\(/.test(s) || !/\)$/.test(s)) return false;
if (/체크리스트/.test(s) && /(작성|마지막|섹션)/.test(s)) return true;
return false;
}
/** 체크리스트 항목 뒤에 붙는 운영/후속 안내 문단(제거 대상) */
function isPostChecklistBoilerplateLine(t) {
const s = String(t || "").trim();
if (!s) return false;
if (isChecklistMetaParentheticalLine(s)) return true;
if (/필요\s*시\s*시연/i.test(s)) return true;
if (/필요\s*시\s*위\s*액션\s*아이템별/i.test(s)) return true;
if (/피드백\s*제출\s*방식/i.test(s) && /(문서|슬랙|이메일)/i.test(s)) return true;
@@ -478,12 +497,54 @@ function removeKnownBoilerplateLines(markdown) {
}
/**
* API·저장·생성 공통: 스크립트 제거 → 체크리스트까지만 → 말미 안내 제거 → 말미 섹션(추가 메모·추가 권고 등) 제거 → 제목 승격
* `## 회의 체크리스트` 등 제목부터 다음 `##`(동일 레벨 다른 섹션) 전까지 제거. 반복 호출로 다중 블록 제거.
* @param {string} markdown
* @returns {string}
*/
function prepareMeetingMinutesForApi(markdown) {
function stripFirstMeetingChecklistSectionBlock(markdown) {
const lines = String(markdown || "").split("\n");
let start = -1;
for (let i = 0; i < lines.length; i++) {
const hm = /^(##)\s+(.+)$/.exec(lines[i].trimEnd());
if (hm && isMeetingChecklistSectionTitle(hm[2])) {
start = i;
break;
}
}
if (start < 0) return markdown;
let end = lines.length;
for (let j = start + 1; j < lines.length; j++) {
const hm = /^(##)\s+(.+)$/.exec(lines[j].trimEnd());
if (hm) {
end = j;
break;
}
}
const out = [...lines.slice(0, start), ...lines.slice(end)];
return out.join("\n").replace(/\n{3,}/g, "\n\n").trim();
}
function stripAllMeetingChecklistSectionBlocks(markdown) {
let md = String(markdown || "");
for (let k = 0; k < 24; k++) {
const next = stripFirstMeetingChecklistSectionBlock(md);
if (next === md) break;
md = next;
}
return md;
}
/**
* API·저장·생성 공통: 스크립트 제거 → (옵션) 체크리스트 섹션 삭제 → 체크리스트 이후 말미 정리 → …
* @param {string} markdown
* @param {{ omitMeetingChecklistSection?: boolean }} [options]
* @returns {string}
*/
function prepareMeetingMinutesForApi(markdown, options = {}) {
let md = stripVerbatimScriptSections(markdown);
if (options.omitMeetingChecklistSection === true) {
md = stripAllMeetingChecklistSectionBlocks(md);
}
md = stripTrailingAfterMeetingChecklistSection(md);
md = removeKnownBoilerplateLines(md);
md = stripTrailingJunkSectionsFromStart(md);
@@ -645,7 +706,7 @@ async function transcribeMeetingAudio(openai, filePath, uiModel = DEFAULT_TRANSC
* @param {string} opts.uiModel - gpt-5-mini | gpt-5.4
* @param {(m: string) => string} opts.resolveApiModel
*/
async function generateMeetingMinutes(openai, { systemPrompt, userContent, uiModel, resolveApiModel }) {
async function generateMeetingMinutes(openai, { systemPrompt, userContent, uiModel, resolveApiModel, omitMeetingChecklistSection }) {
const apiModel = resolveApiModel(uiModel || "gpt-5-mini");
const completion = await openai.chat.completions.create({
model: apiModel,
@@ -658,7 +719,7 @@ async function generateMeetingMinutes(openai, { systemPrompt, userContent, uiMod
],
});
const raw = (completion.choices?.[0]?.message?.content || "").trim();
return prepareMeetingMinutesForApi(raw);
return prepareMeetingMinutesForApi(raw, { omitMeetingChecklistSection: omitMeetingChecklistSection === true });
}
const CHECKLIST_EXTRACT_SYSTEM = `You extract actionable work items from Korean meeting minutes (Markdown).