- ToolCallCard: Display tool execution results with expandable details - MessageFeedback: Thumbs up/down with detailed feedback form - StreamingIndicator: Multiple variants for thinking/analyzing/generating states - AssistantSettingsPanel: Settings modal with risk profile, preferences Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
454 lines
16 KiB
TypeScript
454 lines
16 KiB
TypeScript
/**
|
|
* AssistantSettingsPanel Component
|
|
* Settings for customizing assistant behavior and preferences
|
|
*/
|
|
|
|
import React, { useState } from 'react';
|
|
import {
|
|
Settings,
|
|
X,
|
|
Save,
|
|
RotateCcw,
|
|
Bot,
|
|
Zap,
|
|
Shield,
|
|
Bell,
|
|
Sliders,
|
|
MessageSquare,
|
|
TrendingUp,
|
|
Clock,
|
|
Loader2,
|
|
} from 'lucide-react';
|
|
|
|
export interface AssistantSettings {
|
|
// Response preferences
|
|
responseStyle: 'concise' | 'detailed' | 'technical';
|
|
includeCharts: boolean;
|
|
includeSignals: boolean;
|
|
autoAnalyze: boolean;
|
|
|
|
// Risk profile
|
|
riskProfile: 'conservative' | 'moderate' | 'aggressive';
|
|
maxPositionSize: number;
|
|
preferredTimeframes: string[];
|
|
|
|
// Notification preferences
|
|
signalNotifications: boolean;
|
|
analysisNotifications: boolean;
|
|
priceAlerts: boolean;
|
|
|
|
// Advanced
|
|
streamResponses: boolean;
|
|
rememberContext: boolean;
|
|
maxHistoryMessages: number;
|
|
}
|
|
|
|
interface AssistantSettingsPanelProps {
|
|
settings: AssistantSettings;
|
|
onSave: (settings: AssistantSettings) => Promise<void>;
|
|
onClose: () => void;
|
|
isOpen: boolean;
|
|
}
|
|
|
|
const DEFAULT_SETTINGS: AssistantSettings = {
|
|
responseStyle: 'detailed',
|
|
includeCharts: true,
|
|
includeSignals: true,
|
|
autoAnalyze: false,
|
|
riskProfile: 'moderate',
|
|
maxPositionSize: 2,
|
|
preferredTimeframes: ['H1', 'H4', 'D1'],
|
|
signalNotifications: true,
|
|
analysisNotifications: false,
|
|
priceAlerts: true,
|
|
streamResponses: true,
|
|
rememberContext: true,
|
|
maxHistoryMessages: 50,
|
|
};
|
|
|
|
const TIMEFRAMES = ['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1', 'W1', 'MN'];
|
|
|
|
const AssistantSettingsPanel: React.FC<AssistantSettingsPanelProps> = ({
|
|
settings: initialSettings,
|
|
onSave,
|
|
onClose,
|
|
isOpen,
|
|
}) => {
|
|
const [settings, setSettings] = useState<AssistantSettings>(initialSettings);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [activeTab, setActiveTab] = useState<'general' | 'risk' | 'notifications' | 'advanced'>('general');
|
|
|
|
const handleSave = async () => {
|
|
setIsSaving(true);
|
|
try {
|
|
await onSave(settings);
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Failed to save settings:', error);
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setSettings(DEFAULT_SETTINGS);
|
|
};
|
|
|
|
const toggleTimeframe = (tf: string) => {
|
|
setSettings((prev) => ({
|
|
...prev,
|
|
preferredTimeframes: prev.preferredTimeframes.includes(tf)
|
|
? prev.preferredTimeframes.filter((t) => t !== tf)
|
|
: [...prev.preferredTimeframes, tf],
|
|
}));
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
|
<div className="w-full max-w-lg bg-gray-900 rounded-xl border border-gray-700 shadow-xl overflow-hidden">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-blue-500/20 rounded-lg">
|
|
<Settings className="w-5 h-5 text-blue-400" />
|
|
</div>
|
|
<div>
|
|
<h2 className="font-semibold text-white">Assistant Settings</h2>
|
|
<p className="text-sm text-gray-400">Customize your trading assistant</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 text-gray-400 hover:text-white hover:bg-gray-800 rounded-lg transition-colors"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="flex border-b border-gray-700">
|
|
{[
|
|
{ id: 'general', label: 'General', icon: Bot },
|
|
{ id: 'risk', label: 'Risk', icon: Shield },
|
|
{ id: 'notifications', label: 'Alerts', icon: Bell },
|
|
{ id: 'advanced', label: 'Advanced', icon: Sliders },
|
|
].map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id as typeof activeTab)}
|
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
|
activeTab === tab.id
|
|
? 'text-blue-400 border-b-2 border-blue-400 bg-blue-500/5'
|
|
: 'text-gray-400 hover:text-white hover:bg-gray-800/50'
|
|
}`}
|
|
>
|
|
<tab.icon className="w-4 h-4" />
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-4 space-y-4 max-h-[400px] overflow-y-auto">
|
|
{/* General Tab */}
|
|
{activeTab === 'general' && (
|
|
<div className="space-y-4">
|
|
{/* Response Style */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-white mb-2">
|
|
Response Style
|
|
</label>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
{[
|
|
{ id: 'concise', label: 'Concise', desc: 'Brief answers' },
|
|
{ id: 'detailed', label: 'Detailed', desc: 'Full explanations' },
|
|
{ id: 'technical', label: 'Technical', desc: 'Expert level' },
|
|
].map((style) => (
|
|
<button
|
|
key={style.id}
|
|
onClick={() =>
|
|
setSettings((prev) => ({
|
|
...prev,
|
|
responseStyle: style.id as AssistantSettings['responseStyle'],
|
|
}))
|
|
}
|
|
className={`p-3 rounded-lg border text-left transition-colors ${
|
|
settings.responseStyle === style.id
|
|
? 'border-blue-500 bg-blue-500/10'
|
|
: 'border-gray-700 hover:border-gray-600'
|
|
}`}
|
|
>
|
|
<p className="text-sm font-medium text-white">{style.label}</p>
|
|
<p className="text-xs text-gray-500">{style.desc}</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Toggles */}
|
|
<div className="space-y-3">
|
|
<Toggle
|
|
label="Include Charts"
|
|
description="Show visual charts in responses"
|
|
icon={TrendingUp}
|
|
checked={settings.includeCharts}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, includeCharts: v }))}
|
|
/>
|
|
<Toggle
|
|
label="Include Trading Signals"
|
|
description="Generate buy/sell signals when relevant"
|
|
icon={Zap}
|
|
checked={settings.includeSignals}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, includeSignals: v }))}
|
|
/>
|
|
<Toggle
|
|
label="Auto-Analyze Symbols"
|
|
description="Automatically analyze mentioned symbols"
|
|
icon={Bot}
|
|
checked={settings.autoAnalyze}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, autoAnalyze: v }))}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Risk Tab */}
|
|
{activeTab === 'risk' && (
|
|
<div className="space-y-4">
|
|
{/* Risk Profile */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-white mb-2">
|
|
Risk Profile
|
|
</label>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
{[
|
|
{ id: 'conservative', label: 'Conservative', color: 'green' },
|
|
{ id: 'moderate', label: 'Moderate', color: 'yellow' },
|
|
{ id: 'aggressive', label: 'Aggressive', color: 'red' },
|
|
].map((profile) => (
|
|
<button
|
|
key={profile.id}
|
|
onClick={() =>
|
|
setSettings((prev) => ({
|
|
...prev,
|
|
riskProfile: profile.id as AssistantSettings['riskProfile'],
|
|
}))
|
|
}
|
|
className={`p-3 rounded-lg border transition-colors ${
|
|
settings.riskProfile === profile.id
|
|
? `border-${profile.color}-500 bg-${profile.color}-500/10`
|
|
: 'border-gray-700 hover:border-gray-600'
|
|
}`}
|
|
>
|
|
<p className="text-sm font-medium text-white">{profile.label}</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Max Position Size */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-white mb-2">
|
|
Max Position Size (% of account)
|
|
</label>
|
|
<input
|
|
type="range"
|
|
min="0.5"
|
|
max="10"
|
|
step="0.5"
|
|
value={settings.maxPositionSize}
|
|
onChange={(e) =>
|
|
setSettings((prev) => ({
|
|
...prev,
|
|
maxPositionSize: parseFloat(e.target.value),
|
|
}))
|
|
}
|
|
className="w-full"
|
|
/>
|
|
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
|
<span>0.5%</span>
|
|
<span className="text-white font-medium">{settings.maxPositionSize}%</span>
|
|
<span>10%</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preferred Timeframes */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-white mb-2">
|
|
Preferred Timeframes
|
|
</label>
|
|
<div className="flex flex-wrap gap-2">
|
|
{TIMEFRAMES.map((tf) => (
|
|
<button
|
|
key={tf}
|
|
onClick={() => toggleTimeframe(tf)}
|
|
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
|
|
settings.preferredTimeframes.includes(tf)
|
|
? 'bg-blue-600 text-white'
|
|
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
|
|
}`}
|
|
>
|
|
{tf}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Notifications Tab */}
|
|
{activeTab === 'notifications' && (
|
|
<div className="space-y-3">
|
|
<Toggle
|
|
label="Signal Notifications"
|
|
description="Get notified when new signals are generated"
|
|
icon={Zap}
|
|
checked={settings.signalNotifications}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, signalNotifications: v }))}
|
|
/>
|
|
<Toggle
|
|
label="Analysis Updates"
|
|
description="Get notified when analysis is complete"
|
|
icon={TrendingUp}
|
|
checked={settings.analysisNotifications}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, analysisNotifications: v }))}
|
|
/>
|
|
<Toggle
|
|
label="Price Alerts"
|
|
description="Get notified when prices hit targets"
|
|
icon={Bell}
|
|
checked={settings.priceAlerts}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, priceAlerts: v }))}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Advanced Tab */}
|
|
{activeTab === 'advanced' && (
|
|
<div className="space-y-4">
|
|
<Toggle
|
|
label="Stream Responses"
|
|
description="Show responses as they're generated"
|
|
icon={MessageSquare}
|
|
checked={settings.streamResponses}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, streamResponses: v }))}
|
|
/>
|
|
<Toggle
|
|
label="Remember Context"
|
|
description="Keep conversation context between sessions"
|
|
icon={Clock}
|
|
checked={settings.rememberContext}
|
|
onChange={(v) => setSettings((prev) => ({ ...prev, rememberContext: v }))}
|
|
/>
|
|
|
|
{/* Max History Messages */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-white mb-2">
|
|
Max History Messages
|
|
</label>
|
|
<select
|
|
value={settings.maxHistoryMessages}
|
|
onChange={(e) =>
|
|
setSettings((prev) => ({
|
|
...prev,
|
|
maxHistoryMessages: parseInt(e.target.value),
|
|
}))
|
|
}
|
|
className="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:border-blue-500"
|
|
>
|
|
<option value={20}>20 messages</option>
|
|
<option value={50}>50 messages</option>
|
|
<option value={100}>100 messages</option>
|
|
<option value={200}>200 messages</option>
|
|
</select>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
Older messages will be summarized to save context
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="flex items-center justify-between p-4 border-t border-gray-700 bg-gray-800/50">
|
|
<button
|
|
onClick={handleReset}
|
|
className="flex items-center gap-2 px-4 py-2 text-sm text-gray-400 hover:text-white transition-colors"
|
|
>
|
|
<RotateCcw className="w-4 h-4" />
|
|
Reset to Defaults
|
|
</button>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={onClose}
|
|
className="px-4 py-2 text-sm text-gray-300 hover:text-white transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleSave}
|
|
disabled={isSaving}
|
|
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm transition-colors disabled:opacity-50"
|
|
>
|
|
{isSaving ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
Saving...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Save className="w-4 h-4" />
|
|
Save Settings
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Toggle Component
|
|
interface ToggleProps {
|
|
label: string;
|
|
description: string;
|
|
icon: React.FC<{ className?: string }>;
|
|
checked: boolean;
|
|
onChange: (value: boolean) => void;
|
|
}
|
|
|
|
const Toggle: React.FC<ToggleProps> = ({
|
|
label,
|
|
description,
|
|
icon: Icon,
|
|
checked,
|
|
onChange,
|
|
}) => (
|
|
<div className="flex items-center justify-between p-3 bg-gray-800/50 rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<Icon className="w-5 h-5 text-gray-400" />
|
|
<div>
|
|
<p className="text-sm font-medium text-white">{label}</p>
|
|
<p className="text-xs text-gray-500">{description}</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => onChange(!checked)}
|
|
className={`relative w-11 h-6 rounded-full transition-colors ${
|
|
checked ? 'bg-blue-600' : 'bg-gray-700'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${
|
|
checked ? 'translate-x-6' : 'translate-x-1'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
);
|
|
|
|
export default AssistantSettingsPanel;
|