Hybrid traditional + AI-first webapp scaffold. Sibling to crema-app-template, adds the AI assistant surface, command bus, scripts dialog, and virtual cursor. What's pre-wired: - 6 routes: Overview, Resources, Activity, Assistant, Library, Settings - Collapsible rail + appbar + avatar dropdown shell (template code, not a lib) - Mobile sheet at <md - /assistant: streaming chat via @crema/llm-ui, mock fallback, model selector, token meter, retry probe, stop-while-streaming, persistent UI Control toggle - /settings: editable LM Studio endpoint + context window + response cap, with test-connection button - Markdown rendering for assistant replies; ```action``` blocks rendered as a small "Ran N actions" pill - ⌘⇧P script runner dialog + Play icon in the appbar - Two demo scripts in public/scripts/ - mightypix theme as default, scoped via <AppShell theme="mightypix"> Libs wired in tsconfig + app.css: - @crema/action-bus (the bus, parser, runner, cursor, provider, ws, llm-bridge) - @crema/llm-ui, @crema/chat-ui, @crema/aifirst-ui, @crema/notification-ui - lib-theme-mightypix Docs: - README.md — pitch + quick start + structure - docs/AI_FIRST.md — full system tour (data-action contract, bus, DSL, scripts, cursor, LLM integration) - app/components/layout/THEME_CONTRACT.md — every CSS variable a theme must declare - CLAUDE.md — orientation for an LLM working in the repo Genericized from comfy-cloud (the original prototype): - Brand defaults to "App" / Sparkles icon (override via app/lib/identity.ts) - User defaults to a stub (swap useUser() for real auth) - localStorage namespace is "crema.*" (was "comfy.*") Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
2.0 KiB
TypeScript
80 lines
2.0 KiB
TypeScript
import {
|
|
Links,
|
|
Meta,
|
|
Outlet,
|
|
Scripts,
|
|
ScrollRestoration,
|
|
isRouteErrorResponse,
|
|
} from "react-router"
|
|
|
|
import type { Route } from "./+types/root"
|
|
import "./app.css"
|
|
|
|
import { ToastProvider } from "@crema/notification-ui"
|
|
import { CommandBusProvider } from "@crema/action-bus"
|
|
// CREMA:PROVIDERS-IMPORTS
|
|
|
|
export function Layout({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<html lang="en">
|
|
<head>
|
|
<meta charSet="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<Meta />
|
|
<Links />
|
|
<script
|
|
dangerouslySetInnerHTML={{
|
|
__html: `(function(){try{var t=localStorage.getItem('crema-theme');if(!t)t=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';if(t==='dark')document.documentElement.classList.add('dark');}catch(e){}})();`,
|
|
}}
|
|
/>
|
|
</head>
|
|
<body>
|
|
{children}
|
|
<ScrollRestoration />
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
)
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
/* CREMA:PROVIDERS-WRAP-OPEN */
|
|
<ToastProvider>
|
|
<CommandBusProvider>
|
|
<Outlet />
|
|
</CommandBusProvider>
|
|
</ToastProvider>
|
|
/* CREMA:PROVIDERS-WRAP-CLOSE */
|
|
)
|
|
}
|
|
|
|
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|
let message = "Oops!"
|
|
let details = "An unexpected error occurred."
|
|
let stack: string | undefined
|
|
|
|
if (isRouteErrorResponse(error)) {
|
|
message = error.status === 404 ? "404" : "Error"
|
|
details =
|
|
error.status === 404
|
|
? "The requested page could not be found."
|
|
: error.statusText || details
|
|
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
|
details = error.message
|
|
stack = error.stack
|
|
}
|
|
|
|
return (
|
|
<main className="container mx-auto p-4 pt-16">
|
|
<h1>{message}</h1>
|
|
<p>{details}</p>
|
|
{stack && (
|
|
<pre className="w-full overflow-x-auto p-4">
|
|
<code>{stack}</code>
|
|
</pre>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|