GitHub Actions commited on
Commit ·
60cb866
0
Parent(s):
Deploy docs from github.com/TTS-AGI/TTS-Arena@8bbcfba
Browse files- Dockerfile +46 -0
- README.md +19 -0
- apps/docs/.gitignore +26 -0
- apps/docs/app/[[...slug]]/page.tsx +67 -0
- apps/docs/app/api/search/route.ts +7 -0
- apps/docs/app/global.css +12 -0
- apps/docs/app/layout.tsx +37 -0
- apps/docs/app/llms-full.txt/route.ts +10 -0
- apps/docs/app/llms.mdx/docs/[[...slug]]/route.ts +26 -0
- apps/docs/app/llms.txt/route.ts +8 -0
- apps/docs/app/og/docs/[...slug]/route.tsx +35 -0
- apps/docs/components/mdx.tsx +15 -0
- apps/docs/content/docs/api.mdx +53 -0
- apps/docs/content/docs/development.mdx +97 -0
- apps/docs/content/docs/index.mdx +52 -0
- apps/docs/content/docs/integrity-decisions.mdx +96 -0
- apps/docs/content/docs/integrity.mdx +36 -0
- apps/docs/content/docs/meta.json +15 -0
- apps/docs/content/docs/privacy.mdx +83 -0
- apps/docs/content/docs/provider-agreement.mdx +218 -0
- apps/docs/content/docs/ranking.mdx +65 -0
- apps/docs/content/docs/submit-a-model.mdx +50 -0
- apps/docs/content/docs/voting.mdx +60 -0
- apps/docs/lib/cn.ts +1 -0
- apps/docs/lib/layout.shared.tsx +24 -0
- apps/docs/lib/shared.ts +14 -0
- apps/docs/lib/source.ts +37 -0
- apps/docs/next.config.mjs +25 -0
- apps/docs/package.json +33 -0
- apps/docs/postcss.config.mjs +7 -0
- apps/docs/proxy.ts +29 -0
- apps/docs/public/.gitkeep +1 -0
- apps/docs/source.config.ts +23 -0
- apps/docs/tsconfig.json +35 -0
Dockerfile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1
|
| 2 |
+
#
|
| 3 |
+
# Hugging Face Space image for the docs site (Fumadocs/Next.js). The docs app is
|
| 4 |
+
# fully self-contained — it has no @ttsa/* workspace dependencies — so we build
|
| 5 |
+
# it in isolation, NOT as part of the monorepo workspace. That avoids pulling in
|
| 6 |
+
# unrelated packages (e.g. web's native better-sqlite3) that would need a build
|
| 7 |
+
# toolchain and have nothing to do with the docs.
|
| 8 |
+
FROM oven/bun:1.4.0-debian AS base
|
| 9 |
+
WORKDIR /app/docs
|
| 10 |
+
|
| 11 |
+
# ── deps ──
|
| 12 |
+
FROM base AS deps
|
| 13 |
+
# Standalone install: only the docs package.json (no root workspace context).
|
| 14 |
+
COPY apps/docs/package.json ./package.json
|
| 15 |
+
RUN bun install
|
| 16 |
+
|
| 17 |
+
# ── build ──
|
| 18 |
+
FROM base AS build
|
| 19 |
+
COPY --from=deps /app/docs/node_modules ./node_modules
|
| 20 |
+
COPY apps/docs/ ./
|
| 21 |
+
RUN bun run build
|
| 22 |
+
# Stage a flat runtime dir. In this isolated build (no parent workspace) Next's
|
| 23 |
+
# standalone output is flat — server.js, node_modules and package.json at the
|
| 24 |
+
# standalone root — but we locate server.js explicitly so the image is correct
|
| 25 |
+
# regardless of any workspace-root inference, then add the static + public dirs
|
| 26 |
+
# the standalone server expects beside it.
|
| 27 |
+
RUN set -e; \
|
| 28 |
+
SA="$PWD/.next/standalone"; \
|
| 29 |
+
SERVER="$(find "$SA" -name server.js -not -path '*/node_modules/*' | head -1)"; \
|
| 30 |
+
test -n "$SERVER"; \
|
| 31 |
+
ROOT="$(dirname "$SERVER")"; \
|
| 32 |
+
mkdir -p "$ROOT/.next"; \
|
| 33 |
+
cp -a "$PWD/.next/static" "$ROOT/.next/static"; \
|
| 34 |
+
[ -d "$PWD/public" ] && cp -a "$PWD/public" "$ROOT/public" || true; \
|
| 35 |
+
cp -a "$ROOT" /out
|
| 36 |
+
|
| 37 |
+
# ── runtime ──
|
| 38 |
+
FROM base AS runtime
|
| 39 |
+
ENV NODE_ENV=production
|
| 40 |
+
ENV PORT=7860
|
| 41 |
+
ENV HOSTNAME=0.0.0.0
|
| 42 |
+
EXPOSE 7860
|
| 43 |
+
|
| 44 |
+
WORKDIR /app
|
| 45 |
+
COPY --from=build /out/ ./
|
| 46 |
+
CMD ["bun", "server.js"]
|
README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: TTS Arena Docs
|
| 3 |
+
emoji: 📚
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# TTS Arena Docs
|
| 12 |
+
|
| 13 |
+
Documentation for [TTS Arena](https://huggingface.co/spaces/TTS-AGI/TTS-Arena-V2),
|
| 14 |
+
served at https://docs.ttsarena.org/.
|
| 15 |
+
|
| 16 |
+
> Source: https://github.com/TTS-AGI/TTS-Arena (apps/docs)
|
| 17 |
+
>
|
| 18 |
+
> Built with Fumadocs + Next.js. This Space is deployed automatically from the
|
| 19 |
+
> GitHub repo; do not edit it directly.
|
apps/docs/.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# deps
|
| 2 |
+
/node_modules
|
| 3 |
+
|
| 4 |
+
# generated content
|
| 5 |
+
.source
|
| 6 |
+
|
| 7 |
+
# test & build
|
| 8 |
+
/coverage
|
| 9 |
+
/.next/
|
| 10 |
+
/out/
|
| 11 |
+
/build
|
| 12 |
+
*.tsbuildinfo
|
| 13 |
+
|
| 14 |
+
# misc
|
| 15 |
+
.DS_Store
|
| 16 |
+
*.pem
|
| 17 |
+
/.pnp
|
| 18 |
+
.pnp.js
|
| 19 |
+
npm-debug.log*
|
| 20 |
+
yarn-debug.log*
|
| 21 |
+
yarn-error.log*
|
| 22 |
+
|
| 23 |
+
# others
|
| 24 |
+
.env*.local
|
| 25 |
+
.vercel
|
| 26 |
+
next-env.d.ts
|
apps/docs/app/[[...slug]]/page.tsx
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getPageImage, getPageMarkdownUrl, source } from "@/lib/source";
|
| 2 |
+
import {
|
| 3 |
+
DocsBody,
|
| 4 |
+
DocsDescription,
|
| 5 |
+
DocsPage,
|
| 6 |
+
DocsTitle,
|
| 7 |
+
MarkdownCopyButton,
|
| 8 |
+
ViewOptionsPopover,
|
| 9 |
+
} from "fumadocs-ui/layouts/docs/page";
|
| 10 |
+
import { notFound } from "next/navigation";
|
| 11 |
+
import { getMDXComponents } from "@/components/mdx";
|
| 12 |
+
import type { Metadata } from "next";
|
| 13 |
+
import { createRelativeLink } from "fumadocs-ui/mdx";
|
| 14 |
+
import { gitConfig } from "@/lib/shared";
|
| 15 |
+
|
| 16 |
+
export default async function Page(props: PageProps<"/[[...slug]]">) {
|
| 17 |
+
const params = await props.params;
|
| 18 |
+
const page = source.getPage(params.slug);
|
| 19 |
+
if (!page) notFound();
|
| 20 |
+
|
| 21 |
+
const MDX = page.data.body;
|
| 22 |
+
const markdownUrl = getPageMarkdownUrl(page).url;
|
| 23 |
+
|
| 24 |
+
return (
|
| 25 |
+
<DocsPage toc={page.data.toc} full={page.data.full}>
|
| 26 |
+
<DocsTitle>{page.data.title}</DocsTitle>
|
| 27 |
+
<DocsDescription className="mb-0">
|
| 28 |
+
{page.data.description}
|
| 29 |
+
</DocsDescription>
|
| 30 |
+
<div className="flex flex-row items-center gap-2 border-b pb-6">
|
| 31 |
+
<MarkdownCopyButton markdownUrl={markdownUrl} />
|
| 32 |
+
<ViewOptionsPopover
|
| 33 |
+
markdownUrl={markdownUrl}
|
| 34 |
+
githubUrl={`https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/content/docs/${page.path}`}
|
| 35 |
+
/>
|
| 36 |
+
</div>
|
| 37 |
+
<DocsBody>
|
| 38 |
+
<MDX
|
| 39 |
+
components={getMDXComponents({
|
| 40 |
+
// this allows you to link to other pages with relative file paths
|
| 41 |
+
a: createRelativeLink(source, page),
|
| 42 |
+
})}
|
| 43 |
+
/>
|
| 44 |
+
</DocsBody>
|
| 45 |
+
</DocsPage>
|
| 46 |
+
);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
export async function generateStaticParams() {
|
| 50 |
+
return source.generateParams();
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
export async function generateMetadata(
|
| 54 |
+
props: PageProps<"/[[...slug]]">,
|
| 55 |
+
): Promise<Metadata> {
|
| 56 |
+
const params = await props.params;
|
| 57 |
+
const page = source.getPage(params.slug);
|
| 58 |
+
if (!page) notFound();
|
| 59 |
+
|
| 60 |
+
return {
|
| 61 |
+
title: page.data.title,
|
| 62 |
+
description: page.data.description,
|
| 63 |
+
openGraph: {
|
| 64 |
+
images: getPageImage(page).url,
|
| 65 |
+
},
|
| 66 |
+
};
|
| 67 |
+
}
|
apps/docs/app/api/search/route.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { source } from "@/lib/source";
|
| 2 |
+
import { createFromSource } from "fumadocs-core/search/server";
|
| 3 |
+
|
| 4 |
+
export const { GET } = createFromSource(source, {
|
| 5 |
+
// https://docs.orama.com/docs/orama-js/supported-languages
|
| 6 |
+
language: "english",
|
| 7 |
+
});
|
apps/docs/app/global.css
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import "tailwindcss";
|
| 2 |
+
@import "fumadocs-ui/css/neutral.css";
|
| 3 |
+
@import "fumadocs-ui/css/preset.css";
|
| 4 |
+
|
| 5 |
+
html {
|
| 6 |
+
scrollbar-gutter: stable;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
html > body[data-scroll-locked] {
|
| 10 |
+
margin-right: 0px !important;
|
| 11 |
+
--removed-body-scroll-bar-size: 0px !important;
|
| 12 |
+
}
|
apps/docs/app/layout.tsx
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Metadata } from "next";
|
| 2 |
+
import { RootProvider } from "fumadocs-ui/provider/next";
|
| 3 |
+
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
| 4 |
+
import "./global.css";
|
| 5 |
+
import { Inter } from "next/font/google";
|
| 6 |
+
import { source } from "@/lib/source";
|
| 7 |
+
import { baseOptions } from "@/lib/layout.shared";
|
| 8 |
+
|
| 9 |
+
const inter = Inter({
|
| 10 |
+
subsets: ["latin"],
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
export const metadata: Metadata = {
|
| 14 |
+
metadataBase: new URL("https://docs.ttsarena.org"),
|
| 15 |
+
title: {
|
| 16 |
+
default: "TTS Arena Docs",
|
| 17 |
+
template: "%s · TTS Arena Docs",
|
| 18 |
+
},
|
| 19 |
+
description:
|
| 20 |
+
"Documentation for TTS Arena — the crowdsourced text-to-speech benchmark.",
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
export default function Layout({ children }: LayoutProps<"/">) {
|
| 24 |
+
return (
|
| 25 |
+
<html lang="en" className={inter.className} suppressHydrationWarning>
|
| 26 |
+
<body className="flex min-h-screen flex-col">
|
| 27 |
+
<RootProvider>
|
| 28 |
+
{/* Docs are the whole site — the DocsLayout (sidebar + nav) wraps
|
| 29 |
+
everything at the root; there is no separate homepage. */}
|
| 30 |
+
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
|
| 31 |
+
{children}
|
| 32 |
+
</DocsLayout>
|
| 33 |
+
</RootProvider>
|
| 34 |
+
</body>
|
| 35 |
+
</html>
|
| 36 |
+
);
|
| 37 |
+
}
|
apps/docs/app/llms-full.txt/route.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getLLMText, source } from "@/lib/source";
|
| 2 |
+
|
| 3 |
+
export const revalidate = false;
|
| 4 |
+
|
| 5 |
+
export async function GET() {
|
| 6 |
+
const scan = source.getPages().map(getLLMText);
|
| 7 |
+
const scanned = await Promise.all(scan);
|
| 8 |
+
|
| 9 |
+
return new Response(scanned.join("\n\n"));
|
| 10 |
+
}
|
apps/docs/app/llms.mdx/docs/[[...slug]]/route.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getLLMText, getPageMarkdownUrl, source } from "@/lib/source";
|
| 2 |
+
import { notFound } from "next/navigation";
|
| 3 |
+
|
| 4 |
+
export const revalidate = false;
|
| 5 |
+
|
| 6 |
+
export async function GET(
|
| 7 |
+
_req: Request,
|
| 8 |
+
{ params }: RouteContext<"/llms.mdx/docs/[[...slug]]">,
|
| 9 |
+
) {
|
| 10 |
+
const { slug } = await params;
|
| 11 |
+
const page = source.getPage(slug?.slice(0, -1));
|
| 12 |
+
if (!page) notFound();
|
| 13 |
+
|
| 14 |
+
return new Response(await getLLMText(page), {
|
| 15 |
+
headers: {
|
| 16 |
+
"Content-Type": "text/markdown",
|
| 17 |
+
},
|
| 18 |
+
});
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export function generateStaticParams() {
|
| 22 |
+
return source.getPages().map((page) => ({
|
| 23 |
+
lang: page.locale,
|
| 24 |
+
slug: getPageMarkdownUrl(page).segments,
|
| 25 |
+
}));
|
| 26 |
+
}
|
apps/docs/app/llms.txt/route.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { source } from "@/lib/source";
|
| 2 |
+
import { llms } from "fumadocs-core/source";
|
| 3 |
+
|
| 4 |
+
export const revalidate = false;
|
| 5 |
+
|
| 6 |
+
export function GET() {
|
| 7 |
+
return new Response(llms(source).index());
|
| 8 |
+
}
|
apps/docs/app/og/docs/[...slug]/route.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getPageImage, source } from "@/lib/source";
|
| 2 |
+
import { notFound } from "next/navigation";
|
| 3 |
+
import { ImageResponse } from "next/og";
|
| 4 |
+
import { generate as DefaultImage } from "fumadocs-ui/og";
|
| 5 |
+
import { appName } from "@/lib/shared";
|
| 6 |
+
|
| 7 |
+
export const revalidate = false;
|
| 8 |
+
|
| 9 |
+
export async function GET(
|
| 10 |
+
_req: Request,
|
| 11 |
+
{ params }: RouteContext<"/og/docs/[...slug]">,
|
| 12 |
+
) {
|
| 13 |
+
const { slug } = await params;
|
| 14 |
+
const page = source.getPage(slug.slice(0, -1));
|
| 15 |
+
if (!page) notFound();
|
| 16 |
+
|
| 17 |
+
return new ImageResponse(
|
| 18 |
+
<DefaultImage
|
| 19 |
+
title={page.data.title}
|
| 20 |
+
description={page.data.description}
|
| 21 |
+
site={appName}
|
| 22 |
+
/>,
|
| 23 |
+
{
|
| 24 |
+
width: 1200,
|
| 25 |
+
height: 630,
|
| 26 |
+
},
|
| 27 |
+
);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
export function generateStaticParams() {
|
| 31 |
+
return source.getPages().map((page) => ({
|
| 32 |
+
lang: page.locale,
|
| 33 |
+
slug: getPageImage(page).segments,
|
| 34 |
+
}));
|
| 35 |
+
}
|
apps/docs/components/mdx.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import defaultMdxComponents from "fumadocs-ui/mdx";
|
| 2 |
+
import type { MDXComponents } from "mdx/types";
|
| 3 |
+
|
| 4 |
+
export function getMDXComponents(components?: MDXComponents) {
|
| 5 |
+
return {
|
| 6 |
+
...defaultMdxComponents,
|
| 7 |
+
...components,
|
| 8 |
+
} satisfies MDXComponents;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export const useMDXComponents = getMDXComponents;
|
| 12 |
+
|
| 13 |
+
declare global {
|
| 14 |
+
type MDXProvidedComponents = ReturnType<typeof getMDXComponents>;
|
| 15 |
+
}
|
apps/docs/content/docs/api.mdx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Provider API
|
| 3 |
+
description: The contract your TTS endpoint needs to join the arena.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
The arena talks to your model through a small HTTP contract. Our router calls
|
| 7 |
+
your endpoint server-side, downloads the audio, and proxies it to the client -
|
| 8 |
+
so the response never reaches the browser directly and your keys stay private.
|
| 9 |
+
|
| 10 |
+
## The request
|
| 11 |
+
|
| 12 |
+
We send a line of text and (optionally) a voice id. Your endpoint synthesizes it
|
| 13 |
+
and returns audio. A typical shape:
|
| 14 |
+
|
| 15 |
+
```http
|
| 16 |
+
POST https://your-api.example.com/tts
|
| 17 |
+
Authorization: Bearer <key>
|
| 18 |
+
Content-Type: application/json
|
| 19 |
+
|
| 20 |
+
{
|
| 21 |
+
"text": "The quick brown fox jumps over the lazy dog.",
|
| 22 |
+
"voice_id": "one-of-your-voice-ids"
|
| 23 |
+
}
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
The exact field names are flexible - we adapt a small provider adapter per
|
| 27 |
+
model. What matters is that, given text, you return speech.
|
| 28 |
+
|
| 29 |
+
## The response
|
| 30 |
+
|
| 31 |
+
Either is fine:
|
| 32 |
+
|
| 33 |
+
- **Raw audio bytes** (`audio/mpeg`, `audio/wav`, …) returned directly, or
|
| 34 |
+
- **JSON** containing base64 audio or a public URL we can download.
|
| 35 |
+
|
| 36 |
+
Common formats (mp3, wav, ogg, flac, opus) all work; we normalize on our side.
|
| 37 |
+
|
| 38 |
+
## Voices
|
| 39 |
+
|
| 40 |
+
The arena cycles a **fixed pool of voices** rather than cloning. Provide a list
|
| 41 |
+
of voice ids and we rotate through them across battles, so a model is judged
|
| 42 |
+
across its range rather than a single voice.
|
| 43 |
+
|
| 44 |
+
## Reliability
|
| 45 |
+
|
| 46 |
+
- Aim to respond within ~15-30s for a sentence-length prompt.
|
| 47 |
+
- Return a non-2xx (or an error payload) on failure - we record it, retry with
|
| 48 |
+
another model, and surface failing models in our admin tooling.
|
| 49 |
+
|
| 50 |
+
<Callout type="info">
|
| 51 |
+
Latency and success rate are tracked per model. A model that fails frequently
|
| 52 |
+
can be temporarily timed out so it doesn't disrupt battles.
|
| 53 |
+
</Callout>
|
apps/docs/content/docs/development.mdx
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Development
|
| 3 |
+
description: Run TTS Arena locally and find your way around the codebase.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
TTS Arena is a [Bun](https://bun.sh) monorepo. The web app is Next.js; the
|
| 7 |
+
router is a separate Hono service that talks to providers.
|
| 8 |
+
|
| 9 |
+
## Prerequisites
|
| 10 |
+
|
| 11 |
+
- [Bun](https://bun.sh) 1.4.0
|
| 12 |
+
- A Postgres database
|
| 13 |
+
- A Hugging Face OAuth app for sign-in
|
| 14 |
+
([create one](https://huggingface.co/settings/applications/new))
|
| 15 |
+
|
| 16 |
+
## Setup
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
git clone https://github.com/TTS-AGI/TTS-Arena
|
| 20 |
+
cd TTS-Arena
|
| 21 |
+
bun install
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
Point `DATABASE_URL` at your Postgres instance, then create the schema and seed
|
| 25 |
+
a starter set of models:
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
cd apps/web
|
| 29 |
+
bun run db:migrate
|
| 30 |
+
bun run db:seed
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## Configuration
|
| 34 |
+
|
| 35 |
+
Set these in the environment (e.g. an `.env` the web app can read):
|
| 36 |
+
|
| 37 |
+
| Variable | Required | Notes |
|
| 38 |
+
| ------------------------ | -------- | ------------------------------------------------------------------------- |
|
| 39 |
+
| `DATABASE_URL` | yes | Postgres connection string. Add `?sslmode=require` for a TLS-only server. |
|
| 40 |
+
| `HF_OAUTH_CLIENT_ID` | yes | From your Hugging Face OAuth app. |
|
| 41 |
+
| `HF_OAUTH_CLIENT_SECRET` | yes | Same app. |
|
| 42 |
+
| `SESSION_SECRET` | yes | Any long random string. |
|
| 43 |
+
| `ADMIN_USERS` | no | Comma-separated HF usernames with admin access. |
|
| 44 |
+
| `ROUTER_URL` | no | Router base URL. Defaults to `http://localhost:8080`. |
|
| 45 |
+
| `APP_URL` | no | Public app URL. Defaults to `http://localhost:3000`. |
|
| 46 |
+
| `SECURITY_DISABLED` | no | Set to `1` to turn off the anti-fraud gate in local dev. |
|
| 47 |
+
|
| 48 |
+
Provider API keys are read by the router from the environment too - add them as
|
| 49 |
+
you wire up providers.
|
| 50 |
+
|
| 51 |
+
## Running
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
bun run dev # web app on :3000
|
| 55 |
+
bun run dev:router # router on :8080
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
## Useful scripts
|
| 59 |
+
|
| 60 |
+
Run from the repo root:
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
bun run build # build every workspace
|
| 64 |
+
bun run lint # lint every workspace
|
| 65 |
+
bun run typecheck # type-check every workspace
|
| 66 |
+
bun test # run tests
|
| 67 |
+
bun run format # prettier --write
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
And from `apps/web` for database work:
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
bun run db:generate # generate a migration from schema changes
|
| 74 |
+
bun run db:migrate # apply migrations
|
| 75 |
+
bun run db:seed # seed models from the provider packages
|
| 76 |
+
bun run db:recompute # replay clean votes and rebuild ratings
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## Layout
|
| 80 |
+
|
| 81 |
+
```
|
| 82 |
+
apps/web Next.js app - arena, leaderboard, admin
|
| 83 |
+
apps/router Hono service that calls TTS providers
|
| 84 |
+
apps/docs this documentation site (Fumadocs)
|
| 85 |
+
packages/shared shared types + the rating math
|
| 86 |
+
packages/provider-sdk provider interface + registry
|
| 87 |
+
packages/providers/* individual public providers
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
The rating logic lives in `packages/shared` - `bradley-terry.ts` and
|
| 91 |
+
`glicko.ts` - with tests alongside. See [Ranking](/ranking) for how it's used.
|
| 92 |
+
|
| 93 |
+
## Contributing
|
| 94 |
+
|
| 95 |
+
Issues and pull requests are welcome on
|
| 96 |
+
[GitHub](https://github.com/TTS-AGI/TTS-Arena). The project is licensed under
|
| 97 |
+
[Apache 2.0](https://github.com/TTS-AGI/TTS-Arena/blob/main/LICENSE).
|
apps/docs/content/docs/index.mdx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Introduction
|
| 3 |
+
description: A crowdsourced, blind benchmark for text-to-speech.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
**TTS Arena** ranks text-to-speech models by ear. You type a line, two anonymous
|
| 7 |
+
models read it back, and you pick the one that sounds more human. Each vote
|
| 8 |
+
feeds the leaderboard.
|
| 9 |
+
|
| 10 |
+
The models stay hidden until you've voted, so the choice is about the audio, not
|
| 11 |
+
the name attached to it.
|
| 12 |
+
|
| 13 |
+
[**Open the arena and vote →**](https://huggingface.co/spaces/TTS-AGI/TTS-Arena-V2)
|
| 14 |
+
|
| 15 |
+
## Why
|
| 16 |
+
|
| 17 |
+
There hasn't been a good way to measure how natural a synthetic voice sounds.
|
| 18 |
+
Word error rate tells you whether speech is intelligible, not whether it sounds
|
| 19 |
+
alive. Mean opinion scores rely on a small panel in a lab. TTS Arena uses
|
| 20 |
+
large-scale human preference instead - anyone can listen, compare, and vote, and
|
| 21 |
+
the resulting leaderboard is open.
|
| 22 |
+
|
| 23 |
+
## Start here
|
| 24 |
+
|
| 25 |
+
<Cards>
|
| 26 |
+
<Card title="Voting" href="/voting">
|
| 27 |
+
How to vote and the rules that keep the board fair.
|
| 28 |
+
</Card>
|
| 29 |
+
<Card title="Ranking" href="/ranking">
|
| 30 |
+
How votes become a leaderboard.
|
| 31 |
+
</Card>
|
| 32 |
+
<Card title="Submit a model" href="/submit-a-model">
|
| 33 |
+
Add your model, publicly or under a codename.
|
| 34 |
+
</Card>
|
| 35 |
+
<Card title="Integrity policy" href="/integrity">
|
| 36 |
+
The short version of what providers agree to.
|
| 37 |
+
</Card>
|
| 38 |
+
<Card title="Provider Agreement" href="/provider-agreement">
|
| 39 |
+
The full terms for listing a model on the arena.
|
| 40 |
+
</Card>
|
| 41 |
+
<Card title="Provider API" href="/api">
|
| 42 |
+
The HTTP contract your TTS endpoint needs to meet.
|
| 43 |
+
</Card>
|
| 44 |
+
</Cards>
|
| 45 |
+
|
| 46 |
+
## Quick facts
|
| 47 |
+
|
| 48 |
+
- Sign in with Hugging Face to vote; accounts must be at least 30 days old.
|
| 49 |
+
- Prompts are English-only for now, capped at 1,000 characters.
|
| 50 |
+
- Models are revealed only after you vote.
|
| 51 |
+
- TTS Arena is open source under Apache 2.0 -
|
| 52 |
+
[source on GitHub](https://github.com/TTS-AGI/TTS-Arena).
|
apps/docs/content/docs/integrity-decisions.mdx
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Integrity decisions
|
| 3 |
+
description: The public record of models suspended from TTS Arena for vote manipulation — what happened, the provider's response, and the outcome in each case.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
TTS Arena ranks text-to-speech models through blind A/B voting: voters hear two clips and pick the better one, and the leaderboard is computed from those votes. Manipulated votes break that evaluation.
|
| 7 |
+
|
| 8 |
+
This page is the public record of models suspended for vote manipulation. In every case below, the matter was put to the provider before a decision was made, and the outcome was the same: the manipulated votes were excluded, the leaderboard was recomputed from clean votes, and the model was suspended.
|
| 9 |
+
|
| 10 |
+
Decisions are made under the [Provider Agreement](/provider-agreement). Providers may not vote on their own model, through staff or anyone acting for them (s4.1), or use any side channel, including the sound of their own model, to work out which clip is theirs (s4.2). Intent is not the test, and decisions are final (s9). A suspended model stays on the board, unranked, at the bottom, with the reason and date shown; the label has no expiry, and the model cannot be retracted, withdrawn, renamed, or resubmitted under another name (s8.3).
|
| 11 |
+
|
| 12 |
+
## Suspended models
|
| 13 |
+
|
| 14 |
+
| Model | Provider | Suspended | Accounts | Votes excluded |
|
| 15 |
+
| ---------------------------- | ------------- | ---------- | -------------------- | -------------- |
|
| 16 |
+
| SwanTale (listed as "Venus") | SwanTale team | 2026-10-31 | 3 | 170 |
|
| 17 |
+
| Audio8-TTS-Realistic-0.6B | Edge0 | 2026-10-30 | 11 | 670 |
|
| 18 |
+
| U2-TTS | Unisound | 2026-09-05 | 5 | 703 |
|
| 19 |
+
| Vocu V3.0 | Vocu | 2026-07-03 | Provider-linked ring | ~4,900 |
|
| 20 |
+
|
| 21 |
+
## Cases
|
| 22 |
+
|
| 23 |
+
### SwanTale (stealth codename "Venus") — suspended 2026-10-31
|
| 24 |
+
|
| 25 |
+
**Provider:** SwanTale team
|
| 26 |
+
|
| 27 |
+
SwanTale was listed under the stealth codename "Venus" and is now displayed under its real name.
|
| 28 |
+
|
| 29 |
+
- Between 2026-10-27 and 2026-10-28, three accounts cast 170 votes. The account names were the submitting group's own research project names, and the accounts were active in sequence with no overlap.
|
| 30 |
+
- The three accounts saw Venus in 33 battles and picked Venus in all 33. Other voters picked Venus about 60% of the time.
|
| 31 |
+
- The provider's first response: the accounts belonged to two different students and were not team-controlled.
|
| 32 |
+
- After the evidence was shared, the provider's second response confirmed that a team member had organised the voting, that two colleagues had voted through the group's shared project accounts, and that a VPN/proxy had been used. The provider accepted the suspension.
|
| 33 |
+
|
| 34 |
+
From the provider's response:
|
| 35 |
+
|
| 36 |
+
> "I sincerely apologize for encouraging and coordinating votes in support of Venus."
|
| 37 |
+
|
| 38 |
+
> "I acknowledge that we also deliberately took turns to make the coordinated activity less conspicuous. This was inappropriate, and I deeply regret it."
|
| 39 |
+
|
| 40 |
+
**Outcome.** The 170 votes were excluded, SwanTale's rating was recomputed from 1722 to approximately 1508, and the model was suspended.
|
| 41 |
+
|
| 42 |
+
### Audio8-TTS-Realistic-0.6B — suspended 2026-10-30
|
| 43 |
+
|
| 44 |
+
**Provider:** Edge0
|
| 45 |
+
|
| 46 |
+
- Audio8-TTS-Realistic-0.6B was listed on 2026-10-23. Starting on listing day, eleven accounts — Edge0 staff and company-linked accounts — cast 670 votes.
|
| 47 |
+
- These accounts saw Audio8 in 149 battles and picked it 121 times (81%). Other voters picked Audio8 57% of the time.
|
| 48 |
+
- The provider confirmed the accounts and the activity, describing an office group session of roughly an hour in which colleagues were invited to listen after Audio8 wins and so became familiar with its voices. Per the provider, the activity stopped once the rating rose sharply, and the team had been unaware of the affiliated-voting ban.
|
| 49 |
+
- The voting records show activity from 02:08 to 11:50 UTC on 2026-10-23 — roughly ten hours — and one of the accounts voted again on 2026-10-26 and 2026-10-27.
|
| 50 |
+
- Recognising the model's own voice and picking it is un-blinding under s4.2; under s9, intent is not the test.
|
| 51 |
+
|
| 52 |
+
From the provider's response:
|
| 53 |
+
|
| 54 |
+
> "The account connections and voting activity you described are real: I participated myself"
|
| 55 |
+
|
| 56 |
+
**Outcome.** The provider agreed to the exclusion of the votes and asked for the suspension and label to be lifted; the request was declined. The 670 votes were excluded, the rating was recomputed from approximately 1690 to approximately 1590, and the model's clean battle count fell from 182 to 47. The model was suspended.
|
| 57 |
+
|
| 58 |
+
### U2-TTS — suspended 2026-09-05
|
| 59 |
+
|
| 60 |
+
**Provider:** Unisound
|
| 61 |
+
|
| 62 |
+
- Between 2026-08-21 and 2026-09-04, five accounts cast 703 votes. Per the provider, two of the accounts were company test accounts and one was an employee's personal account.
|
| 63 |
+
- The five accounts voted almost only when U2-TTS appeared in a battle and abandoned most other battles. Voters generally vote at the same rate whether or not a given model appears.
|
| 64 |
+
- The accounts covered 295 of the model's 452 battles. On 2026-08-31 — the day U2-TTS passed 300 votes, the threshold at which the Preliminary label is removed — they covered 265 of 321.
|
| 65 |
+
- Outside voters picked U2-TTS in 54% of battles (85 of 157); the employee account picked it in 93% (38 of 41). The model's listed win rate was 63%.
|
| 66 |
+
- On a call, the provider said the voters had recognised U2-TTS by its voice and picked it when outputs sounded similar, and that the vote count had been watched with the goal of passing 300 votes to drop the Preliminary label. The provider said it had been unaware that this counted as manipulation.
|
| 67 |
+
|
| 68 |
+
The provider, describing the voting pattern:
|
| 69 |
+
|
| 70 |
+
> "we didn't want to spend too much time listening and waiting for the voting buttons to appear just to cast votes, when we did not hear our system sound (i.e, there is no our model in A/B test), because that would have significantly increased our time cost and slowed down our overall evaluation process."
|
| 71 |
+
|
| 72 |
+
The provider asked, in order, for: the votes to be excluded and the model relisted; the model to be retracted and re-evaluated from scratch; the listing to be renamed; the model to be removed from the public board. All four requests were declined — under s8.3 a suspended model cannot be retracted, renamed, or resubmitted, and under s9 decisions are final.
|
| 73 |
+
|
| 74 |
+
**Outcome.** The 703 votes were excluded, the board was recomputed, and the model was suspended. After this case, an explicit rule was added: provider staff may not vote on their own model.
|
| 75 |
+
|
| 76 |
+
### Vocu V3.0 — suspended 2026-07-03
|
| 77 |
+
|
| 78 |
+
**Provider:** Vocu
|
| 79 |
+
|
| 80 |
+
- A ring of provider-linked accounts cast roughly 4,900 votes. The volume and regularity of the voting indicated automation, not listening.
|
| 81 |
+
- When Vocu appeared in a battle, the ring's four main accounts picked it 99% of the time. Among 202 other voters, Vocu won 33% of the time.
|
| 82 |
+
- The provider first denied owning a linked account. When shown that the account was registered to an email address belonging to the provider, the provider confirmed it and apologised.
|
| 83 |
+
|
| 84 |
+
The provider's first reply:
|
| 85 |
+
|
| 86 |
+
> "I actually don't even own that account..."
|
| 87 |
+
|
| 88 |
+
And after being shown the registration:
|
| 89 |
+
|
| 90 |
+
> "Yes, this is my email address. I apologize for my earlier moment of rashness in trying to justify our wrongdoing — it was deeply shameful of me."
|
| 91 |
+
|
| 92 |
+
**Outcome.** The roughly 4,900 votes were excluded, the board was recomputed, and the model was suspended.
|
| 93 |
+
|
| 94 |
+
## Reporting
|
| 95 |
+
|
| 96 |
+
To report suspected vote manipulation or any other breach of the [Provider Agreement](/provider-agreement), use [Discord](https://discord.gg/HB8fMR6GTr) or [email](mailto:me@mrfake.name). All reports are reviewed.
|
apps/docs/content/docs/integrity.mdx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Integrity policy
|
| 3 |
+
description: The short version of what providers agree to. The full terms are in the Provider Agreement.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
TTS Arena only works if the votes are real. Every provider accepts this policy
|
| 7 |
+
by submitting a model. The full terms are in the
|
| 8 |
+
[Provider Agreement](/provider-agreement); this page is the part that matters
|
| 9 |
+
most, kept short so there is no excuse for not reading it.
|
| 10 |
+
|
| 11 |
+
## Rules
|
| 12 |
+
|
| 13 |
+
1. **Nobody on your side votes on your model.** Not staff, contractors, agencies,
|
| 14 |
+
or anyone you ask. Not from personal accounts, not from "test accounts".
|
| 15 |
+
2. **You don't un-blind the arena.** No matching endpoint logs to arena
|
| 16 |
+
sessions, no audio tells, no side channel that reveals which side is yours.
|
| 17 |
+
3. **No bought, botted, or coordinated votes.**
|
| 18 |
+
4. **The arena is for ranking, not internal evaluation.** Want to hear your
|
| 19 |
+
model next to others? Type a custom prompt - those votes never move the board.
|
| 20 |
+
5. **We know who you are**, even for codenamed entries, and you answer honestly
|
| 21 |
+
when we ask.
|
| 22 |
+
|
| 23 |
+
## If it's broken
|
| 24 |
+
|
| 25 |
+
- The votes are excluded and the board is recomputed.
|
| 26 |
+
- The model is **suspended**: it stays on the leaderboard, at the bottom, with a
|
| 27 |
+
label saying why and when. The label doesn't expire. Suspended models can't be
|
| 28 |
+
retracted, renamed, or withdrawn.
|
| 29 |
+
- We may publish what happened, including what the provider told us.
|
| 30 |
+
|
| 31 |
+
Nothing is deleted, so the record can be checked. Past cases are listed in
|
| 32 |
+
[Integrity decisions](/integrity-decisions). Decisions are made by the
|
| 33 |
+
TTS Arena team and are final.
|
| 34 |
+
|
| 35 |
+
Questions: [Discord](https://discord.gg/HB8fMR6GTr) or
|
| 36 |
+
[email](mailto:me@mrfake.name).
|
apps/docs/content/docs/meta.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"title": "Docs",
|
| 3 |
+
"pages": [
|
| 4 |
+
"index",
|
| 5 |
+
"voting",
|
| 6 |
+
"ranking",
|
| 7 |
+
"submit-a-model",
|
| 8 |
+
"integrity",
|
| 9 |
+
"integrity-decisions",
|
| 10 |
+
"provider-agreement",
|
| 11 |
+
"privacy",
|
| 12 |
+
"api",
|
| 13 |
+
"development"
|
| 14 |
+
]
|
| 15 |
+
}
|
apps/docs/content/docs/privacy.mdx
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Privacy
|
| 3 |
+
description: What TTS Arena collects, why, and what we do with it.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
TTS Arena is a volunteer-run, open-source research project. We collect what we
|
| 7 |
+
need to keep the votes honest and to build a useful benchmark, and not much
|
| 8 |
+
else. This page says what that is in plain terms.
|
| 9 |
+
|
| 10 |
+
## What we collect
|
| 11 |
+
|
| 12 |
+
**When you sign in** with Hugging Face, we receive your Hugging Face user ID,
|
| 13 |
+
username, avatar, account creation date, and the email address on the account.
|
| 14 |
+
The account age is used for the 30-day voting requirement. The email is stored
|
| 15 |
+
so we can contact you about your account (for example, if it's been
|
| 16 |
+
quarantined) and is not used for anything else.
|
| 17 |
+
|
| 18 |
+
**When you use the arena**, we record:
|
| 19 |
+
|
| 20 |
+
- the prompts you submit and the audio each model produced for them;
|
| 21 |
+
- your votes, and how long you listened before voting;
|
| 22 |
+
- your IP address, browser user-agent, and a device fingerprint, on sign-in and
|
| 23 |
+
with each vote;
|
| 24 |
+
- session events - battles started, whether they were voted on or abandoned,
|
| 25 |
+
generation timings and errors.
|
| 26 |
+
|
| 27 |
+
A **captcha** may run once per browser. It stores a short-lived token and
|
| 28 |
+
nothing about you.
|
| 29 |
+
|
| 30 |
+
## Why
|
| 31 |
+
|
| 32 |
+
Almost all of this exists for one reason: a blind preference benchmark is only
|
| 33 |
+
worth something if the votes are real. IP, fingerprint, timing, and session
|
| 34 |
+
data are what let us spot automated voting, coordinated rings, and accounts
|
| 35 |
+
that can somehow recognise a model they shouldn't be able to. The
|
| 36 |
+
[voting](/voting) page describes how that system works.
|
| 37 |
+
|
| 38 |
+
The prompts and audio are the benchmark itself. They're what we and others can
|
| 39 |
+
use to study how people judge synthetic speech.
|
| 40 |
+
|
| 41 |
+
## What we publish
|
| 42 |
+
|
| 43 |
+
- The leaderboard: per-model ratings, uncertainty, win rates, and vote counts.
|
| 44 |
+
Never per-voter data.
|
| 45 |
+
- Aggregate statistics and, from time to time, anonymised datasets of prompts,
|
| 46 |
+
audio, and preferences. These contain no account identifiers, IP addresses,
|
| 47 |
+
fingerprints, or email addresses.
|
| 48 |
+
- Accounts of integrity findings against model providers. These name the model
|
| 49 |
+
and the provider, and can quote what the provider told us. They can describe
|
| 50 |
+
the behaviour of accounts involved. See the
|
| 51 |
+
[Provider Agreement](/provider-agreement).
|
| 52 |
+
|
| 53 |
+
## What we don't do
|
| 54 |
+
|
| 55 |
+
- We don't sell data, run ads, or share personal data with model providers.
|
| 56 |
+
Providers see their model's ratings, not who voted.
|
| 57 |
+
- We don't send your identity to the model endpoints. They receive the prompt
|
| 58 |
+
text, nothing else.
|
| 59 |
+
- We don't track you across other sites.
|
| 60 |
+
|
| 61 |
+
## Retention
|
| 62 |
+
|
| 63 |
+
We keep vote, session, and anti-abuse records indefinitely, because a decision
|
| 64 |
+
about the leaderboard has to remain checkable after the fact. Cached audio is
|
| 65 |
+
kept as long as it's useful to the benchmark.
|
| 66 |
+
|
| 67 |
+
If you want your account data removed, contact us. We'll delete your profile
|
| 68 |
+
information (username, email, avatar). Vote records may be retained in
|
| 69 |
+
anonymised form so the leaderboard's history remains consistent.
|
| 70 |
+
|
| 71 |
+
## Where it lives
|
| 72 |
+
|
| 73 |
+
The app runs on Hugging Face Spaces; the database runs on a server we operate.
|
| 74 |
+
Hugging Face handles the sign-in and has its own
|
| 75 |
+
[privacy policy](https://huggingface.co/privacy). TTS Arena is not affiliated
|
| 76 |
+
with Hugging Face.
|
| 77 |
+
|
| 78 |
+
## Contact
|
| 79 |
+
|
| 80 |
+
Questions or deletion requests: [Discord](https://discord.gg/HB8fMR6GTr) or
|
| 81 |
+
[email](mailto:me@mrfake.name).
|
| 82 |
+
|
| 83 |
+
_Last updated 5 September 2026._
|
apps/docs/content/docs/provider-agreement.mdx
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Provider Agreement
|
| 3 |
+
description: The terms under which a model is listed and evaluated on TTS Arena.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
_Effective 5 September 2026._
|
| 7 |
+
|
| 8 |
+
This agreement is between the **TTS Arena** project ("TTS Arena", "we", "us")
|
| 9 |
+
and any person or organisation that submits a text-to-speech model for
|
| 10 |
+
evaluation (the "Provider", "you"). By submitting a model, or by leaving a model
|
| 11 |
+
listed after this agreement is posted, you accept it on behalf of yourself and
|
| 12 |
+
the organisation you represent.
|
| 13 |
+
|
| 14 |
+
It is written in plain English on purpose. Where it says "must", it means it.
|
| 15 |
+
|
| 16 |
+
## 1. Who we are
|
| 17 |
+
|
| 18 |
+
TTS Arena is an independent, open-source community project maintained by
|
| 19 |
+
volunteers. **It is not affiliated with, operated by, sponsored by, or endorsed
|
| 20 |
+
by Hugging Face, Inc.**, or by any model provider. The arena is hosted on
|
| 21 |
+
Hugging Face Spaces and uses Hugging Face accounts for sign-in, in the same way
|
| 22 |
+
any third-party project may; that does not make it a Hugging Face product, and
|
| 23 |
+
nothing in the arena's rankings, labels, or communications is a statement by
|
| 24 |
+
Hugging Face. The source is public under the Apache 2.0 licence.
|
| 25 |
+
|
| 26 |
+
## 2. Submitting a model
|
| 27 |
+
|
| 28 |
+
1. **Accurate information.** Everything you tell us about the model - its name,
|
| 29 |
+
provider, whether it is publicly available, which model is actually behind
|
| 30 |
+
the endpoint - must be true, and you must tell us when it changes.
|
| 31 |
+
2. **Identity.** You must tell us who you are: the organisation, and a contact
|
| 32 |
+
who can speak for it. This applies to codenamed entries too (see section 5).
|
| 33 |
+
3. **Authority.** The person submitting confirms they are authorised to bind the
|
| 34 |
+
Provider to this agreement.
|
| 35 |
+
4. **One model, one entry.** We evaluate one version of a model at a time. A new
|
| 36 |
+
version is a new submission, and we may decline to run two entries from the
|
| 37 |
+
same provider that are substantially the same model.
|
| 38 |
+
5. **Our discretion.** We may accept, decline, delay, or remove any submission,
|
| 39 |
+
for any reason, including capacity, cost, and the integrity of the board.
|
| 40 |
+
|
| 41 |
+
## 3. The endpoint
|
| 42 |
+
|
| 43 |
+
1. **It serves the model you said it does**, and the same model that users of
|
| 44 |
+
your product get. No arena-specific tuning, no better model for arena
|
| 45 |
+
traffic, no silent swaps. If you change what is behind the endpoint, tell us
|
| 46 |
+
first; we will decide whether the entry continues or restarts.
|
| 47 |
+
2. **Prompt handling.** The endpoint must synthesise what it is given. It must
|
| 48 |
+
not detect arena prompts, treat arena traffic differently from ordinary
|
| 49 |
+
traffic, or cache and replay outputs for known prompts. If it refuses a
|
| 50 |
+
prompt (for example on safety grounds), it must return an error, not degraded
|
| 51 |
+
audio.
|
| 52 |
+
3. **Voices.** You supply a fixed pool of voices for us to rotate through. We do
|
| 53 |
+
not do zero-shot cloning in the arena.
|
| 54 |
+
4. **Availability.** The endpoint should be reliably available. We may time out,
|
| 55 |
+
retire, or remove a model whose endpoint is failing, slow enough to distort
|
| 56 |
+
the listening test, or rate-limiting the arena below what evaluation needs.
|
| 57 |
+
5. **Cost.** You bear the cost of serving your own endpoint, and you must not
|
| 58 |
+
charge the arena for it. We may cap the volume of requests we send.
|
| 59 |
+
6. **Keys.** We keep your API credentials confidential, use them only for the
|
| 60 |
+
arena, and delete them when the model is retired. You may rotate them at any
|
| 61 |
+
time by telling us.
|
| 62 |
+
|
| 63 |
+
## 4. Integrity
|
| 64 |
+
|
| 65 |
+
The [integrity policy](/integrity) is part of this agreement. In full:
|
| 66 |
+
|
| 67 |
+
1. **No voting on your own model** by anyone on your side: employees,
|
| 68 |
+
contractors, interns, agencies, investors, family, communities you run, or
|
| 69 |
+
anyone acting at your request or with your encouragement. This includes
|
| 70 |
+
personal accounts and any "test" or shared accounts. You must not create,
|
| 71 |
+
lend, or control voter accounts.
|
| 72 |
+
2. **No un-blinding.** You must not use your endpoint's request logs,
|
| 73 |
+
timestamps, telemetry, watermarks, audio artefacts, prompt matching, or any
|
| 74 |
+
other side channel to work out which side of a battle is yours, whether or
|
| 75 |
+
not you then vote. The arena is a blind test; defeating the blind for
|
| 76 |
+
yourself defeats the test.
|
| 77 |
+
3. **No purchased, incentivised, automated, or coordinated votes.** No paying
|
| 78 |
+
or rewarding people for votes, no bots or scripts, no vote drives, no asking
|
| 79 |
+
a community to "go vote for us".
|
| 80 |
+
4. **No selective participation.** You must not use the arena as an internal
|
| 81 |
+
evaluation harness - for example, generating battles and abandoning the ones
|
| 82 |
+
your model isn't in. Compare models with your own tooling, or with custom
|
| 83 |
+
prompts here, which do not affect the ratings.
|
| 84 |
+
5. **No harvesting.** You must not scrape the arena, or use it to collect other
|
| 85 |
+
providers' audio, prompts, or outputs for training, benchmarking, or
|
| 86 |
+
reverse-engineering.
|
| 87 |
+
6. **Disclosure.** If you become aware that anyone connected to you has done any
|
| 88 |
+
of the above, you must tell us promptly.
|
| 89 |
+
7. **Cooperation.** If we contact you about activity that looks like a breach,
|
| 90 |
+
you must respond honestly and within a reasonable time. Silence, or answers
|
| 91 |
+
that later prove false, are themselves a breach.
|
| 92 |
+
|
| 93 |
+
## 5. Codenamed (anonymous) entries
|
| 94 |
+
|
| 95 |
+
1. You may run an unreleased model under a codename. It is anonymous **to
|
| 96 |
+
voters**, not to us: we must know the provider and the model.
|
| 97 |
+
2. Anonymity is bounded by time or vote count, set when the entry is accepted,
|
| 98 |
+
and ends regardless of how the model performs. You may withdraw before then;
|
| 99 |
+
you may not reveal only because it won, or keep an entry codenamed
|
| 100 |
+
indefinitely.
|
| 101 |
+
3. A model that is already publicly available runs under its real name.
|
| 102 |
+
4. We keep your identity confidential until the reveal **as a courtesy, not a
|
| 103 |
+
guarantee**. If this agreement is breached we may disclose the provider as
|
| 104 |
+
part of the outcome. We may also disclose it if required by law.
|
| 105 |
+
|
| 106 |
+
## 6. Data, audio, and publication
|
| 107 |
+
|
| 108 |
+
1. **Generated audio.** Audio your endpoint produces for the arena may be
|
| 109 |
+
stored, played to voters, cached, and published, including as part of
|
| 110 |
+
research datasets and in illustrations of results. You grant TTS Arena a
|
| 111 |
+
perpetual, worldwide, royalty-free licence to do so, and you confirm you have
|
| 112 |
+
the rights needed to grant it.
|
| 113 |
+
2. **Votes and sessions.** Vote records, session data, and anti-abuse signals
|
| 114 |
+
belong to the arena. We may publish aggregate results and anonymised data.
|
| 115 |
+
Providers do not receive raw voter data.
|
| 116 |
+
3. **Correspondence.** Communications between you and the arena about a listing,
|
| 117 |
+
an integrity review, or a decision may be quoted or published, in whole or
|
| 118 |
+
in part, as part of our account of that matter.
|
| 119 |
+
4. **Outcomes.** We may publish the outcome of any integrity review, including
|
| 120 |
+
the model name, the provider name, what the data showed, and what the
|
| 121 |
+
provider told us. By submitting, you consent to that publication.
|
| 122 |
+
5. **Nothing is deleted.** Votes, sessions, and correspondence are retained so
|
| 123 |
+
that any decision can be checked afterwards.
|
| 124 |
+
|
| 125 |
+
## 7. Using arena results
|
| 126 |
+
|
| 127 |
+
1. You may cite your model's ranking, rating, or win rate, provided you state
|
| 128 |
+
the date and link to the leaderboard. Rankings move; a claim must be true
|
| 129 |
+
when it is made and must not be presented as permanent.
|
| 130 |
+
2. You must not alter, crop, or restyle leaderboard screenshots in a way that
|
| 131 |
+
changes their meaning, omit the Preliminary or Suspended labels, or present
|
| 132 |
+
results of custom prompts, side-by-side listening, or your own testing as
|
| 133 |
+
arena results.
|
| 134 |
+
3. You must not describe TTS Arena results as an official ranking by Hugging
|
| 135 |
+
Face or anyone else, or imply that TTS Arena endorses your model.
|
| 136 |
+
4. You may use the TTS Arena name to refer to the arena factually. You may not
|
| 137 |
+
use it, or the arena's marks, in a way that suggests partnership or
|
| 138 |
+
endorsement.
|
| 139 |
+
|
| 140 |
+
## 8. Labels, withdrawal, and removal
|
| 141 |
+
|
| 142 |
+
1. **Preliminary** marks a model with fewer votes than we need to settle its
|
| 143 |
+
rating. It is ranked normally.
|
| 144 |
+
2. **Retired** marks a model whose provider has stopped serving it. You may
|
| 145 |
+
retire a model at any time by telling us. It stays on the board with the
|
| 146 |
+
rating it earned.
|
| 147 |
+
3. **Suspended** marks a model removed from evaluation for a breach of this
|
| 148 |
+
agreement. It stays on the leaderboard, at the bottom, unranked, with the
|
| 149 |
+
reason and date. The label has no expiry. A suspended model **cannot be
|
| 150 |
+
retracted, withdrawn, renamed, or re-submitted under another name**, and
|
| 151 |
+
retiring it does not remove the label.
|
| 152 |
+
4. We may remove or retire a model for technical reasons (a failing endpoint, a
|
| 153 |
+
discontinued API) without that being a finding against the provider, and we
|
| 154 |
+
will say so.
|
| 155 |
+
|
| 156 |
+
## 9. Consequences of breach
|
| 157 |
+
|
| 158 |
+
If we find that this agreement has been breached:
|
| 159 |
+
|
| 160 |
+
1. affected votes are excluded and the leaderboard is recomputed from the clean
|
| 161 |
+
set;
|
| 162 |
+
2. the model is suspended as described in section 8;
|
| 163 |
+
3. we may publish an account of what happened as described in section 6;
|
| 164 |
+
4. we may decline future submissions from the same provider, or accept them on
|
| 165 |
+
conditions;
|
| 166 |
+
5. we may inform other evaluation platforms of the outcome.
|
| 167 |
+
|
| 168 |
+
Whether the breach was intended is not the test. Conduct that moves the board
|
| 169 |
+
is a breach whether or not that was the purpose.
|
| 170 |
+
|
| 171 |
+
Findings are made by the TTS Arena team on the evidence available to us. We
|
| 172 |
+
will contact the provider before a decision where we reasonably can, and we
|
| 173 |
+
will consider what the provider says. Decisions are final.
|
| 174 |
+
|
| 175 |
+
## 10. Disclaimers
|
| 176 |
+
|
| 177 |
+
1. The arena, its ratings, and its API are provided **"as is"**, without
|
| 178 |
+
warranty of any kind. Ratings are statistical estimates from crowd
|
| 179 |
+
preferences; they are not a measure of fitness for any purpose.
|
| 180 |
+
2. We do not guarantee that a model will be listed, evaluated to any number of
|
| 181 |
+
votes, evaluated within any time, or kept on the board.
|
| 182 |
+
3. We do not guarantee uptime, and we may change the arena's methodology,
|
| 183 |
+
thresholds, design, or rules at any time. Material changes to this agreement
|
| 184 |
+
will be posted here with a new effective date; keeping a model listed after
|
| 185 |
+
that is acceptance.
|
| 186 |
+
|
| 187 |
+
## 11. Liability and indemnity
|
| 188 |
+
|
| 189 |
+
1. To the fullest extent permitted by law, TTS Arena and its maintainers are
|
| 190 |
+
not liable to you for any indirect, incidental, consequential, or special
|
| 191 |
+
damages, or for lost profits, revenue, or business, arising from the arena,
|
| 192 |
+
a listing, a rating, a label, or a published outcome. Where liability cannot
|
| 193 |
+
be excluded, it is limited to US $100.
|
| 194 |
+
2. You will indemnify TTS Arena and its maintainers against claims arising from
|
| 195 |
+
your endpoint, your audio, your use of arena results, or your breach of this
|
| 196 |
+
agreement.
|
| 197 |
+
|
| 198 |
+
## 12. General
|
| 199 |
+
|
| 200 |
+
1. **Governing law.** This agreement is governed by the laws of the State of
|
| 201 |
+
California, USA, without regard to conflict-of-law rules. Disputes will be
|
| 202 |
+
brought in the state or federal courts located in California, and you
|
| 203 |
+
consent to their jurisdiction. Before bringing any claim, you agree to raise
|
| 204 |
+
the matter with us in writing and allow thirty days for an informal
|
| 205 |
+
resolution.
|
| 206 |
+
2. **Relationship.** Nothing here creates a partnership, agency, employment, or
|
| 207 |
+
joint venture. Listing a model is not an endorsement of the provider by the
|
| 208 |
+
arena, or of the arena by the provider.
|
| 209 |
+
3. **Assignment.** You may not transfer this agreement without our written
|
| 210 |
+
consent. We may assign it to a successor maintainer of the project.
|
| 211 |
+
4. **Severability.** If any part is unenforceable, the rest stands.
|
| 212 |
+
5. **Entire agreement.** This page, the [integrity policy](/integrity), and the
|
| 213 |
+
[Provider API](/api) documentation are the whole agreement between you and
|
| 214 |
+
TTS Arena about a listing. Anything said in Discord, email, or on a call
|
| 215 |
+
does not change it unless we confirm the change in writing.
|
| 216 |
+
6. **Contact.** The TTS Arena team can be reached on
|
| 217 |
+
[Discord](https://discord.gg/HB8fMR6GTr) or by
|
| 218 |
+
[email](mailto:me@mrfake.name).
|
apps/docs/content/docs/ranking.mdx
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Ranking
|
| 3 |
+
description: How votes become a leaderboard.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
Each counting vote is one head-to-head result: model A beat model B on a
|
| 7 |
+
controlled prompt from the Random pool. The leaderboard is what falls out when
|
| 8 |
+
you fit all of those results together.
|
| 9 |
+
|
| 10 |
+
## The rating
|
| 11 |
+
|
| 12 |
+
We rank models with a [Bradley–Terry](https://en.wikipedia.org/wiki/Bradley%E2%80%93Terry_model)
|
| 13 |
+
model - a standard way to turn pairwise wins and losses into a single strength
|
| 14 |
+
score per competitor. It's fit over the entire vote history at once, so a
|
| 15 |
+
model's rating reflects every matchup it has been in, not just its recent ones.
|
| 16 |
+
The result doesn't depend on the order votes arrived in.
|
| 17 |
+
|
| 18 |
+
Ratings are centered around 1500, so the numbers read like familiar Elo scores.
|
| 19 |
+
The fit is cached and refreshed as votes come in.
|
| 20 |
+
|
| 21 |
+
## Rank by the lower bound
|
| 22 |
+
|
| 23 |
+
A model's rating comes with a confidence interval - wide when there's little
|
| 24 |
+
data, narrow once it has played a lot. We show the rating but **sort by the
|
| 25 |
+
bottom of that interval**.
|
| 26 |
+
|
| 27 |
+
The effect: a model can't shoot to the top off a handful of lucky wins. It has
|
| 28 |
+
to be both good and well-tested to rank highly. Each row shows a `±` next to its
|
| 29 |
+
rating so you can see how settled it is.
|
| 30 |
+
|
| 31 |
+
## New models
|
| 32 |
+
|
| 33 |
+
- A model needs **100 votes** to appear on the board. Below that the rating
|
| 34 |
+
swings too much to mean anything.
|
| 35 |
+
- Under **300 votes** it's marked **Preliminary** - ranked normally, but still
|
| 36 |
+
moving.
|
| 37 |
+
- Brand-new models with only a few votes are hidden by default. Tick **Show new
|
| 38 |
+
models with few votes** on the leaderboard to see them, with wide error bars.
|
| 39 |
+
|
| 40 |
+
To keep tiny samples from producing absurd numbers (a model that has only ever
|
| 41 |
+
won would otherwise rate infinitely high), the fit is lightly regularized toward
|
| 42 |
+
the average. The nudge fades as real votes accumulate and is gone within a few
|
| 43 |
+
hundred.
|
| 44 |
+
|
| 45 |
+
## Other labels
|
| 46 |
+
|
| 47 |
+
- **Retired** - the provider no longer serves the model, so it's out of rotation.
|
| 48 |
+
Its rating is preserved from past votes.
|
| 49 |
+
- **Suspended** - the model was pulled for breaking the
|
| 50 |
+
[integrity policy](/integrity). It sits at the bottom of the board, unranked,
|
| 51 |
+
with the reason and date. Its votes and history are kept, the label doesn't
|
| 52 |
+
expire, and a suspended model can't be retracted or renamed.
|
| 53 |
+
|
| 54 |
+
## Why blind and pairwise
|
| 55 |
+
|
| 56 |
+
Knowing a model's name changes how people hear it, so identities stay hidden
|
| 57 |
+
until after the vote. And "which of these two is better?" is a far easier and
|
| 58 |
+
more reliable judgment than scoring a single clip out of context - it's how
|
| 59 |
+
listening tests have always been run.
|
| 60 |
+
|
| 61 |
+
<Callout type="info">
|
| 62 |
+
Only clean votes on first-use Random prompts count. Typed custom prompts,
|
| 63 |
+
votes flagged by the anti-fraud system, and votes from quarantined accounts
|
| 64 |
+
are left out of the fit. See [Voting](/voting).
|
| 65 |
+
</Callout>
|
apps/docs/content/docs/submit-a-model.mdx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Submit a model
|
| 3 |
+
description: Add your TTS model for evaluation - public or anonymous.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
Want your model on the board? Open an issue on [GitHub](https://github.com/TTS-AGI/TTS-Arena), join our [Discord](https://discord.gg/HB8fMR6GTr), or [email us](mailto:me@mrfake.name) and include an
|
| 7 |
+
API endpoint and key (see the [Provider API](/api)).
|
| 8 |
+
|
| 9 |
+
Submitting a model means accepting the [Provider Agreement](/provider-agreement).
|
| 10 |
+
The [integrity policy](/integrity) is the short version - read that first at a
|
| 11 |
+
minimum.
|
| 12 |
+
|
| 13 |
+
## What we need
|
| 14 |
+
|
| 15 |
+
- An HTTP endpoint that synthesizes a line of text and returns audio.
|
| 16 |
+
- A pool of voices to rotate through (we don't do zero-shot cloning in the
|
| 17 |
+
arena - we cycle a fixed set).
|
| 18 |
+
- A display name for the leaderboard.
|
| 19 |
+
- **A model that's yours.** Built or fine-tuned by you, or one you hold the
|
| 20 |
+
rights to serve under your own name. We don't list wrappers, resellers, or
|
| 21 |
+
rebrands of another provider's model - if the audio is coming from someone
|
| 22 |
+
else's API, that provider is who belongs on the board.
|
| 23 |
+
|
| 24 |
+
## Anonymous pre-release
|
| 25 |
+
|
| 26 |
+
You can run under a codename before your model is public - a way to get honest,
|
| 27 |
+
blind feedback ahead of launch. Three rules keep this fair:
|
| 28 |
+
|
| 29 |
+
- **Anonymous to voters, not to us.** To run under a codename you tell the TTS
|
| 30 |
+
Arena team who you are. We keep that confidential until the reveal - unless
|
| 31 |
+
the [integrity policy](/integrity) is broken, in which case we may disclose
|
| 32 |
+
the provider as part of the outcome.
|
| 33 |
+
- Anonymity is **time/exposure-bounded, not performance-bounded.** A codenamed
|
| 34 |
+
entry is revealed after a set period or vote count regardless of how it does -
|
| 35 |
+
you can withdraw it before then, but you can't sit on the board indefinitely
|
| 36 |
+
or reveal it _only because it won_.
|
| 37 |
+
- **Permanent anonymity is for genuinely unreleased models.** A model that's
|
| 38 |
+
already publicly available runs under its real name (a codename for a fixed
|
| 39 |
+
window is fine).
|
| 40 |
+
|
| 41 |
+
<Callout type="warn">
|
| 42 |
+
To keep comparisons fair, we don't evaluate multiple versions of the same
|
| 43 |
+
model simultaneously.
|
| 44 |
+
</Callout>
|
| 45 |
+
|
| 46 |
+
## Stealth models
|
| 47 |
+
|
| 48 |
+
Anonymous entries appear on the leaderboard under their codename with a neutral
|
| 49 |
+
mark. Clicking one explains it's a stealth model in evaluation - its identity
|
| 50 |
+
stays hidden until it's revealed.
|
apps/docs/content/docs/voting.mdx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Voting
|
| 3 |
+
description: What makes a vote count, and the rules that keep the board fair.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
## Casting a vote
|
| 7 |
+
|
| 8 |
+
1. Type a line, or hit **Random** for one from the prompt pool.
|
| 9 |
+
2. Two anonymous models - **A** and **B** - synthesize it.
|
| 10 |
+
3. Listen to both, then pick the one that sounds more human. One choice, no
|
| 11 |
+
skips.
|
| 12 |
+
4. The identities are revealed. If the line came unchanged from **Random**, the
|
| 13 |
+
public ratings update.
|
| 14 |
+
|
| 15 |
+
You need to listen to enough of each clip before voting unlocks - this keeps
|
| 16 |
+
votes grounded in the audio rather than reflexive clicks.
|
| 17 |
+
|
| 18 |
+
## Requirements
|
| 19 |
+
|
| 20 |
+
- **Sign in with Hugging Face.** Voting is tied to your account so each vote
|
| 21 |
+
counts once. Accounts must be at least **30 days old**.
|
| 22 |
+
- **English only**, for now - it's the language all models support. Multilingual
|
| 23 |
+
is on the roadmap.
|
| 24 |
+
- Prompts are capped at **1,000 characters**.
|
| 25 |
+
- **Don't vote on a model you're connected to.** If you work for, contract for,
|
| 26 |
+
or are otherwise affiliated with a provider, don't vote in battles that
|
| 27 |
+
include their model. That applies to personal accounts too. Providers agree
|
| 28 |
+
to this in the [integrity policy](/integrity); it binds their people as well.
|
| 29 |
+
- Only clean votes on first-use **Random** prompts move the public leaderboard.
|
| 30 |
+
Typed custom prompts are still useful for side-by-side listening, but they do
|
| 31 |
+
not affect ratings.
|
| 32 |
+
|
| 33 |
+
## Keeping it fair
|
| 34 |
+
|
| 35 |
+
Votes run through an anti-abuse system so the board reflects real preferences:
|
| 36 |
+
|
| 37 |
+
- **Behavioral signals** score each vote for risk (timing, patterns, device and
|
| 38 |
+
network signals). High-risk votes are recorded but **shadow-excluded** - they
|
| 39 |
+
never move the public ratings.
|
| 40 |
+
- A **captcha** appears once per browser, and again if risk rises.
|
| 41 |
+
- A background sweep looks for coordinated rings - accounts sharing an IP or
|
| 42 |
+
device that pile onto one model, whether they signed in together or merely
|
| 43 |
+
voted together - and retroactively excludes their votes.
|
| 44 |
+
- The sweep also runs a **statistical test for recognition**. For every voter
|
| 45 |
+
and every model, it compares how often that voter picks the model against how
|
| 46 |
+
often everyone else does, counting only the battles the model actually
|
| 47 |
+
appeared in. Liking a model puts you a few points above the crowd. Picking it
|
| 48 |
+
almost every time it appears, when the crowd is split, is not a preference -
|
| 49 |
+
it means you can tell which one it is, and a blind test where you can't be
|
| 50 |
+
blind isn't a vote. The comparison excludes the voter's own votes from the
|
| 51 |
+
baseline, so nobody can define the norm they are measured against.
|
| 52 |
+
|
| 53 |
+
Whenever votes are excluded, the leaderboard is recomputed from the clean set,
|
| 54 |
+
so a rating never keeps the lift it was given.
|
| 55 |
+
|
| 56 |
+
<Callout>
|
| 57 |
+
None of this affects honest voting - it's invisible unless your activity looks
|
| 58 |
+
automated or coordinated. What we collect to do it, and why, is on the
|
| 59 |
+
[privacy](/privacy) page.
|
| 60 |
+
</Callout>
|
apps/docs/lib/cn.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export { twMerge as cn } from "tailwind-merge";
|
apps/docs/lib/layout.shared.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
|
| 2 |
+
import { AudioLines } from "lucide-react";
|
| 3 |
+
import { appName, arenaUrl, gitConfig } from "./shared";
|
| 4 |
+
|
| 5 |
+
export function baseOptions(): BaseLayoutProps {
|
| 6 |
+
return {
|
| 7 |
+
nav: {
|
| 8 |
+
title: (
|
| 9 |
+
<>
|
| 10 |
+
<AudioLines className="text-fd-primary size-5" />
|
| 11 |
+
<span className="font-semibold">{appName}</span>
|
| 12 |
+
</>
|
| 13 |
+
),
|
| 14 |
+
},
|
| 15 |
+
links: [
|
| 16 |
+
{
|
| 17 |
+
text: "Open the Arena",
|
| 18 |
+
url: arenaUrl,
|
| 19 |
+
external: true,
|
| 20 |
+
},
|
| 21 |
+
],
|
| 22 |
+
githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`,
|
| 23 |
+
};
|
| 24 |
+
}
|
apps/docs/lib/shared.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const appName = "TTS Arena Docs";
|
| 2 |
+
// Docs are served at the root — there's no separate homepage.
|
| 3 |
+
export const docsRoute = "/";
|
| 4 |
+
export const docsImageRoute = "/og/docs";
|
| 5 |
+
export const docsContentRoute = "/llms.mdx/docs";
|
| 6 |
+
|
| 7 |
+
/** The main arena, linked from the docs nav. */
|
| 8 |
+
export const arenaUrl = "https://huggingface.co/spaces/TTS-AGI/TTS-Arena-V2";
|
| 9 |
+
|
| 10 |
+
export const gitConfig = {
|
| 11 |
+
user: "TTS-AGI",
|
| 12 |
+
repo: "TTS-Arena",
|
| 13 |
+
branch: "main",
|
| 14 |
+
};
|
apps/docs/lib/source.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { docs } from "collections/server";
|
| 2 |
+
import { loader } from "fumadocs-core/source";
|
| 3 |
+
import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons";
|
| 4 |
+
import { docsContentRoute, docsImageRoute, docsRoute } from "./shared";
|
| 5 |
+
|
| 6 |
+
// See https://fumadocs.dev/docs/headless/source-api for more info
|
| 7 |
+
export const source = loader({
|
| 8 |
+
baseUrl: docsRoute,
|
| 9 |
+
source: docs.toFumadocsSource(),
|
| 10 |
+
plugins: [lucideIconsPlugin()],
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
export function getPageImage(page: (typeof source)["$inferPage"]) {
|
| 14 |
+
const segments = [...page.slugs, "image.png"];
|
| 15 |
+
|
| 16 |
+
return {
|
| 17 |
+
segments,
|
| 18 |
+
url: `${docsImageRoute}/${segments.join("/")}`,
|
| 19 |
+
};
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export function getPageMarkdownUrl(page: (typeof source)["$inferPage"]) {
|
| 23 |
+
const segments = [...page.slugs, "content.md"];
|
| 24 |
+
|
| 25 |
+
return {
|
| 26 |
+
segments,
|
| 27 |
+
url: `${docsContentRoute}/${segments.join("/")}`,
|
| 28 |
+
};
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
export async function getLLMText(page: (typeof source)["$inferPage"]) {
|
| 32 |
+
const processed = await page.data.getText("processed");
|
| 33 |
+
|
| 34 |
+
return `# ${page.data.title} (${page.url})
|
| 35 |
+
|
| 36 |
+
${processed}`;
|
| 37 |
+
}
|
apps/docs/next.config.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createMDX } from "fumadocs-mdx/next";
|
| 2 |
+
|
| 3 |
+
const withMDX = createMDX();
|
| 4 |
+
|
| 5 |
+
/** @type {import('next').NextConfig} */
|
| 6 |
+
const config = {
|
| 7 |
+
reactStrictMode: true,
|
| 8 |
+
// Self-contained server bundle for the Docker/HF Space deploy. Built in
|
| 9 |
+
// isolation in Docker (only apps/docs present, no parent workspace), so Next
|
| 10 |
+
// traces from the app dir and server.js lands at the standalone root. No
|
| 11 |
+
// explicit tracing root — that's only needed for monorepo builds, and a wrong
|
| 12 |
+
// value trips Turbopack's workspace-root inference.
|
| 13 |
+
output: "standalone",
|
| 14 |
+
typescript: {
|
| 15 |
+
// The page uses fumadocs' generated MDX page-data fields (body/toc/getText)
|
| 16 |
+
// whose types are injected by the MDX plugin at build time and aren't
|
| 17 |
+
// visible to a plain tsc pass — it's the template's own pattern. The MDX
|
| 18 |
+
// still compiles and renders fine; don't fail the build on this gap. (The
|
| 19 |
+
// docs app is content + template boilerplate, so there's little else to
|
| 20 |
+
// type-check here anyway.)
|
| 21 |
+
ignoreBuildErrors: true,
|
| 22 |
+
},
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
export default withMDX(config);
|
apps/docs/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@ttsa/docs",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"build": "fumadocs-mdx && next build --webpack",
|
| 8 |
+
"dev": "fumadocs-mdx && next dev",
|
| 9 |
+
"start": "next start",
|
| 10 |
+
"typecheck": "echo 'docs: typecheck handled by next build (fumadocs MDX types are build-time)'",
|
| 11 |
+
"lint": "echo 'no lint for docs'"
|
| 12 |
+
},
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"fumadocs-core": "16.9.3",
|
| 15 |
+
"fumadocs-mdx": "15.0.11",
|
| 16 |
+
"fumadocs-ui": "16.9.3",
|
| 17 |
+
"lucide-react": "^1.17.0",
|
| 18 |
+
"next": "16.4.3",
|
| 19 |
+
"react": "^19.3.1",
|
| 20 |
+
"react-dom": "^19.3.1",
|
| 21 |
+
"tailwind-merge": "^3.6.0"
|
| 22 |
+
},
|
| 23 |
+
"devDependencies": {
|
| 24 |
+
"@tailwindcss/postcss": "^4.3.0",
|
| 25 |
+
"@types/mdx": "^2.0.13",
|
| 26 |
+
"@types/node": "^25.9.1",
|
| 27 |
+
"@types/react": "^19.3.0",
|
| 28 |
+
"@types/react-dom": "^19.3.0",
|
| 29 |
+
"postcss": "^8.5.15",
|
| 30 |
+
"tailwindcss": "^4.3.0",
|
| 31 |
+
"typescript": "^7.0.2"
|
| 32 |
+
}
|
| 33 |
+
}
|
apps/docs/postcss.config.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const config = {
|
| 2 |
+
plugins: {
|
| 3 |
+
"@tailwindcss/postcss": {},
|
| 4 |
+
},
|
| 5 |
+
};
|
| 6 |
+
|
| 7 |
+
export default config;
|
apps/docs/proxy.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation";
|
| 3 |
+
import { docsContentRoute, docsRoute } from "@/lib/shared";
|
| 4 |
+
|
| 5 |
+
const { rewrite: rewriteDocs } = rewritePath(
|
| 6 |
+
`${docsRoute}{/*path}`,
|
| 7 |
+
`${docsContentRoute}{/*path}/content.md`,
|
| 8 |
+
);
|
| 9 |
+
const { rewrite: rewriteSuffix } = rewritePath(
|
| 10 |
+
`${docsRoute}{/*path}.md`,
|
| 11 |
+
`${docsContentRoute}{/*path}/content.md`,
|
| 12 |
+
);
|
| 13 |
+
|
| 14 |
+
export default function proxy(request: NextRequest) {
|
| 15 |
+
const result = rewriteSuffix(request.nextUrl.pathname);
|
| 16 |
+
if (result) {
|
| 17 |
+
return NextResponse.rewrite(new URL(result, request.nextUrl));
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
if (isMarkdownPreferred(request)) {
|
| 21 |
+
const result = rewriteDocs(request.nextUrl.pathname);
|
| 22 |
+
|
| 23 |
+
if (result) {
|
| 24 |
+
return NextResponse.rewrite(new URL(result, request.nextUrl));
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
return NextResponse.next();
|
| 29 |
+
}
|
apps/docs/public/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Static assets for the docs site.
|
apps/docs/source.config.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
| 2 |
+
import { metaSchema, pageSchema } from "fumadocs-core/source/schema";
|
| 3 |
+
|
| 4 |
+
// You can customize Zod schemas for frontmatter and `meta.json` here
|
| 5 |
+
// see https://fumadocs.dev/docs/mdx/collections
|
| 6 |
+
export const docs = defineDocs({
|
| 7 |
+
dir: "content/docs",
|
| 8 |
+
docs: {
|
| 9 |
+
schema: pageSchema,
|
| 10 |
+
postprocess: {
|
| 11 |
+
includeProcessedMarkdown: true,
|
| 12 |
+
},
|
| 13 |
+
},
|
| 14 |
+
meta: {
|
| 15 |
+
schema: metaSchema,
|
| 16 |
+
},
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
export default defineConfig({
|
| 20 |
+
mdxOptions: {
|
| 21 |
+
// MDX options
|
| 22 |
+
},
|
| 23 |
+
});
|
apps/docs/tsconfig.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ESNext",
|
| 4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
| 5 |
+
"allowJs": true,
|
| 6 |
+
"skipLibCheck": true,
|
| 7 |
+
"strict": true,
|
| 8 |
+
"forceConsistentCasingInFileNames": true,
|
| 9 |
+
"noEmit": true,
|
| 10 |
+
"esModuleInterop": true,
|
| 11 |
+
"module": "esnext",
|
| 12 |
+
"moduleResolution": "bundler",
|
| 13 |
+
"resolveJsonModule": true,
|
| 14 |
+
"isolatedModules": true,
|
| 15 |
+
"jsx": "react-jsx",
|
| 16 |
+
"incremental": true,
|
| 17 |
+
"paths": {
|
| 18 |
+
"@/*": ["./*"],
|
| 19 |
+
"collections/*": ["./.source/*"]
|
| 20 |
+
},
|
| 21 |
+
"plugins": [
|
| 22 |
+
{
|
| 23 |
+
"name": "next"
|
| 24 |
+
}
|
| 25 |
+
]
|
| 26 |
+
},
|
| 27 |
+
"include": [
|
| 28 |
+
"next-env.d.ts",
|
| 29 |
+
"**/*.ts",
|
| 30 |
+
"**/*.tsx",
|
| 31 |
+
".next/types/**/*.ts",
|
| 32 |
+
".next/dev/types/**/*.ts"
|
| 33 |
+
],
|
| 34 |
+
"exclude": ["node_modules"]
|
| 35 |
+
}
|