项目文件夹

文件
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

92 行
3.0 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
"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>
);
}