Compare commits
11 Commits
7c1fe55a5f
...
20994cfb13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20994cfb13 | ||
|
|
bad5290fe2 | ||
|
|
daee0427e4 | ||
|
|
2a653afea1 | ||
| a37206ec97 | |||
| 5e5e2160b0 | |||
|
|
e8285599e8 | ||
| 0eb760fa31 | |||
| 805b897afa | |||
| 2988af9806 | |||
| 63023adcf3 |
@@ -1999,7 +1999,7 @@ class MCPAgentIntegrated:
|
||||
model=self.kimi_model,
|
||||
messages=messages,
|
||||
temperature=1.0, # Kimi 推荐
|
||||
max_tokens=8192, # 足够容纳 reasoning_content
|
||||
max_tokens=128000, # 足够容纳 reasoning_content
|
||||
)
|
||||
|
||||
choice = response.choices[0]
|
||||
@@ -2074,7 +2074,7 @@ class MCPAgentIntegrated:
|
||||
model=self.deepmoney_model,
|
||||
messages=messages,
|
||||
temperature=0.7,
|
||||
max_tokens=8192,
|
||||
max_tokens=32784,
|
||||
)
|
||||
|
||||
summary = response.choices[0].message.content
|
||||
@@ -2268,7 +2268,7 @@ class MCPAgentIntegrated:
|
||||
model="kimi-k2-turbo-preview", # 使用非思考模型,更快
|
||||
messages=messages,
|
||||
temperature=0.7,
|
||||
max_tokens=8192, # 增加 token 限制以支持图表配置
|
||||
max_tokens=128000, # 增加 token 限制以支持图表配置
|
||||
)
|
||||
|
||||
summary = response.choices[0].message.content
|
||||
@@ -2355,7 +2355,7 @@ class MCPAgentIntegrated:
|
||||
model=self.deepmoney_model,
|
||||
messages=messages,
|
||||
temperature=0.3,
|
||||
max_tokens=4096,
|
||||
max_tokens=32768,
|
||||
)
|
||||
|
||||
title = response.choices[0].message.content.strip()
|
||||
@@ -2450,7 +2450,7 @@ class MCPAgentIntegrated:
|
||||
model=planning_model,
|
||||
messages=messages,
|
||||
temperature=1.0,
|
||||
max_tokens=8192,
|
||||
max_tokens=32768,
|
||||
stream=True, # 启用流式输出
|
||||
)
|
||||
|
||||
@@ -2494,7 +2494,7 @@ class MCPAgentIntegrated:
|
||||
model=self.deepmoney_model,
|
||||
messages=messages,
|
||||
temperature=0.7,
|
||||
max_tokens=8192,
|
||||
max_tokens=32768,
|
||||
)
|
||||
|
||||
plan_content = fallback_response.choices[0].message.content
|
||||
@@ -2690,7 +2690,7 @@ class MCPAgentIntegrated:
|
||||
model="kimi-k2-turbo-preview",
|
||||
messages=messages,
|
||||
temperature=0.7,
|
||||
max_tokens=8192,
|
||||
max_tokens=32768,
|
||||
stream=True, # 启用流式输出
|
||||
)
|
||||
|
||||
@@ -2724,7 +2724,7 @@ class MCPAgentIntegrated:
|
||||
model=self.deepmoney_model,
|
||||
messages=messages,
|
||||
temperature=0.7,
|
||||
max_tokens=8192,
|
||||
max_tokens=32768,
|
||||
)
|
||||
|
||||
final_summary = fallback_response.choices[0].message.content
|
||||
@@ -3676,7 +3676,7 @@ async def stream_role_response(
|
||||
tool_choice="auto",
|
||||
stream=False, # 工具调用不使用流式
|
||||
temperature=0.7,
|
||||
max_tokens=8192, # 增大 token 限制以避免输出被截断
|
||||
max_tokens=32768, # 增大 token 限制以避免输出被截断
|
||||
)
|
||||
|
||||
assistant_message = response.choices[0].message
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// src/hooks/useWatchlist.js
|
||||
// 自选股管理自定义 Hook
|
||||
// 自选股管理自定义 Hook(导航栏专用,与 Redux 状态同步)
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { logger } from '../utils/logger';
|
||||
import { getApiBase } from '../utils/apiConfig';
|
||||
import { toggleWatchlist as toggleWatchlistAction, loadWatchlist } from '../store/slices/stockSlice';
|
||||
|
||||
const WATCHLIST_PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* 自选股管理 Hook
|
||||
* 提供自选股加载、分页、添加、移除等功能
|
||||
* 自选股管理 Hook(导航栏专用)
|
||||
* 提供自选股加载、分页、移除等功能
|
||||
* 监听 Redux 中的 watchlist 变化,自动刷新行情数据
|
||||
*
|
||||
* @returns {{
|
||||
* watchlistQuotes: Array,
|
||||
@@ -19,6 +22,7 @@ const WATCHLIST_PAGE_SIZE = 10;
|
||||
* setWatchlistPage: Function,
|
||||
* WATCHLIST_PAGE_SIZE: number,
|
||||
* loadWatchlistQuotes: Function,
|
||||
* followingEvents: Array,
|
||||
* handleAddToWatchlist: Function,
|
||||
* handleRemoveFromWatchlist: Function,
|
||||
* isInWatchlist: Function
|
||||
@@ -26,9 +30,31 @@ const WATCHLIST_PAGE_SIZE = 10;
|
||||
*/
|
||||
export const useWatchlist = () => {
|
||||
const toast = useToast();
|
||||
const dispatch = useDispatch();
|
||||
const [watchlistQuotes, setWatchlistQuotes] = useState([]);
|
||||
const [watchlistLoading, setWatchlistLoading] = useState(false);
|
||||
const [watchlistPage, setWatchlistPage] = useState(1);
|
||||
const [followingEvents, setFollowingEvents] = useState([]);
|
||||
|
||||
// 从 Redux 获取自选股列表长度(用于监听变化)
|
||||
// 使用 length 作为依赖,避免数组引用变化导致不必要的重新渲染
|
||||
const reduxWatchlistLength = useSelector(state => state.stock.watchlist?.length || 0);
|
||||
|
||||
// 检查 Redux watchlist 是否已初始化(加载状态)
|
||||
const reduxWatchlistLoading = useSelector(state => state.stock.loading?.watchlist);
|
||||
|
||||
// 用于跟踪上一次的 watchlist 长度
|
||||
const prevWatchlistLengthRef = useRef(-1); // 初始设为 -1,确保第一次变化也能检测到
|
||||
|
||||
// 初始化时加载 Redux watchlist(确保 Redux 状态被初始化)
|
||||
const hasInitializedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!hasInitializedRef.current) {
|
||||
hasInitializedRef.current = true;
|
||||
logger.debug('useWatchlist', '初始化 Redux watchlist');
|
||||
dispatch(loadWatchlist());
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
// 加载自选股实时行情
|
||||
const loadWatchlistQuotes = useCallback(async () => {
|
||||
@@ -44,6 +70,7 @@ export const useWatchlist = () => {
|
||||
const data = await resp.json();
|
||||
if (data && data.success && Array.isArray(data.data)) {
|
||||
setWatchlistQuotes(data.data);
|
||||
logger.debug('useWatchlist', '自选股行情加载成功', { count: data.data.length });
|
||||
} else {
|
||||
setWatchlistQuotes([]);
|
||||
}
|
||||
@@ -60,6 +87,33 @@ export const useWatchlist = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 监听 Redux watchlist 长度变化,自动刷新行情数据
|
||||
useEffect(() => {
|
||||
const currentLength = reduxWatchlistLength;
|
||||
const prevLength = prevWatchlistLengthRef.current;
|
||||
|
||||
// 只有当 watchlist 长度发生变化时才刷新
|
||||
// prevLength = -1 表示初始状态,此时不触发刷新(由菜单打开时触发)
|
||||
if (prevLength !== -1 && currentLength !== prevLength) {
|
||||
logger.debug('useWatchlist', 'Redux watchlist 长度变化,刷新行情', {
|
||||
prevLength,
|
||||
currentLength
|
||||
});
|
||||
|
||||
// 延迟一小段时间再刷新,确保后端数据已更新
|
||||
const timer = setTimeout(() => {
|
||||
logger.debug('useWatchlist', '执行 loadWatchlistQuotes');
|
||||
loadWatchlistQuotes();
|
||||
}, 500);
|
||||
|
||||
prevWatchlistLengthRef.current = currentLength;
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
|
||||
// 更新 ref
|
||||
prevWatchlistLengthRef.current = currentLength;
|
||||
}, [reduxWatchlistLength, loadWatchlistQuotes]);
|
||||
|
||||
// 添加到自选股
|
||||
const handleAddToWatchlist = useCallback(async (stockCode, stockName) => {
|
||||
try {
|
||||
@@ -89,33 +143,42 @@ export const useWatchlist = () => {
|
||||
// 从自选股移除
|
||||
const handleRemoveFromWatchlist = useCallback(async (stockCode) => {
|
||||
try {
|
||||
const base = getApiBase();
|
||||
const resp = await fetch(base + `/api/account/watchlist/${stockCode}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include'
|
||||
// 找到股票名称
|
||||
const stockItem = watchlistQuotes.find(item => {
|
||||
const normalize6 = (code) => {
|
||||
const m = String(code || '').match(/(\d{6})/);
|
||||
return m ? m[1] : String(code || '');
|
||||
};
|
||||
return normalize6(item.stock_code) === normalize6(stockCode);
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (resp.ok && data && data.success !== false) {
|
||||
setWatchlistQuotes((prev) => {
|
||||
const normalize6 = (code) => {
|
||||
const m = String(code || '').match(/(\d{6})/);
|
||||
return m ? m[1] : String(code || '');
|
||||
};
|
||||
const target = normalize6(stockCode);
|
||||
const updated = (prev || []).filter((x) => normalize6(x.stock_code) !== target);
|
||||
const newMaxPage = Math.max(1, Math.ceil((updated.length || 0) / WATCHLIST_PAGE_SIZE));
|
||||
setWatchlistPage((p) => Math.min(p, newMaxPage));
|
||||
return updated;
|
||||
});
|
||||
toast({ title: '已从自选股移除', status: 'info', duration: 1500 });
|
||||
} else {
|
||||
toast({ title: '移除失败', status: 'error', duration: 2000 });
|
||||
}
|
||||
const stockName = stockItem?.stock_name || '';
|
||||
|
||||
// 通过 Redux action 移除(会同步更新 Redux 状态)
|
||||
await dispatch(toggleWatchlistAction({
|
||||
stockCode,
|
||||
stockName,
|
||||
isInWatchlist: true // 表示当前在自选股中,需要移除
|
||||
})).unwrap();
|
||||
|
||||
// 更新本地状态(立即响应 UI)
|
||||
setWatchlistQuotes((prev) => {
|
||||
const normalize6 = (code) => {
|
||||
const m = String(code || '').match(/(\d{6})/);
|
||||
return m ? m[1] : String(code || '');
|
||||
};
|
||||
const target = normalize6(stockCode);
|
||||
const updated = (prev || []).filter((x) => normalize6(x.stock_code) !== target);
|
||||
const newMaxPage = Math.max(1, Math.ceil((updated.length || 0) / WATCHLIST_PAGE_SIZE));
|
||||
setWatchlistPage((p) => Math.min(p, newMaxPage));
|
||||
return updated;
|
||||
});
|
||||
|
||||
toast({ title: '已从自选股移除', status: 'info', duration: 1500 });
|
||||
} catch (e) {
|
||||
toast({ title: '网络错误,移除失败', status: 'error', duration: 2000 });
|
||||
return false;
|
||||
logger.error('useWatchlist', '移除自选股失败', e);
|
||||
toast({ title: e.message || '移除失败', status: 'error', duration: 2000 });
|
||||
}
|
||||
}, [toast]);
|
||||
}, [dispatch, watchlistQuotes, toast]);
|
||||
|
||||
// 判断股票是否在自选股中
|
||||
const isInWatchlist = useCallback((stockCode) => {
|
||||
@@ -134,6 +197,7 @@ export const useWatchlist = () => {
|
||||
setWatchlistPage,
|
||||
WATCHLIST_PAGE_SIZE,
|
||||
loadWatchlistQuotes,
|
||||
followingEvents,
|
||||
handleAddToWatchlist,
|
||||
handleRemoveFromWatchlist,
|
||||
isInWatchlist
|
||||
|
||||
@@ -7,6 +7,18 @@ import MiniTimelineChart from './MiniTimelineChart';
|
||||
import { fetchBatchKlineData, klineDataCache, getCacheKey } from '../utils/klineDataCache';
|
||||
import { logger } from '../../../../../utils/logger';
|
||||
|
||||
/**
|
||||
* 标准化股票代码为6位格式
|
||||
* @param {string} code - 股票代码
|
||||
* @returns {string} 6位标准化代码
|
||||
*/
|
||||
const normalizeStockCode = (code) => {
|
||||
if (!code) return '';
|
||||
const s = String(code).trim();
|
||||
const m = s.match(/(\d{6})/);
|
||||
return m ? m[1] : s;
|
||||
};
|
||||
|
||||
/**
|
||||
* 股票列表表格组件
|
||||
* 显示事件相关股票列表,包括分时图、涨跌幅、自选股操作等
|
||||
@@ -260,7 +272,9 @@ const StockTable = ({
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
render: (_, record) => {
|
||||
const isInWatchlist = watchlistSet.has(record.stock_code);
|
||||
// 标准化代码后再比较,确保 600000.SH 和 600000 能匹配
|
||||
const normalizedCode = normalizeStockCode(record.stock_code);
|
||||
const isInWatchlist = watchlistSet.has(normalizedCode);
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: '4px' }}>
|
||||
<Button
|
||||
|
||||
@@ -5,6 +5,27 @@ import { loadWatchlist, toggleWatchlist as toggleWatchlistAction } from '../../.
|
||||
import { message } from 'antd';
|
||||
import { logger } from '../../../../../utils/logger';
|
||||
|
||||
/**
|
||||
* 标准化股票代码为6位格式
|
||||
* 支持: 600000, 600000.SH, 600000.SZ, SH600000 等格式
|
||||
* @param {string} code - 股票代码
|
||||
* @returns {string} 6位标准化代码
|
||||
*/
|
||||
const normalizeStockCode = (code) => {
|
||||
if (!code) return '';
|
||||
const s = String(code).trim().toUpperCase();
|
||||
// 匹配6位数字(可能带 .SH/.SZ 后缀)
|
||||
const m1 = s.match(/^(\d{6})(?:\.(?:SH|SZ))?$/i);
|
||||
if (m1) return m1[1];
|
||||
// 匹配 SH/SZ 前缀格式
|
||||
const m2 = s.match(/^(?:SH|SZ)(\d{6})$/i);
|
||||
if (m2) return m2[1];
|
||||
// 尝试提取任意6位数字
|
||||
const m3 = s.match(/(\d{6})/);
|
||||
if (m3) return m3[1];
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* 自选股管理 Hook
|
||||
* 封装自选股的加载、添加、移除逻辑
|
||||
@@ -19,9 +40,9 @@ export const useWatchlist = (shouldLoad = true) => {
|
||||
const watchlistArray = useSelector(state => state.stock.watchlist, shallowEqual);
|
||||
const loading = useSelector(state => state.stock.loading.watchlist);
|
||||
|
||||
// 转换为 Set 方便快速查询
|
||||
// 转换为 Set 方便快速查询(标准化为6位代码)
|
||||
const watchlistSet = useMemo(() => {
|
||||
return new Set(watchlistArray);
|
||||
return new Set(watchlistArray.map(normalizeStockCode));
|
||||
}, [watchlistArray]);
|
||||
|
||||
// 初始化时加载自选股列表(只在 shouldLoad 为 true 时)
|
||||
@@ -33,32 +54,36 @@ export const useWatchlist = (shouldLoad = true) => {
|
||||
}, [dispatch, shouldLoad]);
|
||||
|
||||
/**
|
||||
* 检查股票是否在自选股中
|
||||
* @param {string} stockCode - 股票代码
|
||||
* 检查股票是否在自选股中(支持带后缀的代码格式)
|
||||
* @param {string} stockCode - 股票代码(支持 600000, 600000.SH 等格式)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isInWatchlist = useCallback((stockCode) => {
|
||||
return watchlistSet.has(stockCode);
|
||||
const normalized = normalizeStockCode(stockCode);
|
||||
return watchlistSet.has(normalized);
|
||||
}, [watchlistSet]);
|
||||
|
||||
/**
|
||||
* 切换自选股状态
|
||||
* @param {string} stockCode - 股票代码
|
||||
* @param {string} stockCode - 股票代码(支持带后缀格式,会自动标准化)
|
||||
* @param {string} stockName - 股票名称
|
||||
* @returns {Promise<boolean>} 操作是否成功
|
||||
*/
|
||||
const toggleWatchlist = useCallback(async (stockCode, stockName) => {
|
||||
const wasInWatchlist = watchlistSet.has(stockCode);
|
||||
const normalized = normalizeStockCode(stockCode);
|
||||
const wasInWatchlist = watchlistSet.has(normalized);
|
||||
|
||||
logger.debug('useWatchlist', '切换自选股状态', {
|
||||
stockCode,
|
||||
normalized,
|
||||
stockName,
|
||||
wasInWatchlist
|
||||
});
|
||||
|
||||
try {
|
||||
// 传递标准化后的6位代码给 Redux action
|
||||
await dispatch(toggleWatchlistAction({
|
||||
stockCode,
|
||||
stockCode: normalized,
|
||||
stockName,
|
||||
isInWatchlist: wasInWatchlist
|
||||
})).unwrap();
|
||||
@@ -68,6 +93,7 @@ export const useWatchlist = (shouldLoad = true) => {
|
||||
} catch (error) {
|
||||
logger.error('useWatchlist', '切换自选股失败', error, {
|
||||
stockCode,
|
||||
normalized,
|
||||
stockName
|
||||
});
|
||||
message.error(error.message || '操作失败,请稍后重试');
|
||||
@@ -87,16 +113,17 @@ export const useWatchlist = (shouldLoad = true) => {
|
||||
|
||||
let successCount = 0;
|
||||
const promises = stocks.map(async ({ code, name }) => {
|
||||
if (!watchlistSet.has(code)) {
|
||||
const normalized = normalizeStockCode(code);
|
||||
if (!watchlistSet.has(normalized)) {
|
||||
try {
|
||||
await dispatch(toggleWatchlistAction({
|
||||
stockCode: code,
|
||||
stockCode: normalized,
|
||||
stockName: name,
|
||||
isInWatchlist: false
|
||||
})).unwrap();
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
logger.error('useWatchlist', '添加失败', error, { code, name });
|
||||
logger.error('useWatchlist', '添加失败', error, { code, normalized, name });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user