using System.Text; using MeetingAssistant.MeetingNotes; using Microsoft.Extensions.Options; namespace MeetingAssistant.Summary; public interface IMeetingSummaryFailureWriter { Task WriteAsync( MeetingSessionArtifacts artifacts, Exception exception, CancellationToken cancellationToken); Task WriteAsync( MeetingSessionArtifacts artifacts, Exception exception, MeetingAssistantOptions options, CancellationToken cancellationToken) { return WriteAsync(artifacts, exception, cancellationToken); } } public sealed class MeetingSummaryFailureWriter : IMeetingSummaryFailureWriter { private readonly MeetingAssistantOptions options; private readonly IMeetingNoteStore meetingNoteStore; public MeetingSummaryFailureWriter( IOptions options, IMeetingNoteStore meetingNoteStore) { this.options = options.Value; this.meetingNoteStore = meetingNoteStore; } public async Task WriteAsync( MeetingSessionArtifacts artifacts, Exception exception, CancellationToken cancellationToken) { return await WriteAsync(artifacts, exception, options, cancellationToken); } public async Task WriteAsync( MeetingSessionArtifacts artifacts, Exception exception, MeetingAssistantOptions options, CancellationToken cancellationToken) { Directory.CreateDirectory(Path.GetDirectoryName(artifacts.SummaryPath)!); var meetingNote = File.Exists(artifacts.MeetingNotePath) ? await meetingNoteStore.ReadAsync(artifacts.MeetingNotePath, cancellationToken) : new MeetingNote("", new MeetingNoteFrontmatter(), ""); var frontmatter = await MeetingSummaryFrontmatterFactory.CreateAsync( artifacts, meetingNote, MeetingArtifactFrontmatterRenderer.DefaultTitle(meetingNote, "Meeting Summary"), cancellationToken); await File.WriteAllTextAsync( artifacts.SummaryPath, MeetingArtifactFrontmatterRenderer.Render( frontmatter, RenderFailureMarkdown(artifacts, exception, options)), cancellationToken); return new MeetingSummaryRunResult( artifacts.SummaryPath, "Summary generation failed. See the summary file for error details.", Succeeded: false, Error: exception.Message); } private static string RenderFailureMarkdown( MeetingSessionArtifacts artifacts, Exception exception, MeetingAssistantOptions options) { var builder = new StringBuilder(); builder.AppendLine("# Meeting Summary"); builder.AppendLine(); builder.AppendLine("## Summary Generation Failed"); builder.AppendLine(); builder.AppendLine($"Summary generation failed at {DateTimeOffset.Now:O}."); builder.AppendLine(); builder.AppendLine(MeetingNoteActionLinks.CreateSummaryRetryLink( options.Api.PublicBaseUrl, artifacts.SummaryPath)); builder.AppendLine(); builder.AppendLine("## Error"); builder.AppendLine(); builder.AppendLine("```text"); builder.AppendLine(exception.ToString().Replace("```", "` ` `", StringComparison.Ordinal)); builder.AppendLine("```"); builder.AppendLine(); builder.AppendLine("## Meeting Artifacts"); builder.AppendLine(); builder.AppendLine($"- Meeting note: `{artifacts.MeetingNotePath}`"); builder.AppendLine($"- Transcript: `{artifacts.TranscriptPath}`"); builder.AppendLine($"- Assistant context: `{artifacts.AssistantContextPath}`"); builder.AppendLine($"- Summary: `{artifacts.SummaryPath}`"); return builder.ToString(); } }