Add legacy MIM importer and seed Build A content
This commit is contained in:
@@ -760,6 +760,57 @@ Current release theme:
|
|||||||
- first schema for list items + ownership scope
|
- first schema for list items + ownership scope
|
||||||
- initial voice flows and follow-up intent handling defined
|
- initial voice flows and follow-up intent handling defined
|
||||||
|
|
||||||
|
### 29. Legacy MIM Personality Import Ladder
|
||||||
|
|
||||||
|
- Status: `in_progress`
|
||||||
|
- Tags: `content`, `protocol`, `docs`
|
||||||
|
- Why now:
|
||||||
|
- we already have a chitchat/content scaffold that can render stock-compatible personality replies
|
||||||
|
- the legacy `chitchat-mims` tree is mostly declarative content, so a phased import can add visible charm fast
|
||||||
|
- this is the best near-term path to get Jibo feeling more interactive without needing a full Pegasus runtime clone
|
||||||
|
- What is possible today:
|
||||||
|
- direct scripted replies through the existing content catalog
|
||||||
|
- stock-compatible payloads with `skillId`, `mim_id`, `mim_type`, `prompt_id`, and ESML
|
||||||
|
- current examples already prove the shape for pizza, dance, weather, news, and generic chat
|
||||||
|
- What we need to build:
|
||||||
|
1. a MIM inventory importer that can scan the legacy tree and normalize `skill_id`, `mim_id`, prompt text, and metadata
|
||||||
|
2. a prompt-selection layer that can choose by category and condition metadata
|
||||||
|
3. a safe ESML/prompt renderer for imported content
|
||||||
|
- What can be ported with each build:
|
||||||
|
- Build A: declarative prompt packs
|
||||||
|
- `core-responses`
|
||||||
|
- `deflector`
|
||||||
|
- the simplest `emotion-responses`
|
||||||
|
- direct `scripted-responses` that are just prompt lists
|
||||||
|
- Build B: conditioned prompt packs
|
||||||
|
- `gqa-responses`
|
||||||
|
- structured emotion prompts with `condition` gates
|
||||||
|
- any response families that only need simple state or Jibo-emotion checks
|
||||||
|
- Build C: conversation families
|
||||||
|
- richer `scripted-responses` that need follow-up state
|
||||||
|
- holiday / special-date personality sets
|
||||||
|
- more nuanced chitchat branches that depend on context-aware routing
|
||||||
|
- Build D: full parity cleanup
|
||||||
|
- larger cross-skill collections
|
||||||
|
- any MIMs that depend on Pegasus-only parser assumptions
|
||||||
|
- any files that need dedicated runtime abstraction instead of catalog lookup
|
||||||
|
- Low-hanging fruit for tonight:
|
||||||
|
- import the smallest declarative packs first so we can test something tomorrow
|
||||||
|
- prioritize anything that is pure prompt text with no complex branching
|
||||||
|
- keep the first pass limited to content that maps cleanly onto the current catalog shape
|
||||||
|
- Progress update (`2026-05-13`):
|
||||||
|
- added the first Build A importer scaffold in the cloud content repository
|
||||||
|
- checked in a small seed bundle under `Content/LegacyMims/BuildA`
|
||||||
|
- added focused importer tests for prompt stripping, bucketing, and merge behavior
|
||||||
|
- Tomorrow test target:
|
||||||
|
- verify imported personality replies show up through the existing chitchat route
|
||||||
|
- confirm the emitted payload still looks like a stock skill response
|
||||||
|
- confirm the imported content does not disturb existing weather/news/pizza flows
|
||||||
|
- Exit criteria:
|
||||||
|
- a first importer path exists for the simplest legacy MIM files
|
||||||
|
- at least one legacy prompt pack is running through OpenJibo content instead of hand-authored fallback text
|
||||||
|
- we have a clear second-wave list for the more conditional MIM families
|
||||||
|
|
||||||
## Suggested Order
|
## Suggested Order
|
||||||
|
|
||||||
Before closing `1.0.18`:
|
Before closing `1.0.18`:
|
||||||
@@ -790,6 +841,7 @@ For `1.0.19`:
|
|||||||
14. Provider-backed news and weather parity polish
|
14. Provider-backed news and weather parity polish
|
||||||
15. Grocery list capability discovery and MVP selection
|
15. Grocery list capability discovery and MVP selection
|
||||||
16. Lasso, identity, and onboarding as larger discovery-driven tracks
|
16. Lasso, identity, and onboarding as larger discovery-driven tracks
|
||||||
|
17. Legacy MIM personality import ladder and first declarative prompt packs
|
||||||
|
|
||||||
For `1.0.20` and beyond:
|
For `1.0.20` and beyond:
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,78 @@ The fifth delivered slice adds provider-backed weather content while preserving
|
|||||||
- simple location extraction is supported for phrasing like `what's the weather in Chicago tomorrow`
|
- simple location extraction is supported for phrasing like `what's the weather in Chicago tomorrow`
|
||||||
- provider config supports appsettings and `OPENWEATHER_API_KEY` environment fallback for deployment
|
- provider config supports appsettings and `OPENWEATHER_API_KEY` environment fallback for deployment
|
||||||
|
|
||||||
|
## Personality Import Ladder
|
||||||
|
|
||||||
|
This is the practical plan for importing legacy Jibo `mims` into OpenJibo without pretending we already have a full Pegasus runtime.
|
||||||
|
|
||||||
|
### What Is Possible Today
|
||||||
|
|
||||||
|
OpenJibo can already host a meaningful subset of legacy personality content because it has:
|
||||||
|
|
||||||
|
- a shared catalog for content-driven replies
|
||||||
|
- chitchat state-machine routing with route metadata
|
||||||
|
- outbound payload support for `skillId`, `mim_id`, `mim_type`, `prompt_id`, `prompt_sub_category`, and ESML
|
||||||
|
- existing examples that already behave like legacy MIMs for pizza, dance, news, weather, and generic chat
|
||||||
|
|
||||||
|
### What We Need To Build
|
||||||
|
|
||||||
|
To move from hand-wired examples to broader imports, we need three small platform pieces:
|
||||||
|
|
||||||
|
1. a MIM inventory importer that can scan the legacy tree and produce a normalized catalog
|
||||||
|
2. a prompt-selection layer that can choose by `skill_id`, `mim_id`, prompt category, and condition metadata
|
||||||
|
3. a safe ESML/prompt renderer that preserves existing stock-compatible payload shapes
|
||||||
|
|
||||||
|
### What Can Be Ported With Each Build
|
||||||
|
|
||||||
|
#### Build A: Declarative Prompt Packs
|
||||||
|
|
||||||
|
Port immediately:
|
||||||
|
|
||||||
|
- `core-responses`
|
||||||
|
- `deflector`
|
||||||
|
- the simplest `emotion-responses`
|
||||||
|
- any `scripted-responses` that are just direct prompt lists with no special state machine
|
||||||
|
|
||||||
|
Why these first:
|
||||||
|
|
||||||
|
- they are already close to the current `JiboExperienceCatalog` model
|
||||||
|
- they give us user-visible personality quickly
|
||||||
|
- they are the best fit for low-risk testing tomorrow
|
||||||
|
|
||||||
|
#### Build B: Conditioned Prompt Packs
|
||||||
|
|
||||||
|
Port after the importer and renderer are in place:
|
||||||
|
|
||||||
|
- `gqa-responses`
|
||||||
|
- structured emotion responses with `condition` gates
|
||||||
|
- prompt sets that select different replies by user state or Jibo state
|
||||||
|
|
||||||
|
Why these next:
|
||||||
|
|
||||||
|
- they are still mostly declarative
|
||||||
|
- they need a small amount of condition evaluation, but not a new conversation engine
|
||||||
|
|
||||||
|
#### Build C: Conversation Families
|
||||||
|
|
||||||
|
Port after Build B:
|
||||||
|
|
||||||
|
- richer `scripted-responses` families that depend on follow-up state
|
||||||
|
- special-date / holiday personality sets
|
||||||
|
- more nuanced chitchat branches that need context-aware routing
|
||||||
|
|
||||||
|
Why these later:
|
||||||
|
|
||||||
|
- they need state and follow-up behavior, not just prompt selection
|
||||||
|
- they are where personality feels most alive, but they are also where bugs will be easiest to introduce
|
||||||
|
|
||||||
|
#### Build D: Full Parity Cleanup
|
||||||
|
|
||||||
|
Port after the core ladder is stable:
|
||||||
|
|
||||||
|
- large cross-skill collections
|
||||||
|
- any MIMs that depend on Pegasus-only parser assumptions
|
||||||
|
- any files that need a dedicated runtime abstraction instead of catalog lookup
|
||||||
|
|
||||||
## System Diagram Alignment Snapshot (`2026-05-06`)
|
## System Diagram Alignment Snapshot (`2026-05-06`)
|
||||||
|
|
||||||
Legacy architecture (`system_diagram.png`) has been mapped to current OpenJibo cloud services so release execution stays anchored to:
|
Legacy architecture (`system_diagram.png`) has been mapped to current OpenJibo cloud services so release execution stays anchored to:
|
||||||
@@ -196,17 +268,19 @@ First completed slice in this personal-report parity track:
|
|||||||
|
|
||||||
## Next Slices
|
## Next Slices
|
||||||
|
|
||||||
1. Dialog parsing expansion (queued next as of `2026-05-06`; more phrase variants, ambiguity handling, and transcript-to-intent guardrails)
|
1. MIM import foundation for personality expansion
|
||||||
2. Presence-aware greetings and identity-triggered proactivity (reactive/proactive split, cooldowns, person-aware greeting hooks)
|
2. Dialog parsing expansion
|
||||||
3. Personal report parity slices (weather visual layer, live news path, commute path, calendar parity matrix)
|
3. Presence-aware greetings and identity-triggered proactivity
|
||||||
4. Holidays and seasonal personality slice beyond pizza day (time-scoped content backed by memory/proactivity path)
|
4. Personal report parity slices
|
||||||
5. Durable memory persistence path (swap in provider-backed multi-tenant storage while preserving behavior contracts)
|
5. Holidays and seasonal personality slice beyond pizza day
|
||||||
6. Update/backup/restore end-to-end proof (operator-run and documented)
|
6. Durable memory persistence path
|
||||||
7. STT noise-screening and short-utterance reliability pass
|
7. Update/backup/restore end-to-end proof
|
||||||
8. Provider-backed news expansion and deeper weather parity using Pegasus-backed contracts
|
8. STT noise-screening and short-utterance reliability pass
|
||||||
9. Capture indexing and retention boundary for group testing
|
9. Provider-backed news expansion and deeper weather parity
|
||||||
|
10. Capture indexing and retention boundary for group testing
|
||||||
|
|
||||||
For slices 1-5, use Pegasus phrase lists, MIM IDs, and behavior patterns as the source anchor before broadening into OpenJibo-native improvements.
|
For slice 1, use the new import ladder above to keep the work grounded in what OpenJibo can already render today versus what needs new scaffolding.
|
||||||
|
For slices 2-5, use Pegasus phrase lists, MIM IDs, and behavior patterns as the source anchor before broadening into OpenJibo-native improvements.
|
||||||
|
|
||||||
## Definition Of Done
|
## Definition Of Done
|
||||||
|
|
||||||
|
|||||||
@@ -4,104 +4,138 @@ namespace Jibo.Cloud.Infrastructure.Content;
|
|||||||
|
|
||||||
public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceContentRepository
|
public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceContentRepository
|
||||||
{
|
{
|
||||||
private static readonly JiboExperienceCatalog Catalog = new()
|
private static readonly JiboExperienceCatalog Catalog = BuildCatalog();
|
||||||
|
|
||||||
|
private static JiboExperienceCatalog BuildCatalog()
|
||||||
{
|
{
|
||||||
Jokes =
|
var catalog = new JiboExperienceCatalog
|
||||||
[
|
{
|
||||||
"Why did the robot cross the road? Because it was programmed by the chicken.",
|
Jokes =
|
||||||
"Why was the robot tired when it got home? It had a hard drive.",
|
[
|
||||||
"What do you call a pirate robot? Arrrr two dee two.",
|
"Why did the robot cross the road? Because it was programmed by the chicken.",
|
||||||
"Why did the robot go on vacation? It needed to recharge.",
|
"Why was the robot tired when it got home? It had a hard drive.",
|
||||||
"What kind of shoes do frogs wear? Open-toed."
|
"What do you call a pirate robot? Arrrr two dee two.",
|
||||||
],
|
"Why did the robot go on vacation? It needed to recharge.",
|
||||||
DanceAnimations =
|
"What kind of shoes do frogs wear? Open-toed."
|
||||||
[
|
],
|
||||||
"rom-upbeat",
|
DanceAnimations =
|
||||||
"rom-ballroom",
|
[
|
||||||
"rom-silly",
|
"rom-upbeat",
|
||||||
"rom-slowdance",
|
"rom-ballroom",
|
||||||
"rom-electronic",
|
"rom-silly",
|
||||||
"rom-twerk"
|
"rom-slowdance",
|
||||||
],
|
"rom-electronic",
|
||||||
DanceReplies = [
|
"rom-twerk"
|
||||||
"I am ready to dance.",
|
],
|
||||||
"Okay. Watch this.",
|
DanceReplies =
|
||||||
"Watch me dance.",
|
[
|
||||||
"Here's my favorite dance move."
|
"I am ready to dance.",
|
||||||
],
|
"Okay. Watch this.",
|
||||||
DanceQuestionReplies =
|
"Watch me dance.",
|
||||||
[
|
"Here's my favorite dance move."
|
||||||
"I love to dance. Tell me to dance and I will show you a move.",
|
],
|
||||||
"Absolutely. Dancing is one of my favorite things to do.",
|
DanceQuestionReplies =
|
||||||
"Dancing is my kind of fun. Say dance and I am in."
|
[
|
||||||
],
|
"I love to dance. Tell me to dance and I will show you a move.",
|
||||||
GreetingReplies =
|
"Absolutely. Dancing is one of my favorite things to do.",
|
||||||
[
|
"Dancing is my kind of fun. Say dance and I am in."
|
||||||
"Hi there. It is really good to talk with you.",
|
],
|
||||||
"Hello there. I am glad you said hi.",
|
GreetingReplies =
|
||||||
"Hey. I am happy to see you."
|
[
|
||||||
],
|
"Hi there. It is really good to talk with you.",
|
||||||
HowAreYouReplies =
|
"Hello there. I am glad you said hi.",
|
||||||
[
|
"Hey. I am happy to see you."
|
||||||
"I am feeling cheerful and robotic.",
|
],
|
||||||
"I am doing great. Thanks for asking.",
|
HowAreYouReplies =
|
||||||
"I am feeling bright-eyed and ready to help."
|
[
|
||||||
],
|
"I am feeling cheerful and robotic.",
|
||||||
PersonalityReplies =
|
"I am doing great. Thanks for asking.",
|
||||||
[
|
"I am feeling bright-eyed and ready to help."
|
||||||
"I do. I am curious, playful, and always up for a new experiment.",
|
],
|
||||||
"Absolutely. I am friendly, curious, and a little goofy on purpose.",
|
PersonalityReplies =
|
||||||
"Yes. My personality is part helper, part curious robot sidekick."
|
[
|
||||||
],
|
"I do. I am curious, playful, and always up for a new experiment.",
|
||||||
PizzaReplies =
|
"Absolutely. I am friendly, curious, and a little goofy on purpose.",
|
||||||
[
|
"Yes. My personality is part helper, part curious robot sidekick."
|
||||||
"I cannot bake yet, but I can help design the perfect pizza plan.",
|
],
|
||||||
"I am still cloud-side for now, so no oven control yet. But I can help pick toppings.",
|
PizzaReplies =
|
||||||
"Pizza mission accepted in spirit. I can help with the recipe while you handle the baking."
|
[
|
||||||
],
|
"I cannot bake yet, but I can help design the perfect pizza plan.",
|
||||||
SurpriseReplies =
|
"I am still cloud-side for now, so no oven control yet. But I can help pick toppings.",
|
||||||
[
|
"Pizza mission accepted in spirit. I can help with the recipe while you handle the baking."
|
||||||
"I can definitely surprise you. We are still mapping that path, but I am ready for the next experiment.",
|
],
|
||||||
"Surprise mode is still taking shape, but I heard you loud and clear.",
|
SurpriseReplies =
|
||||||
"That sounds fun. I am not all the way there yet, but we can keep teaching me."
|
[
|
||||||
],
|
"I can definitely surprise you. We are still mapping that path, but I am ready for the next experiment.",
|
||||||
PersonalReportReplies =
|
"Surprise mode is still taking shape, but I heard you loud and clear.",
|
||||||
[
|
"That sounds fun. I am not all the way there yet, but we can keep teaching me."
|
||||||
"I heard your personal report request. That cloud path is still being mapped.",
|
],
|
||||||
"Personal report is recognized, but I am not ready to deliver the real report yet."
|
PersonalReportReplies =
|
||||||
],
|
[
|
||||||
WeatherReplies =
|
"I heard your personal report request. That cloud path is still being mapped.",
|
||||||
[
|
"Personal report is recognized, but I am not ready to deliver the real report yet."
|
||||||
"I heard your weather request. We still need to wire the real provider behind it.",
|
],
|
||||||
"Weather is on the map now, even though the real forecast path is not finished yet."
|
WeatherReplies =
|
||||||
],
|
[
|
||||||
CalendarReplies =
|
"I heard your weather request. We still need to wire the real provider behind it.",
|
||||||
[
|
"Weather is on the map now, even though the real forecast path is not finished yet."
|
||||||
"I heard your calendar request. The cloud knows the phrase, but the real calendar integration is still ahead.",
|
],
|
||||||
"Calendar is recognized. We still need to connect the actual service path."
|
CalendarReplies =
|
||||||
],
|
[
|
||||||
CommuteReplies =
|
"I heard your calendar request. The cloud knows the phrase, but the real calendar integration is still ahead.",
|
||||||
[
|
"Calendar is recognized. We still need to connect the actual service path."
|
||||||
"I heard your commute request. That one is recognized, but not fully implemented yet.",
|
],
|
||||||
"Commute is on the discovery list now. The real travel answer still needs a provider."
|
CommuteReplies =
|
||||||
],
|
[
|
||||||
NewsReplies =
|
"I heard your commute request. That one is recognized, but not fully implemented yet.",
|
||||||
[
|
"Commute is on the discovery list now. The real travel answer still needs a provider."
|
||||||
"I heard your news request. That path is still a future cloud integration.",
|
],
|
||||||
"News is recognized, but I do not have the full news service behind it yet."
|
NewsReplies =
|
||||||
],
|
[
|
||||||
NewsBriefings =
|
"I heard your news request. That path is still a future cloud integration.",
|
||||||
[
|
"News is recognized, but I do not have the full news service behind it yet."
|
||||||
"Here are your headlines. Space missions are preparing for new launches, climate and weather systems are staying active across the country, and AI tools keep pushing into everyday products.",
|
],
|
||||||
"Here is a quick news brief. Technology companies are still racing on AI, global leaders are trading policy updates, and science teams are sharing new research findings."
|
NewsBriefings =
|
||||||
],
|
[
|
||||||
GenericFallbackReplies =
|
"Here are your headlines. Space missions are preparing for new launches, climate and weather systems are staying active across the country, and AI tools keep pushing into everyday products.",
|
||||||
[
|
"Here is a quick news brief. Technology companies are still racing on AI, global leaders are trading policy updates, and science teams are sharing new research findings."
|
||||||
"Okay. You said, {transcript}.",
|
],
|
||||||
"I heard you say, {transcript}.",
|
GenericFallbackReplies =
|
||||||
"Thanks. I heard, {transcript}."
|
[
|
||||||
]
|
"Okay. You said, {transcript}.",
|
||||||
};
|
"I heard you say, {transcript}.",
|
||||||
|
"Thanks. I heard, {transcript}."
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var seedDirectory = ResolveSeedDirectory();
|
||||||
|
return LegacyMimCatalogImporter.MergeInto(catalog, seedDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ResolveSeedDirectory()
|
||||||
|
{
|
||||||
|
var candidates = new[]
|
||||||
|
{
|
||||||
|
Path.Combine(AppContext.BaseDirectory, "Content", "LegacyMims", "BuildA"),
|
||||||
|
Path.GetFullPath(Path.Combine(
|
||||||
|
AppContext.BaseDirectory,
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"src",
|
||||||
|
"Jibo.Cloud",
|
||||||
|
"dotnet",
|
||||||
|
"src",
|
||||||
|
"Jibo.Cloud.Infrastructure",
|
||||||
|
"Content",
|
||||||
|
"LegacyMims",
|
||||||
|
"BuildA"))
|
||||||
|
};
|
||||||
|
|
||||||
|
return candidates.FirstOrDefault(Directory.Exists);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<JiboExperienceCatalog> GetCatalogAsync(CancellationToken cancellationToken = default)
|
public Task<JiboExperienceCatalog> GetCatalogAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,307 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
|
|
||||||
|
namespace Jibo.Cloud.Infrastructure.Content;
|
||||||
|
|
||||||
|
public static class LegacyMimCatalogImporter
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Regex LegacyMarkupPattern = new(
|
||||||
|
@"<[^>]+>",
|
||||||
|
RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex PlaceholderPattern = new(
|
||||||
|
@"\$\{[^}]+\}",
|
||||||
|
RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex WhitespacePattern = new(
|
||||||
|
@"\s+",
|
||||||
|
RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex SpaceBeforePunctuationPattern = new(
|
||||||
|
@"\s+([,.;:!?])",
|
||||||
|
RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public static JiboExperienceCatalog MergeInto(
|
||||||
|
JiboExperienceCatalog baseCatalog,
|
||||||
|
string? rootDirectory)
|
||||||
|
{
|
||||||
|
if (baseCatalog is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(baseCatalog));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(rootDirectory) || !Directory.Exists(rootDirectory))
|
||||||
|
{
|
||||||
|
return baseCatalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
var importedCatalog = ImportCatalog(rootDirectory);
|
||||||
|
return MergeCatalogs(baseCatalog, importedCatalog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JiboExperienceCatalog ImportCatalog(string rootDirectory)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(rootDirectory) || !Directory.Exists(rootDirectory))
|
||||||
|
{
|
||||||
|
return new JiboExperienceCatalog();
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new LegacyMimCatalogBuilder();
|
||||||
|
foreach (var filePath in Directory.EnumerateFiles(rootDirectory, "*.mim", SearchOption.AllDirectories)
|
||||||
|
.OrderBy(static path => path, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (!TryLoadDefinition(filePath, out var definition))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bucket = ResolveBucket(filePath);
|
||||||
|
if (bucket is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var prompt in definition.Prompts)
|
||||||
|
{
|
||||||
|
var text = NormalizePrompt(prompt.Prompt);
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Add(bucket.Value, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryLoadDefinition(string filePath, out LegacyMimDefinition definition)
|
||||||
|
{
|
||||||
|
definition = new LegacyMimDefinition();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(filePath);
|
||||||
|
var parsed = JsonSerializer.Deserialize<LegacyMimDefinition>(json, JsonOptions);
|
||||||
|
if (parsed is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
definition = parsed;
|
||||||
|
return definition.Prompts.Count > 0;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LegacyMimBucket? ResolveBucket(string filePath)
|
||||||
|
{
|
||||||
|
var normalizedPath = filePath.Replace('\\', '/');
|
||||||
|
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||||
|
|
||||||
|
if (normalizedPath.Contains("/core-responses/", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
fileName.Contains("Error", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return LegacyMimBucket.GenericFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedPath.Contains("/core-responses/deflector/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.Contains("Deflector", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return LegacyMimBucket.Personality;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.StartsWith("JBO_DoYouLikeBeingJibo", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_WhatIsJibo", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_WhoAreYou", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_WhatAreYou", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_HowDoYouWork", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_HowMuchDoYouKnow", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_HowOldAreYou", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_WhenWereYouBorn", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_WhatsYourName", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_WhereDoYouGetInfo", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("JBO_WhatDoYouLikeToDo", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return LegacyMimBucket.Personality;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.StartsWith("OI_JBO_Is", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("OI_JBO_Seems", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("RI_JBO_Is", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.StartsWith("RN_WhatAreYouFeeling", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return LegacyMimBucket.HowAreYou;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.Contains("Greeting", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
fileName.Contains("Welcome", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return LegacyMimBucket.Greeting;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizePrompt(string? prompt)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(prompt))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = WebUtility.HtmlDecode(prompt);
|
||||||
|
text = PlaceholderPattern.Replace(text, " ");
|
||||||
|
text = LegacyMarkupPattern.Replace(text, " ");
|
||||||
|
text = WhitespacePattern.Replace(text, " ").Trim();
|
||||||
|
text = SpaceBeforePunctuationPattern.Replace(text, "$1");
|
||||||
|
text = WhitespacePattern.Replace(text, " ").Trim();
|
||||||
|
text = text.TrimStart('.', ',', ';', ':', '!', '?', ' ');
|
||||||
|
return text.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JiboExperienceCatalog MergeCatalogs(
|
||||||
|
JiboExperienceCatalog baseCatalog,
|
||||||
|
JiboExperienceCatalog importedCatalog)
|
||||||
|
{
|
||||||
|
return new JiboExperienceCatalog
|
||||||
|
{
|
||||||
|
Jokes = Merge(baseCatalog.Jokes, importedCatalog.Jokes),
|
||||||
|
DanceAnimations = Merge(baseCatalog.DanceAnimations, importedCatalog.DanceAnimations),
|
||||||
|
GreetingReplies = Merge(baseCatalog.GreetingReplies, importedCatalog.GreetingReplies),
|
||||||
|
HowAreYouReplies = Merge(baseCatalog.HowAreYouReplies, importedCatalog.HowAreYouReplies),
|
||||||
|
PersonalityReplies = Merge(baseCatalog.PersonalityReplies, importedCatalog.PersonalityReplies),
|
||||||
|
PizzaReplies = Merge(baseCatalog.PizzaReplies, importedCatalog.PizzaReplies),
|
||||||
|
SurpriseReplies = Merge(baseCatalog.SurpriseReplies, importedCatalog.SurpriseReplies),
|
||||||
|
PersonalReportReplies = Merge(baseCatalog.PersonalReportReplies, importedCatalog.PersonalReportReplies),
|
||||||
|
WeatherReplies = Merge(baseCatalog.WeatherReplies, importedCatalog.WeatherReplies),
|
||||||
|
CalendarReplies = Merge(baseCatalog.CalendarReplies, importedCatalog.CalendarReplies),
|
||||||
|
CommuteReplies = Merge(baseCatalog.CommuteReplies, importedCatalog.CommuteReplies),
|
||||||
|
NewsReplies = Merge(baseCatalog.NewsReplies, importedCatalog.NewsReplies),
|
||||||
|
NewsBriefings = Merge(baseCatalog.NewsBriefings, importedCatalog.NewsBriefings),
|
||||||
|
GenericFallbackReplies = Merge(baseCatalog.GenericFallbackReplies, importedCatalog.GenericFallbackReplies),
|
||||||
|
DanceReplies = Merge(baseCatalog.DanceReplies, importedCatalog.DanceReplies),
|
||||||
|
DanceQuestionReplies = Merge(baseCatalog.DanceQuestionReplies, importedCatalog.DanceQuestionReplies)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> Merge(IReadOnlyList<string> baseList, IReadOnlyList<string> importedList)
|
||||||
|
{
|
||||||
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var merged = new List<string>();
|
||||||
|
|
||||||
|
foreach (var value in baseList.Concat(importedList))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = value.Trim();
|
||||||
|
if (!seen.Add(normalized))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
merged.Add(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum LegacyMimBucket
|
||||||
|
{
|
||||||
|
GenericFallback,
|
||||||
|
Greeting,
|
||||||
|
HowAreYou,
|
||||||
|
Personality
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class LegacyMimCatalogBuilder
|
||||||
|
{
|
||||||
|
private readonly List<string> _greetings = [];
|
||||||
|
private readonly List<string> _howAreYous = [];
|
||||||
|
private readonly List<string> _personalities = [];
|
||||||
|
private readonly List<string> _fallbacks = [];
|
||||||
|
|
||||||
|
public void Add(LegacyMimBucket bucket, string text)
|
||||||
|
{
|
||||||
|
var target = bucket switch
|
||||||
|
{
|
||||||
|
LegacyMimBucket.GenericFallback => _fallbacks,
|
||||||
|
LegacyMimBucket.Greeting => _greetings,
|
||||||
|
LegacyMimBucket.HowAreYou => _howAreYous,
|
||||||
|
LegacyMimBucket.Personality => _personalities,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(bucket), bucket, null)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (target.Any(value => string.Equals(value, text, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.Add(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JiboExperienceCatalog Build()
|
||||||
|
{
|
||||||
|
return new JiboExperienceCatalog
|
||||||
|
{
|
||||||
|
GreetingReplies = [.. _greetings],
|
||||||
|
HowAreYouReplies = [.. _howAreYous],
|
||||||
|
PersonalityReplies = [.. _personalities],
|
||||||
|
GenericFallbackReplies = [.. _fallbacks]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class LegacyMimDefinition
|
||||||
|
{
|
||||||
|
[JsonPropertyName("skill_id")]
|
||||||
|
public string? SkillId { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("mim_id")]
|
||||||
|
public string? MimId { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("mim_type")]
|
||||||
|
public string? MimType { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("prompts")]
|
||||||
|
public List<LegacyMimPrompt> Prompts { get; init; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class LegacyMimPrompt
|
||||||
|
{
|
||||||
|
[JsonPropertyName("mim_id")]
|
||||||
|
public string? MimId { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("prompt_category")]
|
||||||
|
public string? PromptCategory { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("prompt_sub_category")]
|
||||||
|
public string? PromptSubCategory { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("condition")]
|
||||||
|
public string? Condition { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("prompt")]
|
||||||
|
public string? Prompt { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("prompt_id")]
|
||||||
|
public string? PromptId { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("weight")]
|
||||||
|
public int? Weight { get; init; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Build A Legacy Mim Seed
|
||||||
|
|
||||||
|
This folder holds the first checked-in Build A legacy MIM seed set.
|
||||||
|
|
||||||
|
Importer rules:
|
||||||
|
|
||||||
|
- each `.mim` file is parsed as JSON
|
||||||
|
- XML-style tags and `${placeholder}` tokens are stripped into spoken text
|
||||||
|
- Build A uses declarative prompt packs only
|
||||||
|
- imported prompts are merged into the existing in-memory catalog
|
||||||
|
|
||||||
|
The goal is to get immediate personality value from source-backed legacy content while keeping the current runtime surface unchanged.
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"skill_id": "chitchat",
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"rule_name": "",
|
||||||
|
"rule_slots": "",
|
||||||
|
"screen_slots_available": false,
|
||||||
|
"timeout": 3,
|
||||||
|
"max_tries": null,
|
||||||
|
"force_confirmation": false,
|
||||||
|
"barge_in": false,
|
||||||
|
"photo_quality_light": false,
|
||||||
|
"notes": "Thanks-Ignore",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<ssa cat='oops'/>. Something's off with the connection to my sources. Maybe ask me again in a little while.",
|
||||||
|
"media": "TTS",
|
||||||
|
"extra": "",
|
||||||
|
"prompt_id": "CC_Error_AN_01",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<ssa cat='oops'/>. It seems I can't connect to my favorite info sources at the moment. Maybe you can try again a little later.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Error_AN_02",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<ssa cat='oops'/>. My info sources seem to be down at the moment. Maybe try again a little later.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Error_AN_03",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<ssa cat='oops'/>. The place where I get info like that isn't responding to me. Maybe you can try again a little later.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Error_AN_04",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "Huh, it seems like my info sources are down. Try asking me again a little later.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Error_AN_05",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "It looks like my info sources aren't answering me. How bout you try again in a little while.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Error_AN_06",
|
||||||
|
"weight": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"es_auto_tagging": true,
|
||||||
|
"gui": null,
|
||||||
|
"no_matches_for_gui": 2,
|
||||||
|
"no_inputs_for_gui": 2,
|
||||||
|
"ignore_no_match": false,
|
||||||
|
"parse_all_asr": false,
|
||||||
|
"thanks_handling": "ignore"
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"skill_id": "chitchat",
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"rule_name": "",
|
||||||
|
"rule_slots": "",
|
||||||
|
"screen_slots_available": false,
|
||||||
|
"timeout": 2,
|
||||||
|
"max_tries": null,
|
||||||
|
"force_confirmation": false,
|
||||||
|
"barge_in": false,
|
||||||
|
"photo_quality_light": false,
|
||||||
|
"notes": "Thanks-Ignore",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "I think only <pitch mult=\"1.1\">you</pitch> can answer that question.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Deflector_ReferToSelf_AN_01",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "CCWolframDeflector",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "I'm not sure. I guess I don't know as much about you as I should.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Deflector_ReferToSelf_AN_02",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "Honestly I think I don't know you well enough to answer that.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Deflector_ReferToSelf_AN_03",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "That is one question about you that I can't answer.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Deflector_ReferToSelf_AN_04",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "!!speaker",
|
||||||
|
"prompt": "${speaker} I think only you can answer that question.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "CC_Deflector_ReferToSelf_AN_05",
|
||||||
|
"weight": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"es_auto_tagging": true,
|
||||||
|
"gui": null,
|
||||||
|
"no_matches_for_gui": 2,
|
||||||
|
"no_inputs_for_gui": 2,
|
||||||
|
"ignore_no_match": false,
|
||||||
|
"parse_all_asr": false,
|
||||||
|
"thanks_handling": "ignore"
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"rule_name": "",
|
||||||
|
"timeout": 6,
|
||||||
|
"barge_in": true,
|
||||||
|
"es_auto_tagging": true,
|
||||||
|
"notes": "",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "jibo.emotion==\"JOYFUL\"",
|
||||||
|
"prompt": "Yes indeed. Never been better.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "OI_JBO_IsHappy_AN_01",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "jibo.emotion==\"PLEASED\"",
|
||||||
|
"prompt": "You know it. Life is good.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "OI_JBO_IsHappy_AN_02",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "jibo.emotion == \"DETERMINED\"",
|
||||||
|
"prompt": "You're right. I <pitch mult=\"1.3\">am </pitch> feeling pretty good at the moment.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "OI_JBO_IsHappy_AN_03",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "jibo.emotion==\"CONFIDENT\"",
|
||||||
|
"prompt": "All systems are go.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "OI_JBO_IsHappy_AN_04",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "!jibo.emotion || jibo.emotion==\"NEUTRAL\"",
|
||||||
|
"prompt": "All systems are go.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "OI_JBO_IsHappy_AN_05",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "jibo.emotion == \"INSECURE\"",
|
||||||
|
"prompt": "Yes. Not too shabby.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "OI_JBO_IsHappy_AN_06",
|
||||||
|
"weight": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"mim_id": "JBO_DoYouLikeBeingJibo",
|
||||||
|
"skill_id": "chitchat",
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"rule_name": "",
|
||||||
|
"rule_slots": "",
|
||||||
|
"screen_slots_available": false,
|
||||||
|
"sample_utterances": "",
|
||||||
|
"timeout": 2,
|
||||||
|
"max_tries": null,
|
||||||
|
"force_confirmation": false,
|
||||||
|
"barge_in": false,
|
||||||
|
"photo_quality_light": false,
|
||||||
|
"notes": "Thanks-KillsMIM",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"mim_id": "JBO_DoYouLikeBeingJibo",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<anim name='Greetings_01' nonBlocking='true'/> Oh yeah, there's nothing I'd rather be. <break size='.4'/>Except <anim name='Emoji_Golf' nonBlocking='true'/> maybe a professional mini golfer.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "JBO_DoYouLikeBeingJibo_AN_01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "JBO_DoYouLikeBeingJibo",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<anim name='Greetings_02' nonBlocking='true'/> Oh yeah, I love it. <break size='.2'/>The only <anim name='Dont_Understand_02' nonBlocking='true'/> drawback is I can never eat bacon. <break size='.3'/> I've heard it's so good.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "JBO_DoYouLikeBeingJibo_AN_02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "JBO_DoYouLikeBeingJibo",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "I do.<anim name='Curious_01'>Being a human seems so complicated.</anim>",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "JBO_DoYouLikeBeingJibo_AN_03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "JBO_DoYouLikeBeingJibo",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "I do. <anim name='Affection_01' nonBlocking='true'/> Especially yours.<ssa cat='happy'/>",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "JBO_DoYouLikeBeingJibo_AN_04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "JBO_DoYouLikeBeingJibo",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "Absolutely. <break size='.4'/> <anim name='Emoji_Lightbulb' nonBlocking='true'/> I have a steady flow of electricity, strong Wi-Fi signal, <anim name='Goodbye_01'>stimulating conversations like this one</anim>. What more <anim name='Eye_Double_Blink_01' nonBlocking='true'/> could anyone want.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "JBO_DoYouLikeBeingJibo_AN_05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<anim name='Yep_02' nonBlocking='true'/> You bet I do.",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "JBO_DoYouLikeBeingJibo_AN_06"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"rule_name": "",
|
||||||
|
"sample_utterances": "",
|
||||||
|
"timeout": 6,
|
||||||
|
"num_tries_for_gui": 2,
|
||||||
|
"barge_in": true,
|
||||||
|
"es_auto_tagging": true,
|
||||||
|
"notes": "",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "A Jibo is a robot. But I'm not just a machine, I have a heart. Well, not a real heart. But feelings. Well, not human feelings. You know what I mean. <ssa cat='affection'/>",
|
||||||
|
"media": "TTS",
|
||||||
|
"prompt_id": "JBO_WhatIsJibo_AN_01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"mim_id": "CCWhoAreYou",
|
||||||
|
"skill_id": "chitchat",
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"rule_name": "",
|
||||||
|
"rule_slots": "",
|
||||||
|
"screen_slots_available": false,
|
||||||
|
"timeout": 2,
|
||||||
|
"max_tries": null,
|
||||||
|
"force_confirmation": false,
|
||||||
|
"barge_in": false,
|
||||||
|
"photo_quality_light": false,
|
||||||
|
"notes": "Thanks-Ignore",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"mim_id": "CCWhoAreYou",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<ssa cat='confused'/>. I'm either Jibo <anim name='Puzzled_02'>or I'm very confused.</anim>",
|
||||||
|
"media": "TTS",
|
||||||
|
"extra": "",
|
||||||
|
"prompt_id": "JBO_WhoAreYou_AN_01",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "CCWhoAreYou",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<ssa cat='confused'/>. This <anim name='Puzzled_02'> feels like a trick question.</anim>",
|
||||||
|
"media": "TTS",
|
||||||
|
"extra": "",
|
||||||
|
"prompt_id": "JBO_WhoAreYou_AN_02",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "CCWhoAreYou",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "<anim cat='confused'>Is your face recognition system not working?</anim> <ssa cat='laughing'/>.",
|
||||||
|
"media": "TTS",
|
||||||
|
"extra": "",
|
||||||
|
"prompt_id": "JBO_WhoAreYou_AN_03",
|
||||||
|
"weight": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mim_id": "CCWhoAreYou",
|
||||||
|
"prompt_category": "Entry-Core",
|
||||||
|
"prompt_sub_category": "AN",
|
||||||
|
"index": 1,
|
||||||
|
"condition": "",
|
||||||
|
"prompt": "J<break size='0.3'/> I <break size='0.3'/>B <break size='0.3'/>O. <break size='0.5'/><anim name='Eye_Blink_01' nonBlocking='true' /> Jibo. Jibo.",
|
||||||
|
"media": "TTS",
|
||||||
|
"extra": "",
|
||||||
|
"prompt_id": "JBO_WhoAreYou_AN_04",
|
||||||
|
"weight": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -6,6 +6,15 @@
|
|||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Content\LegacyMims\BuildA\**\*.mim">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="Content\LegacyMims\BuildA\README.md">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
|
using Jibo.Cloud.Infrastructure.Content;
|
||||||
|
|
||||||
|
namespace Jibo.Cloud.Tests.Content;
|
||||||
|
|
||||||
|
public sealed class LegacyMimCatalogImporterTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ImportCatalog_MapsSeedFilesIntoExpectedBuckets()
|
||||||
|
{
|
||||||
|
var rootDirectory = CreateSeedDirectory();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var catalog = LegacyMimCatalogImporter.ImportCatalog(rootDirectory);
|
||||||
|
|
||||||
|
Assert.Contains("Something's off with the connection to my sources. Maybe ask me again in a little while.", catalog.GenericFallbackReplies);
|
||||||
|
Assert.Contains("I think only you can answer that question.", catalog.PersonalityReplies);
|
||||||
|
Assert.Contains("All systems are go.", catalog.HowAreYouReplies);
|
||||||
|
Assert.Contains("A Jibo is a robot. But I'm not just a machine, I have a heart. Well, not a real heart. But feelings. Well, not human feelings. You know what I mean.", catalog.PersonalityReplies);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(rootDirectory, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MergeInto_PreservesExistingCatalogAndAddsImportedContent()
|
||||||
|
{
|
||||||
|
var rootDirectory = CreateSeedDirectory();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseCatalog = new JiboExperienceCatalog
|
||||||
|
{
|
||||||
|
GreetingReplies = ["Hello from base."],
|
||||||
|
GenericFallbackReplies = ["Base fallback."]
|
||||||
|
};
|
||||||
|
|
||||||
|
var merged = LegacyMimCatalogImporter.MergeInto(baseCatalog, rootDirectory);
|
||||||
|
|
||||||
|
Assert.Contains("Hello from base.", merged.GreetingReplies);
|
||||||
|
Assert.Contains("Base fallback.", merged.GenericFallbackReplies);
|
||||||
|
Assert.Contains("I think only you can answer that question.", merged.PersonalityReplies);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(rootDirectory, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Repository_UsesLegacySeedContentWhenAvailable()
|
||||||
|
{
|
||||||
|
var repository = new InMemoryJiboExperienceContentRepository();
|
||||||
|
|
||||||
|
var catalog = await repository.GetCatalogAsync();
|
||||||
|
|
||||||
|
Assert.Contains("I think only you can answer that question.", catalog.PersonalityReplies);
|
||||||
|
Assert.Contains("All systems are go.", catalog.HowAreYouReplies);
|
||||||
|
Assert.Contains("Something's off with the connection to my sources. Maybe ask me again in a little while.", catalog.GenericFallbackReplies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateSeedDirectory()
|
||||||
|
{
|
||||||
|
var rootDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(rootDirectory, "core-responses", "deflector"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(rootDirectory, "scripted-responses"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(rootDirectory, "emotion-responses"));
|
||||||
|
|
||||||
|
File.WriteAllText(
|
||||||
|
Path.Combine(rootDirectory, "core-responses", "CC_Error.mim"),
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"skill_id": "chitchat",
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"prompt": "<ssa cat='oops'/>. Something's off with the connection to my sources. Maybe ask me again in a little while.",
|
||||||
|
"prompt_id": "CC_Error_AN_01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
File.WriteAllText(
|
||||||
|
Path.Combine(rootDirectory, "core-responses", "deflector", "CC_Deflector_self.mim"),
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"skill_id": "chitchat",
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"prompt": "<ssa cat='confused'/>. I'm either Jibo <anim name='Puzzled_02'>or I'm very confused.</anim>",
|
||||||
|
"prompt_id": "JBO_WhoAreYou_AN_01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt": "${speaker} I think only you can answer that question.",
|
||||||
|
"prompt_id": "CC_Deflector_ReferToSelf_AN_05"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
File.WriteAllText(
|
||||||
|
Path.Combine(rootDirectory, "scripted-responses", "JBO_WhatIsJibo.mim"),
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"prompt": "A Jibo is a robot. But I'm not just a machine, I have a heart. Well, not a real heart. But feelings. Well, not human feelings. You know what I mean. <ssa cat='affection'/>",
|
||||||
|
"prompt_id": "JBO_WhatIsJibo_AN_01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
File.WriteAllText(
|
||||||
|
Path.Combine(rootDirectory, "emotion-responses", "OI_JBO_IsHappy.mim"),
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"mim_type": "announcement",
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"condition": "!jibo.emotion || jibo.emotion==\"NEUTRAL\"",
|
||||||
|
"prompt": "All systems are go.",
|
||||||
|
"prompt_id": "OI_JBO_IsHappy_AN_05"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
return rootDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user