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:
@@ -61,16 +61,15 @@ enum SpeakerEditing {
|
||||
}
|
||||
var extras = result.extras
|
||||
if let x = result.extras {
|
||||
extras = MeetingExtras(
|
||||
tldr: .init(summary: replaceWords(x.tldr.summary, map),
|
||||
primarySpeakers: exactList(x.tldr.primarySpeakers)),
|
||||
decisions: x.decisions.map { .init(statement: replaceWords($0.statement, map),
|
||||
agreedBy: exactList($0.agreedBy), supportingOffset: $0.supportingOffset) },
|
||||
actionItems: x.actionItems.map { .init(description: replaceWords($0.description, map),
|
||||
owner: exact($0.owner), dueHint: $0.dueHint, supportingOffset: $0.supportingOffset) },
|
||||
openQuestions: x.openQuestions.map { .init(question: replaceWords($0.question, map), raisedBy: exact($0.raisedBy)) },
|
||||
keyQuotes: x.keyQuotes.map { .init(speaker: exact($0.speaker), offset: $0.offset,
|
||||
quote: replaceWords($0.quote, map), whyNotable: replaceWords($0.whyNotable, map)) })
|
||||
let rendered = x.sections.map { sec in
|
||||
RenderedSection(title: sec.title, kind: sec.kind,
|
||||
bullets: sec.bullets.map { replaceWords($0, map) },
|
||||
items: sec.items.map { RecapItem(text: replaceWords($0.text, map), who: exact($0.who),
|
||||
when: $0.when, note: $0.note.map { replaceWords($0, map) }) },
|
||||
paragraph: replaceWords(sec.paragraph, map))
|
||||
}
|
||||
extras = RecapExtras(tldr: replaceWords(x.tldr, map),
|
||||
primarySpeakers: exactList(x.primarySpeakers), sections: rendered)
|
||||
}
|
||||
return RecapResult(sections: sections, extras: extras)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user