feat: modernize analysis page layout and coin selection
- Remove hero section from analysis page for cleaner interface - Relocate timeframe selection to Quick Analysis section for better UX flow - Remove outdated Quick Actions timeframe buttons section - Reduce coin selection from 8 to 4 most popular coins (BTC, ETH, SOL, SUI) - Replace Unicode coin symbols with professional CoinGecko icon images - Convert coin layout from 2x2 grid to full-width horizontal row - Improve layout selection with descriptive AI/DIY indicators - Add sessionId support for enhanced progress tracking - Clean up unused quickTimeframeTest and testAllTimeframes functions
This commit is contained in:
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"chat.agent.maxRequests": 5000,
|
||||||
|
"github.copilot.chat.agent.autoFix": true
|
||||||
|
}
|
||||||
@@ -5,16 +5,6 @@ import AIAnalysisPanel from '../../components/AIAnalysisPanel'
|
|||||||
export default function AnalysisPage() {
|
export default function AnalysisPage() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="text-center mb-8">
|
|
||||||
<h1 className="text-3xl font-bold text-white mb-4">
|
|
||||||
🤖 AI-Powered Market Analysis
|
|
||||||
</h1>
|
|
||||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
|
||||||
Get professional trading insights with multi-timeframe analysis, precise entry/exit levels,
|
|
||||||
and institutional-quality recommendations powered by OpenAI.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AIAnalysisPanel />
|
<AIAnalysisPanel />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,14 +18,10 @@ const timeframes = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const popularCoins = [
|
const popularCoins = [
|
||||||
{ name: 'Bitcoin', symbol: 'BTCUSD', icon: '₿', color: 'from-orange-400 to-orange-600' },
|
{ name: 'Bitcoin', symbol: 'BTCUSD', icon: 'https://assets.coingecko.com/coins/images/1/small/bitcoin.png', color: 'from-orange-400 to-orange-600' },
|
||||||
{ name: 'Ethereum', symbol: 'ETHUSD', icon: 'Ξ', color: 'from-blue-400 to-blue-600' },
|
{ name: 'Ethereum', symbol: 'ETHUSD', icon: 'https://assets.coingecko.com/coins/images/279/small/ethereum.png', color: 'from-blue-400 to-blue-600' },
|
||||||
{ name: 'Solana', symbol: 'SOLUSD', icon: '◎', color: 'from-purple-400 to-purple-600' },
|
{ name: 'Solana', symbol: 'SOLUSD', icon: 'https://assets.coingecko.com/coins/images/4128/small/solana.png', color: 'from-purple-400 to-purple-600' },
|
||||||
{ name: 'Sui', symbol: 'SUIUSD', icon: '🔷', color: 'from-cyan-400 to-cyan-600' },
|
{ name: 'Sui', symbol: 'SUIUSD', icon: 'https://assets.coingecko.com/coins/images/26375/small/sui_asset.jpeg', color: 'from-cyan-400 to-cyan-600' },
|
||||||
{ name: 'Avalanche', symbol: 'AVAXUSD', icon: '🔺', color: 'from-red-400 to-red-600' },
|
|
||||||
{ name: 'Cardano', symbol: 'ADAUSD', icon: '♠', color: 'from-indigo-400 to-indigo-600' },
|
|
||||||
{ name: 'Polygon', symbol: 'MATICUSD', icon: '🔷', color: 'from-violet-400 to-violet-600' },
|
|
||||||
{ name: 'Chainlink', symbol: 'LINKUSD', icon: '🔗', color: 'from-blue-400 to-blue-600' },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Progress tracking interfaces
|
// Progress tracking interfaces
|
||||||
@@ -40,6 +36,7 @@ interface ProgressStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AnalysisProgress {
|
interface AnalysisProgress {
|
||||||
|
sessionId?: string
|
||||||
currentStep: number
|
currentStep: number
|
||||||
totalSteps: number
|
totalSteps: number
|
||||||
steps: ProgressStep[]
|
steps: ProgressStep[]
|
||||||
@@ -328,30 +325,6 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const quickTimeframeTest = async (testTimeframe: string) => {
|
|
||||||
// Toggle the timeframe in selection instead of replacing
|
|
||||||
const newTimeframes = selectedTimeframes.includes(testTimeframe)
|
|
||||||
? selectedTimeframes.filter(tf => tf !== testTimeframe)
|
|
||||||
: [...selectedTimeframes, testTimeframe]
|
|
||||||
|
|
||||||
setSelectedTimeframes(newTimeframes)
|
|
||||||
|
|
||||||
if (!loading && symbol && newTimeframes.length > 0) {
|
|
||||||
await performAnalysis(symbol, newTimeframes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const testAllTimeframes = async () => {
|
|
||||||
if (loading) return
|
|
||||||
|
|
||||||
const allTimeframeValues = timeframes.map(tf => tf.value)
|
|
||||||
setSelectedTimeframes(allTimeframeValues)
|
|
||||||
|
|
||||||
if (!loading && symbol) {
|
|
||||||
await performAnalysis(symbol, allTimeframeValues)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleAnalyze() {
|
async function handleAnalyze() {
|
||||||
await performAnalysis()
|
await performAnalysis()
|
||||||
}
|
}
|
||||||
@@ -572,56 +545,8 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Coin Selection */}
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
||||||
{popularCoins.map(coin => (
|
|
||||||
<button
|
|
||||||
key={coin.symbol}
|
|
||||||
onClick={() => quickAnalyze(coin.symbol)}
|
|
||||||
disabled={loading || selectedLayouts.length === 0}
|
|
||||||
className={`group relative p-3 rounded-lg border transition-all ${
|
|
||||||
loading || selectedLayouts.length === 0
|
|
||||||
? 'border-gray-700 bg-gray-800/30 cursor-not-allowed opacity-50'
|
|
||||||
: symbol === coin.symbol
|
|
||||||
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300'
|
|
||||||
: 'border-gray-700 bg-gray-800/30 text-gray-300 hover:border-gray-600 hover:bg-gray-800/50 hover:text-white transform hover:scale-105'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className={`w-8 h-8 bg-gradient-to-br ${coin.color} rounded-lg flex items-center justify-center mx-auto mb-2`}>
|
|
||||||
<span className="text-white font-bold text-sm">{coin.icon}</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs font-medium">{coin.name}</div>
|
|
||||||
<div className="text-xs text-gray-500">{coin.symbol}</div>
|
|
||||||
{symbol === coin.symbol && (
|
|
||||||
<div className="absolute top-1 right-1 w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Advanced Analysis Section */}
|
|
||||||
<div className="mb-8">
|
|
||||||
<h3 className="text-sm font-semibold text-gray-300 flex items-center mb-4">
|
|
||||||
<span className="w-4 h-4 bg-purple-500 rounded-full mr-2"></span>
|
|
||||||
Advanced Analysis
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{/* Symbol Input */}
|
|
||||||
<div className="grid grid-cols-1 gap-4 mb-6">
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-medium text-gray-400 mb-2">Trading Pair</label>
|
|
||||||
<input
|
|
||||||
className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 transition-all"
|
|
||||||
value={symbol}
|
|
||||||
onChange={e => setSymbol(e.target.value.toUpperCase())}
|
|
||||||
placeholder="e.g., BTCUSD, ETHUSD"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Timeframe Selection */}
|
{/* Timeframe Selection */}
|
||||||
<div className="mb-6">
|
<div className="mb-4">
|
||||||
<label className="block text-xs font-medium text-gray-400 mb-3">
|
<label className="block text-xs font-medium text-gray-400 mb-3">
|
||||||
Analysis Timeframes
|
Analysis Timeframes
|
||||||
<span className="text-xs text-cyan-400 ml-2">({selectedTimeframes.length} selected)</span>
|
<span className="text-xs text-cyan-400 ml-2">({selectedTimeframes.length} selected)</span>
|
||||||
@@ -662,38 +587,127 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Coin Selection */}
|
||||||
|
<div className="flex gap-3">
|
||||||
|
{popularCoins.map(coin => (
|
||||||
|
<button
|
||||||
|
key={coin.symbol}
|
||||||
|
onClick={() => quickAnalyze(coin.symbol)}
|
||||||
|
disabled={loading || selectedLayouts.length === 0}
|
||||||
|
className={`group relative flex-1 flex items-center justify-center gap-2 py-3 px-4 rounded-lg border transition-all ${
|
||||||
|
loading || selectedLayouts.length === 0
|
||||||
|
? 'border-gray-700 bg-gray-800/30 cursor-not-allowed opacity-50'
|
||||||
|
: symbol === coin.symbol
|
||||||
|
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300'
|
||||||
|
: 'border-gray-700 bg-gray-800/30 text-gray-300 hover:border-gray-600 hover:bg-gray-800/50 hover:text-white transform hover:scale-105'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={`w-6 h-6 bg-gradient-to-br ${coin.color} rounded flex items-center justify-center p-0.5`}>
|
||||||
|
<img
|
||||||
|
src={coin.icon}
|
||||||
|
alt={coin.name}
|
||||||
|
className="w-5 h-5 rounded-sm"
|
||||||
|
onError={(e) => {
|
||||||
|
// Fallback to text if image fails to load
|
||||||
|
const target = e.currentTarget as HTMLImageElement;
|
||||||
|
target.style.display = 'none';
|
||||||
|
const fallback = target.nextElementSibling as HTMLElement;
|
||||||
|
if (fallback) fallback.style.display = 'block';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-white font-bold text-xs hidden">{coin.name.slice(0,3).toUpperCase()}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium">{coin.name}</span>
|
||||||
|
{symbol === coin.symbol && (
|
||||||
|
<div className="absolute top-1 right-1 w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Advanced Analysis Section */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-300 flex items-center mb-4">
|
||||||
|
<span className="w-4 h-4 bg-purple-500 rounded-full mr-2"></span>
|
||||||
|
Advanced Analysis
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Symbol Input */}
|
||||||
|
<div className="grid grid-cols-1 gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-gray-400 mb-2">Trading Pair</label>
|
||||||
|
<input
|
||||||
|
className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 transition-all"
|
||||||
|
value={symbol}
|
||||||
|
onChange={e => setSymbol(e.target.value.toUpperCase())}
|
||||||
|
placeholder="e.g., BTCUSD, ETHUSD"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Layout Selection */}
|
{/* Layout Selection */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-xs font-medium text-gray-400 mb-3">Analysis Layouts</label>
|
<label className="block text-xs font-medium text-gray-400 mb-3">Analysis Layouts</label>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
{layouts.map(layout => (
|
<label className="group relative">
|
||||||
<label key={layout} className="group relative">
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
checked={selectedLayouts.includes('ai')}
|
||||||
checked={selectedLayouts.includes(layout)}
|
onChange={() => toggleLayout('ai')}
|
||||||
onChange={() => toggleLayout(layout)}
|
className="sr-only"
|
||||||
className="sr-only"
|
/>
|
||||||
/>
|
<div className={`flex items-center p-4 rounded-lg border cursor-pointer transition-all ${
|
||||||
<div className={`flex items-center p-3 rounded-lg border cursor-pointer transition-all ${
|
selectedLayouts.includes('ai')
|
||||||
selectedLayouts.includes(layout)
|
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300'
|
||||||
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300'
|
: 'border-gray-700 bg-gray-800/30 text-gray-300 hover:border-gray-600 hover:bg-gray-800/50'
|
||||||
: 'border-gray-700 bg-gray-800/30 text-gray-300 hover:border-gray-600 hover:bg-gray-800/50'
|
}`}>
|
||||||
|
<div className={`w-4 h-4 rounded border-2 mr-3 flex items-center justify-center ${
|
||||||
|
selectedLayouts.includes('ai')
|
||||||
|
? 'border-cyan-500 bg-cyan-500'
|
||||||
|
: 'border-gray-600'
|
||||||
}`}>
|
}`}>
|
||||||
<div className={`w-4 h-4 rounded border-2 mr-3 flex items-center justify-center ${
|
{selectedLayouts.includes('ai') && (
|
||||||
selectedLayouts.includes(layout)
|
<svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||||
? 'border-cyan-500 bg-cyan-500'
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
: 'border-gray-600'
|
</svg>
|
||||||
}`}>
|
)}
|
||||||
{selectedLayouts.includes(layout) && (
|
|
||||||
<svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span className="text-sm font-medium">{layout}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
<div>
|
||||||
))}
|
<span className="text-sm font-medium">AI Layout</span>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">RSI, EMAs, MACD indicators</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label className="group relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedLayouts.includes('diy')}
|
||||||
|
onChange={() => toggleLayout('diy')}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<div className={`flex items-center p-4 rounded-lg border cursor-pointer transition-all ${
|
||||||
|
selectedLayouts.includes('diy')
|
||||||
|
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300'
|
||||||
|
: 'border-gray-700 bg-gray-800/30 text-gray-300 hover:border-gray-600 hover:bg-gray-800/50'
|
||||||
|
}`}>
|
||||||
|
<div className={`w-4 h-4 rounded border-2 mr-3 flex items-center justify-center ${
|
||||||
|
selectedLayouts.includes('diy')
|
||||||
|
? 'border-cyan-500 bg-cyan-500'
|
||||||
|
: 'border-gray-600'
|
||||||
|
}`}>
|
||||||
|
{selectedLayouts.includes('diy') && (
|
||||||
|
<svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium">DIY Layout</span>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">Stochastic RSI, VWAP, OBV</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{selectedLayouts.length > 0 && (
|
{selectedLayouts.length > 0 && (
|
||||||
<div className="mt-3 p-3 bg-gray-800/30 rounded-lg">
|
<div className="mt-3 p-3 bg-gray-800/30 rounded-lg">
|
||||||
@@ -704,53 +718,6 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Timeframe Actions */}
|
|
||||||
<div className="mb-6">
|
|
||||||
<label className="block text-xs font-medium text-gray-400 mb-3">Quick Actions</label>
|
|
||||||
<div className="grid grid-cols-4 gap-2 mb-3">
|
|
||||||
{timeframes.map(tf => (
|
|
||||||
<button
|
|
||||||
key={tf.value}
|
|
||||||
onClick={() => quickTimeframeTest(tf.value)}
|
|
||||||
disabled={loading || selectedLayouts.length === 0}
|
|
||||||
className={`py-2 px-3 rounded-lg text-xs font-medium transition-all ${
|
|
||||||
selectedTimeframes.includes(tf.value)
|
|
||||||
? 'bg-cyan-500 text-white shadow-lg'
|
|
||||||
: loading
|
|
||||||
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
|
|
||||||
: 'bg-gray-700 text-gray-300 hover:bg-gray-600 hover:text-white transform hover:scale-105'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tf.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
||||||
<button
|
|
||||||
onClick={testAllTimeframes}
|
|
||||||
disabled={loading || selectedLayouts.length === 0 || !symbol}
|
|
||||||
className={`py-2 px-4 rounded-lg text-sm font-medium transition-all ${
|
|
||||||
loading
|
|
||||||
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
|
|
||||||
: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
🔍 Analyze All Timeframes
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setSelectedTimeframes([])}
|
|
||||||
disabled={loading || selectedTimeframes.length === 0}
|
|
||||||
className={`py-2 px-4 rounded-lg text-sm font-medium transition-all ${
|
|
||||||
loading || selectedTimeframes.length === 0
|
|
||||||
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
|
|
||||||
: 'bg-gray-700 text-gray-300 hover:bg-gray-600 hover:text-white transform hover:scale-[1.02] active:scale-[0.98]'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
🗑️ Clear Selection
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Analyze Button */}
|
{/* Analyze Button */}
|
||||||
<button
|
<button
|
||||||
className={`w-full py-3 px-6 rounded-lg font-semibold transition-all duration-300 ${
|
className={`w-full py-3 px-6 rounded-lg font-semibold transition-all duration-300 ${
|
||||||
|
|||||||
3220
lib/tradingview-automation.ts.backup
Normal file
3220
lib/tradingview-automation.ts.backup
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user