Public Access
Add meeting assistant speech and summary automation
This commit is contained in:
@@ -2,8 +2,10 @@ using MeetingAssistant;
|
||||
using MeetingAssistant.Hotkeys;
|
||||
using MeetingAssistant.MeetingNotes;
|
||||
using MeetingAssistant.Recording;
|
||||
using MeetingAssistant.Speakers;
|
||||
using MeetingAssistant.Summary;
|
||||
using MeetingAssistant.Transcription;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -25,8 +27,22 @@ builder.Services.AddSingleton<IMeetingNoteStore, MarkdownMeetingNoteStore>();
|
||||
builder.Services.AddSingleton<IMeetingNoteOpener, ObsidianMeetingNoteOpener>();
|
||||
builder.Services.AddSingleton<IMeetingArtifactStore, MarkdownMeetingArtifactStore>();
|
||||
builder.Services.AddSingleton<IRecordedAudioStore, TemporaryRecordedAudioStore>();
|
||||
builder.Services.AddSingleton<IDictationWordStore, MarkdownDictationWordStore>();
|
||||
builder.Services.AddDbContextFactory<SpeakerIdentityDbContext>((services, dbOptions) =>
|
||||
{
|
||||
var appOptions = services.GetRequiredService<IOptions<MeetingAssistantOptions>>().Value;
|
||||
var databasePath = VaultPath.Resolve(appOptions.SpeakerIdentification.DatabasePath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(databasePath)!);
|
||||
dbOptions.UseSqlite($"Data Source={databasePath}");
|
||||
});
|
||||
builder.Services.AddSingleton<ISpeakerSnippetExtractor, WavSpeakerSnippetExtractor>();
|
||||
builder.Services.AddSingleton<ISpeakerIdentityDiarizationClient, AzureSpeechSpeakerIdentityDiarizationClient>();
|
||||
builder.Services.AddSingleton<ISpeakerIdentityMatcher, AzureSpeechSpeakerIdentityMatcher>();
|
||||
builder.Services.AddSingleton<ISpeakerIdentificationService, SpeakerIdentityService>();
|
||||
builder.Services.AddSingleton<ISpeakerIdentityMergeService, SpeakerIdentityMergeService>();
|
||||
builder.Services.AddSingleton<IMeetingSummaryArtifactResolver, MeetingSummaryArtifactResolver>();
|
||||
builder.Services.AddSingleton<IMeetingSummaryFailureWriter, MeetingSummaryFailureWriter>();
|
||||
builder.Services.AddSingleton<IMeetingSummaryInstructionBuilder, MeetingSummaryInstructionBuilder>();
|
||||
builder.Services.AddSingleton<IMeetingSummaryPipeline, OpenAiMeetingSummaryAgentPipeline>();
|
||||
builder.Services.AddSingleton<AsrDiagnosticService>();
|
||||
builder.Services.AddSingleton<ICommandRunner, ProcessCommandRunner>();
|
||||
@@ -41,6 +57,7 @@ builder.Services.AddTransient<PyannoteTranscriptFinalizer>();
|
||||
builder.Services.AddSingleton<ISpeechRecognitionPipelineFactory, ConfiguredSpeechRecognitionPipelineFactory>();
|
||||
builder.Services.AddSingleton<MeetingRecordingCoordinator>();
|
||||
builder.Services.AddHostedService<TemporaryRecordingCleanupHostedService>();
|
||||
builder.Services.AddHostedService<SpeakerIdentityDatabaseInitializer>();
|
||||
builder.Services.AddHostedService<SpeechRecognitionPipelineHostedService>();
|
||||
#if WINDOWS
|
||||
builder.Services.AddHostedService<GlobalHotkeyService>();
|
||||
@@ -55,6 +72,48 @@ app.MapGet("/health", () => Results.Ok(new
|
||||
}));
|
||||
|
||||
app.MapGet("/recording/status", (MeetingRecordingCoordinator coordinator) => Results.Ok(coordinator.CurrentStatus));
|
||||
app.MapGet("/diagnostics/outlook/current-meeting", async (
|
||||
IMeetingMetadataProvider metadataProvider,
|
||||
IOptions<MeetingAssistantOptions> options,
|
||||
DateTimeOffset? startedAt,
|
||||
double? timeoutSeconds,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var lookupStartedAt = startedAt ?? DateTimeOffset.Now;
|
||||
var timeout = timeoutSeconds is > 0
|
||||
? TimeSpan.FromSeconds(timeoutSeconds.Value)
|
||||
: options.Value.Recording.MetadataLookupTimeout;
|
||||
if (metadataProvider is IMeetingMetadataDiagnosticProvider diagnostics)
|
||||
{
|
||||
return Results.Ok(await diagnostics.DiagnoseCurrentMeetingAsync(
|
||||
lookupStartedAt,
|
||||
timeout,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
var metadata = await metadataProvider.GetCurrentMeetingAsync(lookupStartedAt, cancellationToken);
|
||||
stopwatch.Stop();
|
||||
return Results.Ok(new MeetingMetadataDiagnosticResult(
|
||||
metadataProvider.GetType().Name,
|
||||
lookupStartedAt,
|
||||
metadata is not null,
|
||||
false,
|
||||
stopwatch.ElapsedMilliseconds,
|
||||
metadata,
|
||||
metadata is null ? "No meeting metadata provider result was available." : null));
|
||||
});
|
||||
app.MapPost("/diagnostics/speaker-identities/merge", async (
|
||||
SpeakerIdentityMergeDiagnosticRequest request,
|
||||
ISpeakerIdentityMergeService mergeService,
|
||||
IOptions<MeetingAssistantOptions> options,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var recentAge = request.RecentDays is > 0
|
||||
? TimeSpan.FromDays(request.RecentDays.Value)
|
||||
: options.Value.SpeakerIdentification.MergeRecentIdentityAge;
|
||||
return Results.Ok(await mergeService.MergeRecentIdentitiesAsync(recentAge, cancellationToken));
|
||||
});
|
||||
app.MapPost("/recording/toggle", async (MeetingRecordingCoordinator coordinator, CancellationToken cancellationToken) =>
|
||||
Results.Ok(await coordinator.ToggleAsync(cancellationToken)));
|
||||
app.MapPost("/recording/start", async (MeetingRecordingCoordinator coordinator, CancellationToken cancellationToken) =>
|
||||
@@ -189,3 +248,5 @@ public partial class Program;
|
||||
public sealed record AsrDiagnosticRequest(string Path, int? NumSpeakers = null);
|
||||
|
||||
public sealed record SummaryRetryRequest(string SummaryPath);
|
||||
|
||||
public sealed record SpeakerIdentityMergeDiagnosticRequest(double? RecentDays = null);
|
||||
|
||||
Reference in New Issue
Block a user