Add AGENT_READ label functionality and WhatsApp logout endpoint
This commit is contained in:
parent
eee7189867
commit
fed234e448
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue