# Kokoro Chunked CDN — How to Use

他プロジェクトから、この Cloudflare Pages 上の Kokoro モデルを読み込むためのガイドです。

**ベース URL（例）:** `https://kokoro-chunked.pages.dev`  
以降 `{ORIGIN}` と表記します。実際の URL はデプロイ先に置き換えてください。

---

## 重要な注意点（必読）

この CDN の Kokoro は **通常の単一 `.onnx` ファイルとして配信されていません。**  
統合時に次を守らないと、ダウンロード失敗・破損 ONNX・音質劣化の原因になります。

1. **`model.onnx` を 1 ファイルとして直接 fetch しない**
   - Cloudflare Pages の 25 MiB 制限のため、ONNX は Brotli 圧縮後に 20 MiB チャンクへ分割されています。
   - 論理パス `onnx/model.onnx` は存在しますが、実体は `manifest.json` + `part-*.br` です。

2. **`part-*.br` を個別に解凍しない**
   - Brotli は ONNX 全体を **一括圧縮** したものを分割しただけです。
   - 正しい手順: 全チャンクを `index` 順に連結 → **1 回だけ** 解凍 → 生 ONNX を得る。
   - チャンクごとに解凍すると **必ず失敗** します。

3. **Hugging Face Hub 直リンクでは読み込めない**
   - Hub 上の `model.onnx` URL をそのまま使っても、チャンク再構成は行われません。
   - **必ず** `{ORIGIN}/models` を `modelsRootUrl` にし、`configureHubMirrorChunkedEnv()` または `createChunkedFetch()` を使ってください。

4. **エントリポイントは `manifest.json`**
   - ダウンロードの起点: `{ORIGIN}/models/onnx-community/Kokoro-82M-v1.0-ONNX/onnx/model.onnx/manifest.json`
   - manifest の `chunks` 配列から `part-000.br` … `part-014.br` を取得します。

5. **`dtype: "fp32"` を使う（WebGPU 推奨）**
   - fp16 は WebGPU で音質が劣化する事例があります。この CDN は fp32 モデルです。

6. **Brotli 解凍（Chrome は WASM フォールバック）**
   - **Chrome / Edge は `DecompressionStream('brotli')` 未対応**（2026年6月時点）。
   - `chunked-model-fetch.js` は Safari / Firefox ではネイティブ API、Chrome では `brotli-dec-wasm`（約 200KB）に自動フォールバックします。
   - `fflate` 単体では Brotli 非対応です。

7. **モデル更新後はキャッシュに注意**
   - チャンク名が `part-000.br` 固定のため、再デプロイ直後は `bypassCache: true` またはスーパーリロードを使ってください。

**AI エージェント向け:** 統合前にこのセクションを読み、上記をすべて満たす実装にすること。

---

## 概要

| 項目 | 値 |
|------|-----|
| モデル ID | `onnx-community/Kokoro-82M-v1.0-ONNX` |
| 精度 | `fp32`（WebGPU 推奨） |
| ONNX 論理パス | `onnx/model.onnx` |
| 配信形式 | Brotli 一括圧縮 → 20 MiB チャンク（15 個） |
| 生 ONNX サイズ | 325,532,232 bytes（約 310 MiB） |
| 圧縮後 | 298,017,038 bytes（約 284 MiB） |
| ブラウザ要件 | モダンブラウザ（Chrome は WASM Brotli 解凍を同梱） |

---

## URL 構造（Hub ミラー）

```
{ORIGIN}/models/onnx-community/Kokoro-82M-v1.0-ONNX/
├── config.json
├── tokenizer.json
├── tokenizer_config.json
├── voices/af_heart.bin
└── onnx/model.onnx/
    ├── manifest.json
    ├── part-000.br … part-014.br
```

### 主要エンドポイント

| 用途 | URL |
|------|-----|
| manifest | `{ORIGIN}/models/onnx-community/Kokoro-82M-v1.0-ONNX/onnx/model.onnx/manifest.json` |
| チャンク i | `{ORIGIN}/models/onnx-community/Kokoro-82M-v1.0-ONNX/onnx/model.onnx/part-{i}.br` |
| 共有ライブラリ | `{ORIGIN}/lib/chunked-model-fetch.js` |
| 参照 Worker | `{ORIGIN}/tts-worker-chunked.js` |
| 機械可読 manifest | `{ORIGIN}/kokoro-cdn.json` |

---

## 最短統合（kokoro-js）

`chunked-model-fetch.js` を import し、Transformers.js の `env` を差し替えます。

```javascript
import { KokoroTTS, env as kokoroEnv } from "https://esm.sh/kokoro-js";
import { env } from "https://esm.sh/@huggingface/transformers@3.4.0";
import { configureHubMirrorChunkedEnv } from "{ORIGIN}/lib/chunked-model-fetch.js";

const MODEL_ID = "onnx-community/Kokoro-82M-v1.0-ONNX";
const MODELS_ROOT = "{ORIGIN}/models";

configureHubMirrorChunkedEnv(env, {
  modelsRootUrl: MODELS_ROOT,
  bypassCache: false, // 再デプロイ直後は true 推奨
});

kokoroEnv.allowRemoteModels = true;
kokoroEnv.allowLocalModels = false;
kokoroEnv.useBrowserCache = true;
kokoroEnv.voiceDataUrl = `${MODELS_ROOT}/${MODEL_ID}/voices`;

const tts = await KokoroTTS.from_pretrained(MODEL_ID, {
  dtype: "fp32",
  device: "webgpu", // 不可なら "wasm"
});

const audio = await tts.generate("Hello!", { voice: "af_heart", speed: 1 });
```

### Worker 経由（推奨）

```javascript
const worker = new Worker("{ORIGIN}/tts-worker-chunked.js", { type: "module" });

worker.postMessage({
  id: 1,
  type: "init",
  modelsRootUrl: "{ORIGIN}/models",
  dtype: "fp32",
  device: "webgpu",
  bypassCache: false,
});

// init-done を受信後
worker.postMessage({
  id: 2,
  type: "generate",
  text: "Hello!",
  voice: "af_heart",
  speed: 1,
  index: 0,
});
```

---

## チャンク再構成のみ使う場合

ONNX バイナリだけ必要なとき:

```javascript
import { loadChunkedModel } from "{ORIGIN}/lib/chunked-model-fetch.js";

const manifestUrl =
  "{ORIGIN}/models/onnx-community/Kokoro-82M-v1.0-ONNX/onnx/model.onnx/manifest.json";

const { bytes, manifest } = await loadChunkedModel(manifestUrl, {
  bypassCache: false,
  onProgress: (info) => console.log(info),
});
// bytes → 生の model.onnx（Uint8Array）
```

`createChunkedFetch()` を `env.fetch` に渡すと、`.onnx` リクエストを透過的にチャンク再構成へルーティングします。

---

## manifest.json スキーマ

```json
{
  "version": 1,
  "file": "model.onnx",
  "compression": "br",
  "originalSize": 325532232,
  "compressedSize": 298017038,
  "sha256": "8fbea51ea711f2af382e88c833d9e288c6dc82ce5e98421ea61c058ce21a34cb",
  "chunkBytes": 20971520,
  "chunks": [
    { "index": 0, "path": "part-000.br", "compressedSize": 20971520 }
  ]
}
```

**読み込み手順:**

1. `manifest.json` を fetch
2. `chunks` を `index` 順に並列 fetch
3. 圧縮バイナリを連結
4. `compression` に従い **1 回だけ** 解凍（`br` → ネイティブ `DecompressionStream` または WASM）
5. `originalSize` / `sha256` で検証

---

## CORS

`/models/*` は `Access-Control-Allow-Origin: *` を返します。  
他オリジンのブラウザアプリから直接 fetch 可能です。

---

## キャッシュ

PoC 設定（`_headers`）:

| パス | Cache-Control |
|------|---------------|
| `index.html`, `/lib/*`, Worker | `no-cache` |
| `/models/*` | `max-age=60, must-revalidate` |

モデル更新後は `bypassCache: true` またはスーパーリロードを使ってください。

---

## トラブルシュート

| 症状 | 対処 |
|------|------|
| 古い fp16 / 7 チャンクが見える | スーパーリロード、`bypassCache: true`、ビルド ID 確認 |
| `no callback` | 古い `chunked-model-fetch.js`（fflate で br 解凍）がキャッシュされている |
| 音質がガサつく | `dtype: "fp32"` + WebGPU を使用（fp16 は非推奨） |
| Brotli 解凍失敗 | `chunked-model-fetch.js` が v4 以降か確認（WASM フォールバック同梱） |

---

## AI エージェント向けメモ

- このファイル: `{ORIGIN}/HOW_TO_USE.md`（**まず「重要な注意点」を読む**）
- 機械可読: `{ORIGIN}/kokoro-cdn.json`（`warnings` フィールド参照）
- 索引: `{ORIGIN}/llms.txt`
- デモ UI: `{ORIGIN}/`（ビルド ID でバージョン確認）

他プロジェクトへ渡す URL は **`{ORIGIN}/HOW_TO_USE.md`** を推奨します。
