diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 5862155..47370b2 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -5,9 +5,9 @@ on: branches: - master paths: - - 'web/docs/**' - - 'pnpm-lock.yaml' - - '.github/workflows/deploy-docs.yml' + - "web/docs/**" + - "pnpm-lock.yaml" + - ".github/workflows/deploy-docs.yml" workflow_dispatch: jobs: @@ -28,7 +28,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 22 - cache: 'pnpm' + cache: "pnpm" - name: Install Dependencies run: pnpm install --frozen-lockfile @@ -41,4 +41,4 @@ jobs: with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - workingDirectory: 'web/docs' + workingDirectory: "web/docs" diff --git a/apps/gui/src/app/actions.ts b/apps/gui/src/app/actions.ts index 2fe70e4..4ca0a3c 100644 --- a/apps/gui/src/app/actions.ts +++ b/apps/gui/src/app/actions.ts @@ -4,7 +4,12 @@ import path from "path"; import fs from "fs"; import { simulationManager } from "@/lib/simulation"; import type { SimSnapshot } from "@/lib/simulation"; -import { ProviderManager, ModelProviderInstance, AVAILABLE_PROVIDERS, ModelProviderMeta } from "@omnia/llm"; +import { + ProviderManager, + ModelProviderInstance, + AVAILABLE_PROVIDERS, + ModelProviderMeta, +} from "@omnia/llm"; import { ScenarioSchema } from "@omnia/scenario"; function resolveScenarioPath(relative: string): string { @@ -26,8 +31,7 @@ function resolveScenarioPath(relative: string): string { } type ActionResult = - | { ok: true; snapshot: SimSnapshot } - | { ok: false; error: string }; + { ok: true; snapshot: SimSnapshot } | { ok: false; error: string }; export async function startSimulation(input: { scenario?: string; @@ -169,8 +173,7 @@ export async function getConfigStatus(): Promise<{ } export async function listSavedSimulations(): Promise< - | { ok: true; sessions: SimSnapshot[] } - | { ok: false; error: string } + { ok: true; sessions: SimSnapshot[] } | { ok: false; error: string } > { try { const sessions = simulationManager.listSavedSessions(); @@ -198,7 +201,9 @@ export async function resumeSimulation(simId: string): Promise { } } -export async function getScenarioEntities(scenarioPath: string): Promise< +export async function getScenarioEntities( + scenarioPath: string, +): Promise< | { ok: true; entities: { id: string; name: string }[] } | { ok: false; error: string } > { @@ -208,10 +213,12 @@ export async function getScenarioEntities(scenarioPath: string): Promise< return { ok: false, error: `Scenario file not found: ${scenarioPath}` }; } const content = JSON.parse(fs.readFileSync(resolved, "utf-8")); - const entities = (content.entities || []).map((e: { id: string; name?: string }) => ({ - id: e.id, - name: e.name || e.id, - })); + const entities = (content.entities || []).map( + (e: { id: string; name?: string }) => ({ + id: e.id, + name: e.name || e.id, + }), + ); return { ok: true, entities }; } catch (err) { return { @@ -221,9 +228,9 @@ export async function getScenarioEntities(scenarioPath: string): Promise< } } -export async function deleteSimulation(simId: string): Promise< - { ok: true } | { ok: false; error: string } -> { +export async function deleteSimulation( + simId: string, +): Promise<{ ok: true } | { ok: false; error: string }> { try { simulationManager.deleteSession(simId); return { ok: true }; @@ -235,7 +242,9 @@ export async function deleteSimulation(simId: string): Promise< } } -export async function listProviderInstances(): Promise { +export async function listProviderInstances(): Promise< + ModelProviderInstance[] +> { return ProviderManager.list(); } @@ -247,7 +256,14 @@ export async function createProviderInstance( type: "generative" | "embedding" = "generative", maxContext?: number, ): Promise { - return ProviderManager.create(name, providerName, apiKey, modelName, type, maxContext); + return ProviderManager.create( + name, + providerName, + apiKey, + modelName, + type, + maxContext, + ); } export async function deleteProviderInstance(id: string): Promise { @@ -267,7 +283,15 @@ export async function updateProviderInstance( type: "generative" | "embedding" = "generative", maxContext?: number, ): Promise { - ProviderManager.update(id, name, providerName, apiKey, modelName, type, maxContext); + ProviderManager.update( + id, + name, + providerName, + apiKey, + modelName, + type, + maxContext, + ); } export async function getProviderMappings(): Promise> { @@ -285,11 +309,15 @@ export async function getAvailableProviders(): Promise { return AVAILABLE_PROVIDERS; } -export async function regenerateEmbeddings(newProviderInstanceId?: string): Promise { +export async function regenerateEmbeddings( + newProviderInstanceId?: string, +): Promise { await simulationManager.regenerateAllEmbeddings(newProviderInstanceId); } -export async function saveScenario(scenario: unknown): Promise<{ ok: true } | { ok: false; error: string }> { +export async function saveScenario( + scenario: unknown, +): Promise<{ ok: true } | { ok: false; error: string }> { try { const parsed = ScenarioSchema.parse(scenario); const cwd = process.cwd(); @@ -301,14 +329,16 @@ export async function saveScenario(scenario: unknown): Promise<{ ok: true } | { fs.writeFileSync(filePath, JSON.stringify(parsed, null, 2), "utf-8"); return { ok: true }; } catch (err) { - return { ok: false, error: err instanceof Error ? err.message : String(err) }; + return { + ok: false, + error: err instanceof Error ? err.message : String(err), + }; } } -export async function loadScenarioJson(scenarioPath: string): Promise< - | { ok: true; scenario: unknown } - | { ok: false; error: string } -> { +export async function loadScenarioJson( + scenarioPath: string, +): Promise<{ ok: true; scenario: unknown } | { ok: false; error: string }> { try { const resolved = resolveScenarioPath(scenarioPath); if (!fs.existsSync(resolved)) { @@ -317,6 +347,9 @@ export async function loadScenarioJson(scenarioPath: string): Promise< const content = JSON.parse(fs.readFileSync(resolved, "utf-8")); return { ok: true, scenario: content }; } catch (err) { - return { ok: false, error: err instanceof Error ? err.message : String(err) }; + return { + ok: false, + error: err instanceof Error ? err.message : String(err), + }; } } diff --git a/apps/gui/src/app/builder/page.tsx b/apps/gui/src/app/builder/page.tsx index 15fdb55..2f403aa 100644 --- a/apps/gui/src/app/builder/page.tsx +++ b/apps/gui/src/app/builder/page.tsx @@ -3,26 +3,22 @@ import { useEffect, useState, useMemo } from "react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; -import { +import { SidebarProvider, Sidebar, SidebarContent, } from "@/components/ui/sidebar"; -import { - getConfigStatus, - loadScenarioJson, - saveScenario -} from "@/app/actions"; +import { getConfigStatus, loadScenarioJson, saveScenario } from "@/app/actions"; import type { Scenario } from "@omnia/scenario"; -import { - Save, - FileJson, - Globe, - MapPin, - Users, - Sparkles, +import { + Save, + FileJson, + Globe, + MapPin, + Users, + Sparkles, Info, - Eye + Eye, } from "lucide-react"; // Import refactored builder components @@ -30,14 +26,18 @@ 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 +import type { + LocationData, + EntityData, + AttributeData, } from "@/components/builder/types"; const generateUUID = () => { - if (typeof window !== "undefined" && window.crypto && window.crypto.randomUUID) { + if ( + typeof window !== "undefined" && + window.crypto && + window.crypto.randomUUID + ) { return window.crypto.randomUUID(); } return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { @@ -57,12 +57,16 @@ export default function BuilderPage() { const [selectedTemplatePath, setSelectedTemplatePath] = useState(""); // Tabs: "metadata", "locations", "entities", "json" - const [activeTab, setActiveTab] = useState<"metadata" | "locations" | "entities" | "json">("metadata"); + const [activeTab, setActiveTab] = useState< + "metadata" | "locations" | "entities" | "json" + >("metadata"); // Form State const [scenarioId, setScenarioId] = useState(""); const [name, setName] = useState("My Custom Scenario"); - const [description, setDescription] = useState("A custom scenario template created via builder."); + const [description, setDescription] = useState( + "A custom scenario template created via builder.", + ); const [startTime, setStartTime] = useState("2026-07-06T12:00:00.000Z"); const [worldAttributes, setWorldAttributes] = useState([]); const [locations, setLocations] = useState([]); @@ -73,7 +77,10 @@ export default function BuilderPage() { const [selectedEntIndex, setSelectedEntIndex] = useState(0); // Status & Notification Banners - const [statusMessage, setStatusMessage] = useState<{ text: string; type: "success" | "error" | "info" } | null>(null); + 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 @@ -82,16 +89,25 @@ export default function BuilderPage() { const uId = generateUUID(); const locId = generateUUID(); const entId = generateUUID(); - + setScenarioId(uId); setLocations([{ id: locId, attributes: [], connections: [] }]); - setEntities([{ - id: entId, - locationId: locId, - attributes: [{ name: "role", value: "adventurer", visibility: "PUBLIC", allowedEntities: [] }], - aliases: {}, - initialMemories: [] - }]); + setEntities([ + { + id: entId, + locationId: locId, + attributes: [ + { + name: "role", + value: "adventurer", + visibility: "PUBLIC", + allowedEntities: [], + }, + ], + aliases: {}, + initialMemories: [], + }, + ]); } }, [scenarioId]); @@ -119,8 +135,14 @@ export default function BuilderPage() { }, [statusMessage]); // Populate helper lists - const locationIds = useMemo(() => locations.map(l => l.id).filter(Boolean), [locations]); - const entityIds = useMemo(() => entities.map(e => e.id).filter(Boolean), [entities]); + const locationIds = useMemo( + () => locations.map((l) => l.id).filter(Boolean), + [locations], + ); + const entityIds = useMemo( + () => entities.map((e) => e.id).filter(Boolean), + [entities], + ); // Load selected template const handleLoadTemplate = async (path: string) => { @@ -129,13 +151,19 @@ export default function BuilderPage() { try { const res = await loadScenarioJson(path); if (!res.ok) { - setStatusMessage({ text: res.error || "Failed to load template.", type: "error" }); + setStatusMessage({ + text: res.error || "Failed to load template.", + type: "error", + }); return; } - + const s = res.scenario as Scenario; if (!s) { - setStatusMessage({ text: "Scenario template was empty.", type: "error" }); + setStatusMessage({ + text: "Scenario template was empty.", + type: "error", + }); return; } @@ -143,9 +171,9 @@ export default function BuilderPage() { setName(s.name || "Loaded Scenario"); setDescription(s.description || ""); setStartTime(s.startTime || "2026-07-06T12:00:00.000Z"); - + // World attributes - const wAttrs = (s.world?.attributes || []).map(a => ({ + const wAttrs = (s.world?.attributes || []).map((a) => ({ name: a.name, value: a.value, visibility: a.visibility, @@ -154,16 +182,16 @@ export default function BuilderPage() { setWorldAttributes(wAttrs); // Locations - const locs = (s.locations || []).map(l => ({ + const locs = (s.locations || []).map((l) => ({ id: l.id, parentId: l.parentId || undefined, - attributes: (l.attributes || []).map(a => ({ + attributes: (l.attributes || []).map((a) => ({ name: a.name, value: a.value, visibility: a.visibility, allowedEntities: a.allowedEntities || [], })), - connections: (l.connections || []).map(c => ({ + connections: (l.connections || []).map((c) => ({ targetId: c.targetId, portalName: c.portalName, portalStateDescriptor: c.portalStateDescriptor, @@ -172,21 +200,25 @@ export default function BuilderPage() { bidirectional: c.bidirectional ?? true, })), })); - setLocations(locs.length > 0 ? locs : [{ id: generateUUID(), attributes: [], connections: [] }]); + setLocations( + locs.length > 0 + ? locs + : [{ id: generateUUID(), attributes: [], connections: [] }], + ); setSelectedLocIndex(0); // Entities - const ents = (s.entities || []).map(e => ({ + const ents = (s.entities || []).map((e) => ({ id: e.id, locationId: e.locationId || undefined, - attributes: (e.attributes || []).map(a => ({ + attributes: (e.attributes || []).map((a) => ({ name: a.name, value: a.value, visibility: a.visibility, allowedEntities: a.allowedEntities || [], })), aliases: e.aliases || {}, - initialMemories: (e.initialMemories || []).map(m => ({ + initialMemories: (e.initialMemories || []).map((m) => ({ id: m.id || generateUUID(), timestamp: m.timestamp || s.startTime, locationId: m.locationId || null, @@ -199,18 +231,38 @@ export default function BuilderPage() { targetIds: m.intent.targetIds || [], modifiers: m.intent.modifiers || [], }, - outcome: m.outcome ? { - isValid: m.outcome.isValid, - reason: m.outcome.reason - } : undefined, + outcome: m.outcome + ? { + isValid: m.outcome.isValid, + reason: m.outcome.reason, + } + : undefined, })), })); - setEntities(ents.length > 0 ? ents : [{ id: generateUUID(), locationId: locs[0]?.id || generateUUID(), attributes: [], aliases: {}, initialMemories: [] }]); + setEntities( + ents.length > 0 + ? ents + : [ + { + id: generateUUID(), + locationId: locs[0]?.id || generateUUID(), + attributes: [], + aliases: {}, + initialMemories: [], + }, + ], + ); setSelectedEntIndex(0); - setStatusMessage({ text: "Template loaded successfully!", type: "success" }); + setStatusMessage({ + text: "Template loaded successfully!", + type: "success", + }); } catch (err) { - setStatusMessage({ text: err instanceof Error ? err.message : String(err), type: "error" }); + setStatusMessage({ + text: err instanceof Error ? err.message : String(err), + type: "error", + }); } }; @@ -221,73 +273,114 @@ export default function BuilderPage() { name: name.trim(), description: description.trim(), startTime: startTime.trim(), - world: worldAttributes.length > 0 ? { - attributes: worldAttributes.map(a => ({ - name: a.name.trim(), - value: a.value.trim(), - visibility: a.visibility, - ...(a.visibility === "PRIVATE" && a.allowedEntities.length > 0 ? { allowedEntities: a.allowedEntities } : {}) - })) - } : undefined, - locations: locations.map(l => ({ + world: + worldAttributes.length > 0 + ? { + attributes: worldAttributes.map((a) => ({ + name: a.name.trim(), + value: a.value.trim(), + visibility: a.visibility, + ...(a.visibility === "PRIVATE" && a.allowedEntities.length > 0 + ? { allowedEntities: a.allowedEntities } + : {}), + })), + } + : undefined, + locations: locations.map((l) => ({ id: l.id.trim(), ...(l.parentId ? { parentId: l.parentId } : {}), - ...(l.attributes.length > 0 ? { - attributes: l.attributes.map(a => ({ - name: a.name.trim(), - value: a.value.trim(), - visibility: a.visibility, - ...(a.visibility === "PRIVATE" && a.allowedEntities.length > 0 ? { allowedEntities: a.allowedEntities } : {}) - })) - } : {}), - ...(l.connections.length > 0 ? { - connections: l.connections.map(c => ({ - targetId: c.targetId, - ...(c.portalName ? { portalName: c.portalName.trim() } : {}), - ...(c.portalStateDescriptor ? { portalStateDescriptor: c.portalStateDescriptor.trim() } : {}), - visionProp: Number(c.visionProp), - soundProp: Number(c.soundProp), - bidirectional: !!c.bidirectional - })) - } : {}) + ...(l.attributes.length > 0 + ? { + attributes: l.attributes.map((a) => ({ + name: a.name.trim(), + value: a.value.trim(), + visibility: a.visibility, + ...(a.visibility === "PRIVATE" && a.allowedEntities.length > 0 + ? { allowedEntities: a.allowedEntities } + : {}), + })), + } + : {}), + ...(l.connections.length > 0 + ? { + connections: l.connections.map((c) => ({ + targetId: c.targetId, + ...(c.portalName ? { portalName: c.portalName.trim() } : {}), + ...(c.portalStateDescriptor + ? { portalStateDescriptor: c.portalStateDescriptor.trim() } + : {}), + visionProp: Number(c.visionProp), + soundProp: Number(c.soundProp), + bidirectional: !!c.bidirectional, + })), + } + : {}), })), - entities: entities.map(e => ({ + entities: entities.map((e) => ({ id: e.id.trim(), ...(e.locationId ? { locationId: e.locationId } : {}), - ...(e.attributes.length > 0 ? { - attributes: e.attributes.map(a => ({ - name: a.name.trim(), - value: a.value.trim(), - visibility: a.visibility, - ...(a.visibility === "PRIVATE" && a.allowedEntities.length > 0 ? { allowedEntities: a.allowedEntities } : {}) - })) - } : {}), + ...(e.attributes.length > 0 + ? { + attributes: e.attributes.map((a) => ({ + name: a.name.trim(), + value: a.value.trim(), + visibility: a.visibility, + ...(a.visibility === "PRIVATE" && a.allowedEntities.length > 0 + ? { allowedEntities: a.allowedEntities } + : {}), + })), + } + : {}), ...(Object.keys(e.aliases).length > 0 ? { aliases: e.aliases } : {}), - ...(e.initialMemories.length > 0 ? { - initialMemories: e.initialMemories.map(m => ({ - id: m.id, - timestamp: m.timestamp, - locationId: m.locationId, - intent: { - type: m.intent.type, - originalText: m.intent.originalText.trim(), - description: m.intent.description.trim(), - ...(m.intent.selfDescription ? { selfDescription: m.intent.selfDescription.trim() } : {}), - actorId: m.intent.actorId, - targetIds: m.intent.targetIds, - ...(m.intent.modifiers && m.intent.modifiers.length > 0 ? { modifiers: m.intent.modifiers } : []) - }, - ...(m.outcome ? { outcome: { isValid: !!m.outcome.isValid, reason: m.outcome.reason.trim() } } : {}) - })) - } : {}) - })) + ...(e.initialMemories.length > 0 + ? { + initialMemories: e.initialMemories.map((m) => ({ + id: m.id, + timestamp: m.timestamp, + locationId: m.locationId, + intent: { + type: m.intent.type, + originalText: m.intent.originalText.trim(), + description: m.intent.description.trim(), + ...(m.intent.selfDescription + ? { selfDescription: m.intent.selfDescription.trim() } + : {}), + actorId: m.intent.actorId, + targetIds: m.intent.targetIds, + ...(m.intent.modifiers && m.intent.modifiers.length > 0 + ? { modifiers: m.intent.modifiers } + : []), + }, + ...(m.outcome + ? { + outcome: { + isValid: !!m.outcome.isValid, + reason: m.outcome.reason.trim(), + }, + } + : {}), + })), + } + : {}), + })), }; - }, [scenarioId, name, description, startTime, worldAttributes, locations, entities]); + }, [ + scenarioId, + name, + description, + startTime, + worldAttributes, + locations, + entities, + ]); // Save scenario to server const handleSaveToServer = async () => { if (!scenarioId.trim()) { - setStatusMessage({ text: "Scenario Template ID is required to save.", type: "error" }); + setStatusMessage({ + text: "Scenario Template ID is required to save.", + type: "error", + }); return; } setIsSubmitting(true); @@ -295,15 +388,24 @@ export default function BuilderPage() { try { const res = await saveScenario(compiledScenario); if (res.ok) { - setStatusMessage({ text: `Scenario template saved as ${scenarioId}.json successfully!`, type: "success" }); + setStatusMessage({ + text: `Scenario template saved as ${scenarioId}.json successfully!`, + type: "success", + }); // Refresh template list const config = await getConfigStatus(); setAvailableScenarios(config.availableScenarios); } else { - setStatusMessage({ text: res.error || "Failed to save scenario.", type: "error" }); + setStatusMessage({ + text: res.error || "Failed to save scenario.", + type: "error", + }); } } catch (err) { - setStatusMessage({ text: err instanceof Error ? err.message : String(err), type: "error" }); + setStatusMessage({ + text: err instanceof Error ? err.message : String(err), + type: "error", + }); } finally { setIsSubmitting(false); } @@ -331,14 +433,17 @@ export default function BuilderPage() { return (
- {/* Save Status Banner */} {statusMessage && ( -
+
{statusMessage.text}
@@ -347,65 +452,74 @@ export default function BuilderPage() { )} {/* Viewport-level Vertical Sidebar on the Left Side */} - +
- Configuration - + + Configuration + +
{/* Sidebar Footer link */}
- -
-
); diff --git a/apps/gui/src/app/play/page.tsx b/apps/gui/src/app/play/page.tsx index b7d5487..b4b327c 100644 --- a/apps/gui/src/app/play/page.tsx +++ b/apps/gui/src/app/play/page.tsx @@ -5,11 +5,13 @@ import { Suspense } from "react"; export default function PlayPage() { return ( - -
Loading...
-
- }> + +
Loading...
+
+ } + > ); diff --git a/apps/gui/src/components/config/ConfigView.tsx b/apps/gui/src/components/config/ConfigView.tsx index f4ec9ee..fc060ff 100644 --- a/apps/gui/src/components/config/ConfigView.tsx +++ b/apps/gui/src/components/config/ConfigView.tsx @@ -224,7 +224,8 @@ export function ConfigView() { {instances .filter( - (inst) => (inst.type || "generative") === task.type, + (inst) => + (inst.type || "generative") === task.type, ) .map((inst) => ( diff --git a/apps/gui/src/components/config/ProviderInstancesConfig.tsx b/apps/gui/src/components/config/ProviderInstancesConfig.tsx index f57c209..4b9da0e 100644 --- a/apps/gui/src/components/config/ProviderInstancesConfig.tsx +++ b/apps/gui/src/components/config/ProviderInstancesConfig.tsx @@ -282,7 +282,9 @@ export function ProviderInstancesConfig({ {inst.providerName}
{inst.isActive && Active} - {inst.type === "generative" ? "gen" : "embed"} + + {inst.type === "generative" ? "gen" : "embed"} +
diff --git a/apps/gui/src/components/play/DashboardView.tsx b/apps/gui/src/components/play/DashboardView.tsx index f79e7bf..b92b896 100644 --- a/apps/gui/src/components/play/DashboardView.tsx +++ b/apps/gui/src/components/play/DashboardView.tsx @@ -44,7 +44,9 @@ export function DashboardView() { const [scenarios, setScenarios] = useState< { path: string; name: string; description: string }[] >([]); - const [, setProviderInstances] = useState([]); + const [providerInstances, setProviderInstances] = useState< + ModelProviderInstance[] + >([]); // Modal State const [scenarioForModal, setScenarioForModal] = useState<{ @@ -203,6 +205,24 @@ export function DashboardView() { )} + {providerInstances.length === 0 && !loadingData && ( +
+ + ⚠️ No LLM providers have been configured. + + + Please go to the{" "} + + Configuration + {" "} + page to set up at least one provider before running simulations. + +
+ )} + {/* Simulations Section */}

@@ -235,8 +255,16 @@ export function DashboardView() { {savedSessions.map((s) => (
handleResume(s.id)} - className="flex-shrink-0 w-72 border border-border/30 bg-card p-5 cursor-pointer shadow-sm hover:-translate-y-0.5 hover:shadow-md active:translate-y-0 active:shadow-sm transition-all relative group" + onClick={ + providerInstances.length === 0 + ? undefined + : () => handleResume(s.id) + } + className={`flex-shrink-0 w-72 border border-border/30 bg-card p-5 shadow-sm transition-all relative group ${ + providerInstances.length === 0 + ? "opacity-50 cursor-not-allowed filter grayscale" + : "cursor-pointer hover:-translate-y-0.5 hover:shadow-md active:translate-y-0 active:shadow-sm" + }`} >
@@ -337,6 +365,7 @@ export function DashboardView() { name={s.name} description={s.description} onClick={() => handleScenarioClick(s)} + disabled={providerInstances.length === 0} /> ))}
diff --git a/apps/gui/src/components/play/PlayView.tsx b/apps/gui/src/components/play/PlayView.tsx index dccd739..a8d8a92 100644 --- a/apps/gui/src/components/play/PlayView.tsx +++ b/apps/gui/src/components/play/PlayView.tsx @@ -422,242 +422,247 @@ export function PlayView() {

- {/* Simulation Global Controls */} -
- {snapshot.status !== "done" && snapshot.status !== "error" && ( - <> - {snapshot.status === "running" && - (loading ? ( - - ) : ( - - ))} - - - )} -
- -
- - Status:{" "} - - {getUnifiedStatus()} - - - - Turn:{" "} - {snapshot.turn} - -
- {statusMessage() && ( -

- {loading && "⏳ "} - {statusMessage()} -

- )} - - - {/* Scrollable Center Viewport */} -
- {activeTab === "interact" ? ( -
- {(() => { - const playerEntity = snapshot.entities.find((e) => e.isPlayer); - return snapshot.log.map((entry, i) => ( - - )); - })()} - {loading && ( -
- - {statusText || "Processing..."} -
- )} -
-
- ) : ( -
- {/* Simulation Info */} -
-

- Simulation Info -

-
-
- - Session ID - - - {snapshot.id} - -
-
- - Max Turns - - - {snapshot.maxTurns} - -
-
- - Turn Count - - - {snapshot.turn} - -
-
- - Entities Registered - - - {snapshot.entities.length} - -
-
-
- - {/* Entities Involved */} -
-

- Entities Involved -

-
- {snapshot.entities.map((ent) => ( -
-
- - {ent.name} - - - ID: {ent.id} - -
-
- {ent.isPlayer ? ( - - PLAYER - - ) : ( - - NPC - - )} -
-
- ))} -
-
-
- )} -
- - {/* Sticky Chat / Interaction Input Footer */} - {activeTab === "interact" && ( -