Skip to content

Commit 2a41bc9

Browse files
authored
Merge pull request #10 from camel-ai/header-update
update camel tool and header
2 parents 6bff640 + 60ac320 commit 2a41bc9

9 files changed

+258
-28
lines changed

app/page.tsx

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ function Modal({ server, onClose }: ModalProps) {
101101
<h2 className="text-2xl font-bold flex items-center gap-4 mb-2">
102102
{server.source === 'official' ? (
103103
<ShieldCheck className="w-6 h-6 text-[#4215cc] flex-shrink-0" />
104+
) : server.source === 'camel' ? (
105+
<Image
106+
src="/camel-icon.svg"
107+
alt="Camel"
108+
width={24}
109+
height={24}
110+
className="flex-shrink-0"
111+
/>
104112
) : (
105113
<Image
106114
src="/anthropic.svg"
@@ -119,7 +127,11 @@ function Modal({ server, onClose }: ModalProps) {
119127
</Button>
120128
</div>
121129
<div className="flex justify-between items-center pb-6 border-t ">
122-
<Badge>{server.command}</Badge>
130+
<Badge
131+
variant={server.command === 'uvx' ? 'secondary' : server.command === 'npx' ? 'pink' : undefined}
132+
>
133+
{server.command}
134+
</Badge>
123135
<Button
124136
variant="link"
125137
size="sm"
@@ -157,7 +169,7 @@ function Modal({ server, onClose }: ModalProps) {
157169
export default function Home() {
158170
const [selectedServer, setSelectedServer] = useState<Server | null>(null);
159171
const [isModalOpen, setIsModalOpen] = useState(false);
160-
const [filter, setFilter] = useState<'all' | 'official' | 'anthropic'>('all');
172+
const [filter, setFilter] = useState<'all' | 'official' | 'anthropic' | 'camel'>('all');
161173

162174
const handleCardClick = (server: Server) => {
163175
setSelectedServer(server);
@@ -186,6 +198,13 @@ export default function Home() {
186198
<Grid2X2 className="w-4 h-4" />
187199
All
188200
</button>
201+
<button
202+
onClick={() => setFilter('camel')}
203+
className={`px-4 py-2 text-sm font-bold rounded-md transition-colors flex items-center gap-1.5 cursor-pointer ${filter === 'camel' ? 'bg-card shadow-sm' : 'hover:bg-background/50'}`}
204+
>
205+
<Image src="/camel-icon.svg" alt="Camel" width={16} height={16} />
206+
Camel
207+
</button>
189208
<button
190209
onClick={() => setFilter('official')}
191210
className={`px-4 py-2 text-sm font-bold rounded-md transition-colors flex items-center gap-1.5 cursor-pointer ${filter === 'official' ? 'bg-card shadow-sm' : 'hover:bg-background/50'}`}
@@ -213,12 +232,20 @@ export default function Home() {
213232
<CardTitle className="flex items-center gap-2">
214233
{server.source === 'official' ? (
215234
<ShieldCheck className="w-6 h-6 text-primary flex-shrink-0" />
235+
) : server.source === 'camel' ? (
236+
<Image
237+
src="/camel-icon.svg"
238+
alt="Camel"
239+
width={24}
240+
height={24}
241+
className="flex-shrink-0"
242+
/>
216243
) : (
217244
<Image
218245
src="/anthropic.svg"
219246
alt="Anthropic"
220-
width={16}
221-
height={16}
247+
width={24}
248+
height={24}
222249
className="flex-shrink-0"
223250
/>
224251
)}
@@ -227,7 +254,11 @@ export default function Home() {
227254
<CardDescription className="h-20 line-clamp-4">{server.description}</CardDescription>
228255
</CardHeader>
229256
<CardFooter className="flex justify-between">
230-
<Badge variant="secondary" className="font-bold">{server.command}</Badge>
257+
<Badge
258+
variant={server.command === 'uvx' ? 'secondary' : server.command === 'npx' ? 'pink' : undefined}
259+
>
260+
{server.command}
261+
</Badge>
231262
<Button
232263
variant="link"
233264
size="sm"

components/badge.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const badgeVariants = cva(
1717
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
1818
outline:
1919
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20+
pink:
21+
"border-transparent bg-[#fdd1f7] text-[#d029ae] [a&]:hover:bg-[#fdd1f7]/90",
2022
},
2123
},
2224
defaultVariants: {

components/header.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
import { motion } from "framer-motion"
2-
2+
import SwipeCard from "./swipe.card"
33
export function Header() {
44
return (
5-
<header className="pt-32 pb-16 text-center">
6-
<div className="flex flex-col sm:flex-row md:flex-row lg:flex-row xl:flex-row 2xl:flex-row items-center justify-center text-6xl font-bold mb-6">
7-
<motion.div
8-
className="flex flex-col sm:flex-row md:flex-row lg:flex-row xl:flex-row 2xl:flex-row items-center gap-2"
9-
layout
10-
transition={{ type: "spring", damping: 30, stiffness: 400 }}
11-
>
12-
<motion.span
5+
<header className="pt-22 pb-8">
6+
<div className="flex flex-col lg:flex-row items-center justify-between flex-1 w-full max-w-[1600px] mx-auto py-8">
7+
{/* Left: Text Content */}
8+
<div className="flex-1 min-w-0 text-center mb-8 lg:mb-0 lg:w-1/2">
9+
<div className="text-6xl font-bold mb-6">
10+
<motion.div
11+
className="flex flex-col gap-2 items-center"
1312
layout
1413
transition={{ type: "spring", damping: 30, stiffness: 400 }}
1514
>
16-
CAMEL with MCP
17-
</motion.span>
18-
</motion.div>
15+
<motion.span
16+
layout
17+
transition={{ type: "spring", damping: 30, stiffness: 400 }}
18+
>
19+
CAMEL with MCP
20+
</motion.span>
21+
</motion.div>
22+
</div>
23+
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
24+
Discover and compare Model Context Protocol Servers to build your Agents
25+
</p>
1926
</div>
20-
21-
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
22-
Discover and compare Model Context Protocol Servers to build your Agents
23-
</p>
24-
</header>
27+
{/* Right: Blog Thumbnail Card */}
28+
<div className="flex-1 min-w-0 lg:w-1/2 flex items-center justify-center">
29+
<SwipeCard />
30+
</div>
31+
</div>
32+
</header>
2533
)
2634
}

components/swipe.card.tsx

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import React, { useState, useEffect } from "react";
2+
import { motion, useMotionValue, useTransform } from "framer-motion";
3+
import Image from "next/image";
4+
5+
interface BlogCardData {
6+
id: number;
7+
src: string;
8+
title: string;
9+
description: string;
10+
link: string;
11+
}
12+
13+
const blogCards: BlogCardData[] = [
14+
{
15+
id: 1,
16+
src: "/camel-ai-agent-mcp-integration.png",
17+
title: "How to Connect Your CAMEL-AI Agent to External Tools via MCP",
18+
description: "Seamlessly integrate databases, APIs, and web services into your CAMEL-AI agents using the Model Context Protocol.",
19+
link: "https://www.camel-ai.org/blogs/camel-ai-agent-mcp-integration",
20+
},
21+
{
22+
id: 2,
23+
src: "/connect-your-owl-agent-to-notion-via-the-mcp-server.png",
24+
title: "How to Connect Your OWL Agent to Notion via the MCP Server",
25+
description: "Empower your CAMEL-AI OWL agent to interact with Notion using the MCP server.",
26+
link: "https://www.camel-ai.org/blogs/connect-your-owl-agent-to-notion-via-the-mcp-server",
27+
},
28+
{
29+
id: 3,
30+
src: "/camel-mcp-servers-model-context-protocol-ai-agents.png",
31+
title: "CAMEL x MCP: Making AI Agents Accessible to All Tools",
32+
description: "A lightweight server that exports Camel framework toolkits as MCP-compatible tools.",
33+
link: "https://www.camel-ai.org/blogs/camel-mcp-servers-model-context-protocol-ai-agents",
34+
},
35+
];
36+
37+
const ArrowButton = ({ direction, onClick }: { direction: 'left' | 'right', onClick: () => void }) => (
38+
<button
39+
onClick={onClick}
40+
aria-label={direction === 'left' ? 'Previous card' : 'Next card'}
41+
className="mx-2 p-2 rounded-full bg-card border shadow hover:bg-muted transition-colors focus:outline-none focus:ring-2 focus:ring-primary"
42+
type="button"
43+
>
44+
{direction === 'left' ? (
45+
<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path d="M15 19l-7-7 7-7"/></svg>
46+
) : (
47+
<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
48+
)}
49+
</button>
50+
);
51+
52+
const SwipeCards = () => {
53+
const [cards, setCards] = useState<BlogCardData[]>(blogCards);
54+
55+
// Auto-rotate cards effect
56+
useEffect(() => {
57+
const interval = setInterval(() => {
58+
if (cards.length > 0) {
59+
setCards(prevCards => {
60+
const newCards = [...prevCards];
61+
const lastCard = newCards.pop();
62+
if (lastCard) {
63+
newCards.unshift(lastCard);
64+
}
65+
return newCards;
66+
});
67+
}
68+
}, 10000);
69+
70+
return () => clearInterval(interval);
71+
}, [cards]);
72+
73+
// Manual navigation handlers
74+
const handlePrev = () => {
75+
setCards(prevCards => {
76+
if (prevCards.length === 0) return prevCards;
77+
const newCards = [...prevCards];
78+
const firstCard = newCards.shift();
79+
if (firstCard) {
80+
newCards.push(firstCard);
81+
}
82+
return newCards;
83+
});
84+
};
85+
86+
const handleNext = () => {
87+
setCards(prevCards => {
88+
if (prevCards.length === 0) return prevCards;
89+
const newCards = [...prevCards];
90+
const lastCard = newCards.pop();
91+
if (lastCard) {
92+
newCards.unshift(lastCard);
93+
}
94+
return newCards;
95+
});
96+
};
97+
98+
return (
99+
<div className="relative w-full h-[350px] flex flex-col items-center justify-center">
100+
<div className="relative w-full h-full flex items-center justify-center">
101+
{cards.map((card) => (
102+
<BlogSwipeCard key={card.id} cards={cards} setCards={setCards} {...card} />
103+
))}
104+
</div>
105+
<div className="flex items-center justify-center mt-12">
106+
<ArrowButton direction="left" onClick={handlePrev} />
107+
<ArrowButton direction="right" onClick={handleNext} />
108+
</div>
109+
</div>
110+
);
111+
};
112+
113+
const BlogSwipeCard = ({ id, src, title, description, link, setCards, cards }: BlogCardData & {
114+
setCards: React.Dispatch<React.SetStateAction<BlogCardData[]>>;
115+
cards: BlogCardData[];
116+
}) => {
117+
const x = useMotionValue(0);
118+
const rotateRaw = useTransform(x, [-150, 150], [-18, 18]);
119+
const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]);
120+
121+
const isFront = id === cards[cards.length - 1].id;
122+
123+
const rotate = useTransform(() => {
124+
const offset = isFront ? 0 : id % 2 ? 6 : -6;
125+
return `${rotateRaw.get() + offset}deg`;
126+
});
127+
128+
const handleDragEnd = () => {
129+
if (Math.abs(x.get()) > 100) {
130+
setCards((prevCards: BlogCardData[]) => {
131+
const filtered = prevCards.filter((card: BlogCardData) => card.id !== id);
132+
filtered.unshift({ id, src, title, description, link });
133+
return filtered;
134+
});
135+
}
136+
};
137+
138+
return (
139+
<motion.div
140+
className="absolute w-full h-full flex items-center justify-center"
141+
style={{
142+
x,
143+
opacity,
144+
rotate,
145+
zIndex: isFront ? 1 : 0,
146+
transition: "0.125s transform",
147+
}}
148+
animate={{
149+
scale: isFront ? 1 : 0.95,
150+
}}
151+
drag={isFront ? "x" : false}
152+
dragConstraints={{
153+
left: 0,
154+
right: 0,
155+
}}
156+
onDragEnd={handleDragEnd}
157+
>
158+
<div className="flex-shrink-0 w-full max-w-xs lg:max-w-sm">
159+
<a
160+
href={link}
161+
className="block bg-card rounded-xl shadow-lg overflow-hidden border p-4 transition-all duration-300 hover:border-secondary focus:border-primary outline-none"
162+
tabIndex={0}
163+
target="_blank"
164+
rel="noopener noreferrer"
165+
>
166+
<div className="relative w-full h-48 rounded-lg">
167+
<Image
168+
src={src}
169+
alt={title}
170+
fill
171+
style={{ objectFit: 'cover', borderRadius: '0.5rem' }}
172+
priority={isFront}
173+
/>
174+
</div>
175+
<div className="mt-4">
176+
<h3 className="text-xl font-semibold mb-2">{title}</h3>
177+
</div>
178+
</a>
179+
</div>
180+
</motion.div>
181+
);
182+
};
183+
184+
export default SwipeCards;
2.17 MB
Loading

public/camel-icon.svg

Lines changed: 4 additions & 0 deletions
Loading
Loading
Loading

public/servers/camel.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[
22
{
3-
"name": "camel-tool",
4-
"key": "camel-tool",
5-
"description": "add description here",
3+
"name": "Camel Toolkits",
4+
"key": "camel-toolkits",
5+
"description": "A lightweight server that exports Camel framework toolkits as MCP-compatible tools.",
66
"command": "uvx",
7-
"args": ["add args here"],
7+
"args": ["camel-toolkits-mcp"],
88
"env": {
9-
"add env name here": "add env value here"
9+
"OPENAI_API_KEY": "apiKey@string::Your-OpenAI-API-Key",
10+
"NOTION_TOKEN": "token@string::Your-Notion-Token"
1011
},
11-
"homepage": "https://github.com/camel-ai/mcp-hub"
12+
"homepage": "https://github.com/camel-ai/Camel-toolkits-mcp"
1213
}
1314
]

0 commit comments

Comments
 (0)