React SPA 整合
透過一個小型自訂 hook,把 Quravin SDK 接進 React 元件 — 單一客戶端實例、真正的 loading/error 狀態,不會在每次 render 時重新建立實例。
SDK 沒有專屬於 React 的建置版本 — 用的是任何純網頁都會用的同一個 CDN 客戶端。在 React app 中改變的,是客戶端實例存放在哪裡,以及如何把 loading/error 狀態暴露給元件。本篇涵蓋 這兩點。
1. 載入 SDK
import { Quravin } from "@quravin/sdk" 這種寫法目前還沒上公開的 npm registry — 如果您需要這條
路徑,請洽詢您的平台維運方。現階段,請用 純網頁整合 相同的方式載入:經典的 <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 app — 任何人都能從
打包後的程式碼中讀到它。您的 backend 會發行一把短效、每位使用者一個的 session JWT 交給頁面。
如果您還沒有 token 發行 endpoint,Node.js 伺服器整合
展示了如何建置一個(不論您 backend 用什麼語言,模式都相同 — 只有 endpoint 的實作方式不同)。
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. 只建立一次客戶端 — 不要在每次 render 時建立
一個常見的錯誤:在元件本體中直接 new window.Quravin.Quravin(...) 會在每次 render 時建立新的客戶端,
這會讓任何進行中的 onTokenExpired 重試邏輯失效。請使用 ref 或 module scope,只建立一次:
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;
}
實務上,多數 app 會在掛載時(或登入時)就取得一次 token,而不是等到第一次呼叫時才延遲取得 — 下方完整的 hook 就是這種做法。
4. 一個掌管執行狀態的小型 hook
元件不應該各自重新實作 loading/error/result 狀態。用同一個 hook,到處重複使用:
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 中取得一次 token 並儲存);上面的寫法
刻意保持精簡,讓您能清楚看到該把它接在哪裡。
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>
);
}
批次處理多筆輸入
若要對許多筆輸入執行同一個 pipeline(例如翻譯表格中的每一列),請使用 ai.runMany(...),
而不是逐一呼叫 ai.run() — 它會為您處理有限並發數與逐筆進度回報。SDK 更完整的 Integration
Guide(請洽詢您的平台維運方索取 — 它並非公開文件)記載了完整的選項格式
(concurrency、errorStrategy、onProgress)與每個 pipeline 的結果格式對照表。
錯誤處理
ai.run() 在 FAILED/CANCELED ticket 以及 HTTP 錯誤發生時都會 reject — 上面的 hook 已經把
兩者都導向 state.error。完整的狀態碼清單,以及每個狀態碼對已認證路徑代表的意義,請見
工具參考手冊 的 Errors 章節。