ScaleRocket/Web

Ajouter des fonctionnalités IA

Intégrez Claude, OpenAI ou tout autre fournisseur d'IA dans votre SaaS avec les Edge Functions et le système de crédits.

Ajouter des fonctionnalités IA

ScaleRocket n'inclut pas de SDK IA spécifique — c'est intentionnel. Vos utilisateurs peuvent avoir besoin de génération de texte, d'analyse d'images, d'assistance au code, ou de rien du tout lié à l'IA. Ce guide vous montre comment ajouter n'importe quel fournisseur d'IA et le connecter au système de crédits existant.

Architecture

L'utilisateur clique "Générer" dans le dashboard
  → apps/app appelle une Edge Function
    → Edge Function déduit les crédits (use-credits)
    → Edge Function appelle l'API IA (Claude, OpenAI, etc.)
    → Retourne le résultat à l'utilisateur

Le pattern clé : les appels IA se font dans les Edge Functions, jamais côté client. Cela garde vos clés API sécurisées et vous permet de contrôler les coûts via les crédits.

Exemple : Claude SDK

1. Importer le SDK

Les Edge Functions tournent sur Deno, vous importez directement depuis npm :

// Pas besoin de npm install — Deno importe depuis npm
import Anthropic from "npm:@anthropic-ai/sdk";

2. Définir votre clé API

npx supabase secrets set ANTHROPIC_API_KEY=sk-ant-...

3. Créer une Edge Function

// supabase/functions/generate-text/index.ts
import { corsHeaders, supabaseAdmin } from "../_shared/supabase.ts";
import Anthropic from "npm:@anthropic-ai/sdk";

const anthropic = new Anthropic({
  apiKey: Deno.env.get("ANTHROPIC_API_KEY"),
});

Deno.serve(async (req) => {
  // Gérer le preflight CORS
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  try {
    // 1. Authentifier l'utilisateur
    const authHeader = req.headers.get("Authorization");
    if (!authHeader) {
      return new Response(
        JSON.stringify({ error: "Not authenticated" }),
        { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
      );
    }

    const token = authHeader.replace("Bearer ", "");
    const { data: { user }, error: authError } = await supabaseAdmin.auth.getUser(token);
    if (authError || !user) {
      return new Response(
        JSON.stringify({ error: "Invalid token" }),
        { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
      );
    }

    // 2. Parser la requête
    const { prompt } = await req.json();
    if (!prompt || typeof prompt !== "string") {
      return new Response(
        JSON.stringify({ error: "Missing prompt" }),
        { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
      );
    }

    // 3. Déduire les crédits (1 crédit par génération)
    const { data: credits, error: creditError } = await supabaseAdmin
      .from("credits")
      .update({ balance: supabaseAdmin.rpc("decrement_balance", { amount: 1 }) })
      .eq("user_id", user.id)
      .gte("balance", 1)
      .select()
      .single();

    if (creditError || !credits) {
      return new Response(
        JSON.stringify({ error: "Insufficient credits" }),
        { status: 402, headers: { ...corsHeaders, "Content-Type": "application/json" } }
      );
    }

    // 4. Appeler Claude
    const message = await anthropic.messages.create({
      model: "claude-sonnet-4-20250514",
      max_tokens: 1024,
      messages: [{ role: "user", content: prompt }],
    });

    const text = message.content[0].type === "text" ? message.content[0].text : "";

    // 5. Retourner le résultat
    return new Response(
      JSON.stringify({
        result: text,
        creditsRemaining: credits.balance,
      }),
      { headers: { ...corsHeaders, "Content-Type": "application/json" } }
    );

  } catch (error) {
    console.error("Generation error:", error);
    return new Response(
      JSON.stringify({ error: "Generation failed" }),
      { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
    );
  }
});

4. Appeler depuis le dashboard

// apps/app/src/lib/api.ts — ajoutez cette fonction
export async function generateText(prompt: string) {
  return callFunction<{ result: string; creditsRemaining: number }>(
    "generate-text",
    { method: "POST", body: { prompt } }
  );
}

5. Utiliser dans un composant

// apps/app/src/routes/_authenticated/generate.tsx
import { createFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import { Button, Card, CardHeader, CardTitle, CardContent } from "@saas/ui";
import { generateText } from "@/lib/api";

export const Route = createFileRoute("/_authenticated/generate")({
  component: GeneratePage,
});

function GeneratePage() {
  const [prompt, setPrompt] = useState("");
  const [result, setResult] = useState("");
  const [loading, setLoading] = useState(false);

  const handleGenerate = async () => {
    setLoading(true);
    const { data, error } = await generateText(prompt);
    setLoading(false);

    if (error) {
      alert(error);
      return;
    }

    setResult(data.result);
  };

  return (
    <div className="p-6 max-w-2xl">
      <Card>
        <CardHeader>
          <CardTitle>Générateur IA</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          <textarea
            value={prompt}
            onChange={(e) => setPrompt(e.target.value)}
            placeholder="Entrez votre prompt..."
            className="w-full rounded-md border p-3 min-h-[100px]"
          />
          <Button onClick={handleGenerate} isLoading={loading}>
            Générer (1 crédit)
          </Button>
          {result && (
            <div className="rounded-md bg-muted p-4 whitespace-pre-wrap">
              {result}
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
}

6. Déployer

npx supabase functions deploy generate-text

Utiliser OpenAI à la place

Le pattern est identique — changez juste le SDK :

// supabase/functions/generate-text/index.ts
import OpenAI from "npm:openai";

const openai = new OpenAI({
  apiKey: Deno.env.get("OPENAI_API_KEY"),
});

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: prompt }],
});

const text = response.choices[0].message.content;

Définir la clé :

npx supabase secrets set OPENAI_API_KEY=sk-...

Coûts en crédits

Vous pouvez varier le coût en crédits selon l'opération :

// Opération légère : 1 crédit
const CREDIT_COST = {
  "text-short": 1,    // Génération de texte court
  "text-long": 3,     // Contenu long format
  "image": 5,         // Génération d'image
  "analysis": 2,      // Analyse de document
};

// Déduire le montant approprié
const cost = CREDIT_COST[operationType];

Cela s'intègre naturellement au système de crédits existant de ScaleRocket — les utilisateurs achètent un plan avec une allocation mensuelle, et chaque opération IA déduit de leur solde.

Conseils de sécurité

  • Ne jamais exposer les clés API IA côté client — toujours passer par les Edge Functions
  • Toujours déduire les crédits avant d'appeler l'IA — si l'appel échoue, remboursez les crédits
  • Définir des limites d'usage — plafonnez le max_tokens pour éviter les requêtes coûteuses
  • Valider les entrées — sanitisez les prompts pour prévenir l'injection de prompt
  • Logger l'usage — suivez quels utilisateurs font combien de requêtes pour le monitoring des coûts

Étapes suivantes

Fini ? Marquez cette page comme terminée.

On this page