diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IJiboExperienceContentRepository.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IJiboExperienceContentRepository.cs index d17f971..1626a18 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IJiboExperienceContentRepository.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IJiboExperienceContentRepository.cs @@ -49,12 +49,4 @@ public sealed class JiboExperienceCatalog public IReadOnlyList GenericFallbackReplies { get; init; } = []; public IReadOnlyList DanceReplies { get; init; } = []; public IReadOnlyList DanceQuestionReplies { get; init; } = []; - - // Key = MIM stem (e.g. "RI_JBO_CanMakeCoffee"), Value = list of stripped reply texts - public IReadOnlyDictionary> NamedScriptedReplies { get; init; } - = new Dictionary>(StringComparer.OrdinalIgnoreCase); - - // Key = lowercased trigger phrase, Value = MIM stem it maps to - public IReadOnlyDictionary NamedScriptedTriggers { get; init; } - = new Dictionary(StringComparer.OrdinalIgnoreCase); } 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 3b9aaea..7c3cdcc 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 @@ -753,8 +753,7 @@ public sealed class JiboInteractionService( "calendar" => new JiboInteractionDecision("calendar", randomizer.Choose(catalog.CalendarReplies)), "commute" => new JiboInteractionDecision("commute", randomizer.Choose(catalog.CommuteReplies)), "news" => await BuildNewsDecisionAsync(turn, transcript, catalog, cancellationToken), - _ => TryBuildNamedMimDecision(catalog, lowered) - ?? new JiboInteractionDecision("chat", BuildGenericReply(catalog, transcript, lowered)) + _ => new JiboInteractionDecision("chat", BuildGenericReply(catalog, transcript, lowered)) }; } @@ -2234,16 +2233,10 @@ public sealed class JiboInteractionService( if (lowered.Contains("good afternoon", StringComparison.Ordinal)) return "Good afternoon. I am happy to be here."; - if (lowered.Contains("good night", StringComparison.Ordinal)) - return "Good night. Sleep tight."; - - // For unrecognized chitchat, use personality replies rather than the - // CC_Error "sources unavailable" content (which is reserved for service failures). - var chitchatPool = catalog.PersonalityReplies.Count > 0 - ? catalog.PersonalityReplies - : catalog.GenericFallbackReplies; - return randomizer.Choose(chitchatPool) - .Replace("{transcript}", transcript, StringComparison.Ordinal); + return lowered.Contains("good night", StringComparison.Ordinal) + ? "Good night. Sleep tight." + : randomizer.Choose(catalog.GenericFallbackReplies) + .Replace("{transcript}", transcript, StringComparison.Ordinal); } private JiboInteractionDecision BuildScriptedPersonalityDecision( @@ -2257,31 +2250,6 @@ public sealed class JiboInteractionService( ContextUpdates: BuildScriptedResponseContextUpdates()); } - private JiboInteractionDecision? TryBuildNamedMimDecision( - JiboExperienceCatalog catalog, string loweredTranscript) - { - // Exact trigger match first - if (!catalog.NamedScriptedTriggers.TryGetValue(loweredTranscript, out var stem)) - { - // Partial contains match: find the longest trigger phrase contained in the transcript - stem = catalog.NamedScriptedTriggers - .Where(kv => loweredTranscript.Contains(kv.Key, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(kv => kv.Key.Length) - .Select(static kv => kv.Value) - .FirstOrDefault(); - } - - if (stem is null) return null; - - if (!catalog.NamedScriptedReplies.TryGetValue(stem, out var replies) || replies.Count == 0) - return null; - - return new JiboInteractionDecision( - stem, - randomizer.Choose(replies), - ContextUpdates: BuildScriptedResponseContextUpdates()); - } - private JiboInteractionDecision BuildScriptedGreetingDecision( JiboExperienceCatalog catalog, string intentName, diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs index 6787b33..59ffe2a 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs @@ -30,20 +30,6 @@ public static class LegacyMimCatalogImporter @"\s+([,.;:!?])", RegexOptions.CultureInvariant | RegexOptions.Compiled); - // Splits CamelCase words, e.g. "CanMakeCoffee" → ["Can", "Make", "Coffee"] - private static readonly Regex CamelCaseSplitPattern = new( - @"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", - RegexOptions.CultureInvariant | RegexOptions.Compiled); - - // Known file prefixes to strip when deriving trigger phrases - private static readonly string[] KnownPrefixes = - [ - "RI_JBO_", "OI_JBO_", "JBO_", "RA_JBO_", "RN_JBO_", "RN_", - "KU_JBO_", "KU_", "JF_JBO_", "JF_", "SUP_JBO_", "SUP_", - "SRS_JBO_", "SRS_", "USR_JBO_", "USR_", "PR_JBO_", "PR_", - "CC_", "RA_", "OI_", "RI_" - ]; - public static JiboExperienceCatalog MergeInto( JiboExperienceCatalog baseCatalog, string? rootDirectory) @@ -70,35 +56,18 @@ public static class LegacyMimCatalogImporter var bucket = ResolveBucket(filePath); if (bucket is null) continue; - var fileName = Path.GetFileNameWithoutExtension(filePath); - var isScriptedResponse = IsScriptedResponsePath(filePath); - - var texts = new List(); foreach (var prompt in definition.Prompts) { var text = NormalizePrompt(prompt.Prompt, IsTemplateBucket(bucket.Value)); if (string.IsNullOrWhiteSpace(text)) continue; builder.Add(bucket.Value, prompt.Condition, text, prompt.Prompt); - texts.Add(text); } - - // Build named lookup for all scripted-response files - if (isScriptedResponse && texts.Count > 0) - builder.AddNamed(fileName, texts); } return builder.Build(); } - private static bool IsScriptedResponsePath(string filePath) - { - var normalizedPath = filePath.Replace('\\', '/'); - return normalizedPath.Contains("/scripted-responses/", StringComparison.OrdinalIgnoreCase) - || normalizedPath.Contains("/emotion-responses/", StringComparison.OrdinalIgnoreCase) - || normalizedPath.Contains("/gqa-responses/", StringComparison.OrdinalIgnoreCase); - } - private static bool TryLoadDefinition(string filePath, out LegacyMimDefinition definition) { definition = new LegacyMimDefinition(); @@ -228,128 +197,6 @@ public static class LegacyMimCatalogImporter return null; } - /// - /// Derives natural-language trigger phrases from a MIM filename stem. - /// E.g. "RI_JBO_CanMakeCoffee" → ["can make coffee", "can you make coffee", "are you able to make coffee"] - /// - internal static IReadOnlyList DeriveTriggerPhrases(string fileName) - { - var name = fileName; - - // Strip known prefix - foreach (var prefix in KnownPrefixes) - { - if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - name = name[prefix.Length..]; - break; - } - } - - if (string.IsNullOrWhiteSpace(name)) return []; - - // Split CamelCase and lowercase - var parts = CamelCaseSplitPattern.Split(name); - var lowered = parts.Select(static p => p.ToLowerInvariant()).Where(static p => !string.IsNullOrEmpty(p)).ToArray(); - if (lowered.Length == 0) return []; - - var joined = string.Join(" ", lowered); - var rest = lowered.Length > 1 ? string.Join(" ", lowered.Skip(1)) : string.Empty; - - var triggers = new List { joined }; - - var first = lowered[0]; - - switch (first) - { - case "can": - if (!string.IsNullOrEmpty(rest)) - { - triggers.Add($"can you {rest}"); - triggers.Add($"are you able to {rest}"); - triggers.Add($"could you {rest}"); - } - break; - - case "is": - if (!string.IsNullOrEmpty(rest)) - { - triggers.Add($"are you {rest}"); - triggers.Add($"is jibo {rest}"); - } - break; - - case "are": - if (!string.IsNullOrEmpty(rest)) - triggers.Add($"are you {rest}"); - break; - - case "likes" or "like": - if (!string.IsNullOrEmpty(rest)) - { - triggers.Add($"do you like {rest}"); - triggers.Add($"do you enjoy {rest}"); - triggers.Add($"does jibo like {rest}"); - } - break; - - case "loves" or "love": - if (!string.IsNullOrEmpty(rest)) - { - triggers.Add($"do you love {rest}"); - triggers.Add($"do you like {rest}"); - } - break; - - case "believes" or "believe": - if (!string.IsNullOrEmpty(rest)) - { - triggers.Add($"do you believe {rest}"); - // "BelievesInSanta" → rest = "in santa" → already covered, but also add without "in" - if (rest.StartsWith("in ", StringComparison.Ordinal)) - triggers.Add($"do you believe {rest["in ".Length..]}"); - } - break; - - case "knows" or "know": - if (!string.IsNullOrEmpty(rest)) - { - triggers.Add($"do you know {rest}"); - triggers.Add($"do you know about {rest}"); - } - break; - - case "has" or "have": - if (!string.IsNullOrEmpty(rest)) - { - triggers.Add($"do you have {rest}"); - triggers.Add($"have you {rest}"); - } - break; - - case "wants" or "want": - if (!string.IsNullOrEmpty(rest)) - triggers.Add($"do you want {rest}"); - break; - - case "what": - case "who": - case "where": - case "when": - case "why": - case "how": - // Already in question form — keep as-is, no extra variants needed - break; - - default: - // Generic: emit "do you [all words]" as a fallback variant - triggers.Add($"do you {joined}"); - break; - } - - return triggers.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - } - private static string NormalizePrompt(string? prompt) { return NormalizePrompt(prompt, false); @@ -419,9 +266,7 @@ public static class LegacyMimCatalogImporter NewsBriefings = Merge(baseCatalog.NewsBriefings, importedCatalog.NewsBriefings), GenericFallbackReplies = Merge(baseCatalog.GenericFallbackReplies, importedCatalog.GenericFallbackReplies), DanceReplies = Merge(baseCatalog.DanceReplies, importedCatalog.DanceReplies), - DanceQuestionReplies = Merge(baseCatalog.DanceQuestionReplies, importedCatalog.DanceQuestionReplies), - NamedScriptedReplies = MergeNamed(baseCatalog.NamedScriptedReplies, importedCatalog.NamedScriptedReplies), - NamedScriptedTriggers = MergeTriggers(baseCatalog.NamedScriptedTriggers, importedCatalog.NamedScriptedTriggers) + DanceQuestionReplies = Merge(baseCatalog.DanceQuestionReplies, importedCatalog.DanceQuestionReplies) }; } @@ -469,50 +314,6 @@ public static class LegacyMimCatalogImporter return merged; } - private static IReadOnlyDictionary> MergeNamed( - IReadOnlyDictionary> baseDict, - IReadOnlyDictionary> importedDict) - { - var result = new Dictionary>( - baseDict, StringComparer.OrdinalIgnoreCase); - - foreach (var (key, importedReplies) in importedDict) - { - if (result.TryGetValue(key, out var existing)) - { - // Merge reply lists, deduplicating - var seen = new HashSet(existing, StringComparer.OrdinalIgnoreCase); - var merged = new List(existing); - foreach (var reply in importedReplies) - { - if (!string.IsNullOrWhiteSpace(reply) && seen.Add(reply.Trim())) - merged.Add(reply.Trim()); - } - result[key] = merged; - } - else - { - result[key] = importedReplies; - } - } - - return result; - } - - private static IReadOnlyDictionary MergeTriggers( - IReadOnlyDictionary baseDict, - IReadOnlyDictionary importedDict) - { - // Base catalog's explicit triggers win; imported fills gaps - var result = new Dictionary(baseDict, StringComparer.OrdinalIgnoreCase); - foreach (var (trigger, stem) in importedDict) - { - if (!result.ContainsKey(trigger)) - result[trigger] = stem; - } - return result; - } - private static string NormalizeCondition(string? condition) { return string.IsNullOrWhiteSpace(condition) ? string.Empty : WhitespacePattern.Replace(condition.Trim(), " "); @@ -588,12 +389,6 @@ public static class LegacyMimCatalogImporter private readonly List _weatherTomorrowHighLowReplies = []; private readonly List _weatherTomorrowIntroReplies = []; - // Named MIM dictionaries - private readonly Dictionary> _namedReplies = - new(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _namedTriggers = - new(StringComparer.OrdinalIgnoreCase); - public void Add(LegacyMimBucket bucket, string? condition, string text, string? sourcePrompt = null) { switch (bucket) @@ -716,37 +511,8 @@ public static class LegacyMimCatalogImporter } } - public void AddNamed(string fileName, IReadOnlyList replies) - { - if (!_namedReplies.TryGetValue(fileName, out var list)) - { - list = []; - _namedReplies[fileName] = list; - } - - var seen = new HashSet(list, StringComparer.OrdinalIgnoreCase); - foreach (var reply in replies) - { - if (!string.IsNullOrWhiteSpace(reply) && seen.Add(reply.Trim())) - list.Add(reply.Trim()); - } - - // Derive and register trigger phrases - var triggers = DeriveTriggerPhrases(fileName); - foreach (var trigger in triggers) - { - if (!string.IsNullOrWhiteSpace(trigger) && !_namedTriggers.ContainsKey(trigger)) - _namedTriggers[trigger] = fileName; - } - } - public JiboExperienceCatalog Build() { - var namedReplies = _namedReplies.ToDictionary( - static kv => kv.Key, - static kv => (IReadOnlyList)kv.Value, - StringComparer.OrdinalIgnoreCase); - return new JiboExperienceCatalog { Jokes = [.. _jokes], @@ -773,9 +539,7 @@ public static class LegacyMimCatalogImporter CommuteServiceDownReplies = [.. _commuteServiceDownReplies], NewsIntroReplies = [.. _newsIntroReplies], NewsCategoryIntroReplies = [.. _newsCategoryIntroReplies], - NewsOutroReplies = [.. _newsOutroReplies], - NamedScriptedReplies = namedReplies, - NamedScriptedTriggers = new Dictionary(_namedTriggers, StringComparer.OrdinalIgnoreCase) + NewsOutroReplies = [.. _newsOutroReplies] }; }