feat(api): remove outdated planning document and implement streaming service for real-time task updates
This commit is contained in:
295
static/test_a2a_stream.html
Normal file
295
static/test_a2a_stream.html
Normal file
@@ -0,0 +1,295 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Teste de Streaming A2A</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--secondary-color: #1e40af;
|
||||
--background-color: #f8fafc;
|
||||
--text-color: #1e293b;
|
||||
--border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
margin-top: 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background-color: #e3f2fd;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.agent-message {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
background-color: #fef3c7;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
background-color: #f3f4f6;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Teste de Streaming A2A</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="agentId">Agent ID:</label>
|
||||
<input type="text" id="agentId" placeholder="Digite o ID do agente">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="apiKey">API Key:</label>
|
||||
<input type="text" id="apiKey" placeholder="Digite sua API Key">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="message">Mensagem:</label>
|
||||
<input type="text" id="message" placeholder="Digite sua mensagem">
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startStreaming()">Iniciar Streaming</button>
|
||||
<button onclick="stopStreaming()">Parar Streaming</button>
|
||||
</div>
|
||||
|
||||
<div class="status" id="connectionStatus">
|
||||
Status: Não conectado
|
||||
</div>
|
||||
|
||||
<div class="chat-container" id="chatContainer">
|
||||
<!-- Mensagens serão adicionadas aqui -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let controller = null;
|
||||
const chatContainer = document.getElementById('chatContainer');
|
||||
const statusElement = document.getElementById('connectionStatus');
|
||||
|
||||
function addMessage(content, type = 'agent') {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message ${type}-message`;
|
||||
messageDiv.textContent = content;
|
||||
chatContainer.appendChild(messageDiv);
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
function updateStatus(status) {
|
||||
statusElement.textContent = `Status: ${status}`;
|
||||
}
|
||||
|
||||
async function startStreaming() {
|
||||
const agentId = document.getElementById('agentId').value;
|
||||
const apiKey = document.getElementById('apiKey').value;
|
||||
const message = document.getElementById('message').value;
|
||||
|
||||
if (!agentId || !apiKey || !message) {
|
||||
alert('Por favor, preencha todos os campos');
|
||||
return;
|
||||
}
|
||||
|
||||
// Limpa o chat
|
||||
chatContainer.innerHTML = '';
|
||||
addMessage('Iniciando conexão...', 'status');
|
||||
|
||||
// Configura o payload
|
||||
const payload = {
|
||||
jsonrpc: "2.0",
|
||||
id: "1",
|
||||
method: "tasks/sendSubscribe",
|
||||
params: {
|
||||
id: "test-task",
|
||||
message: {
|
||||
role: "user",
|
||||
parts: [{
|
||||
type: "text",
|
||||
text: message
|
||||
}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Cria um novo AbortController para controlar o streaming
|
||||
controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
// Faz a requisição POST com streaming
|
||||
const response = await fetch(
|
||||
`http://${window.location.host}/api/v1/agents/${agentId}/tasks/sendSubscribe`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream',
|
||||
'X-API-Key': apiKey
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
updateStatus('Conectado');
|
||||
addMessage('Conexão estabelecida', 'status');
|
||||
|
||||
// Lê o stream de dados
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const {
|
||||
done,
|
||||
value
|
||||
} = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
// Decodifica o chunk de dados
|
||||
const chunk = decoder.decode(value);
|
||||
|
||||
// Processa cada linha do chunk
|
||||
const lines = chunk.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
try {
|
||||
const data = JSON.parse(line.slice(5));
|
||||
|
||||
if (data.state) {
|
||||
// Evento de status
|
||||
addMessage(`Estado: ${data.state}`, 'status');
|
||||
} else if (data.content) {
|
||||
// Evento de conteúdo
|
||||
addMessage(data.content, 'agent');
|
||||
}
|
||||
} catch (error) {
|
||||
addMessage('Erro ao processar mensagem: ' + error, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') {
|
||||
updateStatus('Desconectado');
|
||||
addMessage('Conexão encerrada pelo usuário', 'status');
|
||||
} else {
|
||||
updateStatus('Erro');
|
||||
addMessage('Erro ao iniciar streaming: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopStreaming() {
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
controller = null;
|
||||
updateStatus('Desconectado');
|
||||
addMessage('Conexão encerrada', 'status');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -3,40 +3,199 @@
|
||||
|
||||
<head>
|
||||
<title>ADK Streaming Test</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--secondary-color: #f3f4f6;
|
||||
--text-color: #1f2937;
|
||||
--border-color: #e5e7eb;
|
||||
--success-color: #10b981;
|
||||
--error-color: #ef4444;
|
||||
--user-message-bg: #dbeafe;
|
||||
--agent-message-bg: #f3f4f6;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: #f9fafb;
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
#messages {
|
||||
height: 400px;
|
||||
height: 60vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background-color: white;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin: 5px 0;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
max-width: 80%;
|
||||
word-wrap: break-word;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background-color: #e3f2fd;
|
||||
margin-left: 20%;
|
||||
margin-right: 5px;
|
||||
background-color: var(--user-message-bg);
|
||||
margin-left: auto;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.agent-message {
|
||||
background-color: #f5f5f5;
|
||||
margin-right: 20%;
|
||||
margin-left: 5px;
|
||||
background-color: var(--agent-message-bg);
|
||||
margin-right: auto;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
#connection-status {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.connected {
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.disconnected {
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
#messageForm {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
#connectButton {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#connectButton:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
|
||||
#connectButton:disabled {
|
||||
background-color: #93c5fd;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#sendButton {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#sendButton:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
|
||||
#sendButton:disabled {
|
||||
background-color: #93c5fd;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#debug {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background-color: #1f2937;
|
||||
color: #e5e7eb;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
<blade media|%20(max-width%3A%20768px)%20%7B>body {
|
||||
padding: 10px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#messages {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#sendButton {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -44,14 +203,16 @@
|
||||
<body>
|
||||
<h1>ADK Streaming Test</h1>
|
||||
<div id="messages"></div>
|
||||
<div id="connection-status" style="margin-bottom: 10px;"></div>
|
||||
<div id="connection-status" class="disconnected">Desconectado</div>
|
||||
<form id="messageForm">
|
||||
<input type="text" id="agentId" placeholder="Agent ID" style="margin-bottom: 10px; width: 300px;"><br>
|
||||
<input type="text" id="contactId" placeholder="Contact ID" style="margin-bottom: 10px; width: 300px;"><br>
|
||||
<input type="text" id="token" placeholder="JWT Token" style="margin-bottom: 10px; width: 300px;"><br>
|
||||
<button type="button" id="connectButton">Conectar</button><br><br>
|
||||
<input type="text" id="message" placeholder="Digite sua mensagem..." style="width: 300px;">
|
||||
<button type="submit" id="sendButton" disabled>Enviar</button>
|
||||
<input type="text" id="agentId" placeholder="Agent ID">
|
||||
<input type="text" id="contactId" placeholder="Contact ID">
|
||||
<input type="text" id="token" placeholder="JWT Token">
|
||||
<button type="button" id="connectButton">Conectar</button>
|
||||
<div style="display: flex; margin-top: 15px;">
|
||||
<input type="text" id="message" placeholder="Digite sua mensagem...">
|
||||
<button type="submit" id="sendButton" disabled>Enviar</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="debug"></div>
|
||||
@@ -86,12 +247,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Fechar conexão existente se houver
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
// Criar nova conexão WebSocket
|
||||
const ws_url = `ws://${window.location.host}/api/v1/chat/ws/${agentId}/${contactId}`;
|
||||
log('Connecting to WebSocket', {
|
||||
url: ws_url
|
||||
@@ -101,7 +260,6 @@
|
||||
|
||||
ws.onopen = function () {
|
||||
log('WebSocket connected, sending authentication');
|
||||
// Adicionar token no header
|
||||
const authMessage = {
|
||||
type: 'authorization',
|
||||
token: token
|
||||
@@ -110,7 +268,7 @@
|
||||
log('Authentication sent', authMessage);
|
||||
|
||||
statusDiv.textContent = 'Conectado';
|
||||
statusDiv.style.color = 'green';
|
||||
statusDiv.className = 'connected';
|
||||
sendButton.disabled = false;
|
||||
connectButton.disabled = true;
|
||||
};
|
||||
@@ -144,7 +302,7 @@
|
||||
wasClean: event.wasClean
|
||||
});
|
||||
statusDiv.textContent = 'Desconectado';
|
||||
statusDiv.style.color = 'red';
|
||||
statusDiv.className = 'disconnected';
|
||||
sendButton.disabled = true;
|
||||
connectButton.disabled = false;
|
||||
ws = null;
|
||||
@@ -153,7 +311,7 @@
|
||||
ws.onerror = function (error) {
|
||||
log('WebSocket error', error);
|
||||
statusDiv.textContent = 'Erro na conexão';
|
||||
statusDiv.style.color = 'red';
|
||||
statusDiv.className = 'disconnected';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -163,13 +321,11 @@
|
||||
const message = messageInput.value;
|
||||
|
||||
if (message && ws) {
|
||||
// Criar div para mensagem do usuário
|
||||
const userMessageDiv = document.createElement('div');
|
||||
userMessageDiv.className = 'message user-message';
|
||||
userMessageDiv.textContent = message;
|
||||
messagesDiv.appendChild(userMessageDiv);
|
||||
|
||||
// Enviar mensagem via WebSocket
|
||||
const messagePacket = {
|
||||
message: message
|
||||
};
|
||||
Reference in New Issue
Block a user