🎵 Music & Sound Design · artist bio writing

Press Kit Storyteller

Lovable AI extracts career milestones so publicists draft bios; Fish Audio captures it via real-time artist interviews.

Voice Loop Agent· live conversation
Section · Voice

The primitive.

full primer →

Musicians press a microphone button and hold a spoken exchange about artist bio writing — Fish Audio trantranscriptions the question, Lovable AI answers it, and Fish Audio speaks the answer back.

Why this primitivePublicists need to conduct natural, flowing interviews to extract authentic stories for biographies.

Kernel
a full spoken round trip — browser MediaRecorder captures the question, /v1/asr transcribes it, Lovable AI answers it, /v1/tts speaks the answer back
Drives the UI as
a microphone button that opens a back-and-forth spoken exchange with an assistant tuned for the discipline
Appendix · Secrets

Required key.

FISH_AUDIO_API_KEY
Single key for TTS, voice agents, transcription, and voice design. Free tier covers a hackathon weekend.
open ↗

Add this in your Lovable project under Settings → Secrets before pasting the prompt below.

Appendix · Mega-prompt

The build prompt.

Paste into a fresh Lovable project. Make sure the key above is set first. read the build strategy →

Build "Press Kit Storyteller" as a ONE-SHOT Lovable build. The participant has only
5 credits — this single message must produce a working demo with no follow-ups.
Single-page TanStack Start app. Cut scope ruthlessly.

CONCEPT
Lovable AI extracts career milestones so publicists draft bios; Fish Audio captures it via real-time artist interviews.
Discipline: Music & Sound Design (artist bio writing).
Recipe: Lovable AI brain + Voice Loop Agent (live conversation) as the voice surface.
Why this voice surface: Publicists need to conduct natural, flowing interviews to extract authentic stories for biographies.

LOVABLE BUDGET (HARD CAP: ONE-SHOT, ~5 CREDITS TOTAL):
The participant has FIVE Lovable credits for the whole build. This prompt MUST
ship a working demo on the FIRST message with zero follow-ups. Engineer for that.
- ONE TanStack Start app, ONE route (`src/routes/index.tsx`). No extra pages, no auth, no nav.
- TWO TanStack server functions max: one for the Lovable AI call (the brain),
  one for the Fish Audio call (the voice). Fold them into one if the voice
  primitive does not need server-side text generation.
- ONE client surface (a button, a mic, or a prompt box) wired to those server fns.
- NO database, NO Lovable Cloud, NO auth, NO file uploads, NO extra integrations.
- NO tests, NO docs pages, NO settings screens, NO theming toggles.
- Libraries: template defaults + `ai` + `@ai-sdk/openai-compatible` + `zod`.
  Fish Audio is plain `fetch` — do NOT install an audio SDK.
- Keep the diff small enough to land in one build pass. If a feature is not on
  screen in the user flow below, do not build it. Cut scope before adding scope.

STACK
- TanStack Start app, the index route only.
- Lovable AI Gateway (the brain) + Fish Audio (the voice). All calls live
  inside `createServerFn` handlers so both keys stay on the server.
- Client surface fits the kernel: a prompt box for narration and voice design,
  a mic button for the voice loop, a live caption strip for transcription.
  Render markdown from the brain with `react-markdown` if you show the text.
- Tailwind + shadcn. Editorial look: gold accent on a dark or warm-cream
  background, generous type, one strong headline, one primary action.
- Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14".

BRAIN — Lovable AI Gateway (free for participants, no key prompt needed):
```ts
// src/lib/ai-gateway.server.ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
export function gateway() {
  return createOpenAICompatible({
    name: "lovable",
    baseURL: "https://ai.gateway.lovable.dev/v1",
    headers: {
      "Lovable-API-Key": process.env.LOVABLE_API_KEY!,
      "X-Lovable-AIG-SDK": "vercel-ai-sdk",
    },
  });
}
```
Default model: `google/gemini-3-flash-preview`. Use `generateText` (or
`streamText` for long output) from `ai`. Keep prompts and model calls inside
the server function — never call the gateway from client code.

FISH AUDIO NOTES (read before writing the fetch call):
- Base URL `https://api.fish.audio`. Auth is `Authorization: Bearer <key>`.
- The TTS backend is chosen with a REQUEST HEADER, not a body field:
  `model: s2.1-pro`. (`s2.1-pro-free` is the same model at $0 for prototyping; `s1` is legacy.)
- `POST /v1/tts` takes JSON and returns RAW AUDIO BYTES, not JSON. Read it with
  `await res.arrayBuffer()` and base64 it with `Buffer.from(buf).toString("base64")`.
- Body fields: `text` (required), `format` ("mp3" | "wav" | "pcm" | "opus"),
  `mp3_bitrate` (64 | 128 | 192), `sample_rate`, `prosody: { speed, volume }`,
  and `reference_id` to speak in a specific voice model. Omit `reference_id`
  to use the default voice — that is the right call for a one-shot demo.
- `POST /v1/asr` takes `multipart/form-data` (`audio` file, optional `language`,
  `ignore_timestamps=false` for segment times) and returns JSON
  `{ text, duration, segments: [{ text, start, end }] }`.
- Errors come back as `{ "message": "...", "status": 401 }`. Always check
  `res.ok` and surface `res.status` plus a short slice of the body.

SERVER FUNCTION (src/lib/agent.functions.ts) — one spoken round trip:
mic audio in, transcript + spoken answer out. Fish Audio has no hosted agent
runtime, so the loop is ASR -> Lovable AI -> TTS, all in one server function.
```ts
import { createServerFn } from "@tanstack/react-start";
import { generateText } from "ai";
import { z } from "zod";
import { gateway } from "./ai-gateway.server";

/** Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 */
export const converse = createServerFn({ method: "POST" })
  .inputValidator((d) => z.object({
    audioBase64: z.string().min(1),      // webm/opus from MediaRecorder
    history: z.array(z.object({ role: z.enum(["user", "assistant"]), content: z.string() })).default([]),
  }).parse(d))
  .handler(async ({ data }) => {
    const key = process.env.FISH_AUDIO_API_KEY!;
    const bytes = Buffer.from(data.audioBase64, "base64");

    // 1. EARS — Fish Audio ASR. multipart/form-data, NOT JSON.
    const form = new FormData();
    form.append("audio", new Blob([bytes], { type: "audio/webm" }), "turn.webm");
    const asr = await fetch("https://api.fish.audio/v1/asr", {
      method: "POST",
      headers: { Authorization: `Bearer ${key}` },   // let fetch set the boundary
      body: form,
    });
    if (!asr.ok) throw new Error(`ASR failed: ${asr.status}`);
    const { text: heard } = (await asr.json()) as { text: string };

    // 2. BRAIN — Lovable AI answers as a artist bio writing expert, in conversation.
    const { text: reply } = await generateText({
      model: gateway()("google/gemini-3-flash-preview"),
      system: `You are a senior artist bio writing mentor in a spoken conversation. ` +
              `Reply in at most three sentences. Ask one focused follow-up question.`,
      messages: [...data.history, { role: "user", content: heard }],
    });

    // 3. MOUTH — Fish Audio speaks the reply.
    const tts = await fetch("https://api.fish.audio/v1/tts", {
      method: "POST",
      headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json", model: "s2.1-pro" },
      body: JSON.stringify({ text: reply, format: "mp3", mp3_bitrate: 128 }),
    });
    if (!tts.ok) throw new Error(`TTS failed: ${tts.status}`);
    const buf = await tts.arrayBuffer();
    return { heard, reply, audio: Buffer.from(buf).toString("base64") };
  });
```

CLIENT (no SDK — MediaRecorder is built into the browser):
```tsx
const start = async () => {
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const rec = new MediaRecorder(stream, { mimeType: "audio/webm" });
  const parts: Blob[] = [];
  rec.ondataavailable = (e) => parts.push(e.data);
  rec.onstop = async () => {
    const b64 = Buffer.from(await new Blob(parts).arrayBuffer()).toString("base64");
    const { heard, reply, audio } = await talk({ data: { audioBase64: b64, history } });
    setHistory((h) => [...h, { role: "user", content: heard }, { role: "assistant", content: reply }]);
    await new Audio(`data:audio/mpeg;base64,${audio}`).play();
  };
  rec.start();
  // stop on the second tap of the same button
};
```
Show the running transcript on screen so the demo reads well without sound.
Always request microphone permission from a user gesture or the browser blocks it.

TRANSLATION — included by default:
Add a language `<Select>` to the UI (English, Español, Français, Deutsch,
Português, हिंदी, 日本語). Before sending the AI output to Fish Audio, ask the
gateway to translate it into the chosen language in the same server function,
then pass the translated text to `/v1/tts`. Fish Audio's S2 models are
multilingual and detect the language from the text itself, so no extra
parameter is needed. Default the select to English so the demo works without a
click.

USER FLOW (the entire app — nothing else exists)
1. Land on the page; the headline previews what the demo does for artist bio writing.
2. The primary action (a microphone button that opens a back-and-forth spoken exchange with an assistant tuned for the discipline) is one tap away; the rest of the layout supports it.
3. Lovable AI does the thinking, Fish Audio handles the voice surface, and the
   result (audio + any text) stays on screen so the user can retry or share.

KEYS — both already provided to participants for free:
1. `LOVABLE_API_KEY` (the AI brain). Auto-injected in every Lovable project.
   Read it only on the server via `process.env.LOVABLE_API_KEY`. Never prefix
   with `VITE_` and never expose to the client.
2. `FISH_AUDIO_API_KEY` (the voice). Ask Lovable to store it as a project
   secret (Project Settings -> Secrets); grab the key from
   https://fish.audio/go-api. Read it only on the server and send it as
   `Authorization: Bearer ${process.env.FISH_AUDIO_API_KEY}`. Never prefix with
   `VITE_`, never call api.fish.audio from the browser.

CREDIT (must appear in UI footer AND as JSDoc on the server function):
Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Appendix · Market

Market sizing.

TAM
$11B
global music software market
SAM
$500M
artist marketing
SOM
$20M
independent musicians

Indicative figures for hackathon pitches — refine with your own research before raising.

See also

Adjacent entries.