mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-04-28 11:40:47 +00:00
315 lines
15 KiB
HTML
315 lines
15 KiB
HTML
<html>
|
|
<head>
|
|
<title>WhatsApp Integration</title>
|
|
</head>
|
|
|
|
<body>
|
|
<div x-data="{
|
|
testing: false,
|
|
test_results: null,
|
|
projects: [],
|
|
|
|
qr_visible: false,
|
|
qr_status: '',
|
|
qr_message: '',
|
|
qr_data_url: null,
|
|
qr_poll_timer: null,
|
|
|
|
disconnecting: false,
|
|
disconnect_message: '',
|
|
|
|
async init() {
|
|
try {
|
|
const { callJsonApi } = await import('/js/api.js');
|
|
const res = await callJsonApi('projects', { action: 'list' });
|
|
this.projects = res.data || [];
|
|
} catch (e) { this.projects = []; }
|
|
},
|
|
allowed_text() {
|
|
return (config?.allowed_users || []).join(', ');
|
|
},
|
|
set_allowed(val) {
|
|
config.allowed_users = val.split(',')
|
|
.map(s => s.trim().replace(/^\+/, '').replace(/^0+/, ''))
|
|
.filter(s => s);
|
|
},
|
|
async test_connection() {
|
|
this.testing = true;
|
|
this.test_results = null;
|
|
try {
|
|
const { callJsonApi } = await import('/js/api.js');
|
|
const res = await callJsonApi('/plugins/_whatsapp_integration/test_connection', {
|
|
config: config
|
|
});
|
|
this.test_results = res;
|
|
} catch (e) {
|
|
this.test_results = { success: false, results: [{ test: 'Connection', ok: false, message: String(e) }] };
|
|
}
|
|
this.testing = false;
|
|
},
|
|
|
|
async show_qr() {
|
|
this.qr_visible = true;
|
|
this.qr_status = 'loading';
|
|
this.qr_message = 'Starting bridge...';
|
|
this.qr_data_url = null;
|
|
await this.poll_qr();
|
|
this.qr_poll_timer = setInterval(() => this.poll_qr(), 3000);
|
|
},
|
|
hide_qr() {
|
|
this.qr_visible = false;
|
|
this.qr_data_url = null;
|
|
this.qr_status = '';
|
|
if (this.qr_poll_timer) {
|
|
clearInterval(this.qr_poll_timer);
|
|
this.qr_poll_timer = null;
|
|
}
|
|
},
|
|
async poll_qr() {
|
|
try {
|
|
const { callJsonApi } = await import('/js/api.js');
|
|
const res = await callJsonApi('/plugins/_whatsapp_integration/qr_code', {});
|
|
this.qr_status = res.status || 'error';
|
|
this.qr_message = res.message || '';
|
|
this.qr_data_url = res.qr || null;
|
|
|
|
if (res.status === 'connected') {
|
|
if (this.qr_poll_timer) {
|
|
clearInterval(this.qr_poll_timer);
|
|
this.qr_poll_timer = null;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
this.qr_status = 'error';
|
|
this.qr_message = String(e);
|
|
this.qr_data_url = null;
|
|
}
|
|
},
|
|
async disconnect_account() {
|
|
if (!confirm('Disconnect this WhatsApp account? You will need to scan a new QR code to reconnect.')) return;
|
|
this.disconnecting = true;
|
|
this.disconnect_message = '';
|
|
try {
|
|
const { callJsonApi } = await import('/js/api.js');
|
|
const res = await callJsonApi('/plugins/_whatsapp_integration/disconnect', {});
|
|
this.disconnect_message = res.success ? 'Account disconnected' : (res.message || 'Failed');
|
|
} catch (e) {
|
|
this.disconnect_message = String(e);
|
|
}
|
|
this.disconnecting = false;
|
|
}
|
|
}">
|
|
<template x-if="config">
|
|
<div>
|
|
<div class="section-title">WhatsApp Integration</div>
|
|
<div class="section-description">
|
|
Connect Agent Zero to WhatsApp using a Baileys bridge.
|
|
Requires Node.js installed on the system.
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Enabled</div>
|
|
<div class="field-description">Enable WhatsApp bridge and message polling</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<label class="toggle">
|
|
<input type="checkbox" x-model="config.enabled" />
|
|
<span class="toggler"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Mode</div>
|
|
<div class="field-description"
|
|
x-text="config.mode === 'self-chat'
|
|
? 'Message yourself to talk to the agent — no extra number needed'
|
|
: 'Uses a second phone number — others message it directly'">
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<select x-model="config.mode">
|
|
<option value="dedicated">Separate number (dedicated)</option>
|
|
<option value="self-chat">Personal number (self-chat)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WhatsApp Account (shown when enabled) -->
|
|
<template x-if="config.enabled">
|
|
<div>
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">WhatsApp Account</div>
|
|
<div class="field-description">
|
|
<span x-show="!disconnect_message">Pair or switch your WhatsApp account</span>
|
|
<span x-show="disconnect_message" x-text="disconnect_message"
|
|
:style="'color:' + (disconnect_message === 'Account disconnected' ? '#4caf50' : '#f44336')"></span>
|
|
</div>
|
|
</div>
|
|
<div class="field-control" style="display: flex; gap: 8px;">
|
|
<button class="btn btn-field" @click="show_qr()">
|
|
Show QR Code
|
|
</button>
|
|
<button class="btn btn-field" @click="disconnect_account()" :disabled="disconnecting">
|
|
<span x-show="!disconnecting">Disconnect</span>
|
|
<span x-show="disconnecting">Disconnecting...</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- QR Code panel -->
|
|
<template x-if="qr_visible">
|
|
<div style="margin-top: 8px; padding: 16px; border-radius: 8px;
|
|
border: 1px solid var(--border-color, #333);
|
|
text-align: center;">
|
|
|
|
<!-- Connected state -->
|
|
<template x-if="qr_status === 'connected'">
|
|
<div>
|
|
<div style="font-size: 1.5rem; margin-bottom: 8px;">✓</div>
|
|
<div style="font-weight: 500; color: #4caf50;" x-text="qr_message"></div>
|
|
<button class="btn btn-field" @click="hide_qr()" style="margin-top: 12px;">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- QR code ready -->
|
|
<template x-if="qr_status === 'waiting_scan' && qr_data_url">
|
|
<div>
|
|
<div style="font-weight: 500; margin-bottom: 12px;">
|
|
Scan with WhatsApp on your phone
|
|
</div>
|
|
<img :src="qr_data_url" alt="WhatsApp QR Code"
|
|
style="width: 256px; height: 256px; border-radius: 8px;
|
|
background: white; padding: 4px;" />
|
|
<div style="margin-top: 8px; font-size: 0.8rem; opacity: 0.6;">
|
|
QR code refreshes automatically
|
|
</div>
|
|
<button class="btn btn-field" @click="hide_qr()" style="margin-top: 12px;">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Loading / waiting for QR -->
|
|
<template x-if="qr_status !== 'connected' && !(qr_status === 'waiting_scan' && qr_data_url)">
|
|
<div>
|
|
<div style="font-weight: 500; margin-bottom: 8px;" x-text="qr_message || 'Connecting...'"></div>
|
|
<div style="font-size: 0.85rem; opacity: 0.6;">
|
|
<template x-if="qr_status === 'error'">
|
|
<span style="color: #f44336;" x-text="qr_message"></span>
|
|
</template>
|
|
<template x-if="qr_status !== 'error'">
|
|
<span>Please wait...</span>
|
|
</template>
|
|
</div>
|
|
<button class="btn btn-field" @click="hide_qr()" style="margin-top: 12px;">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Bridge Port</div>
|
|
<div class="field-description">Local port for the WhatsApp bridge HTTP server</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="number" x-model.number="config.bridge_port" placeholder="3100" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Poll Interval (seconds)</div>
|
|
<div class="field-description">How often to check for new messages (minimum 2)</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="number" x-model.number="config.poll_interval_seconds" min="2" placeholder="3" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Allowed Users</div>
|
|
<div class="field-description">Use international format without + or leading 0<br>e.g. +1 (415) 555-1234 → 14155551234. Empty = allow all</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="text" :value="allowed_text()" @change="set_allowed($event.target.value)" placeholder="14155551234, 447911123456" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Allow Group</div>
|
|
<div class="field-description">Respond in group chats when mentioned or replied to</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<label class="toggle">
|
|
<input type="checkbox" x-model="config.allow_group" />
|
|
<span class="toggler"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Project</div>
|
|
<div class="field-description">Project to activate for WhatsApp chats</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<select :value="config.project" @change="config.project = $event.target.value">
|
|
<option value="">No project</option>
|
|
<template x-for="proj in projects" :key="proj.name">
|
|
<option :value="proj.name" x-text="proj.title || proj.name" :selected="config.project === proj.name"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Agent Instructions</div>
|
|
<div class="field-description">Extra instructions for the agent in WhatsApp chats</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<textarea x-model="config.agent_instructions" rows="3" placeholder="e.g. Always respond concisely..."></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test connection -->
|
|
<div style="margin-top: 16px; display: flex; align-items: center; gap: 12px;">
|
|
<button class="btn btn-field" @click="test_connection()" :disabled="testing">
|
|
<span x-show="!testing">Test Connection</span>
|
|
<span x-show="testing">Testing...</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Test results -->
|
|
<template x-if="test_results">
|
|
<div style="margin-top: 8px; padding: 8px 12px; border-radius: 6px; font-size: 0.85rem;
|
|
border: 1px solid var(--border-color, #333);">
|
|
<template x-for="r in test_results.results" :key="r.test">
|
|
<div style="display: flex; align-items: center; gap: 8px; padding: 4px 0;">
|
|
<span x-text="r.ok ? '✓' : '✗'"
|
|
:style="'font-weight: bold; color:' + (r.ok ? '#4caf50' : '#f44336')"></span>
|
|
<span style="font-weight: 500; min-width: 50px;" x-text="r.test"></span>
|
|
<span style="opacity: 0.8;" x-text="r.message"></span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</body>
|
|
|
|
</html>
|