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/index.js
CHANGED
|
@@ -4294,6 +4294,166 @@ var FeishuQuestionHandler = class {
|
|
|
4294
4294
|
// src/feishu/sender.ts
|
|
4295
4295
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
4296
4296
|
import fs9 from "fs/promises";
|
|
4297
|
+
|
|
4298
|
+
// src/feishu/markdown-post.ts
|
|
4299
|
+
function escapeAtText(value) {
|
|
4300
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4301
|
+
}
|
|
4302
|
+
function formatTextWithMentions(text, options) {
|
|
4303
|
+
const mentions = options?.mentions ?? [];
|
|
4304
|
+
if (mentions.length === 0) return text;
|
|
4305
|
+
const prefix = mentions.map((mention) => `<at user_id="${escapeAtText(mention.openId)}">${escapeAtText(mention.name)}</at>`).join(" ");
|
|
4306
|
+
return `${prefix} ${text}`.trim();
|
|
4307
|
+
}
|
|
4308
|
+
function findMarkdownLinkEnd(text, start) {
|
|
4309
|
+
let depth = 0;
|
|
4310
|
+
for (let index = start; index < text.length; index += 1) {
|
|
4311
|
+
const char = text[index];
|
|
4312
|
+
if (char === "(") {
|
|
4313
|
+
depth += 1;
|
|
4314
|
+
} else if (char === ")") {
|
|
4315
|
+
if (depth === 0) return index;
|
|
4316
|
+
depth -= 1;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
return -1;
|
|
4320
|
+
}
|
|
4321
|
+
function parseInline(text) {
|
|
4322
|
+
const elements = [];
|
|
4323
|
+
let index = 0;
|
|
4324
|
+
while (index < text.length) {
|
|
4325
|
+
const linkStart = text.indexOf("[", index);
|
|
4326
|
+
const boldStarStart = text.indexOf("**", index);
|
|
4327
|
+
const boldUnderscoreStart = text.indexOf("__", index);
|
|
4328
|
+
const candidates = [linkStart, boldStarStart, boldUnderscoreStart].filter((value) => value >= 0);
|
|
4329
|
+
const next = candidates.length ? Math.min(...candidates) : -1;
|
|
4330
|
+
if (next < 0) {
|
|
4331
|
+
elements.push({ tag: "text", text: text.slice(index) });
|
|
4332
|
+
break;
|
|
4333
|
+
}
|
|
4334
|
+
if (next > index) {
|
|
4335
|
+
elements.push({ tag: "text", text: text.slice(index, next) });
|
|
4336
|
+
}
|
|
4337
|
+
if (next === linkStart) {
|
|
4338
|
+
const labelEnd = text.indexOf("](", next);
|
|
4339
|
+
if (labelEnd > next) {
|
|
4340
|
+
const hrefStart = labelEnd + 2;
|
|
4341
|
+
const hrefEnd = findMarkdownLinkEnd(text, hrefStart);
|
|
4342
|
+
const href = hrefEnd >= 0 ? text.slice(hrefStart, hrefEnd) : "";
|
|
4343
|
+
if (hrefEnd >= 0 && /^https?:\/\/\S+$/.test(href)) {
|
|
4344
|
+
elements.push({ tag: "a", text: text.slice(next + 1, labelEnd), href });
|
|
4345
|
+
index = hrefEnd + 1;
|
|
4346
|
+
continue;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
elements.push({ tag: "text", text: text[next] });
|
|
4350
|
+
index = next + 1;
|
|
4351
|
+
continue;
|
|
4352
|
+
}
|
|
4353
|
+
const marker = next === boldStarStart ? "**" : "__";
|
|
4354
|
+
const close = text.indexOf(marker, next + marker.length);
|
|
4355
|
+
if (close > next + marker.length) {
|
|
4356
|
+
elements.push({ tag: "text", text: text.slice(next + marker.length, close), style: ["bold"] });
|
|
4357
|
+
index = close + marker.length;
|
|
4358
|
+
continue;
|
|
4359
|
+
}
|
|
4360
|
+
elements.push({ tag: "text", text: marker });
|
|
4361
|
+
index = next + marker.length;
|
|
4362
|
+
}
|
|
4363
|
+
const compacted = elements.filter((element) => element.tag !== "text" || element.text.length > 0);
|
|
4364
|
+
return compacted.length ? compacted : [{ tag: "text", text: " " }];
|
|
4365
|
+
}
|
|
4366
|
+
function pushParagraph(content, lines) {
|
|
4367
|
+
if (lines.length === 0) return;
|
|
4368
|
+
content.push(parseInline(lines.join("\n")));
|
|
4369
|
+
lines.length = 0;
|
|
4370
|
+
}
|
|
4371
|
+
function parseMarkdownBlocks(markdown) {
|
|
4372
|
+
if (!markdown.trim()) {
|
|
4373
|
+
return [[{ tag: "text", text: " " }]];
|
|
4374
|
+
}
|
|
4375
|
+
const content = [];
|
|
4376
|
+
const paragraph = [];
|
|
4377
|
+
const code = [];
|
|
4378
|
+
let inCodeBlock = false;
|
|
4379
|
+
for (const rawLine of markdown.replace(/\r\n/g, "\n").split("\n")) {
|
|
4380
|
+
const line = rawLine.trimEnd();
|
|
4381
|
+
if (line.startsWith("```")) {
|
|
4382
|
+
if (inCodeBlock) {
|
|
4383
|
+
content.push([{ tag: "text", text: `\`\`\`
|
|
4384
|
+
${code.join("\n")}
|
|
4385
|
+
\`\`\`` }]);
|
|
4386
|
+
code.length = 0;
|
|
4387
|
+
inCodeBlock = false;
|
|
4388
|
+
} else {
|
|
4389
|
+
pushParagraph(content, paragraph);
|
|
4390
|
+
inCodeBlock = true;
|
|
4391
|
+
}
|
|
4392
|
+
continue;
|
|
4393
|
+
}
|
|
4394
|
+
if (inCodeBlock) {
|
|
4395
|
+
code.push(rawLine);
|
|
4396
|
+
continue;
|
|
4397
|
+
}
|
|
4398
|
+
if (!line.trim()) {
|
|
4399
|
+
pushParagraph(content, paragraph);
|
|
4400
|
+
continue;
|
|
4401
|
+
}
|
|
4402
|
+
const heading = line.match(/^#{1,6}\s+(.+)$/);
|
|
4403
|
+
if (heading) {
|
|
4404
|
+
pushParagraph(content, paragraph);
|
|
4405
|
+
content.push([{ tag: "text", text: heading[1], style: ["bold"] }]);
|
|
4406
|
+
continue;
|
|
4407
|
+
}
|
|
4408
|
+
const unordered = line.match(/^[-*]\s+(.+)$/);
|
|
4409
|
+
if (unordered) {
|
|
4410
|
+
pushParagraph(content, paragraph);
|
|
4411
|
+
content.push(parseInline(`\u2022 ${unordered[1]}`));
|
|
4412
|
+
continue;
|
|
4413
|
+
}
|
|
4414
|
+
const ordered = line.match(/^(\d+)\.\s+(.+)$/);
|
|
4415
|
+
if (ordered) {
|
|
4416
|
+
pushParagraph(content, paragraph);
|
|
4417
|
+
content.push(parseInline(`${ordered[1]}. ${ordered[2]}`));
|
|
4418
|
+
continue;
|
|
4419
|
+
}
|
|
4420
|
+
paragraph.push(line);
|
|
4421
|
+
}
|
|
4422
|
+
if (inCodeBlock) {
|
|
4423
|
+
content.push([{ tag: "text", text: `\`\`\`
|
|
4424
|
+
${code.join("\n")}` }]);
|
|
4425
|
+
}
|
|
4426
|
+
pushParagraph(content, paragraph);
|
|
4427
|
+
return content.length ? content : [[{ tag: "text", text: markdown }]];
|
|
4428
|
+
}
|
|
4429
|
+
function buildFeishuPostContent(markdown, options) {
|
|
4430
|
+
const content = parseMarkdownBlocks(markdown);
|
|
4431
|
+
const mentions = options?.mentions ?? [];
|
|
4432
|
+
if (mentions.length) {
|
|
4433
|
+
const mentionElements = mentions.map((mention) => ({
|
|
4434
|
+
tag: "at",
|
|
4435
|
+
user_id: mention.openId,
|
|
4436
|
+
user_name: mention.name
|
|
4437
|
+
}));
|
|
4438
|
+
const firstLine = content[0] ?? [];
|
|
4439
|
+
const firstText = firstLine[0];
|
|
4440
|
+
if (firstText?.tag === "text") {
|
|
4441
|
+
content[0] = [...mentionElements, { ...firstText, text: ` ${firstText.text}` }, ...firstLine.slice(1)];
|
|
4442
|
+
} else {
|
|
4443
|
+
content[0] = [...mentionElements, { tag: "text", text: " " }, ...firstLine];
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
return {
|
|
4447
|
+
post: {
|
|
4448
|
+
zh_cn: {
|
|
4449
|
+
title: "",
|
|
4450
|
+
content
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
};
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
// src/feishu/sender.ts
|
|
4297
4457
|
function mapDomain(domain) {
|
|
4298
4458
|
return domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
4299
4459
|
}
|
|
@@ -4309,14 +4469,33 @@ function extractImageKey(response) {
|
|
|
4309
4469
|
}
|
|
4310
4470
|
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
4311
4471
|
}
|
|
4312
|
-
function
|
|
4313
|
-
|
|
4472
|
+
function collectErrorFields(error) {
|
|
4473
|
+
const fields = [error];
|
|
4474
|
+
const value = error && typeof error === "object" ? error : {};
|
|
4475
|
+
fields.push(value.code, value.errorCode, value.msg, value.message);
|
|
4476
|
+
const response = value.response && typeof value.response === "object" ? value.response : {};
|
|
4477
|
+
const data = response.data && typeof response.data === "object" ? response.data : {};
|
|
4478
|
+
fields.push(data.code, data.errorCode, data.msg, data.message);
|
|
4479
|
+
return fields;
|
|
4480
|
+
}
|
|
4481
|
+
function isRichTextCompatibilityError(error) {
|
|
4482
|
+
return collectErrorFields(error).some((field) => {
|
|
4483
|
+
if (field === 230001) return true;
|
|
4484
|
+
if (typeof field === "string") {
|
|
4485
|
+
return /post|msg_type|content|unsupported|invalid/i.test(field);
|
|
4486
|
+
}
|
|
4487
|
+
return false;
|
|
4488
|
+
});
|
|
4314
4489
|
}
|
|
4315
|
-
function
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4490
|
+
async function sendWithTextFallback(input) {
|
|
4491
|
+
try {
|
|
4492
|
+
await input.sendPost();
|
|
4493
|
+
} catch (error) {
|
|
4494
|
+
if (!isRichTextCompatibilityError(error)) {
|
|
4495
|
+
throw error;
|
|
4496
|
+
}
|
|
4497
|
+
await input.sendText();
|
|
4498
|
+
}
|
|
4320
4499
|
}
|
|
4321
4500
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
4322
4501
|
constructor(client) {
|
|
@@ -4332,7 +4511,17 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4332
4511
|
return new _FeishuMessageSender(client);
|
|
4333
4512
|
}
|
|
4334
4513
|
async sendTextToChat(chatId, text, options) {
|
|
4335
|
-
const
|
|
4514
|
+
const postPayload = {
|
|
4515
|
+
data: {
|
|
4516
|
+
receive_id: chatId,
|
|
4517
|
+
msg_type: "post",
|
|
4518
|
+
content: JSON.stringify(buildFeishuPostContent(text, options))
|
|
4519
|
+
},
|
|
4520
|
+
params: {
|
|
4521
|
+
receive_id_type: "chat_id"
|
|
4522
|
+
}
|
|
4523
|
+
};
|
|
4524
|
+
const textPayload = {
|
|
4336
4525
|
data: {
|
|
4337
4526
|
receive_id: chatId,
|
|
4338
4527
|
msg_type: "text",
|
|
@@ -4343,16 +4532,20 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4343
4532
|
}
|
|
4344
4533
|
};
|
|
4345
4534
|
if (this.client.im.v1?.message.create) {
|
|
4346
|
-
await
|
|
4535
|
+
await sendWithTextFallback({
|
|
4536
|
+
sendPost: () => this.client.im.v1.message.create(postPayload),
|
|
4537
|
+
sendText: () => this.client.im.v1.message.create(textPayload)
|
|
4538
|
+
});
|
|
4347
4539
|
return;
|
|
4348
4540
|
}
|
|
4349
4541
|
if (this.client.im.message?.create) {
|
|
4350
|
-
await
|
|
4542
|
+
await sendWithTextFallback({
|
|
4543
|
+
sendPost: () => this.client.im.message.create(postPayload),
|
|
4544
|
+
sendText: () => this.client.im.message.create(textPayload)
|
|
4545
|
+
});
|
|
4351
4546
|
return;
|
|
4352
4547
|
}
|
|
4353
|
-
|
|
4354
|
-
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4355
|
-
}
|
|
4548
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4356
4549
|
}
|
|
4357
4550
|
async sendImageToChat(chatId, imagePath) {
|
|
4358
4551
|
const imageCreate = this.client.im.v1?.image?.create;
|
|
@@ -4388,7 +4581,16 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4388
4581
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4389
4582
|
}
|
|
4390
4583
|
async replyTextToMessage(messageId, text) {
|
|
4391
|
-
const
|
|
4584
|
+
const postPayload = {
|
|
4585
|
+
path: {
|
|
4586
|
+
message_id: messageId
|
|
4587
|
+
},
|
|
4588
|
+
data: {
|
|
4589
|
+
msg_type: "post",
|
|
4590
|
+
content: JSON.stringify(buildFeishuPostContent(text))
|
|
4591
|
+
}
|
|
4592
|
+
};
|
|
4593
|
+
const textPayload = {
|
|
4392
4594
|
path: {
|
|
4393
4595
|
message_id: messageId
|
|
4394
4596
|
},
|
|
@@ -4398,11 +4600,17 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4398
4600
|
}
|
|
4399
4601
|
};
|
|
4400
4602
|
if (this.client.im.v1?.message.reply) {
|
|
4401
|
-
await
|
|
4603
|
+
await sendWithTextFallback({
|
|
4604
|
+
sendPost: () => this.client.im.v1.message.reply(postPayload),
|
|
4605
|
+
sendText: () => this.client.im.v1.message.reply(textPayload)
|
|
4606
|
+
});
|
|
4402
4607
|
return;
|
|
4403
4608
|
}
|
|
4404
4609
|
if (this.client.im.message?.reply) {
|
|
4405
|
-
await
|
|
4610
|
+
await sendWithTextFallback({
|
|
4611
|
+
sendPost: () => this.client.im.message.reply(postPayload),
|
|
4612
|
+
sendText: () => this.client.im.message.reply(textPayload)
|
|
4613
|
+
});
|
|
4406
4614
|
return;
|
|
4407
4615
|
}
|
|
4408
4616
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u56DE\u590D\u63A5\u53E3\u3002");
|
|
@@ -5304,516 +5512,867 @@ import Fastify from "fastify";
|
|
|
5304
5512
|
function buildHtml() {
|
|
5305
5513
|
return `<!doctype html>
|
|
5306
5514
|
<html lang="zh-CN">
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
.
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
.
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
.
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5515
|
+
<head>
|
|
5516
|
+
<meta charset="utf-8" />
|
|
5517
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5518
|
+
<meta name="color-scheme" content="dark" />
|
|
5519
|
+
<title>ChatterCatcher</title>
|
|
5520
|
+
<style>
|
|
5521
|
+
:root {
|
|
5522
|
+
--bg-primary: #0a0a0f;
|
|
5523
|
+
--bg-secondary: #12121a;
|
|
5524
|
+
--bg-tertiary: #1a1a28;
|
|
5525
|
+
--glass-bg: rgba(255,255,255,0.05);
|
|
5526
|
+
--glass-border: rgba(255,255,255,0.1);
|
|
5527
|
+
--glass-border-hover: rgba(255,255,255,0.2);
|
|
5528
|
+
--glass-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
5529
|
+
--text-primary: #f0f0f5;
|
|
5530
|
+
--text-secondary: #a0a0b0;
|
|
5531
|
+
--text-muted: #6e6e80;
|
|
5532
|
+
--accent: #64d2ff;
|
|
5533
|
+
--accent-hover: #7dd8ff;
|
|
5534
|
+
--success: #30d158;
|
|
5535
|
+
--warning: #ff9f0a;
|
|
5536
|
+
--danger: #ff453a;
|
|
5537
|
+
--radius-sm: 8px;
|
|
5538
|
+
--radius-md: 12px;
|
|
5539
|
+
--radius-lg: 16px;
|
|
5540
|
+
--radius-xl: 24px;
|
|
5541
|
+
--space-xs: 4px;
|
|
5542
|
+
--space-sm: 8px;
|
|
5543
|
+
--space-md: 16px;
|
|
5544
|
+
--space-lg: 24px;
|
|
5545
|
+
--space-xl: 32px;
|
|
5546
|
+
--space-2xl: 48px;
|
|
5547
|
+
--font-sans: -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
|
|
5548
|
+
--font-mono: "SF Mono","Menlo","Consolas",monospace;
|
|
5549
|
+
}
|
|
5550
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5551
|
+
body {
|
|
5552
|
+
font-family: var(--font-sans);
|
|
5553
|
+
background: var(--bg-primary);
|
|
5554
|
+
color: var(--text-primary);
|
|
5555
|
+
line-height: 1.6;
|
|
5556
|
+
-webkit-font-smoothing: antialiased;
|
|
5557
|
+
overflow-x: hidden;
|
|
5558
|
+
min-height: 100vh;
|
|
5559
|
+
}
|
|
5560
|
+
.glass {
|
|
5561
|
+
background: var(--glass-bg);
|
|
5562
|
+
backdrop-filter: blur(20px) saturate(180%);
|
|
5563
|
+
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
|
5564
|
+
border: 1px solid var(--glass-border);
|
|
5565
|
+
border-radius: var(--radius-lg);
|
|
5566
|
+
box-shadow: var(--glass-shadow);
|
|
5567
|
+
transition: all 0.3s ease;
|
|
5568
|
+
}
|
|
5569
|
+
.glass:hover { border-color: var(--glass-border-hover); box-shadow: 0 12px 40px rgba(0,0,0,0.4); }
|
|
5570
|
+
.gradient-bg {
|
|
5571
|
+
background: linear-gradient(135deg,#0a0a0f 0%,#12121a 50%,#1a1a28 100%);
|
|
5572
|
+
min-height: 100vh;
|
|
5573
|
+
}
|
|
5574
|
+
.sidebar {
|
|
5575
|
+
position: fixed; left: 0; top: 0; width: 260px; height: 100vh;
|
|
5576
|
+
padding: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-md); z-index: 100;
|
|
5577
|
+
background: linear-gradient(180deg,rgba(255,255,255,0.08) 0%,rgba(255,255,255,0.02) 100%);
|
|
5578
|
+
backdrop-filter: blur(40px) saturate(200%);
|
|
5579
|
+
-webkit-backdrop-filter: blur(40px) saturate(200%);
|
|
5580
|
+
border-right: 1px solid var(--glass-border);
|
|
5581
|
+
}
|
|
5582
|
+
.sidebar-logo {
|
|
5583
|
+
display: flex; align-items: center; gap: var(--space-sm);
|
|
5584
|
+
padding: var(--space-md); font-size: 20px; font-weight: 700;
|
|
5585
|
+
color: var(--text-primary); margin-bottom: var(--space-md);
|
|
5586
|
+
}
|
|
5587
|
+
.logo-icon {
|
|
5588
|
+
width: 36px; height: 36px;
|
|
5589
|
+
background: linear-gradient(135deg,var(--accent),#5e60ce);
|
|
5590
|
+
border-radius: var(--radius-md);
|
|
5591
|
+
display: flex; align-items: center; justify-content: center;
|
|
5592
|
+
box-shadow: 0 4px 16px rgba(100,210,255,0.3);
|
|
5593
|
+
}
|
|
5594
|
+
.sidebar-nav { display: flex; flex-direction: column; gap: var(--space-xs); }
|
|
5595
|
+
.nav-item {
|
|
5596
|
+
display: flex; align-items: center; gap: var(--space-sm);
|
|
5597
|
+
padding: var(--space-sm) var(--space-md); border-radius: var(--radius-md);
|
|
5598
|
+
color: var(--text-secondary); text-decoration: none; cursor: pointer;
|
|
5599
|
+
transition: all 0.2s ease; border: none; background: none;
|
|
5600
|
+
font-size: 14px; font-family: inherit; width: 100%; text-align: left;
|
|
5601
|
+
}
|
|
5602
|
+
.nav-item:hover { background: rgba(255,255,255,0.06); color: var(--text-primary); }
|
|
5603
|
+
.nav-item.active {
|
|
5604
|
+
background: rgba(100,210,255,0.15); color: var(--accent);
|
|
5605
|
+
box-shadow: 0 0 20px rgba(100,210,255,0.1);
|
|
5606
|
+
}
|
|
5607
|
+
.nav-icon { width: 20px; height: 20px; flex-shrink: 0; }
|
|
5608
|
+
.main-content { margin-left: 260px; min-height: 100vh; padding: var(--space-xl); }
|
|
5609
|
+
.page-header { margin-bottom: var(--space-xl); }
|
|
5610
|
+
.page-title {
|
|
5611
|
+
font-size: 36px; font-weight: 700; letter-spacing: -0.03em;
|
|
5612
|
+
margin-bottom: var(--space-sm);
|
|
5613
|
+
background: linear-gradient(135deg,var(--text-primary),var(--accent));
|
|
5614
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
|
5615
|
+
}
|
|
5616
|
+
.page-subtitle { color: var(--text-secondary); font-size: 15px; }
|
|
5617
|
+
.metrics-grid {
|
|
5618
|
+
display: grid; grid-template-columns: repeat(auto-fit,minmax(200px,1fr));
|
|
5619
|
+
gap: var(--space-md); margin-bottom: var(--space-xl);
|
|
5620
|
+
}
|
|
5621
|
+
.metric-card {
|
|
5622
|
+
padding: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-sm);
|
|
5623
|
+
position: relative; overflow: hidden;
|
|
5624
|
+
}
|
|
5625
|
+
.metric-card::before {
|
|
5626
|
+
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
|
|
5627
|
+
background: linear-gradient(90deg,var(--accent),transparent); opacity: 0.5;
|
|
5628
|
+
}
|
|
5629
|
+
.metric-value { font-size: 40px; font-weight: 700; color: var(--text-primary); line-height: 1; font-variant-numeric: tabular-nums; }
|
|
5630
|
+
.metric-label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }
|
|
5631
|
+
.metric-note { font-size: 13px; color: var(--text-secondary); margin-top: var(--space-xs); }
|
|
5632
|
+
.content-grid { display: grid; grid-template-columns: 2fr 1fr; gap: var(--space-lg); }
|
|
5633
|
+
.content-panel { padding: var(--space-lg); }
|
|
5634
|
+
.panel-header {
|
|
5635
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
5636
|
+
margin-bottom: var(--space-lg); padding-bottom: var(--space-md);
|
|
5637
|
+
border-bottom: 1px solid var(--glass-border);
|
|
5638
|
+
}
|
|
5639
|
+
.panel-title { font-size: 18px; font-weight: 600; }
|
|
5640
|
+
.message-list { display: flex; flex-direction: column; gap: var(--space-sm); }
|
|
5641
|
+
.message-card {
|
|
5642
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
5643
|
+
background: rgba(255,255,255,0.03); border: 1px solid transparent;
|
|
5644
|
+
transition: all 0.25s ease; cursor: pointer;
|
|
5645
|
+
}
|
|
5646
|
+
.message-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); transform: translateX(4px); }
|
|
5647
|
+
.message-meta {
|
|
5648
|
+
display: flex; align-items: center; gap: var(--space-md);
|
|
5649
|
+
color: var(--text-muted); font-size: 12px; margin-bottom: var(--space-xs); flex-wrap: wrap;
|
|
5650
|
+
}
|
|
5651
|
+
.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; }
|
|
5652
|
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
5653
|
+
.status-dot.online { background: var(--success); box-shadow: 0 0 8px var(--success); }
|
|
5654
|
+
.status-dot.offline { background: var(--danger); }
|
|
5655
|
+
.status-dot.warning { background: var(--warning); box-shadow: 0 0 8px var(--warning); }
|
|
5656
|
+
.status-dot.pending { background: var(--text-muted); }
|
|
5657
|
+
.btn {
|
|
5658
|
+
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-sm);
|
|
5659
|
+
padding: 10px var(--space-md); border-radius: var(--radius-md);
|
|
5660
|
+
border: 1px solid var(--glass-border); background: var(--glass-bg);
|
|
5661
|
+
color: var(--text-primary); font-family: inherit; font-size: 14px;
|
|
5662
|
+
cursor: pointer; transition: all 0.2s ease; text-decoration: none;
|
|
5663
|
+
}
|
|
5664
|
+
.btn:hover { background: rgba(255,255,255,0.1); border-color: var(--glass-border-hover); transform: translateY(-1px); }
|
|
5665
|
+
.btn-primary {
|
|
5666
|
+
background: linear-gradient(135deg,var(--accent),#5e60ce); color: white; border: none;
|
|
5667
|
+
font-weight: 600; box-shadow: 0 4px 16px rgba(100,210,255,0.3);
|
|
5668
|
+
}
|
|
5669
|
+
.btn-primary:hover {
|
|
5670
|
+
background: linear-gradient(135deg,var(--accent-hover),#6b6dd8);
|
|
5671
|
+
box-shadow: 0 6px 20px rgba(100,210,255,0.4); transform: translateY(-1px);
|
|
5672
|
+
}
|
|
5673
|
+
.btn-danger { background: rgba(255,69,58,0.15); color: var(--danger); border-color: rgba(255,69,58,0.3); }
|
|
5674
|
+
.btn-danger:hover { background: rgba(255,69,58,0.25); }
|
|
5675
|
+
.btn-sm { padding: 6px var(--space-sm); font-size: 13px; }
|
|
5676
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
5677
|
+
.tag {
|
|
5678
|
+
display: inline-flex; align-items: center; padding: 2px 10px;
|
|
5679
|
+
border-radius: 20px; font-size: 12px; font-weight: 500;
|
|
5680
|
+
background: rgba(255,255,255,0.06); color: var(--text-secondary);
|
|
5681
|
+
}
|
|
5682
|
+
.tag-success { background: rgba(48,209,88,0.15); color: var(--success); }
|
|
5683
|
+
.tag-warning { background: rgba(255,159,10,0.15); color: var(--warning); }
|
|
5684
|
+
.tag-error { background: rgba(255,69,58,0.15); color: var(--danger); }
|
|
5685
|
+
.tag-info { background: rgba(100,210,255,0.15); color: var(--accent); }
|
|
5686
|
+
.empty-state { text-align: center; padding: var(--space-2xl); color: var(--text-muted); }
|
|
5687
|
+
.empty-state svg { width: 48px; height: 48px; margin: 0 auto var(--space-md); opacity: 0.3; }
|
|
5688
|
+
.skeleton {
|
|
5689
|
+
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%);
|
|
5690
|
+
background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: var(--radius-sm);
|
|
5691
|
+
}
|
|
5692
|
+
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
|
|
5693
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
|
5694
|
+
@keyframes slideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
|
|
5695
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
5696
|
+
.view { display: none; animation: fadeIn 0.35s ease; }
|
|
5697
|
+
.view.active { display: block; }
|
|
5698
|
+
.search-box { position: relative; width: 100%; max-width: 400px; }
|
|
5699
|
+
.search-box input {
|
|
5700
|
+
width: 100%; padding: var(--space-sm) var(--space-md) var(--space-sm) 40px;
|
|
5701
|
+
border-radius: var(--radius-md); border: 1px solid var(--glass-border);
|
|
5702
|
+
background: var(--glass-bg); color: var(--text-primary); font-family: inherit;
|
|
5703
|
+
font-size: 14px; outline: none; transition: all 0.2s ease;
|
|
5704
|
+
}
|
|
5705
|
+
.search-box input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(100,210,255,0.1); }
|
|
5706
|
+
.search-box .search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-muted); }
|
|
5707
|
+
.data-table { width: 100%; border-collapse: collapse; }
|
|
5708
|
+
.data-table th {
|
|
5709
|
+
text-align: left; padding: var(--space-sm) var(--space-md); color: var(--text-muted);
|
|
5710
|
+
font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
|
|
5711
|
+
border-bottom: 1px solid var(--glass-border);
|
|
5712
|
+
}
|
|
5713
|
+
.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; }
|
|
5714
|
+
.data-table tr:hover td { background: rgba(255,255,255,0.02); }
|
|
5715
|
+
.data-table tr:last-child td { border-bottom: none; }
|
|
5716
|
+
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
|
5717
|
+
.truncate-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
5718
|
+
.truncate-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
|
5719
|
+
.flex { display: flex; } .flex-col { flex-direction: column; }
|
|
5720
|
+
.items-center { align-items: center; } .justify-between { justify-content: space-between; }
|
|
5721
|
+
.gap-sm { gap: var(--space-sm); } .gap-md { gap: var(--space-md); }
|
|
5722
|
+
.mt-md { margin-top: var(--space-md); } .mt-lg { margin-top: var(--space-lg); }
|
|
5723
|
+
.mb-md { margin-bottom: var(--space-md); }
|
|
5724
|
+
.toast {
|
|
5725
|
+
padding: var(--space-md) var(--space-lg); border-radius: var(--radius-md);
|
|
5726
|
+
background: var(--glass-bg); backdrop-filter: blur(20px); border: 1px solid var(--glass-border);
|
|
5727
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.4); color: var(--text-primary); font-size: 14px;
|
|
5728
|
+
max-width: 400px; animation: slideIn 0.3s ease;
|
|
5729
|
+
display: flex; align-items: center; gap: var(--space-sm);
|
|
5730
|
+
}
|
|
5731
|
+
.toast-success { border-color: rgba(48,209,88,0.3); background: rgba(48,209,88,0.1); }
|
|
5732
|
+
.toast-error { border-color: rgba(255,69,58,0.3); background: rgba(255,69,58,0.1); }
|
|
5733
|
+
.toast-warning { border-color: rgba(255,159,10,0.3); background: rgba(255,159,10,0.1); }
|
|
5734
|
+
.episode-card {
|
|
5735
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
5736
|
+
background: rgba(255,255,255,0.03); border: 1px solid transparent; transition: all 0.25s ease;
|
|
5737
|
+
}
|
|
5738
|
+
.episode-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); }
|
|
5739
|
+
.qa-card {
|
|
5740
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
5741
|
+
background: rgba(255,255,255,0.03); border-left: 3px solid var(--accent); margin-bottom: var(--space-sm);
|
|
5742
|
+
}
|
|
5743
|
+
.qa-question { font-weight: 600; color: var(--text-primary); margin-bottom: var(--space-xs); font-size: 14px; }
|
|
5744
|
+
.qa-answer { color: var(--text-secondary); font-size: 14px; line-height: 1.6; }
|
|
5745
|
+
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-lg); }
|
|
5746
|
+
.section-title { font-size: 24px; font-weight: 700; }
|
|
5747
|
+
.tabs {
|
|
5748
|
+
display: flex; gap: var(--space-xs); padding: 4px;
|
|
5749
|
+
background: rgba(255,255,255,0.03); border-radius: var(--radius-md); border: 1px solid var(--glass-border);
|
|
5750
|
+
}
|
|
5751
|
+
.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; }
|
|
5752
|
+
.tab:hover { color: var(--text-primary); }
|
|
5753
|
+
.tab.active { background: rgba(255,255,255,0.08); color: var(--text-primary); font-weight: 500; }
|
|
5754
|
+
.file-card {
|
|
5755
|
+
padding: var(--space-md); border-radius: var(--radius-md);
|
|
5756
|
+
background: rgba(255,255,255,0.03); border: 1px solid transparent; transition: all 0.25s ease; cursor: pointer;
|
|
5757
|
+
}
|
|
5758
|
+
.file-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); }
|
|
5759
|
+
.file-icon {
|
|
5760
|
+
width: 40px; height: 40px; border-radius: var(--radius-sm);
|
|
5761
|
+
background: linear-gradient(135deg,var(--accent),#5e60ce);
|
|
5762
|
+
display: flex; align-items: center; justify-content: center; margin-bottom: var(--space-sm);
|
|
5763
|
+
}
|
|
5764
|
+
.timeline { position: relative; padding-left: 28px; }
|
|
5765
|
+
.timeline::before { content: ''; position: absolute; left: 8px; top: 0; bottom: 0; width: 2px; background: linear-gradient(180deg,var(--accent),transparent); opacity: 0.3; }
|
|
5766
|
+
.timeline-item { position: relative; padding-bottom: var(--space-lg); }
|
|
5767
|
+
.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); }
|
|
5768
|
+
.timeline-date { font-size: 12px; color: var(--text-muted); margin-bottom: var(--space-xs); }
|
|
5769
|
+
.timeline-content { color: var(--text-secondary); font-size: 14px; }
|
|
5770
|
+
.status-bar { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md); margin-bottom: var(--space-lg); }
|
|
5771
|
+
.status-item { display: flex; align-items: center; gap: var(--space-sm); }
|
|
5772
|
+
.status-label { font-size: 13px; color: var(--text-muted); }
|
|
5773
|
+
.status-value { font-size: 14px; font-weight: 600; color: var(--text-primary); }
|
|
5774
|
+
.grid-2 { display: grid; grid-template-columns: repeat(2,1fr); gap: var(--space-md); }
|
|
5775
|
+
.grid-3 { display: grid; grid-template-columns: repeat(3,1fr); gap: var(--space-md); }
|
|
5776
|
+
.settings-group { padding: var(--space-lg); margin-bottom: var(--space-lg); }
|
|
5777
|
+
.settings-item { display: flex; justify-content: space-between; align-items: center; padding: var(--space-md) 0; border-bottom: 1px solid var(--glass-border); }
|
|
5778
|
+
.settings-item:last-child { border-bottom: none; }
|
|
5779
|
+
.settings-label { font-size: 14px; font-weight: 500; color: var(--text-primary); }
|
|
5780
|
+
.settings-value { font-size: 14px; color: var(--text-secondary); font-family: var(--font-mono); }
|
|
5781
|
+
.settings-desc { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
|
|
5782
|
+
.mobile-nav {
|
|
5783
|
+
display: none; position: fixed; bottom: 0; left: 0; right: 0;
|
|
5784
|
+
padding: var(--space-sm); z-index: 100; flex-direction: row; justify-content: space-around;
|
|
5785
|
+
border-top: 1px solid var(--glass-border);
|
|
5786
|
+
background: linear-gradient(180deg,rgba(255,255,255,0.08) 0%,rgba(255,255,255,0.02) 100%);
|
|
5787
|
+
backdrop-filter: blur(40px) saturate(200%);
|
|
5788
|
+
}
|
|
5789
|
+
.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; }
|
|
5790
|
+
.mobile-nav-item.active { color: var(--accent); }
|
|
5791
|
+
.pulse { animation: pulse 2s cubic-bezier(0.4,0,0.6,1) infinite; }
|
|
5792
|
+
@media (max-width: 1024px) {
|
|
5793
|
+
.sidebar { width: 72px; padding: var(--space-sm); }
|
|
5794
|
+
.sidebar-logo span, .nav-item span { display: none; }
|
|
5795
|
+
.nav-item { justify-content: center; padding: var(--space-sm); }
|
|
5796
|
+
.main-content { margin-left: 72px; padding: var(--space-lg); }
|
|
5797
|
+
.content-grid { grid-template-columns: 1fr; }
|
|
5798
|
+
.metrics-grid { grid-template-columns: repeat(2,1fr); }
|
|
5799
|
+
}
|
|
5800
|
+
@media (max-width: 768px) {
|
|
5801
|
+
.sidebar { display: none; }
|
|
5802
|
+
.mobile-nav { display: flex; }
|
|
5803
|
+
.main-content { margin-left: 0; margin-bottom: 80px; padding: var(--space-md); }
|
|
5804
|
+
.page-title { font-size: 28px; }
|
|
5805
|
+
.metrics-grid { grid-template-columns: repeat(2,1fr); }
|
|
5806
|
+
.grid-2, .grid-3 { grid-template-columns: 1fr; }
|
|
5807
|
+
.section-header { flex-direction: column; align-items: flex-start; gap: var(--space-sm); }
|
|
5808
|
+
}
|
|
5809
|
+
@media (prefers-reduced-motion: reduce) {
|
|
5810
|
+
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
|
|
5811
|
+
}
|
|
5812
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
5813
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
5814
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 4px; }
|
|
5815
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); }
|
|
5816
|
+
.highlight-text { background: rgba(100,210,255,0.15); padding: 0 4px; border-radius: 3px; color: var(--accent); }
|
|
5817
|
+
</style>
|
|
5818
|
+
</head>
|
|
5819
|
+
<body class="gradient-bg">
|
|
5820
|
+
<aside class="sidebar">
|
|
5821
|
+
<div class="sidebar-logo">
|
|
5822
|
+
<div class="logo-icon">
|
|
5823
|
+
<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>
|
|
5824
|
+
</div>
|
|
5825
|
+
<span>ChatterCatcher</span>
|
|
5826
|
+
</div>
|
|
5827
|
+
<nav class="sidebar-nav">
|
|
5828
|
+
<button class="nav-item active" data-view="overview">
|
|
5829
|
+
<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>
|
|
5830
|
+
<span>\u6982\u89C8</span>
|
|
5831
|
+
</button>
|
|
5832
|
+
<button class="nav-item" data-view="messages">
|
|
5833
|
+
<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>
|
|
5834
|
+
<span>\u6D88\u606F</span>
|
|
5835
|
+
</button>
|
|
5836
|
+
<button class="nav-item" data-view="episodes">
|
|
5837
|
+
<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>
|
|
5838
|
+
<span>\u4F1A\u8BDD\u8BB0\u5FC6</span>
|
|
5839
|
+
</button>
|
|
5840
|
+
<button class="nav-item" data-view="files">
|
|
5841
|
+
<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>
|
|
5842
|
+
<span>\u6587\u4EF6\u5E93</span>
|
|
5843
|
+
</button>
|
|
5844
|
+
<button class="nav-item" data-view="tasks">
|
|
5845
|
+
<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>
|
|
5846
|
+
<span>\u4EFB\u52A1</span>
|
|
5847
|
+
</button>
|
|
5848
|
+
<button class="nav-item" data-view="qa-logs">
|
|
5849
|
+
<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>
|
|
5850
|
+
<span>\u95EE\u7B54\u65E5\u5FD7</span>
|
|
5851
|
+
</button>
|
|
5852
|
+
<button class="nav-item" data-view="settings">
|
|
5853
|
+
<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>
|
|
5854
|
+
<span>\u8BBE\u7F6E</span>
|
|
5855
|
+
</button>
|
|
5856
|
+
</nav>
|
|
5857
|
+
<div style="margin-top: auto; padding: var(--space-md);">
|
|
5858
|
+
<div style="display: flex; align-items: center; gap: var(--space-sm); font-size: 12px; color: var(--text-muted);">
|
|
5859
|
+
<span class="status-dot online" id="gateway-indicator"></span>
|
|
5860
|
+
<span id="gateway-status-text">Gateway \u8FD0\u884C\u4E2D</span>
|
|
5861
|
+
</div>
|
|
5862
|
+
<div style="font-size: 11px; color: var(--text-muted); margin-top: var(--space-xs); opacity: 0.7;" id="version-text">v0.0.0</div>
|
|
5863
|
+
</div>
|
|
5864
|
+
</aside>
|
|
5865
|
+
|
|
5866
|
+
<nav class="mobile-nav glass">
|
|
5867
|
+
<button class="mobile-nav-item active" data-view="overview">
|
|
5868
|
+
<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>
|
|
5869
|
+
<span>\u6982\u89C8</span>
|
|
5870
|
+
</button>
|
|
5871
|
+
<button class="mobile-nav-item" data-view="messages">
|
|
5872
|
+
<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>
|
|
5873
|
+
<span>\u6D88\u606F</span>
|
|
5874
|
+
</button>
|
|
5875
|
+
<button class="mobile-nav-item" data-view="files">
|
|
5876
|
+
<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>
|
|
5877
|
+
<span>\u6587\u4EF6</span>
|
|
5878
|
+
</button>
|
|
5879
|
+
<button class="mobile-nav-item" data-view="tasks">
|
|
5880
|
+
<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>
|
|
5881
|
+
<span>\u4EFB\u52A1</span>
|
|
5882
|
+
</button>
|
|
5883
|
+
<button class="mobile-nav-item" data-view="settings">
|
|
5884
|
+
<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>
|
|
5885
|
+
<span>\u8BBE\u7F6E</span>
|
|
5886
|
+
</button>
|
|
5887
|
+
</nav>
|
|
5888
|
+
|
|
5889
|
+
<main class="main-content">
|
|
5890
|
+
<div class="view active" id="view-overview">
|
|
5891
|
+
<div class="page-header">
|
|
5892
|
+
<h1 class="page-title">Dashboard</h1>
|
|
5893
|
+
<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>
|
|
5894
|
+
</div>
|
|
5895
|
+
<div class="metrics-grid" id="metrics"></div>
|
|
5896
|
+
<div class="content-grid">
|
|
5408
5897
|
<div>
|
|
5409
|
-
<
|
|
5410
|
-
|
|
5898
|
+
<div class="content-panel glass">
|
|
5899
|
+
<div class="panel-header">
|
|
5900
|
+
<h2 class="panel-title">\u6700\u8FD1\u6D88\u606F</h2>
|
|
5901
|
+
<button class="btn btn-sm" onclick="navigateTo('messages')">\u67E5\u770B\u5168\u90E8</button>
|
|
5902
|
+
</div>
|
|
5903
|
+
<div id="recent-messages"></div>
|
|
5904
|
+
</div>
|
|
5905
|
+
<div class="content-panel glass mt-lg">
|
|
5906
|
+
<div class="panel-header">
|
|
5907
|
+
<h2 class="panel-title">\u4F1A\u8BDD\u8BB0\u5FC6</h2>
|
|
5908
|
+
<button class="btn btn-sm" onclick="navigateTo('episodes')">\u67E5\u770B\u5168\u90E8</button>
|
|
5909
|
+
</div>
|
|
5910
|
+
<div id="recent-episodes"></div>
|
|
5911
|
+
</div>
|
|
5411
5912
|
</div>
|
|
5412
5913
|
<div>
|
|
5413
|
-
<div class="
|
|
5414
|
-
<
|
|
5914
|
+
<div class="content-panel glass">
|
|
5915
|
+
<div class="panel-header"><h2 class="panel-title">\u7CFB\u7EDF\u72B6\u6001</h2></div>
|
|
5916
|
+
<div id="system-status"></div>
|
|
5917
|
+
</div>
|
|
5918
|
+
<div class="content-panel glass mt-lg">
|
|
5919
|
+
<div class="panel-header"><h2 class="panel-title">\u5FEB\u6377\u64CD\u4F5C</h2></div>
|
|
5920
|
+
<div style="display: flex; flex-direction: column; gap: var(--space-sm);">
|
|
5921
|
+
<button class="btn btn-primary" id="btn-process-messages" onclick="processNow()">
|
|
5922
|
+
<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>
|
|
5923
|
+
\u7ACB\u5373\u5904\u7406\u6D88\u606F
|
|
5924
|
+
</button>
|
|
5925
|
+
<button class="btn" onclick="navigateTo('settings')">
|
|
5926
|
+
<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>
|
|
5927
|
+
\u7CFB\u7EDF\u8BBE\u7F6E
|
|
5928
|
+
</button>
|
|
5929
|
+
</div>
|
|
5930
|
+
</div>
|
|
5931
|
+
<div class="content-panel glass mt-lg">
|
|
5932
|
+
<div class="panel-header"><h2 class="panel-title">RAG \u68C0\u7D22</h2></div>
|
|
5933
|
+
<div style="font-size: 13px; color: var(--text-secondary); line-height: 1.8;">
|
|
5934
|
+
<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>
|
|
5935
|
+
<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>
|
|
5936
|
+
<div style="display: flex; align-items: center; gap: var(--space-sm);"><span class="tag tag-success">\u6DF7\u5408</span><span>Hybrid RAG</span></div>
|
|
5937
|
+
</div>
|
|
5415
5938
|
</div>
|
|
5416
|
-
<div id="action-status" class="status-line"></div>
|
|
5417
5939
|
</div>
|
|
5418
|
-
</
|
|
5940
|
+
</div>
|
|
5941
|
+
</div>
|
|
5419
5942
|
|
|
5420
|
-
|
|
5943
|
+
<div class="view" id="view-messages">
|
|
5944
|
+
<div class="section-header">
|
|
5945
|
+
<div><h1 class="section-title">\u6D88\u606F</h1><p class="page-subtitle">\u7FA4\u804A\u6D88\u606F\u5386\u53F2</p></div>
|
|
5946
|
+
<div class="search-box">
|
|
5947
|
+
<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>
|
|
5948
|
+
<input type="text" id="message-search" placeholder="\u641C\u7D22\u6D88\u606F..." oninput="filterMessages()" />
|
|
5949
|
+
</div>
|
|
5950
|
+
</div>
|
|
5951
|
+
<div class="content-panel glass"><div id="messages-list"></div></div>
|
|
5952
|
+
</div>
|
|
5421
5953
|
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5954
|
+
<div class="view" id="view-episodes">
|
|
5955
|
+
<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>
|
|
5956
|
+
<div class="content-panel glass"><div id="episodes-list"></div></div>
|
|
5957
|
+
</div>
|
|
5958
|
+
|
|
5959
|
+
<div class="view" id="view-files">
|
|
5960
|
+
<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>
|
|
5961
|
+
<div id="files-list"></div>
|
|
5962
|
+
</div>
|
|
5963
|
+
|
|
5964
|
+
<div class="view" id="view-tasks">
|
|
5965
|
+
<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>
|
|
5966
|
+
<div class="tabs" style="margin-bottom: var(--space-lg);">
|
|
5967
|
+
<button class="tab active" data-tab="file-jobs" onclick="switchTab('file-jobs')">\u6587\u4EF6\u89E3\u6790</button>
|
|
5968
|
+
<button class="tab" data-tab="cron-jobs" onclick="switchTab('cron-jobs')">\u5B9A\u65F6\u4EFB\u52A1</button>
|
|
5969
|
+
</div>
|
|
5970
|
+
<div class="content-panel glass" id="tab-file-jobs"><div id="file-jobs-list"></div></div>
|
|
5971
|
+
<div class="content-panel glass" id="tab-cron-jobs" style="display: none;"><div id="cron-jobs-list"></div></div>
|
|
5972
|
+
</div>
|
|
5973
|
+
|
|
5974
|
+
<div class="view" id="view-qa-logs">
|
|
5975
|
+
<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>
|
|
5976
|
+
<div class="content-panel glass"><div id="qa-logs-list"></div></div>
|
|
5977
|
+
</div>
|
|
5978
|
+
|
|
5979
|
+
<div class="view" id="view-settings">
|
|
5980
|
+
<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>
|
|
5981
|
+
<div class="settings-group glass" id="settings-config"></div>
|
|
5982
|
+
<div class="settings-group glass">
|
|
5983
|
+
<h3 style="font-size: 16px; font-weight: 600; margin-bottom: var(--space-md);">\u64CD\u4F5C</h3>
|
|
5984
|
+
<div style="display: flex; flex-direction: column; gap: var(--space-sm);">
|
|
5985
|
+
<button class="btn btn-primary" onclick="processNow()">
|
|
5986
|
+
<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>
|
|
5987
|
+
\u7ACB\u5373\u5904\u7406\u6D88\u606F\u7D22\u5F15
|
|
5988
|
+
</button>
|
|
5989
|
+
<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);">
|
|
5990
|
+
\u8FD0\u884C CLI \u547D\u4EE4\u8FDB\u884C\u66F4\u591A\u64CD\u4F5C\uFF1A
|
|
5991
|
+
<div style="font-family: var(--font-mono); margin-top: var(--space-xs); line-height: 1.8;">
|
|
5992
|
+
chattercatcher settings<br/>
|
|
5993
|
+
chattercatcher doctor<br/>
|
|
5994
|
+
chattercatcher index rebuild<br/>
|
|
5995
|
+
chattercatcher files add <path...><br/>
|
|
5996
|
+
chattercatcher export
|
|
5997
|
+
</div>
|
|
5998
|
+
</div>
|
|
5436
5999
|
</div>
|
|
5437
|
-
<aside>
|
|
5438
|
-
<section>
|
|
5439
|
-
<h2>\u7FA4\u804A</h2>
|
|
5440
|
-
<div id="chats" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5441
|
-
</section>
|
|
5442
|
-
<section>
|
|
5443
|
-
<h2>\u6587\u4EF6\u5E93</h2>
|
|
5444
|
-
<div id="files" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5445
|
-
</section>
|
|
5446
|
-
<section>
|
|
5447
|
-
<h2>\u89E3\u6790\u4EFB\u52A1</h2>
|
|
5448
|
-
<div id="file-jobs" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5449
|
-
</section>
|
|
5450
|
-
<section>
|
|
5451
|
-
<h2>\u5B9A\u65F6\u4EFB\u52A1</h2>
|
|
5452
|
-
<div id="cron-jobs" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5453
|
-
</section>
|
|
5454
|
-
<section>
|
|
5455
|
-
<h2>\u672C\u5730\u64CD\u4F5C</h2>
|
|
5456
|
-
<p><code>chattercatcher settings</code> \u4FEE\u6539\u914D\u7F6E\u3002</p>
|
|
5457
|
-
<p><code>chattercatcher files add <path...></code> \u5BFC\u5165\u6587\u672C\u3001DOCX \u6216 PDF \u6587\u4EF6\u3002</p>
|
|
5458
|
-
<p><code>chattercatcher doctor</code> \u68C0\u67E5\u98DE\u4E66\u3001\u6A21\u578B\u3001RAG \u548C\u672C\u5730\u5B58\u50A8\u3002</p>
|
|
5459
|
-
</section>
|
|
5460
|
-
</aside>
|
|
5461
6000
|
</div>
|
|
5462
|
-
</
|
|
5463
|
-
|
|
5464
|
-
const metrics = document.querySelector("#metrics");
|
|
5465
|
-
const messages = document.querySelector("#messages");
|
|
5466
|
-
const episodes = document.querySelector("#episodes");
|
|
5467
|
-
const chats = document.querySelector("#chats");
|
|
5468
|
-
const files = document.querySelector("#files");
|
|
5469
|
-
const fileJobs = document.querySelector("#file-jobs");
|
|
5470
|
-
const cronJobs = document.querySelector("#cron-jobs");
|
|
5471
|
-
const qaLogs = document.querySelector("#qa-logs");
|
|
5472
|
-
const processMessages = document.querySelector("#process-messages");
|
|
5473
|
-
const actionStatus = document.querySelector("#action-status");
|
|
6001
|
+
</div>
|
|
6002
|
+
</main>
|
|
5474
6003
|
|
|
5475
|
-
|
|
6004
|
+
<div id="toast-container" style="position: fixed; top: 24px; right: 24px; z-index: 1001; display: flex; flex-direction: column; gap: 12px;"></div>
|
|
5476
6005
|
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
6006
|
+
<script>
|
|
6007
|
+
let currentView = "overview";
|
|
6008
|
+
let allMessages = [];
|
|
6009
|
+
let allEpisodes = [];
|
|
6010
|
+
let allFiles = [];
|
|
6011
|
+
let allFileJobs = [];
|
|
6012
|
+
let allCronJobs = [];
|
|
6013
|
+
let allQaLogs = [];
|
|
6014
|
+
let statusData = null;
|
|
5480
6015
|
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
6016
|
+
function fmt(value) { return value == null || value === "" ? "-" : String(value); }
|
|
6017
|
+
function escapeHtml(value) {
|
|
6018
|
+
return fmt(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
6019
|
+
}
|
|
6020
|
+
function isOpaqueId(value) { return /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(fmt(value)); }
|
|
6021
|
+
function formatDateTime(value) {
|
|
6022
|
+
var date = new Date(value);
|
|
6023
|
+
if (Number.isNaN(date.getTime())) return fmt(value);
|
|
6024
|
+
var pad = function(n) { return String(n).padStart(2, "0"); };
|
|
6025
|
+
return date.getFullYear() + "/" + pad(date.getMonth()+1) + "/" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes());
|
|
6026
|
+
}
|
|
6027
|
+
function displaySender(value) { return isOpaqueId(value) ? "\u7FA4\u6210\u5458" : fmt(value); }
|
|
6028
|
+
function displayChatName(value, platform) { return !isOpaqueId(value) ? fmt(value) : (platform === "feishu" ? "\u98DE\u4E66\u7FA4\u804A" : "\u7FA4\u804A"); }
|
|
5488
6029
|
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
6030
|
+
function showToast(message, type) {
|
|
6031
|
+
type = type || "info";
|
|
6032
|
+
var container = document.getElementById("toast-container");
|
|
6033
|
+
var toast = document.createElement("div");
|
|
6034
|
+
toast.className = "toast toast-" + type;
|
|
6035
|
+
toast.textContent = message;
|
|
6036
|
+
container.appendChild(toast);
|
|
6037
|
+
setTimeout(function() {
|
|
6038
|
+
toast.style.opacity = "0"; toast.style.transform = "translateX(10px)";
|
|
6039
|
+
setTimeout(function() { toast.remove(); }, 300);
|
|
6040
|
+
}, 3000);
|
|
6041
|
+
}
|
|
5492
6042
|
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
}
|
|
6043
|
+
function navigateTo(view) {
|
|
6044
|
+
document.querySelectorAll(".view").forEach(function(el) { el.classList.remove("active"); });
|
|
6045
|
+
document.querySelectorAll(".nav-item, .mobile-nav-item").forEach(function(el) { el.classList.remove("active"); });
|
|
6046
|
+
document.getElementById("view-" + view).classList.add("active");
|
|
6047
|
+
document.querySelectorAll('[data-view="' + view + '"]').forEach(function(el) { el.classList.add("active"); });
|
|
6048
|
+
currentView = view;
|
|
6049
|
+
window.scrollTo(0, 0);
|
|
6050
|
+
if (view === "messages") renderMessagesView();
|
|
6051
|
+
if (view === "episodes") renderEpisodesView();
|
|
6052
|
+
if (view === "files") renderFilesView();
|
|
6053
|
+
if (view === "tasks") renderTasksView();
|
|
6054
|
+
if (view === "qa-logs") renderQaLogsView();
|
|
6055
|
+
}
|
|
5507
6056
|
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
6057
|
+
document.querySelectorAll(".nav-item, .mobile-nav-item").forEach(function(el) {
|
|
6058
|
+
el.addEventListener("click", function() { navigateTo(el.dataset.view); });
|
|
6059
|
+
});
|
|
5511
6060
|
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
6061
|
+
function switchTab(tab) {
|
|
6062
|
+
document.querySelectorAll(".tab").forEach(function(el) { el.classList.remove("active"); });
|
|
6063
|
+
document.querySelector('[data-tab="' + tab + '"]').classList.add("active");
|
|
6064
|
+
document.getElementById("tab-file-jobs").style.display = tab === "file-jobs" ? "block" : "none";
|
|
6065
|
+
document.getElementById("tab-cron-jobs").style.display = tab === "cron-jobs" ? "block" : "none";
|
|
6066
|
+
if (tab === "file-jobs") renderFileJobs();
|
|
6067
|
+
if (tab === "cron-jobs") renderCronJobs();
|
|
6068
|
+
}
|
|
5516
6069
|
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
6070
|
+
async function fetchJson(path) {
|
|
6071
|
+
var response = await fetch(path);
|
|
6072
|
+
if (!response.ok) {
|
|
6073
|
+
var body = await response.text();
|
|
6074
|
+
throw new Error(path + " " + response.status + " " + body);
|
|
5521
6075
|
}
|
|
6076
|
+
return response.json();
|
|
6077
|
+
}
|
|
5522
6078
|
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
6079
|
+
async function postJson(path, options) {
|
|
6080
|
+
var response = await fetch(path, Object.assign({ method: "POST" }, options || {}));
|
|
6081
|
+
var result = await response.json();
|
|
6082
|
+
if (!response.ok) {
|
|
6083
|
+
throw new Error(result.message || result.reason || "\u8BF7\u6C42\u5931\u8D25");
|
|
5526
6084
|
}
|
|
6085
|
+
return result;
|
|
6086
|
+
}
|
|
5527
6087
|
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
["\u7FA4\u804A", status.data.chats, "\u672C\u5730\u7FA4\u804A\u6570", ""],
|
|
5534
|
-
["\u6D88\u606F", status.data.messages, "\u5DF2\u5165\u5E93\u6D88\u606F", ""],
|
|
5535
|
-
["\u4F1A\u8BDD\u8BB0\u5FC6", status.data.episodes, "\u5DF2\u751F\u6210\u6458\u8981", ""],
|
|
5536
|
-
["\u6587\u4EF6", status.data.files, "\u6587\u4EF6\u77E5\u8BC6\u6E90", ""],
|
|
5537
|
-
].map(([label, value, note, extra]) => \`
|
|
5538
|
-
<div class="metric">
|
|
5539
|
-
<div class="label">\${escapeHtml(label)}</div>
|
|
5540
|
-
<div class="value \${extra}">\${escapeHtml(value)}</div>
|
|
5541
|
-
<div class="note">\${escapeHtml(note)}</div>
|
|
5542
|
-
</div>
|
|
5543
|
-
\`).join("");
|
|
6088
|
+
async function deleteJson(path) {
|
|
6089
|
+
var response = await fetch(path, { method: "DELETE" });
|
|
6090
|
+
var result = await response.json();
|
|
6091
|
+
if (!response.ok) {
|
|
6092
|
+
throw new Error(result.message || result.reason || "\u8BF7\u6C42\u5931\u8D25");
|
|
5544
6093
|
}
|
|
6094
|
+
return result;
|
|
6095
|
+
}
|
|
5545
6096
|
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
messages
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
6097
|
+
function renderMetrics(status) {
|
|
6098
|
+
var gatewayClass = status.gateway.configured ? "status-dot online" : "status-dot offline";
|
|
6099
|
+
var gatewayText = status.gateway.connection === "running" ? "\u8FD0\u884C\u4E2D" : (!status.gateway.configured ? "\u672A\u914D\u7F6E" : "\u5F85\u542F\u52A8");
|
|
6100
|
+
var metricsHtml = [
|
|
6101
|
+
["Gateway", gatewayText, "\u98DE\u4E66\u957F\u8FDE\u63A5", gatewayClass],
|
|
6102
|
+
["\u7248\u672C", status.version || "unknown", "\u5F53\u524D\u8FD0\u884C\u7248\u672C", ""],
|
|
6103
|
+
["\u7FA4\u804A", status.data.chats, "\u672C\u5730\u7FA4\u804A\u6570", ""],
|
|
6104
|
+
["\u6D88\u606F", status.data.messages, "\u5DF2\u5165\u5E93\u6D88\u606F", ""],
|
|
6105
|
+
["\u4F1A\u8BDD\u8BB0\u5FC6", status.data.episodes, "\u5DF2\u751F\u6210\u6458\u8981", ""],
|
|
6106
|
+
["\u6587\u4EF6", status.data.files, "\u6587\u4EF6\u77E5\u8BC6\u6E90", ""],
|
|
6107
|
+
["\u95EE\u7B54", status.data.qaLogs, "\u95EE\u7B54\u8BB0\u5F55", ""],
|
|
6108
|
+
["\u4EFB\u52A1", status.data.cronJobs, "\u5B9A\u65F6\u4EFB\u52A1", ""]
|
|
6109
|
+
].map(function(item) {
|
|
6110
|
+
var label = item[0], value = item[1], note = item[2], dotClass = item[3];
|
|
6111
|
+
return '<div class="metric-card glass"><div class="metric-label">' + escapeHtml(label) + '</div>' +
|
|
6112
|
+
'<div class="metric-value">' + (dotClass ? '<span class="' + dotClass + '" style="margin-right:8px;"></span>' : '') + escapeHtml(value) + '</div>' +
|
|
6113
|
+
'<div class="metric-note">' + escapeHtml(note) + '</div></div>';
|
|
6114
|
+
}).join("");
|
|
6115
|
+
document.getElementById("metrics").innerHTML = metricsHtml;
|
|
6116
|
+
document.getElementById("gateway-indicator").className = gatewayClass;
|
|
6117
|
+
document.getElementById("gateway-status-text").textContent = "Gateway " + gatewayText;
|
|
6118
|
+
document.getElementById("version-text").textContent = "v" + (status.version || "unknown");
|
|
6119
|
+
}
|
|
5568
6120
|
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
<div class="message-meta">
|
|
5581
|
-
<span>\${escapeHtml(formatDateTime(item.startedAt))} - \${escapeHtml(formatDateTime(item.endedAt))}</span>
|
|
5582
|
-
<span>\${escapeHtml(displayChatName(item.chatName, "feishu"))}</span>
|
|
5583
|
-
<span>\${escapeHtml(item.messageCount)} \u6761\u6D88\u606F</span>
|
|
5584
|
-
</div>
|
|
5585
|
-
<div class="message-body">\${escapeHtml(item.summary)}</div>
|
|
5586
|
-
</article>
|
|
5587
|
-
\`).join("")}
|
|
5588
|
-
</div>
|
|
5589
|
-
\`;
|
|
5590
|
-
}
|
|
6121
|
+
function renderSystemStatus(status) {
|
|
6122
|
+
var gateway = status.gateway;
|
|
6123
|
+
var html = '<div style="display:flex;flex-direction:column;gap:var(--space-md);">';
|
|
6124
|
+
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>';
|
|
6125
|
+
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>';
|
|
6126
|
+
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>';
|
|
6127
|
+
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>';
|
|
6128
|
+
html += '<div class="settings-item"><div><div class="settings-label">\u5411\u91CF\u68C0\u7D22</div></div><div class="settings-value">SQLite embedding</div></div>';
|
|
6129
|
+
html += '</div>';
|
|
6130
|
+
document.getElementById("system-status").innerHTML = html;
|
|
6131
|
+
}
|
|
5591
6132
|
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
}
|
|
5598
|
-
chats.className = "";
|
|
5599
|
-
chats.innerHTML = \`
|
|
5600
|
-
<table>
|
|
5601
|
-
<thead><tr><th>\u540D\u79F0</th><th>\u5E73\u53F0</th></tr></thead>
|
|
5602
|
-
<tbody>
|
|
5603
|
-
\${items.map((item) => \`
|
|
5604
|
-
<tr>
|
|
5605
|
-
<td><span class="id-text" title="\${escapeHtml(item.name)}">\${escapeHtml(displayChatName(item.name, item.platform))}</span></td>
|
|
5606
|
-
<td>\${escapeHtml(item.platform)}</td>
|
|
5607
|
-
</tr>
|
|
5608
|
-
\`).join("")}
|
|
5609
|
-
</tbody>
|
|
5610
|
-
</table>
|
|
5611
|
-
\`;
|
|
6133
|
+
function renderRecentMessages(items) {
|
|
6134
|
+
var el = document.getElementById("recent-messages");
|
|
6135
|
+
if (!items || items.length === 0) {
|
|
6136
|
+
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>';
|
|
6137
|
+
return;
|
|
5612
6138
|
}
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
files.innerHTML = \`
|
|
5622
|
-
<table>
|
|
5623
|
-
<thead><tr><th>\u6587\u4EF6</th><th>\u89E3\u6790\u5668</th><th>\u5B57\u7B26</th></tr></thead>
|
|
5624
|
-
<tbody>
|
|
5625
|
-
\${items.map((item) => \`
|
|
5626
|
-
<tr>
|
|
5627
|
-
<td>
|
|
5628
|
-
<div>\${escapeHtml(item.fileName)}</div>
|
|
5629
|
-
<div class="path" title="\${escapeHtml(item.storedPath)}">\${escapeHtml(item.storedPath)}</div>
|
|
5630
|
-
</td>
|
|
5631
|
-
<td>\${escapeHtml(item.parser || "unknown")}</td>
|
|
5632
|
-
<td>\${escapeHtml(item.characters)}</td>
|
|
5633
|
-
</tr>
|
|
5634
|
-
\`).join("")}
|
|
5635
|
-
</tbody>
|
|
5636
|
-
</table>
|
|
5637
|
-
\`;
|
|
6139
|
+
var html = '<div class="message-list">';
|
|
6140
|
+
for (var i = 0; i < Math.min(items.length, 5); i++) {
|
|
6141
|
+
var item = items[i];
|
|
6142
|
+
html += '<div class="message-card"><div class="message-meta">' +
|
|
6143
|
+
'<span>' + escapeHtml(formatDateTime(item.sentAt)) + '</span>' +
|
|
6144
|
+
'<span>' + escapeHtml(displaySender(item.senderName)) + '</span>' +
|
|
6145
|
+
'<span>' + escapeHtml(displayChatName(item.chatName, item.platform)) + '</span>' +
|
|
6146
|
+
'</div><div class="message-text">' + escapeHtml(item.text) + '</div></div>';
|
|
5638
6147
|
}
|
|
6148
|
+
html += '</div>';
|
|
6149
|
+
el.innerHTML = html;
|
|
6150
|
+
}
|
|
5639
6151
|
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
<td>
|
|
5654
|
-
<div>\${escapeHtml(item.fileName)}</div>
|
|
5655
|
-
<div class="path" title="\${escapeHtml(item.id)}">ID: \${escapeHtml(item.id)}</div>
|
|
5656
|
-
<div class="path" title="\${escapeHtml(item.error || item.storedPath)}">\${escapeHtml(item.error || item.storedPath)}</div>
|
|
5657
|
-
</td>
|
|
5658
|
-
<td>\${escapeHtml(item.status)}</td>
|
|
5659
|
-
</tr>
|
|
5660
|
-
\`).join("")}
|
|
5661
|
-
</tbody>
|
|
5662
|
-
</table>
|
|
5663
|
-
\`;
|
|
6152
|
+
function renderRecentEpisodes(items) {
|
|
6153
|
+
var el = document.getElementById("recent-episodes");
|
|
6154
|
+
if (!items || items.length === 0) {
|
|
6155
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u8BB0\u5FC6\u3002</div>';
|
|
6156
|
+
return;
|
|
6157
|
+
}
|
|
6158
|
+
var html = '<div class="message-list">';
|
|
6159
|
+
for (var i = 0; i < Math.min(items.length, 3); i++) {
|
|
6160
|
+
var item = items[i];
|
|
6161
|
+
html += '<div class="episode-card"><div class="message-meta">' +
|
|
6162
|
+
'<span>' + escapeHtml(formatDateTime(item.startedAt)) + " - " + escapeHtml(formatDateTime(item.endedAt)) + '</span>' +
|
|
6163
|
+
'<span>' + escapeHtml(item.messageCount) + ' \u6761\u6D88\u606F</span>' +
|
|
6164
|
+
'</div><div class="message-text">' + escapeHtml(item.summary) + '</div></div>';
|
|
5664
6165
|
}
|
|
6166
|
+
html += '</div>';
|
|
6167
|
+
el.innerHTML = html;
|
|
6168
|
+
}
|
|
5665
6169
|
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
}
|
|
5672
|
-
cronJobs.className = "";
|
|
5673
|
-
cronJobs.innerHTML = \`
|
|
5674
|
-
<table>
|
|
5675
|
-
<thead><tr><th>\u4EFB\u52A1</th><th>\u72B6\u6001</th></tr></thead>
|
|
5676
|
-
<tbody>
|
|
5677
|
-
\${items.map((item) => \`
|
|
5678
|
-
<tr>
|
|
5679
|
-
<td>
|
|
5680
|
-
<div>\${escapeHtml(item.schedule)}</div>
|
|
5681
|
-
<div class="message" title="\${escapeHtml(item.prompt)}">\${escapeHtml(item.prompt)}</div>
|
|
5682
|
-
<div class="path" title="\${escapeHtml(item.id)}">ID: \${escapeHtml(item.id)}</div>
|
|
5683
|
-
<div class="path" title="\${escapeHtml(item.chatId)}">\u7FA4: \${escapeHtml(item.chatId)}</div>
|
|
5684
|
-
<div class="path">\u4E0B\u6B21: \${escapeHtml(formatDateTime(item.nextRunAt))}</div>
|
|
5685
|
-
<div class="path" title="\${escapeHtml(item.lastError || "")}">\${escapeHtml(item.lastError || "")}</div>
|
|
5686
|
-
\${item.status === "active" ? \`<button type="button" data-delete-cron-job="\${escapeHtml(item.id)}">\u5220\u9664</button>\` : ""}
|
|
5687
|
-
</td>
|
|
5688
|
-
<td>\${escapeHtml(item.status)}</td>
|
|
5689
|
-
</tr>
|
|
5690
|
-
\`).join("")}
|
|
5691
|
-
</tbody>
|
|
5692
|
-
</table>
|
|
5693
|
-
\`;
|
|
6170
|
+
function renderMessagesView() {
|
|
6171
|
+
var el = document.getElementById("messages-list");
|
|
6172
|
+
if (!allMessages || allMessages.length === 0) {
|
|
6173
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u6D88\u606F\u3002</div>';
|
|
6174
|
+
return;
|
|
5694
6175
|
}
|
|
6176
|
+
var searchInput = document.getElementById("message-search");
|
|
6177
|
+
var searchTerm = searchInput ? searchInput.value.toLowerCase() : "";
|
|
6178
|
+
var filtered = searchTerm ? allMessages.filter(function(m) { return (m.text || "").toLowerCase().indexOf(searchTerm) !== -1; }) : allMessages;
|
|
6179
|
+
if (filtered.length === 0) {
|
|
6180
|
+
el.innerHTML = '<div class="empty-state">\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6D88\u606F\u3002</div>';
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
var html = '<div class="message-list">';
|
|
6184
|
+
for (var i = 0; i < Math.min(filtered.length, 50); i++) {
|
|
6185
|
+
var item = filtered[i];
|
|
6186
|
+
html += '<div class="message-card"><div class="message-meta">' +
|
|
6187
|
+
'<span>' + escapeHtml(formatDateTime(item.sentAt)) + '</span>' +
|
|
6188
|
+
'<span>' + escapeHtml(displaySender(item.senderName)) + '</span>' +
|
|
6189
|
+
'<span>' + escapeHtml(displayChatName(item.chatName, item.platform)) + '</span>' +
|
|
6190
|
+
'</div><div class="message-text" style="-webkit-line-clamp:4;">' + escapeHtml(item.text) + '</div></div>';
|
|
6191
|
+
}
|
|
6192
|
+
html += '</div>';
|
|
6193
|
+
if (filtered.length > 50) {
|
|
6194
|
+
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>';
|
|
6195
|
+
}
|
|
6196
|
+
el.innerHTML = html;
|
|
6197
|
+
}
|
|
5695
6198
|
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
" <span>" + escapeHtml(formatDateTime(item.createdAt)) + "</span>",
|
|
5709
|
-
" <span>" + escapeHtml(item.status) + "</span>",
|
|
5710
|
-
" <span>" + escapeHtml(citationCount) + " \u6761\u5F15\u7528</span>",
|
|
5711
|
-
" </div>",
|
|
5712
|
-
" <div class=\\"message-body\\"><strong>\u95EE\uFF1A</strong>" + escapeHtml(item.question) + "</div>",
|
|
5713
|
-
" <div class=\\"message-body\\"><strong>\u7B54\uFF1A</strong>" + escapeHtml(item.answer) + "</div>",
|
|
5714
|
-
"</article>",
|
|
5715
|
-
].join("\\n");
|
|
5716
|
-
});
|
|
5717
|
-
qaLogs.innerHTML = [
|
|
5718
|
-
'<div class="message-list">',
|
|
5719
|
-
rows.join(""),
|
|
5720
|
-
"</div>",
|
|
5721
|
-
].join("\\n");
|
|
6199
|
+
function filterMessages() { renderMessagesView(); }
|
|
6200
|
+
|
|
6201
|
+
function renderEpisodesView() {
|
|
6202
|
+
var el = document.getElementById("episodes-list");
|
|
6203
|
+
if (!allEpisodes || allEpisodes.length === 0) {
|
|
6204
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u8BB0\u5FC6\u3002</div>';
|
|
6205
|
+
return;
|
|
6206
|
+
}
|
|
6207
|
+
var html = '<div class="timeline">';
|
|
6208
|
+
for (var i = 0; i < allEpisodes.length; i++) {
|
|
6209
|
+
var item = allEpisodes[i];
|
|
6210
|
+
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>';
|
|
5722
6211
|
}
|
|
6212
|
+
html += '</div>';
|
|
6213
|
+
el.innerHTML = html;
|
|
6214
|
+
}
|
|
5723
6215
|
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
}
|
|
5730
|
-
return response.json();
|
|
6216
|
+
function renderFilesView() {
|
|
6217
|
+
var el = document.getElementById("files-list");
|
|
6218
|
+
if (!allFiles || allFiles.length === 0) {
|
|
6219
|
+
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>';
|
|
6220
|
+
return;
|
|
5731
6221
|
}
|
|
6222
|
+
var html = '<div class="grid-2">';
|
|
6223
|
+
for (var i = 0; i < allFiles.length; i++) {
|
|
6224
|
+
var item = allFiles[i];
|
|
6225
|
+
html += '<div class="file-card glass"><div class="file-icon">' +
|
|
6226
|
+
'<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>' +
|
|
6227
|
+
'</div><div style="font-weight:600;margin-bottom:4px;">' + escapeHtml(item.fileName) + '</div>' +
|
|
6228
|
+
'<div style="font-size:13px;color:var(--text-muted);margin-bottom:4px;" class="truncate">' + escapeHtml(item.storedPath) + '</div>' +
|
|
6229
|
+
'<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>';
|
|
6230
|
+
}
|
|
6231
|
+
html += '</div>';
|
|
6232
|
+
el.innerHTML = html;
|
|
6233
|
+
}
|
|
6234
|
+
|
|
6235
|
+
function renderTasksView() {
|
|
6236
|
+
var activeTab = document.querySelector(".tab.active");
|
|
6237
|
+
var tab = activeTab ? activeTab.dataset.tab : "file-jobs";
|
|
6238
|
+
if (tab === "file-jobs") renderFileJobs();
|
|
6239
|
+
else renderCronJobs();
|
|
6240
|
+
}
|
|
5732
6241
|
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
6242
|
+
function renderFileJobs() {
|
|
6243
|
+
var el = document.getElementById("file-jobs-list");
|
|
6244
|
+
if (!allFileJobs || allFileJobs.length === 0) {
|
|
6245
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u6587\u4EF6\u89E3\u6790\u4EFB\u52A1\u3002</div>';
|
|
6246
|
+
return;
|
|
6247
|
+
}
|
|
6248
|
+
var html = '<table class="data-table"><thead><tr><th>\u6587\u4EF6</th><th>\u72B6\u6001</th><th>\u4FE1\u606F</th></tr></thead><tbody>';
|
|
6249
|
+
for (var i = 0; i < allFileJobs.length; i++) {
|
|
6250
|
+
var item = allFileJobs[i];
|
|
6251
|
+
var tagClass = item.status === 'indexed' ? 'tag-success' : item.status === 'failed' ? 'tag-error' : 'tag-warning';
|
|
6252
|
+
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>' +
|
|
6253
|
+
'<td><span class="tag ' + tagClass + '">' + escapeHtml(item.status) + '</span></td>' +
|
|
6254
|
+
'<td style="font-size:13px;color:var(--text-muted);">' + escapeHtml(item.error || "") + '</td></tr>';
|
|
5736
6255
|
}
|
|
6256
|
+
html += '</tbody></table>';
|
|
6257
|
+
el.innerHTML = html;
|
|
6258
|
+
}
|
|
5737
6259
|
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
}
|
|
6260
|
+
function renderCronJobs() {
|
|
6261
|
+
var el = document.getElementById("cron-jobs-list");
|
|
6262
|
+
if (!allCronJobs || allCronJobs.length === 0) {
|
|
6263
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u5B9A\u65F6\u4EFB\u52A1\u3002</div>';
|
|
6264
|
+
return;
|
|
5744
6265
|
}
|
|
6266
|
+
var html = '<table class="data-table"><thead><tr><th>\u4EFB\u52A1</th><th>\u72B6\u6001</th><th>\u64CD\u4F5C</th></tr></thead><tbody>';
|
|
6267
|
+
for (var i = 0; i < allCronJobs.length; i++) {
|
|
6268
|
+
var item = allCronJobs[i];
|
|
6269
|
+
var tagClass = item.status === 'active' ? 'tag-success' : 'tag-warning';
|
|
6270
|
+
html += '<tr><td><div style="font-weight:500;">' + escapeHtml(item.schedule) + '</div>' +
|
|
6271
|
+
'<div style="font-size:13px;color:var(--text-muted);" class="truncate-2">' + escapeHtml(item.prompt) + '</div>' +
|
|
6272
|
+
'<div style="font-size:12px;color:var(--text-muted);">\u4E0B\u6B21: ' + escapeHtml(formatDateTime(item.nextRunAt)) + '</div>' +
|
|
6273
|
+
(item.lastError ? '<div style="font-size:12px;color:var(--danger);margin-top:4px;">' + escapeHtml(item.lastError) + '</div>' : '') +
|
|
6274
|
+
'</td><td><span class="tag ' + tagClass + '">' + escapeHtml(item.status) + '</span></td><td>' +
|
|
6275
|
+
(item.status === "active" ? '<button class="btn btn-sm btn-danger" data-delete-cron-job="' + escapeHtml(item.id) + '">\u5220\u9664</button>' : '-') +
|
|
6276
|
+
'</td></tr>';
|
|
6277
|
+
}
|
|
6278
|
+
html += '</tbody></table>';
|
|
6279
|
+
el.innerHTML = html;
|
|
6280
|
+
}
|
|
5745
6281
|
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
6282
|
+
function renderQaLogsView() {
|
|
6283
|
+
var el = document.getElementById("qa-logs-list");
|
|
6284
|
+
if (!allQaLogs || allQaLogs.length === 0) {
|
|
6285
|
+
el.innerHTML = '<div class="empty-state">\u8FD8\u6CA1\u6709\u95EE\u7B54\u65E5\u5FD7\u3002</div>';
|
|
6286
|
+
return;
|
|
6287
|
+
}
|
|
6288
|
+
var html = '';
|
|
6289
|
+
for (var i = 0; i < allQaLogs.length; i++) {
|
|
6290
|
+
var item = allQaLogs[i];
|
|
6291
|
+
var citationCount = Array.isArray(item.citations) ? item.citations.length : 0;
|
|
6292
|
+
var statusClass = item.status === 'success' ? 'tag-success' : 'tag-warning';
|
|
6293
|
+
html += '<div class="qa-card"><div class="message-meta" style="margin-bottom:var(--space-sm);">' +
|
|
6294
|
+
'<span>' + escapeHtml(formatDateTime(item.createdAt)) + '</span>' +
|
|
6295
|
+
'<span class="tag ' + statusClass + '">' + escapeHtml(item.status) + '</span>' +
|
|
6296
|
+
'<span>' + citationCount + ' \u6761\u5F15\u7528</span></div>' +
|
|
6297
|
+
'<div class="qa-question">' + escapeHtml(item.question) + '</div>' +
|
|
6298
|
+
'<div class="qa-answer">' + escapeHtml(item.answer) + '</div></div>';
|
|
5757
6299
|
}
|
|
6300
|
+
el.innerHTML = html;
|
|
6301
|
+
}
|
|
5758
6302
|
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
return;
|
|
5771
|
-
}
|
|
6303
|
+
function renderSettings(status) {
|
|
6304
|
+
var el = document.getElementById("settings-config");
|
|
6305
|
+
var html = '<h3 style="font-size:16px;font-weight:600;margin-bottom:var(--space-md);">\u7CFB\u7EDF\u914D\u7F6E</h3>';
|
|
6306
|
+
html += '<div style="display:flex;flex-direction:column;">';
|
|
6307
|
+
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>';
|
|
6308
|
+
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>';
|
|
6309
|
+
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>';
|
|
6310
|
+
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>';
|
|
6311
|
+
html += '</div>';
|
|
6312
|
+
el.innerHTML = html;
|
|
6313
|
+
}
|
|
5772
6314
|
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
}
|
|
5778
|
-
await load();
|
|
5779
|
-
} catch (error) {
|
|
5780
|
-
actionStatus.textContent = error instanceof Error ? error.message : String(error);
|
|
5781
|
-
} finally {
|
|
5782
|
-
processMessages.disabled = false;
|
|
5783
|
-
}
|
|
5784
|
-
}
|
|
6315
|
+
async function loadSection(path, setter) {
|
|
6316
|
+
try { setter(await fetchJson(path)); }
|
|
6317
|
+
catch (error) { console.error("\u52A0\u8F7D\u5931\u8D25:", path, error); }
|
|
6318
|
+
}
|
|
5785
6319
|
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
actionStatus.textContent = "\u6B63\u5728\u5220\u9664\u5B9A\u65F6\u4EFB\u52A1...";
|
|
5793
|
-
try {
|
|
5794
|
-
const response = await fetch(\`/api/cron-jobs/\${encodeURIComponent(id)}\`, {
|
|
5795
|
-
method: "DELETE",
|
|
5796
|
-
headers: { "x-chattercatcher-web-token": webActionToken },
|
|
5797
|
-
});
|
|
5798
|
-
const result = await response.json();
|
|
5799
|
-
actionStatus.textContent = result.ok ? "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u5220\u9664\u3002" : result.message || "\u5220\u9664\u5931\u8D25\u3002";
|
|
5800
|
-
await load();
|
|
5801
|
-
} catch (error) {
|
|
5802
|
-
actionStatus.textContent = error instanceof Error ? error.message : String(error);
|
|
5803
|
-
} finally {
|
|
5804
|
-
target.removeAttribute("disabled");
|
|
5805
|
-
}
|
|
6320
|
+
async function load() {
|
|
6321
|
+
await loadSection("/api/status", function(data) {
|
|
6322
|
+
statusData = data;
|
|
6323
|
+
renderMetrics(data);
|
|
6324
|
+
renderSystemStatus(data);
|
|
6325
|
+
renderSettings(data);
|
|
5806
6326
|
});
|
|
6327
|
+
await loadSection("/api/messages/recent?limit=50", function(data) { allMessages = data.items || []; renderRecentMessages(allMessages); });
|
|
6328
|
+
await loadSection("/api/episodes?limit=20", function(data) { allEpisodes = data.items || []; renderRecentEpisodes(allEpisodes); });
|
|
6329
|
+
await loadSection("/api/files", function(data) { allFiles = data.items || []; });
|
|
6330
|
+
await loadSection("/api/file-jobs", function(data) { allFileJobs = data.items || []; });
|
|
6331
|
+
await loadSection("/api/qa-logs?limit=20", function(data) { allQaLogs = data.items || []; });
|
|
6332
|
+
await loadSection("/api/cron-jobs", function(data) { allCronJobs = data.items || []; });
|
|
6333
|
+
if (currentView === "messages") renderMessagesView();
|
|
6334
|
+
if (currentView === "episodes") renderEpisodesView();
|
|
6335
|
+
if (currentView === "files") renderFilesView();
|
|
6336
|
+
if (currentView === "tasks") renderTasksView();
|
|
6337
|
+
if (currentView === "qa-logs") renderQaLogsView();
|
|
6338
|
+
}
|
|
5807
6339
|
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
6340
|
+
async function processNow() {
|
|
6341
|
+
var btn = document.getElementById("btn-process-messages");
|
|
6342
|
+
if (btn) { btn.disabled = true; }
|
|
6343
|
+
showToast("\u6B63\u5728\u5904\u7406\u6D88\u606F\u7D22\u5F15...", "info");
|
|
6344
|
+
try {
|
|
6345
|
+
var result = await postJson("/api/process/messages");
|
|
6346
|
+
if (result.status === "skipped") { showToast(result.reason, "warning"); }
|
|
6347
|
+
else { showToast("\u5904\u7406\u5B8C\u6210\uFF1Achunks=" + result.chunks + ", vectors=" + result.vectors, "success"); }
|
|
6348
|
+
await load();
|
|
6349
|
+
} catch (error) {
|
|
6350
|
+
showToast(error instanceof Error ? error.message : String(error), "error");
|
|
6351
|
+
} finally {
|
|
6352
|
+
if (btn) { btn.disabled = false; }
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
|
|
6356
|
+
document.addEventListener("click", async function(event) {
|
|
6357
|
+
var target = event.target;
|
|
6358
|
+
if (!(target instanceof HTMLElement)) return;
|
|
6359
|
+
var id = target.dataset.deleteCronJob;
|
|
6360
|
+
if (!id) return;
|
|
6361
|
+
target.disabled = true;
|
|
6362
|
+
showToast("\u6B63\u5728\u5220\u9664\u5B9A\u65F6\u4EFB\u52A1...", "info");
|
|
6363
|
+
try {
|
|
6364
|
+
var result = await deleteJson("/api/cron-jobs/" + encodeURIComponent(id));
|
|
6365
|
+
showToast(result.ok ? "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u5220\u9664" : (result.message || "\u5220\u9664\u5931\u8D25"), result.ok ? "success" : "error");
|
|
6366
|
+
await load();
|
|
6367
|
+
} catch (error) {
|
|
6368
|
+
showToast(error instanceof Error ? error.message : String(error), "error");
|
|
6369
|
+
}
|
|
6370
|
+
});
|
|
6371
|
+
|
|
6372
|
+
void load();
|
|
6373
|
+
setInterval(function() { if (document.visibilityState === "visible") void load(); }, 5000);
|
|
6374
|
+
</script>
|
|
6375
|
+
</body>
|
|
5817
6376
|
</html>`;
|
|
5818
6377
|
}
|
|
5819
6378
|
function parseLimit(value, fallback, max) {
|
|
@@ -5823,12 +6382,22 @@ function parseLimit(value, fallback, max) {
|
|
|
5823
6382
|
function getWebActionToken(secrets) {
|
|
5824
6383
|
return secrets.web.actionToken;
|
|
5825
6384
|
}
|
|
5826
|
-
function
|
|
5827
|
-
return
|
|
6385
|
+
function getWebActionCookie(token) {
|
|
6386
|
+
return `chattercatcher_web_token=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Strict`;
|
|
6387
|
+
}
|
|
6388
|
+
function parseCookies(header) {
|
|
6389
|
+
const value = Array.isArray(header) ? header.join("; ") : header;
|
|
6390
|
+
if (!value) return {};
|
|
6391
|
+
const cookies = {};
|
|
6392
|
+
for (const part of value.split(";")) {
|
|
6393
|
+
const [rawName, ...rawValue] = part.trim().split("=");
|
|
6394
|
+
if (!rawName || rawValue.length === 0) continue;
|
|
6395
|
+
cookies[rawName] = decodeURIComponent(rawValue.join("="));
|
|
6396
|
+
}
|
|
6397
|
+
return cookies;
|
|
5828
6398
|
}
|
|
5829
6399
|
function isAuthorizedWebAction(request, token) {
|
|
5830
|
-
|
|
5831
|
-
return provided === token;
|
|
6400
|
+
return parseCookies(request.headers.cookie).chattercatcher_web_token === token;
|
|
5832
6401
|
}
|
|
5833
6402
|
function createWebApp(config, options = {}) {
|
|
5834
6403
|
const app = Fastify({ logger: false });
|
|
@@ -5956,7 +6525,8 @@ function createWebApp(config, options = {}) {
|
|
|
5956
6525
|
app.get("/", async (_request, reply) => {
|
|
5957
6526
|
await tokenReady;
|
|
5958
6527
|
reply.type("text/html; charset=utf-8");
|
|
5959
|
-
|
|
6528
|
+
reply.header("set-cookie", getWebActionCookie(webActionToken));
|
|
6529
|
+
return buildHtml();
|
|
5960
6530
|
});
|
|
5961
6531
|
return app;
|
|
5962
6532
|
}
|