Public Access
253 lines
9.6 KiB
C#
253 lines
9.6 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,
|
|
MeetingNote meetingNote,
|
|
string agenda,
|
|
DateTimeOffset? scheduledEnd,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
|
|
var content = Render(
|
|
artifacts,
|
|
meetingNote,
|
|
AssistantContextState.CollectingMetadata,
|
|
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 metadata = existingFrontmatter.ToMeetingArtifactMetadata();
|
|
var agenda = existingFrontmatter.Agenda ?? "";
|
|
var scheduledEnd = ParseDateTime(existingFrontmatter.ScheduledEnd);
|
|
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
Render(artifacts, metadata, state, agenda, scheduledEnd, body),
|
|
cancellationToken);
|
|
logger.LogInformation(
|
|
"Updated assistant context note {AssistantContextPath} state to {State}",
|
|
artifacts.AssistantContextPath,
|
|
ToYamlState(state));
|
|
}
|
|
|
|
public async Task UpdateAssistantContextMetadataAsync(
|
|
MeetingSessionArtifacts artifacts,
|
|
MeetingNote meetingNote,
|
|
string agenda,
|
|
DateTimeOffset? scheduledEnd,
|
|
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 existingFrontmatter = ReadFrontmatter(existing.Frontmatter);
|
|
var state = ParseState(existingFrontmatter.State);
|
|
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
Render(artifacts, meetingNote, state, agenda, scheduledEnd, existing.Body),
|
|
cancellationToken);
|
|
logger.LogInformation(
|
|
"Updated assistant context note {AssistantContextPath} calendar metadata",
|
|
artifacts.AssistantContextPath);
|
|
}
|
|
|
|
public async Task UpdateAssistantContextMeetingAsync(
|
|
MeetingSessionArtifacts artifacts,
|
|
MeetingNote meetingNote,
|
|
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 existingFrontmatter = ReadFrontmatter(existing.Frontmatter);
|
|
var state = ParseState(existingFrontmatter.State);
|
|
var agenda = existingFrontmatter.Agenda ?? "";
|
|
var scheduledEnd = ParseDateTime(existingFrontmatter.ScheduledEnd);
|
|
|
|
await File.WriteAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
Render(artifacts, meetingNote, state, agenda, scheduledEnd, existing.Body),
|
|
cancellationToken);
|
|
logger.LogInformation(
|
|
"Updated assistant context note {AssistantContextPath} meeting metadata",
|
|
artifacts.AssistantContextPath);
|
|
}
|
|
|
|
public async Task AppendAssistantContextAsync(
|
|
MeetingSessionArtifacts artifacts,
|
|
string content,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(content))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.AssistantContextPath)!);
|
|
var prefix = File.Exists(artifacts.AssistantContextPath) &&
|
|
new FileInfo(artifacts.AssistantContextPath).Length > 0
|
|
? Environment.NewLine
|
|
: "";
|
|
await File.AppendAllTextAsync(
|
|
artifacts.AssistantContextPath,
|
|
prefix + content.TrimEnd() + Environment.NewLine,
|
|
cancellationToken);
|
|
logger.LogInformation(
|
|
"Appended automation context to assistant context note {AssistantContextPath}",
|
|
artifacts.AssistantContextPath);
|
|
}
|
|
|
|
private static string Render(
|
|
MeetingSessionArtifacts artifacts,
|
|
MeetingNote meetingNote,
|
|
AssistantContextState state,
|
|
string? agenda,
|
|
DateTimeOffset? scheduledEnd,
|
|
string body)
|
|
{
|
|
var metadata = new AssistantContextMeetingMetadata(
|
|
meetingNote.Frontmatter.Title,
|
|
meetingNote.Frontmatter.StartTime,
|
|
meetingNote.Frontmatter.EndTime);
|
|
return Render(artifacts, metadata, state, agenda, scheduledEnd, body);
|
|
}
|
|
|
|
private static string Render(
|
|
MeetingSessionArtifacts artifacts,
|
|
AssistantContextMeetingMetadata metadata,
|
|
AssistantContextState state,
|
|
string? agenda,
|
|
DateTimeOffset? scheduledEnd,
|
|
string body)
|
|
{
|
|
var frontmatter = MeetingArtifactFrontmatterRenderer.Create(
|
|
artifacts,
|
|
metadata.Title,
|
|
metadata.StartTime,
|
|
metadata.EndTime,
|
|
artifacts.AssistantContextPath,
|
|
ToYamlState(state));
|
|
frontmatter.Agenda = agenda ?? "";
|
|
frontmatter.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 = "state")]
|
|
public string? State { get; set; }
|
|
|
|
[YamlMember(Alias = "title")]
|
|
public string? Title { get; set; }
|
|
|
|
[YamlMember(Alias = "start_time")]
|
|
public string? StartTime { get; set; }
|
|
|
|
[YamlMember(Alias = "end_time")]
|
|
public string? EndTime { get; set; }
|
|
|
|
[YamlMember(Alias = "agenda")]
|
|
public string? Agenda { get; set; }
|
|
|
|
[YamlMember(Alias = "scheduled_end")]
|
|
public string? ScheduledEnd { get; set; }
|
|
|
|
public AssistantContextMeetingMetadata ToMeetingArtifactMetadata()
|
|
{
|
|
return new AssistantContextMeetingMetadata(
|
|
Title ?? "",
|
|
ParseDateTime(StartTime),
|
|
ParseDateTime(EndTime));
|
|
}
|
|
}
|
|
|
|
private sealed record AssistantContextMeetingMetadata(
|
|
string Title,
|
|
DateTimeOffset? StartTime,
|
|
DateTimeOffset? EndTime);
|
|
|
|
private static AssistantContextState ParseState(string? value)
|
|
{
|
|
return value?.Trim().ToLowerInvariant() switch
|
|
{
|
|
"transcribing" => AssistantContextState.Transcribing,
|
|
"collecting metadata" => AssistantContextState.CollectingMetadata,
|
|
"speaker recognition" => AssistantContextState.SpeakerRecognition,
|
|
"summarizing" => AssistantContextState.Summarizing,
|
|
"finished" => AssistantContextState.Finished,
|
|
"error" => AssistantContextState.Error,
|
|
_ => AssistantContextState.CollectingMetadata
|
|
};
|
|
}
|
|
|
|
private static string ToYamlState(AssistantContextState state)
|
|
{
|
|
return state switch
|
|
{
|
|
AssistantContextState.CollectingMetadata => "collecting metadata",
|
|
AssistantContextState.Transcribing => "transcribing",
|
|
AssistantContextState.SpeakerRecognition => "speaker recognition",
|
|
AssistantContextState.Summarizing => "summarizing",
|
|
AssistantContextState.Finished => "finished",
|
|
AssistantContextState.Error => "error",
|
|
_ => "error"
|
|
};
|
|
}
|
|
|
|
}
|