Commit inicial - upload de todos os arquivos da pasta
This commit is contained in:
333
frontend/app/documentation/components/A2AComplianceCard.tsx
Normal file
333
frontend/app/documentation/components/A2AComplianceCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
78
frontend/app/documentation/components/CodeBlock.tsx
Normal file
78
frontend/app/documentation/components/CodeBlock.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
317
frontend/app/documentation/components/CodeExamplesSection.tsx
Normal file
317
frontend/app/documentation/components/CodeExamplesSection.tsx
Normal 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 "file".
|
||||
</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>
|
||||
);
|
||||
}
|
||||
588
frontend/app/documentation/components/DocumentationSection.tsx
Normal file
588
frontend/app/documentation/components/DocumentationSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
523
frontend/app/documentation/components/HttpLabForm.tsx
Normal file
523
frontend/app/documentation/components/HttpLabForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
201
frontend/app/documentation/components/LabSection.tsx
Normal file
201
frontend/app/documentation/components/LabSection.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
179
frontend/app/documentation/components/QuickStartTemplates.tsx
Normal file
179
frontend/app/documentation/components/QuickStartTemplates.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
366
frontend/app/documentation/components/StreamLabForm.tsx
Normal file
366
frontend/app/documentation/components/StreamLabForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user