- ChatVoicePlayer: POST /api/synthesize, play/pause controls, autoPlay support, blob URL cleanup - ChatVoiceBadge: Voice badge, SPOKEN/DETAILED parsing, collapsible full markdown for voice_full
51 lines
1.6 KiB
TypeScript
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>
|
|
);
|
|
}
|