/* ============================================================
ChatWidget.jsx — ARIA · Assistente AR
Fluxo: captura de lead → confirmação → conversa com agente
conectado ao n8n via webhook. Suporta anexos e áudio.
============================================================ */
/* ⚠️ SUBSTITUA pela sua URL real de webhook do n8n.
Exemplo de produção: https://n8n.suaempresa.com.br/webhook/agente-ar
O site envia um POST JSON para esta URL com o lead e cada mensagem,
e usa a resposta (campo "reply" / "output" / "message") como fala do agente. */
const AR_WEBHOOK_URL = "https://SEU-N8N.exemplo.com/webhook/agente-ar";
async function arPostWebhook(payload) {
try {
const res = await fetch(AR_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error("status " + res.status);
const data = await res.json().catch(() => ({}));
return data.reply || data.output || data.message || data.text || null;
} catch (e) {
return null; // webhook fictício / offline → usa resposta de fallback
}
}
const ChatWidget = () => {
const [open, setOpen] = React.useState(false);
const [stage, setStage] = React.useState("form"); // 'form' | 'chat'
const [lead, setLead] = React.useState({ nome: "", empresa: "", whatsapp: "", email: "" });
const [errors, setErrors] = React.useState({});
const [msgs, setMsgs] = React.useState([]);
const [draft, setDraft] = React.useState("");
const [typing, setTyping] = React.useState(false);
// audio
const [recording, setRecording] = React.useState(false);
const [recSecs, setRecSecs] = React.useState(0);
const recRef = React.useRef(null);
const chunksRef = React.useRef([]);
const recTimer = React.useRef(null);
const feedRef = React.useRef(null);
const fileRef = React.useRef(null);
React.useEffect(() => { if (feedRef.current) feedRef.current.scrollTop = feedRef.current.scrollHeight; }, [msgs, typing, open, stage]);
React.useEffect(() => { window.lucide && window.lucide.createIcons(); });
const fallbackReplies = [
"Perfeito. Posso qualificar seus leads, responder dúvidas e encaminhar oportunidades para sua equipe.",
"Conseguimos integrar com WhatsApp, Instagram, site, CRM, Telegram e outros sistemas.",
"Posso te mostrar como funciona uma automação aplicada ao seu caso. Qual canal você mais usa hoje?",
"Ótimo! Vou registrar isso e um especialista da AR dá sequência com você.",
];
const replyIdx = React.useRef(0);
const pushAgent = async (userText) => {
setTyping(true);
const reply = await arPostWebhook({ tipo: "mensagem", lead, mensagem: userText, historico: msgs });
const wait = reply ? 200 : 900; // simula latência quando offline
setTimeout(() => {
setTyping(false);
const text = reply || fallbackReplies[replyIdx.current++ % fallbackReplies.length];
setMsgs(m => [...m, { from: "agent", type: "text", text }]);
}, wait);
};
const sendText = (text) => {
const t = (text ?? draft).trim();
if (!t) return;
setMsgs(m => [...m, { from: "user", type: "text", text: t }]);
setDraft("");
pushAgent(t);
};
const validate = () => {
const e = {};
if (!lead.nome.trim()) e.nome = true;
if (!lead.whatsapp.trim()) e.whatsapp = true;
if (!lead.email.trim() || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(lead.email)) e.email = true;
setErrors(e);
return Object.keys(e).length === 0;
};
const submitForm = (ev) => {
ev.preventDefault();
if (!validate()) return;
arPostWebhook({ tipo: "lead", lead }); // registra o lead no n8n
setStage("chat");
const nome = lead.nome.split(" ")[0];
setMsgs([
{ from: "system", type: "text", text: `Dados recebidos: ${lead.nome}${lead.empresa ? " · " + lead.empresa : ""} · ${lead.whatsapp} · ${lead.email}` },
{ from: "agent", type: "text", text: `Prazer, ${nome}! Recebi seus dados e já estamos conectados. Sou a ARIA, agente de IA da Agência AR. Como posso ajudar a sua empresa hoje?` },
]);
};
const onFile = (ev) => {
const file = ev.target.files && ev.target.files[0];
if (!file) return;
const isImg = file.type.startsWith("image/");
const url = isImg ? URL.createObjectURL(file) : null;
setMsgs(m => [...m, { from: "user", type: "file", fileName: file.name, isImg, url, size: file.size }]);
ev.target.value = "";
arPostWebhook({ tipo: "anexo", lead, arquivo: file.name });
pushAgent("[anexo] " + file.name);
};
const startRec = async () => {
if (!navigator.mediaDevices || !window.MediaRecorder) {
setMsgs(m => [...m, { from: "system", type: "text", text: "Gravação de áudio não suportada neste navegador." }]);
return;
}
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mr = new MediaRecorder(stream);
chunksRef.current = [];
mr.ondataavailable = e => e.data.size && chunksRef.current.push(e.data);
mr.onstop = () => {
const blob = new Blob(chunksRef.current, { type: "audio/webm" });
const url = URL.createObjectURL(blob);
setMsgs(m => [...m, { from: "user", type: "audio", url, secs: recSecs }]);
stream.getTracks().forEach(t => t.stop());
arPostWebhook({ tipo: "audio", lead });
pushAgent("[áudio]");
};
mr.start();
recRef.current = mr;
setRecording(true);
setRecSecs(0);
recTimer.current = setInterval(() => setRecSecs(s => s + 1), 1000);
} catch (e) {
setMsgs(m => [...m, { from: "system", type: "text", text: "Permissão de microfone negada." }]);
}
};
const stopRec = (save = true) => {
clearInterval(recTimer.current);
setRecording(false);
if (recRef.current) {
if (!save) recRef.current.onstop = () => recRef.current.stream && null;
try { recRef.current.stop(); } catch (e) {}
}
};
const downloadTranscript = () => {
const lines = [
"Conversa — Agência AR (ARIA)",
"Data: " + new Date().toLocaleString("pt-BR"),
"----------------------------------------",
`Nome: ${lead.nome || "-"}`,
`Empresa: ${lead.empresa || "-"}`,
`WhatsApp: ${lead.whatsapp || "-"}`,
`E-mail: ${lead.email || "-"}`,
"----------------------------------------",
"",
];
msgs.forEach(m => {
const who = m.from === "agent" ? "ARIA" : m.from === "user" ? (lead.nome || "Você") : "Sistema";
let body = m.text;
if (m.type === "file") body = "[anexo] " + m.fileName;
if (m.type === "audio") body = "[áudio enviado]";
lines.push(`${who}: ${body}`);
});
const blob = new Blob([lines.join("\n")], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url; a.download = "conversa-agencia-ar.txt";
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
};
const fmt = (s) => `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
const HeaderBtn = ({ icon, onClick, title }) => (
);
return (