#!/usr/bin/env node 'use strict'; /** * 端到端验证:模拟 widget 访客发起对话 → Automation 分配给 AI Assistant → 等待 AI 回复。 * 用法:node scripts/e2e-widget.js "What is the minimum order value?" */ const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const question = process.argv.slice(2).join(' ') || 'What is the minimum order value?'; function loadEnv(file) { const out = {}; for (const line of fs.readFileSync(file, 'utf8').split('\n')) { const t = line.trim(); if (!t || t.startsWith('#')) continue; const eq = t.indexOf('='); if (eq < 0) continue; let v = t.slice(eq + 1).trim(); if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1); out[t.slice(0, eq).trim()] = v; } return out; } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function main() { const env = loadEnv(path.join(ROOT, '.env')); const base = (env.LIBREDESK_BASE_URL || 'http://localhost:9000').replace(/\/+$/, ''); // 1. admin session: 找到 Website inbox 的 UUID const loginRes = await fetch(`${base}/api/v1/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: env.LIBREDESK_SYSTEM_EMAIL || 'System', password: env.LIBREDESK_SYSTEM_USER_PASSWORD }), }); const loginCookies = (loginRes.headers.getSetCookie ? loginRes.headers.getSetCookie() : []) .map((c) => c.split(';')[0]); const adminCookie = loginCookies.join('; '); const csrf = (loginCookies.find((c) => c.startsWith('csrf_token=')) || '').split('=')[1]; if (!loginRes.ok) throw new Error(`admin login failed: HTTP ${loginRes.status}`); const inboxesRes = await fetch(`${base}/api/v1/inboxes`, { headers: { Cookie: adminCookie } }); const inboxes = (await inboxesRes.json()).data || []; const inbox = inboxes.find((i) => i.channel === 'livechat' && i.name === 'Website'); if (!inbox) throw new Error('未找到 Website livechat inbox,先跑 scripts/setup-defaults.js'); console.log(`inbox: #${inbox.id} ${inbox.uuid}`); // 2. widget init(匿名访客,发第一条消息) const initRes = await fetch(`${base}/api/v1/widget/chat/conversations/init`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Libredesk-Inbox-ID': inbox.uuid }, body: JSON.stringify({ message: question }), }); const initText = await initRes.text(); if (!initRes.ok) throw new Error(`widget init failed: HTTP ${initRes.status} ${initText.slice(0, 300)}`); const init = JSON.parse(initText).data; const token = init.session_token; const convUUID = init.conversation.uuid; console.log(`conversation: ${convUUID}`); console.log(`visitor message: ${question}`); // 3. 轮询等待 AI 回复(用管理端 API 读消息,widget API 的返回结构不同) const deadline = Date.now() + 120000; let lastCount = 0; let reply = null; while (Date.now() < deadline) { await sleep(3000); const res = await fetch(`${base}/api/v1/conversations/${convUUID}/messages`, { headers: { Cookie: adminCookie }, }); if (!res.ok) continue; const body = await res.json(); const messages = (body.data && (body.data.results || body.data.messages)) || []; if (messages.length !== lastCount) { lastCount = messages.length; const convRes = await fetch(`${base}/api/v1/conversations/${convUUID}`, { headers: { Cookie: adminCookie } }); const conv = (await convRes.json()).data || {}; console.log(` [${new Date().toLocaleTimeString()}] messages=${messages.length} assigned_user_id=${conv.assigned_user_id} status=${conv.status}`); } const aiMsg = messages .filter((m) => m.type === 'outgoing' && m.sender_type === 'agent' && !m.private) .slice(-1)[0]; if (aiMsg) { reply = aiMsg; break; } } if (!reply) { console.log('\n超时:120 秒内没有收到 AI 回复。检查 docker logs crmnew-libredesk | grep -i ai'); process.exit(2); } console.log('\n===== AI 回复 ====='); console.log(reply.content || reply.text_content || '(empty)'); console.log('\n===== 元信息 ====='); console.log(JSON.stringify({ message_id: reply.id, sender_type: reply.sender_type, type: reply.type, private: reply.private, meta: reply.meta, created_at: reply.created_at, }, null, 2)); } main().catch((err) => { console.error('[e2e-widget]', err.message); process.exit(1); });