Add AGENT_READ label functionality and WhatsApp logout endpoint

This commit is contained in:
Antigravity 2026-06-14 10:25:06 -04:00
parent eee7189867
commit fed234e448
5 changed files with 97 additions and 9 deletions

View File

@ -1,3 +1,15 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gmail-tokens-pvc
namespace: ai-agents
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Mi
---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@ -72,7 +84,8 @@ spec:
- key: credentials.json - key: credentials.json
path: credentials.json path: credentials.json
- name: tokens-volume - name: tokens-volume
emptyDir: {} persistentVolumeClaim:
claimName: gmail-tokens-pvc
--- ---
apiVersion: v1 apiVersion: v1

View File

@ -58,11 +58,32 @@ function loadAuth() {
} }
} }
async function ensureAgentReadLabel(account) {
const gmail = google.gmail({ version: 'v1', auth: account.client });
const res = await gmail.users.labels.list({ userId: 'me' });
const labels = res.data.labels || [];
let agentReadLabel = labels.find(l => l.name === 'AGENT_READ');
if (!agentReadLabel) {
console.log(`Creating AGENT_READ label for account ${account.name}...`);
const createRes = await gmail.users.labels.create({
userId: 'me',
requestBody: {
name: 'AGENT_READ',
labelListVisibility: 'labelShow',
messageListVisibility: 'show'
}
});
agentReadLabel = createRes.data;
}
return agentReadLabel.id;
}
async function getUnreadEmails(account) { async function getUnreadEmails(account) {
const gmail = google.gmail({ version: 'v1', auth: account.client }); const gmail = google.gmail({ version: 'v1', auth: account.client });
const res = await gmail.users.messages.list({ const res = await gmail.users.messages.list({
userId: 'me', userId: 'me',
q: 'is:unread', q: 'is:unread -label:AGENT_READ',
maxResults: 10 maxResults: 10
}); });
@ -90,18 +111,21 @@ async function getUnreadEmails(account) {
return emails; return emails;
} }
async function markAsRead(email) { async function markAsAgentRead(email, labelId) {
const gmail = google.gmail({ version: 'v1', auth: email.client }); const gmail = google.gmail({ version: 'v1', auth: email.client });
await gmail.users.messages.modify({ await gmail.users.messages.modify({
userId: 'me', userId: 'me',
id: email.id, id: email.id,
requestBody: { removeLabelIds: ['UNREAD'] } requestBody: { addLabelIds: [labelId] }
}); });
} }
async function analyzeWithOllama(email) { async function analyzeWithOllama(email) {
const prompt = `You are an AI assistant that determines if an email is important enough to notify the user immediately on WhatsApp. const prompt = `You are an AI assistant that determines if an email is important enough to notify the user immediately on WhatsApp.
Newsletters, marketing, promotions, and generic updates are NOT important.
CRITICAL INSTRUCTION: You MUST strictly IGNORE all promotional emails, newsletters, marketing, discounts, product updates, or any emails of low significance.
For these, you MUST output IMPORTANT: NO.
Important things: Bills due, flight/travel updates, urgent requests from people, security alerts, direct personal messages. Important things: Bills due, flight/travel updates, urgent requests from people, security alerts, direct personal messages.
Email Subject: ${email.subject} Email Subject: ${email.subject}
@ -166,6 +190,10 @@ async function processEmails() {
try { try {
for (const account of accounts) { for (const account of accounts) {
console.log(`Checking account: ${account.name}...`); console.log(`Checking account: ${account.name}...`);
if (!account.agentReadLabelId) {
account.agentReadLabelId = await ensureAgentReadLabel(account);
}
const emails = await getUnreadEmails(account); const emails = await getUnreadEmails(account);
if (emails.length === 0) { if (emails.length === 0) {
console.log(`No new emails for ${account.name}.`); console.log(`No new emails for ${account.name}.`);
@ -181,8 +209,8 @@ async function processEmails() {
} else { } else {
console.log(`Skipped non-important email: ${email.subject}`); console.log(`Skipped non-important email: ${email.subject}`);
} }
// Mark as read so we don't process it again // Mark as processed using our custom label
await markAsRead(email); await markAsAgentRead(email, account.agentReadLabelId);
} }
} }
} catch (err) { } catch (err) {

View File

@ -8,6 +8,25 @@ const groupsTableBody = document.getElementById('groups-table-body');
const appsTableBody = document.getElementById('apps-table-body'); const appsTableBody = document.getElementById('apps-table-body');
const inviteModal = document.getElementById('invite-modal'); const inviteModal = document.getElementById('invite-modal');
const inviteLinkInput = document.getElementById('invite-link-input'); const inviteLinkInput = document.getElementById('invite-link-input');
const logoutBtn = document.getElementById('logout-btn');
if (logoutBtn) {
logoutBtn.addEventListener('click', async () => {
const confirmLogout = confirm('Are you sure you want to log out and change your WhatsApp account? This will disconnect the current session and restart the gateway.');
if (!confirmLogout) return;
logoutBtn.disabled = true;
logoutBtn.textContent = 'Logging out...';
try {
await fetch('api/whatsapp/logout', { method: 'POST' });
alert('Logout initiated! The WhatsApp Gateway is restarting. Please wait 5-10 seconds and then hard-refresh the page to scan a new QR code.');
} catch (err) {
console.error('Logout error:', err);
alert('Failed to log out cleanly, but restarting anyway. Please refresh.');
}
});
}
// Tab Switching // Tab Switching
document.querySelectorAll('.nav-btn').forEach(btn => { document.querySelectorAll('.nav-btn').forEach(btn => {
@ -39,7 +58,11 @@ function logActivity(msg, type = 'info') {
// Socket Events // Socket Events
socket.on('status', (data) => { socket.on('status', (data) => {
updateStatus(data.ready); updateStatus(data.ready);
if (data.qr && !data.ready) renderQR(data.qr); if (data.ready) {
qrContainer.innerHTML = '<h3>✅ Connected</h3><p>Session is active and authenticated.</p>';
} else if (data.qr) {
renderQR(data.qr);
}
}); });
socket.on('qr', (qr) => { socket.on('qr', (qr) => {
@ -66,9 +89,11 @@ function updateStatus(isReady) {
if (isReady) { if (isReady) {
statusBadge.textContent = 'Connected'; statusBadge.textContent = 'Connected';
statusBadge.className = 'badge success'; statusBadge.className = 'badge success';
if (logoutBtn) logoutBtn.style.display = 'block';
} else { } else {
statusBadge.textContent = 'Disconnected'; statusBadge.textContent = 'Disconnected';
statusBadge.className = 'badge error'; statusBadge.className = 'badge error';
if (logoutBtn) logoutBtn.style.display = 'none';
} }
} }

View File

@ -32,7 +32,10 @@
<section id="status" class="tab-pane active"> <section id="status" class="tab-pane active">
<header> <header>
<h2>Connection Status</h2> <h2>Connection Status</h2>
<div style="display:flex; gap:10px; align-items:center;">
<button id="logout-btn" class="btn" style="background-color: #ef4444; color: white; display: none; padding: 0.25rem 0.75rem; font-size: 0.875rem;">Logout & Change WhatsApp</button>
<div id="status-badge" class="badge error">Disconnected</div> <div id="status-badge" class="badge error">Disconnected</div>
</div>
</header> </header>
<div class="card status-card"> <div class="card status-card">

View File

@ -107,6 +107,24 @@ app.get('/api/status', (req, res) => {
}); });
}); });
app.post('/api/whatsapp/logout', async (req, res) => {
try {
console.log('[WhatsApp Bot] Logging out...');
if (client.info) {
await client.logout();
}
res.json({ success: true, message: 'Logged out successfully. Pod restarting...' });
// Wait briefly to allow the response to be sent, then exit so Kubernetes restarts the pod cleanly
setTimeout(() => process.exit(0), 1000);
} catch (err) {
console.error('Logout error:', err);
res.status(500).json({ success: false, error: err.message });
// Still exit to force a clean slate
setTimeout(() => process.exit(1), 1000);
}
});
// ─── App Tracking ─────────────────────────────────────────────────────────── // ─── App Tracking ───────────────────────────────────────────────────────────
const connectedApps = {}; const connectedApps = {};
@ -235,6 +253,7 @@ app.post('/api/groups/add-by-link', async (req, res) => {
res.json({ success: true, group: newGroup }); res.json({ success: true, group: newGroup });
} catch (err) { } catch (err) {
console.error('[Add Group Error]:', err);
res.status(500).json({ error: err.message || 'Failed to process invite link' }); res.status(500).json({ error: err.message || 'Failed to process invite link' });
} }
}); });