From 9223ae3750f70fecd4b8a0265de2f679a99ed54b Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Sat, 18 Jul 2026 13:46:16 +0530 Subject: [PATCH] refactor(gui): Pull out Interact and Manage simulation tabs into their own components --- apps/gui/src/components/play/InteractView.tsx | 218 +++++++++++++ apps/gui/src/components/play/ManageView.tsx | 90 ++++++ apps/gui/src/components/play/PlayView.tsx | 289 +----------------- 3 files changed, 323 insertions(+), 274 deletions(-) create mode 100644 apps/gui/src/components/play/InteractView.tsx create mode 100644 apps/gui/src/components/play/ManageView.tsx diff --git a/apps/gui/src/components/play/InteractView.tsx b/apps/gui/src/components/play/InteractView.tsx new file mode 100644 index 0000000..92ddb04 --- /dev/null +++ b/apps/gui/src/components/play/InteractView.tsx @@ -0,0 +1,218 @@ +"use client"; + +import * as React from "react"; +import { useRouter } from "next/navigation"; +import type { SimSnapshot } from "@/lib/simulation-types"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Spinner } from "@/components/ui/spinner"; +import { cn } from "@/lib/utils"; + +function IntentTag({ + intent, + isSelf, +}: { + intent: SimSnapshot["log"][number]["intents"][number]; + isSelf?: boolean; +}) { + const labels: Record = { + monologue: "thought", + dialogue: "dialogue", + action: "action", + }; + + const label = labels[intent.type] || intent.type; + + let outcome = ""; + if (intent.type === "action") { + outcome = intent.isValid ? " ✅" : ` ❌ (${intent.reason})`; + } + + const textToDisplay = + isSelf && intent.selfDescription + ? intent.selfDescription + : intent.description; + + const modifiersStr = + intent.modifiers && intent.modifiers.length > 0 ? ( + + ({intent.modifiers.join(", ")}) + + ) : null; + + return ( + + [{label}] “{textToDisplay}”{modifiersStr} + {outcome} + {intent.minutesToAdvance ? ` [+${intent.minutesToAdvance}min]` : ""} + + ); +} + +function formatSimTime(isoString: string) { + try { + const d = new Date(isoString); + if (isNaN(d.getTime())) return isoString; + const yyyy = d.getUTCFullYear(); + const mm = String(d.getUTCMonth() + 1).padStart(2, "0"); + const dd = String(d.getUTCDate()).padStart(2, "0"); + const hh = String(d.getUTCHours()).padStart(2, "0"); + const min = String(d.getUTCMinutes()).padStart(2, "0"); + const ss = String(d.getUTCSeconds()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd} ${hh}:${min}:${ss} UTC`; + } catch { + return isoString; + } +} + +function LogEntryCard({ + entry, + onShowPrompt, + isPlayerCard, +}: { + entry: SimSnapshot["log"][number]; + onShowPrompt: (entry: SimSnapshot["log"][number]) => void; + isPlayerCard: boolean; +}) { + const showMenu = !!(entry.rawPrompt || entry.decoderPrompt); + + return ( +
+
+
+ + {entry.entityName} + + + Turn {entry.turn} · {formatSimTime(entry.timestamp)} + +
+ {showMenu && ( + + )} +
+
+ {entry.narrativeProse} +
+
+ {entry.intents.map((intent, i) => ( + + ))} +
+
+ ); +} + +interface InteractViewProps { + snapshot: SimSnapshot; + loading: boolean; + statusText: string; + playerInput: string; + setPlayerInput: (value: string) => void; + onSubmitAction: (e: React.FormEvent) => void; + onShowPrompt: (entry: SimSnapshot["log"][number]) => void; + logEndRef: React.RefObject; +} + +export function InteractView({ + snapshot, + loading, + statusText, + playerInput, + setPlayerInput, + onSubmitAction, + onShowPrompt, + logEndRef, +}: InteractViewProps) { + const router = useRouter(); + + const playerEntity = snapshot.entities.find((e) => e.isPlayer); + + return ( + <> + {/* Scrollable Center Viewport */} +
+
+ {snapshot.log.map((entry, i) => ( + + ))} + {loading && ( +
+ + {statusText || "Processing..."} +
+ )} +
+
+
+ + {/* Sticky Chat / Interaction Input Footer */} +