Download frontend/src/components/SampleLibrary.tsx from vipan-kumar/Audio_Deepfake_Detection: direct link, hf CLI and curl.
- Browser
- Download file 4.75 kB
-
https://huggingface.co/spaces/vipan-kumar/Audio_Deepfake_Detection/resolve/main/frontend/src/components/SampleLibrary.tsx
- Command line
-
hf download hf://spaces/vipan-kumar/Audio_Deepfake_Detection/frontend/src/components/SampleLibrary.tsx
-
curl -L -o SampleLibrary.tsx https://huggingface.co/spaces/vipan-kumar/Audio_Deepfake_Detection/resolve/main/frontend/src/components/SampleLibrary.tsx
4.75 kB
| import { useEffect, useState } from "react"; | |
| import { listSamples, sampleDownloadUrl } from "@/lib/api"; | |
| import type { SampleAudio } from "@/types/inference"; | |
| import { useInferenceStore } from "@/store/inferenceStore"; | |
| import { cn } from "@/lib/utils"; | |
| import { CheckCircle2, Music2 } from "lucide-react"; | |
| export default function SampleLibrary() { | |
| const [samples, setSamples] = useState<SampleAudio[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const audio = useInferenceStore((s) => s.audio); | |
| const setAudio = useInferenceStore((s) => s.setAudio); | |
| useEffect(() => { | |
| let alive = true; | |
| listSamples() | |
| .then((r) => { | |
| if (alive) setSamples(r.samples); | |
| }) | |
| .catch((e) => { | |
| if (alive) | |
| setError( | |
| e instanceof Error ? e.message : "Could not load sample list." | |
| ); | |
| }) | |
| .finally(() => alive && setLoading(false)); | |
| return () => { | |
| alive = false; | |
| }; | |
| }, []); | |
| if (loading) { | |
| return ( | |
| <div className="font-mono text-xs text-ink-dim">loading samples…</div> | |
| ); | |
| } | |
| if (error) { | |
| return <div className="font-mono text-xs text-danger">{error}</div>; | |
| } | |
| const reals = samples.filter((s) => s.label === "real"); | |
| const fakes = samples.filter((s) => s.label === "fake"); | |
| return ( | |
| <div> | |
| <div className="mb-3 flex items-center justify-between"> | |
| <div className="label">sample library</div> | |
| <span className="font-mono text-[10px] text-ink-mute"> | |
| {samples.length} clips | |
| </span> | |
| </div> | |
| <SampleGroup title="real" tint="cyber" items={reals} audio={audio} setAudio={setAudio} /> | |
| <div className="my-3 border-t border-line/60" /> | |
| <SampleGroup title="fake" tint="danger" items={fakes} audio={audio} setAudio={setAudio} /> | |
| <div className="mt-3 font-mono text-[10px] text-ink-mute"> | |
| Tip: drop your own real-world WAVs into{" "} | |
| <code className="font-mono">data/sample_audios/</code> to add them here. | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function SampleGroup({ | |
| title, | |
| tint, | |
| items, | |
| audio, | |
| setAudio, | |
| }: { | |
| title: string; | |
| tint: "cyber" | "danger"; | |
| items: SampleAudio[]; | |
| audio: ReturnType<typeof useInferenceStore.getState>["audio"]; | |
| setAudio: ReturnType<typeof useInferenceStore.getState>["setAudio"]; | |
| }) { | |
| return ( | |
| <div> | |
| <div className="mb-1.5 flex items-center gap-1.5"> | |
| <span | |
| className={cn( | |
| "h-1.5 w-1.5 rounded-full", | |
| tint === "danger" ? "bg-danger" : "bg-cyber" | |
| )} | |
| /> | |
| <span className="font-mono text-[10px] uppercase tracking-wider text-ink-dim"> | |
| {title} · {items.length} | |
| </span> | |
| </div> | |
| <div className="grid gap-1.5"> | |
| {items.map((s) => { | |
| const isSelected = | |
| audio?.kind === "sample" && audio.sampleId === s.sample_id; | |
| return ( | |
| <button | |
| key={s.sample_id} | |
| onClick={() => | |
| setAudio({ | |
| kind: "sample", | |
| sampleId: s.sample_id, | |
| url: sampleDownloadUrl(s.sample_id), | |
| }) | |
| } | |
| className={cn( | |
| "group flex items-start justify-between gap-2 rounded-md border bg-surface-alt/50 px-2.5 py-2 text-left transition-all duration-150", | |
| "hover:bg-surface-alt hover:translate-x-0.5", | |
| isSelected | |
| ? tint === "danger" | |
| ? "border-danger/60 bg-danger/10" | |
| : "border-cyber/60 bg-cyber/10" | |
| : "border-line" | |
| )} | |
| > | |
| <div className="flex min-w-0 items-start gap-2"> | |
| <Music2 | |
| className={cn( | |
| "mt-0.5 h-3.5 w-3.5 shrink-0 transition-transform group-hover:scale-110", | |
| tint === "danger" ? "text-danger" : "text-cyber" | |
| )} | |
| /> | |
| <div className="min-w-0"> | |
| <div className="truncate font-mono text-[11px] text-ink"> | |
| {s.filename.replace(/\.wav$/, "")} | |
| </div> | |
| <div className="mt-0.5 truncate text-[10px] text-ink-dim"> | |
| {s.description} | |
| </div> | |
| </div> | |
| </div> | |
| {isSelected && ( | |
| <CheckCircle2 | |
| className={cn( | |
| "h-3.5 w-3.5 shrink-0", | |
| tint === "danger" ? "text-danger" : "text-cyber" | |
| )} | |
| /> | |
| )} | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| } | |