ai-react-animations 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp/server.js +1062 -0
- package/mcp/registry.ts +857 -0
- package/mcp/server.ts +257 -0
- package/mcp-config.json +8 -0
- package/package.json +26 -5
|
@@ -0,0 +1,1062 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// mcp/server.ts
|
|
5
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import {
|
|
8
|
+
CallToolRequestSchema,
|
|
9
|
+
ListToolsRequestSchema
|
|
10
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
|
|
12
|
+
// mcp/registry.ts
|
|
13
|
+
var components = [
|
|
14
|
+
{
|
|
15
|
+
id: "loading-dots",
|
|
16
|
+
name: "LoadingDots",
|
|
17
|
+
description: "Simple, elegant bouncing dots animation. Perfect for minimal loading states and inline indicators.",
|
|
18
|
+
category: "loading",
|
|
19
|
+
dependencies: ["framer-motion"],
|
|
20
|
+
props: [
|
|
21
|
+
{ name: "size", type: "'sm' | 'md' | 'lg'", required: false, description: "Size of the dots", default: "'md'" },
|
|
22
|
+
{ name: "color", type: "string", required: false, description: "Color of the dots (hex or CSS color)", default: "'#6366F1'" }
|
|
23
|
+
],
|
|
24
|
+
usage: `import { LoadingDots } from '@/components/LoadingDots';
|
|
25
|
+
|
|
26
|
+
function MyComponent() {
|
|
27
|
+
return <LoadingDots size="md" color="#6366F1" />;
|
|
28
|
+
}`,
|
|
29
|
+
source: `'use client';
|
|
30
|
+
|
|
31
|
+
import { motion } from 'framer-motion';
|
|
32
|
+
|
|
33
|
+
interface LoadingDotsProps {
|
|
34
|
+
size?: 'sm' | 'md' | 'lg';
|
|
35
|
+
color?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sizeMap = {
|
|
39
|
+
sm: { dot: 6, gap: 4 },
|
|
40
|
+
md: { dot: 10, gap: 6 },
|
|
41
|
+
lg: { dot: 14, gap: 8 },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default function LoadingDots({ size = 'md', color = '#6366F1' }: LoadingDotsProps) {
|
|
45
|
+
const { dot, gap } = sizeMap[size];
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="flex items-center justify-center" style={{ gap }}>
|
|
49
|
+
{[0, 1, 2].map((i) => (
|
|
50
|
+
<motion.div
|
|
51
|
+
key={i}
|
|
52
|
+
style={{
|
|
53
|
+
width: dot,
|
|
54
|
+
height: dot,
|
|
55
|
+
backgroundColor: color,
|
|
56
|
+
borderRadius: '50%',
|
|
57
|
+
}}
|
|
58
|
+
animate={{ y: [0, -dot, 0] }}
|
|
59
|
+
transition={{
|
|
60
|
+
duration: 0.6,
|
|
61
|
+
repeat: Infinity,
|
|
62
|
+
delay: i * 0.15,
|
|
63
|
+
ease: 'easeInOut',
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}`
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "pulse-circle",
|
|
73
|
+
name: "PulseCircle",
|
|
74
|
+
description: "Circular progress indicator with expanding pulse rings and percentage display. Shows completion checkmark at 100%.",
|
|
75
|
+
category: "processing",
|
|
76
|
+
dependencies: ["framer-motion"],
|
|
77
|
+
props: [
|
|
78
|
+
{ name: "isActive", type: "boolean", required: true, description: "Activates the animation" },
|
|
79
|
+
{ name: "progress", type: "number", required: false, description: "External progress value (0-100)" },
|
|
80
|
+
{ name: "onComplete", type: "() => void", required: false, description: "Callback when progress reaches 100%" }
|
|
81
|
+
],
|
|
82
|
+
usage: `import { PulseCircle } from '@/components/PulseCircle';
|
|
83
|
+
|
|
84
|
+
function MyComponent() {
|
|
85
|
+
const [isActive, setIsActive] = useState(false);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<PulseCircle
|
|
89
|
+
isActive={isActive}
|
|
90
|
+
onComplete={() => setIsActive(false)}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}`,
|
|
94
|
+
source: `'use client';
|
|
95
|
+
|
|
96
|
+
import { useState, useEffect } from 'react';
|
|
97
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
98
|
+
|
|
99
|
+
interface PulseCircleProps {
|
|
100
|
+
isActive: boolean;
|
|
101
|
+
progress?: number;
|
|
102
|
+
onComplete?: () => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default function PulseCircle({ isActive, progress: externalProgress, onComplete }: PulseCircleProps) {
|
|
106
|
+
const [internalProgress, setInternalProgress] = useState(0);
|
|
107
|
+
const progress = externalProgress ?? internalProgress;
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!isActive || externalProgress !== undefined) {
|
|
111
|
+
if (!isActive) setInternalProgress(0);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const interval = setInterval(() => {
|
|
116
|
+
setInternalProgress((prev) => {
|
|
117
|
+
if (prev >= 100) {
|
|
118
|
+
clearInterval(interval);
|
|
119
|
+
onComplete?.();
|
|
120
|
+
return 100;
|
|
121
|
+
}
|
|
122
|
+
return prev + Math.random() * 3 + 1;
|
|
123
|
+
});
|
|
124
|
+
}, 100);
|
|
125
|
+
|
|
126
|
+
return () => clearInterval(interval);
|
|
127
|
+
}, [isActive, externalProgress, onComplete]);
|
|
128
|
+
|
|
129
|
+
const isComplete = progress >= 100;
|
|
130
|
+
const radius = 45;
|
|
131
|
+
const circumference = 2 * Math.PI * radius;
|
|
132
|
+
const strokeDashoffset = circumference - (Math.min(progress, 100) / 100) * circumference;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="relative w-32 h-32">
|
|
136
|
+
{/* Pulse rings */}
|
|
137
|
+
<AnimatePresence>
|
|
138
|
+
{isActive && !isComplete && [0, 1, 2].map((i) => (
|
|
139
|
+
<motion.div
|
|
140
|
+
key={i}
|
|
141
|
+
className="absolute inset-0 rounded-full border-2 border-blue-400"
|
|
142
|
+
initial={{ scale: 1, opacity: 0.6 }}
|
|
143
|
+
animate={{ scale: 2, opacity: 0 }}
|
|
144
|
+
transition={{
|
|
145
|
+
duration: 2,
|
|
146
|
+
repeat: Infinity,
|
|
147
|
+
delay: i * 0.6,
|
|
148
|
+
ease: 'easeOut',
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
))}
|
|
152
|
+
</AnimatePresence>
|
|
153
|
+
|
|
154
|
+
{/* Progress circle */}
|
|
155
|
+
<svg className="w-full h-full -rotate-90" viewBox="0 0 100 100">
|
|
156
|
+
<circle cx="50" cy="50" r={radius} fill="none" stroke="#e5e7eb" strokeWidth="6" />
|
|
157
|
+
<motion.circle
|
|
158
|
+
cx="50"
|
|
159
|
+
cy="50"
|
|
160
|
+
r={radius}
|
|
161
|
+
fill="none"
|
|
162
|
+
stroke={isComplete ? '#10b981' : '#3b82f6'}
|
|
163
|
+
strokeWidth="6"
|
|
164
|
+
strokeLinecap="round"
|
|
165
|
+
strokeDasharray={circumference}
|
|
166
|
+
initial={{ strokeDashoffset: circumference }}
|
|
167
|
+
animate={{ strokeDashoffset }}
|
|
168
|
+
transition={{ duration: 0.3 }}
|
|
169
|
+
/>
|
|
170
|
+
</svg>
|
|
171
|
+
|
|
172
|
+
{/* Center content */}
|
|
173
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
174
|
+
<AnimatePresence mode="wait">
|
|
175
|
+
{isComplete ? (
|
|
176
|
+
<motion.svg
|
|
177
|
+
key="check"
|
|
178
|
+
initial={{ scale: 0 }}
|
|
179
|
+
animate={{ scale: 1 }}
|
|
180
|
+
className="w-12 h-12 text-green-500"
|
|
181
|
+
fill="none"
|
|
182
|
+
viewBox="0 0 24 24"
|
|
183
|
+
stroke="currentColor"
|
|
184
|
+
>
|
|
185
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
|
186
|
+
</motion.svg>
|
|
187
|
+
) : (
|
|
188
|
+
<motion.span
|
|
189
|
+
key="progress"
|
|
190
|
+
className="text-2xl font-bold text-gray-700"
|
|
191
|
+
>
|
|
192
|
+
{Math.round(progress)}%
|
|
193
|
+
</motion.span>
|
|
194
|
+
)}
|
|
195
|
+
</AnimatePresence>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}`
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: "code-typing",
|
|
203
|
+
name: "CodeTyping",
|
|
204
|
+
description: "Realistic code typing effect with syntax highlighting and terminal-style presentation.",
|
|
205
|
+
category: "creative",
|
|
206
|
+
dependencies: ["framer-motion"],
|
|
207
|
+
props: [
|
|
208
|
+
{ name: "isTyping", type: "boolean", required: true, description: "Controls the typing animation" },
|
|
209
|
+
{ name: "onComplete", type: "() => void", required: false, description: "Callback when typing completes" }
|
|
210
|
+
],
|
|
211
|
+
usage: `import { CodeTyping } from '@/components/CodeTyping';
|
|
212
|
+
|
|
213
|
+
function MyComponent() {
|
|
214
|
+
const [isTyping, setIsTyping] = useState(false);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<CodeTyping
|
|
218
|
+
isTyping={isTyping}
|
|
219
|
+
onComplete={() => setIsTyping(false)}
|
|
220
|
+
/>
|
|
221
|
+
);
|
|
222
|
+
}`,
|
|
223
|
+
source: `'use client';
|
|
224
|
+
|
|
225
|
+
import { useState, useEffect } from 'react';
|
|
226
|
+
import { motion } from 'framer-motion';
|
|
227
|
+
|
|
228
|
+
interface CodeTypingProps {
|
|
229
|
+
isTyping: boolean;
|
|
230
|
+
onComplete?: () => void;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const codeLines = [
|
|
234
|
+
{ text: 'const', color: '#c678dd' },
|
|
235
|
+
{ text: ' ai', color: '#e06c75' },
|
|
236
|
+
{ text: ' = ', color: '#abb2bf' },
|
|
237
|
+
{ text: 'await', color: '#c678dd' },
|
|
238
|
+
{ text: ' generateResponse', color: '#61afef' },
|
|
239
|
+
{ text: '(', color: '#abb2bf' },
|
|
240
|
+
{ text: 'prompt', color: '#e5c07b' },
|
|
241
|
+
{ text: ');', color: '#abb2bf' },
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
export default function CodeTyping({ isTyping, onComplete }: CodeTypingProps) {
|
|
245
|
+
const [displayedCode, setDisplayedCode] = useState<typeof codeLines>([]);
|
|
246
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
247
|
+
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (!isTyping) {
|
|
250
|
+
setDisplayedCode([]);
|
|
251
|
+
setCurrentIndex(0);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (currentIndex >= codeLines.length) {
|
|
256
|
+
onComplete?.();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const timeout = setTimeout(() => {
|
|
261
|
+
setDisplayedCode((prev) => [...prev, codeLines[currentIndex]]);
|
|
262
|
+
setCurrentIndex((prev) => prev + 1);
|
|
263
|
+
}, 150);
|
|
264
|
+
|
|
265
|
+
return () => clearTimeout(timeout);
|
|
266
|
+
}, [isTyping, currentIndex, onComplete]);
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<div className="bg-[#282c34] rounded-lg p-4 font-mono text-sm min-w-[300px]">
|
|
270
|
+
<div className="flex gap-2 mb-3">
|
|
271
|
+
<div className="w-3 h-3 rounded-full bg-red-500" />
|
|
272
|
+
<div className="w-3 h-3 rounded-full bg-yellow-500" />
|
|
273
|
+
<div className="w-3 h-3 rounded-full bg-green-500" />
|
|
274
|
+
</div>
|
|
275
|
+
<div className="flex items-center">
|
|
276
|
+
<span className="text-gray-500 mr-2">1</span>
|
|
277
|
+
{displayedCode.map((token, i) => (
|
|
278
|
+
<span key={i} style={{ color: token.color }}>{token.text}</span>
|
|
279
|
+
))}
|
|
280
|
+
{isTyping && currentIndex < codeLines.length && (
|
|
281
|
+
<motion.span
|
|
282
|
+
className="w-2 h-5 bg-white ml-0.5"
|
|
283
|
+
animate={{ opacity: [1, 0] }}
|
|
284
|
+
transition={{ duration: 0.5, repeat: Infinity }}
|
|
285
|
+
/>
|
|
286
|
+
)}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}`
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "data-processing",
|
|
294
|
+
name: "DataProcessing",
|
|
295
|
+
description: "Data pipeline visualization showing input, processing, and output stages with flowing data cards.",
|
|
296
|
+
category: "processing",
|
|
297
|
+
dependencies: ["framer-motion", "lucide-react"],
|
|
298
|
+
props: [
|
|
299
|
+
{ name: "isProcessing", type: "boolean", required: true, description: "Controls the processing animation" },
|
|
300
|
+
{ name: "onComplete", type: "() => void", required: false, description: "Callback when processing completes" }
|
|
301
|
+
],
|
|
302
|
+
usage: `import { DataProcessing } from '@/components/DataProcessing';
|
|
303
|
+
|
|
304
|
+
function MyComponent() {
|
|
305
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<DataProcessing
|
|
309
|
+
isProcessing={isProcessing}
|
|
310
|
+
onComplete={() => setIsProcessing(false)}
|
|
311
|
+
/>
|
|
312
|
+
);
|
|
313
|
+
}`,
|
|
314
|
+
source: `'use client';
|
|
315
|
+
|
|
316
|
+
import { useState, useEffect } from 'react';
|
|
317
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
318
|
+
import { Database, Cpu, CheckCircle } from 'lucide-react';
|
|
319
|
+
|
|
320
|
+
interface DataProcessingProps {
|
|
321
|
+
isProcessing: boolean;
|
|
322
|
+
onComplete?: () => void;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export default function DataProcessing({ isProcessing, onComplete }: DataProcessingProps) {
|
|
326
|
+
const [stage, setStage] = useState<'idle' | 'input' | 'process' | 'output' | 'complete'>('idle');
|
|
327
|
+
const [progress, setProgress] = useState(0);
|
|
328
|
+
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
if (!isProcessing) {
|
|
331
|
+
setStage('idle');
|
|
332
|
+
setProgress(0);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
setStage('input');
|
|
337
|
+
const stages = [
|
|
338
|
+
{ stage: 'process' as const, delay: 800 },
|
|
339
|
+
{ stage: 'output' as const, delay: 1700 },
|
|
340
|
+
{ stage: 'complete' as const, delay: 2500 },
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
const timeouts = stages.map(({ stage, delay }) =>
|
|
344
|
+
setTimeout(() => setStage(stage), delay)
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const progressInterval = setInterval(() => {
|
|
348
|
+
setProgress((p) => Math.min(p + 4, 100));
|
|
349
|
+
}, 100);
|
|
350
|
+
|
|
351
|
+
const completeTimeout = setTimeout(() => {
|
|
352
|
+
onComplete?.();
|
|
353
|
+
}, 3000);
|
|
354
|
+
|
|
355
|
+
return () => {
|
|
356
|
+
timeouts.forEach(clearTimeout);
|
|
357
|
+
clearTimeout(completeTimeout);
|
|
358
|
+
clearInterval(progressInterval);
|
|
359
|
+
};
|
|
360
|
+
}, [isProcessing, onComplete]);
|
|
361
|
+
|
|
362
|
+
const stages = [
|
|
363
|
+
{ id: 'input', icon: Database, label: 'Input', active: ['input', 'process', 'output', 'complete'].includes(stage) },
|
|
364
|
+
{ id: 'process', icon: Cpu, label: 'Processing', active: ['process', 'output', 'complete'].includes(stage) },
|
|
365
|
+
{ id: 'output', icon: CheckCircle, label: 'Output', active: ['output', 'complete'].includes(stage) },
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<div className="flex flex-col items-center gap-6 p-6">
|
|
370
|
+
<div className="flex items-center gap-4">
|
|
371
|
+
{stages.map((s, i) => (
|
|
372
|
+
<div key={s.id} className="flex items-center">
|
|
373
|
+
<motion.div
|
|
374
|
+
className={\`w-16 h-16 rounded-xl flex items-center justify-center \${
|
|
375
|
+
s.active ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-400'
|
|
376
|
+
}\`}
|
|
377
|
+
animate={{ scale: stage === s.id ? [1, 1.1, 1] : 1 }}
|
|
378
|
+
transition={{ duration: 0.3 }}
|
|
379
|
+
>
|
|
380
|
+
<s.icon className="w-8 h-8" />
|
|
381
|
+
</motion.div>
|
|
382
|
+
{i < stages.length - 1 && (
|
|
383
|
+
<div className="w-12 h-1 mx-2 bg-gray-200 rounded overflow-hidden">
|
|
384
|
+
<motion.div
|
|
385
|
+
className="h-full bg-blue-500"
|
|
386
|
+
initial={{ width: 0 }}
|
|
387
|
+
animate={{ width: s.active ? '100%' : 0 }}
|
|
388
|
+
transition={{ duration: 0.5 }}
|
|
389
|
+
/>
|
|
390
|
+
</div>
|
|
391
|
+
)}
|
|
392
|
+
</div>
|
|
393
|
+
))}
|
|
394
|
+
</div>
|
|
395
|
+
<div className="text-center">
|
|
396
|
+
<p className="text-lg font-medium text-gray-700 capitalize">{stage === 'idle' ? 'Ready' : stage}</p>
|
|
397
|
+
<p className="text-sm text-gray-500">{progress}% complete</p>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
);
|
|
401
|
+
}`
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "ai-creating",
|
|
405
|
+
name: "AiCreating",
|
|
406
|
+
description: "Multi-stage animation visualizing AI creation process with thinking, writing, building, and completion phases.",
|
|
407
|
+
category: "creative",
|
|
408
|
+
dependencies: ["framer-motion"],
|
|
409
|
+
props: [
|
|
410
|
+
{ name: "isLoading", type: "boolean", required: true, description: "Controls the animation state" },
|
|
411
|
+
{ name: "size", type: "'sm' | 'md' | 'lg'", required: false, description: "Size of the animation", default: "'md'" },
|
|
412
|
+
{ name: "onComplete", type: "() => void", required: false, description: "Callback when animation completes" }
|
|
413
|
+
],
|
|
414
|
+
usage: `import { AiCreating } from '@/components/AiCreating';
|
|
415
|
+
|
|
416
|
+
function MyComponent() {
|
|
417
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<AiCreating
|
|
421
|
+
isLoading={isLoading}
|
|
422
|
+
size="md"
|
|
423
|
+
onComplete={() => setIsLoading(false)}
|
|
424
|
+
/>
|
|
425
|
+
);
|
|
426
|
+
}`,
|
|
427
|
+
source: `'use client';
|
|
428
|
+
|
|
429
|
+
import { useState, useEffect } from 'react';
|
|
430
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
431
|
+
import './AiCreating.css';
|
|
432
|
+
|
|
433
|
+
interface AiCreatingProps {
|
|
434
|
+
isLoading: boolean;
|
|
435
|
+
size?: 'sm' | 'md' | 'lg';
|
|
436
|
+
onComplete?: () => void;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const stages = ['thinking', 'writing', 'building', 'complete'] as const;
|
|
440
|
+
type Stage = typeof stages[number];
|
|
441
|
+
|
|
442
|
+
const stageMessages: Record<Stage, string> = {
|
|
443
|
+
thinking: 'Thinking...',
|
|
444
|
+
writing: 'Writing...',
|
|
445
|
+
building: 'Building...',
|
|
446
|
+
complete: 'Done!',
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
export default function AiCreating({ isLoading, size = 'md', onComplete }: AiCreatingProps) {
|
|
450
|
+
const [stage, setStage] = useState<Stage>('thinking');
|
|
451
|
+
|
|
452
|
+
const sizeClasses = {
|
|
453
|
+
sm: 'w-16 h-16',
|
|
454
|
+
md: 'w-24 h-24',
|
|
455
|
+
lg: 'w-32 h-32',
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
if (!isLoading) {
|
|
460
|
+
setStage('thinking');
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const timings = [2000, 4000, 6000];
|
|
465
|
+
const timeouts = timings.map((delay, i) =>
|
|
466
|
+
setTimeout(() => setStage(stages[i + 1]), delay)
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const completeTimeout = setTimeout(() => onComplete?.(), 7000);
|
|
470
|
+
|
|
471
|
+
return () => {
|
|
472
|
+
timeouts.forEach(clearTimeout);
|
|
473
|
+
clearTimeout(completeTimeout);
|
|
474
|
+
};
|
|
475
|
+
}, [isLoading, onComplete]);
|
|
476
|
+
|
|
477
|
+
if (!isLoading) return null;
|
|
478
|
+
|
|
479
|
+
return (
|
|
480
|
+
<div className="flex flex-col items-center gap-4">
|
|
481
|
+
<div className={\`relative \${sizeClasses[size]}\`}>
|
|
482
|
+
<motion.div
|
|
483
|
+
className="ai-robot w-full h-full"
|
|
484
|
+
animate={{
|
|
485
|
+
y: stage === 'thinking' ? [0, -5, 0] : 0,
|
|
486
|
+
rotate: stage === 'building' ? [0, 5, -5, 0] : 0,
|
|
487
|
+
}}
|
|
488
|
+
transition={{ duration: 1, repeat: stage !== 'complete' ? Infinity : 0 }}
|
|
489
|
+
>
|
|
490
|
+
{/* Robot SVG */}
|
|
491
|
+
<svg viewBox="0 0 100 100" className="w-full h-full">
|
|
492
|
+
<rect x="25" y="30" width="50" height="45" rx="8" fill="#6366f1" />
|
|
493
|
+
<rect x="35" y="45" width="10" height="10" rx="2" fill="white" />
|
|
494
|
+
<rect x="55" y="45" width="10" height="10" rx="2" fill="white" />
|
|
495
|
+
<rect x="40" y="62" width="20" height="5" rx="2" fill="white" />
|
|
496
|
+
<rect x="42" y="20" width="16" height="15" rx="3" fill="#6366f1" />
|
|
497
|
+
<circle cx="50" cy="12" r="4" fill="#fbbf24" />
|
|
498
|
+
</svg>
|
|
499
|
+
</motion.div>
|
|
500
|
+
</div>
|
|
501
|
+
<motion.p
|
|
502
|
+
key={stage}
|
|
503
|
+
initial={{ opacity: 0, y: 10 }}
|
|
504
|
+
animate={{ opacity: 1, y: 0 }}
|
|
505
|
+
className="text-gray-600 font-medium"
|
|
506
|
+
>
|
|
507
|
+
{stageMessages[stage]}
|
|
508
|
+
</motion.p>
|
|
509
|
+
</div>
|
|
510
|
+
);
|
|
511
|
+
}`
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: "ai-creating-2",
|
|
515
|
+
name: "AiCreating2",
|
|
516
|
+
description: "Full-screen AI brain animation with rotating rings, floating particles, and dynamic status messages.",
|
|
517
|
+
category: "creative",
|
|
518
|
+
dependencies: ["framer-motion", "lucide-react"],
|
|
519
|
+
props: [
|
|
520
|
+
{ name: "isLoading", type: "boolean", required: true, description: "Controls animation visibility" },
|
|
521
|
+
{ name: "message", type: "string", required: false, description: "Main loading message" },
|
|
522
|
+
{ name: "subMessage", type: "string", required: false, description: "Secondary description text" },
|
|
523
|
+
{ name: "primaryColor", type: "string", required: false, description: "Primary color (hex)", default: "'#6366F1'" },
|
|
524
|
+
{ name: "backgroundColor", type: "string", required: false, description: "Background color (hex)", default: "'#0f172a'" },
|
|
525
|
+
{ name: "textColor", type: "string", required: false, description: "Text color (hex)", default: "'#ffffff'" },
|
|
526
|
+
{ name: "contained", type: "boolean", required: false, description: "Render in container vs full overlay", default: "false" },
|
|
527
|
+
{ name: "onComplete", type: "() => void", required: false, description: "Callback when complete" }
|
|
528
|
+
],
|
|
529
|
+
usage: `import { AiCreating2 } from '@/components/AiCreating2';
|
|
530
|
+
|
|
531
|
+
function MyComponent() {
|
|
532
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
533
|
+
|
|
534
|
+
return (
|
|
535
|
+
<AiCreating2
|
|
536
|
+
isLoading={isLoading}
|
|
537
|
+
message="Creating your plan..."
|
|
538
|
+
primaryColor="#6366F1"
|
|
539
|
+
onComplete={() => setIsLoading(false)}
|
|
540
|
+
/>
|
|
541
|
+
);
|
|
542
|
+
}`,
|
|
543
|
+
source: `'use client';
|
|
544
|
+
|
|
545
|
+
import { useEffect, useState } from 'react';
|
|
546
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
547
|
+
import { Brain, Sparkles } from 'lucide-react';
|
|
548
|
+
|
|
549
|
+
interface AiCreating2Props {
|
|
550
|
+
isLoading: boolean;
|
|
551
|
+
message?: string;
|
|
552
|
+
subMessage?: string;
|
|
553
|
+
primaryColor?: string;
|
|
554
|
+
backgroundColor?: string;
|
|
555
|
+
textColor?: string;
|
|
556
|
+
contained?: boolean;
|
|
557
|
+
onComplete?: () => void;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export default function AiCreating2({
|
|
561
|
+
isLoading,
|
|
562
|
+
message = 'AI is creating...',
|
|
563
|
+
subMessage = 'This may take a moment',
|
|
564
|
+
primaryColor = '#6366F1',
|
|
565
|
+
backgroundColor = '#0f172a',
|
|
566
|
+
textColor = '#ffffff',
|
|
567
|
+
contained = false,
|
|
568
|
+
onComplete,
|
|
569
|
+
}: AiCreating2Props) {
|
|
570
|
+
const [progress, setProgress] = useState(0);
|
|
571
|
+
|
|
572
|
+
useEffect(() => {
|
|
573
|
+
if (!isLoading) {
|
|
574
|
+
setProgress(0);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const interval = setInterval(() => {
|
|
579
|
+
setProgress((p) => {
|
|
580
|
+
if (p >= 100) {
|
|
581
|
+
clearInterval(interval);
|
|
582
|
+
setTimeout(() => onComplete?.(), 500);
|
|
583
|
+
return 100;
|
|
584
|
+
}
|
|
585
|
+
return p + Math.random() * 2 + 0.5;
|
|
586
|
+
});
|
|
587
|
+
}, 100);
|
|
588
|
+
|
|
589
|
+
return () => clearInterval(interval);
|
|
590
|
+
}, [isLoading, onComplete]);
|
|
591
|
+
|
|
592
|
+
const containerClass = contained
|
|
593
|
+
? 'w-full h-full flex items-center justify-center'
|
|
594
|
+
: 'fixed inset-0 z-50 flex items-center justify-center';
|
|
595
|
+
|
|
596
|
+
return (
|
|
597
|
+
<AnimatePresence>
|
|
598
|
+
{isLoading && (
|
|
599
|
+
<motion.div
|
|
600
|
+
initial={{ opacity: 0 }}
|
|
601
|
+
animate={{ opacity: 1 }}
|
|
602
|
+
exit={{ opacity: 0 }}
|
|
603
|
+
className={containerClass}
|
|
604
|
+
style={{ backgroundColor }}
|
|
605
|
+
>
|
|
606
|
+
<div className="flex flex-col items-center gap-8">
|
|
607
|
+
{/* Brain with rings */}
|
|
608
|
+
<div className="relative w-32 h-32">
|
|
609
|
+
{[0, 1, 2].map((i) => (
|
|
610
|
+
<motion.div
|
|
611
|
+
key={i}
|
|
612
|
+
className="absolute inset-0 rounded-full border-2"
|
|
613
|
+
style={{ borderColor: primaryColor }}
|
|
614
|
+
animate={{ rotate: 360, scale: [1, 1.1, 1] }}
|
|
615
|
+
transition={{
|
|
616
|
+
rotate: { duration: 3 + i, repeat: Infinity, ease: 'linear' },
|
|
617
|
+
scale: { duration: 2, repeat: Infinity, delay: i * 0.3 },
|
|
618
|
+
}}
|
|
619
|
+
/>
|
|
620
|
+
))}
|
|
621
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
622
|
+
<Brain className="w-12 h-12" style={{ color: primaryColor }} />
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
|
|
626
|
+
{/* Messages */}
|
|
627
|
+
<div className="text-center">
|
|
628
|
+
<motion.h2
|
|
629
|
+
className="text-xl font-semibold mb-2 flex items-center gap-2 justify-center"
|
|
630
|
+
style={{ color: textColor }}
|
|
631
|
+
>
|
|
632
|
+
<Sparkles className="w-5 h-5" style={{ color: primaryColor }} />
|
|
633
|
+
{message}
|
|
634
|
+
</motion.h2>
|
|
635
|
+
<p className="text-sm opacity-70" style={{ color: textColor }}>{subMessage}</p>
|
|
636
|
+
</div>
|
|
637
|
+
|
|
638
|
+
{/* Progress bar */}
|
|
639
|
+
<div className="w-64 h-2 bg-white/10 rounded-full overflow-hidden">
|
|
640
|
+
<motion.div
|
|
641
|
+
className="h-full rounded-full"
|
|
642
|
+
style={{ backgroundColor: primaryColor }}
|
|
643
|
+
initial={{ width: 0 }}
|
|
644
|
+
animate={{ width: \`\${progress}%\` }}
|
|
645
|
+
/>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
</motion.div>
|
|
649
|
+
)}
|
|
650
|
+
</AnimatePresence>
|
|
651
|
+
);
|
|
652
|
+
}`
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: "floating-login",
|
|
656
|
+
name: "FloatingLogin",
|
|
657
|
+
description: "Animated floating login form with smooth bobbing motion, supporting local and OAuth authentication.",
|
|
658
|
+
category: "auth",
|
|
659
|
+
dependencies: ["framer-motion", "lucide-react"],
|
|
660
|
+
props: [
|
|
661
|
+
{ name: "mode", type: "'light' | 'dark'", required: false, description: "Theme mode", default: "'light'" },
|
|
662
|
+
{ name: "primaryColor", type: "string", required: false, description: "Accent color (hex)", default: "'#6366F1'" },
|
|
663
|
+
{ name: "floatingEnabled", type: "boolean", required: false, description: "Enable floating animation", default: "true" },
|
|
664
|
+
{ name: "floatIntensity", type: "number", required: false, description: "Float intensity (1-10)", default: "5" },
|
|
665
|
+
{ name: "onLogin", type: "(data: LoginData) => void", required: false, description: "Login callback with email/password" },
|
|
666
|
+
{ name: "onGoogleLogin", type: "() => void", required: false, description: "Google OAuth callback" },
|
|
667
|
+
{ name: "onAppleLogin", type: "() => void", required: false, description: "Apple Sign-In callback" }
|
|
668
|
+
],
|
|
669
|
+
usage: `import { FloatingLogin } from '@/components/FloatingLogin';
|
|
670
|
+
|
|
671
|
+
function MyComponent() {
|
|
672
|
+
return (
|
|
673
|
+
<FloatingLogin
|
|
674
|
+
mode="dark"
|
|
675
|
+
primaryColor="#6366F1"
|
|
676
|
+
floatIntensity={5}
|
|
677
|
+
onLogin={(data) => console.log(data)}
|
|
678
|
+
onGoogleLogin={() => signInWithGoogle()}
|
|
679
|
+
onAppleLogin={() => signInWithApple()}
|
|
680
|
+
/>
|
|
681
|
+
);
|
|
682
|
+
}`,
|
|
683
|
+
source: `'use client';
|
|
684
|
+
|
|
685
|
+
import { useState } from 'react';
|
|
686
|
+
import { motion } from 'framer-motion';
|
|
687
|
+
import { Mail, Lock, Eye, EyeOff } from 'lucide-react';
|
|
688
|
+
|
|
689
|
+
interface LoginData {
|
|
690
|
+
email: string;
|
|
691
|
+
password: string;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
interface FloatingLoginProps {
|
|
695
|
+
mode?: 'light' | 'dark';
|
|
696
|
+
primaryColor?: string;
|
|
697
|
+
floatingEnabled?: boolean;
|
|
698
|
+
floatIntensity?: number;
|
|
699
|
+
onLogin?: (data: LoginData) => void | Promise<void>;
|
|
700
|
+
onGoogleLogin?: () => void | Promise<void>;
|
|
701
|
+
onAppleLogin?: () => void | Promise<void>;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export default function FloatingLogin({
|
|
705
|
+
mode = 'light',
|
|
706
|
+
primaryColor = '#6366F1',
|
|
707
|
+
floatingEnabled = true,
|
|
708
|
+
floatIntensity = 5,
|
|
709
|
+
onLogin,
|
|
710
|
+
onGoogleLogin,
|
|
711
|
+
onAppleLogin,
|
|
712
|
+
}: FloatingLoginProps) {
|
|
713
|
+
const [email, setEmail] = useState('');
|
|
714
|
+
const [password, setPassword] = useState('');
|
|
715
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
716
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
717
|
+
|
|
718
|
+
const isDark = mode === 'dark';
|
|
719
|
+
const intensity = Math.max(1, Math.min(10, floatIntensity));
|
|
720
|
+
const yOffset = intensity * 2;
|
|
721
|
+
|
|
722
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
if (!onLogin) return;
|
|
725
|
+
setIsLoading(true);
|
|
726
|
+
try {
|
|
727
|
+
await onLogin({ email, password });
|
|
728
|
+
} finally {
|
|
729
|
+
setIsLoading(false);
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
const cardBg = isDark ? 'bg-gray-900' : 'bg-white';
|
|
734
|
+
const textColor = isDark ? 'text-white' : 'text-gray-900';
|
|
735
|
+
const inputBg = isDark ? 'bg-gray-800 border-gray-700' : 'bg-gray-50 border-gray-200';
|
|
736
|
+
|
|
737
|
+
return (
|
|
738
|
+
<div className="flex items-center justify-center p-8">
|
|
739
|
+
<motion.div
|
|
740
|
+
animate={floatingEnabled ? {
|
|
741
|
+
y: [0, -yOffset, 0],
|
|
742
|
+
rotate: [0, 1, -1, 0],
|
|
743
|
+
} : {}}
|
|
744
|
+
transition={{
|
|
745
|
+
duration: 4,
|
|
746
|
+
repeat: Infinity,
|
|
747
|
+
ease: 'easeInOut',
|
|
748
|
+
}}
|
|
749
|
+
className={\`\${cardBg} rounded-2xl shadow-2xl p-8 w-full max-w-md\`}
|
|
750
|
+
>
|
|
751
|
+
<h2 className={\`text-2xl font-bold \${textColor} mb-6 text-center\`}>Welcome back</h2>
|
|
752
|
+
|
|
753
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
754
|
+
<div className="relative">
|
|
755
|
+
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
|
|
756
|
+
<input
|
|
757
|
+
type="email"
|
|
758
|
+
placeholder="Email"
|
|
759
|
+
value={email}
|
|
760
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
761
|
+
className={\`w-full pl-10 pr-4 py-3 rounded-lg border \${inputBg} \${textColor} focus:outline-none focus:ring-2\`}
|
|
762
|
+
style={{ '--tw-ring-color': primaryColor } as any}
|
|
763
|
+
/>
|
|
764
|
+
</div>
|
|
765
|
+
|
|
766
|
+
<div className="relative">
|
|
767
|
+
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
|
|
768
|
+
<input
|
|
769
|
+
type={showPassword ? 'text' : 'password'}
|
|
770
|
+
placeholder="Password"
|
|
771
|
+
value={password}
|
|
772
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
773
|
+
className={\`w-full pl-10 pr-12 py-3 rounded-lg border \${inputBg} \${textColor} focus:outline-none focus:ring-2\`}
|
|
774
|
+
style={{ '--tw-ring-color': primaryColor } as any}
|
|
775
|
+
/>
|
|
776
|
+
<button
|
|
777
|
+
type="button"
|
|
778
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
779
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400"
|
|
780
|
+
>
|
|
781
|
+
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
|
782
|
+
</button>
|
|
783
|
+
</div>
|
|
784
|
+
|
|
785
|
+
<button
|
|
786
|
+
type="submit"
|
|
787
|
+
disabled={isLoading}
|
|
788
|
+
className="w-full py-3 rounded-lg text-white font-medium transition-opacity disabled:opacity-50"
|
|
789
|
+
style={{ backgroundColor: primaryColor }}
|
|
790
|
+
>
|
|
791
|
+
{isLoading ? 'Signing in...' : 'Sign in'}
|
|
792
|
+
</button>
|
|
793
|
+
</form>
|
|
794
|
+
|
|
795
|
+
<div className="mt-6 flex items-center gap-4">
|
|
796
|
+
<div className="flex-1 h-px bg-gray-300" />
|
|
797
|
+
<span className="text-sm text-gray-500">or</span>
|
|
798
|
+
<div className="flex-1 h-px bg-gray-300" />
|
|
799
|
+
</div>
|
|
800
|
+
|
|
801
|
+
<div className="mt-6 space-y-3">
|
|
802
|
+
{onGoogleLogin && (
|
|
803
|
+
<button
|
|
804
|
+
onClick={onGoogleLogin}
|
|
805
|
+
className={\`w-full py-3 rounded-lg border \${inputBg} \${textColor} font-medium flex items-center justify-center gap-2\`}
|
|
806
|
+
>
|
|
807
|
+
<svg className="w-5 h-5" viewBox="0 0 24 24">
|
|
808
|
+
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
|
809
|
+
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
|
810
|
+
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
|
811
|
+
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
|
812
|
+
</svg>
|
|
813
|
+
Continue with Google
|
|
814
|
+
</button>
|
|
815
|
+
)}
|
|
816
|
+
|
|
817
|
+
{onAppleLogin && (
|
|
818
|
+
<button
|
|
819
|
+
onClick={onAppleLogin}
|
|
820
|
+
className={\`w-full py-3 rounded-lg \${isDark ? 'bg-white text-black' : 'bg-black text-white'} font-medium flex items-center justify-center gap-2\`}
|
|
821
|
+
>
|
|
822
|
+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
823
|
+
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/>
|
|
824
|
+
</svg>
|
|
825
|
+
Continue with Apple
|
|
826
|
+
</button>
|
|
827
|
+
)}
|
|
828
|
+
</div>
|
|
829
|
+
</motion.div>
|
|
830
|
+
</div>
|
|
831
|
+
);
|
|
832
|
+
}`
|
|
833
|
+
}
|
|
834
|
+
];
|
|
835
|
+
function getComponent(id) {
|
|
836
|
+
return components.find((c) => c.id === id || c.name.toLowerCase() === id.toLowerCase());
|
|
837
|
+
}
|
|
838
|
+
function listComponents(category) {
|
|
839
|
+
if (!category || category === "all") return components;
|
|
840
|
+
return components.filter((c) => c.category === category);
|
|
841
|
+
}
|
|
842
|
+
function getCategories() {
|
|
843
|
+
return ["all", ...new Set(components.map((c) => c.category))];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// mcp/server.ts
|
|
847
|
+
import * as path from "path";
|
|
848
|
+
var server = new Server(
|
|
849
|
+
{
|
|
850
|
+
name: "ai-react-animations",
|
|
851
|
+
version: "1.0.0"
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
capabilities: {
|
|
855
|
+
tools: {}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
860
|
+
return {
|
|
861
|
+
tools: [
|
|
862
|
+
{
|
|
863
|
+
name: "list_components",
|
|
864
|
+
description: "List all available AI animation components. Optionally filter by category (loading, processing, creative, auth).",
|
|
865
|
+
inputSchema: {
|
|
866
|
+
type: "object",
|
|
867
|
+
properties: {
|
|
868
|
+
category: {
|
|
869
|
+
type: "string",
|
|
870
|
+
description: "Filter by category: loading, processing, creative, auth, or all",
|
|
871
|
+
enum: ["all", "loading", "processing", "creative", "auth"]
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: "get_component",
|
|
878
|
+
description: "Get detailed information about a specific component including props, usage example, and source code.",
|
|
879
|
+
inputSchema: {
|
|
880
|
+
type: "object",
|
|
881
|
+
properties: {
|
|
882
|
+
name: {
|
|
883
|
+
type: "string",
|
|
884
|
+
description: 'Component name or ID (e.g., "LoadingDots", "loading-dots", "PulseCircle")'
|
|
885
|
+
}
|
|
886
|
+
},
|
|
887
|
+
required: ["name"]
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: "add_component",
|
|
892
|
+
description: "Add an animation component to the user's project. Creates the component file in the specified directory.",
|
|
893
|
+
inputSchema: {
|
|
894
|
+
type: "object",
|
|
895
|
+
properties: {
|
|
896
|
+
name: {
|
|
897
|
+
type: "string",
|
|
898
|
+
description: "Component name or ID to add"
|
|
899
|
+
},
|
|
900
|
+
directory: {
|
|
901
|
+
type: "string",
|
|
902
|
+
description: "Directory path where to create the component (default: ./components)"
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
required: ["name"]
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
name: "get_install_command",
|
|
910
|
+
description: "Get the npm install command for required dependencies of a component.",
|
|
911
|
+
inputSchema: {
|
|
912
|
+
type: "object",
|
|
913
|
+
properties: {
|
|
914
|
+
name: {
|
|
915
|
+
type: "string",
|
|
916
|
+
description: "Component name or ID"
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
required: ["name"]
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
};
|
|
924
|
+
});
|
|
925
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
926
|
+
const { name, arguments: args } = request.params;
|
|
927
|
+
switch (name) {
|
|
928
|
+
case "list_components": {
|
|
929
|
+
const category = args?.category || "all";
|
|
930
|
+
const filtered = listComponents(category);
|
|
931
|
+
const summary = filtered.map((c) => ({
|
|
932
|
+
name: c.name,
|
|
933
|
+
id: c.id,
|
|
934
|
+
category: c.category,
|
|
935
|
+
description: c.description
|
|
936
|
+
}));
|
|
937
|
+
return {
|
|
938
|
+
content: [
|
|
939
|
+
{
|
|
940
|
+
type: "text",
|
|
941
|
+
text: JSON.stringify({
|
|
942
|
+
total: filtered.length,
|
|
943
|
+
categories: getCategories(),
|
|
944
|
+
components: summary
|
|
945
|
+
}, null, 2)
|
|
946
|
+
}
|
|
947
|
+
]
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
case "get_component": {
|
|
951
|
+
const componentName = args.name;
|
|
952
|
+
const component = getComponent(componentName);
|
|
953
|
+
if (!component) {
|
|
954
|
+
return {
|
|
955
|
+
content: [
|
|
956
|
+
{
|
|
957
|
+
type: "text",
|
|
958
|
+
text: `Component "${componentName}" not found. Use list_components to see available components.`
|
|
959
|
+
}
|
|
960
|
+
],
|
|
961
|
+
isError: true
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
return {
|
|
965
|
+
content: [
|
|
966
|
+
{
|
|
967
|
+
type: "text",
|
|
968
|
+
text: JSON.stringify({
|
|
969
|
+
name: component.name,
|
|
970
|
+
id: component.id,
|
|
971
|
+
description: component.description,
|
|
972
|
+
category: component.category,
|
|
973
|
+
dependencies: component.dependencies,
|
|
974
|
+
props: component.props,
|
|
975
|
+
usage: component.usage,
|
|
976
|
+
source: component.source
|
|
977
|
+
}, null, 2)
|
|
978
|
+
}
|
|
979
|
+
]
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
case "add_component": {
|
|
983
|
+
const componentName = args.name;
|
|
984
|
+
const directory = args.directory || "./components";
|
|
985
|
+
const component = getComponent(componentName);
|
|
986
|
+
if (!component) {
|
|
987
|
+
return {
|
|
988
|
+
content: [
|
|
989
|
+
{
|
|
990
|
+
type: "text",
|
|
991
|
+
text: `Component "${componentName}" not found. Use list_components to see available components.`
|
|
992
|
+
}
|
|
993
|
+
],
|
|
994
|
+
isError: true
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
const filePath = path.join(directory, `${component.name}.tsx`);
|
|
998
|
+
return {
|
|
999
|
+
content: [
|
|
1000
|
+
{
|
|
1001
|
+
type: "text",
|
|
1002
|
+
text: JSON.stringify({
|
|
1003
|
+
success: true,
|
|
1004
|
+
component: component.name,
|
|
1005
|
+
filePath,
|
|
1006
|
+
source: component.source,
|
|
1007
|
+
dependencies: component.dependencies,
|
|
1008
|
+
installCommand: `npm install ${component.dependencies.join(" ")}`,
|
|
1009
|
+
usage: component.usage,
|
|
1010
|
+
instructions: `
|
|
1011
|
+
1. Create the file at: ${filePath}
|
|
1012
|
+
2. Install dependencies: npm install ${component.dependencies.join(" ")}
|
|
1013
|
+
3. Import and use the component as shown in the usage example.
|
|
1014
|
+
|
|
1015
|
+
Note: Make sure you have 'use client' directive if using Next.js App Router.
|
|
1016
|
+
`.trim()
|
|
1017
|
+
}, null, 2)
|
|
1018
|
+
}
|
|
1019
|
+
]
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
case "get_install_command": {
|
|
1023
|
+
const componentName = args.name;
|
|
1024
|
+
const component = getComponent(componentName);
|
|
1025
|
+
if (!component) {
|
|
1026
|
+
return {
|
|
1027
|
+
content: [
|
|
1028
|
+
{
|
|
1029
|
+
type: "text",
|
|
1030
|
+
text: `Component "${componentName}" not found.`
|
|
1031
|
+
}
|
|
1032
|
+
],
|
|
1033
|
+
isError: true
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
content: [
|
|
1038
|
+
{
|
|
1039
|
+
type: "text",
|
|
1040
|
+
text: `npm install ${component.dependencies.join(" ")}`
|
|
1041
|
+
}
|
|
1042
|
+
]
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
default:
|
|
1046
|
+
return {
|
|
1047
|
+
content: [
|
|
1048
|
+
{
|
|
1049
|
+
type: "text",
|
|
1050
|
+
text: `Unknown tool: ${name}`
|
|
1051
|
+
}
|
|
1052
|
+
],
|
|
1053
|
+
isError: true
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
async function main() {
|
|
1058
|
+
const transport = new StdioServerTransport();
|
|
1059
|
+
await server.connect(transport);
|
|
1060
|
+
console.error("AI React Animations MCP Server running on stdio");
|
|
1061
|
+
}
|
|
1062
|
+
main().catch(console.error);
|