feat: add new widgets for holder analysis, live token data, price, market overview, and trending tokens

- Implemented HolderAnalysisWidget to display holder distribution and concentration risk.
- Created LiveTokenDataWidget for real-time market data including price changes and transaction activity.
- Added LiveTokenPriceWidget to show current token price and changes over various timeframes.
- Developed MarketOverviewWidget to provide a summary of market statistics and token prices.
- Introduced TrendingTokensWidget to showcase trending tokens with price changes and volume.
- Added TradingSuggestionToolUI for AI-powered trading suggestions with detailed entry, targets, and stop-loss information.
- Enhanced settings components for better user configuration options in the SurfSense Browser Extension.
This commit is contained in:
API Test Bot 2026-02-04 13:11:39 +07:00
parent 2bf40ab5ce
commit 8bc092e40e
23 changed files with 2173 additions and 111 deletions

View file

@ -0,0 +1,4 @@
// Hooks for SurfSense Browser Extension
export { useContextAction, getMessageForAction, type ContextAction } from "./useContextAction";
export { useKeyboardShortcuts, getMessageForKeyboardAction, type KeyboardAction } from "./useKeyboardShortcuts";

View file

@ -0,0 +1,104 @@
import { useEffect, useState, useCallback } from "react";
import { Storage } from "@plasmohq/storage";
export interface ContextAction {
action: string;
text: string;
pageUrl?: string;
linkUrl?: string;
timestamp: number;
}
/**
* Hook to handle context menu actions from background script
* Returns pending action and a function to clear it
*/
export function useContextAction() {
const [pendingAction, setPendingAction] = useState<ContextAction | null>(null);
// Check for pending context action on mount and when sidepanel gains focus
const checkPendingAction = useCallback(async () => {
const storage = new Storage({ area: "local" });
const action = await storage.get<ContextAction>("pendingContextAction");
if (action && action.timestamp) {
// Only process actions from last 30 seconds
const isRecent = Date.now() - action.timestamp < 30000;
if (isRecent) {
setPendingAction(action);
// Clear the pending action
await storage.remove("pendingContextAction");
}
}
}, []);
useEffect(() => {
// Check on mount
checkPendingAction();
// Check when window gains focus (sidepanel opened)
const handleFocus = () => {
checkPendingAction();
};
window.addEventListener("focus", handleFocus);
// Also listen for visibility change
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
checkPendingAction();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
window.removeEventListener("focus", handleFocus);
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [checkPendingAction]);
const clearAction = useCallback(() => {
setPendingAction(null);
}, []);
return { pendingAction, clearAction, checkPendingAction };
}
/**
* Generate chat message based on context action
*/
export function getMessageForAction(action: ContextAction): string | null {
const text = action.text;
switch (action.action) {
case "analyze-token":
return `Analyze token: ${text}`;
case "check-safety":
return `Is ${text} safe? Check for rug pull risks.`;
case "add-watchlist":
return `Add ${text} to my watchlist`;
case "copy-address":
// This is handled differently - just copy to clipboard
if (text) {
navigator.clipboard.writeText(text);
}
return null;
case "view-explorer":
// Detect chain and open explorer
if (text.startsWith("0x") && text.length === 42) {
// Ethereum address
window.open(`https://etherscan.io/address/${text}`, "_blank");
} else if (text.length >= 32 && text.length <= 44) {
// Solana address
window.open(`https://solscan.io/account/${text}`, "_blank");
}
return null;
case "capture-page":
return "Capture this page to my knowledge base";
case "ask-ai-page":
return "What is this page about? Summarize the key information.";
default:
return null;
}
}

View file

@ -0,0 +1,92 @@
import { useEffect, useState, useCallback } from "react";
import { Storage } from "@plasmohq/storage";
export interface KeyboardAction {
action: string;
timestamp: number;
}
/**
* Hook to handle keyboard shortcut actions from background script
* Returns pending action and a function to clear it
*
* Keyboard shortcuts defined in manifest:
* - open-sidepanel: Ctrl+Shift+S (just opens panel, no message)
* - analyze-token: Ctrl+Shift+A
* - add-watchlist: Ctrl+Shift+W
* - capture-page: Ctrl+Shift+C
* - show-portfolio: Ctrl+Shift+P
*/
export function useKeyboardShortcuts() {
const [pendingAction, setPendingAction] = useState<KeyboardAction | null>(null);
// Check for pending keyboard action on mount and when sidepanel gains focus
const checkPendingAction = useCallback(async () => {
const storage = new Storage({ area: "local" });
const action = await storage.get<KeyboardAction>("pendingKeyboardAction");
if (action && action.timestamp) {
// Only process actions from last 30 seconds
const isRecent = Date.now() - action.timestamp < 30000;
if (isRecent) {
setPendingAction(action);
// Clear the pending action
await storage.remove("pendingKeyboardAction");
}
}
}, []);
useEffect(() => {
// Check on mount
checkPendingAction();
// Check when window gains focus (sidepanel opened)
const handleFocus = () => {
checkPendingAction();
};
window.addEventListener("focus", handleFocus);
// Also listen for visibility change
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
checkPendingAction();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
window.removeEventListener("focus", handleFocus);
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [checkPendingAction]);
const clearAction = useCallback(() => {
setPendingAction(null);
}, []);
return { pendingAction, clearAction, checkPendingAction };
}
/**
* Generate chat message based on keyboard shortcut action
* Returns null for actions that don't need a chat message (like open-sidepanel)
*/
export function getMessageForKeyboardAction(action: KeyboardAction): string | null {
switch (action.action) {
case "open-sidepanel":
// Just opens the panel, no message needed
return null;
case "analyze-token":
return "Analyze the current token on this page";
case "add-watchlist":
return "Add the current token to my watchlist";
case "capture-page":
return "Capture this page to my knowledge base";
case "show-portfolio":
return "Show my portfolio";
default:
return null;
}
}