update version
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
This is the production-oriented path for restoring device connectivity and creating a foundation for future runtime, AI, and OTA work.
|
||||
|
||||
Current spoken cloud version: `Open Jibo Cloud version 1.0.16.`
|
||||
Current spoken cloud version: `Open Jibo Cloud version 1.0.17.`
|
||||
|
||||
Release hygiene reminder:
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ public sealed class JiboInteractionService(
|
||||
var listenRules = ReadRules(turn, "listenRules").ToArray();
|
||||
var listenAsrHints = ReadRules(turn, "listenAsrHints").ToArray();
|
||||
var clientEntities = ReadEntities(turn);
|
||||
var lastClockDomain = turn.Attributes.TryGetValue("lastClockDomain", out var rawLastClockDomain)
|
||||
? rawLastClockDomain?.ToString()
|
||||
: null;
|
||||
var isYesNoTurn = IsYesNoTurn(turn);
|
||||
|
||||
var isTimerValueTurn = IsClockTimerValueTurn(clientRules, listenRules);
|
||||
@@ -33,6 +36,7 @@ public sealed class JiboInteractionService(
|
||||
clientRules,
|
||||
listenRules,
|
||||
clientEntities,
|
||||
lastClockDomain,
|
||||
isYesNoTurn,
|
||||
isTimerValueTurn,
|
||||
isAlarmValueTurn);
|
||||
@@ -53,8 +57,8 @@ public sealed class JiboInteractionService(
|
||||
"alarm_menu" => BuildClockLaunchDecision("alarm", "Opening the alarm."),
|
||||
"timer_delete" => BuildClockLaunchDecision("timer_delete", "timer", "delete", "Canceling the timer."),
|
||||
"alarm_delete" => BuildClockLaunchDecision("alarm_delete", "alarm", "delete", "Canceling the alarm."),
|
||||
"timer_value" => BuildTimerValueDecision(lowered, isTimerValueTurn),
|
||||
"alarm_value" => BuildAlarmValueDecision(lowered, isAlarmValueTurn, referenceLocalTime),
|
||||
"timer_value" => BuildTimerValueDecision(lowered, isTimerValueTurn, clientEntities),
|
||||
"alarm_value" => BuildAlarmValueDecision(lowered, isAlarmValueTurn, referenceLocalTime, clientEntities),
|
||||
"timer_clarify" => new JiboInteractionDecision("timer_clarify", "How long should I set the timer for?"),
|
||||
"alarm_clarify" => new JiboInteractionDecision("alarm_clarify", "What time should I set the alarm for?"),
|
||||
"photo_gallery" => BuildPhotoGalleryLaunchDecision(),
|
||||
@@ -156,6 +160,7 @@ public sealed class JiboInteractionService(
|
||||
IReadOnlyList<string> clientRules,
|
||||
IReadOnlyList<string> listenRules,
|
||||
IReadOnlyDictionary<string, string> clientEntities,
|
||||
string? lastClockDomain,
|
||||
bool isYesNoTurn,
|
||||
bool isTimerValueTurn,
|
||||
bool isAlarmValueTurn)
|
||||
@@ -219,12 +224,26 @@ public sealed class JiboInteractionService(
|
||||
{
|
||||
return startDomain.ToLowerInvariant() switch
|
||||
{
|
||||
"timer" => "timer_value",
|
||||
"alarm" => "alarm_value",
|
||||
"timer" => HasStructuredTimerValue(clientEntities) || TryParseTimerValue(loweredTranscript, isTimerValueTurn) is not null
|
||||
? "timer_value"
|
||||
: "timer_clarify",
|
||||
"alarm" => HasStructuredAlarmValue(clientEntities) || TryParseAlarmValue(loweredTranscript, isAlarmValueTurn, referenceLocalTime) is not null
|
||||
? "alarm_value"
|
||||
: "alarm_clarify",
|
||||
_ => "chat"
|
||||
};
|
||||
}
|
||||
|
||||
if ((string.Equals(clientIntent, "cancel", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(clientIntent, "delete", StringComparison.OrdinalIgnoreCase)) &&
|
||||
clientRules.Concat(listenRules).Any(rule => string.Equals(rule, "clock/alarm_timer_query_menu", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var cancelDomain = ResolveClockDomain(clientEntities, clientRules, listenRules, lastClockDomain);
|
||||
return string.Equals(cancelDomain, "timer", StringComparison.OrdinalIgnoreCase)
|
||||
? "timer_delete"
|
||||
: "alarm_delete";
|
||||
}
|
||||
|
||||
if (string.Equals(clientIntent, "menu", StringComparison.OrdinalIgnoreCase) &&
|
||||
clientEntities.TryGetValue("domain", out var clockDomain))
|
||||
{
|
||||
@@ -520,9 +539,14 @@ public sealed class JiboInteractionService(
|
||||
return BuildClockLaunchDecision($"{domain}_menu", domain, "menu", replyText);
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildTimerValueDecision(string loweredTranscript, bool allowImplicit)
|
||||
private static JiboInteractionDecision BuildTimerValueDecision(
|
||||
string loweredTranscript,
|
||||
bool allowImplicit,
|
||||
IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
var timer = TryParseTimerValue(loweredTranscript, allowImplicit) ?? new ClockTimerValue("0", "1", "null");
|
||||
var timer = TryReadStructuredTimerValue(clientEntities) ??
|
||||
TryParseTimerValue(loweredTranscript, allowImplicit) ??
|
||||
new ClockTimerValue("0", "1", "null");
|
||||
|
||||
return new JiboInteractionDecision(
|
||||
"timer_value",
|
||||
@@ -542,9 +566,12 @@ public sealed class JiboInteractionService(
|
||||
private static JiboInteractionDecision BuildAlarmValueDecision(
|
||||
string loweredTranscript,
|
||||
bool allowImplicit,
|
||||
DateTimeOffset? referenceLocalTime)
|
||||
DateTimeOffset? referenceLocalTime,
|
||||
IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
var alarm = TryParseAlarmValue(loweredTranscript, allowImplicit, referenceLocalTime) ?? new ClockAlarmValue("7:00", "am");
|
||||
var alarm = TryReadStructuredAlarmValue(clientEntities) ??
|
||||
TryParseAlarmValue(loweredTranscript, allowImplicit, referenceLocalTime) ??
|
||||
new ClockAlarmValue("7:00", "am");
|
||||
|
||||
return new JiboInteractionDecision(
|
||||
"alarm_value",
|
||||
@@ -959,6 +986,81 @@ public sealed class JiboInteractionService(
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private static bool HasStructuredTimerValue(IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
return clientEntities.ContainsKey("hours") ||
|
||||
clientEntities.ContainsKey("minutes") ||
|
||||
clientEntities.ContainsKey("seconds");
|
||||
}
|
||||
|
||||
private static bool HasStructuredAlarmValue(IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
return clientEntities.TryGetValue("time", out var time) &&
|
||||
!string.IsNullOrWhiteSpace(time);
|
||||
}
|
||||
|
||||
private static ClockTimerValue? TryReadStructuredTimerValue(IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
if (!HasStructuredTimerValue(clientEntities))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
clientEntities.TryGetValue("hours", out var hours);
|
||||
clientEntities.TryGetValue("minutes", out var minutes);
|
||||
clientEntities.TryGetValue("seconds", out var seconds);
|
||||
return new ClockTimerValue(
|
||||
string.IsNullOrWhiteSpace(hours) ? "0" : hours,
|
||||
string.IsNullOrWhiteSpace(minutes) ? "0" : minutes,
|
||||
string.IsNullOrWhiteSpace(seconds) ? "null" : seconds);
|
||||
}
|
||||
|
||||
private static ClockAlarmValue? TryReadStructuredAlarmValue(IReadOnlyDictionary<string, string> clientEntities)
|
||||
{
|
||||
if (!clientEntities.TryGetValue("time", out var time) || string.IsNullOrWhiteSpace(time))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
clientEntities.TryGetValue("ampm", out var ampm);
|
||||
return new ClockAlarmValue(time, string.IsNullOrWhiteSpace(ampm) ? "am" : ampm.ToLowerInvariant());
|
||||
}
|
||||
|
||||
private static string? ResolveClockDomain(
|
||||
IReadOnlyDictionary<string, string> clientEntities,
|
||||
IReadOnlyList<string> clientRules,
|
||||
IReadOnlyList<string> listenRules,
|
||||
string? lastClockDomain)
|
||||
{
|
||||
if (clientEntities.TryGetValue("domain", out var clientDomain) &&
|
||||
!string.IsNullOrWhiteSpace(clientDomain))
|
||||
{
|
||||
return clientDomain;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(lastClockDomain))
|
||||
{
|
||||
return lastClockDomain;
|
||||
}
|
||||
|
||||
var combinedRules = clientRules.Concat(listenRules);
|
||||
if (combinedRules.Any(rule =>
|
||||
rule.Contains("timer", StringComparison.OrdinalIgnoreCase) &&
|
||||
!rule.Contains("alarm_timer_query_menu", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return "timer";
|
||||
}
|
||||
|
||||
if (combinedRules.Any(rule =>
|
||||
rule.Contains("alarm", StringComparison.OrdinalIgnoreCase) &&
|
||||
!rule.Contains("alarm_timer_query_menu", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return "alarm";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsTimerRequest(string loweredTranscript)
|
||||
{
|
||||
return MatchesAny(
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Jibo.Cloud.Application.Services;
|
||||
|
||||
public static class OpenJiboCloudBuildInfo
|
||||
{
|
||||
public const string Version = "1.0.16";
|
||||
public const string Version = "1.0.17";
|
||||
|
||||
public static string VersionWords => Version.Replace(".", " dot ");
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@ public sealed class ProtocolToTurnContextMapper
|
||||
attributes["context"] = turnState.ContextPayload;
|
||||
}
|
||||
|
||||
if (session.Metadata.TryGetValue("lastClockDomain", out var lastClockDomain) &&
|
||||
lastClockDomain is string lastClockDomainText &&
|
||||
!string.IsNullOrWhiteSpace(lastClockDomainText))
|
||||
{
|
||||
attributes["lastClockDomain"] = lastClockDomainText;
|
||||
}
|
||||
|
||||
attributes["listenHotphrase"] = turnState.ListenHotphrase;
|
||||
|
||||
if (turnState.ListenRules.Count > 0)
|
||||
|
||||
@@ -95,21 +95,7 @@ public sealed class WebSocketTurnFinalizationService(
|
||||
turnState.IgnoreAdditionalAudioUntilUtc = DateTimeOffset.UtcNow.Add(WebSocketTurnState.DefaultLateAudioIgnoreWindow);
|
||||
ResetBufferedAudio(session);
|
||||
turnState.SawContext = false;
|
||||
return
|
||||
[
|
||||
new WebSocketReply
|
||||
{
|
||||
Text = JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "OPENJIBO_CONTEXT_ACK",
|
||||
data = new
|
||||
{
|
||||
sessionId = session.SessionId,
|
||||
transID = session.LastTransId
|
||||
}
|
||||
})
|
||||
}
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
if (ShouldAutoFinalize(session))
|
||||
@@ -117,21 +103,7 @@ public sealed class WebSocketTurnFinalizationService(
|
||||
return await FinalizeTurnAsync(session, envelope, "AUTO_FINALIZE", allowFallbackOnMissingTranscript: true, cancellationToken);
|
||||
}
|
||||
|
||||
return
|
||||
[
|
||||
new WebSocketReply
|
||||
{
|
||||
Text = JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "OPENJIBO_CONTEXT_ACK",
|
||||
data = new
|
||||
{
|
||||
sessionId = session.SessionId,
|
||||
transID = session.LastTransId
|
||||
}
|
||||
})
|
||||
}
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<WebSocketReply>> HandleTurnAsync(
|
||||
@@ -170,26 +142,7 @@ public sealed class WebSocketTurnFinalizationService(
|
||||
}
|
||||
|
||||
session.TurnState.AwaitingTurnCompletion = true;
|
||||
return
|
||||
[
|
||||
new WebSocketReply
|
||||
{
|
||||
Text = JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "OPENJIBO_TURN_PENDING",
|
||||
data = new
|
||||
{
|
||||
sessionId = session.SessionId,
|
||||
transID = session.LastTransId,
|
||||
bufferedAudioBytes = session.TurnState.BufferedAudioBytes,
|
||||
bufferedAudioChunks = session.TurnState.BufferedAudioChunkCount,
|
||||
awaitingAudio = session.TurnState.BufferedAudioBytes == 0,
|
||||
awaitingTranscriptHint = session.TurnState.BufferedAudioBytes > 0 && string.IsNullOrWhiteSpace(session.TurnState.AudioTranscriptHint),
|
||||
finalizeAttempts = session.TurnState.FinalizeAttemptCount
|
||||
}
|
||||
})
|
||||
}
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
private async Task<TurnContext> ResolveTranscriptAsync(TurnContext turn, CloudSession session, CancellationToken cancellationToken)
|
||||
@@ -475,26 +428,7 @@ public sealed class WebSocketTurnFinalizationService(
|
||||
{
|
||||
turnState.HotphraseEmptyTurnCount += 1;
|
||||
turnState.AwaitingTurnCompletion = true;
|
||||
return
|
||||
[
|
||||
new WebSocketReply
|
||||
{
|
||||
Text = JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "OPENJIBO_TURN_PENDING",
|
||||
data = new
|
||||
{
|
||||
sessionId = session.SessionId,
|
||||
transID = session.LastTransId,
|
||||
bufferedAudioBytes = turnState.BufferedAudioBytes,
|
||||
bufferedAudioChunks = turnState.BufferedAudioChunkCount,
|
||||
awaitingAudio = turnState.BufferedAudioBytes == 0,
|
||||
awaitingTranscriptHint = turnState.BufferedAudioBytes > 0 && string.IsNullOrWhiteSpace(turnState.AudioTranscriptHint),
|
||||
finalizeAttempts = turnState.FinalizeAttemptCount
|
||||
}
|
||||
})
|
||||
}
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
if (ShouldTreatEmptyHotphraseTurnAsGreeting(finalizedTurn))
|
||||
@@ -551,26 +485,7 @@ public sealed class WebSocketTurnFinalizationService(
|
||||
return fallbackReplies;
|
||||
}
|
||||
|
||||
return
|
||||
[
|
||||
new WebSocketReply
|
||||
{
|
||||
Text = JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "OPENJIBO_TURN_PENDING",
|
||||
data = new
|
||||
{
|
||||
sessionId = session.SessionId,
|
||||
transID = session.LastTransId,
|
||||
bufferedAudioBytes = turnState.BufferedAudioBytes,
|
||||
bufferedAudioChunks = turnState.BufferedAudioChunkCount,
|
||||
awaitingAudio = turnState.BufferedAudioBytes == 0,
|
||||
awaitingTranscriptHint = turnState.BufferedAudioBytes > 0 && string.IsNullOrWhiteSpace(turnState.AudioTranscriptHint),
|
||||
finalizeAttempts = turnState.FinalizeAttemptCount
|
||||
}
|
||||
})
|
||||
}
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
var plan = await conversationBroker.HandleTurnAsync(finalizedTurn, cancellationToken);
|
||||
@@ -578,6 +493,12 @@ public sealed class WebSocketTurnFinalizationService(
|
||||
session.LastTranscript = finalizedTurn.NormalizedTranscript ?? finalizedTurn.RawTranscript;
|
||||
session.LastIntent = plan.IntentName;
|
||||
session.LastListenType = listenAction?.Mode;
|
||||
if (plan.Actions.OfType<InvokeNativeSkillAction>().FirstOrDefault() is { SkillName: "@be/clock", Payload: not null } clockAction &&
|
||||
clockAction.Payload.TryGetValue("domain", out var lastClockDomainValue) &&
|
||||
lastClockDomainValue is not null)
|
||||
{
|
||||
session.Metadata["lastClockDomain"] = lastClockDomainValue.ToString();
|
||||
}
|
||||
session.FollowUpExpiresUtc = plan.FollowUp.KeepMicOpen
|
||||
? DateTimeOffset.UtcNow.Add(plan.FollowUp.Timeout)
|
||||
: null;
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_TURN_PENDING"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
},
|
||||
{
|
||||
"binary": [1, 2, 3, 4],
|
||||
@@ -31,9 +29,7 @@
|
||||
"transID": "fixture-trans-pending",
|
||||
"data": { }
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_TURN_PENDING"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_TURN_PENDING"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
},
|
||||
{
|
||||
"text": {
|
||||
@@ -30,9 +28,7 @@
|
||||
"audioTranscriptHint": "tell me a joke"
|
||||
}
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_CONTEXT_ACK"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
},
|
||||
{
|
||||
"binary": [1, 2, 3, 4, 5, 6],
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
"mode": "CLIENT_NLU"
|
||||
}
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_TURN_PENDING"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
},
|
||||
{
|
||||
"text": {
|
||||
|
||||
@@ -33,9 +33,7 @@
|
||||
"screen": "home"
|
||||
}
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_CONTEXT_ACK"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
},
|
||||
{
|
||||
"text": {
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_TURN_PENDING"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
},
|
||||
{
|
||||
"text": {
|
||||
@@ -29,9 +27,7 @@
|
||||
"audioTranscriptHint": "hello from buffered audio"
|
||||
}
|
||||
},
|
||||
"expectedReplyTypes": [
|
||||
"OPENJIBO_CONTEXT_ACK"
|
||||
]
|
||||
"expectedReplyTypes": []
|
||||
},
|
||||
{
|
||||
"binary": [1, 2, 3],
|
||||
|
||||
Reference in New Issue
Block a user