mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 03:52:48 +05:30
feat(gui): Using amber tide styling for Omnia
This commit is contained in:
2
apps/gui/next-env.d.ts
vendored
2
apps/gui/next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -11,6 +11,14 @@ import {
|
||||
} from "@/app/play/actions";
|
||||
import type { ModelProviderInstance, ModelProviderMeta } from "@omnia/llm";
|
||||
import { ProviderInstancesConfig } from "@/components/config/ProviderInstancesConfig";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -89,12 +97,12 @@ export default function ConfigPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-[800px] px-4 py-8">
|
||||
<h1 className="mb-6 text-2xl">Configuration</h1>
|
||||
<div className="mx-auto max-w-[800px] px-10 py-12">
|
||||
<h1 className="mb-6 text-headline-lg text-primary animate-fade-in">Configuration</h1>
|
||||
|
||||
{config === null && loading && <p>Loading configuration...</p>}
|
||||
{config === null && loading && <p className="text-body-md text-muted-foreground">Loading configuration...</p>}
|
||||
{error && (
|
||||
<div className="mb-4 rounded border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||
<div className="mb-4 border border-destructive bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -111,14 +119,14 @@ export default function ConfigPage() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<section className="mb-8 pb-6">
|
||||
<h2 className="mb-3 text-lg">Task Provider Routing</h2>
|
||||
<p className="my-4 rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800">
|
||||
<section className="border-b border-dotted border-border/20 mb-8 pb-8">
|
||||
<h2 className="mb-3 text-headline-md text-foreground">Task Provider Routing</h2>
|
||||
<p className="my-4 border border-border/20 bg-secondary px-3 py-2 text-label-sm text-foreground/80">
|
||||
Configure which LLM Provider Key Instance should handle each
|
||||
specific simulation task. Mappings default to the currently{" "}
|
||||
<strong>Active</strong> instance if not specified.
|
||||
</p>
|
||||
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{[
|
||||
{ key: "actor-prose", label: "Actor Prose Generation", desc: "Generates roleplay/narrative prose for Non-Player Characters.", type: "generative" },
|
||||
{ key: "llm-validator", label: "LLM Validator", desc: "Arbitrates and validates proposed actions against the world state rules.", type: "generative" },
|
||||
@@ -129,39 +137,45 @@ export default function ConfigPage() {
|
||||
].map((task) => (
|
||||
<div
|
||||
key={task.key}
|
||||
className="flex flex-col justify-between gap-3 rounded-lg border-2 bg-card p-4"
|
||||
className="flex flex-col justify-between gap-3 border border-border/30 bg-card p-4 shadow-[2px_2px_0_0_var(--border)]"
|
||||
>
|
||||
<div className="flex flex-col gap-1 text-xs">
|
||||
<strong className="text-sm text-foreground">
|
||||
<div className="flex flex-col gap-1">
|
||||
<strong className="text-body-md text-foreground">
|
||||
{task.label}
|
||||
</strong>
|
||||
<span className="mt-0.5 text-muted-foreground">{task.desc}</span>
|
||||
<span className="mt-0.5 text-xs text-muted-foreground">{task.desc}</span>
|
||||
</div>
|
||||
<select
|
||||
<Select
|
||||
value={mappings[task.key] || ""}
|
||||
onChange={(e) =>
|
||||
handleUpdateMapping(task.key, e.target.value)
|
||||
onValueChange={(value) =>
|
||||
handleUpdateMapping(task.key, value || "")
|
||||
}
|
||||
className="w-full rounded border-2 bg-input px-2 py-1.5 text-xs shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
||||
>
|
||||
<option value="">-- Use Active Key (Default) --</option>
|
||||
{instances
|
||||
.filter((inst) => (inst.type || "generative") === task.type)
|
||||
.map((inst) => (
|
||||
<option key={inst.id} value={inst.id}>
|
||||
{inst.name} ({inst.providerName}){inst.isActive ? " [Active]" : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="-- Use Active Key (Default) --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="">-- Use Active Key (Default) --</SelectItem>
|
||||
{instances
|
||||
.filter((inst) => (inst.type || "generative") === task.type)
|
||||
.map((inst) => (
|
||||
<SelectItem key={inst.id} value={inst.id}>
|
||||
{inst.name} ({inst.providerName}){inst.isActive ? " [Active]" : ""}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-8 pb-6">
|
||||
<h2 className="mb-3 text-lg">Available Scenarios</h2>
|
||||
<section className="mb-8">
|
||||
<h2 className="mb-3 text-headline-md text-foreground">Available Scenarios</h2>
|
||||
{config.availableScenarios.length === 0 ? (
|
||||
<p className="mt-3 rounded border border-amber-200 bg-amber-100 px-3 py-2 text-xs text-amber-800">
|
||||
<p className="mt-3 border border-accent bg-accent/25 px-3 py-2 text-label-sm text-foreground/80">
|
||||
No scenarios found in{" "}
|
||||
<code className="font-mono text-xs">
|
||||
content/demo/scenarios/
|
||||
@@ -179,9 +193,9 @@ export default function ConfigPage() {
|
||||
<TableBody>
|
||||
{config.availableScenarios.map((s) => (
|
||||
<TableRow key={s.path}>
|
||||
<TableCell>{s.name}</TableCell>
|
||||
<TableCell className="text-body-md">{s.name}</TableCell>
|
||||
<TableCell>
|
||||
<code className="font-mono text-xs text-blue-600">
|
||||
<code className="font-mono text-xs text-primary">
|
||||
{s.path}
|
||||
</code>
|
||||
</TableCell>
|
||||
|
||||
@@ -12,7 +12,16 @@
|
||||
@theme inline {
|
||||
--font-head: var(--font-head);
|
||||
--font-sans: var(--font-sans);
|
||||
--radius: var(--radius);
|
||||
--font-mono: var(--font-mono);
|
||||
|
||||
--radius: 0px;
|
||||
--radius-xs: 0px;
|
||||
--radius-sm: 0px;
|
||||
--radius-md: 0px;
|
||||
--radius-lg: 0px;
|
||||
--radius-xl: 0px;
|
||||
--radius-2xl: 0px;
|
||||
--radius-3xl: 0px;
|
||||
|
||||
--shadow-xs: 1px 1px 0 0 var(--border);
|
||||
--shadow-sm: 2px 2px 0 0 var(--border);
|
||||
@@ -42,51 +51,122 @@
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
--color-surface: var(--surface);
|
||||
--color-surface-dim: var(--surface-dim);
|
||||
--color-surface-bright: var(--surface-bright);
|
||||
--color-surface-container-lowest: var(--surface-container-lowest);
|
||||
--color-surface-container-low: var(--surface-container-low);
|
||||
--color-surface-container: var(--surface-container);
|
||||
--color-surface-container-high: var(--surface-container-high);
|
||||
--color-surface-container-highest: var(--surface-container-highest);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0;
|
||||
--background: #fff7e8;
|
||||
--foreground: #000;
|
||||
--card: #fff;
|
||||
--card-foreground: #000;
|
||||
--popover: #fff;
|
||||
--popover-foreground: #000;
|
||||
--primary: #ffdc58;
|
||||
--primary-hover: #ffd12e;
|
||||
--primary-foreground: #000;
|
||||
--secondary: #000;
|
||||
--secondary-foreground: #fff;
|
||||
--muted: #efe7d6;
|
||||
--muted-foreground: #6b6355;
|
||||
--accent: #ffe7a3;
|
||||
--accent-foreground: #000;
|
||||
--destructive: #e63946;
|
||||
--destructive-foreground: #fff;
|
||||
--border: #000;
|
||||
--input: #fff;
|
||||
--ring: #000;
|
||||
--background: #fff8f5;
|
||||
--foreground: #201a16;
|
||||
--card: #fcf3e8;
|
||||
--card-foreground: #201a16;
|
||||
--popover: #fff8f5;
|
||||
--popover-foreground: #201a16;
|
||||
--primary: #e58e58;
|
||||
--primary-hover: #d47e48;
|
||||
--primary-foreground: #201a16;
|
||||
--secondary: #f5e6d3;
|
||||
--secondary-foreground: #201a16;
|
||||
--muted: #efe0cd;
|
||||
--muted-foreground: #6d6354;
|
||||
--accent: #ffd966;
|
||||
--accent-foreground: #201a16;
|
||||
--destructive: #ba1a1a;
|
||||
--destructive-foreground: #ffffff;
|
||||
--border: #5c544e;
|
||||
--input: #fff8f5;
|
||||
--ring: #e58e58;
|
||||
|
||||
--surface: #fff8f5;
|
||||
--surface-dim: #e4d8d0;
|
||||
--surface-bright: #fff8f5;
|
||||
--surface-container-lowest: #ffffff;
|
||||
--surface-container-low: #fef1e9;
|
||||
--surface-container: #f8ece3;
|
||||
--surface-container-high: #f2e6de;
|
||||
--surface-container-highest: #ece0d8;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: #1a1815;
|
||||
--foreground: #f5f0e6;
|
||||
--card: #262320;
|
||||
--card-foreground: #f5f0e6;
|
||||
--popover: #262320;
|
||||
--popover-foreground: #f5f0e6;
|
||||
--primary: #ffdc58;
|
||||
--primary-hover: #ffd12e;
|
||||
--primary-foreground: #000;
|
||||
--secondary: #3a352f;
|
||||
--secondary-foreground: #f5f0e6;
|
||||
--muted: #2e2a24;
|
||||
--muted-foreground: #b3ac9e;
|
||||
--accent: #38342b;
|
||||
--accent-foreground: #f5f0e6;
|
||||
--destructive: #ff6b6b;
|
||||
--destructive-foreground: #1a1815;
|
||||
--border: #000;
|
||||
--input: #262320;
|
||||
--ring: #ffdc58;
|
||||
--background: #201a16;
|
||||
--foreground: #fff8f5;
|
||||
--card: #362f2a;
|
||||
--card-foreground: #fff8f5;
|
||||
--popover: #201a16;
|
||||
--popover-foreground: #fff8f5;
|
||||
--primary: #e58e58;
|
||||
--primary-hover: #ffb68c;
|
||||
--primary-foreground: #201a16;
|
||||
--secondary: #54433a;
|
||||
--secondary-foreground: #fbeee6;
|
||||
--muted: #362f2a;
|
||||
--muted-foreground: #d9c2b6;
|
||||
--accent: #c1a032;
|
||||
--accent-foreground: #201a16;
|
||||
--destructive: #ffdad6;
|
||||
--destructive-foreground: #93000a;
|
||||
--border: #877369;
|
||||
--input: #362f2a;
|
||||
--ring: #e58e58;
|
||||
|
||||
--surface: #201a16;
|
||||
--surface-dim: #362f2a;
|
||||
--surface-bright: #201a16;
|
||||
--surface-container-lowest: #201a16;
|
||||
--surface-container-low: #362f2a;
|
||||
--surface-container: #362f2a;
|
||||
--surface-container-high: #54433a;
|
||||
--surface-container-highest: #54433a;
|
||||
}
|
||||
|
||||
@utility text-headline-lg {
|
||||
font-family: var(--font-head);
|
||||
font-size: 48px;
|
||||
font-weight: 400;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
@utility text-headline-md {
|
||||
font-family: var(--font-head);
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
@utility text-headline-sm {
|
||||
font-family: var(--font-head);
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
@utility text-body-lg {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@utility text-body-md {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@utility text-label-sm {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { Archivo_Black, Space_Grotesk } from "next/font/google";
|
||||
import { Jersey_25, JetBrains_Mono, Space_Mono } from "next/font/google";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import "./globals.css";
|
||||
@@ -12,19 +12,26 @@ import {
|
||||
NavigationMenuLink,
|
||||
} from "@/components/ui/navigation-menu";
|
||||
|
||||
const archivoBlack = Archivo_Black({
|
||||
const jersey25 = Jersey_25({
|
||||
subsets: ["latin"],
|
||||
weight: "400",
|
||||
variable: "--font-head",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const spaceGrotesk = Space_Grotesk({
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const spaceMono = Space_Mono({
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "700"],
|
||||
variable: "--font-mono",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const links = [
|
||||
{ href: "/", label: "Home" },
|
||||
{ href: "/play", label: "Play" },
|
||||
@@ -36,22 +43,26 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${archivoBlack.variable} ${spaceGrotesk.variable} min-h-dvh bg-background text-foreground font-sans`}>
|
||||
<nav className="flex items-center gap-4 border-b-2 px-4 py-3">
|
||||
<Link href="/" className="font-head text-base font-bold no-underline text-foreground">
|
||||
Omnia
|
||||
</Link>
|
||||
<NavigationMenu viewport={false}>
|
||||
<NavigationMenuList>
|
||||
{links.map((link) => (
|
||||
<NavigationMenuItem key={link.href}>
|
||||
<NavigationMenuLink asChild active={pathname === link.href}>
|
||||
<Link href={link.href}>{link.label}</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
))}
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
<body className={`${jersey25.variable} ${jetbrainsMono.variable} ${spaceMono.variable} min-h-dvh bg-background text-foreground font-sans`}>
|
||||
<nav className="border-b border-dotted border-border/20 bg-secondary/30">
|
||||
<div className="mx-auto max-w-[800px] px-10 py-3 flex items-center justify-center gap-8">
|
||||
<Link href="/" className="font-head text-headline-sm text-primary no-underline tracking-wide hover:opacity-85">
|
||||
Omnia
|
||||
</Link>
|
||||
<NavigationMenu viewport={false}>
|
||||
<NavigationMenuList>
|
||||
{links.map((link) => (
|
||||
<NavigationMenuItem key={link.href}>
|
||||
<NavigationMenuLink asChild active={pathname === link.href}>
|
||||
<Link href={link.href} className="text-foreground no-underline font-medium text-sm">
|
||||
{link.label}
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
))}
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
</nav>
|
||||
{children}
|
||||
</body>
|
||||
|
||||
@@ -3,14 +3,14 @@ import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/ca
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="mx-auto max-w-[800px] px-4 py-12">
|
||||
<h1 className="mb-2 text-3xl">Omnia GUI</h1>
|
||||
<p className="mb-8 text-muted-foreground">
|
||||
<main className="mx-auto max-w-[800px] px-10 py-12">
|
||||
<h1 className="mb-2 text-headline-lg text-primary">Omnia GUI</h1>
|
||||
<p className="mb-8 text-body-md text-muted-foreground">
|
||||
Configuration and gameplay interface for the Omnia simulation engine.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<Link href="/play" className="flex-1 no-underline">
|
||||
<Card className="transition-[border-color,box-shadow] duration-150 hover:border-blue-600 hover:shadow-[0_2px_8px_rgba(37,99,235,0.1)]">
|
||||
<div className="flex gap-6">
|
||||
<Link href="/play" className="flex-1 no-underline text-foreground">
|
||||
<Card className="transition-all hover:-translate-y-0.5 hover:shadow-[3px_3px_0_0_var(--border)] active:translate-y-0 active:shadow-[1px_1px_0_0_var(--border)]">
|
||||
<CardHeader>
|
||||
<CardTitle>Play</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -19,8 +19,8 @@ export default function Home() {
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
<Link href="/config" className="flex-1 no-underline">
|
||||
<Card className="transition-[border-color,box-shadow] duration-150 hover:border-blue-600 hover:shadow-[0_2px_8px_rgba(37,99,235,0.1)]">
|
||||
<Link href="/config" className="flex-1 no-underline text-foreground">
|
||||
<Card className="transition-all hover:-translate-y-0.5 hover:shadow-[3px_3px_0_0_var(--border)] active:translate-y-0 active:shadow-[1px_1px_0_0_var(--border)]">
|
||||
<CardHeader>
|
||||
<CardTitle>Config</CardTitle>
|
||||
<CardDescription>
|
||||
|
||||
@@ -18,6 +18,15 @@ import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { PromptModal } from "./PromptModal";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
function IntentTag({
|
||||
intent,
|
||||
@@ -86,11 +95,16 @@ function LogEntryCard({
|
||||
const showMenu = !!(entry.rawPrompt || entry.decoderPrompt);
|
||||
|
||||
return (
|
||||
<div className="rounded border-2 bg-card p-3">
|
||||
<div className="flex justify-between items-center mb-1.5 text-sm">
|
||||
<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>{entry.entityName}</strong>
|
||||
<span className="text-muted-foreground">
|
||||
<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)}
|
||||
</span>
|
||||
@@ -106,8 +120,8 @@ function LogEntryCard({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-[0.9375rem] leading-relaxed mb-1.5">{entry.narrativeProse}</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<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} />
|
||||
))}
|
||||
@@ -389,13 +403,13 @@ export function PlayView() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-[800px] p-8 pt-4">
|
||||
<h1 className="text-2xl mb-4">Omnia Play</h1>
|
||||
<div className="mx-auto max-w-[800px] px-10 py-12">
|
||||
<h1 className="text-headline-lg text-primary mb-6 animate-fade-in">Omnia Play</h1>
|
||||
|
||||
{!snapshot && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-[1.2fr_1fr] gap-8 mt-4">
|
||||
<div className="rounded-xl border-2 bg-card p-6 shadow-sm">
|
||||
<h2 className="text-lg font-head font-medium mb-5 pb-2 border-b">Start New Simulation</h2>
|
||||
<div className="border border-border/30 bg-card p-6 shadow-[2px_2px_0_0_var(--border)]">
|
||||
<h2 className="text-headline-md text-foreground mb-5 pb-2 border-b border-dotted border-border/20">Start New Simulation</h2>
|
||||
<form onSubmit={handleStart} className="flex flex-col gap-4">
|
||||
{error && (
|
||||
<div className="rounded border-2 border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
@@ -404,39 +418,47 @@ export function PlayView() {
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="scenario" className="text-sm font-medium">Scenario</label>
|
||||
<select
|
||||
id="scenario"
|
||||
name="scenario"
|
||||
<Select
|
||||
value={selectedScenario}
|
||||
onChange={(e) => setSelectedScenario(e.target.value)}
|
||||
className="w-full rounded border-2 bg-input px-3 py-2 text-sm shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
||||
onValueChange={(val) => setSelectedScenario(val || "")}
|
||||
>
|
||||
{scenarios.map((s) => (
|
||||
<option key={s.path} value={s.path}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{scenarios.map((s) => (
|
||||
<SelectItem key={s.path} value={s.path}>
|
||||
{s.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="playEntity" className="text-sm font-medium">
|
||||
Play as (Entity)
|
||||
</label>
|
||||
<select
|
||||
id="playEntity"
|
||||
name="playEntity"
|
||||
<Select
|
||||
value={selectedEntity}
|
||||
onChange={(e) => setSelectedEntity(e.target.value)}
|
||||
onValueChange={(val) => setSelectedEntity(val || "")}
|
||||
disabled={availableEntities.length === 0}
|
||||
className="w-full rounded border-2 bg-input px-3 py-2 text-sm shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:opacity-50"
|
||||
>
|
||||
<option value="">-- Spectator (Observer) --</option>
|
||||
{availableEntities.map((ent) => (
|
||||
<option key={ent.id} value={ent.id}>
|
||||
{ent.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="-- Spectator (Observer) --" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="">-- Spectator (Observer) --</SelectItem>
|
||||
{availableEntities.map((ent) => (
|
||||
<SelectItem key={ent.id} value={ent.id}>
|
||||
{ent.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={loading || providerInstances.length === 0}>
|
||||
@@ -445,21 +467,21 @@ export function PlayView() {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border-2 bg-card p-6 shadow-sm">
|
||||
<h2 className="text-lg font-head font-medium mb-5 pb-2 border-b">Resume Simulation</h2>
|
||||
<div className="border border-border/30 bg-card p-6 shadow-[2px_2px_0_0_var(--border)]">
|
||||
<h2 className="text-headline-md text-foreground mb-5 pb-2 border-b border-dotted border-border/20">Resume Simulation</h2>
|
||||
{savedSessions.length === 0 ? (
|
||||
<p className="text-sm italic text-muted-foreground">No saved sessions found. Start a new one!</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3 max-h-[400px] overflow-y-auto pr-1">
|
||||
{savedSessions.map((s) => (
|
||||
<div key={s.id} className="rounded border-2 bg-muted/50 p-3 flex justify-between items-center gap-4 transition-all hover:border-muted-foreground/30">
|
||||
<div key={s.id} className="border border-border/30 bg-secondary/40 p-3 flex justify-between items-center gap-4 shadow-[1px_1px_0_0_var(--border)]">
|
||||
<div className="flex flex-col gap-0.5 text-sm">
|
||||
<strong className="text-sm text-foreground">{s.scenarioName}</strong>
|
||||
<span className="text-muted-foreground">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Turn {s.turn} · {s.entities.length} entities · {s.status}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground/60">
|
||||
Session ID: <code>{s.id}</code>
|
||||
Session ID: <code className="font-mono text-xs">{s.id}</code>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
@@ -488,7 +510,7 @@ export function PlayView() {
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<h2 className="text-xl">{snapshot.scenarioName}</h2>
|
||||
<h2 className="text-headline-md text-primary">{snapshot.scenarioName}</h2>
|
||||
{snapshot.status !== "done" && snapshot.status !== "error" && (
|
||||
<div className="flex gap-2">
|
||||
{snapshot.status === "running" && (
|
||||
@@ -532,7 +554,7 @@ export function PlayView() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 mb-4 max-h-[55vh] overflow-y-auto rounded border-2 bg-muted/30 p-3">
|
||||
<div className="flex flex-col gap-4 mb-6 max-h-[55vh] overflow-y-auto border border-border/20 bg-secondary/30 p-4 shadow-[inset_1px_1px_4px_rgba(0,0,0,0.05)]">
|
||||
{(() => {
|
||||
const playerEntity = snapshot.entities.find((e) => e.isPlayer);
|
||||
return snapshot.log.map((entry, i) => (
|
||||
@@ -545,7 +567,7 @@ export function PlayView() {
|
||||
));
|
||||
})()}
|
||||
{loading && (
|
||||
<div className="flex items-center gap-2 text-sm italic text-muted-foreground p-2">
|
||||
<div className="flex items-center gap-2 text-sm italic text-muted-foreground p-2 font-mono">
|
||||
<Spinner />
|
||||
{statusText || "Processing..."}
|
||||
</div>
|
||||
@@ -554,14 +576,14 @@ export function PlayView() {
|
||||
</div>
|
||||
|
||||
{snapshot.status === "waiting_player" && snapshot.waitingEntity && (
|
||||
<div className="rounded border-2 bg-muted/50 p-4">
|
||||
<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">
|
||||
<summary className="cursor-pointer text-sm font-medium font-head text-primary">
|
||||
<strong>
|
||||
Your context as {snapshot.waitingEntity.name}
|
||||
</strong>
|
||||
</summary>
|
||||
<pre className="text-xs whitespace-pre-wrap bg-muted p-2 rounded max-h-[200px] overflow-y-auto mt-2">
|
||||
<pre className="text-xs whitespace-pre-wrap bg-input border border-border/20 p-2 max-h-[200px] overflow-y-auto mt-2 font-mono">
|
||||
{snapshot.waitingEntity.userContext}
|
||||
</pre>
|
||||
</details>
|
||||
@@ -597,7 +619,7 @@ export function PlayView() {
|
||||
)}
|
||||
|
||||
{error && !loading && (
|
||||
<div className="rounded border-2 border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive mt-4">
|
||||
<div className="border border-destructive bg-destructive/10 px-3 py-2 text-sm text-destructive mt-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,24 +5,24 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium border border-border/30 outline-none transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 shadow-sm active:translate-x-[1px] active:translate-y-[1px] active:shadow-xs focus-visible:ring-2 focus-visible:ring-ring",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary-hover",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
"bg-background hover:bg-secondary",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary-foreground/10",
|
||||
ghost: "border-transparent shadow-none active:translate-x-0 active:translate-y-0 active:shadow-none hover:bg-secondary",
|
||||
link: "border-transparent shadow-none active:translate-x-0 active:translate-y-0 active:shadow-none text-primary hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
sm: "h-9 px-3 text-xs",
|
||||
lg: "h-11 px-8 text-base",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ function Card({
|
||||
data-slot="card"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded border-2 bg-card py-(--card-spacing) text-sm text-card-foreground shadow-md [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
||||
"group/card flex flex-col gap-(--card-spacing) overflow-hidden border border-border/30 bg-card py-(--card-spacing) text-sm text-card-foreground shadow-[2px_2px_0_0_var(--border)] [--card-spacing:--spacing(4)] data-[size=sm]:[--card-spacing:--spacing(3)]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
||||
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] border-b border-dotted border-border/20 pb-4 mb-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -38,7 +38,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn(
|
||||
"text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
||||
"font-head text-headline-sm text-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -84,7 +84,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn(
|
||||
"flex items-center rounded-b-xl border-t-2 bg-muted/50 p-(--card-spacing)",
|
||||
"flex items-center border-t border-dotted border-border/20 bg-muted/30 p-(--card-spacing)",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -59,7 +59,7 @@ function NavigationMenuItem({
|
||||
}
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground"
|
||||
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-secondary data-popup-open:text-foreground data-open:bg-secondary data-open:text-foreground"
|
||||
)
|
||||
|
||||
function NavigationMenuTrigger({
|
||||
@@ -90,7 +90,7 @@ function NavigationMenuContent({
|
||||
<NavigationMenuPrimitive.Content
|
||||
data-slot="navigation-menu-content"
|
||||
className={cn(
|
||||
"top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded group-data-[viewport=false]/navigation-menu:border-2 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:duration-300 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
|
||||
"top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:border-border/30 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:duration-300 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -111,7 +111,7 @@ function NavigationMenuViewport({
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
data-slot="navigation-menu-viewport"
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded border-2 bg-popover text-popover-foreground shadow-md duration-100 md:w-(--radix-navigation-menu-viewport-width) data-open:animate-in data-open:zoom-in-90 data-closed:animate-out data-closed:zoom-out-90",
|
||||
"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden border border-border/30 bg-popover text-popover-foreground shadow-md duration-100 md:w-(--radix-navigation-menu-viewport-width) data-open:animate-in data-open:zoom-in-90 data-closed:animate-out data-closed:zoom-out-90",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -128,7 +128,7 @@ function NavigationMenuLink({
|
||||
<NavigationMenuPrimitive.Link
|
||||
data-slot="navigation-menu-link"
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary in-data-[slot=navigation-menu-content]:rounded-sm data-active:bg-accent data-active:text-accent-foreground [&_svg:not([class*='size-'])]:size-4",
|
||||
"flex items-center gap-2 p-2 text-sm transition-all outline-none hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary in-data-[slot=navigation-menu-content]:rounded-sm data-active:bg-primary/15 data-active:text-primary [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -41,7 +41,7 @@ function SelectTrigger({
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"flex w-fit items-center justify-between gap-1.5 rounded border-2 bg-input py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-sm transition-colors outline-none select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"flex w-fit items-center justify-between gap-1.5 rounded border border-border/30 bg-input py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-sm transition-colors outline-none select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -83,7 +83,7 @@ function SelectContent({
|
||||
<SelectPrimitive.Popup
|
||||
data-slot="select-content"
|
||||
data-align-trigger={alignItemWithTrigger}
|
||||
className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded border-2 bg-popover text-popover-foreground shadow-md duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||
className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded border border-border/30 bg-popover text-popover-foreground shadow-md duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
|
||||
Reference in New Issue
Block a user