diff --git a/apps/api/src/services/ai/altTextGenerator.ts b/apps/api/src/services/ai/altTextGenerator.ts index 35e0b39..9a34a6d 100644 --- a/apps/api/src/services/ai/altTextGenerator.ts +++ b/apps/api/src/services/ai/altTextGenerator.ts @@ -36,8 +36,12 @@ export class AltTextGenerator { ? ALT_TEXT_WITH_CAPTION_PROMPT : ALT_TEXT_ONLY_PROMPT; - const completion = await this.openai.chat.completions.create({ - model: 'gpt-5-2025-08-07', + console.log('[AltTextGenerator] Context length:', context.length); + console.log('[AltTextGenerator] Include caption:', includeCaption); + console.log('[AltTextGenerator] Calling OpenAI with model: gpt-4o'); + + const completionParams: any = { + model: 'gpt-4o', messages: [ { role: 'system', @@ -48,13 +52,24 @@ export class AltTextGenerator { content: context, }, ], - max_completion_tokens: includeCaption ? 200 : 100, - }); + temperature: 0.7, + max_tokens: includeCaption ? 200 : 100, + }; + + if (includeCaption) { + completionParams.response_format = { type: 'json_object' }; + } + + const completion = await this.openai.chat.completions.create(completionParams); + + console.log('[AltTextGenerator] Response:', completion.choices[0]?.message?.content); const response = completion.choices[0]?.message?.content?.trim() || ''; if (!response) { - throw new Error('No content generated'); + console.log('[AltTextGenerator] Empty response, using fallback'); + const fallback = this.buildFallback(params.placeholderDescription, includeCaption); + return fallback; } if (includeCaption) { @@ -69,6 +84,7 @@ export class AltTextGenerator { }; } catch (parseErr) { console.error('[AltTextGenerator] JSON parse error:', parseErr); + console.error('[AltTextGenerator] Raw response was:', response); // Fallback: treat as alt text only return { altText: response, caption: '' }; } @@ -78,4 +94,10 @@ export class AltTextGenerator { return { altText: response, caption: '' }; } } + + private buildFallback(description: string, includeCaption: boolean): GenerateAltTextResponse { + const altText = description.replace(/_/g, ' ').replace(/-/g, ' ').trim(); + const caption = includeCaption ? `Image showing ${altText}` : ''; + return { altText, caption }; + } } diff --git a/apps/api/src/services/ai/metadataGenerator.ts b/apps/api/src/services/ai/metadataGenerator.ts index c40d320..7e6d010 100644 --- a/apps/api/src/services/ai/metadataGenerator.ts +++ b/apps/api/src/services/ai/metadataGenerator.ts @@ -15,9 +15,13 @@ export class MetadataGenerator { // Strip HTML and get preview const textContent = stripHtmlTags(params.contentHtml); const preview = textContent.slice(0, 2000); + + console.log('[MetadataGenerator] Input preview length:', preview.length); + console.log('[MetadataGenerator] Input preview:', preview.slice(0, 200) + '...'); + console.log('[MetadataGenerator] Calling OpenAI with model: gpt-4o'); const completion = await this.openai.chat.completions.create({ - model: 'gpt-5-2025-08-07', + model: 'gpt-4o', messages: [ { role: 'system', @@ -28,13 +32,27 @@ export class MetadataGenerator { content: `Generate metadata for this article:\n\n${preview}`, }, ], - max_completion_tokens: 300, + temperature: 0.7, + max_tokens: 300, + response_format: { type: 'json_object' as any }, }); + console.log('[MetadataGenerator] OpenAI completion object:', JSON.stringify(completion, null, 2)); + console.log('[MetadataGenerator] Choices:', completion.choices); + console.log('[MetadataGenerator] First choice:', completion.choices[0]); + console.log('[MetadataGenerator] Message:', completion.choices[0]?.message); + console.log('[MetadataGenerator] Content:', completion.choices[0]?.message?.content); + const response = completion.choices[0]?.message?.content || ''; + console.log('[MetadataGenerator] Response length:', response.length); + console.log('[MetadataGenerator] Response is empty:', !response); + if (!response) { - throw new Error('No metadata generated'); + console.log('[MetadataGenerator] Empty response from OpenAI, using fallback'); + const fallback = this.buildFallbackMetadata(textContent); + console.log('[MetadataGenerator] Fallback metadata:', fallback); + return fallback; } console.log('[MetadataGenerator] Raw response:', response); @@ -45,13 +63,49 @@ export class MetadataGenerator { console.log('[MetadataGenerator] Generated:', metadata); return { - title: metadata.title || '', - tags: metadata.tags || '', - canonicalUrl: metadata.canonicalUrl || '', + title: metadata.title || this.buildFallbackMetadata(textContent).title, + tags: metadata.tags || this.buildFallbackMetadata(textContent).tags, + canonicalUrl: metadata.canonicalUrl || this.buildFallbackMetadata(textContent).canonicalUrl, }; } catch (parseErr) { console.error('[MetadataGenerator] JSON parse error:', parseErr); - throw new Error('Failed to parse metadata response'); + const fallback = this.buildFallbackMetadata(textContent); + return fallback; } } + + private buildFallbackMetadata(text: string): GenerateMetadataResponse { + const title = this.deriveTitle(text); + const tags = this.deriveTags(text).join(', '); + const canonicalUrl = this.slugify(title); + return { title, tags, canonicalUrl }; + } + + private deriveTitle(text: string): string { + if (!text) return ''; + const sentence = text.split(/[.!?]/)[0] || text; + const words = sentence.trim().split(/\s+/).slice(0, 12).join(' '); + return words.length > 60 ? words.slice(0, 60).trim() : words; + } + + private deriveTags(text: string): string[] { + const stop = new Set(['the','and','or','for','with','your','from','this','that','are','you','our','into','into','about','into','on','in','to','of','a','an','it','is','as','by','at','be','can','will','should','how','what','why']); + const counts: Record = {}; + text.toLowerCase().replace(/[^a-z0-9\s-]/g, ' ').split(/\s+/).forEach(w => { + if (!w || stop.has(w) || w.length < 3) return; + counts[w] = (counts[w] || 0) + 1; + }); + const top = Object.entries(counts).sort((a,b)=>b[1]-a[1]).slice(0,5).map(([w])=>w); + return top; + } + + private slugify(title: string): string { + return (title || '') + .toLowerCase() + .replace(/[^a-z0-9\s-]/g, '') + .trim() + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .slice(0, 80); + } }