import { useState } from "react"; import { usePageContext } from "../context/PageContextProvider"; import { TokenInfoCard } from "../dexscreener/TokenInfoCard"; import { QuickCapture } from "./QuickCapture"; import { ChatHeader, type SearchSpace } from "./ChatHeader"; import { ChatMessages, type Message, type MessageWidget } from "./ChatMessages"; import { ChatInput, type AttachedFile } from "./ChatInput"; import { ThinkingStepsDisplay, type ThinkingStep } from "./ThinkingStepsDisplay"; import { MOCK_MODE, MOCK_SEARCH_SPACES, MOCK_WATCHLIST_TOKENS, MOCK_WATCHLIST_ALERTS, MOCK_SAFETY_SCORE, MOCK_SAFETY_FACTORS, MOCK_SAFETY_SOURCES, } from "../mock/mockData"; import { SafetyScoreDisplay } from "../crypto/SafetyScoreDisplay"; import { WatchlistPanel } from "../crypto/WatchlistPanel"; import { AlertConfigModal } from "../crypto/AlertConfigModal"; import type { WatchlistItem } from "../widgets"; type ViewMode = "chat" | "watchlist" | "safety"; /** * Natural language command patterns for conversational UX */ const COMMAND_PATTERNS = { ADD_WATCHLIST: /add\s+(\w+)\s+to\s+(my\s+)?watchlist/i, REMOVE_WATCHLIST: /remove\s+(\w+)\s+from\s+(my\s+)?watchlist/i, SHOW_WATCHLIST: /(show|display|view)\s+(my\s+)?watchlist/i, SET_ALERT: /set\s+alert\s+(if|when)\s+(\w+)\s+(drops?|pumps?|reaches?|changes?)\s+(\d+)%?/i, ANALYZE_TOKEN: /(analyze|research|check)\s+(\w+)/i, SAFETY_CHECK: /(is\s+)?(\w+)\s+(safe|risky|rug)/i, }; /** * Main chat interface for side panel * Adapts UI based on page context (e.g., shows token card on DexScreener) * * Features: * - Context-aware UI (DexScreener token detection) * - Welcome screen for new users * - Thinking steps visualization * - File attachments support * - Search space selection * - Watchlist panel * - Safety analysis view */ export function ChatInterface() { const { context, isMockMode } = usePageContext(); const [messages, setMessages] = useState([]); const [isStreaming, setIsStreaming] = useState(false); const [thinkingSteps, setThinkingSteps] = useState([]); const [selectedSpace, setSelectedSpace] = useState( MOCK_SEARCH_SPACES[0] ); const [viewMode, setViewMode] = useState("chat"); const [showAlertModal, setShowAlertModal] = useState(false); const [selectedTokenForAlert, setSelectedTokenForAlert] = useState(null); const [watchlistTokens, setWatchlistTokens] = useState(MOCK_WATCHLIST_TOKENS); const [isInWatchlist, setIsInWatchlist] = useState(false); // Mock user data - in production, this would come from auth context const userName = "Crypto Trader"; const handleSendMessage = async (content: string, attachments?: AttachedFile[]) => { console.log("Sending message:", content, attachments); setIsStreaming(true); setViewMode("chat"); // Add user message const userMessage: Message = { id: `msg-${Date.now()}`, role: "user", content, timestamp: new Date(), }; setMessages((prev) => [...prev, userMessage]); // Simulate thinking steps setThinkingSteps([ { id: "1", type: "thinking", title: "Understanding your question...", isActive: true }, ]); setTimeout(() => { setThinkingSteps([ { id: "1", type: "thinking", title: "Understanding your question...", isComplete: true }, { id: "2", type: "searching", title: "Searching knowledge base...", isActive: true }, ]); }, 500); setTimeout(() => { setThinkingSteps([ { id: "1", type: "thinking", title: "Understanding your question...", isComplete: true }, { id: "2", type: "searching", title: "Searching knowledge base...", isComplete: true }, { id: "3", type: "analyzing", title: "Analyzing results...", isActive: true }, ]); }, 1000); // Generate response based on content - with embedded widgets setTimeout(() => { setThinkingSteps([]); let responseContent = ""; let widget: MessageWidget | undefined; const tokenSymbol = context?.tokenData?.tokenSymbol || "BULLA"; // Check for natural language commands const addWatchlistMatch = content.match(COMMAND_PATTERNS.ADD_WATCHLIST); const showWatchlistMatch = content.match(COMMAND_PATTERNS.SHOW_WATCHLIST); const setAlertMatch = content.match(COMMAND_PATTERNS.SET_ALERT); if (addWatchlistMatch || content.toLowerCase().includes("add") && content.toLowerCase().includes("watchlist")) { // Add to watchlist command const token = addWatchlistMatch?.[1] || tokenSymbol; responseContent = `Done! ✅\n\nI've added ${token} to your watchlist.`; widget = { type: "action_confirmation", actionType: "watchlist_add", tokenSymbol: token, details: [ "Price change ±20%", "Liquidity drop >10%", "Whale movement >$50K", ], }; // Actually add to watchlist if (!watchlistTokens.find(t => t.symbol === token)) { const newToken = { id: `token-${Date.now()}`, symbol: token, name: token + " Token", chain: context?.tokenData?.chain || "solana", contractAddress: context?.tokenData?.pairAddress || "unknown", price: context?.tokenData?.price || "$0.00001234", priceChange24h: 156.7, hasAlerts: true, alertCount: 3, }; setWatchlistTokens(prev => [...prev, newToken]); setIsInWatchlist(true); } } else if (showWatchlistMatch || content.toLowerCase().includes("watchlist") && (content.toLowerCase().includes("show") || content.toLowerCase().includes("view"))) { // Show watchlist command responseContent = `Here's your watchlist:`; const watchlistItems: WatchlistItem[] = watchlistTokens.map(t => ({ id: t.id, symbol: t.symbol, name: t.name, chain: t.chain, price: t.price, priceChange24h: t.priceChange24h, alertCount: t.alertCount, })); widget = { type: "watchlist", tokens: watchlistItems, }; if (watchlistTokens.length > 0) { const bestPerformer = watchlistTokens.reduce((a, b) => a.priceChange24h > b.priceChange24h ? a : b ); responseContent += `\n\n${bestPerformer.symbol} is up ${bestPerformer.priceChange24h.toFixed(1)}% - your best performer! Want me to analyze if it's time to take profits?`; } } else if (setAlertMatch || content.toLowerCase().includes("alert") && (content.toLowerCase().includes("set") || content.toLowerCase().includes("notify"))) { // Set alert command const match = content.match(/(\d+)%/); const percentage = match ? match[1] : "20"; const direction = content.toLowerCase().includes("drop") ? "drops" : "changes"; responseContent = `I'll set that up for you:`; widget = { type: "alert_config", config: { tokenSymbol: tokenSymbol, condition: `Price ${direction} ${percentage}%`, currentPrice: context?.tokenData?.price || "$0.00001234", triggerPrice: "$0.00000987", channels: { browser: true, inApp: true, email: false, }, }, isNew: true, }; responseContent += `\n\nDone! I'll notify you if ${tokenSymbol} ${direction} ${percentage}% from current price. Want to set any other alerts?`; } else if (content.toLowerCase().includes("safe") || content.toLowerCase().includes("rug") || content.toLowerCase().includes("analyze") || content.toLowerCase().includes("research")) { // Token analysis with embedded widget responseContent = `Here's my analysis of ${tokenSymbol}:`; widget = { type: "token_analysis", data: { symbol: tokenSymbol, name: context?.tokenData?.tokenName || "Bulla Token", chain: context?.tokenData?.chain || "solana", price: context?.tokenData?.price || "$0.00001234", priceChange24h: 156.7, marketCap: "$2.1M", volume24h: "$1.2M", liquidity: "$450K", safetyScore: MOCK_SAFETY_SCORE, holderCount: 12456, top10HolderPercent: 35, }, isInWatchlist: isInWatchlist, }; responseContent += `\n\nBased on your moderate risk profile, suggested allocation: 2-5% of portfolio. The safety score of ${MOCK_SAFETY_SCORE}/100 indicates medium risk - proceed with caution.`; } else if (content.toLowerCase().includes("holder")) { responseContent = `**Holder Analysis for ${tokenSymbol}:** 📊 **Distribution:** - Total Holders: 12,456 - Top 10 Holders: 35% of supply - Top 50 Holders: 52% of supply 🐋 **Whale Activity (24h):** - 3 large buys (>$10K each) - 1 large sell ($25K) - Net flow: +$15K ⚠️ **Concentration Risk:** Medium The top holder owns 8.5% which is relatively high.`; } else { responseContent = `I can help you with crypto analysis! Try these commands: • **"Add BULLA to my watchlist"** - Track tokens • **"Show my watchlist"** - View tracked tokens • **"Set alert if BULLA drops 20%"** - Price alerts • **"Analyze BULLA"** - Full token analysis • **"Is BULLA safe?"** - Safety check What would you like to know?`; } const assistantMessage: Message = { id: `msg-${Date.now()}`, role: "assistant", content: responseContent, timestamp: new Date(), widget, }; setMessages((prev) => [...prev, assistantMessage]); setIsStreaming(false); }, 1500); }; const handleSuggestionClick = (text: string) => { handleSendMessage(text); }; const handleSpaceChange = (space: SearchSpace) => { setSelectedSpace(space); }; const handleSettingsClick = (item: string) => { console.log("Settings item clicked:", item); if (item === "watchlist") { setViewMode("watchlist"); } }; const handleLogout = () => { console.log("Logout clicked"); }; const handleSafetyCheck = () => { setViewMode("safety"); }; const handleAddToWatchlist = () => { setIsInWatchlist(!isInWatchlist); if (!isInWatchlist && context?.tokenData) { const newToken = { id: `token-${Date.now()}`, symbol: context.tokenData.tokenSymbol || "TOKEN", name: context.tokenData.tokenName || "Unknown Token", chain: context.tokenData.chain, contractAddress: context.tokenData.pairAddress, price: context.tokenData.price || "$0", priceChange24h: context.tokenData.priceChange24h || 0, hasAlerts: false, }; setWatchlistTokens(prev => [...prev, newToken]); } }; const handleConfigureAlerts = (tokenSymbol: string) => { setSelectedTokenForAlert(tokenSymbol); setShowAlertModal(true); }; const handleRugCheck = () => { handleSendMessage("Check this token for rug pull risks"); }; // Handle widget actions from embedded widgets in chat const handleWidgetAction = (action: string, data?: unknown) => { console.log("Widget action:", action, data); switch (action) { case "view_watchlist": handleSendMessage("Show my watchlist"); break; case "edit_alerts": if (typeof data === "string") { handleConfigureAlerts(data); } break; case "analyze_token": if (data && typeof data === "object" && "symbol" in data) { handleSendMessage(`Analyze ${(data as { symbol: string }).symbol}`); } break; case "remove_from_watchlist": if (typeof data === "string") { setWatchlistTokens(prev => prev.filter(t => t.id !== data)); } break; case "add_to_watchlist": if (data && typeof data === "object" && "symbol" in data) { handleSendMessage(`Add ${(data as { symbol: string }).symbol} to my watchlist`); } break; case "set_alert": if (typeof data === "string") { handleConfigureAlerts(data); } break; case "analyze_further": if (data && typeof data === "object" && "symbol" in data) { handleSendMessage(`Tell me more about ${(data as { symbol: string }).symbol} holders and whale activity`); } break; case "tell_more": if (data && typeof data === "object" && "tokenSymbol" in data) { handleSendMessage(`Tell me more about ${(data as { tokenSymbol: string }).tokenSymbol}`); } break; default: console.log("Unhandled widget action:", action); } }; // Quick suggestions based on context const quickSuggestions = context?.pageType === "dexscreener" ? ["Add to watchlist", "Is this safe?", "Set price alert"] : ["Show my watchlist", "What's trending?", "Analyze BULLA"]; return (
{/* Header with space selector and settings */} {/* Token info card (only on DexScreener) */} {context?.pageType === "dexscreener" && context.tokenData && viewMode === "chat" && ( )} {/* Main content area */}
{viewMode === "chat" && ( <> {/* Thinking steps (shown during streaming) */} {isStreaming && thinkingSteps.length > 0 && (
)} )} {viewMode === "watchlist" && ( console.log("Token clicked:", token)} onRemoveToken={(id) => setWatchlistTokens(prev => prev.filter(t => t.id !== id))} onAddToken={() => console.log("Add token clicked")} onConfigureAlerts={(token) => handleConfigureAlerts(token.symbol)} onAlertClick={(alert) => console.log("Alert clicked:", alert)} /> )} {viewMode === "safety" && (
handleConfigureAlerts(context?.tokenData?.tokenSymbol || "TOKEN")} />
)}
{/* Chat input (only in chat mode) */} {viewMode === "chat" && ( )} {/* Back to chat button for other views */} {viewMode !== "chat" && (
)} {/* Quick capture button */} {/* Alert configuration modal */} { console.log("Alerts saved:", alerts); setShowAlertModal(false); }} />
); }