Feat: festes Avatar-VRM (das eine Modell) + Avatar-Picker entfernt

Avatar fest auf /avatar.vrm (frontend/public/avatar.vrm → dist). Galerie/Upload/URL raus;
AvatarPicker → reine VoiceControls (nur Stimme). vrmStore.ts gelöscht. avatar.vrm ist gitignored
(23 MB, lizenz-/redistributionssensibel) — liegt auf der Box, nicht in git.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-28 02:34:35 +02:00
parent b4a8f92cfa
commit 49c9e27502
7 changed files with 388 additions and 548 deletions
+8 -9
View File
@@ -1,10 +1,13 @@
import { useCallback, useEffect, useRef, useState } from "react"
import { useEffect, useRef } from "react"
import { Mic, RotateCcw, Loader2, Volume2 } from "lucide-react"
import { Avatar3D } from "@/components/voice/Avatar3D"
import { AvatarPicker, DEFAULT_AVATAR } from "@/components/voice/AvatarPicker"
import { VoiceControls } from "@/components/voice/AvatarPicker"
import { useVoiceAgent } from "@/lib/voice/useVoiceAgent"
import { cn } from "@/lib/utils"
// Fester Avatar (das eine, gewählte VRM). Liegt unter frontend/public/avatar.vrm → /avatar.vrm.
const FIXED_AVATAR = "/avatar.vrm"
const STATUS_LABEL: Record<string, string> = {
idle: "Bereit — halte zum Sprechen",
listening: "Höre zu …",
@@ -15,14 +18,10 @@ const STATUS_LABEL: Record<string, string> = {
}
export function VoiceView() {
const [avatarUrl, setAvatarUrl] = useState(
() => localStorage.getItem("mc_voice_avatar_url") || DEFAULT_AVATAR,
)
const { status, messages, error, recording, audioLevel, emotion, pressStart, pressEnd, reset } =
useVoiceAgent()
const holding = useRef(false)
const onAvatarChange = useCallback((url: string) => setAvatarUrl(url), [])
// Push-to-talk per Leertaste (solange der Sprechen-Tab fokussiert ist und kein Eingabefeld aktiv).
useEffect(() => {
@@ -49,7 +48,7 @@ export function VoiceView() {
{/* Avatar-Bühne */}
<div className="relative flex min-w-0 flex-1 flex-col rounded-xl border border-border/40 bg-card/30 overflow-hidden">
<div className="flex-1 min-h-0">
<Avatar3D url={avatarUrl} audioLevel={audioLevel} emotion={emotion} />
<Avatar3D url={FIXED_AVATAR} audioLevel={audioLevel} emotion={emotion} />
</div>
{/* Status + Push-to-talk */}
@@ -85,8 +84,8 @@ export function VoiceView() {
{/* Seitenspalte: Einstellungen + Transcript */}
<div className="flex w-80 shrink-0 flex-col gap-4">
<div className="rounded-xl border border-border/40 bg-card/40 p-4 overflow-y-auto scrollbar-thin max-h-[55%]">
<AvatarPicker avatarUrl={avatarUrl} onAvatarChange={onAvatarChange} />
<div className="rounded-xl border border-border/40 bg-card/40 p-4">
<VoiceControls />
</div>
<div className="flex min-h-0 flex-1 flex-col rounded-xl border border-border/40 bg-card/40">