Indian-language native, one key, one build.
Every mega-prompt in this repo uses the same pattern, because it's the only pattern that lets a Lovable account ship a Sarvam AI native demo in one shot — Sarvam-105B for the reasoning, one Sarvam primitive (Bulbul / Saaras / Mayura) for the user surface.
Why Sarvam AI and not a generic English LLM?
Sarvam-105B reasons IN 22 Indian languages, with wiki_grounding for Indian context. Bulbul v3 speaks Hindi, Tamil, Bengali, Telugu, Kannada, Malayalam, Marathi, Gujarati, Punjabi and Odia with 30+ natural speakers. Saaras v3 transcribes code-mixed Hindi/English and telephony audio. Mayura translates across all 22 Indian languages with script control — all behind one HTTP API and one subscription key.
The recipe
# 1. In your Lovable project, add the single required secret (Settings -> Secrets): SARVAM_API_KEY=sk_... # https://dashboard.sarvam.ai # Sarvam's auth header is "api-subscription-key" (NOT "Authorization: Bearer"). # 2. Copy a mega-prompt from this repo into Lovable. One paste: # - scaffolds the React + TanStack Start app # - writes a server function that proxies api.sarvam.ai # (Bulbul TTS / Saaras STT / Sarvam-105B chat / Mayura translate) # - wires the client surface (audio playback, mic recorder, chat, language switcher) # - includes the hackathon credit in the footer and in JSDoc on the server fn # 3. Hit play. Your demo is reasoning + speaking in an Indian language, natively.
1. Bulbul TTS server function
Keep the api-subscription-key on the server with a TanStack createServerFn, POST to /text-to-speech, and return the base64 WAV that Sarvam already gives you.
// src/lib/tts.functions.ts — TanStack server function for Sarvam Bulbul v2
// Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
export const speak = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({
text: z.string().min(1).max(2000),
languageCode: z.string().default("hi-IN"),
speaker: z.string().default("anushka"),
}).parse(d))
.handler(async ({ data }) => {
const r = await fetch("https://api.sarvam.ai/text-to-speech", {
method: "POST",
headers: {
"api-subscription-key": process.env.SARVAM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: data.text,
target_language_code: data.languageCode,
speaker: data.speaker,
model: "bulbul:v2",
enable_preprocessing: true,
}),
});
if (!r.ok) throw new Error(`Bulbul failed: ${r.status}`);
const { audios } = await r.json() as { audios: string[] };
return { audio: audios[0] }; // base64 WAV
});
2. Sarvam-105B as the brain
The OpenAI-compatible endpoint at api.sarvam.ai/v1 works with @ai-sdk/openai-compatible. Override the header to api-subscription-key; everything else (tools, streaming, structured output) behaves like a normal AI SDK provider.
// src/lib/sarvam.server.ts — Sarvam-105B via the OpenAI-compatible endpoint
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createServerFn } from "@tanstack/react-start";
import { generateText } from "ai";
import { z } from "zod";
export function sarvam() {
return createOpenAICompatible({
name: "sarvam",
baseURL: "https://api.sarvam.ai/v1",
headers: { "api-subscription-key": process.env.SARVAM_API_KEY! },
});
}
export const ask = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({
question: z.string().min(1),
languageCode: z.string().default("hi-IN"),
}).parse(d))
.handler(async ({ data }) => {
const { text } = await generateText({
model: sarvam()("sarvam-105b"),
system: `Answer IN ${data.languageCode}. Be specific, ground facts in Wikipedia.`,
prompt: data.question,
providerOptions: { sarvam: { wiki_grounding: true } },
});
return { text };
});
3. Mayura translation across 22 languages
// src/lib/translate.functions.ts — Mayura across 22 Indian languages
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
export const translate = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({
input: z.string().min(1),
source: z.string().default("en-IN"),
target: z.string(), // e.g. "ta-IN", "bn-IN", "hi-IN"
}).parse(d))
.handler(async ({ data }) => {
const r = await fetch("https://api.sarvam.ai/translate", {
method: "POST",
headers: {
"api-subscription-key": process.env.SARVAM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
input: data.input,
source_language_code: data.source,
target_language_code: data.target,
mode: "formal",
model: "mayura:v1",
}),
});
const { translated_text } = await r.json() as { translated_text: string };
return { translated_text };
});
Hackathon rules of thumb
- · One mega-prompt = one build message. Don't iterate the architecture, iterate the UI copy.
- · Keep the API key on the server. Browsers never touch
api-subscription-key. - · Default the language picker to
hi-INso the demo opens in Hindi without a click. - · Use a font stack that covers Devanagari, Tamil, Bengali, Telugu, etc. — system fonts on macOS/iOS/Android handle this; on Windows fall back to Noto Sans Indic.
- · Always show a microphone permission UX before starting Saaras recording, or browsers will silently block it.
- · Footer: "Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14".