Site Builder
Editing:
add-points.php
writable 0666
<?php // ----------------------------------------------------------------------------- // RF Safe – Single‑Prompt Article Generator (v1.7.3 – shared‑cookie) // ----------------------------------------------------------------------------- // • now stores the OpenAI key cookie at the ROOT DOMAIN (e.g. .example.com) // • avoids clashes between /index.php, /index.2.php, etc. across sub‑paths // • dynamic Main Points, multi‑mic, sticky values, smooth preview scroll // ----------------------------------------------------------------------------- // ---------- Constants & helpers ---------- const COOKIE_NAME = 'openai_key'; const COOKIE_TTL = 30 * 24 * 3600; // 30 days const SECRET = __FILE__; // Derive a "root domain" (simple heuristic: last two labels) $host = $_SERVER['HTTP_HOST'] ?? ''; if (filter_var($host, FILTER_VALIDATE_IP)) { // localhost / raw IP – leave blank so cookie is host‑only $cookieDomain = ''; } else { if (preg_match('/([a-z0-9-]+\.[a-z]{2,})$/i', $host, $m)) { $cookieDomain = '.'.$m[1]; // leading dot shares with sub‑domains } else { $cookieDomain = ''; } } function encrypt_val(string $v): 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($v,$m,$k,0,$iv).'::'.$iv); } function decrypt_val(string $c): string { [$ct,$iv] = explode('::', base64_decode($c), 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) ?: ''; } // ---------- Handle API‑key save / delete ---------- $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, encrypt_val($raw), time()+COOKIE_TTL, '/', $cookieDomain, isset($_SERVER['HTTPS']), true); header('Location: '.$_SERVER['REQUEST_URI']); exit; } $msg='❌ Invalid key.'; } if (isset($_GET['delete_key'])) { setcookie(COOKIE_NAME,'',time()-3600,'/', $cookieDomain, isset($_SERVER['HTTPS']), true); header('Location: '.$_SERVER['PHP_SELF']); exit; } // ---------- Defaults & sticky vars ---------- $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($_POST['model'] ?? ($_GET['model'] ?? ''), $models, true) ? ($_POST['model'] ?? $_GET['model']) : 'gpt-4o-mini'; $rawHTML=''; if ($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['generate'])) { // capture post vals for sticky $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'; $points = array_filter(array_map('trim', $_POST['points'] ?? [])); $model = in_array($_POST['model'] ?? '', $models, true) ? $_POST['model'] : $model; $keyCookie = $_COOKIE[COOKIE_NAME] ?? ''; $key = $keyCookie ? decrypt_val($keyCookie) : ''; if (!$key) { $msg='❌ Missing API key.'; } else { $kwText = $keywords ? " Include the following keywords naturally throughout the article: $keywords." : ''; $linkText = ($keywords && $linkurl) ? " Hyperlink each keyword exactly once to $linkurl using <a> tags." : ''; $pointsText = $points ? ' The following main points must each be covered (preferably as section headings): '.implode('; ', $points).'.' : ''; $prompt = "Write a $lang article in a $tone tone and $style style about \"$topic\". " . "Structure it into $sections sections, each starting with an <h2> and containing $paras paragraph".($paras>1?'s':'').". " . "Use additional <h3> sub‑headings if it improves readability.".$kwText.$linkText.$pointsText. " After the body, append <p class=\"excerpt\">Excerpt: …</p>. Return ONLY the complete HTML."; $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 => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer '.$key,'Content-Type: application/json'], CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_TIMEOUT => 50 ]); $resp=curl_exec($ch); curl_close($ch); $data=json_decode($resp,true); $rawHTML=$data['choices'][0]['message']['content'] ?? '❌ No content returned.'; } } $langs = ['English', 'Spanish', 'German', 'French', 'Italian', 'Portuguese', 'Dutch']; $styles = ['Creative', 'Informative', 'Narrative', 'Persuasive', 'Analytical', 'Journalistic']; $tones = ['Neutral', 'Cheerful', 'Humorous', 'Assertive', 'Inspirational', 'Professional', 'Emotional']; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Article Generator – Promptinator</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{--blue:#4f8cff;--grey-l:#f5f7fa;--radius:.75rem} body{margin:0;display:flex;min-height:100vh;font-family:system-ui,sans-serif;background:var(--grey-l)} .sidebar{flex:0 0 280px;background:#181c23;color:#fff;padding:2rem;display:flex;flex-direction:column;gap:1.25rem} .sidebar h1{margin:0;font-size:1.6rem;color:var(--blue)} .main{flex:1;padding:2rem;display:flex;justify-content:center} .panel{background:#fff;border-radius:var(--radius);box-shadow:0 4px 24px rgba(0,0,0,.08);padding:2rem;max-width:780px;width:100%;display:grid;gap:1.3rem} button.copy{background:var(--blue);color:#fff;border:none;border-radius:var(--radius);padding:.55rem 1.1rem;cursor:pointer} .preview{border:1px solid #d0d4e0;padding:1rem;border-radius:var(--radius);background:#fafbff} textarea{width:100%;min-height:130px;font-family:"Fira Code",monospace} /* unified mic style */ .mic-btn{position:absolute;right:.5em;top:50%;transform:translateY(-50%);border:none;background:transparent;font-size:1.1em;cursor:pointer} .point-row{position:relative;display:inline-block;width:100%} .point-row input{width:100%;padding-right:2.5em} @media(max-width:800px){body{flex-direction:column}.sidebar{width:100%;order:2}.main{order:1;padding:1rem}} </style> </head> <body> <?php $cookie = $_COOKIE[COOKIE_NAME] ?? ''; $hasKey = $cookie && decrypt_val($cookie); ?> <div class="sidebar"> <h1>Promptinator</h1> <?php if(!$hasKey): ?> <form method="post" autocomplete="off"> <input type="password" name="api_key" placeholder="sk-..." required> <button name="save_key">Save & Login</button> <a href="https://platform.openai.com/api-keys" target="_blank" style="font-size:.9rem;color:var(--blue)">👉 Get API Key</a> </form> <small><?= $msg ?: 'Your key is stored only in your browser cookie.' ?></small> <?php else: ?> <div style="font-size:.95rem"><span style="color:#72eb99">●</span> Logged in</div> <form method="get" style="margin-bottom:1rem"> <label style="font-size:.8rem">Model <select name="model" onchange="this.form.submit()" style="margin-top:.3rem"> <?php foreach($models as $m): ?> <option value="<?= $m ?>" <?= $model===$m?'selected':''?>><?= $m ?></option> <?php endforeach; ?> </select> </label> </form> <a href="?delete_key=1" style="color:#fff;text-decoration:none;font-size:.9rem">Logout</a> <?php endif; ?> </div> <div class="main"><div class="panel"> <h2>Create an Article</h2> <?php if(!$hasKey): ?> <p>Please enter your OpenAI API key to continue.</p> <?php else: ?> <form method="post" id="frm"> <input type="hidden" name="model" value="<?= htmlspecialchars($model) ?>"> <!-- Topic with mic --> <label>Topic <div class="point-row" style="width:100%"> <input name="topic" id="topic" required value="<?= htmlspecialchars($topic,ENT_QUOTES) ?>" style="padding-right:2.5em;"> <button type="button" class="mic-btn" data-target="topic" title="Speak topic">🎤</button> </div> </label> <!-- Dynamic main points --> <label>Main Points <div id="pointsContainer" style="display:flex;flex-direction:column;gap:.6rem"></div> <button type="button" id="addPointBtn" style="margin-top:.6rem">Add Point</button> </label> <label>Keywords (comma‑separated) <input name="keywords" id="keywords" value="<?= htmlspecialchars($keywords,ENT_QUOTES) ?>"></label> <label>Link URL (optional) <input name="linkurl" id="linkurl" type="url" value="<?= htmlspecialchars($linkurl,ENT_QUOTES) ?>"></label> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1rem"> <label># Sections <select name="sections" id="sections"> <?php for($i=1;$i<=8;$i++):?><option value="<?=$i?>" <?= $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 value="<?=$i?>" <?= $paras==$i?'selected':''?>><?=$i?></option><?php endfor;?> </select></label> <label>Language <select name="lang" id="lang"> <?php foreach($langs as $l):?><option value="<?=$l?>" <?= $lang===$l?'selected':''?>><?=$l?></option><?php endforeach;?> </select></label> <label>Style <select name="style" id="style"> <?php foreach($styles as $s):?><option value="<?=$s?>" <?= $style===$s?'selected':''?>><?=$s?></option><?php endforeach;?> </select></label> <label>Tone <select name="tone" id="tone"> <?php foreach($tones as $t):?><option value="<?=$t?>" <?= $tone===$t?'selected':''?>><?=$t?></option><?php endforeach;?> </select></label> </div> <label>Prompt Preview <textarea id="prompt" readonly></textarea></label> <button type="button" class="copy" id="copyPrompt">Copy Prompt</button> <button name="generate" style="margin-top:1rem">Generate Article</button> </form> <?php if($rawHTML): ?> <details style="margin-top:1rem"><summary style="cursor:pointer;font-weight:600">Raw HTML</summary> <label style="margin-top:1rem"><textarea id="articleHTMLRaw" readonly><?= htmlspecialchars($rawHTML) ?></textarea></label> <button type="button" class="copy" id="copyRaw">Copy HTML</button> </details> <h3>Rendered Preview</h3> <div id="articlePreview" class="preview"></div> <button type="button" class="copy" id="copyRendered">Copy Rendered HTML</button> <?php endif; ?> <?php endif; ?> </div></div> <script> (function(){ const q=id=>document.getElementById(id); const pointsContainer=q('pointsContainer'); const promptBox=q('prompt'); // Helper to create a point row function createPointRow(val=''){ const idx=Date.now()+Math.random(); const wrapper=document.createElement('div'); wrapper.className='point-row'; const input=document.createElement('input'); input.type='text'; input.name='points[]'; input.value=val; input.dataset.targetId='point-'+idx; input.id='point-'+idx; const btn=document.createElement('button'); btn.type='button'; btn.className='mic-btn'; btn.innerHTML='🎤'; btn.dataset.target=input.id; wrapper.appendChild(input); wrapper.appendChild(btn); pointsContainer.appendChild(wrapper); } // Populate existing points from PHP <?php if(empty($points)) $points=['']; foreach($points as $p): ?> createPointRow(<?= json_encode($p) ?>); <?php endforeach; ?> // Add point button q('addPointBtn').addEventListener('click',()=>createPointRow('')); // SpeechRecognition setup const SpeechRecognition=window.SpeechRecognition||window.webkitSpeechRecognition; let recognizer; if(SpeechRecognition){ recognizer=new SpeechRecognition(); recognizer.lang='en-US'; recognizer.interimResults=false; recognizer.maxAlternatives=1; recognizer.addEventListener('result',evt=>{ const transcript=evt.results[0][0].transcript; const tgtId=recognizer._currentTarget; if(tgtId){ const inp=q(tgtId); if(inp){inp.value=transcript; buildPrompt();} } }); } // Delegate mic button clicks document.addEventListener('click',e=>{ if(e.target.classList.contains('mic-btn')){ if(!recognizer){alert('Speech API not supported');return;} recognizer._currentTarget=e.target.dataset.target; recognizer.start(); } }); // Build prompt preview function buildPrompt(){ const topic=q('topic').value.trim(); if(!topic){promptBox.value='';return;} const kw=q('keywords').value.trim(); const link=q('linkurl').value.trim(); const sec=q('sections').value; const par=q('paras').value; const lang=q('lang').value; const style=q('style').value; const tone=q('tone').value; const pts=[...document.querySelectorAll('input[name="points[]"]')].map(i=>i.value.trim()).filter(Boolean); const kwTxt=kw?` Include the following keywords naturally throughout the article: ${kw}.`:''; const linkTxt=(kw&&link)?` Hyperlink each keyword exactly once to ${link} using <a> tags.`:''; const ptsTxt=pts.length?` The following main points must each be covered (preferably as section headings): ${pts.join('; ')}.`:''; const base=`Write a ${lang} article in a ${tone} tone and ${style} style about \"${topic}\". `+ `Structure it into ${sec} sections, each starting with an <h2> and containing ${par} paragraph${par>1?'s':''}. `+ `Use <h3> sub‑headings if helpful.`+ kwTxt+linkTxt+ptsTxt+ ' After the body, append <p class="excerpt">Excerpt: …</p>. Return ONLY the HTML.'; promptBox.value=base; } ['topic','keywords','linkurl','sections','paras','lang','style','tone'].forEach(id=>{const el=q(id); if(el) el.addEventListener('input',buildPrompt);} ); document.addEventListener('input',e=>{if(e.target.name==='points[]') buildPrompt();}); buildPrompt(); // Copy helpers function setupCopy(btnId,producer){ const btn=q(btnId); if(!btn) return; btn.addEventListener('click',()=>{ navigator.clipboard.writeText(producer()).then(()=>{const t=btn.textContent;btn.textContent='Copied!';setTimeout(()=>btn.textContent=t,1200);}); }); } setupCopy('copyPrompt',()=>promptBox.value); setupCopy('copyRaw',()=>q('articleHTMLRaw')?q('articleHTMLRaw').value:''); setupCopy('copyRendered',()=>q('articlePreview')?q('articlePreview').innerHTML:''); // Inject preview + scroll <?php if($rawHTML): ?> const prev=q('articlePreview'); if(prev){ prev.innerHTML=<?php echo json_encode($rawHTML); ?>; prev.scrollIntoView({behavior:'smooth'}); } <?php endif; ?> })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel