FEAT!(voice): Implement intent hydration, dehydration system fixes: #29

This commit is contained in:
2026-07-19 13:13:07 +05:30
parent 84bff92631
commit a4b620502a
38 changed files with 622 additions and 211 deletions

View File

@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
import { hydrate } from "@omnia/voice";
import {
Alert,
AlertAction,
@@ -17,9 +18,13 @@ import {
function IntentTag({
intent,
isSelf,
playerAliases,
playerId,
}: {
intent: SimSnapshot["log"][number]["intents"][number];
isSelf?: boolean;
playerAliases: Record<string, string>;
playerId: string;
}) {
const labels: Record<string, string> = {
monologue: "thought",
@@ -35,10 +40,13 @@ function IntentTag({
outcome = intent.isValid ? " ✅" : ` ❌ (${intent.reason})`;
}
const textToDisplay =
isSelf && intent.selfDescription
? intent.selfDescription
: intent.description;
const viewerAliasesMap = new Map(Object.entries(playerAliases || {}));
const viewerEntityMock = {
id: playerId || "",
aliases: viewerAliasesMap,
};
const textToDisplay = hydrate(intent.content, viewerEntityMock as any);
const modifiersStr =
intent.modifiers && intent.modifiers.length > 0 ? (
@@ -76,10 +84,14 @@ function LogEntryCard({
entry,
onShowPrompt,
isPlayerCard,
playerAliases,
playerId,
}: {
entry: SimSnapshot["log"][number];
onShowPrompt: (entry: SimSnapshot["log"][number]) => void;
isPlayerCard: boolean;
playerAliases: Record<string, string>;
playerId: string;
}) {
const showMenu = !!(entry.rawPrompt || entry.decoderPrompt);
@@ -117,7 +129,13 @@ function LogEntryCard({
</div>
<div className="flex flex-col gap-1.5 mt-2 border-t border-dotted border-border/10 pt-2">
{entry.intents.map((intent, i) => (
<IntentTag key={i} intent={intent} isSelf={isPlayerCard} />
<IntentTag
key={i}
intent={intent}
isSelf={isPlayerCard}
playerAliases={playerAliases}
playerId={playerId}
/>
))}
</div>
</div>
@@ -184,12 +202,17 @@ export function InteractView({
</Alert>
);
}
const playerAliases = playerEntity?.aliases || {};
const playerId = playerEntity?.id || "";
return (
<LogEntryCard
key={i}
entry={entry}
onShowPrompt={onShowPrompt}
isPlayerCard={entry.entityId === playerEntity?.id}
playerAliases={playerAliases}
playerId={playerId}
/>
);
})}

View File

@@ -1,7 +1,6 @@
export interface IntentInfo {
type: string;
description: string;
selfDescription?: string;
content: string;
modifiers: string[];
targetIds: string[];
isValid?: boolean;
@@ -49,6 +48,7 @@ export interface EntityInfo {
name: string;
isPlayer: boolean;
isAgent: boolean;
aliases?: Record<string, string>;
}
export interface WaitingContext {

View File

@@ -468,6 +468,21 @@ export class SimulationManager {
// ---------------------------------------------------------------------------
private snapshot(session: SimSession): SimSnapshot {
const worldState = session.coreRepo.loadWorldState(session.worldInstanceId);
const hydratedEntities = session.entities.map((e) => {
const actualEntity = worldState?.getEntity(e.id);
const aliases: Record<string, string> = {};
if (actualEntity) {
for (const [targetId, alias] of actualEntity.aliases.entries()) {
aliases[targetId] = alias;
}
}
return {
...e,
aliases,
};
});
return {
id: session.worldInstanceId,
status: session.status,
@@ -475,7 +490,7 @@ export class SimulationManager {
maxTurns: session.maxTurns,
scenarioName: session.scenarioName,
scenarioDescription: session.scenarioDescription,
entities: session.entities,
entities: hydratedEntities,
log: session.log,
entityIndex: session.entityIndex,
waitingEntity: session.waitingEntity,

View File

@@ -58,8 +58,7 @@ async function processIntents(
intentInfos.push({
type: intent.type,
description: intent.description,
selfDescription: intent.selfDescription,
content: intent.content,
modifiers: intent.modifiers || [],
targetIds: intent.targetIds,
isValid: outcome.isValid,