From 8c17ad40354f0c8fc46c663be86aebe822fa01bc Mon Sep 17 00:00:00 2001 From: Jacob Dubin Date: Sun, 10 May 2026 20:31:07 -0500 Subject: [PATCH] Update commit message generation --- .../Services/JiboInteractionService.cs | 114 ++++++++++++++++-- .../ResponsePlanToSocketMessagesMapper.cs | 7 +- .../WebSocketTurnFinalizationService.cs | 16 +++ .../WebSockets/JiboInteractionServiceTests.cs | 35 +++++- .../WebSockets/JiboWebSocketServiceTests.cs | 10 +- 5 files changed, 162 insertions(+), 20 deletions(-) diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs index 4479122..25076cd 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs @@ -608,18 +608,64 @@ public sealed class JiboInteractionService( "I can check weather once my weather service is connected."); } + var locationQuery = TryResolveWeatherLocationQuery(transcript); + var weatherCoordinates = string.IsNullOrWhiteSpace(locationQuery) + ? TryResolveWeatherCoordinates(turn) + : null; + var useCelsius = ShouldUseCelsius(turn, transcript); + var isNextWeekForecast = IsNextWeekForecastRequest(normalizedTranscript); + + if (isNextWeekForecast) + { + var weeklySnapshots = new List<(int DayOffset, WeatherReportSnapshot Snapshot)>(); + for (var offset = 1; offset <= MaxWeatherForecastDayOffset; offset += 1) + { + WeatherReportSnapshot? weeklySnapshot; + try + { + weeklySnapshot = await weatherReportProvider.GetReportAsync( + new WeatherReportRequest( + locationQuery, + weatherCoordinates?.Latitude, + weatherCoordinates?.Longitude, + IsTomorrow: offset == 1, + useCelsius, + ForecastDayOffset: offset), + cancellationToken); + } + catch (Exception) when (!cancellationToken.IsCancellationRequested) + { + weeklySnapshot = null; + } + + if (weeklySnapshot is not null) + { + weeklySnapshots.Add((offset, weeklySnapshot)); + } + } + + if (weeklySnapshots.Count == 0) + { + return new JiboInteractionDecision( + "weather", + "I couldn't fetch the weather right now. Please try again."); + } + + var weeklySpokenReply = BuildNextWeekForecastSpokenReply(weeklySnapshots, referenceLocalTime); + var weeklyWeatherPayload = BuildWeatherSkillPayload(weeklySpokenReply, weeklySnapshots[0].Snapshot, referenceLocalTime); + return new JiboInteractionDecision( + "weather", + weeklySpokenReply, + "chitchat-skill", + SkillPayload: weeklyWeatherPayload); + } + if (weatherDate.ForecastDayOffset > MaxWeatherForecastDayOffset) { return new JiboInteractionDecision( "weather", $"I can forecast up to {MaxWeatherForecastDayOffset} days ahead. Try tomorrow or another day this week."); } - - var locationQuery = TryResolveWeatherLocationQuery(transcript); - var weatherCoordinates = string.IsNullOrWhiteSpace(locationQuery) - ? TryResolveWeatherCoordinates(turn) - : null; - var useCelsius = ShouldUseCelsius(turn, transcript); WeatherReportSnapshot? snapshot; try { @@ -688,6 +734,52 @@ public sealed class JiboInteractionService( return $"Right now in {location}, it is {summary} and {snapshot.Temperature} degrees {unit}."; } + private static string BuildNextWeekForecastSpokenReply( + IReadOnlyList<(int DayOffset, WeatherReportSnapshot Snapshot)> snapshots, + DateTimeOffset? referenceLocalTime) + { + if (snapshots.Count == 0) + { + return "I couldn't build a forecast right now."; + } + + var location = snapshots[0].Snapshot.LocationName; + if (string.IsNullOrWhiteSpace(location)) + { + location = "your area"; + } + + var unit = snapshots[0].Snapshot.UseCelsius ? "Celsius" : "Fahrenheit"; + var referenceDate = (referenceLocalTime ?? DateTimeOffset.UtcNow).Date; + var segments = snapshots + .OrderBy(static item => item.DayOffset) + .Take(MaxWeatherForecastDayOffset) + .Select(item => + { + var dayName = referenceDate.AddDays(item.DayOffset).ToString("dddd", CultureInfo.InvariantCulture); + var summary = string.IsNullOrWhiteSpace(item.Snapshot.Summary) + ? "partly cloudy" + : item.Snapshot.Summary.Trim().TrimEnd('.'); + var high = item.Snapshot.HighTemperature ?? item.Snapshot.Temperature; + var low = item.Snapshot.LowTemperature ?? item.Snapshot.Temperature; + return $"{dayName}: {summary}, high {high}, low {low}."; + }); + + return $"I can share the next five-day forecast in {location}. {string.Join(" ", segments)} Temperatures are in {unit}."; + } + + private static bool IsNextWeekForecastRequest(string normalizedTranscript) + { + if (string.IsNullOrWhiteSpace(normalizedTranscript) || + !normalizedTranscript.Contains("next week", StringComparison.Ordinal)) + { + return false; + } + + return normalizedTranscript.Contains("forecast", StringComparison.Ordinal) || + normalizedTranscript.Contains("weather", StringComparison.Ordinal); + } + private static bool ShouldDefaultForecastToTomorrow(string normalizedTranscript, WeatherDateEntity weatherDate) { if (weatherDate.ForecastDayOffset > 0 || @@ -946,7 +1038,11 @@ public sealed class JiboInteractionService( ["skillId"] = "news", ["cloudSkill"] = "news", ["mim_id"] = "runtime-news", - ["mim_type"] = "announcement" + ["mim_type"] = "announcement", + ["prompt_id"] = "NewsHeadline_AN_01", + ["prompt_sub_category"] = "AN", + ["esml"] = + $"{EscapeForEsml(spokenBriefing)}" }; if (!string.IsNullOrWhiteSpace(sourceName)) @@ -4100,6 +4196,10 @@ public sealed class JiboInteractionService( ("technology", "technology"), ("tech", "technology"), ("ai", "technology"), + ("a i", "technology"), + ("a eye", "technology"), + ("aye eye", "technology"), + ("artificial intelligence", "technology"), ("science", "science"), ("business", "business"), ("finance", "business"), diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs index 073a37e..98b39b1 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs @@ -844,9 +844,10 @@ public sealed class ResponsePlanToSocketMessagesMapper ["view"] = resolvedGuiConfig }; - playConfig["gui"] = resolvedGuiConfig; - playConfig["no_matches_for_gui"] = 0; - playConfig["no_inputs_for_gui"] = 0; + jcpConfig["timeout"] = 6; + jcpConfig["barge_in"] = true; + jcpConfig["no_matches_for_gui"] = 0; + jcpConfig["no_inputs_for_gui"] = 0; var weatherViews = new Dictionary(StringComparer.OrdinalIgnoreCase) { diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs index fe4ff92..e92ffc0 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs @@ -668,6 +668,22 @@ public sealed partial class WebSocketTurnFinalizationService( UpdatePendingProactivityOffer(session, plan.IntentName); await ApplyContextUpdatesAsync(session, plan.ContextUpdates, envelope, plan.IntentName, cancellationToken); + var invokedSkillAction = plan.Actions.OfType().FirstOrDefault(); + if ((string.Equals(plan.IntentName, "weather", StringComparison.OrdinalIgnoreCase) || + string.Equals(plan.IntentName, "news", StringComparison.OrdinalIgnoreCase)) && + invokedSkillAction is not null) + { + await sink.RecordTurnDiagnosticAsync( + "skill_payload_summary", + BuildTurnDiagnosticSnapshot(session, envelope, new Dictionary + { + ["intent"] = plan.IntentName, + ["skillName"] = invokedSkillAction.SkillName, + ["payload"] = invokedSkillAction.Payload + }), + cancellationToken); + } + session.FollowUpExpiresUtc = plan.FollowUp.KeepMicOpen ? DateTimeOffset.UtcNow.Add(plan.FollowUp.Timeout) : null; diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs index b9ff01b..2b86951 100644 --- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs +++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs @@ -1761,7 +1761,7 @@ public sealed class JiboInteractionServiceTests } [Fact] - public async Task BuildDecisionAsync_WeatherNextWeek_WithContext_ReturnsGuardrailMessage() + public async Task BuildDecisionAsync_WeatherNextWeek_WithContext_ReturnsFiveDaySummary() { var provider = new CapturingWeatherReportProvider { @@ -1780,8 +1780,12 @@ public sealed class JiboInteractionServiceTests }); Assert.Equal("weather", decision.IntentName); - Assert.Equal("I can forecast up to 5 days ahead. Try tomorrow or another day this week.", decision.ReplyText); - Assert.Null(provider.LastRequest); + Assert.Contains("next five-day forecast", decision.ReplyText, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Seattle, US", decision.ReplyText, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Temperatures are in Fahrenheit.", decision.ReplyText, StringComparison.OrdinalIgnoreCase); + Assert.NotNull(provider.LastRequest); + Assert.Equal("Seattle", provider.LastRequest!.LocationQuery); + Assert.Equal(5, provider.LastRequest.ForecastDayOffset); } [Fact] @@ -2690,6 +2694,7 @@ public sealed class JiboInteractionServiceTests Assert.Equal("news", decision.SkillPayload!["skillId"]); Assert.Equal("news", decision.SkillPayload["cloudSkill"]); Assert.Equal("runtime-news", decision.SkillPayload["mim_id"]); + Assert.Contains("news-stinger", decision.SkillPayload["esml"]?.ToString(), StringComparison.OrdinalIgnoreCase); Assert.Equal("NewsAPI", decision.SkillPayload["news_source"]); Assert.Equal(2, decision.SkillPayload["news_headline_count"]); Assert.Contains("Local robotics team unveils weather-ready helper", decision.ReplyText, StringComparison.OrdinalIgnoreCase); @@ -2697,6 +2702,30 @@ public sealed class JiboInteractionServiceTests Assert.Equal(3, provider.LastRequest!.MaxHeadlines); } + [Fact] + public async Task BuildDecisionAsync_TellMeTheNews_WithAIAlias_UsesTechnologyCategory() + { + var provider = new CapturingNewsBriefingProvider + { + Snapshot = new NewsBriefingSnapshot( + [ + new NewsHeadline("AI labs unveil new home companion behaviors") + ], + "NewsAPI") + }; + var service = CreateService(newsBriefingProvider: provider); + + var decision = await service.BuildDecisionAsync(new TurnContext + { + RawTranscript = "tell me the a i news", + NormalizedTranscript = "tell me the a i news" + }); + + Assert.Equal("news", decision.IntentName); + Assert.NotNull(provider.LastRequest); + Assert.Contains("technology", provider.LastRequest!.PreferredCategories, StringComparer.OrdinalIgnoreCase); + } + [Fact] public async Task BuildDecisionAsync_TellMeTheNews_WithMemoryPreference_UsesCategoryHints() { diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs index ba4ff11..ade8d55 100644 --- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs +++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs @@ -2155,13 +2155,9 @@ public sealed class JiboWebSocketServiceTests Assert.Equal("views.weatherHiLo", gui.GetProperty("data").GetString()); Assert.True(gui.GetProperty("pause").GetBoolean()); - var play = jcpConfig.GetProperty("play"); - Assert.True(play.TryGetProperty("gui", out var playGui)); - Assert.Equal("Javascript", playGui.GetProperty("type").GetString()); - Assert.True(playGui.GetProperty("pause").GetBoolean()); - Assert.Equal("weatherTempView", playGui.GetProperty("data").GetProperty("viewConfig").GetProperty("id").GetString()); - Assert.Equal(0, play.GetProperty("no_matches_for_gui").GetInt32()); - Assert.Equal(0, play.GetProperty("no_inputs_for_gui").GetInt32()); + Assert.Equal(6, jcpConfig.GetProperty("timeout").GetInt32()); + Assert.Equal(0, jcpConfig.GetProperty("no_matches_for_gui").GetInt32()); + Assert.Equal(0, jcpConfig.GetProperty("no_inputs_for_gui").GetInt32()); Assert.True(jcpConfig.TryGetProperty("display", out var display)); Assert.Equal(