Jensin's picture
Upload components/dashboard/review-queue.jsx with huggingface_hub
fa4dd74 verified
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import axios from "axios"
import YouTube from "react-youtube"
const fetchReviewQueue = async () => {
const response = await axios.get("/api/review-queue")
return response.data
}
const updateReviewItem = async (id, status, metadata) => {
const response = await axios.put(`/api/review-queue/${id}`, { status, metadata })
return response.data
}
export function ReviewQueue() {
const [selectedItem, setSelectedItem] = useState(null)
const queryClient = useQueryClient()
const { data: queue, isLoading, error } = useQuery({
queryKey: ["reviewQueue"],
queryFn: fetchReviewQueue,
})
const mutation = useMutation({
mutationFn: updateReviewItem,
onSuccess: () => {
queryClient.invalidateQueries(["reviewQueue"])
toast.success("Item updated successfully")
setSelectedItem(null)
},
onError: (error) => {
toast.error(`Error updating item: ${error.message}`)
},
})
const handleApprove = (item) => {
mutation.mutate({ id: item.id, status: "approved", metadata: item.metadata })
}
const handleReject = (item) => {
mutation.mutate({ id: item.id, status: "rejected", metadata: item.metadata })
}
const handleEdit = (item) => {
setSelectedItem(item)
}
const handleSaveEdit = () => {
mutation.mutate({
id: selectedItem.id,
status: "approved",
metadata: selectedItem.metadata,
})
}
if (isLoading) return <div className="p-4">Loading...</div>
if (error) return <Alert variant="destructive"><AlertTitle>Error</AlertTitle><AlertDescription>{error.message}</AlertDescription></Alert>
return (
<div className="flex flex-col gap-4">
<Card>
<CardHeader>
<CardTitle>Review Queue</CardTitle>
<CardDescription>Manage content flagged for manual review</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Title</TableHead>
<TableHead>Channel</TableHead>
<TableHead>Status</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{queue?.map((item) => (
<TableRow key={item.id}>
<TableCell className="font-medium">{item.title}</TableCell>
<TableCell>{item.channel}</TableCell>
<TableCell>
<Badge variant={item.status === "flagged" ? "destructive" : "secondary"}>
{item.status}
</Badge>
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleApprove(item)}>Approve</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleReject(item)}>Reject</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleEdit(item)}>Edit Metadata</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
{selectedItem && (
<Card>
<CardHeader>
<CardTitle>Edit Metadata</CardTitle>
<CardDescription>Review and edit content details</CardDescription>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-2">
<div className="aspect-video">
<YouTube videoId={selectedItem.videoId} opts={{ width: "100%", height: "100%" }} />
</div>
<div className="grid gap-1">
<div className="font-medium">{selectedItem.title}</div>
<div className="text-sm text-muted-foreground">{selectedItem.channel}</div>
</div>
</div>
<div className="grid gap-2">
<div className="font-medium">Transcript</div>
<div className="rounded-md border p-4 text-sm">
{selectedItem.transcript}
</div>
</div>
<div className="grid gap-2">
<div className="font-medium">Extracted Metadata</div>
<div className="grid gap-2">
<div className="flex items-center gap-2">
<div className="text-sm text-muted-foreground">Series:</div>
<input
type="text"
value={selectedItem.metadata.series || ""}
onChange={(e) => setSelectedItem({
...selectedItem,
metadata: { ...selectedItem.metadata, series: e.target.value }
})}
className="rounded-md border px-2 py-1 text-sm"
/>
</div>
<div className="flex items-center gap-2">
<div className="text-sm text-muted-foreground">Characters:</div>
<input
type="text"
value={selectedItem.metadata.characters || ""}
onChange={(e) => setSelectedItem({
...selectedItem,
metadata: { ...selectedItem.metadata, characters: e.target.value }
})}
className="rounded-md border px-2 py-1 text-sm"
/>
</div>
<div className="flex items-center gap-2">
<div className="text-sm text-muted-foreground">Themes:</div>
<input
type="text"
value={selectedItem.metadata.themes || ""}
onChange={(e) => setSelectedItem({
...selectedItem,
metadata: { ...selectedItem.metadata, themes: e.target.value }
})}
className="rounded-md border px-2 py-1 text-sm"
/>
</div>
</div>
</div>
</CardContent>