import { useState, useEffect } from 'react'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs'; import Button from '../../components/ui/button/Button'; import TextArea from '../../components/form/input/TextArea'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { BoltIcon, UserIcon, ShootingStarIcon, ImageIcon } from '../../icons'; import { fetchAPI } from '../../services/api'; interface PromptData { prompt_type: string; prompt_type_display: string; prompt_value: string; default_prompt: string; is_active: boolean; } const PROMPT_TYPES = [ { key: 'clustering', label: 'Clustering Prompt', description: 'Group keywords into topic clusters. Use [IGNY8_KEYWORDS] to inject keyword data.', icon: '🌐', color: 'green', }, { key: 'ideas', label: 'Ideas Generation Prompt', description: 'Generate content ideas from clusters. Use [IGNY8_CLUSTERS] and [IGNY8_CLUSTER_KEYWORDS] to inject data.', icon: '💡', color: 'amber', }, { key: 'content_generation', label: 'Content Generation Prompt', description: 'Generate content from ideas. Use [IGNY8_IDEA], [IGNY8_CLUSTER], and [IGNY8_KEYWORDS] to inject data.', icon: '📝', color: 'blue', }, { key: 'image_prompt_extraction', label: 'Image Prompt Extraction', description: 'Extract image prompts from article content. Use {title}, {content}, {max_images} placeholders.', icon: '🔍', color: 'indigo', }, { key: 'image_prompt_template', label: 'Image Prompt Template', description: 'Template for generating image prompts. Use {post_title}, {image_prompt}, {image_type} placeholders.', icon: '🖼️', color: 'purple', }, { key: 'negative_prompt', label: 'Negative Prompt', description: 'Specify elements to avoid in generated images (text, watermarks, logos, etc.).', icon: '🚫', color: 'red', }, { key: 'site_structure_generation', label: 'Site Structure Generation', description: 'Generate site structure from business brief. Use [IGNY8_BUSINESS_BRIEF], [IGNY8_OBJECTIVES], [IGNY8_STYLE], and [IGNY8_SITE_INFO] to inject data.', icon: '🏗️', color: 'teal', }, ]; export default function Prompts() { const toast = useToast(); const [prompts, setPrompts] = useState>({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState>({}); // Load all prompts useEffect(() => { loadPrompts(); }, []); const loadPrompts = async () => { setLoading(true); try { const promises = PROMPT_TYPES.map(async (type) => { try { // fetchAPI extracts data from unified format {success: true, data: {...}} // So response IS the data object const response = await fetchAPI(`/v1/system/prompts/by_type/${type.key}/`); return { key: type.key, data: response }; } catch (error) { console.error(`Error loading prompt ${type.key}:`, error); return { key: type.key, data: null }; } }); const results = await Promise.all(promises); const promptsMap: Record = {}; results.forEach(({ key, data }) => { if (data) { promptsMap[key] = data; } else { // Use default if not found promptsMap[key] = { prompt_type: key, prompt_type_display: PROMPT_TYPES.find(t => t.key === key)?.label || key, prompt_value: '', default_prompt: '', is_active: true, }; } }); setPrompts(promptsMap); } catch (error: any) { console.error('Error loading prompts:', error); toast.error('Failed to load prompts'); } finally { setLoading(false); } }; const handleSave = async (promptType: string) => { const prompt = prompts[promptType]; if (!prompt) return; setSaving({ ...saving, [promptType]: true }); try { // fetchAPI extracts data from unified format {success: true, data: {...}, message: "..."} // But save endpoint returns message in the response, so we need to check if it's still wrapped // For now, assume success if no error is thrown await fetchAPI('/v1/system/prompts/save/', { method: 'POST', body: JSON.stringify({ prompt_type: promptType, prompt_value: prompt.prompt_value, }), }); toast.success('Prompt saved successfully'); await loadPrompts(); // Reload to get updated data } catch (error: any) { console.error('Error saving prompt:', error); toast.error(`Failed to save prompt: ${error.message}`); } finally { setSaving({ ...saving, [promptType]: false }); } }; const handleReset = async (promptType: string) => { if (!confirm('Are you sure you want to reset this prompt to default? This will overwrite any custom changes.')) { return; } setSaving({ ...saving, [promptType]: true }); try { // fetchAPI extracts data from unified format {success: true, data: {...}, message: "..."} // But reset endpoint returns message in the response, so we need to check if it's still wrapped // For now, assume success if no error is thrown await fetchAPI('/v1/system/prompts/reset/', { method: 'POST', body: JSON.stringify({ prompt_type: promptType, }), }); toast.success('Prompt reset to default'); await loadPrompts(); // Reload to get default value } catch (error: any) { console.error('Error resetting prompt:', error); toast.error(`Failed to reset prompt: ${error.message}`); } finally { setSaving({ ...saving, [promptType]: false }); } }; const handlePromptChange = (promptType: string, value: string) => { setPrompts({ ...prompts, [promptType]: { ...prompts[promptType], prompt_value: value, }, }); }; if (loading) { return ( <>

Loading prompts...

); } // Thinker navigation tabs const thinkerTabs = [ { label: 'Prompts', path: '/thinker/prompts', icon: }, { label: 'Author Profiles', path: '/thinker/author-profiles', icon: }, { label: 'Strategies', path: '/thinker/strategies', icon: }, { label: 'Image Testing', path: '/thinker/image-testing', icon: }, ]; return ( <> , color: 'orange' }} navigation={} />
{/* Planner Prompts Section */}

Planner Prompts

Configure AI prompt templates for clustering and idea generation

{/* 2-Column Grid for Planner Prompts */}
{/* Clustering Prompt */} {PROMPT_TYPES.filter(t => ['clustering', 'ideas'].includes(t.key)).map((type) => { const prompt = prompts[type.key] || { prompt_type: type.key, prompt_type_display: type.label, prompt_value: '', default_prompt: '', is_active: true, }; return (
{type.icon}

{type.label}

{type.description}