mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 21:12:00 +08:00
* 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>
380 lines
11 KiB
TypeScript
380 lines
11 KiB
TypeScript
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()
|
||
},
|
||
}
|