Polish weather news and STT filtering

This commit is contained in:
Jacob Dubin
2026-05-21 06:09:27 -05:00
parent 1b9efc4226
commit eb509a66e0
8 changed files with 157 additions and 15 deletions

View File

@@ -1969,6 +1969,8 @@ public sealed class JiboInteractionService(
DateTimeOffset? referenceLocalTime)
{
var payload = BuildWeatherSkillPayload(spokenReply, snapshot, referenceLocalTime);
payload["weather_view_kind"] = "weatherWeekly";
payload["weather_view_mode"] = "forecast";
payload["weather_weekly_cards"] = segments
.Select(static segment => new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
{
@@ -2118,6 +2120,7 @@ public sealed class JiboInteractionService(
["prompt_sub_category"] = "AN",
["weather_view_enabled"] = true,
["weather_view_kind"] = "weatherHiLo",
["weather_view_mode"] = "current",
["weather_icon"] = weatherIcon,
["weather_summary"] = snapshot.Summary,
["weather_location"] = snapshot.LocationName,

View File

@@ -7,6 +7,8 @@ public sealed class LocalWhisperCppBufferedAudioSttStrategy(
BufferedAudioSttOptions options,
IExternalProcessRunner processRunner) : ISttStrategy
{
private const int MinimumBufferedAudioBytes = 64;
public string Name => "local-whispercpp-buffered-audio";
public bool CanHandle(TurnContext turn)
@@ -15,7 +17,8 @@ public sealed class LocalWhisperCppBufferedAudioSttStrategy(
IsConfiguredPathAvailable(options.FfmpegPath, false) &&
IsConfiguredPathAvailable(options.WhisperCliPath, true) &&
IsConfiguredPathAvailable(options.WhisperModelPath, true) &&
ReadBufferedAudioFrames(turn).Any(ContainsOpusIdentificationHeader);
ReadBufferedAudioFrames(turn).Any(ContainsOpusIdentificationHeader) &&
!IsBelowNoiseFloor(ReadBufferedAudioBytes(turn));
}
public async Task<SttResult> TranscribeAsync(TurnContext turn, CancellationToken cancellationToken = default)
@@ -28,6 +31,10 @@ public sealed class LocalWhisperCppBufferedAudioSttStrategy(
throw new InvalidOperationException(
"Local whisper.cpp STT requires buffered Ogg/Opus audio with an Opus identification header.");
if (IsBelowNoiseFloor(ReadBufferedAudioBytes(turn)))
throw new InvalidOperationException(
"Local whisper.cpp STT rejected buffered audio as too short or noisy for transcription.");
var tempDirectory = options.TempDirectory;
if (string.IsNullOrWhiteSpace(tempDirectory)) tempDirectory = Path.Combine(Path.GetTempPath(), "openjibo-stt");
@@ -112,6 +119,11 @@ public sealed class LocalWhisperCppBufferedAudioSttStrategy(
: 0;
}
private static bool IsBelowNoiseFloor(int bufferedAudioBytes)
{
return bufferedAudioBytes > 0 && bufferedAudioBytes < MinimumBufferedAudioBytes;
}
private static bool ContainsOpusIdentificationHeader(byte[] frame)
{
return frame.AsSpan().IndexOf("OpusHead"u8) >= 0;
@@ -155,4 +167,4 @@ public sealed class LocalWhisperCppBufferedAudioSttStrategy(
return !checkFileExists || File.Exists(path);
}
}
}

View File

@@ -131,9 +131,9 @@ public sealed class NewsApiBriefingProvider(
foreach (var article in articles.EnumerateArray())
{
var title = NormalizeHeadlineTitle(ReadString(article, "title"));
if (string.IsNullOrWhiteSpace(title) || !seenTitles.Add(title)) continue;
var summary = ReadString(article, "description");
if (title is null || !IsUsableHeadline(title, summary) || !seenTitles.Add(title)) continue;
var source = article.TryGetProperty("source", out var sourceNode) &&
sourceNode.ValueKind == JsonValueKind.Object
? ReadString(sourceNode, "name")
@@ -176,9 +176,9 @@ public sealed class NewsApiBriefingProvider(
foreach (var article in broadArticles.EnumerateArray())
{
var title = NormalizeHeadlineTitle(ReadString(article, "title"));
if (string.IsNullOrWhiteSpace(title) || !seenTitles.Add(title)) continue;
var summary = ReadString(article, "description");
if (title is null || !IsUsableHeadline(title, summary) || !seenTitles.Add(title)) continue;
var source = article.TryGetProperty("source", out var sourceNode) &&
sourceNode.ValueKind == JsonValueKind.Object
? ReadString(sourceNode, "name")
@@ -239,9 +239,9 @@ public sealed class NewsApiBriefingProvider(
foreach (var article in everythingArticles.EnumerateArray())
{
var title = NormalizeHeadlineTitle(ReadString(article, "title"));
if (string.IsNullOrWhiteSpace(title) || !seenTitles.Add(title)) continue;
var summary = ReadString(article, "description");
if (title is null || !IsUsableHeadline(title, summary) || !seenTitles.Add(title)) continue;
var source = article.TryGetProperty("source", out var sourceNode) &&
sourceNode.ValueKind == JsonValueKind.Object
? ReadString(sourceNode, "name")
@@ -453,6 +453,16 @@ public sealed class NewsApiBriefingProvider(
return string.IsNullOrWhiteSpace(trimmed) ? null : trimmed;
}
private static bool IsUsableHeadline(string? title, string? summary)
{
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(summary)) return false;
var loweredTitle = title.Trim().ToLowerInvariant();
return !loweredTitle.StartsWith("correction:", StringComparison.Ordinal) &&
!loweredTitle.StartsWith("corrected:", StringComparison.Ordinal) &&
!loweredTitle.Contains(" correction", StringComparison.Ordinal);
}
private static ApiError? TryParseApiError(string? responseBody)
{
if (string.IsNullOrWhiteSpace(responseBody)) return null;
@@ -529,4 +539,4 @@ public sealed class NewsApiBriefingProvider(
private sealed record ApiError(string? Code, string? Message);
private sealed record CacheEntry<T>(T Value, DateTimeOffset ExpiresUtc);
}
}