chattercatcher 0.2.5 → 0.2.6
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 +208 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.js +207 -15
- 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.2.
|
|
11
|
+
version: "0.2.6",
|
|
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",
|
|
@@ -495,6 +495,7 @@ function migrateDatabase(database) {
|
|
|
495
495
|
answer TEXT NOT NULL,
|
|
496
496
|
citations_json TEXT NOT NULL,
|
|
497
497
|
retrieval_debug_json TEXT NOT NULL,
|
|
498
|
+
trace_json TEXT NOT NULL DEFAULT '{}',
|
|
498
499
|
status TEXT NOT NULL CHECK(status IN ('answered','failed')),
|
|
499
500
|
error TEXT,
|
|
500
501
|
created_at TEXT NOT NULL
|
|
@@ -589,6 +590,10 @@ function migrateDatabase(database) {
|
|
|
589
590
|
ensureCronJobColumn("mention_target_name", "mention_target_name TEXT");
|
|
590
591
|
ensureCronJobColumn("mention_open_id", "mention_open_id TEXT");
|
|
591
592
|
ensureCronJobColumn("mention_user_id", "mention_user_id TEXT");
|
|
593
|
+
const qaLogColumns = database.prepare("PRAGMA table_info(qa_logs)").all();
|
|
594
|
+
if (!qaLogColumns.some((column) => column.name === "trace_json")) {
|
|
595
|
+
database.prepare("ALTER TABLE qa_logs ADD COLUMN trace_json TEXT NOT NULL DEFAULT '{}'").run();
|
|
596
|
+
}
|
|
592
597
|
}
|
|
593
598
|
|
|
594
599
|
// src/doctor/checks.ts
|
|
@@ -4038,10 +4043,22 @@ function createCronJobTools(input2) {
|
|
|
4038
4043
|
|
|
4039
4044
|
// src/rag/qa-logs.ts
|
|
4040
4045
|
import crypto6 from "crypto";
|
|
4046
|
+
|
|
4047
|
+
// src/rag/qa-trace.ts
|
|
4048
|
+
function hasQaTrace(trace) {
|
|
4049
|
+
return Object.keys(trace).length > 0;
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
// src/rag/qa-logs.ts
|
|
4041
4053
|
function clampLimit(limit) {
|
|
4042
4054
|
return Math.max(1, Math.min(200, Math.trunc(limit)));
|
|
4043
4055
|
}
|
|
4056
|
+
function parseTrace(value) {
|
|
4057
|
+
const parsed = JSON.parse(value);
|
|
4058
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4059
|
+
}
|
|
4044
4060
|
function mapQaLogRow(row) {
|
|
4061
|
+
const trace = parseTrace(row.trace_json);
|
|
4045
4062
|
return {
|
|
4046
4063
|
id: row.id,
|
|
4047
4064
|
chatId: row.chat_id,
|
|
@@ -4050,6 +4067,8 @@ function mapQaLogRow(row) {
|
|
|
4050
4067
|
answer: row.answer,
|
|
4051
4068
|
citations: JSON.parse(row.citations_json),
|
|
4052
4069
|
retrievalDebug: JSON.parse(row.retrieval_debug_json),
|
|
4070
|
+
trace,
|
|
4071
|
+
hasTrace: hasQaTrace(trace),
|
|
4053
4072
|
status: row.status,
|
|
4054
4073
|
error: row.error,
|
|
4055
4074
|
createdAt: row.created_at
|
|
@@ -4061,6 +4080,7 @@ var QaLogRepository = class {
|
|
|
4061
4080
|
}
|
|
4062
4081
|
database;
|
|
4063
4082
|
create(input2) {
|
|
4083
|
+
const trace = input2.trace ?? {};
|
|
4064
4084
|
const record = {
|
|
4065
4085
|
id: `qa_${crypto6.randomUUID()}`,
|
|
4066
4086
|
chatId: input2.chatId ?? null,
|
|
@@ -4069,6 +4089,8 @@ var QaLogRepository = class {
|
|
|
4069
4089
|
answer: input2.answer,
|
|
4070
4090
|
citations: input2.citations,
|
|
4071
4091
|
retrievalDebug: input2.retrievalDebug,
|
|
4092
|
+
trace,
|
|
4093
|
+
hasTrace: hasQaTrace(trace),
|
|
4072
4094
|
status: input2.status,
|
|
4073
4095
|
error: input2.error ?? null,
|
|
4074
4096
|
createdAt: input2.createdAt
|
|
@@ -4083,6 +4105,7 @@ var QaLogRepository = class {
|
|
|
4083
4105
|
answer,
|
|
4084
4106
|
citations_json,
|
|
4085
4107
|
retrieval_debug_json,
|
|
4108
|
+
trace_json,
|
|
4086
4109
|
status,
|
|
4087
4110
|
error,
|
|
4088
4111
|
created_at
|
|
@@ -4095,6 +4118,7 @@ var QaLogRepository = class {
|
|
|
4095
4118
|
@answer,
|
|
4096
4119
|
@citationsJson,
|
|
4097
4120
|
@retrievalDebugJson,
|
|
4121
|
+
@traceJson,
|
|
4098
4122
|
@status,
|
|
4099
4123
|
@error,
|
|
4100
4124
|
@createdAt
|
|
@@ -4108,6 +4132,7 @@ var QaLogRepository = class {
|
|
|
4108
4132
|
answer: record.answer,
|
|
4109
4133
|
citationsJson: JSON.stringify(record.citations),
|
|
4110
4134
|
retrievalDebugJson: JSON.stringify(record.retrievalDebug),
|
|
4135
|
+
traceJson: JSON.stringify(record.trace),
|
|
4111
4136
|
status: record.status,
|
|
4112
4137
|
error: record.error,
|
|
4113
4138
|
createdAt: record.createdAt
|
|
@@ -4125,6 +4150,7 @@ var QaLogRepository = class {
|
|
|
4125
4150
|
answer,
|
|
4126
4151
|
citations_json,
|
|
4127
4152
|
retrieval_debug_json,
|
|
4153
|
+
trace_json,
|
|
4128
4154
|
status,
|
|
4129
4155
|
error,
|
|
4130
4156
|
created_at
|
|
@@ -4146,6 +4172,7 @@ var QaLogRepository = class {
|
|
|
4146
4172
|
answer,
|
|
4147
4173
|
citations_json,
|
|
4148
4174
|
retrieval_debug_json,
|
|
4175
|
+
trace_json,
|
|
4149
4176
|
status,
|
|
4150
4177
|
error,
|
|
4151
4178
|
created_at
|
|
@@ -4157,6 +4184,27 @@ var QaLogRepository = class {
|
|
|
4157
4184
|
).all(chatId, clampLimit(limit));
|
|
4158
4185
|
return rows.map(mapQaLogRow);
|
|
4159
4186
|
}
|
|
4187
|
+
getById(id) {
|
|
4188
|
+
const row = this.database.prepare(
|
|
4189
|
+
`
|
|
4190
|
+
SELECT
|
|
4191
|
+
id,
|
|
4192
|
+
chat_id,
|
|
4193
|
+
question_message_id,
|
|
4194
|
+
question,
|
|
4195
|
+
answer,
|
|
4196
|
+
citations_json,
|
|
4197
|
+
retrieval_debug_json,
|
|
4198
|
+
trace_json,
|
|
4199
|
+
status,
|
|
4200
|
+
error,
|
|
4201
|
+
created_at
|
|
4202
|
+
FROM qa_logs
|
|
4203
|
+
WHERE id = ?
|
|
4204
|
+
`
|
|
4205
|
+
).get(id);
|
|
4206
|
+
return row ? mapQaLogRow(row) : null;
|
|
4207
|
+
}
|
|
4160
4208
|
getCount() {
|
|
4161
4209
|
const row = this.database.prepare("SELECT COUNT(*) AS count FROM qa_logs").get();
|
|
4162
4210
|
return row.count;
|
|
@@ -4214,6 +4262,18 @@ ${block.text}`;
|
|
|
4214
4262
|
function toToolErrorContent(message) {
|
|
4215
4263
|
return JSON.stringify({ ok: false, error: message });
|
|
4216
4264
|
}
|
|
4265
|
+
function nowIso5() {
|
|
4266
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
4267
|
+
}
|
|
4268
|
+
function finalizeTrace(trace, status, finalAnswer, startedAtMs) {
|
|
4269
|
+
return {
|
|
4270
|
+
...trace,
|
|
4271
|
+
completedAt: nowIso5(),
|
|
4272
|
+
durationMs: Date.now() - startedAtMs,
|
|
4273
|
+
status,
|
|
4274
|
+
finalAnswer
|
|
4275
|
+
};
|
|
4276
|
+
}
|
|
4217
4277
|
async function executeFeishuTool(tool, input2) {
|
|
4218
4278
|
const result = await tool.execute(input2);
|
|
4219
4279
|
if (isEvidenceBlockArray(result)) {
|
|
@@ -4225,6 +4285,13 @@ async function runFeishuToolLoop(input2) {
|
|
|
4225
4285
|
if (!input2.model.completeWithTools) {
|
|
4226
4286
|
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
4227
4287
|
}
|
|
4288
|
+
const startedAtMs = Date.now();
|
|
4289
|
+
const trace = {
|
|
4290
|
+
startedAt: new Date(startedAtMs).toISOString(),
|
|
4291
|
+
modelTurns: [],
|
|
4292
|
+
toolResults: [],
|
|
4293
|
+
fallbacks: []
|
|
4294
|
+
};
|
|
4228
4295
|
const maxModelTurns = input2.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;
|
|
4229
4296
|
const maxToolCalls = input2.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
4230
4297
|
const systemPromptParts = [FEISHU_TOOL_SYSTEM_PROMPT];
|
|
@@ -4253,19 +4320,36 @@ async function runFeishuToolLoop(input2) {
|
|
|
4253
4320
|
toolCalls: assistantResult.toolCalls,
|
|
4254
4321
|
reasoningContent: assistantResult.reasoningContent
|
|
4255
4322
|
});
|
|
4323
|
+
trace.modelTurns?.push({
|
|
4324
|
+
index: turn,
|
|
4325
|
+
content: assistantResult.content,
|
|
4326
|
+
reasoningContent: assistantResult.reasoningContent,
|
|
4327
|
+
toolCalls: assistantResult.toolCalls,
|
|
4328
|
+
createdAt: nowIso5()
|
|
4329
|
+
});
|
|
4256
4330
|
if (assistantResult.toolCalls.length === 0) {
|
|
4257
4331
|
if (hasRawToolCallMarkup) {
|
|
4332
|
+
trace.fallbacks?.push({ type: "raw_tool_markup", message: "\u6A21\u578B\u8F93\u51FA\u4E86\u539F\u59CB\u5DE5\u5177\u8C03\u7528\u6807\u8BB0\uFF0C\u8F6C\u5165\u6700\u7EC8\u8865\u6551\u56DE\u7B54\u3002", createdAt: nowIso5() });
|
|
4258
4333
|
break;
|
|
4259
4334
|
}
|
|
4260
|
-
|
|
4335
|
+
const answer = assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;
|
|
4336
|
+
return { answer, trace: finalizeTrace(trace, "answered", answer, startedAtMs) };
|
|
4261
4337
|
}
|
|
4262
4338
|
for (const toolCall of assistantResult.toolCalls) {
|
|
4263
4339
|
if (toolCallsUsed >= maxToolCalls) {
|
|
4264
|
-
|
|
4340
|
+
trace.fallbacks?.push({ type: "tool_limit", message: FEISHU_TOOL_LOOP_LIMIT_REACHED, createdAt: nowIso5() });
|
|
4341
|
+
return { answer: FEISHU_TOOL_LOOP_LIMIT_REACHED, trace: finalizeTrace(trace, "failed", FEISHU_TOOL_LOOP_LIMIT_REACHED, startedAtMs) };
|
|
4265
4342
|
}
|
|
4266
4343
|
toolCallsUsed += 1;
|
|
4267
4344
|
const tool = toolsByName.get(toolCall.name);
|
|
4268
4345
|
if (!tool) {
|
|
4346
|
+
trace.toolResults?.push({
|
|
4347
|
+
toolCallId: toolCall.id,
|
|
4348
|
+
name: toolCall.name,
|
|
4349
|
+
input: toolCall.input,
|
|
4350
|
+
error: `\u672A\u77E5\u5DE5\u5177\uFF1A${toolCall.name}`,
|
|
4351
|
+
createdAt: nowIso5()
|
|
4352
|
+
});
|
|
4269
4353
|
messages.push({
|
|
4270
4354
|
role: "tool",
|
|
4271
4355
|
toolCallId: toolCall.id,
|
|
@@ -4275,6 +4359,13 @@ async function runFeishuToolLoop(input2) {
|
|
|
4275
4359
|
}
|
|
4276
4360
|
try {
|
|
4277
4361
|
const result = await executeFeishuTool(tool, toolCall.input);
|
|
4362
|
+
trace.toolResults?.push({
|
|
4363
|
+
toolCallId: toolCall.id,
|
|
4364
|
+
name: toolCall.name,
|
|
4365
|
+
input: toolCall.input,
|
|
4366
|
+
content: result,
|
|
4367
|
+
createdAt: nowIso5()
|
|
4368
|
+
});
|
|
4278
4369
|
messages.push({
|
|
4279
4370
|
role: "tool",
|
|
4280
4371
|
toolCallId: toolCall.id,
|
|
@@ -4282,6 +4373,13 @@ async function runFeishuToolLoop(input2) {
|
|
|
4282
4373
|
});
|
|
4283
4374
|
} catch (error) {
|
|
4284
4375
|
const message = error instanceof Error ? error.message : String(error);
|
|
4376
|
+
trace.toolResults?.push({
|
|
4377
|
+
toolCallId: toolCall.id,
|
|
4378
|
+
name: toolCall.name,
|
|
4379
|
+
input: toolCall.input,
|
|
4380
|
+
error: message,
|
|
4381
|
+
createdAt: nowIso5()
|
|
4382
|
+
});
|
|
4285
4383
|
messages.push({
|
|
4286
4384
|
role: "tool",
|
|
4287
4385
|
toolCallId: toolCall.id,
|
|
@@ -4295,9 +4393,13 @@ async function runFeishuToolLoop(input2) {
|
|
|
4295
4393
|
...messages,
|
|
4296
4394
|
{ role: "system", content: "\u8BF7\u57FA\u4E8E\u4EE5\u4E0A\u6240\u6709\u5DE5\u5177\u8FD4\u56DE\u7684\u4FE1\u606F\uFF0C\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\u3002\u4E0D\u8981\u518D\u8C03\u7528\u5DE5\u5177\u3002" }
|
|
4297
4395
|
]);
|
|
4298
|
-
|
|
4396
|
+
const answer = salvageAnswer || "\u62B1\u6B49\uFF0C\u56DE\u7B54\u751F\u6210\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
4397
|
+
trace.fallbacks?.push({ type: "salvage_completion", message: "\u5DE5\u5177\u5FAA\u73AF\u7ED3\u675F\u540E\u4F7F\u7528\u65E0\u5DE5\u5177\u8865\u6551\u56DE\u7B54\u3002", createdAt: nowIso5() });
|
|
4398
|
+
return { answer, trace: finalizeTrace(trace, "answered", answer, startedAtMs) };
|
|
4299
4399
|
} catch {
|
|
4300
|
-
|
|
4400
|
+
const answer = "\u62B1\u6B49\uFF0C\u56DE\u7B54\u751F\u6210\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
4401
|
+
trace.fallbacks?.push({ type: "answer_generation_failed", message: answer, createdAt: nowIso5() });
|
|
4402
|
+
return { answer, trace: finalizeTrace(trace, "failed", answer, startedAtMs) };
|
|
4301
4403
|
}
|
|
4302
4404
|
}
|
|
4303
4405
|
function formatConversationContext(records) {
|
|
@@ -4417,7 +4519,7 @@ var FeishuQuestionHandler = class {
|
|
|
4417
4519
|
const memberRepository = this.options.memberRepository ?? new FeishuMemberRepository(this.options.database);
|
|
4418
4520
|
const memberPrompt = formatFeishuMemberPrompt(memberRepository.listByChat(decision.chatId));
|
|
4419
4521
|
const conversationContext = formatConversationContext(qaLogs.listRecentByChat(decision.chatId, 6));
|
|
4420
|
-
const
|
|
4522
|
+
const result = await runFeishuToolLoop({
|
|
4421
4523
|
question: decision.question,
|
|
4422
4524
|
now,
|
|
4423
4525
|
tools: allTools,
|
|
@@ -4429,27 +4531,38 @@ var FeishuQuestionHandler = class {
|
|
|
4429
4531
|
chatId: decision.chatId,
|
|
4430
4532
|
questionMessageId,
|
|
4431
4533
|
question: decision.question,
|
|
4432
|
-
answer,
|
|
4534
|
+
answer: result.answer,
|
|
4433
4535
|
citations: [],
|
|
4434
4536
|
retrievalDebug: {},
|
|
4537
|
+
trace: result.trace,
|
|
4435
4538
|
status: "answered",
|
|
4436
4539
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4437
4540
|
});
|
|
4438
|
-
await this.sendResponse(decision.chatId, questionMessageId, answer);
|
|
4541
|
+
await this.sendResponse(decision.chatId, questionMessageId, result.answer);
|
|
4439
4542
|
} catch (error) {
|
|
4440
4543
|
const message = error instanceof Error ? error.message : String(error);
|
|
4544
|
+
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4545
|
+
const failedAnswer = `\u6682\u65F6\u65E0\u6CD5\u56DE\u7B54\uFF1A${message}`;
|
|
4441
4546
|
qaLogs.create({
|
|
4442
4547
|
chatId: decision.chatId,
|
|
4443
4548
|
questionMessageId,
|
|
4444
4549
|
question: decision.question,
|
|
4445
|
-
answer:
|
|
4550
|
+
answer: failedAnswer,
|
|
4446
4551
|
citations: [],
|
|
4447
4552
|
retrievalDebug: {},
|
|
4553
|
+
trace: {
|
|
4554
|
+
startedAt: now.toISOString(),
|
|
4555
|
+
completedAt: failedAt,
|
|
4556
|
+
durationMs: Math.max(0, Date.parse(failedAt) - now.getTime()),
|
|
4557
|
+
status: "failed",
|
|
4558
|
+
finalAnswer: failedAnswer,
|
|
4559
|
+
fallbacks: [{ type: "answer_generation_failed", message, createdAt: failedAt }]
|
|
4560
|
+
},
|
|
4448
4561
|
status: "failed",
|
|
4449
4562
|
error: message,
|
|
4450
|
-
createdAt:
|
|
4563
|
+
createdAt: failedAt
|
|
4451
4564
|
});
|
|
4452
|
-
await this.sendResponse(decision.chatId, questionMessageId,
|
|
4565
|
+
await this.sendResponse(decision.chatId, questionMessageId, failedAnswer);
|
|
4453
4566
|
}
|
|
4454
4567
|
return decision;
|
|
4455
4568
|
} finally {
|
|
@@ -6343,12 +6456,15 @@ function buildHtml() {
|
|
|
6343
6456
|
let allFileJobs = [];
|
|
6344
6457
|
let allCronJobs = [];
|
|
6345
6458
|
let allQaLogs = [];
|
|
6459
|
+
let selectedQaLogId = null;
|
|
6346
6460
|
let statusData = null;
|
|
6347
6461
|
|
|
6348
6462
|
function fmt(value) { return value == null || value === "" ? "-" : String(value); }
|
|
6349
6463
|
function escapeHtml(value) {
|
|
6350
6464
|
return fmt(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
6351
6465
|
}
|
|
6466
|
+
function renderJson(value) { return '<pre style="white-space:pre-wrap;overflow:auto;max-height:320px;">' + escapeHtml(JSON.stringify(value, null, 2)) + '</pre>'; }
|
|
6467
|
+
function renderTextBlock(value) { return '<pre style="white-space:pre-wrap;overflow:auto;max-height:320px;">' + escapeHtml(value || "") + '</pre>'; }
|
|
6352
6468
|
function isOpaqueId(value) { return /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(fmt(value)); }
|
|
6353
6469
|
function formatDateTime(value) {
|
|
6354
6470
|
var date = new Date(value);
|
|
@@ -6621,17 +6737,72 @@ function buildHtml() {
|
|
|
6621
6737
|
for (var i = 0; i < allQaLogs.length; i++) {
|
|
6622
6738
|
var item = allQaLogs[i];
|
|
6623
6739
|
var citationCount = Array.isArray(item.citations) ? item.citations.length : 0;
|
|
6624
|
-
var statusClass = item.status === '
|
|
6740
|
+
var statusClass = item.status === 'answered' ? 'tag-success' : 'tag-warning';
|
|
6625
6741
|
html += '<div class="qa-card"><div class="message-meta" style="margin-bottom:var(--space-sm);">' +
|
|
6626
6742
|
'<span>' + escapeHtml(formatDateTime(item.createdAt)) + '</span>' +
|
|
6627
6743
|
'<span class="tag ' + statusClass + '">' + escapeHtml(item.status) + '</span>' +
|
|
6628
|
-
'<span>' + citationCount + ' \u6761\u5F15\u7528</span
|
|
6744
|
+
'<span>' + citationCount + ' \u6761\u5F15\u7528</span>' +
|
|
6745
|
+
'<span class="tag ' + (item.hasTrace ? 'tag-info' : 'tag-warning') + '">' + (item.hasTrace ? '\u6709 trace' : '\u65E0 trace') + '</span></div>' +
|
|
6629
6746
|
'<div class="qa-question">' + escapeHtml(item.question) + '</div>' +
|
|
6630
|
-
'<div class="qa-answer">' + escapeHtml(item.answer) + '</div
|
|
6747
|
+
'<div class="qa-answer">' + escapeHtml(item.answer) + '</div>' +
|
|
6748
|
+
'<button class="btn btn-sm" style="margin-top:var(--space-sm);" data-view-qa-log="' + escapeHtml(item.id) + '">\u67E5\u770B\u8BE6\u60C5</button>' +
|
|
6749
|
+
'<div id="qa-detail-' + escapeHtml(item.id) + '" style="margin-top:var(--space-md);"></div></div>';
|
|
6631
6750
|
}
|
|
6632
6751
|
el.innerHTML = html;
|
|
6633
6752
|
}
|
|
6634
6753
|
|
|
6754
|
+
async function showQaLogDetail(id) {
|
|
6755
|
+
selectedQaLogId = id;
|
|
6756
|
+
var container = document.getElementById("qa-detail-" + id);
|
|
6757
|
+
if (!container) return;
|
|
6758
|
+
container.innerHTML = '<div class="empty-state">\u6B63\u5728\u52A0\u8F7D\u95EE\u7B54\u8BE6\u60C5...</div>';
|
|
6759
|
+
try {
|
|
6760
|
+
var item = await fetchJson("/api/qa-logs/" + encodeURIComponent(id));
|
|
6761
|
+
renderQaLogDetail(item);
|
|
6762
|
+
} catch (error) {
|
|
6763
|
+
container.innerHTML = '<div class="empty-state">\u8BE6\u60C5\u52A0\u8F7D\u5931\u8D25\uFF1A' + escapeHtml(error instanceof Error ? error.message : String(error)) + '</div>';
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
|
|
6767
|
+
function renderQaLogDetail(item) {
|
|
6768
|
+
var container = document.getElementById("qa-detail-" + item.id);
|
|
6769
|
+
if (!container) return;
|
|
6770
|
+
var trace = item.trace || {};
|
|
6771
|
+
var html = '<div class="content-panel" style="margin-top:var(--space-sm);background:rgba(255,255,255,0.03);">';
|
|
6772
|
+
html += '<h3 style="font-size:15px;margin-bottom:var(--space-sm);">\u95EE\u7B54\u8BE6\u60C5</h3>';
|
|
6773
|
+
html += '<div class="message-meta" style="margin-bottom:var(--space-sm);"><span>\u72B6\u6001\uFF1A' + escapeHtml(item.status) + '</span><span>\u521B\u5EFA\uFF1A' + escapeHtml(formatDateTime(item.createdAt)) + '</span><span>\u8017\u65F6\uFF1A' + escapeHtml(trace.durationMs == null ? '-' : trace.durationMs + 'ms') + '</span></div>';
|
|
6774
|
+
if (item.error) html += '<div style="color:var(--danger);margin-bottom:var(--space-sm);">\u9519\u8BEF\uFF1A' + escapeHtml(item.error) + '</div>';
|
|
6775
|
+
html += '<div class="qa-question">' + escapeHtml(item.question) + '</div>';
|
|
6776
|
+
html += '<div class="qa-answer" style="margin-bottom:var(--space-md);">' + escapeHtml(item.answer) + '</div>';
|
|
6777
|
+
if (!item.hasTrace) {
|
|
6778
|
+
html += '<div class="empty-state">\u8FD9\u6761\u95EE\u7B54\u6CA1\u6709 trace\uFF0C\u53EF\u80FD\u6765\u81EA\u65E7\u7248\u672C\u8BB0\u5F55\u3002</div></div>';
|
|
6779
|
+
container.innerHTML = html;
|
|
6780
|
+
return;
|
|
6781
|
+
}
|
|
6782
|
+
var turns = trace.modelTurns || [];
|
|
6783
|
+
html += '<h4 style="margin:var(--space-md) 0 var(--space-sm);">Reasoning</h4>';
|
|
6784
|
+
if (turns.length === 0) html += '<div class="empty-state">\u65E0 reasoningContent</div>';
|
|
6785
|
+
for (var i = 0; i < turns.length; i++) {
|
|
6786
|
+
html += '<div style="margin-bottom:var(--space-sm);"><div class="message-meta"><span>\u6A21\u578B\u8F6E\u6B21 ' + escapeHtml(turns[i].index) + '</span><span>' + escapeHtml(formatDateTime(turns[i].createdAt)) + '</span></div>' + renderTextBlock(turns[i].reasoningContent || '\u65E0 reasoningContent') + '</div>';
|
|
6787
|
+
}
|
|
6788
|
+
html += '<h4 style="margin:var(--space-md) 0 var(--space-sm);">\u6A21\u578B\u8F6E\u6B21\u4E0E\u5DE5\u5177\u8C03\u7528</h4>';
|
|
6789
|
+
for (var j = 0; j < turns.length; j++) {
|
|
6790
|
+
html += '<div style="margin-bottom:var(--space-sm);"><div class="message-meta"><span>\u8F6E\u6B21 ' + escapeHtml(turns[j].index) + '</span></div>' + renderTextBlock(turns[j].content || '') + renderJson(turns[j].toolCalls || []) + '</div>';
|
|
6791
|
+
}
|
|
6792
|
+
html += '<h4 style="margin:var(--space-md) 0 var(--space-sm);">\u5DE5\u5177\u7ED3\u679C</h4>';
|
|
6793
|
+
var toolResults = trace.toolResults || [];
|
|
6794
|
+
if (toolResults.length === 0) html += '<div class="empty-state">\u6CA1\u6709\u5DE5\u5177\u7ED3\u679C\u3002</div>';
|
|
6795
|
+
for (var k = 0; k < toolResults.length; k++) {
|
|
6796
|
+
html += '<div style="margin-bottom:var(--space-sm);"><div class="message-meta"><span>' + escapeHtml(toolResults[k].name) + '</span><span>' + escapeHtml(toolResults[k].toolCallId) + '</span><span>' + escapeHtml(formatDateTime(toolResults[k].createdAt)) + '</span></div>' + renderJson(toolResults[k].input) + (toolResults[k].error ? '<div style="color:var(--danger);">' + escapeHtml(toolResults[k].error) + '</div>' : renderTextBlock(toolResults[k].content || '')) + '</div>';
|
|
6797
|
+
}
|
|
6798
|
+
html += '<h4 style="margin:var(--space-md) 0 var(--space-sm);">\u5F15\u7528\u4E0E\u68C0\u7D22</h4>' + renderJson({ citations: item.citations || [], retrievalDebug: item.retrievalDebug || {} });
|
|
6799
|
+
html += '<h4 style="margin:var(--space-md) 0 var(--space-sm);">Fallback</h4>';
|
|
6800
|
+
var fallbacks = trace.fallbacks || [];
|
|
6801
|
+
html += fallbacks.length === 0 ? '<div class="empty-state">\u6CA1\u6709 fallback\u3002</div>' : renderJson(fallbacks);
|
|
6802
|
+
html += '</div>';
|
|
6803
|
+
container.innerHTML = html;
|
|
6804
|
+
}
|
|
6805
|
+
|
|
6635
6806
|
function renderSettings(status) {
|
|
6636
6807
|
var el = document.getElementById("settings-config");
|
|
6637
6808
|
var html = '<h3 style="font-size:16px;font-weight:600;margin-bottom:var(--space-md);">\u7CFB\u7EDF\u914D\u7F6E</h3>';
|
|
@@ -6666,7 +6837,10 @@ function buildHtml() {
|
|
|
6666
6837
|
if (currentView === "episodes") renderEpisodesView();
|
|
6667
6838
|
if (currentView === "files") renderFilesView();
|
|
6668
6839
|
if (currentView === "tasks") renderTasksView();
|
|
6669
|
-
if (currentView === "qa-logs")
|
|
6840
|
+
if (currentView === "qa-logs") {
|
|
6841
|
+
renderQaLogsView();
|
|
6842
|
+
if (selectedQaLogId) void showQaLogDetail(selectedQaLogId);
|
|
6843
|
+
}
|
|
6670
6844
|
}
|
|
6671
6845
|
|
|
6672
6846
|
async function processNow() {
|
|
@@ -6688,6 +6862,11 @@ function buildHtml() {
|
|
|
6688
6862
|
document.addEventListener("click", async function(event) {
|
|
6689
6863
|
var target = event.target;
|
|
6690
6864
|
if (!(target instanceof HTMLElement)) return;
|
|
6865
|
+
var qaLogId = target.dataset.viewQaLog;
|
|
6866
|
+
if (qaLogId) {
|
|
6867
|
+
void showQaLogDetail(qaLogId);
|
|
6868
|
+
return;
|
|
6869
|
+
}
|
|
6691
6870
|
var id = target.dataset.deleteCronJob;
|
|
6692
6871
|
if (!id) return;
|
|
6693
6872
|
target.disabled = true;
|
|
@@ -6731,6 +6910,10 @@ function parseCookies(header) {
|
|
|
6731
6910
|
function isAuthorizedWebAction(request, token) {
|
|
6732
6911
|
return parseCookies(request.headers.cookie).chattercatcher_web_token === token;
|
|
6733
6912
|
}
|
|
6913
|
+
function toQaLogListItem(log) {
|
|
6914
|
+
const { trace: _trace, ...item } = log;
|
|
6915
|
+
return item;
|
|
6916
|
+
}
|
|
6734
6917
|
function createWebApp(config, options = {}) {
|
|
6735
6918
|
const app = Fastify({ logger: false });
|
|
6736
6919
|
const database = openDatabase(config);
|
|
@@ -6809,9 +6992,18 @@ function createWebApp(config, options = {}) {
|
|
|
6809
6992
|
app.get("/api/qa-logs", async (request) => {
|
|
6810
6993
|
const limit = parseLimit(request.query.limit, 20, 100);
|
|
6811
6994
|
return {
|
|
6812
|
-
items: qaLogs.listRecent(limit)
|
|
6995
|
+
items: qaLogs.listRecent(limit).map(toQaLogListItem)
|
|
6813
6996
|
};
|
|
6814
6997
|
});
|
|
6998
|
+
app.get("/api/qa-logs/:id", async (request, reply) => {
|
|
6999
|
+
const id = request.params.id;
|
|
7000
|
+
const log = qaLogs.getById(id);
|
|
7001
|
+
if (!log) {
|
|
7002
|
+
reply.code(404);
|
|
7003
|
+
return { ok: false, message: "\u6CA1\u6709\u627E\u5230\u95EE\u7B54\u65E5\u5FD7\u3002" };
|
|
7004
|
+
}
|
|
7005
|
+
return log;
|
|
7006
|
+
});
|
|
6815
7007
|
app.get("/api/cron-jobs", async (request) => {
|
|
6816
7008
|
const limit = parseLimit(request.query.limit, 50, 200);
|
|
6817
7009
|
return {
|