mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 20:12:48 +05:30
feat(gui): Improved responsivity
This commit is contained in:
@@ -52,6 +52,15 @@
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
--color-surface: var(--surface);
|
||||
--color-surface-dim: var(--surface-dim);
|
||||
--color-surface-bright: var(--surface-bright);
|
||||
@@ -85,6 +94,15 @@
|
||||
--input: #fff8f5;
|
||||
--ring: #e58e58;
|
||||
|
||||
--sidebar: #fcf3e8;
|
||||
--sidebar-foreground: #201a16;
|
||||
--sidebar-primary: #e58e58;
|
||||
--sidebar-primary-foreground: #201a16;
|
||||
--sidebar-accent: #ffd966;
|
||||
--sidebar-accent-foreground: #201a16;
|
||||
--sidebar-border: #5c544e;
|
||||
--sidebar-ring: #e58e58;
|
||||
|
||||
--surface: #fff8f5;
|
||||
--surface-dim: #e4d8d0;
|
||||
--surface-bright: #fff8f5;
|
||||
@@ -117,6 +135,15 @@
|
||||
--input: #362f2a;
|
||||
--ring: #e58e58;
|
||||
|
||||
--sidebar: #201a16;
|
||||
--sidebar-foreground: #fff8f5;
|
||||
--sidebar-primary: #e58e58;
|
||||
--sidebar-primary-foreground: #201a16;
|
||||
--sidebar-accent: #c1a032;
|
||||
--sidebar-accent-foreground: #201a16;
|
||||
--sidebar-border: #877369;
|
||||
--sidebar-ring: #e58e58;
|
||||
|
||||
--surface: #201a16;
|
||||
--surface-dim: #362f2a;
|
||||
--surface-bright: #201a16;
|
||||
|
||||
@@ -13,6 +13,14 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { PromptModal } from "./PromptModal";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import {
|
||||
SidebarProvider,
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarTrigger,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
function IntentTag({
|
||||
intent,
|
||||
@@ -34,19 +42,22 @@ function IntentTag({
|
||||
outcome = intent.isValid ? " ✅" : ` ❌ (${intent.reason})`;
|
||||
}
|
||||
|
||||
const textToDisplay = (isSelf && intent.selfDescription)
|
||||
? intent.selfDescription
|
||||
: intent.description;
|
||||
const textToDisplay =
|
||||
isSelf && intent.selfDescription
|
||||
? intent.selfDescription
|
||||
: intent.description;
|
||||
|
||||
const modifiersStr = intent.modifiers && intent.modifiers.length > 0 ? (
|
||||
<span className="italic opacity-80 text-muted-foreground ml-1">
|
||||
({intent.modifiers.join(", ")})
|
||||
</span>
|
||||
) : null;
|
||||
const modifiersStr =
|
||||
intent.modifiers && intent.modifiers.length > 0 ? (
|
||||
<span className="italic opacity-80 text-muted-foreground ml-1">
|
||||
({intent.modifiers.join(", ")})
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
[{label}] “{textToDisplay}”{modifiersStr}{outcome}
|
||||
[{label}] “{textToDisplay}”{modifiersStr}
|
||||
{outcome}
|
||||
{intent.minutesToAdvance ? ` [+${intent.minutesToAdvance}min]` : ""}
|
||||
</span>
|
||||
);
|
||||
@@ -80,18 +91,21 @@ function LogEntryCard({
|
||||
const showMenu = !!(entry.rawPrompt || entry.decoderPrompt);
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
"border p-4 shadow-[2px_2px_0_0_var(--border)]",
|
||||
isPlayerCard
|
||||
? "border-primary bg-surface-container-low"
|
||||
: "border-border/30 bg-card"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
"border p-4 shadow-[2px_2px_0_0_var(--border)]",
|
||||
isPlayerCard
|
||||
? "border-primary bg-surface-container-low"
|
||||
: "border-border/30 bg-card",
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 border-b border-dotted border-border/20 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<strong className="text-body-md font-bold text-foreground">{entry.entityName}</strong>
|
||||
<strong className="text-body-md font-bold text-foreground">
|
||||
{entry.entityName}
|
||||
</strong>
|
||||
<span className="text-xs text-muted-foreground font-mono">
|
||||
Turn {entry.turn} ·{" "}
|
||||
{formatSimTime(entry.timestamp)}
|
||||
Turn {entry.turn} · {formatSimTime(entry.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
{showMenu && (
|
||||
@@ -105,7 +119,9 @@ function LogEntryCard({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-body-md leading-relaxed mb-3 text-foreground/90 whitespace-pre-wrap">{entry.narrativeProse}</div>
|
||||
<div className="text-body-md leading-relaxed mb-3 text-foreground/90 whitespace-pre-wrap">
|
||||
{entry.narrativeProse}
|
||||
</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} />
|
||||
@@ -114,6 +130,25 @@ function LogEntryCard({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function MobileSidebarClose() {
|
||||
const { isMobile, setOpenMobile } = useSidebar();
|
||||
if (!isMobile) return null;
|
||||
return (
|
||||
<div className="flex justify-between items-center px-6 py-4 border-b border-border/10">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground font-mono">
|
||||
Menu
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setOpenMobile(false)}
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlayView() {
|
||||
const router = useRouter();
|
||||
@@ -125,7 +160,9 @@ export function PlayView() {
|
||||
const [playerInput, setPlayerInput] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [statusText, setStatusText] = useState("");
|
||||
const [selectedEntryForModal, setSelectedEntryForModal] = useState<SimSnapshot["log"][number] | null>(null);
|
||||
const [selectedEntryForModal, setSelectedEntryForModal] = useState<
|
||||
SimSnapshot["log"][number] | null
|
||||
>(null);
|
||||
|
||||
const logEndRef = useRef<HTMLDivElement>(null);
|
||||
const steppingRef = useRef(false);
|
||||
@@ -147,79 +184,79 @@ export function PlayView() {
|
||||
scrollToBottom();
|
||||
}, [snapshot, scrollToBottom]);
|
||||
|
||||
const runSteps = useCallback(
|
||||
const runSteps = useCallback(async (id: string) => {
|
||||
if (steppingRef.current) return;
|
||||
steppingRef.current = true;
|
||||
setLoading(true);
|
||||
setError("");
|
||||
pauseRequestedRef.current = false;
|
||||
|
||||
try {
|
||||
let current = snapshotRef.current;
|
||||
while (true) {
|
||||
if (pauseRequestedRef.current) {
|
||||
break;
|
||||
}
|
||||
const result = await stepSimulation({ simId: id });
|
||||
if (!result.ok) {
|
||||
setError(result.error);
|
||||
break;
|
||||
}
|
||||
current = result.snapshot;
|
||||
setSnapshot(current);
|
||||
|
||||
if (
|
||||
current.status === "waiting_player" ||
|
||||
current.status === "done" ||
|
||||
current.status === "error"
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
const entityName =
|
||||
current.entities[current.entityIndex ?? 0]?.name || "";
|
||||
setStatusText(
|
||||
`Turn ${current.turn} — processing ${entityName || "next step"}...`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed during simulation step.",
|
||||
);
|
||||
} finally {
|
||||
steppingRef.current = false;
|
||||
setLoading(false);
|
||||
setStatusText("");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleResume = useCallback(
|
||||
async (id: string) => {
|
||||
if (steppingRef.current) return;
|
||||
steppingRef.current = true;
|
||||
setLoading(true);
|
||||
setError("");
|
||||
pauseRequestedRef.current = false;
|
||||
|
||||
try {
|
||||
let current = snapshotRef.current;
|
||||
while (true) {
|
||||
if (pauseRequestedRef.current) {
|
||||
break;
|
||||
}
|
||||
const result = await stepSimulation({ simId: id });
|
||||
if (!result.ok) {
|
||||
setError(result.error);
|
||||
break;
|
||||
}
|
||||
current = result.snapshot;
|
||||
setSnapshot(current);
|
||||
|
||||
if (
|
||||
current.status === "waiting_player" ||
|
||||
current.status === "done" ||
|
||||
current.status === "error"
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
const entityName =
|
||||
current.entities[current.entityIndex ?? 0]?.name || "";
|
||||
setStatusText(
|
||||
`Turn ${current.turn} — processing ${entityName || "next step"}...`,
|
||||
);
|
||||
const res = await resumeSimulation(id);
|
||||
if (!res.ok) {
|
||||
setError(res.error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setSnapshot(res.snapshot);
|
||||
if (res.snapshot.status === "running") {
|
||||
await runSteps(res.snapshot.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "Failed during simulation step.",
|
||||
err instanceof Error ? err.message : "Failed to resume session.",
|
||||
);
|
||||
} finally {
|
||||
steppingRef.current = false;
|
||||
setLoading(false);
|
||||
setStatusText("");
|
||||
}
|
||||
},
|
||||
[],
|
||||
[runSteps],
|
||||
);
|
||||
|
||||
const handleResume = useCallback(async (id: string) => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const res = await resumeSimulation(id);
|
||||
if (!res.ok) {
|
||||
setError(res.error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setSnapshot(res.snapshot);
|
||||
if (res.snapshot.status === "running") {
|
||||
await runSteps(res.snapshot.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to resume session.");
|
||||
setLoading(false);
|
||||
}
|
||||
}, [runSteps]);
|
||||
|
||||
// Load simulation on mount
|
||||
useEffect(() => {
|
||||
if (!simId) {
|
||||
@@ -256,11 +293,7 @@ export function PlayView() {
|
||||
await runSteps(result.snapshot.id);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "Failed to submit action.",
|
||||
);
|
||||
setError(err instanceof Error ? err.message : "Failed to submit action.");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
@@ -286,7 +319,9 @@ export function PlayView() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[400px] gap-3">
|
||||
<Spinner />
|
||||
<span className="text-sm text-muted-foreground font-mono">Initializing simulation...</span>
|
||||
<span className="text-sm text-muted-foreground font-mono">
|
||||
Initializing simulation...
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -305,66 +340,79 @@ export function PlayView() {
|
||||
if (!snapshot) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 min-h-0 overflow-hidden">
|
||||
{/* Sidebar Navigation */}
|
||||
<aside className="w-64 border-r border-border/30 bg-card/40 flex flex-col justify-between shrink-0">
|
||||
<div className="p-6">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground font-mono mb-4">Simulation</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => setActiveTab("interact")}
|
||||
className={cn(
|
||||
"w-full text-left px-4 py-2.5 text-sm font-medium border transition-all duration-100",
|
||||
activeTab === "interact"
|
||||
? "border-primary bg-primary/10 text-primary shadow-[2px_2px_0_0_var(--primary)]"
|
||||
: "border-border/30 hover:bg-secondary text-foreground"
|
||||
)}
|
||||
>
|
||||
💬 Interact
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("manage")}
|
||||
className={cn(
|
||||
"w-full text-left px-4 py-2.5 text-sm font-medium border transition-all duration-100",
|
||||
activeTab === "manage"
|
||||
? "border-primary bg-primary/10 text-primary shadow-[2px_2px_0_0_var(--primary)]"
|
||||
: "border-border/30 hover:bg-secondary text-foreground"
|
||||
)}
|
||||
>
|
||||
⚙️ Manage
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-dotted border-border/20">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full text-xs font-mono"
|
||||
onClick={() => {
|
||||
pauseRequestedRef.current = true;
|
||||
router.push("/");
|
||||
}}
|
||||
>
|
||||
← Back to Home
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 flex flex-col h-full overflow-hidden bg-background">
|
||||
{/* Sticky Header */}
|
||||
<header className="sticky top-0 bg-background/95 backdrop-blur-xs border-b border-dotted border-border/20 px-8 py-5 z-10 flex flex-col gap-2 shrink-0">
|
||||
<div className="flex justify-between items-start gap-4">
|
||||
<div>
|
||||
<h2 className="text-headline-md text-primary font-head tracking-wide">{snapshot.scenarioName}</h2>
|
||||
<p className="text-sm text-muted-foreground/90 mt-1 max-w-[550px]">{snapshot.scenarioDescription}</p>
|
||||
<SidebarProvider>
|
||||
<div className="flex flex-1 min-h-0 overflow-hidden">
|
||||
{/* Sidebar Navigation */}
|
||||
<Sidebar className="border-r border-border/30 bg-card shrink-0">
|
||||
<SidebarContent className="flex flex-col justify-between h-full bg-card">
|
||||
<MobileSidebarClose />
|
||||
<div className="p-6">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground font-mono mb-4">
|
||||
Simulation
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => setActiveTab("interact")}
|
||||
className={cn(
|
||||
"w-full text-left px-4 py-2.5 text-sm font-medium border transition-all duration-100",
|
||||
activeTab === "interact"
|
||||
? "border-primary bg-primary/10 text-primary shadow-[2px_2px_0_0_var(--primary)]"
|
||||
: "border-border/30 hover:bg-secondary text-foreground",
|
||||
)}
|
||||
>
|
||||
Interact
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("manage")}
|
||||
className={cn(
|
||||
"w-full text-left px-4 py-2.5 text-sm font-medium border transition-all duration-100",
|
||||
activeTab === "manage"
|
||||
? "border-primary bg-primary/10 text-primary shadow-[2px_2px_0_0_var(--primary)]"
|
||||
: "border-border/30 hover:bg-secondary text-foreground",
|
||||
)}
|
||||
>
|
||||
Manage
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-dotted border-border/20">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full text-xs font-mono"
|
||||
onClick={() => {
|
||||
pauseRequestedRef.current = true;
|
||||
router.push("/");
|
||||
}}
|
||||
>
|
||||
← Back to Home
|
||||
</Button>
|
||||
</div>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 flex flex-col h-full overflow-hidden bg-background">
|
||||
{/* Sticky Header */}
|
||||
<header className="sticky top-0 bg-background/95 backdrop-blur-xs border-b border-dotted border-border/20 px-8 py-5 z-10 flex flex-col gap-2 shrink-0">
|
||||
<div className="flex justify-between items-start gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<SidebarTrigger className="md:hidden" />
|
||||
<div>
|
||||
<h2 className="text-headline-md text-primary font-head tracking-wide">
|
||||
{snapshot.scenarioName}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground/90 mt-1 max-w-[550px]">
|
||||
{snapshot.scenarioDescription}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Simulation Global Controls */}
|
||||
<div className="flex gap-2 shrink-0">
|
||||
{snapshot.status !== "done" && snapshot.status !== "error" && (
|
||||
<>
|
||||
{snapshot.status === "running" && (
|
||||
loading ? (
|
||||
{snapshot.status === "running" &&
|
||||
(loading ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@@ -382,8 +430,7 @@ export function PlayView() {
|
||||
>
|
||||
Resume
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
))}
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
@@ -399,8 +446,16 @@ export function PlayView() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs font-mono mt-1 pt-1.5 border-t border-border/10">
|
||||
<span className="text-muted-foreground">Status: <span className="text-primary font-bold">{snapshot.status.toUpperCase()}</span></span>
|
||||
<span className="text-muted-foreground">Turn: <span className="text-foreground font-bold">{snapshot.turn}</span></span>
|
||||
<span className="text-muted-foreground">
|
||||
Status:{" "}
|
||||
<span className="text-primary font-bold">
|
||||
{snapshot.status.toUpperCase()}
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
Turn:{" "}
|
||||
<span className="text-foreground font-bold">{snapshot.turn}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs font-medium text-primary mt-1 font-mono">
|
||||
{loading && "⏳ "}
|
||||
@@ -435,42 +490,73 @@ export function PlayView() {
|
||||
<div className="max-w-[800px] mx-auto space-y-6 pb-12">
|
||||
{/* Simulation Info */}
|
||||
<div className="border border-border/30 bg-card p-6 shadow-[2px_2px_0_0_var(--border)]">
|
||||
<h3 className="text-headline-sm text-primary mb-4 border-b border-dotted border-border/20 pb-2">Simulation Info</h3>
|
||||
<h3 className="text-headline-sm text-primary mb-4 border-b border-dotted border-border/20 pb-2">
|
||||
Simulation Info
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm font-mono">
|
||||
<div className="flex flex-col gap-1 border-b border-border/10 pb-2">
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">Session ID</span>
|
||||
<span className="text-foreground font-bold break-all">{snapshot.id}</span>
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">
|
||||
Session ID
|
||||
</span>
|
||||
<span className="text-foreground font-bold break-all">
|
||||
{snapshot.id}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 border-b border-border/10 pb-2">
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">Max Turns</span>
|
||||
<span className="text-foreground font-bold">{snapshot.maxTurns}</span>
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">
|
||||
Max Turns
|
||||
</span>
|
||||
<span className="text-foreground font-bold">
|
||||
{snapshot.maxTurns}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 border-b border-border/10 pb-2">
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">Turn Count</span>
|
||||
<span className="text-foreground font-bold">{snapshot.turn}</span>
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">
|
||||
Turn Count
|
||||
</span>
|
||||
<span className="text-foreground font-bold">
|
||||
{snapshot.turn}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 border-b border-border/10 pb-2">
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">Entities Registered</span>
|
||||
<span className="text-foreground font-bold">{snapshot.entities.length}</span>
|
||||
<span className="text-muted-foreground text-xs uppercase tracking-wider">
|
||||
Entities Registered
|
||||
</span>
|
||||
<span className="text-foreground font-bold">
|
||||
{snapshot.entities.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Entities Involved */}
|
||||
<div className="border border-border/30 bg-card p-6 shadow-[2px_2px_0_0_var(--border)]">
|
||||
<h3 className="text-headline-sm text-primary mb-4 border-b border-dotted border-border/20 pb-2">Entities Involved</h3>
|
||||
<h3 className="text-headline-sm text-primary mb-4 border-b border-dotted border-border/20 pb-2">
|
||||
Entities Involved
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{snapshot.entities.map((ent) => (
|
||||
<div key={ent.id} className="border border-border/20 bg-secondary/20 p-4 shadow-[1px_1px_0_0_var(--border)] flex justify-between items-center">
|
||||
<div
|
||||
key={ent.id}
|
||||
className="border border-border/20 bg-secondary/20 p-4 shadow-[1px_1px_0_0_var(--border)] flex justify-between items-center"
|
||||
>
|
||||
<div>
|
||||
<strong className="text-sm text-foreground block font-head tracking-wide">{ent.name}</strong>
|
||||
<span className="text-xs text-muted-foreground font-mono block mt-1">ID: {ent.id}</span>
|
||||
<strong className="text-sm text-foreground block font-head tracking-wide">
|
||||
{ent.name}
|
||||
</strong>
|
||||
<span className="text-xs text-muted-foreground font-mono block mt-1">
|
||||
ID: {ent.id}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{ent.isPlayer ? (
|
||||
<span className="bg-primary/20 text-primary border border-primary/30 px-2 py-0.5 text-xs font-mono">PLAYER</span>
|
||||
<span className="bg-primary/20 text-primary border border-primary/30 px-2 py-0.5 text-xs font-mono">
|
||||
PLAYER
|
||||
</span>
|
||||
) : (
|
||||
<span className="bg-secondary/60 text-muted-foreground border border-border/20 px-2 py-0.5 text-xs font-mono">NPC</span>
|
||||
<span className="bg-secondary/60 text-muted-foreground border border-border/20 px-2 py-0.5 text-xs font-mono">
|
||||
NPC
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -485,18 +571,24 @@ export function PlayView() {
|
||||
{activeTab === "interact" && (
|
||||
<footer className="sticky bottom-0 bg-background/95 backdrop-blur-xs border-t border-dotted border-border/20 px-8 py-4 z-10 shrink-0">
|
||||
<div className="max-w-[800px] mx-auto">
|
||||
{snapshot.status === "waiting_player" && snapshot.waitingEntity ? (
|
||||
{snapshot.status === "waiting_player" &&
|
||||
snapshot.waitingEntity ? (
|
||||
<div className="border border-border/30 bg-card p-4 shadow-[2px_2px_0_0_var(--border)]">
|
||||
<details className="mb-3">
|
||||
<summary className="cursor-pointer text-sm font-medium font-head text-primary select-none outline-none">
|
||||
<strong>Your context as {snapshot.waitingEntity.name}</strong>
|
||||
<strong>
|
||||
Your context as {snapshot.waitingEntity.name}
|
||||
</strong>
|
||||
</summary>
|
||||
<pre className="text-xs whitespace-pre-wrap bg-input border border-border/20 p-2 max-h-[150px] overflow-y-auto mt-2 font-mono">
|
||||
{snapshot.waitingEntity.userContext}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
<form onSubmit={handleSubmitAction} className="flex flex-col gap-2">
|
||||
<form
|
||||
onSubmit={handleSubmitAction}
|
||||
className="flex flex-col gap-2"
|
||||
>
|
||||
<Textarea
|
||||
value={playerInput}
|
||||
onChange={(e) => setPlayerInput(e.target.value)}
|
||||
@@ -504,15 +596,20 @@ export function PlayView() {
|
||||
rows={3}
|
||||
disabled={loading}
|
||||
/>
|
||||
<Button type="submit" disabled={loading || !playerInput.trim()}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading || !playerInput.trim()}
|
||||
>
|
||||
{loading ? "Processing..." : "Submit Action"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
) : (snapshot.status === "done" || snapshot.status === "error") ? (
|
||||
) : snapshot.status === "done" || snapshot.status === "error" ? (
|
||||
<div className="flex justify-between items-center bg-card border border-border/30 p-4 shadow-[2px_2px_0_0_var(--border)]">
|
||||
<span className="text-sm font-mono text-muted-foreground">
|
||||
{snapshot.status === "error" ? "Simulation finished with an error." : "Simulation complete."}
|
||||
{snapshot.status === "error"
|
||||
? "Simulation finished with an error."
|
||||
: "Simulation complete."}
|
||||
</span>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -520,7 +617,9 @@ export function PlayView() {
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
{snapshot.status === "error" ? "Back to Dashboard" : "New Simulation"}
|
||||
{snapshot.status === "error"
|
||||
? "Back to Dashboard"
|
||||
: "New Simulation"}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -542,5 +641,6 @@ export function PlayView() {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ function Sidebar({
|
||||
<div
|
||||
data-slot="sidebar"
|
||||
className={cn(
|
||||
"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
|
||||
"flex h-full w-(--sidebar-width) flex-col bg-card text-sidebar-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -186,7 +186,7 @@ function Sidebar({
|
||||
data-sidebar="sidebar"
|
||||
data-slot="sidebar"
|
||||
data-mobile="true"
|
||||
className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
|
||||
className="w-(--sidebar-width) bg-card p-0 text-sidebar-foreground [&>button]:hidden"
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
||||
@@ -241,7 +241,7 @@ function Sidebar({
|
||||
<div
|
||||
data-sidebar="sidebar"
|
||||
data-slot="sidebar-inner"
|
||||
className="flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border"
|
||||
className="flex size-full flex-col bg-card group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user