Files
nofx/web/src/lib/api.ts
0xYYBB | ZYY | Bobo 5a0ffca9ac fix(ui): remove duplicate exchange configuration fields (Aster & Hyperliquid) (#921)
* fix(ui): remove duplicate Aster exchange form rendering

修復 Aster 交易所配置表單重複渲染問題。

Issue:
- Aster 表單代碼在 AITradersPage.tsx 中出現兩次(lines 2334 和 2559)
- 導致用戶界面顯示 6 個輸入欄位(應該是 3 個)
- 用戶體驗混亂

Fix:
- 刪除重複的 Aster 表單代碼塊(lines 2559-2710,共 153 行)
- 保留第一個表單塊(lines 2334-2419)
- 修復 prettier 格式問題

Result:
- Aster 配置現在正確顯示 3 個欄位:user, signer, private key
- Lint 檢查通過
- Hyperliquid Agent Wallet 翻譯已存在無需修改

Technical:
- 刪除了完全重複的 JSX 條件渲染塊
- 移除空白行以符合 prettier 規範

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ui): remove legacy Hyperliquid single private key field

修復 Hyperliquid 配置頁面顯示舊版私鑰欄位的問題。

Issue:
- Hyperliquid 配置同時顯示舊版和新版欄位
- 舊版:單一「私钥」欄位(不安全,已廢棄)
- 新版:「代理私钥」+「主钱包地址」(Agent Wallet 安全模式)
- 用戶看到重複的欄位配置,造成混淆

Root Cause:
- AITradersPage.tsx 存在兩個 Hyperliquid 條件渲染塊
- Lines 2302-2332: 舊版單私鑰模式(應刪除)
- Lines 2424-2557: 新版 Agent Wallet 模式(正確)

Fix:
- 刪除舊版 Hyperliquid 單私鑰欄位代碼塊(lines 2302-2332,共 32 行)
- 保留新版 Agent Wallet 配置(代理私鑰 + 主錢包地址)
- 移除 `t('privateKey')` 和 `t('hyperliquidPrivateKeyDesc')` 舊版翻譯引用

Result:
- Hyperliquid 配置現在只顯示正確的 Agent Wallet 欄位
- 安全提示 banner 正確顯示
- 用戶體驗改善,不再混淆

Technical Details:
- 新版使用 `apiKey` 儲存 Agent Private Key
- 新版使用 `hyperliquidWalletAddr` 儲存 Main Wallet Address
- 符合 Hyperliquid Agent Wallet 最佳安全實踐

Related:
- 之前已修復 Aster 重複渲染問題(commit 5462eba0)
- Hyperliquid 翻譯 key 已存在於 translations.ts (lines 206-216, 1017-1027)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(i18n): add missing Hyperliquid Agent Wallet translation keys

補充 Hyperliquid 代理錢包配置的翻譯文本,修復前端顯示 key 名稱的問題。

Changes:
- 新增 8 個英文翻譯 key (Agent Wallet 配置說明)
- 新增 8 個中文翻譯 key (代理錢包配置說明)
- 修正 Hyperliquid 配置頁面顯示問題(從顯示 key 名稱改為顯示翻譯文本)

Technical Details:
- hyperliquidAgentWalletTitle: Banner 標題
- hyperliquidAgentWalletDesc: 安全說明文字
- hyperliquidAgentPrivateKey: 代理私鑰欄位標籤
- hyperliquidMainWalletAddress: 主錢包地址欄位標籤
- 相應的 placeholder 和 description 文本

Related Issue: 用戶反饋前端顯示 key 名稱而非翻譯文本

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-11 20:59:57 -05:00

380 lines
11 KiB
TypeScript
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 type {
SystemStatus,
AccountInfo,
Position,
DecisionRecord,
Statistics,
TraderInfo,
AIModel,
Exchange,
CreateTraderRequest,
UpdateModelConfigRequest,
UpdateExchangeConfigRequest,
CompetitionData,
} from '../types'
import { CryptoService } from './crypto'
import { httpClient } from './httpClient'
const API_BASE = '/api'
// Helper function to get auth headers
function getAuthHeaders(): Record<string, string> {
const token = localStorage.getItem('auth_token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
return headers
}
export const api = {
// AI交易员管理接口
async getTraders(): Promise<TraderInfo[]> {
const res = await httpClient.get(`${API_BASE}/my-traders`, getAuthHeaders())
if (!res.ok) throw new Error('获取trader列表失败')
return res.json()
},
// 获取公开的交易员列表(无需认证)
async getPublicTraders(): Promise<any[]> {
const res = await httpClient.get(`${API_BASE}/traders`)
if (!res.ok) throw new Error('获取公开trader列表失败')
return res.json()
},
async createTrader(request: CreateTraderRequest): Promise<TraderInfo> {
const res = await httpClient.post(
`${API_BASE}/traders`,
request,
getAuthHeaders()
)
if (!res.ok) throw new Error('创建交易员失败')
return res.json()
},
async deleteTrader(traderId: string): Promise<void> {
const res = await httpClient.delete(
`${API_BASE}/traders/${traderId}`,
getAuthHeaders()
)
if (!res.ok) throw new Error('删除交易员失败')
},
async startTrader(traderId: string): Promise<void> {
const res = await httpClient.post(
`${API_BASE}/traders/${traderId}/start`,
undefined,
getAuthHeaders()
)
if (!res.ok) throw new Error('启动交易员失败')
},
async stopTrader(traderId: string): Promise<void> {
const res = await httpClient.post(
`${API_BASE}/traders/${traderId}/stop`,
undefined,
getAuthHeaders()
)
if (!res.ok) throw new Error('停止交易员失败')
},
async updateTraderPrompt(
traderId: string,
customPrompt: string
): Promise<void> {
const res = await httpClient.put(
`${API_BASE}/traders/${traderId}/prompt`,
{ custom_prompt: customPrompt },
getAuthHeaders()
)
if (!res.ok) throw new Error('更新自定义策略失败')
},
async getTraderConfig(traderId: string): Promise<any> {
const res = await httpClient.get(
`${API_BASE}/traders/${traderId}/config`,
getAuthHeaders()
)
if (!res.ok) throw new Error('获取交易员配置失败')
return res.json()
},
async updateTrader(
traderId: string,
request: CreateTraderRequest
): Promise<TraderInfo> {
const res = await httpClient.put(
`${API_BASE}/traders/${traderId}`,
request,
getAuthHeaders()
)
if (!res.ok) throw new Error('更新交易员失败')
return res.json()
},
// AI模型配置接口
async getModelConfigs(): Promise<AIModel[]> {
const res = await httpClient.get(`${API_BASE}/models`, getAuthHeaders())
if (!res.ok) throw new Error('获取模型配置失败')
return res.json()
},
// 获取系统支持的AI模型列表无需认证
async getSupportedModels(): Promise<AIModel[]> {
const res = await httpClient.get(`${API_BASE}/supported-models`)
if (!res.ok) throw new Error('获取支持的模型失败')
return res.json()
},
async updateModelConfigs(request: UpdateModelConfigRequest): Promise<void> {
// 获取RSA公钥
const publicKey = await CryptoService.fetchPublicKey()
// 初始化加密服务
await CryptoService.initialize(publicKey)
// 获取用户信息从localStorage或其他地方
const userId = localStorage.getItem('user_id') || ''
const sessionId = sessionStorage.getItem('session_id') || ''
// 加密敏感数据
const encryptedPayload = await CryptoService.encryptSensitiveData(
JSON.stringify(request),
userId,
sessionId
)
// 发送加密数据
const res = await httpClient.put(
`${API_BASE}/models`,
encryptedPayload,
getAuthHeaders()
)
if (!res.ok) throw new Error('更新模型配置失败')
},
// 交易所配置接口
async getExchangeConfigs(): Promise<Exchange[]> {
const res = await httpClient.get(`${API_BASE}/exchanges`, getAuthHeaders())
if (!res.ok) throw new Error('获取交易所配置失败')
return res.json()
},
// 获取系统支持的交易所列表(无需认证)
async getSupportedExchanges(): Promise<Exchange[]> {
const res = await httpClient.get(`${API_BASE}/supported-exchanges`)
if (!res.ok) throw new Error('获取支持的交易所失败')
return res.json()
},
async updateExchangeConfigs(
request: UpdateExchangeConfigRequest
): Promise<void> {
const res = await httpClient.put(
`${API_BASE}/exchanges`,
request,
getAuthHeaders()
)
if (!res.ok) throw new Error('更新交易所配置失败')
},
// 使用加密传输更新交易所配置
async updateExchangeConfigsEncrypted(
request: UpdateExchangeConfigRequest
): Promise<void> {
// 获取RSA公钥
const publicKey = await CryptoService.fetchPublicKey()
// 初始化加密服务
await CryptoService.initialize(publicKey)
// 获取用户信息从localStorage或其他地方
const userId = localStorage.getItem('user_id') || ''
const sessionId = sessionStorage.getItem('session_id') || ''
// 加密敏感数据
const encryptedPayload = await CryptoService.encryptSensitiveData(
JSON.stringify(request),
userId,
sessionId
)
// 发送加密数据
const res = await httpClient.put(
`${API_BASE}/exchanges`,
encryptedPayload,
getAuthHeaders()
)
if (!res.ok) throw new Error('更新交易所配置失败')
},
// 获取系统状态支持trader_id
async getStatus(traderId?: string): Promise<SystemStatus> {
const url = traderId
? `${API_BASE}/status?trader_id=${traderId}`
: `${API_BASE}/status`
const res = await httpClient.get(url, getAuthHeaders())
if (!res.ok) throw new Error('获取系统状态失败')
return res.json()
},
// 获取账户信息支持trader_id
async getAccount(traderId?: string): Promise<AccountInfo> {
const url = traderId
? `${API_BASE}/account?trader_id=${traderId}`
: `${API_BASE}/account`
const res = await httpClient.request(url, {
cache: 'no-store',
headers: {
...getAuthHeaders(),
'Cache-Control': 'no-cache',
},
})
if (!res.ok) throw new Error('获取账户信息失败')
const data = await res.json()
console.log('Account data fetched:', data)
return data
},
// 获取持仓列表支持trader_id
async getPositions(traderId?: string): Promise<Position[]> {
const url = traderId
? `${API_BASE}/positions?trader_id=${traderId}`
: `${API_BASE}/positions`
const res = await httpClient.get(url, getAuthHeaders())
if (!res.ok) throw new Error('获取持仓列表失败')
return res.json()
},
// 获取决策日志支持trader_id
async getDecisions(traderId?: string): Promise<DecisionRecord[]> {
const url = traderId
? `${API_BASE}/decisions?trader_id=${traderId}`
: `${API_BASE}/decisions`
const res = await httpClient.get(url, getAuthHeaders())
if (!res.ok) throw new Error('获取决策日志失败')
return res.json()
},
// 获取最新决策支持trader_id和limit参数
async getLatestDecisions(
traderId?: string,
limit: number = 5
): Promise<DecisionRecord[]> {
const params = new URLSearchParams()
if (traderId) {
params.append('trader_id', traderId)
}
params.append('limit', limit.toString())
const res = await httpClient.get(
`${API_BASE}/decisions/latest?${params}`,
getAuthHeaders()
)
if (!res.ok) throw new Error('获取最新决策失败')
return res.json()
},
// 获取统计信息支持trader_id
async getStatistics(traderId?: string): Promise<Statistics> {
const url = traderId
? `${API_BASE}/statistics?trader_id=${traderId}`
: `${API_BASE}/statistics`
const res = await httpClient.get(url, getAuthHeaders())
if (!res.ok) throw new Error('获取统计信息失败')
return res.json()
},
// 获取收益率历史数据支持trader_id
async getEquityHistory(traderId?: string): Promise<any[]> {
const url = traderId
? `${API_BASE}/equity-history?trader_id=${traderId}`
: `${API_BASE}/equity-history`
const res = await httpClient.get(url, getAuthHeaders())
if (!res.ok) throw new Error('获取历史数据失败')
return res.json()
},
// 批量获取多个交易员的历史数据(无需认证)
async getEquityHistoryBatch(traderIds: string[]): Promise<any> {
const res = await httpClient.post(`${API_BASE}/equity-history-batch`, {
trader_ids: traderIds,
})
if (!res.ok) throw new Error('获取批量历史数据失败')
return res.json()
},
// 获取前5名交易员数据无需认证
async getTopTraders(): Promise<any[]> {
const res = await httpClient.get(`${API_BASE}/top-traders`)
if (!res.ok) throw new Error('获取前5名交易员失败')
return res.json()
},
// 获取公开交易员配置(无需认证)
async getPublicTraderConfig(traderId: string): Promise<any> {
const res = await httpClient.get(`${API_BASE}/trader/${traderId}/config`)
if (!res.ok) throw new Error('获取公开交易员配置失败')
return res.json()
},
// 获取AI学习表现分析支持trader_id
async getPerformance(traderId?: string): Promise<any> {
const url = traderId
? `${API_BASE}/performance?trader_id=${traderId}`
: `${API_BASE}/performance`
const res = await httpClient.get(url, getAuthHeaders())
if (!res.ok) throw new Error('获取AI学习数据失败')
return res.json()
},
// 获取竞赛数据(无需认证)
async getCompetition(): Promise<CompetitionData> {
const res = await httpClient.get(`${API_BASE}/competition`)
if (!res.ok) throw new Error('获取竞赛数据失败')
return res.json()
},
// 用户信号源配置接口
async getUserSignalSource(): Promise<{
coin_pool_url: string
oi_top_url: string
}> {
const res = await httpClient.get(
`${API_BASE}/user/signal-sources`,
getAuthHeaders()
)
if (!res.ok) throw new Error('获取用户信号源配置失败')
return res.json()
},
async saveUserSignalSource(
coinPoolUrl: string,
oiTopUrl: string
): Promise<void> {
const res = await httpClient.post(
`${API_BASE}/user/signal-sources`,
{
coin_pool_url: coinPoolUrl,
oi_top_url: oiTopUrl,
},
getAuthHeaders()
)
if (!res.ok) throw new Error('保存用户信号源配置失败')
},
// 获取服务器IP需要认证用于白名单配置
async getServerIP(): Promise<{
public_ip: string
message: string
}> {
const res = await httpClient.get(`${API_BASE}/server-ip`, getAuthHeaders())
if (!res.ok) throw new Error('获取服务器IP失败')
return res.json()
},
}