diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d04197b --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# Dependencies +node_modules +.pnp +.pnp.js + +# Testing +/coverage + +# Next.js +.next/ +out/ +build +dist + +# Environment Variables +.env +.env.local +.env.* +!.env.example +!.env.local.example + +# Database +*.db +*.db-journal +*.sqlite +*.sqlite3 +chat.db* + +# Debug logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# IDE/Editor +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# Production builds +/build +/dist + +# Cache +.cache/ +node_modules diff --git a/app/agentstats/page.tsx b/app/agentstats/page.tsx new file mode 100644 index 0000000..55071d9 --- /dev/null +++ b/app/agentstats/page.tsx @@ -0,0 +1,10 @@ +import AgentStats from '@/components/AgentStats'; + +export default function AgentsPage() { + return ( +
+

Agent Statistics

+ +
+ ); +} diff --git a/app/api/rooms/route.ts b/app/api/rooms/route.ts index 8b9f63a..86146cc 100755 --- a/app/api/rooms/route.ts +++ b/app/api/rooms/route.ts @@ -2,49 +2,49 @@ import { NextResponse } from "next/server"; import { listRooms, createRoom } from "@/server/store"; import { ChatRoom, ModelInfo } from "@/server/types"; -// List all rooms export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const tags = searchParams.get("tags")?.split(",") || []; - - const rooms = await listRooms(tags); - console.log('Available rooms:', rooms); // Debug log - - return NextResponse.json({ rooms }); - } catch (error) { - console.error('Error in GET /api/rooms:', error); - return NextResponse.json( - { error: "Failed to fetch rooms", details: error }, - { status: 500 } - ); - } + try { + const { searchParams } = new URL(request.url); + const tags = searchParams.get("tags")?.split(",") || []; + + const rooms = await listRooms(tags); + console.log('Available rooms:', rooms); + + return NextResponse.json({ rooms }); + } catch (error) { + console.error('Error in GET /api/rooms:', error); + return NextResponse.json({ + error: "Failed to fetch rooms", + message: error instanceof Error ? error.message : String(error), + stack: process.env.NODE_ENV === 'development' ? error instanceof Error ? error.stack : undefined : undefined + }, { status: 500 }); + } } -// Create a new room export async function POST(request: Request) { - try { - const { name, topic, tags, creator } = await request.json() as { - name: string; - topic: string; - tags: string[]; - creator: ModelInfo; - }; - - const room = await createRoom({ - name, - topic, - tags, - participants: [creator], - createdAt: new Date().toISOString(), - messageCount: 0 - }); - - return NextResponse.json({ room }); - } catch (error) { - return NextResponse.json( - { error: "Failed to create room" }, - { status: 500 } - ); - } -} \ No newline at end of file + try { + const { name, topic, tags, creator } = await request.json() as { + name: string; + topic: string; + tags: string[]; + creator: ModelInfo; + }; + + const room = await createRoom({ + name, + topic, + tags, + participants: [creator], + createdAt: new Date().toISOString(), + messageCount: 0 + }); + + return NextResponse.json({ room }); + } catch (error) { + return NextResponse.json({ + error: "Failed to create room", + message: error instanceof Error ? error.message : String(error), + stack: process.env.NODE_ENV === 'development' ? error instanceof Error ? error.stack : undefined : undefined + }, { status: 500 }); + } +} \ No newline at end of file diff --git a/components/AgentStats.tsx b/components/AgentStats.tsx new file mode 100644 index 0000000..331eaff --- /dev/null +++ b/components/AgentStats.tsx @@ -0,0 +1,185 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { fetchAgents } from '@/server/actions'; +import { ArrowUpIcon, ArrowDownIcon } from '@heroicons/react/20/solid'; + +interface Tweet { + tweetUrl: string; + tweetAuthorDisplayName: string; + smartEngagementPoints: number; + impressionsCount: number; +} + +interface Contract { + chain: number; + contractAddress: string; +} + +interface Agent { + agentName: string; + contracts: Contract[]; + twitterUsernames: string[]; + mindshare: number; + marketCap: number; + price: number; + volume24Hours: number; + holdersCount: number; + followersCount: number; + smartFollowersCount: number; + topTweets: Tweet[]; + mindshareDeltaPercent: number; + marketCapDeltaPercent: number; + priceDeltaPercent: number; + volume24HoursDeltaPercent: number; + holdersCountDeltaPercent: number; + averageImpressionsCount: number; + averageImpressionsCountDeltaPercent: number; + averageEngagementsCount: number; + averageEngagementsCountDeltaPercent: number; +} + +interface ApiResponse { + ok: { + data: Agent[]; + currentPage: number; + totalPages: number; + totalCount: number; + }; +} + +function DeltaIndicator({ value }: { value: number }) { + if (value === 0) return null; + const isPositive = value > 0; + return ( + + {isPositive ? : } + {Math.abs(value).toFixed(2)}% + + ); +} + +export default function AgentStats() { + const [agents, setAgents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + + useEffect(() => { + const loadAgents = async () => { + try { + const data = await fetchAgents(currentPage); + setAgents(data.ok.data); + setTotalPages(data.ok.totalPages); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch agents'); + } finally { + setLoading(false); + } + }; + + loadAgents(); + }, [currentPage]); + + if (loading) return
Loading...
; + if (error) return
Error: {error}
; + + return ( +
+
+ {agents.map((agent) => ( +
+

{agent.agentName}

+ +
+
+
+ Mindshare: {agent.mindshare.toFixed(2)} + +
+ +
+ Market Cap: ${(agent.marketCap / 1000000).toFixed(2)}M + +
+ +
+ Price: ${agent.price.toFixed(4)} + +
+ +
+ 24h Volume: ${(agent.volume24Hours / 1000000).toFixed(2)}M + +
+
+ +
+
+ Holders: {agent.holdersCount.toLocaleString()} + +
+ +
+ Followers: {agent.followersCount.toLocaleString()} +
+ +
+ Smart Followers: {agent.smartFollowersCount.toLocaleString()} +
+ +
+ Avg Impressions: {agent.averageImpressionsCount.toLocaleString()} + +
+ +
+ Avg Engagements: {agent.averageEngagementsCount.toFixed(1)} + +
+
+
+ +
+

Top Tweets

+
+ {agent.topTweets.slice(0, 3).map((tweet, index) => ( +
+ + {tweet.tweetAuthorDisplayName} + + + ({tweet.smartEngagementPoints} points, {tweet.impressionsCount.toLocaleString()} impressions) + +
+ ))} +
+
+
+ ))} +
+ +
+ + + Page {currentPage} of {totalPages} + + +
+
+ ); +} \ No newline at end of file diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..42d5b5f --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { cn } from "@/lib/utils" +import { ChevronDownIcon } from "@radix-ui/react-icons" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..57760f2 --- /dev/null +++ b/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx new file mode 100644 index 0000000..5afd41d --- /dev/null +++ b/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..d6a5226 --- /dev/null +++ b/components/ui/aspect-ratio.tsx @@ -0,0 +1,7 @@ +"use client" + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..e87d62b --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..1fd4b2c --- /dev/null +++ b/components/ui/breadcrumb.tsx @@ -0,0 +1,114 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cn } from "@/lib/utils" +import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>