← Guides pour développeurs

Intégration React SPA

Intégrez le SDK Quravin dans un composant React avec un petit hook personnalisé — une seule instance de client, un véritable état de chargement/erreur, aucune ré-instanciation à chaque rendu.

Le SDK n’a pas de build spécifique à React — c’est le même client CDN que n’importe quelle page web simple utilise. Ce qui change dans une application React, c’est où vit l’instance du client et comment vous exposez l’état de chargement/erreur aux composants. Ce guide couvre les deux.

1. Chargez le SDK

L’ergonomie import { Quravin } from "@quravin/sdk" n’est pas encore sur le registre npm public — demandez à l’opérateur de la plateforme si vous avez besoin de ce chemin. Pour l’instant, chargez-le de la même façon que le fait Intégration directe d’une page web simple : la balise classique <script src> fonctionne aussi dans une SPA bundlée — ajoutez-la à index.html, puis récupérez le client via window.Quravin.Quravin (le nom en double est maladroit ; une future version du SDK simplifiera cela).

<!-- index.html -->
<script src="https://js.quravin.com/v1.js"></script>

2. Obtenez un session token depuis votre propre backend

Même règle que pour tout code navigateur : ne mettez jamais une x-api-key statique dans une application React — n’importe qui peut la lire dans le bundle. Votre backend crée un session JWT de courte durée, propre à chaque utilisateur, et le transmet à la page. Si vous n’avez pas encore d’endpoint de création de token, Intégration serveur Node.js montre comment en construire un (le schéma est le même quel que soit le langage de votre backend — seule l’implémentation de l’endpoint diffère).

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. Créez le client une seule fois — pas à chaque rendu

Une erreur courante : instancier new window.Quravin.Quravin(...) dans le corps d’un composant crée un nouveau client à chaque rendu, ce qui interrompt toute logique de nouvelle tentative onTokenExpired en cours. Créez-le une seule fois, en utilisant un ref ou une portée de module :

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;
}

En pratique, la plupart des applications récupèrent le token une seule fois au montage (ou à la connexion) plutôt que paresseusement au premier appel — voir le hook complet ci-dessous pour une version qui fait cela.

4. Un petit hook qui possède l’état de l’appel

Les composants ne devraient pas chacun réimplémenter l’état loading/error/result. Un seul hook, réutilisé partout :

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() construit toujours le client paresseusement avec un sessionToken vide — remplacez par un véritable flux de création au montage (récupérez le token une fois dans un useEffect et stockez-le) une fois que vous avez câblé votre propre route /api/ai-token ; la forme ci-dessus est délibérément minimale afin qu’il soit évident où brancher cela.

5. Utilisez-le dans un composant

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>
  );
}

Traiter plusieurs entrées en lot

Pour exécuter le même pipeline sur de nombreuses entrées (par exemple traduire chaque ligne d’un tableau), utilisez ai.runMany(...) au lieu d’un appel ai.run() par élément — il gère pour vous la concurrence bornée et la progression par élément. Le guide d’intégration complet du SDK (demandez-en une copie à l’opérateur de la plateforme — il n’est pas public) documente la forme complète des options (concurrency, errorStrategy, onProgress) et le tableau de forme de résultat par pipeline.

Gestion des erreurs

ai.run() rejette sur les tickets FAILED/CANCELED et sur les erreurs HTTP — le hook ci-dessus route déjà les deux vers state.error. Voir la section Errors de Référence des outils pour la liste complète des codes de statut et ce que chacun signifie pour le chemin authentifié.