mirror of
https://github.com/HumanAIGC-Engineering/gradio-webrtc.git
synced 2026-02-05 18:09:23 +08:00
sync code of fastrtc, add text support through datachannel, fix safari connect problem support chat without camera or mic
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { Mic, Square } from "lucide-react";
|
|
import { useState, useEffect } from "react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface AIVoiceInputProps {
|
|
onStart?: () => void;
|
|
onStop?: (duration: number) => void;
|
|
isConnected?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
export function AIVoiceInput({
|
|
onStart,
|
|
onStop,
|
|
isConnected = false,
|
|
className
|
|
}: AIVoiceInputProps) {
|
|
const [active, setActive] = useState(false);
|
|
const [time, setTime] = useState(0);
|
|
const [isClient, setIsClient] = useState(false);
|
|
const [status, setStatus] = useState<'disconnected' | 'connecting' | 'connected'>('disconnected');
|
|
|
|
useEffect(() => {
|
|
setIsClient(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let intervalId: NodeJS.Timeout;
|
|
|
|
if (active) {
|
|
intervalId = setInterval(() => {
|
|
setTime((t) => t + 1);
|
|
}, 1000);
|
|
} else {
|
|
setTime(0);
|
|
}
|
|
|
|
return () => clearInterval(intervalId);
|
|
}, [active]);
|
|
|
|
useEffect(() => {
|
|
if (isConnected) {
|
|
setStatus('connected');
|
|
setActive(true);
|
|
} else {
|
|
setStatus('disconnected');
|
|
setActive(false);
|
|
}
|
|
}, [isConnected]);
|
|
|
|
const formatTime = (seconds: number) => {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
};
|
|
|
|
const handleStart = () => {
|
|
setStatus('connecting');
|
|
onStart?.();
|
|
};
|
|
|
|
const handleStop = () => {
|
|
onStop?.(time);
|
|
setStatus('disconnected');
|
|
};
|
|
|
|
return (
|
|
<div className={cn("w-full py-4", className)}>
|
|
<div className="relative max-w-xl w-full mx-auto flex items-center flex-col gap-4">
|
|
<div className={cn(
|
|
"px-2 py-1 rounded-md text-xs font-medium bg-black/10 dark:bg-white/10 text-gray-700 dark:text-white"
|
|
)}>
|
|
{status === 'connected' ? 'Connected' : status === 'connecting' ? 'Connecting...' : 'Disconnected'}
|
|
</div>
|
|
|
|
<button
|
|
className={cn(
|
|
"group w-16 h-16 rounded-xl flex items-center justify-center transition-colors",
|
|
active
|
|
? "bg-red-500/20 hover:bg-red-500/30"
|
|
: "bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
|
|
)}
|
|
type="button"
|
|
onClick={active ? handleStop : handleStart}
|
|
disabled={status === 'connecting'}
|
|
>
|
|
{status === 'connecting' ? (
|
|
<div
|
|
className="w-6 h-6 rounded-sm animate-spin bg-black dark:bg-white cursor-pointer pointer-events-auto"
|
|
style={{ animationDuration: "3s" }}
|
|
/>
|
|
) : active ? (
|
|
<Square className="w-6 h-6 text-red-500" />
|
|
) : (
|
|
<Mic className="w-6 h-6 text-black/70 dark:text-white/70" />
|
|
)}
|
|
</button>
|
|
|
|
<span
|
|
className={cn(
|
|
"font-mono text-sm transition-opacity duration-300",
|
|
active
|
|
? "text-black/70 dark:text-white/70"
|
|
: "text-black/30 dark:text-white/30"
|
|
)}
|
|
>
|
|
{formatTime(time)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |