Files
evotraders/frontend/src/components/RuntimeSettingsPanel.jsx
cillin 4b5ac86b83 feat: Add evaluation hooks, skill adaptation and team pipeline config
- Add EvaluationHook for post-execution agent evaluation
- Add SkillAdaptationHook for dynamic skill adaptation
- Add team/ directory with team coordination logic
- Add TEAM_PIPELINE.yaml for smoke_fullstack pipeline config
- Update RuntimeView, TraderView and RuntimeSettingsPanel UI
- Add runtimeApi and websocket services
- Add runtime_state.json to smoke_fullstack state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 18:52:12 +08:00

577 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { createPortal } from 'react-dom';
export default function RuntimeSettingsPanel({
showTrigger = true,
isOpen,
isConnected,
isSaving,
feedback,
scheduleMode,
intervalMinutes,
triggerTime,
maxCommCycles,
initialCash,
marginRequirement,
enableMemory,
mode,
pollInterval,
startDate,
endDate,
enableMock,
watchlistSymbols,
watchlistInputValue,
watchlistSuggestions,
onToggle,
onClose,
onScheduleModeChange,
onIntervalMinutesChange,
onTriggerTimeChange,
onMaxCommCyclesChange,
onInitialCashChange,
onMarginRequirementChange,
onEnableMemoryChange,
onModeChange,
onPollIntervalChange,
onStartDateChange,
onEndDateChange,
onEnableMockChange,
onWatchlistInputChange,
onWatchlistInputKeyDown,
onWatchlistAdd,
onWatchlistRemove,
onWatchlistRestoreCurrent,
onWatchlistRestoreDefault,
onWatchlistSuggestionClick,
onSave,
onRestoreDefaults
}) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
{showTrigger && (
<button
onClick={onToggle}
style={{
padding: '6px 10px',
borderRadius: 4,
border: '1px solid #333333',
background: isOpen ? '#1E1E1E' : '#111111',
color: '#FFFFFF',
fontSize: '11px',
fontWeight: 700,
letterSpacing: '0.6px',
cursor: 'pointer',
whiteSpace: 'nowrap'
}}
>
启动配置
</button>
)}
{isOpen && createPortal((
<div
onClick={onClose}
style={{
position: 'fixed',
inset: 0,
background: 'rgba(15, 23, 42, 0.28)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 24,
zIndex: 9998
}}
>
<div
onClick={(event) => event.stopPropagation()}
style={{
width: 'min(760px, 92vw)',
maxHeight: '80vh',
overflowY: 'auto',
borderRadius: 16,
border: '1px solid #D9E0E7',
background: '#FFFFFF',
boxShadow: '0 24px 60px rgba(15, 23, 42, 0.18)',
padding: 18,
paddingTop: 22,
display: 'grid',
gap: 16,
position: 'relative',
zIndex: 9999
}}
>
<button
onClick={onClose}
style={{
position: 'absolute',
top: 16,
right: 16,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
borderRadius: 999,
width: 40,
height: 40,
fontSize: 16,
lineHeight: 1,
color: '#111111',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
boxShadow: '0 4px 12px rgba(15, 23, 42, 0.08)'
}}
aria-label="关闭启动配置"
>
×
</button>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'center', paddingRight: 56 }}>
<div style={{ display: 'grid', gap: 4 }}>
<div style={{ fontSize: 14, fontWeight: 800, color: '#111111' }}>启动配置</div>
<div style={{ fontSize: 11, color: '#6B7280' }}>
配置本次任务的启动参数与调度方式
</div>
</div>
</div>
<div style={{
border: '1px solid #E5EAF1',
borderRadius: 12,
background: '#FCFDFE',
padding: 14,
display: 'grid',
gap: 12
}}>
<div style={{ fontSize: 12, fontWeight: 800, color: '#111111' }}>自选股</div>
<div style={{
display: 'flex',
flexWrap: 'wrap',
gap: 8,
minHeight: 40,
padding: '2px 0'
}}>
{watchlistSymbols.map((symbol) => (
<button
key={symbol}
onClick={() => onWatchlistRemove(symbol)}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 6,
padding: '6px 10px',
borderRadius: 999,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '11px',
fontWeight: 700,
cursor: 'pointer'
}}
>
<span>{symbol}</span>
<span style={{ color: '#777777' }}>×</span>
</button>
))}
{watchlistSymbols.length === 0 && (
<div style={{ fontSize: '11px', color: '#888888', padding: '8px 2px' }}>
还没有股票输入代码后回车添加
</div>
)}
</div>
<div style={{ display: 'flex', gap: 8 }}>
<input
value={watchlistInputValue}
onChange={(e) => onWatchlistInputChange(e.target.value)}
onKeyDown={onWatchlistInputKeyDown}
placeholder="输入股票代码,回车添加"
style={{
flex: 1,
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
<button
onClick={onWatchlistAdd}
style={{
padding: '9px 12px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '11px',
fontWeight: 700,
cursor: 'pointer'
}}
>
添加
</button>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
{watchlistSuggestions.map((symbol) => {
const active = watchlistSymbols.includes(symbol);
return (
<button
key={symbol}
onClick={() => onWatchlistSuggestionClick(symbol)}
disabled={active}
style={{
padding: '5px 8px',
borderRadius: 999,
border: '1px solid',
borderColor: active ? '#B6E3C5' : '#D0D7DE',
background: active ? '#ECFDF3' : '#FFFFFF',
color: active ? '#157347' : '#4A5568',
fontSize: '10px',
fontWeight: 700,
cursor: active ? 'default' : 'pointer'
}}
>
{symbol}
</button>
);
})}
</div>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<button
onClick={onWatchlistRestoreCurrent}
style={{
padding: '8px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '11px',
fontWeight: 700,
cursor: 'pointer'
}}
>
恢复当前
</button>
<button
onClick={onWatchlistRestoreDefault}
style={{
padding: '8px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '11px',
fontWeight: 700,
cursor: 'pointer'
}}
>
恢复默认
</button>
</div>
</div>
<div style={{
border: '1px solid #E5EAF1',
borderRadius: 12,
background: '#FCFDFE',
padding: 14,
display: 'grid',
gap: 12
}}>
<div style={{ fontSize: 12, fontWeight: 800, color: '#111111' }}>调度参数</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>调度模式</span>
<select
value={scheduleMode}
onChange={(e) => onScheduleModeChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px'
}}
>
<option value="daily">每日定时</option>
<option value="intraday">盘中轮询</option>
</select>
</label>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>间隔(分钟)</span>
<input
type="number"
min="1"
value={intervalMinutes}
onChange={(e) => onIntervalMinutesChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
</div>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>每日定时时间 (NYSE)</span>
<input
type="time"
value={triggerTime}
onChange={(e) => onTriggerTimeChange(e.target.value)}
disabled={scheduleMode !== 'daily'}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: scheduleMode === 'daily' ? '#FFFFFF' : '#F3F4F6',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>讨论轮数上限</span>
<input
type="number"
min="1"
value={maxCommCycles}
onChange={(e) => onMaxCommCyclesChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>初始资金</span>
<input
type="number"
min="1"
step="1000"
value={initialCash}
onChange={(e) => onInitialCashChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>保证金要求</span>
<input
type="number"
min="0"
step="0.01"
value={marginRequirement}
onChange={(e) => onMarginRequirementChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 2 }}>
<input
type="checkbox"
checked={enableMemory}
onChange={(e) => onEnableMemoryChange(e.target.checked)}
style={{
width: 16,
height: 16,
accentColor: '#0D47A1',
cursor: 'pointer'
}}
/>
<span style={{ fontSize: '11px', color: '#111111', fontWeight: 700 }}>启用长期记忆</span>
</label>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>运行模式</span>
<select
value={mode}
onChange={(e) => onModeChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px'
}}
>
<option value="live">实盘模式 (Live)</option>
<option value="backtest">回测模式 (Backtest)</option>
</select>
</label>
{mode === 'backtest' && (
<>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>回测开始日期</span>
<input
type="date"
value={startDate}
onChange={(e) => onStartDateChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>回测结束日期</span>
<input
type="date"
value={endDate}
onChange={(e) => onEndDateChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
</div>
</>
)}
<label style={{ display: 'grid', gap: 4 }}>
<span style={{ fontSize: '10px', color: '#4B5563', fontWeight: 700 }}>轮询间隔()</span>
<input
type="number"
min="1"
max="300"
value={pollInterval}
onChange={(e) => onPollIntervalChange(e.target.value)}
style={{
padding: '9px 10px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '12px',
fontFamily: '"Courier New", monospace'
}}
/>
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 2 }}>
<input
type="checkbox"
checked={enableMock}
onChange={(e) => onEnableMockChange(e.target.checked)}
style={{
width: 16,
height: 16,
accentColor: '#0D47A1',
cursor: 'pointer'
}}
/>
<span style={{ fontSize: '11px', color: '#111111', fontWeight: 700 }}>启用模拟数据 (Mock)</span>
</label>
</div>
<div style={{
border: '1px solid #E5EAF1',
borderRadius: 12,
background: '#FCFDFE',
padding: 14,
display: 'grid',
gap: 10
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<div style={{ fontSize: 12, fontWeight: 800, color: '#111111' }}>操作</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<button
onClick={onRestoreDefaults}
style={{
padding: '9px 12px',
borderRadius: 8,
border: '1px solid #D0D7DE',
background: '#FFFFFF',
color: '#111111',
fontSize: '11px',
fontWeight: 700,
cursor: 'pointer'
}}
>
恢复默认
</button>
<button
onClick={onSave}
disabled={!isConnected || isSaving}
style={{
padding: '9px 14px',
borderRadius: 8,
border: '1px solid #1565C0',
background: isConnected && !isSaving ? '#0D47A1' : '#94A3B8',
color: '#FFFFFF',
fontSize: '11px',
fontWeight: 700,
letterSpacing: '0.4px',
cursor: isConnected && !isSaving ? 'pointer' : 'not-allowed'
}}
>
{isSaving ? '启动中' : '启动任务'}
</button>
</div>
</div>
{feedback && (
<span style={{
color: feedback.type === 'success' ? '#00C853' : '#FF5252',
fontSize: '11px',
fontFamily: '"Courier New", monospace'
}}>
{feedback.text}
</span>
)}
</div>
</div>
</div>
), document.body)}
</div>
);
}