← Entwickler-Anleitungen

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.