From 47e444bd85a6f559737d999f9375144966b83dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n?= Date: Sun, 1 Feb 2026 16:34:52 +0100 Subject: [PATCH] feat(stage-ui): add Kokoro TTS (Local) speech provider (#1005) --- .github/actions/setup-swiftlint/action.yml | 11 +- packages/i18n/src/locales/en/settings.yaml | 25 ++ .../src/pages/settings/modules/speech.vue | 6 + .../providers/speech/kokoro-local.vue | 204 ++++++++++++++ packages/stage-ui/package.json | 1 + .../scenarios/providers/speech-playground.vue | 9 +- packages/stage-ui/src/stores/providers.ts | 190 +++++++++++++ .../stage-ui/src/workers/kokoro/constants.ts | 104 +++++++ packages/stage-ui/src/workers/kokoro/index.ts | 253 ++++++++++++++++++ packages/stage-ui/src/workers/kokoro/types.ts | 59 ++++ .../stage-ui/src/workers/kokoro/worker.ts | 107 ++++++++ pnpm-lock.yaml | 16 ++ 12 files changed, 980 insertions(+), 5 deletions(-) create mode 100644 packages/stage-pages/src/pages/settings/providers/speech/kokoro-local.vue create mode 100644 packages/stage-ui/src/workers/kokoro/constants.ts create mode 100644 packages/stage-ui/src/workers/kokoro/index.ts create mode 100644 packages/stage-ui/src/workers/kokoro/types.ts create mode 100644 packages/stage-ui/src/workers/kokoro/worker.ts diff --git a/.github/actions/setup-swiftlint/action.yml b/.github/actions/setup-swiftlint/action.yml index 355cb8f83..102b3157a 100644 --- a/.github/actions/setup-swiftlint/action.yml +++ b/.github/actions/setup-swiftlint/action.yml @@ -45,7 +45,16 @@ runs: run: | if [[ "${{ inputs.version }}" == "latest" ]]; then # Fetch the latest release tag using a more robust method - API_RESPONSE=$(curl -s https://api.github.com/repos/realm/SwiftLint/releases/latest) + CURL_ARGS=(-s) + if [[ -n "${{ github.token }}" ]]; then + CURL_ARGS+=(-H "Authorization: Bearer ${{ github.token }}") + fi + CURL_ARGS+=(-H "Accept: application/vnd.github+json") + CURL_ARGS+=(-H "X-GitHub-Api-Version: 2022-11-28") + CURL_ARGS+=("https://api.github.com/repos/realm/SwiftLint/releases/latest") + + API_RESPONSE=$(curl "${CURL_ARGS[@]}") + # Try using jq if available (most GitHub Actions runners have it) if command -v jq >/dev/null 2>&1; then VERSION=$(echo "$API_RESPONSE" | jq -r '.tag_name // empty') diff --git a/packages/i18n/src/locales/en/settings.yaml b/packages/i18n/src/locales/en/settings.yaml index e755dd2b3..16f9f2013 100644 --- a/packages/i18n/src/locales/en/settings.yaml +++ b/packages/i18n/src/locales/en/settings.yaml @@ -368,6 +368,7 @@ pages: stop: label: Stop select-voice: + loading: Loading model... required: Please select a voice provider-voice-selection: custom_model_placeholder: Enter custom model name... @@ -632,6 +633,30 @@ pages: validation: error-missing-api-key: Please enter an API key to test the voice. title: ElevenLabs + kokoro-local: + description: Local text-to-speech using Kokoro-82M. + fields: + field: + model: + label: Model Selection + description: Smaller models load faster but may have slightly lower quality. + models: + fp32-webgpu: + description: Full precision model using WebGPU - Recommended for supported devices + fp32: + description: Full precision model + fp16: + description: Half precision + q8: + description: 8-bit quantized + q4: + description: 4-bit quantized + q4f16: + description: 4-bit with FP16 + playground: + default-text: Hello! This is a test of the Kokoro text-to-speech system. + title: Voice Playground + title: Kokoro TTS (Local) fireworks: description: fireworks.ai title: Fireworks.ai diff --git a/packages/stage-pages/src/pages/settings/modules/speech.vue b/packages/stage-pages/src/pages/settings/modules/speech.vue index 2bb4ab7c3..334391e7d 100644 --- a/packages/stage-pages/src/pages/settings/modules/speech.vue +++ b/packages/stage-pages/src/pages/settings/modules/speech.vue @@ -96,6 +96,12 @@ watch(activeSpeechProvider, async (newProvider) => { syncOpenAICompatibleSettings() }) +watch(activeSpeechModel, async () => { + if (activeSpeechProvider.value) { + await speechStore.loadVoicesForProvider(activeSpeechProvider.value) + } +}) + // Function to generate speech async function generateTestSpeech() { if (!testText.value.trim() && !useSSML.value) diff --git a/packages/stage-pages/src/pages/settings/providers/speech/kokoro-local.vue b/packages/stage-pages/src/pages/settings/providers/speech/kokoro-local.vue new file mode 100644 index 000000000..a7e6bdb2b --- /dev/null +++ b/packages/stage-pages/src/pages/settings/providers/speech/kokoro-local.vue @@ -0,0 +1,204 @@ + + + + + + meta: + layout: settings + stageTransition: + name: slide + diff --git a/packages/stage-ui/package.json b/packages/stage-ui/package.json index 1bbc8af1d..7c4538554 100644 --- a/packages/stage-ui/package.json +++ b/packages/stage-ui/package.json @@ -100,6 +100,7 @@ "hono": "catalog:", "html2canvas": "^1.4.1", "idb-keyval": "catalog:", + "kokoro-js": "^1.0.0", "localforage": "^1.10.0", "mediabunny": "^1.29.0", "nanoid": "^5.1.6", diff --git a/packages/stage-ui/src/components/scenarios/providers/speech-playground.vue b/packages/stage-ui/src/components/scenarios/providers/speech-playground.vue index cb9e9d825..65ba43c72 100644 --- a/packages/stage-ui/src/components/scenarios/providers/speech-playground.vue +++ b/packages/stage-ui/src/components/scenarios/providers/speech-playground.vue @@ -19,6 +19,7 @@ const props = defineProps<{ // Current state apiKeyConfigured?: boolean + voicesLoading?: boolean }>() const { t } = useI18n() @@ -174,8 +175,8 @@ defineExpose({