@hedgehog-finance/hedgehog-plugin 1.0.19 → 1.0.21

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.
Files changed (46) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/src/channel.js +143 -4
  3. package/dist/src/channel.js.map +1 -1
  4. package/dist/src/core/database.js +156 -0
  5. package/dist/src/core/database.js.map +1 -1
  6. package/dist/src/features/index.js +9 -1
  7. package/dist/src/features/index.js.map +1 -1
  8. package/dist/src/features/notes/schema.d.ts +84 -0
  9. package/dist/src/features/notes/schema.js +40 -0
  10. package/dist/src/features/notes/schema.js.map +1 -0
  11. package/dist/src/features/notes/tools.d.ts +11 -0
  12. package/dist/src/features/notes/tools.js +297 -0
  13. package/dist/src/features/notes/tools.js.map +1 -0
  14. package/dist/src/features/pluginInfo/tools.d.ts +11 -0
  15. package/dist/src/features/pluginInfo/tools.js +49 -0
  16. package/dist/src/features/pluginInfo/tools.js.map +1 -0
  17. package/dist/src/features/profileLibrary/schema.d.ts +29 -0
  18. package/dist/src/features/profileLibrary/schema.js +21 -0
  19. package/dist/src/features/profileLibrary/schema.js.map +1 -0
  20. package/dist/src/features/profileLibrary/tools.d.ts +11 -0
  21. package/dist/src/features/profileLibrary/tools.js +163 -0
  22. package/dist/src/features/profileLibrary/tools.js.map +1 -0
  23. package/dist/src/features/stockAnalysis/schema.d.ts +61 -0
  24. package/dist/src/features/stockAnalysis/schema.js +30 -0
  25. package/dist/src/features/stockAnalysis/schema.js.map +1 -0
  26. package/dist/src/features/stockAnalysis/tools.d.ts +20 -0
  27. package/dist/src/features/stockAnalysis/tools.js +138 -0
  28. package/dist/src/features/stockAnalysis/tools.js.map +1 -0
  29. package/dist/src/features/watchlist/logic.js +7 -60
  30. package/dist/src/features/watchlist/logic.js.map +1 -1
  31. package/dist/src/features/watchlist/tools.js +61 -26
  32. package/dist/src/features/watchlist/tools.js.map +1 -1
  33. package/dist/src/types.d.ts +1 -1
  34. package/package.json +5 -5
  35. package/src/channel.ts +150 -4
  36. package/src/core/database.ts +155 -0
  37. package/src/features/index.ts +9 -1
  38. package/src/features/notes/schema.ts +75 -0
  39. package/src/features/notes/tools.ts +352 -0
  40. package/src/features/pluginInfo/tools.ts +63 -0
  41. package/src/features/profileLibrary/schema.ts +35 -0
  42. package/src/features/profileLibrary/tools.ts +194 -0
  43. package/src/features/stockAnalysis/schema.ts +60 -0
  44. package/src/features/stockAnalysis/tools.ts +192 -0
  45. package/src/features/watchlist/logic.ts +7 -63
  46. package/src/features/watchlist/tools.ts +83 -48
@@ -135,6 +135,11 @@ export const watchlistTools = {
135
135
  return JSON.stringify({ success: true, skipped: true, reason: "duplicate", id: existingBeforeClassify.id });
136
136
  }
137
137
 
138
+ const currentCount = (db.prepare("SELECT COUNT(*) as count FROM watchlist WHERE userId = ? AND isDeleted = 0").get(uId) as { count: number }).count;
139
+ if (currentCount >= 30) {
140
+ return JSON.stringify({ success: false, error: "自选股数量已达 30 只上限,请先移除部分股票" });
141
+ }
142
+
138
143
  let classification: Awaited<ReturnType<typeof watchlistLogic.getStockClassification>>;
139
144
  try {
140
145
  classification = await watchlistLogic.getStockClassification(ctx.runtime, stock.stockName, stock.stockCode, stock.exchange, uId);
@@ -227,6 +232,14 @@ export const watchlistTools = {
227
232
  return JSON.stringify({ success: true, ids: [], skipped });
228
233
  }
229
234
 
235
+ const currentCount = (db.prepare("SELECT COUNT(*) as count FROM watchlist WHERE userId = ? AND isDeleted = 0").get(uId) as { count: number }).count;
236
+ if (currentCount + stocksToAdd.length > 30) {
237
+ return JSON.stringify({
238
+ success: false,
239
+ error: `自选股数量已达 30 只上限(当前已有 ${currentCount} 只,本次尝试添加 ${stocksToAdd.length} 只)`
240
+ });
241
+ }
242
+
230
243
  let batchResults: Awaited<ReturnType<typeof watchlistLogic.classifyStocksTogether>>;
231
244
  try {
232
245
  batchResults = await watchlistLogic.classifyStocksTogether(ctx.runtime, stocksToAdd, uId);
@@ -325,6 +338,14 @@ export const watchlistTools = {
325
338
  if (info.changes > 0) {
326
339
  db.prepare("DELETE FROM watchlist_industry_items WHERE watchlistId = ? AND userId = ?").run(args.id, uId);
327
340
  db.prepare("DELETE FROM watchlist_theme_items WHERE watchlistId = ? AND userId = ?").run(args.id, uId);
341
+
342
+ // 同时删除该自选股关联的所有投研笔记及笔记内保存的资料库关联记录
343
+ db.prepare(`
344
+ DELETE FROM stock_note_profile_libraries
345
+ WHERE userId = ?
346
+ AND noteId IN (SELECT id FROM stock_notes WHERE watchlistId = ? AND userId = ?)
347
+ `).run(uId, args.id, uId);
348
+ db.prepare("DELETE FROM stock_notes WHERE watchlistId = ? AND userId = ?").run(args.id, uId);
328
349
  }
329
350
 
330
351
  db.exec("COMMIT");
@@ -356,23 +377,15 @@ export const watchlistTools = {
356
377
  FROM watchlist w
357
378
  JOIN ${table} ci ON w.id = ci.watchlistId
358
379
  WHERE w.userId = ? AND w.isDeleted = 0 AND ci.categoryId = ?
359
- ORDER BY ci.weight DESC, w.sortOrder ASC
380
+ ORDER BY w.sortOrder ASC
360
381
  `;
361
382
  params.push(args.categoryId);
362
383
  } else {
363
384
  query = `
364
- SELECT w.*, (
365
- SELECT MAX(total.weight) FROM (
366
- SELECT weight FROM watchlist_industry_items WHERE watchlistId = w.id
367
- UNION ALL
368
- SELECT weight FROM watchlist_theme_items WHERE watchlistId = w.id
369
- UNION ALL
370
- SELECT 0 as weight
371
- ) total
372
- ) as globalWeight
385
+ SELECT w.*
373
386
  FROM watchlist w
374
387
  WHERE w.userId = ? AND w.isDeleted = 0
375
- ORDER BY globalWeight DESC, w.sortOrder ASC
388
+ ORDER BY w.sortOrder ASC
376
389
  `;
377
390
  }
378
391
 
@@ -498,7 +511,7 @@ export const watchlistTools = {
498
511
  WHERE userId = ? AND stockCode = ? AND isDeleted = 0
499
512
  `);
500
513
  sortedResults.forEach((item: any, i: number) => {
501
- const currentOrder = i * 10;
514
+ const currentOrder = i * 10;
502
515
  stmt.run(currentOrder, uId, item.code);
503
516
  });
504
517
  db.exec("COMMIT");
@@ -584,48 +597,70 @@ export const watchlistTools = {
584
597
  if (!ctx.runtime) return JSON.stringify({ success: false, error: "Runtime not available" });
585
598
  const db = getDB();
586
599
  const uId = String(ctx.userId);
587
- db.exec("BEGIN TRANSACTION");
588
600
  try {
589
- db.prepare("DELETE FROM watchlist_industry_items WHERE userId = ?").run(uId);
590
- db.prepare("DELETE FROM watchlist_theme_items WHERE userId = ?").run(uId);
591
601
  const stocks = db.prepare("SELECT stockName, stockCode, exchange, market FROM watchlist WHERE userId = ? AND isDeleted = 0").all(uId) as any[];
602
+ if (stocks.length > 30) {
603
+ return JSON.stringify({ success: false, error: "自选股数量超过 30 只,暂不支持批量重置分类" });
604
+ }
592
605
  if (stocks.length > 0) {
593
- watchlistLogic.getBatchStockClassification(ctx.runtime, stocks, uId, { forceRefresh: true })
594
- .then(batchResults => {
595
- const db2 = getDB();
596
- const userStocks = db2.prepare("SELECT id, stockCode, exchange FROM watchlist WHERE userId = ? AND isDeleted = 0").all(uId) as any[];
597
- batchResults.forEach((res, i) => {
598
- if (res) {
599
- const s = stocks[i];
600
- const match = userStocks.find(us => us.stockCode === s.stockCode && us.exchange === s.exchange);
601
- if (match) {
602
- upsertStockClassificationCache(db2, {
603
- stockName: s.stockName,
604
- stockCode: s.stockCode,
605
- exchange: s.exchange,
606
- market: s.market
607
- }, res);
608
- const cats = [
609
- ...(res.industry ? [{ name: res.industry.name, type: 'industry' as const, weight: res.industry.weight }] : []),
610
- ...res.theme.map((t: any) => ({ name: t.name, type: 'theme' as const, weight: t.weight }))
611
- ];
612
- cats.forEach(c => {
613
- const catId = watchlistLogic._ensureCategory(db2, c.name, c.type, uId);
614
- if (catId) {
615
- const table = c.type === 'industry' ? 'watchlist_industry_items' : 'watchlist_theme_items';
616
- db2.prepare(`INSERT OR IGNORE INTO ${table} (id, watchlistId, userId, categoryId, weight) VALUES (?, ?, ?, ?, ?)`).run(randomUUID(), match.id, uId, catId, c.weight);
617
- }
618
- });
619
- }
606
+ // 1. 强制等待 AI 批量分类结果(在事务外部执行耗时 API 请求,避免锁库)
607
+ const batchResults = await watchlistLogic.getBatchStockClassification(ctx.runtime, stocks, uId, {
608
+ forceRefresh: true,
609
+ requireComplete: true
610
+ });
611
+
612
+ // 2. 开启数据库事务,清空并更新分类
613
+ db.exec("BEGIN TRANSACTION");
614
+ try {
615
+ db.prepare("DELETE FROM watchlist_industry_items WHERE userId = ?").run(uId);
616
+ db.prepare("DELETE FROM watchlist_theme_items WHERE userId = ?").run(uId);
617
+
618
+ const userStocks = db.prepare("SELECT id, stockCode, exchange FROM watchlist WHERE userId = ? AND isDeleted = 0").all(uId) as any[];
619
+ batchResults.forEach((res, i) => {
620
+ if (res) {
621
+ const s = stocks[i];
622
+ const match = userStocks.find(us => us.stockCode === s.stockCode && us.exchange === s.exchange);
623
+ if (match) {
624
+ upsertStockClassificationCache(db, {
625
+ stockName: s.stockName,
626
+ stockCode: s.stockCode,
627
+ exchange: s.exchange,
628
+ market: s.market
629
+ }, res);
630
+ const cats = [
631
+ ...(res.industry ? [{ name: res.industry.name, type: 'industry' as const, weight: res.industry.weight }] : []),
632
+ ...res.theme.map((t: any) => ({ name: t.name, type: 'theme' as const, weight: t.weight }))
633
+ ];
634
+ cats.forEach(c => {
635
+ const catId = watchlistLogic._ensureCategory(db, c.name, c.type, uId);
636
+ if (catId) {
637
+ const table = c.type === 'industry' ? 'watchlist_industry_items' : 'watchlist_theme_items';
638
+ db.prepare(`INSERT OR IGNORE INTO ${table} (id, watchlistId, userId, categoryId, weight) VALUES (?, ?, ?, ?, ?)`).run(randomUUID(), match.id, uId, catId, c.weight);
639
+ }
640
+ });
620
641
  }
621
- });
622
- }).catch(err => logger.error({ err }, "[Watchlist] 重置分类失败"));
642
+ }
643
+ });
644
+ db.exec("COMMIT");
645
+ } catch (transactionError: any) {
646
+ if (db.inTransaction) db.exec("ROLLBACK");
647
+ throw transactionError;
648
+ }
649
+ } else {
650
+ // 如果没有自选股,也正常开启事务清空旧的分类数据
651
+ db.exec("BEGIN TRANSACTION");
652
+ try {
653
+ db.prepare("DELETE FROM watchlist_industry_items WHERE userId = ?").run(uId);
654
+ db.prepare("DELETE FROM watchlist_theme_items WHERE userId = ?").run(uId);
655
+ db.exec("COMMIT");
656
+ } catch (transactionError: any) {
657
+ if (db.inTransaction) db.exec("ROLLBACK");
658
+ throw transactionError;
659
+ }
623
660
  }
624
- db.exec("COMMIT");
625
- return JSON.stringify({ success: true, message: "重置分类请求已提交" });
661
+ return JSON.stringify({ success: true, message: "智能分类已完成重置" });
626
662
  } catch (e: any) {
627
- db.exec("ROLLBACK");
628
- return JSON.stringify({ success: false, error: e.message });
663
+ return JSON.stringify({ success: false, error: e.message || "重置分类失败" });
629
664
  }
630
665
  }
631
666
  }