import { Check, ChevronDown, Pause, Play, Search } from "lucide-react";
import { useMemo, useState } from "react";
import type { RecordingResponseSchema } from "@/client/types.gen";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Popover, PopoverContentInline, PopoverTrigger } from "@/components/ui/popover";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useAudioPlayback } from "@/hooks/useAudioPlayback";
import { cn } from "@/lib/utils";
interface TextOrAudioInputProps {
type: 'text' | 'audio';
onTypeChange: (type: 'text' | 'audio') => void;
recordingId: string;
onRecordingIdChange: (id: string) => void;
recordings?: RecordingResponseSchema[];
/** Rendered when type === 'text' */
children: React.ReactNode;
}
export function TextOrAudioInput({
type,
onTypeChange,
recordingId,
onRecordingIdChange,
recordings = [],
children,
}: TextOrAudioInputProps) {
return (
<>
onTypeChange(value as 'text' | 'audio')}
className="flex items-center gap-4"
>
{type === 'text' ? (
children
) : (
)}
>
);
}
interface RecordingSelectProps {
value: string;
onChange: (id: string) => void;
recordings: RecordingResponseSchema[];
}
/**
* Dropdown to select a pre-recorded audio file.
* Re-exported so callers that only need the dropdown (e.g. tool configs with
* their own none/custom/audio radio) can use it directly.
*/
export function RecordingSelect({ value, onChange, recordings }: RecordingSelectProps) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const { playingId, toggle, stop } = useAudioPlayback();
const selected = recordings.find((r) => String(r.id) === value);
const filtered = useMemo(() => {
if (!search) return recordings;
const q = search.toLowerCase();
return recordings.filter((r) =>
r.recording_id.toLowerCase().includes(q) ||
r.transcript.toLowerCase().includes(q) ||
((r.metadata?.original_filename as string) || "").toLowerCase().includes(q)
);
}, [recordings, search]);
const handleSelect = (rec: RecordingResponseSchema) => {
stop();
onChange(String(rec.id));
setOpen(false);
};
const handlePlay = async (e: React.MouseEvent, rec: RecordingResponseSchema) => {
e.stopPropagation();
try {
await toggle(rec.recording_id, rec.storage_key, rec.storage_backend);
} catch {
// Ignore playback errors
}
};
return (
{ if (!v) { stop(); setSearch(""); } setOpen(v); }}>
{recordings.length === 0 ? (
No recordings available
) : (
{filtered.length === 0 ? (
No recordings match “{search}”
) : filtered.map((r) => {
const filename = (r.metadata?.original_filename as string) || "";
const isSelected = String(r.id) === value;
const isPlaying = playingId === r.recording_id;
return (
handleSelect(r)}
>
{r.recording_id}
{filename && (
{filename}
)}
{r.transcript}
);
})}
)}
);
}