This commit is contained in:
Jacob Dubin
2026-05-19 06:03:34 -05:00
parent 6bae858da9
commit 54b32bc9cf
41 changed files with 135 additions and 117 deletions

View File

@@ -44,4 +44,4 @@ public interface ICloudStateStore
IReadOnlyList<KeyRequestRecord> GetBinaryRequests();
IReadOnlyList<object> GetHolidays();
void UpdateRobot(DeviceRegistration registration);
}
}

View File

@@ -12,4 +12,4 @@ public sealed record CommuteReportSnapshot(
string Summary,
int DurationMinutes,
string? Mode = null,
bool EventIsEarly = false);
bool EventIsEarly = false);

View File

@@ -50,4 +50,4 @@ public sealed class JiboExperienceCatalog
public IReadOnlyList<string> GenericFallbackReplies { get; init; } = [];
public IReadOnlyList<string> DanceReplies { get; init; } = [];
public IReadOnlyList<string> DanceQuestionReplies { get; init; } = [];
}
}

View File

@@ -12,6 +12,7 @@ public sealed record MediaContentSnapshot
{
public string ContentType { get; init; } = "application/octet-stream";
public byte[] Content { get; init; } = [];
public IReadOnlyDictionary<string, object?> Meta { get; init; } =
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
}
}

View File

@@ -620,4 +620,4 @@ internal static class ChitchatStateMachine
mappings.Sort(static (left, right) => right.Phrase.Length.CompareTo(left.Phrase.Length));
return [.. mappings];
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Text;
using System.Text.Json;
using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Domain.Models;
@@ -6,7 +7,6 @@ namespace Jibo.Cloud.Application.Services;
public sealed class JiboCloudProtocolService(ICloudStateStore stateStore, IMediaContentStore? mediaContentStore = null)
{
private readonly IMediaContentStore _mediaContentStore = mediaContentStore ?? new NullMediaContentStore();
private static readonly string[] AcceptedHosts =
[
"api.jibo.com",
@@ -15,6 +15,8 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore, IMedia
"localhost"
];
private readonly IMediaContentStore _mediaContentStore = mediaContentStore ?? new NullMediaContentStore();
public Task<ProtocolDispatchResult> DispatchAsync(ProtocolEnvelope envelope,
CancellationToken cancellationToken = default)
{
@@ -344,7 +346,7 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore, IMedia
if (!string.IsNullOrWhiteSpace(envelope.BodyText)) meta["bodyText"] = envelope.BodyText;
_mediaContentStore.StoreAsync(path, contentType,
string.IsNullOrWhiteSpace(envelope.BodyText) ? [] : System.Text.Encoding.UTF8.GetBytes(envelope.BodyText),
string.IsNullOrWhiteSpace(envelope.BodyText) ? [] : Encoding.UTF8.GetBytes(envelope.BodyText),
meta as IReadOnlyDictionary<string, object?>, CancellationToken.None).GetAwaiter().GetResult();
return ProtocolDispatchResult.Ok(
@@ -367,7 +369,8 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore, IMedia
{
var body = envelope.TryParseBody();
var requestedName = ReadString(body, "name") ?? ReadString(body, "backupName");
return ProtocolDispatchResult.Ok(stateStore.CreateBackup(requestedName ?? $"backup-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}"));
return ProtocolDispatchResult.Ok(
stateStore.CreateBackup(requestedName ?? $"backup-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}"));
}
return ProtocolDispatchResult.Ok(Array.Empty<object>());
@@ -512,27 +515,13 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore, IMedia
var storedContent = _mediaContentStore.LoadAsync(media.Path, CancellationToken.None).GetAwaiter().GetResult();
var contentType = storedContent?.ContentType ?? TryReadMetaString(media.Meta, "contentType") ??
"application/octet-stream";
"application/octet-stream";
var bodyText = storedContent is not null
? System.Text.Encoding.UTF8.GetString(storedContent.Content)
? Encoding.UTF8.GetString(storedContent.Content)
: TryReadMetaString(media.Meta, "bodyText") ?? string.Empty;
return ProtocolDispatchResult.Raw(200, bodyText, contentType);
}
private sealed class NullMediaContentStore : IMediaContentStore
{
public Task StoreAsync(string path, string contentType, byte[] content,
IReadOnlyDictionary<string, object?>? meta, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public Task<MediaContentSnapshot?> LoadAsync(string path, CancellationToken cancellationToken = default)
{
return Task.FromResult<MediaContentSnapshot?>(null);
}
}
private ProtocolDispatchResult HandleGetUpdateFrom(string? subsystem, string? fromVersion, string? filter)
{
var update = stateStore.GetUpdateFrom(subsystem, fromVersion, filter);
@@ -662,4 +651,18 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore, IMedia
bool.TryParse(value, out var parsed) &&
parsed;
}
}
private sealed class NullMediaContentStore : IMediaContentStore
{
public Task StoreAsync(string path, string contentType, byte[] content,
IReadOnlyDictionary<string, object?>? meta, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public Task<MediaContentSnapshot?> LoadAsync(string path, CancellationToken cancellationToken = default)
{
return Task.FromResult<MediaContentSnapshot?>(null);
}
}
}

View File

@@ -1524,10 +1524,10 @@ public sealed class JiboInteractionService(
{
"walking" => lowered.Contains("walk", StringComparison.OrdinalIgnoreCase),
"transit" => lowered.Contains("public transportation", StringComparison.OrdinalIgnoreCase) ||
lowered.Contains("transit", StringComparison.OrdinalIgnoreCase) ||
lowered.Contains("transportation", StringComparison.OrdinalIgnoreCase),
lowered.Contains("transit", StringComparison.OrdinalIgnoreCase) ||
lowered.Contains("transportation", StringComparison.OrdinalIgnoreCase),
"bicycling" => lowered.Contains("bike", StringComparison.OrdinalIgnoreCase) ||
lowered.Contains("ride", StringComparison.OrdinalIgnoreCase),
lowered.Contains("ride", StringComparison.OrdinalIgnoreCase),
_ => lowered.Contains("drive", StringComparison.OrdinalIgnoreCase) ||
lowered.Contains("commute", StringComparison.OrdinalIgnoreCase)
};
@@ -4617,13 +4617,13 @@ public sealed class JiboInteractionService(
{
var normalized = NormalizeCommandPhrase(loweredTranscript);
return normalized.StartsWith("what is my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("what s my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("what's my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("what is my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("what s my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("what's my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("do you remember my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("do you remember my favourite", StringComparison.Ordinal);
normalized.StartsWith("what s my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("what's my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("what is my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("what s my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("what's my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("do you remember my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("do you remember my favourite", StringComparison.Ordinal);
}
private static string? TryExtractPreferenceLookupCategory(string transcript)
@@ -5410,4 +5410,4 @@ public sealed record JiboInteractionDecision(
string ReplyText,
string? SkillName = null,
IDictionary<string, object?>? SkillPayload = null,
IDictionary<string, object?>? ContextUpdates = null);
IDictionary<string, object?>? ContextUpdates = null);

View File

@@ -676,7 +676,8 @@ internal static class PersonalReportOrchestrator
var selected = ChooseShortestTemplate(briefings);
if (string.IsNullOrWhiteSpace(selected)) return string.Empty;
var firstSentence = selected.Split(['.', '!', '?'], 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
var firstSentence = selected.Split(['.', '!', '?'], 2,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.FirstOrDefault();
return string.IsNullOrWhiteSpace(firstSentence) ? selected : firstSentence;
}
@@ -705,4 +706,4 @@ internal static class PersonalReportOrchestrator
bool CalendarEnabled,
bool CommuteEnabled,
bool NewsEnabled);
}
}

View File

@@ -1459,4 +1459,4 @@ public sealed class ResponsePlanToSocketMessagesMapper
string? SpokenLine);
public sealed record SocketReplyPlan(string Text, int DelayMs = 0);
}
}

View File

@@ -1,3 +1,4 @@
using System.Text.RegularExpressions;
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services;
@@ -57,10 +58,10 @@ public sealed class SyntheticBufferedAudioSttStrategy : ISttStrategy
if (string.IsNullOrWhiteSpace(value)) return string.Empty;
var lowered = value.Trim().ToLowerInvariant();
lowered = System.Text.RegularExpressions.Regex.Replace(lowered, @"[^\p{L}\p{N}\s']+", " ",
System.Text.RegularExpressions.RegexOptions.CultureInvariant | System.Text.RegularExpressions.RegexOptions.Compiled);
lowered = System.Text.RegularExpressions.Regex.Replace(lowered, @"\s+"," ",
System.Text.RegularExpressions.RegexOptions.CultureInvariant | System.Text.RegularExpressions.RegexOptions.Compiled);
lowered = Regex.Replace(lowered, @"[^\p{L}\p{N}\s']+", " ",
RegexOptions.CultureInvariant | RegexOptions.Compiled);
lowered = Regex.Replace(lowered, @"\s+", " ",
RegexOptions.CultureInvariant | RegexOptions.Compiled);
return lowered.Trim();
}
}
}

View File

@@ -2,7 +2,7 @@ using System.Text.RegularExpressions;
namespace Jibo.Cloud.Application.Services;
internal static partial class TranscriptTextNormalizer
internal static class TranscriptTextNormalizer
{
private static readonly Regex PunctuationToSpaceRegex = new(
@"[^\p{L}\p{N}\s']+",
@@ -55,4 +55,4 @@ internal static partial class TranscriptTextNormalizer
trimmed = normalizedValue;
return false;
}
}
}

View File

@@ -1,12 +1,11 @@
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 WebSocketTurnFinalizationService(
public sealed class WebSocketTurnFinalizationService(
IConversationBroker conversationBroker,
ISttStrategySelector sttStrategySelector,
ITurnTelemetrySink sink
@@ -1943,4 +1942,4 @@ public sealed partial class WebSocketTurnFinalizationService(
Affirmative = 1,
Negative = 2
}
}
}

View File

@@ -21,4 +21,4 @@ internal static class AudioTranscriptNormalizer
" ")
.Trim();
}
}
}

View File

@@ -155,4 +155,4 @@ public sealed class LocalWhisperCppBufferedAudioSttStrategy(
return !checkFileExists || File.Exists(path);
}
}
}

View File

@@ -11,4 +11,4 @@ public sealed class UnavailableCommuteReportProvider : ICommuteReportProvider
{
return Task.FromResult<CommuteReportSnapshot?>(null);
}
}
}

View File

@@ -246,4 +246,4 @@ public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceCon
return candidates.Where(Directory.Exists).ToArray();
}
}
}

View File

@@ -371,18 +371,17 @@ public static class LegacyMimCatalogImporter
{
private readonly List<string> _calendarNothingReplies = [];
private readonly List<string> _calendarNothingTodayReplies = [];
private readonly List<string> _calendarServiceDownReplies = [];
private readonly List<string> _calendarOutroReplies = [];
private readonly List<string> _calendarServiceDownReplies = [];
private readonly List<string> _commuteNowReplies = [];
private readonly List<string> _commuteServiceDownReplies = [];
private readonly List<JiboConditionedReply> _emotionReplies = [];
private readonly List<string> _fallbacks = [];
private readonly List<string> _greetings = [];
private readonly List<string> _jokes = [];
private readonly List<string> _robotFacts = [];
private readonly List<string> _humanFacts = [];
private readonly List<string> _howAreYous = [];
private readonly List<string> _funFacts = [];
private readonly List<string> _greetings = [];
private readonly List<string> _howAreYous = [];
private readonly List<string> _humanFacts = [];
private readonly List<string> _jokes = [];
private readonly List<string> _newsCategoryIntroReplies = [];
private readonly List<string> _newsIntroReplies = [];
private readonly List<string> _newsOutroReplies = [];
@@ -390,6 +389,7 @@ public static class LegacyMimCatalogImporter
private readonly List<string> _personalReportKickOffReplies = [];
private readonly List<string> _personalReportOutroReplies = [];
private readonly List<string> _reportSkillTemplates = [];
private readonly List<string> _robotFacts = [];
private readonly List<string> _weatherIntroReplies = [];
private readonly List<string> _weatherServiceDownReplies = [];
private readonly List<string> _weatherTodayHighLowReplies = [];
@@ -564,7 +564,8 @@ public static class LegacyMimCatalogImporter
private LegacyMimBucket ResolveFunFactTarget(string prompt)
{
var lowered = NormalizePrompt(prompt, false).ToLowerInvariant();
if (ContainsAny(lowered, "robot", "humanoid", "machine", "about me", "my cameras", "turing", "deep blue", "rossum"))
if (ContainsAny(lowered, "robot", "humanoid", "machine", "about me", "my cameras", "turing", "deep blue",
"rossum"))
return LegacyMimBucket.RobotFacts;
if (ContainsAny(lowered, "human", "people", "grown ups", "human being", "humans"))
@@ -607,4 +608,4 @@ public static class LegacyMimCatalogImporter
[JsonPropertyName("weight")] public double? Weight { get; init; }
}
}
}

View File

@@ -67,7 +67,8 @@ public static class ServiceCollectionExtensions
configuration.GetSection("OpenJibo:Media").Bind(mediaOptions);
if (string.IsNullOrWhiteSpace(mediaOptions.ConnectionString))
mediaOptions.ConnectionString = Environment.GetEnvironmentVariable("OPENJIBO_MEDIA_STORAGE_CONNECTION_STRING");
mediaOptions.ConnectionString =
Environment.GetEnvironmentVariable("OPENJIBO_MEDIA_STORAGE_CONNECTION_STRING");
services.AddSingleton<IPersistenceSnapshotStoreFactory, PersistenceSnapshotStoreFactory>();
services.AddSingleton<IMediaContentStoreFactory, MediaContentStoreFactory>();
@@ -116,4 +117,4 @@ public static class ServiceCollectionExtensions
? backendKind
: PersistenceBackendKind.File;
}
}
}

View File

@@ -30,14 +30,14 @@ internal sealed class AzureBlobMediaContentStore : IMediaContentStore
var contentBlob = _containerClient.GetBlobClient($"{relative}.bin");
var metaBlob = _containerClient.GetBlobClient($"{relative}.json");
await _containerClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
await contentBlob.UploadAsync(new MemoryStream(content), overwrite: true, cancellationToken);
await contentBlob.UploadAsync(new MemoryStream(content), true, cancellationToken);
var payload = JsonSerializer.Serialize(new
{
path,
contentType,
meta
}, JsonOptions);
await metaBlob.UploadAsync(BinaryData.FromString(payload), overwrite: true, cancellationToken);
await metaBlob.UploadAsync(BinaryData.FromString(payload), true, cancellationToken);
}
public async Task<MediaContentSnapshot?> LoadAsync(string path, CancellationToken cancellationToken = default)
@@ -52,7 +52,6 @@ internal sealed class AzureBlobMediaContentStore : IMediaContentStore
var metaBlob = _containerClient.GetBlobClient($"{relative}.json");
if (await metaBlob.ExistsAsync(cancellationToken))
{
try
{
var json = (await metaBlob.DownloadContentAsync(cancellationToken)).Value.Content.ToString();
@@ -70,7 +69,6 @@ internal sealed class AzureBlobMediaContentStore : IMediaContentStore
{
// Keep the raw binary available even if metadata parsing fails.
}
}
return new MediaContentSnapshot
{
@@ -79,4 +77,4 @@ internal sealed class AzureBlobMediaContentStore : IMediaContentStore
Meta = meta as IReadOnlyDictionary<string, object?> ?? new Dictionary<string, object?>(meta)
};
}
}
}

View File

@@ -53,7 +53,6 @@ internal sealed class FileMediaContentStore : IMediaContentStore
IDictionary<string, object?> meta = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
if (File.Exists(metaPath))
{
try
{
using var document = JsonDocument.Parse(await File.ReadAllTextAsync(metaPath, cancellationToken));
@@ -72,7 +71,6 @@ internal sealed class FileMediaContentStore : IMediaContentStore
{
// Keep binary content available even if the manifest is malformed.
}
}
return new MediaContentSnapshot
{
@@ -81,4 +79,4 @@ internal sealed class FileMediaContentStore : IMediaContentStore
Meta = meta as IReadOnlyDictionary<string, object?> ?? new Dictionary<string, object?>(meta)
};
}
}
}

View File

@@ -6,4 +6,4 @@ internal interface IMediaContentStoreFactory
{
IMediaContentStore Create(string? directoryPath, MediaContentStoreKind backendKind, string containerName,
string? connectionString);
}
}

View File

@@ -13,4 +13,4 @@ internal sealed class MediaContentStoreFactory : IMediaContentStoreFactory
_ => new FileMediaContentStore(directoryPath)
};
}
}
}

View File

@@ -4,4 +4,4 @@ public enum MediaContentStoreKind
{
File,
AzureBlob
}
}

View File

@@ -6,4 +6,4 @@ public sealed class MediaContentStoreOptions
public string? DirectoryPath { get; set; }
public string? ConnectionString { get; set; }
public string ContainerName { get; set; } = "openjibo-media";
}
}

View File

@@ -5,7 +5,8 @@ internal static class MediaPathHelper
public static string GetRelativeStoragePath(string path)
{
var trimmed = path.Trim().TrimStart('/', '\\');
var segments = trimmed.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
var segments = trimmed
.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(SanitizeSegment)
.Where(segment => !string.IsNullOrWhiteSpace(segment))
.ToArray();
@@ -20,4 +21,4 @@ internal static class MediaPathHelper
.ToArray();
return string.Join(string.Empty, chars);
}
}
}

View File

@@ -673,4 +673,4 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
};
}
}
}
}

View File

@@ -10,7 +10,8 @@ internal static class CaptureIndexWriter
WriteIndented = false
};
private static readonly ConcurrentDictionary<string, SemaphoreSlim> DirectoryLocks = new(StringComparer.OrdinalIgnoreCase);
private static readonly ConcurrentDictionary<string, SemaphoreSlim> DirectoryLocks =
new(StringComparer.OrdinalIgnoreCase);
public static async Task AppendAsync(
string directoryPath,
@@ -45,4 +46,4 @@ internal static class CaptureIndexWriter
gate.Release();
}
}
}
}

View File

@@ -89,4 +89,4 @@ public sealed class FileProtocolTelemetrySink(
$"{envelope.ServicePrefix}.{envelope.Operation}".Trim('.'),
result.StatusCode);
}
}
}

View File

@@ -79,4 +79,4 @@ public sealed class FileTurnTelemetrySink(
Directory.GetCurrentDirectory(),
AppContext.BaseDirectory);
}
}
}

View File

@@ -291,4 +291,4 @@ public sealed class FileWebSocketTelemetrySink(
public CapturedWebSocketFixtureSession Session { get; init; } = new();
public List<CapturedWebSocketFixtureStep> Steps { get; } = [];
}
}
}

View File

@@ -218,7 +218,8 @@ public sealed class OpenWeatherReportProvider(
var summary = TryReadWeatherSummary(root);
var condition = TryReadWeatherCondition(root);
var temperature = TryReadInt(main, "temp");
var currentDayHighLow = await TryResolveCurrentDayHighLowFromForecastAsync(location, useCelsius, cancellationToken);
var currentDayHighLow =
await TryResolveCurrentDayHighLowFromForecastAsync(location, useCelsius, cancellationToken);
var high = currentDayHighLow?.High ?? TryReadInt(main, "temp_max");
var low = currentDayHighLow?.Low ?? TryReadInt(main, "temp_min");
@@ -509,4 +510,4 @@ public sealed class OpenWeatherReportProvider(
int? LowTemperature,
string? Summary,
string? Condition);
}
}