Skip to content

Publish Whisper Transcriber as a micro-architecture utility#130

Merged
YurMil merged 1 commit into
mainfrom
feat/whisper-transcriber
Jul 21, 2026
Merged

Publish Whisper Transcriber as a micro-architecture utility#130
YurMil merged 1 commit into
mainfrom
feat/whisper-transcriber

Conversation

@YurMil

@YurMil YurMil commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Publishes the client-side Whisper speech-to-text app from YurMil/ws-speech-text as a full site utility, following the same micro-architecture as the other embedded apps, and automates future updates.

Route: /utilities/whisper-transcriber/ · Docs: /docs/utilities/whisper-transcriber/
Companion PR: YurMil/ws-speech-text#1

Artifact and sync

  • scripts/sync-whisper-transcriber.mjs (npm run sync:whisper-transcriber) audits the bundle — no symlinks, no path traversal, no source maps, no dev/localhost references, entry HTML may only reference packaged relative assets — stages it and swaps it in atomically. It publishes either from a local checkout or, with --archive … --sha256 …, from a release archive whose checksum is verified before anything is extracted.
  • manifest.json records version, build id and a SHA-256 per file, and takes its build time from the source build rather than now(), so republishing an unchanged release produces no diff. A .gitattributes rule keeps the artifact byte-exact through checkout so those checksums stay verifiable.
  • index.html is published next to app.html so the directory URL also serves the app under trailing-slash hosting.
  • The site never bundles Transformers.js, ONNX Runtime or model weights.

Automated publication

.github/workflows/sync-whisper-transcriber.yml reacts to a whisper-transcriber-release repository_dispatch from the source repository (or runs manually with a tag): it downloads the release archive, refuses it unless the SHA-256 matches, republishes the artifact, runs typecheck/lint/build, and opens a pull request. Nothing deploys without review.

Requires: the SITE_DISPATCH_TOKEN secret in YurMil/ws-speech-text — a fine-grained PAT scoped to this repository with Contents: read and write. Optional here: SOURCE_RELEASE_TOKEN, only needed if the source repository becomes private.

Site wiring

Catalog descriptor, shell config, route, sidebar and overview entries; the name and description in all six locale dictionaries; English documentation plus ru/ua/de/es/et translations; catalog thumbnail and social card.

Security headers — please review

  • Device capabilities are now delegated per utility through UtilityPageConfig.iframeAllow. The transcriber is the only utility that receives microphone 'self'; every other utility keeps the previous camera-only default.
  • vercel.json relaxes Permissions-Policy to microphone=(self) only on /utilities/whisper-transcriber/* and /utility-apps/whisper-transcriber/*. The site default stays microphone=().
  • connect-src gains the Hugging Face Hub origins the model is fetched from. This was added to the global policy rather than a path rule on purpose: if Vercel sent both policies for those paths the browser would enforce the intersection and the model download would break. The Permissions-Policy path rule fails closed the same way — worst case the microphone stays blocked and file upload still works.
  • The model revision is pinned to an immutable commit SHA, so rollback and the CSP allowlist stay meaningful.
  • No audio, video, filenames, transcript text or user edits ever leave the page.

Preview checks worth doing before merge: confirm the response headers on both transcriber paths, that the microphone prompt appears only after clicking Record, that the file workflow still works when the microphone is denied, and that the WASM fallback works without WebGPU.

Verification

npm run typecheck, npm run lint (0 errors) and a six-locale npm run build pass. The embedded app boots in the local preview and reports its injected build id; the docs pages render in en and ru; the route is in the sitemap. The archive path was exercised both with a matching checksum (byte-identical republish) and a deliberate mismatch (rejected, exit 1, target untouched).

🤖 Generated with Claude Code

Adds the client-side Whisper speech-to-text tool from the ws-speech-text
repository as a full site utility, following the same micro-architecture as the
other embedded apps: the app is built in its own repository and published as a
static artifact under static/utility-apps/, and the Docusaurus shell owns
navigation, SEO, reactions, comments and access.

Artifact and sync
- scripts/sync-whisper-transcriber.mjs builds the source app, audits the bundle
  (no symlinks, no traversal, no source maps, no dev/localhost references, only
  packaged relative assets), stages it, and swaps it in atomically.
- manifest.json records the version, build id and a SHA-256 per file; the
  .gitattributes rule keeps those files byte-exact through checkout.
- index.html is published next to app.html so the directory URL also serves the
  app under trailing-slash hosting.

Site wiring
- catalog descriptor, shell config, route, sidebar and overview entries;
- name and description added to all six locale dictionaries;
- English user documentation plus ru/ua/de/es/et translations;
- catalog thumbnail and social card.

Security headers
- Device capabilities are now delegated per utility via UtilityPageConfig
  .iframeAllow. The transcriber is the only utility granted microphone access;
  every other utility keeps the previous camera-only default.
- vercel.json relaxes Permissions-Policy to microphone=(self) only on the
  transcriber routes — the site default stays microphone=().
- connect-src allows the Hugging Face Hub origins the pinned model is fetched
  from. No audio, filenames or transcript text ever leaves the page.

Verified: typecheck, lint and a six-locale production build pass, and the
embedded app boots and reports its build id in the local preview.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cadautoscript-com Ready Ready Preview, Comment Jul 21, 2026 11:27am

@supabase

supabase Bot commented Jul 21, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project bkcimygtsnckzexbfqxh because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request integrates the new 'Whisper Transcriber' utility, enabling private, on-device speech-to-text transcription. It adds localized documentation, updates the site shell to support microphone permissions via iframe delegation, configures security policies in vercel.json, and introduces a synchronization script to build and publish the static transcriber artifact. The review feedback highlights several key improvements for the synchronization script, including robust error handling for subprocesses, supporting single quotes in the HTML asset audit, and implementing a safer atomic directory swap. Additionally, the feedback correctly notes that the canonical links and schema URLs in the localized MDX documentation files should be updated to point to their respective localized paths rather than the English version.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +53 to +62
function run(command, args, cwd) {
const result = spawnSync(command, args, {
cwd,
stdio: 'inherit',
shell: process.platform === 'win32',
});
if (result.status !== 0) {
fail(`Command failed: ${command} ${args.join(' ')}`);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The spawnSync error handling is incomplete. If the command fails to start (e.g., pnpm is not installed or not in PATH), result.status will be null and result.error will be populated. Checking only result.status !== 0 throws a generic 'Command failed' error without the underlying error message (like ENOENT). Checking result.error explicitly improves debuggability.

function run(command, args, cwd) {
  const result = spawnSync(command, args, {
    cwd,
    stdio: 'inherit',
    shell: process.platform === 'win32',
  });
  if (result.error) {
    fail(`Failed to start command: ${command} ${args.join(' ')}. Error: ${result.error.message}`);
  }
  if (result.status !== 0) {
    fail(`Command failed with exit code ${result.status}: ${command} ${args.join(' ')}`);
  }
}

}
}

const referenced = [...html.matchAll(/(?:src|href)="([^"]+)"/g)].map((match) => match[1]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The regex /(?:src|href)="([^"]+)"/g only matches double-quoted attributes. If the build tool or a manual edit uses single quotes (e.g., src='./assets/...'), the audit will miss them or fail. Supporting both single and double quotes makes the audit more robust.

Suggested change
const referenced = [...html.matchAll(/(?:src|href)="([^"]+)"/g)].map((match) => match[1]);
const referenced = [...html.matchAll(/(?:src|href)=["']([^"']+)["']/g)].map((match) => match[1]);

Comment on lines +207 to +208
fs.rmSync(TARGET_DIR, {recursive: true, force: true});
fs.renameSync(STAGING_DIR, TARGET_DIR);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Deleting the target directory before renaming the staging directory is not safe. If the rename fails (e.g., due to permission issues or locked files), the site is left with a deleted/missing utility directory. Using a temporary backup directory for rollback ensures atomic updates and zero downtime.

Suggested change
fs.rmSync(TARGET_DIR, {recursive: true, force: true});
fs.renameSync(STAGING_DIR, TARGET_DIR);
const backupDir = `${TARGET_DIR}.backup`;
fs.rmSync(backupDir, {recursive: true, force: true});
if (fs.existsSync(TARGET_DIR)) {
fs.renameSync(TARGET_DIR, backupDir);
}
try {
fs.renameSync(STAGING_DIR, TARGET_DIR);
fs.rmSync(backupDir, {recursive: true, force: true});
} catch (renameError) {
if (fs.existsSync(backupDir)) {
fs.rmSync(TARGET_DIR, {recursive: true, force: true});
fs.renameSync(backupDir, TARGET_DIR);
}
throw renameError;
}

Comment on lines +27 to +35
<link rel="canonical" href="https://cadautoscript.com/utilities/whisper-transcriber/" />
<script type="application/ld+json">{`{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Whisper Transcriber",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Web",
"description": "Audio- und Videodateien oder Mikrofonaufnahmen mit Whisper vollständig im Browser in bearbeitbaren Text transkribieren und als TXT, SRT oder WebVTT exportieren.",
"url": "https://cadautoscript.com/utilities/whisper-transcriber/",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The canonical link and schema URL point to the English version of the page (https://cadautoscript.com/utilities/whisper-transcriber/). For localized pages, they should point to the localized URL (https://cadautoscript.com/de/utilities/whisper-transcriber/) to ensure search engines index the translated content correctly.

  <link rel="canonical" href="https://cadautoscript.com/de/utilities/whisper-transcriber/" />
  <script type="application/ld+json">{`{
  "@context": "https://schema.org",
  "@type": "SoftwareApplication",
  "name": "Whisper Transcriber",
  "applicationCategory": "MultimediaApplication",
  "operatingSystem": "Web",
  "description": "Audio- und Videodateien oder Mikrofonaufnahmen mit Whisper vollständig im Browser in bearbeitbaren Text transkribieren und als TXT, SRT oder WebVTT exportieren.",
  "url": "https://cadautoscript.com/de/utilities/whisper-transcriber/",

Comment on lines +27 to +35
<link rel="canonical" href="https://cadautoscript.com/utilities/whisper-transcriber/" />
<script type="application/ld+json">{`{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Whisper Transcriber",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Web",
"description": "Transcribe archivos de audio y vídeo o grabaciones de micrófono a texto editable con Whisper ejecutándose por completo en el navegador, y exporta TXT, SRT o WebVTT.",
"url": "https://cadautoscript.com/utilities/whisper-transcriber/",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The canonical link and schema URL point to the English version of the page (https://cadautoscript.com/utilities/whisper-transcriber/). For localized pages, they should point to the localized URL (https://cadautoscript.com/es/utilities/whisper-transcriber/) to ensure search engines index the translated content correctly.

  <link rel="canonical" href="https://cadautoscript.com/es/utilities/whisper-transcriber/" />
  <script type="application/ld+json">{`{
  "@context": "https://schema.org",
  "@type": "SoftwareApplication",
  "name": "Whisper Transcriber",
  "applicationCategory": "MultimediaApplication",
  "operatingSystem": "Web",
  "description": "Transcribe archivos de audio y vídeo o grabaciones de micrófono a texto editable con Whisper ejecutándose por completo en el navegador, y exporta TXT, SRT o WebVTT.",
  "url": "https://cadautoscript.com/es/utilities/whisper-transcriber/",

Comment on lines +27 to +35
<link rel="canonical" href="https://cadautoscript.com/utilities/whisper-transcriber/" />
<script type="application/ld+json">{`{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Whisper Transcriber",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Web",
"description": "Transkribeeri heli- ja videofaile või mikrofonisalvestisi redigeeritavaks tekstiks — Whisper töötab täielikult brauseris, eksport TXT, SRT ja WebVTT vormingusse.",
"url": "https://cadautoscript.com/utilities/whisper-transcriber/",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The canonical link and schema URL point to the English version of the page (https://cadautoscript.com/utilities/whisper-transcriber/). For localized pages, they should point to the localized URL (https://cadautoscript.com/et/utilities/whisper-transcriber/) to ensure search engines index the translated content correctly.

  <link rel="canonical" href="https://cadautoscript.com/et/utilities/whisper-transcriber/" />
  <script type="application/ld+json">{`{
  "@context": "https://schema.org",
  "@type": "SoftwareApplication",
  "name": "Whisper Transcriber",
  "applicationCategory": "MultimediaApplication",
  "operatingSystem": "Web",
  "description": "Transkribeeri heli- ja videofaile või mikrofonisalvestisi redigeeritavaks tekstiks — Whisper töötab täielikult brauseris, eksport TXT, SRT ja WebVTT vormingusse.",
  "url": "https://cadautoscript.com/et/utilities/whisper-transcriber/",

Comment on lines +27 to +35
<link rel="canonical" href="https://cadautoscript.com/utilities/whisper-transcriber/" />
<script type="application/ld+json">{`{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Whisper Transcriber",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Web",
"description": "Расшифровка аудио- и видеофайлов или записи с микрофона в редактируемый текст: Whisper работает целиком в браузере, экспорт в TXT, SRT и WebVTT.",
"url": "https://cadautoscript.com/utilities/whisper-transcriber/",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The canonical link and schema URL point to the English version of the page (https://cadautoscript.com/utilities/whisper-transcriber/). For localized pages, they should point to the localized URL (https://cadautoscript.com/ru/utilities/whisper-transcriber/) to ensure search engines index the translated content correctly.

  <link rel="canonical" href="https://cadautoscript.com/ru/utilities/whisper-transcriber/" />
  <script type="application/ld+json">{`{
  "@context": "https://schema.org",
  "@type": "SoftwareApplication",
  "name": "Whisper Transcriber",
  "applicationCategory": "MultimediaApplication",
  "operatingSystem": "Web",
  "description": "Расшифровка аудио- и видеофайлов или записи с микрофона в редактируемый текст: Whisper работает целиком в браузере, экспорт в TXT, SRT и WebVTT.",
  "url": "https://cadautoscript.com/ru/utilities/whisper-transcriber/",

Comment on lines +27 to +35
<link rel="canonical" href="https://cadautoscript.com/utilities/whisper-transcriber/" />
<script type="application/ld+json">{`{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Whisper Transcriber",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Web",
"description": "Розшифровка аудіо- та відеофайлів або запису з мікрофона в редагований текст: Whisper працює цілком у браузері, експорт у TXT, SRT і WebVTT.",
"url": "https://cadautoscript.com/utilities/whisper-transcriber/",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The canonical link and schema URL point to the English version of the page (https://cadautoscript.com/utilities/whisper-transcriber/). For localized pages, they should point to the localized URL (https://cadautoscript.com/ua/utilities/whisper-transcriber/) to ensure search engines index the translated content correctly.

  <link rel="canonical" href="https://cadautoscript.com/ua/utilities/whisper-transcriber/" />
  <script type="application/ld+json">{`{
  "@context": "https://schema.org",
  "@type": "SoftwareApplication",
  "name": "Whisper Transcriber",
  "applicationCategory": "MultimediaApplication",
  "operatingSystem": "Web",
  "description": "Розшифровка аудіо- та відеофайлів або запису з мікрофона в редагований текст: Whisper працює цілком у браузері, експорт у TXT, SRT і WebVTT.",
  "url": "https://cadautoscript.com/ua/utilities/whisper-transcriber/",

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9aa4345384

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vercel.json
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' https://challenges.cloudflare.com https://va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https://*.supabase.co wss://*.supabase.co https://api.stripe.com https://challenges.cloudflare.com https://*.vercel-insights.com https://vitals.vercel-insights.com; frame-src 'self' blob: https://challenges.cloudflare.com; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
"value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' https://challenges.cloudflare.com https://va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https://*.supabase.co wss://*.supabase.co https://api.stripe.com https://challenges.cloudflare.com https://*.vercel-insights.com https://vitals.vercel-insights.com https://huggingface.co https://*.huggingface.co https://*.hf.co; frame-src 'self' blob: https://challenges.cloudflare.com; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow the ONNX wasm URL used by the worker

When the transcriber initializes ONNX Runtime, the committed worker sets env.backends.onnx.wasm.wasmPaths to https://cdn.jsdelivr.net/npm/@huggingface/transformers@.../dist/ when no path is configured, but this CSP update only allows Hugging Face origins in connect-src. In deployed browsers that take the WASM/ORT path, the fetch for ort-wasm-simd-threaded.jsep.wasm is blocked before inference starts, so the advertised WASM fallback (and likely model initialization) fails despite the local .wasm file being shipped. Configure the worker to use the packaged ./assets/ort-...wasm URL or add the actual CDN origin here.

Useful? React with 👍 / 👎.

@YurMil
YurMil merged commit dfba12c into main Jul 21, 2026
8 checks passed
@YurMil

YurMil commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Fixed: MODEL_DOWNLOAD_FAILED under the production CSP

Reported from the preview:

Loading the script 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1/dist/ort-wasm-simd-threaded.jsep.mjs'
violates the following Content Security Policy directive: "script-src 'self' …"

Cause. Transformers.js defaults ONNX Runtime's wasmPaths to a jsDelivr URL. That default made the runtime glue load from a third-party CDN and overrode the runtime copies the bundler already emits next to the worker — the 21 MB .wasm we ship was being ignored.

Fix (YurMil/ws-speech-text#1, artifact republished here): the worker clears wasmPaths, so ORT resolves its own bundled assets. No CSP change was needed — widening script-src to a CDN would have been the wrong trade for a file we already host.

Verified by serving the published artifact with the exact headers from vercel.json:

Runtime Result Prep
auto → WebGPU transcription complete, no console errors 15.0 s
wasm only transcription complete, no console errors 4.4 s

The only third-party request left at runtime is the pinned model download from the Hugging Face Hub, which the connect-src entries in this PR already cover.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant