chattercatcher 0.1.15 → 0.1.16

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/cli.js CHANGED
@@ -8,7 +8,7 @@ import fs13 from "fs/promises";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "chattercatcher",
11
- version: "0.1.14",
11
+ version: "0.1.16",
12
12
  description: "\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93\u673A\u5668\u4EBA",
13
13
  type: "module",
14
14
  main: "dist/index.js",
@@ -36,6 +36,7 @@ var package_default = {
36
36
  },
37
37
  scripts: {
38
38
  build: "tsup",
39
+ prepack: "npm run build",
39
40
  dev: "tsx src/cli.ts",
40
41
  lint: "tsc --noEmit",
41
42
  typecheck: "tsc --noEmit",
@@ -52,7 +53,7 @@ var package_default = {
52
53
  license: "MIT",
53
54
  dependencies: {
54
55
  "@inquirer/prompts": "^8.4.2",
55
- "@larksuiteoapi/node-sdk": "^1.62.0",
56
+ "@larksuiteoapi/node-sdk": "^1.62.1",
56
57
  "better-sqlite3": "^12.9.0",
57
58
  commander: "^14.0.3",
58
59
  fastify: "^5.8.5",
@@ -114,7 +115,7 @@ var appConfigSchema = z.object({
114
115
  episodes: z.object({
115
116
  windowMinutes: z.number().int().positive().default(10),
116
117
  quietMinutes: z.number().int().positive().default(2)
117
- })
118
+ }).default({ windowMinutes: 10, quietMinutes: 2 })
118
119
  });
119
120
  var appSecretsSchema = z.object({
120
121
  feishu: z.object({
@@ -1443,6 +1444,29 @@ var EpisodeRepository = class {
1443
1444
  messageIds: window.messages.map((message) => message.id)
1444
1445
  };
1445
1446
  }
1447
+ getEpisodeCount() {
1448
+ const row = this.database.prepare("SELECT count(*) AS count FROM memory_episodes").get();
1449
+ return row.count;
1450
+ }
1451
+ listRecentEpisodes(limit = 20) {
1452
+ return this.database.prepare(
1453
+ `
1454
+ SELECT
1455
+ e.id,
1456
+ e.chat_id AS chatId,
1457
+ c.name AS chatName,
1458
+ e.summary,
1459
+ e.message_count AS messageCount,
1460
+ e.started_at AS startedAt,
1461
+ e.ended_at AS endedAt,
1462
+ e.created_at AS createdAt
1463
+ FROM memory_episodes e
1464
+ JOIN chats c ON c.id = e.chat_id
1465
+ ORDER BY e.ended_at DESC
1466
+ LIMIT ?
1467
+ `
1468
+ ).all(limit);
1469
+ }
1446
1470
  searchEpisodes(query, limit = 8) {
1447
1471
  const ftsQuery = escapeFtsQuery2(query);
1448
1472
  return this.database.prepare(
@@ -2668,6 +2692,13 @@ function assertFeishuConfig(config, secrets) {
2668
2692
  throw new Error("\u98DE\u4E66\u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u5148\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
2669
2693
  }
2670
2694
  }
2695
+ function formatGatewayStartError(error) {
2696
+ const message = error instanceof Error ? error.message : String(error);
2697
+ if (message.includes("PingInterval") || message.includes("system busy") || message.includes("1000040345")) {
2698
+ return new Error(`\u98DE\u4E66\u957F\u8FDE\u63A5\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5 App ID / App Secret \u662F\u5426\u6B63\u786E\uFF1B\u539F\u59CB\u9519\u8BEF\uFF1A${message}`);
2699
+ }
2700
+ return error instanceof Error ? error : new Error(message);
2701
+ }
2671
2702
  function createFeishuEventDispatcher(options) {
2672
2703
  const answeredMessageIds = /* @__PURE__ */ new Set();
2673
2704
  return new lark2.EventDispatcher({}).register({
@@ -2772,7 +2803,11 @@ function createFeishuGateway(options) {
2772
2803
  });
2773
2804
  return {
2774
2805
  async start() {
2775
- await wsClient.start({ eventDispatcher });
2806
+ try {
2807
+ await wsClient.start({ eventDispatcher });
2808
+ } catch (error) {
2809
+ throw formatGatewayStartError(error);
2810
+ }
2776
2811
  },
2777
2812
  stop() {
2778
2813
  wsClient.close({ force: true });
@@ -3626,6 +3661,10 @@ function buildHtml() {
3626
3661
  <h2>\u6700\u8FD1\u6D88\u606F</h2>
3627
3662
  <div id="messages" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
3628
3663
  </section>
3664
+ <section>
3665
+ <h2>\u4F1A\u8BDD\u8BB0\u5FC6</h2>
3666
+ <div id="episodes" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
3667
+ </section>
3629
3668
  </div>
3630
3669
  <aside>
3631
3670
  <section>
@@ -3652,6 +3691,7 @@ function buildHtml() {
3652
3691
  <script>
3653
3692
  const metrics = document.querySelector("#metrics");
3654
3693
  const messages = document.querySelector("#messages");
3694
+ const episodes = document.querySelector("#episodes");
3655
3695
  const chats = document.querySelector("#chats");
3656
3696
  const files = document.querySelector("#files");
3657
3697
  const fileJobs = document.querySelector("#file-jobs");
@@ -3715,6 +3755,7 @@ function buildHtml() {
3715
3755
  ["Gateway", formatGatewayValue(status.gateway), formatGatewayNote(status.gateway), gatewayClass],
3716
3756
  ["\u7FA4\u804A", status.data.chats, "\u672C\u5730\u7FA4\u804A\u6570", ""],
3717
3757
  ["\u6D88\u606F", status.data.messages, "\u5DF2\u5165\u5E93\u6D88\u606F", ""],
3758
+ ["\u4F1A\u8BDD\u8BB0\u5FC6", status.data.episodes, "\u5DF2\u751F\u6210\u6458\u8981", ""],
3718
3759
  ["\u6587\u4EF6", status.data.files, "\u6587\u4EF6\u77E5\u8BC6\u6E90", ""],
3719
3760
  ].map(([label, value, note, extra]) => \`
3720
3761
  <div class="metric">
@@ -3748,6 +3789,29 @@ function buildHtml() {
3748
3789
  \`;
3749
3790
  }
3750
3791
 
3792
+ function renderEpisodes(items) {
3793
+ if (items.length === 0) {
3794
+ episodes.className = "empty";
3795
+ episodes.textContent = "\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u8BB0\u5FC6\u3002\u9ED8\u8BA4\u5728 10 \u5206\u949F\u7A97\u53E3\u9759\u9ED8 2 \u5206\u949F\u540E\u751F\u6210\uFF0C\u4E5F\u53EF\u4EE5\u8FD0\u884C chattercatcher process episodes \u624B\u52A8\u89E6\u53D1\u3002";
3796
+ return;
3797
+ }
3798
+ episodes.className = "";
3799
+ episodes.innerHTML = \`
3800
+ <div class="message-list">
3801
+ \${items.map((item) => \`
3802
+ <article class="message-item">
3803
+ <div class="message-meta">
3804
+ <span>\${escapeHtml(formatDateTime(item.startedAt))} - \${escapeHtml(formatDateTime(item.endedAt))}</span>
3805
+ <span>\${escapeHtml(displayChatName(item.chatName, "feishu"))}</span>
3806
+ <span>\${escapeHtml(item.messageCount)} \u6761\u6D88\u606F</span>
3807
+ </div>
3808
+ <div class="message-body">\${escapeHtml(item.summary)}</div>
3809
+ </article>
3810
+ \`).join("")}
3811
+ </div>
3812
+ \`;
3813
+ }
3814
+
3751
3815
  function renderChats(items) {
3752
3816
  if (items.length === 0) {
3753
3817
  chats.className = "empty";
@@ -3823,15 +3887,17 @@ function buildHtml() {
3823
3887
  }
3824
3888
 
3825
3889
  async function load() {
3826
- const [status, recent, chatList, fileList, jobList] = await Promise.all([
3890
+ const [status, recent, episodeList, chatList, fileList, jobList] = await Promise.all([
3827
3891
  fetch("/api/status").then((response) => response.json()),
3828
3892
  fetch("/api/messages/recent?limit=20").then((response) => response.json()),
3893
+ fetch("/api/episodes?limit=10").then((response) => response.json()),
3829
3894
  fetch("/api/chats").then((response) => response.json()),
3830
3895
  fetch("/api/files").then((response) => response.json()),
3831
3896
  fetch("/api/file-jobs").then((response) => response.json()),
3832
3897
  ]);
3833
3898
  renderMetrics(status);
3834
3899
  renderMessages(recent.items);
3900
+ renderEpisodes(episodeList.items);
3835
3901
  renderChats(chatList.items);
3836
3902
  renderFiles(fileList.items);
3837
3903
  renderFileJobs(jobList.items);
@@ -3880,6 +3946,7 @@ function createWebApp(config) {
3880
3946
  const app = Fastify({ logger: false });
3881
3947
  const database = openDatabase(config);
3882
3948
  const messages = new MessageRepository(database);
3949
+ const episodes = new EpisodeRepository(database);
3883
3950
  const fileJobs = new FileJobRepository(database);
3884
3951
  app.addHook("onClose", async () => {
3885
3952
  database.close();
@@ -3890,6 +3957,7 @@ function createWebApp(config) {
3890
3957
  data: {
3891
3958
  chats: messages.getChatCount(),
3892
3959
  messages: messages.getMessageCount(),
3960
+ episodes: episodes.getEpisodeCount(),
3893
3961
  files: messages.listFiles(1e3).length
3894
3962
  },
3895
3963
  rag: {
@@ -3925,6 +3993,12 @@ function createWebApp(config) {
3925
3993
  items: messages.listRecentMessages(limit)
3926
3994
  };
3927
3995
  });
3996
+ app.get("/api/episodes", async (request) => {
3997
+ const limit = parseLimit(request.query.limit, 20, 100);
3998
+ return {
3999
+ items: episodes.listRecentEpisodes(limit)
4000
+ };
4001
+ });
3928
4002
  app.post("/api/process/messages", async (_request, reply) => {
3929
4003
  try {
3930
4004
  return await processMessagesNow({