chatlab-cli 0.26.2 → 0.26.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.
Files changed (70) hide show
  1. package/dist/{chat-command-BVIG2LCO.mjs → chat-command-D37MMEJ7.mjs} +4 -4
  2. package/dist/{chunk-6AOEMUCM.mjs → chunk-742TAKEP.mjs} +2 -2
  3. package/dist/{chunk-3XFHIQIW.mjs → chunk-D5MWER7N.mjs} +2 -2
  4. package/dist/{chunk-JFSTXSX2.mjs → chunk-GNGZHBKM.mjs} +3 -3
  5. package/dist/{chunk-GTXX72AQ.mjs → chunk-K5SGXTVJ.mjs} +13 -13
  6. package/dist/{chunk-GBQQJJKK.mjs → chunk-QMXNJRTT.mjs} +372 -173
  7. package/dist/chunk-QMXNJRTT.mjs.map +1 -0
  8. package/dist/{chunk-SCE4YL3M.mjs → chunk-RMITK4IC.mjs} +2 -2
  9. package/dist/{chunk-QKQRV7RV.mjs → chunk-VCN64OD6.mjs} +2 -2
  10. package/dist/{chunk-TMZC3SNX.mjs → chunk-YPTJVTVJ.mjs} +1037 -49
  11. package/dist/chunk-YPTJVTVJ.mjs.map +1 -0
  12. package/dist/cli.mjs +3 -3
  13. package/dist/{http-NLFLQDI7.mjs → http-6F6TYWCI.mjs} +8 -8
  14. package/dist/{import-ILK6MGYR.mjs → import-VYPOMSSA.mjs} +4 -4
  15. package/dist/index.mjs +8 -8
  16. package/dist/{mcp-FOLR5XJL.mjs → mcp-AR7L5FA3.mjs} +4 -4
  17. package/dist/{src-OQN3VE73.mjs → src-6S7JPKHX.mjs} +6 -2
  18. package/dist/{stream-import-F5QI2HTO.mjs → stream-import-KZFC3XT2.mjs} +3 -3
  19. package/dist/{update-checker-JIBA7T4U.mjs → update-checker-3N7HQEP2.mjs} +36 -12
  20. package/dist/update-checker-3N7HQEP2.mjs.map +1 -0
  21. package/dist-web/assets/{DebugToolsPanel-CrqULdXp.css → DebugToolsPanel-DzZmSzIU.css} +1 -1
  22. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-BTdqmm0y.js +107 -0
  23. package/dist-web/assets/{MessageExportModal-ttP_JPqO.js → MessageExportModal-CF6yaor7.js} +1 -1
  24. package/dist-web/assets/{electron-BeJHY587.js → electron-DKVw61Ww.js} +1 -1
  25. package/dist-web/assets/{fetch-H4B1CK_T.js → fetch-BC8jyJ_U.js} +1 -1
  26. package/dist-web/assets/{fetch-4s20GRbb.js → fetch-BMdgfBVS.js} +1 -1
  27. package/dist-web/assets/fetch-BQlEQsqi.js +5 -0
  28. package/dist-web/assets/{fetch-DtXUkazy.js → fetch-Bh6HC-IR.js} +1 -1
  29. package/dist-web/assets/{fetch-DBJ7Xkjt.js → fetch-BvVfWR-P.js} +1 -1
  30. package/dist-web/assets/{fetch-DW8r_JmP.js → fetch-CPeSfqsU.js} +1 -1
  31. package/dist-web/assets/fetch-CUw-PUYP.js +1 -0
  32. package/dist-web/assets/{fetch-B7USXx9R.js → fetch-CpYSD089.js} +1 -1
  33. package/dist-web/assets/{fetch-rtQEc8Bu.js → fetch-D2jNBqs8.js} +1 -1
  34. package/dist-web/assets/{fetch-BETFYYe1.js → fetch-DqEMAea3.js} +1 -1
  35. package/dist-web/assets/{index-Dov-rTKE.js → index-BHQnAj6l.js} +1 -1
  36. package/dist-web/assets/{index-CsxrXMkt.js → index-BwzuCu5R.js} +2 -2
  37. package/dist-web/assets/index-CG746E15.js +64 -0
  38. package/dist-web/assets/index-CXoQ_U22.js +173 -0
  39. package/dist-web/assets/index-DqybGdJH.css +1 -0
  40. package/dist-web/assets/{index-CjnMyejM.css → index-HgIuP1Td.css} +1 -1
  41. package/dist-web/assets/{index-CYMTvtEI.js → index-Qh0XLt3R.js} +1 -1
  42. package/dist-web/assets/{index-Dtm2oajn.css → index-R82N2_0b.css} +1 -1
  43. package/dist-web/assets/index-wPAmuQZY.js +11 -0
  44. package/dist-web/assets/{web-1-WNRDnP.js → web-Bx2TiHDk.js} +1 -1
  45. package/dist-web/favicon.ico +0 -0
  46. package/dist-web/index.html +3 -3
  47. package/package.json +1 -1
  48. package/dist/chunk-GBQQJJKK.mjs.map +0 -1
  49. package/dist/chunk-TMZC3SNX.mjs.map +0 -1
  50. package/dist/update-checker-JIBA7T4U.mjs.map +0 -1
  51. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-D9mCNaYM.js +0 -182
  52. package/dist-web/assets/electron-Dbml-SPN.js +0 -1
  53. package/dist-web/assets/fetch-DcEBAz_3.js +0 -1
  54. package/dist-web/assets/fetch-DsCNr_M0.js +0 -1
  55. package/dist-web/assets/index-C2ts-hTr.js +0 -173
  56. package/dist-web/assets/index-DkownpUj.js +0 -163
  57. package/dist-web/assets/index-LCedyGl2.css +0 -1
  58. package/dist-web/assets/index-m7v4PP7r.js +0 -11
  59. /package/dist/{chat-command-BVIG2LCO.mjs.map → chat-command-D37MMEJ7.mjs.map} +0 -0
  60. /package/dist/{chunk-6AOEMUCM.mjs.map → chunk-742TAKEP.mjs.map} +0 -0
  61. /package/dist/{chunk-3XFHIQIW.mjs.map → chunk-D5MWER7N.mjs.map} +0 -0
  62. /package/dist/{chunk-JFSTXSX2.mjs.map → chunk-GNGZHBKM.mjs.map} +0 -0
  63. /package/dist/{chunk-GTXX72AQ.mjs.map → chunk-K5SGXTVJ.mjs.map} +0 -0
  64. /package/dist/{chunk-SCE4YL3M.mjs.map → chunk-RMITK4IC.mjs.map} +0 -0
  65. /package/dist/{chunk-QKQRV7RV.mjs.map → chunk-VCN64OD6.mjs.map} +0 -0
  66. /package/dist/{http-NLFLQDI7.mjs.map → http-6F6TYWCI.mjs.map} +0 -0
  67. /package/dist/{import-ILK6MGYR.mjs.map → import-VYPOMSSA.mjs.map} +0 -0
  68. /package/dist/{mcp-FOLR5XJL.mjs.map → mcp-AR7L5FA3.mjs.map} +0 -0
  69. /package/dist/{src-OQN3VE73.mjs.map → src-6S7JPKHX.mjs.map} +0 -0
  70. /package/dist/{stream-import-F5QI2HTO.mjs.map → stream-import-KZFC3XT2.mjs.map} +0 -0
@@ -1797,6 +1797,72 @@ function getMessageLengthDistribution(db, filter) {
1797
1797
  }));
1798
1798
  return { detail, grouped };
1799
1799
  }
1800
+ function getTextStats(db, filter) {
1801
+ const { clause, params } = buildTimeFilter(filter);
1802
+ const typeCondition = buildSystemMessageFilter(clause) + " AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) > 0";
1803
+ const row = db.prepare(
1804
+ `SELECT
1805
+ COUNT(*) as textCount,
1806
+ ROUND(AVG(LENGTH(msg.content)), 1) as avgLength,
1807
+ MAX(LENGTH(msg.content)) as maxLength,
1808
+ SUM(CASE WHEN LENGTH(msg.content) <= 5 THEN 1 ELSE 0 END) as shortCount
1809
+ FROM message msg JOIN member m ON msg.sender_id = m.id
1810
+ ${typeCondition}`
1811
+ ).get(...params);
1812
+ return {
1813
+ textCount: row?.textCount ?? 0,
1814
+ avgLength: row?.avgLength ?? 0,
1815
+ maxLength: row?.maxLength ?? 0,
1816
+ shortCount: row?.shortCount ?? 0
1817
+ };
1818
+ }
1819
+ function getLongMessageCount(db, filter, minLength = 30) {
1820
+ const { clause, params } = buildTimeFilter(filter);
1821
+ const typeCondition = buildSystemMessageFilter(clause) + " AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) >= ?";
1822
+ const row = db.prepare(
1823
+ `SELECT COUNT(*) as cnt
1824
+ FROM message msg JOIN member m ON msg.sender_id = m.id
1825
+ ${typeCondition}`
1826
+ ).get(...params, minLength);
1827
+ return row?.cnt ?? 0;
1828
+ }
1829
+ function getMemberMonthlyTrend(db, filter) {
1830
+ const { clause, params } = buildTimeFilter(filter);
1831
+ const clauseWithSystem = buildSystemMessageFilter(clause);
1832
+ return db.prepare(
1833
+ `SELECT
1834
+ strftime('%Y-%m', msg.ts, 'unixepoch', 'localtime') as month,
1835
+ msg.sender_id as memberId,
1836
+ m.account_name as memberName,
1837
+ COUNT(*) as count
1838
+ FROM message msg JOIN member m ON msg.sender_id = m.id
1839
+ ${clauseWithSystem}
1840
+ GROUP BY month, msg.sender_id
1841
+ ORDER BY month`
1842
+ ).all(...params);
1843
+ }
1844
+ function getTextLengthPercentiles(db, filter) {
1845
+ const { clause, params } = buildTimeFilter(filter);
1846
+ const typeCondition = buildSystemMessageFilter(clause) + " AND msg.type = 0 AND msg.content IS NOT NULL AND LENGTH(msg.content) > 0";
1847
+ const rows = db.prepare(
1848
+ `SELECT LENGTH(msg.content) as len
1849
+ FROM message msg JOIN member m ON msg.sender_id = m.id
1850
+ ${typeCondition}
1851
+ ORDER BY len`
1852
+ ).all(...params);
1853
+ if (rows.length === 0) return { p25: 0, p50: 0, p75: 0, p90: 0 };
1854
+ const lengths = rows.map((r) => r.len);
1855
+ const getPercentile = (arr, p) => {
1856
+ const idx = Math.ceil(p / 100 * arr.length) - 1;
1857
+ return arr[Math.max(0, idx)];
1858
+ };
1859
+ return {
1860
+ p25: getPercentile(lengths, 25),
1861
+ p50: getPercentile(lengths, 50),
1862
+ p75: getPercentile(lengths, 75),
1863
+ p90: getPercentile(lengths, 90)
1864
+ };
1865
+ }
1800
1866
 
1801
1867
  // ../../packages/core/src/query/session-queries.ts
1802
1868
  function buildSessionInfo(meta, overview, summaryCount = 0) {
@@ -2081,14 +2147,16 @@ function updateSessionOwnerId(db, ownerId) {
2081
2147
  function renameSession(db, newName) {
2082
2148
  db.prepare("UPDATE meta SET name = ?").run(newName);
2083
2149
  }
2084
- function clearSessionIndex(db) {
2150
+ function clearSessionIndexRows(db) {
2085
2151
  db.exec("DELETE FROM message_context");
2086
2152
  db.exec("DELETE FROM segment");
2087
2153
  }
2154
+ function clearSessionIndex(db) {
2155
+ db.transaction(() => clearSessionIndexRows(db));
2156
+ }
2088
2157
  function generateSessionIndex(db, gapThreshold = DEFAULT_SESSION_GAP_THRESHOLD, onProgress) {
2089
2158
  const countRow = db.prepare("SELECT COUNT(*) as count FROM message").get();
2090
2159
  if (!countRow || countRow.count === 0) return 0;
2091
- clearSessionIndex(db);
2092
2160
  const sessionMarkSQL = `
2093
2161
  WITH message_ordered AS (
2094
2162
  SELECT id, ts, LAG(ts) OVER (ORDER BY ts, id) AS prev_ts FROM message
@@ -2121,6 +2189,7 @@ function generateSessionIndex(db, gapThreshold = DEFAULT_SESSION_GAP_THRESHOLD,
2121
2189
  );
2122
2190
  const insertContext = db.prepare("INSERT INTO message_context (message_id, segment_id, topic_id) VALUES (?, ?, NULL)");
2123
2191
  return db.transaction(() => {
2192
+ clearSessionIndexRows(db);
2124
2193
  let processed = 0;
2125
2194
  const total = sessionMap.size;
2126
2195
  for (const [, data] of sessionMap) {
@@ -2772,6 +2841,35 @@ function deleteMember(db, memberId) {
2772
2841
  }
2773
2842
  }
2774
2843
 
2844
+ // ../../packages/core/src/query/advanced/text-filters.ts
2845
+ var SYSTEM_PLACEHOLDER_CONTENTS = /* @__PURE__ */ new Set([
2846
+ "[\u56FE\u7247]",
2847
+ "[\u89C6\u9891]",
2848
+ "[\u8BED\u97F3]",
2849
+ "[\u6587\u4EF6]",
2850
+ "[\u52A8\u753B\u8868\u60C5]",
2851
+ "[\u8868\u60C5]",
2852
+ "[\u94FE\u63A5]",
2853
+ "[\u4F4D\u7F6E]",
2854
+ "[\u5730\u7406\u4F4D\u7F6E]",
2855
+ "[\u540D\u7247]",
2856
+ "[\u7EA2\u5305]",
2857
+ "[\u8F6C\u8D26]",
2858
+ "[\u97F3\u4E50]",
2859
+ "[\u56DE\u590D\u6D88\u606F]",
2860
+ "[Image]",
2861
+ "[Photo]",
2862
+ "[Video]",
2863
+ "[Voice]",
2864
+ "[File]",
2865
+ "[Sticker]",
2866
+ "[Link]",
2867
+ "[Location]"
2868
+ ]);
2869
+ function isSystemPlaceholderContent(content) {
2870
+ return SYSTEM_PLACEHOLDER_CONTENTS.has(content.trim());
2871
+ }
2872
+
2775
2873
  // ../../packages/core/src/query/advanced/repeat.ts
2776
2874
  function getCatchphraseAnalysis(db, filter) {
2777
2875
  const { clause, params } = buildTimeFilter(filter);
@@ -2798,6 +2896,7 @@ function getCatchphraseAnalysis(db, filter) {
2798
2896
  ).all(...params);
2799
2897
  const memberMap = /* @__PURE__ */ new Map();
2800
2898
  for (const row of rows) {
2899
+ if (isSystemPlaceholderContent(row.content)) continue;
2801
2900
  if (!memberMap.has(row.memberId)) {
2802
2901
  memberMap.set(row.memberId, {
2803
2902
  memberId: row.memberId,
@@ -3625,6 +3724,7 @@ function getLanguagePreferenceAnalysis(db, params) {
3625
3724
  if (rows.length === 0) return { members: [], sharedWords: [], similarityScore: 0 };
3626
3725
  const memberMessages = /* @__PURE__ */ new Map();
3627
3726
  for (const row of rows) {
3727
+ if (isSystemPlaceholderContent(row.content)) continue;
3628
3728
  let entry = memberMessages.get(row.memberId);
3629
3729
  if (!entry) {
3630
3730
  entry = { name: row.name, messages: [] };
@@ -3736,6 +3836,743 @@ function getLanguagePreferenceAnalysis(db, params) {
3736
3836
  return { members: memberProfiles, sharedWords, similarityScore };
3737
3837
  }
3738
3838
 
3839
+ // ../../packages/core/src/query/advanced/ranking.ts
3840
+ var SYSTEM_FILTER = "AND COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'";
3841
+ function buildFilter(filter) {
3842
+ const parts = [];
3843
+ const params = [];
3844
+ if (filter?.startTs != null) {
3845
+ parts.push("AND msg.ts >= ?");
3846
+ params.push(filter.startTs);
3847
+ }
3848
+ if (filter?.endTs != null) {
3849
+ parts.push("AND msg.ts <= ?");
3850
+ params.push(filter.endTs);
3851
+ }
3852
+ if (filter?.memberId != null) {
3853
+ parts.push("AND msg.sender_id = ?");
3854
+ params.push(filter.memberId);
3855
+ }
3856
+ return { conditions: parts.join(" "), params };
3857
+ }
3858
+ function getDragonKingAnalysis(db, filter) {
3859
+ const { conditions, params } = buildFilter(filter);
3860
+ const rankRows = db.prepare(
3861
+ `WITH daily_counts AS (
3862
+ SELECT
3863
+ strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as date,
3864
+ msg.sender_id,
3865
+ m.platform_id,
3866
+ COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,
3867
+ COUNT(*) as msg_count
3868
+ FROM message msg
3869
+ JOIN member m ON msg.sender_id = m.id
3870
+ WHERE 1=1 ${SYSTEM_FILTER} ${conditions}
3871
+ GROUP BY date, msg.sender_id
3872
+ ),
3873
+ daily_max AS (
3874
+ SELECT date, MAX(msg_count) as max_count
3875
+ FROM daily_counts
3876
+ GROUP BY date
3877
+ )
3878
+ SELECT dc.sender_id, dc.platform_id, dc.name, COUNT(*) as dragon_days
3879
+ FROM daily_counts dc
3880
+ JOIN daily_max dm ON dc.date = dm.date AND dc.msg_count = dm.max_count
3881
+ GROUP BY dc.sender_id
3882
+ ORDER BY dragon_days DESC`
3883
+ ).all(...params);
3884
+ const totalRow = db.prepare(
3885
+ `SELECT COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as total
3886
+ FROM message msg
3887
+ JOIN member m ON msg.sender_id = m.id
3888
+ WHERE 1=1 ${SYSTEM_FILTER} ${conditions}`
3889
+ ).get(...params);
3890
+ const totalDays = totalRow?.total ?? 0;
3891
+ const rank = rankRows.map((item) => ({
3892
+ memberId: item.sender_id,
3893
+ platformId: item.platform_id,
3894
+ name: item.name,
3895
+ count: item.dragon_days,
3896
+ percentage: totalDays > 0 ? Math.round(item.dragon_days / totalDays * 1e4) / 100 : 0
3897
+ }));
3898
+ return { rank, totalDays };
3899
+ }
3900
+ function getDivingAnalysis(db, filter) {
3901
+ const { conditions, params } = buildFilter(filter);
3902
+ const rows = db.prepare(
3903
+ `SELECT
3904
+ m.id as member_id,
3905
+ m.platform_id,
3906
+ COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,
3907
+ MAX(msg.ts) as last_ts
3908
+ FROM member m
3909
+ JOIN message msg ON m.id = msg.sender_id
3910
+ WHERE 1=1 ${SYSTEM_FILTER} ${conditions}
3911
+ GROUP BY m.id
3912
+ ORDER BY last_ts ASC`
3913
+ ).all(...params);
3914
+ const now = Math.floor(Date.now() / 1e3);
3915
+ const rank = rows.map((item) => ({
3916
+ memberId: item.member_id,
3917
+ platformId: item.platform_id,
3918
+ name: item.name,
3919
+ lastMessageTs: item.last_ts,
3920
+ daysSinceLastMessage: Math.floor((now - item.last_ts) / 86400)
3921
+ }));
3922
+ return { rank };
3923
+ }
3924
+ function computeCheckIn(dailyActivity) {
3925
+ if (dailyActivity.length === 0) {
3926
+ return { streakRank: [], loyaltyRank: [], totalDays: 0 };
3927
+ }
3928
+ const allDays = new Set(dailyActivity.map((r) => r.day));
3929
+ const totalDays = allDays.size;
3930
+ const sortedDays = Array.from(allDays).sort();
3931
+ const lastDay = sortedDays[sortedDays.length - 1];
3932
+ const memberDays = /* @__PURE__ */ new Map();
3933
+ for (const record of dailyActivity) {
3934
+ if (!memberDays.has(record.senderId)) {
3935
+ memberDays.set(record.senderId, { name: record.name, days: /* @__PURE__ */ new Set() });
3936
+ }
3937
+ memberDays.get(record.senderId).days.add(record.day);
3938
+ }
3939
+ const streakData = [];
3940
+ const loyaltyData = [];
3941
+ for (const [memberId, data] of memberDays) {
3942
+ const sortedMemberDays = Array.from(data.days).sort();
3943
+ const totalMemberDays = sortedMemberDays.length;
3944
+ let maxStreak = 1;
3945
+ let maxStreakStart = sortedMemberDays[0];
3946
+ let maxStreakEnd = sortedMemberDays[0];
3947
+ let currentStreakCount = 1;
3948
+ let currentStreakStart = sortedMemberDays[0];
3949
+ for (let i = 1; i < sortedMemberDays.length; i++) {
3950
+ const prevDate = new Date(sortedMemberDays[i - 1]);
3951
+ const currDate = new Date(sortedMemberDays[i]);
3952
+ const diffDays = Math.round((currDate.getTime() - prevDate.getTime()) / (1e3 * 60 * 60 * 24));
3953
+ if (diffDays === 1) {
3954
+ currentStreakCount++;
3955
+ } else {
3956
+ if (currentStreakCount > maxStreak) {
3957
+ maxStreak = currentStreakCount;
3958
+ maxStreakStart = currentStreakStart;
3959
+ maxStreakEnd = sortedMemberDays[i - 1];
3960
+ }
3961
+ currentStreakCount = 1;
3962
+ currentStreakStart = sortedMemberDays[i];
3963
+ }
3964
+ }
3965
+ if (currentStreakCount > maxStreak) {
3966
+ maxStreak = currentStreakCount;
3967
+ maxStreakStart = currentStreakStart;
3968
+ maxStreakEnd = sortedMemberDays[sortedMemberDays.length - 1];
3969
+ }
3970
+ let finalCurrentStreak = 0;
3971
+ if (sortedMemberDays[sortedMemberDays.length - 1] === lastDay) {
3972
+ finalCurrentStreak = 1;
3973
+ for (let i = sortedMemberDays.length - 2; i >= 0; i--) {
3974
+ const currDate = new Date(sortedMemberDays[i + 1]);
3975
+ const prevDate = new Date(sortedMemberDays[i]);
3976
+ const diffDays = Math.round((currDate.getTime() - prevDate.getTime()) / (1e3 * 60 * 60 * 24));
3977
+ if (diffDays === 1) {
3978
+ finalCurrentStreak++;
3979
+ } else {
3980
+ break;
3981
+ }
3982
+ }
3983
+ }
3984
+ streakData.push({
3985
+ memberId,
3986
+ name: data.name,
3987
+ maxStreak,
3988
+ maxStreakStart,
3989
+ maxStreakEnd,
3990
+ currentStreak: finalCurrentStreak
3991
+ });
3992
+ loyaltyData.push({
3993
+ memberId,
3994
+ name: data.name,
3995
+ totalDays: totalMemberDays
3996
+ });
3997
+ }
3998
+ const streakRank = streakData.sort((a, b) => b.maxStreak - a.maxStreak);
3999
+ const sortedLoyalty = loyaltyData.sort((a, b) => b.totalDays - a.totalDays);
4000
+ const maxLoyaltyDays = sortedLoyalty.length > 0 ? sortedLoyalty[0].totalDays : 1;
4001
+ const loyaltyRank = sortedLoyalty.map((item) => ({
4002
+ ...item,
4003
+ percentage: Math.round(item.totalDays / maxLoyaltyDays * 100)
4004
+ }));
4005
+ return { streakRank, loyaltyRank, totalDays };
4006
+ }
4007
+ function getCheckInAnalysis(db, filter) {
4008
+ const { conditions, params } = buildFilter(filter);
4009
+ const dailyActivity = db.prepare(
4010
+ `SELECT
4011
+ msg.sender_id as senderId,
4012
+ COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,
4013
+ DATE(msg.ts, 'unixepoch', 'localtime') as day
4014
+ FROM message msg
4015
+ JOIN member m ON msg.sender_id = m.id
4016
+ WHERE 1=1 ${SYSTEM_FILTER} ${conditions}
4017
+ GROUP BY msg.sender_id, day
4018
+ ORDER BY msg.sender_id, day`
4019
+ ).all(...params);
4020
+ if (dailyActivity.length === 0) {
4021
+ return { streakRank: [], loyaltyRank: [], totalDays: 0 };
4022
+ }
4023
+ return computeCheckIn(dailyActivity);
4024
+ }
4025
+ function computeMemeBattle(messages) {
4026
+ const emptyResult = {
4027
+ topBattles: [],
4028
+ rankByCount: [],
4029
+ rankByImageCount: [],
4030
+ totalBattles: 0
4031
+ };
4032
+ const battles = [];
4033
+ let currentChain = [];
4034
+ const processChain = () => {
4035
+ if (currentChain.length >= 3) {
4036
+ const senders = new Set(currentChain.map((m) => m.senderId));
4037
+ if (senders.size >= 2) {
4038
+ battles.push({
4039
+ startTime: currentChain[0].ts,
4040
+ endTime: currentChain[currentChain.length - 1].ts,
4041
+ msgs: currentChain.map(({ senderId, name, platformId }) => ({ senderId, name, platformId }))
4042
+ });
4043
+ }
4044
+ }
4045
+ currentChain = [];
4046
+ };
4047
+ for (const msg of messages) {
4048
+ if (msg.type === 1 || msg.type === 5) {
4049
+ currentChain.push({
4050
+ senderId: msg.senderId,
4051
+ name: msg.name,
4052
+ platformId: msg.platformId,
4053
+ ts: msg.ts
4054
+ });
4055
+ } else {
4056
+ processChain();
4057
+ }
4058
+ }
4059
+ processChain();
4060
+ if (battles.length === 0) return emptyResult;
4061
+ const topBattles = battles.map((battle) => ({
4062
+ startTime: battle.startTime,
4063
+ endTime: battle.endTime,
4064
+ totalImages: battle.msgs.length,
4065
+ participantCount: new Set(battle.msgs.map((m) => m.senderId)).size,
4066
+ participants: Object.values(
4067
+ battle.msgs.reduce((acc, curr) => {
4068
+ if (!acc[curr.senderId]) {
4069
+ acc[curr.senderId] = { memberId: curr.senderId, name: curr.name, imageCount: 0 };
4070
+ }
4071
+ acc[curr.senderId].imageCount++;
4072
+ return acc;
4073
+ }, {})
4074
+ ).sort((a, b) => b.imageCount - a.imageCount)
4075
+ })).sort((a, b) => b.totalImages - a.totalImages).slice(0, 30);
4076
+ const memberStats = /* @__PURE__ */ new Map();
4077
+ for (const battle of battles) {
4078
+ const participantsInBattle = /* @__PURE__ */ new Set();
4079
+ for (const msg of battle.msgs) {
4080
+ if (!memberStats.has(msg.senderId)) {
4081
+ memberStats.set(msg.senderId, {
4082
+ memberId: msg.senderId,
4083
+ platformId: msg.platformId,
4084
+ name: msg.name,
4085
+ battleCount: 0,
4086
+ imageCount: 0
4087
+ });
4088
+ }
4089
+ memberStats.get(msg.senderId).imageCount++;
4090
+ participantsInBattle.add(msg.senderId);
4091
+ }
4092
+ for (const memberId of participantsInBattle) {
4093
+ memberStats.get(memberId).battleCount++;
4094
+ }
4095
+ }
4096
+ const allStats = Array.from(memberStats.values());
4097
+ const rankByCount = [...allStats].sort((a, b) => b.battleCount - a.battleCount).map((item) => ({
4098
+ memberId: item.memberId,
4099
+ platformId: item.platformId,
4100
+ name: item.name,
4101
+ count: item.battleCount,
4102
+ percentage: battles.length > 0 ? Math.round(item.battleCount / battles.length * 1e4) / 100 : 0
4103
+ }));
4104
+ const totalBattleImages = battles.reduce((sum, b) => sum + b.msgs.length, 0);
4105
+ const rankByImageCount = [...allStats].sort((a, b) => b.imageCount - a.imageCount).map((item) => ({
4106
+ memberId: item.memberId,
4107
+ platformId: item.platformId,
4108
+ name: item.name,
4109
+ count: item.imageCount,
4110
+ percentage: totalBattleImages > 0 ? Math.round(item.imageCount / totalBattleImages * 1e4) / 100 : 0
4111
+ }));
4112
+ return {
4113
+ topBattles,
4114
+ rankByCount,
4115
+ rankByImageCount,
4116
+ totalBattles: battles.length
4117
+ };
4118
+ }
4119
+ function getMemeBattleAnalysis(db, filter) {
4120
+ const { conditions, params } = buildFilter(filter);
4121
+ const messages = db.prepare(
4122
+ `SELECT
4123
+ msg.sender_id as senderId,
4124
+ msg.type,
4125
+ msg.ts,
4126
+ m.platform_id as platformId,
4127
+ COALESCE(m.group_nickname, m.account_name, m.platform_id) as name
4128
+ FROM message msg
4129
+ JOIN member m ON msg.sender_id = m.id
4130
+ WHERE msg.type != 6 ${conditions}
4131
+ ORDER BY msg.ts ASC`
4132
+ ).all(...params);
4133
+ if (messages.length === 0) {
4134
+ return { topBattles: [], rankByCount: [], rankByImageCount: [], totalBattles: 0 };
4135
+ }
4136
+ return computeMemeBattle(messages);
4137
+ }
4138
+ function computeNightOwl(messages) {
4139
+ const emptyResult = {
4140
+ nightOwlRank: [],
4141
+ lastSpeakerRank: [],
4142
+ firstSpeakerRank: [],
4143
+ consecutiveRecords: [],
4144
+ champions: [],
4145
+ totalDays: 0
4146
+ };
4147
+ if (messages.length === 0) return emptyResult;
4148
+ function getNightOwlTitleByCount(count) {
4149
+ if (count === 0) return "\u517B\u751F\u8FBE\u4EBA";
4150
+ if (count <= 20) return "\u5076\u5C14\u5931\u7720";
4151
+ if (count <= 50) return "\u7ECF\u5E38\u5931\u7720";
4152
+ if (count <= 100) return "\u591C\u732B\u5B50";
4153
+ if (count <= 200) return "\u79C3\u5934\u9884\u5907\u5F79";
4154
+ if (count <= 500) return "\u4FEE\u4ED9\u7EC3\u4E60\u751F";
4155
+ return "\u5B88\u591C\u51A0\u519B";
4156
+ }
4157
+ function getAdjustedDate(ts) {
4158
+ const date = new Date(ts * 1e3);
4159
+ const hour = date.getHours();
4160
+ if (hour < 5) {
4161
+ date.setDate(date.getDate() - 1);
4162
+ }
4163
+ return date.toISOString().split("T")[0];
4164
+ }
4165
+ function formatMinutes(minutes) {
4166
+ const h = Math.floor(minutes / 60);
4167
+ const m = Math.round(minutes % 60);
4168
+ return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`;
4169
+ }
4170
+ const memberInfo = /* @__PURE__ */ new Map();
4171
+ const nightStats = /* @__PURE__ */ new Map();
4172
+ const dailyMessages = /* @__PURE__ */ new Map();
4173
+ const memberNightDays = /* @__PURE__ */ new Map();
4174
+ for (const msg of messages) {
4175
+ if (!memberInfo.has(msg.senderId)) {
4176
+ memberInfo.set(msg.senderId, { platformId: msg.platformId, name: msg.name });
4177
+ }
4178
+ const date = new Date(msg.ts * 1e3);
4179
+ const hour = date.getHours();
4180
+ const minute = date.getMinutes();
4181
+ const adjustedDate = getAdjustedDate(msg.ts);
4182
+ if (!nightStats.has(msg.senderId)) {
4183
+ nightStats.set(msg.senderId, { total: 0, h23: 0, h0: 0, h1: 0, h2: 0, h3to4: 0, totalMessages: 0 });
4184
+ }
4185
+ const stats = nightStats.get(msg.senderId);
4186
+ stats.totalMessages++;
4187
+ if (hour === 23) {
4188
+ stats.h23++;
4189
+ stats.total++;
4190
+ } else if (hour === 0) {
4191
+ stats.h0++;
4192
+ stats.total++;
4193
+ } else if (hour === 1) {
4194
+ stats.h1++;
4195
+ stats.total++;
4196
+ } else if (hour === 2) {
4197
+ stats.h2++;
4198
+ stats.total++;
4199
+ } else if (hour >= 3 && hour < 5) {
4200
+ stats.h3to4++;
4201
+ stats.total++;
4202
+ }
4203
+ if (hour >= 23 || hour < 5) {
4204
+ if (!memberNightDays.has(msg.senderId)) {
4205
+ memberNightDays.set(msg.senderId, /* @__PURE__ */ new Set());
4206
+ }
4207
+ memberNightDays.get(msg.senderId).add(adjustedDate);
4208
+ }
4209
+ if (!dailyMessages.has(adjustedDate)) {
4210
+ dailyMessages.set(adjustedDate, []);
4211
+ }
4212
+ dailyMessages.get(adjustedDate).push({ senderId: msg.senderId, ts: msg.ts, hour, minute });
4213
+ }
4214
+ const totalDays = dailyMessages.size;
4215
+ const nightOwlRank = [];
4216
+ for (const [memberId, stats] of nightStats.entries()) {
4217
+ if (stats.total === 0) continue;
4218
+ const info = memberInfo.get(memberId);
4219
+ nightOwlRank.push({
4220
+ memberId,
4221
+ platformId: info.platformId,
4222
+ name: info.name,
4223
+ totalNightMessages: stats.total,
4224
+ title: getNightOwlTitleByCount(stats.total),
4225
+ hourlyBreakdown: { h23: stats.h23, h0: stats.h0, h1: stats.h1, h2: stats.h2, h3to4: stats.h3to4 },
4226
+ percentage: stats.totalMessages > 0 ? Math.round(stats.total / stats.totalMessages * 1e4) / 100 : 0
4227
+ });
4228
+ }
4229
+ nightOwlRank.sort((a, b) => b.totalNightMessages - a.totalNightMessages);
4230
+ const lastSpeakerStats = /* @__PURE__ */ new Map();
4231
+ const firstSpeakerStats = /* @__PURE__ */ new Map();
4232
+ for (const [, dayMessages] of dailyMessages.entries()) {
4233
+ if (dayMessages.length === 0) continue;
4234
+ const lastMsg = dayMessages[dayMessages.length - 1];
4235
+ if (!lastSpeakerStats.has(lastMsg.senderId)) {
4236
+ lastSpeakerStats.set(lastMsg.senderId, { count: 0, times: [] });
4237
+ }
4238
+ const lastStats = lastSpeakerStats.get(lastMsg.senderId);
4239
+ lastStats.count++;
4240
+ lastStats.times.push(lastMsg.hour * 60 + lastMsg.minute);
4241
+ const firstMsg = dayMessages[0];
4242
+ if (!firstSpeakerStats.has(firstMsg.senderId)) {
4243
+ firstSpeakerStats.set(firstMsg.senderId, { count: 0, times: [] });
4244
+ }
4245
+ const firstStats = firstSpeakerStats.get(firstMsg.senderId);
4246
+ firstStats.count++;
4247
+ firstStats.times.push(firstMsg.hour * 60 + firstMsg.minute);
4248
+ }
4249
+ const lastSpeakerRank = [];
4250
+ for (const [memberId, stats] of lastSpeakerStats.entries()) {
4251
+ const info = memberInfo.get(memberId);
4252
+ const avgMinutes = stats.times.reduce((a, b) => a + b, 0) / stats.times.length;
4253
+ const maxMinutes = Math.max(...stats.times);
4254
+ lastSpeakerRank.push({
4255
+ memberId,
4256
+ platformId: info.platformId,
4257
+ name: info.name,
4258
+ count: stats.count,
4259
+ avgTime: formatMinutes(avgMinutes),
4260
+ extremeTime: formatMinutes(maxMinutes),
4261
+ percentage: totalDays > 0 ? Math.round(stats.count / totalDays * 1e4) / 100 : 0
4262
+ });
4263
+ }
4264
+ lastSpeakerRank.sort((a, b) => b.count - a.count);
4265
+ const firstSpeakerRank = [];
4266
+ for (const [memberId, stats] of firstSpeakerStats.entries()) {
4267
+ const info = memberInfo.get(memberId);
4268
+ const avgMinutes = stats.times.reduce((a, b) => a + b, 0) / stats.times.length;
4269
+ const minMinutes = Math.min(...stats.times);
4270
+ firstSpeakerRank.push({
4271
+ memberId,
4272
+ platformId: info.platformId,
4273
+ name: info.name,
4274
+ count: stats.count,
4275
+ avgTime: formatMinutes(avgMinutes),
4276
+ extremeTime: formatMinutes(minMinutes),
4277
+ percentage: totalDays > 0 ? Math.round(stats.count / totalDays * 1e4) / 100 : 0
4278
+ });
4279
+ }
4280
+ firstSpeakerRank.sort((a, b) => b.count - a.count);
4281
+ const consecutiveRecords = [];
4282
+ for (const [memberId, nightDaysSet] of memberNightDays.entries()) {
4283
+ if (nightDaysSet.size === 0) continue;
4284
+ const info = memberInfo.get(memberId);
4285
+ const sortedNightDays = Array.from(nightDaysSet).sort();
4286
+ let maxStreak = 1;
4287
+ let currentStreak = 1;
4288
+ for (let i = 1; i < sortedNightDays.length; i++) {
4289
+ const prevDate = new Date(sortedNightDays[i - 1]);
4290
+ const currDate = new Date(sortedNightDays[i]);
4291
+ const diffDays = (currDate.getTime() - prevDate.getTime()) / (1e3 * 60 * 60 * 24);
4292
+ if (diffDays === 1) {
4293
+ currentStreak++;
4294
+ maxStreak = Math.max(maxStreak, currentStreak);
4295
+ } else {
4296
+ currentStreak = 1;
4297
+ }
4298
+ }
4299
+ const lastNightDay = sortedNightDays[sortedNightDays.length - 1];
4300
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4301
+ const yesterday = new Date(Date.now() - 864e5).toISOString().split("T")[0];
4302
+ const isCurrentStreak = lastNightDay === today || lastNightDay === yesterday;
4303
+ consecutiveRecords.push({
4304
+ memberId,
4305
+ platformId: info.platformId,
4306
+ name: info.name,
4307
+ maxConsecutiveDays: maxStreak,
4308
+ currentStreak: isCurrentStreak ? currentStreak : 0
4309
+ });
4310
+ }
4311
+ consecutiveRecords.sort((a, b) => b.maxConsecutiveDays - a.maxConsecutiveDays);
4312
+ const championScores = /* @__PURE__ */ new Map();
4313
+ for (const item of nightOwlRank) {
4314
+ if (!championScores.has(item.memberId)) {
4315
+ championScores.set(item.memberId, { nightMessages: 0, lastSpeakerCount: 0, consecutiveDays: 0 });
4316
+ }
4317
+ championScores.get(item.memberId).nightMessages = item.totalNightMessages;
4318
+ }
4319
+ for (const item of lastSpeakerRank) {
4320
+ if (!championScores.has(item.memberId)) {
4321
+ championScores.set(item.memberId, { nightMessages: 0, lastSpeakerCount: 0, consecutiveDays: 0 });
4322
+ }
4323
+ championScores.get(item.memberId).lastSpeakerCount = item.count;
4324
+ }
4325
+ for (const item of consecutiveRecords) {
4326
+ if (!championScores.has(item.memberId)) {
4327
+ championScores.set(item.memberId, { nightMessages: 0, lastSpeakerCount: 0, consecutiveDays: 0 });
4328
+ }
4329
+ championScores.get(item.memberId).consecutiveDays = item.maxConsecutiveDays;
4330
+ }
4331
+ const champions = [];
4332
+ for (const [memberId, scores] of championScores.entries()) {
4333
+ const info = memberInfo.get(memberId);
4334
+ const score = scores.nightMessages * 1 + scores.lastSpeakerCount * 10 + scores.consecutiveDays * 20;
4335
+ if (score > 0) {
4336
+ champions.push({
4337
+ memberId,
4338
+ platformId: info.platformId,
4339
+ name: info.name,
4340
+ score,
4341
+ nightMessages: scores.nightMessages,
4342
+ lastSpeakerCount: scores.lastSpeakerCount,
4343
+ consecutiveDays: scores.consecutiveDays
4344
+ });
4345
+ }
4346
+ }
4347
+ champions.sort((a, b) => b.score - a.score);
4348
+ return { nightOwlRank, lastSpeakerRank, firstSpeakerRank, consecutiveRecords, champions, totalDays };
4349
+ }
4350
+ function getNightOwlAnalysis(db, filter) {
4351
+ const { conditions, params } = buildFilter(filter);
4352
+ const messages = db.prepare(
4353
+ `SELECT
4354
+ msg.id,
4355
+ msg.sender_id as senderId,
4356
+ msg.ts,
4357
+ m.platform_id as platformId,
4358
+ COALESCE(m.group_nickname, m.account_name, m.platform_id) as name
4359
+ FROM message msg
4360
+ JOIN member m ON msg.sender_id = m.id
4361
+ WHERE 1=1 ${SYSTEM_FILTER} ${conditions}
4362
+ ORDER BY msg.ts ASC`
4363
+ ).all(...params);
4364
+ if (messages.length === 0) {
4365
+ return {
4366
+ nightOwlRank: [],
4367
+ lastSpeakerRank: [],
4368
+ firstSpeakerRank: [],
4369
+ consecutiveRecords: [],
4370
+ champions: [],
4371
+ totalDays: 0
4372
+ };
4373
+ }
4374
+ return computeNightOwl(messages);
4375
+ }
4376
+ function computeRepeat(messages) {
4377
+ const originatorCount = /* @__PURE__ */ new Map();
4378
+ const initiatorCount = /* @__PURE__ */ new Map();
4379
+ const breakerCount = /* @__PURE__ */ new Map();
4380
+ const memberMessageCount = /* @__PURE__ */ new Map();
4381
+ const memberInfo = /* @__PURE__ */ new Map();
4382
+ const chainLengthCount = /* @__PURE__ */ new Map();
4383
+ const contentStats = /* @__PURE__ */ new Map();
4384
+ let currentContent = null;
4385
+ let repeatChain = [];
4386
+ let totalRepeatChains = 0;
4387
+ let totalChainLength = 0;
4388
+ const fastestRepeaterStats = /* @__PURE__ */ new Map();
4389
+ const processRepeatChain = (chain8, breakerId) => {
4390
+ if (chain8.length < 3) return;
4391
+ totalRepeatChains++;
4392
+ const chainLength = chain8.length;
4393
+ totalChainLength += chainLength;
4394
+ const oId = chain8[0].senderId;
4395
+ originatorCount.set(oId, (originatorCount.get(oId) || 0) + 1);
4396
+ const iId = chain8[1].senderId;
4397
+ initiatorCount.set(iId, (initiatorCount.get(iId) || 0) + 1);
4398
+ if (breakerId !== void 0) {
4399
+ breakerCount.set(breakerId, (breakerCount.get(breakerId) || 0) + 1);
4400
+ }
4401
+ chainLengthCount.set(chainLength, (chainLengthCount.get(chainLength) || 0) + 1);
4402
+ const content = chain8[0].content;
4403
+ const chainTs = chain8[0].ts;
4404
+ const firstMsgId = chain8[0].id;
4405
+ const existing = contentStats.get(content);
4406
+ if (existing) {
4407
+ existing.count++;
4408
+ existing.lastTs = Math.max(existing.lastTs, chainTs);
4409
+ if (chainLength > existing.maxChainLength) {
4410
+ existing.maxChainLength = chainLength;
4411
+ existing.originatorId = oId;
4412
+ existing.firstMessageId = firstMsgId;
4413
+ }
4414
+ } else {
4415
+ contentStats.set(content, {
4416
+ count: 1,
4417
+ maxChainLength: chainLength,
4418
+ originatorId: oId,
4419
+ lastTs: chainTs,
4420
+ firstMessageId: firstMsgId
4421
+ });
4422
+ }
4423
+ for (let i = 1; i < chain8.length; i++) {
4424
+ const currentMsg = chain8[i];
4425
+ const prevMsg = chain8[i - 1];
4426
+ const diff = (currentMsg.ts - prevMsg.ts) * 1e3;
4427
+ if (diff <= 20 * 1e3) {
4428
+ if (!fastestRepeaterStats.has(currentMsg.senderId)) {
4429
+ fastestRepeaterStats.set(currentMsg.senderId, { totalDiff: 0, count: 0 });
4430
+ }
4431
+ const stats = fastestRepeaterStats.get(currentMsg.senderId);
4432
+ stats.totalDiff += diff;
4433
+ stats.count++;
4434
+ }
4435
+ }
4436
+ };
4437
+ for (const msg of messages) {
4438
+ if (!memberInfo.has(msg.senderId)) {
4439
+ memberInfo.set(msg.senderId, { platformId: msg.platformId, name: msg.name });
4440
+ }
4441
+ memberMessageCount.set(msg.senderId, (memberMessageCount.get(msg.senderId) || 0) + 1);
4442
+ const content = msg.content.trim();
4443
+ if (content === currentContent) {
4444
+ const lastSender = repeatChain[repeatChain.length - 1]?.senderId;
4445
+ if (lastSender !== msg.senderId) {
4446
+ repeatChain.push({ id: msg.id, senderId: msg.senderId, content, ts: msg.ts });
4447
+ }
4448
+ } else {
4449
+ processRepeatChain(repeatChain, msg.senderId);
4450
+ currentContent = content;
4451
+ repeatChain = [{ id: msg.id, senderId: msg.senderId, content, ts: msg.ts }];
4452
+ }
4453
+ }
4454
+ processRepeatChain(repeatChain);
4455
+ const buildRankList = (countMap, total) => {
4456
+ const items = [];
4457
+ for (const [memberId, count] of countMap.entries()) {
4458
+ const info = memberInfo.get(memberId);
4459
+ if (info) {
4460
+ items.push({
4461
+ memberId,
4462
+ platformId: info.platformId,
4463
+ name: info.name,
4464
+ count,
4465
+ percentage: total > 0 ? Math.round(count / total * 1e4) / 100 : 0
4466
+ });
4467
+ }
4468
+ }
4469
+ return items.sort((a, b) => b.count - a.count);
4470
+ };
4471
+ const buildRateList = (countMap) => {
4472
+ const items = [];
4473
+ for (const [memberId, count] of countMap.entries()) {
4474
+ const info = memberInfo.get(memberId);
4475
+ const totalMessages = memberMessageCount.get(memberId) || 0;
4476
+ if (info && totalMessages > 0) {
4477
+ items.push({
4478
+ memberId,
4479
+ platformId: info.platformId,
4480
+ name: info.name,
4481
+ count,
4482
+ totalMessages,
4483
+ rate: Math.round(count / totalMessages * 1e4) / 100
4484
+ });
4485
+ }
4486
+ }
4487
+ return items.sort((a, b) => b.rate - a.rate);
4488
+ };
4489
+ const buildFastestList = () => {
4490
+ const items = [];
4491
+ for (const [memberId, stats] of fastestRepeaterStats.entries()) {
4492
+ if (stats.count < 5) continue;
4493
+ const info = memberInfo.get(memberId);
4494
+ if (info) {
4495
+ items.push({
4496
+ memberId,
4497
+ platformId: info.platformId,
4498
+ name: info.name,
4499
+ count: stats.count,
4500
+ avgTimeDiff: Math.round(stats.totalDiff / stats.count)
4501
+ });
4502
+ }
4503
+ }
4504
+ return items.sort((a, b) => a.avgTimeDiff - b.avgTimeDiff);
4505
+ };
4506
+ const chainLengthDistribution = [];
4507
+ for (const [length, count] of chainLengthCount.entries()) {
4508
+ chainLengthDistribution.push({ length, count });
4509
+ }
4510
+ chainLengthDistribution.sort((a, b) => a.length - b.length);
4511
+ const hotContents = [];
4512
+ for (const [content, stats] of contentStats.entries()) {
4513
+ const originatorInfo = memberInfo.get(stats.originatorId);
4514
+ hotContents.push({
4515
+ content,
4516
+ count: stats.count,
4517
+ maxChainLength: stats.maxChainLength,
4518
+ originatorName: originatorInfo?.name || "\u672A\u77E5",
4519
+ lastTs: stats.lastTs,
4520
+ firstMessageId: stats.firstMessageId
4521
+ });
4522
+ }
4523
+ hotContents.sort((a, b) => b.maxChainLength - a.maxChainLength);
4524
+ const top50HotContents = hotContents.slice(0, 100);
4525
+ return {
4526
+ originators: buildRankList(originatorCount, totalRepeatChains),
4527
+ initiators: buildRankList(initiatorCount, totalRepeatChains),
4528
+ breakers: buildRankList(breakerCount, totalRepeatChains),
4529
+ fastestRepeaters: buildFastestList(),
4530
+ originatorRates: buildRateList(originatorCount),
4531
+ initiatorRates: buildRateList(initiatorCount),
4532
+ breakerRates: buildRateList(breakerCount),
4533
+ chainLengthDistribution,
4534
+ hotContents: top50HotContents,
4535
+ avgChainLength: totalRepeatChains > 0 ? Math.round(totalChainLength / totalRepeatChains * 100) / 100 : 0,
4536
+ totalRepeatChains
4537
+ };
4538
+ }
4539
+ function getRepeatAnalysis(db, filter) {
4540
+ const { conditions, params } = buildFilter(filter);
4541
+ const messages = db.prepare(
4542
+ `SELECT
4543
+ msg.id,
4544
+ msg.sender_id as senderId,
4545
+ msg.content,
4546
+ msg.ts,
4547
+ m.platform_id as platformId,
4548
+ COALESCE(m.group_nickname, m.account_name, m.platform_id) as name
4549
+ FROM message msg
4550
+ JOIN member m ON msg.sender_id = m.id
4551
+ WHERE 1=1 ${SYSTEM_FILTER}
4552
+ AND msg.type = 0
4553
+ AND msg.content IS NOT NULL
4554
+ AND TRIM(msg.content) != ''
4555
+ ${conditions}
4556
+ ORDER BY msg.ts ASC, msg.id ASC`
4557
+ ).all(...params);
4558
+ if (messages.length === 0) {
4559
+ return {
4560
+ originators: [],
4561
+ initiators: [],
4562
+ breakers: [],
4563
+ fastestRepeaters: [],
4564
+ originatorRates: [],
4565
+ initiatorRates: [],
4566
+ breakerRates: [],
4567
+ chainLengthDistribution: [],
4568
+ hotContents: [],
4569
+ avgChainLength: 0,
4570
+ totalRepeatChains: 0
4571
+ };
4572
+ }
4573
+ return computeRepeat(messages);
4574
+ }
4575
+
3739
4576
  // ../../packages/core/src/version.ts
3740
4577
  function parseStableVersion(version) {
3741
4578
  const normalized = version.trim().replace(/^v/i, "");
@@ -4797,8 +5634,9 @@ var PUNCTUATION_REGEX = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、;
4797
5634
  var URL_REGEX = /https?:\/\/[^\s]+/g;
4798
5635
  var MENTION_REGEX = /@[^\s@]+/g;
4799
5636
  var PURE_NUMBER_REGEX = /^\d+$/;
5637
+ var SYSTEM_PLACEHOLDER_REGEX = /\[(?:图片|视频|语音|文件|动画表情|表情|链接|位置|名片|红包|转账|音乐|Image|Video|Voice|File|Sticker|Link)\]/gi;
4800
5638
  function cleanText(text) {
4801
- return text.replace(URL_REGEX, " ").replace(MENTION_REGEX, " ").replace(EMOJI_REGEX, " ").replace(PUNCTUATION_REGEX, " ").replace(/\s+/g, " ").trim();
5639
+ return text.replace(URL_REGEX, " ").replace(MENTION_REGEX, " ").replace(SYSTEM_PLACEHOLDER_REGEX, " ").replace(EMOJI_REGEX, " ").replace(PUNCTUATION_REGEX, " ").replace(/\s+/g, " ").trim();
4802
5640
  }
4803
5641
  function isValidWord(word, locale, minLength, enableStopwords, isStopwordFn) {
4804
5642
  if (!word || word.trim().length === 0) return false;
@@ -6442,7 +7280,7 @@ function mergeMembers2(sources, collidingIds) {
6442
7280
  }
6443
7281
 
6444
7282
  // ../../packages/core/src/schema/tables.ts
6445
- var CURRENT_SCHEMA_VERSION = 6;
7283
+ var CURRENT_SCHEMA_VERSION = 7;
6446
7284
  var CHAT_DB_TABLES = `
6447
7285
  CREATE TABLE IF NOT EXISTS meta (
6448
7286
  name TEXT NOT NULL,
@@ -10423,6 +11261,49 @@ function batchSegmentWithFrequency(texts, locale, options = {}) {
10423
11261
  const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
10424
11262
  return { words: new Map(sorted), uniqueWords: filtered.size, totalWords };
10425
11263
  }
11264
+ function batchSegmentChineseWithStats(texts, locale, options = {}) {
11265
+ const {
11266
+ minLength,
11267
+ minCount = 2,
11268
+ topN = 100,
11269
+ posFilterMode = "meaningful",
11270
+ customPosTags,
11271
+ enableStopwords = true,
11272
+ dictType = "default",
11273
+ excludeWords
11274
+ } = options;
11275
+ const effectiveMinLength = minLength ?? 2;
11276
+ const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
11277
+ const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
11278
+ const wordFrequency = /* @__PURE__ */ new Map();
11279
+ const posTagStats = /* @__PURE__ */ new Map();
11280
+ try {
11281
+ const jieba = getJieba(dictType);
11282
+ for (const text of texts) {
11283
+ const cleaned = cleanText(text);
11284
+ if (!cleaned) continue;
11285
+ for (const { tag, word } of jieba.tag(cleaned)) {
11286
+ if (!isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword)) continue;
11287
+ posTagStats.set(tag, (posTagStats.get(tag) || 0) + 1);
11288
+ if (!allowedTags.has(tag)) continue;
11289
+ if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
11290
+ wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
11291
+ }
11292
+ }
11293
+ } catch (error) {
11294
+ console.error("[NLP] Chinese single-pass segmentation failed:", error);
11295
+ }
11296
+ const filtered = /* @__PURE__ */ new Map();
11297
+ let totalWords = 0;
11298
+ for (const [word, count] of wordFrequency) {
11299
+ if (count >= minCount) {
11300
+ filtered.set(word, count);
11301
+ totalWords += count;
11302
+ }
11303
+ }
11304
+ const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
11305
+ return { words: new Map(sorted), uniqueWords: filtered.size, totalWords, posTagStats };
11306
+ }
10426
11307
  function collectPosTagStats(texts, minWordLength = 2, enableStopwords = true, dictType = "default") {
10427
11308
  const posStats = /* @__PURE__ */ new Map();
10428
11309
  try {
@@ -10490,12 +11371,7 @@ function computeWordFrequency(db, params) {
10490
11371
  return { words: [], totalWords: 0, totalMessages: 0, uniqueWords: 0 };
10491
11372
  }
10492
11373
  const texts = messages.map((m) => m.content);
10493
- let posTagStats;
10494
- if (locale.startsWith("zh")) {
10495
- const posStatsMap = collectPosTagStats(texts, minWordLength ?? 2, enableStopwords, dictType);
10496
- posTagStats = [...posStatsMap.entries()].map(([tag, count]) => ({ tag, count }));
10497
- }
10498
- const result = batchSegmentWithFrequency(texts, locale, {
11374
+ const segmentOptions = {
10499
11375
  minLength: minWordLength,
10500
11376
  minCount,
10501
11377
  topN,
@@ -10504,7 +11380,20 @@ function computeWordFrequency(db, params) {
10504
11380
  enableStopwords,
10505
11381
  dictType,
10506
11382
  excludeWords
10507
- });
11383
+ };
11384
+ let posTagStats;
11385
+ let result;
11386
+ if (locale.startsWith("zh") && posFilterMode !== "all") {
11387
+ const combined = batchSegmentChineseWithStats(texts, locale, segmentOptions);
11388
+ result = { words: combined.words, uniqueWords: combined.uniqueWords, totalWords: combined.totalWords };
11389
+ posTagStats = [...combined.posTagStats.entries()].map(([tag, count]) => ({ tag, count }));
11390
+ } else {
11391
+ if (locale.startsWith("zh")) {
11392
+ const posStatsMap = collectPosTagStats(texts, minWordLength ?? 2, enableStopwords, dictType);
11393
+ posTagStats = [...posStatsMap.entries()].map(([tag, count]) => ({ tag, count }));
11394
+ }
11395
+ result = batchSegmentWithFrequency(texts, locale, segmentOptions);
11396
+ }
10508
11397
  let topNTotalWords = 0;
10509
11398
  for (const count of result.words.values()) topNTotalWords += count;
10510
11399
  const words = [...result.words.entries()].map(([word, count]) => ({
@@ -11947,8 +12836,8 @@ var AssistantManager = class {
11947
12836
  }
11948
12837
  // ==================== Init ====================
11949
12838
  init() {
11950
- const { fs: fs42, assistantsDir } = this.deps;
11951
- fs42.ensureDir(assistantsDir);
12839
+ const { fs: fs43, assistantsDir } = this.deps;
12840
+ fs43.ensureDir(assistantsDir);
11952
12841
  const generalCreated = this.ensureGeneralAssistants();
11953
12842
  this.loadAll();
11954
12843
  this.initialized = true;
@@ -11959,26 +12848,26 @@ var AssistantManager = class {
11959
12848
  return { total: this.cache.size, generalCreated };
11960
12849
  }
11961
12850
  ensureGeneralAssistants() {
11962
- const { fs: fs42, assistantsDir } = this.deps;
12851
+ const { fs: fs43, assistantsDir } = this.deps;
11963
12852
  let anyCreated = false;
11964
12853
  for (const id of this.generalIds) {
11965
12854
  const config = this.getBuiltinConfig(id);
11966
12855
  if (!config) continue;
11967
- const filePath = fs42.joinPath(assistantsDir, `${id}.md`);
11968
- if (fs42.fileExists(filePath)) continue;
11969
- fs42.writeFile(filePath, serializeAssistant({ ...config, builtinId: config.id }));
12856
+ const filePath = fs43.joinPath(assistantsDir, `${id}.md`);
12857
+ if (fs43.fileExists(filePath)) continue;
12858
+ fs43.writeFile(filePath, serializeAssistant({ ...config, builtinId: config.id }));
11970
12859
  anyCreated = true;
11971
12860
  }
11972
12861
  return anyCreated;
11973
12862
  }
11974
12863
  loadAll() {
11975
- const { fs: fs42, assistantsDir } = this.deps;
12864
+ const { fs: fs43, assistantsDir } = this.deps;
11976
12865
  this.cache.clear();
11977
- const files = fs42.listFiles(assistantsDir, ".md");
12866
+ const files = fs43.listFiles(assistantsDir, ".md");
11978
12867
  for (const file of files) {
11979
12868
  try {
11980
- const filePath = fs42.joinPath(assistantsDir, file);
11981
- const content = fs42.readFile(filePath);
12869
+ const filePath = fs43.joinPath(assistantsDir, file);
12870
+ const content = fs43.readFile(filePath);
11982
12871
  const config = parseAssistantFile(content, filePath);
11983
12872
  if (config) {
11984
12873
  this.cache.set(config.id, config);
@@ -12986,21 +13875,21 @@ var SkillManagerCore = class {
12986
13875
  }
12987
13876
  // ==================== Init ====================
12988
13877
  init() {
12989
- const { fs: fs42, skillsDir } = this.deps;
12990
- fs42.ensureDir(skillsDir);
13878
+ const { fs: fs43, skillsDir } = this.deps;
13879
+ fs43.ensureDir(skillsDir);
12991
13880
  this.loadAll();
12992
13881
  this.initialized = true;
12993
13882
  this.deps.logger?.info("SkillManager", "Initialized", { total: this.cache.size });
12994
13883
  return { total: this.cache.size };
12995
13884
  }
12996
13885
  loadAll() {
12997
- const { fs: fs42, skillsDir } = this.deps;
13886
+ const { fs: fs43, skillsDir } = this.deps;
12998
13887
  this.cache.clear();
12999
- const files = fs42.listFiles(skillsDir, ".md");
13888
+ const files = fs43.listFiles(skillsDir, ".md");
13000
13889
  for (const file of files) {
13001
13890
  try {
13002
- const filePath = fs42.joinPath(skillsDir, file);
13003
- const content = fs42.readFile(filePath);
13891
+ const filePath = fs43.joinPath(skillsDir, file);
13892
+ const content = fs43.readFile(filePath);
13004
13893
  const def = parseSkillFile(content, filePath);
13005
13894
  if (def) {
13006
13895
  this.cache.set(def.id, def);
@@ -18642,8 +19531,8 @@ var SQLiteVectorStore = class {
18642
19531
  const dimRow = this.db.prepare("SELECT dimensions FROM vectors LIMIT 1").get();
18643
19532
  let sizeBytes;
18644
19533
  try {
18645
- const fs42 = await import("fs");
18646
- const stats = fs42.statSync(this.dbPath);
19534
+ const fs43 = await import("fs");
19535
+ const stats = fs43.statSync(this.dbPath);
18647
19536
  sizeBytes = stats.size;
18648
19537
  } catch {
18649
19538
  }
@@ -20663,6 +21552,69 @@ function getChatDbMigrations(deps) {
20663
21552
  CREATE INDEX IF NOT EXISTS idx_context_segment ON message_context(segment_id);
20664
21553
  `);
20665
21554
  }
21555
+ },
21556
+ {
21557
+ version: 7,
21558
+ description: "Repair fully missing segment message contexts",
21559
+ up: (db) => {
21560
+ const counts = db.prepare(
21561
+ `SELECT
21562
+ (SELECT COUNT(*) FROM message) AS messageCount,
21563
+ (SELECT COUNT(*) FROM segment) AS segmentCount,
21564
+ (SELECT COUNT(*) FROM message_context) AS contextCount,
21565
+ (SELECT COALESCE(SUM(message_count), 0) FROM segment) AS declaredMessageCount`
21566
+ ).get();
21567
+ if (!counts || counts.messageCount === 0 || counts.segmentCount === 0 || counts.contextCount > 0) {
21568
+ return;
21569
+ }
21570
+ if (counts.declaredMessageCount !== counts.messageCount) {
21571
+ throw new Error(
21572
+ "Cannot safely repair missing message_context rows: segment message counts do not match message table"
21573
+ );
21574
+ }
21575
+ const invalidRange = db.prepare(
21576
+ `WITH ordered_segments AS (
21577
+ SELECT
21578
+ id,
21579
+ start_ts,
21580
+ end_ts,
21581
+ LAG(end_ts) OVER (ORDER BY start_ts, id) AS previousEndTs
21582
+ FROM segment
21583
+ )
21584
+ SELECT COUNT(*) AS count
21585
+ FROM ordered_segments
21586
+ WHERE start_ts > end_ts OR (previousEndTs IS NOT NULL AND start_ts <= previousEndTs)`
21587
+ ).get();
21588
+ if ((invalidRange?.count ?? 0) > 0) {
21589
+ throw new Error(
21590
+ "Cannot safely repair missing message_context rows: segment time ranges are invalid or overlapping"
21591
+ );
21592
+ }
21593
+ const mismatchedSegments = db.prepare(
21594
+ `SELECT COUNT(*) AS count
21595
+ FROM (
21596
+ SELECT s.id
21597
+ FROM segment s
21598
+ LEFT JOIN message m ON m.ts >= s.start_ts AND m.ts <= s.end_ts
21599
+ GROUP BY s.id
21600
+ HAVING COUNT(m.id) != s.message_count
21601
+ )`
21602
+ ).get();
21603
+ if ((mismatchedSegments?.count ?? 0) > 0) {
21604
+ throw new Error(
21605
+ "Cannot safely repair missing message_context rows: messages do not match segment time ranges"
21606
+ );
21607
+ }
21608
+ const result = db.prepare(
21609
+ `INSERT INTO message_context (message_id, segment_id, topic_id)
21610
+ SELECT m.id, s.id, NULL
21611
+ FROM segment s
21612
+ JOIN message m ON m.ts >= s.start_ts AND m.ts <= s.end_ts`
21613
+ ).run();
21614
+ if (result.changes !== counts.messageCount) {
21615
+ throw new Error("Cannot safely repair missing message_context rows: rebuilt row count is inconsistent");
21616
+ }
21617
+ }
20666
21618
  }
20667
21619
  ];
20668
21620
  }
@@ -21263,8 +22215,32 @@ function exportWithFormat(params, openDatabase) {
21263
22215
  }
21264
22216
  }
21265
22217
 
21266
- // ../../packages/node-runtime/src/preferences.ts
22218
+ // ../../packages/node-runtime/src/cache/analytics-cache.ts
21267
22219
  import * as fs39 from "fs";
22220
+ function getDbFileVersion(dbPath) {
22221
+ const parts = [];
22222
+ for (const p of [dbPath, `${dbPath}-wal`]) {
22223
+ try {
22224
+ const st = fs39.statSync(p);
22225
+ parts.push(`${Math.floor(st.mtimeMs)}:${st.size}`);
22226
+ } catch {
22227
+ parts.push("-");
22228
+ }
22229
+ }
22230
+ return parts.join("|");
22231
+ }
22232
+ function getOrComputeAnalysisCache(sessionId, key, queryCacheDir, version, compute) {
22233
+ const cached = getCache(sessionId, key, queryCacheDir);
22234
+ if (cached && cached.v === version) {
22235
+ return cached.data;
22236
+ }
22237
+ const data = compute();
22238
+ setCache(sessionId, key, { v: version, data }, queryCacheDir);
22239
+ return data;
22240
+ }
22241
+
22242
+ // ../../packages/node-runtime/src/preferences.ts
22243
+ import * as fs40 from "fs";
21268
22244
  import * as path37 from "path";
21269
22245
  var DEFAULTS = {
21270
22246
  pinnedSessionIds: [],
@@ -21316,8 +22292,8 @@ var PreferencesManager = class {
21316
22292
  load() {
21317
22293
  if (this.cache) return this.cache;
21318
22294
  try {
21319
- if (fs39.existsSync(this.filePath)) {
21320
- const raw = fs39.readFileSync(this.filePath, "utf-8");
22295
+ if (fs40.existsSync(this.filePath)) {
22296
+ const raw = fs40.readFileSync(this.filePath, "utf-8");
21321
22297
  const parsed = JSON.parse(raw);
21322
22298
  const migrated = this.migrateLegacyDesensitizeRules(parsed, raw);
21323
22299
  this.cache = this.mergeDefaults(migrated.preferences);
@@ -21433,20 +22409,20 @@ var PreferencesManager = class {
21433
22409
  }
21434
22410
  backupLegacyPreferences(rawContent) {
21435
22411
  const backupDir = path37.join(path37.dirname(this.filePath), "backups");
21436
- if (!fs39.existsSync(backupDir)) {
21437
- fs39.mkdirSync(backupDir, { recursive: true });
22412
+ if (!fs40.existsSync(backupDir)) {
22413
+ fs40.mkdirSync(backupDir, { recursive: true });
21438
22414
  }
21439
22415
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
21440
22416
  const backupPath = path37.join(backupDir, `preferences-pre-desensitize-groups-${timestamp}.json`);
21441
- fs39.writeFileSync(backupPath, rawContent, "utf-8");
22417
+ fs40.writeFileSync(backupPath, rawContent, "utf-8");
21442
22418
  console.info(`[Preferences] Backed up legacy desensitize rules to ${backupPath}`);
21443
22419
  }
21444
22420
  writePreferences(preferences) {
21445
22421
  const dir = path37.dirname(this.filePath);
21446
- if (!fs39.existsSync(dir)) {
21447
- fs39.mkdirSync(dir, { recursive: true });
22422
+ if (!fs40.existsSync(dir)) {
22423
+ fs40.mkdirSync(dir, { recursive: true });
21448
22424
  }
21449
- fs39.writeFileSync(this.filePath, JSON.stringify(preferences, null, 2), "utf-8");
22425
+ fs40.writeFileSync(this.filePath, JSON.stringify(preferences, null, 2), "utf-8");
21450
22426
  }
21451
22427
  /**
21452
22428
  * Deep merge: arrays are replaced (not concatenated), objects are merged.
@@ -21566,7 +22542,7 @@ function* serializeChatLabToJsonl(data) {
21566
22542
  }
21567
22543
 
21568
22544
  // ../../packages/node-runtime/src/merger/temp-db.ts
21569
- import * as fs40 from "fs";
22545
+ import * as fs41 from "fs";
21570
22546
  import * as path38 from "path";
21571
22547
  var TEMP_DB_SCHEMA = `
21572
22548
  CREATE TABLE IF NOT EXISTS meta (
@@ -21812,18 +22788,18 @@ function exportSessionToJson(db) {
21812
22788
  }
21813
22789
  function deleteTempDatabase(dbPath) {
21814
22790
  try {
21815
- if (fs40.existsSync(dbPath)) fs40.unlinkSync(dbPath);
22791
+ if (fs41.existsSync(dbPath)) fs41.unlinkSync(dbPath);
21816
22792
  const walPath = dbPath + "-wal";
21817
22793
  const shmPath = dbPath + "-shm";
21818
- if (fs40.existsSync(walPath)) fs40.unlinkSync(walPath);
21819
- if (fs40.existsSync(shmPath)) fs40.unlinkSync(shmPath);
22794
+ if (fs41.existsSync(walPath)) fs41.unlinkSync(walPath);
22795
+ if (fs41.existsSync(shmPath)) fs41.unlinkSync(shmPath);
21820
22796
  } catch {
21821
22797
  }
21822
22798
  }
21823
22799
  function cleanupTempDatabases(tempDir) {
21824
22800
  try {
21825
- if (!fs40.existsSync(tempDir)) return;
21826
- const files = fs40.readdirSync(tempDir);
22801
+ if (!fs41.existsSync(tempDir)) return;
22802
+ const files = fs41.readdirSync(tempDir);
21827
22803
  for (const file of files) {
21828
22804
  if (file.startsWith("merge_") && file.endsWith(".db")) {
21829
22805
  deleteTempDatabase(path38.join(tempDir, file));
@@ -22220,7 +23196,7 @@ function createDatabaseManagerAdapter(dbManager) {
22220
23196
  }
22221
23197
 
22222
23198
  // ../../packages/node-runtime/src/services/merge-cache.ts
22223
- import * as fs41 from "fs";
23199
+ import * as fs42 from "fs";
22224
23200
  import * as path39 from "path";
22225
23201
  import * as crypto from "crypto";
22226
23202
  var MergeSessionCache = class {
@@ -22230,8 +23206,8 @@ var MergeSessionCache = class {
22230
23206
  constructor(pathProvider, options) {
22231
23207
  this.tempDir = pathProvider.getTempDir();
22232
23208
  this.nativeBinding = options?.nativeBinding;
22233
- if (!fs41.existsSync(this.tempDir)) {
22234
- fs41.mkdirSync(this.tempDir, { recursive: true });
23209
+ if (!fs42.existsSync(this.tempDir)) {
23210
+ fs42.mkdirSync(this.tempDir, { recursive: true });
22235
23211
  }
22236
23212
  }
22237
23213
  generateTempDbPath(filename) {
@@ -22249,7 +23225,7 @@ var MergeSessionCache = class {
22249
23225
  /** Open a TempDbReader for a given handle. Caller must close the reader. */
22250
23226
  openReader(handle) {
22251
23227
  const entry = this.cache.get(handle);
22252
- if (!entry || !fs41.existsSync(entry.tempDbPath)) return null;
23228
+ if (!entry || !fs42.existsSync(entry.tempDbPath)) return null;
22253
23229
  const adapter = openBetterSqliteDatabase(entry.tempDbPath, {
22254
23230
  readonly: true,
22255
23231
  nativeBinding: this.nativeBinding
@@ -22325,6 +23301,10 @@ export {
22325
23301
  getMonthlyActivity,
22326
23302
  getYearlyActivity,
22327
23303
  getMessageLengthDistribution,
23304
+ getTextStats,
23305
+ getLongMessageCount,
23306
+ getMemberMonthlyTrend,
23307
+ getTextLengthPercentiles,
22328
23308
  getSessionInfo,
22329
23309
  getSessionMeta,
22330
23310
  getSessionOverview,
@@ -22356,6 +23336,12 @@ export {
22356
23336
  getClusterGraph,
22357
23337
  getRelationshipStats,
22358
23338
  getLanguagePreferenceAnalysis,
23339
+ getDragonKingAnalysis,
23340
+ getDivingAnalysis,
23341
+ getCheckInAnalysis,
23342
+ getMemeBattleAnalysis,
23343
+ getNightOwlAnalysis,
23344
+ getRepeatAnalysis,
22359
23345
  isNewerStableVersion,
22360
23346
  buildChartPayload,
22361
23347
  POS_TAG_DEFINITIONS,
@@ -22557,6 +23543,8 @@ export {
22557
23543
  createJiebaNlpProvider,
22558
23544
  exportFilterResultToMarkdown,
22559
23545
  exportWithFormat,
23546
+ getDbFileVersion,
23547
+ getOrComputeAnalysisCache,
22560
23548
  PreferencesManager,
22561
23549
  checkConflictsFromSources,
22562
23550
  buildMergedOutput,
@@ -22576,4 +23564,4 @@ export {
22576
23564
  createDatabaseManagerAdapter,
22577
23565
  MergeSessionCache
22578
23566
  };
22579
- //# sourceMappingURL=chunk-TMZC3SNX.mjs.map
23567
+ //# sourceMappingURL=chunk-YPTJVTVJ.mjs.map