chattercatcher 0.1.30 → 0.2.1
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 +1058 -488
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1055 -485
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs15 from "fs/promises";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "chattercatcher",
|
|
11
|
-
version: "0.1
|
|
11
|
+
version: "0.2.1",
|
|
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",
|
|
@@ -4439,6 +4439,166 @@ var FeishuQuestionHandler = class {
|
|
|
4439
4439
|
// src/feishu/sender.ts
|
|
4440
4440
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
4441
4441
|
import fs9 from "fs/promises";
|
|
4442
|
+
|
|
4443
|
+
// src/feishu/markdown-post.ts
|
|
4444
|
+
function escapeAtText(value) {
|
|
4445
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4446
|
+
}
|
|
4447
|
+
function formatTextWithMentions(text, options) {
|
|
4448
|
+
const mentions = options?.mentions ?? [];
|
|
4449
|
+
if (mentions.length === 0) return text;
|
|
4450
|
+
const prefix = mentions.map((mention) => `<at user_id="${escapeAtText(mention.openId)}">${escapeAtText(mention.name)}</at>`).join(" ");
|
|
4451
|
+
return `${prefix} ${text}`.trim();
|
|
4452
|
+
}
|
|
4453
|
+
function findMarkdownLinkEnd(text, start) {
|
|
4454
|
+
let depth = 0;
|
|
4455
|
+
for (let index2 = start; index2 < text.length; index2 += 1) {
|
|
4456
|
+
const char = text[index2];
|
|
4457
|
+
if (char === "(") {
|
|
4458
|
+
depth += 1;
|
|
4459
|
+
} else if (char === ")") {
|
|
4460
|
+
if (depth === 0) return index2;
|
|
4461
|
+
depth -= 1;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
return -1;
|
|
4465
|
+
}
|
|
4466
|
+
function parseInline(text) {
|
|
4467
|
+
const elements = [];
|
|
4468
|
+
let index2 = 0;
|
|
4469
|
+
while (index2 < text.length) {
|
|
4470
|
+
const linkStart = text.indexOf("[", index2);
|
|
4471
|
+
const boldStarStart = text.indexOf("**", index2);
|
|
4472
|
+
const boldUnderscoreStart = text.indexOf("__", index2);
|
|
4473
|
+
const candidates = [linkStart, boldStarStart, boldUnderscoreStart].filter((value) => value >= 0);
|
|
4474
|
+
const next = candidates.length ? Math.min(...candidates) : -1;
|
|
4475
|
+
if (next < 0) {
|
|
4476
|
+
elements.push({ tag: "text", text: text.slice(index2) });
|
|
4477
|
+
break;
|
|
4478
|
+
}
|
|
4479
|
+
if (next > index2) {
|
|
4480
|
+
elements.push({ tag: "text", text: text.slice(index2, next) });
|
|
4481
|
+
}
|
|
4482
|
+
if (next === linkStart) {
|
|
4483
|
+
const labelEnd = text.indexOf("](", next);
|
|
4484
|
+
if (labelEnd > next) {
|
|
4485
|
+
const hrefStart = labelEnd + 2;
|
|
4486
|
+
const hrefEnd = findMarkdownLinkEnd(text, hrefStart);
|
|
4487
|
+
const href = hrefEnd >= 0 ? text.slice(hrefStart, hrefEnd) : "";
|
|
4488
|
+
if (hrefEnd >= 0 && /^https?:\/\/\S+$/.test(href)) {
|
|
4489
|
+
elements.push({ tag: "a", text: text.slice(next + 1, labelEnd), href });
|
|
4490
|
+
index2 = hrefEnd + 1;
|
|
4491
|
+
continue;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
elements.push({ tag: "text", text: text[next] });
|
|
4495
|
+
index2 = next + 1;
|
|
4496
|
+
continue;
|
|
4497
|
+
}
|
|
4498
|
+
const marker = next === boldStarStart ? "**" : "__";
|
|
4499
|
+
const close = text.indexOf(marker, next + marker.length);
|
|
4500
|
+
if (close > next + marker.length) {
|
|
4501
|
+
elements.push({ tag: "text", text: text.slice(next + marker.length, close), style: ["bold"] });
|
|
4502
|
+
index2 = close + marker.length;
|
|
4503
|
+
continue;
|
|
4504
|
+
}
|
|
4505
|
+
elements.push({ tag: "text", text: marker });
|
|
4506
|
+
index2 = next + marker.length;
|
|
4507
|
+
}
|
|
4508
|
+
const compacted = elements.filter((element) => element.tag !== "text" || element.text.length > 0);
|
|
4509
|
+
return compacted.length ? compacted : [{ tag: "text", text: " " }];
|
|
4510
|
+
}
|
|
4511
|
+
function pushParagraph(content, lines) {
|
|
4512
|
+
if (lines.length === 0) return;
|
|
4513
|
+
content.push(parseInline(lines.join("\n")));
|
|
4514
|
+
lines.length = 0;
|
|
4515
|
+
}
|
|
4516
|
+
function parseMarkdownBlocks(markdown) {
|
|
4517
|
+
if (!markdown.trim()) {
|
|
4518
|
+
return [[{ tag: "text", text: " " }]];
|
|
4519
|
+
}
|
|
4520
|
+
const content = [];
|
|
4521
|
+
const paragraph = [];
|
|
4522
|
+
const code = [];
|
|
4523
|
+
let inCodeBlock = false;
|
|
4524
|
+
for (const rawLine of markdown.replace(/\r\n/g, "\n").split("\n")) {
|
|
4525
|
+
const line = rawLine.trimEnd();
|
|
4526
|
+
if (line.startsWith("```")) {
|
|
4527
|
+
if (inCodeBlock) {
|
|
4528
|
+
content.push([{ tag: "text", text: `\`\`\`
|
|
4529
|
+
${code.join("\n")}
|
|
4530
|
+
\`\`\`` }]);
|
|
4531
|
+
code.length = 0;
|
|
4532
|
+
inCodeBlock = false;
|
|
4533
|
+
} else {
|
|
4534
|
+
pushParagraph(content, paragraph);
|
|
4535
|
+
inCodeBlock = true;
|
|
4536
|
+
}
|
|
4537
|
+
continue;
|
|
4538
|
+
}
|
|
4539
|
+
if (inCodeBlock) {
|
|
4540
|
+
code.push(rawLine);
|
|
4541
|
+
continue;
|
|
4542
|
+
}
|
|
4543
|
+
if (!line.trim()) {
|
|
4544
|
+
pushParagraph(content, paragraph);
|
|
4545
|
+
continue;
|
|
4546
|
+
}
|
|
4547
|
+
const heading = line.match(/^#{1,6}\s+(.+)$/);
|
|
4548
|
+
if (heading) {
|
|
4549
|
+
pushParagraph(content, paragraph);
|
|
4550
|
+
content.push([{ tag: "text", text: heading[1], style: ["bold"] }]);
|
|
4551
|
+
continue;
|
|
4552
|
+
}
|
|
4553
|
+
const unordered = line.match(/^[-*]\s+(.+)$/);
|
|
4554
|
+
if (unordered) {
|
|
4555
|
+
pushParagraph(content, paragraph);
|
|
4556
|
+
content.push(parseInline(`\u2022 ${unordered[1]}`));
|
|
4557
|
+
continue;
|
|
4558
|
+
}
|
|
4559
|
+
const ordered = line.match(/^(\d+)\.\s+(.+)$/);
|
|
4560
|
+
if (ordered) {
|
|
4561
|
+
pushParagraph(content, paragraph);
|
|
4562
|
+
content.push(parseInline(`${ordered[1]}. ${ordered[2]}`));
|
|
4563
|
+
continue;
|
|
4564
|
+
}
|
|
4565
|
+
paragraph.push(line);
|
|
4566
|
+
}
|
|
4567
|
+
if (inCodeBlock) {
|
|
4568
|
+
content.push([{ tag: "text", text: `\`\`\`
|
|
4569
|
+
${code.join("\n")}` }]);
|
|
4570
|
+
}
|
|
4571
|
+
pushParagraph(content, paragraph);
|
|
4572
|
+
return content.length ? content : [[{ tag: "text", text: markdown }]];
|
|
4573
|
+
}
|
|
4574
|
+
function buildFeishuPostContent(markdown, options) {
|
|
4575
|
+
const content = parseMarkdownBlocks(markdown);
|
|
4576
|
+
const mentions = options?.mentions ?? [];
|
|
4577
|
+
if (mentions.length) {
|
|
4578
|
+
const mentionElements = mentions.map((mention) => ({
|
|
4579
|
+
tag: "at",
|
|
4580
|
+
user_id: mention.openId,
|
|
4581
|
+
user_name: mention.name
|
|
4582
|
+
}));
|
|
4583
|
+
const firstLine = content[0] ?? [];
|
|
4584
|
+
const firstText = firstLine[0];
|
|
4585
|
+
if (firstText?.tag === "text") {
|
|
4586
|
+
content[0] = [...mentionElements, { ...firstText, text: ` ${firstText.text}` }, ...firstLine.slice(1)];
|
|
4587
|
+
} else {
|
|
4588
|
+
content[0] = [...mentionElements, { tag: "text", text: " " }, ...firstLine];
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
return {
|
|
4592
|
+
post: {
|
|
4593
|
+
zh_cn: {
|
|
4594
|
+
title: "",
|
|
4595
|
+
content
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
// src/feishu/sender.ts
|
|
4442
4602
|
function mapDomain(domain) {
|
|
4443
4603
|
return domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
4444
4604
|
}
|
|
@@ -4454,14 +4614,33 @@ function extractImageKey(response) {
|
|
|
4454
4614
|
}
|
|
4455
4615
|
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
4456
4616
|
}
|
|
4457
|
-
function
|
|
4458
|
-
|
|
4617
|
+
function collectErrorFields(error) {
|
|
4618
|
+
const fields = [error];
|
|
4619
|
+
const value = error && typeof error === "object" ? error : {};
|
|
4620
|
+
fields.push(value.code, value.errorCode, value.msg, value.message);
|
|
4621
|
+
const response = value.response && typeof value.response === "object" ? value.response : {};
|
|
4622
|
+
const data2 = response.data && typeof response.data === "object" ? response.data : {};
|
|
4623
|
+
fields.push(data2.code, data2.errorCode, data2.msg, data2.message);
|
|
4624
|
+
return fields;
|
|
4625
|
+
}
|
|
4626
|
+
function isRichTextCompatibilityError(error) {
|
|
4627
|
+
return collectErrorFields(error).some((field) => {
|
|
4628
|
+
if (field === 230001) return true;
|
|
4629
|
+
if (typeof field === "string") {
|
|
4630
|
+
return /post|msg_type|content|unsupported|invalid/i.test(field);
|
|
4631
|
+
}
|
|
4632
|
+
return false;
|
|
4633
|
+
});
|
|
4459
4634
|
}
|
|
4460
|
-
function
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4635
|
+
async function sendWithTextFallback(input2) {
|
|
4636
|
+
try {
|
|
4637
|
+
await input2.sendPost();
|
|
4638
|
+
} catch (error) {
|
|
4639
|
+
if (!isRichTextCompatibilityError(error)) {
|
|
4640
|
+
throw error;
|
|
4641
|
+
}
|
|
4642
|
+
await input2.sendText();
|
|
4643
|
+
}
|
|
4465
4644
|
}
|
|
4466
4645
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
4467
4646
|
constructor(client) {
|
|
@@ -4477,7 +4656,17 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4477
4656
|
return new _FeishuMessageSender(client);
|
|
4478
4657
|
}
|
|
4479
4658
|
async sendTextToChat(chatId, text, options) {
|
|
4480
|
-
const
|
|
4659
|
+
const postPayload = {
|
|
4660
|
+
data: {
|
|
4661
|
+
receive_id: chatId,
|
|
4662
|
+
msg_type: "post",
|
|
4663
|
+
content: JSON.stringify(buildFeishuPostContent(text, options))
|
|
4664
|
+
},
|
|
4665
|
+
params: {
|
|
4666
|
+
receive_id_type: "chat_id"
|
|
4667
|
+
}
|
|
4668
|
+
};
|
|
4669
|
+
const textPayload = {
|
|
4481
4670
|
data: {
|
|
4482
4671
|
receive_id: chatId,
|
|
4483
4672
|
msg_type: "text",
|
|
@@ -4488,16 +4677,20 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4488
4677
|
}
|
|
4489
4678
|
};
|
|
4490
4679
|
if (this.client.im.v1?.message.create) {
|
|
4491
|
-
await
|
|
4680
|
+
await sendWithTextFallback({
|
|
4681
|
+
sendPost: () => this.client.im.v1.message.create(postPayload),
|
|
4682
|
+
sendText: () => this.client.im.v1.message.create(textPayload)
|
|
4683
|
+
});
|
|
4492
4684
|
return;
|
|
4493
4685
|
}
|
|
4494
4686
|
if (this.client.im.message?.create) {
|
|
4495
|
-
await
|
|
4687
|
+
await sendWithTextFallback({
|
|
4688
|
+
sendPost: () => this.client.im.message.create(postPayload),
|
|
4689
|
+
sendText: () => this.client.im.message.create(textPayload)
|
|
4690
|
+
});
|
|
4496
4691
|
return;
|
|
4497
4692
|
}
|
|
4498
|
-
|
|
4499
|
-
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4500
|
-
}
|
|
4693
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4501
4694
|
}
|
|
4502
4695
|
async sendImageToChat(chatId, imagePath) {
|
|
4503
4696
|
const imageCreate = this.client.im.v1?.image?.create;
|
|
@@ -4533,7 +4726,16 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4533
4726
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4534
4727
|
}
|
|
4535
4728
|
async replyTextToMessage(messageId, text) {
|
|
4536
|
-
const
|
|
4729
|
+
const postPayload = {
|
|
4730
|
+
path: {
|
|
4731
|
+
message_id: messageId
|
|
4732
|
+
},
|
|
4733
|
+
data: {
|
|
4734
|
+
msg_type: "post",
|
|
4735
|
+
content: JSON.stringify(buildFeishuPostContent(text))
|
|
4736
|
+
}
|
|
4737
|
+
};
|
|
4738
|
+
const textPayload = {
|
|
4537
4739
|
path: {
|
|
4538
4740
|
message_id: messageId
|
|
4539
4741
|
},
|
|
@@ -4543,11 +4745,17 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4543
4745
|
}
|
|
4544
4746
|
};
|
|
4545
4747
|
if (this.client.im.v1?.message.reply) {
|
|
4546
|
-
await
|
|
4748
|
+
await sendWithTextFallback({
|
|
4749
|
+
sendPost: () => this.client.im.v1.message.reply(postPayload),
|
|
4750
|
+
sendText: () => this.client.im.v1.message.reply(textPayload)
|
|
4751
|
+
});
|
|
4547
4752
|
return;
|
|
4548
4753
|
}
|
|
4549
4754
|
if (this.client.im.message?.reply) {
|
|
4550
|
-
await
|
|
4755
|
+
await sendWithTextFallback({
|
|
4756
|
+
sendPost: () => this.client.im.message.reply(postPayload),
|
|
4757
|
+
sendText: () => this.client.im.message.reply(textPayload)
|
|
4758
|
+
});
|
|
4551
4759
|
return;
|
|
4552
4760
|
}
|
|
4553
4761
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u56DE\u590D\u63A5\u53E3\u3002");
|
|
@@ -5730,516 +5938,867 @@ import Fastify from "fastify";
|
|
|
5730
5938
|
function buildHtml() {
|
|
5731
5939
|
return `<!doctype html>
|
|
5732
5940
|
<html lang="zh-CN">
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
.
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
.
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
.
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5941
|
+
<head>
|
|
5942
|
+
<meta charset="utf-8" />
|
|
5943
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5944
|
+
<meta name="color-scheme" content="dark" />
|
|
5945
|
+
<title>ChatterCatcher</title>
|
|
5946
|
+
<style>
|
|
5947
|
+
:root {
|
|
5948
|
+
--bg-primary: #0a0a0f;
|
|
5949
|
+
--bg-secondary: #12121a;
|
|
5950
|
+
--bg-tertiary: #1a1a28;
|
|
5951
|
+
--glass-bg: rgba(255,255,255,0.05);
|
|
5952
|
+
--glass-border: rgba(255,255,255,0.1);
|
|
5953
|
+
--glass-border-hover: rgba(255,255,255,0.2);
|
|
5954
|
+
--glass-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
5955
|
+
--text-primary: #f0f0f5;
|
|
5956
|
+
--text-secondary: #a0a0b0;
|
|
5957
|
+
--text-muted: #6e6e80;
|
|
5958
|
+
--accent: #64d2ff;
|
|
5959
|
+
--accent-hover: #7dd8ff;
|
|
5960
|
+
--success: #30d158;
|
|
5961
|
+
--warning: #ff9f0a;
|
|
5962
|
+
--danger: #ff453a;
|
|
5963
|
+
--radius-sm: 8px;
|
|
5964
|
+
--radius-md: 12px;
|
|
5965
|
+
--radius-lg: 16px;
|
|
5966
|
+
--radius-xl: 24px;
|
|
5967
|
+
--space-xs: 4px;
|
|
5968
|
+
--space-sm: 8px;
|
|
5969
|
+
--space-md: 16px;
|
|
5970
|
+
--space-lg: 24px;
|
|
5971
|
+
--space-xl: 32px;
|
|
5972
|
+
--space-2xl: 48px;
|
|
5973
|
+
--font-sans: -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
|
|
5974
|
+
--font-mono: "SF Mono","Menlo","Consolas",monospace;
|
|
5975
|
+
}
|
|
5976
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5977
|
+
body {
|
|
5978
|
+
font-family: var(--font-sans);
|
|
5979
|
+
background: var(--bg-primary);
|
|
5980
|
+
color: var(--text-primary);
|
|
5981
|
+
line-height: 1.6;
|
|
5982
|
+
-webkit-font-smoothing: antialiased;
|
|
5983
|
+
overflow-x: hidden;
|
|
5984
|
+
min-height: 100vh;
|
|
5985
|
+
}
|
|
5986
|
+
.glass {
|
|
5987
|
+
background: var(--glass-bg);
|
|
5988
|
+
backdrop-filter: blur(20px) saturate(180%);
|
|
5989
|
+
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
|
5990
|
+
border: 1px solid var(--glass-border);
|
|
5991
|
+
border-radius: var(--radius-lg);
|
|
5992
|
+
box-shadow: var(--glass-shadow);
|
|
5993
|
+
transition: all 0.3s ease;
|
|
5994
|
+
}
|
|
5995
|
+
.glass:hover { border-color: var(--glass-border-hover); box-shadow: 0 12px 40px rgba(0,0,0,0.4); }
|
|
5996
|
+
.gradient-bg {
|
|
5997
|
+
background: linear-gradient(135deg,#0a0a0f 0%,#12121a 50%,#1a1a28 100%);
|
|
5998
|
+
min-height: 100vh;
|
|
5999
|
+
}
|
|
6000
|
+
.sidebar {
|
|
6001
|
+
position: fixed; left: 0; top: 0; width: 260px; height: 100vh;
|
|
6002
|
+
padding: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-md); z-index: 100;
|
|
6003
|
+
background: linear-gradient(180deg,rgba(255,255,255,0.08) 0%,rgba(255,255,255,0.02) 100%);
|
|
6004
|
+
backdrop-filter: blur(40px) saturate(200%);
|
|
6005
|
+
-webkit-backdrop-filter: blur(40px) saturate(200%);
|
|
6006
|
+
border-right: 1px solid var(--glass-border);
|
|
6007
|
+
}
|
|
6008
|
+
.sidebar-logo {
|
|
6009
|
+
display: flex; align-items: center; gap: var(--space-sm);
|
|
6010
|
+
padding: var(--space-md); font-size: 20px; font-weight: 700;
|
|
6011
|
+
color: var(--text-primary); margin-bottom: var(--space-md);
|
|
6012
|
+
}
|
|
6013
|
+
.logo-icon {
|
|
6014
|
+
width: 36px; height: 36px;
|
|
6015
|
+
background: linear-gradient(135deg,var(--accent),#5e60ce);
|
|
6016
|
+
border-radius: var(--radius-md);
|
|
6017
|
+
display: flex; align-items: center; justify-content: center;
|
|
6018
|
+
box-shadow: 0 4px 16px rgba(100,210,255,0.3);
|
|
6019
|
+
}
|
|
6020
|
+
.sidebar-nav { display: flex; flex-direction: column; gap: var(--space-xs); }
|
|
6021
|
+
.nav-item {
|
|
6022
|
+
display: flex; align-items: center; gap: var(--space-sm);
|
|
6023
|
+
padding: var(--space-sm) var(--space-md); border-radius: var(--radius-md);
|
|
6024
|
+
color: var(--text-secondary); text-decoration: none; cursor: pointer;
|
|
6025
|
+
transition: all 0.2s ease; border: none; background: none;
|
|
6026
|
+
font-size: 14px; font-family: inherit; width: 100%; text-align: left;
|
|
6027
|
+
}
|
|
6028
|
+
.nav-item:hover { background: rgba(255,255,255,0.06); color: var(--text-primary); }
|
|
6029
|
+
.nav-item.active {
|
|
6030
|
+
background: rgba(100,210,255,0.15); color: var(--accent);
|
|
6031
|
+
box-shadow: 0 0 20px rgba(100,210,255,0.1);
|
|
6032
|
+
}
|
|
6033
|
+
.nav-icon { width: 20px; height: 20px; flex-shrink: 0; }
|
|
6034
|
+
.main-content { margin-left: 260px; min-height: 100vh; padding: var(--space-xl); }
|
|
6035
|
+
.page-header { margin-bottom: var(--space-xl); }
|
|
6036
|
+
.page-title {
|
|
6037
|
+
font-size: 36px; font-weight: 700; letter-spacing: -0.03em;
|
|
6038
|
+
margin-bottom: var(--space-sm);
|
|
6039
|
+
background: linear-gradient(135deg,var(--text-primary),var(--accent));
|
|
6040
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
|
6041
|
+
}
|
|
6042
|
+
.page-subtitle { color: var(--text-secondary); font-size: 15px; }
|
|
6043
|
+
.metrics-grid {
|
|
6044
|
+
display: grid; grid-template-columns: repeat(auto-fit,minmax(200px,1fr));
|
|
6045
|
+
gap: var(--space-md); margin-bottom: var(--space-xl);
|
|
6046
|
+
}
|
|
6047
|
+
.metric-card {
|
|
6048
|
+
padding: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-sm);
|
|
6049
|
+
position: relative; overflow: hidden;
|
|
6050
|
+
}
|
|
6051
|
+
.metric-card::before {
|
|
6052
|
+
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
|
|
6053
|
+
background: linear-gradient(90deg,var(--accent),transparent); opacity: 0.5;
|
|
6054
|
+
}
|
|
6055
|
+
.metric-value { font-size: 40px; font-weight: 700; color: var(--text-primary); line-height: 1; font-variant-numeric: tabular-nums; }
|
|
6056
|
+
.metric-label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }
|
|
6057
|
+
.metric-note { font-size: 13px; color: var(--text-secondary); margin-top: var(--space-xs); }
|
|
6058
|
+
.content-grid { display: grid; grid-template-columns: 2fr 1fr; gap: var(--space-lg); }
|
|
6059
|
+
.content-panel { padding: var(--space-lg); }
|
|
6060
|
+
.panel-header {
|
|
6061
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
6062
|
+
margin-bottom: var(--space-lg); padding-bottom: var(--space-md);
|
|
6063
|
+
border-bottom: 1px solid var(--glass-border);
|
|
6064
|
+
}
|
|
6065
|
+
.panel-title { font-size: 18px; font-weight: 600; }
|
|
6066
|
+
.message-list { display: flex; flex-direction: column; gap: var(--space-sm); }
|
|
6067
|
+
.message-card {
|
|
6068
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
6069
|
+
background: rgba(255,255,255,0.03); border: 1px solid transparent;
|
|
6070
|
+
transition: all 0.25s ease; cursor: pointer;
|
|
6071
|
+
}
|
|
6072
|
+
.message-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); transform: translateX(4px); }
|
|
6073
|
+
.message-meta {
|
|
6074
|
+
display: flex; align-items: center; gap: var(--space-md);
|
|
6075
|
+
color: var(--text-muted); font-size: 12px; margin-bottom: var(--space-xs); flex-wrap: wrap;
|
|
6076
|
+
}
|
|
6077
|
+
.message-text { color: var(--text-secondary); font-size: 14px; line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
6078
|
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
6079
|
+
.status-dot.online { background: var(--success); box-shadow: 0 0 8px var(--success); }
|
|
6080
|
+
.status-dot.offline { background: var(--danger); }
|
|
6081
|
+
.status-dot.warning { background: var(--warning); box-shadow: 0 0 8px var(--warning); }
|
|
6082
|
+
.status-dot.pending { background: var(--text-muted); }
|
|
6083
|
+
.btn {
|
|
6084
|
+
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-sm);
|
|
6085
|
+
padding: 10px var(--space-md); border-radius: var(--radius-md);
|
|
6086
|
+
border: 1px solid var(--glass-border); background: var(--glass-bg);
|
|
6087
|
+
color: var(--text-primary); font-family: inherit; font-size: 14px;
|
|
6088
|
+
cursor: pointer; transition: all 0.2s ease; text-decoration: none;
|
|
6089
|
+
}
|
|
6090
|
+
.btn:hover { background: rgba(255,255,255,0.1); border-color: var(--glass-border-hover); transform: translateY(-1px); }
|
|
6091
|
+
.btn-primary {
|
|
6092
|
+
background: linear-gradient(135deg,var(--accent),#5e60ce); color: white; border: none;
|
|
6093
|
+
font-weight: 600; box-shadow: 0 4px 16px rgba(100,210,255,0.3);
|
|
6094
|
+
}
|
|
6095
|
+
.btn-primary:hover {
|
|
6096
|
+
background: linear-gradient(135deg,var(--accent-hover),#6b6dd8);
|
|
6097
|
+
box-shadow: 0 6px 20px rgba(100,210,255,0.4); transform: translateY(-1px);
|
|
6098
|
+
}
|
|
6099
|
+
.btn-danger { background: rgba(255,69,58,0.15); color: var(--danger); border-color: rgba(255,69,58,0.3); }
|
|
6100
|
+
.btn-danger:hover { background: rgba(255,69,58,0.25); }
|
|
6101
|
+
.btn-sm { padding: 6px var(--space-sm); font-size: 13px; }
|
|
6102
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
6103
|
+
.tag {
|
|
6104
|
+
display: inline-flex; align-items: center; padding: 2px 10px;
|
|
6105
|
+
border-radius: 20px; font-size: 12px; font-weight: 500;
|
|
6106
|
+
background: rgba(255,255,255,0.06); color: var(--text-secondary);
|
|
6107
|
+
}
|
|
6108
|
+
.tag-success { background: rgba(48,209,88,0.15); color: var(--success); }
|
|
6109
|
+
.tag-warning { background: rgba(255,159,10,0.15); color: var(--warning); }
|
|
6110
|
+
.tag-error { background: rgba(255,69,58,0.15); color: var(--danger); }
|
|
6111
|
+
.tag-info { background: rgba(100,210,255,0.15); color: var(--accent); }
|
|
6112
|
+
.empty-state { text-align: center; padding: var(--space-2xl); color: var(--text-muted); }
|
|
6113
|
+
.empty-state svg { width: 48px; height: 48px; margin: 0 auto var(--space-md); opacity: 0.3; }
|
|
6114
|
+
.skeleton {
|
|
6115
|
+
background: linear-gradient(90deg,rgba(255,255,255,0.03) 25%,rgba(255,255,255,0.08) 50%,rgba(255,255,255,0.03) 75%);
|
|
6116
|
+
background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: var(--radius-sm);
|
|
6117
|
+
}
|
|
6118
|
+
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
|
|
6119
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
|
6120
|
+
@keyframes slideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
|
|
6121
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
6122
|
+
.view { display: none; animation: fadeIn 0.35s ease; }
|
|
6123
|
+
.view.active { display: block; }
|
|
6124
|
+
.search-box { position: relative; width: 100%; max-width: 400px; }
|
|
6125
|
+
.search-box input {
|
|
6126
|
+
width: 100%; padding: var(--space-sm) var(--space-md) var(--space-sm) 40px;
|
|
6127
|
+
border-radius: var(--radius-md); border: 1px solid var(--glass-border);
|
|
6128
|
+
background: var(--glass-bg); color: var(--text-primary); font-family: inherit;
|
|
6129
|
+
font-size: 14px; outline: none; transition: all 0.2s ease;
|
|
6130
|
+
}
|
|
6131
|
+
.search-box input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(100,210,255,0.1); }
|
|
6132
|
+
.search-box .search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-muted); }
|
|
6133
|
+
.data-table { width: 100%; border-collapse: collapse; }
|
|
6134
|
+
.data-table th {
|
|
6135
|
+
text-align: left; padding: var(--space-sm) var(--space-md); color: var(--text-muted);
|
|
6136
|
+
font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
|
|
6137
|
+
border-bottom: 1px solid var(--glass-border);
|
|
6138
|
+
}
|
|
6139
|
+
.data-table td { padding: var(--space-sm) var(--space-md); color: var(--text-secondary); font-size: 14px; border-bottom: 1px solid rgba(255,255,255,0.03); vertical-align: top; }
|
|
6140
|
+
.data-table tr:hover td { background: rgba(255,255,255,0.02); }
|
|
6141
|
+
.data-table tr:last-child td { border-bottom: none; }
|
|
6142
|
+
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
6143
|
+
.truncate-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
6144
|
+
.truncate-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
|
6145
|
+
.flex { display: flex; } .flex-col { flex-direction: column; }
|
|
6146
|
+
.items-center { align-items: center; } .justify-between { justify-content: space-between; }
|
|
6147
|
+
.gap-sm { gap: var(--space-sm); } .gap-md { gap: var(--space-md); }
|
|
6148
|
+
.mt-md { margin-top: var(--space-md); } .mt-lg { margin-top: var(--space-lg); }
|
|
6149
|
+
.mb-md { margin-bottom: var(--space-md); }
|
|
6150
|
+
.toast {
|
|
6151
|
+
padding: var(--space-md) var(--space-lg); border-radius: var(--radius-md);
|
|
6152
|
+
background: var(--glass-bg); backdrop-filter: blur(20px); border: 1px solid var(--glass-border);
|
|
6153
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.4); color: var(--text-primary); font-size: 14px;
|
|
6154
|
+
max-width: 400px; animation: slideIn 0.3s ease;
|
|
6155
|
+
display: flex; align-items: center; gap: var(--space-sm);
|
|
6156
|
+
}
|
|
6157
|
+
.toast-success { border-color: rgba(48,209,88,0.3); background: rgba(48,209,88,0.1); }
|
|
6158
|
+
.toast-error { border-color: rgba(255,69,58,0.3); background: rgba(255,69,58,0.1); }
|
|
6159
|
+
.toast-warning { border-color: rgba(255,159,10,0.3); background: rgba(255,159,10,0.1); }
|
|
6160
|
+
.episode-card {
|
|
6161
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
6162
|
+
background: rgba(255,255,255,0.03); border: 1px solid transparent; transition: all 0.25s ease;
|
|
6163
|
+
}
|
|
6164
|
+
.episode-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); }
|
|
6165
|
+
.qa-card {
|
|
6166
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
6167
|
+
background: rgba(255,255,255,0.03); border-left: 3px solid var(--accent); margin-bottom: var(--space-sm);
|
|
6168
|
+
}
|
|
6169
|
+
.qa-question { font-weight: 600; color: var(--text-primary); margin-bottom: var(--space-xs); font-size: 14px; }
|
|
6170
|
+
.qa-answer { color: var(--text-secondary); font-size: 14px; line-height: 1.6; }
|
|
6171
|
+
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-lg); }
|
|
6172
|
+
.section-title { font-size: 24px; font-weight: 700; }
|
|
6173
|
+
.tabs {
|
|
6174
|
+
display: flex; gap: var(--space-xs); padding: 4px;
|
|
6175
|
+
background: rgba(255,255,255,0.03); border-radius: var(--radius-md); border: 1px solid var(--glass-border);
|
|
6176
|
+
}
|
|
6177
|
+
.tab { padding: 8px 16px; border-radius: var(--radius-sm); border: none; background: none; color: var(--text-secondary); font-family: inherit; font-size: 14px; cursor: pointer; transition: all 0.2s ease; }
|
|
6178
|
+
.tab:hover { color: var(--text-primary); }
|
|
6179
|
+
.tab.active { background: rgba(255,255,255,0.08); color: var(--text-primary); font-weight: 500; }
|
|
6180
|
+
.file-card {
|
|
6181
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
6182
|
+
background: rgba(255,255,255,0.03); border: 1px solid transparent; transition: all 0.25s ease; cursor: pointer;
|
|
6183
|
+
}
|
|
6184
|
+
.file-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); }
|
|
6185
|
+
.file-icon {
|
|
6186
|
+
width: 40px; height: 40px; border-radius: var(--radius-sm);
|
|
6187
|
+
background: linear-gradient(135deg,var(--accent),#5e60ce);
|
|
6188
|
+
display: flex; align-items: center; justify-content: center; margin-bottom: var(--space-sm);
|
|
6189
|
+
}
|
|
6190
|
+
.timeline { position: relative; padding-left: 28px; }
|
|
6191
|
+
.timeline::before { content: ''; position: absolute; left: 8px; top: 0; bottom: 0; width: 2px; background: linear-gradient(180deg,var(--accent),transparent); opacity: 0.3; }
|
|
6192
|
+
.timeline-item { position: relative; padding-bottom: var(--space-lg); }
|
|
6193
|
+
.timeline-item::before { content: ''; position: absolute; left: -24px; top: 4px; width: 10px; height: 10px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg-primary); box-shadow: 0 0 0 2px var(--accent); }
|
|
6194
|
+
.timeline-date { font-size: 12px; color: var(--text-muted); margin-bottom: var(--space-xs); }
|
|
6195
|
+
.timeline-content { color: var(--text-secondary); font-size: 14px; }
|
|
6196
|
+
.status-bar { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md); margin-bottom: var(--space-lg); }
|
|
6197
|
+
.status-item { display: flex; align-items: center; gap: var(--space-sm); }
|
|
6198
|
+
.status-label { font-size: 13px; color: var(--text-muted); }
|
|
6199
|
+
.status-value { font-size: 14px; font-weight: 600; color: var(--text-primary); }
|
|
6200
|
+
.grid-2 { display: grid; grid-template-columns: repeat(2,1fr); gap: var(--space-md); }
|
|
6201
|
+
.grid-3 { display: grid; grid-template-columns: repeat(3,1fr); gap: var(--space-md); }
|
|
6202
|
+
.settings-group { padding: var(--space-lg); margin-bottom: var(--space-lg); }
|
|
6203
|
+
.settings-item { display: flex; justify-content: space-between; align-items: center; padding: var(--space-md) 0; border-bottom: 1px solid var(--glass-border); }
|
|
6204
|
+
.settings-item:last-child { border-bottom: none; }
|
|
6205
|
+
.settings-label { font-size: 14px; font-weight: 500; color: var(--text-primary); }
|
|
6206
|
+
.settings-value { font-size: 14px; color: var(--text-secondary); font-family: var(--font-mono); }
|
|
6207
|
+
.settings-desc { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
|
|
6208
|
+
.mobile-nav {
|
|
6209
|
+
display: none; position: fixed; bottom: 0; left: 0; right: 0;
|
|
6210
|
+
padding: var(--space-sm); z-index: 100; flex-direction: row; justify-content: space-around;
|
|
6211
|
+
border-top: 1px solid var(--glass-border);
|
|
6212
|
+
background: linear-gradient(180deg,rgba(255,255,255,0.08) 0%,rgba(255,255,255,0.02) 100%);
|
|
6213
|
+
backdrop-filter: blur(40px) saturate(200%);
|
|
6214
|
+
}
|
|
6215
|
+
.mobile-nav-item { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: var(--space-xs); color: var(--text-secondary); text-decoration: none; cursor: pointer; border: none; background: none; font-size: 10px; font-family: inherit; }
|
|
6216
|
+
.mobile-nav-item.active { color: var(--accent); }
|
|
6217
|
+
.pulse { animation: pulse 2s cubic-bezier(0.4,0,0.6,1) infinite; }
|
|
6218
|
+
@media (max-width: 1024px) {
|
|
6219
|
+
.sidebar { width: 72px; padding: var(--space-sm); }
|
|
6220
|
+
.sidebar-logo span, .nav-item span { display: none; }
|
|
6221
|
+
.nav-item { justify-content: center; padding: var(--space-sm); }
|
|
6222
|
+
.main-content { margin-left: 72px; padding: var(--space-lg); }
|
|
6223
|
+
.content-grid { grid-template-columns: 1fr; }
|
|
6224
|
+
.metrics-grid { grid-template-columns: repeat(2,1fr); }
|
|
6225
|
+
}
|
|
6226
|
+
@media (max-width: 768px) {
|
|
6227
|
+
.sidebar { display: none; }
|
|
6228
|
+
.mobile-nav { display: flex; }
|
|
6229
|
+
.main-content { margin-left: 0; margin-bottom: 80px; padding: var(--space-md); }
|
|
6230
|
+
.page-title { font-size: 28px; }
|
|
6231
|
+
.metrics-grid { grid-template-columns: repeat(2,1fr); }
|
|
6232
|
+
.grid-2, .grid-3 { grid-template-columns: 1fr; }
|
|
6233
|
+
.section-header { flex-direction: column; align-items: flex-start; gap: var(--space-sm); }
|
|
6234
|
+
}
|
|
6235
|
+
@media (prefers-reduced-motion: reduce) {
|
|
6236
|
+
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
|
|
6237
|
+
}
|
|
6238
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
6239
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
6240
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 4px; }
|
|
6241
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); }
|
|
6242
|
+
.highlight-text { background: rgba(100,210,255,0.15); padding: 0 4px; border-radius: 3px; color: var(--accent); }
|
|
6243
|
+
</style>
|
|
6244
|
+
</head>
|
|
6245
|
+
<body class="gradient-bg">
|
|
6246
|
+
<aside class="sidebar">
|
|
6247
|
+
<div class="sidebar-logo">
|
|
6248
|
+
<div class="logo-icon">
|
|
6249
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
6250
|
+
</div>
|
|
6251
|
+
<span>ChatterCatcher</span>
|
|
6252
|
+
</div>
|
|
6253
|
+
<nav class="sidebar-nav">
|
|
6254
|
+
<button class="nav-item active" data-view="overview">
|
|
6255
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
|
6256
|
+
<span>\u6982\u89C8</span>
|
|
6257
|
+
</button>
|
|
6258
|
+
<button class="nav-item" data-view="messages">
|
|
6259
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
6260
|
+
<span>\u6D88\u606F</span>
|
|
6261
|
+
</button>
|
|
6262
|
+
<button class="nav-item" data-view="episodes">
|
|
6263
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
|
|
6264
|
+
<span>\u4F1A\u8BDD\u8BB0\u5FC6</span>
|
|
6265
|
+
</button>
|
|
6266
|
+
<button class="nav-item" data-view="files">
|
|
6267
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
6268
|
+
<span>\u6587\u4EF6\u5E93</span>
|
|
6269
|
+
</button>
|
|
6270
|
+
<button class="nav-item" data-view="tasks">
|
|
6271
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
|
6272
|
+
<span>\u4EFB\u52A1</span>
|
|
6273
|
+
</button>
|
|
6274
|
+
<button class="nav-item" data-view="qa-logs">
|
|
6275
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
|
6276
|
+
<span>\u95EE\u7B54\u65E5\u5FD7</span>
|
|
6277
|
+
</button>
|
|
6278
|
+
<button class="nav-item" data-view="settings">
|
|
6279
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
6280
|
+
<span>\u8BBE\u7F6E</span>
|
|
6281
|
+
</button>
|
|
6282
|
+
</nav>
|
|
6283
|
+
<div style="margin-top: auto; padding: var(--space-md);">
|
|
6284
|
+
<div style="display: flex; align-items: center; gap: var(--space-sm); font-size: 12px; color: var(--text-muted);">
|
|
6285
|
+
<span class="status-dot online" id="gateway-indicator"></span>
|
|
6286
|
+
<span id="gateway-status-text">Gateway \u8FD0\u884C\u4E2D</span>
|
|
6287
|
+
</div>
|
|
6288
|
+
<div style="font-size: 11px; color: var(--text-muted); margin-top: var(--space-xs); opacity: 0.7;" id="version-text">v0.0.0</div>
|
|
6289
|
+
</div>
|
|
6290
|
+
</aside>
|
|
6291
|
+
|
|
6292
|
+
<nav class="mobile-nav glass">
|
|
6293
|
+
<button class="mobile-nav-item active" data-view="overview">
|
|
6294
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
|
6295
|
+
<span>\u6982\u89C8</span>
|
|
6296
|
+
</button>
|
|
6297
|
+
<button class="mobile-nav-item" data-view="messages">
|
|
6298
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
6299
|
+
<span>\u6D88\u606F</span>
|
|
6300
|
+
</button>
|
|
6301
|
+
<button class="mobile-nav-item" data-view="files">
|
|
6302
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
6303
|
+
<span>\u6587\u4EF6</span>
|
|
6304
|
+
</button>
|
|
6305
|
+
<button class="mobile-nav-item" data-view="tasks">
|
|
6306
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
|
6307
|
+
<span>\u4EFB\u52A1</span>
|
|
6308
|
+
</button>
|
|
6309
|
+
<button class="mobile-nav-item" data-view="settings">
|
|
6310
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
6311
|
+
<span>\u8BBE\u7F6E</span>
|
|
6312
|
+
</button>
|
|
6313
|
+
</nav>
|
|
6314
|
+
|
|
6315
|
+
<main class="main-content">
|
|
6316
|
+
<div class="view active" id="view-overview">
|
|
6317
|
+
<div class="page-header">
|
|
6318
|
+
<h1 class="page-title">Dashboard</h1>
|
|
6319
|
+
<p class="page-subtitle">\u672C\u5730\u4F18\u5148\u7684\u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93 \xB7 \u95EE\u7B54\u5FC5\u987B\u5148\u68C0\u7D22 RAG \u8BC1\u636E\uFF0C\u4E0D\u5806\u53E0\u5168\u91CF\u4E0A\u4E0B\u6587</p>
|
|
6320
|
+
</div>
|
|
6321
|
+
<div class="metrics-grid" id="metrics"></div>
|
|
6322
|
+
<div class="content-grid">
|
|
5834
6323
|
<div>
|
|
5835
|
-
<
|
|
5836
|
-
|
|
6324
|
+
<div class="content-panel glass">
|
|
6325
|
+
<div class="panel-header">
|
|
6326
|
+
<h2 class="panel-title">\u6700\u8FD1\u6D88\u606F</h2>
|
|
6327
|
+
<button class="btn btn-sm" onclick="navigateTo('messages')">\u67E5\u770B\u5168\u90E8</button>
|
|
6328
|
+
</div>
|
|
6329
|
+
<div id="recent-messages"></div>
|
|
6330
|
+
</div>
|
|
6331
|
+
<div class="content-panel glass mt-lg">
|
|
6332
|
+
<div class="panel-header">
|
|
6333
|
+
<h2 class="panel-title">\u4F1A\u8BDD\u8BB0\u5FC6</h2>
|
|
6334
|
+
<button class="btn btn-sm" onclick="navigateTo('episodes')">\u67E5\u770B\u5168\u90E8</button>
|
|
6335
|
+
</div>
|
|
6336
|
+
<div id="recent-episodes"></div>
|
|
6337
|
+
</div>
|
|
5837
6338
|
</div>
|
|
5838
6339
|
<div>
|
|
5839
|
-
<div class="
|
|
5840
|
-
<
|
|
6340
|
+
<div class="content-panel glass">
|
|
6341
|
+
<div class="panel-header"><h2 class="panel-title">\u7CFB\u7EDF\u72B6\u6001</h2></div>
|
|
6342
|
+
<div id="system-status"></div>
|
|
6343
|
+
</div>
|
|
6344
|
+
<div class="content-panel glass mt-lg">
|
|
6345
|
+
<div class="panel-header"><h2 class="panel-title">\u5FEB\u6377\u64CD\u4F5C</h2></div>
|
|
6346
|
+
<div style="display: flex; flex-direction: column; gap: var(--space-sm);">
|
|
6347
|
+
<button class="btn btn-primary" id="btn-process-messages" onclick="processNow()">
|
|
6348
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
6349
|
+
\u7ACB\u5373\u5904\u7406\u6D88\u606F
|
|
6350
|
+
</button>
|
|
6351
|
+
<button class="btn" onclick="navigateTo('settings')">
|
|
6352
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
6353
|
+
\u7CFB\u7EDF\u8BBE\u7F6E
|
|
6354
|
+
</button>
|
|
6355
|
+
</div>
|
|
5841
6356
|
</div>
|
|
5842
|
-
<div
|
|
6357
|
+
<div class="content-panel glass mt-lg">
|
|
6358
|
+
<div class="panel-header"><h2 class="panel-title">RAG \u68C0\u7D22</h2></div>
|
|
6359
|
+
<div style="font-size: 13px; color: var(--text-secondary); line-height: 1.8;">
|
|
6360
|
+
<div style="display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-sm);"><span class="tag tag-success">FTS5</span><span>\u5173\u952E\u8BCD\u68C0\u7D22</span></div>
|
|
6361
|
+
<div style="display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-sm);"><span class="tag tag-info">\u5411\u91CF</span><span>\u8BED\u4E49\u68C0\u7D22</span></div>
|
|
6362
|
+
<div style="display: flex; align-items: center; gap: var(--space-sm);"><span class="tag tag-success">\u6DF7\u5408</span><span>Hybrid RAG</span></div>
|
|
6363
|
+
</div>
|
|
6364
|
+
</div>
|
|
6365
|
+
</div>
|
|
6366
|
+
</div>
|
|
6367
|
+
</div>
|
|
6368
|
+
|
|
6369
|
+
<div class="view" id="view-messages">
|
|
6370
|
+
<div class="section-header">
|
|
6371
|
+
<div><h1 class="section-title">\u6D88\u606F</h1><p class="page-subtitle">\u7FA4\u804A\u6D88\u606F\u5386\u53F2</p></div>
|
|
6372
|
+
<div class="search-box">
|
|
6373
|
+
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
6374
|
+
<input type="text" id="message-search" placeholder="\u641C\u7D22\u6D88\u606F..." oninput="filterMessages()" />
|
|
5843
6375
|
</div>
|
|
5844
|
-
</
|
|
6376
|
+
</div>
|
|
6377
|
+
<div class="content-panel glass"><div id="messages-list"></div></div>
|
|
6378
|
+
</div>
|
|
5845
6379
|
|
|
5846
|
-
|
|
6380
|
+
<div class="view" id="view-episodes">
|
|
6381
|
+
<div class="section-header"><div><h1 class="section-title">\u4F1A\u8BDD\u8BB0\u5FC6</h1><p class="page-subtitle">\u81EA\u52A8\u805A\u5408\u7684\u804A\u5929\u7247\u6BB5</p></div></div>
|
|
6382
|
+
<div class="content-panel glass"><div id="episodes-list"></div></div>
|
|
6383
|
+
</div>
|
|
5847
6384
|
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
6385
|
+
<div class="view" id="view-files">
|
|
6386
|
+
<div class="section-header"><div><h1 class="section-title">\u6587\u4EF6\u5E93</h1><p class="page-subtitle">\u5DF2\u5BFC\u5165\u7684\u6587\u4EF6\u77E5\u8BC6\u6E90</p></div></div>
|
|
6387
|
+
<div id="files-list"></div>
|
|
6388
|
+
</div>
|
|
6389
|
+
|
|
6390
|
+
<div class="view" id="view-tasks">
|
|
6391
|
+
<div class="section-header"><div><h1 class="section-title">\u4EFB\u52A1</h1><p class="page-subtitle">\u6587\u4EF6\u89E3\u6790\u4E0E\u5B9A\u65F6\u4EFB\u52A1</p></div></div>
|
|
6392
|
+
<div class="tabs" style="margin-bottom: var(--space-lg);">
|
|
6393
|
+
<button class="tab active" data-tab="file-jobs" onclick="switchTab('file-jobs')">\u6587\u4EF6\u89E3\u6790</button>
|
|
6394
|
+
<button class="tab" data-tab="cron-jobs" onclick="switchTab('cron-jobs')">\u5B9A\u65F6\u4EFB\u52A1</button>
|
|
6395
|
+
</div>
|
|
6396
|
+
<div class="content-panel glass" id="tab-file-jobs"><div id="file-jobs-list"></div></div>
|
|
6397
|
+
<div class="content-panel glass" id="tab-cron-jobs" style="display: none;"><div id="cron-jobs-list"></div></div>
|
|
6398
|
+
</div>
|
|
6399
|
+
|
|
6400
|
+
<div class="view" id="view-qa-logs">
|
|
6401
|
+
<div class="section-header"><div><h1 class="section-title">\u95EE\u7B54\u65E5\u5FD7</h1><p class="page-subtitle">\u95EE\u7B54\u5386\u53F2\u8BB0\u5F55</p></div></div>
|
|
6402
|
+
<div class="content-panel glass"><div id="qa-logs-list"></div></div>
|
|
6403
|
+
</div>
|
|
6404
|
+
|
|
6405
|
+
<div class="view" id="view-settings">
|
|
6406
|
+
<div class="section-header"><div><h1 class="section-title">\u8BBE\u7F6E</h1><p class="page-subtitle">\u7CFB\u7EDF\u914D\u7F6E\u4E0E\u64CD\u4F5C</p></div></div>
|
|
6407
|
+
<div class="settings-group glass" id="settings-config"></div>
|
|
6408
|
+
<div class="settings-group glass">
|
|
6409
|
+
<h3 style="font-size: 16px; font-weight: 600; margin-bottom: var(--space-md);">\u64CD\u4F5C</h3>
|
|
6410
|
+
<div style="display: flex; flex-direction: column; gap: var(--space-sm);">
|
|
6411
|
+
<button class="btn btn-primary" onclick="processNow()">
|
|
6412
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
6413
|
+
\u7ACB\u5373\u5904\u7406\u6D88\u606F\u7D22\u5F15
|
|
6414
|
+
</button>
|
|
6415
|
+
<div style="font-size: 12px; color: var(--text-muted); padding: var(--space-sm); background: rgba(255,255,255,0.03); border-radius: var(--radius-sm);">
|
|
6416
|
+
\u8FD0\u884C CLI \u547D\u4EE4\u8FDB\u884C\u66F4\u591A\u64CD\u4F5C\uFF1A
|
|
6417
|
+
<div style="font-family: var(--font-mono); margin-top: var(--space-xs); line-height: 1.8;">
|
|
6418
|
+
chattercatcher settings<br/>
|
|
6419
|
+
chattercatcher doctor<br/>
|
|
6420
|
+
chattercatcher index rebuild<br/>
|
|
6421
|
+
chattercatcher files add <path...><br/>
|
|
6422
|
+
chattercatcher export
|
|
6423
|
+
</div>
|
|
6424
|
+
</div>
|
|
5862
6425
|
</div>
|
|
5863
|
-
<aside>
|
|
5864
|
-
<section>
|
|
5865
|
-
<h2>\u7FA4\u804A</h2>
|
|
5866
|
-
<div id="chats" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5867
|
-
</section>
|
|
5868
|
-
<section>
|
|
5869
|
-
<h2>\u6587\u4EF6\u5E93</h2>
|
|
5870
|
-
<div id="files" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5871
|
-
</section>
|
|
5872
|
-
<section>
|
|
5873
|
-
<h2>\u89E3\u6790\u4EFB\u52A1</h2>
|
|
5874
|
-
<div id="file-jobs" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5875
|
-
</section>
|
|
5876
|
-
<section>
|
|
5877
|
-
<h2>\u5B9A\u65F6\u4EFB\u52A1</h2>
|
|
5878
|
-
<div id="cron-jobs" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5879
|
-
</section>
|
|
5880
|
-
<section>
|
|
5881
|
-
<h2>\u672C\u5730\u64CD\u4F5C</h2>
|
|
5882
|
-
<p><code>chattercatcher settings</code> \u4FEE\u6539\u914D\u7F6E\u3002</p>
|
|
5883
|
-
<p><code>chattercatcher files add <path...></code> \u5BFC\u5165\u6587\u672C\u3001DOCX \u6216 PDF \u6587\u4EF6\u3002</p>
|
|
5884
|
-
<p><code>chattercatcher doctor</code> \u68C0\u67E5\u98DE\u4E66\u3001\u6A21\u578B\u3001RAG \u548C\u672C\u5730\u5B58\u50A8\u3002</p>
|
|
5885
|
-
</section>
|
|
5886
|
-
</aside>
|
|
5887
6426
|
</div>
|
|
5888
|
-
</
|
|
5889
|
-
|
|
5890
|
-
const metrics = document.querySelector("#metrics");
|
|
5891
|
-
const messages = document.querySelector("#messages");
|
|
5892
|
-
const episodes = document.querySelector("#episodes");
|
|
5893
|
-
const chats = document.querySelector("#chats");
|
|
5894
|
-
const files = document.querySelector("#files");
|
|
5895
|
-
const fileJobs = document.querySelector("#file-jobs");
|
|
5896
|
-
const cronJobs = document.querySelector("#cron-jobs");
|
|
5897
|
-
const qaLogs = document.querySelector("#qa-logs");
|
|
5898
|
-
const processMessages = document.querySelector("#process-messages");
|
|
5899
|
-
const actionStatus = document.querySelector("#action-status");
|
|
5900
|
-
|
|
5901
|
-
let webActionToken = "__WEB_ACTION_TOKEN__";
|
|
5902
|
-
|
|
5903
|
-
function fmt(value) {
|
|
5904
|
-
return value == null || value === "" ? "-" : String(value);
|
|
5905
|
-
}
|
|
6427
|
+
</div>
|
|
6428
|
+
</main>
|
|
5906
6429
|
|
|
5907
|
-
|
|
5908
|
-
return fmt(value)
|
|
5909
|
-
.replaceAll("&", "&")
|
|
5910
|
-
.replaceAll("<", "<")
|
|
5911
|
-
.replaceAll(">", ">")
|
|
5912
|
-
.replaceAll('"', """);
|
|
5913
|
-
}
|
|
6430
|
+
<div id="toast-container" style="position: fixed; top: 24px; right: 24px; z-index: 1001; display: flex; flex-direction: column; gap: 12px;"></div>
|
|
5914
6431
|
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
6432
|
+
<script>
|
|
6433
|
+
let currentView = "overview";
|
|
6434
|
+
let allMessages = [];
|
|
6435
|
+
let allEpisodes = [];
|
|
6436
|
+
let allFiles = [];
|
|
6437
|
+
let allFileJobs = [];
|
|
6438
|
+
let allCronJobs = [];
|
|
6439
|
+
let allQaLogs = [];
|
|
6440
|
+
let statusData = null;
|
|
5918
6441
|
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
}
|
|
6442
|
+
function fmt(value) { return value == null || value === "" ? "-" : String(value); }
|
|
6443
|
+
function escapeHtml(value) {
|
|
6444
|
+
return fmt(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
6445
|
+
}
|
|
6446
|
+
function isOpaqueId(value) { return /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(fmt(value)); }
|
|
6447
|
+
function formatDateTime(value) {
|
|
6448
|
+
var date = new Date(value);
|
|
6449
|
+
if (Number.isNaN(date.getTime())) return fmt(value);
|
|
6450
|
+
var pad = function(n) { return String(n).padStart(2, "0"); };
|
|
6451
|
+
return date.getFullYear() + "/" + pad(date.getMonth()+1) + "/" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes());
|
|
6452
|
+
}
|
|
6453
|
+
function displaySender(value) { return isOpaqueId(value) ? "\u7FA4\u6210\u5458" : fmt(value); }
|
|
6454
|
+
function displayChatName(value, platform) { return !isOpaqueId(value) ? fmt(value) : (platform === "feishu" ? "\u98DE\u4E66\u7FA4\u804A" : "\u7FA4\u804A"); }
|
|
5933
6455
|
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
6456
|
+
function showToast(message, type) {
|
|
6457
|
+
type = type || "info";
|
|
6458
|
+
var container = document.getElementById("toast-container");
|
|
6459
|
+
var toast = document.createElement("div");
|
|
6460
|
+
toast.className = "toast toast-" + type;
|
|
6461
|
+
toast.textContent = message;
|
|
6462
|
+
container.appendChild(toast);
|
|
6463
|
+
setTimeout(function() {
|
|
6464
|
+
toast.style.opacity = "0"; toast.style.transform = "translateX(10px)";
|
|
6465
|
+
setTimeout(function() { toast.remove(); }, 300);
|
|
6466
|
+
}, 3000);
|
|
6467
|
+
}
|
|
5937
6468
|
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
6469
|
+
function navigateTo(view) {
|
|
6470
|
+
document.querySelectorAll(".view").forEach(function(el) { el.classList.remove("active"); });
|
|
6471
|
+
document.querySelectorAll(".nav-item, .mobile-nav-item").forEach(function(el) { el.classList.remove("active"); });
|
|
6472
|
+
document.getElementById("view-" + view).classList.add("active");
|
|
6473
|
+
document.querySelectorAll('[data-view="' + view + '"]').forEach(function(el) { el.classList.add("active"); });
|
|
6474
|
+
currentView = view;
|
|
6475
|
+
window.scrollTo(0, 0);
|
|
6476
|
+
if (view === "messages") renderMessagesView();
|
|
6477
|
+
if (view === "episodes") renderEpisodesView();
|
|
6478
|
+
if (view === "files") renderFilesView();
|
|
6479
|
+
if (view === "tasks") renderTasksView();
|
|
6480
|
+
if (view === "qa-logs") renderQaLogsView();
|
|
6481
|
+
}
|
|
5942
6482
|
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
return "\u5F85\u542F\u52A8";
|
|
5947
|
-
}
|
|
6483
|
+
document.querySelectorAll(".nav-item, .mobile-nav-item").forEach(function(el) {
|
|
6484
|
+
el.addEventListener("click", function() { navigateTo(el.dataset.view); });
|
|
6485
|
+
});
|
|
5948
6486
|
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
6487
|
+
function switchTab(tab) {
|
|
6488
|
+
document.querySelectorAll(".tab").forEach(function(el) { el.classList.remove("active"); });
|
|
6489
|
+
document.querySelector('[data-tab="' + tab + '"]').classList.add("active");
|
|
6490
|
+
document.getElementById("tab-file-jobs").style.display = tab === "file-jobs" ? "block" : "none";
|
|
6491
|
+
document.getElementById("tab-cron-jobs").style.display = tab === "cron-jobs" ? "block" : "none";
|
|
6492
|
+
if (tab === "file-jobs") renderFileJobs();
|
|
6493
|
+
if (tab === "cron-jobs") renderCronJobs();
|
|
6494
|
+
}
|
|
5953
6495
|
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
["\u7FA4\u804A", status.data.chats, "\u672C\u5730\u7FA4\u804A\u6570", ""],
|
|
5960
|
-
["\u6D88\u606F", status.data.messages, "\u5DF2\u5165\u5E93\u6D88\u606F", ""],
|
|
5961
|
-
["\u4F1A\u8BDD\u8BB0\u5FC6", status.data.episodes, "\u5DF2\u751F\u6210\u6458\u8981", ""],
|
|
5962
|
-
["\u6587\u4EF6", status.data.files, "\u6587\u4EF6\u77E5\u8BC6\u6E90", ""],
|
|
5963
|
-
].map(([label, value, note, extra]) => \`
|
|
5964
|
-
<div class="metric">
|
|
5965
|
-
<div class="label">\${escapeHtml(label)}</div>
|
|
5966
|
-
<div class="value \${extra}">\${escapeHtml(value)}</div>
|
|
5967
|
-
<div class="note">\${escapeHtml(note)}</div>
|
|
5968
|
-
</div>
|
|
5969
|
-
\`).join("");
|
|
6496
|
+
async function fetchJson(path) {
|
|
6497
|
+
var response = await fetch(path);
|
|
6498
|
+
if (!response.ok) {
|
|
6499
|
+
var body = await response.text();
|
|
6500
|
+
throw new Error(path + " " + response.status + " " + body);
|
|
5970
6501
|
}
|
|
6502
|
+
return response.json();
|
|
6503
|
+
}
|
|
5971
6504
|
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
}
|
|
5978
|
-
messages.className = "";
|
|
5979
|
-
messages.innerHTML = \`
|
|
5980
|
-
<div class="message-list">
|
|
5981
|
-
\${items.map((item) => \`
|
|
5982
|
-
<article class="message-item">
|
|
5983
|
-
<div class="message-meta">
|
|
5984
|
-
<span>\${escapeHtml(formatDateTime(item.sentAt))}</span>
|
|
5985
|
-
<span>\${escapeHtml(displaySender(item.senderName))}</span>
|
|
5986
|
-
<span>\${escapeHtml(displayChatName(item.chatName, item.platform))}</span>
|
|
5987
|
-
</div>
|
|
5988
|
-
<div class="message-body">\${escapeHtml(item.text)}</div>
|
|
5989
|
-
</article>
|
|
5990
|
-
\`).join("")}
|
|
5991
|
-
</div>
|
|
5992
|
-
\`;
|
|
6505
|
+
async function postJson(path, options) {
|
|
6506
|
+
var response = await fetch(path, Object.assign({ method: "POST" }, options || {}));
|
|
6507
|
+
var result = await response.json();
|
|
6508
|
+
if (!response.ok) {
|
|
6509
|
+
throw new Error(result.message || result.reason || "\u8BF7\u6C42\u5931\u8D25");
|
|
5993
6510
|
}
|
|
6511
|
+
return result;
|
|
6512
|
+
}
|
|
5994
6513
|
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
}
|
|
6001
|
-
episodes.className = "";
|
|
6002
|
-
episodes.innerHTML = \`
|
|
6003
|
-
<div class="message-list">
|
|
6004
|
-
\${items.map((item) => \`
|
|
6005
|
-
<article class="message-item">
|
|
6006
|
-
<div class="message-meta">
|
|
6007
|
-
<span>\${escapeHtml(formatDateTime(item.startedAt))} - \${escapeHtml(formatDateTime(item.endedAt))}</span>
|
|
6008
|
-
<span>\${escapeHtml(displayChatName(item.chatName, "feishu"))}</span>
|
|
6009
|
-
<span>\${escapeHtml(item.messageCount)} \u6761\u6D88\u606F</span>
|
|
6010
|
-
</div>
|
|
6011
|
-
<div class="message-body">\${escapeHtml(item.summary)}</div>
|
|
6012
|
-
</article>
|
|
6013
|
-
\`).join("")}
|
|
6014
|
-
</div>
|
|
6015
|
-
\`;
|
|
6514
|
+
async function deleteJson(path) {
|
|
6515
|
+
var response = await fetch(path, { method: "DELETE" });
|
|
6516
|
+
var result = await response.json();
|
|
6517
|
+
if (!response.ok) {
|
|
6518
|
+
throw new Error(result.message || result.reason || "\u8BF7\u6C42\u5931\u8D25");
|
|
6016
6519
|
}
|
|
6520
|
+
return result;
|
|
6521
|
+
}
|
|
6017
6522
|
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
chats
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6523
|
+
function renderMetrics(status) {
|
|
6524
|
+
var gatewayClass = status.gateway.configured ? "status-dot online" : "status-dot offline";
|
|
6525
|
+
var gatewayText = status.gateway.connection === "running" ? "\u8FD0\u884C\u4E2D" : (!status.gateway.configured ? "\u672A\u914D\u7F6E" : "\u5F85\u542F\u52A8");
|
|
6526
|
+
var metricsHtml = [
|
|
6527
|
+
["Gateway", gatewayText, "\u98DE\u4E66\u957F\u8FDE\u63A5", gatewayClass],
|
|
6528
|
+
["\u7248\u672C", status.version || "unknown", "\u5F53\u524D\u8FD0\u884C\u7248\u672C", ""],
|
|
6529
|
+
["\u7FA4\u804A", status.data.chats, "\u672C\u5730\u7FA4\u804A\u6570", ""],
|
|
6530
|
+
["\u6D88\u606F", status.data.messages, "\u5DF2\u5165\u5E93\u6D88\u606F", ""],
|
|
6531
|
+
["\u4F1A\u8BDD\u8BB0\u5FC6", status.data.episodes, "\u5DF2\u751F\u6210\u6458\u8981", ""],
|
|
6532
|
+
["\u6587\u4EF6", status.data.files, "\u6587\u4EF6\u77E5\u8BC6\u6E90", ""],
|
|
6533
|
+
["\u95EE\u7B54", status.data.qaLogs, "\u95EE\u7B54\u8BB0\u5F55", ""],
|
|
6534
|
+
["\u4EFB\u52A1", status.data.cronJobs, "\u5B9A\u65F6\u4EFB\u52A1", ""]
|
|
6535
|
+
].map(function(item) {
|
|
6536
|
+
var label = item[0], value = item[1], note = item[2], dotClass = item[3];
|
|
6537
|
+
return '<div class="metric-card glass"><div class="metric-label">' + escapeHtml(label) + '</div>' +
|
|
6538
|
+
'<div class="metric-value">' + (dotClass ? '<span class="' + dotClass + '" style="margin-right:8px;"></span>' : '') + escapeHtml(value) + '</div>' +
|
|
6539
|
+
'<div class="metric-note">' + escapeHtml(note) + '</div></div>';
|
|
6540
|
+
}).join("");
|
|
6541
|
+
document.getElementById("metrics").innerHTML = metricsHtml;
|
|
6542
|
+
document.getElementById("gateway-indicator").className = gatewayClass;
|
|
6543
|
+
document.getElementById("gateway-status-text").textContent = "Gateway " + gatewayText;
|
|
6544
|
+
document.getElementById("version-text").textContent = "v" + (status.version || "unknown");
|
|
6545
|
+
}
|
|
6039
6546
|
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
<td>\${escapeHtml(item.parser || "unknown")}</td>
|
|
6058
|
-
<td>\${escapeHtml(item.characters)}</td>
|
|
6059
|
-
</tr>
|
|
6060
|
-
\`).join("")}
|
|
6061
|
-
</tbody>
|
|
6062
|
-
</table>
|
|
6063
|
-
\`;
|
|
6547
|
+
function renderSystemStatus(status) {
|
|
6548
|
+
var gateway = status.gateway;
|
|
6549
|
+
var html = '<div style="display:flex;flex-direction:column;gap:var(--space-md);">';
|
|
6550
|
+
html += '<div class="settings-item"><div><div class="settings-label">Gateway</div></div><div class="settings-value">' + (gateway.connection === "running" ? '<span class="tag tag-success">\u8FD0\u884C\u4E2D</span>' : '<span class="tag tag-warning">\u672A\u8FD0\u884C</span>') + '</div></div>';
|
|
6551
|
+
html += '<div class="settings-item"><div><div class="settings-label">Web UI</div></div><div class="settings-value">' + escapeHtml((status.web && status.web.host ? status.web.host : "127.0.0.1") + ":" + (status.web && status.web.port ? status.web.port : "3878")) + '</div></div>';
|
|
6552
|
+
html += '<div class="settings-item"><div><div class="settings-label">RAG \u6A21\u5F0F</div></div><div class="settings-value"><span class="tag tag-success">\u5F3A\u5236\u68C0\u7D22</span></div></div>';
|
|
6553
|
+
html += '<div class="settings-item"><div><div class="settings-label">\u5173\u952E\u8BCD\u68C0\u7D22</div></div><div class="settings-value">SQLite FTS5</div></div>';
|
|
6554
|
+
html += '<div class="settings-item"><div><div class="settings-label">\u5411\u91CF\u68C0\u7D22</div></div><div class="settings-value">SQLite embedding</div></div>';
|
|
6555
|
+
html += '</div>';
|
|
6556
|
+
document.getElementById("system-status").innerHTML = html;
|
|
6557
|
+
}
|
|
6558
|
+
|
|
6559
|
+
function renderRecentMessages(items) {
|
|
6560
|
+
var el = document.getElementById("recent-messages");
|
|
6561
|
+
if (!items || items.length === 0) {
|
|
6562
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u6D88\u606F\u3002\u542F\u52A8 Gateway \u540E\uFF0C\u7FA4\u804A\u6587\u672C\u4F1A\u8FDB\u5165\u672C\u5730 RAG \u7D22\u5F15\u3002</div>';
|
|
6563
|
+
return;
|
|
6064
6564
|
}
|
|
6565
|
+
var html = '<div class="message-list">';
|
|
6566
|
+
for (var i = 0; i < Math.min(items.length, 5); i++) {
|
|
6567
|
+
var item = items[i];
|
|
6568
|
+
html += '<div class="message-card"><div class="message-meta">' +
|
|
6569
|
+
'<span>' + escapeHtml(formatDateTime(item.sentAt)) + '</span>' +
|
|
6570
|
+
'<span>' + escapeHtml(displaySender(item.senderName)) + '</span>' +
|
|
6571
|
+
'<span>' + escapeHtml(displayChatName(item.chatName, item.platform)) + '</span>' +
|
|
6572
|
+
'</div><div class="message-text">' + escapeHtml(item.text) + '</div></div>';
|
|
6573
|
+
}
|
|
6574
|
+
html += '</div>';
|
|
6575
|
+
el.innerHTML = html;
|
|
6576
|
+
}
|
|
6065
6577
|
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
}
|
|
6072
|
-
fileJobs.className = "";
|
|
6073
|
-
fileJobs.innerHTML = \`
|
|
6074
|
-
<table>
|
|
6075
|
-
<thead><tr><th>\u4EFB\u52A1</th><th>\u72B6\u6001</th></tr></thead>
|
|
6076
|
-
<tbody>
|
|
6077
|
-
\${items.map((item) => \`
|
|
6078
|
-
<tr>
|
|
6079
|
-
<td>
|
|
6080
|
-
<div>\${escapeHtml(item.fileName)}</div>
|
|
6081
|
-
<div class="path" title="\${escapeHtml(item.id)}">ID: \${escapeHtml(item.id)}</div>
|
|
6082
|
-
<div class="path" title="\${escapeHtml(item.error || item.storedPath)}">\${escapeHtml(item.error || item.storedPath)}</div>
|
|
6083
|
-
</td>
|
|
6084
|
-
<td>\${escapeHtml(item.status)}</td>
|
|
6085
|
-
</tr>
|
|
6086
|
-
\`).join("")}
|
|
6087
|
-
</tbody>
|
|
6088
|
-
</table>
|
|
6089
|
-
\`;
|
|
6578
|
+
function renderRecentEpisodes(items) {
|
|
6579
|
+
var el = document.getElementById("recent-episodes");
|
|
6580
|
+
if (!items || items.length === 0) {
|
|
6581
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u8BB0\u5FC6\u3002</div>';
|
|
6582
|
+
return;
|
|
6090
6583
|
}
|
|
6584
|
+
var html = '<div class="message-list">';
|
|
6585
|
+
for (var i = 0; i < Math.min(items.length, 3); i++) {
|
|
6586
|
+
var item = items[i];
|
|
6587
|
+
html += '<div class="episode-card"><div class="message-meta">' +
|
|
6588
|
+
'<span>' + escapeHtml(formatDateTime(item.startedAt)) + " - " + escapeHtml(formatDateTime(item.endedAt)) + '</span>' +
|
|
6589
|
+
'<span>' + escapeHtml(item.messageCount) + ' \u6761\u6D88\u606F</span>' +
|
|
6590
|
+
'</div><div class="message-text">' + escapeHtml(item.summary) + '</div></div>';
|
|
6591
|
+
}
|
|
6592
|
+
html += '</div>';
|
|
6593
|
+
el.innerHTML = html;
|
|
6594
|
+
}
|
|
6091
6595
|
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
}
|
|
6098
|
-
cronJobs.className = "";
|
|
6099
|
-
cronJobs.innerHTML = \`
|
|
6100
|
-
<table>
|
|
6101
|
-
<thead><tr><th>\u4EFB\u52A1</th><th>\u72B6\u6001</th></tr></thead>
|
|
6102
|
-
<tbody>
|
|
6103
|
-
\${items.map((item) => \`
|
|
6104
|
-
<tr>
|
|
6105
|
-
<td>
|
|
6106
|
-
<div>\${escapeHtml(item.schedule)}</div>
|
|
6107
|
-
<div class="message" title="\${escapeHtml(item.prompt)}">\${escapeHtml(item.prompt)}</div>
|
|
6108
|
-
<div class="path" title="\${escapeHtml(item.id)}">ID: \${escapeHtml(item.id)}</div>
|
|
6109
|
-
<div class="path" title="\${escapeHtml(item.chatId)}">\u7FA4: \${escapeHtml(item.chatId)}</div>
|
|
6110
|
-
<div class="path">\u4E0B\u6B21: \${escapeHtml(formatDateTime(item.nextRunAt))}</div>
|
|
6111
|
-
<div class="path" title="\${escapeHtml(item.lastError || "")}">\${escapeHtml(item.lastError || "")}</div>
|
|
6112
|
-
\${item.status === "active" ? \`<button type="button" data-delete-cron-job="\${escapeHtml(item.id)}">\u5220\u9664</button>\` : ""}
|
|
6113
|
-
</td>
|
|
6114
|
-
<td>\${escapeHtml(item.status)}</td>
|
|
6115
|
-
</tr>
|
|
6116
|
-
\`).join("")}
|
|
6117
|
-
</tbody>
|
|
6118
|
-
</table>
|
|
6119
|
-
\`;
|
|
6596
|
+
function renderMessagesView() {
|
|
6597
|
+
var el = document.getElementById("messages-list");
|
|
6598
|
+
if (!allMessages || allMessages.length === 0) {
|
|
6599
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u6D88\u606F\u3002</div>';
|
|
6600
|
+
return;
|
|
6120
6601
|
}
|
|
6602
|
+
var searchInput = document.getElementById("message-search");
|
|
6603
|
+
var searchTerm = searchInput ? searchInput.value.toLowerCase() : "";
|
|
6604
|
+
var filtered = searchTerm ? allMessages.filter(function(m) { return (m.text || "").toLowerCase().indexOf(searchTerm) !== -1; }) : allMessages;
|
|
6605
|
+
if (filtered.length === 0) {
|
|
6606
|
+
el.innerHTML = '<div class="empty-state">\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6D88\u606F\u3002</div>';
|
|
6607
|
+
return;
|
|
6608
|
+
}
|
|
6609
|
+
var html = '<div class="message-list">';
|
|
6610
|
+
for (var i = 0; i < Math.min(filtered.length, 50); i++) {
|
|
6611
|
+
var item = filtered[i];
|
|
6612
|
+
html += '<div class="message-card"><div class="message-meta">' +
|
|
6613
|
+
'<span>' + escapeHtml(formatDateTime(item.sentAt)) + '</span>' +
|
|
6614
|
+
'<span>' + escapeHtml(displaySender(item.senderName)) + '</span>' +
|
|
6615
|
+
'<span>' + escapeHtml(displayChatName(item.chatName, item.platform)) + '</span>' +
|
|
6616
|
+
'</div><div class="message-text" style="-webkit-line-clamp:4;">' + escapeHtml(item.text) + '</div></div>';
|
|
6617
|
+
}
|
|
6618
|
+
html += '</div>';
|
|
6619
|
+
if (filtered.length > 50) {
|
|
6620
|
+
html += '<div style="text-align:center;padding:var(--space-md);color:var(--text-muted);font-size:13px;">\u8FD8\u6709 ' + (filtered.length - 50) + ' \u6761\u6D88\u606F...</div>';
|
|
6621
|
+
}
|
|
6622
|
+
el.innerHTML = html;
|
|
6623
|
+
}
|
|
6121
6624
|
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
const rows = items.map((item) => {
|
|
6130
|
-
const citationCount = Array.isArray(item.citations) ? item.citations.length : 0;
|
|
6131
|
-
return [
|
|
6132
|
-
'<article class="message-item">',
|
|
6133
|
-
' <div class="message-meta">',
|
|
6134
|
-
" <span>" + escapeHtml(formatDateTime(item.createdAt)) + "</span>",
|
|
6135
|
-
" <span>" + escapeHtml(item.status) + "</span>",
|
|
6136
|
-
" <span>" + escapeHtml(citationCount) + " \u6761\u5F15\u7528</span>",
|
|
6137
|
-
" </div>",
|
|
6138
|
-
" <div class=\\"message-body\\"><strong>\u95EE\uFF1A</strong>" + escapeHtml(item.question) + "</div>",
|
|
6139
|
-
" <div class=\\"message-body\\"><strong>\u7B54\uFF1A</strong>" + escapeHtml(item.answer) + "</div>",
|
|
6140
|
-
"</article>",
|
|
6141
|
-
].join("\\n");
|
|
6142
|
-
});
|
|
6143
|
-
qaLogs.innerHTML = [
|
|
6144
|
-
'<div class="message-list">',
|
|
6145
|
-
rows.join(""),
|
|
6146
|
-
"</div>",
|
|
6147
|
-
].join("\\n");
|
|
6625
|
+
function filterMessages() { renderMessagesView(); }
|
|
6626
|
+
|
|
6627
|
+
function renderEpisodesView() {
|
|
6628
|
+
var el = document.getElementById("episodes-list");
|
|
6629
|
+
if (!allEpisodes || allEpisodes.length === 0) {
|
|
6630
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u8BB0\u5FC6\u3002</div>';
|
|
6631
|
+
return;
|
|
6148
6632
|
}
|
|
6633
|
+
var html = '<div class="timeline">';
|
|
6634
|
+
for (var i = 0; i < allEpisodes.length; i++) {
|
|
6635
|
+
var item = allEpisodes[i];
|
|
6636
|
+
html += '<div class="timeline-item"><div class="timeline-date">' + escapeHtml(formatDateTime(item.startedAt)) + " - " + escapeHtml(formatDateTime(item.endedAt)) + " \xB7 " + escapeHtml(item.messageCount) + ' \u6761\u6D88\u606F</div><div class="timeline-content">' + escapeHtml(item.summary) + '</div></div>';
|
|
6637
|
+
}
|
|
6638
|
+
html += '</div>';
|
|
6639
|
+
el.innerHTML = html;
|
|
6640
|
+
}
|
|
6149
6641
|
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6642
|
+
function renderFilesView() {
|
|
6643
|
+
var el = document.getElementById("files-list");
|
|
6644
|
+
if (!allFiles || allFiles.length === 0) {
|
|
6645
|
+
el.innerHTML = '<div class="content-panel glass"><div class="empty-state">\u8FD8\u6CA1\u6709\u6587\u4EF6\u3002\u8FD0\u884C <code>chattercatcher files add <path...></code> \u5BFC\u5165\u6587\u4EF6\u3002</div></div>';
|
|
6646
|
+
return;
|
|
6647
|
+
}
|
|
6648
|
+
var html = '<div class="grid-2">';
|
|
6649
|
+
for (var i = 0; i < allFiles.length; i++) {
|
|
6650
|
+
var item = allFiles[i];
|
|
6651
|
+
html += '<div class="file-card glass"><div class="file-icon">' +
|
|
6652
|
+
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>' +
|
|
6653
|
+
'</div><div style="font-weight:600;margin-bottom:4px;">' + escapeHtml(item.fileName) + '</div>' +
|
|
6654
|
+
'<div style="font-size:13px;color:var(--text-muted);margin-bottom:4px;" class="truncate">' + escapeHtml(item.storedPath) + '</div>' +
|
|
6655
|
+
'<div style="display:flex;gap:var(--space-sm);"><span class="tag">' + escapeHtml(item.parser || "unknown") + '</span><span class="tag">' + escapeHtml(item.characters) + ' \u5B57\u7B26</span></div></div>';
|
|
6157
6656
|
}
|
|
6657
|
+
html += '</div>';
|
|
6658
|
+
el.innerHTML = html;
|
|
6659
|
+
}
|
|
6158
6660
|
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6661
|
+
function renderTasksView() {
|
|
6662
|
+
var activeTab = document.querySelector(".tab.active");
|
|
6663
|
+
var tab = activeTab ? activeTab.dataset.tab : "file-jobs";
|
|
6664
|
+
if (tab === "file-jobs") renderFileJobs();
|
|
6665
|
+
else renderCronJobs();
|
|
6666
|
+
}
|
|
6667
|
+
|
|
6668
|
+
function renderFileJobs() {
|
|
6669
|
+
var el = document.getElementById("file-jobs-list");
|
|
6670
|
+
if (!allFileJobs || allFileJobs.length === 0) {
|
|
6671
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u6587\u4EF6\u89E3\u6790\u4EFB\u52A1\u3002</div>';
|
|
6672
|
+
return;
|
|
6673
|
+
}
|
|
6674
|
+
var html = '<table class="data-table"><thead><tr><th>\u6587\u4EF6</th><th>\u72B6\u6001</th><th>\u4FE1\u606F</th></tr></thead><tbody>';
|
|
6675
|
+
for (var i = 0; i < allFileJobs.length; i++) {
|
|
6676
|
+
var item = allFileJobs[i];
|
|
6677
|
+
var tagClass = item.status === 'indexed' ? 'tag-success' : item.status === 'failed' ? 'tag-error' : 'tag-warning';
|
|
6678
|
+
html += '<tr><td><div style="font-weight:500;">' + escapeHtml(item.fileName) + '</div><div style="font-size:12px;color:var(--text-muted);" class="truncate">' + escapeHtml(item.storedPath || item.id) + '</div></td>' +
|
|
6679
|
+
'<td><span class="tag ' + tagClass + '">' + escapeHtml(item.status) + '</span></td>' +
|
|
6680
|
+
'<td style="font-size:13px;color:var(--text-muted);">' + escapeHtml(item.error || "") + '</td></tr>';
|
|
6162
6681
|
}
|
|
6682
|
+
html += '</tbody></table>';
|
|
6683
|
+
el.innerHTML = html;
|
|
6684
|
+
}
|
|
6163
6685
|
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6686
|
+
function renderCronJobs() {
|
|
6687
|
+
var el = document.getElementById("cron-jobs-list");
|
|
6688
|
+
if (!allCronJobs || allCronJobs.length === 0) {
|
|
6689
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u5B9A\u65F6\u4EFB\u52A1\u3002</div>';
|
|
6690
|
+
return;
|
|
6691
|
+
}
|
|
6692
|
+
var html = '<table class="data-table"><thead><tr><th>\u4EFB\u52A1</th><th>\u72B6\u6001</th><th>\u64CD\u4F5C</th></tr></thead><tbody>';
|
|
6693
|
+
for (var i = 0; i < allCronJobs.length; i++) {
|
|
6694
|
+
var item = allCronJobs[i];
|
|
6695
|
+
var tagClass = item.status === 'active' ? 'tag-success' : 'tag-warning';
|
|
6696
|
+
html += '<tr><td><div style="font-weight:500;">' + escapeHtml(item.schedule) + '</div>' +
|
|
6697
|
+
'<div style="font-size:13px;color:var(--text-muted);" class="truncate-2">' + escapeHtml(item.prompt) + '</div>' +
|
|
6698
|
+
'<div style="font-size:12px;color:var(--text-muted);">\u4E0B\u6B21: ' + escapeHtml(formatDateTime(item.nextRunAt)) + '</div>' +
|
|
6699
|
+
(item.lastError ? '<div style="font-size:12px;color:var(--danger);margin-top:4px;">' + escapeHtml(item.lastError) + '</div>' : '') +
|
|
6700
|
+
'</td><td><span class="tag ' + tagClass + '">' + escapeHtml(item.status) + '</span></td><td>' +
|
|
6701
|
+
(item.status === "active" ? '<button class="btn btn-sm btn-danger" data-delete-cron-job="' + escapeHtml(item.id) + '">\u5220\u9664</button>' : '-') +
|
|
6702
|
+
'</td></tr>';
|
|
6170
6703
|
}
|
|
6704
|
+
html += '</tbody></table>';
|
|
6705
|
+
el.innerHTML = html;
|
|
6706
|
+
}
|
|
6171
6707
|
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6708
|
+
function renderQaLogsView() {
|
|
6709
|
+
var el = document.getElementById("qa-logs-list");
|
|
6710
|
+
if (!allQaLogs || allQaLogs.length === 0) {
|
|
6711
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u95EE\u7B54\u65E5\u5FD7\u3002</div>';
|
|
6712
|
+
return;
|
|
6713
|
+
}
|
|
6714
|
+
var html = '';
|
|
6715
|
+
for (var i = 0; i < allQaLogs.length; i++) {
|
|
6716
|
+
var item = allQaLogs[i];
|
|
6717
|
+
var citationCount = Array.isArray(item.citations) ? item.citations.length : 0;
|
|
6718
|
+
var statusClass = item.status === 'success' ? 'tag-success' : 'tag-warning';
|
|
6719
|
+
html += '<div class="qa-card"><div class="message-meta" style="margin-bottom:var(--space-sm);">' +
|
|
6720
|
+
'<span>' + escapeHtml(formatDateTime(item.createdAt)) + '</span>' +
|
|
6721
|
+
'<span class="tag ' + statusClass + '">' + escapeHtml(item.status) + '</span>' +
|
|
6722
|
+
'<span>' + citationCount + ' \u6761\u5F15\u7528</span></div>' +
|
|
6723
|
+
'<div class="qa-question">' + escapeHtml(item.question) + '</div>' +
|
|
6724
|
+
'<div class="qa-answer">' + escapeHtml(item.answer) + '</div></div>';
|
|
6183
6725
|
}
|
|
6726
|
+
el.innerHTML = html;
|
|
6727
|
+
}
|
|
6184
6728
|
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
return;
|
|
6197
|
-
}
|
|
6729
|
+
function renderSettings(status) {
|
|
6730
|
+
var el = document.getElementById("settings-config");
|
|
6731
|
+
var html = '<h3 style="font-size:16px;font-weight:600;margin-bottom:var(--space-md);">\u7CFB\u7EDF\u914D\u7F6E</h3>';
|
|
6732
|
+
html += '<div style="display:flex;flex-direction:column;">';
|
|
6733
|
+
html += '<div class="settings-item"><div><div class="settings-label">Web UI</div><div class="settings-desc">' + escapeHtml((status.web && status.web.host ? status.web.host : "127.0.0.1") + ":" + (status.web && status.web.port ? status.web.port : "3878")) + '</div></div></div>';
|
|
6734
|
+
html += '<div class="settings-item"><div><div class="settings-label">Gateway</div><div class="settings-desc">' + (status.gateway.configured ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E") + '</div></div></div>';
|
|
6735
|
+
html += '<div class="settings-item"><div><div class="settings-label">RAG \u6A21\u5F0F</div><div class="settings-desc">\u5F3A\u5236\u5148\u68C0\u7D22\u8BC1\u636E\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0</div></div></div>';
|
|
6736
|
+
html += '<div class="settings-item"><div><div class="settings-label">\u6570\u636E\u76EE\u5F55</div><div class="settings-desc">SQLite + \u672C\u5730\u6587\u4EF6</div></div></div>';
|
|
6737
|
+
html += '</div>';
|
|
6738
|
+
el.innerHTML = html;
|
|
6739
|
+
}
|
|
6198
6740
|
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
}
|
|
6204
|
-
await load();
|
|
6205
|
-
} catch (error) {
|
|
6206
|
-
actionStatus.textContent = error instanceof Error ? error.message : String(error);
|
|
6207
|
-
} finally {
|
|
6208
|
-
processMessages.disabled = false;
|
|
6209
|
-
}
|
|
6210
|
-
}
|
|
6741
|
+
async function loadSection(path, setter) {
|
|
6742
|
+
try { setter(await fetchJson(path)); }
|
|
6743
|
+
catch (error) { console.error("\u52A0\u8F7D\u5931\u8D25:", path, error); }
|
|
6744
|
+
}
|
|
6211
6745
|
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
actionStatus.textContent = "\u6B63\u5728\u5220\u9664\u5B9A\u65F6\u4EFB\u52A1...";
|
|
6219
|
-
try {
|
|
6220
|
-
const response = await fetch(\`/api/cron-jobs/\${encodeURIComponent(id)}\`, {
|
|
6221
|
-
method: "DELETE",
|
|
6222
|
-
headers: { "x-chattercatcher-web-token": webActionToken },
|
|
6223
|
-
});
|
|
6224
|
-
const result = await response.json();
|
|
6225
|
-
actionStatus.textContent = result.ok ? "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u5220\u9664\u3002" : result.message || "\u5220\u9664\u5931\u8D25\u3002";
|
|
6226
|
-
await load();
|
|
6227
|
-
} catch (error) {
|
|
6228
|
-
actionStatus.textContent = error instanceof Error ? error.message : String(error);
|
|
6229
|
-
} finally {
|
|
6230
|
-
target.removeAttribute("disabled");
|
|
6231
|
-
}
|
|
6746
|
+
async function load() {
|
|
6747
|
+
await loadSection("/api/status", function(data) {
|
|
6748
|
+
statusData = data;
|
|
6749
|
+
renderMetrics(data);
|
|
6750
|
+
renderSystemStatus(data);
|
|
6751
|
+
renderSettings(data);
|
|
6232
6752
|
});
|
|
6753
|
+
await loadSection("/api/messages/recent?limit=50", function(data) { allMessages = data.items || []; renderRecentMessages(allMessages); });
|
|
6754
|
+
await loadSection("/api/episodes?limit=20", function(data) { allEpisodes = data.items || []; renderRecentEpisodes(allEpisodes); });
|
|
6755
|
+
await loadSection("/api/files", function(data) { allFiles = data.items || []; });
|
|
6756
|
+
await loadSection("/api/file-jobs", function(data) { allFileJobs = data.items || []; });
|
|
6757
|
+
await loadSection("/api/qa-logs?limit=20", function(data) { allQaLogs = data.items || []; });
|
|
6758
|
+
await loadSection("/api/cron-jobs", function(data) { allCronJobs = data.items || []; });
|
|
6759
|
+
if (currentView === "messages") renderMessagesView();
|
|
6760
|
+
if (currentView === "episodes") renderEpisodesView();
|
|
6761
|
+
if (currentView === "files") renderFilesView();
|
|
6762
|
+
if (currentView === "tasks") renderTasksView();
|
|
6763
|
+
if (currentView === "qa-logs") renderQaLogsView();
|
|
6764
|
+
}
|
|
6233
6765
|
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6766
|
+
async function processNow() {
|
|
6767
|
+
var btn = document.getElementById("btn-process-messages");
|
|
6768
|
+
if (btn) { btn.disabled = true; }
|
|
6769
|
+
showToast("\u6B63\u5728\u5904\u7406\u6D88\u606F\u7D22\u5F15...", "info");
|
|
6770
|
+
try {
|
|
6771
|
+
var result = await postJson("/api/process/messages");
|
|
6772
|
+
if (result.status === "skipped") { showToast(result.reason, "warning"); }
|
|
6773
|
+
else { showToast("\u5904\u7406\u5B8C\u6210\uFF1Achunks=" + result.chunks + ", vectors=" + result.vectors, "success"); }
|
|
6774
|
+
await load();
|
|
6775
|
+
} catch (error) {
|
|
6776
|
+
showToast(error instanceof Error ? error.message : String(error), "error");
|
|
6777
|
+
} finally {
|
|
6778
|
+
if (btn) { btn.disabled = false; }
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
|
|
6782
|
+
document.addEventListener("click", async function(event) {
|
|
6783
|
+
var target = event.target;
|
|
6784
|
+
if (!(target instanceof HTMLElement)) return;
|
|
6785
|
+
var id = target.dataset.deleteCronJob;
|
|
6786
|
+
if (!id) return;
|
|
6787
|
+
target.disabled = true;
|
|
6788
|
+
showToast("\u6B63\u5728\u5220\u9664\u5B9A\u65F6\u4EFB\u52A1...", "info");
|
|
6789
|
+
try {
|
|
6790
|
+
var result = await deleteJson("/api/cron-jobs/" + encodeURIComponent(id));
|
|
6791
|
+
showToast(result.ok ? "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u5220\u9664" : (result.message || "\u5220\u9664\u5931\u8D25"), result.ok ? "success" : "error");
|
|
6792
|
+
await load();
|
|
6793
|
+
} catch (error) {
|
|
6794
|
+
showToast(error instanceof Error ? error.message : String(error), "error");
|
|
6795
|
+
}
|
|
6796
|
+
});
|
|
6797
|
+
|
|
6798
|
+
void load();
|
|
6799
|
+
setInterval(function() { if (document.visibilityState === "visible") void load(); }, 5000);
|
|
6800
|
+
</script>
|
|
6801
|
+
</body>
|
|
6243
6802
|
</html>`;
|
|
6244
6803
|
}
|
|
6245
6804
|
function parseLimit(value, fallback, max) {
|
|
@@ -6249,12 +6808,22 @@ function parseLimit(value, fallback, max) {
|
|
|
6249
6808
|
function getWebActionToken(secrets) {
|
|
6250
6809
|
return secrets.web.actionToken;
|
|
6251
6810
|
}
|
|
6252
|
-
function
|
|
6253
|
-
return
|
|
6811
|
+
function getWebActionCookie(token) {
|
|
6812
|
+
return `chattercatcher_web_token=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Strict`;
|
|
6813
|
+
}
|
|
6814
|
+
function parseCookies(header) {
|
|
6815
|
+
const value = Array.isArray(header) ? header.join("; ") : header;
|
|
6816
|
+
if (!value) return {};
|
|
6817
|
+
const cookies = {};
|
|
6818
|
+
for (const part of value.split(";")) {
|
|
6819
|
+
const [rawName, ...rawValue] = part.trim().split("=");
|
|
6820
|
+
if (!rawName || rawValue.length === 0) continue;
|
|
6821
|
+
cookies[rawName] = decodeURIComponent(rawValue.join("="));
|
|
6822
|
+
}
|
|
6823
|
+
return cookies;
|
|
6254
6824
|
}
|
|
6255
6825
|
function isAuthorizedWebAction(request, token) {
|
|
6256
|
-
|
|
6257
|
-
return provided === token;
|
|
6826
|
+
return parseCookies(request.headers.cookie).chattercatcher_web_token === token;
|
|
6258
6827
|
}
|
|
6259
6828
|
function createWebApp(config, options = {}) {
|
|
6260
6829
|
const app = Fastify({ logger: false });
|
|
@@ -6382,7 +6951,8 @@ function createWebApp(config, options = {}) {
|
|
|
6382
6951
|
app.get("/", async (_request, reply) => {
|
|
6383
6952
|
await tokenReady;
|
|
6384
6953
|
reply.type("text/html; charset=utf-8");
|
|
6385
|
-
|
|
6954
|
+
reply.header("set-cookie", getWebActionCookie(webActionToken));
|
|
6955
|
+
return buildHtml();
|
|
6386
6956
|
});
|
|
6387
6957
|
return app;
|
|
6388
6958
|
}
|