methodalgo-cli 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,7 +64,7 @@ npm link # Link local command
64
64
  #### 🖥️ Market Dashboard (`dashboard`)
65
65
  Launch a real-time TUI (Terminal User Interface) dashboard for a global view of market insights, news, and signals.
66
66
 
67
- ![Dashboard Preview](./screenshot/dashboard.png)
67
+ ![Dashboard Preview](./screenshot/dashboard.jpg)
68
68
 
69
69
  * **Usage**: `methodalgo dashboard` (Alias: `top`)
70
70
  * **Controls**: Use `TAB` to switch panels, `UP/DOWN` to scroll, and `ENTER` to view details.
@@ -192,7 +192,7 @@ npm link # 链接本地指令
192
192
  #### 🖥️ 市场看板 (`dashboard`)
193
193
  启动实时 TUI(终端用户界面)仪表盘,全局俯瞰市场洞察、新闻与信号。
194
194
 
195
- ![Dashboard Preview](./screenshot/dashboard.png)
195
+ ![Dashboard Preview](./screenshot/dashboard.jpg)
196
196
 
197
197
  * **用法**: `methodalgo dashboard` (别名: `top`)
198
198
  * **操作**: 使用 `TAB` 切换面板,`UP/DOWN` 滚动列表,`ENTER` 弹出详情。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "methodalgo-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -239,9 +239,18 @@ const Dashboard = () => {
239
239
  const [statusInfo, setStatusInfo] = useState({ time: "", mem: "0", error: null });
240
240
  const dataTimerRef = useRef(null);
241
241
  const lastFetchRef = useRef(null); // 记录上次拉取时间,用于增量请求
242
+ const isFetchingRef = useRef(false); // 防止并发重入
243
+ const refreshRef = useRef(null); // 始终持有最新 refreshData 引用
242
244
  const lang = getLang();
243
245
 
246
+ const scheduleRefresh = (delay = 60000) => {
247
+ if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
248
+ dataTimerRef.current = setTimeout(() => refreshRef.current?.(), delay);
249
+ };
250
+
244
251
  const refreshData = async () => {
252
+ if (isFetchingRef.current) return; // 防止并发
253
+ isFetchingRef.current = true;
245
254
  const memUsage = (process.memoryUsage().rss / 1024 / 1024).toFixed(1);
246
255
  try {
247
256
  const newsTypes = ["article", "breaking", "onchain", "report"];
@@ -262,7 +271,7 @@ const Dashboard = () => {
262
271
  (r.status === "fulfilled" && r.value.data.status === false &&
263
272
  (r.value.data.msg?.includes("auth") || r.value.data.msg?.includes("key")))
264
273
  );
265
- if (authFailed) { setAuthError(true); setLoading(false); if (dataTimerRef.current) clearInterval(dataTimerRef.current); return; }
274
+ if (authFailed) { setAuthError(true); setLoading(false); if (dataTimerRef.current) clearTimeout(dataTimerRef.current); return; }
266
275
 
267
276
  lastFetchRef.current = new Date().toISOString();
268
277
 
@@ -294,7 +303,8 @@ const Dashboard = () => {
294
303
  return (res?.status === "fulfilled" && res.value.data.status) ? res.value.data.data : [];
295
304
  };
296
305
 
297
- const formatSig = (item, overrideDir, type) => {
306
+ const formatSig = (rawItem, overrideDir, type) => {
307
+ const item = { ...rawItem }; // 浅拷贝,避免直接修改原始数据
298
308
  const sig = item.signals?.[0];
299
309
  let breakPrice = sig?.details?.BreakPrice || sig?.breakPrice || sig?.break_price || item.breakPrice || item.break_price;
300
310
  if (!breakPrice && sig?.fields) {
@@ -363,7 +373,8 @@ const Dashboard = () => {
363
373
  };
364
374
 
365
375
  const mergeAndSort = (items, existing, overrideDir, type, preferNew = false) => {
366
- const combined = preferNew ? [...items, ...existing] : [...items.filter(i => !new Set(existing.map(e => e.id)).has(i.id)), ...existing];
376
+ const existingIds = new Set(existing.map(e => e.id)); // 提前构建 Set,避免 O()
377
+ const combined = preferNew ? [...items, ...existing] : [...items.filter(i => !existingIds.has(i.id)), ...existing];
367
378
  const unique = Array.from(new Map(combined.map(item => [item.id, item])).values());
368
379
  return unique.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)).slice(0, 50).map(i => formatSig(i, overrideDir, type));
369
380
  };
@@ -445,8 +456,7 @@ const Dashboard = () => {
445
456
  return next;
446
457
  });
447
458
  setStatusInfo({ time: new Date().toLocaleTimeString(), mem: memUsage, error: null });
448
- if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
449
- dataTimerRef.current = setTimeout(refreshData, 60000);
459
+ scheduleRefresh(60000);
450
460
  } catch (error) {
451
461
  if (error.status === 429) {
452
462
  const secMatch = error.message.match(/(\d+)\s+seconds/);
@@ -455,21 +465,22 @@ const Dashboard = () => {
455
465
  if (secMatch) delay = parseInt(secMatch[1]) * 1000 + 2000;
456
466
  else if (minMatch) delay = parseInt(minMatch[1]) * 60000 + 2000;
457
467
  setStatusInfo(prev => ({ ...prev, error: `Rate Limited (Retry in ${Math.round(delay / 1000)}s)` }));
458
- if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
459
- dataTimerRef.current = setTimeout(refreshData, delay);
468
+ scheduleRefresh(delay);
460
469
  } else {
461
470
  setStatusInfo(prev => ({ ...prev, error: `Err: ${error.message.substring(0, 20)}` }));
462
- if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
463
- dataTimerRef.current = setTimeout(refreshData, 60000);
471
+ scheduleRefresh(60000);
464
472
  }
473
+ } finally {
474
+ isFetchingRef.current = false;
475
+ setLoading(false); // 确保任何路径退出都会重置 loading
465
476
  }
466
- setLoading(false);
467
477
  };
478
+ refreshRef.current = refreshData; // 每次渲染后同步最新引用
468
479
 
469
480
  useEffect(() => {
470
- refreshData();
481
+ refreshRef.current(); // 挂载时首次拉取(ref 已在渲染体 L476 同步)
471
482
  return () => { if (dataTimerRef.current) clearTimeout(dataTimerRef.current); };
472
- }, []);
483
+ }, []); // 仅挂载执行一次,后续刷新由 scheduleRefresh + refreshRef 统一管理
473
484
 
474
485
  useInput((input, key) => {
475
486
  if (dialog) return; // 弹窗时由 DetailDialog 处理