diff --git a/apps/admin/src/components/steps/StepGenerate.tsx b/apps/admin/src/components/steps/StepGenerate.tsx index 1aba913..c83078c 100644 --- a/apps/admin/src/components/steps/StepGenerate.tsx +++ b/apps/admin/src/components/steps/StepGenerate.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Box, Stack, TextField, Typography, Button, Alert, CircularProgress } from '@mui/material'; +import { Box, Stack, TextField, Typography, Button, Alert, CircularProgress, FormControlLabel, Checkbox, Link } from '@mui/material'; import SelectedImages from './SelectedImages'; import CollapsibleSection from './CollapsibleSection'; import StepHeader from './StepHeader'; @@ -29,6 +29,8 @@ export default function StepGenerate({ }) { const [generating, setGenerating] = useState(false); const [error, setError] = useState(''); + const [useWebSearch, setUseWebSearch] = useState(false); + const [sources, setSources] = useState>([]); return ( - onChangePrompt(e.target.value)} - fullWidth - multiline - minRows={4} - placeholder="Example: Write a comprehensive technical article about building a modern blog platform. Include sections on architecture, key features, and deployment. Target audience: developers with React experience." - /> + + onChangePrompt(e.target.value)} + fullWidth + multiline + minRows={4} + placeholder="Example: Write a comprehensive technical article about building a modern blog platform. Include sections on architecture, key features, and deployment. Target audience: developers with React experience." + /> + setUseWebSearch(e.target.checked)} + /> + } + label={ + + Research with web search (gpt-4o-mini-search) + + AI will search the internet for current information, facts, and statistics + + + } + /> + {/* Generate Button */} @@ -96,10 +116,12 @@ export default function StepGenerate({ prompt: promptText, audioTranscriptions: transcriptions.length > 0 ? transcriptions : undefined, selectedImageUrls: imageUrls.length > 0 ? imageUrls : undefined, + useWebSearch, }); onGeneratedDraft(result.content); onImagePlaceholders(result.imagePlaceholders); + setSources(result.sources || []); } catch (err: any) { setError(err?.message || 'Generation failed'); } finally { @@ -127,6 +149,18 @@ export default function StepGenerate({ {generatedDraft && ( + {sources.length > 0 && ( + + Sources ({sources.length}): + + {sources.map((source, idx) => ( + + {idx + 1}. {source.title} + + ))} + + + )} {imagePlaceholders.length > 0 && ( Image Placeholders Detected: diff --git a/apps/admin/src/services/ai.ts b/apps/admin/src/services/ai.ts index c7ce40a..36f9a1d 100644 --- a/apps/admin/src/services/ai.ts +++ b/apps/admin/src/services/ai.ts @@ -2,6 +2,7 @@ export async function generateDraft(payload: { prompt: string; audioTranscriptions?: string[]; selectedImageUrls?: string[]; + useWebSearch?: boolean; }) { const res = await fetch('/api/ai/generate', { method: 'POST', @@ -14,6 +15,7 @@ export async function generateDraft(payload: { imagePlaceholders: string[]; tokensUsed: number; model: string; + sources?: Array<{ title: string; url: string }>; }>; } diff --git a/apps/api/src/ai-generate.ts b/apps/api/src/ai-generate.ts index 6edbb2d..69d4e2f 100644 --- a/apps/api/src/ai-generate.ts +++ b/apps/api/src/ai-generate.ts @@ -33,10 +33,12 @@ router.post('/generate', async (req, res) => { prompt, audioTranscriptions, selectedImageUrls, + useWebSearch = false, } = req.body as { prompt: string; audioTranscriptions?: string[]; selectedImageUrls?: string[]; + useWebSearch?: boolean; }; if (!prompt) { @@ -87,9 +89,13 @@ router.post('/generate', async (req, res) => { console.log('[AI Generate] Starting generation with OpenAI...'); console.log('[AI Generate] User prompt length:', userPrompt.length); + console.log('[AI Generate] Web search enabled:', useWebSearch); - const completion = await openai.chat.completions.create({ - model: 'gpt-4o', // Using GPT-4 with internet access capability + // Choose model based on web search requirement + const model = useWebSearch ? 'gpt-4o-mini-search-preview-2025-03-11' : 'gpt-4o'; + + const completionParams: any = { + model, messages: [ { role: 'system', @@ -100,9 +106,18 @@ router.post('/generate', async (req, res) => { content: userPrompt, }, ], - temperature: 0.7, max_tokens: 4000, - }); + }; + + // Add web search options if enabled + if (useWebSearch) { + completionParams.web_search_options = {}; + } else { + // Only add temperature for non-search models + completionParams.temperature = 0.7; + } + + const completion = await openai.chat.completions.create(completionParams); const generatedContent = completion.choices[0]?.message?.content || ''; @@ -120,11 +135,27 @@ router.post('/generate', async (req, res) => { imagePlaceholders.push(match[1]); } + // Extract source citations if web search was used + const sources: Array<{ title: string; url: string }> = []; + if (useWebSearch && completion.choices[0]?.message?.annotations) { + const annotations = completion.choices[0].message.annotations as any[]; + for (const annotation of annotations) { + if (annotation.type === 'url_citation' && annotation.url_citation) { + sources.push({ + title: annotation.url_citation.title || 'Source', + url: annotation.url_citation.url, + }); + } + } + console.log('[AI Generate] Found', sources.length, 'source citations'); + } + return res.json({ content: generatedContent, imagePlaceholders, tokensUsed: completion.usage?.total_tokens || 0, model: completion.model, + sources: sources.length > 0 ? sources : undefined, }); } catch (err: any) { console.error('[AI Generate] Error:', err);