diff --git a/apps/gui/components.json b/apps/gui/components.json
new file mode 100644
index 0000000..8c21e30
--- /dev/null
+++ b/apps/gui/components.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "registries": {
+ "@retroui": "https://retroui.dev/r/radix/{name}.json",
+ "@retroui-base": "https://retroui.dev/r/base/{name}.json"
+ }
+}
diff --git a/apps/gui/package.json b/apps/gui/package.json
index b7ce140..013a8fa 100644
--- a/apps/gui/package.json
+++ b/apps/gui/package.json
@@ -18,18 +18,27 @@
"@omnia/memory": "workspace:*",
"@omnia/scenario": "workspace:*",
"@omnia/spatial": "workspace:*",
+ "@radix-ui/react-slot": "^1.3.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
"dotenv": "^17.4.2",
+ "lucide-react": "^1.24.0",
"next": "^16.2.10",
+ "radix-ui": "^1.6.2",
"react": "^19.2.0",
- "react-dom": "^19.2.0"
+ "react-dom": "^19.2.0",
+ "tailwind-merge": "^3.6.0",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
+ "@tailwindcss/postcss": "^4.3.2",
"@types/node": "^26.1.0",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"autoprefixer": "^10.5.2",
"postcss": "^8.5.16",
- "tailwindcss": "^3.4.19",
+ "shadcn": "^4.13.0",
+ "tailwindcss": "^4.3.2",
"typescript": "^6.0.3"
}
}
diff --git a/apps/gui/postcss.config.mjs b/apps/gui/postcss.config.mjs
index a982c64..61e3684 100644
--- a/apps/gui/postcss.config.mjs
+++ b/apps/gui/postcss.config.mjs
@@ -1,7 +1,6 @@
const config = {
plugins: {
- tailwindcss: {},
- autoprefixer: {},
+ "@tailwindcss/postcss": {},
},
};
diff --git a/apps/gui/src/app/config/page.tsx b/apps/gui/src/app/config/page.tsx
index af0174c..9dd6ebf 100644
--- a/apps/gui/src/app/config/page.tsx
+++ b/apps/gui/src/app/config/page.tsx
@@ -14,6 +14,26 @@ import {
regenerateEmbeddings,
} from "@/app/play/actions";
import type { ModelProviderInstance, ModelProviderMeta } from "@omnia/llm";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Badge } from "@/components/ui/badge";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
interface ConfigStatus {
apiKeySet: boolean;
@@ -244,13 +264,13 @@ export default function ConfigPage() {
Instances
-
+
{instances.length === 0 ? (
@@ -273,10 +293,10 @@ export default function ConfigPage() {
{inst.providerName} ({inst.type || "generative"})
- {inst.isActive && (
-
+ {inst.isActive && (
+
Active
-
+
)}
@@ -301,63 +321,53 @@ export default function ConfigPage() {
-
- Friendly Name
+ setEditName(e.target.value)}
placeholder="e.g. Gemini - Production"
required
- className="w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm outline-none transition-[border-color,box-shadow] focus:border-blue-500 focus:ring-3 focus:ring-blue-500/15"
/>
-
-
+
+
-
-
+
+
{editProvider && availableProviders.length > 0 && (
-
+
{availableProviders.find((p) => p.id === editProvider)?.description}
)}
-
- API Key
+
-
- Model Name
+ setEditModel(e.target.value)}
placeholder="e.g. gemini-2.5-flash, gemini-2.5-pro"
- className="w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm outline-none transition-[border-color,box-shadow] focus:border-blue-500 focus:ring-3 focus:ring-blue-500/15"
/>
{editType === "generative" && (
-
- Max Context Length (Tokens, 0 for infinite)
+ setEditMaxContext(parseInt(e.target.value) || 0)}
min={0}
placeholder="e.g. 32768"
- className="w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm outline-none transition-[border-color,box-shadow] focus:border-blue-500 focus:ring-3 focus:ring-blue-500/15"
/>
)}
- setEditIsActive(e.target.checked)}
- className="h-4 w-4 cursor-pointer"
+ onCheckedChange={(v) => setEditIsActive(v === true)}
/>
-
-
+
{selectedInstanceId !== "new" && (
-
+
)}
-
+
@@ -464,20 +463,20 @@ export default function ConfigPage() {
].map((task) => (
-
+
{task.label}
- {task.desc}
+ {task.desc}
diff --git a/apps/gui/src/app/globals.css b/apps/gui/src/app/globals.css
index 1008075..66ff5ba 100644
--- a/apps/gui/src/app/globals.css
+++ b/apps/gui/src/app/globals.css
@@ -1,10 +1,92 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@import "tailwindcss";
+@plugin "tailwindcss-animate";
-@layer base {
- html {
- font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
- Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
- }
+@custom-variant data-open (&[data-state="open"], &[data-state="active"]);
+@custom-variant data-closed (&[data-state="closed"], &[data-state="inactive"]);
+@custom-variant data-active (&[data-state="active"], &[data-state="open"]);
+@custom-variant data-checked (&[data-state="checked"], &[aria-checked="true"]);
+@custom-variant data-horizontal (&[data-orientation="horizontal"]);
+@custom-variant data-vertical (&[data-orientation="vertical"]);
+@custom-variant data-popup-open (&[data-state="open"]);
+
+@theme inline {
+ --font-head: var(--font-head);
+ --font-sans: var(--font-sans);
+ --radius: var(--radius);
+
+ --shadow-xs: 1px 1px 0 0 var(--border);
+ --shadow-sm: 2px 2px 0 0 var(--border);
+ --shadow: 3px 3px 0 0 var(--border);
+ --shadow-md: 4px 4px 0 0 var(--border);
+ --shadow-lg: 6px 6px 0 0 var(--border);
+ --shadow-xl: 10px 10px 0 1px var(--border);
+ --shadow-2xl: 16px 16px 0 1px var(--border);
+
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary-hover: var(--primary-hover);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+}
+
+: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;
+}
+
+.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;
}
diff --git a/apps/gui/src/app/layout.tsx b/apps/gui/src/app/layout.tsx
index 55675c6..a248d01 100644
--- a/apps/gui/src/app/layout.tsx
+++ b/apps/gui/src/app/layout.tsx
@@ -1,16 +1,30 @@
import type { ReactNode } from "react";
+import { Archivo_Black, Space_Grotesk } from "next/font/google";
import { NavBar } from "@/components/nav/NavBar";
import "./globals.css";
+const archivoBlack = Archivo_Black({
+ subsets: ["latin"],
+ weight: "400",
+ variable: "--font-head",
+ display: "swap",
+});
+
+const spaceGrotesk = Space_Grotesk({
+ subsets: ["latin"],
+ variable: "--font-sans",
+ display: "swap",
+});
+
export const metadata = {
- title: "Omnia GUI",
- description: "Omnia Narrative Simulation Engine — Web Interface",
+ title: "Omnia",
+ description: "Omnia Narrative Simulation Engine",
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
-
+
{children}
diff --git a/apps/gui/src/app/page.tsx b/apps/gui/src/app/page.tsx
index 3553dd4..77b76d8 100644
--- a/apps/gui/src/app/page.tsx
+++ b/apps/gui/src/app/page.tsx
@@ -1,30 +1,33 @@
import Link from "next/link";
+import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
export default function Home() {
return (
Omnia GUI
-
+
Configuration and gameplay interface for the Omnia simulation engine.
-
-
Play
-
- Start a simulation and interact with NPCs
-
+
+
+
+ Play
+
+ Start a simulation and interact with NPCs
+
+
+
-
-
Config
-
- Check environment, API keys, and available scenarios
-
+
+
+
+ Config
+
+ Check environment, API keys, and available scenarios
+
+
+
diff --git a/apps/gui/src/components/nav/NavBar.tsx b/apps/gui/src/components/nav/NavBar.tsx
index 40741e6..7e31660 100644
--- a/apps/gui/src/components/nav/NavBar.tsx
+++ b/apps/gui/src/components/nav/NavBar.tsx
@@ -2,6 +2,7 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
+import { Button } from "@/components/ui/button";
const links = [
{ href: "/", label: "Home" },
@@ -13,57 +14,22 @@ export function NavBar() {
const pathname = usePathname();
return (
-