diff --git a/store/ai_model.go b/store/ai_model.go index 37be8a54..b59498ed 100644 --- a/store/ai_model.go +++ b/store/ai_model.go @@ -204,16 +204,25 @@ func (s *AIModelStore) firstEnabled(userID string) (*AIModel, error) { } // Update updates AI model, creates if not exists +// IMPORTANT: If apiKey is empty string, the existing API key will be preserved (not overwritten) func (s *AIModelStore) Update(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error { // Try exact ID match first var existingID string err := s.db.QueryRow(`SELECT id FROM ai_models WHERE user_id = ? AND id = ? LIMIT 1`, userID, id).Scan(&existingID) if err == nil { - encryptedAPIKey := s.encrypt(apiKey) - _, err = s.db.Exec(` - UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now') - WHERE id = ? AND user_id = ? - `, enabled, encryptedAPIKey, customAPIURL, customModelName, existingID, userID) + // If apiKey is empty, preserve the existing API key + if apiKey == "" { + _, err = s.db.Exec(` + UPDATE ai_models SET enabled = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now') + WHERE id = ? AND user_id = ? + `, enabled, customAPIURL, customModelName, existingID, userID) + } else { + encryptedAPIKey := s.encrypt(apiKey) + _, err = s.db.Exec(` + UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now') + WHERE id = ? AND user_id = ? + `, enabled, encryptedAPIKey, customAPIURL, customModelName, existingID, userID) + } return err } @@ -222,11 +231,19 @@ func (s *AIModelStore) Update(userID, id string, enabled bool, apiKey, customAPI err = s.db.QueryRow(`SELECT id FROM ai_models WHERE user_id = ? AND provider = ? LIMIT 1`, userID, provider).Scan(&existingID) if err == nil { logger.Warnf("⚠️ Using legacy provider matching to update model: %s -> %s", provider, existingID) - encryptedAPIKey := s.encrypt(apiKey) - _, err = s.db.Exec(` - UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now') - WHERE id = ? AND user_id = ? - `, enabled, encryptedAPIKey, customAPIURL, customModelName, existingID, userID) + // If apiKey is empty, preserve the existing API key + if apiKey == "" { + _, err = s.db.Exec(` + UPDATE ai_models SET enabled = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now') + WHERE id = ? AND user_id = ? + `, enabled, customAPIURL, customModelName, existingID, userID) + } else { + encryptedAPIKey := s.encrypt(apiKey) + _, err = s.db.Exec(` + UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now') + WHERE id = ? AND user_id = ? + `, enabled, encryptedAPIKey, customAPIURL, customModelName, existingID, userID) + } return err } diff --git a/store/strategy.go b/store/strategy.go index 23432a40..6a58d599 100644 --- a/store/strategy.go +++ b/store/strategy.go @@ -191,7 +191,8 @@ func GetDefaultStrategyConfig(lang string) StrategyConfig { CoinPoolLimit: 30, CoinPoolAPIURL: "http://nofxaios.com:30006/api/ai500/list?auth=cm_568c67eae410d912c54c", UseOITop: false, - OITopLimit: 0, + OITopLimit: 20, + OITopAPIURL: "http://nofxaios.com:30006/api/oi/top-ranking?limit=20&duration=1h&auth=cm_568c67eae410d912c54c", }, Indicators: IndicatorConfig{ Klines: KlineConfig{ diff --git a/web/src/components/strategy/CoinSourceEditor.tsx b/web/src/components/strategy/CoinSourceEditor.tsx index 16117883..b6d046d5 100644 --- a/web/src/components/strategy/CoinSourceEditor.tsx +++ b/web/src/components/strategy/CoinSourceEditor.tsx @@ -2,6 +2,10 @@ import { useState } from 'react' import { Plus, X, Database, TrendingUp, List, Link, AlertCircle } from 'lucide-react' import type { CoinSourceConfig } from '../../types' +// Default API URLs for data sources +const DEFAULT_COIN_POOL_API_URL = 'http://nofxaios.com:30006/api/ai500/list?auth=cm_568c67eae410d912c54c' +const DEFAULT_OI_TOP_API_URL = 'http://nofxaios.com:30006/api/oi/top-ranking?limit=20&duration=1h&auth=cm_568c67eae410d912c54c' + interface CoinSourceEditorProps { config: CoinSourceConfig onChange: (config: CoinSourceConfig) => void @@ -49,6 +53,7 @@ export function CoinSourceEditor({ }, apiUrlRequired: { zh: '需要填写 API URL 才能获取数据', en: 'API URL required to fetch data' }, dataSourceConfig: { zh: '数据源配置', en: 'Data Source Configuration' }, + fillDefault: { zh: '填入默认', en: 'Fill Default' }, } return translations[key]?.[language] || key } @@ -228,9 +233,21 @@ export function CoinSourceEditor({ {config.use_coin_pool && (
- +
+ + {!disabled && !config.coin_pool_api_url && ( + + )} +
- +
+ + {!disabled && !config.oi_top_api_url && ( + + )} +
void @@ -54,6 +57,7 @@ export function IndicatorEditor({ quantData: { zh: '量化数据', en: 'Quant Data' }, quantDataDesc: { zh: '资金流向、持仓变化、价格变化(按币种查询)', en: 'Netflow, OI delta, price change (per coin)' }, quantDataUrl: { zh: '量化数据 API', en: 'Quant Data API' }, + fillDefault: { zh: '填入默认', en: 'Fill Default' }, } return translations[key]?.[language] || key } @@ -293,9 +297,21 @@ export function IndicatorEditor({ {/* API URL */} {config.enable_quant_data && (
- +
+ + {!disabled && !config.quant_data_api_url && ( + + )} +