React SPA 統合
小さなカスタムフックで Quravin SDK を React コンポーネントに組み込みます — クライアントインスタンスは 1 つ、実際の loading/error state を持ち、レンダーのたびに再インスタンス化しません。
SDK に React 専用のビルドはありません — プレーンな Web ページが使うのと同じ CDN クライアントです。 React アプリで変わるのは、クライアントインスタンスがどこに存在するかと loading/error state をどうコンポーネントに公開するかです。このガイドでは両方を扱います。
1. SDK を読み込む
import { Quravin } from "@quravin/sdk" というエルゴノミクスはまだ公開 npm レジストリには
載っていません — その経路が必要な場合はプラットフォームの運営者にお問い合わせください。
今日のところは、プレーンな Web ページ統合
と同じ方法で読み込みます:おなじみの <script src> タグは、バンドルされた SPA の中でも動作します
— index.html に追加し、window.Quravin.Quravin からクライアントを取得してください
(この二重の名前は少し扱いにくいですが、将来の SDK バージョンで解消される予定です)。
<!-- index.html -->
<script src="https://js.quravin.com/v1.js"></script>
2. 自分の backend から session token を取得する
どのブラウザコードでも同じルールです:静的な x-api-key を React アプリに絶対に含めないで
ください — バンドルから誰でも読み取れてしまいます。あなたの backend が短期の、ユーザーごとの
session JWT を発行してページに渡します。トークン発行エンドポイントがまだない場合は、Node.js
サーバー統合 にその作り方が示されています
(backend の言語が何であってもパターンは同じで、エンドポイントの実装だけが異なります)。
async function fetchSessionToken() {
const r = await fetch("/api/ai-token", { credentials: "include" });
if (!r.ok) throw new Error("Token fetch failed");
return (await r.json()).token;
}
3. クライアントを一度だけ作成する — レンダーのたびに作らない
よくある間違い:コンポーネント本体の中で new window.Quravin.Quravin(...) をインスタンス化すると、
レンダーのたびに新しいクライアントが作られ、進行中の onTokenExpired リトライロジックが
失われます。ref かモジュールスコープを使って一度だけ作成してください。
import { useRef } from "react";
// window.Quravin is the CDN script's global (see Step 1) — there's no npm type
// import yet, so declare just enough shape for what this hook uses.
type QuravinClient = { run(args: { pipeline: string; inputs: Record<string, unknown> }): Promise<unknown> };
declare global {
interface Window {
Quravin: {
Quravin: new (opts: {
endpoint: string;
sessionToken: string;
onTokenExpired: () => Promise<string>;
}) => QuravinClient;
};
}
}
function useQuravinClient() {
const clientRef = useRef<QuravinClient | null>(null);
if (!clientRef.current) {
clientRef.current = new window.Quravin.Quravin({
endpoint: import.meta.env.VITE_QURAVIN_API_BASE, // your bundler's env-var convention
sessionToken: "", // placeholder — set for real on first fetchSessionToken() below
onTokenExpired: fetchSessionToken,
});
}
return clientRef.current;
}
実際には、ほとんどのアプリはトークンを、呼び出しのたびに遅延取得するのではなく、マウント時 (またはログイン時)に一度だけ取得します — それを行うバージョンについては下記の完全なフックを 参照してください。
4. 実行状態を保持する小さなフック
各コンポーネントがそれぞれ loading/error/result の state を再実装すべきではありません。1 つの フックをどこでも再利用します。
import { useCallback, useRef, useState } from "react";
type RunState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "error"; error: string }
| { status: "done"; result: T };
export function useQuravin() {
const clientRef = useRef<QuravinClient | null>(null);
const getClient = useCallback(() => {
if (!clientRef.current) {
clientRef.current = new window.Quravin.Quravin({
endpoint: import.meta.env.VITE_QURAVIN_API_BASE,
sessionToken: "",
onTokenExpired: fetchSessionToken,
});
}
return clientRef.current;
}, []);
const [state, setState] = useState<RunState<unknown>>({ status: "idle" });
const run = useCallback(
async (pipeline: string, inputs: Record<string, unknown>) => {
setState({ status: "loading" });
try {
const result = await getClient().run({ pipeline, inputs });
setState({ status: "done", result });
return result;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setState({ status: "error", error: message });
throw err;
}
},
[getClient],
);
return { state, run };
}
getClient() は空の sessionToken でクライアントを遅延構築します — 自分の /api/ai-token
ルートを配線し終えたら、実際のマウント時発行フロー(useEffect の中で一度トークンを取得して
保存する)に差し替えてください。上記の形はどこにそれを組み込めばよいかが分かりやすいよう、
意図的に最小限にしてあります。
5. コンポーネントで使う
function TranslateBox() {
const { state, run } = useQuravin();
const [text, setText] = useState("");
const onTranslate = () =>
run("translate-string", { text, target_language: "de" }).catch(() => {
/* state.error already holds the message */
});
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={onTranslate} disabled={state.status === "loading"}>
Translate
</button>
{state.status === "error" && <p role="alert">{state.error}</p>}
{state.status === "done" && <p>{(state.result as { translation: string }).translation}</p>}
</div>
);
}
複数の入力をバッチ処理する
同じパイプラインを多数の入力にわたって実行する場合(例えばテーブルの全行を翻訳する場合)は、
入力ごとに ai.run() を 1 回呼ぶ代わりに ai.runMany(...) を使ってください — 並行数の上限と
項目ごとの進捗をあなたの代わりに処理してくれます。SDK のより詳しい Integration Guide(公開されて
いないため、必要な場合はプラットフォームの運営者にコピーを依頼してください)に、オプションの
完全な形(concurrency、errorStrategy、onProgress)とパイプラインごとの結果形式のテーブルが
記載されています。
エラー処理
ai.run() は FAILED/CANCELED チケットと HTTP エラーの両方で reject します — 上記のフックは
すでにその両方を state.error にルーティングしています。認証済みの経路が返しうるステータス
コードの完全な一覧については、ツールリファレンス の Errors
セクションを参照してください。