From abde158e34402daed0a01819d0480f1d3856edcb Mon Sep 17 00:00:00 2001 From: Jack Merrill Date: Mon, 5 May 2025 13:52:17 -0400 Subject: [PATCH] i dont know what im doing at this point man --- components/TTSProvider.tsx | 192 ++----- public/workers/kokoro-worker.js | 1 - supabase/config.toml | 11 + supabase/functions/generate-tts/.npmrc | 3 + supabase/functions/generate-tts/deno.json | 3 + supabase/functions/generate-tts/deno.lock | 664 ++++++++++++++++++++++ supabase/functions/generate-tts/index.ts | 72 +++ 7 files changed, 786 insertions(+), 160 deletions(-) create mode 100644 supabase/functions/generate-tts/.npmrc create mode 100644 supabase/functions/generate-tts/deno.json create mode 100644 supabase/functions/generate-tts/deno.lock create mode 100644 supabase/functions/generate-tts/index.ts diff --git a/components/TTSProvider.tsx b/components/TTSProvider.tsx index d3072e9..c00ddff 100644 --- a/components/TTSProvider.tsx +++ b/components/TTSProvider.tsx @@ -1,4 +1,5 @@ "use client"; +import { createClient } from "@/utils/supabase/client"; import React, { createContext, useContext, @@ -25,7 +26,6 @@ function splitIntoSentences(text: string): string[] { interface TTSContextType { sentences: string[]; currentSentence: number; - ttsBuffer: (string | null)[]; voices: any[]; selectedSpeaker: string; status: "ready" | "running" | null; @@ -48,6 +48,7 @@ export const TTSProvider = ({ pages: string[]; children: ReactNode; }) => { + const supabase = createClient(); // Combine pages and split into sentences. const fullText = pages.join("\n"); const sentences = splitIntoSentences(fullText).filter( @@ -55,169 +56,48 @@ export const TTSProvider = ({ ); const [currentSentence, setCurrentSentence] = useState(0); - const [ttsBuffer, setTtsBuffer] = useState<(string | null)[]>( - Array(sentences.length).fill(null) - ); const audioRef = useRef(null); - // Create a reference to the worker object. - const worker = useRef(null); - const [selectedSpeaker, setSelectedSpeaker] = useState("af_heart"); - - const [playing, setPlaying] = useState(false); - const [sentence, setSentence] = useState(); const [voices, setVoices] = useState([]); - const [status, setStatus] = useState<"ready" | "running" | null>(null); - const [error, setError] = useState(null); - const [loadingMessage, setLoadingMessage] = useState("Loading..."); + const [status, setStatus] = useState<"ready" | "running" | null>("ready"); - const [results, setResults] = useState<{ text: string; src: string }[]>([]); - - async function generateTTSForIndex( - sentence: string, - index: number - ): Promise { - const key = `tts-${index}`; - const cached = localStorage.getItem(key); - if (cached) { - return cached; - } - - return new Promise((resolve, reject) => { - const handleMessage = (e: MessageEvent) => { - if (e.data.index !== index) return; // Ignore messages for other indices - - if (e.data.status === "complete") { - localStorage.setItem(key, e.data.audio); - worker.current!.removeEventListener("message", handleMessage); // Clean up listener - resolve(e.data.audio); - } else if (e.data.status === "error") { - worker.current!.removeEventListener("message", handleMessage); // Clean up listener - toast.error(`Error generating audio: ${e.data.error}`); - reject(e.data.error); - } - }; - - worker.current!.addEventListener("message", handleMessage); - - worker.current!.postMessage({ - type: "generate", - index, - text: sentence, - voice: selectedSpeaker, + async function generateTTS(sentence: string, index: number): Promise { + try { + const { data, error } = await supabase.functions.invoke("generate-tts", { + body: { + text: sentence, + voice: selectedSpeaker, + index, + }, }); - }); + + setStatus("running"); + + const { audioUrl } = data as { audioUrl: string }; + return audioUrl; + } catch (error) { + console.error("Error generating TTS:", error); + toast.error("Failed to generate TTS. Please try again."); + throw error; + } } - // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted. - useEffect(() => { - console.log("Initializing worker..."); - worker.current ??= new Worker("/workers/kokoro-worker.js", { - type: "module", - }); - - console.log("Worker initialized"); - - const onMessageReceived = (e: any) => { - switch (e.data.status) { - case "device": - setLoadingMessage(`Loading model (device="${e.data.device}")`); - break; - case "ready": - setStatus("ready"); - setVoices(e.data.voices); - break; - case "error": - setError(e.data.data); - break; - case "complete": - const { audio, text } = e.data; - setResults((prev) => [{ text, src: audio }, ...prev]); - setStatus("ready"); - break; - } - }; - - const onErrorReceived = (e: any) => { - console.error("Worker error:", e); - setError(e.message); - }; - - worker.current.addEventListener("message", onMessageReceived); - worker.current.addEventListener("error", onErrorReceived); - - return () => { - worker.current!.removeEventListener("message", onMessageReceived); - worker.current!.removeEventListener("error", onErrorReceived); - }; - }, []); - - // Pre-buffer current and next 5 sentences. - useEffect(() => { - let isCancelled = false; - - async function preloadBuffer() { - const newBuffer = [...ttsBuffer]; - const end = Math.min(sentences.length, currentSentence + 5); // Preload 5 sentences ahead - - for (let i = currentSentence; i < end; i++) { - if (isCancelled) break; - if (!newBuffer[i]) { - console.log("Preloading TTS for sentence:", i, sentences[i]); - try { - newBuffer[i] = await generateTTSForIndex( - removeMarkdown(sentences[i]), - i - ); - } catch (error) { - console.error("Error preloading TTS:", error); - } - } - } - - if (!isCancelled) { - setTtsBuffer((prev) => { - // Only update state if the buffer has changed - if (JSON.stringify(prev) !== JSON.stringify(newBuffer)) { - return newBuffer; - } - return prev; - }); - } - } - - preloadBuffer(); - - return () => { - isCancelled = true; // Cancel preloading if the component unmounts or dependencies change - }; - }, [currentSentence, sentences]); - const playSentence = async (index: number) => { - if (index === currentSentence) return; // Prevent redundant updates setCurrentSentence(index); - let audioUrl = ttsBuffer[index]; - if (!audioUrl) { - audioUrl = await generateTTSForIndex( - removeMarkdown(sentences[index]), - index - ); - setTtsBuffer((prev) => { - const updated = [...prev]; - updated[index] = audioUrl; - return updated; - }); - } - - if (audioRef.current) { - audioRef.current.src = audioUrl; - await new Promise((res) => { - audioRef.current!.play(); - - audioRef.current!.onended = () => res(true); - }); + const sentence = removeMarkdown(sentences[index]); + try { + const audioUrl = await generateTTS(sentence, index); + if (audioRef.current) { + audioRef.current.src = audioUrl; + await new Promise((res) => { + audioRef.current!.play(); + audioRef.current!.onended = () => res(true); + }); + } + } catch (error) { + console.error("Error playing sentence:", error); } }; @@ -228,9 +108,7 @@ export const TTSProvider = ({ const playInOrder = async (index: number) => { if (index < 0 || index >= sentences.length) return; - if (index === currentSentence && playing) return; // Prevent redundant playback setCurrentSentence(index); - setPlaying(true); for (let i = index; i < sentences.length; i++) { console.log("Playing sentence:", i, sentences[i]); @@ -241,8 +119,6 @@ export const TTSProvider = ({ break; // Stop playback on error } } - - setPlaying(false); }; const pause = () => { @@ -267,7 +143,6 @@ export const TTSProvider = ({ const value: TTSContextType = { sentences, currentSentence, - ttsBuffer, voices, playSentence, skipToSentence, @@ -284,7 +159,6 @@ export const TTSProvider = ({ return ( {children} - {/* Hidden audio element used for playback */} ); diff --git a/public/workers/kokoro-worker.js b/public/workers/kokoro-worker.js index dd9e267..323ebeb 100644 --- a/public/workers/kokoro-worker.js +++ b/public/workers/kokoro-worker.js @@ -52,7 +52,6 @@ self.addEventListener("message", async (e) => { // Push the text to the splitter splitter.push(text); - splitter.push(""); // Signal the end of the text // Process the stream and include the correct index for await (const { text: processedText, phonemes, audio } of stream) { diff --git a/supabase/config.toml b/supabase/config.toml index 705a63e..2691649 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -306,3 +306,14 @@ s3_region = "env(S3_REGION)" s3_access_key = "env(S3_ACCESS_KEY)" # Configures AWS_SECRET_ACCESS_KEY for S3 bucket s3_secret_key = "env(S3_SECRET_KEY)" + +[functions.generate-tts] +enabled = true +verify_jwt = true +import_map = "./functions/generate-tts/deno.json" +# Uncomment to specify a custom file path to the entrypoint. +# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx +entrypoint = "./functions/generate-tts/index.ts" +# Specifies static files to be bundled with the function. Supports glob patterns. +# For example, if you want to serve static HTML pages in your function: +# static_files = [ "./functions/generate-tts/*.html" ] diff --git a/supabase/functions/generate-tts/.npmrc b/supabase/functions/generate-tts/.npmrc new file mode 100644 index 0000000..48c6388 --- /dev/null +++ b/supabase/functions/generate-tts/.npmrc @@ -0,0 +1,3 @@ +# Configuration for private npm package dependencies +# For more information on using private registries with Edge Functions, see: +# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries diff --git a/supabase/functions/generate-tts/deno.json b/supabase/functions/generate-tts/deno.json new file mode 100644 index 0000000..f6ca845 --- /dev/null +++ b/supabase/functions/generate-tts/deno.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/supabase/functions/generate-tts/deno.lock b/supabase/functions/generate-tts/deno.lock new file mode 100644 index 0000000..502fad9 --- /dev/null +++ b/supabase/functions/generate-tts/deno.lock @@ -0,0 +1,664 @@ +{ + "version": "4", + "specifiers": { + "jsr:@supabase/functions-js@*": "2.4.4", + "npm:@mistralai/mistralai@*": "1.6.0_zod@3.24.4", + "npm:@types/node@*": "22.12.0", + "npm:kokoro-js@*": "1.2.1", + "npm:kokoro-tts@*": "0.0.1-security", + "npm:openai@^4.52.5": "4.97.0", + "npm:p-limit@*": "6.2.0" + }, + "jsr": { + "@supabase/functions-js@2.4.4": { + "integrity": "38456509a6e22fb116b118464cbb36357256f9048d3580632b63af91f63769f7", + "dependencies": [ + "npm:openai" + ] + } + }, + "npm": { + "@emnapi/runtime@1.4.3": { + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "dependencies": [ + "tslib" + ] + }, + "@huggingface/jinja@0.4.1": { + "integrity": "sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw==" + }, + "@huggingface/transformers@3.5.1": { + "integrity": "sha512-qWsPoJMBPYcrGuzRMVL//3dwcLXED9r+j+Hzw+hZpBfrMPdyq57ehNs7lew0D34Paj0DO0E0kZQjOZcIVN17hQ==", + "dependencies": [ + "@huggingface/jinja", + "onnxruntime-node", + "onnxruntime-web", + "sharp" + ] + }, + "@img/sharp-darwin-arm64@0.34.1": { + "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==", + "dependencies": [ + "@img/sharp-libvips-darwin-arm64" + ] + }, + "@img/sharp-darwin-x64@0.34.1": { + "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==", + "dependencies": [ + "@img/sharp-libvips-darwin-x64" + ] + }, + "@img/sharp-libvips-darwin-arm64@1.1.0": { + "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==" + }, + "@img/sharp-libvips-darwin-x64@1.1.0": { + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==" + }, + "@img/sharp-libvips-linux-arm64@1.1.0": { + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==" + }, + "@img/sharp-libvips-linux-arm@1.1.0": { + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==" + }, + "@img/sharp-libvips-linux-ppc64@1.1.0": { + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==" + }, + "@img/sharp-libvips-linux-s390x@1.1.0": { + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==" + }, + "@img/sharp-libvips-linux-x64@1.1.0": { + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==" + }, + "@img/sharp-libvips-linuxmusl-arm64@1.1.0": { + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==" + }, + "@img/sharp-libvips-linuxmusl-x64@1.1.0": { + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==" + }, + "@img/sharp-linux-arm64@0.34.1": { + "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==", + "dependencies": [ + "@img/sharp-libvips-linux-arm64" + ] + }, + "@img/sharp-linux-arm@0.34.1": { + "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==", + "dependencies": [ + "@img/sharp-libvips-linux-arm" + ] + }, + "@img/sharp-linux-s390x@0.34.1": { + "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==", + "dependencies": [ + "@img/sharp-libvips-linux-s390x" + ] + }, + "@img/sharp-linux-x64@0.34.1": { + "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==", + "dependencies": [ + "@img/sharp-libvips-linux-x64" + ] + }, + "@img/sharp-linuxmusl-arm64@0.34.1": { + "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==", + "dependencies": [ + "@img/sharp-libvips-linuxmusl-arm64" + ] + }, + "@img/sharp-linuxmusl-x64@0.34.1": { + "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==", + "dependencies": [ + "@img/sharp-libvips-linuxmusl-x64" + ] + }, + "@img/sharp-wasm32@0.34.1": { + "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==", + "dependencies": [ + "@emnapi/runtime" + ] + }, + "@img/sharp-win32-ia32@0.34.1": { + "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==" + }, + "@img/sharp-win32-x64@0.34.1": { + "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==" + }, + "@isaacs/fs-minipass@4.0.1": { + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dependencies": [ + "minipass" + ] + }, + "@mistralai/mistralai@1.6.0_zod@3.24.4": { + "integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==", + "dependencies": [ + "zod", + "zod-to-json-schema" + ] + }, + "@protobufjs/aspromise@1.1.2": { + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64@1.1.2": { + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen@2.0.4": { + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter@1.1.0": { + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch@1.1.0": { + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": [ + "@protobufjs/aspromise", + "@protobufjs/inquire" + ] + }, + "@protobufjs/float@1.0.2": { + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire@1.1.0": { + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path@1.1.2": { + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool@1.1.0": { + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8@1.1.0": { + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@types/node-fetch@2.6.12": { + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": [ + "@types/node@22.12.0", + "form-data" + ] + }, + "@types/node@18.19.87": { + "integrity": "sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==", + "dependencies": [ + "undici-types@5.26.5" + ] + }, + "@types/node@22.12.0": { + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "dependencies": [ + "undici-types@6.20.0" + ] + }, + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": [ + "event-target-shim" + ] + }, + "agentkeepalive@4.6.0": { + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dependencies": [ + "humanize-ms" + ] + }, + "asynckit@0.4.0": { + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "boolean@3.2.0": { + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" + }, + "call-bind-apply-helpers@1.0.2": { + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": [ + "es-errors", + "function-bind" + ] + }, + "chownr@3.0.0": { + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string@1.9.1": { + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": [ + "color-name", + "simple-swizzle" + ] + }, + "color@4.2.3": { + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": [ + "color-convert", + "color-string" + ] + }, + "combined-stream@1.0.8": { + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": [ + "delayed-stream" + ] + }, + "define-data-property@1.1.4": { + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": [ + "es-define-property", + "es-errors", + "gopd" + ] + }, + "define-properties@1.2.1": { + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": [ + "define-data-property", + "has-property-descriptors", + "object-keys" + ] + }, + "delayed-stream@1.0.0": { + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "detect-libc@2.0.4": { + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==" + }, + "detect-node@2.1.0": { + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "dunder-proto@1.0.1": { + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": [ + "call-bind-apply-helpers", + "es-errors", + "gopd" + ] + }, + "es-define-property@1.0.1": { + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors@1.3.0": { + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms@1.1.1": { + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": [ + "es-errors" + ] + }, + "es-set-tostringtag@2.1.0": { + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": [ + "es-errors", + "get-intrinsic", + "has-tostringtag", + "hasown" + ] + }, + "es6-error@4.1.1": { + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "escape-string-regexp@4.0.0": { + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "flatbuffers@25.2.10": { + "integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw==" + }, + "form-data-encoder@1.7.2": { + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "form-data@4.0.2": { + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": [ + "asynckit", + "combined-stream", + "es-set-tostringtag", + "mime-types" + ] + }, + "formdata-node@4.4.1": { + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": [ + "node-domexception", + "web-streams-polyfill" + ] + }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic@1.3.0": { + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "es-errors", + "es-object-atoms", + "function-bind", + "get-proto", + "gopd", + "has-symbols", + "hasown", + "math-intrinsics" + ] + }, + "get-proto@1.0.1": { + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": [ + "dunder-proto", + "es-object-atoms" + ] + }, + "global-agent@3.0.0": { + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dependencies": [ + "boolean", + "es6-error", + "matcher", + "roarr", + "semver", + "serialize-error" + ] + }, + "globalthis@1.0.4": { + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": [ + "define-properties", + "gopd" + ] + }, + "gopd@1.2.0": { + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "guid-typescript@1.0.9": { + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==" + }, + "has-property-descriptors@1.0.2": { + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": [ + "es-define-property" + ] + }, + "has-symbols@1.1.0": { + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag@1.0.2": { + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": [ + "has-symbols" + ] + }, + "hasown@2.0.2": { + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": [ + "function-bind" + ] + }, + "humanize-ms@1.2.1": { + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": [ + "ms" + ] + }, + "is-arrayish@0.3.2": { + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "json-stringify-safe@5.0.1": { + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "kokoro-js@1.2.1": { + "integrity": "sha512-oq0HZJWis3t8lERkMJh84WLU86dpYD0EuBPtqYnLlQzyFP1OkyBRDcweAqCfhNOpltyN9j/azp1H6uuC47gShw==", + "dependencies": [ + "@huggingface/transformers", + "phonemizer" + ] + }, + "kokoro-tts@0.0.1-security": { + "integrity": "sha512-VK7ILDqP0J88kiXdFrq7ymugipw42xLGmAU3kqpyFwJKlBqcsiTXVbvdTtehwYhYPw5QKJPmN2j7tD6fhPe6qw==" + }, + "long@5.3.2": { + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "matcher@3.0.0": { + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dependencies": [ + "escape-string-regexp" + ] + }, + "math-intrinsics@1.1.0": { + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db" + ] + }, + "minipass@7.1.2": { + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "minizlib@3.0.2": { + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dependencies": [ + "minipass" + ] + }, + "mkdirp@3.0.1": { + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node-domexception@1.0.0": { + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch@2.7.0": { + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": [ + "whatwg-url" + ] + }, + "object-keys@1.1.1": { + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "onnxruntime-common@1.21.0": { + "integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==" + }, + "onnxruntime-common@1.22.0-dev.20250409-89f8206ba4": { + "integrity": "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==" + }, + "onnxruntime-node@1.21.0": { + "integrity": "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==", + "dependencies": [ + "global-agent", + "onnxruntime-common@1.21.0", + "tar" + ] + }, + "onnxruntime-web@1.22.0-dev.20250409-89f8206ba4": { + "integrity": "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==", + "dependencies": [ + "flatbuffers", + "guid-typescript", + "long", + "onnxruntime-common@1.22.0-dev.20250409-89f8206ba4", + "platform", + "protobufjs" + ] + }, + "openai@4.97.0": { + "integrity": "sha512-LRoiy0zvEf819ZUEJhgfV8PfsE8G5WpQi4AwA1uCV8SKvvtXQkoWUFkepD6plqyJQRghy2+AEPQ07FrJFKHZ9Q==", + "dependencies": [ + "@types/node@18.19.87", + "@types/node-fetch", + "abort-controller", + "agentkeepalive", + "form-data-encoder", + "formdata-node", + "node-fetch" + ] + }, + "p-limit@6.2.0": { + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "dependencies": [ + "yocto-queue" + ] + }, + "phonemizer@1.2.1": { + "integrity": "sha512-v0KJ4mi2T4Q7eJQ0W15Xd4G9k4kICSXE8bpDeJ8jisL4RyJhNWsweKTOi88QXFc4r4LZlz5jVL5lCHhkpdT71A==" + }, + "platform@1.3.6": { + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" + }, + "protobufjs@7.5.0": { + "integrity": "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==", + "dependencies": [ + "@protobufjs/aspromise", + "@protobufjs/base64", + "@protobufjs/codegen", + "@protobufjs/eventemitter", + "@protobufjs/fetch", + "@protobufjs/float", + "@protobufjs/inquire", + "@protobufjs/path", + "@protobufjs/pool", + "@protobufjs/utf8", + "@types/node@22.12.0", + "long" + ] + }, + "roarr@2.15.4": { + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dependencies": [ + "boolean", + "detect-node", + "globalthis", + "json-stringify-safe", + "semver-compare", + "sprintf-js" + ] + }, + "semver-compare@1.0.0": { + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" + }, + "semver@7.7.1": { + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + }, + "serialize-error@7.0.1": { + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dependencies": [ + "type-fest" + ] + }, + "sharp@0.34.1": { + "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==", + "dependencies": [ + "@img/sharp-darwin-arm64", + "@img/sharp-darwin-x64", + "@img/sharp-libvips-darwin-arm64", + "@img/sharp-libvips-darwin-x64", + "@img/sharp-libvips-linux-arm", + "@img/sharp-libvips-linux-arm64", + "@img/sharp-libvips-linux-ppc64", + "@img/sharp-libvips-linux-s390x", + "@img/sharp-libvips-linux-x64", + "@img/sharp-libvips-linuxmusl-arm64", + "@img/sharp-libvips-linuxmusl-x64", + "@img/sharp-linux-arm", + "@img/sharp-linux-arm64", + "@img/sharp-linux-s390x", + "@img/sharp-linux-x64", + "@img/sharp-linuxmusl-arm64", + "@img/sharp-linuxmusl-x64", + "@img/sharp-wasm32", + "@img/sharp-win32-ia32", + "@img/sharp-win32-x64", + "color", + "detect-libc", + "semver" + ] + }, + "simple-swizzle@0.2.2": { + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": [ + "is-arrayish" + ] + }, + "sprintf-js@1.1.3": { + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "tar@7.4.3": { + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dependencies": [ + "@isaacs/fs-minipass", + "chownr", + "minipass", + "minizlib", + "mkdirp", + "yallist" + ] + }, + "tr46@0.0.3": { + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tslib@2.8.1": { + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "type-fest@0.13.1": { + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" + }, + "undici-types@5.26.5": { + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "undici-types@6.20.0": { + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, + "web-streams-polyfill@4.0.0-beta.3": { + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==" + }, + "webidl-conversions@3.0.1": { + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url@5.0.0": { + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": [ + "tr46", + "webidl-conversions" + ] + }, + "yallist@5.0.0": { + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" + }, + "yocto-queue@1.2.1": { + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==" + }, + "zod-to-json-schema@3.24.5_zod@3.24.4": { + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dependencies": [ + "zod" + ] + }, + "zod@3.24.4": { + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==" + } + }, + "remote": { + "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.5.1/+esm": "fb0a2ba5088bbde44cddff091a0d1844147cd08c75c75985df70672c7151efd5", + "https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/+esm": "cf3368de4ad22103fe3083600aa9a1bb764d12a0795b2994110836fb5b9091e4", + "https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/dist/kokoro.js/+esm": "cf3368de4ad22103fe3083600aa9a1bb764d12a0795b2994110836fb5b9091e4", + "https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/dist/kokoro.web.js": "6067712fe4c43cdb36c762f860a5ab8d96ea1d9c8882466438eb9f3d0d20be8f", + "https://cdn.jsdelivr.net/npm/onnxruntime-common/+esm": "54aea20a2aaa80823ca7c15c084008e838fe74b61f3026d7d5e0b0187b6b9fcb", + "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0-dev.20250409-89f8206ba4/+esm": "53bf11d80531b341f46d4dc695a3d143d8b4c71d30e43905f9588db9804832db", + "https://cdn.jsdelivr.net/npm/phonemizer@1.2.1/+esm": "b8b4327641797c57274be04e4c82a561dd1e63ec7d37bcbb9820f8be1e332696" + } +} diff --git a/supabase/functions/generate-tts/index.ts b/supabase/functions/generate-tts/index.ts new file mode 100644 index 0000000..f6d7ae2 --- /dev/null +++ b/supabase/functions/generate-tts/index.ts @@ -0,0 +1,72 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +// Import necessary modules +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; +import { KokoroTTS } from "https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/dist/kokoro.js/+esm"; + +console.log("generate-tts function initialized"); + +Deno.serve(async (req) => { + if (req.method === "OPTIONS") { + // Handle preflight requests + return new Response(null, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": + "authorization, x-client-info, apikey, content-type", + }, + }); + } + + try { + console.log("Received request for TTS generation"); + // Parse the incoming request body + const { text, voice, index } = await req.json(); + console.log("Parsed request body:", { text, voice, index }); + + // Validate the input + if (!text || !voice) { + return new Response( + JSON.stringify({ error: "Missing required parameters: text or voice" }), + { status: 400, headers: { "Content-Type": "application/json" } } + ); + } + + console.log(`Generating TTS for text: "${text}" with voice: "${voice}"`); + + // Initialize KokoroTTS + const model_id = "onnx-community/Kokoro-82M-v1.0-ONNX"; + const tts = await KokoroTTS.from_pretrained(model_id, { + dtype: "fp32", + }); + + // Generate the speech audio + const audio = await tts.generate(text, { + voice, + }); + + const arrayBuffer = await audio.toWav(); + + // audioUrl should be the base64 encoded audio blob + const audioUrl = `data:audio/wav;base64,${btoa( + String.fromCharCode(...new Uint8Array(arrayBuffer)) + )}`; + + console.log(`TTS generated successfully for index: ${index}`); + + // Return the audio URL + return new Response(JSON.stringify({ audioUrl }), { + headers: { "Content-Type": "application/json" }, + }); + } catch (error) { + console.error("Error generating TTS:", error); + + // Return an error response + return new Response(JSON.stringify({ error: "Failed to generate TTS" }), { + status: 500, + headers: { "Content-Type": "application/json" }, + }); + } +});