React-SPA-Integration
Binden Sie das Quravin-SDK mit einem kleinen Custom Hook in eine React-Komponente ein — eine Client-Instanz, echter Loading-/Error-Zustand, keine Neu-Instanziierung bei jedem Render.
Das SDK hat keinen React-spezifischen Build — es ist derselbe CDN-Client, den jede einfache Webseite verwendet. Was sich in einer React-App ändert, ist wo die Client-Instanz lebt und wie Sie Komponenten Loading-/Error-Zustand zugänglich machen. Diese Anleitung behandelt beides.
1. Das SDK laden
Die import { Quravin } from "@quravin/sdk"-Ergonomie ist noch nicht im öffentlichen
npm-Registry verfügbar — fragen Sie Ihren Plattformbetreiber, falls Sie diesen Pfad benötigen.
Laden Sie es heute auf demselben Weg wie Integration für
einfache Webseiten: Das klassische <script src>-Tag
funktioniert auch innerhalb einer gebündelten SPA — fügen Sie es zu index.html hinzu und lesen
Sie den Client dann über window.Quravin.Quravin (der doppelte Name ist unschön; eine künftige
SDK-Version glättet das).
<!-- index.html -->
<script src="https://js.quravin.com/v1.js"></script>
2. Ein Session-Token von Ihrem eigenen Backend holen
Dieselbe Regel wie bei jedem Browser-Code: Setzen Sie niemals einen statischen x-api-key in
eine React-App — jeder kann ihn aus dem Bundle lesen. Ihr Backend erstellt ein kurzlebiges,
nutzerbezogenes Session-JWT und übergibt es der Seite. Falls Sie noch keinen
Token-Erstellungs-Endpunkt haben, zeigt Node.js-Server-Integration,
wie Sie einen bauen (das Muster ist unabhängig von der Sprache Ihres Backends dasselbe — nur die
Implementierung des Endpunkts unterscheidet sich).
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. Den Client einmal erstellen — nicht bei jedem Render
Ein häufiger Fehler: new window.Quravin.Quravin(...) innerhalb des Komponentenkörpers zu
instanziieren erzeugt bei jedem Render einen neuen Client, was jede laufende
onTokenExpired-Wiederholungslogik verwirft. Erstellen Sie ihn einmal, mit einem Ref oder
Modul-Scope:
import { useRef } from "react";
// window.Quravin ist das Global des CDN-Scripts (siehe Schritt 1) — es gibt noch keinen
// npm-Typ-Import, daher deklarieren wir nur so viel Form, wie dieser Hook braucht.
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, // die Env-Variablen-Konvention Ihres Bundlers
sessionToken: "", // Platzhalter — wird beim ersten fetchSessionToken() unten real gesetzt
onTokenExpired: fetchSessionToken,
});
}
return clientRef.current;
}
In der Praxis holen die meisten Apps das Token einmal beim Mount (oder beim Login), statt es beim ersten Aufruf lazy zu holen — siehe den vollständigen Hook unten für eine Version, die das tut.
4. Ein kleiner Hook, der den Run-Zustand verwaltet
Komponenten sollten nicht jeweils den Loading-/Error-/Result-Zustand neu implementieren. Ein Hook, überall wiederverwendet:
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() konstruiert den Client weiterhin lazy mit einem leeren sessionToken — ersetzen Sie
das durch einen echten Mint-on-Mount-Ablauf (holen Sie das Token einmal in einem useEffect und
speichern Sie es), sobald Sie Ihre eigene /api/ai-token-Route verdrahtet haben; die obige Form
ist bewusst minimal gehalten, damit klar ist, wo das eingesetzt wird.
5. In einer Komponente verwenden
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>
);
}
Mehrere Eingaben im Batch verarbeiten
Um dieselbe Pipeline über viele Eingaben hinweg auszuführen (z. B. jede Zeile einer Tabelle zu
übersetzen), verwenden Sie ai.runMany(...) statt eines ai.run()-Aufrufs pro Element — es
übernimmt begrenzte Nebenläufigkeit und Fortschritt pro Element für Sie. Der ausführlichere
Integration Guide des SDK (fragen Sie Ihren Plattformbetreiber nach einer Kopie — er ist nicht
öffentlich) dokumentiert die vollständige Optionsform (concurrency, errorStrategy,
onProgress) und die Pipeline-spezifische Ergebnisform-Tabelle.
Fehlerbehandlung
ai.run() lehnt bei FAILED/CANCELED-Tickets und bei HTTP-Fehlern ab — der obige Hook leitet
beides bereits in state.error um. Siehe den Errors-Abschnitt der
Tool-Referenz für die vollständige Liste der Statuscodes und was
jeder davon für den authentifizierten Pfad bedeutet.