videofolxtv/server/aiService.ts
sebastjanartic 15e57c3294 Enhance AI description generation with custom instructions for users
Add a custom instructions input field to the admin video edit dialog and backend to allow users to guide the AI description generation process.

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/LY6xmBI
2025-09-02 13:18:56 +00:00

147 lines
5.3 KiB
TypeScript

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 });
// Memory for generated descriptions to avoid repetition
const generatedDescriptions = new Map<string, Set<string>>();
function getArtistFromTitle(title: string): string {
// Extract artist name from title (everything before the first " - ")
const match = title.match(/^([^-]+)/);
return match ? match[1].trim() : "";
}
function addToMemory(artist: string, description: string) {
if (!generatedDescriptions.has(artist)) {
generatedDescriptions.set(artist, new Set());
}
generatedDescriptions.get(artist)!.add(description);
}
function getPreviousDescriptions(artist: string): string[] {
return Array.from(generatedDescriptions.get(artist) || []);
}
export interface DescriptionGenerationOptions {
maxCharacters?: number;
language?: string;
includeArtistInfo?: boolean;
includeLabelInfo?: boolean;
customInstructions?: string;
}
export async function generateVideoDescription(
title: string,
options: DescriptionGenerationOptions = {}
): Promise<string> {
const {
maxCharacters = 500,
language = "slovenian",
includeArtistInfo = true,
includeLabelInfo = true,
customInstructions = ""
} = options;
try {
// Extract artist name for memory checking
const artist = getArtistFromTitle(title);
const previousDescriptions = getPreviousDescriptions(artist);
const avoidRepetition = previousDescriptions.length > 0
? `\n\nWICHTIG: Für diesen Künstler wurden bereits folgende Beschreibungen erstellt:\n${previousDescriptions.map((desc, i) => `${i+1}. ${desc.substring(0, 100)}...`).join('\n')}\n\nErstelle eine VÖLLIG ANDERE Beschreibung mit anderen Worten, Fokus und Stil.`
: '';
const prompt = `Analysiere diesen Musikvideo-Titel: "${title}"
Schreibe auf DEUTSCH eine informative Beschreibung.
Aus dem Titel extrahieren:
- Name des Interpreten/Künstlers
- Titel des Liedes/Stücks
- Art des Inhalts (Lied, Instrumental, Live-Auftritt, etc.)
Erstelle eine informative Beschreibung, die Folgendes beinhaltet:
${includeArtistInfo ? '- Informationen über den Interpreten (Musikstil, kurze Geschichte, bekannte Stücke)' : ''}
- Beschreibung des Musikstils und Genres
- Kurzer Hintergrund zum Stück, falls bekannt
- Wofür es gedacht ist (Tanz, Zuhören, Konzert, etc.)
NUR wenn du SICHERE Informationen hast, erwähne:
${includeLabelInfo ? '- Label/Plattenfirma (nur wenn du es sicher weißt)' : ''}
Wenn du keine sicheren Informationen zu einem Punkt hast, lasse ihn WEG. Schreibe NIEMALS "Label unbekannt" oder ähnliches.
Die Beschreibung soll maximal ${maxCharacters} Zeichen lang sein.
Schreibe in einem freundlichen, informativen Ton.
Verwende nicht die Wörter "Video" oder "Aufnahme" - schreibe über die Musik selbst.
${customInstructions ? `\nZUSÄTZLICHE ANWEISUNGEN: ${customInstructions}` : ''}
Schreibe nur die Beschreibung, keine zusätzlichen Erklärungen.${avoidRepetition}";`
console.log("Sending request to OpenAI with title:", title); // Debug log
const response = await openai.chat.completions.create({
model: "gpt-4o", // Use gpt-4o instead of gpt-5 for better reliability
messages: [
{
role: "system",
content: "Du bist ein Musikexperte und hilfst bei der Erstellung hochwertiger Beschreibungen für Musikinhalte. Du antwortest immer auf Deutsch und vermeidest Wiederholungen."
},
{
role: "user",
content: prompt
}
],
max_tokens: Math.ceil(maxCharacters / 2), // Use max_tokens for gpt-4o
temperature: 0.7, // gpt-4o supports temperature
});
console.log("OpenAI response received:", response.choices[0]?.message?.content); // Debug log
const description = response.choices[0].message.content?.trim() || "";
// Add to memory for this artist
if (artist && description) {
addToMemory(artist, description);
}
console.log("Generated description:", description); // Debug log
// Ensure we don't exceed character limit
if (description.length > maxCharacters) {
return description.substring(0, maxCharacters - 3) + "...";
}
return description;
} catch (error: any) {
console.error("Error generating video description:", error);
console.error("Error details:", error?.message || error); // More detailed error logging
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;
}