chore: sync current workspace changes

This commit is contained in:
2026-03-27 11:27:26 +08:00
parent 6ecc224427
commit 5c08c1865c
33 changed files with 1450 additions and 724 deletions

View File

@@ -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.

View File

@@ -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,

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
frontend/trader-view.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB