mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 00:07:01 +08:00
feat: cream terminal redesign, English-only UI, autopilot launch fixes
- Redesign dashboard into a cream-paper + vermilion IBM Plex Mono terminal (live L2 order book, cost/liq map, WS K-line, signal matrix, orchestration topology, risk radar, execution log, current positions, equity curve) - Convert all user-facing UI and backend strings/prompts from Chinese to English (multi-language retained, default English) - Add /api/statistics/full endpoint + full-stats frontend wiring - Fix Autopilot launch: reuse the existing trader instead of creating duplicates (eliminates repeat ~35s create cost and stale-trader 404s); launch sends 5m scan interval - Fix unreadable toasts: cream theme with high-contrast text + per-type accent - Silence background dashboard polls (getTraderConfig) to stop error-toast spam
This commit is contained in:
@@ -273,7 +273,7 @@ func (a *Agent) Run(userMessage string, onChunk func(string)) string {
|
||||
|
||||
// Safety: max iterations reached.
|
||||
logger.Warnf("Agent: max iterations (%d) reached for message: %q", maxIterations, userMessage)
|
||||
reply := "Operation completed. Please check your account for the latest status. / 操作已完成,请检查您的账户查看最新状态。"
|
||||
reply := "Operation completed. Please check your account for the latest status."
|
||||
a.memory.Add("user", userMessage)
|
||||
a.memory.Add("assistant", reply)
|
||||
return reply
|
||||
|
||||
@@ -213,7 +213,7 @@ func TestNarrationStructurallyImpossible(t *testing.T) {
|
||||
|
||||
// Simulate a (malformed) response that has both Content and ToolCalls.
|
||||
malformed := &mcp.LLMResponse{
|
||||
Content: "现在我将为您查询策略。", // narration — must NOT reach user
|
||||
Content: "Now I will look up your strategies.", // narration — must NOT reach user
|
||||
ToolCalls: []mcp.ToolCall{{
|
||||
ID: "c1",
|
||||
Type: "function",
|
||||
@@ -226,15 +226,15 @@ func TestNarrationStructurallyImpossible(t *testing.T) {
|
||||
|
||||
llm := &mockLLM{responses: []*mcp.LLMResponse{
|
||||
malformed,
|
||||
textReply("你有1个策略:BTC Trend。"),
|
||||
textReply("You have 1 strategy: BTC Trend."),
|
||||
}}
|
||||
a := New(port, "tok", "test-user", mockGetLLM(llm), testPrompt)
|
||||
reply := a.Run("查询我的策略", nil)
|
||||
reply := a.Run("look up my strategies", nil)
|
||||
|
||||
if strings.Contains(reply, "现在我将") {
|
||||
if strings.Contains(reply, "Now I will") {
|
||||
t.Fatalf("narration leaked into final reply: %q", reply)
|
||||
}
|
||||
if reply != "你有1个策略:BTC Trend。" {
|
||||
if reply != "You have 1 strategy: BTC Trend." {
|
||||
t.Fatalf("unexpected reply: %q", reply)
|
||||
}
|
||||
}
|
||||
@@ -276,18 +276,18 @@ func TestOnChunkCalledWithFinalReply(t *testing.T) {
|
||||
// Verifies: POST strategy → GET verify → final reply shows strategy info.
|
||||
func TestCreateStrategyWorkflow(t *testing.T) {
|
||||
srv, port := mockAPIServer(map[string]string{
|
||||
"POST /api/strategies": `{"id":"s1","name":"BTC趋势"}`,
|
||||
"GET /api/strategies/s1": `{"id":"s1","name":"BTC趋势","config":{"coin_source":{"source_type":"static","static_coins":["BTC/USDT"]},"leverage":5}}`,
|
||||
"POST /api/strategies": `{"id":"s1","name":"BTC Trend"}`,
|
||||
"GET /api/strategies/s1": `{"id":"s1","name":"BTC Trend","config":{"coin_source":{"source_type":"static","static_coins":["BTC/USDT"]},"leverage":5}}`,
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
llm := &mockLLM{responses: []*mcp.LLMResponse{
|
||||
toolCall("c1", "POST", "/api/strategies", `{"name":"BTC趋势","config":{}}`),
|
||||
toolCall("c1", "POST", "/api/strategies", `{"name":"BTC Trend","config":{}}`),
|
||||
toolCall("c2", "GET", "/api/strategies/s1", "{}"),
|
||||
textReply("策略已创建:BTC趋势,币种 BTC/USDT,杠杆 5x。"),
|
||||
textReply("Strategy created: BTC Trend, coin BTC/USDT, leverage 5x."),
|
||||
}}
|
||||
a := New(port, "tok", "test-user", mockGetLLM(llm), testPrompt)
|
||||
reply := a.Run("帮我配置个btc趋势交易的策略", nil)
|
||||
reply := a.Run("set up a BTC trend trading strategy for me", nil)
|
||||
|
||||
if llm.calls != 3 {
|
||||
t.Fatalf("expected 3 LLM calls, got %d", llm.calls)
|
||||
@@ -298,7 +298,7 @@ func TestCreateStrategyWorkflow(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestFullSetupWorkflow: create strategy → verify → create trader → start trader.
|
||||
// This is the "帮我配置策略并跑起来" workflow.
|
||||
// This is the "create strategy and start it" workflow.
|
||||
func TestFullSetupWorkflow(t *testing.T) {
|
||||
calls := map[string]int{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -306,11 +306,11 @@ func TestFullSetupWorkflow(t *testing.T) {
|
||||
calls[key]++
|
||||
switch key {
|
||||
case "POST /api/strategies":
|
||||
w.Write([]byte(`{"id":"s1","name":"BTC趋势"}`)) //nolint:errcheck
|
||||
w.Write([]byte(`{"id":"s1","name":"BTC Trend"}`)) //nolint:errcheck
|
||||
case "GET /api/strategies/s1":
|
||||
w.Write([]byte(`{"id":"s1","name":"BTC趋势","config":{}}`)) //nolint:errcheck
|
||||
w.Write([]byte(`{"id":"s1","name":"BTC Trend","config":{}}`)) //nolint:errcheck
|
||||
case "POST /api/traders":
|
||||
w.Write([]byte(`{"id":"tr1","name":"BTC趋势交易员"}`)) //nolint:errcheck
|
||||
w.Write([]byte(`{"id":"tr1","name":"BTC Trend Trader"}`)) //nolint:errcheck
|
||||
case "POST /api/traders/tr1/start":
|
||||
w.Write([]byte(`{"ok":true}`)) //nolint:errcheck
|
||||
default:
|
||||
@@ -322,14 +322,14 @@ func TestFullSetupWorkflow(t *testing.T) {
|
||||
fmt.Sscanf(srv.Listener.Addr().String(), "127.0.0.1:%d", &port)
|
||||
|
||||
llm := &mockLLM{responses: []*mcp.LLMResponse{
|
||||
toolCall("c1", "POST", "/api/strategies", `{"name":"BTC趋势"}`),
|
||||
toolCall("c1", "POST", "/api/strategies", `{"name":"BTC Trend"}`),
|
||||
toolCall("c2", "GET", "/api/strategies/s1", "{}"),
|
||||
toolCall("c3", "POST", "/api/traders", `{"name":"BTC趋势交易员","strategy_id":"s1"}`),
|
||||
toolCall("c3", "POST", "/api/traders", `{"name":"BTC Trend Trader","strategy_id":"s1"}`),
|
||||
toolCall("c4", "POST", "/api/traders/tr1/start", "{}"),
|
||||
textReply("策略和交易员已创建并启动!BTC趋势交易员正在运行。"),
|
||||
textReply("Strategy and trader created and started! BTC Trend Trader is running."),
|
||||
}}
|
||||
a := New(port, "tok", "test-user", mockGetLLM(llm), testPrompt)
|
||||
reply := a.Run("帮我配置个btc趋势交易的策略交易 跑起来", nil)
|
||||
reply := a.Run("set up a BTC trend trading strategy and start it", nil)
|
||||
|
||||
if llm.calls != 5 {
|
||||
t.Fatalf("expected 5 LLM calls, got %d", llm.calls)
|
||||
@@ -370,15 +370,15 @@ func TestStartExistingTrader(t *testing.T) {
|
||||
llm := &mockLLM{responses: []*mcp.LLMResponse{
|
||||
toolCall("c1", "GET", "/api/my-traders", "{}"),
|
||||
toolCall("c2", "POST", "/api/traders/tr1/start", "{}"),
|
||||
textReply("交易员 BTC Trader 已启动。"),
|
||||
textReply("Trader BTC Trader has been started."),
|
||||
}}
|
||||
a := New(port, "tok", "test-user", mockGetLLM(llm), testPrompt)
|
||||
reply := a.Run("启动交易员", nil)
|
||||
reply := a.Run("start the trader", nil)
|
||||
|
||||
if calls["POST /api/traders/tr1/start"] != 1 {
|
||||
t.Errorf("expected trader to be started, got %d start calls", calls["POST /api/traders/tr1/start"])
|
||||
}
|
||||
if reply != "交易员 BTC Trader 已启动。" {
|
||||
if reply != "Trader BTC Trader has been started." {
|
||||
t.Fatalf("unexpected reply: %q", reply)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (m *Manager) Run(chatID int64, userMessage string, onChunk func(string)) st
|
||||
case lane <- struct{}{}:
|
||||
case <-time.After(60 * time.Second):
|
||||
logger.Warnf("Agent: lane wait timeout for chat %d — previous message still processing", chatID)
|
||||
return "Previous message is still being processed. Please wait a moment and try again. / 上一条消息仍在处理中,请稍等片刻后再试。"
|
||||
return "Previous message is still being processed. Please wait a moment and try again."
|
||||
}
|
||||
defer func() { <-lane }()
|
||||
return a.Run(userMessage, onChunk)
|
||||
|
||||
@@ -36,7 +36,7 @@ The Account State block at the start of this conversation lists every resource w
|
||||
Read the id field from there and copy it verbatim — do not abbreviate, shorten, or guess.
|
||||
|
||||
## Behavior Rules
|
||||
1. Reply in the same language the user used (中文→中文, English→English)
|
||||
1. Reply in the same language the user used (match the user's language exactly)
|
||||
2. Keep final replies concise — show results, not process
|
||||
3. Ask for ALL missing required info in ONE message — never ask one field at a time
|
||||
4. When user provides enough info, act immediately — no confirmation needed
|
||||
@@ -77,7 +77,7 @@ Use this to:
|
||||
- Only include "config" when user explicitly requests custom settings (specific coins, custom leverage, different timeframes).
|
||||
- After POST: GET /api/strategies/:id to verify → show user: name, coin_source.source_type, key risk_control values
|
||||
|
||||
**"帮我配置策略并跑起来" / "create strategy and start" (full setup workflow)**:
|
||||
**"create strategy and start" (full setup workflow)**:
|
||||
Execute these steps IN ORDER with NO user confirmation between them:
|
||||
1. POST /api/strategies — body: {"name":"<descriptive name>"} — no config needed, defaults are complete
|
||||
2. GET /api/strategies/:id — verify strategy was saved
|
||||
|
||||
Reference in New Issue
Block a user