diff --git a/apps/web/FEATURES.md b/apps/web/FEATURES.md new file mode 100644 index 0000000..b28719b --- /dev/null +++ b/apps/web/FEATURES.md @@ -0,0 +1,389 @@ +# TPMJS Features + +TPMJS is the universal tool registry for AI agents. Discover, publish, and execute AI tools with one URL. + +--- + +## Table of Contents + +- [Tool Discovery](#tool-discovery) +- [Tool Execution](#tool-execution) +- [AI Agents](#ai-agents) +- [Collections](#collections) +- [MCP Integration](#mcp-integration) +- [Publishing Tools](#publishing-tools) +- [API Access](#api-access) +- [Dashboard](#dashboard) +- [Community Features](#community-features) + +--- + +## Tool Discovery + +### Search & Browse + +- **Full-text search** - BM25-ranked semantic search across 170+ tools +- **Category filtering** - Filter by category (research, web, data, engineering, security, utilities, etc.) +- **Health status filtering** - Filter by import/execution health (Healthy, Degraded, Broken) +- **Official tools** - Browse verified official TPMJS tools +- **Virtualized browsing** - Smooth scrolling through large tool lists + +### Tool Details + +Each tool page displays: + +- Package name and version +- Description and usage examples +- Input parameter schemas (extracted from Zod) +- Required environment variables +- Quality score (0.0 - 1.0) +- Monthly npm downloads +- Health status indicators +- Community ratings and reviews + +### Quality Scoring + +Tools are automatically scored based on: + +- Documentation tier (Rich, Basic, Minimal) +- npm download count +- GitHub stars +- Import and execution health + +--- + +## Tool Execution + +### Interactive Playground + +Test any tool directly in the browser: + +- AI-generated test parameters +- Real-time streaming output +- Execution history +- Error reporting with health status + +### Sandboxed Execution + +- Tools execute in isolated Deno runtime +- Secure sandboxing on Railway infrastructure +- Rate limiting by tier (Free, Pro, Enterprise) +- Automatic timeout handling + +### Dynamic Tool Loading (Beta) + +- Tools discovered based on conversation context +- AI automatically selects relevant tools +- No manual configuration required + +--- + +## AI Agents + +### Create Custom Agents + +Build AI agents with: + +- **System prompts** - Define agent behavior and personality +- **Model selection** - Choose from available AI models +- **Tool selection** - Pick specific tools or entire collections +- **Temperature control** - Adjust response creativity + +### Agent Features + +- **Web chat interface** - Test agents directly in browser +- **Conversation history** - Save and review past conversations +- **Public sharing** - Share agents via unique URLs +- **Forking** - Clone and customize existing agents +- **Analytics** - Track execution history and usage + +### Agent Management + +- Create, edit, and delete agents +- Toggle public/private visibility +- View fork relationships +- Monitor health and performance + +--- + +## Collections + +### Curated Tool Bundles + +Group related tools into collections: + +- **Create collections** - Bundle tools for specific use cases +- **AI-generated descriptions** - Auto-generate use case descriptions +- **Public sharing** - Share collections with the community +- **Forking** - Clone and customize collections + +### Collection Features + +- Add/remove tools dynamically +- Set visibility (public/private) +- View included tools with full metadata +- Copy MCP configuration for instant use + +--- + +## MCP Integration + +### Universal Compatibility + +TPMJS works with any MCP-compatible client: + +| Client | Support | +|--------|---------| +| Claude Desktop | Full integration | +| Cursor | Full integration | +| Windsurf | Full integration | +| Any MCP client | JSON-RPC 2.0 | + +### Quick Start + +Add to `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "tpmjs": { + "command": "npx", + "args": ["-y", "@anthropic/mcp-remote", + "https://tpmjs.com/api/mcp/ajax/ajax-collection/sse"] + } + } +} +``` + +### MCP Endpoints + +- **Collection endpoints** - Each collection exposes an MCP server +- **Authentication** - API key-based auth via headers +- **Streaming** - Server-Sent Events (SSE) for real-time responses + +### MCP Protocol + +Full JSON-RPC 2.0 implementation: + +- `initialize` - Establish session +- `tools/list` - Get available tools +- `tools/call` - Execute tools + +--- + +## Publishing Tools + +### Auto-Discovery + +Publish tools to npm and they appear on TPMJS within minutes: + +1. Add `tpmjs` keyword to `package.json` +2. Configure the `tpmjs` field with metadata +3. Publish to npm +4. Tool appears in 2-15 minutes + +### Package Configuration + +```json +{ + "name": "@your-scope/your-tool", + "keywords": ["tpmjs"], + "tpmjs": { + "category": "utilities", + "tools": ["myTool", "anotherTool"], + "envVars": ["API_KEY"] + } +} +``` + +### Discovery Methods + +- **Changes feed** - Monitors npm real-time (every 2 min) +- **Keyword search** - Active npm search (every 15 min) +- **Manual curation** - High-quality verified tools + +### Package Generator + +Quick start with the CLI generator: + +```bash +npx @tpmjs/create-basic-tools +``` + +Creates a production-ready package with: + +- 2-3 example tools +- Complete TypeScript setup +- Best practices included + +--- + +## API Access + +### REST API + +``` +GET /api/tools - List tools with filters +GET /api/tools/search - BM25 semantic search +GET /api/tools/[pkg]/[tool] - Get tool details +POST /api/tools/execute/[pkg]/[tool] - Execute tool +GET /api/public/collections - List collections +GET /api/public/agents - List agents +GET /api/stats - Platform statistics +``` + +### Authentication + +Generate API keys with granular scopes: + +| Scope | Permission | +|-------|------------| +| `mcp:execute` | Execute tools via MCP | +| `agent:chat` | Agent conversations | +| `bridge:connect` | Bridge connections | +| `collection:read` | Read collections | +| `usage:read` | View usage analytics | + +### Rate Limits + +| Tier | Requests/Hour | +|------|---------------| +| Free | 100 | +| Pro | 1,000 | +| Enterprise | 10,000 | + +### Streaming Responses + +All execution endpoints support SSE streaming for real-time output. + +--- + +## Dashboard + +### Overview + +- Welcome screen with quick stats +- Recent activity stream +- Quick access to agents and collections + +### Agents Management + +- View all your agents +- Create new agents +- Edit agent configuration +- Test via chat interface +- View conversation history + +### Collections Management + +- View all your collections +- Create and edit collections +- Add/remove tools +- Toggle visibility + +### Likes & Bookmarks + +Save your favorites: + +- `/dashboard/likes/tools` - Liked tools +- `/dashboard/likes/agents` - Liked agents +- `/dashboard/likes/collections` - Liked collections + +### Settings + +- **Profile** - Name, email, avatar +- **API Keys** - Generate and manage TPMJS keys +- **Third-Party Keys** - Store external API credentials +- **Bridge** - Configure Bridge integration + +### Usage Analytics + +- API call statistics +- Execution counts and success rates +- Error tracking +- Usage against rate limits + +--- + +## Community Features + +### Social Engagement + +- **Likes** - Like tools, agents, and collections +- **Ratings** - Rate tools (1-5 stars) +- **Reviews** - Leave comments on tools + +### Sharing + +- **Public profiles** - View user profiles at `/[username]` +- **Copy options** - Multiple formats for sharing: + - Direct URLs + - MCP configurations + - API endpoints + +### Forking + +Clone and customize: + +- Fork public agents +- Fork public collections +- Track fork relationships +- Attribution to original creators + +--- + +## Framework Compatibility + +TPMJS tools work with popular AI frameworks: + +- **Vercel AI SDK** - Native compatibility +- **LangChain** - Full support +- **LlamaIndex** - Compatible +- **Custom frameworks** - JSON-RPC protocol + +--- + +## Security + +- **Sandboxed execution** - Isolated Deno runtime +- **API key rotation** - Secure key management +- **Rate limiting** - Protection against abuse +- **Email verification** - Account security +- **Scoped permissions** - Fine-grained access control + +--- + +## Documentation + +Comprehensive guides available: + +- [API Documentation](/docs/api) - REST and MCP APIs +- [Publishing Guide](/publish) - How to publish tools +- [MCP Tutorial](/docs/tutorials/mcp) - MCP client setup +- [Custom Executors](/docs/tutorials/custom-executor) - Self-hosted execution +- [Bridge Setup](/docs/tutorials/bridge) - External tool integration +- [Agent Tutorial](/docs/tutorials/agents) - Building AI agents + +--- + +## Platform Stats + +- **170+ tools** indexed and searchable +- **Multiple categories** covering diverse use cases +- **Real-time sync** with npm registry +- **Automatic health monitoring** +- **Quality scoring** for all tools + +--- + +## Getting Started + +1. **Browse tools** at [tpmjs.com/tool/tool-search](https://tpmjs.com/tool/tool-search) +2. **Try the playground** to test tools interactively +3. **Create an account** to build agents and collections +4. **Generate an API key** for programmatic access +5. **Configure MCP** for Claude, Cursor, or Windsurf + +--- + +*TPMJS - The Tool Package Manager for AI* diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index b415a34..ddb5cf1 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -5,6 +5,7 @@ import { Container } from '@tpmjs/ui/Container/Container'; import Link from 'next/link'; import { AppHeader } from '../components/AppHeader'; // import { ArchitectureDiagramWrapper } from '../components/home/ArchitectureDiagramWrapper'; +import { FeaturesSection } from '../components/home/FeaturesSection'; import { HeroSection } from '../components/home/HeroSection'; export const dynamic = 'force-dynamic'; @@ -84,6 +85,9 @@ export default async function HomePage(): Promise { {/* Hero Section - Dithered Design */} + {/* Features Section - Interactive & Animated */} + + {/* Architecture Diagram Section - temporarily disabled
@@ -188,58 +192,104 @@ export default async function HomePage(): Promise {
{/* Integration Section */} -
+
-
-

- Works Everywhere + {/* Section Header */} +
+

+ universal compatibility +

+

+ works everywhere

-

+

Use TPMJS tools with any MCP-compatible AI client. One URL, instant access.

-
-
-
Claude
-

Desktop, Projects, and API

-
-
-
Cursor
-

AI-powered code editor

-
-
-
Windsurf
-

Agentic IDE

-
-
-
Any MCP
-

Open protocol support

-
-
- - {/* Code Example */} -
-
-
- claude_desktop_config.json + {/* Client Cards - Fieldset Style */} +
+ + supported clients + +
+
+
+ C +
+
+ Claude +
+

+ desktop / projects / api +

+
+
+
+ {'>_'} +
+
+ Cursor +
+

+ ai-powered code editor +

+
+
+
+ W +
+
+ Windsurf +
+

+ agentic ide +

+
+
+
+ * +
+
+ Any MCP +
+

+ open protocol support +

-
-                  {`{
-  "mcpServers": {
-    "tpmjs": {
-      "command": "npx",
-      "args": ["-y", "@anthropic/mcp-remote",
-        "https://tpmjs.com/api/mcp/ajax/ajax-collection/sse"]
-    }
-  }
-}`}
-                
-

- Add this to your config and get instant access to 170+ tools. -

-
+ + + {/* Code Example - Fieldset Style */} +
+ + quick start + +
+

+ claude_desktop_config.json +

+
+
+                    {'{'}
+                    {'\n  '}"mcpServers": {'{'}
+                    {'\n    '}"tpmjs": {'{'}
+                    {'\n      '}"command": "npx",
+                    {'\n      '}"args": ["-y", "@anthropic/mcp-remote",
+                    {'\n        '}"https://tpmjs.com/api/mcp/ajax/ajax-collection/sse"]
+                    {'\n    '}{'}'}
+                    {'\n  '}{'}'}
+                    {'\n'}{'}'}
+                  
+
+
+
+
+

+ add to config → instant access to 170+ tools +

+
+

diff --git a/apps/web/src/components/home/FeaturesSection.tsx b/apps/web/src/components/home/FeaturesSection.tsx new file mode 100644 index 0000000..7fcc400 --- /dev/null +++ b/apps/web/src/components/home/FeaturesSection.tsx @@ -0,0 +1,607 @@ +'use client'; + +import { Badge } from '@tpmjs/ui/Badge/Badge'; +import { Button } from '@tpmjs/ui/Button/Button'; +import { Container } from '@tpmjs/ui/Container/Container'; +import { Icon } from '@tpmjs/ui/Icon/Icon'; +import Link from 'next/link'; +import { useEffect, useRef, useState } from 'react'; +import { ToolConnectionViz } from './ToolConnectionViz'; + +// ============================================================================ +// Animated Terminal Component +// ============================================================================ + +function AnimatedTerminal(): React.ReactElement { + const [currentLine, setCurrentLine] = useState(0); + const [displayedText, setDisplayedText] = useState(''); + const [isTyping, setIsTyping] = useState(true); + + const lines = [ + { type: 'input', text: '$ npx @tpmjs/tools-unsandbox' }, + { type: 'output', text: '✓ Tool loaded: executeCodeAsync' }, + { type: 'input', text: '$ execute --lang python --code "print(sum(range(100)))"' }, + { type: 'output', text: '→ Spinning up secure sandbox...' }, + { type: 'output', text: '→ Executing code...' }, + { type: 'success', text: '✓ Output: 4950' }, + { type: 'input', text: '$ _' }, + ]; + + useEffect(() => { + if (currentLine >= lines.length) { + // Reset after delay + const timeout = setTimeout(() => { + setCurrentLine(0); + setDisplayedText(''); + setIsTyping(true); + }, 3000); + return () => clearTimeout(timeout); + } + + const line = lines[currentLine]; + if (!line) return; + + if (line.type === 'input') { + // Type out input lines character by character + let charIndex = 0; + setIsTyping(true); + const interval = setInterval(() => { + if (charIndex <= line.text.length) { + setDisplayedText(line.text.slice(0, charIndex)); + charIndex++; + } else { + clearInterval(interval); + setIsTyping(false); + setTimeout(() => { + setCurrentLine((prev) => prev + 1); + setDisplayedText(''); + }, 500); + } + }, 50); + return () => clearInterval(interval); + } else { + // Show output lines instantly + setDisplayedText(line.text); + setIsTyping(false); + const timeout = setTimeout(() => { + setCurrentLine((prev) => prev + 1); + setDisplayedText(''); + }, 800); + return () => clearTimeout(timeout); + } + }, [currentLine]); + + const getLineColor = (type: string) => { + switch (type) { + case 'input': + return 'text-foreground'; + case 'output': + return 'text-foreground-secondary'; + case 'success': + return 'text-success'; + case 'error': + return 'text-error'; + default: + return 'text-foreground-secondary'; + } + }; + + return ( +
+ {/* Terminal Header */} +
+
+
+
+
+
+ + tpmjs terminal + +
+ + {/* Terminal Content */} +
+ {/* Previous lines */} + {lines.slice(0, currentLine).map((line, i) => ( +
+ {line.text} +
+ ))} + + {/* Current line being typed */} + {currentLine < lines.length && ( +
+ {displayedText} + {isTyping && ( + + )} +
+ )} +
+
+ ); +} + +// ============================================================================ +// Feature Card with Hover Animation +// ============================================================================ + +interface FeatureCardProps { + icon: string; + title: string; + description: string; + stats?: string; + delay?: number; +} + +function FeatureCard({ icon, title, description, stats, delay = 0 }: FeatureCardProps): React.ReactElement { + const [isVisible, setIsVisible] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry?.isIntersecting) { + setTimeout(() => setIsVisible(true), delay); + } + }, + { threshold: 0.2 } + ); + + if (ref.current) { + observer.observe(ref.current); + } + + return () => observer.disconnect(); + }, [delay]); + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + className={` + relative p-6 border-2 border-dashed border-border bg-surface + transition-all duration-300 ease-out cursor-pointer + ${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'} + ${isHovered ? 'border-primary bg-primary/5 shadow-[4px_4px_0_0_rgba(166,89,45,0.2)]' : ''} + `} + > + {/* Animated corner accent */} +
+
+ + {/* Icon */} +
+ +
+ + {/* Content */} +

+ {title} +

+

+ {description} +

+ + {/* Stats badge */} + {stats && ( + + {stats} + + )} +
+ ); +} + +// ============================================================================ +// Animated Counter +// ============================================================================ + +interface AnimatedCounterProps { + end: number; + duration?: number; + suffix?: string; + prefix?: string; +} + +function AnimatedCounter({ end, duration = 2000, suffix = '', prefix = '' }: AnimatedCounterProps): React.ReactElement { + const [count, setCount] = useState(0); + const [hasStarted, setHasStarted] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry?.isIntersecting && !hasStarted) { + setHasStarted(true); + } + }, + { threshold: 0.5 } + ); + + if (ref.current) { + observer.observe(ref.current); + } + + return () => observer.disconnect(); + }, [hasStarted]); + + useEffect(() => { + if (!hasStarted) return; + + let startTime: number; + let animationFrame: number; + + const animate = (timestamp: number) => { + if (!startTime) startTime = timestamp; + const progress = Math.min((timestamp - startTime) / duration, 1); + + // Easing function for smooth animation + const easeOutQuart = 1 - Math.pow(1 - progress, 4); + setCount(Math.floor(easeOutQuart * end)); + + if (progress < 1) { + animationFrame = requestAnimationFrame(animate); + } + }; + + animationFrame = requestAnimationFrame(animate); + return () => cancelAnimationFrame(animationFrame); + }, [hasStarted, end, duration]); + + return ( + + {prefix}{count.toLocaleString()}{suffix} + + ); +} + +// ============================================================================ +// Flow Diagram Component +// ============================================================================ + +function FlowDiagram(): React.ReactElement { + const [activeStep, setActiveStep] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setActiveStep((prev) => (prev + 1) % 4); + }, 2000); + return () => clearInterval(interval); + }, []); + + const steps = [ + { label: 'npm publish', icon: 'box', desc: 'publish to npm' }, + { label: 'auto-discover', icon: 'search', desc: 'indexed in minutes' }, + { label: 'validate', icon: 'check', desc: 'health checks run' }, + { label: 'available', icon: 'globe', desc: 'ready for agents' }, + ]; + + return ( +
+ {steps.map((step, i) => ( +
+ {/* Step */} +
+
+ +
+ + {step.label} + + + {step.desc} + + + {/* Pulse ring when active */} + {activeStep === i && ( +
+
+
+ )} +
+ + {/* Arrow */} + {i < steps.length - 1 && ( +
+
i ? 'bg-primary' : 'bg-border'} + `} + /> +
i ? 'border-l-primary' : 'border-l-border'} + `} + /> +
+ )} +
+ ))} +
+ ); +} + +// ============================================================================ +// Interactive Tool Grid +// ============================================================================ + +function InteractiveToolGrid(): React.ReactElement { + const tools = [ + { name: 'web-scraper', category: 'web', color: 'bg-info' }, + { name: 'code-executor', category: 'sandbox', color: 'bg-success' }, + { name: 'pdf-parser', category: 'data', color: 'bg-warning' }, + { name: 'image-gen', category: 'ai', color: 'bg-error' }, + { name: 'db-query', category: 'data', color: 'bg-info' }, + { name: 'api-caller', category: 'web', color: 'bg-success' }, + { name: 'file-convert', category: 'utilities', color: 'bg-warning' }, + { name: 'text-analyze', category: 'ai', color: 'bg-error' }, + { name: 'email-send', category: 'integration', color: 'bg-info' }, + ]; + + const [hoveredTool, setHoveredTool] = useState(null); + + return ( +
+ {tools.map((tool, i) => ( +
setHoveredTool(tool.name)} + onMouseLeave={() => setHoveredTool(null)} + className={` + relative p-3 border border-dashed border-border bg-surface + transition-all duration-300 cursor-pointer + ${hoveredTool === tool.name ? 'border-primary scale-105 z-10 shadow-lg' : ''} + `} + style={{ + animationDelay: `${i * 100}ms`, + }} + > +
+
+ {tool.name} +
+ {hoveredTool === tool.name && ( +
+ {tool.category} +
+ )} +
+ ))} +
+ ); +} + +// ============================================================================ +// Main Features Section +// ============================================================================ + +export function FeaturesSection(): React.ReactElement { + return ( +
+ {/* Subtle grid background */} +
+
+
+ + + {/* Section Header */} +
+

+ everything you need +

+

+ powerful features +

+

+ From discovery to execution, TPMJS provides the complete infrastructure + for AI tool development. +

+
+ + {/* Stats Row */} +
+ {[ + { value: 170, suffix: '+', label: 'tools indexed' }, + { value: 15, suffix: ' min', label: 'discovery time' }, + { value: 99, suffix: '%', label: 'uptime' }, + { value: 4, suffix: '', label: 'mcp clients' }, + ].map((stat, i) => ( +
+
+ +
+
+ {stat.label} +
+
+ ))} +
+ + {/* Main Feature Grid */} +
+ {/* Left: Terminal Demo */} +
+
+ + live execution + +

+ Execute any tool directly from your terminal or AI agent. Secure sandboxed + execution with real-time streaming output. +

+ +
+
+ + {/* Right: Tool Grid */} +
+
+ + tool registry + +

+ Browse 170+ tools across multiple categories. Each tool is validated, + documented, and ready to use. +

+ +
+ + + +
+
+
+
+ + {/* Flow Diagram */} +
+ + how it works + + +
+ + {/* Interactive Connection Visualization */} +
+ + tools → tpmjs → agents + +

+ TPMJS acts as the central hub connecting npm packages to AI agents. + Watch data flow in real-time as tools serve agent requests. +

+ +
+ + {/* Feature Cards Grid */} +
+ + + + + + +
+ + {/* CTA */} +
+
+ + + + + + +
+
+
+
+ ); +} diff --git a/apps/web/src/components/home/ToolConnectionViz.tsx b/apps/web/src/components/home/ToolConnectionViz.tsx new file mode 100644 index 0000000..a2f2178 --- /dev/null +++ b/apps/web/src/components/home/ToolConnectionViz.tsx @@ -0,0 +1,320 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +interface Node { + id: string; + x: number; + y: number; + vx: number; + vy: number; + type: 'tool' | 'agent' | 'hub'; + label: string; + color: string; +} + +interface Connection { + from: string; + to: string; + progress: number; + active: boolean; +} + +export function ToolConnectionViz(): React.ReactElement { + const canvasRef = useRef(null); + const containerRef = useRef(null); + const animationRef = useRef(null); + const [dimensions, setDimensions] = useState({ width: 800, height: 400 }); + const nodesRef = useRef([]); + const connectionsRef = useRef([]); + const mouseRef = useRef({ x: 0, y: 0 }); + + // Initialize nodes and connections + useEffect(() => { + const tools = [ + { label: 'web-scraper', color: '#3380CC' }, + { label: 'code-exec', color: '#327D52' }, + { label: 'pdf-parse', color: '#D9A020' }, + { label: 'img-gen', color: '#C44545' }, + { label: 'api-call', color: '#3380CC' }, + { label: 'db-query', color: '#327D52' }, + ]; + + const agents = [ + { label: 'Claude', color: '#A6592D' }, + { label: 'GPT', color: '#A6592D' }, + { label: 'Agent', color: '#A6592D' }, + ]; + + const nodes: Node[] = []; + + // Central hub (TPMJS) + nodes.push({ + id: 'hub', + x: dimensions.width / 2, + y: dimensions.height / 2, + vx: 0, + vy: 0, + type: 'hub', + label: 'TPMJS', + color: '#A6592D', + }); + + // Tools on the left + tools.forEach((tool, i) => { + const angle = (Math.PI / (tools.length + 1)) * (i + 1) - Math.PI / 2; + const radius = 150; + nodes.push({ + id: `tool-${i}`, + x: dimensions.width / 2 - radius + Math.cos(angle) * 80, + y: dimensions.height / 2 + Math.sin(angle) * 120, + vx: (Math.random() - 0.5) * 0.5, + vy: (Math.random() - 0.5) * 0.5, + type: 'tool', + label: tool.label, + color: tool.color, + }); + }); + + // Agents on the right + agents.forEach((agent, i) => { + const angle = (Math.PI / (agents.length + 1)) * (i + 1) + Math.PI / 2; + const radius = 150; + nodes.push({ + id: `agent-${i}`, + x: dimensions.width / 2 + radius + Math.cos(angle) * 80, + y: dimensions.height / 2 + Math.sin(angle) * 80, + vx: (Math.random() - 0.5) * 0.5, + vy: (Math.random() - 0.5) * 0.5, + type: 'agent', + label: agent.label, + color: agent.color, + }); + }); + + nodesRef.current = nodes; + + // Create connections + const connections: Connection[] = []; + tools.forEach((_, i) => { + connections.push({ + from: `tool-${i}`, + to: 'hub', + progress: Math.random(), + active: Math.random() > 0.5, + }); + }); + agents.forEach((_, i) => { + connections.push({ + from: 'hub', + to: `agent-${i}`, + progress: Math.random(), + active: Math.random() > 0.5, + }); + }); + connectionsRef.current = connections; + }, [dimensions]); + + // Handle resize + useEffect(() => { + const updateDimensions = () => { + if (containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + setDimensions({ width: rect.width, height: 400 }); + } + }; + + updateDimensions(); + window.addEventListener('resize', updateDimensions); + return () => window.removeEventListener('resize', updateDimensions); + }, []); + + // Handle mouse movement + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + mouseRef.current = { + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }; + } + }; + + window.addEventListener('mousemove', handleMouseMove); + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); + + // Animation loop + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + const animate = () => { + ctx.clearRect(0, 0, dimensions.width, dimensions.height); + + const nodes = nodesRef.current; + const connections = connectionsRef.current; + + // Update node positions (subtle floating) + nodes.forEach((node) => { + if (node.type !== 'hub') { + node.x += node.vx; + node.y += node.vy; + + // Bounce off boundaries + if (node.x < 50 || node.x > dimensions.width - 50) node.vx *= -1; + if (node.y < 50 || node.y > dimensions.height - 50) node.vy *= -1; + + // Mouse repulsion + const dx = mouseRef.current.x - node.x; + const dy = mouseRef.current.y - node.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 100) { + node.vx -= (dx / dist) * 0.1; + node.vy -= (dy / dist) * 0.1; + } + + // Damping + node.vx *= 0.99; + node.vy *= 0.99; + } + }); + + // Draw connections + connections.forEach((conn) => { + const fromNode = nodes.find((n) => n.id === conn.from); + const toNode = nodes.find((n) => n.id === conn.to); + if (!fromNode || !toNode) return; + + // Update progress + if (conn.active) { + conn.progress += 0.01; + if (conn.progress > 1) { + conn.progress = 0; + conn.active = Math.random() > 0.3; + } + } else { + if (Math.random() > 0.995) { + conn.active = true; + } + } + + // Draw line + ctx.beginPath(); + ctx.moveTo(fromNode.x, fromNode.y); + ctx.lineTo(toNode.x, toNode.y); + ctx.strokeStyle = 'rgba(166, 89, 45, 0.2)'; + ctx.lineWidth = 1; + ctx.stroke(); + + // Draw data packet + if (conn.active) { + const packetX = fromNode.x + (toNode.x - fromNode.x) * conn.progress; + const packetY = fromNode.y + (toNode.y - fromNode.y) * conn.progress; + + ctx.beginPath(); + ctx.arc(packetX, packetY, 4, 0, Math.PI * 2); + ctx.fillStyle = '#A6592D'; + ctx.fill(); + + // Glow effect + ctx.beginPath(); + ctx.arc(packetX, packetY, 8, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(166, 89, 45, 0.3)'; + ctx.fill(); + } + }); + + // Draw nodes + nodes.forEach((node) => { + // Node background + if (node.type === 'hub') { + // Central hub - larger with glow + ctx.beginPath(); + ctx.arc(node.x, node.y, 40, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(166, 89, 45, 0.1)'; + ctx.fill(); + + ctx.beginPath(); + ctx.arc(node.x, node.y, 30, 0, Math.PI * 2); + ctx.fillStyle = '#A6592D'; + ctx.fill(); + + ctx.beginPath(); + ctx.arc(node.x, node.y, 30, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(166, 89, 45, 0.5)'; + ctx.lineWidth = 3; + ctx.stroke(); + } else { + // Regular nodes + const size = node.type === 'agent' ? 24 : 20; + + ctx.beginPath(); + ctx.arc(node.x, node.y, size + 4, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; + ctx.fill(); + + ctx.beginPath(); + ctx.arc(node.x, node.y, size, 0, Math.PI * 2); + ctx.fillStyle = node.color; + ctx.fill(); + + ctx.beginPath(); + ctx.arc(node.x, node.y, size, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + ctx.lineWidth = 2; + ctx.stroke(); + } + + // Label + ctx.font = '11px monospace'; + ctx.textAlign = 'center'; + ctx.fillStyle = node.type === 'hub' ? '#FFFFFF' : '#1a1715'; + if (node.type === 'hub') { + ctx.font = 'bold 12px monospace'; + ctx.fillText(node.label, node.x, node.y + 4); + } else { + ctx.fillText(node.label, node.x, node.y + (node.type === 'agent' ? 40 : 36)); + } + }); + + animationRef.current = requestAnimationFrame(animate); + }; + + animate(); + + return () => { + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, [dimensions]); + + return ( +
+ + + {/* Labels */} +
+ + tools + +
+
+ + agents + +
+
+ ); +}