UI コンポーネント
セッションブロックの組み込み
SessionList と SessionDetail でセッション一覧とセッション画面を描画する
セッションブロックは、API からデータを読み取って画面の一塊を描画する。アプリが渡すのは、ブロックを置く場所、ロード中とエラーの見た目、リンクの作り方だけである。ここでは、インストールとテーマの適用で作ったアプリにセッション一覧とセッション画面を組み込む。
ブロックが API を呼ぶ方法
ブロックは @werft-ai/client のクライアントで同じ origin の /v1 を呼び、Web コンソールのログイン cookie で認証する。cookie を使うリクエストに必要な CSRF ヘッダー(X-Requested-With: werft-web)はクライアントが付ける。そのため、次のことが必要である。
- アプリと API が同じ origin であること。開発中は Vite の
server.proxyが/v1をスタックへ転送する(インストールのvite.config.ts)。 - ブラウザがログインしていること。cookie はポートを区別しないので、同じブラウザで
http://127.0.0.1:3000の Web コンソールにログインすると、http://127.0.0.1:5173のアプリのリクエストにもその cookie が送られる。 - 見えるセッションとできることは、ログインした人の権限に従う。
アプリのコード
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createApiClient } from "@werft-ai/client";
import {
WerftProvider,
QueryScopeProvider,
queryScopeOf,
type RenderLink,
useMe,
} from "@werft-ai/react";
import { SessionDetail, SessionList } from "@werft-ai/react/blocks";
import { ErrorState, Skeleton } from "@werft-ai/ui";
import { type ReactNode, StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";
const client = createApiClient();
const queryClient = new QueryClient();
const renderLink: RenderLink = ({ to: _to, id, children, ...rest }) => (
<a href={`/sessions/${id}`} {...rest}>
{children}
</a>
);
const loading = <Skeleton lines={3} />;
const renderError = (_error: unknown, retry: () => void) => (
<ErrorState message="불러오지 못했다." retry={retry} retryLabel="다시 시도" />
);
const sessionId = location.pathname.match(/^\/sessions\/([^/]+)$/)?.[1];
function SignedIn({ children }: { children: ReactNode }) {
const me = useMe();
if (me.isError) return renderError(me.error, () => void me.refetch());
const scope = me.data ? queryScopeOf(me.data) : null;
if (!scope) return loading;
return <QueryScopeProvider scope={scope}>{children}</QueryScopeProvider>;
}
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<WerftProvider
client={client}
locale="ko"
renderLink={renderLink}
>
<main className="mx-auto max-w-4xl p-6">
<SignedIn>
{sessionId ? (
<SessionDetail
id={sessionId}
back={<a href="/">세션 목록</a>}
loading={loading}
renderError={renderError}
/>
) : (
<SessionList loading={loading} renderError={renderError} />
)}
</SignedIn>
</main>
</WerftProvider>
</QueryClientProvider>
</StrictMode>,
);WerftProviderは、ブロックと hook が読むクライアント、言語(ko,ja,en)、リンク関数を保持する。QueryClientProviderの内側に置く。- ブロックはログインした人とワークスペースごとにキャッシュする。
SignedInのようにuseMe()の応答をqueryScopeOfでスコープに変えてQueryScopeProviderに渡し、ブロックはその内側に置く。外側に置くと、ブロックは最初の描画でエラーを出す。ログイン前でuseMe()が 401 で失敗すると、ブロックの代わりにrenderErrorで再試行を出す。別の人がログインしてスコープが変わると、内側のツリーを描き直し、前の人のキャッシュを消す。 renderLinkは、ブロックがセッションへのリンクを描画するときに呼ばれる。URL はアプリが決める。ルーターを使うアプリなら、そのルーターのLinkを返す。loadingとrenderErrorはブロックごとに渡す。文言もアプリのものである。
実行
cd ui-demo
../node_modules/.bin/vite --host 127.0.0.1 --port 5173同じブラウザで Web コンソールにログインしてから http://127.0.0.1:5173 を開く。一覧にはログインした人が作ったセッションが表示され、セッション id を押すと /sessions/<id> で SessionDetail が、状態、待機中のリクエスト、次の指示の入力、使用量、セッションの制御、進行イベントを描画する。進行イベントはイベントストリームで続けて受け取る。
ログインしていない場合は、一覧の位置に renderError が描画したエラーと再試行ボタンが表示される。
ブロック
| ブロック | 描画するもの | 受け取るもの(ロード中·エラー以外) |
|---|---|---|
SessionList | ログインした人のセッション一覧。一ページずつ読み取る | なし |
RecentSessions | 最近のセッション数件 | label, limit |
WaitingSessions | 回答を待っているセッション | label |
SessionDetail | セッション一つの画面 | id, back, controls, unavailable, refusal |
ApprovalInbox | 回答を待っているセッションとそのリクエスト。ここからすぐに回答できる | unavailable, refusal |
unavailableは、できない操作(answer,control,send)とその理由である。名前を渡した操作は、理由とともに無効状態で描画する。サーバーはどちらの場合も権限で再度拒否する。refusalは、サーバーが拒否したコマンドのエラーを、アプリの文言に置き換える。- ブロックより小さい単位で使うには、
@werft-ai/reactの hook(useSessions,useSession,useSessionEvents,useSendMessage,useAnswer,useSessionControl)と@werft-ai/uiのコンポーネントを直接組み合わせる。
次のステップ
- 承認待ちと再開:
SessionDetailとApprovalInboxが回答するリクエストの種類