feat: replace ASCII diagram with D3.js visualization in bridge tutorial

This commit is contained in:
Ajax Davis 2026-01-13 01:54:12 +10:00
parent c8c1a12f22
commit b4be05e004
2 changed files with 235 additions and 26 deletions

View file

@ -6,6 +6,7 @@ import Link from 'next/link';
import { useState } from 'react';
import { AppFooter } from '~/components/AppFooter';
import { AppHeader } from '~/components/AppHeader';
import { BridgeArchitectureDiagram } from '~/components/docs/BridgeArchitectureDiagram';
interface Slide {
id: string;
@ -129,32 +130,7 @@ const slides: Slide[] = [
<div className="space-y-8">
<div className="max-w-4xl mx-auto">
<div className="bg-surface-secondary rounded-lg border border-border p-6">
<pre className="text-sm text-foreground-secondary font-mono overflow-x-auto whitespace-pre">
{`┌─────────────────────────────────────────────────────────────┐
Your Machine
Chrome MCP
Server @tpmjs/bridge CLI
Connects to MCP servers
Filesystem Registers tools with TPMJS
Server Polls for tool calls
Returns results
Your Custom
Server
HTTPS
TPMJS Cloud
Your AI uses
bridge tools
`}
</pre>
<BridgeArchitectureDiagram />
</div>
</div>
<div className="max-w-2xl mx-auto text-center">

View file

@ -0,0 +1,233 @@
'use client';
import * as d3 from 'd3';
import { useEffect, useRef } from 'react';
interface BridgeArchitectureDiagramProps {
className?: string;
}
export function BridgeArchitectureDiagram({ className }: BridgeArchitectureDiagramProps) {
const svgRef = useRef<SVGSVGElement>(null);
useEffect(() => {
if (!svgRef.current) return;
const svg = d3.select(svgRef.current);
svg.selectAll('*').remove();
const width = 700;
const height = 500;
svg.attr('viewBox', `0 0 ${width} ${height}`);
// Colors
const colors = {
machineBox: '#1e293b',
machineStroke: '#475569',
serverBox: '#334155',
serverStroke: '#64748b',
bridgeBox: '#0f766e',
bridgeStroke: '#14b8a6',
cloudBox: '#1e40af',
cloudStroke: '#3b82f6',
text: '#e2e8f0',
textMuted: '#94a3b8',
arrow: '#64748b',
};
// User's Machine container
const machineGroup = svg.append('g');
// Machine box
machineGroup
.append('rect')
.attr('x', 20)
.attr('y', 20)
.attr('width', width - 40)
.attr('height', 280)
.attr('rx', 12)
.attr('fill', colors.machineBox)
.attr('stroke', colors.machineStroke)
.attr('stroke-width', 2);
// Machine label
machineGroup
.append('text')
.attr('x', width / 2)
.attr('y', 50)
.attr('text-anchor', 'middle')
.attr('fill', colors.text)
.attr('font-size', '16px')
.attr('font-weight', '600')
.text('Your Machine');
// MCP Servers
const servers = [
{ name: 'Chrome MCP', y: 90 },
{ name: 'Filesystem', y: 160 },
{ name: 'Custom Server', y: 230 },
];
servers.forEach((server) => {
// Server box
machineGroup
.append('rect')
.attr('x', 50)
.attr('y', server.y)
.attr('width', 140)
.attr('height', 50)
.attr('rx', 8)
.attr('fill', colors.serverBox)
.attr('stroke', colors.serverStroke)
.attr('stroke-width', 1);
// Server label
machineGroup
.append('text')
.attr('x', 120)
.attr('y', server.y + 30)
.attr('text-anchor', 'middle')
.attr('fill', colors.text)
.attr('font-size', '13px')
.text(server.name);
// Arrow from server to bridge
machineGroup
.append('path')
.attr('d', `M 190 ${server.y + 25} L 270 ${server.y + 25}`)
.attr('stroke', colors.arrow)
.attr('stroke-width', 2)
.attr('fill', 'none')
.attr('marker-end', 'url(#arrowhead)');
});
// Bridge CLI box
machineGroup
.append('rect')
.attr('x', 280)
.attr('y', 80)
.attr('width', 360)
.attr('height', 210)
.attr('rx', 10)
.attr('fill', colors.bridgeBox)
.attr('stroke', colors.bridgeStroke)
.attr('stroke-width', 2);
// Bridge label
machineGroup
.append('text')
.attr('x', 460)
.attr('y', 115)
.attr('text-anchor', 'middle')
.attr('fill', colors.text)
.attr('font-size', '15px')
.attr('font-weight', '600')
.text('@tpmjs/bridge CLI');
// Bridge features
const features = [
'Connects to MCP servers',
'Registers tools with TPMJS',
'Polls for tool calls',
'Returns results',
];
features.forEach((feature, i) => {
machineGroup
.append('text')
.attr('x', 320)
.attr('y', 155 + i * 28)
.attr('fill', colors.textMuted)
.attr('font-size', '13px')
.text(`${feature}`);
});
// Arrow marker definition
svg
.append('defs')
.append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 0 10 10')
.attr('refX', 8)
.attr('refY', 5)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
.attr('fill', colors.arrow);
// HTTPS Arrow from machine to cloud
svg
.append('path')
.attr('d', `M ${width / 2} 300 L ${width / 2} 340`)
.attr('stroke', colors.arrow)
.attr('stroke-width', 3)
.attr('fill', 'none')
.attr('marker-end', 'url(#arrowhead)');
// HTTPS label
svg
.append('text')
.attr('x', width / 2 + 15)
.attr('y', 328)
.attr('fill', colors.textMuted)
.attr('font-size', '12px')
.text('HTTPS');
// TPMJS Cloud box
const cloudGroup = svg.append('g');
cloudGroup
.append('rect')
.attr('x', width / 2 - 140)
.attr('y', 350)
.attr('width', 280)
.attr('height', 120)
.attr('rx', 12)
.attr('fill', colors.cloudBox)
.attr('stroke', colors.cloudStroke)
.attr('stroke-width', 2);
// Cloud label
cloudGroup
.append('text')
.attr('x', width / 2)
.attr('y', 385)
.attr('text-anchor', 'middle')
.attr('fill', colors.text)
.attr('font-size', '16px')
.attr('font-weight', '600')
.text('TPMJS Cloud');
// Cloud description
cloudGroup
.append('text')
.attr('x', width / 2)
.attr('y', 420)
.attr('text-anchor', 'middle')
.attr('fill', colors.textMuted)
.attr('font-size', '14px')
.text('Your AI uses');
cloudGroup
.append('text')
.attr('x', width / 2)
.attr('y', 445)
.attr('text-anchor', 'middle')
.attr('fill', colors.textMuted)
.attr('font-size', '14px')
.text('bridge tools');
}, []);
return (
<div className={className}>
<svg
ref={svgRef}
className="w-full h-auto max-w-3xl mx-auto"
style={{ minHeight: '400px' }}
/>
</div>
);
}