> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Streaming i cache'owanie z Supabase

> **Note**
>
> **Przewodnik** · Zakłada, że masz za sobą [szybki start ElevenAPI ](/docs/pl/eleven-api/quickstart) i konto Supabase.

## Wprowadzenie

Z tego przewodnika dowiesz się, jak zbudować brzegowe API do generowania, przesyłania strumieniowego, przechowywania i buforowania mowy za pomocą Supabase Edge Functions, Supabase Storage i ElevenLabs.

## Wymagania

* Konto ElevenLabs z [kluczem API](https://elevenlabs.io/app/settings/api-keys).
* Konto [Supabase](https://supabase.com) (bezpłatne konto założysz przez [database.new](https://database.new)).
* Zainstalowane na komputerze [Supabase CLI](https://supabase.com/docs/guides/local-development).
* Zainstalowane na komputerze [środowisko Deno](https://docs.deno.com/runtime/getting_started/installation/) oraz opcjonalnie [skonfigurowane w ulubionym IDE](https://docs.deno.com/runtime/getting_started/setup_your_environment).

## Konfiguracja

### Utwórz lokalny projekt Supabase

Po zainstalowaniu [Supabase CLI](https://supabase.com/docs/guides/local-development) uruchom poniższe polecenie, aby lokalnie utworzyć nowy projekt Supabase:

```bash
supabase init
```

### Skonfiguruj bucket magazynu

Możesz skonfigurować Supabase CLI tak, aby automatycznie utworzyło bucket magazynu. Dodaj tę konfigurację do pliku `config.toml`:

**`./supabase/config.toml`**

```toml ./supabase/config.toml
[storage.buckets.audio]
public = false
file_size_limit = "50MiB"
allowed_mime_types = ["audio/mp3"]
objects_path = "./audio"
```

> **Note**
>
> Po uruchomieniu `supabase start` powstanie nowy bucket magazynu w lokalnym projekcie Supabase.
> Jeśli chcesz przesłać go do hostowanego projektu Supabase, uruchom `supabase seed
>   buckets --linked`.

### Skonfiguruj zadania w tle dla Supabase Edge Functions

Aby używać zadań w tle w Supabase Edge Functions podczas lokalnego tworzenia, dodaj tę konfigurację do pliku `config.toml`:

**`./supabase/config.toml`**

```toml ./supabase/config.toml
[edge_runtime]
policy = "per_worker"
```

> **Note**
>
> Przy zasadzie `per_worker` funkcja nie będzie automatycznie przeładowywana po zmianach. Musisz
> ręcznie uruchomić ją ponownie za pomocą `supabase functions serve`.

### Utwórz Supabase Edge Function do generowania mowy

Utwórz nową Edge Function, uruchamiając poniższe polecenie:

```bash
supabase functions new text-to-speech
```

Jeśli używasz VS Code lub Cursor, wybierz `y`, gdy CLI wyświetli pytanie „Generate VS Code settings for Deno? \[y/N]”!

### Ustaw zmienne środowiskowe

W katalogu `supabase/functions` utwórz nowy plik `.env` i dodaj poniższe zmienne:

**`supabase/functions/.env`**

```env supabase/functions/.env
# Find / create an API key at https://elevenlabs.io/app/settings/api-keys
ELEVENLABS_API_KEY=your_api_key
```

### Zależności

Projekt używa kilku zależności:

* Biblioteki [@supabase/supabase-js](https://supabase.com/docs/reference/javascript) do interakcji z bazą danych Supabase.
* [SDK JavaScript](/docs/pl/eleven-api/quickstart) ElevenLabs do interakcji z API zamiany tekstu na mowę.
* Otwartoźródłowej biblioteki [object-hash](https://www.npmjs.com/package/object-hash) do generowania hasha z parametrów żądania.

Ponieważ Supabase Edge Function używa [środowiska Deno](https://deno.land/), nie musisz instalować zależności. Możesz je [importować](https://docs.deno.com/examples/npm/) przez prefiks `npm:`.

## Napisz kod Supabase Edge Function

W nowo utworzonym pliku `supabase/functions/text-to-speech/index.ts` dodaj poniższy kod:

**`supabase/functions/text-to-speech/index.ts`**

```ts supabase/functions/text-to-speech/index.ts
// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";
import { ElevenLabsClient } from "npm:elevenlabs";
import * as hash from "npm:object-hash";

const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);

const elevenlabs = new ElevenLabsClient({
  apiKey: Deno.env.get("ELEVENLABS_API_KEY"),
});

// Upload audio to Supabase Storage in a background task
async function uploadAudioToStorage(stream: ReadableStream, requestHash: string) {
  const { data, error } = await supabase.storage
    .from("audio")
    .upload(`${requestHash}.mp3`, stream, {
      contentType: "audio/mp3",
    });

  console.log("Storage upload result", { data, error });
}

Deno.serve(async (req) => {
  // To secure your function for production, you can for example validate the request origin,
  // or append a user access token and validate it with Supabase Auth.
  console.log("Request origin", req.headers.get("host"));
  const url = new URL(req.url);
  const params = new URLSearchParams(url.search);
  const text = params.get("text");
  const voiceId = params.get("voiceId") ?? "JBFqnCBsd6RMkjVDRZzb";

  const requestHash = hash.MD5({ text, voiceId });
  console.log("Request hash", requestHash);

  // Check storage for existing audio file
  const { data } = await supabase.storage.from("audio").createSignedUrl(`${requestHash}.mp3`, 60);

  if (data) {
    console.log("Audio file found in storage", data);
    const storageRes = await fetch(data.signedUrl);
    if (storageRes.ok) return storageRes;
  }

  if (!text) {
    return new Response(JSON.stringify({ error: "Text parameter is required" }), {
      status: 400,
      headers: { "Content-Type": "application/json" },
    });
  }

  try {
    console.log("ElevenLabs API call");
    const response = await elevenlabs.textToSpeech.stream(voiceId, {
      output_format: "mp3_44100_128",
      model_id: "eleven_multilingual_v2",
      text,
    });

    const stream = new ReadableStream({
      async start(controller) {
        for await (const chunk of response) {
          controller.enqueue(chunk);
        }
        controller.close();
      },
    });

    // Branch stream to Supabase Storage
    const [browserStream, storageStream] = stream.tee();

    // Upload to Supabase Storage in the background
    EdgeRuntime.waitUntil(uploadAudioToStorage(storageStream, requestHash));

    // Return the streaming response immediately
    return new Response(browserStream, {
      headers: {
        "Content-Type": "audio/mpeg",
      },
    });
  } catch (error) {
    console.log("error", { error });
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});
```

### Omówienie kodu

Warto zwrócić uwagę na kilka elementów kodu. Przejdźmy przez nie krok po kroku.

#### Obsłuż przychodzące żądanie

Do obsługi przychodzącego żądania użyj procedury `Deno.serve`. W tym demo nie sprawdzamy pochodzenia żądania, ale możesz na przykład je zweryfikować albo dołączyć token dostępu użytkownika i sprawdzić go w [Supabase Auth](https://supabase.com/docs/guides/functions/auth).

Z przychodzącego żądania funkcja pobiera parametry `text` i `voiceId`. Parametr `voiceId` jest opcjonalny, a domyślnie ustawiany na identyfikator ElevenLabs głosu „Allison”.

Za pomocą biblioteki `object-hash` funkcja generuje hash z parametrów żądania. Ten hash służy do sprawdzania istniejących plików audio w Supabase Storage.

```ts {1,5-8}
Deno.serve(async (req) => {
// To secure your function for production, you can for example validate the request origin,
// or append a user access token and validate it with Supabase Auth.
console.log("Request origin", req.headers.get("host"));
const url = new URL(req.url);
const params = new URLSearchParams(url.search);
const text = params.get("text");
const voiceId = params.get("voiceId") ?? "JBFqnCBsd6RMkjVDRZzb";

const requestHash = hash.MD5({ text, voiceId });
console.log("Request hash", requestHash);

// ...
})
```

#### Sprawdź istniejący plik audio w Supabase Storage

Supabase Storage ma wbudowaną [inteligentną sieć CDN](https://supabase.com/docs/guides/storage/cdn/smart-cdn), która pozwala łatwo buforować i udostępniać pliki.

Tutaj funkcja sprawdza, czy plik audio istnieje w Supabase Storage. Jeśli tak, zwraca go z Supabase Storage.

```ts {4,9}
const { data } = await supabase
  .storage
  .from("audio")
  .createSignedUrl(`${requestHash}.mp3`, 60);

if (data) {
  console.log("Audio file found in storage", data);
  const storageRes = await fetch(data.signedUrl);
  if (storageRes.ok) return storageRes;
}
```

#### Wygeneruj mowę jako strumień i podziel go na dwie gałęzie

Korzystając z przesyłania strumieniowego API ElevenLabs, funkcja generuje strumień. Dzięki temu nawet w przypadku dłuższego tekstu możesz od razu zacząć przesyłać audio do użytkownika, a następnie w tle przesłać strumień do Supabase Storage.

Zapewnia to jak najlepsze wrażenia użytkownika — nawet duże bloki tekstu wydają się błyskawiczne. Kluczowy moment jest w wierszu 17, gdzie metoda `stream.tee()` dzieli odczytywalny strumień na dwie gałęzie: jedną dla przeglądarki i drugą dla Supabase Storage.

```ts {1,17,20,22-27}
try {
  const response = await elevenlabs.textToSpeech.stream(voiceId, {
    output_format: "mp3_44100_128",
    model_id: "eleven_multilingual_v2",
    text,
  });

  const stream = new ReadableStream({
    async start(controller) {
      for await (const chunk of response) {
        controller.enqueue(chunk);
      }
      controller.close();
    },
  });

  // Branch stream to Supabase Storage
  const [browserStream, storageStream] = stream.tee();

  // Upload to Supabase Storage in the background
  EdgeRuntime.waitUntil(uploadAudioToStorage(storageStream, requestHash));

  // Return the streaming response immediately
  return new Response(browserStream, {
    headers: {
      "Content-Type": "audio/mpeg",
    },
  });
} catch (error) {
  console.log("error", { error });
  return new Response(JSON.stringify({ error: error.message }), {
    status: 500,
    headers: { "Content-Type": "application/json" },
  });
}
```

#### Prześlij strumień audio do Supabase Storage w tle

Metoda `EdgeRuntime.waitUntil` w wierszu 20 poprzedniego kroku służy do przesłania strumienia audio do Supabase Storage w tle za pomocą funkcji `uploadAudioToStorage`. Dzięki temu funkcja może od razu zwrócić odpowiedź strumieniową do przeglądarki, podczas gdy audio jest przesyłane do Supabase Storage.

Po utworzeniu obiektu w magazynie, gdy użytkownicy następnym razem wyślą żądanie z tymi samymi parametrami, funkcja zwróci plik audio z CDN Supabase Storage.

```ts {2,8-10}
// Upload audio to Supabase Storage in a background task
async function uploadAudioToStorage(
  stream: ReadableStream,
  requestHash: string,
) {
  const { data, error } = await supabase.storage
    .from("audio")
    .upload(`${requestHash}.mp3`, stream, {
      contentType: "audio/mp3",
    });

  console.log("Storage upload result", { data, error });
}
```

## Uruchom lokalnie

Aby uruchomić funkcję lokalnie, wykonaj poniższe polecenia:

```bash
supabase start
```

Gdy lokalny stos Supabase będzie działać, uruchom poniższe polecenie, aby wystartować funkcję i obserwować logi:

```bash
supabase functions serve
```

### Wypróbuj

Wejdź na `http://127.0.0.1:54321/functions/v1/text-to-speech?text=hello%20world`, aby usłyszeć działanie funkcji.

Następnie wejdź na `http://127.0.0.1:54323/project/default/storage/buckets/audio`, aby zobaczyć plik audio w lokalnym buckecie Supabase Storage.

## Wdróż w Supabase

Jeśli jeszcze tego nie zrobiono, utwórz nowe konto Supabase na [database.new](https://database.new) i połącz lokalny projekt z kontem Supabase:

```bash
supabase link
```

Następnie uruchom poniższe polecenie, aby wdrożyć funkcję:

```bash
supabase functions deploy
```

### Ustaw sekrety funkcji

Gdy wszystkie sekrety są już ustawione lokalnie, uruchom poniższe polecenie, aby ustawić je w projekcie Supabase:

```bash
supabase secrets set --env-file supabase/functions/.env
```

## Przetestuj funkcję

Funkcja została zaprojektowana tak, by można było użyć jej bezpośrednio jako źródła elementu `<audio>`.

```html
<audio
  src="https://${SUPABASE_PROJECT_REF}.supabase.co/functions/v1/text-to-speech?text=Hello%2C%20world!&voiceId=JBFqnCBsd6RMkjVDRZzb"
  controls
/>
```

## Kolejne kroki

#### [Strumieniowanie TTS](/docs/pl/eleven-api/guides/how-to/text-to-speech/streaming)

Przesyłaj audio stopniowo bez Supabase — to prostszy punkt startowy.

#### [Dokumentacja API](/docs/pl/api-reference/text-to-speech/stream)

Pełna dokumentacja API i parametrów strumieniowania TTS.