chore: merge upstream with local feature additions

- Merged dexscreener connector, composio connectors, crypto realtime tools from upstream
- Kept local additions: dropbox/onedrive connectors, memory routes, model_list routes, RefreshToken model
- Resolved frontend conflicts: kept tool UIs from both sides
- Accepted upstream lock files (uv.lock, pnpm-lock.yaml)
This commit is contained in:
Vonic 2026-04-13 23:31:52 +07:00
commit 6e86cd7e8a
803 changed files with 152168 additions and 14005 deletions

View file

@ -0,0 +1,770 @@
"use client";
import { cn } from "@/lib/utils";
import { Shield, TrendingUp, TrendingDown, Users, AlertTriangle, Star, Bell, ExternalLink, Trash2, Plus, Activity, Zap, CheckCircle, Eye, Settings, Edit2, Percent, DollarSign, X, Flame, Fish, ArrowUpRight, ArrowDownRight, Globe, Wallet, User as UserIcon, Target } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
import { ChainIcon } from "@/components/crypto/ChainIcon";
import { SafetyBadge } from "@/components/crypto/SafetyBadge";
// ============ MOCK DATA ============
const MOCK_TOKEN_ANALYSIS = {
symbol: "BULLA",
name: "Bulla Token",
chain: "solana",
contractAddress: "BULLAxK9xGJxGqPwPqTbGpLd9yKthvfNUet9V8wj8rWD",
price: 0.00001234,
priceChange24h: 156.7,
marketCap: 2100000,
volume24h: 1200000,
liquidity: 450000,
safetyScore: 72,
holderCount: 12500,
top10HolderPercent: 45,
};
const MOCK_WATCHLIST = [
{ id: "1", symbol: "BULLA", name: "Bulla Token", chain: "solana", price: 0.00001234, priceChange24h: 156.7, alertCount: 2 },
{ id: "2", symbol: "SOL", name: "Solana", chain: "solana", price: 98.45, priceChange24h: 3.2, alertCount: 1 },
{ id: "3", symbol: "BONK", name: "Bonk", chain: "solana", price: 0.00002156, priceChange24h: -12.5, alertCount: 0 },
{ id: "4", symbol: "WIF", name: "dogwifhat", chain: "solana", price: 2.34, priceChange24h: -5.8, alertCount: 3 },
];
const MOCK_ALERTS = [
{ id: "1", type: "price_above" as const, value: 0.00002, enabled: true },
{ id: "2", type: "price_below" as const, value: 0.000008, enabled: true },
{ id: "3", type: "percent_change" as const, value: 20, enabled: false },
{ id: "4", type: "whale_activity" as const, value: 50000, enabled: true },
];
const MOCK_PROACTIVE_ALERT = {
alertType: "price_surge" as const,
tokenSymbol: "BULLA",
tokenName: "Bulla Token",
value: 0.00001234,
previousValue: 0.00000482,
message: "BULLA just surged 156% in the last hour! This is unusual activity - consider taking profits or setting a stop-loss.",
severity: "warning" as const,
timestamp: "2 min ago",
};
const MOCK_ACTION_CONFIRMATION = {
actionType: "watchlist_add" as const,
tokenSymbol: "BULLA",
details: ["Price alerts (±10%)", "Whale activity monitoring", "Safety score changes"],
};
// ============ HELPER FUNCTIONS ============
const formatPrice = (price: number): string => {
if (price < 0.00001) return `$${price.toExponential(2)}`;
if (price < 1) return `$${price.toFixed(6)}`;
return `$${price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
};
const formatLargeNumber = (num: number): string => {
if (num >= 1e9) return `$${(num / 1e9).toFixed(2)}B`;
if (num >= 1e6) return `$${(num / 1e6).toFixed(2)}M`;
if (num >= 1e3) return `$${(num / 1e3).toFixed(2)}K`;
return `$${num.toFixed(2)}`;
};
// ============ DEMO COMPONENTS ============
// 1. Token Analysis Demo
function TokenAnalysisDemo() {
const args = MOCK_TOKEN_ANALYSIS;
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center gap-2">
<span>📊</span>
Token Analysis
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<ChainIcon chain={args.chain} size="md" />
<div>
<div className="flex items-center gap-2">
<span className="font-bold text-lg">{args.symbol}</span>
<span className="text-muted-foreground text-sm">{args.name}</span>
</div>
<div className="flex items-center gap-2">
<span className="font-medium">{formatPrice(args.price)}</span>
<span className={cn("flex items-center gap-0.5 text-sm font-medium", args.priceChange24h >= 0 ? "text-green-500" : "text-red-500")}>
{args.priceChange24h >= 0 ? <TrendingUp className="h-3 w-3" /> : <TrendingDown className="h-3 w-3" />}
{args.priceChange24h >= 0 ? "+" : ""}{args.priceChange24h.toFixed(2)}%
</span>
</div>
</div>
</div>
<SafetyBadge score={args.safetyScore} size="lg" />
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Market Cap</p>
<p className="font-medium">{formatLargeNumber(args.marketCap)}</p>
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">24h Volume</p>
<p className="font-medium">{formatLargeNumber(args.volume24h)}</p>
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Liquidity</p>
<p className="font-medium">{formatLargeNumber(args.liquidity)}</p>
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Holders</p>
<p className="font-medium flex items-center gap-1">
<Users className="h-3 w-3" />
{args.holderCount.toLocaleString()}
</p>
</div>
</div>
{args.top10HolderPercent > 50 && (
<div className="flex items-center gap-2 text-yellow-600 dark:text-yellow-400 text-sm bg-yellow-500/10 rounded-lg p-2">
<AlertTriangle className="h-4 w-4" />
<span>Top 10 holders own {args.top10HolderPercent}% of supply - high concentration risk</span>
</div>
)}
<div className="flex gap-2 pt-2">
<Button variant="outline" size="sm" className="flex-1">
<Star className="h-4 w-4 mr-2" />Add to Watchlist
</Button>
<Button variant="outline" size="sm"><Bell className="h-4 w-4" /></Button>
<Button variant="outline" size="sm"><ExternalLink className="h-4 w-4" /></Button>
</div>
</CardContent>
</Card>
);
}
// 2. Watchlist Display Demo
function WatchlistDisplayDemo() {
const tokens = MOCK_WATCHLIST;
const sortedByChange = [...tokens].sort((a, b) => b.priceChange24h - a.priceChange24h);
const bestPerformer = sortedByChange[0];
const worstPerformer = sortedByChange[sortedByChange.length - 1];
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center justify-between">
<div className="flex items-center gap-2">
<Star className="h-5 w-5 text-yellow-500" />
Your Watchlist
<Badge variant="secondary">{tokens.length}</Badge>
</div>
<Button variant="outline" size="sm"><Plus className="h-4 w-4 mr-1" />Add Token</Button>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="divide-y">
{tokens.map((token) => (
<div key={token.id} className="flex items-center justify-between py-3 hover:bg-muted/50 -mx-2 px-2 rounded cursor-pointer transition-colors">
<div className="flex items-center gap-3">
<ChainIcon chain={token.chain} size="sm" />
<div>
<div className="flex items-center gap-2">
<span className="font-medium">{token.symbol}</span>
{token.alertCount > 0 && (
<Badge variant="outline" className="text-xs px-1.5 py-0">
<Bell className="h-2.5 w-2.5 mr-0.5" />{token.alertCount}
</Badge>
)}
</div>
<span className="text-xs text-muted-foreground">{token.name}</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className="font-medium">{formatPrice(token.price)}</p>
<p className={cn("text-sm flex items-center justify-end gap-0.5", token.priceChange24h >= 0 ? "text-green-500" : "text-red-500")}>
{token.priceChange24h >= 0 ? <TrendingUp className="h-3 w-3" /> : <TrendingDown className="h-3 w-3" />}
{token.priceChange24h >= 0 ? "+" : ""}{token.priceChange24h.toFixed(1)}%
</p>
</div>
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-red-500">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
<div className="pt-3 border-t text-sm text-muted-foreground">
<span className="text-green-500 font-medium">{bestPerformer.symbol}</span> is your best performer (+{bestPerformer.priceChange24h.toFixed(1)}%)
{worstPerformer.priceChange24h < 0 && (
<span> <span className="text-red-500 font-medium">{worstPerformer.symbol}</span> needs attention ({worstPerformer.priceChange24h.toFixed(1)}%)</span>
)}
</div>
</CardContent>
</Card>
);
}
// 3. Action Confirmation Demo
function ActionConfirmationDemo() {
const args = MOCK_ACTION_CONFIRMATION;
return (
<Card className="overflow-hidden border-l-4 border-l-green-500">
<CardContent className="py-4">
<div className="flex items-start gap-3">
<div className="p-2 rounded-full bg-yellow-500/10">
<CheckCircle className="h-5 w-5 text-green-500" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<Star className="h-4 w-4 text-yellow-500" />
<span className="font-medium">Added to Watchlist</span>
<Badge variant="secondary" className="font-mono">{args.tokenSymbol}</Badge>
</div>
<div className="mt-2 text-sm text-muted-foreground">
<p className="mb-1">Default monitoring enabled:</p>
<ul className="list-disc list-inside space-y-0.5">
{args.details.map((detail, i) => (<li key={i}>{detail}</li>))}
</ul>
</div>
</div>
</div>
<div className="flex gap-2 mt-4 ml-11">
<Button variant="outline" size="sm"><Eye className="h-3 w-3 mr-1" />View Watchlist</Button>
<Button variant="outline" size="sm"><Settings className="h-3 w-3 mr-1" />Edit Alerts</Button>
</div>
</CardContent>
</Card>
);
}
// 4. Alert Configuration Demo
const ALERT_TYPE_CONFIG = {
price_above: { icon: TrendingUp, label: "Price Above", color: "text-green-500" },
price_below: { icon: TrendingDown, label: "Price Below", color: "text-red-500" },
percent_change: { icon: Percent, label: "% Change", color: "text-blue-500" },
volume_spike: { icon: Activity, label: "Volume Spike", color: "text-purple-500" },
whale_activity: { icon: DollarSign, label: "Whale Activity", color: "text-orange-500" },
};
const formatAlertValue = (type: string, value: number): string => {
if (type === "percent_change") return `${value > 0 ? "+" : ""}${value}%`;
if (type === "volume_spike") return `${value}x normal`;
if (type === "whale_activity") return `>${value.toLocaleString()} USD`;
return `$${value < 1 ? value.toFixed(6) : value.toLocaleString()}`;
};
function AlertConfigurationDemo() {
const alerts = MOCK_ALERTS;
const enabledCount = alerts.filter(a => a.enabled).length;
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center justify-between">
<div className="flex items-center gap-2">
<Bell className="h-5 w-5 text-blue-500" />
Alerts for BULLA
<Badge variant="secondary">{enabledCount} active</Badge>
</div>
<Button variant="outline" size="sm"><Bell className="h-4 w-4 mr-1" />Add Alert</Button>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="divide-y">
{alerts.map((alert) => {
const config = ALERT_TYPE_CONFIG[alert.type];
const Icon = config.icon;
return (
<div key={alert.id} className="flex items-center justify-between py-3">
<div className="flex items-center gap-3">
<Icon className={cn("h-4 w-4", config.color)} />
<div>
<p className="font-medium">{config.label}</p>
<p className="text-sm text-muted-foreground">{formatAlertValue(alert.type, alert.value)}</p>
</div>
</div>
<div className="flex items-center gap-2">
<Switch checked={alert.enabled} />
<Button variant="ghost" size="icon" className="h-8 w-8"><Edit2 className="h-3 w-3" /></Button>
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-500"><Trash2 className="h-3 w-3" /></Button>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
);
}
// 5. Proactive Alert Demo
function ProactiveAlertDemo() {
const args = MOCK_PROACTIVE_ALERT;
const change = ((args.value - args.previousValue) / args.previousValue) * 100;
return (
<Card className="overflow-hidden border-l-4 border-l-green-500">
<CardContent className="py-4">
<div className="flex items-start gap-3">
<div className="p-2 rounded-full bg-green-500/10">
<TrendingUp className="h-5 w-5 text-green-500" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="secondary" className="uppercase text-xs">PRICE SURGE</Badge>
<span className="font-bold">{args.tokenSymbol}</span>
<span className="font-medium text-green-500">+{change.toFixed(1)}%</span>
<span className="text-xs text-muted-foreground ml-auto">{args.timestamp}</span>
</div>
<p className="mt-2 text-sm">{args.message}</p>
</div>
<Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground"><X className="h-4 w-4" /></Button>
</div>
<div className="flex gap-2 mt-3 ml-11">
<Button variant="outline" size="sm"><Eye className="h-3 w-3 mr-1" />View Details</Button>
<Button variant="outline" size="sm"><Bell className="h-3 w-3 mr-1" />Adjust Alert</Button>
</div>
</CardContent>
</Card>
);
}
// 6. Trending Tokens Demo
const MOCK_TRENDING = [
{ symbol: "BULLA", name: "Bulla Token", chain: "solana", price: 0.00001234, priceChange24h: 156.7, volume24h: 1200000, rank: 1 },
{ symbol: "POPCAT", name: "Popcat", chain: "solana", price: 0.89, priceChange24h: 45.2, volume24h: 8500000, rank: 2 },
{ symbol: "WIF", name: "dogwifhat", chain: "solana", price: 2.34, priceChange24h: 32.1, volume24h: 15000000, rank: 3 },
{ symbol: "BONK", name: "Bonk", chain: "solana", price: 0.00002156, priceChange24h: 18.5, volume24h: 5200000, rank: 4 },
];
function TrendingTokensDemo() {
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center gap-2">
<Flame className="h-5 w-5 text-orange-500" />
Trending on Solana
<Badge variant="secondary">24h</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<div className="divide-y">
{MOCK_TRENDING.map((token) => (
<div key={token.symbol} className="flex items-center justify-between py-3 hover:bg-muted/50 -mx-2 px-2 rounded cursor-pointer">
<div className="flex items-center gap-3">
<span className="text-lg font-bold text-muted-foreground w-6">#{token.rank}</span>
<ChainIcon chain={token.chain} size="sm" />
<div>
<span className="font-medium">{token.symbol}</span>
<p className="text-xs text-muted-foreground">{token.name}</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className="font-medium">{formatPrice(token.price)}</p>
<p className={cn("text-sm", token.priceChange24h >= 0 ? "text-green-500" : "text-red-500")}>
+{token.priceChange24h.toFixed(1)}%
</p>
</div>
<div className="text-right hidden md:block">
<p className="text-xs text-muted-foreground">Volume</p>
<p className="text-sm">{formatLargeNumber(token.volume24h)}</p>
</div>
<Button variant="ghost" size="icon" className="h-8 w-8"><Star className="h-4 w-4" /></Button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}
// 7. Whale Activity Demo
const MOCK_WHALE_TXS = [
{ id: "1", type: "buy" as const, amountUsd: 250000, walletLabel: "Smart Money 1", timestamp: "5m ago" },
{ id: "2", type: "sell" as const, amountUsd: 180000, walletLabel: "Whale #42", timestamp: "12m ago" },
{ id: "3", type: "buy" as const, amountUsd: 320000, walletLabel: null, timestamp: "25m ago" },
{ id: "4", type: "transfer" as const, amountUsd: 500000, walletLabel: "Exchange Hot Wallet", timestamp: "1h ago" },
];
function WhaleActivityDemo() {
const summary = { totalBuyVolume: 570000, totalSellVolume: 180000, netFlow: 390000, uniqueWhales: 4 };
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center justify-between">
<div className="flex items-center gap-2">
<Fish className="h-5 w-5 text-blue-500" />
Whale Activity - BULLA
</div>
<ChainIcon chain="solana" size="sm" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div className="bg-green-500/10 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Buy Volume</p>
<p className="font-medium text-green-500">{formatLargeNumber(summary.totalBuyVolume)}</p>
</div>
<div className="bg-red-500/10 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Sell Volume</p>
<p className="font-medium text-red-500">{formatLargeNumber(summary.totalSellVolume)}</p>
</div>
<div className="bg-green-500/10 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Net Flow</p>
<p className="font-medium text-green-500">+{formatLargeNumber(summary.netFlow)}</p>
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Unique Whales</p>
<p className="font-medium">{summary.uniqueWhales}</p>
</div>
</div>
<div className="divide-y">
{MOCK_WHALE_TXS.map((tx) => (
<div key={tx.id} className="flex items-center justify-between py-3">
<div className="flex items-center gap-3">
<div className={cn("p-2 rounded-full", tx.type === "buy" ? "bg-green-500/10" : tx.type === "sell" ? "bg-red-500/10" : "bg-muted")}>
{tx.type === "buy" ? <ArrowUpRight className="h-4 w-4 text-green-500" /> : tx.type === "sell" ? <ArrowDownRight className="h-4 w-4 text-red-500" /> : <ArrowUpRight className="h-4 w-4" />}
</div>
<div>
<span className={cn("font-medium capitalize", tx.type === "buy" ? "text-green-500" : tx.type === "sell" ? "text-red-500" : "")}>{tx.type}</span>
<span className="font-medium ml-2">{formatLargeNumber(tx.amountUsd)}</span>
<p className="text-xs text-muted-foreground">{tx.walletLabel || "Unknown Wallet"} {tx.timestamp}</p>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}
// 8. Market Overview Demo
const MOCK_MARKET = [
{ symbol: "BTC", name: "Bitcoin", price: 67500, priceChange24h: 2.3 },
{ symbol: "ETH", name: "Ethereum", price: 3450, priceChange24h: -1.2 },
{ symbol: "SOL", name: "Solana", price: 98.45, priceChange24h: 5.7 },
];
function MarketOverviewDemo() {
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center gap-2">
<Globe className="h-5 w-5 text-blue-500" />
Market Overview
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Total Market Cap</p>
<p className="font-medium">$2.45T</p>
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">24h Volume</p>
<p className="font-medium">$89.2B</p>
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">BTC Dominance</p>
<p className="font-medium">52.3%</p>
</div>
<div className="bg-green-500/10 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Fear & Greed</p>
<p className="font-medium text-green-500">72 - Greed</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{MOCK_MARKET.map((token) => (
<div key={token.symbol} className="bg-muted/50 rounded-lg p-4 flex items-center justify-between">
<div>
<p className="font-bold text-lg">{token.symbol}</p>
<p className="text-xs text-muted-foreground">{token.name}</p>
</div>
<div className="text-right">
<p className="font-medium">${token.price.toLocaleString()}</p>
<p className={cn("text-sm", token.priceChange24h >= 0 ? "text-green-500" : "text-red-500")}>
{token.priceChange24h >= 0 ? "+" : ""}{token.priceChange24h.toFixed(2)}%
</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}
// 9. Holder Analysis Demo
const MOCK_HOLDERS = [
{ rank: 1, address: "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", label: "Raydium LP", percentage: 15.2, balance: 152000000 },
{ rank: 2, address: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", label: null, percentage: 8.5, balance: 85000000 },
{ rank: 3, address: "HN7cABqLq46Es1jh92dQQisAq662SmxELLLsHHe4YWrH", label: "Team Wallet", percentage: 7.2, balance: 72000000 },
];
function HolderAnalysisDemo() {
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center justify-between">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-purple-500" />
Holder Analysis - BULLA
</div>
<ChainIcon chain="solana" size="sm" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Total Holders</p>
<p className="font-medium">12,500</p>
</div>
<div className="bg-red-500/10 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Top 10 Hold</p>
<p className="font-medium text-red-500">45.2%</p>
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Top 50 Hold</p>
<p className="font-medium">68.5%</p>
</div>
<div className="bg-yellow-500/10 rounded-lg p-3">
<p className="text-xs text-muted-foreground">Concentration Risk</p>
<p className="font-medium text-yellow-500">Medium</p>
</div>
</div>
<div className="divide-y">
{MOCK_HOLDERS.map((holder) => (
<div key={holder.rank} className="flex items-center justify-between py-2">
<div className="flex items-center gap-3">
<span className="text-sm font-bold text-muted-foreground w-6">#{holder.rank}</span>
<div>
<p className="font-medium text-sm">{holder.label || `${holder.address.slice(0, 6)}...${holder.address.slice(-4)}`}</p>
</div>
</div>
<div className="text-right">
<p className="font-medium text-sm">{holder.percentage.toFixed(2)}%</p>
<p className="text-xs text-muted-foreground">{(holder.balance / 1e6).toFixed(1)}M</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}
// 10. Portfolio Display Demo
const MOCK_PORTFOLIO_HOLDINGS = [
{ symbol: "SOL", name: "Solana", chain: "solana", balance: 50, value: 4922.5, pnlPercent: 125.5, allocation: 45 },
{ symbol: "BULLA", name: "Bulla Token", chain: "solana", balance: 5000000, value: 61.7, pnlPercent: 256.7, allocation: 5.6 },
{ symbol: "ETH", name: "Ethereum", chain: "ethereum", balance: 1.5, value: 5175, pnlPercent: 45.2, allocation: 47.3 },
];
function PortfolioDisplayDemo() {
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center gap-2">
<Wallet className="h-5 w-5 text-emerald-500" />
Your Portfolio
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="bg-gradient-to-r from-emerald-500/10 to-blue-500/10 rounded-lg p-4">
<p className="text-sm text-muted-foreground">Total Value</p>
<p className="text-3xl font-bold">$10,934.20</p>
<p className="text-sm text-green-500 flex items-center gap-1 mt-1">
<TrendingUp className="h-4 w-4" />+$3,245.80 (+42.3%)
</p>
</div>
<div className="divide-y">
{MOCK_PORTFOLIO_HOLDINGS.map((holding) => (
<div key={holding.symbol} className="flex items-center justify-between py-3">
<div className="flex items-center gap-3">
<ChainIcon chain={holding.chain} size="sm" />
<div>
<div className="flex items-center gap-2">
<span className="font-medium">{holding.symbol}</span>
<Badge variant="secondary" className="text-xs">{holding.allocation.toFixed(1)}%</Badge>
</div>
<span className="text-xs text-muted-foreground">{holding.balance.toLocaleString()} tokens</span>
</div>
</div>
<div className="text-right">
<p className="font-medium">${holding.value.toLocaleString()}</p>
<p className={cn("text-sm", holding.pnlPercent >= 0 ? "text-green-500" : "text-red-500")}>
+{holding.pnlPercent.toFixed(2)}%
</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}
// 11. User Profile Demo
function UserProfileDemo() {
return (
<Card className="overflow-hidden">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center gap-2">
<UserIcon className="h-5 w-5 text-indigo-500" />
Your Investment Profile
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="rounded-lg p-4 border text-yellow-500 bg-yellow-500/10 border-yellow-500/20">
<div className="flex items-center gap-2 mb-2">
<Shield className="h-4 w-4" />
<span className="text-sm font-medium">Risk Tolerance</span>
</div>
<p className="text-lg font-bold">Moderate</p>
<p className="text-xs text-muted-foreground mt-1">Balance between risk and reward</p>
</div>
<div className="rounded-lg p-4 border bg-muted/50">
<div className="flex items-center gap-2 mb-2">
<Target className="h-4 w-4" />
<span className="text-sm font-medium">Investment Style</span>
</div>
<p className="text-lg font-bold">Swing Trader</p>
<p className="text-xs text-muted-foreground mt-1">Hold for days to weeks</p>
</div>
</div>
<div>
<p className="text-sm font-medium text-muted-foreground mb-2">Preferred Chains</p>
<div className="flex flex-wrap gap-2">
<Badge variant="default">Solana</Badge>
<Badge variant="default">Ethereum</Badge>
<Badge variant="outline">Base</Badge>
</div>
</div>
<p className="text-xs text-muted-foreground text-center pt-2">
Say "update my risk tolerance to aggressive" to change settings
</p>
</CardContent>
</Card>
);
}
export default function CryptoToolsDemoPage() {
return (
<div className="container mx-auto py-8 px-4 max-w-4xl">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">🧪 Crypto Tools Demo</h1>
<p className="text-muted-foreground">Preview of all crypto tool UI components with mock data</p>
<p className="text-sm text-muted-foreground mt-2">These components render inline in chat when AI calls the corresponding tools.</p>
</div>
<div className="space-y-8">
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-blue-500/10 text-blue-500 px-2 py-1 rounded text-sm">1</span>
Token Analysis <code className="text-xs bg-muted px-2 py-1 rounded ml-2">analyze_token</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "Analyze BULLA", "Is BULLA safe?", "Research this token"</p>
<TokenAnalysisDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-blue-500/10 text-blue-500 px-2 py-1 rounded text-sm">2</span>
Watchlist Display <code className="text-xs bg-muted px-2 py-1 rounded ml-2">show_watchlist</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "Show my watchlist", "What tokens am I tracking?"</p>
<WatchlistDisplayDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-blue-500/10 text-blue-500 px-2 py-1 rounded text-sm">3</span>
Action Confirmation <code className="text-xs bg-muted px-2 py-1 rounded ml-2">confirm_action</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "Add BULLA to watchlist", "Remove SOL from watchlist"</p>
<ActionConfirmationDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-blue-500/10 text-blue-500 px-2 py-1 rounded text-sm">4</span>
Alert Configuration <code className="text-xs bg-muted px-2 py-1 rounded ml-2">configure_alerts</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "Show my alerts for BULLA", "Set alert if BULLA drops 20%"</p>
<AlertConfigurationDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-blue-500/10 text-blue-500 px-2 py-1 rounded text-sm">5</span>
Proactive Alert <code className="text-xs bg-muted px-2 py-1 rounded ml-2">proactive_alert</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">AI-initiated: Automatically sent when price surges, whale activity detected, etc.</p>
<ProactiveAlertDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-orange-500/10 text-orange-500 px-2 py-1 rounded text-sm">6</span>
Trending Tokens <code className="text-xs bg-muted px-2 py-1 rounded ml-2">get_trending_tokens</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "What's hot on Solana?", "Show trending tokens", "What's pumping today?"</p>
<TrendingTokensDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-orange-500/10 text-orange-500 px-2 py-1 rounded text-sm">7</span>
Whale Activity <code className="text-xs bg-muted px-2 py-1 rounded ml-2">get_whale_activity</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "Show whale activity for BULLA", "Any big buys?", "Who's accumulating?"</p>
<WhaleActivityDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-orange-500/10 text-orange-500 px-2 py-1 rounded text-sm">8</span>
Market Overview <code className="text-xs bg-muted px-2 py-1 rounded ml-2">get_market_overview</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "How's the market?", "Show market overview", "What's the sentiment?"</p>
<MarketOverviewDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-orange-500/10 text-orange-500 px-2 py-1 rounded text-sm">9</span>
Holder Analysis <code className="text-xs bg-muted px-2 py-1 rounded ml-2">analyze_holders</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "Who holds BULLA?", "Show holder distribution", "Is it concentrated?"</p>
<HolderAnalysisDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-orange-500/10 text-orange-500 px-2 py-1 rounded text-sm">10</span>
Portfolio Display <code className="text-xs bg-muted px-2 py-1 rounded ml-2">get_portfolio</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "How's my portfolio?", "Show my holdings", "What's my P&L?"</p>
<PortfolioDisplayDemo />
</section>
<section>
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<span className="bg-orange-500/10 text-orange-500 px-2 py-1 rounded text-sm">11</span>
User Profile <code className="text-xs bg-muted px-2 py-1 rounded ml-2">get_user_profile</code>
</h2>
<p className="text-sm text-muted-foreground mb-3">Triggered by: "Show my profile", "What's my risk tolerance?", "Update my investment style"</p>
<UserProfileDemo />
</section>
</div>
<div className="mt-12 p-4 bg-muted/50 rounded-lg">
<h3 className="font-semibold mb-2">💡 How it works</h3>
<p className="text-sm text-muted-foreground">
When you chat with the AI and ask crypto-related questions, the AI calls these tools and the corresponding UI components render inline in the chat.
This creates a seamless conversational experience where data and actions are embedded directly in the conversation.
</p>
</div>
<div className="mt-4 p-4 bg-green-500/10 rounded-lg border border-green-500/20">
<h3 className="font-semibold mb-2 text-green-600"> All 11 Tool-UI Components Complete</h3>
<p className="text-sm text-muted-foreground">
Components 1-5 (blue) are the original tools. Components 6-11 (orange) are newly added to cover all crypto features in the spec.
</p>
</div>
</div>
);
}

View file

@ -0,0 +1,272 @@
"use client";
import { useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { MessageSquare, Sparkles, Star, Bell, TrendingUp, ArrowRight, X, Plus, User } from "lucide-react";
import {
MarketOverview,
WatchlistTable,
AlertsPanel,
PortfolioSummary,
AddTokenModal,
CreateAlertModal,
UserProfileSection,
type AlertConfig,
type UserProfile,
} from "@/components/crypto";
import {
MOCK_MARKET_PRICES,
MOCK_WATCHLIST,
MOCK_ALERTS,
MOCK_PORTFOLIO,
} from "@/lib/mock/cryptoMockData";
// Default user profile
const DEFAULT_PROFILE: UserProfile = {
riskTolerance: "moderate",
investmentStyle: "swing",
preferredChains: ["solana", "ethereum"],
notifications: {
priceAlerts: true,
whaleAlerts: true,
newsAlerts: false,
},
};
/**
* Crypto Dashboard Page
*
* Full-featured crypto management dashboard with:
* - Market Overview
* - Watchlist Management (with Add Token modal)
* - Alerts Management (with Create Alert modal)
* - Portfolio Summary
* - User Profile Settings
*
* Also includes a banner promoting the AI Chat for research & analysis.
*/
export default function CryptoDashboardPage() {
const router = useRouter();
const params = useParams();
const searchSpaceId = params.search_space_id;
// UI State
const [showAIBanner, setShowAIBanner] = useState(true);
const [activeTab, setActiveTab] = useState("watchlist");
const [showAddTokenModal, setShowAddTokenModal] = useState(false);
const [showCreateAlertModal, setShowCreateAlertModal] = useState(false);
const [alertPrefilledToken, setAlertPrefilledToken] = useState<{ symbol: string; chain: string } | undefined>();
// Data State (mock - would be from API in production)
const [watchlist, setWatchlist] = useState(MOCK_WATCHLIST);
const [alerts, setAlerts] = useState(MOCK_ALERTS);
const [userProfile, setUserProfile] = useState<UserProfile>(DEFAULT_PROFILE);
const handleGoToChat = () => {
router.push(`/dashboard/${searchSpaceId}/new-chat`);
};
const handleTokenClick = (token: any) => {
router.push(`/dashboard/${searchSpaceId}/new-chat?query=Analyze ${token.symbol}`);
};
const handleConfigureAlerts = (token: any) => {
setAlertPrefilledToken({ symbol: token.symbol, chain: token.chain });
setShowCreateAlertModal(true);
};
const handleAlertClick = (alert: any) => {
router.push(`/dashboard/${searchSpaceId}/new-chat?query=Tell me about ${alert.tokenSymbol}`);
};
const handleRemoveToken = (tokenId: string) => {
setWatchlist((prev) => prev.filter((t) => t.id !== tokenId));
};
const handleAddToken = (token: { symbol: string; name: string; chain: string; contractAddress?: string }) => {
const newToken = {
id: `token-${Date.now()}`,
symbol: token.symbol,
name: token.name,
chain: token.chain,
contractAddress: token.contractAddress,
price: 0,
priceChange24h: 0,
safetyScore: undefined,
alertCount: 0,
};
setWatchlist((prev) => [...prev, newToken]);
};
const handleCreateAlert = (alertConfig: AlertConfig) => {
const newAlert = {
id: `alert-${Date.now()}`,
tokenSymbol: alertConfig.tokenSymbol,
chain: alertConfig.chain,
type: alertConfig.alertType,
message: `${alertConfig.alertType.replace("_", " ")} alert for ${alertConfig.tokenSymbol}`,
severity: "info" as const,
timestamp: new Date().toISOString(),
isRead: false,
};
setAlerts((prev) => [newAlert, ...prev]);
};
const handleSaveProfile = (profile: UserProfile) => {
setUserProfile(profile);
// In production, save to backend
};
return (
<div className="flex flex-col h-full overflow-auto">
{/* AI Chat Promotion Banner */}
{showAIBanner && (
<div className="bg-gradient-to-r from-primary/10 via-primary/5 to-transparent border-b px-4 py-3">
<div className="max-w-7xl mx-auto flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="p-2 rounded-full bg-primary/10">
<Sparkles className="h-5 w-5 text-primary" />
</div>
<div>
<p className="font-medium text-sm">
💡 Try our AI Crypto Advisor for deeper analysis!
</p>
<p className="text-xs text-muted-foreground">
Ask questions like "Is BULLA safe?" or "Set alert if SOL drops 10%"
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button size="sm" onClick={handleGoToChat} className="gap-1">
<MessageSquare className="h-4 w-4" />
Open AI Chat
<ArrowRight className="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setShowAIBanner(false)}>
<X className="h-4 w-4" />
</Button>
</div>
</div>
</div>
)}
{/* Main Content */}
<div className="flex-1 p-4 md:p-6 max-w-7xl mx-auto w-full">
<div className="mb-6">
<h1 className="text-2xl font-bold flex items-center gap-2">
🚀 Crypto Dashboard
</h1>
<p className="text-muted-foreground text-sm mt-1">
Manage your watchlist, alerts, and track market trends
</p>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
<TabsList>
<TabsTrigger value="watchlist" className="gap-2">
<Star className="h-4 w-4" />
Watchlist
</TabsTrigger>
<TabsTrigger value="alerts" className="gap-2">
<Bell className="h-4 w-4" />
Alerts
</TabsTrigger>
<TabsTrigger value="market" className="gap-2">
<TrendingUp className="h-4 w-4" />
Market
</TabsTrigger>
<TabsTrigger value="profile" className="gap-2">
<User className="h-4 w-4" />
Profile
</TabsTrigger>
</TabsList>
{/* Watchlist Tab */}
<TabsContent value="watchlist" className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => setShowAddTokenModal(true)} className="gap-2">
<Plus className="h-4 w-4" />
Add Token
</Button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<div className="lg:col-span-2">
<WatchlistTable
tokens={watchlist}
onTokenClick={handleTokenClick}
onConfigureAlerts={handleConfigureAlerts}
onRemoveToken={handleRemoveToken}
/>
</div>
<div>
<PortfolioSummary portfolio={MOCK_PORTFOLIO} />
</div>
</div>
</TabsContent>
{/* Alerts Tab */}
<TabsContent value="alerts" className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => { setAlertPrefilledToken(undefined); setShowCreateAlertModal(true); }} className="gap-2">
<Plus className="h-4 w-4" />
Create Alert
</Button>
</div>
<AlertsPanel
alerts={alerts}
onAlertClick={handleAlertClick}
/>
</TabsContent>
{/* Market Tab */}
<TabsContent value="market" className="space-y-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<MarketOverview tokens={MOCK_MARKET_PRICES} />
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-center justify-center py-8 text-center">
<Sparkles className="h-12 w-12 text-primary/50 mb-4" />
<h3 className="font-semibold mb-2">Want deeper market insights?</h3>
<p className="text-sm text-muted-foreground mb-4">
Ask our AI about trending tokens, market sentiment, or specific analysis
</p>
<Button onClick={handleGoToChat} className="gap-2">
<MessageSquare className="h-4 w-4" />
Ask AI Advisor
</Button>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
{/* Profile Tab */}
<TabsContent value="profile" className="space-y-4">
<div className="max-w-xl">
<UserProfileSection
profile={userProfile}
onSave={handleSaveProfile}
/>
</div>
</TabsContent>
</Tabs>
</div>
{/* Modals */}
<AddTokenModal
open={showAddTokenModal}
onOpenChange={setShowAddTokenModal}
onAddToken={handleAddToken}
/>
<CreateAlertModal
open={showCreateAlertModal}
onOpenChange={setShowCreateAlertModal}
onCreateAlert={handleCreateAlert}
prefilledToken={alertPrefilledToken}
/>
</div>
);
}

View file

@ -38,6 +38,31 @@ import { removeChatTabAtom, updateChatTabTitleAtom } from "@/atoms/tabs/tabs.ato
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { ThinkingStepsDataUI } from "@/components/assistant-ui/thinking-steps";
import { Thread } from "@/components/assistant-ui/thread";
import { ChatHeader } from "@/components/new-chat/chat-header";
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
import { LinkPreviewToolUI } from "@/components/tool-ui/link-preview";
import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage";
import { RecallMemoryToolUI, SaveMemoryToolUI } from "@/components/tool-ui/user-memory";
// Crypto Tool UI Components - Conversational Crypto Advisor
import {
TokenAnalysisToolUI,
WatchlistDisplayToolUI,
ActionConfirmationToolUI,
AlertConfigurationToolUI,
ProactiveAlertToolUI,
TrendingTokensToolUI,
WhaleActivityToolUI,
MarketOverviewToolUI,
HolderAnalysisToolUI,
PortfolioDisplayToolUI,
UserProfileToolUI,
// Real-time crypto tools (Hybrid approach: RAG + Real-time)
LiveTokenPriceToolUI,
LiveTokenDataToolUI,
} from "@/components/tool-ui/crypto";
import { Spinner } from "@/components/ui/spinner";
import { useChatSessionStateSync } from "@/hooks/use-chat-session-state";
import { useMessagesSync } from "@/hooks/use-messages-sync";
import { documentsApiService } from "@/lib/apis/documents-api.service";
@ -1570,6 +1595,28 @@ export default function NewChatPage() {
return (
<AssistantRuntimeProvider runtime={runtime}>
<ThinkingStepsDataUI />
<GeneratePodcastToolUI />
<LinkPreviewToolUI />
<DisplayImageToolUI />
<ScrapeWebpageToolUI />
<SaveMemoryToolUI />
<RecallMemoryToolUI />
{/* Crypto Tool UI Components - Conversational Crypto Advisor */}
<TokenAnalysisToolUI />
<WatchlistDisplayToolUI />
<ActionConfirmationToolUI />
<AlertConfigurationToolUI />
<ProactiveAlertToolUI />
<TrendingTokensToolUI />
<WhaleActivityToolUI />
<MarketOverviewToolUI />
<HolderAnalysisToolUI />
<PortfolioDisplayToolUI />
<UserProfileToolUI />
{/* Real-time Crypto Tools - Hybrid approach (RAG + Real-time) */}
<LiveTokenPriceToolUI />
<LiveTokenDataToolUI />
{/* <WriteTodosToolUI /> Disabled for now */}
<div key={searchSpaceId} className="flex h-full overflow-hidden">
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
<Thread />