README / Telegram_Job_Bot /dashboard_FINAL.html
nmnm969989's picture
Upload 550 files
ece3e8e verified
Raw History Blame Contribute Delete
38.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Elite Job Bot | Command Center</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
* { font-family: 'Outfit', sans-serif; }
body { background: #060918; color: #f1f5f9; min-height: 100vh; overflow-x: hidden; }
.orb { position: fixed; border-radius: 50%; filter: blur(80px); opacity: 0.15; pointer-events: none; animation: float 8s ease-in-out infinite; }
.orb1 { width: 400px; height: 400px; background: #3b82f6; top: -100px; left: -100px; animation-delay: 0s; }
.orb2 { width: 350px; height: 350px; background: #8b5cf6; bottom: -80px; right: -80px; animation-delay: 3s; }
.orb3 { width: 250px; height: 250px; background: #06b6d4; top: 40%; left: 40%; animation-delay: 5s; }
@keyframes float { 0%,100%{transform:translateY(0) scale(1)} 50%{transform:translateY(-30px) scale(1.05)} }
.glass { background: rgba(255,255,255,0.04); backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.08); }
.glass-strong { background: rgba(255,255,255,0.07); backdrop-filter: blur(30px); border: 1px solid rgba(255,255,255,0.12); }
.stat-card { background: linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02)); border: 1px solid rgba(255,255,255,0.08); transition: all 0.3s ease; }
.stat-card:hover { transform: translateY(-4px); border-color: rgba(99,102,241,0.4); box-shadow: 0 20px 40px rgba(0,0,0,0.3); }
.glow-blue { text-shadow: 0 0 30px rgba(59,130,246,0.6); }
.glow-green { text-shadow: 0 0 20px rgba(34,197,94,0.5); }
.feed-card { transition: all 0.2s ease; }
.channel-chip { transition: all 0.2s ease; cursor: default; }
.ring-track { stroke: rgba(255,255,255,0.08); }
.ring-progress { stroke: url(#ringGrad); stroke-dasharray: 339.3; stroke-dashoffset: {{stroke_offset}}; stroke-linecap: round; transition: stroke-dashoffset 1s ease; transform: rotate(-90deg); transform-origin: center; }
.pulse-dot { animation: pulse-glow 2s infinite; }
@keyframes pulse-glow { 0%,100%{box-shadow:0 0 0 0 rgba(34,197,94,0.4)} 50%{box-shadow:0 0 0 8px rgba(34,197,94,0)} }
.ai-online { background: linear-gradient(135deg, rgba(34,197,94,0.15), rgba(16,185,129,0.05)); border-color: rgba(34,197,94,0.3); }
/* NEW PRO STYLES */
.pro-gradient {
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.pro-card {
background: linear-gradient(180deg, rgba(30,41,59,0.7) 0%, rgba(15,23,42,0.8) 100%);
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 10px 30px -10px rgba(0,0,0,0.5);
backdrop-filter: blur(20px);
}
.pro-btn {
position: relative; overflow: hidden;
transition: all 0.3s ease;
}
.pro-btn::after {
content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, transparent 60%);
opacity: 0; transition: opacity 0.3s ease; transform: scale(0.5);
}
.pro-btn:hover::after { opacity: 1; transform: scale(1); }
.pro-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(0,0,0,0.3); }
.ai-offline { background: linear-gradient(135deg, rgba(239,68,68,0.15), rgba(220,38,38,0.05)); border-color: rgba(239,68,68,0.3); }
#countdown { color: #94a3b8; }
.glass-btn { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.glass-btn:hover { transform: translateY(-2px) scale(1.05); filter: brightness(1.2); }
</style>
</head>
<body>
<!-- Background Orbs -->
<div class="orb orb1"></div>
<div class="orb orb2"></div>
<div class="orb orb3"></div>
<div class="relative z-10 max-w-6xl mx-auto px-4 py-8">
<!-- ENHANCED PRO TOP BAR -->
<header class="pro-card rounded-3xl p-8 mb-8 flex flex-col md:flex-row justify-between items-center relative overflow-hidden">
<div class="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-20 mix-blend-overlay"></div>
<div class="absolute -top-24 -left-24 w-64 h-64 bg-blue-500 rounded-full blur-[100px] opacity-20 pointer-events-none"></div>
<div class="relative z-10 flex flex-col md:flex-row items-center gap-6">
<div class="w-20 h-20 rounded-2xl bg-gradient-to-br from-blue-600 to-indigo-800 flex items-center justify-center shadow-[0_0_40px_rgba(37,99,235,0.5)] border border-blue-400/30">
<span class="text-5xl animate-pulse">⚑</span>
</div>
<div class="text-center md:text-left">
<h1 class="text-4xl md:text-5xl font-black tracking-tighter text-white drop-shadow-2xl flex items-center gap-3">
Job Bot <span class="pro-gradient">PRO</span>
</h1>
<p class="text-blue-200 mt-2 font-semibold tracking-wider text-sm flex items-center justify-center md:justify-start gap-2">
<span class="w-2 h-2 rounded-full bg-blue-400 animate-pulse"></span> Autonomous Telegram Engine
</p>
</div>
</div>
<div class="relative z-10 mt-6 md:mt-0 flex flex-col items-center md:items-end gap-4">
<div class="flex gap-4">
<a href="https://t.me/" target="_blank" class="pro-btn bg-gradient-to-r from-blue-600 to-blue-500 border border-blue-400/50 text-white px-6 py-2.5 rounded-xl text-sm font-bold flex items-center gap-2 shadow-[0_0_20px_rgba(37,99,235,0.4)]">
✈️ Open Telegram
</a>
</div>
<div class="flex items-center gap-6">
<div class="text-center">
<p class="text-[10px] text-gray-500 mb-1 uppercase tracking-widest">Active API</p>
<p class="font-black text-blue-400">#{{active_key_num}}/{{gemini_key_count}}</p>
</div>
<div class="flex items-center gap-2 bg-green-900/40 border border-green-500/40 px-5 py-2 rounded-full backdrop-blur-md">
<div class="w-2.5 h-2.5 rounded-full bg-green-500 shadow-[0_0_10px_#22c55e] animate-pulse pulse-dot"></div>
<span class="text-xs font-bold text-green-400 tracking-widest uppercase">System Online</span>
</div>
</div>
</div>
</header>
<!-- Stats Row -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="pro-card rounded-3xl p-6 text-center transform transition-all hover:scale-105 hover:-translate-y-2 relative overflow-hidden group">
<div class="text-4xl mb-3 drop-shadow-[0_0_15px_rgba(255,255,255,0.8)] group-hover:scale-110 transition-transform">πŸš€</div>
<div class="text-4xl font-black text-white mb-1">{{stats.get('applied', 0)}}</div>
<div class="text-xs text-gray-400 uppercase tracking-widest">Applied Today</div>
</div>
<div class="pro-card rounded-3xl p-6 text-center transform transition-all hover:scale-105 hover:-translate-y-2 relative overflow-hidden group">
<div class="text-4xl mb-3 drop-shadow-[0_0_15px_rgba(249,115,22,0.8)] group-hover:scale-110 transition-transform">πŸ”₯</div>
<div class="text-4xl font-black text-orange-400 mb-1">{{stats.get('current_streak', 0)}}</div>
<div class="text-xs text-gray-400 uppercase tracking-widest">Day Streak</div>
</div>
<div class="pro-card rounded-3xl p-6 text-center transform transition-all hover:scale-105 hover:-translate-y-2 relative overflow-hidden group">
<div class="text-4xl mb-3 drop-shadow-[0_0_15px_rgba(236,72,153,0.8)] group-hover:scale-110 transition-transform">🎯</div>
<div class="text-4xl font-black text-blue-400 mb-1">{{len(applied)}}</div>
<div class="text-xs text-gray-400 uppercase tracking-widest">Total Processed</div>
</div>
<div class="pro-card rounded-3xl p-6 text-center transform transition-all hover:scale-105 hover:-translate-y-2 relative overflow-hidden group">
<div class="text-3xl mb-2">⏭️</div>
<div class="text-4xl font-black text-gray-400 mb-1">{{stats.get('skipped', 0)}}</div>
<div class="text-xs text-gray-400 uppercase tracking-widest">Skipped Today</div>
</div>
</div>
<!-- Middle Row: AI Status + Success Ring + Config -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<!-- AI Engines -->
<div class="glass rounded-2xl p-5">
<h2 class="text-sm font-bold text-gray-300 uppercase tracking-widest mb-4">🧠 AI Engine Status</h2>
<div class="space-y-3">
<div class="{{'ai-online' if gemini_status == 'ONLINE' else 'ai-offline'}} rounded-xl p-3 border flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="text-xl">✨</span>
<div>
<p class="text-sm font-semibold text-white">Google Gemini</p>
<p class="text-xs text-gray-400">gemini-2.5-flash</p>
</div>
</div>
<span class="text-xs font-bold {{'text-green-400' if gemini_status == 'ONLINE' else 'text-red-400'}}">{{gemini_status}}</span>
</div>
<div class="{{'ai-online' if groq_status == 'ONLINE' else 'ai-offline'}} rounded-xl p-3 border flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="text-xl">⚑</span>
<div>
<p class="text-sm font-semibold text-white">Groq Llama 4</p>
<p class="text-xs text-gray-400">llama-4-scout (Backup)</p>
</div>
</div>
<span class="text-xs font-bold {{'text-green-400' if groq_status == 'ONLINE' else 'text-red-400'}}">{{groq_status}}</span>
</div>
<div class="rounded-xl p-3 border border-white/10 bg-white/3 flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="text-xl">πŸ“„</span>
<div>
<p class="text-sm font-semibold text-white">Resume</p>
<p class="text-xs text-gray-400">resume.pdf</p>
</div>
</div>
<span class="text-xs font-bold {{'text-green-400' if os.path.exists(RESUME_FILE) else 'text-red-400'}}">{{'LOADED' if os.path.exists(RESUME_FILE) else 'MISSING'}}</span>
</div>
</div>
</div>
<!-- Active Automations (Expanded) -->
<div class="glass rounded-2xl p-5 relative overflow-hidden group">
<div class="absolute inset-0 bg-gradient-to-br from-indigo-500/10 to-purple-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<h2 class="text-sm font-bold text-gray-300 uppercase tracking-widest mb-4">πŸ›‘οΈ Active Subsystems</h2>
<div class="space-y-3 relative z-10">
<div class="rounded-xl p-3 bg-white/5 border border-white/10 flex items-center justify-between hover:bg-white/10 transition-all cursor-default">
<div class="flex items-center gap-3">
<span class="text-xl">🎭</span>
<div>
<p class="text-sm font-semibold text-white">Anti-Ban Engine</p>
<p class="text-xs text-gray-400">Jitter & Mimicry</p>
</div>
</div>
<span class="text-xs font-bold text-green-400 drop-shadow-[0_0_8px_rgba(74,222,128,0.5)]">ACTIVE</span>
</div>
<div class="rounded-xl p-3 bg-white/5 border border-white/10 flex items-center justify-between hover:bg-white/10 transition-all cursor-default">
<div class="flex items-center gap-3">
<span class="text-xl">πŸ•³οΈ</span>
<div>
<p class="text-sm font-semibold text-white">DOM Piercing</p>
<p class="text-xs text-gray-400">Shadow & iFrame extraction</p>
</div>
</div>
<span class="text-xs font-bold text-green-400 drop-shadow-[0_0_8px_rgba(74,222,128,0.5)]">ACTIVE</span>
</div>
<div class="rounded-xl p-3 bg-white/5 border border-white/10 flex items-center justify-between hover:bg-white/10 transition-all cursor-default">
<div class="flex items-center gap-3">
<span class="text-xl">πŸ“§</span>
<div>
<p class="text-sm font-semibold text-white">IMAP Auto-OTP</p>
<p class="text-xs text-gray-400">Verification Bypass</p>
</div>
</div>
<span class="text-xs font-bold {{'text-green-400 drop-shadow-[0_0_8px_rgba(74,222,128,0.5)]' if imap_status == 'CONNECTED' else 'text-gray-500'}}">{{imap_status}}</span>
</div>
<div class="rounded-xl p-3 bg-white/5 border border-white/10 flex items-center justify-between hover:bg-white/10 transition-all cursor-default">
<div class="flex items-center gap-3">
<span class="text-xl">πŸ‘»</span>
<div>
<p class="text-sm font-semibold text-white">Ghost Mode</p>
<p class="text-xs text-gray-400">Live Telemetry</p>
</div>
</div>
<span class="text-xs font-bold {{'text-fuchsia-400 drop-shadow-[0_0_8px_rgba(232,121,249,0.5)]' if ghost_mode_active else 'text-gray-500'}}">{{'STREAMING' if ghost_mode_active else 'STANDBY'}}</span>
</div>
</div>
</div>
<!-- Success Rate Ring -->
<div class="glass rounded-2xl p-5 flex flex-col items-center justify-center text-center">
<h2 class="text-sm font-bold text-gray-300 uppercase tracking-widest mb-4">πŸ“Š Success Rate</h2>
<div class="relative w-40 h-40">
<svg class="w-full h-full" viewBox="0 0 120 120">
<defs>
<linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3b82f6"/>
<stop offset="100%" style="stop-color:#8b5cf6"/>
</linearGradient>
</defs>
<circle class="ring-track" cx="60" cy="60" r="54" fill="none" stroke-width="10"/>
<circle class="ring-progress" cx="60" cy="60" r="54" fill="none" stroke-width="10"/>
</svg>
<div class="absolute inset-0 flex flex-col items-center justify-center">
<span class="text-4xl font-black text-white">{{success_pct}}%</span>
<span class="text-xs text-gray-400">Applied</span>
</div>
</div>
<p class="text-xs text-gray-500 mt-3">{{total_applied}} applied Β· {{total_skipped}} skipped today</p>
</div>
<!-- Config Panel -->
<div class="glass rounded-2xl p-5">
<h2 class="text-sm font-bold text-gray-300 uppercase tracking-widest mb-4">βš™οΈ Configuration</h2>
<ul class="space-y-3 text-sm">
<li class="flex justify-between pb-2 border-b border-white/5">
<span class="text-gray-400">Primary Channel</span>
<span class="font-mono text-blue-300 text-xs">@{{TARGET_CHANNEL}}</span>
</li>
<li class="flex justify-between pb-2 border-b border-white/5">
<span class="text-gray-400">Total Channels</span>
<span class="text-green-400 font-bold">{{len(TARGET_CHANNELS)}} Active</span>
</li>
<li class="flex justify-between pb-2 border-b border-white/5">
<span class="text-gray-400">Gemini Keys</span>
<span class="text-blue-400 font-bold">{{gemini_key_count}} Keys Β· #{{active_key_num}} Active</span>
</li>
<li class="flex justify-between pb-2 border-b border-white/5">
<span class="text-gray-400">Q&A Brain Size</span>
<span class="text-fuchsia-400 font-bold">{{qa_size}} Semantic Memories</span>
</li>
<li class="flex flex-col pb-2 border-b border-white/5">
<span class="text-gray-400 mb-1">Telegram Chat ID</span>
<span class="font-mono text-xs text-gray-500 truncate">{{active_chat_id if active_chat_id else "Send /start to bot"}}</span>
</li>
<li class="flex justify-between">
<span class="text-gray-400">Bot Status</span>
<span class="text-green-400 font-bold">{{'⏸ PAUSED' if BOT_PAUSED else 'β–Ά RUNNING'}}</span>
</li>
</ul>
</div>
</div>
<!-- Control Panel -->
<div class="glass rounded-2xl p-5 mb-6">
<h2 class="text-sm font-bold text-gray-300 uppercase tracking-widest mb-4">πŸŽ›οΈ Bot Controls</h2>
<div class="flex flex-wrap gap-3 items-center">
<button id="pauseBtn" onclick="controlBot('pause')" class="glass-btn px-4 py-2 rounded-xl font-bold text-sm" style="background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;box-shadow:0 4px 15px rgba(245,158,11,0.4)">
⏸️ Pause
</button>
<button id="resumeBtn" onclick="controlBot('resume')" class="glass-btn px-4 py-2 rounded-xl font-bold text-sm" style="background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;box-shadow:0 4px 15px rgba(34,197,94,0.4)">
▢️ Resume
</button>
<button onclick="window.location.reload()" class="glass-btn px-3 py-1.5 rounded-xl font-bold text-xs" style="background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 4px 15px rgba(59,130,246,0.4)">
πŸ”„ Refresh
</button>
<button onclick="forceScan()" class="pro-btn px-4 py-2.5 rounded-xl font-bold text-xs" style="background:linear-gradient(135deg,#8b5cf6,#6d28d9);color:#fff;box-shadow:0 4px 15px rgba(139,92,246,0.4)">
πŸ“‘ Force Scan
</button>
<button onclick="rotateKey()" class="pro-btn px-4 py-2.5 rounded-xl font-bold text-xs" style="background:linear-gradient(135deg,#ec4899,#be185d);color:#fff;box-shadow:0 4px 15px rgba(236,72,153,0.4)">
πŸ”‘ Rotate Key
</button>
<button onclick="regenResume()" class="pro-btn px-4 py-2.5 rounded-xl font-bold text-xs" style="background:linear-gradient(135deg,#6366f1,#4338ca);color:#fff;box-shadow:0 4px 15px rgba(99,102,241,0.4)">
πŸ“„ Regen Resume
</button>
<button onclick="testHealth()" class="pro-btn px-4 py-2.5 rounded-xl font-bold text-xs" style="background:linear-gradient(135deg,#06b6d4,#0891b2);color:#fff;box-shadow:0 4px 15px rgba(6,182,212,0.4)">
🩺 Test AI Health
</button>
<button onclick="scanInbox()" class="pro-btn px-4 py-2.5 rounded-xl font-bold text-xs" style="background:linear-gradient(135deg,#a855f7,#7e22ce);color:#fff;box-shadow:0 4px 15px rgba(168,85,247,0.4)">
πŸ“§ Scan Inbox (Interviews)
</button>
<button onclick="if(confirm('Are you sure you want to delete all application logs and stats?')) resetHistory()" class="pro-btn px-4 py-2.5 rounded-xl font-bold text-xs" style="background:linear-gradient(135deg,#ef4444,#b91c1c);color:#fff;box-shadow:0 4px 15px rgba(239,68,68,0.4)">
πŸ—‘οΈ Reset Logs
</button>
<div id="ctrlStatus" class="text-sm text-gray-400 ml-2"></div>
<div class="ml-auto text-xs px-3 py-1.5 rounded-full font-semibold" id="botStateBadge" style="background:{{'rgba(34,197,94,0.15)' if not BOT_PAUSED else 'rgba(245,158,11,0.15)'}};border:1px solid {{'rgba(34,197,94,0.4)' if not BOT_PAUSED else 'rgba(245,158,11,0.4)'}};color:{{'#4ade80' if not BOT_PAUSED else '#fbbf24'}}">
{{'▢️ RUNNING' if not BOT_PAUSED else '⏸️ PAUSED'}}
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6 pt-4 border-t border-white/10">
<!-- Quick Apply -->
<div>
<h3 class="text-xs text-gray-400 font-bold mb-2 uppercase">🎯 Manual Quick Apply</h3>
<div class="flex gap-2">
<input type="text" id="manualUrl" placeholder="Paste Job URL (e.g. Workday, Lever)..." class="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-blue-500">
<button onclick="manualApply()" class="pro-btn bg-gradient-to-r from-blue-600 to-indigo-600 text-white px-6 py-2 rounded-xl text-sm font-bold shadow-[0_4px_15px_rgba(37,99,235,0.4)]">Apply</button>
</div>
</div>
<!-- Update Links -->
<div>
<h3 class="text-xs text-gray-400 font-bold mb-2 uppercase">πŸ”— Quick Update Links</h3>
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<input type="text" id="linkedinUrl" placeholder="Paste LinkedIn URL..." class="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-blue-500">
<button onclick="updateProfile('linkedin')" class="pro-btn bg-white/10 hover:bg-white/20 text-white px-5 py-2 rounded-xl text-xs font-bold border border-white/10">Save</button>
</div>
<div class="flex gap-2">
<input type="text" id="githubUrl" placeholder="Paste GitHub URL..." class="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-blue-500">
<button onclick="updateProfile('github')" class="pro-btn bg-white/10 hover:bg-white/20 text-white px-5 py-2 rounded-xl text-xs font-bold border border-white/10">Save</button>
</div>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6 pt-4 border-t border-white/10">
<!-- Advanced Config & Exports -->
<div>
<h3 class="text-xs text-gray-400 font-bold mb-2 uppercase">βš™οΈ Advanced Config & Resume</h3>
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<input type="text" id="newChannel" placeholder="Add Telegram Channel (e.g. @JobAlerts)..." class="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-blue-500">
<button onclick="addChannel()" class="pro-btn bg-gradient-to-r from-purple-600 to-pink-600 text-white px-6 py-2 rounded-xl text-sm font-bold shadow-[0_4px_15px_rgba(147,51,234,0.4)]">Add</button>
</div>
<div class="relative border-2 border-dashed border-white/20 rounded-lg p-3 text-center hover:border-blue-500/50 transition-all bg-white/5 group overflow-hidden">
<input type="file" id="resumeUpload" accept=".pdf" onchange="uploadResume()" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10">
<div class="relative z-0 group-hover:scale-105 transition-all">
<span class="text-xl block mb-1">πŸ“„</span>
<p class="text-xs text-gray-300 font-bold">Drag & Drop new Resume PDF</p>
<p class="text-[10px] text-gray-500 mt-1">Or click to browse (Instantly Updates System)</p>
</div>
</div>
</div>
</div>
<div>
<h3 class="text-xs text-gray-400 font-bold mb-2 uppercase">πŸ“₯ Data Exports & Links</h3>
<div class="flex flex-wrap gap-2">
<button onclick="window.open('{{notion_url}}', '_blank')" class="pro-btn bg-gradient-to-r from-blue-600 to-indigo-600 text-white px-5 py-2.5 rounded-xl text-xs font-bold transition-all shadow-[0_4px_15px_rgba(79,70,229,0.4)]">↗️ Open Notion CRM</button>
<button onclick="window.location.href='api/download_log'" class="pro-btn bg-gradient-to-r from-teal-600 to-emerald-600 text-white px-5 py-2.5 rounded-xl text-xs font-bold transition-all shadow-[0_4px_15px_rgba(20,184,166,0.3)]">CSV Log</button>
<button onclick="window.location.href='api/download_profile'" class="pro-btn bg-gradient-to-r from-gray-700 to-gray-600 text-white px-5 py-2.5 rounded-xl text-xs font-bold transition-all shadow-[0_4px_15px_rgba(75,85,99,0.3)]">profile.json</button>
<button onclick="window.location.href='api/download_qa'" class="pro-btn bg-gradient-to-r from-purple-600 to-fuchsia-600 text-white px-5 py-2.5 rounded-xl text-xs font-bold transition-all shadow-[0_4px_15px_rgba(168,85,247,0.3)]">Q&A Brain</button>
</div>
</div>
</div>
</div>
<!-- Channels Being Monitored -->
<div class="glass rounded-2xl p-5 mb-6">
<h2 class="text-sm font-bold text-gray-300 uppercase tracking-widest mb-4">πŸ“‘ Channels Being Monitored ({{len(TARGET_CHANNELS)}} Active)</h2>
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-2">
{{channels_html|safe}}
</div>
</div>
<!-- Live Activity Feed -->
<div class="glass rounded-2xl p-5 mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-sm font-bold text-gray-300 uppercase tracking-widest">⚑ Live Activity Feed</h2>
<span class="text-xs px-3 py-1 rounded-full font-semibold" style="background:rgba(139,92,246,0.2);border:1px solid rgba(139,92,246,0.4);color:#c4b5fd">Auto-updating</span>
</div>
<div>{{recent_jobs_html|safe}}</div>
</div>
<!-- πŸ“‘ Job Radar Section -->
<div class="glass rounded-2xl p-5 mb-6" id="radarSection">
<div class="flex justify-between items-center mb-4">
<div>
<h2 class="text-sm font-bold uppercase tracking-widest" style="color:#f59e0b">πŸ“‘ Job Radar β€” Multi-Platform</h2>
<p class="text-xs text-gray-400 mt-0.5">Sources: LinkedIn, Indeed, Adzuna, Internshala, Unstop, Global Remotes β€’ Sends links to Telegram</p>
</div>
<button onclick="triggerRadar()" id="radarBtn"
class="text-xs font-bold px-4 py-2 rounded-xl transition-all hover:scale-105"
style="background:linear-gradient(135deg,#f59e0b,#d97706);color:#000;box-shadow:0 0 20px rgba(245,158,11,0.4)">
πŸš€ Scan Now
</button>
</div>
<div id="radarStatus" class="text-xs text-gray-400 mb-4">Last scan: Loading...</div>
<div id="radarJobList" class="space-y-2">
<div class="text-center text-gray-500 text-xs py-4">⏳ Loading radar results...</div>
</div>
</div>
<footer class="text-center text-xs text-gray-600 pb-4">
Elite Job Bot v3.0 β€’ Hybrid AI Engine (Gemini + Groq) β€’ Refreshing every 60s
</footer>
</div>
<script>
let secs = 60;
const el = document.getElementById('countdown');
setInterval(() => {
secs--;
el.textContent = secs + 's';
if (secs <= 0) window.location.reload();
}, 1000);
// Load radar results on page load
async function loadRadar() {
try {
const res = await fetch('api/radar');
const data = await res.json();
document.getElementById('radarStatus').textContent =
`Last scan: ${data.last_scan || 'Never'} β€’ ${data.total || 0} new jobs found`;
const container = document.getElementById('radarJobList');
if (!data.jobs || data.jobs.length === 0) {
container.innerHTML = '<div class="text-center text-gray-500 text-xs py-4">No new jobs found yet. Click "Scan Now" to start!</div>';
return;
}
// Group by source
const grouped = {};
data.jobs.forEach(j => {
if (!grouped[j.source]) grouped[j.source] = [];
grouped[j.source].push(j);
});
const timeAgo = (dateStr) => {
if (!dateStr || dateStr.length < 4) return dateStr;
try {
const date = new Date(dateStr);
if (isNaN(date)) return dateStr;
const seconds = Math.floor((new Date() - date) / 1000);
let interval = seconds / 31536000;
if (interval > 1) return Math.floor(interval) + " years ago";
interval = seconds / 2592000;
if (interval > 1) return Math.floor(interval) + " months ago";
interval = seconds / 86400;
if (interval > 1) return Math.floor(interval) + " days ago";
interval = seconds / 3600;
if (interval > 1) return Math.floor(interval) + " hours ago";
interval = seconds / 60;
if (interval > 1) return Math.floor(interval) + " mins ago";
return "Just now";
} catch(e) { return dateStr; }
};
let html = '';
for (const [src, jobs] of Object.entries(grouped)) {
html += `<div class="mb-4">`;
html += `<h3 class="text-xs font-bold mb-2 uppercase tracking-widest" style="color:#f59e0b">${src}</h3>`;
html += `<div class="grid grid-cols-1 md:grid-cols-2 gap-2">`;
jobs.forEach(j => {
const displayDate = timeAgo(j.date_posted);
const descSnippet = j.description ? `<div class="text-gray-500 text-[9px] mt-1.5 line-clamp-2 italic leading-tight border-l-2 border-white/10 pl-2">${j.description}</div>` : '';
html += `
<a href="${j.link}" target="_blank" class="block p-3 rounded-xl border transition-all hover:scale-[1.02] hover:border-yellow-500/50 relative overflow-hidden"
style="background:rgba(245,158,11,0.05);border-color:rgba(245,158,11,0.15)">
<div class="font-bold text-xs text-yellow-300 truncate pr-6">${j.title}</div>
<div class="text-gray-400 text-[10px] mt-1 font-semibold truncate">🏒 ${j.company}</div>
<div class="text-gray-400 text-[10px] mt-0.5 truncate">πŸ“ ${j.location} β€’ πŸ—“οΈ ${displayDate}</div>
${descSnippet}
<div class="mt-2 text-[10px] font-bold uppercase tracking-wide" style="color:#f59e0b">πŸ‘† Click to Apply Manually β†’</div>
</a>`;
});
html += `</div></div>`;
}
container.innerHTML = html;
} catch(e) {
document.getElementById('radarJobList').innerHTML = '<div class="text-red-400 text-xs">Failed to load radar data.</div>';
}
}
async function triggerRadar() {
const btn = document.getElementById('radarBtn');
const status = document.getElementById('radarStatus');
btn.textContent = '⏳ Scanning...';
btn.disabled = true;
try {
const res = await fetch('api/run_radar', {method: 'POST'});
const data = await res.json();
status.textContent = 'βœ… ' + data.message;
status.style.color = '#4ade80';
setTimeout(loadRadar, 65000); // Reload results after ~1 min
} catch(e) {
status.textContent = '❌ Scan failed';
}
btn.textContent = 'πŸš€ Scan Now';
btn.disabled = false;
}
loadRadar(); // Load on page open
async function controlBot(action) {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = action === 'pause' ? '⏳ Pausing...' : '⏳ Resuming...';
try {
const res = await fetch('api/' + action, {method: 'POST'});
const data = await res.json();
statusEl.textContent = data.message;
statusEl.style.color = action === 'pause' ? '#fbbf24' : '#4ade80';
const badge = document.getElementById('botStateBadge');
if (action === 'pause') {
badge.textContent = '⏸️ PAUSED';
badge.style.color = '#fbbf24';
badge.style.background = 'rgba(245,158,11,0.15)';
badge.style.border = '1px solid rgba(245,158,11,0.4)';
} else {
badge.textContent = '▢️ RUNNING';
badge.style.color = '#4ade80';
badge.style.background = 'rgba(34,197,94,0.15)';
badge.style.border = '1px solid rgba(34,197,94,0.4)';
}
setTimeout(() => statusEl.textContent = '', 3000);
} catch(e) {
statusEl.textContent = '❌ Error: ' + e;
}
}
async function forceScan() {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Forcing scan...';
try {
const res = await fetch('api/run_radar', {method: 'POST'});
const data = await res.json();
statusEl.textContent = 'βœ… ' + data.message;
statusEl.style.color = '#4ade80';
setTimeout(() => statusEl.textContent = '', 4000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function rotateKey() {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Rotating key...';
try {
const res = await fetch('api/rotate_key', {method: 'POST'});
const data = await res.json();
statusEl.textContent = 'βœ… ' + data.message;
statusEl.style.color = '#4ade80';
setTimeout(() => window.location.reload(), 2000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function manualApply() {
const url = document.getElementById('manualUrl').value;
if(!url) return;
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Initiating manual apply...';
try {
const res = await fetch('api/manual_apply', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({url: url})
});
const data = await res.json();
statusEl.textContent = 'βœ… ' + data.message;
statusEl.style.color = '#4ade80';
document.getElementById('manualUrl').value = '';
setTimeout(() => statusEl.textContent = '', 4000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function updateProfile(field) {
const val = document.getElementById(field + 'Url').value;
if(!val) return;
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Updating ' + field + '...';
try {
const res = await fetch('api/update_profile', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({field: field, value: val})
});
const data = await res.json();
statusEl.textContent = 'βœ… Updated!';
statusEl.style.color = '#4ade80';
document.getElementById(field + 'Url').value = '';
setTimeout(() => statusEl.textContent = '', 3000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function regenResume() {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Regenerating PDF...';
try {
const res = await fetch('api/regenerate_resume', {method: 'POST'});
const data = await res.json();
statusEl.textContent = 'βœ… ' + data.message;
statusEl.style.color = '#4ade80';
setTimeout(() => statusEl.textContent = '', 3000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function resetHistory() {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Resetting logs...';
try {
const res = await fetch('api/reset_history', {method: 'POST'});
const data = await res.json();
statusEl.textContent = 'βœ… ' + data.message;
statusEl.style.color = '#4ade80';
setTimeout(() => window.location.reload(), 2000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function testHealth() {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Pinging AI Models...';
try {
const res = await fetch('api/health_check');
const data = await res.json();
alert(`🩺 AI Engine Health:\n\nGemini: ${data.gemini}\nGroq: ${data.groq}`);
statusEl.textContent = '';
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function scanInbox() {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Scanning Gmail for Interviews...';
try {
const res = await fetch('api/scan_inbox', {method: 'POST'});
const data = await res.json();
alert(data.message);
statusEl.textContent = 'βœ… Inbox scan complete!';
statusEl.style.color = '#4ade80';
setTimeout(() => statusEl.textContent = '', 3000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function uploadResume() {
const fileInput = document.getElementById('resumeUpload');
if (!fileInput.files.length) return;
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Uploading and syncing Resume...';
const formData = new FormData();
formData.append('resume', fileInput.files[0]);
try {
const res = await fetch('api/upload_resume', {method: 'POST', body: formData});
const data = await res.json();
statusEl.textContent = 'βœ… ' + data.message;
statusEl.style.color = '#4ade80';
setTimeout(() => window.location.reload(), 2000);
} catch(e) { statusEl.textContent = '❌ Error uploading resume'; }
}
async function markCRM(url, newStatus) {
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Updating CRM...';
try {
const res = await fetch('api/mark_crm', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({url: url, status: newStatus})
});
const data = await res.json();
statusEl.textContent = 'βœ… Updated to ' + newStatus + '!';
setTimeout(() => window.location.reload(), 1000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
async function addChannel() {
const ch = document.getElementById('newChannel').value;
if(!ch) return;
const statusEl = document.getElementById('ctrlStatus');
statusEl.textContent = '⏳ Adding channel...';
try {
const res = await fetch('api/add_channel', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({channel: ch})
});
const data = await res.json();
statusEl.textContent = 'βœ… ' + data.message;
statusEl.style.color = '#4ade80';
document.getElementById('newChannel').value = '';
setTimeout(() => window.location.reload(), 2000);
} catch(e) { statusEl.textContent = '❌ Error'; }
}
</script>
</body>
</html>