From 54744309dd24cb928de56d2c16692739c20d3cfb Mon Sep 17 00:00:00 2001 From: Diego <45224689+tangmengqiu@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:32:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B/=E4=BA=A4=E6=98=93=E6=89=80=E6=97=B6?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E5=8D=A1=E6=AD=BB=E9=97=AE=E9=A2=98=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E4=BE=9D=E8=B5=96=E6=A3=80=E6=9F=A5=20(#578)?= =?UTF-8?q?=20*=20fix:=20=E4=BF=AE=E5=A4=8D=E5=88=A0=E9=99=A4=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B/=E4=BA=A4=E6=98=93=E6=89=80=E6=97=B6=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=8D=A1=E6=AD=BB=E9=97=AE=E9=A2=98=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E4=BE=9D=E8=B5=96=E6=A3=80=E6=9F=A5=20##=20=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E6=8F=8F=E8=BF=B0=201.=20=E5=88=A0=E9=99=A4=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E7=9A=84AI=E6=A8=A1=E5=9E=8B=E6=88=96=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E6=89=80=E9=85=8D=E7=BD=AE=E6=97=B6=EF=BC=8C=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E4=BC=9A=E5=8D=A1=E6=AD=BB=E6=95=B0=E7=A7=92=202.=20?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=90=8E=E9=85=8D=E7=BD=AE=E4=BB=8D=E7=84=B6?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=9C=A8=E5=88=97=E8=A1=A8=E4=B8=AD=203.=20?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=88=A0=E9=99=A4=E8=A2=AB=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E5=91=98=E4=BD=BF=E7=94=A8=E7=9A=84=E9=85=8D=E7=BD=AE=EF=BC=8C?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=95=B0=E6=8D=AE=E4=B8=8D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=20##=20=E4=BF=AE=E5=A4=8D=E5=86=85=E5=AE=B9=20###=20=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=20(manager/trader?= =?UTF-8?q?=5Fmanager.go)=20-=20=E5=B0=86=E5=BE=AA=E7=8E=AF=E5=86=85?= =?UTF-8?q?=E7=9A=84=E9=87=8D=E5=A4=8D=E6=95=B0=E6=8D=AE=E5=BA=93=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E7=A7=BB=E5=88=B0=E5=BE=AA=E7=8E=AF=E5=A4=96=20-=20?= =?UTF-8?q?=E5=87=8F=E5=B0=91N=E6=AC=A1=E9=87=8D=E5=A4=8D=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=EF=BC=88GetAIModels=20+=20GetExchanges=EF=BC=89?= =?UTF-8?q?=E4=B8=BA1=E6=AC=A1=E6=9F=A5=E8=AF=A2=20-=20=E5=A4=A7=E5=B9=85?= =?UTF-8?q?=E5=87=8F=E5=B0=91=E9=94=81=E6=8C=81=E6=9C=89=E6=97=B6=E9=97=B4?= =?UTF-8?q?=EF=BC=8C=E4=BB=8E=E6=95=B0=E7=A7=92=E9=99=8D=E8=87=B3=E6=AF=AB?= =?UTF-8?q?=E7=A7=92=E7=BA=A7=20###=20=E5=89=8D=E7=AB=AF=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20(web/src/components/AITradersPage.tsx)=20-?= =?UTF-8?q?=20=E8=BF=87=E6=BB=A4=E6=98=BE=E7=A4=BA=E5=88=97=E8=A1=A8?= =?UTF-8?q?=EF=BC=8C=E5=8F=AA=E6=98=BE=E7=A4=BA=E7=9C=9F=E6=AD=A3=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=BF=87=E7=9A=84=E6=A8=A1=E5=9E=8B/=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E6=89=80=EF=BC=88=E6=9C=89apiKey=E7=9A=84=EF=BC=89=20?= =?UTF-8?q?-=20=E5=88=A0=E9=99=A4=E5=90=8E=E9=87=8D=E6=96=B0=E4=BB=8E?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E8=8E=B7=E5=8F=96=E6=9C=80=E6=96=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=95=8C=E9=9D=A2=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=20###=20=E5=89=8D=E7=AB=AF=E4=BE=9D=E8=B5=96=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=20(web/src/components/AITradersPage.tsx)=20-=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AE=8C=E6=95=B4=E7=9A=84=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=EF=BC=8C=E5=8C=85=E6=8B=AC=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=9A=84=E4=BA=A4=E6=98=93=E5=91=98=20-=20?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=89=8D=E6=A3=80=E6=9F=A5=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E6=9C=89=E4=BA=A4=E6=98=93=E5=91=98=E4=BD=BF=E7=94=A8=E8=AF=A5?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=20-=20=E6=98=BE=E7=A4=BA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=AF=A5=E9=85=8D=E7=BD=AE=E7=9A=84=E4=BA=A4=E6=98=93=E5=91=98?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E5=88=97=E8=A1=A8=20-=20=E9=98=BB=E6=AD=A2?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E8=A2=AB=E4=BD=BF=E7=94=A8=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E4=BF=9D=E8=AF=81=E6=95=B0=E6=8D=AE=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=20###=20=E5=A4=9A=E8=AF=AD=E8=A8=80=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20(web/src/i18n/translations.ts)=20-=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BE=9D=E8=B5=96=E6=A3=80=E6=9F=A5=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E4=B8=AD=E8=8B=B1=E6=96=87=E6=8F=90=E7=A4=BA=E6=96=87?= =?UTF-8?q?=E6=9C=AC=20-=20cannotDeleteModelInUse=20/=20cannotDeleteExchan?= =?UTF-8?q?geInUse=20-=20tradersUsing=20/=20pleaseDeleteTradersFirst=20##?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E5=BB=BA=E8=AE=AE=201.=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E4=BA=A4=E6=98=93=E5=91=98=E5=90=8E=E5=B0=9D=E8=AF=95?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=85=B6=E4=BD=BF=E7=94=A8=E7=9A=84=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B/=E4=BA=A4=E6=98=93=E6=89=80=EF=BC=8C=E5=BA=94?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E8=AD=A6=E5=91=8A=E5=B9=B6=E9=98=BB=E6=AD=A2?= =?UTF-8?q?=E5=88=A0=E9=99=A4=202.=20=E5=88=A0=E9=99=A4=E6=9C=AA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=A8=A1=E5=9E=8B/=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E6=89=80=EF=BC=8C=E5=BA=94=E7=AB=8B=E5=8D=B3=E4=BB=8E=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=B6=88=E5=A4=B1=E4=B8=94=E7=95=8C=E9=9D=A2=E4=B8=8D?= =?UTF-8?q?=E5=8D=A1=E6=AD=BB=203.=20=E5=88=B7=E6=96=B0=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E5=B7=B2=E5=88=A0=E9=99=A4=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=B8=8D=E5=BA=94=E5=86=8D=E5=87=BA=E7=8E=B0=20Co-Aut?= =?UTF-8?q?hored-By:=20tinkle-community=20=20*=20ref?= =?UTF-8?q?actor:=20=E9=87=8D=E6=9E=84=E5=88=A0=E9=99=A4=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=87=8F=E5=B0=91=E9=87=8D=E5=A4=8D=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20##=20=E9=87=8D=E6=9E=84=E5=86=85=E5=AE=B9=20-=20?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E9=80=9A=E7=94=A8=E7=9A=84=20handleDeleteCon?= =?UTF-8?q?fig=20=E5=87=BD=E6=95=B0=20-=20=E4=BD=BF=E7=94=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=AF=B9=E8=B1=A1=E6=A8=A1=E5=BC=8F=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=92=8C=E4=BA=A4=E6=98=93=E6=89=80=E7=9A=84?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=80=BB=E8=BE=91=20-=20=E6=B6=88=E9=99=A4?= =?UTF-8?q?=20handleDeleteModelConfig=20=E5=92=8C=20handleDeleteExchangeCo?= =?UTF-8?q?nfig=20=E4=B9=8B=E9=97=B4=E7=9A=84=E9=87=8D=E5=A4=8D=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20##=20=E9=87=8D=E6=9E=84=E6=95=88=E6=9E=9C=20-=20?= =?UTF-8?q?=E5=87=8F=E5=B0=91=E4=BB=A3=E7=A0=81=E8=A1=8C=E6=95=B0=E7=BA=A6?= =?UTF-8?q?=2040%=20-=20=E6=8F=90=E9=AB=98=E4=BB=A3=E7=A0=81=E5=8F=AF?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=80=A7=E5=92=8C=E5=8F=AF=E8=AF=BB=E6=80=A7?= =?UTF-8?q?=20-=20=E4=BE=BF=E4=BA=8E=E6=9C=AA=E6=9D=A5=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E9=85=8D=E7=BD=AE=E7=B1=BB=E5=9E=8B=20##=20?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=BF=9D=E6=8C=81=E4=B8=8D=E5=8F=98=20-=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91=E5=AE=8C?= =?UTF-8?q?=E5=85=A8=E7=9B=B8=E5=90=8C=20-=20=E5=88=A0=E9=99=A4=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=AE=8C=E5=85=A8=E7=9B=B8=E5=90=8C=20-=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=E5=AE=8C=E5=85=A8=E7=9B=B8=E5=90=8C?= =?UTF-8?q?=20Co-Authored-By:=20tinkle-community=20?= =?UTF-8?q?=20---------=20Co-authored-by:=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manager/trader_manager.go | 31 +++-- web/src/components/AITradersPage.tsx | 200 +++++++++++++++++++-------- web/src/i18n/translations.ts | 11 ++ 3 files changed, 172 insertions(+), 70 deletions(-) diff --git a/manager/trader_manager.go b/manager/trader_manager.go index e53c96b0..3585e963 100644 --- a/manager/trader_manager.go +++ b/manager/trader_manager.go @@ -762,7 +762,21 @@ func (tm *TraderManager) LoadUserTraders(database *config.Database, userID strin } } - // 为每个交易员获取AI模型和交易所配置 + // 🔧 性能优化:在循环外只查询一次AI模型和交易所配置 + // 避免在循环中重复查询相同的数据,减少数据库压力和锁持有时间 + aiModels, err := database.GetAIModels(userID) + if err != nil { + log.Printf("⚠️ 获取用户 %s 的AI模型配置失败: %v", userID, err) + return fmt.Errorf("获取AI模型配置失败: %w", err) + } + + exchanges, err := database.GetExchanges(userID) + if err != nil { + log.Printf("⚠️ 获取用户 %s 的交易所配置失败: %v", userID, err) + return fmt.Errorf("获取交易所配置失败: %w", err) + } + + // 为每个交易员加载配置 for _, traderCfg := range traders { // 检查是否已经加载过这个交易员 if _, exists := tm.traders[traderCfg.ID]; exists { @@ -770,12 +784,7 @@ func (tm *TraderManager) LoadUserTraders(database *config.Database, userID strin continue } - // 获取AI模型配置(使用该用户的配置) - aiModels, err := database.GetAIModels(userID) - if err != nil { - log.Printf("⚠️ 获取用户 %s 的AI模型配置失败: %v", userID, err) - continue - } + // 从已查询的列表中查找AI模型配置 var aiModelCfg *config.AIModelConfig // 优先精确匹配 model.ID(新版逻辑) @@ -806,13 +815,7 @@ func (tm *TraderManager) LoadUserTraders(database *config.Database, userID strin continue } - // 获取交易所配置(使用该用户的配置) - exchanges, err := database.GetExchanges(userID) - if err != nil { - log.Printf("⚠️ 获取用户 %s 的交易所配置失败: %v", userID, err) - continue - } - + // 从已查询的列表中查找交易所配置 var exchangeCfg *config.ExchangeConfig for _, exchange := range exchanges { if exchange.ID == traderCfg.ExchangeID { diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index 7f621115..74a2e9b6 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -131,9 +131,20 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { loadConfigs() }, [user, token]) - // 显示所有用户的模型和交易所配置(用于调试) - const configuredModels = allModels || [] - const configuredExchanges = allExchanges || [] + // 只显示已配置的模型和交易所(有API Key的才算配置过) + const configuredModels = allModels?.filter((m) => m.apiKey && m.apiKey.trim() !== '') || [] + const configuredExchanges = allExchanges?.filter((e) => { + // Aster 交易所检查特殊字段 + if (e.id === 'aster') { + return e.asterUser && e.asterUser.trim() !== '' + } + // Hyperliquid 只检查私钥 + if (e.id === 'hyperliquid') { + return e.apiKey && e.apiKey.trim() !== '' + } + // 其他交易所检查 apiKey + return e.apiKey && e.apiKey.trim() !== '' + }) || [] // 只在创建交易员时使用已启用且配置完整的 const enabledModels = allModels?.filter((m) => m.enabled && m.apiKey) || [] @@ -167,19 +178,38 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { ) }) || [] - // 检查模型是否正在被运行中的交易员使用 + // 检查模型是否正在被运行中的交易员使用(用于UI禁用) const isModelInUse = (modelId: string) => { - return traders?.some((t) => t.ai_model === modelId && t.is_running) || false + return traders?.some((t) => t.ai_model === modelId && t.is_running) } - // 检查交易所是否正在被运行中的交易员使用 + // 检查交易所是否正在被运行中的交易员使用(用于UI禁用) const isExchangeInUse = (exchangeId: string) => { return ( - traders?.some((t) => t.exchange_id === exchangeId && t.is_running) || - false + traders?.some((t) => t.exchange_id === exchangeId && t.is_running) ) } + // 检查模型是否被任何交易员使用(包括停止状态的) + const isModelUsedByAnyTrader = (modelId: string) => { + return traders?.some((t) => t.ai_model === modelId) || false + } + + // 检查交易所是否被任何交易员使用(包括停止状态的) + const isExchangeUsedByAnyTrader = (exchangeId: string) => { + return traders?.some((t) => t.exchange_id === exchangeId) || false + } + + // 获取使用特定模型的交易员列表 + const getTradersUsingModel = (modelId: string) => { + return traders?.filter((t) => t.ai_model === modelId) || [] + } + + // 获取使用特定交易所的交易员列表 + const getTradersUsingExchange = (exchangeId: string) => { + return traders?.filter((t) => t.exchange_id === exchangeId) || [] + } + const handleCreateTrader = async (data: CreateTraderRequest) => { try { const model = allModels?.find((m) => m.id === data.ai_model_id) @@ -298,27 +328,81 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { } } - const handleDeleteModelConfig = async (modelId: string) => { - if (!confirm(t('confirmDeleteModel', language))) return + // 通用删除配置处理函数 + const handleDeleteConfig = async (config: { + id: string + type: 'model' | 'exchange' + checkInUse: (id: string) => boolean + getUsingTraders: (id: string) => any[] + cannotDeleteKey: string + confirmDeleteKey: string + allItems: T[] | undefined + clearFields: (item: T) => T + buildRequest: (items: T[]) => any + updateApi: (request: any) => Promise + refreshApi: () => Promise + setItems: (items: T[]) => void + closeModal: () => void + errorKey: string + }) => { + // 检查是否有交易员正在使用 + if (config.checkInUse(config.id)) { + const usingTraders = config.getUsingTraders(config.id) + const traderNames = usingTraders.map((t) => t.trader_name).join(', ') + alert( + t(config.cannotDeleteKey, language) + + '\n\n' + + t('tradersUsing', language) + + ': ' + + traderNames + + '\n\n' + + t('pleaseDeleteTradersFirst', language) + ) + return + } + + if (!confirm(t(config.confirmDeleteKey, language))) return try { - const updatedModels = - allModels?.map((m) => - m.id === modelId - ? { - ...m, - apiKey: '', - customApiUrl: '', - customModelName: '', - enabled: false, - } - : m + const updatedItems = + config.allItems?.map((item) => + item.id === config.id ? config.clearFields(item) : item ) || [] - const request = { + const request = config.buildRequest(updatedItems) + await config.updateApi(request) + + // 重新获取用户配置以确保数据同步 + const refreshedItems = await config.refreshApi() + config.setItems(refreshedItems) + + config.closeModal() + } catch (error) { + console.error(`Failed to delete ${config.type} config:`, error) + alert(t(config.errorKey, language)) + } + } + + const handleDeleteModelConfig = async (modelId: string) => { + await handleDeleteConfig({ + id: modelId, + type: 'model', + checkInUse: isModelUsedByAnyTrader, + getUsingTraders: getTradersUsingModel, + cannotDeleteKey: 'cannotDeleteModelInUse', + confirmDeleteKey: 'confirmDeleteModel', + allItems: allModels, + clearFields: (m) => ({ + ...m, + apiKey: '', + customApiUrl: '', + customModelName: '', + enabled: false, + }), + buildRequest: (models) => ({ models: Object.fromEntries( - updatedModels.map((model) => [ - model.provider, // 使用 provider 而不是 id + models.map((model) => [ + model.provider, { enabled: model.enabled, api_key: model.apiKey || '', @@ -327,16 +411,16 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { }, ]) ), - } - - await api.updateModelConfigs(request) - setAllModels(updatedModels) - setShowModelModal(false) - setEditingModel(null) - } catch (error) { - console.error('Failed to delete model config:', error) - alert(t('deleteConfigFailed', language)) - } + }), + updateApi: api.updateModelConfigs, + refreshApi: api.getModelConfigs, + setItems: setAllModels, + closeModal: () => { + setShowModelModal(false) + setEditingModel(null) + }, + errorKey: 'deleteConfigFailed', + }) } const handleSaveModelConfig = async ( @@ -413,19 +497,23 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { } const handleDeleteExchangeConfig = async (exchangeId: string) => { - if (!confirm(t('confirmDeleteExchange', language))) return - - try { - const updatedExchanges = - allExchanges?.map((e) => - e.id === exchangeId - ? { ...e, apiKey: '', secretKey: '', enabled: false } - : e - ) || [] - - const request = { + await handleDeleteConfig({ + id: exchangeId, + type: 'exchange', + checkInUse: isExchangeUsedByAnyTrader, + getUsingTraders: getTradersUsingExchange, + cannotDeleteKey: 'cannotDeleteExchangeInUse', + confirmDeleteKey: 'confirmDeleteExchange', + allItems: allExchanges, + clearFields: (e) => ({ + ...e, + apiKey: '', + secretKey: '', + enabled: false, + }), + buildRequest: (exchanges) => ({ exchanges: Object.fromEntries( - updatedExchanges.map((exchange) => [ + exchanges.map((exchange) => [ exchange.id, { enabled: exchange.enabled, @@ -435,16 +523,16 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { }, ]) ), - } - - await api.updateExchangeConfigs(request) - setAllExchanges(updatedExchanges) - setShowExchangeModal(false) - setEditingExchange(null) - } catch (error) { - console.error('Failed to delete exchange config:', error) - alert(t('deleteExchangeConfigFailed', language)) - } + }), + updateApi: api.updateExchangeConfigs, + refreshApi: api.getExchangeConfigs, + setItems: setAllExchanges, + closeModal: () => { + setShowExchangeModal(false) + setEditingExchange(null) + }, + errorKey: 'deleteExchangeConfigFailed', + }) } const handleSaveExchangeConfig = async ( diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts index 5cc07c5e..c552e5ad 100644 --- a/web/src/i18n/translations.ts +++ b/web/src/i18n/translations.ts @@ -265,6 +265,11 @@ export const translations = { addAIModel: 'Add AI Model', confirmDeleteModel: 'Are you sure you want to delete this AI model configuration?', + cannotDeleteModelInUse: + 'Cannot delete this AI model because it is being used by traders', + tradersUsing: 'Traders using this configuration', + pleaseDeleteTradersFirst: + 'Please delete or reconfigure these traders first', selectModel: 'Select AI Model', pleaseSelectModel: 'Please select a model', customBaseURL: 'Base URL (Optional)', @@ -281,6 +286,8 @@ export const translations = { addExchange: 'Add Exchange', confirmDeleteExchange: 'Are you sure you want to delete this exchange configuration?', + cannotDeleteExchangeInUse: + 'Cannot delete this exchange because it is being used by traders', pleaseSelectExchange: 'Please select an exchange', exchangeConfigWarning1: '• API keys will be encrypted, recommend using read-only or futures trading permissions', @@ -929,6 +936,9 @@ export const translations = { editAIModel: '编辑AI模型', addAIModel: '添加AI模型', confirmDeleteModel: '确定要删除此AI模型配置吗?', + cannotDeleteModelInUse: '无法删除此AI模型,因为有交易员正在使用', + tradersUsing: '正在使用此配置的交易员', + pleaseDeleteTradersFirst: '请先删除或重新配置这些交易员', selectModel: '选择AI模型', pleaseSelectModel: '请选择模型', customBaseURL: 'Base URL (可选)', @@ -941,6 +951,7 @@ export const translations = { editExchange: '编辑交易所', addExchange: '添加交易所', confirmDeleteExchange: '确定要删除此交易所配置吗?', + cannotDeleteExchangeInUse: '无法删除此交易所,因为有交易员正在使用', pleaseSelectExchange: '请选择交易所', exchangeConfigWarning1: '• API密钥将被加密存储,建议使用只读或期货交易权限', exchangeConfigWarning2: '• 不要授予提现权限,确保资金安全',