Add person-aware state and sync roadmap
This commit is contained in:
@@ -13,6 +13,7 @@ public interface ICloudStateStore
|
||||
CloudSession OpenSession(string kind, string? deviceId, string? token, string? hostName, string? path);
|
||||
CloudSession? FindSessionByToken(string token);
|
||||
IReadOnlyList<LoopRecord> GetLoops();
|
||||
IReadOnlyList<PersonRecord> GetPeople();
|
||||
IReadOnlyList<UpdateManifest> ListUpdates(string? subsystem = null, string? filter = null);
|
||||
UpdateManifest? GetUpdateFrom(string? subsystem, string? fromVersion, string? filter);
|
||||
UpdateManifest CreateUpdate(string? fromVersion, string? toVersion, string? changes, string? shaHash, long? length, string? subsystem, string? filter, IDictionary<string, object?>? dependencies);
|
||||
|
||||
@@ -18,7 +18,7 @@ public interface IPersonalMemoryStore
|
||||
void ClearListItems(PersonalMemoryTenantScope tenantScope, string listName);
|
||||
}
|
||||
|
||||
public sealed record PersonalMemoryTenantScope(string AccountId, string LoopId, string DeviceId);
|
||||
public sealed record PersonalMemoryTenantScope(string AccountId, string LoopId, string DeviceId, string? PersonId = null);
|
||||
|
||||
public enum PersonalAffinity
|
||||
{
|
||||
|
||||
@@ -78,7 +78,7 @@ public sealed class JiboInteractionService(
|
||||
randomizer,
|
||||
personalMemoryStore,
|
||||
BuildWeatherReportDecisionAsync,
|
||||
ResolveTenantScope,
|
||||
turnContext => ResolveTenantScope(turnContext),
|
||||
cancellationToken);
|
||||
if (personalReportDecision is not null)
|
||||
{
|
||||
@@ -92,7 +92,7 @@ public sealed class JiboInteractionService(
|
||||
lowered,
|
||||
randomizer,
|
||||
personalMemoryStore,
|
||||
ResolveTenantScope);
|
||||
turnContext => ResolveTenantScope(turnContext));
|
||||
if (householdListDecision is not null)
|
||||
{
|
||||
return householdListDecision;
|
||||
@@ -151,7 +151,7 @@ public sealed class JiboInteractionService(
|
||||
"good_night" => BuildReactiveGreetingDecision(turn, "good_night", referenceLocalTime),
|
||||
"welcome_back" => BuildReactiveGreetingDecision(turn, "welcome_back", referenceLocalTime),
|
||||
"memory_set_name" => BuildRememberNameDecision(turn, transcript),
|
||||
"memory_get_name" => BuildRecallNameDecision(turn),
|
||||
"memory_get_name" => BuildRecallNameDecision(turn, greetingPresence),
|
||||
"memory_set_birthday" => BuildRememberBirthdayDecision(turn, transcript),
|
||||
"memory_get_birthday" => BuildRecallBirthdayDecision(turn),
|
||||
"memory_set_important_date" => BuildRememberImportantDateDecision(turn, transcript),
|
||||
@@ -270,12 +270,18 @@ public sealed class JiboInteractionService(
|
||||
|
||||
private string? ResolvePreferredGreetingName(TurnContext turn, GreetingPresenceProfile presence)
|
||||
{
|
||||
var rememberedName = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
var rememberedName = personalMemoryStore.GetName(ResolveTenantScope(turn, presence.PrimaryPersonId));
|
||||
if (!string.IsNullOrWhiteSpace(rememberedName))
|
||||
{
|
||||
return ToDisplayName(rememberedName);
|
||||
}
|
||||
|
||||
var tenantRememberedName = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
if (!string.IsNullOrWhiteSpace(tenantRememberedName))
|
||||
{
|
||||
return ToDisplayName(tenantRememberedName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(presence.PrimaryPersonId) &&
|
||||
presence.LoopUserFirstNames.TryGetValue(presence.PrimaryPersonId, out var firstName) &&
|
||||
!string.IsNullOrWhiteSpace(firstName))
|
||||
@@ -372,16 +378,35 @@ public sealed class JiboInteractionService(
|
||||
$"Nice to meet you, {name}. I will remember your name.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallNameDecision(TurnContext turn)
|
||||
private JiboInteractionDecision BuildRecallNameDecision(TurnContext turn, GreetingPresenceProfile? presence = null)
|
||||
{
|
||||
var name = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
var personScope = ResolveTenantScope(turn, presence?.PrimaryPersonId);
|
||||
var name = personalMemoryStore.GetName(personScope);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name) &&
|
||||
presence is not null &&
|
||||
!string.IsNullOrWhiteSpace(presence.PrimaryPersonId) &&
|
||||
presence.LoopUserFirstNames.TryGetValue(presence.PrimaryPersonId, out var firstName) &&
|
||||
!string.IsNullOrWhiteSpace(firstName))
|
||||
{
|
||||
name = ToDisplayName(firstName);
|
||||
}
|
||||
|
||||
name = ToDisplayName(name ?? string.Empty);
|
||||
|
||||
return string.IsNullOrWhiteSpace(name)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_name",
|
||||
"I do not know your name yet. You can say, my name is Alex.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_name",
|
||||
$"You told me your name is {name}.");
|
||||
presence is not null && !string.IsNullOrWhiteSpace(presence.PrimaryPersonId)
|
||||
? $"I think you are {name}."
|
||||
: $"You told me your name is {name}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberBirthdayDecision(TurnContext turn, string transcript)
|
||||
@@ -4137,12 +4162,15 @@ public sealed class JiboInteractionService(
|
||||
};
|
||||
}
|
||||
|
||||
private static PersonalMemoryTenantScope ResolveTenantScope(TurnContext turn)
|
||||
private static PersonalMemoryTenantScope ResolveTenantScope(TurnContext turn, string? personId = null)
|
||||
{
|
||||
var accountId = ReadTenantAttribute(turn, "accountId") ?? "usr_openjibo_owner";
|
||||
var loopId = ReadTenantAttribute(turn, "loopId") ?? "openjibo-default-loop";
|
||||
var deviceId = turn.DeviceId ?? ReadTenantAttribute(turn, "deviceId") ?? "unknown-device";
|
||||
return new PersonalMemoryTenantScope(accountId, loopId, deviceId);
|
||||
var resolvedPersonId = !string.IsNullOrWhiteSpace(personId)
|
||||
? personId
|
||||
: ReadTenantAttribute(turn, "personId") ?? ReadTenantAttribute(turn, "speakerId");
|
||||
return new PersonalMemoryTenantScope(accountId, loopId, deviceId, resolvedPersonId);
|
||||
}
|
||||
|
||||
private static string? ReadTenantAttribute(TurnContext turn, string key)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Jibo.Cloud.Domain.Models;
|
||||
|
||||
public sealed class PersonRecord
|
||||
{
|
||||
public string PersonId { get; init; } = "person-openjibo-owner";
|
||||
public string AccountId { get; init; } = "usr_openjibo_owner";
|
||||
public string LoopId { get; init; } = "openjibo-default-loop";
|
||||
public string RobotId { get; init; } = "my-robot-name";
|
||||
public string DisplayName { get; init; } = "Jibo Owner";
|
||||
public string? Alias { get; init; }
|
||||
public bool IsPrimary { get; init; } = true;
|
||||
public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset UpdatedUtc { get; init; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
||||
private readonly List<MediaRecord> _media = [];
|
||||
private readonly List<BackupRecord> _backups = [];
|
||||
private readonly List<LoopRecord> _loops;
|
||||
private readonly List<PersonRecord> _people;
|
||||
private DeviceRegistration _robot;
|
||||
private RobotProfile _robotProfile;
|
||||
|
||||
@@ -60,6 +61,29 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
||||
RobotFriendlyId = _robot.DeviceId
|
||||
}
|
||||
];
|
||||
_people =
|
||||
[
|
||||
new PersonRecord
|
||||
{
|
||||
PersonId = "person-openjibo-owner",
|
||||
AccountId = _account.AccountId,
|
||||
LoopId = _loops[0].LoopId,
|
||||
RobotId = _robot.RobotId,
|
||||
DisplayName = $"{_account.FirstName} {_account.LastName}",
|
||||
Alias = _account.FirstName,
|
||||
IsPrimary = true
|
||||
},
|
||||
new PersonRecord
|
||||
{
|
||||
PersonId = "person-openjibo-household-member",
|
||||
AccountId = _account.AccountId,
|
||||
LoopId = _loops[0].LoopId,
|
||||
RobotId = _robot.RobotId,
|
||||
DisplayName = "OpenJibo Household Member",
|
||||
Alias = "Household Member",
|
||||
IsPrimary = false
|
||||
}
|
||||
];
|
||||
|
||||
_updates = [];
|
||||
LoadPersistentState();
|
||||
@@ -154,6 +178,8 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
||||
|
||||
public IReadOnlyList<LoopRecord> GetLoops() => _loops.ToArray();
|
||||
|
||||
public IReadOnlyList<PersonRecord> GetPeople() => _people.ToArray();
|
||||
|
||||
public IReadOnlyList<UpdateManifest> ListUpdates(string? subsystem = null, string? filter = null)
|
||||
{
|
||||
return _updates
|
||||
|
||||
@@ -148,7 +148,9 @@ public sealed class InMemoryPersonalMemoryStore : IPersonalMemoryStore
|
||||
|
||||
private static string BuildTenantKey(PersonalMemoryTenantScope tenantScope)
|
||||
{
|
||||
return $"{tenantScope.AccountId}|{tenantScope.LoopId}|{tenantScope.DeviceId}";
|
||||
return string.IsNullOrWhiteSpace(tenantScope.PersonId)
|
||||
? $"{tenantScope.AccountId}|{tenantScope.LoopId}|{tenantScope.DeviceId}"
|
||||
: $"{tenantScope.AccountId}|{tenantScope.LoopId}|{tenantScope.DeviceId}|{tenantScope.PersonId}";
|
||||
}
|
||||
|
||||
private static string NormalizeCategory(string category)
|
||||
|
||||
Reference in New Issue
Block a user