We’ll wire Recraft (SVG mode) straight into the app right now. Full-file drops below (server route + React UI). Paste them in and it’ll work the second you put in your keys.

1) Env keys (new)

Create/update your env files:

.env.local

REPLICATE_API_TOKEN=YOUR_REPLICATE_API_TOKEN
REPLICATE_RECRAFT_V3_VERSION=YOUR_RECRAFT_V3_MODEL_VERSION
# Example version string looks like: a1b2c3...  (from the model page on Replicate)


Why Replicate? It’s the fastest way to hit Recraft V3 with output_format: "svg". If you have Recraft’s native API later, we’ll drop it in as a second provider.

2) API route (Next.js App Router)

src/app/api/vector/recraft/route.ts

import { NextRequest, NextResponse } from "next/server";

/**
 * Recraft V3 via Replicate
 * Requires:
 *  - REPLICATE_API_TOKEN
 *  - REPLICATE_RECRAFT_V3_VERSION (model version id from Replicate)
 *
 * POST body:
 *   { "prompt": string }
 *
 * Returns:
 *   { "svgUrl": string, "predictionId": string }
 */

const REPLICATE_TOKEN = process.env.REPLICATE_API_TOKEN!;
const RECRAFT_VERSION = process.env.REPLICATE_RECRAFT_V3_VERSION!;

export async function POST(req: NextRequest) {
  try {
    if (!REPLICATE_TOKEN || !RECRAFT_VERSION) {
      return NextResponse.json(
        { error: "Server missing REPLICATE env vars" },
        { status: 500 }
      );
    }

    const { prompt } = await req.json();
    if (!prompt || typeof prompt !== "string") {
      return NextResponse.json({ error: "Missing prompt" }, { status: 400 });
    }

    // 1) Start a prediction
    const startRes = await fetch("https://api.replicate.com/v1/predictions", {
      method: "POST",
      headers: {
        Authorization: `Token ${REPLICATE_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        version: RECRAFT_VERSION,
        input: {
          prompt,
          output_format: "svg",
        },
      }),
    });

    if (!startRes.ok) {
      const t = await startRes.text();
      return NextResponse.json({ error: `Create failed: ${t}` }, { status: 502 });
    }

    const start = await startRes.json();
    const id: string = start.id;
    if (!id) {
      return NextResponse.json({ error: "No prediction id" }, { status: 502 });
    }

    // 2) Poll until done (simple server poll; OK for first pass)
    const timeoutAt = Date.now() + 90_000;
    let svgUrl: string | null = null;

    while (Date.now() < timeoutAt) {
      await sleep(1800);

      const stRes = await fetch(
        `https://api.replicate.com/v1/predictions/${id}`,
        { headers: { Authorization: `Token ${REPLICATE_TOKEN}` } }
      );
      if (!stRes.ok) {
        const t = await stRes.text();
        return NextResponse.json({ error: `Status failed: ${t}` }, { status: 502 });
      }
      const data = await stRes.json();

      if (data.status === "succeeded") {
        // Replicate typically returns array of URLs in output
        // Try to pick the .svg
        const out = data.output;
        if (Array.isArray(out)) {
          const candidate = out.find(
            (u: unknown) => typeof u === "string" && u.toLowerCase().endsWith(".svg")
          );
          if (candidate) svgUrl = candidate;
        } else if (typeof out === "string" && out.toLowerCase().endsWith(".svg")) {
          svgUrl = out;
        }
        break;
      }
      if (data.status === "failed") {
        return NextResponse.json({ error: "Generation failed" }, { status: 502 });
      }
    }

    if (!svgUrl) {
      return NextResponse.json({ error: "Timed out or missing SVG URL" }, { status: 504 });
    }

    return NextResponse.json({ svgUrl, predictionId: id });
  } catch (err: any) {
    return NextResponse.json(
      { error: err?.message ?? "Server error" },
      { status: 500 }
    );
  }
}

function sleep(ms: number) {
  return new Promise((r) => setTimeout(r, ms));
}

3) React component (BrandKit UI)

src/components/BrandKit/RecraftGenerator.tsx

"use client";
import React, { useState } from "react";

type GenState = "idle" | "running" | "done" | "error";

export default function RecraftGenerator() {
  const [prompt, setPrompt] = useState(
    "minimal monoline coffee bean icon, geometric, clean paths, no text, vector"
  );
  const [status, setStatus] = useState<GenState>("idle");
  const [error, setError] = useState<string | null>(null);
  const [svgUrl, setSvgUrl] = useState<string | null>(null);
  const [predictionId, setPredictionId] = useState<string | null>(null);

  async function onGenerate() {
    setStatus("running");
    setError(null);
    setSvgUrl(null);
    setPredictionId(null);

    try {
      const res = await fetch("/api/vector/recraft", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ prompt }),
      });

      const json = await res.json();
      if (!res.ok) throw new Error(json.error || "Generation failed");

      setSvgUrl(json.svgUrl);
      setPredictionId(json.predictionId);
      setStatus("done");
    } catch (e: any) {
      setError(e.message || "Something went wrong");
      setStatus("error");
    }
  }

  async function onDownload() {
    if (!svgUrl) return;
    const resp = await fetch(svgUrl);
    const svgText = await resp.text();

    const blob = new Blob([svgText], { type: "image/svg+xml;charset=utf-8" });
    const url = URL.createObjectURL(blob);

    const a = document.createElement("a");
    a.href = url;
    a.download = makeFileName(prompt);
    a.click();

    URL.revokeObjectURL(url);
  }

  return (
    <div className="w-full max-w-3xl space-y-4">
      <div className="space-y-2">
        <label className="block text-sm font-medium">Prompt</label>
        <textarea
          className="w-full rounded-lg border p-3 leading-relaxed"
          rows={3}
          value={prompt}
          onChange={(e) => setPrompt(e.target.value)}
          placeholder="Describe the icon/logo you want…"
        />
      </div>

      <div className="flex items-center gap-3">
        <button
          onClick={onGenerate}
          disabled={status === "running"}
          className="rounded-xl px-4 py-2 font-semibold text-white disabled:opacity-60"
          style={{ background: "#05445e" }}
        >
          {status === "running" ? "Generating…" : "Generate SVG"}
        </button>

        {svgUrl && (
          <button
            onClick={onDownload}
            className="rounded-xl px-4 py-2 font-semibold"
            style={{ border: "1px solid #05445e", color: "#05445e" }}
          >
            Download SVG
          </button>
        )}
      </div>

      {predictionId && (
        <p className="text-xs opacity-70">
          Prediction: <code>{predictionId}</code>
        </p>
      )}

      {error && (
        <div className="rounded-lg border border-red-300 bg-red-50 p-3 text-sm text-red-700">
          {error}
        </div>
      )}

      <div className="rounded-2xl border p-4">
        <div className="mb-2 text-sm opacity-70">Preview</div>
        <div
          className="flex items-center justify-center rounded-xl bg-white"
          style={{
            height: 280,
            border: "1px dashed #d0d7de",
          }}
        >
          {!svgUrl && status !== "running" && (
            <div className="text-sm opacity-60">No image yet</div>
          )}
          {status === "running" && (
            <div className="text-sm opacity-60">Working…</div>
          )}
          {svgUrl && (
            <img
              src={svgUrl}
              alt="SVG preview"
              style={{
                maxWidth: "100%",
                maxHeight: "100%",
                objectFit: "contain",
                display: "block",
              }}
            />
          )}
        </div>
        <div className="mt-2 text-xs opacity-70">
          The preview uses <code>object-fit: contain</code> so your logo never
          gets cropped. Download for the original SVG paths.
        </div>
      </div>
    </div>
  );
}

function makeFileName(prompt: string) {
  const base =
    prompt
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/(^-|-$)/g, "") || "vector";
  return `${base}.svg`;
}


Drop this component anywhere in your Brand Kit page, e.g. src/app/brandkit/page.tsx or inside your existing BrandKit screen.