Skip to content

[feat] lab module#2

Merged
MasamiYui merged 10 commits into
mainfrom
feat/lab-module
Jun 23, 2026
Merged

[feat] lab module#2
MasamiYui merged 10 commits into
mainfrom
feat/lab-module

Conversation

@MasamiYui

Copy link
Copy Markdown
Owner

No description provided.

MasamiYui added 10 commits June 22, 2026 17:11
- Register wenetspeech_drama in datasets package
- Implement adapter that filters segments by subsets containing 'D' (drama)
- Add asr-drama-wenetspeech.toml suite with whisper-large-v3 baseline
- Cover the adapter with 10 unit tests under tests/lab/
- Update README to document the new dataset and suite
…gnment

- Add /lab route with four tabs: Datasets, Experiments, Leaderboard, Regression
- Implement labApi client with auto fallback to mock data on network errors
- Provide rich mock dataset (6 scenarios, 8 suites, 6 datasets, 7 runs) so the
  page works without the lab server (toggle via VITE_LAB_USE_MOCK=1)
- Show a SourceBadge (live / mock) so users always know what they look at
- Reuse shared PageContainer and StatusBadge, align colors, table skeleton,
  pill tabs and empty/error states with TaskListPage UI conventions
- Add bilingual (zh/en) i18n entries for the whole module
- Register lab in App router and main navigation
Inventory current capabilities (datasets, scenarios, suites/runs,
leaderboard, regression, mock layer) and turn them into a structured
requirement document with clickable code references, gap analysis and
a P0/P1/P2 roadmap to guide further iteration.
Two new GT-anchored evaluation paths, both offline-tested with fabricated data
(no downloads), filling the two biggest gaps from the dataset-landscape research.

tts-clone — the dubbing/TTS stage had no GT metric, only e2e-dub's intrinsic score:
- metrics/speaker.py: SIM = cosine over ECAPA embeddings (reuses translip's in-tree
  speaker_embedding; lazy + injectable + degrades to sim=None, never crashes)
- scenarios/tts_clone.py: clone reference voice -> re-transcribe -> SIM (primary) + CER
- tts_synth.py: lab-side worker reusing translip.dubbing qwen voice-clone core only
  (avoids the server adapters' heavy eager-import), isolated subprocess
- datasets/synthetic_clone.py + folder .clone.txt/.ref.wav sidecars + suite
- GroundTruth gains clone_text / clone_ref_wav

magicdata-ramc — extends CER+DER from meetings into spontaneous dialogue:
- datasets/magicdata_ramc.py: dedicated parser for RAMC's
  "[start,end] speaker gender,dialect text" transcripts (NOT TextGrid), reusing the
  shared RTTM/SRT emitters; tolerant of flat or WAV/TXT layouts
- suites/asr-diar-ramc.toml (collar 0.25 / 2 speakers -> official CER 19.1/DER 7.96)

Tests: +19 (tests/lab now 104 passing). Design doc at
docs/translip-lab-tts-clone-eval.zh-CN.md. Format + real-TTS paths flagged as
"verify against real data/models" in describe()/docs (cannot download here).

translip core untouched - lab stays one-way-dependent and deletable.
The lab server lists scenarios/datasets/suites dynamically, so the new tts-clone
scenario, synthetic-clone / magicdata-ramc datasets, and their suites appear in
live mode automatically. This wires up the rest:

- ScenarioTag: render the tts-clone tag (音色克隆 / Voice Clone) and normalize
  hyphenated backend names (e2e-dub, tts-clone) to the underscore label keys, so
  live data renders localized tags too (not just mock)
- leaderboard/regression: include `sim` (higher-is-better) in the primary-metric
  picker so tts-clone runs surface their SIM score
- experiments: infer dataset/tags for the tts-clone-synthetic + asr-diar-ramc suites
- labMock.ts: add the new scenario/datasets/suites + two demo runs so the offline
  demo represents the new capabilities
- i18n: tagClone (zh/en)

Verified in-browser (system Chrome via Playwright): mock and live modes both render
the new datasets/suites/tag and the tts-clone run's SIM metric; live shows
"实时数据" against the real :8799 server with 0 console errors.
… footgun)

The cache was keyed only by config + input fingerprints, so changing a scenario's
invoke/score logic (or a metric it uses) without touching config/inputs silently
served stale cached scores. Add Scenario.version (default 1) into the cache key;
bump it to invalidate. Also recorded in each run's scenario_meta for traceability.
--no-cache still bypasses.

Tests: scenario_cache_key version sensitivity + default-is-1; runner-level
version-bump-invalidates-cache (tests/lab 107 passing).
…PRD P0-1/2/4)

Leaderboard rows now link to a per-run detail view the lab server already had data
for (GET /api/lab/runs/{id}) but the UI never surfaced.

- RunDetailPage: run header, meta cards (samples/scenarios/started/elapsed),
  aggregates table (primary metric / mean / micro / std / scored-failed-skipped),
  and a per-sample results table. Reads the real run-manifest shape with defensive
  fallbacks to the mock summary shape.
- P0-2 failure traceability: failed samples show an expandable stderr/traceback panel.
- P0-4 report download: GET /api/lab/runs/{id}/report.md (run_to_markdown) + a
  download button (live fetch; mock builds the markdown client-side).
- labMock: enriched run details (full-shape aggregates + per-sample results incl. a
  failure) so the offline demo represents the page; i18n detail block (zh/en).

Tests: backend report endpoint (tests/lab 108 passing); frontend build + 189 vitest
+ eslint clean. Browser-verified (system Chrome) in mock AND live (fabricated real
manifest) modes: detail renders, error expands, 0 console errors live.
…UI polling (PRD P0-3)

POST /api/lab/runs was fire-and-forget; it now submits a tracked job with a real
lifecycle (queued -> running -> succeeded/failed), serialized through a single worker
so heavy ML runs don't stampede a 16 GB host, with captured logs. Job id == run id,
so a finished job links straight to its detail page.

- server/jobs.py: JobManager (queue + single worker, log capture, in-memory + JSON
  records under <runs>/.jobs/ that survive a restart; orphaned running/queued jobs
  are marked failed on reload).
- server/app.py: lazy per-runs-dir JobManager; POST /runs submits a job;
  GET /api/lab/jobs and GET /api/lab/jobs/{id} (+ log tail).
- frontend: triggerRun returns job_id; ExperimentsTab shows a polling JobStatus that
  resolves to a "view results" link on success / "run failed"; mock job state machine
  for the offline demo; i18n (zh/en).
- PRD 6.1: P0-1..P0-4 marked done.

Tests: JobManager lifecycle incl. serial execution + log capture + orphan recovery,
plus endpoints (tests/lab 115 passing). Browser-verified: mock job flow (run ->
status -> results link -> detail), and a live server actually running the worker to
a tracked terminal state with the subprocess output captured.
The leaderboard mixed all scenarios, making single-scenario decisions hard. Add a
filter row above the table:
- scenario filter — when set, runs are filtered to that scenario AND ranked/shown by
  that scenario's metric (handles scenario and scenario@arm aggregate keys), not just
  the first metric found across the run
- status filter (finished/running/failed) + a run-id/suite search box, with a
  "<shown> / <total>" count

Pure frontend over existing run data; i18n (zh/en). Time-window filter deferred.

Verified: build + 189 vitest + eslint clean; browser (system Chrome) — scenario,
status, and search filters each narrow to the right rows (0 page errors).
@MasamiYui MasamiYui merged commit 4d15549 into main Jun 23, 2026
1 of 3 checks passed
MasamiYui added a commit that referenced this pull request Jun 29, 2026
…ith sidechain ducking

Two coupled features landed together because they touch the same UI surface
(NewTaskPage commentary settings) and share the same render pipeline.

== Viral-recap narrator presets (5 new voices) ==
* src/translip/commentary/voices.py: add 5 narrator-recap-* presets
  (classic / suspense / hype / gossip / sichuan-funny) — all routed through
  Qwen3-TTS CustomVoice using the existing 9 named speakers, no new model.
* tests/test_commentary_voices.py: extend coverage to assert 14-preset total
  and unique-id invariants.
* frontend/src/pages/NewTaskPage.tsx → NarratorVoicePicker: split into two
  accessible <section role="region"> groups — "Built-in narrator voices" and
  "🔥 Viral short-video recap" (with NEW badge). The "borrow from source"
  escape hatch stays outside both groups.
* frontend/src/pages/__tests__/NarratorVoicePicker.grouping.test.tsx: new
  vitest covering zh/en section labels, NEW badge, group membership and the
  empty-viral-list fallback.

== Built-in BGM presets (6 mood beds, learning-only) ==
Backend:
* src/translip/commentary/bgm.py: new registry of 6 BgmPreset dataclasses
  (suspense / hype / warm / documentary / comedy / action) with gain_db,
  duck_db, license metadata and resolve_bgm_path() three-state semantics
  (None | preset id | arbitrary path).
* src/translip/server/atomic_tools/schemas.py: CommentaryRenderToolRequest
  gains bgm_preset / bgm_file_id / bgm_gain_db / bgm_duck_db with a
  validator forcing duck_db <= 0 (it's an attenuation).
* src/translip/commentary/render.py: when BGM is supplied on an OST=0 clip,
  build_clip_command now emits a 3-input filtergraph —
    [bgm_raw][narr] sidechaincompress threshold=0.05 ratio=duck attack=150
                    release=600 -> [bgm]
    [src][bgm][narr] amix=inputs=3, alimiter=limit=0.97
  Input #2 uses -stream_loop -1 so a short placeholder loops under any
  clip length. Without BGM the legacy 2-input mix is preserved
  byte-for-byte (backwards compatible).
* src/translip/server/atomic_tools/adapters/commentary_render.py: resolve
  bgm_file_id (upload) > bgm_preset > None and forward 3 params to render.
* src/translip/server/routes/config.py: GET /api/config/bgm-presets and
  /bgm-presets/{id}/preview (FileResponse audio/wav).

Assets (CC0, algorithmically synthesised):
* scripts/build_bgm_placeholders.py: pure-Python (wave + math.sin) WAV
  builder — drone/heartbeat/pad/arp/comedy/action moods, 30s mono 16-bit
  44.1 kHz. Re-runnable, no external deps.
* assets/bgm/*.wav: 6 generated placeholders (~2.6 MB each). Verified via
  ffprobe + an end-to-end /preview round-trip.
* assets/bgm/LICENSE.md: CC0 declaration + replacement table pointing at
  Pixabay/Uppbeat/Bensound/FMA + compliance disclaimer ("for local learning
  and research only; replace with licensed tracks before publishing").

Frontend:
* frontend/src/api/config.ts: new BgmPresetInfo, bgmPresetPreviewUrl(),
  configApi.bgmPresets(). Both narratorVoices() and bgmPresets() now
  defensively coerce non-array payloads to [] so a bad-cached or
  HTML-fallback response can't crash callers.
* frontend/src/types/index.ts: TaskConfig gains commentary_bgm_preset /
  commentary_bgm_gain_db / commentary_bgm_duck_db.
* frontend/src/pages/NewTaskPage.tsx: new BgmPresetPicker component
  (violet-themed section, NEW badge, mood chip, "No BGM" opt-out at the
  top, independent fetch/abort/play state machine). Wired into the
  commentary settings Field below the narrator picker.
* frontend/src/pages/__tests__/BgmPresetPicker.test.tsx: 8 vitest cases —
  zh/en section labels, NEW badge, opt-out outside the section, click
  emits id, aria-pressed on selection, empty registry, and a regression
  guard against non-array payloads (the items.map crash reported in
  the IDE console).

== Verification ==
* pytest tests/test_commentary_bgm.py
        tests/test_commentary_voices.py
        tests/test_atomic_tools_adapters/test_commentary_render.py
  -> 48 passed
* vitest BgmPresetPicker.test.tsx + NarratorVoicePicker.grouping.test.tsx
  -> 13 passed
* GetDiagnostics -> [] (0 errors / 0 warnings)
* curl /api/config/bgm-presets -> 6 entries
  curl /api/config/bgm-presets/bgm-suspense-dark/preview
       -> HTTP 200 audio/wav 2.6 MB, RIFF 16-bit mono 44.1 kHz

License note: bundled WAVs are CC0 algorithmic synthesis suitable for
verifying the audio chain only. Replace assets/bgm/*.wav in-place with
licensed tracks (same filename) for any published work — no code change
required.
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