Add meeting assistant speech and summary automation

This commit is contained in:
2026-05-21 09:55:39 +02:00
parent 1e97d2b9ff
commit 48d98d9cbb
63 changed files with 5143 additions and 151 deletions
@@ -26,10 +26,11 @@ public sealed class FunAsrStreamingTranscriptionProvider : IStreamingTranscripti
public async IAsyncEnumerable<TranscriptionSegment> TranscribeAsync(
IAsyncEnumerable<AudioChunk> audio,
SpeechRecognitionPipelineOptions options,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
var segments = Channel.CreateUnbounded<TranscriptionSegment>();
var session = Task.Run(() => RunSessionAsync(audio, segments.Writer, cancellationToken), CancellationToken.None);
var session = Task.Run(() => RunSessionAsync(audio, options, segments.Writer, cancellationToken), CancellationToken.None);
await foreach (var segment in segments.Reader.ReadAllAsync(cancellationToken))
{
@@ -41,6 +42,7 @@ public sealed class FunAsrStreamingTranscriptionProvider : IStreamingTranscripti
private async Task RunSessionAsync(
IAsyncEnumerable<AudioChunk> audio,
SpeechRecognitionPipelineOptions pipelineOptions,
ChannelWriter<TranscriptionSegment> segments,
CancellationToken cancellationToken)
{
@@ -61,7 +63,7 @@ public sealed class FunAsrStreamingTranscriptionProvider : IStreamingTranscripti
var finalReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var receiveTask = ReceiveAsync(connection, parser, segments, finalReceived, cancellationToken);
await SendStartAsync(connection, firstChunk, cancellationToken);
await SendStartAsync(connection, firstChunk, pipelineOptions.DictationWords, cancellationToken);
await connection.SendBinaryAsync(firstChunk.Pcm, cancellationToken);
while (await enumerator.MoveNextAsync())
@@ -139,6 +141,7 @@ public sealed class FunAsrStreamingTranscriptionProvider : IStreamingTranscripti
private async Task SendStartAsync(
IFunAsrWebSocketConnection connection,
AudioChunk firstChunk,
IReadOnlyList<string>? dictationWords,
CancellationToken cancellationToken)
{
var message = new Dictionary<string, object?>
@@ -152,24 +155,17 @@ public sealed class FunAsrStreamingTranscriptionProvider : IStreamingTranscripti
["wav_name"] = options.FunAsr.WavName,
["wav_format"] = "pcm",
["is_speaking"] = true,
["hotwords"] = await LoadHotwordsAsync(cancellationToken),
["hotwords"] = BuildHotwords(dictationWords),
["itn"] = options.FunAsr.Itn
};
await connection.SendTextAsync(JsonSerializer.Serialize(message), cancellationToken);
}
private async Task<string> LoadHotwordsAsync(CancellationToken cancellationToken)
internal static string BuildHotwords(IReadOnlyList<string>? dictationWords)
{
var path = VaultPath.Resolve(options.Vault.DictationWordsPath);
if (!File.Exists(path))
{
return "";
}
var words = await File.ReadAllLinesAsync(path, cancellationToken);
var hotwords = words
.Select(line => line.Trim().TrimStart('-', '*').Trim())
var hotwords = (dictationWords ?? [])
.Select(word => word.Trim())
.Where(line => !string.IsNullOrWhiteSpace(line))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToDictionary(word => word, _ => 20);