diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ChitchatStateMachine.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ChitchatStateMachine.cs index afe696e..83df9fb 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ChitchatStateMachine.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ChitchatStateMachine.cs @@ -167,6 +167,7 @@ internal static class ChitchatStateMachine JiboExperienceCatalog catalog, IJiboRandomizer randomizer, string? currentEmotion, + string? preferredName, Func buildErrorResponse) { var normalizedLoweredTranscript = NormalizeForPhraseMatching(loweredTranscript); @@ -187,7 +188,7 @@ internal static class ChitchatStateMachine case "how_are_you": return BuildEmotionQueryDecision( "how_are_you", - SelectEmotionQueryReply(catalog, randomizer, currentEmotion)); + SelectEmotionQueryReply(catalog, randomizer, currentEmotion, preferredName)); case "robot_desire": return BuildScriptedResponseDecision( "robot_desire", @@ -285,9 +286,9 @@ internal static class ChitchatStateMachine SelectLegacyPersonalityReply(catalog, randomizer, "know a lot", "not as much as i will someday")); case "chat": if (IsEmotionQuery(normalizedLoweredTranscript)) - return BuildEmotionQueryDecision( + return BuildEmotionQueryDecision( "emotion_query", - SelectEmotionQueryReply(catalog, randomizer, currentEmotion)); + SelectEmotionQueryReply(catalog, randomizer, currentEmotion, preferredName)); if (TryResolveEmotionCommand(normalizedLoweredTranscript, out var emotion)) return BuildEmotionCommandDecision(randomizer, emotion!); @@ -391,16 +392,36 @@ internal static class ChitchatStateMachine private static string SelectEmotionQueryReply( JiboExperienceCatalog catalog, IJiboRandomizer randomizer, - string? currentEmotion) + string? currentEmotion, + string? preferredName) { - if (catalog.EmotionReplies.Count == 0) return randomizer.Choose(catalog.HowAreYouReplies); + if (catalog.EmotionReplies.Count == 0) + return PersonalizeHowAreYouReply(randomizer.Choose(catalog.HowAreYouReplies), preferredName); var emotionVariants = ResolveEmotionVariants(currentEmotion); foreach (var reply in catalog.EmotionReplies) if (ConditionMatches(reply.Condition, emotionVariants)) - return reply.Reply; + return PersonalizeHowAreYouReply(reply.Reply, preferredName); - return randomizer.Choose(catalog.HowAreYouReplies); + return PersonalizeHowAreYouReply(randomizer.Choose(catalog.HowAreYouReplies), preferredName); + } + + private static string PersonalizeHowAreYouReply(string replyText, string? preferredName) + { + if (string.IsNullOrWhiteSpace(replyText) || string.IsNullOrWhiteSpace(preferredName)) return replyText; + + var trimmedName = preferredName.Trim(); + if (replyText.Contains(trimmedName, StringComparison.OrdinalIgnoreCase)) return replyText; + + var trimmedReply = replyText.Trim(); + var firstSentenceEnd = trimmedReply.IndexOfAny(['.', '!', '?']); + if (firstSentenceEnd <= 0) + return $"{trimmedReply}, {trimmedName}."; + + if (firstSentenceEnd == trimmedReply.Length - 1) + return $"{trimmedReply[..firstSentenceEnd]}, {trimmedName}."; + + return $"{trimmedReply[..firstSentenceEnd]}, {trimmedName}{trimmedReply[firstSentenceEnd..]}"; } private static bool ConditionMatches(string? condition, IReadOnlyList emotionVariants) @@ -620,4 +641,4 @@ internal static class ChitchatStateMachine mappings.Sort(static (left, right) => right.Phrase.Length.CompareTo(left.Phrase.Length)); return [.. mappings]; } -} \ No newline at end of file +} 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 e7224fe..374a2b6 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 @@ -502,6 +502,7 @@ public sealed partial class JiboInteractionService( turnContext => ResolveTenantScope(turnContext)); if (householdListDecision is not null) return householdListDecision; + var preferredName = ResolvePreferredGreetingName(turn, greetingPresence); var chitchatDecision = ChitchatStateMachine.TryBuildDecision( semanticIntent, transcript, @@ -509,6 +510,7 @@ public sealed partial class JiboInteractionService( catalog, randomizer, chitchatEmotion, + preferredName, () => BuildGenericReply(catalog, transcript, lowered)); if (chitchatDecision is not null) return chitchatDecision; diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs index cc66a97..57a0257 100644 --- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs +++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs @@ -861,6 +861,29 @@ public sealed class JiboInteractionServiceTests Assert.Equal("EmotionQuery", decision.ContextUpdates![ChitchatRouteKey]); } + [Fact] + public async Task BuildDecisionAsync_HowAreYou_UsesRememberedNameForStateDrivenReply() + { + var memoryStore = new InMemoryPersonalMemoryStore(); + memoryStore.SetName(new PersonalMemoryTenantScope("acct-how", "loop-how", "device-how"), "jake"); + var service = CreateService(memoryStore); + + var decision = await service.BuildDecisionAsync(new TurnContext + { + RawTranscript = "how are you", + NormalizedTranscript = "how are you", + Attributes = new Dictionary + { + ["accountId"] = "acct-how", + ["loopId"] = "loop-how" + }, + DeviceId = "device-how" + }); + + Assert.Equal("how_are_you", decision.IntentName); + Assert.Equal("All systems are go, Jake.", decision.ReplyText); + } + [Theory] [InlineData("what are you up to", "being helpful")] [InlineData("what are you doing", "making people smile")]