a couple more features for version 18
This commit is contained in:
@@ -74,6 +74,11 @@ public sealed class DemoConversationBroker(JiboInteractionService interactionSer
|
||||
"word_of_the_day_guess" => false,
|
||||
"radio" => false,
|
||||
"radio_genre" => false,
|
||||
"stop" => false,
|
||||
"volume_up" => false,
|
||||
"volume_down" => false,
|
||||
"volume_to_value" => false,
|
||||
"volume_query" => false,
|
||||
"time" => false,
|
||||
"date" => false,
|
||||
"day" => false,
|
||||
|
||||
@@ -51,6 +51,11 @@ public sealed class JiboInteractionService(
|
||||
"cloud_version" => new JiboInteractionDecision("cloud_version", OpenJiboCloudBuildInfo.SpokenVersion),
|
||||
"radio" => BuildRadioLaunchDecision(),
|
||||
"radio_genre" => BuildRadioGenreLaunchDecision(lowered),
|
||||
"stop" => BuildStopDecision(),
|
||||
"volume_up" => BuildVolumeControlDecision("volume_up", "volumeUp", "null"),
|
||||
"volume_down" => BuildVolumeControlDecision("volume_down", "volumeDown", "null"),
|
||||
"volume_to_value" => BuildVolumeControlDecision("volume_to_value", "volumeToValue", ResolveVolumeLevel(lowered, clientEntities) ?? "7"),
|
||||
"volume_query" => BuildSettingsVolumeDecision(),
|
||||
"clock_open" => BuildClockLaunchDecision("clock_open", "clock", "askForTime", "Opening the clock."),
|
||||
"clock_menu" => BuildClockLaunchDecision("clock_menu", "clock", "menu", "Opening the clock menu."),
|
||||
"timer_menu" => BuildClockLaunchDecision("timer", "Opening the timer."),
|
||||
@@ -309,6 +314,27 @@ public sealed class JiboInteractionService(
|
||||
return "radio_genre";
|
||||
}
|
||||
|
||||
if (TryResolveVolumeLevel(loweredTranscript) is not null ||
|
||||
clientEntities.ContainsKey("volumeLevel"))
|
||||
{
|
||||
return "volume_to_value";
|
||||
}
|
||||
|
||||
if (IsVolumeQueryRequest(loweredTranscript))
|
||||
{
|
||||
return "volume_query";
|
||||
}
|
||||
|
||||
if (IsVolumeUpRequest(loweredTranscript))
|
||||
{
|
||||
return "volume_up";
|
||||
}
|
||||
|
||||
if (IsVolumeDownRequest(loweredTranscript))
|
||||
{
|
||||
return "volume_down";
|
||||
}
|
||||
|
||||
if (MatchesAny(loweredTranscript, "open the clock", "open clock", "show the clock", "show clock"))
|
||||
{
|
||||
return "clock_open";
|
||||
@@ -346,6 +372,11 @@ public sealed class JiboInteractionService(
|
||||
return "timer_delete";
|
||||
}
|
||||
|
||||
if (IsGlobalStopRequest(loweredTranscript, clientIntent, clientEntities))
|
||||
{
|
||||
return "stop";
|
||||
}
|
||||
|
||||
if (TryParseAlarmValue(loweredTranscript, isAlarmValueTurn, referenceLocalTime) is not null)
|
||||
{
|
||||
return "alarm_value";
|
||||
@@ -535,6 +566,47 @@ public sealed class JiboInteractionService(
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildStopDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"stop",
|
||||
"Stopping.",
|
||||
"@be/idle",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["skillId"] = "@be/idle",
|
||||
["globalIntent"] = "stop",
|
||||
["nluDomain"] = "global_commands"
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildVolumeControlDecision(string intentName, string globalIntent, string volumeLevel)
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
intentName,
|
||||
"Adjusting volume.",
|
||||
"global_commands",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["globalIntent"] = globalIntent,
|
||||
["nluDomain"] = "global_commands",
|
||||
["volumeLevel"] = volumeLevel
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildSettingsVolumeDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"volume_query",
|
||||
"Opening volume controls.",
|
||||
"@be/settings",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["skillId"] = "@be/settings",
|
||||
["localIntent"] = "volumeQuery"
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildClockLaunchDecision(string intentName, string domain, string clockIntent, string replyText)
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
@@ -1120,6 +1192,144 @@ public sealed class JiboInteractionService(
|
||||
loweredTranscript is "cancel" or "stop" or "never mind" or "nevermind";
|
||||
}
|
||||
|
||||
private static bool IsGlobalStopRequest(
|
||||
string loweredTranscript,
|
||||
string? clientIntent,
|
||||
IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
if (string.Equals(clientIntent, "stop", StringComparison.OrdinalIgnoreCase) &&
|
||||
IsGlobalCommandsDomain(clientEntities))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return loweredTranscript is "stop" or "stop it" or "stop that" or "stop talking" or "be quiet" or "never mind" or "nevermind" or "forget it" ||
|
||||
MatchesAny(loweredTranscript, "that s enough", "that's enough", "that will do", "that ll do", "that'll do", "cut it out", "cut that out");
|
||||
}
|
||||
|
||||
private static bool IsVolumeQueryRequest(string loweredTranscript)
|
||||
{
|
||||
return MatchesAny(
|
||||
loweredTranscript,
|
||||
"volume controls",
|
||||
"volume control",
|
||||
"volume menu",
|
||||
"volume level",
|
||||
"show volume",
|
||||
"show the volume",
|
||||
"open volume",
|
||||
"open the volume",
|
||||
"what is your volume",
|
||||
"what's your volume",
|
||||
"how is your volume",
|
||||
"how s your volume");
|
||||
}
|
||||
|
||||
private static bool IsVolumeUpRequest(string loweredTranscript)
|
||||
{
|
||||
return MatchesAny(
|
||||
loweredTranscript,
|
||||
"turn it up",
|
||||
"turn this up",
|
||||
"turn that up",
|
||||
"turn up the volume",
|
||||
"turn the volume up",
|
||||
"turn volume up",
|
||||
"turn your volume up",
|
||||
"increase the volume",
|
||||
"increase your volume",
|
||||
"raise the volume",
|
||||
"raise your volume",
|
||||
"make it louder",
|
||||
"make that louder",
|
||||
"speak louder",
|
||||
"talk louder",
|
||||
"be louder",
|
||||
"louder");
|
||||
}
|
||||
|
||||
private static bool IsVolumeDownRequest(string loweredTranscript)
|
||||
{
|
||||
return MatchesAny(
|
||||
loweredTranscript,
|
||||
"turn it down",
|
||||
"turn this down",
|
||||
"turn that down",
|
||||
"turn down the volume",
|
||||
"turn the volume down",
|
||||
"turn volume down",
|
||||
"turn your volume down",
|
||||
"decrease the volume",
|
||||
"decrease your volume",
|
||||
"lower the volume",
|
||||
"lower your volume",
|
||||
"make it quieter",
|
||||
"make that quieter",
|
||||
"make it softer",
|
||||
"speak quieter",
|
||||
"talk quieter",
|
||||
"be quieter",
|
||||
"quieter",
|
||||
"softer");
|
||||
}
|
||||
|
||||
private static string? ResolveVolumeLevel(string loweredTranscript, IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
if (clientEntities.TryGetValue("volumeLevel", out var entityValue) &&
|
||||
TryNormalizeVolumeLevel(entityValue) is { } structuredLevel)
|
||||
{
|
||||
return structuredLevel;
|
||||
}
|
||||
|
||||
return TryResolveVolumeLevel(loweredTranscript);
|
||||
}
|
||||
|
||||
private static string? TryResolveVolumeLevel(string loweredTranscript)
|
||||
{
|
||||
if (!loweredTranscript.Contains("volume", StringComparison.Ordinal) &&
|
||||
!loweredTranscript.Contains("loudness", StringComparison.Ordinal))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (MatchesAny(loweredTranscript, "max volume", "maximum volume", "volume max", "volume maximum"))
|
||||
{
|
||||
return "10";
|
||||
}
|
||||
|
||||
if (MatchesAny(loweredTranscript, "min volume", "minimum volume", "volume min", "volume minimum"))
|
||||
{
|
||||
return "1";
|
||||
}
|
||||
|
||||
var match = VolumeLevelPattern.Match(loweredTranscript);
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return TryNormalizeVolumeLevel(match.Groups["value"].Value);
|
||||
}
|
||||
|
||||
private static string? TryNormalizeVolumeLevel(string token)
|
||||
{
|
||||
if (string.Equals(token, "null", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
|
||||
var parsed = ParseNumberToken(token);
|
||||
return parsed is >= 1 and <= 10
|
||||
? parsed.Value.ToString()
|
||||
: null;
|
||||
}
|
||||
|
||||
private static bool IsGlobalCommandsDomain(IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
return clientEntities.TryGetValue("domain", out var domain) &&
|
||||
string.Equals(domain, "global_commands", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsClockTimerValueTurn(
|
||||
IReadOnlyList<string> clientRules,
|
||||
IReadOnlyList<string> listenRules)
|
||||
@@ -1235,6 +1445,10 @@ public sealed class JiboInteractionService(
|
||||
@"\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 (string Phrase, string Station)[] RadioGenreAliases =
|
||||
[
|
||||
("country music", "Country"),
|
||||
|
||||
@@ -25,6 +25,12 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
var isWordOfDayGuess = string.Equals(plan.IntentName, "word_of_the_day_guess", StringComparison.OrdinalIgnoreCase);
|
||||
var isRadioLaunch = string.Equals(plan.IntentName, "radio", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(plan.IntentName, "radio_genre", StringComparison.OrdinalIgnoreCase);
|
||||
var isStopCommand = string.Equals(plan.IntentName, "stop", StringComparison.OrdinalIgnoreCase);
|
||||
var isVolumeControl = string.Equals(plan.IntentName, "volume_up", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(plan.IntentName, "volume_down", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(plan.IntentName, "volume_to_value", StringComparison.OrdinalIgnoreCase);
|
||||
var isSettingsLaunch = string.Equals(skill?.SkillName, "@be/settings", StringComparison.OrdinalIgnoreCase);
|
||||
var isGlobalCommand = isStopCommand || isVolumeControl;
|
||||
var isPhotoGalleryLaunch = string.Equals(plan.IntentName, "photo_gallery", StringComparison.OrdinalIgnoreCase);
|
||||
var isPhotoCreateLaunch = string.Equals(plan.IntentName, "snapshot", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(plan.IntentName, "photobooth", StringComparison.OrdinalIgnoreCase);
|
||||
@@ -39,12 +45,19 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
var alarmAmPm = ReadSkillPayloadString(skill, "ampm");
|
||||
var radioStation = ReadSkillPayloadString(skill, "station");
|
||||
var cloudSkill = ReadSkillPayloadString(skill, "cloudSkill");
|
||||
var globalIntent = ReadSkillPayloadString(skill, "globalIntent");
|
||||
var nluDomain = ReadSkillPayloadString(skill, "nluDomain");
|
||||
var volumeLevel = ReadSkillPayloadString(skill, "volumeLevel");
|
||||
var nluGuess = ReadClientEntity(turn, "guess");
|
||||
var wordOfDayGuess = ResolveWordOfDayGuess(turn, transcript, nluGuess);
|
||||
var outboundIntent = isWordOfDayLaunch
|
||||
var outboundIntent = isGlobalCommand && !string.IsNullOrWhiteSpace(globalIntent)
|
||||
? globalIntent
|
||||
: isWordOfDayLaunch
|
||||
? "menu"
|
||||
: isRadioLaunch
|
||||
? "menu"
|
||||
: isSettingsLaunch && !string.IsNullOrWhiteSpace(localIntent)
|
||||
? localIntent
|
||||
: (isPhotoGalleryLaunch || isPhotoCreateLaunch) && !string.IsNullOrWhiteSpace(localIntent)
|
||||
? localIntent
|
||||
: isClockSkillLaunch && !string.IsNullOrWhiteSpace(clockIntent)
|
||||
@@ -58,8 +71,12 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
? wordOfDayGuess
|
||||
: isWordOfDayLaunch
|
||||
? string.Empty
|
||||
: isGlobalCommand
|
||||
? transcript
|
||||
: isRadioLaunch
|
||||
? transcript
|
||||
: isSettingsLaunch
|
||||
? transcript
|
||||
: isPhotoGalleryLaunch || isPhotoCreateLaunch
|
||||
? transcript
|
||||
: isClockSkillLaunch
|
||||
@@ -73,8 +90,12 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
: transcript;
|
||||
var outboundRules = isWordOfDayLaunch
|
||||
? ["word-of-the-day/menu"]
|
||||
: isGlobalCommand
|
||||
? BuildGlobalCommandRules(rules)
|
||||
: isRadioLaunch
|
||||
? Array.Empty<string>()
|
||||
: isSettingsLaunch
|
||||
? string.Equals(messageType, "CLIENT_NLU", StringComparison.OrdinalIgnoreCase) ? rules : Array.Empty<string>()
|
||||
: isPhotoGalleryLaunch || isPhotoCreateLaunch
|
||||
? string.Equals(messageType, "CLIENT_NLU", StringComparison.OrdinalIgnoreCase) ? rules : Array.Empty<string>()
|
||||
: isClockSkillLaunch
|
||||
@@ -88,6 +109,8 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
isYesNoTurn && isYesNoIntent,
|
||||
ShouldIncludeCreateDomain(yesNoRule),
|
||||
isWordOfDayLaunch,
|
||||
isGlobalCommand,
|
||||
volumeLevel,
|
||||
isRadioLaunch,
|
||||
isWordOfDayGuess,
|
||||
wordOfDayGuess,
|
||||
@@ -118,10 +141,12 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
entities,
|
||||
isWordOfDayLaunch ? "@be/word-of-the-day" :
|
||||
isRadioLaunch ? "@be/radio" :
|
||||
isSettingsLaunch ? "@be/settings" :
|
||||
isPhotoGalleryLaunch ? "@be/gallery" :
|
||||
isPhotoCreateLaunch ? "@be/create" :
|
||||
isClockSkillLaunch ? "@be/clock" :
|
||||
null),
|
||||
null,
|
||||
isGlobalCommand ? nluDomain ?? "global_commands" : null),
|
||||
match = new
|
||||
{
|
||||
intent = outboundIntent,
|
||||
@@ -177,6 +202,39 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
DelayMs: 125));
|
||||
}
|
||||
|
||||
if (isStopCommand)
|
||||
{
|
||||
messages.Add(new SocketReplyPlan(
|
||||
JsonSerializer.Serialize(BuildSkillRedirectPayload(
|
||||
transId,
|
||||
"@be/idle",
|
||||
outboundIntent,
|
||||
outboundAsrText,
|
||||
outboundRules,
|
||||
entities)),
|
||||
DelayMs: 75));
|
||||
messages.Add(new SocketReplyPlan(
|
||||
JsonSerializer.Serialize(BuildCompletionOnlySkillPayload(transId, "@be/idle")),
|
||||
DelayMs: 125));
|
||||
}
|
||||
|
||||
if (isSettingsLaunch &&
|
||||
!string.Equals(messageType, "CLIENT_NLU", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
messages.Add(new SocketReplyPlan(
|
||||
JsonSerializer.Serialize(BuildSkillRedirectPayload(
|
||||
transId,
|
||||
"@be/settings",
|
||||
outboundIntent,
|
||||
outboundAsrText,
|
||||
outboundRules,
|
||||
entities)),
|
||||
DelayMs: 75));
|
||||
messages.Add(new SocketReplyPlan(
|
||||
JsonSerializer.Serialize(BuildCompletionOnlySkillPayload(transId, "@be/settings")),
|
||||
DelayMs: 125));
|
||||
}
|
||||
|
||||
if (isClockSkillLaunch &&
|
||||
!string.Equals(messageType, "CLIENT_NLU", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -355,6 +413,8 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
bool yesNoTurn,
|
||||
bool includeCreateDomain,
|
||||
bool wordOfDayLaunch,
|
||||
bool globalCommand,
|
||||
string? volumeLevel,
|
||||
bool radioLaunch,
|
||||
bool wordOfDayGuess,
|
||||
string? guess,
|
||||
@@ -389,6 +449,17 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
};
|
||||
}
|
||||
|
||||
if (globalCommand)
|
||||
{
|
||||
var entities = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
||||
if (!string.IsNullOrWhiteSpace(volumeLevel))
|
||||
{
|
||||
entities["volumeLevel"] = volumeLevel;
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
if (radioLaunch)
|
||||
{
|
||||
var entities = new Dictionary<string, object?>();
|
||||
@@ -702,7 +773,8 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
string outboundIntent,
|
||||
IReadOnlyList<string> outboundRules,
|
||||
object entities,
|
||||
string? skillId)
|
||||
string? skillId,
|
||||
string? domain = null)
|
||||
{
|
||||
var payload = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
@@ -717,9 +789,21 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
||||
payload["skill"] = skillId;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(domain))
|
||||
{
|
||||
payload["domain"] = domain;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> BuildGlobalCommandRules(IReadOnlyList<string> rules)
|
||||
{
|
||||
return rules.Any(static rule => string.Equals(rule, "globals/global_commands_launch", StringComparison.OrdinalIgnoreCase))
|
||||
? ["globals/global_commands_launch"]
|
||||
: Array.Empty<string>();
|
||||
}
|
||||
|
||||
private static object BuildGenericFallbackSkillPayload(string transId)
|
||||
{
|
||||
return new
|
||||
|
||||
@@ -504,6 +504,11 @@ public sealed class WebSocketTurnFinalizationService(
|
||||
var emitSkillActions = !string.Equals(plan.IntentName, "word_of_the_day", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "radio", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "radio_genre", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "stop", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "volume_up", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "volume_down", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "volume_to_value", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "volume_query", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "time", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "date", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(plan.IntentName, "day", StringComparison.OrdinalIgnoreCase) &&
|
||||
|
||||
Reference in New Issue
Block a user