"use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; import { z } from "zod"; import { cn } from "@/lib/utils"; import { Wallet, TrendingUp, TrendingDown, PieChart } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { ChainIcon } from "@/components/crypto/ChainIcon"; // Schema for portfolio holding const HoldingSchema = z.object({ symbol: z.string(), name: z.string(), chain: z.string(), balance: z.number(), value: z.number(), costBasis: z.number().optional(), pnl: z.number().optional(), pnlPercent: z.number().optional(), allocation: z.number(), }); // Schema for portfolio display tool arguments export const PortfolioDisplayArgsSchema = z.object({ holdings: z.array(HoldingSchema), totalValue: z.number(), totalPnl: z.number().optional(), totalPnlPercent: z.number().optional(), lastUpdated: z.string().optional(), }); export type PortfolioDisplayArgs = z.infer; // Schema for portfolio display result export const PortfolioDisplayResultSchema = z.object({ success: z.boolean(), message: z.string().optional(), }); export type PortfolioDisplayResult = z.infer; const formatValue = (value: number): string => { if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`; if (value >= 1e3) return `$${(value / 1e3).toFixed(2)}K`; return `$${value.toFixed(2)}`; }; /** * PortfolioDisplayToolUI - Displays user's portfolio inline in chat * Used when AI responds to "how's my portfolio?" or "show my holdings" */ export const PortfolioDisplayToolUI = makeAssistantToolUI({ toolName: "get_portfolio", render: ({ args, status }) => { const isLoading = status.type === "running"; const holdings = args.holdings || []; const hasPnl = args.totalPnl !== undefined; return (
Your Portfolio {isLoading && Loading...}
{args.lastUpdated && ( Updated {args.lastUpdated} )}
{/* Total Value */}

Total Value

{formatValue(args.totalValue)}

{hasPnl && (

= 0 ? "text-green-500" : "text-red-500")}> {(args.totalPnl || 0) >= 0 ? : } {(args.totalPnl || 0) >= 0 ? "+" : ""}{formatValue(args.totalPnl || 0)} ({(args.totalPnlPercent || 0) >= 0 ? "+" : ""}{(args.totalPnlPercent || 0).toFixed(2)}%)

)}
{/* Holdings List */} {holdings.length === 0 ? (

No holdings found

) : (
{holdings.map((holding) => (
{holding.symbol} {holding.allocation.toFixed(1)}%
{holding.balance.toLocaleString()} tokens

{formatValue(holding.value)}

{holding.pnlPercent !== undefined && (

= 0 ? "text-green-500" : "text-red-500")}> {holding.pnlPercent >= 0 ? "+" : ""}{holding.pnlPercent.toFixed(2)}%

)}
))}
)}
); }, });