Site Builder
Editing:
copywrite3.php
writable 0666
<?php /* ------------------------------------------------------------------ RF Safe – OpenAI Article Generator + Promptinator (v2.0.1) ------------------------------------------------------------------ • Cookie‑based API‑key login (unchanged) • ?ph=########## → pre‑load /ph/{ph}/business.json • ?idx=N → pre‑select prompt from prompts.json • Full Melanie‑AI Promptinator UI embedded • "Custom Prompt" toggle lets you override the auto‑built article prompt • FIX ▸ always sends the final prompt to OpenAI (previous build dropped it) • FIX ▸ adds explicit system instruction + better error handling ------------------------------------------------------------------ */ /* ---------- CONFIG CONSTANTS ------------------------------------ */ const COOKIE_NAME = 'openai_key'; const COOKIE_TTL = 30 * 24 * 3600; // 30 days const SECRET = __FILE__; /* ---------- SHARED COOKIE DOMAIN -------------------------------- */ $host = $_SERVER['HTTP_HOST'] ?? ''; $cookieDomain = ''; if (!filter_var($host, FILTER_VALIDATE_IP) && preg_match('/([a-z0-9-]+\.[a-z]{2,})$/i', $host, $m)) { $cookieDomain = '.'.$m[1]; // leading dot shares with sub‑domains } /* ---------- ENCRYPT / DECRYPT HELPERS --------------------------- */ 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) ?: ''; } /* ---------- BUSINESS PROFILE LOAD ------------------------------- */ $ph = preg_replace('/\D/','', $_GET['ph'] ?? ''); // last 10 digits if(strlen($ph)>10) $ph = substr($ph,-10); $biz = []; if($ph){ $bizPath = $_SERVER['DOCUMENT_ROOT']."/ph/$ph/business.json"; if(is_readable($bizPath)) $biz = json_decode(file_get_contents($bizPath), true) ?: []; } /* ---------- PROMPT SEED (prompts.json) -------------------------- */ $idx = (int)($_GET['idx'] ?? 0); $basePrompt=''; if(is_readable(__DIR__.'/prompts.json')){ $ary=json_decode(file_get_contents(__DIR__.'/prompts.json'),true) ?: []; $basePrompt=$ary[$idx]['prompt'] ?? ''; } /* ---------- DEFAULT ARTICLE SETTINGS ---------------------------- */ $topic = $biz['name'] ?? ''; $keywords = implode(', ', $biz['tags'] ?? []); $linkurl = $biz['website'] ?? ''; $sections = 3; $paras = 3; $lang = 'English'; $style='Creative'; $tone='Neutral'; $points = array_slice($biz['tags'] ?? [], 0, 6); $models = ['gpt-3.5-turbo','gpt-4o-mini','gpt-4']; $model = in_array($_GET['model'] ?? '', $models, true) ? $_GET['model'] : 'gpt-3.5-turbo'; $custom = false; // will flip true if "useCustom" POSTed $rawHTML = ''; $msg = ''; /* ---------- HANDLE KEY SAVE / DELETE --------------------------- */ 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,'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 API key.'; } if(isset($_GET['delete_key'])){ setcookie(COOKIE_NAME,'',time()-3600,'/',$cookieDomain,isset($_SERVER['HTTPS']),true); header('Location: '.$_SERVER['PHP_SELF']);exit; } /* ---------- GENERATE ARTICLE / CUSTOM PROMPT ------------------- */ if($_SERVER['REQUEST_METHOD']==='POST' && (isset($_POST['generate'])||isset($_POST['generateCustom']))){ // grab posted fields (sticky) $topic = trim($_POST['topic'] ?? $topic); $keywords = trim($_POST['keywords'] ?? $keywords); $linkurl = trim($_POST['linkurl'] ?? $linkurl); $sections = max(1,intval($_POST['sections'] ?? $sections)); $paras = max(1,intval($_POST['paras'] ?? $paras)); $lang = $_POST['lang'] ?? $lang; $style = $_POST['style'] ?? $style; $tone = $_POST['tone'] ?? $tone; $points = array_filter(array_map('trim', $_POST['points'] ?? $points)); $model = in_array($_POST['model'] ?? $model, $models, true) ? $_POST['model'] : $model; $custom = isset($_POST['generateCustom']); $finalPrompt = ''; if($custom){ $finalPrompt = trim($_POST['customPrompt'] ?? ''); } 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." : ''; $ptsText = $points ? ' The following main points must each be covered (preferably as section headings): '.implode('; ',$points).'.' : ''; $finalPrompt = "Write an $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.$ptsText. " After the body, append <p class=\"excerpt\">Excerpt: …</p>. Return ONLY the complete HTML."; } if(!$finalPrompt){ $msg='❌ Prompt is empty.'; } else { $keyCookie = $_COOKIE[COOKIE_NAME] ?? ''; $apiKey = $keyCookie ? decrypt_val($keyCookie) : ''; if(!$apiKey){ $msg='❌ Missing API key.'; } else { $payload=[ 'model'=>$model, 'messages'=>[ ['role'=>'system','content'=>'You are an expert copywriter. When the user asks for an article, respond ONLY with the complete HTML for that article—no extra commentary.'], ['role'=>'user','content'=>$finalPrompt] ], '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 '.$apiKey, 'Content-Type: application/json' ], CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_TIMEOUT => 60 ]); $resp=curl_exec($ch); $httpCode=curl_getinfo($ch,CURLINFO_HTTP_CODE); $curlErr=curl_error($ch); curl_close($ch); if($curlErr){ $rawHTML='❌ cURL error: '.htmlspecialchars($curlErr); } elseif(!$resp){ $rawHTML='❌ Empty response from API.'; } else { $data=json_decode($resp,true); if(isset($data['error'])){ $rawHTML='❌ OpenAI error: '.htmlspecialchars($data['error']['message']); } else { $rawHTML=$data['choices'][0]['message']['content'] ?? ''; if(!$rawHTML || strip_tags($rawHTML)==='Hello! How can I assist you today?'){ $snippet=htmlspecialchars(substr($resp,0,400)); $rawHTML="❌ Unexpected response from OpenAI.<pre>$snippet</pre>"; } } } } } } /* ---------- LANGUAGE / STYLE / TONE LISTS ----------------------- */ $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>OpenAI 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:840px;width:100%;display:grid;gap:1.4rem} button.copy{background:var(--blue);color:#fff;border:none;border-radius:var(--radius);padding:.55rem 1.2rem;cursor:pointer} .preview{border:1px solid #d0d4e0;padding:1rem;border-radius:var(--radius);background:#fafbff} textarea{width:100%;min-height:140px;font-family:"Fira Code",monospace} .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.6em} .custom-toggle{display:flex;align-items:center;gap:.6rem;margin:1rem 0} .custom-toggle input{transform:translateY(1px)} @media(max-width:840px){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: ?> <?php if($biz): ?> <div style="background:#e9f7ff;border:1px solid #b6e0ff;padding:.6rem 1rem;border-radius:var(--radius);font-size:.9rem;">Loaded business profile for <strong><?= htmlspecialchars($biz['name']) ?></strong> – feel free to tweak any fields below.</div> <?php endif; ?> <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.6em;"> <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> <div class="custom-toggle"><input type="checkbox" id="useCustom" name="useCustom"><label for="useCustom" style="user-select:none;">Use custom prompt</label></div> <label id="customHolder" style="display:none;">Custom Prompt<textarea name="customPrompt" id="customPrompt"></textarea></label> <button name="generate" id="genArticle">Generate Article</button> <button name="generateCustom" id="genCustom" style="display:none;">Send Custom Prompt</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;input.style.paddingRight='2.6em'; 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 passed from PHP <?php if(empty($points)) $points=['']; foreach($points as $p): ?> createPointRow(<?= json_encode($p) ?>); <?php endforeach; ?> // add point btn q('addPointBtn').addEventListener('click',()=>createPointRow('')); // SpeechRecognition const SR=window.SpeechRecognition||window.webkitSpeechRecognition;let rec; if(SR){ rec=new SR();rec.lang='en-US';rec.interimResults=false;rec.maxAlternatives=1; rec.addEventListener('result',e=>{ const txt=e.results[0][0].transcript;const tgtId=rec._tgt;if(tgtId){const inp=q(tgtId);if(inp){inp.value=txt;buildPrompt();}} }); } document.addEventListener('click',e=>{ if(e.target.classList.contains('mic-btn')){ if(!rec){alert('Speech API not supported');return;} rec._tgt=e.target.dataset.target;rec.start(); } }); // buildPrompt 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 an ${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 additional <h3> sub-headings if it improves readability.`+kwTxt+linkTxt+ptsTxt+ ' After the body, append <p class="excerpt">Excerpt: …</p>. Return ONLY the complete 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 if PHP populated <?php if($rawHTML): ?> const prev=q('articlePreview');if(prev){prev.innerHTML=<?php echo json_encode($rawHTML); ?>;prev.scrollIntoView({behavior:'smooth'});} <?php endif; ?> // custom toggle UI const useCustom=q('useCustom');const holder=q('customHolder');const genArticleBtn=q('genArticle');const genCustomBtn=q('genCustom'); useCustom.addEventListener('change',()=>{ holder.style.display=useCustom.checked?'block':'none'; genArticleBtn.style.display=useCustom.checked?'none':'inline-block'; genCustomBtn.style.display=useCustom.checked?'inline-block':'none'; }); })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel