Public Access
Implement meeting assistant v1
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
using MeetingAssistant.MeetingNotes;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MeetingAssistant.Summary;
|
||||
|
||||
public interface IMeetingSummaryArtifactResolver
|
||||
{
|
||||
Task<MeetingSessionArtifacts?> ResolveBySummaryPathAsync(
|
||||
string summaryPath,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed class MeetingSummaryArtifactResolver : IMeetingSummaryArtifactResolver
|
||||
{
|
||||
private readonly MeetingAssistantOptions options;
|
||||
private readonly IMeetingNoteStore meetingNoteStore;
|
||||
|
||||
public MeetingSummaryArtifactResolver(
|
||||
IOptions<MeetingAssistantOptions> options,
|
||||
IMeetingNoteStore meetingNoteStore)
|
||||
{
|
||||
this.options = options.Value;
|
||||
this.meetingNoteStore = meetingNoteStore;
|
||||
}
|
||||
|
||||
public async Task<MeetingSessionArtifacts?> ResolveBySummaryPathAsync(
|
||||
string summaryPath,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var requestedSummaryPath = ResolveRequestedSummaryPath(summaryPath);
|
||||
if (requestedSummaryPath is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var meetingNotesFolder = VaultPath.Resolve(options.Vault.MeetingNotesFolder);
|
||||
if (!Directory.Exists(meetingNotesFolder))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var meetingNotePath in Directory.EnumerateFiles(meetingNotesFolder, "*.md"))
|
||||
{
|
||||
var note = await meetingNoteStore.ReadAsync(meetingNotePath, cancellationToken);
|
||||
var noteSummaryPath = ResolveNoteLink(note.Frontmatter.Summary, note.Path);
|
||||
if (!PathsEqual(requestedSummaryPath, noteSummaryPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return new MeetingSessionArtifacts(
|
||||
note.Path,
|
||||
ResolveNoteLink(note.Frontmatter.Transcript, note.Path),
|
||||
ResolveNoteLink(note.Frontmatter.AssistantContext, note.Path),
|
||||
requestedSummaryPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ResolveRequestedSummaryPath(string summaryPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(summaryPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var summariesFolder = VaultPath.Resolve(options.Vault.SummariesFolder);
|
||||
var requested = Environment.ExpandEnvironmentVariables(summaryPath);
|
||||
var fullPath = Path.IsPathRooted(requested)
|
||||
? Path.GetFullPath(requested)
|
||||
: Path.GetFullPath(Path.Combine(summariesFolder, requested));
|
||||
|
||||
return IsWithinDirectory(summariesFolder, fullPath) ? fullPath : null;
|
||||
}
|
||||
|
||||
private static string ResolveNoteLink(string pathOrLink, string sourceNotePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pathOrLink))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var sourceFolder = Path.GetDirectoryName(sourceNotePath) ?? "";
|
||||
var target = ExtractWikiLinkTarget(pathOrLink) ?? pathOrLink;
|
||||
target = target.Replace('/', Path.DirectorySeparatorChar);
|
||||
|
||||
if (!Path.HasExtension(target))
|
||||
{
|
||||
target += ".md";
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.IsPathRooted(target)
|
||||
? target
|
||||
: Path.Combine(sourceFolder, target));
|
||||
}
|
||||
|
||||
private static string? ExtractWikiLinkTarget(string value)
|
||||
{
|
||||
if (!value.StartsWith("[[", StringComparison.Ordinal) ||
|
||||
!value.EndsWith("]]", StringComparison.Ordinal))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var inner = value[2..^2];
|
||||
var labelSeparator = inner.IndexOf('|', StringComparison.Ordinal);
|
||||
return labelSeparator < 0 ? inner : inner[..labelSeparator];
|
||||
}
|
||||
|
||||
private static bool PathsEqual(string left, string right)
|
||||
{
|
||||
return string.Equals(
|
||||
Path.GetFullPath(left),
|
||||
Path.GetFullPath(right),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsWithinDirectory(string directory, string path)
|
||||
{
|
||||
var normalizedDirectory = Path.GetFullPath(directory)
|
||||
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
var normalizedPath = Path.GetFullPath(path);
|
||||
return normalizedPath.StartsWith(normalizedDirectory, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user