feat: auto-select STT engine (Azure → Google Cloud → Whisper) and show provider in results

Removes user-facing engine/model/key/beam controls. The server now picks
the best available engine automatically:
1. Microsoft Azure Speech — short clips (≤1MB, no diarization, audio/*)
2. Google Cloud Speech v2 — long audio, diarization, all languages
3. OpenAI Whisper GPU — local fallback

Results display which provider was used (e.g. "Transcribed with Google
Cloud Speech") via transcript-engine-badge and traceMeta.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 13:22:24 +02:00
parent c6a9cc9199
commit 08d1e3cee3
14 changed files with 2937 additions and 416 deletions
+101
View File
@@ -0,0 +1,101 @@
/* global React, ReactDOM,
Topbar, Manifesto, Disclaimer, ToolRail, ReasoningPanel, FootStrip, TRACE_WAITING,
AskView, SearchView, AdvocateView, RedactView, TimelineView, CasebookView, VoicesView,
TweaksPanel, useTweaks, TweakSection, TweakSlider, TweakRadio, TweakToggle, TweakSelect
*/
const { useState, useEffect, useMemo } = React;
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"distress": 0.6,
"headline": "twelve",
"surface": "light"
}/*EDITMODE-END*/;
const TOOL_INSTRUMENT = {
ask: "ECHR Art. 8 · Barneloven §43",
search: "Lovdata + Strasbourg + UNCRC",
advocate: "ECHR Art. 8 (partisan)",
redact: "GDPR · Personopplysningsloven",
timeline: "Pedersen v. Norway pattern",
cases: "ECHR Art. 8 · 23 judgments",
voices: "Verified testimony · court documents on file",
};
function App() {
const [tool, setTool] = useState("ask");
const [trace, setTrace] = useState(TRACE_WAITING);
const [t, setT] = useTweaks(TWEAK_DEFAULTS);
// Apply visual layer to <html>
useEffect(() => {
const root = document.documentElement;
root.style.setProperty("--distress", String(t.distress));
root.classList.toggle("surface-dark", t.surface === "dark");
}, [t.distress, t.surface]);
const headline = window.DBN_DATA.headlines[t.headline] || window.DBN_DATA.headlines.twelve;
const stats = window.DBN_DATA.stats;
// Reset trace when tool changes
useEffect(() => { setTrace(TRACE_WAITING); }, [tool]);
const view = useMemo(() => {
switch (tool) {
case "ask": return <AskView onTrace={setTrace}/>;
case "search": return <SearchView onTrace={setTrace}/>;
case "advocate": return <AdvocateView onTrace={setTrace}/>;
case "redact": return <RedactView onTrace={setTrace}/>;
case "timeline": return <TimelineView onTrace={setTrace}/>;
case "cases": return <CasebookView onTrace={setTrace}/>;
case "voices": return <VoicesView onTrace={setTrace}/>;
default: return <AskView onTrace={setTrace}/>;
}
}, [tool]);
return (
<main className="app-shell" data-screen-label="00 Tools Workspace">
<Topbar caseNo="case ELENA-K-2024 · session #a1c8" status="Live · 23 violations indexed"/>
<Disclaimer />
<Manifesto headline={headline} stats={stats} intensity={t.distress}/>
<section className="workspace" aria-label="Tools workspace">
<ToolRail active={tool} onSelect={setTool}/>
<section className="tool-panel" aria-labelledby="toolTitle">
<div className="corner-fold" aria-hidden="true"></div>
{view}
</section>
<ReasoningPanel steps={trace} caseNo="ELENA-K-2024" instrument={TOOL_INSTRUMENT[tool]}/>
</section>
<FootStrip />
<TweaksPanel title="Tweaks · Case workbench">
<TweakSection title="Distress treatment">
<TweakSlider tweak="distress" label="Intensity" min={0} max={1} step={0.05}
value={t.distress} onChange={(v)=>setT("distress", v)}
help="0 = somber & restrained · 1 = defiant editorial layout (bigger headline, deeper bleed, larger fold)."/>
</TweakSection>
<TweakSection title="Headline variant">
<TweakSelect tweak="headline" label="Manifesto" value={t.headline} onChange={(v)=>setT("headline", v)}
options={[
{ value: "twelve", label: "Twelve minutes" },
{ value: "twentythree", label: "Twenty-three violations" },
{ value: "members", label: "Numbers · 9,482 / 20,000" },
{ value: "compliance", label: "Compliance is love" },
]}/>
</TweakSection>
<TweakSection title="Surface">
<TweakRadio tweak="surface" label="Mode" value={t.surface} onChange={(v)=>setT("surface", v)}
options={[
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
]}/>
</TweakSection>
</TweaksPanel>
</main>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App/>);