mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-10-19 07:23:51 +00:00
298 lines
14 KiB
HTML
298 lines
14 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,
|
|
|
|
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()).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;
|
|
}
|
|
}
|
|
}">
|
|
<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">How the agent connects to WhatsApp</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>
|
|
|
|
<template x-if="config.mode === 'dedicated'">
|
|
<div style="padding: 8px 12px; border-radius: 6px; font-size: 0.85rem; opacity: 0.7;
|
|
border: 1px solid var(--border-color, #333); margin-bottom: 8px;">
|
|
Uses a second phone number. Others message it directly.
|
|
</div>
|
|
</template>
|
|
<template x-if="config.mode === 'self-chat'">
|
|
<div style="padding: 8px 12px; border-radius: 6px; font-size: 0.85rem; opacity: 0.7;
|
|
border: 1px solid var(--border-color, #333); margin-bottom: 8px;">
|
|
Message yourself to talk to the agent. No extra number needed.
|
|
</div>
|
|
</template>
|
|
|
|
<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>
|
|
|
|
<!-- WhatsApp Link section (shown when enabled) -->
|
|
<template x-if="config.enabled">
|
|
<div style="margin: 12px 0;">
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">WhatsApp Link</div>
|
|
<div class="field-description">Pair your WhatsApp account by scanning a QR code</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<button class="btn btn-field" @click="show_qr()">
|
|
Show QR Code
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- QR Code modal -->
|
|
<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">Comma-separated phone numbers without + prefix. Empty = allow all</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="text" :value="allowed_text()" @input="set_allowed($event.target.value)" placeholder="34652029134, 1234567890" />
|
|
</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>
|