e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { Thread } from "../components/assistant-ui/thread";
|
||
import { ThreadList } from "@/components/assistant-ui/thread-list";
|
||
import { usePiThreadState } from "@assistant-ui/react-pi";
|
||
import type { PiRuntimeReadiness } from "@assistant-ui/react-pi";
|
||
// Type-only import — erased at build time, so no server code reaches the client.
|
||
import type { PiHandshake } from "@/lib/pi-server";
|
||
import { PiHandshakeProvider } from "../components/pi-handshake";
|
||
import { PiRuntimeProvider } from "./PiRuntimeProvider";
|
||
import { WorkspaceBrowser } from "../components/workspace-browser";
|
||
|
||
export default function Home() {
|
||
const [handshake, setHandshake] = useState<PiHandshake | null>(null);
|
||
const [workspacePath, setWorkspacePath] = useState("");
|
||
|
||
useEffect(() => {
|
||
let active = true;
|
||
fetch("/api/pi/handshake")
|
||
.then((response) => response.json())
|
||
.then((data: PiHandshake) => {
|
||
if (!active) return;
|
||
setHandshake(data);
|
||
setWorkspacePath(data.workspacePath ?? "");
|
||
})
|
||
.catch(() => {
|
||
/* handshake is best-effort; the runtime still works on env defaults. */
|
||
});
|
||
return () => {
|
||
active = false;
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<PiRuntimeProvider workspacePath={workspacePath || undefined}>
|
||
<PiHandshakeProvider value={handshake}>
|
||
<div className="flex h-dvh flex-col overflow-hidden">
|
||
<Header
|
||
workspacePath={workspacePath}
|
||
onCommitWorkspace={setWorkspacePath}
|
||
/>
|
||
<ReadinessBanner fallback={handshake?.readiness} />
|
||
<div className="flex min-h-0 flex-grow">
|
||
<aside className="flex w-72 shrink-0 flex-col overflow-hidden border-r">
|
||
<div className="flex-1 overflow-y-auto p-3">
|
||
<ThreadList />
|
||
</div>
|
||
</aside>
|
||
<main className="flex min-h-0 min-w-0 flex-grow flex-col overflow-hidden">
|
||
<Thread />
|
||
</main>
|
||
</div>
|
||
</div>
|
||
</PiHandshakeProvider>
|
||
</PiRuntimeProvider>
|
||
);
|
||
}
|
||
|
||
function Header({
|
||
workspacePath,
|
||
onCommitWorkspace,
|
||
}: {
|
||
workspacePath: string;
|
||
onCommitWorkspace: (value: string) => void;
|
||
}) {
|
||
return (
|
||
<header className="flex items-center gap-3 border-b px-4 py-2">
|
||
<span className="font-semibold">assistant-ui × Pi</span>
|
||
|
||
<div className="ml-auto flex items-center gap-2">
|
||
<WorkspaceBrowser value={workspacePath} onCommit={onCommitWorkspace} />
|
||
</div>
|
||
</header>
|
||
);
|
||
}
|
||
|
||
function ReadinessBanner({
|
||
fallback,
|
||
}: {
|
||
fallback?: PiRuntimeReadiness | undefined;
|
||
}) {
|
||
const live = usePiThreadState((state) => state.readiness);
|
||
const readiness = live ?? fallback;
|
||
if (!readiness || readiness.state === "ready") return null;
|
||
return (
|
||
<div className="bg-destructive/10 text-destructive border-destructive/20 border-b px-4 py-2 text-sm">
|
||
{readiness.message ?? "The Pi runtime has no usable model selected."}
|
||
</div>
|
||
);
|
||
}
|