refactor: rewrite tutorial slides with coherent AI SDK narrative
Complete rewrite of all 9 slides to create a coherent story for engineers: 1. Welcome - TPMJS: A Tool Registry for the AI SDK 2. Problem - AI SDK Tools Are Static (compiled in, bundle grows) 3. Solution - Two imports enable dynamic tools at runtime 4. HowItWorks - The two meta-tools (search + execute) 5. Discovery - searchTpmjsToolsTool schema and response 6. Integration - registryExecuteTool execution flow 7. Quality - What gets indexed (schemas, scores, health) 8. ToolDetail - Full agent conversation flow example 9. GetStarted - How to publish tools to the registry Also adds eslint.config.mjs to ignore .next build directory.
This commit is contained in:
parent
70d0c5ba94
commit
e298109e94
10 changed files with 640 additions and 717 deletions
16
apps/tutorial/eslint.config.mjs
Normal file
16
apps/tutorial/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import reactConfig from '@tpmjs/eslint-config/react.js';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'.next/**',
|
||||
'.turbo/**',
|
||||
'node_modules/**',
|
||||
'*.config.js',
|
||||
'*.config.ts',
|
||||
'next-env.d.ts',
|
||||
'eslint.config.mjs',
|
||||
],
|
||||
},
|
||||
...reactConfig,
|
||||
];
|
||||
|
|
@ -2,76 +2,91 @@
|
|||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const searchSchema = [
|
||||
' query: z.string(),',
|
||||
' category?: z.enum([...]),',
|
||||
' limit?: z.number().max(20),',
|
||||
];
|
||||
|
||||
const responseExample = [
|
||||
{ id: 'open', line: '{' },
|
||||
{ id: 'tools', line: ' "tools": [{' },
|
||||
{ id: 'toolId', line: ' "toolId": "@tpmjs/markdown::formatTable",' },
|
||||
{ id: 'desc', line: ' "description": "Format markdown tables",' },
|
||||
{ id: 'schema', line: ' "inputSchema": { ... },' },
|
||||
{ id: 'score', line: ' "qualityScore": 0.92' },
|
||||
{ id: 'closeArr', line: ' }]' },
|
||||
{ id: 'close', line: '}' },
|
||||
];
|
||||
|
||||
export function DiscoverySlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Background pulse */}
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-br from-cyan-900/20 via-transparent to-blue-900/20 pointer-events-none"
|
||||
animate={{ opacity: [0.3, 0.5, 0.3] }}
|
||||
transition={{ duration: 3, repeat: Number.POSITIVE_INFINITY, ease: 'easeInOut' }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-900/10 via-transparent to-blue-900/10 pointer-events-none" />
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-4xl"
|
||||
className="relative z-10 max-w-5xl w-full"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 200, delay: 0.2 }}
|
||||
className="text-6xl mb-8"
|
||||
>
|
||||
⚙️
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-5xl md:text-6xl font-bold text-white mb-4">Schema Extraction</h2>
|
||||
<p className="text-xl text-white/40 mb-12">
|
||||
The hard part.{' '}
|
||||
<span className="text-cyan-400 font-semibold">We run your code in a sandbox.</span>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">searchTpmjsToolsTool</h2>
|
||||
<p className="text-xl text-white/50 mb-10">
|
||||
Agent describes what it needs. Registry returns matches.
|
||||
</p>
|
||||
|
||||
{/* Code block showing extraction */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="max-w-2xl mx-auto p-6 rounded-xl bg-[#1e1e2e] text-left font-mono text-sm"
|
||||
>
|
||||
<div className="text-white/40 mb-3">
|
||||
{/* comment */}
|
||||
{'// We extract this from your actual code:'}
|
||||
</div>
|
||||
<div className="text-purple-400">inputSchema: {'{'}</div>
|
||||
<div className="text-white/80 pl-4">
|
||||
type: <span className="text-emerald-400">"object"</span>,
|
||||
</div>
|
||||
<div className="text-white/80 pl-4">properties: {'{'}</div>
|
||||
<div className="text-white/80 pl-8">
|
||||
url: {'{'} type: <span className="text-emerald-400">"string"</span>, format:{' '}
|
||||
<span className="text-emerald-400">"uri"</span> {'}'}
|
||||
</div>
|
||||
<div className="text-white/80 pl-8">
|
||||
timeout: {'{'} type: <span className="text-emerald-400">"number"</span>,
|
||||
default: <span className="text-cyan-400">30000</span> {'}'}
|
||||
</div>
|
||||
<div className="text-white/80 pl-4">{'}'}</div>
|
||||
<div className="text-purple-400">{'}'}</div>
|
||||
</motion.div>
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto text-left">
|
||||
{/* Input */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
>
|
||||
<div className="text-xs text-cyan-400/60 font-mono mb-2">inputSchema</div>
|
||||
<div className="bg-[#1e1e2e] p-4 rounded-xl font-mono text-sm">
|
||||
<div className="text-white/40">{'z.object({'}</div>
|
||||
{searchSchema.map((line) => (
|
||||
<div key={line} className="text-cyan-400 pl-2">
|
||||
{line}
|
||||
</div>
|
||||
))}
|
||||
<div className="text-white/40">{'})'}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Output */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
>
|
||||
<div className="text-xs text-emerald-400/60 font-mono mb-2">returns</div>
|
||||
<div className="bg-[#1e1e2e] p-4 rounded-xl font-mono text-sm">
|
||||
{responseExample.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={
|
||||
item.line.includes('toolId')
|
||||
? 'text-emerald-400'
|
||||
: item.line.includes('qualityScore')
|
||||
? 'text-yellow-400'
|
||||
: 'text-white/60'
|
||||
}
|
||||
>
|
||||
{item.line}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Key insight */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.2 }}
|
||||
className="mt-8 text-white/40 text-lg"
|
||||
transition={{ delay: 0.8 }}
|
||||
className="mt-8 text-white/40 text-sm"
|
||||
>
|
||||
Not documentation.{' '}
|
||||
<span className="text-emerald-400 font-semibold">
|
||||
Ground truth from TypeScript types.
|
||||
</span>
|
||||
BM25 search over 200+ tools. Ranked by relevance and quality score.
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,46 +2,31 @@
|
|||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const links = [
|
||||
{
|
||||
label: 'Browse Tools',
|
||||
href: 'https://tpmjs.com/tool-search',
|
||||
description: 'Search by name, category, quality',
|
||||
icon: '🔍',
|
||||
gradient: 'from-cyan-500 to-blue-500',
|
||||
},
|
||||
{
|
||||
label: 'Publish a Tool',
|
||||
href: 'https://tpmjs.com/docs/publishing',
|
||||
description: 'Add tpmjs-tool keyword to npm',
|
||||
icon: '📦',
|
||||
gradient: 'from-purple-500 to-pink-500',
|
||||
},
|
||||
{
|
||||
label: 'The Playground',
|
||||
href: 'https://tpmjs.com/playground',
|
||||
description: 'Run tools in browser sandbox',
|
||||
icon: '🎮',
|
||||
gradient: 'from-emerald-500 to-teal-500',
|
||||
},
|
||||
const packageJsonLines = [
|
||||
{ id: 'open', line: '{' },
|
||||
{ id: 'name', line: ' "name": "@your-org/your-tools",' },
|
||||
{ id: 'keywords', line: ' "keywords": ["tpmjs-tool"],' },
|
||||
{ id: 'tpmjs', line: ' "tpmjs": {' },
|
||||
{ id: 'tools', line: ' "tools": [{' },
|
||||
{ id: 'toolName', line: ' "name": "yourTool",' },
|
||||
{ id: 'desc', line: ' "description": "Does something useful"' },
|
||||
{ id: 'closeArr', line: ' }]' },
|
||||
{ id: 'closeTpmjs', line: ' }' },
|
||||
{ id: 'close', line: '}' },
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{ step: '1', text: 'Add tpmjs-tool keyword', color: 'cyan' },
|
||||
{ step: '2', text: 'Define tpmjs field with tool metadata', color: 'purple' },
|
||||
{ step: '3', text: 'npm publish', color: 'emerald' },
|
||||
];
|
||||
|
||||
export function GetStartedSlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Celebratory background */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<motion.div
|
||||
className="absolute w-[800px] h-[800px] rounded-full bg-gradient-to-r from-cyan-500/10 to-purple-500/10 blur-[150px]"
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
rotate: [0, 180, 360],
|
||||
}}
|
||||
transition={{
|
||||
duration: 20,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: 'linear',
|
||||
}}
|
||||
<div
|
||||
className="absolute w-[600px] h-[600px] rounded-full bg-gradient-to-r from-cyan-500/5 to-purple-500/5 blur-[100px]"
|
||||
style={{ top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -52,83 +37,107 @@ export function GetStartedSlide(): React.ReactElement {
|
|||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-4xl w-full"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 200, delay: 0.2 }}
|
||||
className="text-7xl mb-8"
|
||||
>
|
||||
🚀
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-5xl md:text-7xl font-bold text-white mb-4">Get Started</h2>
|
||||
<p className="text-xl md:text-2xl text-white/40 mb-16">
|
||||
Indexed automatically. Updated every 2 minutes.
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">Publish Your Tool</h2>
|
||||
<p className="text-xl text-white/50 mb-10">
|
||||
Add keyword. Define metadata. Publish. Indexed in 2 minutes.
|
||||
</p>
|
||||
|
||||
{/* CTA buttons */}
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-6">
|
||||
{links.map((link, index) => (
|
||||
<motion.a
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 + index * 0.15 }}
|
||||
whileHover={{ scale: 1.05, y: -5 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
className={`
|
||||
group relative w-full md:w-auto
|
||||
px-8 py-6 rounded-2xl
|
||||
bg-gradient-to-br ${link.gradient}
|
||||
text-white font-semibold text-lg
|
||||
shadow-lg shadow-black/20
|
||||
transition-shadow hover:shadow-xl hover:shadow-black/30
|
||||
`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-3xl">{link.icon}</span>
|
||||
<div className="text-left">
|
||||
<div className="font-bold">{link.label}</div>
|
||||
<div className="text-sm text-white/70">{link.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hover arrow */}
|
||||
<motion.div
|
||||
className="absolute right-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
initial={{ x: -10 }}
|
||||
whileHover={{ x: 0 }}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{/* package.json */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="text-left"
|
||||
>
|
||||
<div className="text-xs text-cyan-400/60 font-mono mb-2">package.json</div>
|
||||
<div className="bg-[#1e1e2e] p-4 rounded-xl font-mono text-sm">
|
||||
{packageJsonLines.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={
|
||||
item.line.includes('tpmjs-tool')
|
||||
? 'text-cyan-400'
|
||||
: item.line.includes('tpmjs')
|
||||
? 'text-purple-400'
|
||||
: item.line.includes('name') || item.line.includes('description')
|
||||
? 'text-emerald-400'
|
||||
: 'text-white/60'
|
||||
}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</motion.div>
|
||||
</motion.a>
|
||||
))}
|
||||
{item.line}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Steps */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="text-left"
|
||||
>
|
||||
<div className="text-xs text-white/40 font-mono mb-2">three steps</div>
|
||||
<div className="space-y-3">
|
||||
{steps.map((item, i) => (
|
||||
<motion.div
|
||||
key={item.step}
|
||||
initial={{ opacity: 0, x: 10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.7 + i * 0.1 }}
|
||||
className="flex items-center gap-3"
|
||||
>
|
||||
<span
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-mono ${
|
||||
item.color === 'cyan'
|
||||
? 'bg-cyan-500/20 text-cyan-400'
|
||||
: item.color === 'purple'
|
||||
? 'bg-purple-500/20 text-purple-400'
|
||||
: 'bg-emerald-500/20 text-emerald-400'
|
||||
}`}
|
||||
>
|
||||
{item.step}
|
||||
</span>
|
||||
<span className="text-sm text-white/70">{item.text}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.2 }}
|
||||
className="mt-6 p-4 rounded-lg bg-emerald-500/10 border border-emerald-500/20"
|
||||
>
|
||||
<code className="text-emerald-400 text-sm">npm publish</code>
|
||||
<div className="text-xs text-white/40 mt-1">That's it. We find you.</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.2 }}
|
||||
className="mt-16 text-white/30 text-sm"
|
||||
transition={{ delay: 1.5 }}
|
||||
className="mt-10 flex flex-wrap justify-center gap-4"
|
||||
>
|
||||
The missing layer between npm and AI agents.
|
||||
<a
|
||||
href="https://tpmjs.com/tool-search"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-6 py-3 rounded-lg bg-cyan-500/20 text-cyan-400 text-sm font-medium hover:bg-cyan-500/30 transition-colors"
|
||||
>
|
||||
Browse Registry
|
||||
</a>
|
||||
<a
|
||||
href="https://tpmjs.com/playground"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-6 py-3 rounded-lg bg-purple-500/20 text-purple-400 text-sm font-medium hover:bg-purple-500/30 transition-colors"
|
||||
>
|
||||
Try Playground
|
||||
</a>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,89 +2,73 @@
|
|||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const steps = [
|
||||
{ icon: '📦', label: 'npm publish', description: 'Add tpmjs-tool keyword' },
|
||||
{ icon: '🔄', label: 'Auto-sync', description: 'Indexed in ~2 minutes' },
|
||||
{ icon: '⚙️', label: 'Schema extracted', description: 'From actual code' },
|
||||
{ icon: '✅', label: 'Health checked', description: 'Import + execution' },
|
||||
const tools = [
|
||||
{
|
||||
name: 'searchTpmjsToolsTool',
|
||||
package: '@tpmjs/search-registry',
|
||||
description: 'Agent searches the registry by query, category, or context',
|
||||
color: 'cyan',
|
||||
},
|
||||
{
|
||||
name: 'registryExecuteTool',
|
||||
package: '@tpmjs/registry-execute',
|
||||
description: 'Agent executes a discovered tool by ID in a sandbox',
|
||||
color: 'purple',
|
||||
},
|
||||
];
|
||||
|
||||
export function HowItWorksSlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Background gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-900/10 via-transparent to-purple-900/10 pointer-events-none" />
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-900/10 via-transparent to-purple-900/10 pointer-events-none" />
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-5xl w-full"
|
||||
className="relative z-10 max-w-4xl w-full"
|
||||
>
|
||||
<h2 className="text-5xl md:text-6xl font-bold text-white mb-4">How It Works</h2>
|
||||
<p className="text-xl text-white/40 mb-16">Automated pipeline. No manual curation.</p>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">Two Meta-Tools</h2>
|
||||
<p className="text-xl text-white/50 mb-12">Tools that discover and execute other tools</p>
|
||||
|
||||
{/* Flow diagram */}
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-4 md:gap-0">
|
||||
{steps.map((step, index) => (
|
||||
<div key={step.label} className="flex items-center">
|
||||
{/* Step card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 + index * 0.2 }}
|
||||
className="flex flex-col items-center"
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-3xl mx-auto">
|
||||
{tools.map((tool, index) => (
|
||||
<motion.div
|
||||
key={tool.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 + index * 0.2 }}
|
||||
className={`p-6 rounded-xl bg-white/5 border text-left ${
|
||||
tool.color === 'cyan' ? 'border-cyan-500/30' : 'border-purple-500/30'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`text-xs font-mono mb-2 ${
|
||||
tool.color === 'cyan' ? 'text-cyan-400/60' : 'text-purple-400/60'
|
||||
}`}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{
|
||||
delay: 0.5 + index * 0.2,
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
}}
|
||||
className="w-20 h-20 md:w-24 md:h-24 rounded-2xl bg-gradient-to-br from-white/10 to-white/5 border border-white/10 flex items-center justify-center text-4xl md:text-5xl mb-4 hover:from-white/15 hover:to-white/10 transition-colors"
|
||||
>
|
||||
{step.icon}
|
||||
</motion.div>
|
||||
<span className="text-white font-semibold text-lg">{step.label}</span>
|
||||
<span className="text-white/40 text-sm mt-1">{step.description}</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Arrow */}
|
||||
{index < steps.length - 1 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scaleX: 0 }}
|
||||
animate={{ opacity: 1, scaleX: 1 }}
|
||||
transition={{ delay: 0.8 + index * 0.2 }}
|
||||
className="hidden md:flex items-center mx-6"
|
||||
>
|
||||
<div className="w-12 h-px bg-gradient-to-r from-cyan-500/50 to-purple-500/50" />
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="w-4 h-4 text-purple-500/50 -ml-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
{tool.package}
|
||||
</div>
|
||||
<div
|
||||
className={`text-lg font-semibold mb-3 ${
|
||||
tool.color === 'cyan' ? 'text-cyan-400' : 'text-purple-400'
|
||||
}`}
|
||||
>
|
||||
{tool.name}
|
||||
</div>
|
||||
<div className="text-sm text-white/50">{tool.description}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Animated highlight line */}
|
||||
<motion.div
|
||||
initial={{ scaleX: 0, opacity: 0 }}
|
||||
animate={{ scaleX: 1, opacity: 1 }}
|
||||
transition={{ delay: 1.8, duration: 1 }}
|
||||
className="mt-16 h-1 bg-gradient-to-r from-transparent via-cyan-500/30 to-transparent rounded-full max-w-md mx-auto"
|
||||
/>
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1 }}
|
||||
className="mt-10 text-white/40 text-sm font-mono"
|
||||
>
|
||||
Both are standard AI SDK tools. Pass them to generateText like any other.
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,123 +1,100 @@
|
|||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const codeLines = [
|
||||
{ text: '// Quality score: 0.00 - 1.00', delay: 0.5 },
|
||||
{ text: "const tier = tier === 'rich' ? 0.6 : 0.4;", delay: 0.8 },
|
||||
{ text: 'const downloads = Math.min(0.2, log10(d));', delay: 1.1 },
|
||||
{ text: 'const stars = Math.min(0.1, log10(s));', delay: 1.4 },
|
||||
{ text: '', delay: 1.7 },
|
||||
{ text: '// Rich = has params, returns, env', delay: 2.0 },
|
||||
{ text: 'const score = tier + downloads + stars;', delay: 2.3 },
|
||||
const executeSchema = [
|
||||
' toolId: z.string(),',
|
||||
' params: z.record(z.unknown()),',
|
||||
' env?: z.record(z.string()),',
|
||||
];
|
||||
|
||||
function TypewriterLine({ text, delay }: { text: string; delay: number }) {
|
||||
const [displayText, setDisplayText] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!text) {
|
||||
setDisplayText('');
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
let i = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (i <= text.length) {
|
||||
setDisplayText(text.slice(0, i));
|
||||
i++;
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 30);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, delay * 1000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [text, delay]);
|
||||
|
||||
// Simple text display without syntax highlighting to avoid dangerouslySetInnerHTML
|
||||
const isComment = displayText.startsWith('//');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`font-mono text-sm md:text-base leading-relaxed ${isComment ? 'text-white/30' : 'text-white/80'}`}
|
||||
>
|
||||
{displayText || '\u00A0'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const flowSteps = [
|
||||
{ step: '1', text: 'Fetch tool metadata from registry', color: 'cyan' },
|
||||
{ step: '2', text: 'Import package from esm.sh', color: 'purple' },
|
||||
{ step: '3', text: 'Execute in isolated sandbox', color: 'emerald' },
|
||||
{ step: '4', text: 'Return result to agent', color: 'yellow' },
|
||||
];
|
||||
|
||||
export function IntegrationSlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-purple-900/10 via-transparent to-pink-900/10 pointer-events-none" />
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-purple-900/10 via-transparent to-emerald-900/10 pointer-events-none" />
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-4xl w-full"
|
||||
className="relative z-10 max-w-5xl w-full"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 200, delay: 0.2 }}
|
||||
className="text-6xl mb-8"
|
||||
>
|
||||
📊
|
||||
</motion.div>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">registryExecuteTool</h2>
|
||||
<p className="text-xl text-white/50 mb-10">
|
||||
Run any tool from the registry. No install. No bundle.
|
||||
</p>
|
||||
|
||||
<h2 className="text-5xl md:text-6xl font-bold text-white mb-4">Quality Scoring</h2>
|
||||
<p className="text-xl text-white/40 mb-12">Deliberately simple. Not trying to be clever.</p>
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{/* Input */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="text-left"
|
||||
>
|
||||
<div className="text-xs text-purple-400/60 font-mono mb-2">inputSchema</div>
|
||||
<div className="bg-[#1e1e2e] p-4 rounded-xl font-mono text-sm">
|
||||
<div className="text-white/40">{'z.object({'}</div>
|
||||
{executeSchema.map((line) => (
|
||||
<div key={line} className="text-purple-400 pl-2">
|
||||
{line}
|
||||
</div>
|
||||
))}
|
||||
<div className="text-white/40">{'})'}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Code block */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="relative max-w-2xl mx-auto"
|
||||
>
|
||||
{/* Window chrome */}
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-[#1e1e2e] rounded-t-xl border-b border-white/5">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/80" />
|
||||
<span className="ml-4 text-xs text-white/30 font-mono">agent.ts</span>
|
||||
</div>
|
||||
{/* Flow */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="text-left"
|
||||
>
|
||||
<div className="text-xs text-white/40 font-mono mb-2">execution flow</div>
|
||||
<div className="space-y-2">
|
||||
{flowSteps.map((item, i) => (
|
||||
<motion.div
|
||||
key={item.step}
|
||||
initial={{ opacity: 0, x: 10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.7 + i * 0.1 }}
|
||||
className="flex items-center gap-3"
|
||||
>
|
||||
<span
|
||||
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-mono ${
|
||||
item.color === 'cyan'
|
||||
? 'bg-cyan-500/20 text-cyan-400'
|
||||
: item.color === 'purple'
|
||||
? 'bg-purple-500/20 text-purple-400'
|
||||
: item.color === 'emerald'
|
||||
? 'bg-emerald-500/20 text-emerald-400'
|
||||
: 'bg-yellow-500/20 text-yellow-400'
|
||||
}`}
|
||||
>
|
||||
{item.step}
|
||||
</span>
|
||||
<span className="text-sm text-white/60">{item.text}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Code content */}
|
||||
<div className="bg-[#1e1e2e] p-6 rounded-b-xl text-left overflow-x-auto">
|
||||
{codeLines.map((line) => (
|
||||
<TypewriterLine key={line.text || line.delay} text={line.text} delay={line.delay} />
|
||||
))}
|
||||
<motion.span
|
||||
animate={{ opacity: [1, 0, 1] }}
|
||||
transition={{ duration: 1, repeat: Number.POSITIVE_INFINITY }}
|
||||
className="inline-block w-2 h-5 bg-cyan-400 ml-0.5"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Score factors */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 3 }}
|
||||
className="mt-8 flex flex-wrap justify-center gap-3"
|
||||
transition={{ delay: 1.2 }}
|
||||
className="mt-8 text-white/40 text-sm"
|
||||
>
|
||||
{['Tier: 40-60%', 'Downloads: 0-20%', 'Stars: 0-10%', 'Richness: 0-10%'].map((factor) => (
|
||||
<span
|
||||
key={factor}
|
||||
className="px-3 py-1 rounded-full bg-cyan-500/10 border border-cyan-500/20 text-cyan-400/70 text-sm font-mono"
|
||||
>
|
||||
{factor}
|
||||
</span>
|
||||
))}
|
||||
Sandbox runs on Railway. 5s timeout. Isolated per execution.
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,103 +2,85 @@
|
|||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const floatingIcons = [
|
||||
{ icon: '🔧', x: '15%', y: '20%', delay: 0 },
|
||||
{ icon: '⚙️', x: '75%', y: '15%', delay: 0.2 },
|
||||
{ icon: '🔌', x: '85%', y: '60%', delay: 0.4 },
|
||||
{ icon: '📦', x: '10%', y: '70%', delay: 0.6 },
|
||||
{ icon: '🛠️', x: '60%', y: '80%', delay: 0.8 },
|
||||
{ icon: '🔗', x: '25%', y: '45%', delay: 1 },
|
||||
{ icon: '📡', x: '80%', y: '35%', delay: 1.2 },
|
||||
{ icon: '💾', x: '40%', y: '25%', delay: 1.4 },
|
||||
const codeLines = [
|
||||
"import { weatherTool } from './tools/weather';",
|
||||
"import { searchTool } from './tools/search';",
|
||||
"import { calendarTool } from './tools/calendar';",
|
||||
"import { emailTool } from './tools/email';",
|
||||
'// ... 47 more imports',
|
||||
'',
|
||||
'const result = await generateText({',
|
||||
" model: openai('gpt-4-turbo'),",
|
||||
' tools: { weather, search, calendar, email, ... },',
|
||||
'});',
|
||||
];
|
||||
|
||||
export function ProblemSlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Floating chaotic icons */}
|
||||
{floatingIcons.map((item) => (
|
||||
<motion.div
|
||||
key={item.icon}
|
||||
className="absolute text-4xl md:text-5xl opacity-20"
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{
|
||||
opacity: 0.2,
|
||||
scale: 1,
|
||||
x: [0, Math.random() * 40 - 20, 0],
|
||||
y: [0, Math.random() * 40 - 20, 0],
|
||||
rotate: [0, Math.random() * 30 - 15, 0],
|
||||
}}
|
||||
transition={{
|
||||
opacity: { duration: 0.5, delay: item.delay },
|
||||
scale: { duration: 0.5, delay: item.delay },
|
||||
x: {
|
||||
duration: 4 + Math.random() * 2,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
y: {
|
||||
duration: 3 + Math.random() * 2,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
rotate: {
|
||||
duration: 5 + Math.random() * 2,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
}}
|
||||
style={{ left: item.x, top: item.y }}
|
||||
>
|
||||
{item.icon}
|
||||
</motion.div>
|
||||
))}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-orange-900/10 via-transparent to-red-900/10 pointer-events-none" />
|
||||
|
||||
{/* Red warning gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-red-900/10 via-transparent to-orange-900/10 pointer-events-none" />
|
||||
|
||||
{/* Content */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
className="relative z-10 max-w-4xl"
|
||||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-4xl w-full"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 200, delay: 0.2 }}
|
||||
className="text-6xl mb-8"
|
||||
>
|
||||
😵
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-5xl md:text-6xl font-bold text-white mb-6">The Problem</h2>
|
||||
|
||||
<p className="text-xl md:text-2xl text-white/60 leading-relaxed max-w-2xl">
|
||||
npm has <span className="text-red-400 font-semibold">2 million packages</span>.
|
||||
<br />
|
||||
Which ones are AI-callable tools? Which ones{' '}
|
||||
<span className="text-orange-400 font-semibold">actually work</span>?
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">AI SDK Tools Are Static</h2>
|
||||
<p className="text-xl text-white/50 mb-12">
|
||||
You import them. You bundle them. They're compiled in.
|
||||
</p>
|
||||
|
||||
{/* Code block */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="max-w-2xl mx-auto text-left"
|
||||
>
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-[#1e1e2e] rounded-t-xl border-b border-white/5">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/80" />
|
||||
<span className="ml-4 text-xs text-white/30 font-mono">agent.ts</span>
|
||||
</div>
|
||||
<div className="bg-[#1e1e2e] p-6 rounded-b-xl overflow-x-auto">
|
||||
{codeLines.map((line, i) => (
|
||||
<motion.div
|
||||
key={line || `empty-${i}`}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 + i * 0.1 }}
|
||||
className={`font-mono text-sm leading-relaxed ${
|
||||
line.startsWith('import')
|
||||
? 'text-purple-400'
|
||||
: line.startsWith('//')
|
||||
? 'text-white/30'
|
||||
: line.includes('generateText')
|
||||
? 'text-cyan-400'
|
||||
: 'text-white/70'
|
||||
}`}
|
||||
>
|
||||
{line || '\u00A0'}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1 }}
|
||||
className="mt-12 flex flex-wrap justify-center gap-4"
|
||||
transition={{ delay: 1.5 }}
|
||||
className="mt-8 flex flex-wrap justify-center gap-3"
|
||||
>
|
||||
{["Can't find them", "Can't trust them", "Can't compare them", 'No schemas'].map(
|
||||
(word, i) => (
|
||||
<motion.span
|
||||
key={word}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 1.2 + i * 0.1 }}
|
||||
className="px-4 py-2 rounded-full border border-red-500/30 text-red-400/60 text-sm font-mono"
|
||||
{['Every tool compiled in', 'Bundle grows forever', 'No runtime discovery'].map(
|
||||
(text) => (
|
||||
<span
|
||||
key={text}
|
||||
className="px-3 py-1.5 rounded-full border border-orange-500/30 text-orange-400/70 text-sm font-mono"
|
||||
>
|
||||
{word}
|
||||
</motion.span>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -1,137 +1,106 @@
|
|||
'use client';
|
||||
|
||||
import { animate, motion, useMotionValue } from 'framer-motion';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
function AnimatedGauge({ value, delay = 0 }: { value: number; delay?: number }) {
|
||||
const progress = useMotionValue(0);
|
||||
const [displayValue, setDisplayValue] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
animate(progress, value, {
|
||||
duration: 1.5,
|
||||
ease: 'easeOut',
|
||||
});
|
||||
}, delay * 1000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [progress, value, delay]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = progress.on('change', (v) => setDisplayValue(Math.round(v)));
|
||||
return unsubscribe;
|
||||
}, [progress]);
|
||||
|
||||
const circumference = 2 * Math.PI * 45;
|
||||
const strokeDashoffset = circumference - (displayValue / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div className="relative w-32 h-32">
|
||||
<svg aria-hidden="true" className="w-full h-full transform -rotate-90">
|
||||
{/* Background circle */}
|
||||
<circle cx="64" cy="64" r="45" fill="none" stroke="rgba(255,255,255,0.1)" strokeWidth="8" />
|
||||
{/* Progress circle */}
|
||||
<circle
|
||||
cx="64"
|
||||
cy="64"
|
||||
r="45"
|
||||
fill="none"
|
||||
stroke="url(#gradient)"
|
||||
strokeWidth="8"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#22d3ee" />
|
||||
<stop offset="100%" stopColor="#a855f7" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-2xl font-bold text-white">{displayValue}%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const registryData = [
|
||||
{ field: 'toolId', value: '"@tpmjs/markdown::formatTable"', color: 'cyan' },
|
||||
{ field: 'description', value: '"Format markdown tables"', color: 'white' },
|
||||
{ field: 'inputSchema', value: '{ type: "object", ... }', color: 'purple' },
|
||||
{ field: 'returnSchema', value: '{ type: "string" }', color: 'purple' },
|
||||
{ field: 'envKeys', value: '[]', color: 'yellow' },
|
||||
{ field: 'qualityScore', value: '0.92', color: 'emerald' },
|
||||
];
|
||||
|
||||
const qualityFactors = [
|
||||
{ name: 'Import Health', icon: '📦', score: 95, color: 'cyan', desc: 'Can we require() it?' },
|
||||
{ name: 'Execution Health', icon: '▶️', score: 88, color: 'emerald', desc: 'Does it run at all?' },
|
||||
{ name: 'HEALTHY', icon: '✅', score: 100, color: 'emerald', desc: 'Passed both checks' },
|
||||
{ name: 'BROKEN', icon: '❌', score: 0, color: 'yellow', desc: 'Labeled, not hidden' },
|
||||
{ name: 'Schema Completeness', desc: 'Input/output types defined' },
|
||||
{ name: 'npm Downloads', desc: 'Community adoption signal' },
|
||||
{ name: 'Health Status', desc: 'Can it import and execute?' },
|
||||
];
|
||||
|
||||
export function QualitySlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-yellow-900/10 via-transparent to-emerald-900/10 pointer-events-none" />
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-4xl w-full"
|
||||
className="relative z-10 max-w-5xl w-full"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 200, delay: 0.2 }}
|
||||
className="text-6xl mb-8"
|
||||
>
|
||||
⭐
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-5xl md:text-6xl font-bold text-white mb-4">Health Checks</h2>
|
||||
<p className="text-xl text-white/40 mb-12">
|
||||
We don't hide broken tools. We <span className="text-yellow-400">label them</span>.
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">What Gets Indexed</h2>
|
||||
<p className="text-xl text-white/50 mb-10">
|
||||
Schemas extracted. Quality scored. Health checked.
|
||||
</p>
|
||||
|
||||
{/* Main gauge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
className="flex justify-center mb-12"
|
||||
>
|
||||
<AnimatedGauge value={92} delay={0.6} />
|
||||
</motion.div>
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{/* Registry entry */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="text-left"
|
||||
>
|
||||
<div className="text-xs text-emerald-400/60 font-mono mb-2">registry entry</div>
|
||||
<div className="bg-[#1e1e2e] p-4 rounded-xl font-mono text-sm">
|
||||
<div className="text-white/40">{'{'}</div>
|
||||
{registryData.map((item) => (
|
||||
<div key={item.field} className="pl-2">
|
||||
<span className="text-white/60">{item.field}: </span>
|
||||
<span
|
||||
className={
|
||||
item.color === 'cyan'
|
||||
? 'text-cyan-400'
|
||||
: item.color === 'purple'
|
||||
? 'text-purple-400'
|
||||
: item.color === 'emerald'
|
||||
? 'text-emerald-400'
|
||||
: item.color === 'yellow'
|
||||
? 'text-yellow-400'
|
||||
: 'text-white/60'
|
||||
}
|
||||
>
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="text-white/40">{'}'}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Quality factors */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
{qualityFactors.map((factor, index) => (
|
||||
<motion.div
|
||||
key={factor.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 1 + index * 0.15 }}
|
||||
className="p-4 rounded-xl bg-white/5 border border-white/10"
|
||||
>
|
||||
<div className="text-3xl mb-2">{factor.icon}</div>
|
||||
<div className="text-base font-semibold text-white mb-1">{factor.name}</div>
|
||||
<div className="text-xs text-white/40 mb-3">{factor.desc}</div>
|
||||
<div className="w-full h-2 bg-white/10 rounded-full overflow-hidden">
|
||||
{/* Quality factors */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="text-left"
|
||||
>
|
||||
<div className="text-xs text-yellow-400/60 font-mono mb-2">qualityScore factors</div>
|
||||
<div className="space-y-3">
|
||||
{qualityFactors.map((factor, i) => (
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${factor.score}%` }}
|
||||
transition={{ delay: 1.5 + index * 0.15, duration: 1 }}
|
||||
className={`h-full rounded-full ${
|
||||
factor.color === 'cyan'
|
||||
? 'bg-cyan-400'
|
||||
: factor.color === 'purple'
|
||||
? 'bg-purple-400'
|
||||
: factor.color === 'emerald'
|
||||
? 'bg-emerald-400'
|
||||
: 'bg-yellow-400'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
key={factor.name}
|
||||
initial={{ opacity: 0, x: 10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.7 + i * 0.1 }}
|
||||
className="p-3 rounded-lg bg-white/5 border border-white/10"
|
||||
>
|
||||
<div className="text-sm font-medium text-white">{factor.name}</div>
|
||||
<div className="text-xs text-white/40">{factor.desc}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.2 }}
|
||||
className="mt-8 text-white/40 text-sm"
|
||||
>
|
||||
Synced from npm every 2 minutes. 200+ tools indexed.
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,84 +2,82 @@
|
|||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const gridItems = Array.from({ length: 12 }, (_, i) => ({
|
||||
icon: ['🔧', '⚙️', '📦', '🔌', '🛠️', '📡', '💾', '🔗', '🎯', '⚡', '🌐', '🔐'][i],
|
||||
delay: i * 0.05,
|
||||
}));
|
||||
const codeLines = [
|
||||
"import { searchTpmjsToolsTool } from '@tpmjs/search-registry';",
|
||||
"import { registryExecuteTool } from '@tpmjs/registry-execute';",
|
||||
'',
|
||||
'const result = await generateText({',
|
||||
" model: openai('gpt-4-turbo'),",
|
||||
' tools: { search: searchTpmjsToolsTool, execute: registryExecuteTool },',
|
||||
" prompt: 'Format this markdown table for me...',",
|
||||
'});',
|
||||
];
|
||||
|
||||
export function SolutionSlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Green success gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-emerald-900/10 via-transparent to-cyan-900/10 pointer-events-none" />
|
||||
|
||||
{/* Content */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-4xl"
|
||||
className="relative z-10 max-w-4xl w-full"
|
||||
>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">
|
||||
What If Tools Were Dynamic?
|
||||
</h2>
|
||||
<p className="text-xl text-white/50 mb-12">Two imports. Infinite tools at runtime.</p>
|
||||
|
||||
{/* Code block */}
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 200, delay: 0.2 }}
|
||||
className="text-6xl mb-8"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="max-w-2xl mx-auto text-left"
|
||||
>
|
||||
✨
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-5xl md:text-6xl font-bold text-white mb-6">What TPMJS Is</h2>
|
||||
|
||||
<p className="text-xl md:text-2xl text-white/60 leading-relaxed mb-12 max-w-2xl">
|
||||
A <span className="text-cyan-400 font-semibold">registry</span> that indexes npm packages
|
||||
for AI tool use.
|
||||
<br />
|
||||
<span className="text-emerald-400 font-semibold">Extracts schemas from code</span>. Scores
|
||||
quality. Checks health.
|
||||
</p>
|
||||
|
||||
{/* Organized grid of icons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="grid grid-cols-4 md:grid-cols-6 gap-4 max-w-lg mx-auto"
|
||||
>
|
||||
{gridItems.map((item) => (
|
||||
<motion.div
|
||||
key={item.icon}
|
||||
initial={{ opacity: 0, scale: 0, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
transition={{
|
||||
delay: 0.8 + item.delay,
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
damping: 20,
|
||||
}}
|
||||
className="w-12 h-12 md:w-14 md:h-14 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center text-2xl hover:bg-white/10 hover:border-white/20 transition-colors cursor-default"
|
||||
>
|
||||
{item.icon}
|
||||
</motion.div>
|
||||
))}
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-[#1e1e2e] rounded-t-xl border-b border-white/5">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/80" />
|
||||
<span className="ml-4 text-xs text-white/30 font-mono">agent.ts</span>
|
||||
</div>
|
||||
<div className="bg-[#1e1e2e] p-6 rounded-b-xl overflow-x-auto">
|
||||
{codeLines.map((line, i) => (
|
||||
<motion.div
|
||||
key={line || `empty-${i}`}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.5 + i * 0.1 }}
|
||||
className={`font-mono text-sm leading-relaxed ${
|
||||
line.startsWith('import')
|
||||
? 'text-emerald-400'
|
||||
: line.includes('searchTpmjsToolsTool') || line.includes('registryExecuteTool')
|
||||
? 'text-cyan-400'
|
||||
: line.includes('generateText')
|
||||
? 'text-purple-400'
|
||||
: 'text-white/70'
|
||||
}`}
|
||||
>
|
||||
{line || '\u00A0'}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.5 }}
|
||||
className="mt-12 flex flex-wrap justify-center gap-4"
|
||||
className="mt-8 flex flex-wrap justify-center gap-3"
|
||||
>
|
||||
{['Registry', 'Schema extraction', 'Quality scoring', 'Health checks'].map((word, i) => (
|
||||
<motion.span
|
||||
key={word}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 1.7 + i * 0.1 }}
|
||||
className="px-4 py-2 rounded-full border border-emerald-500/30 text-emerald-400/60 text-sm font-mono"
|
||||
{['Discover at runtime', 'Execute in sandbox', 'No bundling'].map((text) => (
|
||||
<span
|
||||
key={text}
|
||||
className="px-3 py-1.5 rounded-full border border-emerald-500/30 text-emerald-400/70 text-sm font-mono"
|
||||
>
|
||||
{word}
|
||||
</motion.span>
|
||||
{text}
|
||||
</span>
|
||||
))}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -2,111 +2,107 @@
|
|||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const roleStyles: Record<string, string> = {
|
||||
user: 'bg-white/10 text-white/60',
|
||||
agent: 'bg-cyan-500/20 text-cyan-400',
|
||||
registry: 'bg-emerald-500/20 text-emerald-400',
|
||||
sandbox: 'bg-yellow-500/20 text-yellow-400',
|
||||
};
|
||||
|
||||
const colorStyles: Record<string, string> = {
|
||||
white: 'text-white/70',
|
||||
cyan: 'text-cyan-400',
|
||||
purple: 'text-purple-400',
|
||||
emerald: 'text-emerald-400',
|
||||
yellow: 'text-yellow-400',
|
||||
};
|
||||
|
||||
const conversation = [
|
||||
{
|
||||
id: 'user-ask',
|
||||
role: 'user',
|
||||
content: '"Format this CSV as a markdown table"',
|
||||
color: 'white',
|
||||
},
|
||||
{
|
||||
id: 'agent-search',
|
||||
role: 'agent',
|
||||
content: 'searchTpmjsToolsTool({ query: "markdown table" })',
|
||||
color: 'cyan',
|
||||
},
|
||||
{
|
||||
id: 'registry-found',
|
||||
role: 'registry',
|
||||
content: '→ Found: @tpmjs/markdown::formatTable',
|
||||
color: 'emerald',
|
||||
},
|
||||
{
|
||||
id: 'agent-execute',
|
||||
role: 'agent',
|
||||
content: 'registryExecuteTool({ toolId: "...", params: {...} })',
|
||||
color: 'purple',
|
||||
},
|
||||
{
|
||||
id: 'sandbox-result',
|
||||
role: 'sandbox',
|
||||
content: '→ Executed in 120ms',
|
||||
color: 'yellow',
|
||||
},
|
||||
{
|
||||
id: 'agent-respond',
|
||||
role: 'agent',
|
||||
content: '"Here\'s your formatted table: ..."',
|
||||
color: 'white',
|
||||
},
|
||||
];
|
||||
|
||||
export function ToolDetailSlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-indigo-900/10 via-transparent to-cyan-900/10 pointer-events-none" />
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="relative z-10 max-w-4xl w-full"
|
||||
className="relative z-10 max-w-3xl w-full"
|
||||
>
|
||||
<h2 className="text-5xl md:text-6xl font-bold text-white mb-4">What We Store</h2>
|
||||
<p className="text-xl text-white/40 mb-12">
|
||||
Everything an agent needs to <span className="text-cyan-400">call a tool correctly</span>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">Full Agent Flow</h2>
|
||||
<p className="text-xl text-white/50 mb-10">
|
||||
User asks. Agent searches. Tool executes. Done.
|
||||
</p>
|
||||
|
||||
{/* Mock tool card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="max-w-xl mx-auto p-6 rounded-2xl bg-gradient-to-br from-white/10 to-white/5 border border-white/10 text-left"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.5, type: 'spring' }}
|
||||
className="w-12 h-12 rounded-xl bg-gradient-to-br from-cyan-500 to-blue-500 flex items-center justify-center text-2xl"
|
||||
>
|
||||
🔍
|
||||
</motion.div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-white">webSearch</h3>
|
||||
<span className="text-sm text-white/40 font-mono">@tpmjs/web-tools</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3 text-left">
|
||||
{conversation.map((msg, i) => (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.7 }}
|
||||
className="px-2 py-1 rounded bg-emerald-500/20 text-emerald-400 text-xs font-medium"
|
||||
key={msg.id}
|
||||
initial={{ opacity: 0, x: msg.role === 'user' ? -20 : 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.3 + i * 0.15 }}
|
||||
className={`flex items-start gap-3 ${msg.role === 'user' ? '' : 'pl-4'}`}
|
||||
>
|
||||
Verified
|
||||
<span
|
||||
className={`text-xs font-mono px-2 py-1 rounded ${roleStyles[msg.role] ?? roleStyles.user}`}
|
||||
>
|
||||
{msg.role}
|
||||
</span>
|
||||
<span className={`font-mono text-sm ${colorStyles[msg.color] ?? colorStyles.white}`}>
|
||||
{msg.content}
|
||||
</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.5 }}
|
||||
className="mt-10 p-4 rounded-xl bg-white/5 border border-white/10"
|
||||
>
|
||||
<div className="text-sm text-white/60">
|
||||
The agent never imported <span className="text-cyan-400 font-mono">formatTable</span>.
|
||||
It discovered and executed it at runtime.
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.6 }}
|
||||
className="text-white/60 mb-6"
|
||||
>
|
||||
Search the web and return structured results. Supports query filtering, result limits,
|
||||
and domain restrictions.
|
||||
</motion.p>
|
||||
|
||||
{/* Metadata grid */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.8 }}
|
||||
className="grid grid-cols-3 gap-4 mb-6"
|
||||
>
|
||||
<div className="p-3 rounded-lg bg-white/5">
|
||||
<div className="text-xs text-white/40 mb-1">Downloads</div>
|
||||
<div className="text-lg font-bold text-white">12.4k</div>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-white/5">
|
||||
<div className="text-xs text-white/40 mb-1">Quality</div>
|
||||
<div className="text-lg font-bold text-cyan-400">94%</div>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-white/5">
|
||||
<div className="text-xs text-white/40 mb-1">Category</div>
|
||||
<div className="text-lg font-bold text-purple-400">Search</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Schema preview */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1 }}
|
||||
className="p-4 rounded-lg bg-black/30 font-mono text-sm"
|
||||
>
|
||||
<div className="text-white/40 mb-2">{'// Extracted from actual code'}</div>
|
||||
<div className="text-purple-400">
|
||||
inputSchema: <span className="text-white/60">JSON Schema</span>
|
||||
</div>
|
||||
<div className="text-purple-400">
|
||||
returnSchema: <span className="text-white/60">JSON Schema</span>
|
||||
</div>
|
||||
<div className="text-purple-400">
|
||||
envKeys: <span className="text-white/60">["API_KEY"]</span>
|
||||
</div>
|
||||
<div className="text-purple-400">
|
||||
tier: <span className="text-emerald-400">"rich"</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,76 +4,53 @@ import { motion } from 'framer-motion';
|
|||
|
||||
export function WelcomeSlide(): React.ReactElement {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full px-8 text-center">
|
||||
{/* Animated background orbs */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<motion.div
|
||||
className="absolute w-[600px] h-[600px] rounded-full bg-cyan-500/10 blur-[120px]"
|
||||
animate={{
|
||||
x: [0, 100, 0],
|
||||
y: [0, -50, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 10,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
style={{ top: '10%', left: '20%' }}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute w-[500px] h-[500px] rounded-full bg-purple-500/10 blur-[100px]"
|
||||
animate={{
|
||||
x: [0, -80, 0],
|
||||
y: [0, 60, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 12,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
style={{ bottom: '20%', right: '15%' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center overflow-hidden">
|
||||
{/* Subtle gradient background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-900/20 via-transparent to-purple-900/20 pointer-events-none" />
|
||||
|
||||
{/* Logo / Title */}
|
||||
{/* Main content */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="relative z-10"
|
||||
>
|
||||
<h1 className="text-8xl md:text-9xl font-bold tracking-tighter">
|
||||
<span className="bg-gradient-to-r from-cyan-400 via-blue-400 to-purple-500 bg-clip-text text-transparent">
|
||||
TPMJS
|
||||
</span>
|
||||
</h1>
|
||||
</motion.div>
|
||||
|
||||
{/* Tagline */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5 }}
|
||||
className="mt-6 text-2xl md:text-3xl text-white/60 font-light max-w-2xl"
|
||||
transition={{ duration: 0.8, ease: 'easeOut' }}
|
||||
className="relative z-10 max-w-4xl"
|
||||
>
|
||||
The missing layer between "LLMs can call tools" and "which tool,
|
||||
exactly?"
|
||||
</motion.p>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<span className="text-6xl md:text-7xl font-bold bg-gradient-to-r from-cyan-400 to-purple-400 bg-clip-text text-transparent">
|
||||
TPMJS
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Decorative line */}
|
||||
<motion.div
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 1, delay: 0.8 }}
|
||||
className="mt-12 w-32 h-px bg-gradient-to-r from-transparent via-white/30 to-transparent"
|
||||
/>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="text-2xl md:text-3xl font-medium text-white/90 mb-6"
|
||||
>
|
||||
A Tool Registry for the AI SDK
|
||||
</motion.h1>
|
||||
|
||||
{/* CTA hint */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}
|
||||
className="text-lg text-white/50 font-mono"
|
||||
>
|
||||
Dynamic tool discovery and execution for agents
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
|
||||
{/* Press to continue */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 1.2 }}
|
||||
className="mt-8 flex flex-col items-center"
|
||||
className="absolute bottom-12 flex flex-col items-center"
|
||||
>
|
||||
<motion.div
|
||||
animate={{ y: [0, 8, 0] }}
|
||||
|
|
@ -95,7 +72,7 @@ export function WelcomeSlide(): React.ReactElement {
|
|||
/>
|
||||
</svg>
|
||||
</motion.div>
|
||||
<span className="mt-2 text-sm text-white/30 font-mono">Press any key to begin</span>
|
||||
<span className="mt-2 text-sm text-white/30 font-mono">Press any key</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue