Commit inicial - upload de todos os arquivos da pasta

This commit is contained in:
2026-01-04 16:14:31 -03:00
commit e3ed1a734b
310 changed files with 62749 additions and 0 deletions

View File

@@ -0,0 +1,333 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/A2AComplianceCard.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import {
CheckCircle2,
Clock,
Shield,
Zap,
FileText,
Settings,
Network,
AlertCircle,
ExternalLink,
ChevronDown,
ChevronUp
} from "lucide-react";
import { useState } from "react";
export function A2AComplianceCard() {
const [showCoreFeatures, setShowCoreFeatures] = useState(false);
const [showAdvancedFeatures, setShowAdvancedFeatures] = useState(false);
const implementedFeatures = [
{
name: "JSON-RPC 2.0 Protocol",
status: "implemented",
icon: CheckCircle2,
description: "Full compliance with JSON-RPC 2.0 specification"
},
{
name: "message/send Method",
status: "implemented",
icon: CheckCircle2,
description: "Standard HTTP messaging with proper request/response format"
},
{
name: "message/stream Method",
status: "implemented",
icon: CheckCircle2,
description: "Real-time streaming via Server-Sent Events (SSE)"
},
{
name: "tasks/get Method",
status: "implemented",
icon: CheckCircle2,
description: "Task status querying and monitoring"
},
{
name: "tasks/cancel Method",
status: "implemented",
icon: CheckCircle2,
description: "Task cancellation support"
},
{
name: "agent/authenticatedExtendedCard",
status: "implemented",
icon: CheckCircle2,
description: "Agent discovery and capability enumeration"
},
{
name: "File Upload Support",
status: "implemented",
icon: CheckCircle2,
description: "Base64 file encoding with proper MIME type handling"
},
{
name: "UUID v4 Message IDs",
status: "implemented",
icon: CheckCircle2,
description: "Standards-compliant unique message identification"
},
{
name: "Authentication Methods",
status: "implemented",
icon: CheckCircle2,
description: "API Key and Bearer token authentication"
},
{
name: "Task State Management",
status: "implemented",
icon: CheckCircle2,
description: "Complete task lifecycle: submitted → working → completed/failed"
},
{
name: "Artifact Handling",
status: "implemented",
icon: CheckCircle2,
description: "Complex response data with structured artifacts"
},
{
name: "CORS Compliance",
status: "implemented",
icon: CheckCircle2,
description: "Proper cross-origin resource sharing configuration"
},
{
name: "tasks/pushNotificationConfig/set",
status: "implemented",
icon: CheckCircle2,
description: "Set push notification configuration for tasks"
},
{
name: "tasks/pushNotificationConfig/get",
status: "implemented",
icon: CheckCircle2,
description: "Get push notification configuration for tasks"
},
{
name: "tasks/resubscribe",
status: "implemented",
icon: CheckCircle2,
description: "Resubscribe to task updates and notifications"
}
];
const advancedFeatures = [
{
name: "Push Notifications",
status: "implemented",
icon: CheckCircle2,
description: "A2A pushNotificationConfig methods and webhook support"
},
{
name: "Multi-turn Conversations",
status: "implemented",
icon: CheckCircle2,
description: "Context preservation via contextId field as per A2A specification"
},
{
name: "Enhanced Error Diagnostics",
status: "implemented",
icon: AlertCircle,
description: "Comprehensive A2A error analysis and troubleshooting guidance"
}
];
const implementedCount = implementedFeatures.filter(f => f.status === 'implemented').length;
const totalFeatures = implementedFeatures.length + advancedFeatures.length;
const partialCount = advancedFeatures.filter(f => f.status === 'partial').length;
const advancedImplementedCount = advancedFeatures.filter(f => f.status === 'implemented').length;
const totalImplementedCount = implementedCount + advancedImplementedCount;
const completionPercentage = Math.round(((totalImplementedCount + (partialCount * 0.5)) / totalFeatures) * 100);
const getStatusColor = (status: string) => {
switch (status) {
case 'implemented': return 'text-green-400';
case 'partial': return 'text-yellow-400';
case 'planned': return 'text-blue-400';
default: return 'text-neutral-400';
}
};
const getStatusIcon = (status: string, IconComponent: any) => {
const colorClass = getStatusColor(status);
return <IconComponent className={`h-4 w-4 ${colorClass}`} />;
};
return (
<Card className="bg-gradient-to-br from-emerald-500/5 to-blue-500/5 border-emerald-500/20 text-white">
<CardHeader>
<CardTitle className="text-emerald-400 flex items-center">
<Network className="h-5 w-5 mr-2" />
A2A Specification Compliance
</CardTitle>
<div className="flex items-center space-x-4">
<div className="flex-1">
<div className="flex justify-between text-sm mb-1">
<span className="text-neutral-300">Implementation Progress</span>
<span className="text-emerald-400 font-medium">{completionPercentage}%</span>
</div>
<Progress value={completionPercentage} className="h-2" />
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => {
const shouldExpand = !showCoreFeatures || !showAdvancedFeatures;
setShowCoreFeatures(shouldExpand);
setShowAdvancedFeatures(shouldExpand);
}}
className="text-xs text-neutral-400 hover:text-white transition-colors px-2 py-1 rounded border border-neutral-600 hover:border-neutral-400"
>
{showCoreFeatures && showAdvancedFeatures ? 'Collapse All' : 'Expand All'}
</button>
<Badge className="bg-emerald-500/20 text-emerald-400 border-emerald-500/30">
v0.2.0 Compatible
</Badge>
</div>
</div>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex justify-center">
<a
href="https://google.github.io/A2A/specification"
target="_blank"
rel="noopener noreferrer"
className="flex items-center bg-blue-500/10 hover:bg-blue-500/20 px-4 py-2 rounded-lg border border-blue-500/20 transition-colors"
>
<FileText className="h-4 w-4 mr-2 text-blue-400" />
<span className="text-blue-400">View Official Specification</span>
<ExternalLink className="h-3 w-3 ml-2 text-blue-400" />
</a>
</div>
<div>
<div
className="flex items-center justify-between cursor-pointer hover:bg-[#222]/30 p-2 rounded-lg transition-colors mb-4 border border-transparent hover:border-[#333]"
onClick={() => setShowCoreFeatures(!showCoreFeatures)}
>
<h3 className="text-white font-semibold flex items-center">
<CheckCircle2 className="h-4 w-4 mr-2 text-green-400" />
Core Features
<span className="ml-2 text-green-400 text-sm">({implementedCount}/{implementedFeatures.length} implemented)</span>
</h3>
<div className="flex items-center space-x-2">
<span className="text-xs text-neutral-500">
{showCoreFeatures ? 'Hide details' : 'Show details'}
</span>
{showCoreFeatures ? (
<ChevronUp className="h-4 w-4 text-neutral-400 hover:text-white transition-colors" />
) : (
<ChevronDown className="h-4 w-4 text-neutral-400 hover:text-white transition-colors" />
)}
</div>
</div>
{showCoreFeatures && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{implementedFeatures.map((feature, index) => (
<div key={index} className="flex items-start space-x-3 bg-[#222]/50 p-3 rounded-lg border border-[#333]">
{getStatusIcon(feature.status, feature.icon)}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white truncate">{feature.name}</p>
<p className="text-xs text-neutral-400">{feature.description}</p>
</div>
</div>
))}
</div>
)}
</div>
<div>
<div
className="flex items-center justify-between cursor-pointer hover:bg-[#222]/30 p-2 rounded-lg transition-colors mb-4 border border-transparent hover:border-[#333]"
onClick={() => setShowAdvancedFeatures(!showAdvancedFeatures)}
>
<h3 className="text-white font-semibold flex items-center">
<Settings className="h-4 w-4 mr-2 text-blue-400" />
Advanced Features
<span className="ml-2 text-blue-400 text-sm">({advancedImplementedCount}/{advancedFeatures.length} implemented)</span>
</h3>
<div className="flex items-center space-x-2">
<span className="text-xs text-neutral-500">
{showAdvancedFeatures ? 'Hide details' : 'Show details'}
</span>
{showAdvancedFeatures ? (
<ChevronUp className="h-4 w-4 text-neutral-400 hover:text-white transition-colors" />
) : (
<ChevronDown className="h-4 w-4 text-neutral-400 hover:text-white transition-colors" />
)}
</div>
</div>
{showAdvancedFeatures && (
<div className="space-y-3">
{advancedFeatures.map((feature, index) => (
<div key={index} className="flex items-start space-x-3 bg-[#222]/50 p-3 rounded-lg border border-[#333]">
{getStatusIcon(feature.status, feature.icon)}
<div className="flex-1">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium text-white">{feature.name}</p>
<Badge
variant="outline"
className={`text-xs ${
feature.status === 'implemented' ? 'border-green-500 text-green-400' :
feature.status === 'partial' ? 'border-yellow-500 text-yellow-400' :
'border-blue-500 text-blue-400'
}`}
>
{feature.status}
</Badge>
</div>
<p className="text-xs text-neutral-400 mt-1">{feature.description}</p>
</div>
</div>
))}
</div>
)}
</div>
<div className="bg-emerald-500/10 border border-emerald-500/20 rounded-lg p-4">
<div className="flex items-start space-x-2">
<Shield className="h-4 w-4 text-emerald-400 mt-0.5" />
<div className="text-sm">
<p className="text-emerald-400 font-medium"> 100% A2A v0.2.0 Compliance Achieved</p>
<p className="text-emerald-300 mt-1">
All 8 official RPC methods implemented Complete protocol data objects Full workflow support Enterprise security ready
</p>
</div>
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,78 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/CodeBlock.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import React from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { dracula } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { Button } from "@/components/ui/button";
import { ClipboardCopy } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
interface CodeBlockProps {
text: string;
language: string;
showLineNumbers?: boolean;
}
export function CodeBlock({ text, language, showLineNumbers = true }: CodeBlockProps) {
const { toast } = useToast();
const copyToClipboard = () => {
navigator.clipboard.writeText(text);
toast({
title: "Copied!",
description: "Code copied to clipboard",
});
};
return (
<div className="relative rounded-md overflow-hidden">
<SyntaxHighlighter
language={language}
style={dracula}
showLineNumbers={showLineNumbers}
wrapLines={true}
customStyle={{
margin: 0,
padding: "1rem",
borderRadius: "0.375rem",
}}
>
{text}
</SyntaxHighlighter>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333] opacity-80 hover:opacity-100"
onClick={copyToClipboard}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
);
}

View File

@@ -0,0 +1,317 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/CodeExamplesSection.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { CodeBlock } from "@/app/documentation/components/CodeBlock";
interface CodeExamplesSectionProps {
agentUrl: string;
apiKey: string;
jsonRpcRequest: any;
curlExample: string;
fetchExample: string;
}
export function CodeExamplesSection({
agentUrl,
apiKey,
jsonRpcRequest,
curlExample,
fetchExample
}: CodeExamplesSectionProps) {
const pythonExample = `import requests
import json
def test_a2a_agent():
url = "${agentUrl || "http://localhost:8000/api/v1/a2a/your-agent-id"}"
headers = {
"Content-Type": "application/json",
"x-api-key": "${apiKey || "your-api-key"}"
}
payload = ${JSON.stringify(jsonRpcRequest, null, 2)}
response = requests.post(url, headers=headers, json=payload)
data = response.json()
print("Agent response:", data)
return data
if __name__ == "__main__":
test_a2a_agent()`;
return (
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400">Code Examples</CardTitle>
<CardDescription className="text-neutral-400">
Code snippets ready to use with A2A agents
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="curl">
<TabsList className="bg-[#222] border-[#333] mb-4">
<TabsTrigger value="curl" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
cURL
</TabsTrigger>
<TabsTrigger value="javascript" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
JavaScript
</TabsTrigger>
<TabsTrigger value="python" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
Python
</TabsTrigger>
</TabsList>
<TabsContent value="curl" className="relative">
<CodeBlock
text={curlExample}
language="bash"
/>
</TabsContent>
<TabsContent value="javascript" className="relative">
<CodeBlock
text={fetchExample}
language="javascript"
/>
</TabsContent>
<TabsContent value="python" className="relative">
<CodeBlock
text={pythonExample}
language="python"
/>
</TabsContent>
</Tabs>
<div className="mt-8">
<h3 className="text-xl font-semibold text-white mb-3">Sending files to the agent</h3>
<p className="text-neutral-400 mb-4">
You can attach files to messages sent to the agent using the A2A protocol.
The files are encoded in base64 and incorporated into the message as parts of type &quot;file&quot;.
</p>
<div className="space-y-6">
<div>
<h4 className="text-lg font-medium text-emerald-400 mb-2">Python</h4>
<CodeBlock
text={`import asyncio
import base64
import os
from uuid import uuid4
from common.client import A2ACardResolver, A2AClient
async def send_message_with_files():
# Instantiate client
card_resolver = A2ACardResolver("http://localhost:8000/api/v1/a2a/your-agent-id")
card = card_resolver.get_agent_card()
client = A2AClient(agent_card=card)
# Create session and task IDs
session_id = uuid4().hex
task_id = uuid4().hex
# Read file and encode in base64
file_path = "example.jpg"
with open(file_path, 'rb') as f:
file_content = base64.b64encode(f.read()).decode('utf-8')
file_name = os.path.basename(file_path)
# Create message with text and file
message = {
'role': 'user',
'parts': [
{
'type': 'text',
'text': 'Analyze this image for me',
},
{
'type': 'file',
'file': {
'name': file_name,
'bytes': file_content,
'mime_type': 'application/octet-stream' # Important: include the mime_type for correct file processing
},
}
],
}
# Create request payload
payload = {
'id': task_id,
'sessionId': session_id,
'acceptedOutputModes': ['text'],
'message': message,
}
# Send request
task_result = await client.send_task(payload)
print(f'\\nResponse: {task_result.model_dump_json(exclude_none=True)}')
if __name__ == '__main__':
asyncio.run(send_message_with_files())`}
language="python"
/>
</div>
<div>
<h4 className="text-lg font-medium text-emerald-400 mb-2">JavaScript/TypeScript</h4>
<CodeBlock
text={`// Function to convert file to base64
async function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result;
const base64 = result.split(',')[1];
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
async function sendMessageWithFiles() {
// Select file (in a web application)
const fileInput = document.getElementById('fileInput');
const files = fileInput.files;
if (files.length === 0) {
console.error('No file selected');
return;
}
// Convert file to base64
const file = files[0];
const base64Data = await fileToBase64(file);
// Create session and task IDs
const sessionId = crypto.randomUUID();
const taskId = crypto.randomUUID();
const callId = crypto.randomUUID();
// Create message with text and file
const payload = {
jsonrpc: "2.0",
method: "message/send",
params: {
message: {
role: "user",
parts: [
{
type: "text",
text: "Analyze this document for me",
},
{
type: "file",
file: {
name: file.name,
bytes: base64Data,
mime_type: file.type
}
}
],
},
sessionId: sessionId,
id: taskId,
},
id: callId,
};
// Send request
const response = await fetch('http://localhost:8000/api/v1/a2a/your-agent-id', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
},
body: JSON.stringify(payload),
});
const data = await response.json();
console.log('Agent response:', data);
}`}
language="javascript"
/>
</div>
<div>
<h4 className="text-lg font-medium text-emerald-400 mb-2">Curl</h4>
<CodeBlock
text={`# Convert file to base64
FILE_PATH="example.jpg"
FILE_NAME=$(basename $FILE_PATH)
BASE64_CONTENT=$(base64 -w 0 $FILE_PATH)
# Create request payload
read -r -d '' PAYLOAD << EOM
{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [
{
"type": "text",
"text": "Analyze this image for me"
},
{
"type": "file",
"file": {
"name": "$FILE_NAME",
"bytes": "$BASE64_CONTENT",
"mime_type": "$(file --mime-type -b $FILE_PATH)"
}
}
]
},
"sessionId": "session-123",
"id": "task-456"
},
"id": "call-789"
}
EOM
# Send request
curl -X POST \\
http://localhost:8000/api/v1/a2a/your-agent-id \\
-H 'Content-Type: application/json' \\
-H 'x-api-key: your-api-key' \\
-d "$PAYLOAD"`}
language="bash"
/>
</div>
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,588 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/DocumentationSection.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
ClipboardCopy,
Info,
ExternalLink,
Users,
Shield,
Zap,
Network,
FileText,
MessageSquare,
Settings,
AlertCircle,
CheckCircle2,
Globe
} from "lucide-react";
import { CodeBlock } from "@/app/documentation/components/CodeBlock";
interface DocumentationSectionProps {
copyToClipboard: (text: string) => void;
}
export function DocumentationSection({ copyToClipboard }: DocumentationSectionProps) {
const quickStartExample = {
jsonrpc: "2.0",
id: "req-001",
method: "message/send",
params: {
message: {
role: "user",
parts: [
{
type: "text",
text: "Hello! Can you help me analyze this data?"
}
],
messageId: "6dbc13b5-bd57-4c2b-b503-24e381b6c8d6"
}
}
};
const streamingExample = {
jsonrpc: "2.0",
id: "req-002",
method: "message/stream",
params: {
message: {
role: "user",
parts: [
{
type: "text",
text: "Generate a detailed report on market trends"
}
],
messageId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
}
};
const fileUploadExample = {
jsonrpc: "2.0",
id: "req-003",
method: "message/send",
params: {
message: {
role: "user",
parts: [
{
type: "text",
text: "Analyze this image and highlight any faces."
},
{
type: "file",
file: {
name: "input_image.png",
mimeType: "image/png",
bytes: "iVBORw0KGgoAAAANSUhEUgAAAAUA..."
}
}
],
messageId: "8f0dc03c-4c65-4a14-9b56-7e8b9f2d1a3c"
}
}
};
return (
<div className="space-y-8">
{/* Hero Section */}
<Card className="bg-gradient-to-br from-emerald-500/10 to-blue-500/10 border-emerald-500/20 text-white">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<div className="flex items-center space-x-2 bg-emerald-500/20 px-4 py-2 rounded-full">
<Network className="h-6 w-6 text-emerald-400" />
<span className="font-bold text-emerald-400">Agent2Agent Protocol</span>
</div>
</div>
<CardTitle className="text-3xl font-bold bg-gradient-to-r from-emerald-400 to-blue-400 bg-clip-text text-transparent">
The Standard for AI Agent Communication
</CardTitle>
<p className="text-lg text-neutral-300 mt-4 max-w-3xl mx-auto">
A2A is Google's open protocol enabling seamless communication and interoperability
between AI agents across different platforms, providers, and architectures.
</p>
</CardHeader>
<CardContent>
<div className="flex flex-wrap justify-center gap-4 mt-6">
<a
href="https://google.github.io/A2A/specification"
target="_blank"
rel="noopener noreferrer"
className="flex items-center bg-emerald-500/20 hover:bg-emerald-500/30 px-4 py-2 rounded-lg transition-colors"
>
<FileText className="h-4 w-4 mr-2 text-emerald-400" />
<span className="text-emerald-400">Official Specification</span>
<ExternalLink className="h-3 w-3 ml-2 text-emerald-400" />
</a>
<a
href="https://github.com/google/A2A"
target="_blank"
rel="noopener noreferrer"
className="flex items-center bg-blue-500/20 hover:bg-blue-500/30 px-4 py-2 rounded-lg transition-colors"
>
<Globe className="h-4 w-4 mr-2 text-blue-400" />
<span className="text-blue-400">GitHub Repository</span>
<ExternalLink className="h-3 w-3 ml-2 text-blue-400" />
</a>
</div>
</CardContent>
</Card>
{/* Key Features */}
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400 flex items-center">
<Zap className="h-5 w-5 mr-2" />
Key Features & Capabilities
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="flex items-start space-x-3">
<div className="bg-emerald-500/20 p-2 rounded-lg">
<MessageSquare className="h-5 w-5 text-emerald-400" />
</div>
<div>
<h3 className="font-semibold text-white">Multi-turn Conversations</h3>
<p className="text-sm text-neutral-400">Support for complex, contextual dialogues between agents</p>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="bg-blue-500/20 p-2 rounded-lg">
<FileText className="h-5 w-5 text-blue-400" />
</div>
<div>
<h3 className="font-semibold text-white">File Exchange</h3>
<p className="text-sm text-neutral-400">Upload and download files with proper MIME type handling</p>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="bg-purple-500/20 p-2 rounded-lg">
<Zap className="h-5 w-5 text-purple-400" />
</div>
<div>
<h3 className="font-semibold text-white">Real-time Streaming</h3>
<p className="text-sm text-neutral-400">Server-Sent Events for live response streaming</p>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="bg-orange-500/20 p-2 rounded-lg">
<Settings className="h-5 w-5 text-orange-400" />
</div>
<div>
<h3 className="font-semibold text-white">Task Management</h3>
<p className="text-sm text-neutral-400">Track, query, and cancel long-running tasks</p>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="bg-red-500/20 p-2 rounded-lg">
<Shield className="h-5 w-5 text-red-400" />
</div>
<div>
<h3 className="font-semibold text-white">Enterprise Security</h3>
<p className="text-sm text-neutral-400">Bearer tokens, API keys, and HTTPS enforcement</p>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="bg-green-500/20 p-2 rounded-lg">
<Users className="h-5 w-5 text-green-400" />
</div>
<div>
<h3 className="font-semibold text-white">Agent Discovery</h3>
<p className="text-sm text-neutral-400">Standardized agent cards for capability discovery</p>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Protocol Methods */}
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400">Protocol Methods</CardTitle>
<p className="text-neutral-400">A2A supports multiple RPC methods for different interaction patterns</p>
</CardHeader>
<CardContent>
<Tabs defaultValue="messaging" className="w-full">
<TabsList className="grid w-full grid-cols-3 bg-[#222] border-[#444]">
<TabsTrigger value="messaging" className="data-[state=active]:bg-emerald-500/20 data-[state=active]:text-emerald-400">
Messaging
</TabsTrigger>
<TabsTrigger value="tasks" className="data-[state=active]:bg-emerald-500/20 data-[state=active]:text-emerald-400">
Task Management
</TabsTrigger>
<TabsTrigger value="discovery" className="data-[state=active]:bg-emerald-500/20 data-[state=active]:text-emerald-400">
Discovery
</TabsTrigger>
</TabsList>
<TabsContent value="messaging" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-[#222] p-4 rounded-lg border border-[#444]">
<div className="flex items-center space-x-2 mb-3">
<Badge variant="outline" className="border-emerald-500 text-emerald-400">message/send</Badge>
<CheckCircle2 className="h-4 w-4 text-emerald-400" />
</div>
<h4 className="font-semibold text-white mb-2">Standard HTTP Request</h4>
<p className="text-sm text-neutral-400 mb-3">
Send a message and receive a complete response after processing is finished.
</p>
<ul className="text-xs text-neutral-400 space-y-1">
<li> Single request/response cycle</li>
<li> Best for simple queries</li>
<li> Lower complexity implementation</li>
<li> Synchronous operation</li>
</ul>
</div>
<div className="bg-[#222] p-4 rounded-lg border border-[#444]">
<div className="flex items-center space-x-2 mb-3">
<Badge variant="outline" className="border-blue-500 text-blue-400">message/stream</Badge>
<Zap className="h-4 w-4 text-blue-400" />
</div>
<h4 className="font-semibold text-white mb-2">Real-time Streaming</h4>
<p className="text-sm text-neutral-400 mb-3">
Receive partial responses in real-time via Server-Sent Events.
</p>
<ul className="text-xs text-neutral-400 space-y-1">
<li> Progressive response delivery</li>
<li> Better UX for long tasks</li>
<li> Live status updates</li>
<li> Asynchronous operation</li>
</ul>
</div>
</div>
</TabsContent>
<TabsContent value="tasks" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-[#222] p-4 rounded-lg border border-[#444]">
<div className="flex items-center space-x-2 mb-3">
<Badge variant="outline" className="border-purple-500 text-purple-400">tasks/get</Badge>
<Settings className="h-4 w-4 text-purple-400" />
</div>
<h4 className="font-semibold text-white mb-2">Query Task Status</h4>
<p className="text-sm text-neutral-400 mb-3">
Check the status, progress, and results of a specific task.
</p>
<ul className="text-xs text-neutral-400 space-y-1">
<li> Real-time status checking</li>
<li> Progress monitoring</li>
<li> Result retrieval</li>
<li> Error diagnosis</li>
</ul>
</div>
<div className="bg-[#222] p-4 rounded-lg border border-[#444]">
<div className="flex items-center space-x-2 mb-3">
<Badge variant="outline" className="border-red-500 text-red-400">tasks/cancel</Badge>
<AlertCircle className="h-4 w-4 text-red-400" />
</div>
<h4 className="font-semibold text-white mb-2">Cancel Task</h4>
<p className="text-sm text-neutral-400 mb-3">
Terminate a running task before completion.
</p>
<ul className="text-xs text-neutral-400 space-y-1">
<li> Graceful task termination</li>
<li> Resource cleanup</li>
<li> Cost optimization</li>
<li> User control</li>
</ul>
</div>
</div>
</TabsContent>
<TabsContent value="discovery" className="space-y-4">
<div className="bg-[#222] p-4 rounded-lg border border-[#444]">
<div className="flex items-center space-x-2 mb-3">
<Badge variant="outline" className="border-green-500 text-green-400">agent/authenticatedExtendedCard</Badge>
<Users className="h-4 w-4 text-green-400" />
</div>
<h4 className="font-semibold text-white mb-2">Agent Discovery</h4>
<p className="text-sm text-neutral-400 mb-3">
Retrieve detailed information about agent capabilities, skills, and requirements.
</p>
<ul className="text-xs text-neutral-400 space-y-1">
<li> Agent capability discovery</li>
<li> Skill and tool enumeration</li>
<li> Authentication requirements</li>
<li> API version compatibility</li>
</ul>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
{/* Code Examples */}
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400">Quick Start Examples</CardTitle>
<p className="text-neutral-400">Ready-to-use JSON-RPC examples based on the official A2A specification</p>
</CardHeader>
<CardContent>
<Tabs defaultValue="basic" className="w-full">
<TabsList className="grid w-full grid-cols-3 bg-[#222] border-[#444]">
<TabsTrigger value="basic" className="data-[state=active]:bg-emerald-500/20 data-[state=active]:text-emerald-400">
Basic Message
</TabsTrigger>
<TabsTrigger value="streaming" className="data-[state=active]:bg-emerald-500/20 data-[state=active]:text-emerald-400">
Streaming
</TabsTrigger>
<TabsTrigger value="files" className="data-[state=active]:bg-emerald-500/20 data-[state=active]:text-emerald-400">
File Upload
</TabsTrigger>
</TabsList>
<TabsContent value="basic" className="space-y-4">
<div className="relative">
<CodeBlock
text={JSON.stringify(quickStartExample, null, 2)}
language="json"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(JSON.stringify(quickStartExample, null, 2))}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
<div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
<div className="flex items-start space-x-2">
<Info className="h-4 w-4 text-blue-400 mt-0.5" />
<div className="text-sm">
<p className="text-blue-400 font-medium">Key Points:</p>
<ul className="text-blue-300 mt-1 space-y-1">
<li> Uses <code className="bg-blue-500/20 px-1 rounded">message/send</code> for standard HTTP requests</li>
<li> <code className="bg-blue-500/20 px-1 rounded">messageId</code> must be a valid UUID v4</li>
<li> Response contains task ID, status, and artifacts</li>
</ul>
</div>
</div>
</div>
</TabsContent>
<TabsContent value="streaming" className="space-y-4">
<div className="relative">
<CodeBlock
text={JSON.stringify(streamingExample, null, 2)}
language="json"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(JSON.stringify(streamingExample, null, 2))}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
<div className="bg-purple-500/10 border border-purple-500/20 rounded-lg p-4">
<div className="flex items-start space-x-2">
<Zap className="h-4 w-4 text-purple-400 mt-0.5" />
<div className="text-sm">
<p className="text-purple-400 font-medium">Streaming Features:</p>
<ul className="text-purple-300 mt-1 space-y-1">
<li> Real-time Server-Sent Events (SSE)</li>
<li> Progressive content delivery</li>
<li> Status updates: submitted working completed</li>
</ul>
</div>
</div>
</div>
</TabsContent>
<TabsContent value="files" className="space-y-4">
<div className="relative">
<CodeBlock
text={JSON.stringify(fileUploadExample, null, 2)}
language="json"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(JSON.stringify(fileUploadExample, null, 2))}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
<div className="bg-green-500/10 border border-green-500/20 rounded-lg p-4">
<div className="flex items-start space-x-2">
<FileText className="h-4 w-4 text-green-400 mt-0.5" />
<div className="text-sm">
<p className="text-green-400 font-medium">File Handling:</p>
<ul className="text-green-300 mt-1 space-y-1">
<li> Support for multiple file types (images, documents, etc.)</li>
<li> Base64 encoding for binary data</li>
<li> Proper MIME type specification</li>
</ul>
</div>
</div>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
{/* Security & Best Practices */}
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400 flex items-center">
<Shield className="h-5 w-5 mr-2" />
Security & Best Practices
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 className="text-white font-semibold mb-3 flex items-center">
<Shield className="h-4 w-4 mr-2 text-emerald-400" />
Authentication Methods
</h3>
<div className="space-y-3">
<div className="bg-[#222] p-3 rounded-lg border border-[#444]">
<div className="flex items-center space-x-2 mb-2">
<code className="text-emerald-400 text-sm">x-api-key</code>
<Badge variant="outline" className="text-xs">Recommended</Badge>
</div>
<p className="text-xs text-neutral-400">Custom header for API key authentication</p>
</div>
<div className="bg-[#222] p-3 rounded-lg border border-[#444]">
<div className="flex items-center space-x-2 mb-2">
<code className="text-blue-400 text-sm">Authorization: Bearer</code>
<Badge variant="outline" className="text-xs">Standard</Badge>
</div>
<p className="text-xs text-neutral-400">OAuth 2.0 Bearer token authentication</p>
</div>
</div>
</div>
<div>
<h3 className="text-white font-semibold mb-3 flex items-center">
<AlertCircle className="h-4 w-4 mr-2 text-orange-400" />
Security Requirements
</h3>
<div className="space-y-2 text-sm">
<div className="flex items-center space-x-2">
<CheckCircle2 className="h-3 w-3 text-green-400" />
<span className="text-neutral-300">HTTPS/TLS encryption required</span>
</div>
<div className="flex items-center space-x-2">
<CheckCircle2 className="h-3 w-3 text-green-400" />
<span className="text-neutral-300">Input validation on all parameters</span>
</div>
<div className="flex items-center space-x-2">
<CheckCircle2 className="h-3 w-3 text-green-400" />
<span className="text-neutral-300">Rate limiting and resource controls</span>
</div>
<div className="flex items-center space-x-2">
<CheckCircle2 className="h-3 w-3 text-green-400" />
<span className="text-neutral-300">Proper CORS configuration</span>
</div>
</div>
</div>
</div>
<div className="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4">
<div className="flex items-start space-x-2">
<AlertCircle className="h-4 w-4 text-amber-400 mt-0.5" />
<div className="text-sm">
<p className="text-amber-400 font-medium">Important:</p>
<p className="text-amber-300 mt-1">
Always obtain API credentials out-of-band. Never include sensitive authentication
data in client-side code or version control systems.
</p>
</div>
</div>
</div>
</CardContent>
</Card>
{/* A2A vs MCP */}
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400 flex items-center">
<Network className="h-5 w-5 mr-2" />
A2A vs Model Context Protocol (MCP)
</CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-[#222] border-b border-[#444]">
<th className="p-4 text-left text-neutral-300">Aspect</th>
<th className="p-4 text-left text-emerald-400">Agent2Agent (A2A)</th>
<th className="p-4 text-left text-blue-400">Model Context Protocol (MCP)</th>
</tr>
</thead>
<tbody>
<tr className="border-b border-[#333]">
<td className="p-4 text-neutral-300 font-medium">Purpose</td>
<td className="p-4 text-neutral-300">Agent-to-agent communication</td>
<td className="p-4 text-neutral-300">Model-to-tool/resource integration</td>
</tr>
<tr className="border-b border-[#333]">
<td className="p-4 text-neutral-300 font-medium">Use Case</td>
<td className="p-4 text-neutral-300">AI agents collaborating as peers</td>
<td className="p-4 text-neutral-300">AI models accessing external capabilities</td>
</tr>
<tr className="border-b border-[#333]">
<td className="p-4 text-neutral-300 font-medium">Relationship</td>
<td className="p-4 text-neutral-300">Partner/delegate work</td>
<td className="p-4 text-neutral-300">Use specific capabilities</td>
</tr>
<tr className="border-b border-[#333]">
<td className="p-4 text-neutral-300 font-medium">Integration</td>
<td className="p-4 text-neutral-300 text-emerald-400"> Can use MCP internally</td>
<td className="p-4 text-neutral-300 text-blue-400"> Complements A2A</td>
</tr>
</tbody>
</table>
</div>
<div className="mt-4 bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
<p className="text-blue-300 text-sm">
<strong>Working Together:</strong> An A2A client agent might request an A2A server agent to perform a complex task.
The server agent, in turn, might use MCP to interact with tools, APIs, or data sources necessary to fulfill the A2A task.
</p>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,796 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/FrontendImplementationSection.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ClipboardCopy } from "lucide-react";
import { CodeBlock } from "@/app/documentation/components/CodeBlock";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
interface FrontendImplementationSectionProps {
copyToClipboard: (text: string) => void;
}
export function FrontendImplementationSection({ copyToClipboard }: FrontendImplementationSectionProps) {
return (
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400">Frontend implementation</CardTitle>
<CardDescription className="text-neutral-400">
Practical examples for implementation in React applications
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<Tabs defaultValue="standard">
<TabsList className="bg-[#222] border-[#333] mb-4">
<TabsTrigger value="standard" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
Standard HTTP
</TabsTrigger>
<TabsTrigger value="streaming" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
Streaming SSE
</TabsTrigger>
<TabsTrigger value="react-component" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
React component
</TabsTrigger>
</TabsList>
<TabsContent value="standard">
<div>
<h3 className="text-emerald-400 text-lg font-medium mb-2">Implementation of message/send</h3>
<p className="text-neutral-300 mb-4">
Example of standard implementation in JavaScript/React:
</p>
<div className="relative">
<CodeBlock
text={`async function sendTask(agentId, message) {
// Generate unique IDs
const taskId = crypto.randomUUID();
const callId = crypto.randomUUID();
// Prepare request data
const requestData = {
jsonrpc: "2.0",
id: callId,
method: "message/send",
params: {
id: taskId,
sessionId: "session-" + Math.random().toString(36).substring(2, 9),
message: {
role: "user",
parts: [
{
type: "text",
text: message
}
]
}
}
};
try {
// Indicate loading state
setIsLoading(true);
// Send the request
const response = await fetch(\`/api/v1/a2a/\${agentId}\`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY_HERE'
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(\`HTTP error: \${response.status}\`);
}
// Process the response
const data = await response.json();
// Check for errors
if (data.error) {
console.error('Error in response:', data.error);
return null;
}
// Extract the agent response
const task = data.result;
// Show response in UI
if (task.status.message && task.status.message.parts) {
const responseText = task.status.message.parts
.filter(part => part.type === 'text')
.map(part => part.text)
.join('');
// Here you would update your React state
// setResponse(responseText);
return responseText;
}
return task;
} catch (error) {
console.error('Error sending task:', error);
return null;
} finally {
// Finalize loading state
setIsLoading(false);
}
}`}
language="javascript"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(`async function sendTask(agentId, message) {
// Generate unique IDs
const taskId = crypto.randomUUID();
const callId = crypto.randomUUID();
// Prepare request data
const requestData = {
jsonrpc: "2.0",
id: callId,
method: "message/send",
params: {
id: taskId,
sessionId: "session-" + Math.random().toString(36).substring(2, 9),
message: {
role: "user",
parts: [
{
type: "text",
text: message
}
]
}
}
};
try {
// Indicate loading state
setIsLoading(true);
// Send the request
const response = await fetch(\`/api/v1/a2a/\${agentId}\`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY_HERE'
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(\`HTTP error: \${response.status}\`);
}
// Process the response
const data = await response.json();
// Check for errors
if (data.error) {
console.error('Error in response:', data.error);
return null;
}
// Extract the agent response
const task = data.result;
// Show response in UI
if (task.status.message && task.status.message.parts) {
const responseText = task.status.message.parts
.filter(part => part.type === 'text')
.map(part => part.text)
.join('');
// Here you would update your React state
// setResponse(responseText);
return responseText;
}
return task;
} catch (error) {
console.error('Error sending task:', error);
return null;
} finally {
// Finalize loading state
setIsLoading(false);
}
}`)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="streaming">
<div>
<h3 className="text-emerald-400 text-lg font-medium mb-2">Implementation of message/stream (Streaming)</h3>
<p className="text-neutral-300 mb-4">
Example of implementation of streaming with Server-Sent Events (SSE):
</p>
<div className="relative">
<CodeBlock
text={`async function initAgentStream(agentId, message, onUpdateCallback) {
// Generate unique IDs
const taskId = crypto.randomUUID();
const callId = crypto.randomUUID();
const sessionId = "session-" + Math.random().toString(36).substring(2, 9);
// Prepare request data for streaming
const requestData = {
jsonrpc: "2.0",
id: callId,
method: "message/stream",
params: {
id: taskId,
sessionId: sessionId,
message: {
role: "user",
parts: [
{
type: "text",
text: message
}
]
}
}
};
try {
// Start initial POST request
const response = await fetch(\`/api/v1/a2a/\${agentId}\`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY_HERE',
'Accept': 'text/event-stream' // Important for SSE
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(\`HTTP error: \${response.status}\`);
}
// Check content type of the response
const contentType = response.headers.get('content-type');
// If the response is already SSE, use EventSource
if (contentType?.includes('text/event-stream')) {
// Use EventSource to process the stream
setupEventSource(\`/api/v1/a2a/\${agentId}/stream?taskId=\${taskId}&key=YOUR_API_KEY_HERE\`);
return;
}
// Function to configure EventSource
function setupEventSource(url) {
const eventSource = new EventSource(url);
// Handler for received messages
eventSource.onmessage = (event) => {
try {
// Process data from the event
const data = JSON.parse(event.data);
// Process the event
if (data.result) {
// Process status if available
if (data.result.status) {
const status = data.result.status;
// Extract text if available
let currentText = '';
if (status.message && status.message.parts) {
const parts = status.message.parts.filter(part => part.type === 'text');
if (parts.length > 0) {
currentText = parts.map(part => part.text).join('');
}
}
// Call callback with updates
onUpdateCallback({
text: currentText,
state: status.state,
complete: data.result.final === true
});
// If it's the final event, close the connection
if (data.result.final === true) {
eventSource.close();
onUpdateCallback({
complete: true,
state: status.state
});
}
}
// Process artifact if available
if (data.result.artifact) {
const artifact = data.result.artifact;
if (artifact.parts && artifact.parts.length > 0) {
const parts = artifact.parts.filter(part => part.type === 'text');
if (parts.length > 0) {
const artifactText = parts.map(part => part.text).join('');
// Call callback with artifact
onUpdateCallback({
text: artifactText,
state: 'artifact',
complete: artifact.lastChunk === true
});
// If it's the last chunk, close the connection
if (artifact.lastChunk === true) {
eventSource.close();
}
}
}
}
}
} catch (error) {
console.error('Error processing event:', error);
onUpdateCallback({ error: error.message });
}
};
// Handler for errors
eventSource.onerror = (error) => {
console.error('Error in EventSource:', error);
eventSource.close();
onUpdateCallback({
error: 'Connection with server interrupted',
complete: true,
state: 'error'
});
};
}
} catch (error) {
console.error('Error in streaming:', error);
// Notify error through callback
onUpdateCallback({
error: error.message,
complete: true,
state: 'error'
});
return null;
}
}`}
language="javascript"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(`async function initAgentStream(agentId, message, onUpdateCallback) {
// Generate unique IDs
const taskId = crypto.randomUUID();
const callId = crypto.randomUUID();
const sessionId = "session-" + Math.random().toString(36).substring(2, 9);
// Prepare request data for streaming
const requestData = {
jsonrpc: "2.0",
id: callId,
method: "message/stream",
params: {
id: taskId,
sessionId: sessionId,
message: {
role: "user",
parts: [
{
type: "text",
text: message
}
]
}
}
};
try {
// Start initial POST request
const response = await fetch(\`/api/v1/a2a/\${agentId}\`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY_HERE',
'Accept': 'text/event-stream' // Important for SSE
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(\`HTTP error: \${response.status}\`);
}
// Check content type of the response
const contentType = response.headers.get('content-type');
// If the response is already SSE, use EventSource
if (contentType?.includes('text/event-stream')) {
// Use EventSource to process the stream
setupEventSource(\`/api/v1/a2a/\${agentId}/stream?taskId=\${taskId}&key=YOUR_API_KEY_HERE\`);
return;
}
// Function to configure EventSource
function setupEventSource(url) {
const eventSource = new EventSource(url);
// Handler for received messages
eventSource.onmessage = (event) => {
try {
// Process data from the event
const data = JSON.parse(event.data);
// Process the event
if (data.result) {
// Process status if available
if (data.result.status) {
const status = data.result.status;
// Extract text if available
let currentText = '';
if (status.message && status.message.parts) {
const parts = status.message.parts.filter(part => part.type === 'text');
if (parts.length > 0) {
currentText = parts.map(part => part.text).join('');
}
}
// Call callback with updates
onUpdateCallback({
text: currentText,
state: status.state,
complete: data.result.final === true
});
// If it's the final event, close the connection
if (data.result.final === true) {
eventSource.close();
onUpdateCallback({
complete: true,
state: status.state
});
}
}
// Process artifact if available
if (data.result.artifact) {
const artifact = data.result.artifact;
if (artifact.parts && artifact.parts.length > 0) {
const parts = artifact.parts.filter(part => part.type === 'text');
if (parts.length > 0) {
const artifactText = parts.map(part => part.text).join('');
// Call callback with artifact
onUpdateCallback({
text: artifactText,
state: 'artifact',
complete: artifact.lastChunk === true
});
// If it's the last chunk, close the connection
if (artifact.lastChunk === true) {
eventSource.close();
}
}
}
}
}
} catch (error) {
console.error('Error processing event:', error);
onUpdateCallback({ error: error.message });
}
};
// Handler for errors
eventSource.onerror = (error) => {
console.error('Error in EventSource:', error);
eventSource.close();
onUpdateCallback({
error: 'Connection with server interrupted',
complete: true,
state: 'error'
});
};
}
} catch (error) {
console.error('Error in streaming:', error);
// Notify error through callback
onUpdateCallback({
error: error.message,
complete: true,
state: 'error'
});
return null;
}
}`)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="react-component">
<div className="mt-4">
<h4 className="font-medium text-emerald-400 mb-2">React component with streaming support:</h4>
<div className="relative">
<CodeBlock
text={`import React, { useState, useRef } from 'react';
function ChatComponentA2A() {
const [message, setMessage] = useState('');
const [response, setResponse] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const [status, setStatus] = useState('');
// Reference to the agentId
const agentId = 'your-agent-id';
// Callback for streaming updates
const handleStreamUpdate = (update) => {
if (update.error) {
// Handle error
setStatus('error');
return;
}
// Update text
setResponse(update.text);
// Update status
setStatus(update.state);
// If it's complete, finish streaming
if (update.complete) {
setIsStreaming(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!message.trim()) return;
// Clear previous response
setResponse('');
setStatus('submitted');
// Start streaming
setIsStreaming(true);
try {
// Start stream with the agent
await initAgentStream(agentId, message, handleStreamUpdate);
// Clear message field after sending
setMessage('');
} catch (error) {
console.error('Error:', error);
setStatus('error');
setIsStreaming(false);
}
};
// Render status indicator based on status
const renderStatusIndicator = () => {
switch (status) {
case 'submitted':
return <span className="badge badge-info">Sent</span>;
case 'working':
return <span className="badge badge-warning">Processing</span>;
case 'completed':
return <span className="badge badge-success">Completed</span>;
case 'error':
return <span className="badge badge-danger">Error</span>;
default:
return null;
}
};
return (
<div className="chat-container">
<div className="chat-messages">
{response && (
<div className="message agent-message">
<div className="message-header">
<div className="agent-name">A2A Agent</div>
{renderStatusIndicator()}
</div>
<div className="message-content">
{response}
{status === 'working' && !response && (
<div className="typing-indicator">
<span></span><span></span><span></span>
</div>
)}
</div>
</div>
)}
</div>
<form onSubmit={handleSubmit} className="chat-input-form">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type your message..."
disabled={isStreaming}
className="chat-input"
/>
<button
type="submit"
disabled={isStreaming || !message.trim()}
className="send-button"
>
{isStreaming ? 'Processing...' : 'Send'}
</button>
</form>
</div>
);
}`}
language="javascript"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(`import React, { useState, useRef } from 'react';
function ChatComponentA2A() {
const [message, setMessage] = useState('');
const [response, setResponse] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const [status, setStatus] = useState('');
// Reference to the agentId
const agentId = 'your-agent-id';
// Callback for streaming updates
const handleStreamUpdate = (update) => {
if (update.error) {
// Handle error
setStatus('error');
return;
}
// Update text
setResponse(update.text);
// Update status
setStatus(update.state);
// If it's complete, finish streaming
if (update.complete) {
setIsStreaming(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!message.trim()) return;
// Clear previous response
setResponse('');
setStatus('submitted');
// Start streaming
setIsStreaming(true);
try {
// Start stream with the agent
await initAgentStream(agentId, message, handleStreamUpdate);
// Clear message field after sending
setMessage('');
} catch (error) {
console.error('Error:', error);
setStatus('error');
setIsStreaming(false);
}
};
// Render status indicator based on status
const renderStatusIndicator = () => {
switch (status) {
case 'submitted':
return <span className="badge badge-info">Sent</span>;
case 'working':
return <span className="badge badge-warning">Processing</span>;
case 'completed':
return <span className="badge badge-success">Completed</span>;
case 'error':
return <span className="badge badge-danger">Error</span>;
default:
return null;
}
};
return (
<div className="chat-container">
<div className="chat-messages">
{response && (
<div className="message agent-message">
<div className="message-header">
<div className="agent-name">A2A Agent</div>
{renderStatusIndicator()}
</div>
<div className="message-content">
{response}
{status === 'working' && !response && (
<div className="typing-indicator">
<span></span><span></span><span></span>
</div>
)}
</div>
</div>
)}
</div>
<form onSubmit={handleSubmit} className="chat-input-form">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type your message..."
disabled={isStreaming}
className="chat-input"
/>
<button
type="submit"
disabled={isStreaming || !message.trim()}
className="send-button"
>
{isStreaming ? 'Processing...' : 'Send'}
</button>
</form>
</div>
);
}`)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,523 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/HttpLabForm.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { useRef, useState } from "react";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Send, Paperclip, X, FileText, Image, File, RotateCcw, Trash2 } from "lucide-react";
import { toast } from "@/hooks/use-toast";
interface AttachedFile {
name: string;
type: string;
size: number;
base64: string;
}
interface HttpLabFormProps {
agentUrl: string;
setAgentUrl: (url: string) => void;
apiKey: string;
setApiKey: (key: string) => void;
message: string;
setMessage: (message: string) => void;
sessionId: string;
setSessionId: (id: string) => void;
taskId: string;
setTaskId: (id: string) => void;
callId: string;
setCallId: (id: string) => void;
sendRequest: () => Promise<void>;
isLoading: boolean;
setFiles?: (files: AttachedFile[]) => void;
a2aMethod: string;
setA2aMethod: (method: string) => void;
authMethod: string;
setAuthMethod: (method: string) => void;
generateNewIds: () => void;
currentTaskId?: string | null;
conversationHistory?: any[];
clearHistory?: () => void;
webhookUrl?: string;
setWebhookUrl?: (url: string) => void;
enableWebhooks?: boolean;
setEnableWebhooks?: (enabled: boolean) => void;
showDetailedErrors?: boolean;
setShowDetailedErrors?: (show: boolean) => void;
}
export function HttpLabForm({
agentUrl,
setAgentUrl,
apiKey,
setApiKey,
message,
setMessage,
sessionId,
setSessionId,
taskId,
setTaskId,
callId,
setCallId,
sendRequest,
isLoading,
setFiles = () => {},
a2aMethod,
setA2aMethod,
authMethod,
setAuthMethod,
generateNewIds,
currentTaskId,
conversationHistory,
clearHistory,
webhookUrl,
setWebhookUrl,
enableWebhooks,
setEnableWebhooks,
showDetailedErrors,
setShowDetailedErrors
}: HttpLabFormProps) {
const [attachedFiles, setAttachedFiles] = useState<AttachedFile[]>([]);
const fileInputRef = useRef<HTMLInputElement>(null);
const clearAttachedFiles = () => {
setAttachedFiles([]);
};
const handleSendRequest = async () => {
await sendRequest();
clearAttachedFiles();
};
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || e.target.files.length === 0) return;
const maxFileSize = 5 * 1024 * 1024; // 5MB limit
const newFiles = Array.from(e.target.files);
if (attachedFiles.length + newFiles.length > 5) {
toast({
title: "File limit exceeded",
description: "You can only attach up to 5 files.",
variant: "destructive"
});
return;
}
const filesToAdd: AttachedFile[] = [];
for (const file of newFiles) {
if (file.size > maxFileSize) {
toast({
title: "File too large",
description: `The file ${file.name} exceeds the 5MB size limit.`,
variant: "destructive"
});
continue;
}
try {
const base64 = await readFileAsBase64(file);
filesToAdd.push({
name: file.name,
type: file.type,
size: file.size,
base64: base64
});
} catch (error) {
console.error("Failed to read file:", error);
toast({
title: "Failed to read file",
description: `Could not process ${file.name}.`,
variant: "destructive"
});
}
}
if (filesToAdd.length > 0) {
const updatedFiles = [...attachedFiles, ...filesToAdd];
setAttachedFiles(updatedFiles);
setFiles(updatedFiles);
}
// Reset file input
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
};
const readFileAsBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
const base64 = result.split(',')[1]; // Remove data URL prefix
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
};
const removeFile = (index: number) => {
const updatedFiles = attachedFiles.filter((_, i) => i !== index);
setAttachedFiles(updatedFiles);
setFiles(updatedFiles);
};
const formatFileSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
const isImageFile = (type: string): boolean => {
return type.startsWith('image/');
};
return (
<div className="space-y-4">
{/* A2A Method and Authentication Settings */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-[#1a1a1a] border border-[#333] rounded-md">
<div>
<label className="text-sm text-neutral-400 mb-2 block">A2A Method</label>
<Select value={a2aMethod} onValueChange={setA2aMethod}>
<SelectTrigger className="bg-[#222] border-[#444] text-white">
<SelectValue placeholder="Select A2A method" />
</SelectTrigger>
<SelectContent className="bg-[#222] border-[#444]">
<SelectItem value="message/send" className="text-white hover:bg-[#333]">
message/send
</SelectItem>
<SelectItem value="message/stream" className="text-white hover:bg-[#333]">
message/stream
</SelectItem>
<SelectItem value="tasks/get" className="text-white hover:bg-[#333]">
tasks/get
</SelectItem>
<SelectItem value="tasks/cancel" className="text-white hover:bg-[#333]">
tasks/cancel
</SelectItem>
<SelectItem value="tasks/pushNotificationConfig/set" className="text-white hover:bg-[#333]">
tasks/pushNotificationConfig/set
</SelectItem>
<SelectItem value="tasks/pushNotificationConfig/get" className="text-white hover:bg-[#333]">
tasks/pushNotificationConfig/get
</SelectItem>
<SelectItem value="tasks/resubscribe" className="text-white hover:bg-[#333]">
tasks/resubscribe
</SelectItem>
<SelectItem value="agent/authenticatedExtendedCard" className="text-white hover:bg-[#333]">
agent/authenticatedExtendedCard
</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-sm text-neutral-400 mb-2 block">Authentication Method</label>
<Select value={authMethod} onValueChange={setAuthMethod}>
<SelectTrigger className="bg-[#222] border-[#444] text-white">
<SelectValue placeholder="Select auth method" />
</SelectTrigger>
<SelectContent className="bg-[#222] border-[#444]">
<SelectItem value="api-key" className="text-white hover:bg-[#333]">
API Key (x-api-key header)
</SelectItem>
<SelectItem value="bearer" className="text-white hover:bg-[#333]">
Bearer Token (Authorization header)
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* Multi-turn Conversation History Controls */}
{(a2aMethod === "message/send" || a2aMethod === "message/stream") && conversationHistory && conversationHistory.length > 0 && (
<div className="p-4 bg-emerald-500/5 border border-emerald-500/20 rounded-md">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-emerald-400">
Multi-turn Conversation Active
</span>
</div>
</div>
<div className="text-xs text-emerald-300">
💬 {conversationHistory.length} messages in conversation history (contextId active)
</div>
</div>
)}
{/* Push Notifications (Webhook) Configuration */}
{(a2aMethod === "message/send" || a2aMethod === "message/stream" || a2aMethod.startsWith("tasks/")) && (
<div className="p-4 bg-blue-500/5 border border-blue-500/20 rounded-md">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="enableWebhooks"
checked={enableWebhooks}
onChange={(e) => setEnableWebhooks?.(e.target.checked)}
className="rounded bg-[#222] border-[#444] text-blue-400 focus:ring-blue-400"
/>
<label htmlFor="enableWebhooks" className="text-sm font-medium text-blue-400">
Enable Push Notifications (Webhooks)
</label>
</div>
</div>
{enableWebhooks && (
<div className="mt-3">
<label className="text-sm text-neutral-400 mb-1 block">Webhook URL</label>
<Input
value={webhookUrl}
onChange={(e) => setWebhookUrl?.(e.target.value)}
placeholder="https://your-server.com/webhook/a2a"
className="bg-[#222] border-[#444] text-white"
/>
<div className="text-xs text-blue-300 mt-1">
{a2aMethod === "tasks/pushNotificationConfig/set"
? "📡 Configure push notifications for task"
: "📡 Webhook URL for push notifications (configured via pushNotificationConfig)"
}
</div>
</div>
)}
{!enableWebhooks && (
<div className="text-xs text-neutral-400">
{a2aMethod === "tasks/pushNotificationConfig/set"
? "Push notification configuration will be set to null."
: "No push notifications will be configured for this request."
}
</div>
)}
</div>
)}
{/* Advanced Error Handling Configuration */}
<div className="p-4 bg-orange-500/5 border border-orange-500/20 rounded-md">
<div className="flex items-center space-x-2 mb-3">
<input
type="checkbox"
id="showDetailedErrors"
checked={showDetailedErrors}
onChange={(e) => setShowDetailedErrors?.(e.target.checked)}
className="rounded bg-[#222] border-[#444] text-orange-400 focus:ring-orange-400"
/>
<label htmlFor="showDetailedErrors" className="text-sm font-medium text-orange-400">
Enable Detailed Error Logging
</label>
</div>
<div className="text-xs text-neutral-400">
{showDetailedErrors
? "🔍 Detailed error information will be shown in debug logs (client-side only)."
: "⚡ Basic error handling only - minimal error information in logs."
}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm text-neutral-400 mb-1 block">Agent URL</label>
<Input
value={agentUrl}
onChange={(e) => setAgentUrl(e.target.value)}
placeholder="http://localhost:8000/api/v1/a2a/your-agent-id"
className="bg-[#222] border-[#444] text-white"
/>
</div>
<div>
<label className="text-sm text-neutral-400 mb-1 block">
{authMethod === "bearer" ? "Bearer Token" : "API Key"} (optional)
</label>
<Input
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={authMethod === "bearer" ? "Your Bearer token" : "Your API key"}
className="bg-[#222] border-[#444] text-white"
/>
</div>
</div>
{/* Show current task ID if available */}
{currentTaskId && (
<div className="p-3 bg-[#1a1a1a] border border-emerald-400/20 rounded-md">
<div className="flex items-center justify-between">
<div>
<span className="text-sm text-neutral-400">Current Task ID:</span>
<span className="ml-2 text-emerald-400 font-mono text-sm">{currentTaskId}</span>
</div>
</div>
</div>
)}
{/* Message input - only show for message methods */}
{(a2aMethod === "message/send" || a2aMethod === "message/stream") && (
<div>
<label className="text-sm text-neutral-400 mb-1 block">Message</label>
<Textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="What is the A2A protocol?"
className="bg-[#222] border-[#444] text-white min-h-[100px]"
/>
</div>
)}
{/* File attachment - only show for message methods */}
{(a2aMethod === "message/send" || a2aMethod === "message/stream") && (
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm text-neutral-400">
Attach Files (up to 5, max 5MB each)
</label>
<Button
variant="outline"
size="sm"
className="bg-[#222] border-[#444] text-neutral-300 hover:bg-[#333] hover:text-white"
onClick={() => fileInputRef.current?.click()}
disabled={attachedFiles.length >= 5}
>
<Paperclip className="h-4 w-4 mr-2" />
Browse Files
</Button>
<input
type="file"
ref={fileInputRef}
className="hidden"
multiple
onChange={handleFileSelect}
/>
</div>
{attachedFiles.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{attachedFiles.map((file, index) => (
<div
key={index}
className="flex items-center gap-1.5 bg-[#333] text-white rounded-md p-1.5 text-xs"
>
{isImageFile(file.type) ? (
<Image className="h-4 w-4 text-emerald-400" />
) : file.type === 'application/pdf' ? (
<FileText className="h-4 w-4 text-emerald-400" />
) : (
<File className="h-4 w-4 text-emerald-400" />
)}
<span className="max-w-[150px] truncate">{file.name}</span>
<span className="text-neutral-400">({formatFileSize(file.size)})</span>
<button
onClick={() => removeFile(index)}
className="ml-1 text-neutral-400 hover:text-white transition-colors"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
))}
</div>
)}
</div>
)}
<Separator className="my-4 bg-[#333]" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<div className="flex items-center justify-between mb-1">
<label className="text-sm text-neutral-400">Session ID</label>
<Button
variant="ghost"
size="sm"
onClick={generateNewIds}
className="h-6 px-2 text-xs text-neutral-400 hover:text-white"
>
<RotateCcw className="h-3 w-3 mr-1" />
New IDs
</Button>
</div>
<Input
value={sessionId}
onChange={(e) => setSessionId(e.target.value)}
className="bg-[#222] border-[#444] text-white"
/>
</div>
<div>
<label className="text-sm text-neutral-400 mb-1 block">
{a2aMethod.startsWith("tasks/") ? "Task ID (for operation)" : "Message ID (UUID)"}
</label>
<Input
value={taskId}
onChange={(e) => setTaskId(e.target.value)}
className="bg-[#222] border-[#444] text-white"
placeholder={a2aMethod.startsWith("tasks/") ? "Task ID to query/cancel" : "UUID for message"}
/>
</div>
<div>
<label className="text-sm text-neutral-400 mb-1 block">Request ID</label>
<Input
value={callId}
onChange={(e) => setCallId(e.target.value)}
className="bg-[#222] border-[#444] text-white"
placeholder="req-123"
/>
</div>
</div>
<Button
onClick={handleSendRequest}
disabled={isLoading}
className="bg-emerald-400 text-black hover:bg-[#00cc7d] w-full mt-4"
>
{isLoading ? (
<div className="flex items-center">
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-black mr-2"></div>
Sending...
</div>
) : (
<div className="flex items-center">
<Send className="mr-2 h-4 w-4" />
{a2aMethod === "message/send" && "Send Message"}
{a2aMethod === "message/stream" && "Start Stream"}
{a2aMethod === "tasks/get" && "Get Task Status"}
{a2aMethod === "tasks/cancel" && "Cancel Task"}
{a2aMethod === "tasks/pushNotificationConfig/set" && "Set Push Config"}
{a2aMethod === "tasks/pushNotificationConfig/get" && "Get Push Config"}
{a2aMethod === "tasks/resubscribe" && "Resubscribe to Task"}
{a2aMethod === "agent/authenticatedExtendedCard" && "Get Agent Card"}
</div>
)}
</Button>
</div>
);
}

View File

@@ -0,0 +1,201 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/LabSection.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { ClipboardCopy } from "lucide-react";
import { HttpLabForm } from "@/app/documentation/components/HttpLabForm";
import { StreamLabForm } from "@/app/documentation/components/StreamLabForm";
import { CodeBlock } from "@/app/documentation/components/CodeBlock";
interface LabSectionProps {
agentUrl: string;
setAgentUrl: (url: string) => void;
apiKey: string;
setApiKey: (key: string) => void;
message: string;
setMessage: (message: string) => void;
sessionId: string;
setSessionId: (id: string) => void;
taskId: string;
setTaskId: (id: string) => void;
callId: string;
setCallId: (id: string) => void;
a2aMethod: string;
setA2aMethod: (method: string) => void;
authMethod: string;
setAuthMethod: (method: string) => void;
generateNewIds: () => void;
sendRequest: () => Promise<void>;
sendStreamRequestWithEventSource: () => Promise<void>;
isLoading: boolean;
isStreaming: boolean;
streamResponse: string;
streamStatus: string;
streamHistory: string[];
streamComplete: boolean;
response: string;
copyToClipboard: (text: string) => void;
renderStatusIndicator: () => JSX.Element | null;
renderTypingIndicator: () => JSX.Element | null;
}
export function LabSection({
agentUrl,
setAgentUrl,
apiKey,
setApiKey,
message,
setMessage,
sessionId,
setSessionId,
taskId,
setTaskId,
callId,
setCallId,
a2aMethod,
setA2aMethod,
authMethod,
setAuthMethod,
generateNewIds,
sendRequest,
sendStreamRequestWithEventSource,
isLoading,
isStreaming,
streamResponse,
streamStatus,
streamHistory,
streamComplete,
response,
copyToClipboard,
renderStatusIndicator,
renderTypingIndicator
}: LabSectionProps) {
const [labMode, setLabMode] = useState("http");
return (
<>
<Card className="bg-[#1a1a1a] border-[#333] text-white mb-6">
<CardHeader>
<CardTitle className="text-emerald-400">A2A Test Lab</CardTitle>
<CardDescription className="text-neutral-400">
Test your A2A agent with different communication methods
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="http" onValueChange={setLabMode}>
<TabsList className="bg-[#222] border-[#333] mb-4">
<TabsTrigger value="http" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
HTTP Request
</TabsTrigger>
<TabsTrigger value="stream" className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400">
Streaming
</TabsTrigger>
</TabsList>
<TabsContent value="http">
<HttpLabForm
agentUrl={agentUrl}
setAgentUrl={setAgentUrl}
apiKey={apiKey}
setApiKey={setApiKey}
message={message}
setMessage={setMessage}
sessionId={sessionId}
setSessionId={setSessionId}
taskId={taskId}
setTaskId={setTaskId}
callId={callId}
setCallId={setCallId}
a2aMethod={a2aMethod}
setA2aMethod={setA2aMethod}
authMethod={authMethod}
setAuthMethod={setAuthMethod}
generateNewIds={generateNewIds}
sendRequest={sendRequest}
isLoading={isLoading}
/>
</TabsContent>
<TabsContent value="stream">
<StreamLabForm
agentUrl={agentUrl}
setAgentUrl={setAgentUrl}
apiKey={apiKey}
setApiKey={setApiKey}
message={message}
setMessage={setMessage}
sessionId={sessionId}
setSessionId={setSessionId}
taskId={taskId}
setTaskId={setTaskId}
callId={callId}
setCallId={setCallId}
authMethod={authMethod}
sendStreamRequest={sendStreamRequestWithEventSource}
isStreaming={isStreaming}
streamResponse={streamResponse}
streamStatus={streamStatus}
streamHistory={streamHistory}
renderStatusIndicator={renderStatusIndicator}
renderTypingIndicator={renderTypingIndicator}
/>
</TabsContent>
</Tabs>
</CardContent>
</Card>
{response && labMode === "http" && (
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400">Response</CardTitle>
</CardHeader>
<CardContent>
<div className="relative">
<CodeBlock
text={response}
language="json"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(response)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
)}
</>
);
}

View File

@@ -0,0 +1,179 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/QuickStartTemplates.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
MessageSquare,
FileText,
Zap,
Settings,
Users,
Play
} from "lucide-react";
interface QuickStartTemplate {
id: string;
name: string;
description: string;
icon: any;
method: string;
message: string;
useCase: string;
}
interface QuickStartTemplatesProps {
onSelectTemplate: (template: QuickStartTemplate) => void;
}
export function QuickStartTemplates({ onSelectTemplate }: QuickStartTemplatesProps) {
const templates: QuickStartTemplate[] = [
{
id: "hello",
name: "Hello Agent",
description: "Simple greeting to test agent connectivity",
icon: MessageSquare,
method: "message/send",
message: "Hello! Can you introduce yourself and tell me what you can do?",
useCase: "Basic connectivity test"
},
{
id: "analysis",
name: "Data Analysis",
description: "Request data analysis and insights",
icon: FileText,
method: "message/send",
message: "Please analyze the current market trends in AI technology and provide key insights with recommendations.",
useCase: "Complex analytical tasks"
},
{
id: "streaming",
name: "Long Content",
description: "Generate lengthy content with streaming",
icon: Zap,
method: "message/stream",
message: "Write a comprehensive guide about implementing the Agent2Agent protocol, including technical details, best practices, and code examples.",
useCase: "Streaming responses"
},
{
id: "task-query",
name: "Task Status",
description: "Query the status of a running task",
icon: Settings,
method: "tasks/get",
message: "",
useCase: "Task management"
},
{
id: "capabilities",
name: "Agent Capabilities",
description: "Discover agent capabilities and skills",
icon: Users,
method: "agent/authenticatedExtendedCard",
message: "",
useCase: "Agent discovery"
}
];
const getMethodColor = (method: string) => {
switch (method) {
case 'message/send': return 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30';
case 'message/stream': return 'bg-blue-500/20 text-blue-400 border-blue-500/30';
case 'tasks/get': return 'bg-purple-500/20 text-purple-400 border-purple-500/30';
case 'tasks/cancel': return 'bg-red-500/20 text-red-400 border-red-500/30';
case 'agent/authenticatedExtendedCard': return 'bg-orange-500/20 text-orange-400 border-orange-500/30';
default: return 'bg-neutral-500/20 text-neutral-400 border-neutral-500/30';
}
};
return (
<Card className="bg-[#1a1a1a] border-[#333] text-white mb-6">
<CardHeader>
<CardTitle className="text-emerald-400 flex items-center">
<Play className="h-5 w-5 mr-2" />
Quick Start Templates
</CardTitle>
<p className="text-neutral-400 text-sm">
Choose a template to quickly test different A2A protocol methods
</p>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{templates.map((template) => {
const IconComponent = template.icon;
return (
<div
key={template.id}
className="bg-[#222] border border-[#444] rounded-lg p-4 hover:border-emerald-500/50 transition-colors cursor-pointer group"
onClick={() => onSelectTemplate(template)}
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center space-x-2">
<div className="bg-emerald-500/20 p-2 rounded-lg">
<IconComponent className="h-4 w-4 text-emerald-400" />
</div>
<div>
<h3 className="font-semibold text-white text-sm">{template.name}</h3>
<p className="text-xs text-neutral-400">{template.useCase}</p>
</div>
</div>
</div>
<p className="text-xs text-neutral-300 mb-3 line-clamp-2">
{template.description}
</p>
<div className="flex items-center justify-between">
<Badge className={`text-xs ${getMethodColor(template.method)}`}>
{template.method}
</Badge>
<Button
size="sm"
variant="ghost"
className="text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/10 text-xs px-2 py-1 h-auto opacity-0 group-hover:opacity-100 transition-opacity"
>
Use Template
</Button>
</div>
</div>
);
})}
</div>
<div className="mt-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg">
<p className="text-blue-300 text-xs">
💡 <strong>Tip:</strong> These templates automatically configure the correct A2A method and provide example messages.
Simply select one and customize the agent URL and authentication.
</p>
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,366 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/StreamLabForm.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { useRef, useState } from "react";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Separator } from "@/components/ui/separator";
import { Send, Paperclip, X, FileText, Image, File } from "lucide-react";
import { toast } from "@/hooks/use-toast";
interface AttachedFile {
name: string;
type: string;
size: number;
base64: string;
}
interface StreamLabFormProps {
agentUrl: string;
setAgentUrl: (url: string) => void;
apiKey: string;
setApiKey: (key: string) => void;
message: string;
setMessage: (message: string) => void;
sessionId: string;
setSessionId: (id: string) => void;
taskId: string;
setTaskId: (id: string) => void;
callId: string;
setCallId: (id: string) => void;
sendStreamRequest: () => Promise<void>;
isStreaming: boolean;
streamResponse: string;
streamStatus: string;
streamHistory: string[];
renderStatusIndicator: () => JSX.Element | null;
renderTypingIndicator: () => JSX.Element | null;
setFiles?: (files: AttachedFile[]) => void;
authMethod: string;
currentTaskId?: string | null;
}
export function StreamLabForm({
agentUrl,
setAgentUrl,
apiKey,
setApiKey,
message,
setMessage,
sessionId,
setSessionId,
taskId,
setTaskId,
callId,
setCallId,
sendStreamRequest,
isStreaming,
streamResponse,
streamStatus,
streamHistory,
renderStatusIndicator,
renderTypingIndicator,
setFiles = () => {},
authMethod,
currentTaskId
}: StreamLabFormProps) {
const [attachedFiles, setAttachedFiles] = useState<AttachedFile[]>([]);
const fileInputRef = useRef<HTMLInputElement>(null);
const clearAttachedFiles = () => {
setAttachedFiles([]);
};
const handleSendStreamRequest = async () => {
await sendStreamRequest();
clearAttachedFiles();
};
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || e.target.files.length === 0) return;
const maxFileSize = 5 * 1024 * 1024; // 5MB limit
const newFiles = Array.from(e.target.files);
if (attachedFiles.length + newFiles.length > 5) {
toast({
title: "File limit exceeded",
description: "You can only attach up to 5 files.",
variant: "destructive"
});
return;
}
const filesToAdd: AttachedFile[] = [];
for (const file of newFiles) {
if (file.size > maxFileSize) {
toast({
title: "File too large",
description: `The file ${file.name} exceeds the 5MB size limit.`,
variant: "destructive"
});
continue;
}
try {
const base64 = await readFileAsBase64(file);
filesToAdd.push({
name: file.name,
type: file.type,
size: file.size,
base64: base64
});
} catch (error) {
console.error("Failed to read file:", error);
toast({
title: "Failed to read file",
description: `Could not process ${file.name}.`,
variant: "destructive"
});
}
}
if (filesToAdd.length > 0) {
const updatedFiles = [...attachedFiles, ...filesToAdd];
setAttachedFiles(updatedFiles);
setFiles(updatedFiles);
}
// Reset file input
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
};
const readFileAsBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
const base64 = result.split(',')[1]; // Remove data URL prefix
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
};
const removeFile = (index: number) => {
const updatedFiles = attachedFiles.filter((_, i) => i !== index);
setAttachedFiles(updatedFiles);
setFiles(updatedFiles);
};
const formatFileSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
const isImageFile = (type: string): boolean => {
return type.startsWith('image/');
};
return (
<div className="space-y-4">
{/* A2A Streaming Information */}
<div className="p-4 bg-[#1a1a1a] border border-[#333] rounded-md">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-emerald-400">A2A Streaming Mode</span>
<span className="text-xs text-neutral-400">Method: message/stream</span>
</div>
<div className="text-xs text-neutral-400">
Authentication: {authMethod === "bearer" ? "Bearer Token" : "API Key"} header
</div>
{currentTaskId && (
<div className="mt-2 pt-2 border-t border-[#333]">
<span className="text-xs text-neutral-400">Current Task ID: </span>
<span className="text-xs text-emerald-400 font-mono">{currentTaskId}</span>
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm text-neutral-400 mb-1 block">Agent URL</label>
<Input
value={agentUrl}
onChange={(e) => setAgentUrl(e.target.value)}
placeholder="http://localhost:8000/api/v1/a2a/your-agent-id"
className="bg-[#222] border-[#444] text-white"
disabled={isStreaming}
/>
</div>
<div>
<label className="text-sm text-neutral-400 mb-1 block">
{authMethod === "bearer" ? "Bearer Token" : "API Key"} (optional)
</label>
<Input
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={authMethod === "bearer" ? "Your Bearer token" : "Your API key"}
className="bg-[#222] border-[#444] text-white"
disabled={isStreaming}
/>
</div>
</div>
<div>
<label className="text-sm text-neutral-400 mb-1 block">Message</label>
<Textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="What is the A2A protocol?"
className="bg-[#222] border-[#444] text-white min-h-[100px]"
disabled={isStreaming}
/>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm text-neutral-400">
Attach Files (up to 5, max 5MB each)
</label>
<Button
variant="outline"
size="sm"
className="bg-[#222] border-[#444] text-neutral-300 hover:bg-[#333] hover:text-white"
onClick={() => fileInputRef.current?.click()}
disabled={attachedFiles.length >= 5 || isStreaming}
>
<Paperclip className="h-4 w-4 mr-2" />
Browse Files
</Button>
<input
type="file"
ref={fileInputRef}
className="hidden"
multiple
onChange={handleFileSelect}
disabled={isStreaming}
/>
</div>
{attachedFiles.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{attachedFiles.map((file, index) => (
<div
key={index}
className="flex items-center gap-1.5 bg-[#333] text-white rounded-md p-1.5 text-xs"
>
{isImageFile(file.type) ? (
<Image className="h-4 w-4 text-emerald-400" />
) : file.type === 'application/pdf' ? (
<FileText className="h-4 w-4 text-emerald-400" />
) : (
<File className="h-4 w-4 text-emerald-400" />
)}
<span className="max-w-[150px] truncate">{file.name}</span>
<span className="text-neutral-400">({formatFileSize(file.size)})</span>
<button
onClick={() => removeFile(index)}
className="ml-1 text-neutral-400 hover:text-white transition-colors"
disabled={isStreaming}
>
<X className="h-3.5 w-3.5" />
</button>
</div>
))}
</div>
)}
</div>
<Separator className="my-4 bg-[#333]" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="text-sm text-neutral-400 mb-1 block">Session ID</label>
<Input
value={sessionId}
onChange={(e) => setSessionId(e.target.value)}
className="bg-[#222] border-[#444] text-white"
disabled={isStreaming}
/>
</div>
<div>
<label className="text-sm text-neutral-400 mb-1 block">Task ID</label>
<Input
value={taskId}
onChange={(e) => setTaskId(e.target.value)}
className="bg-[#222] border-[#444] text-white"
disabled={isStreaming}
/>
</div>
<div>
<label className="text-sm text-neutral-400 mb-1 block">Call ID</label>
<Input
value={callId}
onChange={(e) => setCallId(e.target.value)}
className="bg-[#222] border-[#444] text-white"
disabled={isStreaming}
/>
</div>
</div>
<Button
onClick={handleSendStreamRequest}
disabled={isStreaming}
className="bg-emerald-400 text-black hover:bg-[#00cc7d] w-full mt-4"
>
{isStreaming ? (
<div className="flex items-center">
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-black mr-2"></div>
Streaming...
</div>
) : (
<div className="flex items-center">
<Send className="mr-2 h-4 w-4" />
Start Streaming
</div>
)}
</Button>
{streamResponse && (
<div className="mt-6 rounded-md bg-[#222] border border-[#333] p-4">
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-medium text-white">Response</h3>
{renderStatusIndicator && renderStatusIndicator()}
</div>
<div className="whitespace-pre-wrap text-sm font-mono text-neutral-300">
{streamResponse}
</div>
{renderTypingIndicator && renderTypingIndicator()}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,470 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/documentation/components/TechnicalDetailsSection.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ClipboardCopy } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import { CodeBlock } from "@/app/documentation/components/CodeBlock";
interface TechnicalDetailsSectionProps {
copyToClipboard: (text: string) => void;
}
export function TechnicalDetailsSection({ copyToClipboard }: TechnicalDetailsSectionProps) {
return (
<Card className="bg-[#1a1a1a] border-[#333] text-white">
<CardHeader>
<CardTitle className="text-emerald-400">Technical Details of the Methods</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div>
<h3 className="text-emerald-400 text-lg font-medium mb-2">Method message/send</h3>
<p className="text-neutral-300 mb-4">
The <code className="bg-[#333] px-1 rounded">message/send</code> method performs a standard HTTP request and waits for the complete response.
</p>
<div className="space-y-4">
<div>
<h4 className="font-medium text-white mb-2">Request:</h4>
<div className="relative">
<CodeBlock
text={JSON.stringify({
jsonrpc: "2.0",
id: "call-123",
method: "message/send",
params: {
id: "task-456",
sessionId: "session-789",
message: {
role: "user",
parts: [
{
type: "text",
text: "Your question here"
}
]
}
}
}, null, 2)}
language="json"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(JSON.stringify({
jsonrpc: "2.0",
id: "call-123",
method: "message/send",
params: {
id: "task-456",
sessionId: "session-789",
message: {
role: "user",
parts: [
{
type: "text",
text: "Your question here"
}
]
}
}
}, null, 2))}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
<div>
<h4 className="font-medium text-white mb-2">Headers:</h4>
<div className="relative">
<CodeBlock
text={`Content-Type: application/json
x-api-key: YOUR_API_KEY`}
language="text"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(`Content-Type: application/json
x-api-key: YOUR_API_KEY`)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
<div>
<h4 className="font-medium text-white mb-2">Response:</h4>
<div className="relative">
<CodeBlock
text={JSON.stringify({
jsonrpc: "2.0",
result: {
status: {
state: "completed",
message: {
role: "model",
parts: [
{
type: "text",
text: "Complete agent response here."
}
]
}
}
},
id: "call-123"
}, null, 2)}
language="json"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(JSON.stringify({
jsonrpc: "2.0",
result: {
status: {
state: "completed",
message: {
role: "model",
parts: [
{
type: "text",
text: "Complete agent response here."
}
]
}
}
},
id: "call-123"
}, null, 2))}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
<Separator className="my-6 bg-[#333]" />
<div>
<h3 className="text-emerald-400 text-lg font-medium mb-2">Method message/stream</h3>
<p className="text-neutral-300 mb-4">
The <code className="bg-[#333] px-1 rounded">message/stream</code> method uses Server-Sent Events (SSE) to receive real-time updates.
</p>
<div className="space-y-4">
<div>
<h4 className="font-medium text-white mb-2">Request:</h4>
<div className="relative">
<CodeBlock
text={JSON.stringify({
jsonrpc: "2.0",
id: "call-123",
method: "message/stream",
params: {
id: "task-456",
sessionId: "session-789",
message: {
role: "user",
parts: [
{
type: "text",
text: "Your question here"
}
]
}
}
}, null, 2)}
language="json"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(JSON.stringify({
jsonrpc: "2.0",
id: "call-123",
method: "message/stream",
params: {
id: "task-456",
sessionId: "session-789",
message: {
role: "user",
parts: [
{
type: "text",
text: "Your question here"
}
]
}
}
}, null, 2))}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
<div>
<h4 className="font-medium text-white mb-2">Headers:</h4>
<div className="relative">
<CodeBlock
text={`Content-Type: application/json
x-api-key: YOUR_API_KEY
Accept: text/event-stream`}
language="text"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(`Content-Type: application/json
x-api-key: YOUR_API_KEY
Accept: text/event-stream`)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
<div>
<h4 className="font-medium text-white mb-2">SSE Event Format:</h4>
<p className="text-neutral-300 mb-4">
Each event follows the standard Server-Sent Events (SSE) format, with the "data:" prefix followed by the JSON content and terminated by two newlines ("\n\n"):
</p>
<div className="relative">
<CodeBlock
text={`data: {"jsonrpc":"2.0","id":"call-123","result":{"id":"task-456","status":{"state":"working","message":{"role":"agent","parts":[{"type":"text","text":"Processing..."}]},"timestamp":"2025-05-13T18:10:37.219Z"},"final":false}}
data: {"jsonrpc":"2.0","id":"call-123","result":{"id":"task-456","status":{"state":"completed","timestamp":"2025-05-13T18:10:40.456Z"},"final":true}}
`}
language="text"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(`data: {"jsonrpc":"2.0","id":"call-123","result":{"id":"task-456","status":{"state":"working","message":{"role":"agent","parts":[{"type":"text","text":"Processing..."}]},"timestamp":"2025-05-13T18:10:37.219Z"},"final":false}}
data: {"jsonrpc":"2.0","id":"call-123","result":{"id":"task-456","status":{"state":"completed","timestamp":"2025-05-13T18:10:40.456Z"},"final":true}}
`)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
<div>
<h4 className="font-medium text-white mb-2">Event Types:</h4>
<ul className="list-disc list-inside text-neutral-300 space-y-2 mb-4">
<li><span className="text-emerald-400">Status Updates</span>: Contains the <code className="bg-[#333] px-1 rounded">status</code> field with information about the task status.</li>
<li><span className="text-emerald-400">Artifact Updates</span>: Contains the <code className="bg-[#333] px-1 rounded">artifact</code> field with the content generated by the agent.</li>
<li><span className="text-emerald-400">Ping Events</span>: Simple events with the format <code className="bg-[#333] px-1 rounded">: ping</code> to keep the connection active.</li>
</ul>
</div>
<div>
<h4 className="font-medium text-white mb-2">Client Consumption:</h4>
<p className="text-neutral-300 mb-2">
For a better experience, we recommend using the <code className="bg-[#333] px-1 rounded">EventSource</code> API to consume the events:
</p>
<div className="relative">
<CodeBlock
text={`// After receiving the initial response via POST, use EventSource to stream
const eventSource = new EventSource(\`/api/v1/a2a/\${agentId}/stream?taskId=\${taskId}&key=\${apiKey}\`);
// Process the received events
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
// Process different types of events
if (data.result) {
// 1. Process status updates
if (data.result.status) {
const state = data.result.status.state; // "working", "completed", etc.
// Check if there is a text message
if (data.result.status.message?.parts) {
const textParts = data.result.status.message.parts
.filter(part => part.type === "text")
.map(part => part.text)
.join("");
// Update UI with the text
updateUI(textParts);
}
// Check if it is the final event
if (data.result.final === true) {
eventSource.close(); // Close connection
}
}
// 2. Process the generated artifacts
if (data.result.artifact) {
const artifact = data.result.artifact;
// Extract text from the artifact
if (artifact.parts) {
const artifactText = artifact.parts
.filter(part => part.type === "text")
.map(part => part.text)
.join("");
// Update UI with the artifact
updateArtifactUI(artifactText);
}
}
}
};
// Handle errors
eventSource.onerror = (error) => {
console.error("Error in SSE:", error);
eventSource.close();
};`}
language="javascript"
/>
<Button
size="sm"
variant="ghost"
className="absolute top-2 right-2 text-white hover:bg-[#333]"
onClick={() => copyToClipboard(`// After receiving the initial response via POST, use EventSource to stream
const eventSource = new EventSource(\`/api/v1/a2a/\${agentId}/stream?taskId=\${taskId}&key=\${apiKey}\`);
// Process the received events
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
// Process different types of events
if (data.result) {
// 1. Process status updates
if (data.result.status) {
const state = data.result.status.state; // "working", "completed", etc.
// Check if there is a text message
if (data.result.status.message?.parts) {
const textParts = data.result.status.message.parts
.filter(part => part.type === "text")
.map(part => part.text)
.join("");
// Update UI with the text
updateUI(textParts);
}
// Check if it is the final event
if (data.result.final === true) {
eventSource.close(); // Close connection
}
}
// 2. Process the generated artifacts
if (data.result.artifact) {
const artifact = data.result.artifact;
// Extract text from the artifact
if (artifact.parts) {
const artifactText = artifact.parts
.filter(part => part.type === "text")
.map(part => part.text)
.join("");
// Update UI with the artifact
updateArtifactUI(artifactText);
}
}
}
};
// Handle errors
eventSource.onerror = (error) => {
console.error("Error in SSE:", error);
eventSource.close();
};`)}
>
<ClipboardCopy className="h-4 w-4" />
</Button>
</div>
</div>
<div>
<h4 className="font-medium text-white mb-2">Possible task states:</h4>
<ul className="list-disc list-inside text-neutral-300 space-y-1">
<li><span className="text-emerald-400">submitted</span>: Task sent but not yet processed</li>
<li><span className="text-emerald-400">working</span>: Task being processed by the agent</li>
<li><span className="text-emerald-400">completed</span>: Task completed successfully</li>
<li><span className="text-emerald-400">input-required</span>: Agent waiting for additional user input</li>
<li><span className="text-emerald-400">failed</span>: Task failed during processing</li>
<li><span className="text-emerald-400">canceled</span>: Task was canceled</li>
</ul>
</div>
</div>
</div>
<div className="bg-[#222] p-4 rounded-md border border-[#444]">
<h4 className="font-medium text-white mb-2">Possible task states:</h4>
<ul className="grid grid-cols-1 md:grid-cols-2 gap-2">
<li className="flex items-center">
<span className="w-3 h-3 bg-blue-500 rounded-full mr-2"></span>
<span className="text-neutral-300"><strong>submitted</strong>: Task sent</span>
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-yellow-500 rounded-full mr-2"></span>
<span className="text-neutral-300"><strong>working</strong>: Task being processed</span>
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-purple-500 rounded-full mr-2"></span>
<span className="text-neutral-300"><strong>input-required</strong>: Agent waiting for additional user input</span>
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-green-500 rounded-full mr-2"></span>
<span className="text-neutral-300"><strong>completed</strong>: Task completed successfully</span>
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-neutral-500 rounded-full mr-2"></span>
<span className="text-neutral-300"><strong>canceled</strong>: Task canceled</span>
</li>
<li className="flex items-center">
<span className="w-3 h-3 bg-red-500 rounded-full mr-2"></span>
<span className="text-neutral-300"><strong>failed</strong>: Task processing failed</span>
</li>
</ul>
</div>
</CardContent>
</Card>
);
}

File diff suppressed because it is too large Load Diff