From 03a5bec366e1c59aa7b2af4a2becf3d45c7f961c Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Wed, 15 Jul 2026 19:04:03 +0530 Subject: [PATCH] refactor(builder): tab content out of page.tsx --- apps/gui/src/app/builder/page.tsx | 970 +----------------- .../components/builder/AttributeEditor.tsx | 149 +++ .../src/components/builder/EntitiesTab.tsx | 507 +++++++++ apps/gui/src/components/builder/JsonTab.tsx | 38 + .../src/components/builder/LocationsTab.tsx | 370 +++++++ .../src/components/builder/MetadataTab.tsx | 120 +++ apps/gui/src/components/builder/types.ts | 49 + 7 files changed, 1290 insertions(+), 913 deletions(-) create mode 100644 apps/gui/src/components/builder/AttributeEditor.tsx create mode 100644 apps/gui/src/components/builder/EntitiesTab.tsx create mode 100644 apps/gui/src/components/builder/JsonTab.tsx create mode 100644 apps/gui/src/components/builder/LocationsTab.tsx create mode 100644 apps/gui/src/components/builder/MetadataTab.tsx create mode 100644 apps/gui/src/components/builder/types.ts diff --git a/apps/gui/src/app/builder/page.tsx b/apps/gui/src/app/builder/page.tsx index ca1a694..15fdb55 100644 --- a/apps/gui/src/app/builder/page.tsx +++ b/apps/gui/src/app/builder/page.tsx @@ -3,10 +3,6 @@ import { useEffect, useState, useMemo } from "react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Label } from "@/components/ui/label"; import { SidebarProvider, Sidebar, @@ -19,68 +15,26 @@ import { } from "@/app/actions"; import type { Scenario } from "@omnia/scenario"; import { - Plus, - Trash2, Save, FileJson, Globe, MapPin, Users, Sparkles, - ChevronRight, Info, Eye } from "lucide-react"; -interface AttributeData { - name: string; - value: string; - visibility: "PUBLIC" | "PRIVATE"; - allowedEntities: string[]; -} - -interface ConnectionData { - targetId: string; - portalName?: string; - portalStateDescriptor?: string; - visionProp: number; - soundProp: number; - bidirectional: boolean; -} - -interface LocationData { - id: string; - parentId?: string; - attributes: AttributeData[]; - connections: ConnectionData[]; -} - -interface MemoryData { - id: string; - timestamp: string; - locationId: string | null; - intent: { - type: "dialogue" | "action" | "monologue"; - originalText: string; - description: string; - selfDescription?: string; - actorId: string; - targetIds: string[]; - modifiers?: string[]; - }; - outcome?: { - isValid: boolean; - reason: string; - }; -} - -interface EntityData { - id: string; - locationId?: string; - attributes: AttributeData[]; - aliases: Record; // targetId -> subjective descriptor - initialMemories: MemoryData[]; -} +// Import refactored builder components +import { MetadataTab } from "@/components/builder/MetadataTab"; +import { LocationsTab } from "@/components/builder/LocationsTab"; +import { EntitiesTab } from "@/components/builder/EntitiesTab"; +import { JsonTab } from "@/components/builder/JsonTab"; +import type { + LocationData, + EntityData, + AttributeData +} from "@/components/builder/types"; const generateUUID = () => { if (typeof window !== "undefined" && window.crypto && window.crypto.randomUUID) { @@ -114,6 +68,14 @@ export default function BuilderPage() { const [locations, setLocations] = useState([]); const [entities, setEntities] = useState([]); + // Selected sub-items for active editing lists + const [selectedLocIndex, setSelectedLocIndex] = useState(0); + const [selectedEntIndex, setSelectedEntIndex] = useState(0); + + // Status & Notification Banners + const [statusMessage, setStatusMessage] = useState<{ text: string; type: "success" | "error" | "info" } | null>(null); + const [isSubmitting, setIsSubmitting] = useState(false); + // Initialize dynamic UUIDs client-side to prevent NextJS SSR hydration mismatch useEffect(() => { if (!scenarioId) { @@ -133,14 +95,6 @@ export default function BuilderPage() { } }, [scenarioId]); - // Selected sub-items for active editing lists - const [selectedLocIndex, setSelectedLocIndex] = useState(0); - const [selectedEntIndex, setSelectedEntIndex] = useState(0); - - // Status & Notification Banners - const [statusMessage, setStatusMessage] = useState<{ text: string; type: "success" | "error" | "info" } | null>(null); - const [isSubmitting, setIsSubmitting] = useState(false); - // Fetch available templates on load useEffect(() => { async function loadTemplates() { @@ -374,150 +328,6 @@ export default function BuilderPage() { } }; - // World Attribute Management - const addWorldAttribute = () => { - setWorldAttributes([...worldAttributes, { name: "", value: "", visibility: "PUBLIC", allowedEntities: [] }]); - }; - - // Helper component for Attributes list editor - const AttributeEditor = ({ - attributes, - onChange, - onAdd, - title = "Attributes" - }: { - attributes: AttributeData[]; - onChange: (attrs: AttributeData[]) => void; - onAdd: () => void; - title?: string; - }) => { - const handleAttrChange = (index: number, key: K, val: AttributeData[K]) => { - const copy = [...attributes]; - copy[index] = { ...copy[index], [key]: val }; - onChange(copy); - }; - - const handleToggleEntityAccess = (index: number, entId: string) => { - const copy = [...attributes]; - const allowed = copy[index].allowedEntities || []; - if (allowed.includes(entId)) { - copy[index].allowedEntities = allowed.filter(id => id !== entId); - } else { - copy[index].allowedEntities = [...allowed, entId]; - } - onChange(copy); - }; - - return ( -
-
-

{title}

- -
- {attributes.length === 0 ? ( -

No attributes defined yet.

- ) : ( -
- {attributes.map((attr, index) => ( -
-
-
- handleAttrChange(index, "name", e.target.value)} - className="h-8 font-mono text-xs" - /> - handleAttrChange(index, "value", e.target.value)} - className="h-8 text-xs" - /> -
- -
-
-
- -
- {attr.visibility === "PRIVATE" && ( -
- Visible to Entities: - {entityIds.length === 0 ? ( - Add entities first to grant private access - ) : ( -
- {entityIds.map(entId => { - const isAllowed = attr.allowedEntities?.includes(entId); - return ( - - ); - })} -
- )} -
- )} -
-
- ))} -
- )} -
- ); - }; - - // Location connections manager - const addLocationConnection = (locIndex: number) => { - const copy = [...locations]; - copy[locIndex].connections = [ - ...copy[locIndex].connections, - { targetId: locationIds[0] || "", visionProp: 10, soundProp: 10, bidirectional: true } - ]; - setLocations(copy); - }; - - const updateLocationConnection = (locIndex: number, connIndex: number, key: K, val: ConnectionData[K]) => { - const copy = [...locations]; - copy[locIndex].connections[connIndex] = { ...copy[locIndex].connections[connIndex], [key]: val }; - setLocations(copy); - }; - - const removeLocationConnection = (locIndex: number, connIndex: number) => { - const copy = [...locations]; - copy[locIndex].connections = copy[locIndex].connections.filter((_, i) => i !== connIndex); - setLocations(copy); - }; - return (
@@ -615,6 +425,9 @@ export default function BuilderPage() {

Scenario Builder

+

+ Define entities, spatial layouts, and initial metadata for custom Omnia scenarios. +

@@ -662,723 +475,54 @@ export default function BuilderPage() { {/* TAB 1: World Metadata & Attributes */} {activeTab === "metadata" && ( -
- - {/* Basic Fields */} -
-

Scenario Metadata

- -
-
- - setScenarioId(e.target.value.toLowerCase().replace(/[^a-z0-9-_]/g, ""))} - className="font-mono text-xs" - /> - Unique filename ID. Alphanumeric, hyphens and underscores only. -
- -
- - setStartTime(e.target.value)} - className="font-mono text-xs" - /> - Global clock starting timestamp. -
-
- -
- - setName(e.target.value)} - className="text-xs" - /> -
- -
- -