From 324e34d537be0970237daa10ce3867b1335069be Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Wed, 6 May 2026 15:49:04 -0700 Subject: [PATCH] feat(ui): migrate trade plan and chat controls --- web/src/components/ChatControl.tsx | 520 +++++++++++++++-------------- web/src/views/ScreenerView.tsx | 29 +- web/src/views/SimpleView.tsx | 74 ++-- 3 files changed, 318 insertions(+), 305 deletions(-) diff --git a/web/src/components/ChatControl.tsx b/web/src/components/ChatControl.tsx index de00500..6f6470e 100644 --- a/web/src/components/ChatControl.tsx +++ b/web/src/components/ChatControl.tsx @@ -4,22 +4,22 @@ import { tradingRuntime } from '../lib/runtime'; import { getPlatformAccessToken } from '../lib/authSession'; import { createRequestId } from '../../../shared/request-id.js'; import { - Send, X, Bot, User, + Send, X, Bot, User, Check, Loader2, Zap, Copy } from 'lucide-react'; -import { Button } from './ui/button'; +import { Button, Input, Select, Textarea } from './ui/Primitives'; import { cn } from '../lib/utils'; - -interface ChatMessage { - id: number; - role: 'user' | 'assistant'; - content: string; - profileData?: any; - action?: string; - timestamp: Date; -} - + +interface ChatMessage { + id: number; + role: 'user' | 'assistant'; + content: string; + profileData?: any; + action?: string; + timestamp: Date; +} + interface ChatControlProps { profiles: any[]; onApplyProfile: (action: string, profile: any) => Promise<{ success: boolean; error?: string }>; @@ -84,58 +84,58 @@ export const normalizeProfileForApply = (profileData: any) => ({ symbols: String(profileData?.symbols || '').trim(), is_active: profileData?.is_active !== false, }); - -// 3D Robot SVG Icon -const RobotIcon = ({ size = 32 }: { size?: number }) => ( - - {/* Antenna */} - - - - - {/* Head */} - - {/* Eyes */} - - - - - - - - - {/* Mouth */} - - {/* Body */} - - {/* Body detail */} - - {/* Arms */} - - - - - - - - - - - - - -); - + +// 3D Robot SVG Icon +const RobotIcon = ({ size = 32 }: { size?: number }) => ( + + {/* Antenna */} + + + + + {/* Head */} + + {/* Eyes */} + + + + + + + + + {/* Mouth */} + + {/* Body */} + + {/* Body detail */} + + {/* Arms */} + + + + + + + + + + + + + +); + export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { - const [isOpen, setIsOpen] = useState(false); - const [messages, setMessages] = useState([ - { - id: 0, - role: 'assistant', - content: "Hi! I'm your trading assistant. Tell me what kind of strategy profile you'd like to create or modify, and I'll generate the configuration for you.\n\nTry: \"Create a conservative BTC scalper with $1000 capital\"", - timestamp: new Date(), - } - ]); + const [isOpen, setIsOpen] = useState(false); + const [messages, setMessages] = useState([ + { + id: 0, + role: 'assistant', + content: "Hi! I'm your trading assistant. Tell me what kind of strategy profile you'd like to create or modify, and I'll generate the configuration for you.\n\nTry: \"Create a conservative BTC scalper with $1000 capital\"", + timestamp: new Date(), + } + ]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [appliedIds, setAppliedIds] = useState>(new Set()); @@ -175,31 +175,31 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { }, })); }; - - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages]); - - useEffect(() => { - if (isOpen) { - setTimeout(() => inputRef.current?.focus(), 300); - } - }, [isOpen]); - - const sendMessage = async (text?: string) => { - const msg = text || input.trim(); - if (!msg || isLoading) return; - - const userMsg: ChatMessage = { - id: Date.now(), - role: 'user', - content: msg, - timestamp: new Date(), - }; - setMessages(prev => [...prev, userMsg]); - setInput(''); - setIsLoading(true); - + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + useEffect(() => { + if (isOpen) { + setTimeout(() => inputRef.current?.focus(), 300); + } + }, [isOpen]); + + const sendMessage = async (text?: string) => { + const msg = text || input.trim(); + if (!msg || isLoading) return; + + const userMsg: ChatMessage = { + id: Date.now(), + role: 'user', + content: msg, + timestamp: new Date(), + }; + setMessages(prev => [...prev, userMsg]); + setInput(''); + setIsLoading(true); + try { const apiUrl = tradingRuntime.tradingApiUrl; const accessToken = await getPlatformAccessToken(); @@ -213,50 +213,50 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { body: JSON.stringify({ message: msg, context: profiles.map(p => ({ - id: p.id, - name: p.name, - allocated_capital: p.allocated_capital, - risk_per_trade_percent: p.risk_per_trade_percent, - symbols: p.symbols, - is_active: p.is_active, - strategy_config: p.strategy_config, - })), - }), - }); - - if (!res.ok) { - const err = await res.json(); - throw new Error(err.error || 'Chat request failed'); - } - - const data = await res.json(); - - const assistantMsg: ChatMessage = { - id: Date.now() + 1, - role: 'assistant', - content: data.summary || data.reasoning || 'Profile configuration generated.', - profileData: data.profile || null, - action: data.action, - timestamp: new Date(), - }; - - if (data.reasoning && data.summary) { - assistantMsg.content = `${data.summary}\n\n${data.reasoning}`; - } - - setMessages(prev => [...prev, assistantMsg]); - } catch (err: any) { - setMessages(prev => [...prev, { - id: Date.now() + 1, - role: 'assistant', - content: `Error: ${err.message}`, - timestamp: new Date(), - }]); - } - - setIsLoading(false); - }; - + id: p.id, + name: p.name, + allocated_capital: p.allocated_capital, + risk_per_trade_percent: p.risk_per_trade_percent, + symbols: p.symbols, + is_active: p.is_active, + strategy_config: p.strategy_config, + })), + }), + }); + + if (!res.ok) { + const err = await res.json(); + throw new Error(err.error || 'Chat request failed'); + } + + const data = await res.json(); + + const assistantMsg: ChatMessage = { + id: Date.now() + 1, + role: 'assistant', + content: data.summary || data.reasoning || 'Profile configuration generated.', + profileData: data.profile || null, + action: data.action, + timestamp: new Date(), + }; + + if (data.reasoning && data.summary) { + assistantMsg.content = `${data.summary}\n\n${data.reasoning}`; + } + + setMessages(prev => [...prev, assistantMsg]); + } catch (err: any) { + setMessages(prev => [...prev, { + id: Date.now() + 1, + role: 'assistant', + content: `Error: ${err.message}`, + timestamp: new Date(), + }]); + } + + setIsLoading(false); + }; + const handleApply = async (msg: ChatMessage) => { if (msg.profileData && msg.action) { const activeDraft = draftProfiles[msg.id] || msg.profileData; @@ -275,15 +275,15 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { }]); } else { setMessages(prev => [...prev, { - id: Date.now(), - role: 'assistant', - content: `Failed to ${msg.action === 'create_profile' ? 'create' : 'update'} profile: ${result.error || 'Unknown error'}. Please try again or check your permissions.`, - timestamp: new Date(), - }]); - } - } - }; - + id: Date.now(), + role: 'assistant', + content: `Failed to ${msg.action === 'create_profile' ? 'create' : 'update'} profile: ${result.error || 'Unknown error'}. Please try again or check your permissions.`, + timestamp: new Date(), + }]); + } + } + }; + const handleCancel = (msgId: number) => { setCancelledIds(prev => new Set(prev).add(msgId)); closeDraftEditor(msgId); @@ -291,14 +291,14 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { id: Date.now(), role: 'assistant', content: 'Profile creation cancelled. You can ask me to create a different one or modify the parameters.', - timestamp: new Date(), - }]); - }; - - const copyJson = (data: any) => { - navigator.clipboard.writeText(JSON.stringify(data, null, 2)); - }; - + timestamp: new Date(), + }]); + }; + + const copyJson = (data: any) => { + navigator.clipboard.writeText(JSON.stringify(data, null, 2)); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); @@ -323,21 +323,23 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { // Floating robot button - bottom right corner (portaled to body to avoid parent CSS issues) if (!isOpen) { return createPortal( - , - document.body - ); - } - - return createPortal( - <> - {/* Backdrop */} + + , + document.body + ); + } + + return createPortal( + <> + {/* Backdrop */}
setIsOpen(false)} style={{ @@ -407,14 +409,14 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { animation: 'fadeIn 0.15s ease-out', }} /> -
{
- - {/* Bubble */} -
+ + {/* Bubble */} +
{ }}> {msg.content}
- - {/* Profile preview card */} + + {/* Profile preview card */} {msg.profileData && msg.action !== 'explain' && (() => { const activeProfileData = draftProfiles[msg.id] || msg.profileData; const isEditing = editingIds.has(msg.id); @@ -509,13 +511,16 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { {msg.action === 'create_profile' ? 'New Profile' : 'Update Profile'}
- +
@@ -554,7 +559,7 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { {isEditing ? (
Edit Parameters Before Apply
- updateDraftField(msg.id, 'name', e.target.value)} placeholder="Profile Name" @@ -562,7 +567,7 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { style={inputStyle} />
- { className="w-full rounded-lg px-2.5 py-1.5 text-[11px] outline-none" style={inputStyle} /> - { style={inputStyle} />
- updateDraftField(msg.id, 'symbols', e.target.value)} placeholder="Symbols (e.g. BTC/USDT,ETH/USDT)" @@ -592,15 +597,16 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { />
Auto Trading - + options={[ + { value: 'true', label: 'Active' }, + { value: 'false', label: 'Paused' }, + ]} + />
- - +
) : cancelledIds.has(msg.id) ? (
{ {msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
-
- ))} - +
+ ))} + {/* Suggested quick actions - shown when only welcome message exists */} {messages.length <= 1 && !isLoading && (

Quick Actions

{quickActions.map((action, i) => ( - + ))}
-
- )} +
+ )} {isLoading && (
@@ -726,10 +740,10 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => {
)} - -
-
- + +
+
+ {/* Input area */}
{ }}>
-