Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
www
/
talha_silentcontent
/
src
/
components
/
Organization
/
Filename :
OrganizationJobDetails.tsx
back
Copy
// @ts-nocheck "use client"; import { useQuery, useMutation } from "@tanstack/react-query"; import axios from "axios"; import { useState, useEffect } from "react"; import { useParams, useRouter } from "next/navigation"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Search, Edit, Pencil } from "lucide-react"; import { toast } from "@/components/ui/use-toast"; import { Badge } from "@/components/ui/badge"; import { RiDeleteBin6Line } from "react-icons/ri"; import DynamicAlertDialog from "@/components/ConfirmationDialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; const editJobSchema = z.object({ gpt: z.string().min(1, "Model is required"), apiKey: z.string().min(1, "API Key is required"), customOutlinePrompt: z.string().optional(), customSectionPrompt: z.string().optional(), }); export default function OrganizationJobDetails() { const { _id } = useParams(); const [searchTerm, setSearchTerm] = useState(""); const router = useRouter(); const [isDialogOpen, setIsDialogOpen] = useState(false); const [organizationId, setOrganizationId] = useState(null); const [apiKeys, setApiKeys] = useState([]); // Form for editing job settings const form = useForm({ resolver: zodResolver(editJobSchema), defaultValues: { gpt: "", apiKey: "", customOutlinePrompt: "", customSectionPrompt: "", }, }); // Fetch job details const { data: jobData, isLoading: isJobLoading, refetch } = useQuery( ["organization-job", _id], async () => { const response = await axios.get(`/api/organizations/jobs/${_id}`); return response.data.job; }, { refetchInterval: 5000 } ); // Update organization ID when job data loads useEffect(() => { if (jobData?.organization) { setOrganizationId(jobData.organization); } }, [jobData]); // Fetch API keys useEffect(() => { const fetchAPIKeys = async () => { try { const response = await axios.get("/api/keys"); setApiKeys(response.data.data.API_Keys); } catch (error) { console.error("Error fetching API keys:", error); } }; fetchAPIKeys(); }, []); // Set form values when job data is loaded useEffect(() => { if (jobData?.settings) { form.reset({ gpt: jobData.settings.gpt || "", apiKey: jobData.settings.apiKey || "", customOutlinePrompt: jobData.settings.customOutlinePrompt || "", customSectionPrompt: jobData.settings.customSectionPrompt || "", }); } }, [jobData, form]); // Filter batches based on search term const filteredBatches = jobData?.batches?.filter(batch => batch.name.toLowerCase().includes(searchTerm.toLowerCase()) ) || []; // Upload CSV file to a job const { mutate: uploadCSV, isLoading: isUploading } = useMutation({ mutationFn: async (formData: FormData) => { const response = await axios.post(`/api/organizations/jobs/${_id}/upload`, formData); return response.data; }, onSuccess: () => { toast({ title: "CSV Uploaded", description: "Your CSV file has been uploaded and is being processed", }); refetch(); }, onError: (error) => { toast({ title: "Error", description: "Failed to upload CSV file", variant: "destructive", }); } }); // Delete job mutation const { mutate: deleteJob, isLoading: isDeleting } = useMutation({ mutationFn: async () => { const response = await axios.delete(`/api/organizations/jobs/${_id}`); return response.data; }, onSuccess: () => { toast({ title: "Job Deleted", description: "The job and all its batches have been deleted", }); router.push(`/organizations/${organizationId}`); }, onError: (error) => { toast({ title: "Error", description: "Failed to delete job", variant: "destructive", }); } }); // Update job settings mutation const { mutate: updateJobSettings, isLoading: isUpdating } = useMutation({ mutationFn: async (data) => { const response = await axios.patch(`/api/organizations/jobs/${_id}`, { settings: { gpt: data.gpt, apiKey: data.apiKey, customOutlinePrompt: data.customOutlinePrompt, customSectionPrompt: data.customSectionPrompt, scraping: jobData?.settings?.scraping || true, prompt: jobData?.settings?.prompt, // Maintain existing prompt } }); return response.data; }, onSuccess: () => { toast({ title: "Settings Updated", description: "Job settings have been updated successfully", }); setIsDialogOpen(false); refetch(); }, onError: (error) => { toast({ title: "Error", description: "Failed to update job settings", variant: "destructive", }); } }); const handleFileUpload = (event) => { const file = event.target.files[0]; if (file) { const formData = new FormData(); formData.append('csv', file); uploadCSV(formData); } }; const onSubmitSettings = (data) => { updateJobSettings(data); }; const getKeyLabel = (key) => { return key.length > 35 ? "OpenAI - GPT" : "Deepseek"; }; return ( <div className="space-y-8"> <div className="flex items-center justify-between"> <div> <h1 className="text-3xl font-bold">{jobData?.name || 'Job Details'}</h1> <span className="text-gray-500 flex gap-2 items-center mt-2"> Status: <Badge variant={jobData?.status === "completed" ? "success" : jobData?.status === "processing" ? "secondary" : "destructive"}> {jobData?.status || 'Loading...'} </Badge> </span> </div> <div className="flex gap-4"> <div> <Input id="csvFile" type="file" accept=".csv" onChange={handleFileUpload} className="hidden" /> <label htmlFor="csvFile"> <Button variant="outline" asChild> <span>{isUploading ? "Uploading..." : "Upload CSV"}</span> </Button> </label> </div> <DynamicAlertDialog title="Delete Job" description="Are you sure you want to delete this job? All batches and articles within it will also be deleted. This action cannot be undone." onConfirm={deleteJob} confirmText="Delete" cancelText="Cancel" keyToDelete={_id} triggerButton={ <Button variant="destructive" disabled={isDeleting}> {isDeleting ? "Deleting..." : "Delete Job"} <RiDeleteBin6Line className="ml-2" /> </Button> } /> </div> </div> <Card> <CardHeader className="flex flex-row items-center justify-between"> <CardTitle>Job Settings</CardTitle> <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> <DialogTrigger asChild> <Button variant="outline" size="sm"> <Pencil className="mr-2 h-4 w-4" /> Edit Settings </Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Edit Job Settings</DialogTitle> </DialogHeader> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmitSettings)} className="space-y-4"> <FormField control={form.control} name="gpt" render={({ field }) => ( <FormItem> <FormLabel>Model</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value} > <FormControl> <SelectTrigger> <SelectValue placeholder="Select a model" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="gpt-4o-2024-08-06">GPT-4o-2024-08-06</SelectItem> <SelectItem value="gpt-4o">GPT-4o</SelectItem> <SelectItem value="gpt-4-turbo">GPT-4 Turbo</SelectItem> <SelectItem value="gpt-3.5-turbo">GPT-3.5 Turbo</SelectItem> <SelectItem value="deepseek-chat">Deepseek</SelectItem> </SelectContent> </Select> </FormItem> )} /> <FormField control={form.control} name="apiKey" render={({ field }) => ( <FormItem> <FormLabel>API Key</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value} > <FormControl> <SelectTrigger> <SelectValue placeholder="Select an API key" /> </SelectTrigger> </FormControl> <SelectContent> {apiKeys.map((key, index) => ( <SelectItem key={index} value={key}> {getKeyLabel(key)} ({key.substring(0, 10)}...) </SelectItem> ))} </SelectContent> </Select> </FormItem> )} /> <FormField control={form.control} name="customOutlinePrompt" render={({ field }) => ( <FormItem> <FormLabel>Custom Outline Prompt</FormLabel> <FormControl> <Textarea {...field} placeholder="Enter custom outline prompt (optional)" rows={4} /> </FormControl> </FormItem> )} /> <FormField control={form.control} name="customSectionPrompt" render={({ field }) => ( <FormItem> <FormLabel>Custom Section Prompt</FormLabel> <FormControl> <Textarea {...field} placeholder="Enter custom section prompt (optional)" rows={4} /> </FormControl> </FormItem> )} /> <div className="flex justify-end gap-2"> <Button type="button" variant="outline" onClick={() => setIsDialogOpen(false)}> Cancel </Button> <Button type="submit" disabled={isUpdating}> {isUpdating ? "Saving..." : "Save Changes"} </Button> </div> </form> </Form> </DialogContent> </Dialog> </CardHeader> <CardContent> {isJobLoading ? ( <div className="flex items-center justify-center py-4"> <p>Loading settings...</p> </div> ) : ( <> <div className="grid grid-cols-2 gap-4"> <div> <h3 className="font-semibold">Model:</h3> <p>{jobData?.settings?.gpt || 'N/A'}</p> </div> <div> <h3 className="font-semibold">API Key:</h3> <p>{jobData?.settings?.apiKey ? `${jobData.settings.apiKey.substring(0, 20)}...` : 'N/A'}</p> </div> <div> <h3 className="font-semibold">Scraping:</h3> <p>{jobData?.settings?.scraping ? 'Enabled' : 'Disabled'}</p> </div> </div> <div className="mt-4"> <h3 className="font-semibold">Custom Outline Prompt:</h3> <p className="whitespace-pre-wrap">{jobData?.settings?.customOutlinePrompt || 'None'}</p> </div> <div className="mt-4"> <h3 className="font-semibold">Custom Section Prompt:</h3> <p className="whitespace-pre-wrap">{jobData?.settings?.customSectionPrompt || 'None'}</p> </div> </> )} </CardContent> </Card> <div> <div className="flex justify-between items-center mb-4"> <h2 className="text-2xl font-semibold">Batches</h2> <div className="relative"> <Input type="text" placeholder="Search batches..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="pl-10 w-64" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" /> </div> </div> {filteredBatches.length > 0 ? ( <Table> <TableHeader> <TableRow> <TableHead>Batch Name</TableHead> <TableHead>Keywords</TableHead> <TableHead>Status</TableHead> <TableHead>Created</TableHead> <TableHead>Actions</TableHead> </TableRow> </TableHeader> <TableBody> {filteredBatches.map((batch) => ( <TableRow key={batch._id}> <TableCell className="font-medium">{batch.name}</TableCell> <TableCell>{batch.keywordCount || 0}</TableCell> <TableCell> <Badge variant={ batch.status === "completed" ? "success" : batch.status === "processing" ? "secondary" : batch.status === "failed" ? "destructive" : "default" }> {batch.status} </Badge> </TableCell> <TableCell>{new Date(batch.createdAt).toLocaleDateString()}</TableCell> <TableCell> <Button variant="outline" onClick={() => router.push(`/organizations/batches/${batch._id}`)} > View Keywords </Button> </TableCell> </TableRow> ))} </TableBody> </Table> ) : ( <div className="text-center py-8 border rounded-md"> {isJobLoading ? ( <p>Loading batches...</p> ) : ( <div className="space-y-4"> <p>No batches found for this job.</p> <p className="text-sm text-gray-500">Upload a CSV file to create a new batch.</p> </div> )} </div> )} </div> </div> ); }