Jensin commited on
Commit
fa4dd74
·
verified ·
1 Parent(s): dbd35be

Upload components/dashboard/review-queue.jsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. components/dashboard/review-queue.jsx +194 -0
components/dashboard/review-queue.jsx ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, useEffect } from "react"
4
+ import { Button } from "@/components/ui/button"
5
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
6
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
7
+ import { Badge } from "@/components/ui/badge"
8
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
9
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
10
+ import { toast } from "sonner"
11
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
12
+ import axios from "axios"
13
+ import YouTube from "react-youtube"
14
+
15
+ const fetchReviewQueue = async () => {
16
+ const response = await axios.get("/api/review-queue")
17
+ return response.data
18
+ }
19
+
20
+ const updateReviewItem = async (id, status, metadata) => {
21
+ const response = await axios.put(`/api/review-queue/${id}`, { status, metadata })
22
+ return response.data
23
+ }
24
+
25
+ export function ReviewQueue() {
26
+ const [selectedItem, setSelectedItem] = useState(null)
27
+ const queryClient = useQueryClient()
28
+
29
+ const { data: queue, isLoading, error } = useQuery({
30
+ queryKey: ["reviewQueue"],
31
+ queryFn: fetchReviewQueue,
32
+ })
33
+
34
+ const mutation = useMutation({
35
+ mutationFn: updateReviewItem,
36
+ onSuccess: () => {
37
+ queryClient.invalidateQueries(["reviewQueue"])
38
+ toast.success("Item updated successfully")
39
+ setSelectedItem(null)
40
+ },
41
+ onError: (error) => {
42
+ toast.error(`Error updating item: ${error.message}`)
43
+ },
44
+ })
45
+
46
+ const handleApprove = (item) => {
47
+ mutation.mutate({ id: item.id, status: "approved", metadata: item.metadata })
48
+ }
49
+
50
+ const handleReject = (item) => {
51
+ mutation.mutate({ id: item.id, status: "rejected", metadata: item.metadata })
52
+ }
53
+
54
+ const handleEdit = (item) => {
55
+ setSelectedItem(item)
56
+ }
57
+
58
+ const handleSaveEdit = () => {
59
+ mutation.mutate({
60
+ id: selectedItem.id,
61
+ status: "approved",
62
+ metadata: selectedItem.metadata,
63
+ })
64
+ }
65
+
66
+ if (isLoading) return <div className="p-4">Loading...</div>
67
+ if (error) return <Alert variant="destructive"><AlertTitle>Error</AlertTitle><AlertDescription>{error.message}</AlertDescription></Alert>
68
+
69
+ return (
70
+ <div className="flex flex-col gap-4">
71
+ <Card>
72
+ <CardHeader>
73
+ <CardTitle>Review Queue</CardTitle>
74
+ <CardDescription>Manage content flagged for manual review</CardDescription>
75
+ </CardHeader>
76
+ <CardContent>
77
+ <Table>
78
+ <TableHeader>
79
+ <TableRow>
80
+ <TableHead>Title</TableHead>
81
+ <TableHead>Channel</TableHead>
82
+ <TableHead>Status</TableHead>
83
+ <TableHead>Actions</TableHead>
84
+ </TableRow>
85
+ </TableHeader>
86
+ <TableBody>
87
+ {queue?.map((item) => (
88
+ <TableRow key={item.id}>
89
+ <TableCell className="font-medium">{item.title}</TableCell>
90
+ <TableCell>{item.channel}</TableCell>
91
+ <TableCell>
92
+ <Badge variant={item.status === "flagged" ? "destructive" : "secondary"}>
93
+ {item.status}
94
+ </Badge>
95
+ </TableCell>
96
+ <TableCell>
97
+ <DropdownMenu>
98
+ <DropdownMenuTrigger asChild>
99
+ <Button variant="ghost" className="h-8 w-8 p-0">
100
+ <svg
101
+ xmlns="http://www.w3.org/2000/svg"
102
+ width="24"
103
+ height="24"
104
+ viewBox="0 0 24 24"
105
+ fill="none"
106
+ stroke="currentColor"
107
+ strokeWidth="2"
108
+ strokeLinecap="round"
109
+ strokeLinejoin="round"
110
+ className="h-4 w-4"
111
+ >
112
+ <path d="M5 12h14" />
113
+ <path d="M12 5v14" />
114
+ </svg>
115
+ </Button>
116
+ </DropdownMenuTrigger>
117
+ <DropdownMenuContent align="end">
118
+ <DropdownMenuItem onClick={() => handleApprove(item)}>Approve</DropdownMenuItem>
119
+ <DropdownMenuItem onClick={() => handleReject(item)}>Reject</DropdownMenuItem>
120
+ <DropdownMenuItem onClick={() => handleEdit(item)}>Edit Metadata</DropdownMenuItem>
121
+ </DropdownMenuContent>
122
+ </DropdownMenu>
123
+ </TableCell>
124
+ </TableRow>
125
+ ))}
126
+ </TableBody>
127
+ </Table>
128
+ </CardContent>
129
+ </Card>
130
+
131
+ {selectedItem && (
132
+ <Card>
133
+ <CardHeader>
134
+ <CardTitle>Edit Metadata</CardTitle>
135
+ <CardDescription>Review and edit content details</CardDescription>
136
+ </CardHeader>
137
+ <CardContent className="grid gap-4">
138
+ <div className="grid gap-2">
139
+ <div className="aspect-video">
140
+ <YouTube videoId={selectedItem.videoId} opts={{ width: "100%", height: "100%" }} />
141
+ </div>
142
+ <div className="grid gap-1">
143
+ <div className="font-medium">{selectedItem.title}</div>
144
+ <div className="text-sm text-muted-foreground">{selectedItem.channel}</div>
145
+ </div>
146
+ </div>
147
+ <div className="grid gap-2">
148
+ <div className="font-medium">Transcript</div>
149
+ <div className="rounded-md border p-4 text-sm">
150
+ {selectedItem.transcript}
151
+ </div>
152
+ </div>
153
+ <div className="grid gap-2">
154
+ <div className="font-medium">Extracted Metadata</div>
155
+ <div className="grid gap-2">
156
+ <div className="flex items-center gap-2">
157
+ <div className="text-sm text-muted-foreground">Series:</div>
158
+ <input
159
+ type="text"
160
+ value={selectedItem.metadata.series || ""}
161
+ onChange={(e) => setSelectedItem({
162
+ ...selectedItem,
163
+ metadata: { ...selectedItem.metadata, series: e.target.value }
164
+ })}
165
+ className="rounded-md border px-2 py-1 text-sm"
166
+ />
167
+ </div>
168
+ <div className="flex items-center gap-2">
169
+ <div className="text-sm text-muted-foreground">Characters:</div>
170
+ <input
171
+ type="text"
172
+ value={selectedItem.metadata.characters || ""}
173
+ onChange={(e) => setSelectedItem({
174
+ ...selectedItem,
175
+ metadata: { ...selectedItem.metadata, characters: e.target.value }
176
+ })}
177
+ className="rounded-md border px-2 py-1 text-sm"
178
+ />
179
+ </div>
180
+ <div className="flex items-center gap-2">
181
+ <div className="text-sm text-muted-foreground">Themes:</div>
182
+ <input
183
+ type="text"
184
+ value={selectedItem.metadata.themes || ""}
185
+ onChange={(e) => setSelectedItem({
186
+ ...selectedItem,
187
+ metadata: { ...selectedItem.metadata, themes: e.target.value }
188
+ })}
189
+ className="rounded-md border px-2 py-1 text-sm"
190
+ />
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </CardContent>