Feat: Chatterbox-Klon-Referenz hochladen (eigene Stimme via ElevenLabs → lokal unbegrenzt klonen)
Sidecar: /reference (POST Upload → 24kHz-Mono-WAV via PyAV/faster-whisper, kein System-ffmpeg; GET/DELETE). Chatterbox nutzt aktive Referenz (active.txt, überlebt Restart). Backend: /api/voice/ reference-Proxy. Picker (Chatterbox): „Referenz-Stimme hochladen (mp3/wav)" → setzt Stimme auf »deine Referenz«. Ermöglicht den User-Plan: Stimme besorgen → EL erzeugen → Chatterbox klont. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+151
-151
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-DRD104Im.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Dtig7Izl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-jNLzkOWh.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -27,9 +27,35 @@ export function AvatarPicker({ avatarUrl, onAvatarChange }: {
|
||||
const [engine, setEngine] = useState(localStorage.getItem("mc_voice_engine") || "piper")
|
||||
const [voice, setVoice] = useState(localStorage.getItem("mc_voice_voice") || "")
|
||||
const [previewing, setPreviewing] = useState(false)
|
||||
const [refActive, setRefActive] = useState(false)
|
||||
const [refUploading, setRefUploading] = useState(false)
|
||||
const fileRef = useRef<HTMLInputElement>(null)
|
||||
const refFileRef = useRef<HTMLInputElement>(null)
|
||||
const previewAudio = useRef<HTMLAudioElement | null>(null)
|
||||
|
||||
// Aktive Chatterbox-Klon-Referenz anzeigen.
|
||||
useEffect(() => {
|
||||
fetch("/api/voice/reference").then((r) => r.json()).then((d) => setRefActive(!!d.active)).catch(() => {})
|
||||
}, [])
|
||||
|
||||
const uploadReference = async (file: File) => {
|
||||
setRefUploading(true)
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append("audio", file)
|
||||
const r = await fetch("/api/voice/reference", { method: "POST", body: fd })
|
||||
if (!r.ok) throw new Error(`${r.status}`)
|
||||
setRefActive(true)
|
||||
const v = await fetch("/api/voice/voices").then((r) => r.json())
|
||||
setVoices(v.voices || [])
|
||||
saveVoice("chatterbox", "clone") // direkt auf die geklonte Stimme schalten
|
||||
} catch (e) {
|
||||
console.error("Referenz-Upload fehlgeschlagen:", e)
|
||||
} finally {
|
||||
setRefUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// „Probe hören": synthetisiert einen festen Satz mit der aktuellen Engine+Stimme und spielt ihn ab.
|
||||
const playPreview = async () => {
|
||||
if (previewing) return
|
||||
@@ -218,9 +244,29 @@ export function AvatarPicker({ avatarUrl, onAvatarChange }: {
|
||||
</p>
|
||||
)}
|
||||
{engine === "chatterbox" && (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Chatterbox läuft auf CPU → erste Antwort kann ein paar Sekunden dauern. Lokal + Voice-Cloning (dt. mit Akzent).
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
<input
|
||||
ref={refFileRef}
|
||||
type="file"
|
||||
accept="audio/*,.mp3,.wav,.m4a,.ogg"
|
||||
className="hidden"
|
||||
onChange={(e) => { const f = e.target.files?.[0]; if (f) void uploadReference(f) }}
|
||||
/>
|
||||
<button
|
||||
onClick={() => refFileRef.current?.click()}
|
||||
disabled={refUploading}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-md border border-border/40 bg-background/40 px-2.5 py-2 text-xs hover:bg-accent transition-colors disabled:opacity-60"
|
||||
>
|
||||
{refUploading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Upload className="h-3.5 w-3.5" />}
|
||||
{refActive ? "Referenz-Stimme ersetzen" : "Referenz-Stimme hochladen (mp3/wav)"}
|
||||
</button>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{refActive
|
||||
? "✓ Referenz aktiv → wähle oben die Stimme »Chatterbox (deine Referenz)«. "
|
||||
: "Lad deine (z.B. via ElevenLabs erzeugte) Stimme hoch — Chatterbox klont sie. "}
|
||||
CPU: paar Sek./Satz. Der Akzent der Referenz überträgt sich.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{engine === "elevenlabs" && (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user