Polish grocery list alias wording and backlog MVP decision
This commit is contained in:
@@ -7,11 +7,15 @@ internal static class HouseholdListOrchestrator
|
||||
{
|
||||
internal const string StateMetadataKey = "householdListState";
|
||||
internal const string TypeMetadataKey = "householdListType";
|
||||
internal const string DisplayTypeMetadataKey = "householdListDisplayType";
|
||||
internal const string NoMatchCountMetadataKey = "householdListNoMatchCount";
|
||||
internal const string NoInputCountMetadataKey = "householdListNoInputCount";
|
||||
|
||||
private const string IdleState = "idle";
|
||||
private const string AwaitingItemState = "awaiting_item";
|
||||
private const string ShoppingListType = "shopping";
|
||||
private const string GroceryListType = "grocery";
|
||||
private const string TodoListType = "todo";
|
||||
|
||||
private static readonly string[] ItemPrefixes =
|
||||
[
|
||||
@@ -31,6 +35,10 @@ internal static class HouseholdListOrchestrator
|
||||
" to my shopping list",
|
||||
" to the shopping list",
|
||||
" on my shopping list",
|
||||
" to my grocery list",
|
||||
" to the grocery list",
|
||||
" on my grocery list",
|
||||
" my grocery list",
|
||||
" to my to do list",
|
||||
" to the to do list",
|
||||
" on my to do list",
|
||||
@@ -50,6 +58,7 @@ internal static class HouseholdListOrchestrator
|
||||
{
|
||||
var state = ReadString(turn, StateMetadataKey);
|
||||
var listType = ReadString(turn, TypeMetadataKey);
|
||||
var displayType = ReadString(turn, DisplayTypeMetadataKey);
|
||||
var isActiveState = !string.IsNullOrWhiteSpace(state) &&
|
||||
!string.Equals(state, IdleState, StringComparison.OrdinalIgnoreCase);
|
||||
var isShoppingIntent = string.Equals(semanticIntent, "shopping_list", StringComparison.OrdinalIgnoreCase);
|
||||
@@ -58,17 +67,19 @@ internal static class HouseholdListOrchestrator
|
||||
if (!isActiveState && !isShoppingIntent && !isTodoIntent)
|
||||
return Task.FromResult<JiboInteractionDecision?>(null);
|
||||
|
||||
var resolvedListType = isShoppingIntent ? "shopping" : isTodoIntent ? "todo" : NormalizeListType(listType);
|
||||
if (string.IsNullOrWhiteSpace(resolvedListType)) resolvedListType = "shopping";
|
||||
var resolvedListType = isShoppingIntent ? ShoppingListType : isTodoIntent ? TodoListType : NormalizeListType(listType);
|
||||
if (string.IsNullOrWhiteSpace(resolvedListType)) resolvedListType = ShoppingListType;
|
||||
var resolvedDisplayType = ResolveDisplayType(resolvedListType, displayType, isActiveState, loweredTranscript);
|
||||
|
||||
var tenantScope = tenantScopeResolver(turn);
|
||||
|
||||
if (ContainsAny(loweredTranscript, "cancel", "stop", "never mind", "nevermind", "forget it"))
|
||||
return Task.FromResult<JiboInteractionDecision?>(BuildCancelledDecision(resolvedListType));
|
||||
return Task.FromResult<JiboInteractionDecision?>(BuildCancelledDecision(resolvedListType, resolvedDisplayType));
|
||||
|
||||
if (IsRecallRequest(loweredTranscript))
|
||||
return Task.FromResult<JiboInteractionDecision?>(BuildRecallDecision(
|
||||
resolvedListType,
|
||||
resolvedDisplayType,
|
||||
personalMemoryStore.GetListItems(tenantScope, resolvedListType)));
|
||||
|
||||
var directItem = TryExtractListItem(loweredTranscript);
|
||||
@@ -76,9 +87,9 @@ internal static class HouseholdListOrchestrator
|
||||
{
|
||||
if (IsConversationComplete(loweredTranscript))
|
||||
return Task.FromResult<JiboInteractionDecision?>(new JiboInteractionDecision(
|
||||
resolvedListType == "shopping" ? "shopping_list_done" : "todo_list_done",
|
||||
BuildDoneReply(resolvedListType, personalMemoryStore.GetListItems(tenantScope, resolvedListType)),
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, IdleState)));
|
||||
BuildListIntentName(resolvedListType, "done"),
|
||||
BuildDoneReply(resolvedDisplayType, personalMemoryStore.GetListItems(tenantScope, resolvedListType)),
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, resolvedDisplayType, IdleState)));
|
||||
|
||||
directItem = NormalizeItem(transcript);
|
||||
}
|
||||
@@ -87,104 +98,108 @@ internal static class HouseholdListOrchestrator
|
||||
{
|
||||
personalMemoryStore.AddListItem(tenantScope, resolvedListType, directItem);
|
||||
return Task.FromResult<JiboInteractionDecision?>(new JiboInteractionDecision(
|
||||
resolvedListType == "shopping" ? "shopping_list_add" : "todo_list_add",
|
||||
BuildAddedReply(resolvedListType, directItem,
|
||||
BuildListIntentName(resolvedListType, "add"),
|
||||
BuildAddedReply(resolvedDisplayType, directItem,
|
||||
personalMemoryStore.GetListItems(tenantScope, resolvedListType)),
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, AwaitingItemState)));
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, resolvedDisplayType, AwaitingItemState)));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(transcript))
|
||||
return Task.FromResult<JiboInteractionDecision?>(new JiboInteractionDecision(
|
||||
resolvedListType == "shopping" ? "shopping_list_prompt" : "todo_list_prompt",
|
||||
BuildPromptReply(resolvedListType),
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, AwaitingItemState)));
|
||||
BuildListIntentName(resolvedListType, "prompt"),
|
||||
BuildPromptReply(resolvedDisplayType),
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, resolvedDisplayType, AwaitingItemState)));
|
||||
|
||||
return Task.FromResult<JiboInteractionDecision?>(new JiboInteractionDecision(
|
||||
resolvedListType == "shopping" ? "shopping_list_prompt" : "todo_list_prompt",
|
||||
BuildPromptReply(resolvedListType),
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, AwaitingItemState)));
|
||||
BuildListIntentName(resolvedListType, "prompt"),
|
||||
BuildPromptReply(resolvedDisplayType),
|
||||
ContextUpdates: BuildContextUpdates(resolvedListType, resolvedDisplayType, AwaitingItemState)));
|
||||
}
|
||||
|
||||
private static IDictionary<string, object?> BuildContextUpdates(string listType, string state)
|
||||
private static IDictionary<string, object?> BuildContextUpdates(string listType, string displayType, string state)
|
||||
{
|
||||
return new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[StateMetadataKey] = state,
|
||||
[TypeMetadataKey] = listType,
|
||||
[DisplayTypeMetadataKey] = displayType,
|
||||
[NoMatchCountMetadataKey] = 0,
|
||||
[NoInputCountMetadataKey] = 0
|
||||
};
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildCancelledDecision(string listType)
|
||||
private static JiboInteractionDecision BuildCancelledDecision(string listType, string displayType)
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
listType == "shopping" ? "shopping_list_cancel" : "todo_list_cancel",
|
||||
listType == "shopping" ? "Okay. I stopped the shopping list." : "Okay. I stopped the to-do list.",
|
||||
BuildListIntentName(listType, "cancel"),
|
||||
$"Okay. I stopped the {BuildListLabel(displayType)}.",
|
||||
ContextUpdates: new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[StateMetadataKey] = IdleState,
|
||||
[TypeMetadataKey] = listType,
|
||||
[DisplayTypeMetadataKey] = displayType,
|
||||
[NoMatchCountMetadataKey] = 0,
|
||||
[NoInputCountMetadataKey] = 0
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildRecallDecision(string listType, IReadOnlyList<string> items)
|
||||
private static JiboInteractionDecision BuildRecallDecision(string listType, string displayType, IReadOnlyList<string> items)
|
||||
{
|
||||
if (items.Count == 0)
|
||||
return new JiboInteractionDecision(
|
||||
listType == "shopping" ? "shopping_list_recall" : "todo_list_recall",
|
||||
listType == "shopping"
|
||||
? "Your shopping list is empty."
|
||||
: "Your to-do list is empty.",
|
||||
BuildListIntentName(listType, "recall"),
|
||||
$"Your {BuildListLabel(displayType)} is empty.",
|
||||
ContextUpdates: new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[StateMetadataKey] = IdleState,
|
||||
[TypeMetadataKey] = listType,
|
||||
[DisplayTypeMetadataKey] = displayType,
|
||||
[NoMatchCountMetadataKey] = 0,
|
||||
[NoInputCountMetadataKey] = 0
|
||||
});
|
||||
|
||||
return new JiboInteractionDecision(
|
||||
listType == "shopping" ? "shopping_list_recall" : "todo_list_recall",
|
||||
listType == "shopping"
|
||||
? $"Your shopping list has {JoinList(items)}."
|
||||
: $"Your to-do list has {JoinList(items)}.",
|
||||
BuildListIntentName(listType, "recall"),
|
||||
$"Your {BuildListLabel(displayType)} has {JoinList(items)}.",
|
||||
ContextUpdates: new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[StateMetadataKey] = IdleState,
|
||||
[TypeMetadataKey] = listType,
|
||||
[DisplayTypeMetadataKey] = displayType,
|
||||
[NoMatchCountMetadataKey] = 0,
|
||||
[NoInputCountMetadataKey] = 0
|
||||
});
|
||||
}
|
||||
|
||||
private static string BuildAddedReply(string listType, string addedItem, IReadOnlyList<string> items)
|
||||
private static string BuildAddedReply(string displayType, string addedItem, IReadOnlyList<string> items)
|
||||
{
|
||||
var itemLabel = listType == "shopping" ? "shopping list" : "to-do list";
|
||||
var itemLabel = BuildListLabel(displayType);
|
||||
return items.Count == 1
|
||||
? $"Added {addedItem} to your {itemLabel}. What else should I add?"
|
||||
: $"Added {addedItem} to your {itemLabel}. You now have {JoinList(items)}.";
|
||||
}
|
||||
|
||||
private static string BuildPromptReply(string listType)
|
||||
private static string BuildPromptReply(string displayType)
|
||||
{
|
||||
return listType == "shopping"
|
||||
? "What should I add to your shopping list?"
|
||||
: "What should I add to your to-do list?";
|
||||
return $"What should I add to your {BuildListLabel(displayType)}?";
|
||||
}
|
||||
|
||||
private static string BuildDoneReply(string listType, IReadOnlyList<string> items)
|
||||
private static string BuildDoneReply(string displayType, IReadOnlyList<string> items)
|
||||
{
|
||||
if (items.Count == 0)
|
||||
return listType == "shopping"
|
||||
? "Okay. Your shopping list is empty."
|
||||
: "Okay. Your to-do list is empty.";
|
||||
return $"Okay. Your {BuildListLabel(displayType)} is empty.";
|
||||
|
||||
return listType == "shopping"
|
||||
? $"Okay. Your shopping list has {JoinList(items)}."
|
||||
: $"Okay. Your to-do list has {JoinList(items)}.";
|
||||
return $"Okay. Your {BuildListLabel(displayType)} has {JoinList(items)}.";
|
||||
}
|
||||
|
||||
private static string BuildListLabel(string displayType)
|
||||
{
|
||||
return NormalizeDisplayType(displayType) switch
|
||||
{
|
||||
GroceryListType => "grocery list",
|
||||
TodoListType => "to-do list",
|
||||
_ => "shopping list"
|
||||
};
|
||||
}
|
||||
|
||||
private static string JoinList(IReadOnlyList<string> items)
|
||||
@@ -205,7 +220,13 @@ internal static class HouseholdListOrchestrator
|
||||
if (!loweredTranscript.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
var remainder = loweredTranscript[prefix.Length..].Trim();
|
||||
if (IsListOnlyRemainder(remainder))
|
||||
return null;
|
||||
|
||||
remainder = TrimTrailingListPhrases(remainder);
|
||||
if (IsListOnlyRemainder(remainder))
|
||||
return null;
|
||||
|
||||
return NormalizeItem(remainder);
|
||||
}
|
||||
|
||||
@@ -218,6 +239,9 @@ internal static class HouseholdListOrchestrator
|
||||
"what is on my shopping list",
|
||||
"what's on my shopping list",
|
||||
"show my shopping list",
|
||||
"what is on my grocery list",
|
||||
"what's on my grocery list",
|
||||
"show my grocery list",
|
||||
"what is on my to do list",
|
||||
"what's on my to do list",
|
||||
"show my to do list",
|
||||
@@ -246,13 +270,96 @@ internal static class HouseholdListOrchestrator
|
||||
var normalized = NormalizeItem(listType ?? string.Empty).ToLowerInvariant();
|
||||
return normalized.Contains("todo", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalized.Contains("to do", StringComparison.OrdinalIgnoreCase)
|
||||
? "todo"
|
||||
? TodoListType
|
||||
: normalized.Contains("shopping", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalized.Contains("grocery", StringComparison.OrdinalIgnoreCase)
|
||||
? "shopping"
|
||||
? ShoppingListType
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static string ResolveDisplayType(string listType, string? storedDisplayType, bool isActiveState, string loweredTranscript)
|
||||
{
|
||||
var transcriptDisplayType = InferDisplayTypeFromTranscript(loweredTranscript);
|
||||
var normalizedStoredDisplayType = NormalizeDisplayType(storedDisplayType);
|
||||
|
||||
if (isActiveState && !string.IsNullOrWhiteSpace(normalizedStoredDisplayType))
|
||||
return normalizedStoredDisplayType;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(transcriptDisplayType))
|
||||
return transcriptDisplayType;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(normalizedStoredDisplayType))
|
||||
return normalizedStoredDisplayType;
|
||||
|
||||
return string.Equals(listType, TodoListType, StringComparison.OrdinalIgnoreCase)
|
||||
? TodoListType
|
||||
: ShoppingListType;
|
||||
}
|
||||
|
||||
private static string InferDisplayTypeFromTranscript(string loweredTranscript)
|
||||
{
|
||||
if (loweredTranscript.Contains("grocery", StringComparison.OrdinalIgnoreCase))
|
||||
return GroceryListType;
|
||||
|
||||
if (loweredTranscript.Contains("to do", StringComparison.OrdinalIgnoreCase) ||
|
||||
loweredTranscript.Contains("todo", StringComparison.OrdinalIgnoreCase) ||
|
||||
loweredTranscript.Contains("task", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return TodoListType;
|
||||
}
|
||||
|
||||
if (loweredTranscript.Contains("shopping", StringComparison.OrdinalIgnoreCase))
|
||||
return ShoppingListType;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string NormalizeDisplayType(string? displayType)
|
||||
{
|
||||
var normalized = NormalizeItem(displayType ?? string.Empty).ToLowerInvariant();
|
||||
return normalized.Contains("grocery", StringComparison.OrdinalIgnoreCase)
|
||||
? GroceryListType
|
||||
: normalized.Contains("todo", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalized.Contains("to do", StringComparison.OrdinalIgnoreCase)
|
||||
? TodoListType
|
||||
: normalized.Contains("shopping", StringComparison.OrdinalIgnoreCase)
|
||||
? ShoppingListType
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static string BuildListIntentName(string listType, string action)
|
||||
{
|
||||
var normalizedListType = string.Equals(listType, TodoListType, StringComparison.OrdinalIgnoreCase)
|
||||
? TodoListType
|
||||
: ShoppingListType;
|
||||
return $"{normalizedListType}_list_{action}";
|
||||
}
|
||||
|
||||
private static bool IsListOnlyRemainder(string value)
|
||||
{
|
||||
var normalized = NormalizeItem(value).ToLowerInvariant();
|
||||
return normalized is "shopping list" or
|
||||
"grocery list" or
|
||||
"to do list" or
|
||||
"todo list" or
|
||||
"my shopping list" or
|
||||
"my grocery list" or
|
||||
"my to do list" or
|
||||
"my todo list" or
|
||||
"to my shopping list" or
|
||||
"to my grocery list" or
|
||||
"to my to do list" or
|
||||
"to my todo list" or
|
||||
"to the shopping list" or
|
||||
"to the grocery list" or
|
||||
"to the to do list" or
|
||||
"to the todo list" or
|
||||
"on my shopping list" or
|
||||
"on my grocery list" or
|
||||
"on my to do list" or
|
||||
"on my todo list";
|
||||
}
|
||||
|
||||
private static bool ContainsAny(string loweredTranscript, params string[] phrases)
|
||||
{
|
||||
return phrases.Any(phrase => loweredTranscript.Contains(phrase, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -274,4 +381,4 @@ internal static class HouseholdListOrchestrator
|
||||
{
|
||||
return turn.Attributes.TryGetValue(key, out var value) ? value?.ToString() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,13 +780,19 @@ public sealed partial class JiboInteractionService
|
||||
loweredTranscript,
|
||||
"shopping list",
|
||||
"grocery list",
|
||||
"my grocery list",
|
||||
"create grocery list",
|
||||
"start grocery list",
|
||||
"to do list",
|
||||
"todo list",
|
||||
"add to my shopping list",
|
||||
"add to my grocery list",
|
||||
"add to my to do list",
|
||||
"add to my todo list",
|
||||
"what's on my shopping list",
|
||||
"what is on my shopping list",
|
||||
"what's on my grocery list",
|
||||
"what is on my grocery list",
|
||||
"what's on my to do list",
|
||||
"what is on my to do list",
|
||||
"what are my tasks",
|
||||
|
||||
Reference in New Issue
Block a user