PageSourceSearch

https://fishaudio.lovable.app/assets/strategy-DI7QEnR_.js

js fishaudio.lovable.app collected 2026-10-03 03:31:44 UTC 10,498 bytes, 90 lines download raw bytes

1import{r as e}from"./useRouter-Bbx_8wWX.js";import{t}from"./link-7PGWsBKZ.js";import{t as n}from"./site-shell-BKS6obED.js";import{t as r}from"./copy-button-kwADQFDW.js";var i=e(),a=`// src/lib/tts.functions.ts — TanStack server function that calls Fish Audio TTS
2// Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14
3import { createServerFn } from "@tanstack/react-start";
4import { z } from "zod";
5
6export const speak = createServerFn({ method: "POST" })
7  .inputValidator((d) => z.object({ text: z.string().min(1).max(4000) }).parse(d))
8  .handler(async ({ data }) => {
9    const r = await fetch("https://api.fish.audio/v1/tts", {
10      method: "POST",
11      headers: {
12        Authorization: \`Bearer \${process.env.FISH_AUDIO_API_KEY!}\`,
13        "Content-Type": "application/json",
14        model: "s2.1-pro", // backend is picked with a header, not a body field
15      },
16      body: JSON.stringify({ text: data.text, format: "mp3", mp3_bitrate: 128 }),
17    });
18    if (!r.ok) throw new Error(\`TTS failed: \${r.status}\`);
19    const buf = await r.arrayBuffer(); // raw audio bytes, not JSON
20    return { audio: Buffer.from(buf).toString("base64") };
21  });
22`,o=`// src/lib/agent.functions.ts — one spoken turn: ASR -> Lovable AI -> TTS
23import { createServerFn } from "@tanstack/react-start";
24import { generateText } from "ai";
25import { z } from "zod";
26import { gateway } from "./ai-gateway.server";
27
28export const converse = createServerFn({ method: "POST" })
29  .inputValidator((d) => z.object({ audioBase64: z.string() }).parse(d))
30  .handler(async ({ data }) => {
31    const key = process.env.FISH_AUDIO_API_KEY!;
32    const form = new FormData();
33    form.append("audio", new Blob([Buffer.from(data.audioBase64, "base64")]), "turn.webm");
34
35    // ears
36    const asr = await fetch("https://api.fish.audio/v1/asr", {
37      method: "POST",
38      headers: { Authorization: \`Bearer \${key}\` },
39      body: form,
40    });
41    const { text: heard } = await asr.json();
42
43    // brain
44    const { text: reply } = await generateText({
45      model: gateway()("google/gemini-3-flash-preview"),
46      prompt: heard,
47    });
48
49    // mouth
50    const tts = await fetch("https://api.fish.audio/v1/tts", {
51      method: "POST",
52      headers: { Authorization: \`Bearer \${key}\`, "Content-Type": "application/json", model: "s2.1-pro" },
53      body: JSON.stringify({ text: reply, format: "mp3" }),
54    });
55    return { heard, reply, audio: Buffer.from(await tts.arrayBuffer()).toString("base64") };
56  });
57`,s=`// src/lib/voice.functions.ts — invent a voice from a written brief
58import { createServerFn } from "@tanstack/react-start";
59import { z } from "zod";
60
61export const designVoice = createServerFn({ method: "POST" })
62  .inputValidator((d) => z.object({ instruction: z.string() }).parse(d))
63  .handler(async ({ data }) => {
64    const r = await fetch("https://api.fish.audio/v1/voice-design", {
65      method: "POST",
66      headers: {
67        Authorization: \`Bearer \${process.env.FISH_AUDIO_API_KEY!}\`,
68        "Content-Type": "application/json",
69        model: "voice-design-1",
70      },
71      body: JSON.stringify({
72        instruction: data.instruction, // "warm elder storyteller, slow, Lagos accent"
73        reference_text: "This is how the voice sounds in your project.",
74        language: "en",
75        n: 2,
76      }),
77    });
78    return await r.json(); // candidate voices, each with base64 audio
79  });
80`,c=`# 1. In your Lovable project, add the single required secret (Settings -> Secrets):
81FISH_AUDIO_API_KEY=...           # https://fish.audio/go-api
82
83# 2. Copy a mega-prompt from this repo into Lovable. One paste:
84#    - scaffolds the React + TanStack Start app
85#    - writes a server function that calls Fish Audio (TTS / ASR / voice design)
86#    - wires the client surface (<audio>, MediaRecorder, caption strip) for the chosen kernel
87#    - includes the hackathon credit in the footer and in JSDoc on the server fn
88
89# 3. Hit play. Your demo is speaking with a real Fish Audio voice.
90`;function l(){return(0,i.jsx)(n,{children:(0,i.jsxs)(`article`,{className:`max-w-3xl mx-auto px-5 pt-14 pb-20`,children:[(0,i.jsx)(`div`,{className:`eyebrow text-primary mb-4`,children:`build strategy · voice`}),(0,i.jsxs)(`h1`,{className:`font-display text-4xl sm:text-5xl md:text-6xl font-bold leading-[0.95] tracking-tight text-foreground`,children:[`Real voice, `,(0,i.jsx)(`span`,{className:`text-primary italic`,children:`one key`}),`, one build.`]}),(0,i.jsx)(`p`,{className:`mt-6 text-lg text-muted-foreground leading-relaxed font-light`,children:`Every mega-prompt in this repo uses the same pattern, because it's the only pattern that lets a Lovable account ship a real Fish Audio voice demo in one shot.`}),(0,i.jsxs)(`section`,{className:`mt-10 p-6 border border-primary/30 bg-card`,children:[(0,i.jsx)(`h2`,{className:`font-display text-xl font-semibold text-foreground italic`,children:`Why Fish Audio and not a stock browser voice?`}),(0,i.jsx)(`p`,{className:`mt-3 text-sm text-muted-foreground leading-relaxed font-light`,children:`The browser's built-in speechSynthesis sounds like 2007. Fish Audio gives you broadcast-grade multilingual voices, low-latency S2 streaming, timestamped speech-to-text, and voices you can design from a sentence or clone from a thirty-second clip — all behind one HTTP API and one secret. That is the difference between a demo and a presentation that actually opens with sound.`})]}),(0,i.jsxs)(`section`,{className:`mt-10`,children:[(0,i.jsx)(`h2`,{className:`font-display text-2xl font-semibold mb-3 text-foreground italic`,children:`The recipe`}),(0,i.jsxs)(`div`,{className:`flex items-baseline justify-between mb-2`,children:[(0,i.jsx)(`span`,{className:`eyebrow text-primary`,children:`recipe`}),(0,i.jsx)(r,{text:c,label:`Copy recipe`})]}),(0,i.jsx)(`pre`,{className:`whitespace-pre-wrap break-words [overflow-wrap:anywhere] font-mono text-[12px] sm:text-[13px] leading-relaxed p-4 sm:p-5 max-w-full overflow-x-hidden border border-border bg-card text-foreground
90/90`,children:c})]}),(0,i.jsxs)(`section`,{className:`mt-10`,children:[(0,i.jsx)(`h2`,{className:`font-display text-2xl font-semibold mb-3 text-foreground italic`,children:`1. The TTS server function`}),(0,i.jsxs)(`p`,{className:`text-sm text-muted-foreground mb-3 font-light`,children:[`Every prompt in the library follows the same shape: keep the API key on the server with a TanStack `,(0,i.jsx)(`code`,{children:`createServerFn`}),`, POST the text to Fish Audio, and return the raw audio bytes to the client as base64.`]}),(0,i.jsxs)(`div`,{className:`flex items-baseline justify-between mb-2`,children:[(0,i.jsx)(`span`,{className:`eyebrow text-primary`,children:`src/lib/tts.functions.ts`}),(0,i.jsx)(r,{text:a,label:`Copy`})]}),(0,i.jsx)(`pre`,{className:`whitespace-pre-wrap break-words [overflow-wrap:anywhere] font-mono text-[11px] sm:text-[12px] leading-relaxed p-4 sm:p-5 max-w-full overflow-x-hidden border border-border bg-card text-foreground/90`,children:a})]}),(0,i.jsxs)(`section`,{className:`mt-10`,children:[(0,i.jsx)(`h2`,{className:`font-display text-2xl font-semibold mb-3 text-foreground italic`,children:`2. The spoken loop`}),(0,i.jsxs)(`p`,{className:`text-sm text-muted-foreground mb-3 font-light`,children:[`Fish Audio has no hosted agent runtime, and it doesn't need one: chain`,(0,i.jsx)(`code`,{children:`/v1/asr`}),` → Lovable AI → `,(0,i.jsx)(`code`,{children:`/v1/tts`}),` inside a single server function and the browser only has to record and play.`]}),(0,i.jsxs)(`div`,{className:`flex items-baseline justify-between mb-2`,children:[(0,i.jsx)(`span`,{className:`eyebrow text-primary`,children:`src/lib/agent.functions.ts`}),(0,i.jsx)(r,{text:o,label:`Copy`})]}),(0,i.jsx)(`pre`,{className:`whitespace-pre-wrap break-words [overflow-wrap:anywhere] font-mono text-[11px] sm:text-[12px] leading-relaxed p-4 sm:p-5 max-w-full overflow-x-hidden border border-border bg-card text-foreground/90`,children:o})]}),(0,i.jsxs)(`section`,{className:`mt-10`,children:[(0,i.jsx)(`h2`,{className:`font-display text-2xl font-semibold mb-3 text-foreground italic`,children:`3. Voice design on demand`}),(0,i.jsxs)(`div`,{className:`flex items-baseline justify-between mb-2`,children:[(0,i.jsx)(`span`,{className:`eyebrow text-primary`,children:`src/lib/voice.functions.ts`}),(0,i.jsx)(r,{text:s,label:`Copy`})]}),(0,i.jsx)(`pre`,{className:`whitespace-pre-wrap break-words [overflow-wrap:anywhere] font-mono text-[11px] sm:text-[12px] leading-relaxed p-4 sm:p-5 max-w-full overflow-x-hidden border border-border bg-card text-foreground/90`,children:s})]}),(0,i.jsxs)(`section`,{className:`mt-10 p-6 border border-border bg-card`,children:[(0,i.jsx)(`h2`,{className:`font-display text-xl font-semibold text-foreground italic`,children:`Hackathon rules of thumb`}),(0,i.jsxs)(`ul`,{className:`mt-3 space-y-2 text-sm text-muted-foreground font-light`,children:[(0,i.jsx)(`li`,{children:`· One mega-prompt = one build message. Don't iterate the architecture, iterate the UI.`}),(0,i.jsxs)(`li`,{children:[`· Keep the API key on the server. Browsers never see the `,(0,i.jsx)(`code`,{children:`Authorization`}),` header.`]}),(0,i.jsxs)(`li`,{children:[`· Pick the backend with the `,(0,i.jsx)(`code`,{children:`model`}),` request header: `,(0,i.jsx)(`code`,{children:`s2.1-pro`}),` for production, `,(0,i.jsx)(`code`,{children:`s2.1-pro-free`}),` for the same model at $0 while prototyping, `,(0,i.jsx)(`code`,{children:`s1`}),` is legacy.`]}),(0,i.jsxs)(`li`,{children:[`· `,(0,i.jsx)(`code`,{children:`/v1/tts`}),` returns raw audio, not JSON. Read it with `,(0,i.jsx)(`code`,{children:`arrayBuffer()`}),`; `,(0,i.jsx)(`code`,{children:`res.json()`}),` will throw.`]}),(0,i.jsx)(`li`,{children:`· Always show a microphone permission UX before opening realtime hooks, or browsers will silently block them.`}),(0,i.jsx)(`li`,{children:`· Add a "Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14" line to your footer.`})]})]}),(0,i.jsxs)(`div`,{className:`mt-10 flex flex-wrap gap-3`,children:[(0,i.jsx)(t,{to:`/themes`,className:`px-5 py-2.5 bg-primary text-primary-foreground font-semibold hover:bg-foreground transition`,children:`Pick an i
90dea →`}),(0,i.jsx)(t,{to:`/quantum-primer`,className:`px-5 py-2.5 border border-primary/40 text-foreground hover:bg-primary hover:text-primary-foreground transition`,children:`Voice primer`})]})]})})}export{l as component};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.