diff --git a/.env.example b/.env.example index aa97fc8..c3c4ba7 100644 --- a/.env.example +++ b/.env.example @@ -13,3 +13,4 @@ ASSEMBLYAI_API_KEY="" ELEVENLABS_API_KEY="" SPEECHMATICS_API_KEY="" MISTRAL_API_KEY="" +SMALLEST_API_KEY="" diff --git a/prisma/seed.ts b/prisma/seed.ts index ed542ef..501fa2e 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -13,6 +13,7 @@ const providers = [ { name: "AssemblyAI", slug: "assemblyai", logoUrl: "/providers/assemblyai.svg" }, { name: "Speechmatics", slug: "speechmatics", logoUrl: "/providers/speechmatics.svg" }, { name: "Mistral", slug: "mistral", logoUrl: "/providers/mistral.svg" }, + { name: "Smallest AI", slug: "smallest", logoUrl: "/providers/smallest.svg" }, ]; const removedSlugs = ["soniox", "openai-whisper"]; diff --git a/public/providers/smallest.svg b/public/providers/smallest.svg new file mode 100644 index 0000000..c0d449a --- /dev/null +++ b/public/providers/smallest.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/lib/providers.ts b/src/lib/providers.ts index 24373e4..2283649 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -13,6 +13,7 @@ export const PROVIDERS: ProviderDef[] = [ { name: "AssemblyAI", slug: "assemblyai", logoUrl: "/providers/assemblyai.svg", color: "#0055FF", model: "Universal-3 Pro" }, { name: "Speechmatics", slug: "speechmatics", logoUrl: "/providers/speechmatics.svg", color: "#FF3D00", model: "Enhanced" }, { name: "Mistral", slug: "mistral", logoUrl: "/providers/mistral.svg", color: "#FF7000", model: "Voxtral Mini" }, + { name: "Smallest AI", slug: "smallest", logoUrl: "/providers/smallest.svg", color: "#1D4E52", model: "Pulse" }, ]; export function getProviderBySlug(slug: string): ProviderDef | undefined { diff --git a/src/lib/providers/smallest.ts b/src/lib/providers/smallest.ts new file mode 100644 index 0000000..1a0938c --- /dev/null +++ b/src/lib/providers/smallest.ts @@ -0,0 +1,60 @@ +import type { TranscribeResult, WordTimestamp } from "../transcribe"; + +const SMALLEST_BASE = "https://api.smallest.ai/waves/v1/stt"; + +interface SmallestWord { + word: string; + start: number; + end: number; + confidence?: number; +} + +interface SmallestResponse { + status: string; + transcription?: string; + words?: SmallestWord[]; +} + +export async function transcribeWithSmallest( + audio: Buffer, + _mimeType: string +): Promise { + const apiKey = process.env.SMALLEST_API_KEY; + if (!apiKey) throw new Error("SMALLEST_API_KEY not set"); + + const start = Date.now(); + + const url = new URL(SMALLEST_BASE); + url.searchParams.set("model", "pulse"); + url.searchParams.set("word_timestamps", "true"); + + const response = await fetch(url.toString(), { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/octet-stream", + }, + body: audio, + }); + + if (!response.ok) { + const body = await response.text(); + throw new Error(`HTTP ${response.status}: Smallest AI request failed: ${body}`); + } + + const data: SmallestResponse = await response.json(); + + const words: WordTimestamp[] = (data.words || []).map((w) => ({ + word: w.word, + start: w.start, + end: w.end, + })); + + const durationMs = Date.now() - start; + + return { + transcript: data.transcription ?? "", + words, + durationMs, + }; +} diff --git a/src/lib/transcribe.ts b/src/lib/transcribe.ts index 4430f71..9b55db0 100644 --- a/src/lib/transcribe.ts +++ b/src/lib/transcribe.ts @@ -5,6 +5,7 @@ import { transcribeWithAssemblyAI } from "./providers/assemblyai"; import { transcribeWithElevenLabs } from "./providers/elevenlabs"; import { transcribeWithSpeechmatics } from "./providers/speechmatics"; import { transcribeWithMistral } from "./providers/mistral"; +import { transcribeWithSmallest } from "./providers/smallest"; export interface WordTimestamp { word: string; @@ -28,6 +29,7 @@ const PROVIDER_MAP: Record = { elevenlabs: transcribeWithElevenLabs, speechmatics: transcribeWithSpeechmatics, mistral: transcribeWithMistral, + smallest: transcribeWithSmallest, }; const MAX_RETRIES = 2; @@ -151,6 +153,7 @@ function sanitizeError(message: string, slug: string): string { elevenlabs: "ElevenLabs", speechmatics: "Speechmatics", mistral: "Mistral", + smallest: "Smallest AI", }; const name = providerNames[slug] || slug;