Site Builder
Editing:
tools-writing-article-points.php
writable 0666
<?php /********************************************************************** * Promptinator — Article‑with‑Points Generator (Aug 2025 final) **********************************************************************/ require_once $_SERVER['DOCUMENT_ROOT'] . '/openai/init.php'; ai_handle_key_post(); /* ---------- AJAX end‑point ------------------------------------ */ if ($_SERVER['REQUEST_METHOD']==='POST' && ($_GET['ajax']??'')==='1') { header('Content-Type: application/json; charset=utf-8'); if (!ai_has_key()) { echo json_encode(['error'=>'No API key']); exit; } $topic = trim($_POST['topic']??''); $points = array_filter(array_map('trim', $_POST['points']??[])); $kw = trim($_POST['keywords']??''); $url = trim($_POST['linkurl']??''); $sec = max(1,intval($_POST['sections']??3)); $par = max(1,intval($_POST['paras']??3)); $lang = $_POST['lang'] ?? 'English'; $style = $_POST['style'] ?? 'Creative'; $tone = $_POST['tone'] ?? 'Neutral'; if ($topic===''){ echo json_encode(['error'=>'Topic required']); exit; } $prompt = "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 additional <h3> sub‑headings if it improves readability."; if ($kw) $prompt.=" Include the following keywords naturally throughout the article: $kw."; if ($kw && $url)$prompt.=" Hyperlink each keyword exactly once to $url using <a> tags."; if ($points) $prompt.=" The following main points must each be covered (preferably as section headings): " .implode('; ',$points).'.'; $prompt.=" After the body, append <p class=\"excerpt\">Excerpt: …</p>. Return ONLY the complete HTML."; $html = ai_chat($prompt,['max_tokens'=>max(800,$sec*$par*200)]); echo json_encode(['html'=>$html]); exit; } /* ---------- static 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>Article Generator • Promptinator</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> :root{ --bg:#f1f4fb;--card:#fff;--brand:#004cff;--brand-d:#0d5bfd; --shadow:0 6px 30px rgba(0,0,0,.08);--radius:26px; --red:#e24d4b } *,*::before,*::after{box-sizing:border-box} body{margin:0;min-height:100vh;display:flex;flex-direction:column;background:var(--bg); font:400 16px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif} .breadcrumb{background:#eee;padding:.6rem 1rem;font-weight:600} .breadcrumb a{color:var(--brand);text-decoration:none} main{max-width:1000px;width:100%;margin:2.2rem auto;padding:0 1rem;flex:1} .tool-card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow); padding:2rem;display:grid;gap:1.6rem} label{font-weight:600;font-size:.95rem;display:block;margin-bottom:.25rem} input,select,textarea{width:100%;height:56px;padding:0 1rem;border:1px solid #d0d6e7; border-radius:10px;font-size:1rem} textarea{padding:1rem;height:auto;min-height:90px;resize:vertical;font-family:ui-monospace,monospace} input:focus,select:focus,textarea:focus{outline:none;border-color:var(--brand)} button.btn{background:var(--brand);color:#fff;border:none;border-radius:10px;padding:.9rem 1.8rem; font-size:1rem;font-weight:600;cursor:pointer;min-height:56px} button.btn:hover{background:var(--brand-d)} button.copyBtn{margin-top:.8rem} #cfg{display:grid;gap:1.4rem} @media(min-width:768px){#cfg{grid-template-columns:1fr 1fr} #cfg .full{grid-column:1/-1}} .seed-wrap{position:relative} .seed-wrap input{padding-right:3.5rem} .seed-wrap .micBtn{position:absolute;right:14px;top:50%;transform:translateY(-50%);border:none; background:none;font-size:1.35rem;cursor:pointer} .points-row{position:relative;width:100%;flex:0 0 100%} .points-row input{padding-right:5.3rem} .points-row .micBtn,.points-row .delBtn{ position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border:none;background:none; font-size:1.35rem;cursor:pointer } .points-row .micBtn{right:48px} .points-row .delBtn{right:12px;color:var(--red)} #spin{display:none;gap:.6ch;align-items:center;margin-top:.8rem} #spin.show{display:flex} #spin svg{width:20px;height:20px;animation:rot 1s linear infinite}@keyframes rot{to{transform:rotate(360deg)}} .preview{border:1px solid #d0d4e0;padding:1rem;border-radius:var(--radius);background:#fafbff} </style> </head><body> <nav class="breadcrumb"> <a href="/members/dashboard.php">Dashboard</a> » <a href="/ai-tools/tools.php">AI Toolbox</a> » Article Generator <a href="/<?= htmlspecialchars($_SESSION['slug']??'') ?>/" style="float:right">View Site</a> </nav> <?php ai_render_key_bar(); ?> <?php require_once $_SERVER['DOCUMENT_ROOT'].'/ai-tools/share.php'; ?> <main> <?php if(!ai_has_key()): ?> <div class="tool-card" style="text-align:center"> <h2>Get started</h2> <p>Save your OpenAI key in the black bar above to unlock the generator.</p> </div> <?php else: ?> <div class="tool-card"> <h2>Create an Article</h2> <form id="cfg" autocomplete="off"> <textarea name="prompt" style="display:none" tabindex="-1"></textarea> <div class="seed-wrap full"> <label for="topic">Topic</label> <input id="topic" name="topic" placeholder="e.g. Benefits of Li‑Fi over Wi‑Fi"> <button type="button" class="micBtn" aria-label="Dictate topic">🎤</button> </div> <div class="full"> <label>Main Points</label> <div id="pointsWrap" style="display:flex;flex-direction:column;gap:.8rem"></div> <button type="button" id="addPoint" class="btn" style="margin-top:.8rem;width:160px">Add Point</button> </div> <label for="keywords">Keywords (comma‑separated) <input id="keywords" name="keywords" placeholder="keyword 1, keyword 2"> </label> <label for="linkurl">Link URL (optional) <input id="linkurl" name="linkurl" type="url" placeholder="https://example.com"> </label> <label for="sections"># Sections <select id="sections" name="sections"><?php for($i=1;$i<=8;$i++) echo "<option>$i</option>"; ?></select> </label> <label for="paras"># Paragraphs / section <select id="paras" name="paras"><?php for($i=1;$i<=6;$i++) echo "<option>$i</option>"; ?></select> </label> <label for="lang">Language <select id="lang" name="lang"><?php foreach($langs as $l) echo "<option>$l</option>"; ?></select> </label> <label for="style">Style <select id="style" name="style"><?php foreach($styles as $s) echo "<option>$s</option>"; ?></select> </label> <label for="tone">Tone <select id="tone" name="tone"><?php foreach($tones as $t) echo "<option>$t</option>"; ?></select> </label> <label class="full" for="promptPreview">Prompt Preview <textarea id="promptPreview" readonly></textarea> </label> <button type="button" class="btn copyBtn" id="copyPrompt">Copy Prompt</button> <button id="generate" class="btn full">Generate Article</button> <div id="spin"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/></svg><span id="sec">0</span> s</div> </form> <div id="out" style="display:none"> <h3>Raw HTML</h3> <textarea id="articleRaw" readonly></textarea> <button type="button" class="btn copyBtn" id="copyRaw">Copy HTML</button> <h3 style="margin-top:1.4rem">Rendered Preview</h3> <div id="articlePreview" class="preview"></div> <button type="button" class="btn copyBtn" id="copyRendered">Copy Rendered</button> </div> </div> <?php endif; ?> </main> <script> (()=>{ const $=q=>document.querySelector(q); /* ----- Points rows ------------------------------------------- */ const wrap=$('#pointsWrap'); function addPoint(val=''){ const row=document.createElement('div');row.className='points-row'; const inp=document.createElement('input'); inp.type='text';inp.name='points[]';inp.value=val;inp.placeholder='Main point…'; const mic=document.createElement('button'); mic.type='button';mic.className='micBtn';mic.innerHTML='🎤';mic.setAttribute('aria-label','Dictate point'); const del=document.createElement('button'); del.type='button';del.className='delBtn';del.innerHTML='❌';del.setAttribute('aria-label','Delete point'); mic.onclick=()=>startRec(inp); del.onclick=()=>{row.remove();buildPrompt();}; row.append(inp,mic,del);wrap.appendChild(row); } $('#addPoint').onclick=()=>addPoint(''); addPoint(''); // initial empty row /* ----- Speech dictation -------------------------------------- */ const SR=window.SpeechRecognition||window.webkitSpeechRecognition; function startRec(target){ if(!SR){alert('Speech Recognition not supported');return;} const rec=new SR();rec.lang='en-US'; rec.onresult=e=>{target.value=e.results[0][0].transcript;buildPrompt();}; try{rec.start();}catch{} } $('.seed-wrap .micBtn').onclick=()=>startRec($('#topic')); /* ----- Prompt preview ---------------------------------------- */ function buildPrompt(){ const topic=$('#topic').value.trim(); if(!topic){$('#promptPreview').value='';return;} const kw=$('#keywords').value.trim(),url=$('#linkurl').value.trim(); const sec=$('#sections').value,par=$('#paras').value; const lang=$('#lang').value,style=$('#style').value,tone=$('#tone').value; const pts=[...document.querySelectorAll('input[name="points[]"]')].map(i=>i.value.trim()).filter(Boolean); let out=`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 additional <h3> sub‑headings if it improves readability.'; if(kw) out+=` Include the following keywords naturally throughout the article: ${kw}.`; if(kw&&url) out+=` Hyperlink each keyword exactly once to ${url} using <a> tags.`; if(pts.length) out+=` The following main points must each be covered (preferably as section headings): ${pts.join('; ')}.`; out+=' After the body, append <p class="excerpt">Excerpt: …</p>. Return ONLY the HTML.'; $('#promptPreview').value=out; } document.addEventListener('input',e=>{ if(['topic','keywords','linkurl','sections','paras','lang','style','tone'].includes(e.target.id) || e.target.name==='points[]') buildPrompt(); }); buildPrompt(); /* ----- Copy helpers ------------------------------------------ */ function copy(btnSel,srcSel,isHtml=false){ const btn=$(btnSel); if(!btn) return; btn.onclick=()=>{ const txt=isHtml?$(srcSel).innerHTML:$(srcSel).value; navigator.clipboard.writeText(txt) .then(()=>{const t=btn.textContent;btn.textContent='✔ Copied'; setTimeout(()=>btn.textContent=t,1200);}); }; } copy('#copyPrompt','#promptPreview'); copy('#copyRaw','#articleRaw'); copy('#copyRendered','#articlePreview',true); /* ----- AJAX generation --------------------------------------- */ const frm=$('#cfg'); if(frm){ const spin=$('#spin'),sec=$('#sec'),gen=$('#generate'); frm.onsubmit=e=>{ e.preventDefault(); gen.disabled=true;let t=0;sec.textContent='0';spin.classList.add('show'); const tick=setInterval(()=>sec.textContent=++t,1000); const fd=new FormData(frm); // toolbar helper injects model/temp/max_tokens fetch('?ajax=1',{method:'POST',body:fd}) .then(r=>r.json()) .then(j=>{ clearInterval(tick);spin.classList.remove('show');gen.disabled=false; if(j.error){alert(j.error);return;} $('#out').style.display='block'; $('#articleRaw').value=j.html; $('#articlePreview').innerHTML=j.html; $('#articlePreview').scrollIntoView({behavior:'smooth'}); }) .catch(err=>{ clearInterval(tick);spin.classList.remove('show');gen.disabled=false; alert(err); }); }; } })(); </script> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel