Public Access
Improve toast duration and speaker override samples
PR and Push Build/Test / build-and-test (push) Failing after 9m48s
PR and Push Build/Test / build-and-test (push) Failing after 9m48s
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#if WINDOWS
|
||||
using System.Collections.Concurrent;
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using MeetingAssistant.Notifications;
|
||||
using MeetingAssistant.Recording;
|
||||
|
||||
namespace MeetingAssistant.Calendar;
|
||||
@@ -44,7 +45,7 @@ public sealed class WindowsMeetingStartPromptService : IMeetingStartPromptServic
|
||||
{
|
||||
toast.Group = NotificationGroup;
|
||||
toast.Tag = promptId;
|
||||
toast.ExpirationTime = request.Meeting.End;
|
||||
toast.ExpirationTime = MeetingToastExpirationPolicy.StartRecordingPromptExpiration(DateTimeOffset.Now);
|
||||
});
|
||||
logger.LogInformation(
|
||||
"Displayed native Windows meeting-start notification {PromptId} for {Subject}",
|
||||
@@ -134,6 +135,8 @@ public sealed class WindowsMeetingStartPromptService : IMeetingStartPromptServic
|
||||
return new ToastContentBuilder()
|
||||
.AddArgument("source", NotificationSource)
|
||||
.AddArgument("promptId", promptId)
|
||||
.SetToastScenario(ToastScenario.Reminder)
|
||||
.SetToastDuration(ToastDuration.Long)
|
||||
.AddText("Record meeting?")
|
||||
.AddText($"{request.Meeting.Subject} starts at {request.Meeting.Start.LocalDateTime:t}.")
|
||||
.AddButton(yesButton)
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed class AutomationOptions
|
||||
|
||||
public sealed class CalendarRecordingPromptOptions
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public TimeSpan SyncInterval { get; set; } = TimeSpan.FromMinutes(30);
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace MeetingAssistant.Notifications;
|
||||
|
||||
internal static class MeetingToastExpirationPolicy
|
||||
{
|
||||
public static DateTimeOffset StopReminderExpiration(DateTimeOffset now)
|
||||
{
|
||||
return now.AddMinutes(1);
|
||||
}
|
||||
|
||||
public static DateTimeOffset StartRecordingPromptExpiration(DateTimeOffset now)
|
||||
{
|
||||
return now.AddMinutes(5);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#if WINDOWS
|
||||
using System.Collections.Concurrent;
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using MeetingAssistant.Notifications;
|
||||
|
||||
namespace MeetingAssistant.Recording;
|
||||
|
||||
@@ -48,7 +49,7 @@ public sealed class WindowsMeetingInactivityPromptService : IMeetingInactivityPr
|
||||
{
|
||||
toast.Group = NotificationGroup;
|
||||
toast.Tag = promptId;
|
||||
toast.ExpirationTime = DateTimeOffset.Now.AddMinutes(15);
|
||||
toast.ExpirationTime = MeetingToastExpirationPolicy.StopReminderExpiration(DateTimeOffset.Now);
|
||||
});
|
||||
logger.LogInformation(
|
||||
"Displayed native Windows inactivity notification {PromptId} at threshold {Threshold}",
|
||||
@@ -138,6 +139,8 @@ public sealed class WindowsMeetingInactivityPromptService : IMeetingInactivityPr
|
||||
return new ToastContentBuilder()
|
||||
.AddArgument("source", NotificationSource)
|
||||
.AddArgument("promptId", promptId)
|
||||
.SetToastScenario(ToastScenario.Reminder)
|
||||
.SetToastDuration(ToastDuration.Long)
|
||||
.AddText("Stop meeting?")
|
||||
.AddText($"No transcript text has arrived for {FormatDuration(request.InactivityDuration)}.")
|
||||
.AddButton(yesButton)
|
||||
|
||||
@@ -79,6 +79,17 @@ public sealed class SpeakerIdentityService : ISpeakerIdentificationService
|
||||
var meetingReference = CreateReference(request.MeetingNote, now);
|
||||
var snippetResolution = await ResolveOverrideSnippetAsync(request, sourceLabel, cancellationToken);
|
||||
var snippet = snippetResolution.WavBytes;
|
||||
if (snippet.Length > 0 && !await matchValidator.ValidateSampleAsync(snippet, cancellationToken))
|
||||
{
|
||||
logger.LogInformation(
|
||||
"Speaker override from {SourceSpeaker} to {TargetSpeaker} rejected source sample after secondary validation: sample source {SampleSource}, sample bytes {SampleBytes}",
|
||||
sourceLabel,
|
||||
targetName,
|
||||
snippetResolution.Source,
|
||||
snippet.Length);
|
||||
snippet = [];
|
||||
}
|
||||
|
||||
var target = await FindIdentityByAcceptedNameAsync(context, targetName, cancellationToken);
|
||||
var sourceCandidate = await FindCurrentRunCandidateAsync(
|
||||
context,
|
||||
@@ -277,20 +288,10 @@ public sealed class SpeakerIdentityService : ISpeakerIdentificationService
|
||||
if (snippet.Length == 0)
|
||||
{
|
||||
snippetSource = "extracted";
|
||||
var span = SpeakerSampleSpanSelector.SelectBestContinuousSpan(
|
||||
request.Segments,
|
||||
(snippet, _) = await ExtractBestContinuousSampleAsync(
|
||||
request,
|
||||
speaker,
|
||||
options.MaximumSampleSegmentGap,
|
||||
options.MinimumSampleSpeechDuration);
|
||||
logger.LogInformation(
|
||||
"Speaker identity processing extracting sample for {Speaker}: selected {SegmentCount} segment(s), span {SpanDuration}, minimum {MinimumDuration}",
|
||||
speaker,
|
||||
span.Count,
|
||||
SpeakerSampleSpanSelector.SpanDuration(span),
|
||||
options.MinimumSampleSpeechDuration);
|
||||
snippet = await snippetExtractor.ExtractSnippetAsync(
|
||||
request.AudioPath,
|
||||
span,
|
||||
"Speaker identity processing",
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
@@ -521,13 +522,39 @@ public sealed class SpeakerIdentityService : ISpeakerIdentificationService
|
||||
return new SpeakerSnippetResolution([], "missing-segments", SegmentCount: 0, Score: null, Duration: TimeSpan.Zero);
|
||||
}
|
||||
|
||||
var snippet = await snippetExtractor.ExtractSnippetAsync(request.AudioPath, segments, cancellationToken);
|
||||
var (snippet, span) = await ExtractBestContinuousSampleAsync(
|
||||
request,
|
||||
sourceSpeaker,
|
||||
"Speaker override",
|
||||
cancellationToken);
|
||||
return new SpeakerSnippetResolution(
|
||||
snippet,
|
||||
"extracted-from-recording",
|
||||
segments.Count,
|
||||
span.Count,
|
||||
Score: null,
|
||||
segments.Max(segment => segment.End) - segments.Min(segment => segment.Start));
|
||||
SpeakerSampleSpanSelector.SpanDuration(span));
|
||||
}
|
||||
|
||||
private async Task<(byte[] Snippet, IReadOnlyList<TranscriptionSegment> Span)> ExtractBestContinuousSampleAsync(
|
||||
SpeakerIdentificationRequest request,
|
||||
string speaker,
|
||||
string operation,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var span = SpeakerSampleSpanSelector.SelectBestContinuousSpan(
|
||||
request.Segments,
|
||||
speaker,
|
||||
options.MaximumSampleSegmentGap,
|
||||
options.MinimumSampleSpeechDuration);
|
||||
logger.LogInformation(
|
||||
"{Operation} extracting fallback sample for {Speaker}: selected {SegmentCount} segment(s), span {SpanDuration}, minimum {MinimumDuration}",
|
||||
operation,
|
||||
speaker,
|
||||
span.Count,
|
||||
SpeakerSampleSpanSelector.SpanDuration(span),
|
||||
options.MinimumSampleSpeechDuration);
|
||||
var snippet = await snippetExtractor.ExtractSnippetAsync(request.AudioPath, span, cancellationToken);
|
||||
return (snippet, span);
|
||||
}
|
||||
|
||||
private static async Task<SpeakerIdentity?> FindIdentityByAcceptedNameAsync(
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
"RulesPath": "C:\\Manuel\\meeting-assistant\\meeting-rules.local.yaml"
|
||||
},
|
||||
"CalendarRecordingPrompts": {
|
||||
"Enabled": false,
|
||||
"Enabled": true,
|
||||
"SyncInterval": "00:30:00",
|
||||
"PromptWindow": "00:05:00"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user