diff --git a/web/src/components/traders/ExchangeConfigModal.tsx b/web/src/components/traders/ExchangeConfigModal.tsx
index 80f944c3..57631499 100644
--- a/web/src/components/traders/ExchangeConfigModal.tsx
+++ b/web/src/components/traders/ExchangeConfigModal.tsx
@@ -84,6 +84,9 @@ export function ExchangeConfigModal({
null | 'hyperliquid' | 'aster' | 'lighter'
>(null)
+ // 保存中状态
+ const [isSaving, setIsSaving] = useState(false)
+
// 获取当前编辑的交易所信息
const selectedExchange = allExchanges?.find(
(e) => e.id === selectedExchangeId
@@ -218,59 +221,64 @@ export function ExchangeConfigModal({
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
- if (!selectedExchangeId) return
+ if (!selectedExchangeId || isSaving) return
- // 根据交易所类型验证不同字段
- if (selectedExchange?.id === 'binance') {
- if (!apiKey.trim() || !secretKey.trim()) return
- await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet)
- } else if (selectedExchange?.id === 'okx') {
- if (!apiKey.trim() || !secretKey.trim() || !passphrase.trim()) return
- await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), passphrase.trim(), testnet)
- } else if (selectedExchange?.id === 'hyperliquid') {
- if (!apiKey.trim() || !hyperliquidWalletAddr.trim()) return // 验证私钥和钱包地址
- await onSave(
- selectedExchangeId,
- apiKey.trim(),
- '',
- '',
- testnet,
- hyperliquidWalletAddr.trim()
- )
- } else if (selectedExchange?.id === 'aster') {
- if (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim())
- return
- await onSave(
- selectedExchangeId,
- '',
- '',
- '',
- testnet,
- undefined,
- asterUser.trim(),
- asterSigner.trim(),
- asterPrivateKey.trim()
- )
- } else if (selectedExchange?.id === 'lighter') {
- if (!lighterWalletAddr.trim() || !lighterPrivateKey.trim()) return
- await onSave(
- selectedExchangeId,
- lighterPrivateKey.trim(),
- '',
- '',
- testnet,
- lighterWalletAddr.trim(),
- undefined,
- undefined,
- undefined,
- lighterWalletAddr.trim(),
- lighterPrivateKey.trim(),
- lighterApiKeyPrivateKey.trim()
- )
- } else {
- // 默认情况(其他CEX交易所)
- if (!apiKey.trim() || !secretKey.trim()) return
- await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet)
+ setIsSaving(true)
+ try {
+ // 根据交易所类型验证不同字段
+ if (selectedExchange?.id === 'binance') {
+ if (!apiKey.trim() || !secretKey.trim()) return
+ await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet)
+ } else if (selectedExchange?.id === 'okx') {
+ if (!apiKey.trim() || !secretKey.trim() || !passphrase.trim()) return
+ await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), passphrase.trim(), testnet)
+ } else if (selectedExchange?.id === 'hyperliquid') {
+ if (!apiKey.trim() || !hyperliquidWalletAddr.trim()) return // 验证私钥和钱包地址
+ await onSave(
+ selectedExchangeId,
+ apiKey.trim(),
+ '',
+ '',
+ testnet,
+ hyperliquidWalletAddr.trim()
+ )
+ } else if (selectedExchange?.id === 'aster') {
+ if (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim())
+ return
+ await onSave(
+ selectedExchangeId,
+ '',
+ '',
+ '',
+ testnet,
+ undefined,
+ asterUser.trim(),
+ asterSigner.trim(),
+ asterPrivateKey.trim()
+ )
+ } else if (selectedExchange?.id === 'lighter') {
+ if (!lighterWalletAddr.trim() || !lighterPrivateKey.trim()) return
+ await onSave(
+ selectedExchangeId,
+ lighterPrivateKey.trim(),
+ '',
+ '',
+ testnet,
+ lighterWalletAddr.trim(),
+ undefined,
+ undefined,
+ undefined,
+ lighterWalletAddr.trim(),
+ lighterPrivateKey.trim(),
+ lighterApiKeyPrivateKey.trim()
+ )
+ } else {
+ // 默认情况(其他CEX交易所)
+ if (!apiKey.trim() || !secretKey.trim()) return
+ await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet)
+ }
+ } finally {
+ setIsSaving(false)
}
}
@@ -1000,6 +1008,7 @@ export function ExchangeConfigModal({
diff --git a/web/src/components/traders/ModelConfigModal.tsx b/web/src/components/traders/ModelConfigModal.tsx
index 86297ba5..851b7538 100644
--- a/web/src/components/traders/ModelConfigModal.tsx
+++ b/web/src/components/traders/ModelConfigModal.tsx
@@ -14,8 +14,8 @@ interface ModelConfigModalProps {
apiKey: string,
baseUrl?: string,
modelName?: string
- ) => void
- onDelete: (modelId: string) => void
+ ) => Promise
+ onDelete: (modelId: string) => Promise
onClose: () => void
language: Language
}
@@ -48,16 +48,23 @@ export function ModelConfigModal({
}
}, [editingModelId, selectedModel])
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault()
- if (!selectedModelId || !apiKey.trim()) return
+ const [isSaving, setIsSaving] = useState(false)
- onSave(
- selectedModelId,
- apiKey.trim(),
- baseUrl.trim() || undefined,
- modelName.trim() || undefined
- )
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!selectedModelId || !apiKey.trim() || isSaving) return
+
+ setIsSaving(true)
+ try {
+ await onSave(
+ selectedModelId,
+ apiKey.trim(),
+ baseUrl.trim() || undefined,
+ modelName.trim() || undefined
+ )
+ } finally {
+ setIsSaving(false)
+ }
}
// 可选择的模型列表(所有支持的模型)
@@ -277,11 +284,11 @@ export function ModelConfigModal({
diff --git a/web/src/pages/StrategyStudioPage.tsx b/web/src/pages/StrategyStudioPage.tsx
index 783dd035..d83241d6 100644
--- a/web/src/pages/StrategyStudioPage.tsx
+++ b/web/src/pages/StrategyStudioPage.tsx
@@ -168,7 +168,25 @@ export function StrategyStudioPage() {
}),
})
if (!response.ok) throw new Error('Failed to create strategy')
+ const result = await response.json()
await fetchStrategies()
+ // Auto-select the newly created strategy
+ if (result.id) {
+ const now = new Date().toISOString()
+ const newStrategy = {
+ id: result.id,
+ name: language === 'zh' ? '新策略' : 'New Strategy',
+ description: '',
+ is_active: false,
+ is_default: false,
+ config: defaultConfig,
+ created_at: now,
+ updated_at: now,
+ }
+ setSelectedStrategy(newStrategy)
+ setEditingConfig(defaultConfig)
+ setHasChanges(false)
+ }
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
}
@@ -195,6 +213,12 @@ export function StrategyStudioPage() {
})
if (!response.ok) throw new Error('Failed to delete strategy')
notify.success(language === 'zh' ? '策略已删除' : 'Strategy deleted')
+ // Clear selection if deleted strategy was selected
+ if (selectedStrategy?.id === id) {
+ setSelectedStrategy(null)
+ setEditingConfig(null)
+ setHasChanges(false)
+ }
await fetchStrategies()
} catch (err) {
const errorMsg = err instanceof Error ? err.message : 'Unknown error'