agent-zero/plugins/_whatsapp_integration/webui/config.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;">&#10003;</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>