Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fern/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
// explicit `/index` file paths rather than bare directory paths. `export *` pulls in each
// component's public surface (components + types) without enumerating them.
export * from "./voice-widget/index";
export * from "./voice-widget-rows/index";
export * from "./skeleton/index";
462 changes: 462 additions & 0 deletions fern/components/voice-widget-rows/index.tsx

Large diffs are not rendered by default.

255 changes: 255 additions & 0 deletions fern/components/voice-widget-rows/styles.css

Large diffs are not rendered by default.

34 changes: 18 additions & 16 deletions fern/components/voice-widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import { Skeleton } from "../skeleton/index";
const ASSET_BASE = "https://mcdn.signalwire.com/voice_widget/dist"; // catalog.json + manifest.json
const AUDIO_BASE = "https://mcdn.signalwire.com"; // /audio/<engine>/<voice_id>.mp3

const ALL = "__all__";
const DEFAULT_PAGE_SIZE = 48;
const MOBILE_PAGE_SIZE = 12;
// Exported (with the helpers/sub-components/types below) so the sibling rows preview
// (components/voice-widget-rows) reuses this module's data layer and display logic verbatim instead
// of forking it — keeping the two previews' behavior identical. See that file's header.
export const ALL = "__all__";
export const DEFAULT_PAGE_SIZE = 48;
export const MOBILE_PAGE_SIZE = 12;
const MOBILE_QUERY = "(max-width: 640px)";
// Multiples of the max column count (4) so every page fills complete rows; 4 = a single row.
const PAGE_SIZE_OPTIONS = [4, 8, 12, 24, 48, 96];
export const PAGE_SIZE_OPTIONS = [4, 8, 12, 24, 48, 96];

// ── Display-time data cleanup ────────────────────────────────────────────────────────────────
// These three helpers normalize the raw catalog values for display only — the underlying data,
Expand All @@ -41,7 +44,7 @@ const PAGE_SIZE_OPTIONS = [4, 8, 12, 24, 48, 96];
// Cartesia (535), 0 false positives — Google/Polly/Azure names use spaceless hyphens
// ("ar-XA-Chirp3-HD-Achernar", "Joanna-Neural") and are never touched. The full original stays in
// title=/the icon tooltip. Pipeline home: providers/{elevenlabs,cartesia}.py make_voice().
function cleanName(displayName: string): string {
export function cleanName(displayName: string): string {
const m = displayName.match(/\s[-–—]\s/);
if (!m || m.index === undefined) return displayName;
const lead = displayName.slice(0, m.index).trim();
Expand All @@ -51,7 +54,7 @@ function cleanName(displayName: string): string {

// Friendly language: turn a BCP-47 code ("af-ZA", "el") into an English name via Intl.DisplayNames
// (resolves ~99% of real codes); fall back to the raw code on any miss/throw.
function friendlyLanguage(code: string): string {
export function friendlyLanguage(code: string): string {
if (!code) return code;
try {
return new Intl.DisplayNames(["en"], { type: "language" }).of(code) || code;
Expand All @@ -62,7 +65,7 @@ function friendlyLanguage(code: string): string {

// Normalize gender: the catalog mixes "feminine"/"masculine" with "male"/"female"; map to the
// display forms and HIDE "unknown" (599 voices) rather than render "Unknown". Returns "" to hide.
function normalizeGender(gender: string): string {
export function normalizeGender(gender: string): string {
switch (gender) {
case "feminine": case "female": return "Female";
case "masculine": case "male": return "Male";
Expand All @@ -73,22 +76,21 @@ function normalizeGender(gender: string): string {
}
}


type FilterKey = "search" | "provider" | "language" | "gender" | "group" | "pageSize";

// Client-side row: a catalog voice joined with its clip, plus two precomputed fields — `_search`,
// a lowercased blob so the per-keystroke filter runs one substring test per row instead of
// re-lowercasing 4–5 fields, and `_uid`, a collision-free identity for React keys and play state
// (assigned in loadBundle; the catalog's key+model is not unique on its own).
type Row = VoiceRow & { _search: string; _uid: string };
export type Row = VoiceRow & { _search: string; _uid: string };

// The asset bundle (catalog.json + manifest.json) is one large artifact (~5 MB, ~4.9k voices)
// shared by every TTS page that embeds the widget. Cache the parsed+joined rows per URL pair so
// navigating across the index and the provider pages fetches and parses it once per session, not
// once per mount. Each embed then narrows this shared set down to the voices it shows (see baseRows).
const bundleCache = new Map<string, Promise<Row[]>>();
export const bundleCache = new Map<string, Promise<Row[]>>();

function loadBundle(catalogUrl: string, manifestUrl: string): Promise<Row[]> {
export function loadBundle(catalogUrl: string, manifestUrl: string): Promise<Row[]> {
const cacheKey = `${catalogUrl}\n${manifestUrl}`;
let cached = bundleCache.get(cacheKey);
if (!cached) {
Expand Down Expand Up @@ -128,7 +130,7 @@ function loadBundle(catalogUrl: string, manifestUrl: string): Promise<Row[]> {
}

// Tracks whether the viewport is in the mobile breakpoint (matches the CSS media query below).
function useIsMobile() {
export function useIsMobile() {
const [mobile, setMobile] = useState(false);
useEffect(() => {
if (typeof window === "undefined" || !window.matchMedia) return;
Expand Down Expand Up @@ -645,7 +647,7 @@ function VoiceWidgetSkeleton({ gridStyle, pills = 3 }: { gridStyle?: CSSProperti
);
}

function Select({ label, value, onChange, opts }:
export function Select({ label, value, onChange, opts }:
{ label: string; value: string; onChange: (v: string) => void; opts: string[] }) {
return (
<label className="vw-select">
Expand All @@ -658,15 +660,15 @@ function Select({ label, value, onChange, opts }:
);
}

function uniq(xs?: string[]): string[] {
export function uniq(xs?: string[]): string[] {
return [...new Set((xs ?? []).filter(Boolean))].sort();
}

// Windowed page list for the numbered pager: first page, last page, and current ±1, with `null`
// marking each collapsed gap (rendered as an ellipsis). A gap of exactly one page emits that page
// number instead — an ellipsis standing in for a single page reads worse than just showing it.
// Pages are 0-based here, 1-based in the UI.
function pageNumbers(current: number, count: number): (number | null)[] {
export function pageNumbers(current: number, count: number): (number | null)[] {
const wanted = [...new Set([0, count - 1, current - 1, current, current + 1])]
.filter((p) => p >= 0 && p < count)
.sort((a, b) => a - b);
Expand All @@ -685,6 +687,6 @@ function pageNumbers(current: number, count: number): (number | null)[] {
// the voiceIds allowlist. NOT unique in the wild: a few rime voices repeat key+model (once per
// language, plus literal duplicate rows), so row identity uses Row._uid — this plus a collision
// counter, assigned in loadBundle.
function modelKeyOf(v: { key: string; model: string | null }): string {
export function modelKeyOf(v: { key: string; model: string | null }): string {
return v.model ? `${v.key}:${v.model}` : v.key;
}
1 change: 1 addition & 0 deletions fern/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ css:
- styles.css
- components/skeleton/styles.css
- components/voice-widget/styles.css
- components/voice-widget-rows/styles.css

redirects:
- source: /docs/agents-sdk
Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/azure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ description: Learn how to use Azure TTS voices on the SignalWire platform.
slug: /voice/tts/azure
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";

Microsoft's Azure platform offers an impressive array of high-quality, multilingual voices in its Neural model.

## Voices

Press play to audition any Azure voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="azure" />
<VoiceWidgetRows provider="azure" />

<CardGroup cols={2}>
<Card title="Voices" href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts">
Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/cartesia.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use Cartesia TTS voices on the SignalWire platform.
slug: /voice/tts/cartesia
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";


Cartesia offers a wide selection of fully multilingual voices with very low latency.
Expand Down Expand Up @@ -38,7 +38,7 @@ All Cartesia voices can be used with any model.

Press play to audition any Cartesia voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="cartesia" />
<VoiceWidgetRows provider="cartesia" />

Copy the voice ID from the table below:

Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/deepgram.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use Deepgram TTS voices on the SignalWire platform.
slug: /voice/tts/deepgram
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";

Deepgram's Aura model offers a wide range of voices for its text-to-speech API,
each designed to produce natural-sounding speech output in an array of different accents and speaking styles.
Expand All @@ -29,7 +29,7 @@ Deepgram's Aura model provides ultra-low latency text-to-speech optimized for co

Press play to audition any Deepgram voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="deepgram" />
<VoiceWidgetRows provider="deepgram" />

Deepgram Aura voices are designed for natural-sounding English speech. Each voice follows the pattern `aura-<name>-en`.

Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/elevenlabs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use ElevenLabs TTS voices on the SignalWire platform.
slug: /voice/tts/elevenlabs
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";

ElevenLabs voices offer expressive, human-like pronunciation and an extensive list of supported languages.
Every ElevenLabs [voice](#voices) can be used with all of the ElevenLabs [models](#models).
Expand All @@ -24,7 +24,7 @@ Consult ElevenLabs' documentation for an up-to-date list of available models and

Press play to audition any ElevenLabs voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="elevenlabs" />
<VoiceWidgetRows provider="elevenlabs" />

You can use the **Name** and **ID** values for each voice in the below table interchangeably.

Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/google.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use Google Cloud TTS voices on the SignalWire platform
slug: /voice/tts/gcloud
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";

Google Cloud offers a number of robust text-to-speech
[voice models](https://cloud.google.com/text-to-speech/docs/voice-types).
Expand Down Expand Up @@ -42,7 +42,7 @@ the `polyglot-1` voice has variants for English (Australia), English (US), Frenc

Press play to audition any Google Cloud voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="gcloud" />
<VoiceWidgetRows provider="gcloud" />

## Billing

Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ x-custom:
description: Detailed list of all the TTS providers and voices SignalWire supports.
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";


[polly]: /docs/platform/voice/tts/amazon-polly
Expand All @@ -37,7 +37,7 @@ Choose a provider to browse and audition its full voice catalog. Press play to a
grab the engine and voice values for your SWML or SDK code. Each provider's complete voice list
lives on its reference page, linked in the table below.

<VoiceWidget groupBy="none" />
<VoiceWidgetRows groupBy="none" />

## Compare providers and models

Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/openai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use OpenAI TTS voices on the SignalWire platform.
slug: /voice/tts/openai
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";

OpenAI offers versatile multilingual voices balancing low latency and good quality.
While voices are optimized for English, they perform well across all
Expand All @@ -28,7 +28,7 @@ OpenAI offers two TTS models with different quality and latency characteristics:

Press play to audition any OpenAI voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="openai" />
<VoiceWidgetRows provider="openai" />

OpenAI provides 6 fully multilingual voices optimized for natural-sounding speech:

Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/polly.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use Polly TTS voices on the SignalWire platform.
slug: /voice/tts/amazon-polly
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";

Amazon Web Services' Polly TTS engine includes several models to accommodate different use cases.

Expand Down Expand Up @@ -39,7 +39,7 @@ SignalWire supports the following three Amazon models.

Press play to audition any Amazon Polly voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="polly" />
<VoiceWidgetRows provider="polly" />

## Languages

Expand Down
4 changes: 2 additions & 2 deletions fern/products/platform/pages/calling/voice/TTS/rime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ slug: /voice/tts/rime
description: Learn how to use Rime's Arcana and Mist v2 TTS models with SignalWire AI Voice applications.
---

import { VoiceWidget } from "@/components/index";
import { VoiceWidgetRows } from "@/components/index";


Rime offers uniquely realistic voices with a focus on natural expressiveness.
Expand Down Expand Up @@ -37,7 +37,7 @@ Rime offers uniquely realistic voices with a focus on natural expressiveness.

Press play to audition any Rime voice, then **copy config** to grab the value for SWML or your SDK.

<VoiceWidget provider="rime" />
<VoiceWidgetRows provider="rime" />

Mist v2 is the default Rime model on the SignalWire platform.
To use this model, simply set the voice ID.
Expand Down
Loading