feat(api): remove outdated planning document and implement streaming service for real-time task updates

This commit is contained in:
Davidson Gomes
2025-04-29 20:35:33 -03:00
parent 34734b6da7
commit 690168fa5d
7 changed files with 793 additions and 225 deletions

295
static/test_a2a_stream.html Normal file
View 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>