From 475534134cbadf05233bd26bdf2c5537b0580520 Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Tue, 2 Sep 2025 12:59:45 +0000 Subject: [PATCH] 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 --- .replit | 1 + client/src/pages/admin.tsx | 62 ++++++++++++++++++++++- package-lock.json | 22 ++++++++ package.json | 1 + server/aiService.ts | 101 +++++++++++++++++++++++++++++++++++++ server/routes.ts | 83 ++++++++++++++++++++++++++++++ 6 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 server/aiService.ts diff --git a/.replit b/.replit index c806144..63699fb 100644 --- a/.replit +++ b/.replit @@ -40,3 +40,4 @@ args = "npm run dev" waitForPort = 5000 [agent] +integrations = ["javascript_openai==1.0.0"] diff --git a/client/src/pages/admin.tsx b/client/src/pages/admin.tsx index 5d34dd7..33a1564 100644 --- a/client/src/pages/admin.tsx +++ b/client/src/pages/admin.tsx @@ -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 ( @@ -263,13 +300,34 @@ function EditVideoDialog({
- +
+ + +