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:
sebastjanartic
2025-09-02 12:59:45 +00:00
parent a9d7322fa8
commit 475534134c
6 changed files with 268 additions and 2 deletions
+101
View File
@@ -0,0 +1,101 @@
import OpenAI from "openai";
// the newest OpenAI model is "gpt-5" which was released August 7, 2025. do not change this unless explicitly requested by the user
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export interface DescriptionGenerationOptions {
maxCharacters?: number;
language?: string;
includeArtistInfo?: boolean;
includeLabelInfo?: boolean;
}
export async function generateVideoDescription(
title: string,
options: DescriptionGenerationOptions = {}
): Promise<string> {
const {
maxCharacters = 500,
language = "slovenian",
includeArtistInfo = true,
includeLabelInfo = true
} = options;
try {
const prompt = `Analiziraj naslov videoisranja: "${title}"
Nalogo izvršit v slovenskem jeziku.
Iz naslova izvleci:
- Ime izvajalca/umetnika
- Naslov skladbe/komada
- Tip vsebine (pesem, instrumental, live nastop, itd.)
Ustvari informativen opis, ki vključuje:
${includeArtistInfo ? '- Informacije o izvajalcu (stil glasbe, kratka zgodovina, znani komadi)' : ''}
${includeLabelInfo ? '- Informacije o založbi ali labelu, če je znan' : ''}
- Opis stila glasbe in žanra
- Kratko ozadje o komadu, če je znan
- Čemu je namenjen (ples, poslušanje, koncert, itd.)
Opis naj bo dolg maksimalno ${maxCharacters} znakov.
Opis naj bo napisan v prijaznem, informativnem tonu.
Ne uporabljaj besed "video" ali "posnetek" - piši o glasbi sami.
Odgovori samo z opisom, brez dodatnih pojasnil.`;
const response = await openai.chat.completions.create({
model: "gpt-5", // the newest OpenAI model is "gpt-5" which was released August 7, 2025
messages: [
{
role: "system",
content: "Si strokovnjak za glasbo in pomagaš ustvarjati kakovostne opise za glasbene vsebine. Odgovarjaš vedno v slovenskem jeziku."
},
{
role: "user",
content: prompt
}
],
max_tokens: Math.ceil(maxCharacters / 2), // Rough estimate: 2 characters per token
temperature: 0.7,
});
const description = response.choices[0].message.content?.trim() || "";
// Ensure we don't exceed character limit
if (description.length > maxCharacters) {
return description.substring(0, maxCharacters - 3) + "...";
}
return description;
} catch (error) {
console.error("Error generating video description:", error);
throw new Error("Napaka pri generiranju opisa s strani AI");
}
}
export async function generateBulkDescriptions(
videos: Array<{ id: string; title: string }>,
options: DescriptionGenerationOptions = {}
): Promise<Array<{ id: string; description: string; error?: string }>> {
const results = [];
for (const video of videos) {
try {
const description = await generateVideoDescription(video.title, options);
results.push({ id: video.id, description });
// Add small delay to respect API rate limits
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
results.push({
id: video.id,
description: "",
error: error instanceof Error ? error.message : "Unknown error"
});
}
}
return results;
}
+83
View File
@@ -18,6 +18,7 @@ import sharp from "sharp";
import fetch from "node-fetch";
import { setupAuth, isAuthenticated, isAdmin } from "./replitAuth";
import { ObjectStorageService, ObjectNotFoundError } from "./objectStorage";
import { generateVideoDescription, generateBulkDescriptions } from "./aiService";
// Extend express session
declare module "express-session" {
@@ -1080,6 +1081,88 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
// ===== AI DESCRIPTION ROUTES =====
// Generate AI description for single video (admin only)
app.post('/api/admin/videos/:id/generate-description', isAdmin, async (req, res) => {
try {
const videoId = req.params.id;
const { maxCharacters = 500, includeArtistInfo = true, includeLabelInfo = true } = req.body;
// Get video details
const video = await storage.getVideo(videoId);
if (!video) {
return res.status(404).json({ message: "Video not found" });
}
// Generate description using AI
const description = await generateVideoDescription(video.title, {
maxCharacters,
language: "slovenian",
includeArtistInfo,
includeLabelInfo
});
res.json({
description,
title: video.title,
characterCount: description.length,
maxCharacters
});
} catch (error) {
console.error("Error generating AI description:", error);
res.status(500).json({
message: error instanceof Error ? error.message : "Failed to generate description"
});
}
});
// Generate AI descriptions for multiple videos (admin only)
app.post('/api/admin/videos/generate-descriptions-bulk', isAdmin, async (req, res) => {
try {
const { videoIds, maxCharacters = 500, includeArtistInfo = true, includeLabelInfo = true } = req.body;
if (!Array.isArray(videoIds) || videoIds.length === 0) {
return res.status(400).json({ message: "Video IDs array is required" });
}
if (videoIds.length > 50) {
return res.status(400).json({ message: "Maximum 50 videos can be processed at once" });
}
// Get video details for all requested videos
const videos = [];
for (const id of videoIds) {
const video = await storage.getVideo(id);
if (video) {
videos.push({ id: video.id, title: video.title });
}
}
// Generate descriptions using AI
const results = await generateBulkDescriptions(videos, {
maxCharacters,
language: "slovenian",
includeArtistInfo,
includeLabelInfo
});
res.json({
results,
processed: results.length,
successful: results.filter(r => !r.error).length,
failed: results.filter(r => r.error).length
});
} catch (error) {
console.error("Error generating bulk AI descriptions:", error);
res.status(500).json({
message: error instanceof Error ? error.message : "Failed to generate descriptions"
});
}
});
const httpServer = createServer(app);
return httpServer;
}