Uso Básico
Instalação
O hook vem na mesma biblioteca do useAgent. O eitri-agents entra na raiz do
eitri-app.conf.js, ao lado do eitri-luminus e do eitri-bifrost — e não
em eitri-app-dependencies, que é para Eitri-Apps compartilhados:
// eitri-app.conf.js
module.exports = {
name: 'meu-app',
'eitri-luminus': '2.4.1',
'eitri-bifrost': '4.8.0',
'eitri-agents': '1.14.3',
// ...
};
Não é preciso rodar eitri agents setup — quem responde é o agente do CX.
Primeira conversa
import { useState } from 'react';
import { useAgentVtexCX, AgentRole } from 'eitri-agents';
import { Button, Chat, Page, Text, TextInput, View } from 'eitri-luminus';
export default function Atendimento() {
const agent = useAgentVtexCX({ channelUuid: '04fa9af0-f043-447e-bb55-7585c45192da' });
const [draft, setDraft] = useState('');
const enviar = async () => {
const texto = draft.trim();
if (!texto) return;
setDraft('');
await agent.call({ content: texto, role: AgentRole.User });
};
return (
<Page>
<Chat>
{agent.messages.map((message) =>
message.role === AgentRole.User ? (
<Chat.End key={message.id}>
<Chat.Bubble>{message.content}</Chat.Bubble>
</Chat.End>
) : (
<Chat.Start key={message.id}>
<Chat.Bubble>
{message.content}
{message.status === 'streaming' ? ' ▍' : ''}
</Chat.Bubble>
</Chat.Start>
),
)}
</Chat>
<View>
<TextInput value={draft} onChange={(e) => setDraft(e.target.value)} />
<Button onClick={enviar} disabled={!agent.isReady}>
Enviar
</Button>
</View>
</Page>
);
}
Repare que não é preciso tratar o streaming: agent.messages é atualizado a
cada pedaço de texto que chega, e o React re-renderiza. O ▍ é só um sinal
visual de que aquela mensagem ainda está sendo escrita.
Parâmetros
useAgentVtexCX(options)
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
channelUuid | string | — | Obrigatório. Canal do CX Platform. |
host | string | https://flows.weni.ai | Host de flows; monta o callback do registro. |
socketUrl | string | https://websocket.weni.ai | Host do WebSocket, na forma https://. |
sessionId | string | id anônimo persistido | Identidade da conversa (campo from). |
initPayload | string | — | Mensagem oculta que dispara o flow numa conversa nova. |
autoConnect | boolean | true | Conectar ao montar o hook. |
historyLimit | number | 20 | Tamanho da página do histórico. |
responseTimeout | number | 60000 | Espera máxima, em ms, pela resposta que resolve o call(). |
verbose | boolean | false | Liga os logs da biblioteca (mesmo Logger do useAgent). |
onDelta | (chunk, message) => void | — | Chamado a cada pedaço de texto recebido. |
Para escopar a conversa por usuário, passe o sessionId — o padrão da loja é
email:conta:
const agent = useAgentVtexCX({
channelUuid,
sessionId: `${cliente.email}:${vtexAccount}`,
});
Sem sessionId, o hook gera um id anônimo e o reaproveita nas próximas
aberturas, então a conversa continua de onde parou.
Valores de Retorno
| Campo | Tipo | Descrição |
|---|---|---|
messages | VtexCXMessage[] | A conversa em ordem cronológica; muda a cada delta. |
status | VtexCXAgentStatus | idle, connecting, thinking, typing, streaming ou error. |
connectionStatus | VtexCXConnectionStatus | disconnected, connecting, connected, reconnecting, error. |
isReady | boolean | Sessão resolvida e socket conectado. |
error | Error | null | Último erro não recuperado. |
call | (message) => Promise<{ message: string }> | Envia texto e/ou arquivo. |
connect / disconnect | () => … | Controle manual da conexão. |
newConversation | () => Promise<void> | Começa uma conversa nova. |
conversations | VtexCXConversation[] | Conversas que este app já abriu. |
openConversation | (sessionId) => Promise<void> | Reabre uma conversa existente. |
loadMore / hasMore | — | Paginação do histórico. |
sessionId | string | null | Identidade em uso. |
A mensagem
Cada item de messages tem esta forma — os campos estruturados mantêm o nome
do protocolo do CX:
{
id: string;
role: AgentRole; // User | Assistant
content: string; // texto (acumulado durante o streaming)
status: 'pending' | 'sent' | 'streaming' | 'delivered' | 'error';
timestamp: number; // epoch em ms
type?: 'text' | 'image' | 'video' | 'audio' | 'file';
media?: string;
caption?: string;
quick_replies?: VtexCXQuickReply[];
list_message?: VtexCXListMessage;
cta_message?: { url: string; display_text: string };
product_list?: VtexCXProductList;
header?: string;
footer?: string;
raw?: Record<string, unknown>; // o payload cru do frame
}
Ver Uso Avançado para renderizar esses campos.
Enviando imagem
O formato é o mesmo do useAgent (file: { mimeType, data }), então o código
do picker não muda:
const [file] = await Eitri.fs.openImagePicker({ allowsMultipleSelection: false });
await agent.call({
content: 'O que você consegue me dizer sobre esta imagem?',
role: AgentRole.User,
file: { mimeType: file.mimeType, data: await file.toBase64() },
});
O hook converte o base64 em data: URL antes de enviar.
O retorno do call()
O protocolo do CX não correlaciona pergunta e resposta. A Promise resolve no primeiro texto finalizado que o agente enviar depois do seu envio:
const { message } = await agent.call({ content: 'quais as promoções?' });
Em flows que respondem com várias mensagens, leia agent.messages em vez do
retorno do call(). Se nada chegar dentro de responseTimeout, a Promise é
rejeitada — trate com try/catch.