nexus/ui/src/components/ChatVoiceBadge.tsx
Nexus Dev 6b60f42a25 feat(37-03): ChatVoicePlayer + ChatVoiceBadge components
- ChatVoicePlayer: POST /api/synthesize, play/pause controls, autoPlay support, blob URL cleanup
- ChatVoiceBadge: Voice badge, SPOKEN/DETAILED parsing, collapsible full markdown for voice_full
2026-04-04 03:55:50 +00:00

51 lines
1.6 KiB
TypeScript

import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ChatVoicePlayer } from "./ChatVoicePlayer";
import { ChatMarkdownMessage } from "./ChatMarkdownMessage";
interface ChatVoiceBadgeProps {
content: string;
messageType: string; // "voice_input" | "voice_full"
autoPlayVoice?: boolean;
}
export function ChatVoiceBadge({
content,
messageType,
autoPlayVoice = false,
}: ChatVoiceBadgeProps) {
const [open, setOpen] = useState(false);
const spokenMatch = content.match(/SPOKEN:\s*([\s\S]*?)(?=\nDETAILED:|$)/);
const spokenText = spokenMatch?.[1]?.trim() ?? content;
const detailedMatch = content.match(/DETAILED:\s*([\s\S]*)/);
return (
<div className="flex flex-col gap-1">
<Badge variant="outline" className="text-xs mb-2 w-fit">
Voice
</Badge>
<p className="text-sm">{spokenText}</p>
{messageType === "voice_full" && (
<>
<ChatVoicePlayer text={spokenText} autoPlay={autoPlayVoice} />
{detailedMatch && (
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger className="text-xs text-muted-foreground hover:text-foreground mt-1">
{open ? "Hide full response" : "Show full response"}
</CollapsibleTrigger>
<CollapsibleContent>
<ChatMarkdownMessage content={detailedMatch[1].trim()} />
</CollapsibleContent>
</Collapsible>
)}
</>
)}
</div>
);
}