Site Builder
Editing:
tools-writing-blogsg.php
writable 0666
<?php /***************************************************************** * RF Safe – Single‑Prompt Article Generator (v1.9, toolbox edition) * ---------------------------------------------------------------- * • Shares the same encrypted cookie (‘openai_key’) with all AI tools * • Compact header toolbar; fully mobile‑friendly with modern design *****************************************************************/ /* ---------- 1. Constants & encryption helpers ----------------- */ const COOKIE_NAME = 'openai_key'; const COOKIE_TTL = 30 * 24 * 3600; // 30 days const SECRET = __FILE__; // file-specific pepper /* derive “root” for cross-sub-domain cookie */ $host = $_SERVER['HTTP_HOST'] ?? ''; $cookieDomain = (preg_match('/([a-z0-9-]+\.[a-z]{2,})$/i', $host, $m) && !filter_var($host, FILTER_VALIDATE_IP)) ? '.'.$m[1] : ''; // ‘’ when localhost / raw IP function enc(string $plain): string { $m = 'aes-128-ctr'; $k = substr(hash('sha256', SECRET, true), 0, 16); $iv = random_bytes(openssl_cipher_iv_length($m)); return base64_encode(openssl_encrypt($plain, $m, $k, 0, $iv).'::'.$iv); } function dec(string $cipher): string { [$ct,$iv] = explode('::', base64_decode($cipher), 2) + [null,null]; if(!$ct||!$iv) return ''; $m='aes-128-ctr'; $k=substr(hash('sha256', SECRET, true), 0, 16); return openssl_decrypt($ct,$m,$k,0,$iv) ?: ''; } /* ---------- 2. Save / delete API key -------------------------- */ $msg = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_key'])) { $raw = trim($_POST['api_key']); $ctx = stream_context_create(['http' => [ 'method' => 'GET', 'header' => "Authorization: Bearer $raw\r\n", 'timeout' => 8 ]]); if (@file_get_contents('https://api.openai.com/v1/models', false, $ctx)) { setcookie(COOKIE_NAME, enc($raw), time() + COOKIE_TTL, '/', $cookieDomain, isset($_SERVER['HTTPS']), true); echo json_encode(['success' => true]); exit; } $msg = '❌ Invalid key'; } if (isset($_GET['logout'])) { setcookie(COOKIE_NAME, '', time() - 3600, '/', $cookieDomain, isset($_SERVER['HTTPS']), true); echo json_encode(['logout' => true]); exit; } /* ---------- 3. Sticky form defaults --------------------------- */ $topic = $keywords = $linkurl = ''; $sections = $paras = 3; $lang = 'English'; $style = 'Creative'; $tone = 'Neutral'; $points = []; $models = ['gpt-3.5-turbo', 'gpt-4o-mini', 'gpt-4']; $model = in_array($_REQUEST['model'] ?? '', $models, true) ? $_REQUEST['model'] : 'gpt-4o-mini'; $html = ''; /* ---------- 4. Generate (AJAX-friendly) ------------------------ */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['generate'])) { /* grab inputs */ $topic = trim($_POST['topic'] ?? ''); $keywords = trim($_POST['keywords'] ?? ''); $linkurl = trim($_POST['linkurl'] ?? ''); $sections = max(1, intval($_POST['sections'] ?? 3)); $paras = max(1, intval($_POST['paras'] ?? 3)); $lang = $_POST['lang'] ?? 'English'; $style = $_POST['style'] ?? 'Creative'; $tone = $_POST['tone'] ?? 'Neutral'; $model = in_array($_POST['model'] ?? '', $models, true) ? $_POST['model'] : $model; $points = array_filter(array_map('trim', $_POST['points'] ?? [])); $key = isset($_COOKIE[COOKIE_NAME]) ? dec($_COOKIE[COOKIE_NAME]) : ''; if (!$key) { echo json_encode(['error' => '❌ Missing API key.']); } elseif ($topic === '') { echo json_encode(['error' => '❌ Topic required.']); } else { /* build prompt */ $kwText = $keywords ? " Include keywords: $keywords." : ''; $linkText = ($keywords && $linkurl) ? " Hyperlink each keyword to $linkurl." : ''; $pointsText = $points ? ' Cover: '.implode('; ', $points).'.' : ''; $prompt = "Write a $lang $style article in a $tone tone about «$topic». ". "Use $sections sections (each <h2>) and $paras paragraph".($paras > 1 ? 's' : '').' per section. '. "Use <h3> where helpful.".$kwText.$linkText.$pointsText. ' Append <p class="excerpt">Excerpt: …</p>. Output ONLY HTML.'; /* OpenAI call */ $payload = ['model' => $model, 'messages' => [['role' => 'user', 'content' => $prompt]], 'max_tokens' => max(800, $sections * $paras * 200), 'temperature' => 0.7]; $ch = curl_init('https://api.openai.com/v1/chat/completions'); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => 1, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'], CURLOPT_POST => 1, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_TIMEOUT => 50]); $resp = curl_exec($ch); curl_close($ch); $result = json_decode($resp, true); $html = $result['choices'][0]['message']['content'] ?? '❌ No content.'; echo json_encode(['html' => $html]); } exit; } /* ---------- 5. Option lists (unchanged) ----------------------- */ $langs = ['English', 'Spanish', 'German', 'French', 'Italian', 'Portuguese', 'Dutch']; $styles = ['Creative', 'Informative', 'Narrative', 'Persuasive', 'Analytical', 'Journalistic']; $tones = ['Neutral', 'Cheerful', 'Humorous', 'Assertive', 'Inspirational', 'Professional', 'Emotional']; /* ---------- 6. Page ------------------------------------------------------ */ ?> <!doctype html><html lang="en"><head> <meta charset="utf-8"><title>Article Generator • AI Toolbox</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"> <style> :root { --primary: #1e3a8a; --secondary: #9333ea; --accent: #10b981; --bg: #f3f4f6; } body { background: var(--bg); margin: 0; font-family: 'Inter', -apple-system, sans-serif; } header { background: white; padding: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 10; } header nav a { color: var(--primary); text-decoration: none; margin-right: 1rem; } header nav a:hover { text-decoration: underline; } .container { max-width: 1000px; margin: 2rem auto; padding: 0 1rem; } .toolbar { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .toolbar input, .toolbar select { padding: 0.75rem; border: 1px solid #e5e7eb; border-radius: 6px; width: 100%; max-width: 300px; } .badge { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; } .badge.green { color: var(--accent); } .badge.red { color: #dc2626; } button.primary { background: var(--primary); color: white; border: none; border-radius: 6px; padding: 0.75rem 1.5rem; font-weight: 500; cursor: pointer; transition: background 0.2s; } button.primary:hover { background: #1e40af; } .form-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } #generateBtn { grid-column: 1 / -1; } .spinner { display: none; width: 1.5rem; height: 1.5rem; border: 3px solid #f3f3f3; border-top: 3px solid var(--primary); border-radius: 50%; animation: spin 1s linear infinite; margin-left: 1rem; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .thinking-clock { display: none; color: var(--primary); font-size: 0.9rem; margin-left: 0.5rem; } .preview { background: white; border: 1px solid #e5e7eb; border-radius: 8px; padding: 1rem; max-height: 50vh; overflow: auto; margin-top: 1rem; } @media (max-width: 768px) { .toolbar { flex-direction: column; align-items: stretch; } .toolbar input, .toolbar select { width: 100%; } .form-grid { grid-template-columns: 1fr; } } </style></head><body> <header> <nav> <a href="/">BestDealOn</a> » <a href="/members/dashboard.php">Dashboard</a> » <a href="/ai-tools/tools.php">AI Toolbox</a> » Article Generator </nav> <a href="/<?=$_SESSION['slug']??''?>" style="text-decoration:none; color: var(--primary);">View Site</a> </header> <div class="container"> <?php $keySaved = isset($_COOKIE[COOKIE_NAME]) && dec($_COOKIE[COOKIE_NAME]); ?> <div class="toolbar"> <?php if (!$keySaved): ?> <form id="saveKeyForm" style="display: flex; gap: 1rem; flex-wrap: wrap; width: 100%;"> <input type="password" name="api_key" placeholder="Enter OpenAI key…" required> <button type="submit" class="primary" name="save_key">Save Key</button> <?= $msg ? "<span class='badge red'>$msg</span>" : '' ?> </form> <?php else: ?> <span class="badge green">● API key saved <button title="Delete key" onclick="logout()">❌</button></span> <form id="modelForm"> <select name="model" onchange="this.form.submit()"> <?php foreach ($models as $m): ?><option value="<?= $m ?>" <?= $m === $model ? 'selected' : '' ?>><?= $m ?></option><?php endforeach; ?> </select> </form> <?php endif; ?> </div> <?php if ($keySaved): ?> <form id="mainForm" class="form-grid"> <label>Topic <div style="position: relative;"> <input name="topic" id="topic" placeholder="e.g. Electric bicycles" required> <button type="button" id="micBtn" aria-label="Speak" style="position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%); border: none; background: none; font-size: 1.2rem; cursor: pointer;">🎤</button> </div> </label> <label>Keywords (comma-separated) <input name="keywords" id="keywords"> </label> <label>Link URL (optional) <input type="url" name="linkurl" id="linkurl"> </label> <label># Sections <select name="sections" id="sections"><?php for ($i = 1; $i <= 8; $i++): ?><option <?= $sections == $i ? 'selected' : '' ?>><?= $i ?></option><?php endfor; ?></select> </label> <label># Paragraphs/Section <select name="paras" id="paras"><?php for ($i = 1; $i <= 6; $i++): ?><option <?= $paras == $i ? 'selected' : '' ?>><?= $i ?></option><?php endfor; ?></select> </label> <label>Language <select name="lang" id="lang"><?php foreach ($langs as $l): ?><option <?= $lang === $l ? 'selected' : '' ?>><?= $l ?></option><?php endforeach; ?></select> </label> <label>Style <select name="style" id="style"><?php foreach ($styles as $s): ?><option <?= $style === $s ? 'selected' : '' ?>><?= $s ?></option><?php endforeach; ?></select> </label> <label>Tone <select name="tone" id="tone"><?php foreach ($tones as $t): ?><option <?= $tone === $t ? 'selected' : '' ?>><?= $t ?></option><?php endforeach; ?></select> </label> <button type="submit" id="generateBtn" class="primary">Generate Article <span class="spinner"></span> <span class="thinking-clock">Thinking: 0s</span></button> </form> <div id="previewContainer" class="preview" style="display: none;"> <h3>Rendered Preview</h3> <div id="renderPreview"></div> <button type="button" class="primary copyBtn" id="copyRendered">Copy Rendered HTML</button> </div> <?php else: ?> <article class="preview"><h2>Get Started</h2><p>Save your API key above to unlock the generator.</p></article> <?php endif; ?> </div> <script> (() => { const form = document.getElementById('mainForm'); const generateBtn = document.getElementById('generateBtn'); const spinner = generateBtn.querySelector('.spinner'); const clock = generateBtn.querySelector('.thinking-clock'); const previewContainer = document.getElementById('previewContainer'); const renderPreview = document.getElementById('renderPreview'); let timer = null; /* ---- Speech mic ---- */ const micBtn = document.getElementById('micBtn'); if (micBtn) { const rec = window.SpeechRecognition || window.webkitSpeechRecognition; if (!rec) micBtn.style.display = 'none'; else { const recog = new rec(); recog.lang = 'en-US'; recog.maxAlternatives = 1; recog.onresult = e => { document.getElementById('topic').value = e.results[0][0].transcript; buildPrompt(); }; micBtn.onclick = () => { try { recog.start(); } catch {} }; } } /* ---- Build prompt live ---- */ const ids = ['topic', 'keywords', 'linkurl', 'sections', 'paras', 'lang', 'style', 'tone']; ids.forEach(id => document.getElementById(id)?.addEventListener('input', buildPrompt)); function buildPrompt() { const g = id => document.getElementById(id).value.trim(); const topic = g('topic'); if (!topic) return; const kw = g('keywords'), link = g('linkurl'); const prompt = `Write a ${g('lang')} ${g('style')} article in a ${g('tone')} tone about «${topic}». ` + `Use ${g('sections')} sections (each <h2>) & ${g('paras')} paragraph${g('paras') > 1 ? 's' : ''} per section. ` + `${kw ? `Include keywords: ${kw}. ` : ''}${kw && link ? `Hyperlink each keyword to ${link}. ` : ''}` + `Append <p class="excerpt">Excerpt: …</p>. Return ONLY HTML.`; console.log(prompt); // For debugging } /* ---- Handle form submission with AJAX ---- */ form.addEventListener('submit', async (e) => { e.preventDefault(); spinner.style.display = 'inline-block'; clock.style.display = 'inline-block'; let seconds = 0; timer = setInterval(() => { clock.textContent = `Thinking: ${++seconds}s`; }, 1000); const formData = new FormData(form); const response = await fetch(window.location.href, { method: 'POST', body: formData }); const result = await response.json(); clearInterval(timer); spinner.style.display = 'none'; clock.style.display = 'none'; clock.textContent = 'Thinking: 0s'; if (result.error) alert(result.error); else { renderPreview.innerHTML = result.html; previewContainer.style.display = 'block'; renderPreview.scrollIntoView({ behavior: 'smooth' }); } }); /* ---- Copy functionality ---- */ function makeCopy(btnId, selector) { const btn = document.getElementById(btnId); if (!btn) return; btn.onclick = () => { const txt = typeof selector === 'function' ? selector() : document.querySelector(selector)?.value || ''; navigator.clipboard.writeText(txt).then(() => { btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = 'Copy Rendered HTML', 1200); }); }; } makeCopy('copyRendered', () => renderPreview.innerHTML); /* ---- Save key and logout with AJAX ---- */ document.getElementById('saveKeyForm')?.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(e.target); const response = await fetch(window.location.href, { method: 'POST', body: formData }); const result = await response.json(); if (result.success) location.reload(); }); function logout() { fetch(`?logout=1`).then(() => location.reload()); } document.getElementById('modelForm')?.addEventListener('submit', (e) => e.preventDefault()); })(); </script> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel