Aller au contenu

Next.js

Marquez chaque fichier IA que crée votre application Next.js avec une seule fonction utilitaire côté serveur. La clé reste sur le serveur, les gros fichiers et les vidéos sont pris en charge, et un fichier qui ne peut pas être marqué n’est jamais servi.

En bref

  • Où : un route handler ou une server action, jamais du code client.
  • Quoi : une seule fonction utilitaire, lib/kennmarq.ts, pour les images, les morceaux et les vidéos.
  • Champs : votre entreprise, votre application et vos marchés : les cinq éléments de la loi californienne, et un justificatif par fichier.
  • En cas d’échec : la requête échoue, et l’original non marqué n’est jamais stocké ni affiché.

1. La clé

Créez une clé dans le tableau de bord (démarrage rapide) et placez-la dans votre environnement : sur Vercel, dans les variables d’environnement du projet ; en local, dans .env.local.

.env.local
# .env.local: never NEXT_PUBLIC_, so it never reaches the browser
KENNMARQ_API_KEY=sk_test_…

2. La fonction utilitaire

Une seule fonction, mark(), pour les images, les morceaux et les vidéos. Les fichiers de plus de 4 Mo sont d’abord téléversés via un envoi, et une vidéo est marquée sous forme de tâche, dont la fonction attend la fin. Elle n’a besoin que de fetch.

lib/kennmarq.ts
// lib/kennmarq.ts: server code only (npm install server-only)
import "server-only";

const API = "https://api.kennmarq.com/v1";
const auth = { Authorization: `Bearer ${process.env.KENNMARQ_API_KEY}` };

// Your company and your AI system: with them, every mark carries California's five items.
const fields: Record<string, string> = {
  source: "created", // or "edited", "composite"
  provider: "Acme Inc.",
  app_name: "Acme Studio",
  app_version: "2.1",
  markets: "eu,us-ca",
};

async function failed(res: Response): Promise<never> {
  const { error } = await res.json();
  throw new Error(`Kennmarq.com ${res.status} ${error.code}: ${error.message}`);
}

/** The marked copy of an image, song or video. Throws rather than hand back the original. */
export async function mark(file: Blob, filename: string, extra: Record<string, string> = {}): Promise<Blob> {
  const form = new FormData();
  for (const [k, v] of Object.entries({ ...fields, ...extra })) form.append(k, v);
  // A request carries at most 4 MB: a bigger file goes up first.
  if (file.size < 3_900_000) form.append("file", file, filename);
  else form.append("upload", await upload(file));
  const res = await fetch(`${API}/mark`, { method: "POST", headers: auth, body: form });
  if (res.status === 202) return finished(await res.json()); // a video: marked as a job
  if (!res.ok) return failed(res);
  return res.blob();
}

async function upload(file: Blob): Promise<string> {
  const res = await fetch(`${API}/uploads`, {
    method: "POST",
    headers: { ...auth, "Content-Type": "application/json" },
    body: JSON.stringify({ bytes: file.size }),
  });
  if (!res.ok) return failed(res);
  const { id, url } = await res.json();
  // No key here: the URL is the permission. fetch sends the Content-Length it's signed for.
  const put = await fetch(url, { method: "PUT", body: file });
  if (!put.ok) throw new Error(`Kennmarq.com upload: ${put.status}`);
  return id;
}

async function finished(job: { url: string }): Promise<Blob> {
  for (;;) {
    await new Promise((r) => setTimeout(r, 5_000));
    const res = await fetch(job.url, { headers: auth });
    if (!res.ok) return failed(res);
    const m = await res.json();
    if (m.status === "failed") throw new Error(`Kennmarq.com: ${m.error?.code}: ${m.error?.message}`);
    if (m.status === "completed") return (await fetch(m.download.url)).blob();
  }
}

Vous préférez ne pas maintenir de fonction utilitaire ? Le SDK JavaScript fait la même chose en un seul appel, avec des types : npm install @kennmarq/sdk.

3. Marquez ce que crée votre modèle

Dans un route handler, juste après la génération :

app/api/generate/route.ts
// app/api/generate/route.ts
import { mark } from "@/lib/kennmarq";
import { generate } from "@/lib/model"; // your model: Replicate, fal, OpenAI, your own ComfyUI…

// Marking takes a second or two; leave room for a video, or for a slow first call.
export const maxDuration = 60;

export async function POST(request: Request) {
  const { prompt } = await request.json();
  const image: Blob = await generate(prompt);
  const marked = await mark(image, "image.png", { model: "FLUX.1 schnell" });
  // Store or return the marked copy only. If mark() threw, this request fails instead.
  return new Response(marked, { headers: { "Content-Type": marked.type || "image/png" } });
}

Ou dans une server action qui stocke le résultat :

app/actions.ts
// app/actions.ts
"use server";

import { put } from "@/lib/storage"; // S3, R2, Vercel Blob…
import { mark } from "@/lib/kennmarq";
import { generate } from "@/lib/model";

export async function createImage(prompt: string) {
  const marked = await mark(await generate(prompt), "image.png");
  return put(`images/${crypto.randomUUID()}.png`, marked); // never the unmarked original
}

Vidéos

Une vidéo est marquée sous forme de tâche : l’appel répond 202, et la fonction utilitaire interroge l’API toutes les cinq secondes jusqu’à ce qu’elle soit prête, en général au bout d’une minute ou deux. En production, laissez plutôt un webhook (mark.completed) vous prévenir, et gardez la requête courte.

Quand un appel échoue

  • 429 rate_limited : trop d’appels cette minute pour votre offre ; attendez le nombre de secondes indiqué par Retry-After.
  • 402 quota_exceeded : les marquages du mois sont épuisés ; passez à une offre supérieure ou attendez le 1er du mois.
  • 400 invalid_request : son message indique ce qui manque ; markets=us-ca exige provider, app_name et app_version.

Tous les codes figurent sur la page des erreurs. Pour voir le résultat, déposez une image marquée sur le vérificateur. Vous utilisez le Vercel AI SDK ? Son guide utilise la même fonction utilitaire.