Feat: Chat-Verbesserungen — echte Umlaute, TTS-Filter, Lautstärke-Regler, Sprechblasen
- Umlaute: System-Prompt erzwingt echte ä/ö/ü/ß (Hermes spiegelte bei Technik den ASCII-Hint). - TTS-Filter: cleanForTTS entfernt URLs/Markdown/Code/Emojis vor der Sprachausgabe (Anzeige bleibt). - Lautstärke: GainNode in AudioQueue + Regler (Default 60%, live); Probe-hören respektiert ihn. (Bugfix: Number(null)===0 → wäre sonst stumm gewesen.) - Chat: Sprechblasen (User rechts / Hermes links), Zeilenumbrüche, Auto-Scroll, Tipp-Indikator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,21 @@ 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"
|
||||
|
||||
// Kleiner Tipp-Indikator („Hermes schreibt …").
|
||||
function Dots() {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 align-middle">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="h-1.5 w-1.5 animate-pulse rounded-full bg-muted-foreground"
|
||||
style={{ animationDelay: `${i * 0.15}s` }}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const STATUS_LABEL: Record<string, string> = {
|
||||
idle: "Bereit — halte zum Sprechen",
|
||||
listening: "Höre zu …",
|
||||
@@ -22,6 +37,12 @@ export function VoiceView() {
|
||||
useVoiceAgent()
|
||||
|
||||
const holding = useRef(false)
|
||||
const convEndRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Gespräch automatisch nach unten scrollen, wenn neue Tokens/Nachrichten kommen.
|
||||
useEffect(() => {
|
||||
convEndRef.current?.scrollIntoView({ behavior: "smooth", block: "end" })
|
||||
}, [messages])
|
||||
|
||||
// Push-to-talk per Leertaste (solange der Sprechen-Tab fokussiert ist und kein Eingabefeld aktiv).
|
||||
useEffect(() => {
|
||||
@@ -95,7 +116,7 @@ export function VoiceView() {
|
||||
<RotateCcw className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 space-y-2.5 overflow-y-auto p-4 scrollbar-thin">
|
||||
<div className="flex-1 space-y-3 overflow-y-auto p-4 scrollbar-thin">
|
||||
{messages.length === 0 && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Halte den Knopf (oder die Leertaste) und sprich. Hermes hört zu, denkt mit seinem
|
||||
@@ -103,13 +124,21 @@ export function VoiceView() {
|
||||
</p>
|
||||
)}
|
||||
{messages.map((m, i) => (
|
||||
<div key={i} className={cn("text-sm", m.role === "user" ? "text-foreground" : "text-primary/90")}>
|
||||
<span className="mr-1.5 text-[10px] font-semibold uppercase text-muted-foreground">
|
||||
<div key={i} className={cn("flex flex-col gap-1", m.role === "user" ? "items-end" : "items-start")}>
|
||||
<span className="px-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{m.role === "user" ? "Du" : "Hermes"}
|
||||
</span>
|
||||
{m.text || <span className="text-muted-foreground">…</span>}
|
||||
<div className={cn(
|
||||
"max-w-[88%] whitespace-pre-wrap break-words rounded-2xl px-3 py-2 text-sm leading-relaxed",
|
||||
m.role === "user"
|
||||
? "rounded-br-sm bg-primary/15 text-foreground"
|
||||
: "rounded-bl-sm border border-border/40 bg-background/40 text-foreground/90",
|
||||
)}>
|
||||
{m.text || <Dots />}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={convEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user