Files
a-legacy-forged-obsid-sync/Templates/Generate Session Summary.md
T

3.9 KiB

<%* /* PF2e AI Session Summary — FULL NOTE MODE (single-string call)

  • Uses the whole note body (not just bullets)
  • Strips frontmatter, code fences, and prior summaries/history
  • Lets you pick Short / Standard / Detailed length */

const file = app.workspace.getActiveFile(); if (!file) { new Notice("Open a session note first."); return; }

/* ---------- Read & sanitize whole note ---------- */ let body = await app.vault.read(file);

// strip YAML frontmatter body = body.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");

// strip prior summaries & history blocks if present body = body.replace(/[\s\S]?/gi, ""); body = body.replace(/[\s\S]?/gi, "");

// strip code fences & inline dataview body = body.replace(/[\s\S]*?/g, ""); body = body.replace(/=\s*[^]+`/g, "");

// normalize whitespace body = body.replace(/\r/g, "").trim();

// If the note is very long, cap input (you can raise this safely) const MAX_INPUT_CHARS = 40000; if (body.length > MAX_INPUT_CHARS) body = body.slice(0, MAX_INPUT_CHARS);

/* ---------- Metadata ---------- */ const cache = app.metadataCache.getFileCache(file) || {}; const fm = cache.frontmatter || {}; const seshName = fm.Session_Name || tp.file.title || file.basename; const seshDate = fm.Date || tp.date.now("YYYY-MM-DD");

/* ---------- Length selector ---------- */ const lenPick = await tp.system.suggester( ["Short (~6 bullets)","Standard (~12 bullets)","Detailed (~20 bullets)"], ["short","standard","detailed"] ) ?? "standard"; const bulletTarget = (lenPick === "short") ? 6 : (lenPick === "detailed") ? 20 : 12;

/* ---------- Build single-string prompt ---------- */ const prompt = `You are a concise Pathfinder 2e GM assistant. Summarize the FULL session note below.

Return ONLY: One-line recap (bold)

  • Key beats (bulleted, chronological, ${bulletTarget} bullets max)
  • NPCs & factions touched (preserve existing wikilinks verbatim if present)
  • Encounters/checks/outcomes (brief)
  • Loot/rewards/XP (brief)
  • Next hooks/to-dos (bullets)

DO NOT echo raw headings, “Highlight::”, or placeholder text. Cover ALL major content, not just bullet lists.

Session: "${seshName}" on ${seshDate}

=== NOTE START === ${body} === NOTE END ===`;

/* ---------- Call AI (string-only) ---------- */ let summary = ""; try { const resp = await tp.ai.chat(prompt); summary = (typeof resp === "string") ? resp : (resp?.content ?? resp?.text ?? ""); } catch (e) { console.error("AI error:", e); }

/* ---------- Fallback if model returns empty ---------- / if (!summary || !summary.trim()) { // Build a tidy, non-noisy fallback from the whole note (no headings/labels) const lines = body .split("\n") .map(s => s.trim()) .filter(s => s && !/^#{1,6}\s/.test(s)) // no markdown headings .filter(s => !/^**[^]+:**$/.test(s)) // no bold-only section labels .filter(s => !/^Highlight::/i.test(s)) // no "Highlight::" .filter(s => !/^Round\s+\d+/i.test(s)) // no "Round X" .filter(s => s !== " "); // no empty wikilinks

const oneLine = **${seshName} — ${seshDate}:** ${lines[0] || "Session recap unavailable."}; const bullets = lines.slice(1, 1 + Math.min(20, bulletTarget + 4)).map(s => - ${s}); summary = [oneLine, ...bullets].join("\n"); }

/* ---------- Inject between SUMMARY markers ---------- */ const START = ""; const END = ""; let content = await app.vault.read(file); const sIdx = content.indexOf(START), eIdx = content.indexOf(END); if (sIdx === -1 || eIdx === -1 || eIdx < sIdx) { new Notice("Could not find SUMMARY markers."); return; }

const before = content.slice(0, sIdx + START.length); const after = content.slice(eIdx); await app.vault.modify(file, before + "\n" + summary.trim() + "\n" + after); new Notice("AI session summary generated."); %>