chore: sync current workspace changes
This commit is contained in:
@@ -1,31 +1,56 @@
|
||||
## QuickStart
|
||||
## Frontend Quick Start
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Optional Direct Service Calls
|
||||
Default dev URL: `http://localhost:5173`
|
||||
|
||||
The frontend still works with the compatibility backend entrypoint by default.
|
||||
In the current test-stage setup, split services are the recommended default.
|
||||
Point the frontend directly at those standalone services:
|
||||
The frontend expects the EvoTraders gateway WebSocket on `ws://localhost:8765` unless overridden.
|
||||
|
||||
## Recommended Local Backend Stack
|
||||
|
||||
Start the split backend services from the project root:
|
||||
|
||||
```bash
|
||||
./start-dev.sh
|
||||
```
|
||||
|
||||
That gives you:
|
||||
|
||||
- control plane at `http://localhost:8000/api`
|
||||
- trading service at `http://localhost:8001`
|
||||
- news service at `http://localhost:8002`
|
||||
- runtime service at `http://localhost:8003/api/runtime`
|
||||
- gateway WebSocket at `ws://localhost:8765`
|
||||
|
||||
## Frontend Environment Variables
|
||||
|
||||
You can point the frontend directly at those services with:
|
||||
|
||||
```bash
|
||||
VITE_CONTROL_API_BASE_URL=http://localhost:8000/api
|
||||
VITE_RUNTIME_API_BASE_URL=http://localhost:8003/api/runtime
|
||||
VITE_NEWS_SERVICE_URL=http://localhost:8002
|
||||
VITE_TRADING_SERVICE_URL=http://localhost:8001
|
||||
VITE_WS_URL=ws://localhost:8765
|
||||
```
|
||||
|
||||
Current direct-call coverage:
|
||||
There is also a starter template at [frontend/env.template](./env.template).
|
||||
|
||||
- runtime panel + gateway port discovery
|
||||
## Direct-Service Coverage
|
||||
|
||||
Current direct-call coverage includes:
|
||||
|
||||
- runtime panel data loading
|
||||
- gateway port/runtime discovery
|
||||
- `story`
|
||||
- `similar days`
|
||||
- `range explain`
|
||||
- `news for date`
|
||||
- `news categories`
|
||||
- selected trading reads such as price history and insider trades
|
||||
|
||||
If these variables are not set, the frontend falls back to the existing
|
||||
WebSocket-driven compatibility flow.
|
||||
If these variables are not set, the frontend falls back to local defaults and compatibility paths where they still exist.
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useAgentStore } from './store/agentStore';
|
||||
import { useMarketStore } from './store/marketStore';
|
||||
import { usePortfolioStore } from './store/portfolioStore';
|
||||
import { useRuntimeStore } from './store/runtimeStore';
|
||||
import { useOpenClawStore } from './store/openclawStore';
|
||||
import { useUIStore } from './store/uiStore';
|
||||
|
||||
const EDITABLE_AGENT_WORKSPACE_FILES = [
|
||||
@@ -141,6 +142,11 @@ export default function LiveTradingApp() {
|
||||
addSystemMessage,
|
||||
});
|
||||
|
||||
// Make clientRef available to OpenClaw panel via store
|
||||
useEffect(() => {
|
||||
useOpenClawStore.getState().setClientRef(clientRef);
|
||||
}, [clientRef]);
|
||||
|
||||
const runtimeControls = useRuntimeControls({
|
||||
clientRef,
|
||||
currentTickers: tickers,
|
||||
|
||||
@@ -14,6 +14,7 @@ const AgentFeed = lazy(() => import('./AgentFeed'));
|
||||
const StatisticsView = lazy(() => import('./StatisticsView'));
|
||||
const StockExplainView = lazy(() => import('./StockExplainView.jsx'));
|
||||
const TraderView = lazy(() => import('./TraderView.jsx'));
|
||||
const OpenClawView = lazy(() => import('./OpenClawView.jsx'));
|
||||
|
||||
function ViewLoadingFallback({ label = '加载中...' }) {
|
||||
return (
|
||||
@@ -171,7 +172,8 @@ export default function AppShell({
|
||||
const base = `view-slider-five ${currentView === 'traders' ? 'show-traders' :
|
||||
currentView === 'room' ? 'show-room' :
|
||||
currentView === 'explain' ? 'show-explain' :
|
||||
currentView === 'statistics' ? 'show-statistics' : 'show-chart'}`;
|
||||
currentView === 'chart' ? 'show-chart' :
|
||||
currentView === 'statistics' ? 'show-statistics' : 'show-openclaw'}`;
|
||||
return base;
|
||||
}, [currentView]);
|
||||
|
||||
@@ -382,6 +384,12 @@ export default function AppShell({
|
||||
>
|
||||
统计
|
||||
</button>
|
||||
<button
|
||||
className={`view-nav-btn ${currentView === 'openclaw' ? 'active' : ''}`}
|
||||
onClick={() => setCurrentView('openclaw')}
|
||||
>
|
||||
OpenClaw
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={viewClassName}>
|
||||
@@ -485,6 +493,13 @@ export default function AppShell({
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
{/* OpenClaw View Panel */}
|
||||
<div className="view-panel">
|
||||
<Suspense fallback={<ViewLoadingFallback label="加载 OpenClaw 视图..." />}>
|
||||
<OpenClawView />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,7 +127,7 @@ export default function TraderView({
|
||||
padding: '18px',
|
||||
background: 'linear-gradient(180deg, #ffffff 0%, #f4f7fb 100%)',
|
||||
display: 'grid',
|
||||
gridTemplateRows: 'auto minmax(0, 1fr)',
|
||||
gridTemplateRows: 'auto auto 1fr',
|
||||
gap: 18
|
||||
}}>
|
||||
<div style={{ display: 'grid', gap: 4 }}>
|
||||
@@ -138,82 +138,86 @@ export default function TraderView({
|
||||
聚焦查看每个 Agent 的模型、工具组、技能编排和工作区记忆,不展示交易表现数据
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '120px minmax(0, 1fr)',
|
||||
gap: 16,
|
||||
alignItems: 'stretch',
|
||||
minHeight: 0
|
||||
minHeight: 0,
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div style={{
|
||||
border: '1px solid #D9E0E7',
|
||||
borderRadius: 14,
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0 10px 24px rgba(15, 23, 42, 0.06)',
|
||||
padding: 12,
|
||||
display: 'grid',
|
||||
gap: 10,
|
||||
minHeight: 0,
|
||||
overflowY: 'auto',
|
||||
alignContent: 'start'
|
||||
}}>
|
||||
{agents.map((agent) => {
|
||||
const isSelected = agent.id === selectedAgentId;
|
||||
return (
|
||||
<button
|
||||
key={agent.id}
|
||||
type="button"
|
||||
onClick={() => onAgentChange(agent.id)}
|
||||
title={agent.name}
|
||||
{/* Left: agent avatar list */}
|
||||
<div style={{
|
||||
border: '1px solid #D9E0E7',
|
||||
borderRadius: 14,
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0 10px 24px rgba(15, 23, 42, 0.06)',
|
||||
padding: 12,
|
||||
display: 'grid',
|
||||
gap: 10,
|
||||
minHeight: 0,
|
||||
overflowY: 'auto',
|
||||
alignContent: 'start'
|
||||
}}>
|
||||
{agents.map((agent) => {
|
||||
const isSelected = agent.id === selectedAgentId;
|
||||
return (
|
||||
<button
|
||||
key={agent.id}
|
||||
type="button"
|
||||
onClick={() => onAgentChange(agent.id)}
|
||||
title={agent.name}
|
||||
style={{
|
||||
border: isSelected ? `2px solid ${agent.colors.accent}` : '1px solid #D9E0E7',
|
||||
borderRadius: 16,
|
||||
background: isSelected ? `${agent.colors.accent}10` : '#FFFFFF',
|
||||
boxShadow: isSelected ? `0 10px 20px ${agent.colors.accent}18` : 'none',
|
||||
padding: 8,
|
||||
display: 'grid',
|
||||
gap: 6,
|
||||
justifyItems: 'center',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={agent.avatar}
|
||||
alt={agent.name}
|
||||
style={{
|
||||
border: isSelected ? `2px solid ${agent.colors.accent}` : '1px solid #D9E0E7',
|
||||
borderRadius: 16,
|
||||
background: isSelected ? `${agent.colors.accent}10` : '#FFFFFF',
|
||||
boxShadow: isSelected ? `0 10px 20px ${agent.colors.accent}18` : 'none',
|
||||
padding: 8,
|
||||
display: 'grid',
|
||||
gap: 6,
|
||||
justifyItems: 'center',
|
||||
cursor: 'pointer'
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
objectFit: 'cover',
|
||||
border: `1px solid ${agent.colors.accent}33`
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={agent.avatar}
|
||||
alt={agent.name}
|
||||
style={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
objectFit: 'cover',
|
||||
border: `1px solid ${agent.colors.accent}33`
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
fontSize: 10,
|
||||
fontWeight: 800,
|
||||
color: isSelected ? agent.colors.accent : '#374151',
|
||||
textAlign: 'center',
|
||||
lineHeight: 1.4
|
||||
}}>
|
||||
{agent.name}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
/>
|
||||
<div style={{
|
||||
fontSize: 10,
|
||||
fontWeight: 800,
|
||||
color: isSelected ? agent.colors.accent : '#374151',
|
||||
textAlign: 'center',
|
||||
lineHeight: 1.4
|
||||
}}>
|
||||
{agent.name}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
border: '1px solid #D9E0E7',
|
||||
borderRadius: 14,
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0 10px 24px rgba(15, 23, 42, 0.06)',
|
||||
padding: 18,
|
||||
display: 'grid',
|
||||
gap: 16,
|
||||
minHeight: 0,
|
||||
overflowY: 'auto',
|
||||
alignContent: 'start'
|
||||
}}>
|
||||
{/* Right: agent detail content */}
|
||||
<div style={{
|
||||
border: '1px solid #D9E0E7',
|
||||
borderRadius: 14,
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0 10px 24px rgba(15, 23, 42, 0.06)',
|
||||
padding: 18,
|
||||
display: 'grid',
|
||||
gap: 16,
|
||||
minHeight: 0,
|
||||
overflowY: 'auto',
|
||||
alignContent: 'start'
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<img
|
||||
@@ -265,7 +269,8 @@ export default function TraderView({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(300px, 420px) minmax(0, 1fr)',
|
||||
gap: 16,
|
||||
alignItems: 'start'
|
||||
alignItems: 'start',
|
||||
minHeight: 0
|
||||
}}>
|
||||
<div style={{ display: 'grid', gap: 10 }}>
|
||||
<div style={{
|
||||
@@ -626,7 +631,7 @@ export default function TraderView({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isSkillPickerOpen && createPortal((
|
||||
<div
|
||||
|
||||
@@ -9,7 +9,7 @@ const resolveValue = (updater, currentValue) => (
|
||||
*/
|
||||
export const useUIStore = create((set) => ({
|
||||
// Current view
|
||||
currentView: 'traders', // 'traders' | 'room' | 'explain' | 'chart' | 'statistics' | 'runtime'
|
||||
currentView: 'traders', // 'traders' | 'room' | 'explain' | 'chart' | 'statistics' | 'openclaw' | 'runtime'
|
||||
setCurrentView: (currentView) => set((state) => ({ currentView: resolveValue(currentView, state.currentView) })),
|
||||
|
||||
// Chart tab
|
||||
|
||||
@@ -1098,6 +1098,10 @@ export default function GlobalStyles() {
|
||||
transform: translateX(-80%);
|
||||
}
|
||||
|
||||
.view-slider-five.show-openclaw {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.view-panel {
|
||||
flex: 0 0 33.333%;
|
||||
width: 33.333%;
|
||||
|
||||
BIN
frontend/trader-full.png
Normal file
BIN
frontend/trader-full.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
BIN
frontend/trader-view.png
Normal file
BIN
frontend/trader-view.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
Reference in New Issue
Block a user