Configurable recap templates (categories per meeting type, in Settings)
Takeaways categories are no longer hardcoded — they're editable templates. A
template = the always-on TLDR + an ordered list of sections, each with a title, a
type (attributed items / bulleted list / paragraph), and an instruction (the prompt
text for that category). The analyzer assembles the LLM prompt FROM the template
and parses generically, so adding/removing/renaming a category needs zero code and
the output always renders.
- RecapTemplate / TemplateSection / SectionKind + TopicGranularity; built-in
defaults (Internal Meeting, 1:1, Company/Sales Call), all editable.
- Generic extras: RecapExtras{tldr, primarySpeakers, sections:[RenderedSection]} +
RecapItem{text,who,when,note} replaces the fixed MeetingExtras. Analyzer builds
per-section sec_N fields + parses by kind; renderer + remap are generic.
- Topic granularity (coarse/auto/fine) answers 'should chunking be configurable' —
it scales the target topic count; raw window sizes stay as tuned defaults.
- AppSettings persists templates + defaultTemplateId (seeded once). Settings gets a
default-template picker + 'Manage…' → TemplatesView (CRUD, edit sections/
instructions, set default, **Preview prompt** for full transparency).
- Recap editor gains a template picker; Regenerate uses the chosen template. Auto
recap uses the default template.
54/54 XCTest (template prompt build, generic parse/remap/render updated).
This commit is contained in:
@@ -9,32 +9,46 @@ struct TopicSection: Equatable, Codable {
|
||||
var endIndex: Int
|
||||
}
|
||||
|
||||
/// Structured "meeting extras" extracted from the named transcript. Mirrors
|
||||
/// recap-relay's schema; speakers are real names (we already have them from
|
||||
/// label-merge), not anonymous cluster ids.
|
||||
struct MeetingExtras: Equatable, Codable {
|
||||
struct TLDR: Equatable, Codable { var summary: String; var primarySpeakers: [String] }
|
||||
struct Decision: Equatable, Codable { var statement: String; var agreedBy: [String]; var supportingOffset: Int? }
|
||||
struct ActionItem: Equatable, Codable { var description: String; var owner: String?; var dueHint: String?; var supportingOffset: Int? }
|
||||
struct OpenQuestion: Equatable, Codable { var question: String; var raisedBy: String? }
|
||||
struct KeyQuote: Equatable, Codable { var speaker: String?; var offset: Int?; var quote: String; var whyNotable: String }
|
||||
/// Generic, template-driven takeaways extracted from the named transcript: a TLDR
|
||||
/// plus an ordered list of sections whose categories come from the active
|
||||
/// `RecapTemplate` (Settings), not from code. Speakers are real names (label-merge).
|
||||
struct RecapExtras: Equatable, Codable {
|
||||
var tldr: String
|
||||
var primarySpeakers: [String]
|
||||
var sections: [RenderedSection]
|
||||
|
||||
var tldr: TLDR
|
||||
var decisions: [Decision]
|
||||
var actionItems: [ActionItem]
|
||||
var openQuestions: [OpenQuestion]
|
||||
var keyQuotes: [KeyQuote]
|
||||
var isEmpty: Bool { tldr.isEmpty && sections.allSatisfy { $0.isEmpty } }
|
||||
}
|
||||
|
||||
var isEmptyBeyondTLDR: Bool {
|
||||
decisions.isEmpty && actionItems.isEmpty && openQuestions.isEmpty && keyQuotes.isEmpty
|
||||
/// One rendered takeaways section. Only the field matching `kind` is populated.
|
||||
struct RenderedSection: Equatable, Codable {
|
||||
var title: String
|
||||
var kind: SectionKind
|
||||
var bullets: [String]
|
||||
var items: [RecapItem]
|
||||
var paragraph: String
|
||||
|
||||
var isEmpty: Bool { bullets.isEmpty && items.isEmpty && paragraph.isEmpty }
|
||||
|
||||
init(title: String, kind: SectionKind, bullets: [String] = [], items: [RecapItem] = [], paragraph: String = "") {
|
||||
self.title = title; self.kind = kind; self.bullets = bullets; self.items = items; self.paragraph = paragraph
|
||||
}
|
||||
}
|
||||
|
||||
/// A single attributed point: the statement plus optional speaker / timestamp / note.
|
||||
/// Subsumes decisions, action items, questions, quotes, etc.
|
||||
struct RecapItem: Equatable, Codable {
|
||||
var text: String
|
||||
var who: String?
|
||||
var when: Int? // seconds
|
||||
var note: String?
|
||||
}
|
||||
|
||||
/// The assembled recap for one session: the topic sections + (optional) extras,
|
||||
/// over the session's transcript. Rendered to `transcript.md` / `recap.html`.
|
||||
struct RecapResult: Equatable, Codable {
|
||||
var sections: [TopicSection]
|
||||
var extras: MeetingExtras?
|
||||
var extras: RecapExtras?
|
||||
}
|
||||
|
||||
/// Persisted `recap.json` — the recap result plus its title, so the speaker editor
|
||||
|
||||
Reference in New Issue
Block a user