639 lines
31 KiB
HTML
639 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>WEVADS - Brain Send Studio</title>
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=DM+Sans:wght@300;400;500;600;700;900&display=swap');
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
:root{
|
|
--bg:#0a0e17;--s1:#111827;--s2:#1a2236;--s3:#232d42;
|
|
--cy:#22d3ee;--gn:#060a14;--rd:#ef4444;--or:#f59e0b;--pk:#ec4899;--pu:#a78bfa;--bl:#3b82f6;
|
|
--tx:#1e293b;--t2:#94a3b8;--t3:#64748b;--bd:#1e293b;
|
|
--acc:var(--cy);--acc2:var(--pk)
|
|
}
|
|
body{background:#060a14;color:var(--tx);font-family:'DM Sans',sans-serif;min-height:100vh}
|
|
|
|
/* TOPBAR */
|
|
.topbar{height:52px;background:var(--s1);border-bottom:1px solid var(--bd);display:flex;align-items:center;padding:0 20px;gap:14px;position:sticky;top:0;z-index:100}
|
|
.topbar .logo{font-size:16px;font-weight:900;letter-spacing:2px;background:linear-gradient(135deg,var(--cy),var(--pk));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
|
.topbar .badge{padding:3px 8px;border-radius:4px;font-size:9px;font-weight:700;letter-spacing:1px;background:var(--s3);color:var(--t2)}
|
|
.topbar .right{margin-left:auto;display:flex;gap:10px;align-items:center}
|
|
.topbar .dot{width:7px;height:7px;border-radius:50%;display:inline-block}
|
|
.dot-g{background:var(--gn);box-shadow:0 0 6px var(--gn)}.dot-r{background:var(--rd)}.dot-y{background:var(--or)}
|
|
.topbar .provider-badge{font-size:10px;color:var(--t3);font-family:'JetBrains Mono',monospace}
|
|
|
|
/* LAYOUT */
|
|
.shell{display:grid;grid-template-columns:1fr 380px;gap:0;min-height:calc(100vh - 52px)}
|
|
.main{padding:16px;overflow-y:auto}
|
|
.sidebar{background:var(--s1);border-left:1px solid var(--bd);display:flex;flex-direction:column;overflow:hidden}
|
|
|
|
/* CARDS */
|
|
.card{background:var(--s2);border:1px solid var(--bd);border-radius:8px;padding:14px;margin-bottom:10px;transition:.2s}
|
|
.card:hover{border-color:rgba(34,211,238,.3)}
|
|
.card h3{font-size:10px;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:1.2px;margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
.card h3 i{color:var(--cy);font-size:11px}
|
|
|
|
/* FORM */
|
|
.form-row{display:grid;gap:10px;margin-bottom:10px}
|
|
.r2{grid-template-columns:1fr 1fr}.r3{grid-template-columns:1fr 1fr 1fr}.r4{grid-template-columns:1fr 1fr 1fr 1fr}
|
|
label{font-size:10px;font-weight:600;color:var(--t2);text-transform:uppercase;letter-spacing:.8px;margin-bottom:4px;display:block}
|
|
input,textarea,select{width:100%;background:var(--s3);border:1px solid var(--bd);color:var(--tx);border-radius:6px;padding:8px 10px;font-family:'JetBrains Mono',monospace;font-size:12px;transition:.2s;outline:none}
|
|
input:focus,textarea:focus,select:focus{border-color:var(--cy);box-shadow:0 0 0 2px rgba(34,211,238,.15)}
|
|
textarea{resize:vertical;min-height:100px}
|
|
select{cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%2394a3b8'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center}
|
|
.field{display:flex;flex-direction:column}
|
|
.injected{border-color:var(--cy)!important;background:rgba(34,211,238,.08)!important;animation:pulse .6s}
|
|
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(34,211,238,.4)}70%{box-shadow:0 0 0 6px rgba(34,211,238,0)}100%{box-shadow:0 0 0 0 rgba(34,211,238,0)}}
|
|
|
|
/* BUTTONS */
|
|
.btn{padding:8px 16px;border:none;border-radius:6px;font-family:'DM Sans',sans-serif;font-size:12px;font-weight:600;cursor:pointer;transition:.2s;display:inline-flex;align-items:center;gap:6px}
|
|
.btn:hover{transform:translateY(-1px);filter:brightness(1.1)}
|
|
.btn-cy{background:linear-gradient(135deg,#22d3ee,#06b6d4);color:#0a0e17}
|
|
.btn-pk{background:linear-gradient(135deg,#ec4899,#db2777);color:#fff}
|
|
.btn-gn{background:linear-gradient(135deg,#060a14,#059669);color:#fff}
|
|
.btn-pu{background:linear-gradient(135deg,#a78bfa,#7c3aed);color:#fff}
|
|
.btn-ghost{background:transparent;border:1px solid var(--bd);color:var(--t2)}.btn-ghost:hover{border-color:var(--cy);color:var(--cy)}
|
|
.btn-sm{padding:5px 10px;font-size:10px}
|
|
|
|
/* SIDEBAR - WINNERS */
|
|
.sidebar-header{padding:14px;background:linear-gradient(135deg,var(--s2),var(--s3));border-bottom:1px solid var(--bd)}
|
|
.sidebar-header h2{font-size:14px;font-weight:800;display:flex;align-items:center;gap:8px}
|
|
.sidebar-header .stats{font-size:10px;color:var(--t3);margin-top:4px}
|
|
.isp-filter{padding:8px 14px;border-bottom:1px solid var(--bd)}
|
|
.isp-filter select{font-family:'DM Sans',sans-serif;font-size:11px}
|
|
.winners-list{flex:1;overflow-y:auto;padding:8px;scrollbar-width:thin;scrollbar-color:var(--bd) transparent}
|
|
.winner-card{background:var(--s2);border:1px solid var(--bd);border-radius:8px;padding:10px;margin-bottom:6px;cursor:pointer;transition:.2s}
|
|
.winner-card:hover{border-color:var(--cy);background:var(--s3)}
|
|
.winner-card.selected{border-color:var(--cy);background:rgba(34,211,238,.06)}
|
|
.winner-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}
|
|
.winner-isp{font-weight:800;font-size:13px}
|
|
.winner-rate{font-weight:900;font-size:16px;font-family:'JetBrains Mono',monospace}
|
|
.rate-gn{color:var(--gn)}.rate-or{color:var(--or)}.rate-rd{color:var(--rd)}
|
|
.winner-meta{font-size:9px;color:var(--t3);margin-bottom:6px}
|
|
.winner-actions{display:flex;gap:4px}
|
|
|
|
/* AI PANEL */
|
|
.ai-section{background:var(--s2);border:1px solid var(--bd);border-radius:8px;padding:14px;margin-bottom:10px}
|
|
.ai-section h3{font-size:11px;font-weight:700;color:var(--pk);margin-bottom:8px;display:flex;align-items:center;gap:6px}
|
|
.ai-response{background:var(--bg);border:1px solid var(--bd);border-radius:6px;padding:10px;font-size:11px;color:var(--tx);max-height:200px;overflow-y:auto;white-space:pre-wrap;font-family:'JetBrains Mono',monospace;display:none}
|
|
.ai-response.visible{display:block}
|
|
.ai-opts{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
|
|
|
|
/* PERSONA */
|
|
.persona-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px;max-height:200px;overflow-y:auto}
|
|
.persona-chip{background:var(--s3);border:1px solid var(--bd);border-radius:6px;padding:6px 8px;font-size:10px;cursor:pointer;transition:.2s}
|
|
.persona-chip:hover{border-color:var(--pk);background:rgba(236,72,153,.06)}
|
|
.persona-chip.sel{border-color:var(--pk);background:rgba(236,72,153,.1)}
|
|
.persona-name{font-weight:600;color:var(--tx)}.persona-email{color:var(--t3);font-size:9px}
|
|
|
|
/* TOAST */
|
|
.toast{position:fixed;top:16px;left:50%;transform:translateX(-50%) translateY(-100px);padding:12px 24px;border-radius:8px;color:#fff;font-weight:600;font-size:13px;z-index:9999;transition:transform .3s;box-shadow:0 10px 30px rgba(0,0,0,.4)}
|
|
.toast.show{transform:translateX(-50%) translateY(0)}
|
|
.toast-cy{background:linear-gradient(135deg,#22d3ee,#06b6d4);color:#0a0e17}
|
|
.toast-gn{background:linear-gradient(135deg,#060a14,#059669)}
|
|
.toast-rd{background:linear-gradient(135deg,#ef4444,#dc2626)}
|
|
|
|
/* PREVIEW */
|
|
.preview-box{background:var(--bg);border:1px solid var(--bd);border-radius:6px;padding:10px;font-family:'JetBrains Mono',monospace;font-size:10px;line-height:1.6;max-height:300px;overflow-y:auto;white-space:pre-wrap;color:var(--t2)}
|
|
.preview-box .hl{color:var(--cy)}.preview-box .hv{color:var(--gn)}
|
|
|
|
/* LOADING */
|
|
.spinner{display:inline-block;width:14px;height:14px;border:2px solid var(--bd);border-top:2px solid var(--cy);border-radius:50%;animation:spin .6s linear infinite}
|
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
.loading-overlay{position:absolute;inset:0;background:rgba(10,14,23,.7);display:flex;align-items:center;justify-content:center;border-radius:8px;z-index:10}
|
|
|
|
/* EXPORT SECTION */
|
|
.export-bar{display:flex;gap:8px;padding:14px;border-top:1px solid var(--bd);background:var(--s1)}
|
|
.wv-status{position:fixed;top:12px;right:140px;z-index:9998;background:rgba(52,211,153,.15);border:1px solid #34d399;border-radius:12px;padding:3px 10px;color:#34d399;font-size:10px;font-weight:700;font-family:'JetBrains Mono',monospace}
|
|
</style>
|
|
<link rel="stylesheet" href="wevads-global.css?v1770777318">
|
|
</head>
|
|
<body>
|
|
|
|
|
|
|
|
<!-- TOPBAR -->
|
|
<div class="topbar">
|
|
<div class="logo">WEVADS</div>
|
|
<div class="badge">BRAIN SEND STUDIO</div>
|
|
<div class="right">
|
|
<span class="provider-badge" id="providerInfo">Loading providers...</span>
|
|
<span class="dot dot-g"></span>
|
|
<span style="font-size:10px;color:var(--t3)" id="clock"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SHELL -->
|
|
<div class="shell">
|
|
|
|
<!-- MAIN -->
|
|
<div class="main">
|
|
|
|
<!-- ROW 1: Email Config -->
|
|
<div class="card">
|
|
<h3><i class="fas fa-envelope"></i> Email Configuration</h3>
|
|
<div class="form-row r3">
|
|
<div class="field">
|
|
<label>Return Path</label>
|
|
<input type="text" id="f-return-path" ="noreply@[domain]">
|
|
</div>
|
|
<div class="field">
|
|
<label>From Name</label>
|
|
<input type="text" id="f-from-name" ='"[Name]"'>
|
|
</div>
|
|
<div class="field">
|
|
<label>From Email</label>
|
|
<input type="text" id="f-from-email" ="contact@[domain]">
|
|
</div>
|
|
</div>
|
|
<div class="form-row r3">
|
|
<div class="field">
|
|
<label>Subject</label>
|
|
<input type="text" id="f-subject" ="Your subject line">
|
|
</div>
|
|
<div class="field">
|
|
<label>Reply-To</label>
|
|
<input type="text" id="f-reply-to" ="reply@[domain]">
|
|
</div>
|
|
<div class="field">
|
|
<label>ISP Target</label>
|
|
<select id="f-isp">
|
|
<option value="">Auto-detect</option>
|
|
<option>GMX</option><option>OUTLOOK</option><option>T-ONLINE</option>
|
|
<option>ALICE</option><option>ZIGGO</option><option>YAHOO</option>
|
|
<option>GMAIL</option><option>WEB.DE</option><option>ORANGE</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="form-row r3">
|
|
<div class="field">
|
|
<label>Content Type</label>
|
|
<select id="f-content-type">
|
|
<option value="text/html">text/html</option>
|
|
<option value="text/plain">text/plain</option>
|
|
<option value="multipart/alternative">multipart/alternative</option>
|
|
</select>
|
|
</div>
|
|
<div class="field">
|
|
<label>Charset</label>
|
|
<select id="f-charset">
|
|
<option value="UTF-8">UTF-8</option>
|
|
<option value="UTF-7">UTF-7</option>
|
|
<option value="ISO-8859-1">ISO-8859-1</option>
|
|
<option value="ASCII">ASCII</option>
|
|
<option value="Windows-1252">Windows-1252</option>
|
|
</select>
|
|
</div>
|
|
<div class="field">
|
|
<label>Transfer Encoding</label>
|
|
<select id="f-encoding">
|
|
<option value="7bit">7bit</option>
|
|
<option value="8bit">8bit</option>
|
|
<option value="quoted-printable">Quoted-Printable</option>
|
|
<option value="base64">Base64</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ROW 2: Headers -->
|
|
<div class="card">
|
|
<h3><i class="fas fa-code"></i> Headers <span style="margin-left:auto;font-size:9px;color:var(--t3)" id="headerCount">0 lines</span></h3>
|
|
<textarea id="f-headers" rows="10" ="MIME-Version: 1.0
|
|
Message-Id: <[a_7][n_5][n_3][a_3][email_b64]@[domain]>
|
|
From: "[Name]" <contact@[domain]>
|
|
..."></textarea>
|
|
</div>
|
|
|
|
<!-- ROW 3: Body -->
|
|
<div class="card">
|
|
<h3><i class="fas fa-file-code"></i> Email Body <button class="btn btn-sm btn-ghost" onclick="toggleBodyPreview()" style="margin-left:auto"><i class="fas fa-eye"></i> Preview</button></h3>
|
|
<textarea id="f-body" rows="8" ="HTML email body..."></textarea>
|
|
<div id="bodyPreview" style="display:none;margin-top:8px;background:#0c1220;border-radius:6px;padding:12px;max-height:300px;overflow-y:auto"></div>
|
|
</div>
|
|
|
|
<!-- ROW 4: AI Content Generation -->
|
|
<div class="ai-section">
|
|
<h3><i class="fas fa-robot"></i> WEVAL IA — Content Generator</h3>
|
|
<div class="ai-opts">
|
|
<select id="ai-lang" style="width:auto;min-width:100px">
|
|
<option value="de">🇩🇪 German</option>
|
|
<option value="fr">🇫🇷 French</option>
|
|
<option value="en">🇬🇧 English</option>
|
|
<option value="nl">🇳🇱 Dutch</option>
|
|
<option value="it">🇮🇹 Italian</option>
|
|
<option value="es">🇪🇸 Spanish</option>
|
|
</select>
|
|
<select id="ai-style" style="width:auto;min-width:120px">
|
|
<option value="commercial">📦 Commercial</option>
|
|
<option value="notification">🔔 Notification</option>
|
|
<option value="transactional">🧾 Transactional</option>
|
|
<option value="newsletter">📰 Newsletter</option>
|
|
<option value="personal">👤 Personal</option>
|
|
</select>
|
|
<select id="ai-provider" style="width:auto;min-width:120px">
|
|
<option value="">🔄 Auto (failover)</option>
|
|
</select>
|
|
<button class="btn btn-sm btn-pk" onclick="aiGenerate('subject')"><i class="fas fa-heading"></i> Subjects</button>
|
|
<button class="btn btn-sm btn-pu" onclick="aiGenerate('body')"><i class="fas fa-file-code"></i> Body</button>
|
|
<button class="btn btn-sm btn-cy" onclick="aiGenerate('full')"><i class="fas fa-magic"></i> Full Config</button>
|
|
</div>
|
|
<div class="ai-response" id="aiResponse"></div>
|
|
</div>
|
|
|
|
<!-- ROW 5: Personas -->
|
|
<div class="card">
|
|
<h3><i class="fas fa-users"></i> Personas
|
|
<button class="btn btn-sm btn-ghost" onclick="loadPersonas()" style="margin-left:auto"><i class="fas fa-sync"></i> Load</button>
|
|
<button class="btn btn-sm btn-pk" onclick="generatePersonas()"><i class="fas fa-plus"></i> Generate</button>
|
|
</h3>
|
|
<div class="persona-grid" id="personaGrid">
|
|
<div style="grid-column:1/-1;text-align:center;color:var(--t3);font-size:11px;padding:20px">Click "Load" to fetch personas from DB</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ROW 6: Preview -->
|
|
<div class="card">
|
|
<h3><i class="fas fa-binoculars"></i> Full Header Preview</h3>
|
|
<div class="preview-box" id="fullPreview">Configure a winner to see preview...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SIDEBAR -->
|
|
<div class="sidebar">
|
|
<div class="sidebar-header">
|
|
<h2>🧠 Brain Winners</h2>
|
|
<div class="stats" id="brainStats">Chargement...</div>
|
|
</div>
|
|
<div class="isp-filter">
|
|
<select id="ispFilter" onchange="loadWinners()">
|
|
<option value="">Tous les ISP</option>
|
|
</select>
|
|
</div>
|
|
<div class="winners-list" id="winnersList">Chargement...</div>
|
|
<div class="export-bar">
|
|
<button class="btn btn-cy btn-sm" onclick="copyConfig()" style="flex:1"><i class="fas fa-copy"></i> Copy Config</button>
|
|
<button class="btn btn-gn btn-sm" onclick="openSendPage()" style="flex:1"><i class="fas fa-paper-plane"></i> → Send Page</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- TOAST -->
|
|
<div class="toast" id="toast"></div>
|
|
|
|
<script>
|
|
const API = '/api/brain-send.php';
|
|
let selectedWinner = null;
|
|
let selectedPersona = null;
|
|
|
|
// ═══════ CLOCK ═══════
|
|
setInterval(()=>{document.getElementById('clock').textContent=new Date().toLocaleTimeString('fr-FR')},1000);
|
|
|
|
// ═══════ TOAST ═══════
|
|
function toast(msg, type='cy'){
|
|
const t=document.getElementById('toast');
|
|
t.textContent=msg;t.className='toast toast-'+type+' show';
|
|
setTimeout(()=>t.classList.remove('show'),3000);
|
|
}
|
|
|
|
// ═══════ LOAD STATS + ISP FILTER ═══════
|
|
async function loadStats(){
|
|
try{
|
|
const r=await fetch(API+'?action=stats');const d=await r.json();
|
|
if(d.stats) document.getElementById('brainStats').textContent=
|
|
d.stats.winners+' winners | '+d.stats.total+' configs | Avg '+d.stats.avg_rate+'% inbox';
|
|
if(d.isps){
|
|
const sel=document.getElementById('ispFilter');
|
|
d.isps.forEach(isp=>{const o=document.createElement('option');o.value=isp;o.textContent=isp;sel.appendChild(o);});
|
|
}
|
|
}catch(e){console.error('Stats error:',e)}
|
|
}
|
|
|
|
// ═══════ LOAD WINNERS ═══════
|
|
async function loadWinners(){
|
|
const isp=document.getElementById('ispFilter').value;
|
|
const list=document.getElementById('winnersList');
|
|
list.innerHTML='<div style="text-align:center;padding:30px"><div class="spinner"></div></div>';
|
|
try{
|
|
const r=await fetch(API+'?action=winners'+(isp?'&isp='+encodeURIComponent(isp):''));
|
|
const d=await r.json();
|
|
if(!d.winners||!d.winners.length){list.innerHTML='<div style="text-align:center;padding:30px;color:var(--t3)">No winners found</div>';return;}
|
|
list.innerHTML=d.winners.map(w=>{
|
|
const rate=parseFloat(w.inbox_rate)||0;
|
|
const rateClass=rate>=95?'rate-gn':rate>=80?'rate-or':'rate-rd';
|
|
const details=['Method: <b>'+w.send_method+'</b>'];
|
|
if(w.domain_used) details.push('Domain: <b>'+w.domain_used+'</b>');
|
|
if(w.total_sent) details.push('Sent: '+w.total_sent);
|
|
return `<div class="winner-card" data-id="${w.id}" onclick="selectWinner(${w.id})">
|
|
<div class="winner-top">
|
|
<span class="winner-isp">${w.isp_target}</span>
|
|
<span class="winner-rate ${rateClass}">${Math.min(rate,100).toFixed(0)}%</span>
|
|
</div>
|
|
<div class="winner-meta">${details.join(' | ')}</div>
|
|
<div class="winner-actions">
|
|
<button class="btn btn-sm btn-cy" onclick="event.stopPropagation();injectWinner(${w.id})"><i class="fas fa-bolt"></i> Inject</button>
|
|
<button class="btn btn-sm btn-ghost" onclick="event.stopPropagation();previewWinner(${w.id})"><i class="fas fa-eye"></i></button>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}catch(e){list.innerHTML='<div style="color:var(--rd);padding:20px">Error: '+e.message+'</div>';}
|
|
}
|
|
|
|
// ═══════ SELECT WINNER ═══════
|
|
function selectWinner(id){
|
|
document.querySelectorAll('.winner-card').forEach(c=>c.classList.remove('selected'));
|
|
const card=document.querySelector(`.winner-card[data-id="${id}"]`);
|
|
if(card) card.classList.add('selected');
|
|
injectWinner(id);
|
|
}
|
|
|
|
// ═══════ INJECT WINNER ═══════
|
|
async function injectWinner(id){
|
|
try{
|
|
const r=await fetch(API+'?action=inject&id='+id);const d=await r.json();
|
|
if(!d.config){toast('Config not found','rd');return;}
|
|
const c=d.config;
|
|
selectedWinner=c;
|
|
let count=0;
|
|
|
|
function setField(fieldId, val){
|
|
if(!val) return;
|
|
const el=document.getElementById(fieldId);
|
|
if(!el) return;
|
|
el.value=val;
|
|
el.classList.add('injected');
|
|
setTimeout(()=>el.classList.remove('injected'),3000);
|
|
count++;
|
|
}
|
|
|
|
function setSelect(fieldId, val){
|
|
if(!val) return;
|
|
const el=document.getElementById(fieldId);
|
|
if(!el) return;
|
|
const norm=val.split(';')[0].trim().toLowerCase();
|
|
for(let i=0;i<el.options.length;i++){
|
|
if(el.options[i].value.toLowerCase()===norm || el.options[i].value.toLowerCase()===val.toLowerCase()){
|
|
el.value=el.options[i].value;
|
|
el.classList.add('injected');
|
|
setTimeout(()=>el.classList.remove('injected'),3000);
|
|
count++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Text fields
|
|
setField('f-return-path', c.return_path);
|
|
setField('f-from-name', c.from_name);
|
|
setField('f-from-email', c.from_email);
|
|
setField('f-subject', c.subject_template);
|
|
setField('f-reply-to', c.reply_to);
|
|
|
|
// Selects
|
|
setSelect('f-content-type', c.content_type);
|
|
setSelect('f-charset', c.charset);
|
|
setSelect('f-encoding', c.encoding);
|
|
|
|
// ISP target
|
|
setSelect('f-isp', c.isp_target);
|
|
|
|
// Build full header block
|
|
let hdr='MIME-Version: 1.0\n';
|
|
hdr+='Message-Id: <[a_7][n_5][n_3][a_3][email_b64]@[domain]>\n';
|
|
if(c.from_name && c.from_email) hdr+='From: '+c.from_name+' <'+c.from_email+'>\n';
|
|
else if(c.from_email) hdr+='From: <'+c.from_email+'>\n';
|
|
if(c.subject_template) hdr+='Subject: '+c.subject_template+'\n';
|
|
if(c.reply_to) hdr+='Reply-To: '+c.reply_to+'\n';
|
|
hdr+='To: [email]\n';
|
|
if(c.encoding) hdr+='Content-Transfer-Encoding: '+c.encoding+'\n';
|
|
if(c.content_type) hdr+='Content-Type: '+c.content_type+(c.charset?'; charset='+c.charset:'')+'\n';
|
|
hdr+='Date: [mail_date]\n';
|
|
for(let i=1;i<=5;i++){
|
|
if(c['header_'+i+'_name']&&c['header_'+i+'_value'])
|
|
hdr+=c['header_'+i+'_name']+': '+c['header_'+i+'_value']+'\n';
|
|
}
|
|
setField('f-headers', hdr);
|
|
|
|
// Body if available
|
|
if(c.body_template) setField('f-body', c.body_template);
|
|
|
|
// Update header count
|
|
document.getElementById('headerCount').textContent=hdr.split('\n').filter(l=>l.trim()).length+' lines';
|
|
|
|
// Update preview
|
|
updatePreview(c, hdr);
|
|
|
|
toast('🧠 '+c.isp_target+' ('+Math.min(parseFloat(c.inbox_rate),100).toFixed(0)+'%) → '+count+' fields injected','cy');
|
|
|
|
}catch(e){toast('Error: '+e.message,'rd');}
|
|
}
|
|
|
|
// ═══════ PREVIEW ═══════
|
|
function previewWinner(id){
|
|
injectWinner(id); // same as inject but also scrolls to preview
|
|
setTimeout(()=>document.getElementById('fullPreview').scrollIntoView({behavior:'smooth'}),300);
|
|
}
|
|
|
|
function updatePreview(c, hdr){
|
|
const pre=document.getElementById('fullPreview');
|
|
let html='';
|
|
hdr.split('\n').forEach(line=>{
|
|
if(!line.trim()) return;
|
|
const parts=line.split(':');
|
|
if(parts.length>=2){
|
|
html+='<span class="hl">'+parts[0]+'</span>: <span class="hv">'+parts.slice(1).join(':')+'</span>\n';
|
|
} else html+=line+'\n';
|
|
});
|
|
html+='\n--- Config Info ---\n';
|
|
html+='ISP: '+c.isp_target+' | Method: '+c.send_method+'\n';
|
|
html+='Domain: '+(c.domain_used||'—')+' | IP: '+(c.ip_used||'—')+'\n';
|
|
html+='Inbox: '+c.inbox_rate+'% | Conf: '+(c.confidence_score||'—')+'% | Stab: '+(c.stability_score||'—')+'%\n';
|
|
html+='Sent: '+c.total_sent+' | Tracking: '+(c.tracking_domain||'—')+'\n';
|
|
pre.innerHTML=html;
|
|
}
|
|
|
|
function toggleBodyPreview(){
|
|
const p=document.getElementById('bodyPreview');
|
|
const b=document.getElementById('f-body');
|
|
if(p.style.display==='none'){p.style.display='block';p.innerHTML=b.value||'<i>No body content</i>';}
|
|
else p.style.display='none';
|
|
}
|
|
|
|
// ═══════ AI GENERATE ═══════
|
|
async function aiGenerate(type){
|
|
const resp=document.getElementById('aiResponse');
|
|
resp.className='ai-response visible';
|
|
resp.innerHTML='<div class="spinner"></div> Generating with HAMID...';
|
|
const isp=document.getElementById('f-isp').value||'OUTLOOK';
|
|
const lang=document.getElementById('ai-lang').value;
|
|
const style=document.getElementById('ai-style').value;
|
|
const provider=document.getElementById('ai-provider').value;
|
|
|
|
try{
|
|
const r=await fetch(API,{
|
|
method:'POST',
|
|
headers:{'Content-Type':'application/json'},
|
|
body:JSON.stringify({action:'ai_generate',type,isp,lang,style,provider:provider||null})
|
|
});
|
|
const d=await r.json();
|
|
const text=d.response||d.error||JSON.stringify(d,null,2);
|
|
resp.textContent=text;
|
|
|
|
// If provider info is available, show it
|
|
if(d.provider) document.getElementById('providerInfo').textContent='🤖 '+d.provider;
|
|
|
|
// Try to auto-apply generated content
|
|
if(type==='subject'){
|
|
try{
|
|
const subjects=JSON.parse(text);
|
|
if(Array.isArray(subjects)&&subjects.length){
|
|
document.getElementById('f-subject').value=subjects[0];
|
|
document.getElementById('f-subject').classList.add('injected');
|
|
setTimeout(()=>document.getElementById('f-subject').classList.remove('injected'),3000);
|
|
toast('Subject set: '+subjects[0],'gn');
|
|
}
|
|
}catch(e){/* not JSON, just display */}
|
|
} else if(type==='body'){
|
|
const bodyField=document.getElementById('f-body');
|
|
// Extract HTML if wrapped in code blocks
|
|
let bodyText=text.replace(/```html?\n?/g,'').replace(/```\n?/g,'').trim();
|
|
if(bodyText.includes('<')){
|
|
bodyField.value=bodyText;
|
|
bodyField.classList.add('injected');
|
|
setTimeout(()=>bodyField.classList.remove('injected'),3000);
|
|
toast('Body injected','gn');
|
|
}
|
|
} else if(type==='full'){
|
|
try{
|
|
const clean=text.replace(/```json?\n?/g,'').replace(/```\n?/g,'').trim();
|
|
const config=JSON.parse(clean);
|
|
if(config.subject) document.getElementById('f-subject').value=config.subject;
|
|
if(config.from_name_template) document.getElementById('f-from-name').value=config.from_name_template;
|
|
if(config.return_path_pattern) document.getElementById('f-return-path').value=config.return_path_pattern;
|
|
if(config.content_type){
|
|
const ct=document.getElementById('f-content-type');
|
|
for(let i=0;i<ct.options.length;i++){
|
|
if(ct.options[i].value.toLowerCase()===config.content_type.toLowerCase()){ct.selectedIndex=i;break;}
|
|
}
|
|
}
|
|
toast('Full config applied','gn');
|
|
}catch(e){/* not JSON */}
|
|
}
|
|
}catch(e){
|
|
resp.textContent='Error: '+e.message;
|
|
toast('AI error: '+e.message,'rd');
|
|
}
|
|
}
|
|
|
|
// ═══════ PERSONAS ═══════
|
|
async function loadPersonas(){
|
|
const grid=document.getElementById('personaGrid');
|
|
grid.innerHTML='<div style="grid-column:1/-1;text-align:center"><div class="spinner"></div></div>';
|
|
try{
|
|
const r=await fetch(API+'?action=personas&limit=20');const d=await r.json();
|
|
if(!d.personas||!d.personas.length){grid.innerHTML='<div style="grid-column:1/-1;text-align:center;color:var(--t3)">No personas in DB</div>';return;}
|
|
renderPersonas(d.personas);
|
|
}catch(e){grid.innerHTML='<div style="color:var(--rd)">'+e.message+'</div>';}
|
|
}
|
|
|
|
async function generatePersonas(){
|
|
const grid=document.getElementById('personaGrid');
|
|
grid.innerHTML='<div style="grid-column:1/-1;text-align:center"><div class="spinner"></div></div>';
|
|
try{
|
|
const r=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'persona_generate',count:10})});
|
|
const d=await r.json();
|
|
renderPersonas(d.personas||[]);
|
|
}catch(e){grid.innerHTML='<div style="color:var(--rd)">'+e.message+'</div>';}
|
|
}
|
|
|
|
function renderPersonas(personas){
|
|
const grid=document.getElementById('personaGrid');
|
|
grid.innerHTML=personas.map((p,i)=>`
|
|
<div class="persona-chip" onclick="selectPersona(${i})" data-idx="${i}"
|
|
data-fn="${p.first_name||p.display_name||''}" data-ln="${p.last_name||''}" data-email="${p.email||''}">
|
|
<div class="persona-name">${p.first_name||p.display_name||'?'} ${p.last_name||''}</div>
|
|
<div class="persona-email">${p.email||''}</div>
|
|
</div>`).join('');
|
|
window._personas=personas;
|
|
}
|
|
|
|
function selectPersona(idx){
|
|
document.querySelectorAll('.persona-chip').forEach(c=>c.classList.remove('sel'));
|
|
const chip=document.querySelector(`.persona-chip[data-idx="${idx}"]`);
|
|
if(chip) chip.classList.add('sel');
|
|
const p=window._personas[idx];
|
|
if(!p) return;
|
|
selectedPersona=p;
|
|
// Apply persona to from-name
|
|
const name=(p.display_name||((p.first_name||'')+' '+(p.last_name||'')).trim());
|
|
if(name) document.getElementById('f-from-name').value='"'+name+'"';
|
|
if(p.email) document.getElementById('f-from-email').value=p.email;
|
|
toast('Persona: '+name,'pk');
|
|
}
|
|
|
|
// ═══════ EXPORT ═══════
|
|
function copyConfig(){
|
|
const config={
|
|
return_path:document.getElementById('f-return-path').value,
|
|
from_name:document.getElementById('f-from-name').value,
|
|
from_email:document.getElementById('f-from-email').value,
|
|
subject:document.getElementById('f-subject').value,
|
|
reply_to:document.getElementById('f-reply-to').value,
|
|
content_type:document.getElementById('f-content-type').value,
|
|
charset:document.getElementById('f-charset').value,
|
|
encoding:document.getElementById('f-encoding').value,
|
|
headers:document.getElementById('f-headers').value,
|
|
body:document.getElementById('f-body').value
|
|
};
|
|
navigator.clipboard.writeText(JSON.stringify(config,null,2)).then(()=>toast('Config copied to clipboard','gn'));
|
|
}
|
|
|
|
function openSendPage(){
|
|
// Store config in sessionStorage for send-process to pick up
|
|
const config={
|
|
'return-path':document.getElementById('f-return-path').value,
|
|
'from-name':document.getElementById('f-from-name').value,
|
|
'subject':document.getElementById('f-subject').value,
|
|
'header1':document.getElementById('f-headers').value,
|
|
'creatives-content-type':document.getElementById('f-content-type').value,
|
|
'creatives-charset':document.getElementById('f-charset').value,
|
|
'creatives-content-transfert-encoding':document.getElementById('f-encoding').value,
|
|
'body':document.getElementById('f-body').value
|
|
};
|
|
sessionStorage.setItem('brain-send-config',JSON.stringify(config));
|
|
window.open('/production/send-process.html','_blank');
|
|
toast('Config saved → Opening Send Page','gn');
|
|
}
|
|
|
|
// ═══════ LOAD PROVIDERS ═══════
|
|
async function loadProviders(){
|
|
try{
|
|
const r=await fetch(API+'?action=providers');const d=await r.json();
|
|
if(d.providers){
|
|
const sel=document.getElementById('ai-provider');
|
|
d.providers.forEach(p=>{const o=document.createElement('option');o.value=p.name;o.textContent=p.name+' ('+p.model+')';sel.appendChild(o);});
|
|
document.getElementById('providerInfo').textContent=d.count+' AI providers';
|
|
}
|
|
}catch(e){console.error(e)}
|
|
}
|
|
|
|
// ═══════ HEADER LINE COUNT ═══════
|
|
document.getElementById('f-headers').addEventListener('input',function(){
|
|
document.getElementById('headerCount').textContent=this.value.split('\n').filter(l=>l.trim()).length+' lines';
|
|
});
|
|
|
|
// ═══════ INIT ═══════
|
|
loadStats();
|
|
loadWinners();
|
|
loadProviders();
|
|
</script>
|
|
<script src="arsenal-common.js?v1770778169">
|
|
</body>
|
|
</html>
|
|
</script>
|