Files
JiboExperiments/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs
2026-05-23 00:37:22 -05:00

418 lines
15 KiB
C#

using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;
using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Domain.Models;
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services;
public sealed partial class JiboInteractionService(
JiboExperienceContentCache contentCache,
IJiboRandomizer randomizer,
IPersonalMemoryStore personalMemoryStore,
IWeatherReportProvider? weatherReportProvider = null,
ICalendarReportProvider? calendarReportProvider = null,
ICommuteReportProvider? commuteReportProvider = null,
INewsBriefingProvider? newsBriefingProvider = null,
ICloudStateStore? cloudStateStore = null)
{
private const string GreetingRouteMetadataKey = "greetingsRoute";
private const string GreetingSpeakerMetadataKey = "greetingsSpeaker";
private const string LastProactiveGreetingUtcMetadataKey = "greetingsLastProactiveUtc";
private const string LastReactiveGreetingUtcMetadataKey = "greetingsLastReactiveUtc";
private const int MaxWeatherForecastDayOffset = 5;
private const int MaxNewsHeadlines = 3;
private const int MaxPreferredNewsCategories = 2;
private static readonly Regex SplitAlarmPattern = new(
@"\b(?<hour>\d{1,2}|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)(?:[:\s,-]+(?<minute>\d{2}|[a-z\-]+(?:\s+[a-z\-]+)?))?\s*(?<ampm>a[\s\.]*m\.?|p[\s\.]*m\.?)?\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex CompactAlarmPattern = new(
@"\b(?<compact>\d{3,4})\s*(?<ampm>a[\s\.]*m\.?|p[\s\.]*m\.?)?\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex VolumeLevelPattern = new(
@"\b(?:volume|loudness)\s*(?:to|at|level|is)?\s*(?<value>10|\d|one|two|three|four|five|six|seven|eight|nine|ten)\b|\b(?:set|change|make|turn)\s+(?:the\s+|your\s+)?(?:volume|loudness)\s*(?:to|at)?\s*(?<value>10|\d|one|two|three|four|five|six|seven|eight|nine|ten)\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex VolumeToValueHomophonePattern = new(
@"\b(?:volume|loudness)\s+(?:2|two|to)\s+(?<value>10|\d|one|two|three|four|five|six|seven|eight|nine|ten)\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly string[] CommandLeadPhrases =
[
"hey jibo",
"hello jibo",
"hi jibo",
"jibo",
"o",
"oh",
"so",
"well",
"um",
"uh",
"hmm",
"erm",
"ah",
"please",
"ok jibo",
"okay jibo"
];
private static readonly Regex AlarmDeletePattern = new(
@"\b(?:cancel|delete|remove|stop|turn\s+off)\s+(?:the\s+)?(?:alarm|along|elo)\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex WeatherLocationPattern = new(
@"\b(?:in|for|at)\s+(?<location>[a-z][a-z\s'\-]+)$",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex WeatherLocationSuffixPattern = new(
@"\b(?:today|tonight|tomorrow|day after tomorrow|outside|right now|please|thanks|this weekend|next weekend|the weekend|weekend|this week|next week|on monday|on tuesday|on wednesday|on thursday|on friday|on saturday|on sunday|this monday|this tuesday|this wednesday|this thursday|this friday|this saturday|this sunday|next monday|next tuesday|next wednesday|next thursday|next friday|next saturday|next sunday|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex WeatherConditionForecastPattern = new(
@"\bwill it be\s+(sunny|cloudy|windy|foggy|stormy|rainy|snowy|hail|hailing)\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex WeatherTopicLocationPattern = new(
@"\b(?:weather|forecast|temperature|humidity)\b.*\b(?:in|for|at)\s+[a-z]",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex WeatherDayOfWeekPattern = new(
@"\b(?<next>next\s+)?(?<this>this\s+)?(?:on\s+)?(?<day>monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly PizzaMimPrompt[] PizzaMimPrompts =
[
new("RA_JBO_ShowPizzaMaking_AN_01", "<speak><anim cat='jiboji' filter='pizza-making'/></speak>"),
new("RA_JBO_ShowPizzaMaking_AN_02",
"<speak><anim cat='jiboji' filter='pizza-making' nonBlocking='true'/><pitch mult='1.2'>One </pitch> pizza, coming right up.</speak>"),
new("RA_JBO_ShowPizzaMaking_AN_03",
"<speak><anim cat='jiboji' filter='pizza-making' nonBlocking='true'/>My <pitch mult='1.2'>specialty </pitch>.</speak>")
];
private static readonly string[] PreferenceSetMarkers =
[
"my favorite ",
"my favourite "
];
private static readonly string[] PreferenceReverseMarkers =
[
" is my favorite ",
" is my favourite ",
" are my favorite ",
" are my favourite "
];
private static readonly string[] WeatherDateEntityKeys =
[
"date",
"sys.date",
"datetime",
"dateTime",
"date_time",
"day"
];
private static readonly string[] YesNoAcknowledgementPrefixes =
[
"uh",
"um",
"hmm",
"well",
"so",
"actually",
"honestly",
"thanks",
"thank you"
];
private static readonly HashSet<string> YesNoAffirmativeLeadTokens = new(StringComparer.Ordinal)
{
"yes",
"yeah",
"yep",
"yup",
"sure",
"ok",
"okay",
"absolutely",
"affirmative",
"definitely",
"certainly",
"indeed"
};
private static readonly HashSet<string> YesNoNegativeLeadTokens = new(StringComparer.Ordinal)
{
"no",
"nope",
"nah",
"negative",
"never"
};
private static readonly HashSet<string> YesNoAffirmativeLeadPhrases = new(StringComparer.Ordinal)
{
"uh huh",
"sounds good",
"sure thing",
"why not",
"please do",
"go ahead",
"of course",
"i guess so",
"i think so"
};
private static readonly HashSet<string> YesNoNegativeLeadPhrases = new(StringComparer.Ordinal)
{
"not now",
"not today",
"not really",
"no thanks",
"no thank you",
"maybe later",
"i guess not",
"i do not",
"i dont",
"i don t"
};
// Directly imported from Pegasus parser intent phrase families:
// userLikesThing / userDislikesThing / doesUserLikeThing / doesUserDislikeThing.
private static readonly (string Prefix, PersonalAffinity Affinity)[] PegasusUserAffinitySetPrefixes =
[
("i love ", PersonalAffinity.Love),
("i like ", PersonalAffinity.Like),
("i like the ", PersonalAffinity.Like),
("i enjoy ", PersonalAffinity.Like),
("i do like ", PersonalAffinity.Like),
("we love ", PersonalAffinity.Love),
("we like ", PersonalAffinity.Like),
("we enjoy ", PersonalAffinity.Like),
("i dislike ", PersonalAffinity.Dislike),
("i hate ", PersonalAffinity.Dislike),
("i hate the ", PersonalAffinity.Dislike),
("i loathe ", PersonalAffinity.Dislike),
("i don t like ", PersonalAffinity.Dislike),
("i dont like ", PersonalAffinity.Dislike),
("i not like ", PersonalAffinity.Dislike),
("i do not like ", PersonalAffinity.Dislike),
("i did not like ", PersonalAffinity.Dislike),
("i did not like the ", PersonalAffinity.Dislike),
("i didn t like ", PersonalAffinity.Dislike),
("i didnt like ", PersonalAffinity.Dislike),
("i didn t like the ", PersonalAffinity.Dislike),
("i didnt like the ", PersonalAffinity.Dislike),
("i didn t really like ", PersonalAffinity.Dislike),
("i didnt really like ", PersonalAffinity.Dislike),
("i don t really like ", PersonalAffinity.Dislike),
("i dont really like ", PersonalAffinity.Dislike),
("i don t enjoy ", PersonalAffinity.Dislike),
("i dont enjoy ", PersonalAffinity.Dislike),
("i do not enjoy ", PersonalAffinity.Dislike),
("i did not enjoy ", PersonalAffinity.Dislike),
("i didn t enjoy ", PersonalAffinity.Dislike),
("i didnt enjoy ", PersonalAffinity.Dislike),
("i didn t really enjoy ", PersonalAffinity.Dislike),
("i didnt really enjoy ", PersonalAffinity.Dislike),
("i don t love ", PersonalAffinity.Dislike),
("i dont love ", PersonalAffinity.Dislike),
("i do not love ", PersonalAffinity.Dislike),
("i don t love to ", PersonalAffinity.Dislike),
("i dont love to ", PersonalAffinity.Dislike),
("i do not love to ", PersonalAffinity.Dislike),
("i can t stand ", PersonalAffinity.Dislike),
("i cant stand ", PersonalAffinity.Dislike),
("i can t stand the ", PersonalAffinity.Dislike),
("i cant stand the ", PersonalAffinity.Dislike),
("we dislike ", PersonalAffinity.Dislike),
("we hate ", PersonalAffinity.Dislike),
("we despise ", PersonalAffinity.Dislike),
("we detest ", PersonalAffinity.Dislike),
("we loathe ", PersonalAffinity.Dislike),
("we can t stand ", PersonalAffinity.Dislike),
("we cant stand ", PersonalAffinity.Dislike),
("i despise ", PersonalAffinity.Dislike),
("i detest ", PersonalAffinity.Dislike)
];
private static readonly (string Prefix, PersonalAffinity? ExpectedAffinity)[] PegasusUserAffinityLookupPrefixes =
[
("do i love ", PersonalAffinity.Love),
("do i like ", PersonalAffinity.Like),
("do i enjoy ", PersonalAffinity.Like),
("do i dislike ", PersonalAffinity.Dislike),
("do i hate ", PersonalAffinity.Dislike),
("do i loathe ", PersonalAffinity.Dislike),
("do i not like ", PersonalAffinity.Dislike),
("do i despise ", PersonalAffinity.Dislike),
("do i detest ", PersonalAffinity.Dislike),
("do you think i like ", PersonalAffinity.Like),
("do you believe i like ", PersonalAffinity.Like),
("do you think i don t like ", PersonalAffinity.Dislike),
("do you believe i don t like ", PersonalAffinity.Dislike),
("how do i feel about ", null),
("what do i think about ", null)
];
private static readonly HashSet<string> GenericWeatherLocationTerms = new(StringComparer.OrdinalIgnoreCase)
{
"my area",
"our area",
"this area",
"the area",
"the city",
"this city",
"our city",
"my city",
"the town",
"this town",
"our town",
"my town",
"our street",
"this street",
"my street",
"the neighborhood",
"the neighbourhood",
"this neighborhood",
"this neighbourhood",
"our neighborhood",
"our neighbourhood"
};
private static readonly HashSet<string> SpokenAbbreviationTokens = new(StringComparer.Ordinal)
{
"US",
"USA",
"UK",
"UAE",
"EU",
"DC",
"AL",
"AK",
"AZ",
"AR",
"CA",
"CO",
"CT",
"DE",
"FL",
"GA",
"HI",
"ID",
"IL",
"IN",
"IA",
"KS",
"KY",
"LA",
"ME",
"MD",
"MA",
"MI",
"MN",
"MS",
"MO",
"MT",
"NE",
"NV",
"NH",
"NJ",
"NM",
"NY",
"NC",
"ND",
"OH",
"OK",
"OR",
"PA",
"RI",
"SC",
"SD",
"TN",
"TX",
"UT",
"VT",
"VA",
"WA",
"WV",
"WI",
"WY"
};
private static readonly TimeSpan ProactiveGreetingCooldown = TimeSpan.FromMinutes(20);
private static readonly (string Keyword, string Category)[] NewsCategoryKeywordMap =
[
("sports", "sports"),
("sport", "sports"),
("football", "sports"),
("baseball", "sports"),
("basketball", "sports"),
("hockey", "sports"),
("technology", "technology"),
("tech", "technology"),
("ai", "technology"),
("a i", "technology"),
("a eye", "technology"),
("aye eye", "technology"),
("artificial intelligence", "technology"),
("science", "science"),
("business", "business"),
("finance", "business"),
("market", "business"),
("stock", "business"),
("politics", "general"),
("political", "general"),
("world", "general"),
("entertainment", "entertainment"),
("movie", "entertainment"),
("music", "entertainment")
];
private static readonly (string Phrase, string Station)[] RadioGenreAliases =
[
("country music", "Country"),
("country radio", "Country"),
("country", "Country"),
("football", "Sports"),
("sports", "Sports"),
("classic rock", "ClassicRock"),
("soft rock", "SoftRock"),
("hip hop", "HipHop"),
("hip-hop", "HipHop"),
("news and talk", "NewsAndTalk"),
("news talk", "NewsAndTalk"),
("news radio", "NewsAndTalk"),
("sports radio", "Sports"),
("christian music", "ChristianAndGospel"),
("gospel music", "ChristianAndGospel"),
("oldies", "Oldies"),
("pop music", "Pop"),
("jazz", "Jazz"),
("latin music", "Latin"),
("dance music", "Dance"),
("reggae", "ReggaeAndIsland"),
("island music", "ReggaeAndIsland"),
("alternative", "Alternative"),
("blues", "Blues"),
("classical music", "Classical"),
("classical", "Classical"),
("college radio", "CollegeRadio"),
("comedy radio", "Comedy"),
("npr", "NPR")
];
public Task<JiboInteractionDecision> BuildDecisionAsync(TurnContext turn,
CancellationToken cancellationToken = default)
=> BuildDecisionCoreAsync(turn, cancellationToken);
}