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.