Skip to content

update camel tool and header #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ function Modal({ server, onClose }: ModalProps) {
<h2 className="text-2xl font-bold flex items-center gap-4 mb-2">
{server.source === 'official' ? (
<ShieldCheck className="w-6 h-6 text-[#4215cc] flex-shrink-0" />
) : server.source === 'camel' ? (
<Image
src="/camel-icon.svg"
alt="Camel"
width={24}
height={24}
className="flex-shrink-0"
/>
) : (
<Image
src="/anthropic.svg"
Expand All @@ -119,7 +127,11 @@ function Modal({ server, onClose }: ModalProps) {
</Button>
</div>
<div className="flex justify-between items-center pb-6 border-t ">
<Badge>{server.command}</Badge>
<Badge
variant={server.command === 'uvx' ? 'secondary' : server.command === 'npx' ? 'pink' : undefined}
>
{server.command}
</Badge>
<Button
variant="link"
size="sm"
Expand Down Expand Up @@ -157,7 +169,7 @@ function Modal({ server, onClose }: ModalProps) {
export default function Home() {
const [selectedServer, setSelectedServer] = useState<Server | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [filter, setFilter] = useState<'all' | 'official' | 'anthropic'>('all');
const [filter, setFilter] = useState<'all' | 'official' | 'anthropic' | 'camel'>('all');

const handleCardClick = (server: Server) => {
setSelectedServer(server);
Expand Down Expand Up @@ -186,6 +198,13 @@ export default function Home() {
<Grid2X2 className="w-4 h-4" />
All
</button>
<button
onClick={() => setFilter('camel')}
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'}`}
>
<Image src="/camel-icon.svg" alt="Camel" width={16} height={16} />
Camel
</button>
<button
onClick={() => setFilter('official')}
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'}`}
Expand Down Expand Up @@ -213,12 +232,20 @@ export default function Home() {
<CardTitle className="flex items-center gap-2">
{server.source === 'official' ? (
<ShieldCheck className="w-6 h-6 text-primary flex-shrink-0" />
) : server.source === 'camel' ? (
<Image
src="/camel-icon.svg"
alt="Camel"
width={24}
height={24}
className="flex-shrink-0"
/>
) : (
<Image
src="/anthropic.svg"
alt="Anthropic"
width={16}
height={16}
width={24}
height={24}
className="flex-shrink-0"
/>
)}
Expand All @@ -227,7 +254,11 @@ export default function Home() {
<CardDescription className="h-20 line-clamp-4">{server.description}</CardDescription>
</CardHeader>
<CardFooter className="flex justify-between">
<Badge variant="secondary" className="font-bold">{server.command}</Badge>
<Badge
variant={server.command === 'uvx' ? 'secondary' : server.command === 'npx' ? 'pink' : undefined}
>
{server.command}
</Badge>
<Button
variant="link"
size="sm"
Expand Down
2 changes: 2 additions & 0 deletions components/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const badgeVariants = cva(
"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",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
pink:
"border-transparent bg-[#fdd1f7] text-[#d029ae] [a&]:hover:bg-[#fdd1f7]/90",
},
},
defaultVariants: {
Expand Down
42 changes: 25 additions & 17 deletions components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
import { motion } from "framer-motion"

import SwipeCard from "./swipe.card"
export function Header() {
return (
<header className="pt-32 pb-16 text-center">
<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">
<motion.div
className="flex flex-col sm:flex-row md:flex-row lg:flex-row xl:flex-row 2xl:flex-row items-center gap-2"
layout
transition={{ type: "spring", damping: 30, stiffness: 400 }}
>
<motion.span
<header className="pt-22 pb-8">
<div className="flex flex-col lg:flex-row items-center justify-between flex-1 w-full max-w-[1600px] mx-auto py-8">
{/* Left: Text Content */}
<div className="flex-1 min-w-0 text-center mb-8 lg:mb-0 lg:w-1/2">
<div className="text-6xl font-bold mb-6">
<motion.div
className="flex flex-col gap-2 items-center"
layout
transition={{ type: "spring", damping: 30, stiffness: 400 }}
>
CAMEL with MCP
</motion.span>
</motion.div>
<motion.span
layout
transition={{ type: "spring", damping: 30, stiffness: 400 }}
>
CAMEL with MCP
</motion.span>
</motion.div>
</div>
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
Discover and compare Model Context Protocol Servers to build your Agents
</p>
</div>

<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
Discover and compare Model Context Protocol Servers to build your Agents
</p>
</header>
{/* Right: Blog Thumbnail Card */}
<div className="flex-1 min-w-0 lg:w-1/2 flex items-center justify-center">
<SwipeCard />
</div>
</div>
</header>
)
}
184 changes: 184 additions & 0 deletions components/swipe.card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import React, { useState, useEffect } from "react";
import { motion, useMotionValue, useTransform } from "framer-motion";
import Image from "next/image";

interface BlogCardData {
id: number;
src: string;
title: string;
description: string;
link: string;
}

const blogCards: BlogCardData[] = [
{
id: 1,
src: "/camel-ai-agent-mcp-integration.png",
title: "How to Connect Your CAMEL-AI Agent to External Tools via MCP",
description: "Seamlessly integrate databases, APIs, and web services into your CAMEL-AI agents using the Model Context Protocol.",
link: "https://www.camel-ai.org/blogs/camel-ai-agent-mcp-integration",
},
{
id: 2,
src: "/connect-your-owl-agent-to-notion-via-the-mcp-server.png",
title: "How to Connect Your OWL Agent to Notion via the MCP Server",
description: "Empower your CAMEL-AI OWL agent to interact with Notion using the MCP server.",
link: "https://www.camel-ai.org/blogs/connect-your-owl-agent-to-notion-via-the-mcp-server",
},
{
id: 3,
src: "/camel-mcp-servers-model-context-protocol-ai-agents.png",
title: "CAMEL x MCP: Making AI Agents Accessible to All Tools",
description: "A lightweight server that exports Camel framework toolkits as MCP-compatible tools.",
link: "https://www.camel-ai.org/blogs/camel-mcp-servers-model-context-protocol-ai-agents",
},
];

const ArrowButton = ({ direction, onClick }: { direction: 'left' | 'right', onClick: () => void }) => (
<button
onClick={onClick}
aria-label={direction === 'left' ? 'Previous card' : 'Next card'}
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"
type="button"
>
{direction === 'left' ? (
<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>
) : (
<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
)}
</button>
);

const SwipeCards = () => {
const [cards, setCards] = useState<BlogCardData[]>(blogCards);

// Auto-rotate cards effect
useEffect(() => {
const interval = setInterval(() => {
if (cards.length > 0) {
setCards(prevCards => {
const newCards = [...prevCards];
const lastCard = newCards.pop();
if (lastCard) {
newCards.unshift(lastCard);
}
return newCards;
});
}
}, 10000);

return () => clearInterval(interval);
}, [cards]);
Comment on lines +54 to +71
Copy link
Preview

Copilot AI May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auto-rotate interval is recreated on every change to 'cards' due to its dependency array including 'cards'. Consider using an empty dependency array along with a functional state update or a ref to avoid unnecessary interval re-creations.

Suggested change
// Auto-rotate cards effect
useEffect(() => {
const interval = setInterval(() => {
if (cards.length > 0) {
setCards(prevCards => {
const newCards = [...prevCards];
const lastCard = newCards.pop();
if (lastCard) {
newCards.unshift(lastCard);
}
return newCards;
});
}
}, 10000);
return () => clearInterval(interval);
}, [cards]);
const intervalRef = React.useRef<NodeJS.Timeout | null>(null);
// Auto-rotate cards effect
useEffect(() => {
intervalRef.current = setInterval(() => {
setCards(prevCards => {
if (prevCards.length === 0) return prevCards;
const newCards = [...prevCards];
const lastCard = newCards.pop();
if (lastCard) {
newCards.unshift(lastCard);
}
return newCards;
});
}, 10000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);

Copilot uses AI. Check for mistakes.


// Manual navigation handlers
const handlePrev = () => {
setCards(prevCards => {
if (prevCards.length === 0) return prevCards;
const newCards = [...prevCards];
const firstCard = newCards.shift();
if (firstCard) {
newCards.push(firstCard);
}
return newCards;
});
};

const handleNext = () => {
setCards(prevCards => {
if (prevCards.length === 0) return prevCards;
const newCards = [...prevCards];
const lastCard = newCards.pop();
if (lastCard) {
newCards.unshift(lastCard);
}
return newCards;
});
};

return (
<div className="relative w-full h-[350px] flex flex-col items-center justify-center">
<div className="relative w-full h-full flex items-center justify-center">
{cards.map((card) => (
<BlogSwipeCard key={card.id} cards={cards} setCards={setCards} {...card} />
))}
</div>
<div className="flex items-center justify-center mt-12">
<ArrowButton direction="left" onClick={handlePrev} />
<ArrowButton direction="right" onClick={handleNext} />
</div>
</div>
);
};

const BlogSwipeCard = ({ id, src, title, description, link, setCards, cards }: BlogCardData & {
setCards: React.Dispatch<React.SetStateAction<BlogCardData[]>>;
cards: BlogCardData[];
}) => {
const x = useMotionValue(0);
const rotateRaw = useTransform(x, [-150, 150], [-18, 18]);
const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]);

const isFront = id === cards[cards.length - 1].id;

const rotate = useTransform(() => {
const offset = isFront ? 0 : id % 2 ? 6 : -6;
return `${rotateRaw.get() + offset}deg`;
});

const handleDragEnd = () => {
if (Math.abs(x.get()) > 100) {
setCards((prevCards: BlogCardData[]) => {
const filtered = prevCards.filter((card: BlogCardData) => card.id !== id);
filtered.unshift({ id, src, title, description, link });
return filtered;
});
}
};

return (
<motion.div
className="absolute w-full h-full flex items-center justify-center"
style={{
x,
opacity,
rotate,
zIndex: isFront ? 1 : 0,
transition: "0.125s transform",
}}
animate={{
scale: isFront ? 1 : 0.95,
}}
drag={isFront ? "x" : false}
dragConstraints={{
left: 0,
right: 0,
}}
onDragEnd={handleDragEnd}
>
<div className="flex-shrink-0 w-full max-w-xs lg:max-w-sm">
<a
href={link}
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"
tabIndex={0}
target="_blank"
rel="noopener noreferrer"
>
<div className="relative w-full h-48 rounded-lg">
<Image
src={src}
alt={title}
fill
style={{ objectFit: 'cover', borderRadius: '0.5rem' }}
priority={isFront}
/>
</div>
<div className="mt-4">
<h3 className="text-xl font-semibold mb-2">{title}</h3>
</div>
</a>
</div>
</motion.div>
);
};

export default SwipeCards;
Binary file added public/camel-ai-agent-mcp-integration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/camel-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 7 additions & 6 deletions public/servers/camel.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[
{
"name": "camel-tool",
"key": "camel-tool",
"description": "add description here",
"name": "Camel Toolkits",
"key": "camel-toolkits",
"description": "A lightweight server that exports Camel framework toolkits as MCP-compatible tools.",
"command": "uvx",
"args": ["add args here"],
"args": ["camel-toolkits-mcp"],
"env": {
"add env name here": "add env value here"
"OPENAI_API_KEY": "apiKey@string::Your-OpenAI-API-Key",
"NOTION_TOKEN": "token@string::Your-Notion-Token"
},
"homepage": "https://github.com/camel-ai/mcp-hub"
"homepage": "https://github.com/camel-ai/Camel-toolkits-mcp"
}
]