Files
meeting-assistant/MeetingAssistant/MeetingNotes/MarkdownMeetingArtifactStore.cs
T
2026-05-20 02:06:16 +02:00

124 lines
4.5 KiB
C#

namespace MeetingAssistant.MeetingNotes;
using YamlDotNet.Serialization;
public sealed class MarkdownMeetingArtifactStore : IMeetingArtifactStore
{
private static readonly IDeserializer YamlDeserializer = new DeserializerBuilder()
.IgnoreUnmatchedProperties()
.Build();
private readonly ILogger<MarkdownMeetingArtifactStore> logger;
public MarkdownMeetingArtifactStore(ILogger<MarkdownMeetingArtifactStore> logger)
{
this.logger = logger;
}
public async Task CreateAssistantContextAsync(
MeetingSessionArtifacts artifacts,
string agenda,
DateTimeOffset? scheduledEnd,
CancellationToken cancellationToken)
{
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
var content = Render(
artifacts,
AssistantContextState.Transcribing,
agenda,
scheduledEnd,
"# Assistant Context" + Environment.NewLine + Environment.NewLine + "## Live Context" + Environment.NewLine);
await File.WriteAllTextAsync(artifacts.AssistantContextPath, content, cancellationToken);
logger.LogInformation("Created assistant context note {AssistantContextPath}", artifacts.AssistantContextPath);
}
public async Task UpdateAssistantContextStateAsync(
MeetingSessionArtifacts artifacts,
AssistantContextState state,
CancellationToken cancellationToken)
{
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
var existing = File.Exists(artifacts.AssistantContextPath)
? MarkdownDocumentParser.SplitOptional(await File.ReadAllTextAsync(artifacts.AssistantContextPath, cancellationToken))
: new MarkdownDocument(false, "", "## Live Context" + Environment.NewLine);
var body = existing.Body;
var existingFrontmatter = ReadFrontmatter(existing.Frontmatter);
var agenda = existingFrontmatter.Agenda ?? "";
var scheduledEnd = ParseDateTime(existingFrontmatter.ScheduledEnd);
await File.WriteAllTextAsync(
artifacts.AssistantContextPath,
Render(artifacts, state, agenda, scheduledEnd, body),
cancellationToken);
logger.LogInformation(
"Updated assistant context note {AssistantContextPath} state to {State}",
artifacts.AssistantContextPath,
ToYamlState(state));
}
private static string Render(
MeetingSessionArtifacts artifacts,
AssistantContextState state,
string? agenda,
DateTimeOffset? scheduledEnd,
string body)
{
var frontmatter = new MeetingArtifactFrontmatter
{
Meeting = ObsidianLink.ToWikiLink(artifacts.MeetingNotePath, artifacts.AssistantContextPath, "Meeting Note"),
Transcript = ObsidianLink.ToWikiLink(artifacts.TranscriptPath, artifacts.AssistantContextPath, "Transcript"),
Summary = ObsidianLink.ToWikiLink(artifacts.SummaryPath, artifacts.AssistantContextPath, "Summary"),
State = ToYamlState(state),
Agenda = agenda ?? "",
ScheduledEnd = scheduledEnd
};
return MeetingArtifactFrontmatterRenderer.Render(
frontmatter,
body);
}
private static AssistantContextFrontmatterYaml ReadFrontmatter(string frontmatter)
{
if (string.IsNullOrWhiteSpace(frontmatter))
{
return new AssistantContextFrontmatterYaml();
}
return YamlDeserializer.Deserialize<AssistantContextFrontmatterYaml>(frontmatter)
?? new AssistantContextFrontmatterYaml();
}
private static DateTimeOffset? ParseDateTime(string? value)
{
return DateTimeOffset.TryParse(value, out var parsed)
? parsed
: null;
}
private sealed class AssistantContextFrontmatterYaml
{
[YamlMember(Alias = "agenda")]
public string? Agenda { get; set; }
[YamlMember(Alias = "scheduled_end")]
public string? ScheduledEnd { get; set; }
}
private static string ToYamlState(AssistantContextState state)
{
return state switch
{
AssistantContextState.Transcribing => "transcribing",
AssistantContextState.SpeakerRecognition => "speaker recognition",
AssistantContextState.Summarizing => "summarizing",
AssistantContextState.Finished => "finished",
AssistantContextState.Error => "error",
_ => "error"
};
}
}