bugfix/ fix delete AI Model issue (#594)

* fix: 修复删除AI模型/交易所后UI未刷新的问题

问题描述:
在配置界面删除AI模型或交易所后,虽然后端数据已更新,但前端UI仍然显示已删除的配置项。

根本原因:
React的状态更新机制可能无法检测到数组内容的变化,特别是当API返回的数据与之前的引用相同时。

修复方案:
在 handleDeleteModelConfig 和 handleDeleteExchangeConfig 中使用数组展开运算符 [...items] 创建新数组,确保React能够检测到状态变化并触发重新渲染。

修改文件:
- web/src/components/AITradersPage.tsx

影响范围:
- AI模型删除功能
- 交易所删除功能

Fixes #591

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

* fix: 删除重复的确认对话框

问题描述:
删除AI模型或交易所时,确认对话框会弹出两次

根本原因:
1. ModelConfigModal 的删除按钮 onClick 中有一个 confirm
2. handleDeleteConfig 函数内部也有一个 confirm

修复方案:
移除 Modal 组件中的 confirm,保留 handleDeleteConfig 内部的确认逻辑,因为它包含了更完整的依赖检查功能

修改内容:
- 移除 ModelConfigModal 删除按钮中的 confirm
- 移除 ExchangeConfigModal 删除按钮中的 confirm
- 更新 title 属性为更合适的翻译键

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

Co-Authored-By: tinkle-community <tinklefund@gmail.com>

---------

Co-authored-by: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
Ember
2025-11-06 12:25:25 +08:00
committed by GitHub
parent 80719f382a
commit 506e9c5277

View File

@@ -132,19 +132,21 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
}, [user, token])
// 只显示已配置的模型和交易所有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') {
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() !== ''
}
// 其他交易所检查 apiKey
return e.apiKey && e.apiKey.trim() !== ''
}) || []
}) || []
// 只在创建交易员时使用已启用且配置完整的
const enabledModels = allModels?.filter((m) => m.enabled && m.apiKey) || []
@@ -185,9 +187,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
// 检查交易所是否正在被运行中的交易员使用用于UI禁用
const isExchangeInUse = (exchangeId: string) => {
return (
traders?.some((t) => t.exchange_id === exchangeId && t.is_running)
)
return traders?.some((t) => t.exchange_id === exchangeId && t.is_running)
}
// 检查模型是否被任何交易员使用(包括停止状态的)
@@ -414,7 +414,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
}),
updateApi: api.updateModelConfigs,
refreshApi: api.getModelConfigs,
setItems: setAllModels,
setItems: (items) => {
// 使用函数式更新确保状态正确更新
setAllModels([...items])
},
closeModal: () => {
setShowModelModal(false)
setEditingModel(null)
@@ -526,7 +529,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
}),
updateApi: api.updateExchangeConfigs,
refreshApi: api.getExchangeConfigs,
setItems: setAllExchanges,
setItems: (items) => {
// 使用函数式更新确保状态正确更新
setAllExchanges([...items])
},
closeModal: () => {
setShowExchangeModal(false)
setEditingExchange(null)
@@ -1442,14 +1448,10 @@ function ModelConfigModal({
{editingModelId && (
<button
type="button"
onClick={() => {
if (confirm(t('confirmDeleteModel', language))) {
onDelete(editingModelId)
}
}}
onClick={() => onDelete(editingModelId)}
className="p-2 rounded hover:bg-red-100 transition-colors"
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
title={t('deleteConfigFailed', language)}
title={t('delete', language)}
>
<Trash2 className="w-4 h-4" />
</button>
@@ -1801,17 +1803,13 @@ function ExchangeConfigModal({
{editingExchangeId && (
<button
type="button"
onClick={() => {
if (confirm(t('confirmDeleteExchange', language))) {
onDelete(editingExchangeId)
}
}}
onClick={() => onDelete(editingExchangeId)}
className="p-2 rounded hover:bg-red-100 transition-colors"
style={{
background: 'rgba(246, 70, 93, 0.1)',
color: '#F6465D',
}}
title={t('deleteConfigFailed', language)}
title={t('delete', language)}
>
<Trash2 className="w-4 h-4" />
</button>