feat(platform): Updates to Agent Page (#8664)

Co-authored-by: Swifty <craigswift13@gmail.com>
This commit is contained in:
Bently
2024-11-20 09:28:26 +00:00
committed by GitHub
parent 65344b9783
commit afe5c12afb
7 changed files with 274 additions and 117 deletions

View File

@@ -13,7 +13,8 @@ const meta = {
onRunAgent: { action: "run agent clicked" },
name: { control: "text" },
creator: { control: "text" },
description: { control: "text" },
shortDescription: { control: "text" },
longDescription: { control: "text" },
rating: { control: "number", min: 0, max: 5, step: 0.1 },
runs: { control: "number" },
categories: { control: "object" },
@@ -28,12 +29,21 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
onRunAgent: () => console.log("Run agent clicked"),
name: "SEO Optimizer",
creator: "AI Labs",
description: "Optimize your website's SEO with AI-powered suggestions",
rating: 4.5,
runs: 10000,
categories: ["SEO", "Marketing", "AI"],
name: "AI Video Generator",
creator: "Toran Richards",
shortDescription: "Transform ideas into breathtaking images with this AI-powered Image Generator.",
longDescription: `Create Viral-Ready Content in Seconds! Transform trending topics into engaging videos with this cutting-edge AI Video Generator. Perfect for content creators, social media managers, and marketers looking to quickly produce high-quality content.
Key features include:
- Customizable video output
- 15+ pre-made templates
- Auto scene detection
- Smart text-to-speech
- Multiple export formats
- SEO-optimized suggestions`,
rating: 4.7,
runs: 1500,
categories: ["Video", "Content Creation", "Social Media"],
lastUpdated: "2 days ago",
version: "1.2.0",
},
@@ -44,10 +54,11 @@ export const LowRating: Story = {
...Default.args,
name: "Data Analyzer",
creator: "DataTech",
description: "Analyze complex datasets with machine learning algorithms",
shortDescription: "Analyze complex datasets with machine learning algorithms",
longDescription: "A comprehensive data analysis tool that leverages machine learning to provide deep insights into your datasets. Currently in beta testing phase.",
rating: 2.7,
runs: 5000,
categories: ["Data Analysis"],
categories: ["Data Analysis", "Machine Learning"],
lastUpdated: "1 week ago",
version: "0.9.5",
},
@@ -58,7 +69,8 @@ export const HighRuns: Story = {
...Default.args,
name: "Code Assistant",
creator: "DevAI",
description: "Get AI-powered coding help for various programming languages",
shortDescription: "Get AI-powered coding help for various programming languages",
longDescription: "An advanced AI coding assistant that supports multiple programming languages and frameworks. Features include code completion, refactoring suggestions, and bug detection.",
rating: 4.8,
runs: 1000000,
categories: ["Programming", "AI", "Developer Tools"],
@@ -72,7 +84,8 @@ export const WithInteraction: Story = {
...Default.args,
name: "Task Planner",
creator: "Productivity AI",
description: "Plan and organize your tasks efficiently with AI",
shortDescription: "Plan and organize your tasks efficiently with AI",
longDescription: "An intelligent task management system that helps you organize, prioritize, and complete your tasks more efficiently. Features smart scheduling and AI-powered suggestions.",
rating: 4.2,
runs: 50000,
categories: ["Productivity", "Task Management", "AI"],
@@ -81,10 +94,21 @@ export const WithInteraction: Story = {
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Test run agent button
const runButton = canvas.getByText("Run agent");
await userEvent.hover(runButton);
await userEvent.click(runButton);
// Test rating interaction
const ratingStars = canvas.getAllByLabelText(/Star Icon/);
await userEvent.hover(ratingStars[3]);
await userEvent.click(ratingStars[3]);
// Test category interaction
const category = canvas.getByText("Productivity");
await userEvent.hover(category);
await userEvent.click(category);
},
};
@@ -93,8 +117,8 @@ export const LongDescription: Story = {
...Default.args,
name: "AI Writing Assistant",
creator: "WordCraft AI",
description:
"Enhance your writing with our advanced AI-powered assistant. It offers real-time suggestions for grammar, style, and tone, helps with research and fact-checking, and can even generate content ideas based on your input.",
shortDescription: "Enhance your writing with our advanced AI-powered assistant.",
longDescription: "It offers real-time suggestions for grammar, style, and tone, helps with research and fact-checking, and can even generate content ideas based on your input.",
rating: 4.7,
runs: 75000,
categories: ["Writing", "AI", "Content Creation"],

View File

@@ -1,13 +1,15 @@
"use client";
import * as React from "react";
import { Button } from "./Button";
import { IconPlay, IconStar, StarRatingIcons } from "@/components/ui/icons";
import Link from "next/link";
import { StarRatingIcons } from "@/components/ui/icons";
import { Separator } from "@/components/ui/separator";
interface AgentInfoProps {
name: string;
creator: string;
description: string;
shortDescription: string;
longDescription: string;
rating: number;
runs: number;
categories: string[];
@@ -18,80 +20,117 @@ interface AgentInfoProps {
export const AgentInfo: React.FC<AgentInfoProps> = ({
name,
creator,
description,
shortDescription,
longDescription,
rating,
runs,
categories,
lastUpdated,
version,
}) => {
const onRunAgent = () => {
// TODO: Implement run agent functionality
console.log("Running agent:", name);
};
return (
<div className="flow-root w-full lg:w-[27.5rem]">
<div className="mb-2 font-neue text-3xl font-medium tracking-wide text-[#272727] md:mb-4 md:text-4xl lg:text-5xl">
<div className="w-full max-w-[396px] lg:w-[396px] px-4 sm:px-6 lg:px-0">
{/* Title */}
<div className="w-full text-neutral-900 text-2xl sm:text-3xl lg:text-[35px] font-medium font-['Poppins'] leading-normal lg:leading-10 mb-3 lg:mb-4">
{name}
</div>
<div className="mb-2 font-neue text-lg font-medium leading-9 tracking-tight text-[#737373] md:mb-4 md:text-xl lg:text-2xl">
by{" "}
<Link
href={`/creator/${creator.replace(/\s+/g, "-")}`}
className="text-[#272727]"
>
{/* Creator */}
<div className="w-full flex items-center gap-1.5 mb-3 lg:mb-4">
<div className="text-neutral-800 text-base sm:text-lg lg:text-xl font-normal font-['Geist']">
by
</div>
<div className="text-neutral-800 text-base sm:text-lg lg:text-xl font-medium font-['Geist']">
{creator}
</Link>
</div>
</div>
<Button onClick={onRunAgent} className="mb-8" variants="outline">
Run agent
</Button>
<div className="font-['PP Neue Montreal TT'] mb-6 text-[1.1875rem] font-normal leading-relaxed tracking-tight text-[#282828]">
{description}
{/* Short Description */}
<div className="w-full text-neutral-600 text-base sm:text-lg lg:text-xl font-normal font-['Geist'] leading-normal lg:leading-7 mb-4 lg:mb-6 line-clamp-2">
{shortDescription}
</div>
<div className="mb-6 mr-6 flex items-center justify-between">
<div className="flex items-center">
<div className="font-['PP Neue Montreal TT'] mr-2 text-xl font-normal tracking-tight text-[#272727]">
{/* Run Agent Button */}
<div className="w-full mb-4 lg:mb-6">
<button className="w-full sm:w-auto px-4 sm:px-5 lg:px-6 py-3 sm:py-3.5 lg:py-4 bg-violet-600 hover:bg-violet-700 transition-colors rounded-[38px] inline-flex items-center justify-center gap-2 sm:gap-2.5">
<IconPlay className="w-5 h-5 sm:w-5 sm:h-5 lg:w-6 lg:h-6 text-white" />
<span className="text-neutral-50 text-base sm:text-lg font-medium font-['Poppins']">
Run agent
</span>
</button>
</div>
{/* Rating and Runs */}
<div className="w-full flex justify-between items-center mb-4 lg:mb-6">
<div className="flex items-center gap-1.5 sm:gap-2">
<span className="text-neutral-800 text-base sm:text-lg font-semibold font-['Geist'] whitespace-nowrap">
{rating.toFixed(1)}
</div>
<div className="flex items-center gap-px">
</span>
<div className="flex gap-0.5">
{StarRatingIcons(rating)}
</div>
</div>
<div>
<span className="font-neue text-xl font-medium tracking-tight text-[#272727]">
{runs.toLocaleString()}+
</span>
<span className="font-neue text-xl font-normal tracking-tight text-[#272727]">
{" "}
runs
</span>
<div className="text-neutral-800 text-base sm:text-lg font-semibold font-['Geist'] whitespace-nowrap">
{runs.toLocaleString()} runs
</div>
</div>
<div className="mb-3 font-neue text-lg font-medium leading-9 tracking-tight text-[#282828]">
Categories
{/* Separator */}
<Separator className="mb-4 lg:mb-6" />
{/* Description Section */}
<div className="w-full mb-4 lg:mb-6">
<div className="text-neutral-800 text-xs sm:text-sm font-medium mb-1.5 sm:mb-2">
Description
</div>
<div className="w-full text-neutral-600 text-sm sm:text-base font-normal font-['Geist'] whitespace-pre-line">
{longDescription}
</div>
</div>
<div className="mb-6 flex flex-wrap gap-2.5">
{categories.map((category, index) => (
<div
key={index}
className="flex items-center rounded-[2.125rem] border border-black/50 px-4 py-1.5"
>
<div className="font-neue text-[1.1875rem] font-normal leading-relaxed tracking-tight text-[#474747]">
{/* Categories */}
<div className="w-full flex flex-col gap-1.5 sm:gap-2 mb-4 lg:mb-6">
<div className="text-neutral-800 text-xs sm:text-sm font-medium">
Categories
</div>
<div className="flex flex-wrap gap-1.5 sm:gap-2">
{categories.map((category, index) => (
<div
key={index}
className="px-2 sm:px-3 py-0.5 sm:py-1 bg-white rounded-full border border-neutral-200 text-neutral-800 text-xs sm:text-sm whitespace-nowrap"
>
{category}
</div>
</div>
))}
))}
</div>
</div>
<div className="mb-3 font-neue text-lg font-medium leading-9 tracking-tight text-[#282828]">
Version history
{/* Rate Agent */}
<div className="w-full flex flex-col gap-1.5 sm:gap-2 mb-4 lg:mb-6">
<div className="text-neutral-800 text-xs sm:text-sm font-medium">
Rate agent
</div>
<div className="flex gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<IconStar
key={star}
className="w-4 h-4 sm:w-5 sm:h-5 text-neutral-300 cursor-pointer hover:text-neutral-800"
/>
))}
</div>
</div>
<div className="mb-2 font-neue text-[1.1875rem] font-normal leading-relaxed tracking-tight text-[#474747]">
Last updated {lastUpdated}
</div>
<div className="font-neue text-[1.1875rem] font-normal leading-relaxed tracking-tight text-[#474747]">
Version {version}
{/* Version History */}
<div className="w-full flex flex-col gap-0.5 sm:gap-1">
<div className="text-neutral-800 text-xs sm:text-sm font-medium">
Version history
</div>
<div className="text-neutral-600 text-xs sm:text-sm">
Last updated {lastUpdated}
</div>
<div className="text-neutral-600 text-xs sm:text-sm">
Version {version}
</div>
</div>
</div>
);

View File

@@ -11,29 +11,49 @@ interface BecomeACreatorProps {
}
export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
title = "Want to contribute?",
heading = "We're always looking for more Creators!",
description = "Join our ever-growing community of hackers and tinkerers",
buttonText = "Become a Creator",
title = "Become a creator",
heading = "Build AI agents and share your vision",
description = "Join a community where your AI creations can inspire, engage,\nand be downloaded by users around the world.",
buttonText = "Upload your agent",
}) => {
const handleButtonClick = () => {
console.log("Become a Creator clicked");
console.log("Upload agent clicked");
};
return (
<div className="flex w-full flex-col items-center justify-between space-y-4 py-8 leading-9 md:space-y-8">
<div className="mb:mb-8 mb-4 self-start font-neue text-xl font-bold tracking-tight text-[#282828] md:text-[23px]">
<div className="relative h-auto min-h-[300px] md:min-h-[400px] lg:h-[459px] w-full max-w-[1360px] mx-auto px-4 md:px-6 lg:px-8">
{/* Top border */}
<div className="absolute left-0 top-0 h-px w-full bg-gray-200" />
{/* Title */}
<div className="absolute left-4 md:left-6 lg:left-8 top-[26px] text-base md:text-lg font-semibold font-poppins leading-7 text-neutral-800">
{title}
</div>
<div className="max-w-full text-center font-neue text-4xl font-medium tracking-wide text-[#272727] md:text-5xl">
{heading}
{/* Content Container - Centered */}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-[900px] text-center px-4 md:px-6 lg:px-0 pt-[40px]">
{/* Heading with highlighted word */}
<h2 className="text-3xl md:text-4xl lg:text-5xl font-semibold font-poppins leading-tight md:leading-[1.2] lg:leading-[54px] text-neutral-950 mb-6 md:mb-8 lg:mb-12">
Build AI agents and share{' '}
<span className="text-violet-600">your</span>
{' '}vision
</h2>
{/* Description */}
<p className="font-geist text-lg md:text-xl lg:text-2xl font-normal leading-relaxed md:leading-loose text-neutral-700 mb-8 md:mb-10 lg:mb-14 max-w-[90%] mx-auto">
{description}
</p>
{/* Button */}
<button
onClick={handleButtonClick}
className="inline-flex h-[48px] md:h-[56px] lg:h-[68px] cursor-pointer items-center justify-center rounded-[38px] bg-neutral-800 px-4 md:px-5 lg:px-6 py-3 md:py-4 lg:py-5 hover:bg-neutral-700 transition-colors"
>
<span className="font-poppins text-base md:text-lg lg:text-xl font-medium leading-normal md:leading-relaxed lg:leading-7 text-neutral-50 whitespace-nowrap">
{buttonText}
</span>
</button>
</div>
<div className="max-w-full text-center font-neue text-xl font-medium tracking-tight text-[#737373] md:text-[26px]">
{description}
</div>
<Button onClick={handleButtonClick} className="mt-8">
{buttonText}
</Button>
</div>
);
};

View File

@@ -32,11 +32,19 @@ export const StoreCard: React.FC<StoreCardProps> = ({
className="flex h-96 w-64 flex-col rounded-xl pb-2 transition-shadow duration-300 hover:shadow-lg sm:w-64 md:w-80 xl:w-110"
onClick={handleClick}
data-testid="store-card"
role="button"
tabIndex={0}
aria-label={`${agentName} agent card`}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleClick();
}
}}
>
<div className="relative h-48 w-full">
<Image
src={agentImage}
alt={`${agentName} preview`}
alt={`${agentName} preview image`}
fill
sizes="192px"
className="rounded-xl object-cover"
@@ -45,33 +53,44 @@ export const StoreCard: React.FC<StoreCardProps> = ({
<div className="-mt-8 flex flex-col px-4">
{!hideAvatar ? (
<Avatar className="mb-2 h-16 w-16">
<AvatarImage src={avatarSrc} alt={agentName} />
<AvatarFallback className="h-16 w-16">
<AvatarImage
src={avatarSrc}
alt={`${agentName} creator avatar`}
/>
<AvatarFallback
className="h-16 w-16"
role="img"
aria-label={`${agentName} creator initial`}
>
{agentName.charAt(0)}
</AvatarFallback>
</Avatar>
) : (
<div className="h-16" />
<div className="h-16" aria-hidden="true" />
)}
<div className="mb-1 font-neue text-xl font-bold tracking-tight text-[#272727]">
<h2 className="mb-1 font-neue text-xl font-bold tracking-tight text-neutral-900">
{agentName}
</div>
<div className="mb-4 font-neue text-base font-normal leading-[21px] tracking-tight text-[#282828]">
{description}
</div>
<div className="flex items-center justify-between">
<div className="font-neue text-base font-medium tracking-tight text-[#272727]">
</h2>
<div className="flex items-center justify-between mb-4">
<div className="font-neue text-base font-medium tracking-tight text-neutral-900">
{runs.toLocaleString()}+ runs
</div>
<div className="flex items-center">
<div className="mr-2 font-neue text-base font-medium tracking-tight text-[#272727]">
<div className="mr-2 font-neue text-base font-medium tracking-tight text-neutral-900">
{rating.toFixed(1)}
</div>
<div className="inline-flex items-center justify-start gap-px">
<div
className="inline-flex items-center justify-start gap-px"
role="img"
aria-label={`Rating: ${rating.toFixed(1)} out of 5 stars`}
>
{StarRatingIcons(rating)}
</div>
</div>
</div>
<p className="font-neue text-base font-normal leading-[21px] tracking-tight text-neutral-900">
{description}
</p>
</div>
</div>
);

View File

@@ -24,12 +24,14 @@ interface AgentsSectionProps {
sectionTitle: string;
agents: Agent[];
hideAvatars?: boolean;
onCardClick: (agentName: string) => void;
}
export const AgentsSection: React.FC<AgentsSectionProps> = ({
sectionTitle,
agents: topAgents,
hideAvatars = false,
onCardClick,
}) => {
const router = useRouter();
@@ -64,7 +66,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
rating={agent.rating}
avatarSrc={agent.creator_avatar}
hideAvatar={hideAvatars}
onClick={() => handleCardClick(agent.creator, agent.slug)}
onClick={() => onCardClick(agent.agent_name)}
/>
</CarouselItem>
))}
@@ -81,7 +83,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
rating={agent.rating}
avatarSrc={agent.creator_avatar}
hideAvatar={hideAvatars}
onClick={() => handleCardClick(agent.creator, agent.slug)}
onClick={() => onCardClick(agent.agent_name)}
/>
))}
</div>

View File

@@ -72,15 +72,25 @@ const mockMenuItemGroups = [
];
const mockAgentInfo = {
name: "Super SEO Optimizer",
creator: "AI Labs",
description:
"Boost your website's search engine rankings with our advanced AI-powered SEO optimization tool.",
rating: 4.9,
runs: 100000,
categories: ["SEO", "Marketing", "Content"],
lastUpdated: "2023-05-15",
version: "2.1.0",
name: "AI Video Generator",
creator: "Toran Richards",
shortDescription: "Transform ideas into breathtaking images with this AI-powered Image Generator.",
longDescription: `Create Viral-Ready Content in Seconds! Transform trending topics into engaging videos with this cutting-edge AI Video Generator. Perfect for content creators, social media managers, and marketers looking to quickly produce high-quality content.
Key features include:
- Customizable video output
- 15+ pre-made templates
- Auto scene detection
- Smart text-to-speech
- Multiple export formats
- SEO-optimized suggestions
Watch as the AI transforms your ideas into attention-grabbing scripts optimized for maximum engagement - SEO-optimized titles that capture attention in the first 3 seconds - Dual narrative storytelling, using metaphors, and strategically placed calls-to-action. The AI Short-form Video Generator consistently produces viral-worthy videos that resonate with your audience.`,
rating: 4.7,
runs: 1500,
categories: ["Video", "Content Creation", "Social Media"],
lastUpdated: "2 days ago",
version: "4.2.0",
};
const mockAgentImages = [
@@ -123,11 +133,11 @@ const mockOtherAgentsByCreator = [
const mockSimilarAgents = [
{
agentName: "SEO Master",
agentName: "Video Master Pro",
agentImage:
"https://ddz4ak4pa3d19.cloudfront.net/cache/59/b9/59b9415d4044f48f9b9e318c4c5a7984.jpg",
description:
"Comprehensive SEO tool for website optimization and ranking improvement.",
"Professional video editing and enhancement tool powered by AI.",
runs: 80000,
rating: 4.8,
avatarSrc: "https://example.com/avatar2.jpg",
@@ -154,6 +164,18 @@ const mockSimilarAgents = [
},
];
const mockAgentInfoClear = {
name: "",
creator: "",
shortDescription: "",
longDescription: "",
rating: 0,
runs: 0,
categories: [""],
lastUpdated: "",
version: "",
};
export const Default: Story = {
args: {
isLoggedIn: true,
@@ -197,3 +219,10 @@ export const LongLists: Story = {
similarAgents: Array(10).fill(mockSimilarAgents[0]),
},
};
export const Empty: Story = {
args: {
...Default.args,
agentInfo: mockAgentInfoClear,
},
};

View File

@@ -26,7 +26,8 @@ interface AgentPageProps {
agentInfo: {
name: string;
creator: string;
description: string;
shortDescription: string;
longDescription: string;
rating: number;
runs: number;
categories: string[];
@@ -97,12 +98,13 @@ export const AgentPage: React.FC<AgentPageProps> = ({
<main className="px-4 md:mt-4 lg:mt-8">
<BreadCrumbs items={breadcrumbs} />
<div className="flex flex-col gap-5 lg:flex-row">
<div>
<div className="flex flex-col lg:flex-row lg:gap-8 xl:gap-12">
<div className="w-full lg:max-w-[396px]">
<AgentInfo
name={agentInfo.name}
creator={agentInfo.creator}
description={agentInfo.description}
shortDescription={agentInfo.shortDescription}
longDescription={agentInfo.longDescription}
rating={agentInfo.rating}
runs={agentInfo.runs}
categories={agentInfo.categories}
@@ -110,21 +112,43 @@ export const AgentPage: React.FC<AgentPageProps> = ({
version={agentInfo.version}
/>
</div>
<AgentImages images={agentImages} />
<div className="flex-1">
<AgentImages images={agentImages} />
</div>
</div>
<Separator className="my-6" />
<AgentsSection
agents={otherAgentsByCreator}
agents={otherAgentsByCreator.map((agent) => ({
slug: agent.agentName.toLowerCase().replace(/\s+/g, '-'),
agent_name: agent.agentName,
agent_image: agent.agentImage,
creator: agentInfo.creator,
creator_avatar: agent.avatarSrc,
sub_heading: "",
description: agent.description,
runs: agent.runs,
rating: agent.rating,
}))}
onCardClick={handleCardClick}
sectionTitle={`Other agents by ${agentInfo.creator}`}
/>
<Separator className="my-6" />
<AgentsSection
agents={similarAgents}
agents={similarAgents.map((agent) => ({
slug: agent.agentName.toLowerCase().replace(/\s+/g, '-'),
agent_name: agent.agentName,
agent_image: agent.agentImage,
creator: agentInfo.creator,
creator_avatar: agent.avatarSrc,
sub_heading: "",
description: agent.description,
runs: agent.runs,
rating: agent.rating,
}))}
onCardClick={handleCardClick}
sectionTitle="Similar agents"
/>
<Separator className="my-6" />
<BecomeACreator
title="Want to contribute?"
heading="We're always looking for more Creators!"