mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-11-05 19:04:13 +00:00
177 lines
8.9 KiB
JavaScript
177 lines
8.9 KiB
JavaScript
const chat = document.getElementById("chat");
|
|
const form = document.getElementById("form");
|
|
let eventSource = null;
|
|
let currentSessionId = null;
|
|
const renderedResponses = new Set();
|
|
|
|
function status(text) { const el = document.getElementById("status"); if (el) el.textContent = text; }
|
|
function val(id) { return (document.getElementById(id)?.value || "").trim(); }
|
|
function uuid() { return crypto.randomUUID(); }
|
|
function humanizeAgentName(value) {
|
|
return String(value || "agent").trim().replace(/([a-zà-ÿ])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
}
|
|
function responseText(value) {
|
|
if (typeof value === "string") return value;
|
|
if (value == null) return "";
|
|
return JSON.stringify(value, null, 2);
|
|
}
|
|
function splitTaggedAgentMessages(value) {
|
|
const text = responseText(value).trim();
|
|
if (!text) return [];
|
|
const marker = /^\[([^\]\r\n]*Agent)\]\s*/gim;
|
|
const matches = [...text.matchAll(marker)];
|
|
if (!matches.length || matches[0].index !== 0) return [];
|
|
return matches.map((match, index) => {
|
|
const start = match.index + match[0].length;
|
|
const end = index + 1 < matches.length ? matches[index + 1].index : text.length;
|
|
return { agent: match[1].trim(), text: text.slice(start, end).trim() };
|
|
}).filter((item) => item.agent && item.text);
|
|
}
|
|
function stripOwnAgentMarker(value, agent) {
|
|
const text = responseText(value).trim();
|
|
const marker = text.match(/^\[([^\]\r\n]*Agent)\]\s*/i);
|
|
if (!marker) return text;
|
|
const normalize = (name) => String(name || "").replace(/[_\s-]+/g, "").toLowerCase();
|
|
return normalize(marker[1]) === normalize(agent) ? text.slice(marker[0].length).trim() : text;
|
|
}
|
|
function addUserMessage(text) {
|
|
const div = document.createElement("div");
|
|
div.className = "msg user chat-bubble--user";
|
|
div.textContent = text;
|
|
chat.appendChild(div);
|
|
chat.scrollTop = chat.scrollHeight;
|
|
}
|
|
function addAgentMessage(agentId, text, group) {
|
|
const article = document.createElement("article");
|
|
article.className = "agent-message";
|
|
article.dataset.agentId = agentId || "agent";
|
|
const header = document.createElement("header");
|
|
header.className = "agent-message__header";
|
|
const avatar = document.createElement("span");
|
|
avatar.className = "agent-message__avatar";
|
|
avatar.textContent = humanizeAgentName(agentId).charAt(0) || "A";
|
|
avatar.setAttribute("aria-hidden", "true");
|
|
const name = document.createElement("span");
|
|
name.className = "agent-message__name";
|
|
name.textContent = humanizeAgentName(agentId);
|
|
const body = document.createElement("div");
|
|
body.className = "agent-message__body";
|
|
body.textContent = responseText(text);
|
|
header.append(avatar, name);
|
|
article.append(header, body);
|
|
group.appendChild(article);
|
|
}
|
|
function getAgentMessages(data) {
|
|
const metadata = data?.metadata || {};
|
|
const candidates = data?.supervisor_results || metadata.supervisor_results || data?.agent_responses || metadata.agent_responses || (Array.isArray(data?.messages) ? data.messages : null);
|
|
if (Array.isArray(candidates)) {
|
|
const messages = candidates.map((item) => ({
|
|
agent: item?.agent || item?.agent_id || item?.agentId || item?.name,
|
|
text: stripOwnAgentMarker(item?.answer ?? item?.text ?? item?.message ?? item?.response ?? item?.content, item?.agent || item?.agent_id || item?.agentId || item?.name),
|
|
})).filter((item) => item.agent && responseText(item.text).trim());
|
|
if (messages.length) return messages;
|
|
}
|
|
const text = data?.text ?? data?.speak ?? data?.message ?? data?.response ?? data?.content ?? data?.output;
|
|
const taggedMessages = splitTaggedAgentMessages(text);
|
|
if (taggedMessages.length) return taggedMessages;
|
|
const agent = metadata.active_agent || metadata.route || data?.agent_id || metadata.agent_id || val("agent") || "agent";
|
|
return responseText(text).trim() ? [{ agent, text }] : [];
|
|
}
|
|
function responseKey(data) {
|
|
const stableId = data?.metadata?.message_id || data?.message_id;
|
|
return stableId ? `id:${stableId}` : JSON.stringify(getAgentMessages(data));
|
|
}
|
|
function renderAgentResponse(data) {
|
|
const normalized = typeof data === "string" ? { text: data } : (data || {});
|
|
const messages = getAgentMessages(normalized);
|
|
if (!messages.length) return false;
|
|
const key = responseKey(normalized);
|
|
if (renderedResponses.has(key)) return false;
|
|
renderedResponses.add(key);
|
|
const group = document.createElement("section");
|
|
group.className = "agent-response-group";
|
|
group.setAttribute("aria-label", "Respostas dos agentes");
|
|
messages.forEach((message) => addAgentMessage(message.agent, message.text, group));
|
|
chat.appendChild(group);
|
|
chat.scrollTop = chat.scrollHeight;
|
|
return true;
|
|
}
|
|
function buildBusinessContext(session, messageId) {
|
|
return {
|
|
customer_key: val("customerKey") || null, contract_key: val("contractKey") || null,
|
|
interaction_key: val("interactionKey") || messageId, account_key: val("accountKey") || null,
|
|
resource_key: val("resourceKey") || null, session_key: session || null,
|
|
metadata: { frontend: "agent_frontend", version: "business-context-v2" },
|
|
};
|
|
}
|
|
function syncDomainAliases(payload, businessContext) {
|
|
if (val("agent") === "retail_orders") {
|
|
payload.customer_id = businessContext.customer_key; payload.order_id = businessContext.contract_key;
|
|
} else {
|
|
payload.msisdn = businessContext.customer_key; payload.invoice_id = businessContext.contract_key;
|
|
payload.ura_call_id = businessContext.interaction_key; payload.asset_id = businessContext.resource_key;
|
|
}
|
|
}
|
|
function normalizeSessionId(value) {
|
|
if (!value) return uuid();
|
|
const parts = value.split(":"); return parts[parts.length - 1];
|
|
}
|
|
function connectSSE(backend, sessionId) {
|
|
if (!backend || !sessionId) return;
|
|
if (eventSource) eventSource.close();
|
|
const url = `${backend.replace(/\/$/, "")}/gateway/events/${encodeURIComponent(sessionId)}`;
|
|
eventSource = new EventSource(url);
|
|
eventSource.onopen = () => status("SSE conectado");
|
|
eventSource.onerror = () => {
|
|
if (eventSource?.readyState === EventSource.CONNECTING) status("SSE aguardando/reconectando");
|
|
else if (eventSource?.readyState === EventSource.CLOSED) status("SSE fechado");
|
|
else status("SSE com erro");
|
|
};
|
|
const statuses = { waiting: "SSE aguardando backend", "backend.selected": "Backend selecionado", "flow.start": "Fluxo iniciado", "workflow.started": "Workflow em execução", "workflow.completed": "Workflow concluído", "flow.end": "Fluxo finalizado" };
|
|
Object.entries(statuses).forEach(([eventName, label]) => eventSource.addEventListener(eventName, () => status(label)));
|
|
eventSource.addEventListener("message.responded", (event) => {
|
|
try { renderAgentResponse(JSON.parse(event.data)); } catch { renderAgentResponse(event.data); }
|
|
status("Resposta recebida");
|
|
});
|
|
eventSource.addEventListener("server.error", (event) => {
|
|
renderAgentResponse({ text: `Erro SSE: ${event.data || "erro informado pelo servidor"}`, agent_id: "sistema" });
|
|
status("Erro no fluxo SSE");
|
|
});
|
|
}
|
|
|
|
form.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
const input = document.getElementById("message");
|
|
const text = input.value.trim();
|
|
if (!text) return;
|
|
addUserMessage(text); input.value = "";
|
|
const backend = val("backend").replace(/\/$/, "");
|
|
const channel = val("channel");
|
|
const session = normalizeSessionId(val("session"));
|
|
const messageId = uuid();
|
|
const tenantId = val("tenant") || "default";
|
|
const agentId = val("agent") || "telecom_contas";
|
|
document.getElementById("session").value = session;
|
|
const businessContext = buildBusinessContext(session, messageId);
|
|
const commonContext = { channel_id: "browser", tenant_id: tenantId, agent_id: agentId, business_context: businessContext };
|
|
const payload = channel === "voice"
|
|
? { transcript: text, session_id: session, ani: businessContext.customer_key, message_id: messageId, tenant_id: tenantId, agent_id: agentId, context: commonContext }
|
|
: { message: text, text, session_id: session, user_id: businessContext.customer_key || "web-user", message_id: messageId, tenant_id: tenantId, agent_id: agentId, context: commonContext };
|
|
syncDomainAliases(payload, businessContext);
|
|
if (document.getElementById("useSse")?.checked) connectSSE(backend, session);
|
|
try {
|
|
status("Enviando mensagem");
|
|
const response = await fetch(`${backend}/gateway/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ channel, tenant_id: tenantId, agent_id: agentId, payload }) });
|
|
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
const data = await response.json();
|
|
const returnedSessionId = data.session_id || session;
|
|
currentSessionId = returnedSessionId;
|
|
document.getElementById("session").value = returnedSessionId;
|
|
renderAgentResponse(data);
|
|
status("Resposta recebida");
|
|
} catch (error) {
|
|
renderAgentResponse({ text: `Erro ao chamar backend: ${error.message}`, agent_id: "sistema" });
|
|
status("Erro de conexão");
|
|
}
|
|
});
|