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.
- package/dist/{chat-command-BVIG2LCO.mjs → chat-command-D37MMEJ7.mjs} +4 -4
- package/dist/{chunk-6AOEMUCM.mjs → chunk-742TAKEP.mjs} +2 -2
- package/dist/{chunk-3XFHIQIW.mjs → chunk-D5MWER7N.mjs} +2 -2
- package/dist/{chunk-JFSTXSX2.mjs → chunk-GNGZHBKM.mjs} +3 -3
- package/dist/{chunk-GTXX72AQ.mjs → chunk-K5SGXTVJ.mjs} +13 -13
- package/dist/{chunk-GBQQJJKK.mjs → chunk-QMXNJRTT.mjs} +372 -173
- package/dist/chunk-QMXNJRTT.mjs.map +1 -0
- package/dist/{chunk-SCE4YL3M.mjs → chunk-RMITK4IC.mjs} +2 -2
- package/dist/{chunk-QKQRV7RV.mjs → chunk-VCN64OD6.mjs} +2 -2
- package/dist/{chunk-TMZC3SNX.mjs → chunk-YPTJVTVJ.mjs} +1037 -49
- package/dist/chunk-YPTJVTVJ.mjs.map +1 -0
- package/dist/cli.mjs +3 -3
- package/dist/{http-NLFLQDI7.mjs → http-6F6TYWCI.mjs} +8 -8
- package/dist/{import-ILK6MGYR.mjs → import-VYPOMSSA.mjs} +4 -4
- package/dist/index.mjs +8 -8
- package/dist/{mcp-FOLR5XJL.mjs → mcp-AR7L5FA3.mjs} +4 -4
- package/dist/{src-OQN3VE73.mjs → src-6S7JPKHX.mjs} +6 -2
- package/dist/{stream-import-F5QI2HTO.mjs → stream-import-KZFC3XT2.mjs} +3 -3
- package/dist/{update-checker-JIBA7T4U.mjs → update-checker-3N7HQEP2.mjs} +36 -12
- package/dist/update-checker-3N7HQEP2.mjs.map +1 -0
- package/dist-web/assets/{DebugToolsPanel-CrqULdXp.css → DebugToolsPanel-DzZmSzIU.css} +1 -1
- package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-BTdqmm0y.js +107 -0
- package/dist-web/assets/{MessageExportModal-ttP_JPqO.js → MessageExportModal-CF6yaor7.js} +1 -1
- package/dist-web/assets/{electron-BeJHY587.js → electron-DKVw61Ww.js} +1 -1
- package/dist-web/assets/{fetch-H4B1CK_T.js → fetch-BC8jyJ_U.js} +1 -1
- package/dist-web/assets/{fetch-4s20GRbb.js → fetch-BMdgfBVS.js} +1 -1
- package/dist-web/assets/fetch-BQlEQsqi.js +5 -0
- package/dist-web/assets/{fetch-DtXUkazy.js → fetch-Bh6HC-IR.js} +1 -1
- package/dist-web/assets/{fetch-DBJ7Xkjt.js → fetch-BvVfWR-P.js} +1 -1
- package/dist-web/assets/{fetch-DW8r_JmP.js → fetch-CPeSfqsU.js} +1 -1
- package/dist-web/assets/fetch-CUw-PUYP.js +1 -0
- package/dist-web/assets/{fetch-B7USXx9R.js → fetch-CpYSD089.js} +1 -1
- package/dist-web/assets/{fetch-rtQEc8Bu.js → fetch-D2jNBqs8.js} +1 -1
- package/dist-web/assets/{fetch-BETFYYe1.js → fetch-DqEMAea3.js} +1 -1
- package/dist-web/assets/{index-Dov-rTKE.js → index-BHQnAj6l.js} +1 -1
- package/dist-web/assets/{index-CsxrXMkt.js → index-BwzuCu5R.js} +2 -2
- package/dist-web/assets/index-CG746E15.js +64 -0
- package/dist-web/assets/index-CXoQ_U22.js +173 -0
- package/dist-web/assets/index-DqybGdJH.css +1 -0
- package/dist-web/assets/{index-CjnMyejM.css → index-HgIuP1Td.css} +1 -1
- package/dist-web/assets/{index-CYMTvtEI.js → index-Qh0XLt3R.js} +1 -1
- package/dist-web/assets/{index-Dtm2oajn.css → index-R82N2_0b.css} +1 -1
- package/dist-web/assets/index-wPAmuQZY.js +11 -0
- package/dist-web/assets/{web-1-WNRDnP.js → web-Bx2TiHDk.js} +1 -1
- package/dist-web/favicon.ico +0 -0
- package/dist-web/index.html +3 -3
- package/package.json +1 -1
- package/dist/chunk-GBQQJJKK.mjs.map +0 -1
- package/dist/chunk-TMZC3SNX.mjs.map +0 -1
- package/dist/update-checker-JIBA7T4U.mjs.map +0 -1
- package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-D9mCNaYM.js +0 -182
- package/dist-web/assets/electron-Dbml-SPN.js +0 -1
- package/dist-web/assets/fetch-DcEBAz_3.js +0 -1
- package/dist-web/assets/fetch-DsCNr_M0.js +0 -1
- package/dist-web/assets/index-C2ts-hTr.js +0 -173
- package/dist-web/assets/index-DkownpUj.js +0 -163
- package/dist-web/assets/index-LCedyGl2.css +0 -1
- package/dist-web/assets/index-m7v4PP7r.js +0 -11
- /package/dist/{chat-command-BVIG2LCO.mjs.map → chat-command-D37MMEJ7.mjs.map} +0 -0
- /package/dist/{chunk-6AOEMUCM.mjs.map → chunk-742TAKEP.mjs.map} +0 -0
- /package/dist/{chunk-3XFHIQIW.mjs.map → chunk-D5MWER7N.mjs.map} +0 -0
- /package/dist/{chunk-JFSTXSX2.mjs.map → chunk-GNGZHBKM.mjs.map} +0 -0
- /package/dist/{chunk-GTXX72AQ.mjs.map → chunk-K5SGXTVJ.mjs.map} +0 -0
- /package/dist/{chunk-SCE4YL3M.mjs.map → chunk-RMITK4IC.mjs.map} +0 -0
- /package/dist/{chunk-QKQRV7RV.mjs.map → chunk-VCN64OD6.mjs.map} +0 -0
- /package/dist/{http-NLFLQDI7.mjs.map → http-6F6TYWCI.mjs.map} +0 -0
- /package/dist/{import-ILK6MGYR.mjs.map → import-VYPOMSSA.mjs.map} +0 -0
- /package/dist/{mcp-FOLR5XJL.mjs.map → mcp-AR7L5FA3.mjs.map} +0 -0
- /package/dist/{src-OQN3VE73.mjs.map → src-6S7JPKHX.mjs.map} +0 -0
- /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
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
11951
|
-
|
|
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:
|
|
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 =
|
|
11968
|
-
if (
|
|
11969
|
-
|
|
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:
|
|
12864
|
+
const { fs: fs43, assistantsDir } = this.deps;
|
|
11976
12865
|
this.cache.clear();
|
|
11977
|
-
const files =
|
|
12866
|
+
const files = fs43.listFiles(assistantsDir, ".md");
|
|
11978
12867
|
for (const file of files) {
|
|
11979
12868
|
try {
|
|
11980
|
-
const filePath =
|
|
11981
|
-
const content =
|
|
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:
|
|
12990
|
-
|
|
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:
|
|
13886
|
+
const { fs: fs43, skillsDir } = this.deps;
|
|
12998
13887
|
this.cache.clear();
|
|
12999
|
-
const files =
|
|
13888
|
+
const files = fs43.listFiles(skillsDir, ".md");
|
|
13000
13889
|
for (const file of files) {
|
|
13001
13890
|
try {
|
|
13002
|
-
const filePath =
|
|
13003
|
-
const content =
|
|
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
|
|
18646
|
-
const stats =
|
|
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/
|
|
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 (
|
|
21320
|
-
const raw =
|
|
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 (!
|
|
21437
|
-
|
|
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
|
-
|
|
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 (!
|
|
21447
|
-
|
|
22422
|
+
if (!fs40.existsSync(dir)) {
|
|
22423
|
+
fs40.mkdirSync(dir, { recursive: true });
|
|
21448
22424
|
}
|
|
21449
|
-
|
|
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
|
|
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 (
|
|
22791
|
+
if (fs41.existsSync(dbPath)) fs41.unlinkSync(dbPath);
|
|
21816
22792
|
const walPath = dbPath + "-wal";
|
|
21817
22793
|
const shmPath = dbPath + "-shm";
|
|
21818
|
-
if (
|
|
21819
|
-
if (
|
|
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 (!
|
|
21826
|
-
const files =
|
|
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
|
|
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 (!
|
|
22234
|
-
|
|
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 || !
|
|
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-
|
|
23567
|
+
//# sourceMappingURL=chunk-YPTJVTVJ.mjs.map
|