From 1e34becec7e079c45025ba5f4e8c0adf2fa416a4 Mon Sep 17 00:00:00 2001
From: rhit-lid2 <59123825+NeoLi00@users.noreply.github.com>
Date: Sun, 19 Jul 2026 16:18:15 +0530
Subject: [PATCH] refactor(gui): Unify prompt analysis components for actor,
intent decoder and Handoff
---
apps/gui/src/components/play/HandoffModal.tsx | 54 +-
.../src/components/play/PromptAnalyzer.tsx | 172 +++++++
apps/gui/src/components/play/PromptModal.tsx | 473 +++---------------
apps/gui/src/lib/simulation-types.ts | 27 +-
apps/gui/src/lib/simulation/alias-handoff.ts | 40 +-
apps/gui/src/lib/simulation/turn-executor.ts | 21 +
packages/actor/src/actor-prompt-builder.ts | 51 +-
packages/actor/src/actor.ts | 11 +-
packages/voice/src/index.ts | 12 +
9 files changed, 381 insertions(+), 480 deletions(-)
create mode 100644 apps/gui/src/components/play/PromptAnalyzer.tsx
diff --git a/apps/gui/src/components/play/HandoffModal.tsx b/apps/gui/src/components/play/HandoffModal.tsx
index 4daa079..c8186a4 100644
--- a/apps/gui/src/components/play/HandoffModal.tsx
+++ b/apps/gui/src/components/play/HandoffModal.tsx
@@ -9,6 +9,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Badge } from "@/components/ui/badge";
+import { PromptAnalyzer } from "@/components/play/PromptAnalyzer";
interface HandoffModalProps {
entry: SimSnapshot["log"][number];
@@ -191,25 +192,40 @@ export function HandoffModal({ entry, onClose }: HandoffModalProps) {
)}
{activeTab === "prompt" && entry.rawPrompt && (
-
-
-
- System Prompt
-
-
- {entry.rawPrompt.systemPrompt}
-
-
-
-
-
- User Context (Candidates)
-
-
- {entry.rawPrompt.userContext}
-
-
-
+ 0
+ ? entry.rawPrompt.components
+ : [
+ {
+ label: "System Prompt",
+ type: "system",
+ content: entry.rawPrompt.systemPrompt || "",
+ },
+ {
+ label: "User Context",
+ type: "world",
+ content: entry.rawPrompt.userContext || "",
+ },
+ ]
+ }
+ inputTokens={entry.usage?.inputTokens || 0}
+ maxContext={
+ entry.usage?.maxContext !== undefined
+ ? entry.usage.maxContext
+ : 32768
+ }
+ modelName={entry.usage?.modelName}
+ providerInstanceName={entry.usage?.providerInstanceName}
+ outputLabel="LLM Output (Promoted Memory Chunks)"
+ outputText={
+ handoffResult
+ ? JSON.stringify(handoffResult, null, 2)
+ : undefined
+ }
+ outputTokens={entry.usage?.outputTokens}
+ />
)}
{activeTab === "output" && (
diff --git a/apps/gui/src/components/play/PromptAnalyzer.tsx b/apps/gui/src/components/play/PromptAnalyzer.tsx
new file mode 100644
index 0000000..8a064a7
--- /dev/null
+++ b/apps/gui/src/components/play/PromptAnalyzer.tsx
@@ -0,0 +1,172 @@
+"use client";
+
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/components/ui/accordion";
+import type { PromptComponent } from "@/lib/simulation-types";
+
+interface PromptAnalyzerProps {
+ components: PromptComponent[];
+ inputTokens: number;
+ maxContext?: number;
+ modelName?: string;
+ providerInstanceName?: string;
+ outputLabel?: string;
+ outputText?: string;
+ outputTokens?: number;
+}
+
+export function PromptAnalyzer({
+ components,
+ inputTokens,
+ maxContext = 32768,
+ modelName,
+ providerInstanceName,
+ outputLabel = "LLM Output",
+ outputText,
+ outputTokens,
+}: PromptAnalyzerProps) {
+ const totalLen = components.reduce((sum, s) => sum + s.content.length, 0);
+
+ if (totalLen === 0) {
+ return (
+
+ No prompt context recorded.
+
+ );
+ }
+
+ const sections = components.map((s) => {
+ const pct = totalLen > 0 ? (s.content.length / totalLen) * 100 : 0;
+ return {
+ ...s,
+ pct,
+ tokens: Math.round((s.content.length / totalLen) * inputTokens),
+ };
+ });
+
+ const usagePctOfContext =
+ maxContext > 0 ? (inputTokens / maxContext) * 100 : 0;
+ const isAbsolute = maxContext > 0 && usagePctOfContext >= 20;
+
+ const getColorClass = (type: string) => {
+ switch (type) {
+ case "system":
+ return "bg-blue-500";
+ case "world":
+ return "bg-emerald-500";
+ case "events":
+ return "bg-purple-500";
+ case "memories":
+ return "bg-pink-500";
+ case "input":
+ return "bg-amber-500";
+ default:
+ return "bg-slate-500";
+ }
+ };
+
+ return (
+
+ {/* Provider Details */}
+ {(providerInstanceName || modelName) && (
+
+ LLM Instance:{" "}
+ {providerInstanceName || "Default"}
+ {modelName && ({modelName})}
+
+ )}
+
+ {/* Progress Bar & Breakdown */}
+
+
+ Input Prompt Breakdown
+
+ Total Input Tokens: {inputTokens}
+ {maxContext > 0 ? (
+
+ {" "}
+ / {maxContext} ({usagePctOfContext.toFixed(1)}% used)
+
+ ) : (
+ (infinite context)
+ )}
+
+
+
+ {/* Token Bar */}
+
+ {sections.map((item, idx) => {
+ const widthPct = isAbsolute
+ ? item.pct * (inputTokens / maxContext)
+ : item.pct;
+ return (
+
+ );
+ })}
+ {isAbsolute && (
+
+ )}
+
+
+ {/* Accordion Components */}
+
+ {sections.map((item, idx) => {
+ return (
+
+
+
+
+ {item.label}:
+
+ {item.tokens} tokens (
+ {item.pct.toFixed(0)}%)
+
+
+
+
+
+ {item.content}
+
+
+
+ );
+ })}
+
+
+
+ {/* Output Section */}
+ {outputText && (
+
+
+ {outputLabel}
+ {outputTokens !== undefined && (
+
+ Total Output Tokens: {outputTokens}
+
+ )}
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/gui/src/components/play/PromptModal.tsx b/apps/gui/src/components/play/PromptModal.tsx
index 3d2c1fe..1d91392 100644
--- a/apps/gui/src/components/play/PromptModal.tsx
+++ b/apps/gui/src/components/play/PromptModal.tsx
@@ -9,13 +9,8 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
-import {
- Accordion,
- AccordionItem,
- AccordionTrigger,
- AccordionContent,
-} from "@/components/ui/accordion";
import { PromptSwitcher } from "@/components/play/PromptSwitcher";
+import { PromptAnalyzer } from "@/components/play/PromptAnalyzer";
interface PromptModalProps {
entry: SimSnapshot["log"][number];
@@ -25,191 +20,39 @@ interface PromptModalProps {
export function PromptModal({ entry, onClose }: PromptModalProps) {
const [activeTab, setActiveTab] = useState<"actor" | "decoder">("actor");
- const parseActorPrompt = (
- systemPrompt: string,
- userContext: string,
- inputTokens: number,
- sectionsObj?: {
- worldInfo: string;
- memoryLedger: string;
- cognitiveBuffer: string;
- },
- ) => {
- let worldStr = "";
- let recentStr = "";
- let ledgerStr = "";
-
- if (sectionsObj) {
- worldStr = sectionsObj.worldInfo;
- recentStr = sectionsObj.cognitiveBuffer;
- ledgerStr = sectionsObj.memoryLedger;
- } else {
- const recentHeader = "=== COGNITIVE BUFFER ===";
- const ledgerHeader = "=== MEMORY LEDGER ===";
-
- const recentIdx = userContext.indexOf(recentHeader);
- worldStr = userContext;
-
- if (recentIdx !== -1) {
- worldStr = userContext.substring(0, recentIdx).trim();
- const rest = userContext.substring(recentIdx).trim();
- const ledgerIdx = rest.indexOf(ledgerHeader);
- if (ledgerIdx !== -1) {
- recentStr = rest.substring(0, ledgerIdx).trim();
- ledgerStr = rest.substring(ledgerIdx).trim();
- } else {
- recentStr = rest;
- }
- }
- }
-
- const sections: { label: string; type: string; content: string }[] = [
- { label: "System Prompt", type: "system", content: systemPrompt },
- { label: "World Info", type: "world", content: worldStr },
- {
- label: "Cognitive Buffer",
- type: "events",
- content: recentStr || "(No cognitive buffer entries.)",
- },
- {
- label: "Memory Ledger",
- type: "memories",
- content: ledgerStr || "(No memory ledger entries.)",
- },
- ];
-
- const totalLen = sections.reduce((sum, s) => sum + s.content.length, 0);
- if (totalLen === 0) return null;
-
- return sections.map((s) => {
- const pct = (s.content.length / totalLen) * 100;
- return {
- ...s,
- pct,
- relativePct: pct,
- tokens: Math.round((s.content.length / totalLen) * inputTokens),
- };
- });
- };
-
- const parseDecoderPrompt = (
- systemPrompt: string,
- userContext: string,
- inputTokens: number,
- ) => {
- const proseHeader = "=== NARRATIVE PROSE ===";
- const idx = userContext.indexOf(proseHeader);
-
- let worldStr = userContext;
- let proseStr = "";
-
- if (idx !== -1) {
- worldStr = userContext.substring(0, idx).trim();
- proseStr = userContext.substring(idx).trim();
- }
-
- const sysLen = systemPrompt.length;
- const worldLen = worldStr.length;
- const proseLen = proseStr.length;
- const totalLen = sysLen + worldLen + proseLen;
-
- if (totalLen === 0) return null;
-
- const sysPct = (sysLen / totalLen) * 100;
- const worldPct = (worldLen / totalLen) * 100;
- const prosePct = (proseLen / totalLen) * 100;
-
- const sysTokens = Math.round((sysLen / totalLen) * inputTokens);
- const worldTokens = Math.round((worldLen / totalLen) * inputTokens);
- const proseTokens = Math.max(0, inputTokens - sysTokens - worldTokens);
-
- return [
- {
- label: "System Prompt",
- pct: sysPct,
- relativePct: sysPct,
- tokens: sysTokens,
- type: "system",
- content: systemPrompt,
- },
- {
- label: "Decoder Context",
- pct: worldPct,
- relativePct: worldPct,
- tokens: worldTokens,
- type: "world",
- content: worldStr,
- },
- {
- label: "Narrative Prose",
- pct: prosePct,
- relativePct: prosePct,
- tokens: proseTokens,
- type: "memories",
- content: proseStr,
- },
- ];
- };
-
- const actorBreakdown =
- entry.rawPrompt && entry.usage
- ? parseActorPrompt(
- entry.rawPrompt.systemPrompt,
- entry.rawPrompt.userContext,
- entry.usage.inputTokens,
- entry.rawPrompt.sections,
- )
- : null;
- const decoderBreakdown =
- entry.decoderPrompt && entry.decoderUsage
- ? parseDecoderPrompt(
- entry.decoderPrompt.systemPrompt,
- entry.decoderPrompt.userContext,
- entry.decoderUsage.inputTokens,
- )
- : null;
-
- const actorMaxContext =
- entry.usage?.maxContext !== undefined ? entry.usage.maxContext : 32768;
- const actorUsedTokens = entry.usage?.inputTokens || 0;
- const actorUsagePctOfContext =
- actorMaxContext > 0 ? (actorUsedTokens / actorMaxContext) * 100 : 0;
- const isActorAbsolute = actorMaxContext > 0 && actorUsagePctOfContext >= 20;
-
- const scaledActorBreakdown = actorBreakdown
- ? actorBreakdown.map((item) => ({
- ...item,
- pct: isActorAbsolute
- ? item.relativePct * (actorUsedTokens / actorMaxContext)
- : item.relativePct,
- }))
- : null;
-
- const decoderMaxContext =
- entry.decoderUsage?.maxContext !== undefined
- ? entry.decoderUsage.maxContext
- : 32768;
- const decoderUsedTokens = entry.decoderUsage?.inputTokens || 0;
- const decoderUsagePctOfContext =
- decoderMaxContext > 0 ? (decoderUsedTokens / decoderMaxContext) * 100 : 0;
- const isDecoderAbsolute =
- decoderMaxContext > 0 && decoderUsagePctOfContext >= 20;
-
- const scaledDecoderBreakdown = decoderBreakdown
- ? decoderBreakdown.map((item) => ({
- ...item,
- pct: isDecoderAbsolute
- ? item.relativePct * (decoderUsedTokens / decoderMaxContext)
- : item.relativePct,
- }))
- : null;
-
useEffect(() => {
if (!entry.rawPrompt && entry.decoderPrompt) {
setActiveTab("decoder");
}
}, [entry]);
+ // Helper to resolve components with a fallback if none exist (for backwards-compatibility)
+ const getComponents = (
+ promptBreakdown: any,
+ defaultType: "world" | "input",
+ ) => {
+ if (!promptBreakdown) return [];
+ if (promptBreakdown.components && promptBreakdown.components.length > 0) {
+ return promptBreakdown.components;
+ }
+ // Fallback: convert flat strings into components list
+ return [
+ {
+ label: "System Prompt",
+ type: "system" as const,
+ content: promptBreakdown.systemPrompt || "",
+ },
+ {
+ label: "User Context",
+ type: defaultType,
+ content: promptBreakdown.userContext || "",
+ },
+ ];
+ };
+
+ const actorComponents = getComponents(entry.rawPrompt, "world");
+ const decoderComponents = getComponents(entry.decoderPrompt, "input");
+
return (