Add AI-powered description generation for video content
Integrates OpenAI API to generate video descriptions using AI, with new API endpoints for single and bulk generation, and a UI button in the admin panel to trigger the process. Includes OpenAI dependency and a new `aiService.ts` file. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 170e18f0-0f13-4eca-8643-546bba1dd8cc Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/170e18f0-0f13-4eca-8643-546bba1dd8cc/td5Y4HG
This commit is contained in:
@@ -13,7 +13,7 @@ import { useToast } from "@/hooks/use-toast";
|
||||
import { LoadingSpinner } from "@/components/loading-spinner";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import type { Video } from "@shared/schema";
|
||||
import { Shield, Edit, Upload, Search, Filter, Save, X } from "lucide-react";
|
||||
import { Shield, Edit, Upload, Search, Filter, Save, X, Sparkles, Loader2 } from "lucide-react";
|
||||
|
||||
export default function AdminPage() {
|
||||
const { user, isLoading: authLoading, isAuthenticated, isAdmin } = useAuth();
|
||||
@@ -216,6 +216,7 @@ function EditVideoDialog({
|
||||
genre: video.genre,
|
||||
customThumbnailUrl: video.customThumbnailUrl || "",
|
||||
});
|
||||
const [isGeneratingAI, setIsGeneratingAI] = useState(false);
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: any) => apiRequest("PATCH", `/api/admin/videos/${video.id}`, data),
|
||||
@@ -240,6 +241,42 @@ function EditVideoDialog({
|
||||
updateMutation.mutate(formData);
|
||||
};
|
||||
|
||||
const generateAIDescription = async () => {
|
||||
if (!formData.title.trim()) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Please enter a title first",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGeneratingAI(true);
|
||||
try {
|
||||
const response = await apiRequest("POST", `/api/admin/videos/${video.id}/generate-description`, {
|
||||
maxCharacters: 500,
|
||||
includeArtistInfo: true,
|
||||
includeLabelInfo: true
|
||||
});
|
||||
|
||||
if (response.description) {
|
||||
setFormData(prev => ({ ...prev, description: response.description }));
|
||||
toast({
|
||||
title: "Success",
|
||||
description: `AI description generated (${response.characterCount}/${response.maxCharacters} characters)`,
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: error.message || "Failed to generate AI description",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsGeneratingAI(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl bg-[#2D1B69] border-white/20 text-white">
|
||||
@@ -263,13 +300,34 @@ function EditVideoDialog({
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<Label className="text-white/90">Description</Label>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label className="text-white/90">Description</Label>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={generateAIDescription}
|
||||
disabled={isGeneratingAI || !formData.title.trim()}
|
||||
className="border-white/20 text-white hover:bg-white/10 disabled:opacity-50"
|
||||
>
|
||||
{isGeneratingAI ? (
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
{isGeneratingAI ? "Generating..." : "Generate AI Description"}
|
||||
</Button>
|
||||
</div>
|
||||
<Textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
className="bg-white/10 border-white/20 text-white min-h-[100px]"
|
||||
rows={3}
|
||||
placeholder="Enter description or use AI to generate one from the title..."
|
||||
/>
|
||||
<div className="text-xs text-white/60 mt-1">
|
||||
{formData.description.length}/500 characters
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user