// PromptsView.jsx — AI Prompts with Premium Presentation Mode const { useState, useEffect, useRef, useMemo } = React; function syntaxHighlight(jsonObj) { let json = typeof jsonObj !== 'string' ? JSON.stringify(jsonObj, null, 2) : jsonObj; if (typeof jsonObj === 'string') { try { const parsed = JSON.parse(jsonObj); json = JSON.stringify(parsed, null, 2); } catch (e) { return json.replace(/&/g, '&').replace(//g, '>'); } } json = json.replace(/&/g, '&').replace(//g, '>'); return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { let cls = 'color: var(--accent-strong);'; if (/^"/.test(match)) { if (/:$/.test(match)) cls = 'color: var(--ink); font-weight: 600;'; else cls = 'color: var(--pos);'; } else if (/true|false/.test(match)) { cls = 'color: #d946ef; font-weight: 600;'; } else if (/null/.test(match)) { cls = 'color: var(--ink-3); font-weight: 600;'; } return '' + match + ''; }); } // SVG Icons const IconFile = (p) => ; const IconRocket = (p) => ; const IconMonitorPlay = (p) => ; const IconChevronLeft = (p) => ; const IconChevronRight = (p) => ; const IconX = (p) => ; const IconClock = (p) => ; const PROMPTS_CONFIG = { '1': { title: '1. เช็กสุขภาพบัญชีด่วน', badge: 'READ', icon: IconActivity, instruction: (d) => `เช็กสุขภาพบัญชีใน 5 นาที (read-only)\n\nดึงข้อมูล campaign ในช่วง ${d.timeframe?.start || ''} ถึง ${d.timeframe?.end || ''}\nสรุป 3 อย่าง: 1. ทำงานดี 2. ไม่คุ้ม 3. data น้อยไป`, inputs: [{ type: 'daterange', id: 'timeframe', label: 'เลือกช่วงเวลา (Timeframe)' }] }, '2': { title: '2. หาเงินรั่ว', badge: 'READ', icon: IconCoins, instruction: (d) => `หาเงินรั่วแบบอ่านง่าย (read-only)\n\nหา campaign ที่ใช้เงินสูงแต่ผลลัพธ์อ่อนในช่วง ${d.timeframe?.start || ''} ถึง ${d.timeframe?.end || ''}\nแสดงผลเป็นตาราง และสรุปปัญหา`, inputs: [{ type: 'daterange', id: 'timeframe', label: 'เลือกช่วงเวลา (Timeframe)' }] }, '3': { title: '3. เช็ก Creative Fatigue', badge: 'READ', icon: IconImage, instruction: (d) => `เช็ก creative fatigue (read-only)\nช่วง ${d.timeframe?.start || ''} ถึง ${d.timeframe?.end || ''}\nflag ad ที่ frequency เพิ่ม, CTR ลด, CPA แย่ลง`, inputs: [{ type: 'daterange', id: 'timeframe', label: 'เลือกช่วงเวลาหลัก' }] }, '4': { title: '4. สรุปให้เจ้าของอ่าน', badge: 'READ', icon: IconFile, instruction: (d) => `สรุปให้เจ้าของธุรกิจอ่าน (read-only)\n\nช่วง ${d.timeframe?.start || ''} ถึง ${d.timeframe?.end || ''}\nสรุป 4 ส่วน: อะไรเวิร์ก อะไรไม่คุ้ม อะไรยังพิจารณาไม่ได้ อะไรห้ามแตะ`, inputs: [{ type: 'daterange', id: 'timeframe', label: 'เลือกช่วงเวลาที่ต้องการสรุป' }] }, '5': { title: '5. สร้าง Draft Campaign', badge: 'DRAFT', icon: IconPen, instruction: (d) => `สร้าง campaign draft แบบ PAUSED\nสินค้า: ${d.product_name}\nLanding: ${d.landing_page}\nงบ: ${d.daily_budget}\nพื้นที่: ${d.location}\nอายุ: ${d.age}\nข้อความ: ${d.primary_text}\nหัวข้อ: ${d.headline}`, inputs: [ { type: 'text', id: 'product_name', label: 'สินค้า/บริการ', placeholder: 'เช่น คอร์สเรียนออนไลน์' }, { type: 'text', id: 'landing_page', label: 'Landing Page URL', placeholder: 'https://...' }, { type: 'number', id: 'daily_budget', label: 'งบต่อวัน (บาท)', placeholder: '500' }, { type: 'text', id: 'location', label: 'พื้นที่ยิงโฆษณา', placeholder: 'ทั่วประเทศไทย' }, { type: 'text', id: 'age', label: 'ช่วงอายุ', placeholder: '25-45' }, { type: 'textarea', id: 'primary_text', label: 'ข้อความหลัก', placeholder: 'เขียนแคปชันโฆษณา...' }, { type: 'text', id: 'headline', label: 'หัวข้อโฆษณา', placeholder: 'โปรโมชันพิเศษ...' } ] }, '6': { title: '6. อนุมัติเปิดยิง', badge: 'ACTION', icon: IconRocket, instruction: (d) => `อนุมัติเปิดยิง (status change)\nCampaign ID = ${d.campaign_id}\nตรวจ 5 ข้อก่อนเปลี่ยน status`, inputs: [{ type: 'text', id: 'campaign_id', label: 'Campaign ID', placeholder: 'ระบุ ID แคมเปญ' }] }, '7': { title: '7. ตรวจหลังยิงวันแรก', badge: 'READ', icon: IconSearch, instruction: (d) => `ตรวจหลังเปิดยิงวันแรก (read-only)\nCampaign ID = ${d.campaign_id}\nสรุปผลลัพธ์ว่าปกติไหม ควรหยุดหรือไปต่อ`, inputs: [{ type: 'text', id: 'campaign_id', label: 'Campaign ID', placeholder: 'ระบุ ID แคมเปญที่เพิ่งเปิดยิง' }] }, '8': { title: '8. สรุปยอดขายจาก Ads', badge: 'READ', icon: IconTrend, instruction: (d) => `สรุปยอดขายจาก Ads ช่วง ${d.timeframe?.start || ''} ถึง ${d.timeframe?.end || ''}\nแยกตารางตามหมวดหมู่ แสดง Budget, Spend, ROAS, CPA, ยอดขาย เรียงจากตัวคุ้มสุดขึ้นก่อน`, inputs: [{ type: 'daterange', id: 'timeframe', label: 'ช่วงเวลาที่ต้องการดู (Date Range)' }] }, 'logs': { title: 'ประวัติการทำงาน (Logs)', badge: 'SYSTEM', icon: IconClock, instruction: null, inputs: [{ type: 'select', id: 'log_limit', label: 'จำนวนรายการ', options: ['10', '20', '50', '100'] }] } }; // ═══════════════════════════════════════════════════════════════ // Smart AI Text Parser for Slides // ═══════════════════════════════════════════════════════════════ function parseAItoSlides(raw, title, dateRange) { if (!raw) return []; let text = typeof raw !== 'string' ? JSON.stringify(raw, null, 2) : raw; try { const parsed = JSON.parse(text); if (typeof parsed === 'string') text = parsed; else if (parsed.output) text = parsed.output; } catch(e) {} // Clean code blocks text = text.replace(/```[a-z]*/gi, '').replace(/```/g, ''); // Detect if text has markdown tables const hasTable = text.includes('|') && text.split('\n').some(l => l.trim().startsWith('|')); // Parse tables into structured data const parseTable = (lines) => { const rows = []; lines.forEach(l => { const trimmed = l.trim(); if (!trimmed.startsWith('|') || trimmed.match(/^\|[-:\s|]+\|$/)) return; // skip separator const cells = trimmed.split('|').filter(c => c.trim()).map(c => c.trim().replace(/\*\*/g, '')); if (cells.length > 0) rows.push(cells); }); return rows.length > 1 ? { headers: rows[0], data: rows.slice(1) } : null; }; // Detect sentiment/status of a bullet const getStatus = (text) => { const lower = text.toLowerCase(); if (lower.match(/(good|positive|increase|growth|strong|high|success|profit|improve|effective|recommend|\u0e14\u0e35|\u0e40\u0e27\u0e34\u0e23\u0e4c\u0e01|\u0e01\u0e33\u0e44\u0e23|\u0e40\u0e1e\u0e34\u0e48\u0e21|\u0e2a\u0e39\u0e07|\u0e41\u0e19\u0e30\u0e19\u0e33|\u0e04\u0e38\u0e49\u0e21)/)) return 'good'; if (lower.match(/(bad|negative|decrease|decline|weak|low|fail|loss|poor|expensive|problem|issue|\u0e44\u0e21\u0e48\u0e04\u0e38\u0e49\u0e21|\u0e41\u0e1e\u0e07|\u0e25\u0e14|\u0e2b\u0e22\u0e38\u0e14|\u0e1b\u0e34\u0e14|\u0e2d\u0e31\u0e19\u0e15\u0e23\u0e32\u0e22|\u0e40\u0e07\u0e34\u0e19\u0e23\u0e31\u0e48\u0e27)/)) return 'bad'; if (lower.match(/(warning|caution|monitor|watch|uncertain|risk|\u0e23\u0e30\u0e27\u0e31\u0e07|\u0e15\u0e34\u0e14\u0e15\u0e32\u0e21|\u0e2a\u0e31\u0e07\u0e40\u0e01\u0e15|\u0e1e\u0e34\u0e08\u0e32\u0e23\u0e13\u0e32)/)) return 'warn'; return 'info'; }; const statusIcons = { good: '\u2705', bad: '\u274c', warn: '\u26a0\ufe0f', info: '\u2139\ufe0f' }; const statusColors = { good: '#34d399', bad: '#f87171', warn: '#fbbf24', info: '#60a5fa' }; // Parse into sections const sections = []; let current = { title: '', items: [], table: null }; let tableLines = []; let inTable = false; text.split('\n').forEach(line => { const trimmed = line.trim(); if (!trimmed) { if (inTable && tableLines.length > 0) { current.table = parseTable(tableLines); tableLines = []; inTable = false; } return; } // Table line if (trimmed.startsWith('|')) { inTable = true; tableLines.push(trimmed); return; } else if (inTable) { current.table = parseTable(tableLines); tableLines = []; inTable = false; } const heading = trimmed.match(/^#{1,3}\s+(.+)/); const numbered = trimmed.match(/^(\d+)\.\s+\*?\*?(.+?)\*?\*?\s*$/); if (heading || numbered) { if (current.title || current.items.length > 0 || current.table) sections.push({ ...current }); current = { title: (heading ? heading[1] : numbered[2]).replace(/\*\*/g, ''), items: [], table: null }; } else { let clean = trimmed.replace(/^[-\u2022*]\s*/, ''); // Parse bold text: **text** -> keep but mark let label = ''; let detail = clean; const boldMatch = clean.match(/^\*\*(.+?)\*\*[:\s]*(.*)$/); if (boldMatch) { label = boldMatch[1]; detail = boldMatch[2] || ''; } else { detail = clean.replace(/\*\*/g, ''); } // Extract numbers/currency const numMatch = detail.match(/([\d,]+\.?\d*)\s*(THB|USD|\u0e1a\u0e32\u0e17|%|x)?/i); if (clean) { current.items.push({ label: label, detail: detail, status: getStatus(clean), metric: numMatch ? numMatch[0] : null }); } } }); if (inTable) current.table = parseTable(tableLines); if (current.title || current.items.length > 0 || current.table) sections.push(current); // Fallback if (sections.length === 0) { const lines = text.split('\n').filter(l => l.trim()); for (let i = 0; i < lines.length; i += 3) { sections.push({ title: '', items: lines.slice(i, i + 3).map(l => ({ label: '', detail: l.replace(/^[-\u2022*]\s*/, '').replace(/\*\*/g, ''), status: 'info', metric: null })), table: null }); } } const bgs = [ 'linear-gradient(135deg, #0f172a 0%, #1e3a5f 100%)', 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)', 'linear-gradient(135deg, #0d1117 0%, #161b22 50%, #21262d 100%)', 'linear-gradient(135deg, #1b2838 0%, #2a475e 100%)', 'linear-gradient(135deg, #0c0e1a 0%, #1a1f3a 50%, #2d1b69 100%)', 'linear-gradient(135deg, #0a192f 0%, #112240 100%)', ]; const slides = [{ type: 'title', title, dateRange, bg: bgs[0] }]; sections.forEach((sec, idx) => { // Table slide if (sec.table) { slides.push({ type: 'table', title: sec.title, table: sec.table, bg: bgs[(idx + 1) % bgs.length] }); } // Content slides (max 4 per slide for readability) const max = 4; if (sec.items.length > 0) { if (sec.items.length <= max) { slides.push({ type: 'content', title: sec.title, items: sec.items, bg: bgs[(idx + 1) % bgs.length] }); } else { for (let i = 0; i < sec.items.length; i += max) { slides.push({ type: 'content', title: sec.title + (i > 0 ? ' (\u0e15\u0e48\u0e2d)' : ''), items: sec.items.slice(i, i + max), bg: bgs[(idx + 1 + i) % bgs.length] }); } } } }); slides.push({ type: 'end', bg: bgs[0] }); return slides; } // ═══════════════════════════════════════════════════════════════ // Main Component // ═══════════════════════════════════════════════════════════════ function PromptsView({ onNavigate }) { const [activeTab, setActiveTab] = useState('1'); const [formData, setFormData] = useState({}); const [loading, setLoading] = useState(false); const [results, setResults] = useState({}); const [errorMsg, setErrorMsg] = useState(''); const [slideMode, setSlideMode] = useState(false); const [currentSlide, setCurrentSlide] = useState(0); const config = PROMPTS_CONFIG[activeTab]; const n8nUrl = typeof getN8nSettings === 'function' ? getN8nSettings().hookPrompts : localStorage.getItem('n8nWebhookUrl'); const hasWebhook = Boolean(n8nUrl); useEffect(() => { const defaultData = {}; if (config?.inputs) { config.inputs.forEach(inp => { if (inp.type === 'daterange') defaultData[inp.id] = { start:'', end:'' }; else if (inp.type === 'select') defaultData[inp.id] = inp.options[0]; else defaultData[inp.id] = ''; }); } setFormData(defaultData); setErrorMsg(''); }, [activeTab]); const handleInputChange = (id, value, isStart) => { setFormData(prev => { const updated = { ...prev }; if (isStart !== undefined) { updated[id] = { ...updated[id], [isStart ? 'start' : 'end']: value }; } else { updated[id] = value; } return updated; }); }; const handleExportPdf = () => window.print(); const presRef = useRef(null); const handleViewSlides = () => { setCurrentSlide(0); setSlideMode(true); // Request real browser fullscreen after a tick (element needs to mount first) setTimeout(() => { const el = presRef.current || document.documentElement; if (el.requestFullscreen) el.requestFullscreen().catch(() => {}); else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen(); }, 50); }; const closeSlides = () => { setSlideMode(false); if (document.fullscreenElement || document.webkitFullscreenElement) { if (document.exitFullscreen) document.exitFullscreen().catch(() => {}); else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); } }; // Sync state when user exits fullscreen via browser ESC useEffect(() => { const onFsChange = () => { if (!document.fullscreenElement && !document.webkitFullscreenElement) { setSlideMode(false); } }; document.addEventListener('fullscreenchange', onFsChange); document.addEventListener('webkitfullscreenchange', onFsChange); return () => { document.removeEventListener('fullscreenchange', onFsChange); document.removeEventListener('webkitfullscreenchange', onFsChange); }; }, []); const handleClear = () => setResults(prev => ({...prev, [activeTab]: null})); const handleSubmit = async (e) => { e.preventDefault(); if (!hasWebhook) { setErrorMsg('ยังไม่ได้ตั้งค่า Webhook (ไปที่หน้า Settings)'); return; } setLoading(true); setErrorMsg(''); try { if (activeTab === 'logs') { const limit = formData.log_limit || 50; const res = await fetch(`api.php?limit=${limit}`); const data = await res.json(); setResults(prev => ({...prev, [activeTab]: (data.status === 'success' && data.data) ? data.data : []})); } else { const payload = { promptId: activeTab, instruction: typeof config.instruction === 'function' ? config.instruction(formData) : config.instruction, formData, timestamp: new Date().toISOString() }; const response = await fetch('api.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'proxy', webhookUrl: n8nUrl, payload }) }); const data = await response.json(); let output = data; if (Array.isArray(data) && data.length > 0 && data[0].output) output = data[0].output; else if (data.output) output = data.output; setResults(prev => ({...prev, [activeTab]: output})); } } catch (err) { setErrorMsg(err.message); } setLoading(false); }; // Slides data const slides = slideMode ? parseAItoSlides(results[activeTab], config.title, formData.timeframe ? formData.timeframe.start + ' \u2014 ' + formData.timeframe.end : null) : []; const accentColors = ['#60a5fa', '#34d399', '#f472b6', '#a78bfa', '#fbbf24', '#fb923c']; // Keyboard nav for slides useEffect(() => { if (!slideMode) return; const handleKey = (e) => { if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); setCurrentSlide(s => Math.min(s + 1, slides.length - 1)); } if (e.key === 'ArrowLeft') { e.preventDefault(); setCurrentSlide(s => Math.max(s - 1, 0)); } if (e.key === 'Escape') closeSlides(); }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [slideMode, slides.length]); return (
{/* Header */}
{/* Navigation Sidebar */}
PROMPT COMMANDS
{Object.keys(PROMPTS_CONFIG).map(key => { const p = PROMPTS_CONFIG[key]; const active = activeTab === key; const badgeColors = { 'READ': 'var(--info)', 'DRAFT': 'var(--accent-strong)', 'ACTION': 'var(--neg)', 'SYSTEM': 'var(--ink-2)' }; return ( ); })}
{/* Main Panel */}
{/* Configuration Form */}

Configuration

{!hasWebhook && (
{"\u0e22\u0e31\u0e07\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 Webhook"}
{"\u0e23\u0e30\u0e1a\u0e1a\u0e44\u0e21\u0e48\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e2a\u0e48\u0e07\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e44\u0e14\u0e49 \u0e42\u0e1b\u0e23\u0e14\u0e44\u0e1b\u0e17\u0e35\u0e48\u0e2b\u0e19\u0e49\u0e32 Settings \u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e43\u0e2a\u0e48 URL"}
)} {config.inputs?.map(inp => (
{inp.type === 'daterange' ? (
handleInputChange(inp.id, e.target.value, true)} style={{ flex:1, padding:'10px 14px', borderRadius:8, border:'1px solid var(--border-strong)', fontSize:13, background:'var(--surface)' }} /> {"\u0e16\u0e36\u0e07"} handleInputChange(inp.id, e.target.value, false)} style={{ flex:1, padding:'10px 14px', borderRadius:8, border:'1px solid var(--border-strong)', fontSize:13, background:'var(--surface)' }} />
) : inp.type === 'select' ? ( ) : inp.type === 'textarea' ? (