mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-15 18:25:18 +02:00
feat(search): add universal token search bar to sidepanel header
- Add search input field to ChatHeader component - Search bar works on any page, not just DexScreener - Supports token symbol, name, or contract address search - Add onTokenSearch callback to handle search queries - Implement token analysis widget display on search - Part of hybrid token detection system (manual + auto-detect) Implements Task 1: Add Universal Token Search Bar
This commit is contained in:
parent
23cc09b1a7
commit
cb879fca37
3 changed files with 399 additions and 69 deletions
245
_bmad-epics/DEMO-EPIC-2.md
Normal file
245
_bmad-epics/DEMO-EPIC-2.md
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
# 🎬 Demo Guide: Epic 2 - Smart Monitoring & Alerts
|
||||
|
||||
**Status:** Frontend UI Complete ✅ | Backend Logic Pending ⏳
|
||||
|
||||
---
|
||||
|
||||
## 🚀 How to Demo
|
||||
|
||||
### Prerequisites
|
||||
1. Extension loaded in Chrome from `build/chrome-mv3-prod/`
|
||||
2. Navigate to any DexScreener page (e.g., https://dexscreener.com/solana/...)
|
||||
3. Open sidepanel (click extension icon or right-click → Open side panel)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Demo Script
|
||||
|
||||
### **Demo 1: Watchlist Management** (Story 2.1)
|
||||
|
||||
#### Step 1: Add Token to Watchlist
|
||||
**User says:** `"Add BULLA to my watchlist"`
|
||||
|
||||
**Expected Response:**
|
||||
```
|
||||
✅ Done! I've added BULLA to your watchlist.
|
||||
|
||||
[Action Confirmation Widget]
|
||||
Token: BULLA
|
||||
Alerts configured:
|
||||
• Price change ±20%
|
||||
• Liquidity drop >10%
|
||||
• Whale movement >$50K
|
||||
```
|
||||
|
||||
**Alternative commands:**
|
||||
- `"add this token to watchlist"`
|
||||
- `"watchlist add BULLA"`
|
||||
- `"track BULLA"`
|
||||
|
||||
---
|
||||
|
||||
#### Step 2: View Watchlist
|
||||
**User says:** `"Show my watchlist"`
|
||||
|
||||
**Expected Response:**
|
||||
```
|
||||
Here's your watchlist:
|
||||
|
||||
[Watchlist Widget]
|
||||
📊 5 tokens tracked
|
||||
• BULLA/SOL - $0.0001 (+156.7%) 🔔 3 alerts
|
||||
• PEPE/ETH - $0.000001 (-5.2%) 🔔 2 alerts
|
||||
• DOGE/SOL - $0.0002 (+12.3%) 🔕 No alerts
|
||||
...
|
||||
|
||||
BULLA is up 156.7% - your best performer!
|
||||
Want me to analyze if it's time to take profits?
|
||||
```
|
||||
|
||||
**Alternative commands:**
|
||||
- `"view watchlist"`
|
||||
- `"show my tokens"`
|
||||
- `"what's in my watchlist"`
|
||||
|
||||
---
|
||||
|
||||
#### Step 3: Set Price Alert
|
||||
**User says:** `"Set alert if BULLA drops 20%"`
|
||||
|
||||
**Expected Response:**
|
||||
```
|
||||
I'll set that up for you:
|
||||
|
||||
[Alert Config Widget]
|
||||
Token: BULLA
|
||||
Condition: Price drops 20%
|
||||
Current: $0.00001234
|
||||
Trigger: $0.00000987
|
||||
Channels: Browser ✅ | In-App ✅ | Email ❌
|
||||
|
||||
Done! I'll notify you if BULLA drops 20% from current price.
|
||||
Want to set any other alerts?
|
||||
```
|
||||
|
||||
**Alternative commands:**
|
||||
- `"notify me when BULLA pumps 50%"`
|
||||
- `"alert if price reaches $0.0002"`
|
||||
- `"set alert for 30% change"`
|
||||
|
||||
---
|
||||
|
||||
### **Demo 2: Whale Activity Tracking** (Story 2.2)
|
||||
|
||||
#### Step 1: View Whale Activity
|
||||
**User says:** `"Show whale activity"`
|
||||
|
||||
**Expected Response:**
|
||||
```
|
||||
Here's recent whale activity for BULLA:
|
||||
|
||||
[Whale Activity Widget]
|
||||
🐋 Recent Large Transactions
|
||||
|
||||
2 min ago
|
||||
🟢 BUY $100K BULLA/SOL
|
||||
Wallet: 0x742d...35BA ⭐ Smart Money
|
||||
Amount: 1B tokens
|
||||
[Track Wallet] [View on Explorer]
|
||||
|
||||
5 min ago
|
||||
🔴 SELL $50K BULLA/SOL
|
||||
Wallet: 0x1234...5678
|
||||
Amount: 500M tokens
|
||||
[Track Wallet] [View on Explorer]
|
||||
|
||||
🐋 I'm tracking 5 large transactions (>$10K) in the last hour.
|
||||
The smart money wallet 0x742d...35BA just bought $50K worth -
|
||||
this could be a bullish signal!
|
||||
```
|
||||
|
||||
**Alternative commands:**
|
||||
- `"show large transactions"`
|
||||
- `"whale trades"`
|
||||
- `"big buys and sells"`
|
||||
|
||||
---
|
||||
|
||||
### **Demo 3: Safety Analysis** (Story 2.3)
|
||||
|
||||
#### Step 1: Check Token Safety
|
||||
**User says:** `"Is BULLA safe?"`
|
||||
|
||||
**Expected Response:**
|
||||
```
|
||||
Here's my analysis of BULLA:
|
||||
|
||||
[Token Analysis Widget]
|
||||
Safety Score: 72/100 (Medium Risk)
|
||||
|
||||
Contract Analysis:
|
||||
✅ Contract verified
|
||||
✅ Ownership renounced
|
||||
⚠️ Top 10 holders: 35%
|
||||
|
||||
Liquidity Analysis:
|
||||
✅ LP locked for 90 days
|
||||
✅ Liquidity: $450K
|
||||
✅ Volume/Liquidity: 2.67x
|
||||
|
||||
Risk Factors:
|
||||
🟡 Moderate holder concentration
|
||||
🟢 Strong liquidity
|
||||
🟢 Verified contract
|
||||
|
||||
Based on your moderate risk profile, suggested allocation: 2-5% of portfolio.
|
||||
The safety score of 72/100 indicates medium risk - proceed with caution.
|
||||
```
|
||||
|
||||
**Alternative commands:**
|
||||
- `"analyze BULLA"`
|
||||
- `"research this token"`
|
||||
- `"is this a rug pull"`
|
||||
- `"check safety"`
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI Components Showcase
|
||||
|
||||
### 1. **Watchlist Panel** (`WatchlistPanel.tsx`)
|
||||
- Token list with prices and 24h changes
|
||||
- Alert count badges
|
||||
- Quick actions: [Edit] [Remove] [Add Alert]
|
||||
- Empty state with suggestions
|
||||
|
||||
### 2. **Alert Config Modal** (`AlertConfigModal.tsx`)
|
||||
- Alert type selection (Price Above/Below, % Change, Volume Spike)
|
||||
- Threshold input
|
||||
- Notification channels (Browser, In-App, Email)
|
||||
- Sound toggle
|
||||
- Preview of trigger conditions
|
||||
|
||||
### 3. **Whale Activity Feed** (`WhaleActivityFeed.tsx`)
|
||||
- Real-time transaction feed
|
||||
- Buy/Sell indicators (🟢/🔴)
|
||||
- Smart money badges (⭐)
|
||||
- Wallet tracking buttons
|
||||
- Explorer links
|
||||
|
||||
### 4. **Safety Score Display** (`SafetyScoreDisplay.tsx`)
|
||||
- Overall safety score (0-100)
|
||||
- Risk level badge (Low/Medium/High)
|
||||
- Detailed risk factors
|
||||
- Recommendations
|
||||
- Expandable explanations
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Features to Highlight
|
||||
|
||||
### ✅ **Implemented (Frontend)**
|
||||
1. **Conversational UX** - Natural language commands
|
||||
2. **Inline Widgets** - Rich UI embedded in chat
|
||||
3. **Context Awareness** - Detects current token from DexScreener
|
||||
4. **Mock Data** - Realistic demo data for all features
|
||||
5. **Responsive Design** - Works in narrow sidepanel
|
||||
|
||||
### ⏳ **Not Yet Implemented (Backend)**
|
||||
1. **Real-time Monitoring** - Background service worker
|
||||
2. **Browser Notifications** - Chrome notifications API
|
||||
3. **Persistent Storage** - Watchlist/alerts saved
|
||||
4. **API Integration** - Helius, Alchemy, RugCheck APIs
|
||||
5. **Sound Alerts** - Audio notifications
|
||||
6. **Alert History** - Past alerts tracking
|
||||
|
||||
---
|
||||
|
||||
## 📝 Demo Tips
|
||||
|
||||
1. **Start with context** - Navigate to DexScreener first
|
||||
2. **Use natural language** - Show conversational UX
|
||||
3. **Highlight widgets** - Point out inline UI components
|
||||
4. **Show multiple commands** - Demonstrate variety
|
||||
5. **Explain limitations** - Frontend only, no real monitoring yet
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Known Limitations
|
||||
|
||||
- ⚠️ No actual price monitoring (mock data only)
|
||||
- ⚠️ Alerts don't trigger (no background worker)
|
||||
- ⚠️ Watchlist not persisted (resets on reload)
|
||||
- ⚠️ No real blockchain data (using mocks)
|
||||
- ⚠️ No browser notifications
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps to Complete Epic 2
|
||||
|
||||
See main Epic 2 file for full implementation plan:
|
||||
- Background service worker
|
||||
- Chrome alarms & notifications
|
||||
- API integrations (Helius, Alchemy, RugCheck)
|
||||
- Persistent storage
|
||||
- Real-time monitoring logic
|
||||
|
||||
|
|
@ -8,7 +8,9 @@ import {
|
|||
Star,
|
||||
Bell,
|
||||
MessageSquare,
|
||||
Plug
|
||||
Plug,
|
||||
Search,
|
||||
X
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/routes/ui/button";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
|
@ -39,12 +41,15 @@ export interface ChatHeaderProps {
|
|||
onLogout?: () => void;
|
||||
/** Callback when settings item is clicked */
|
||||
onSettingsClick?: (item: string) => void;
|
||||
/** Callback when token search is triggered */
|
||||
onTokenSearch?: (query: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced Chat header with branding, space selector, settings, and user menu
|
||||
* Enhanced Chat header with branding, token search, space selector, settings, and user menu
|
||||
*
|
||||
* Features:
|
||||
* - Universal token search bar (works on any page)
|
||||
* - Search space selector dropdown
|
||||
* - Settings dropdown with full menu
|
||||
* - User avatar with logout option
|
||||
|
|
@ -57,9 +62,11 @@ export function ChatHeader({
|
|||
userAvatar,
|
||||
onLogout,
|
||||
onSettingsClick,
|
||||
onTokenSearch,
|
||||
}: ChatHeaderProps) {
|
||||
const [spaceOpen, setSpaceOpen] = useState(false);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const defaultSpaces: SearchSpace[] = [
|
||||
{ id: "crypto", name: "Crypto", icon: "🪙" },
|
||||
|
|
@ -70,81 +77,118 @@ export function ChatHeader({
|
|||
const spaces = searchSpaces.length > 0 ? searchSpaces : defaultSpaces;
|
||||
const currentSpace = selectedSpace || spaces[0];
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (searchQuery.trim() && onTokenSearch) {
|
||||
onTokenSearch(searchQuery.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearSearch = () => {
|
||||
setSearchQuery("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between p-3 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
{/* Logo and brand */}
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src="/assets/icon.png"
|
||||
alt="SurfSense"
|
||||
className="w-6 h-6"
|
||||
/>
|
||||
<h1 className="font-semibold text-base">SurfSense</h1>
|
||||
</div>
|
||||
<div className="flex flex-col border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
{/* Top row: Logo, Space Selector, Settings */}
|
||||
<div className="flex items-center justify-between p-3 pb-2">
|
||||
{/* Logo and brand */}
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src="/assets/icon.png"
|
||||
alt="SurfSense"
|
||||
className="w-6 h-6"
|
||||
/>
|
||||
<h1 className="font-semibold text-base">SurfSense</h1>
|
||||
</div>
|
||||
|
||||
{/* Search Space Selector */}
|
||||
<Popover open={spaceOpen} onOpenChange={setSpaceOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 gap-1 px-2"
|
||||
>
|
||||
<span>{currentSpace.icon}</span>
|
||||
<span className="max-w-[80px] truncate">{currentSpace.name}</span>
|
||||
<ChevronDown className="h-3 w-3 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-48 p-1" align="center">
|
||||
<div className="space-y-0.5">
|
||||
{spaces.map((space) => (
|
||||
<button
|
||||
key={space.id}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors",
|
||||
currentSpace.id === space.id
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
)}
|
||||
onClick={() => {
|
||||
onSpaceChange?.(space);
|
||||
setSpaceOpen(false);
|
||||
}}
|
||||
>
|
||||
<span>{space.icon}</span>
|
||||
<span>{space.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Settings Dropdown */}
|
||||
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
|
||||
{/* Search Space Selector */}
|
||||
<Popover open={spaceOpen} onOpenChange={setSpaceOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
<Settings className="h-4 w-4" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 gap-1 px-2"
|
||||
>
|
||||
<span>{currentSpace.icon}</span>
|
||||
<span className="max-w-[80px] truncate">{currentSpace.name}</span>
|
||||
<ChevronDown className="h-3 w-3 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-56 p-1" align="end">
|
||||
<SettingsMenu
|
||||
onItemClick={(item) => {
|
||||
onSettingsClick?.(item);
|
||||
setSettingsOpen(false);
|
||||
}}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
<PopoverContent className="w-48 p-1" align="center">
|
||||
<div className="space-y-0.5">
|
||||
{spaces.map((space) => (
|
||||
<button
|
||||
key={space.id}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors",
|
||||
currentSpace.id === space.id
|
||||
? "bg-primary/10 text-primary"
|
||||
: "hover:bg-muted"
|
||||
)}
|
||||
onClick={() => {
|
||||
onSpaceChange?.(space);
|
||||
setSpaceOpen(false);
|
||||
}}
|
||||
>
|
||||
<span>{space.icon}</span>
|
||||
<span>{space.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* User Avatar */}
|
||||
<UserAvatar
|
||||
name={userName}
|
||||
avatarUrl={userAvatar}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Settings Dropdown */}
|
||||
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-56 p-1" align="end">
|
||||
<SettingsMenu
|
||||
onItemClick={(item) => {
|
||||
onSettingsClick?.(item);
|
||||
setSettingsOpen(false);
|
||||
}}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* User Avatar */}
|
||||
<UserAvatar
|
||||
name={userName}
|
||||
avatarUrl={userAvatar}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom row: Token Search Bar */}
|
||||
<div className="px-3 pb-2">
|
||||
<form onSubmit={handleSearch} className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search token (symbol, name, or address)..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full h-8 pl-9 pr-8 text-sm rounded-md border border-input bg-background/50 focus:bg-background focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-all"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClearSearch}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 h-5 w-5 flex items-center justify-center rounded-sm hover:bg-muted transition-colors"
|
||||
>
|
||||
<X className="h-3 w-3 text-muted-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -431,6 +431,46 @@ What would you like to know?`;
|
|||
? ["Add to watchlist", "Is this safe?", "Set price alert"]
|
||||
: ["Show my watchlist", "What's trending?", "Analyze BULLA"];
|
||||
|
||||
// Handle token search from header
|
||||
const handleTokenSearch = async (query: string) => {
|
||||
// Add user message
|
||||
const userMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
role: "user",
|
||||
content: `Analyze ${query}`,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
setMessages((prev) => [...prev, userMessage]);
|
||||
|
||||
// Simulate AI response with token analysis
|
||||
setTimeout(() => {
|
||||
const aiMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: "assistant",
|
||||
content: `Searching for token: ${query}...`,
|
||||
timestamp: new Date(),
|
||||
widget: {
|
||||
type: "token_analysis",
|
||||
data: {
|
||||
symbol: query.toUpperCase(),
|
||||
name: `${query} Token`,
|
||||
chain: "solana",
|
||||
price: "$0.00001234",
|
||||
priceChange24h: 156.7,
|
||||
marketCap: "$2.1M",
|
||||
volume24h: "$1.2M",
|
||||
liquidity: "$450K",
|
||||
safetyScore: MOCK_SAFETY_SCORE,
|
||||
holderCount: 12456,
|
||||
top10HolderPercent: 35,
|
||||
},
|
||||
isInWatchlist: false,
|
||||
},
|
||||
};
|
||||
setMessages((prev) => [...prev, aiMessage]);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header with space selector and settings */}
|
||||
|
|
@ -441,6 +481,7 @@ What would you like to know?`;
|
|||
userName={userName}
|
||||
onSettingsClick={handleSettingsClick}
|
||||
onLogout={handleLogout}
|
||||
onTokenSearch={handleTokenSearch}
|
||||
/>
|
||||
|
||||
{/* Token info card (only on DexScreener) */}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue