@musnows/scriverse 0.4.12 → 0.5.0
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/ai.js +175 -4
- package/dist/ai.js.map +1 -1
- package/dist/app.js +30 -11
- package/dist/app.js.map +1 -1
- package/dist/database.js +55 -0
- package/dist/database.js.map +1 -1
- package/dist/pagination.js +1 -1
- package/dist/public/app.js +400 -74
- package/dist/public/global-search.d.ts +12 -0
- package/dist/public/global-search.js +23 -0
- package/dist/public/index.html +17 -4
- package/dist/public/relationship-filters.d.ts +10 -0
- package/dist/public/relationship-filters.js +11 -0
- package/dist/public/relationship-graph.js +59 -3
- package/dist/public/styles.css +209 -2
- package/dist/store.js +110 -32
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +12 -0
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/ai.js
CHANGED
|
@@ -33,6 +33,59 @@ function thinkingParameters(provider, model) {
|
|
|
33
33
|
return { thinking: { type: boolValue(model, "thinking_enabled") ? "enabled" : "disabled" } };
|
|
34
34
|
}
|
|
35
35
|
const AGENT_TOOL_IDS = ["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections"];
|
|
36
|
+
function redactProviderSecret(value, apiKey) {
|
|
37
|
+
if (!apiKey)
|
|
38
|
+
return value;
|
|
39
|
+
return value.split(apiKey).join("[REDACTED]");
|
|
40
|
+
}
|
|
41
|
+
function redactProviderSecrets(value, apiKey, depth = 0) {
|
|
42
|
+
if (typeof value === "string")
|
|
43
|
+
return redactProviderSecret(value, apiKey);
|
|
44
|
+
if (value === null || typeof value === "number" || typeof value === "boolean")
|
|
45
|
+
return value;
|
|
46
|
+
if (depth >= 32)
|
|
47
|
+
return "[REDACTED_DEPTH_LIMIT]";
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
return value.map((item) => redactProviderSecrets(item, apiKey, depth + 1));
|
|
50
|
+
if (!value || typeof value !== "object")
|
|
51
|
+
return null;
|
|
52
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactProviderSecrets(item, apiKey, depth + 1)]));
|
|
53
|
+
}
|
|
54
|
+
function sanitizeCompletionTraceResponse(value) {
|
|
55
|
+
const response = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
56
|
+
const choices = Array.isArray(response.choices) ? response.choices : [];
|
|
57
|
+
return {
|
|
58
|
+
choices: choices.map((choice) => {
|
|
59
|
+
const choiceRecord = choice && typeof choice === "object" && !Array.isArray(choice) ? choice : {};
|
|
60
|
+
const message = choiceRecord.message && typeof choiceRecord.message === "object" && !Array.isArray(choiceRecord.message)
|
|
61
|
+
? choiceRecord.message
|
|
62
|
+
: {};
|
|
63
|
+
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
64
|
+
return {
|
|
65
|
+
finish_reason: typeof choiceRecord.finish_reason === "string" || choiceRecord.finish_reason === null ? choiceRecord.finish_reason : null,
|
|
66
|
+
message: {
|
|
67
|
+
content: typeof message.content === "string" || message.content === null ? message.content : null,
|
|
68
|
+
reasoning_content: typeof message.reasoning_content === "string" || message.reasoning_content === null ? message.reasoning_content : null,
|
|
69
|
+
tool_calls: toolCalls.map((toolCall) => {
|
|
70
|
+
const toolCallRecord = toolCall && typeof toolCall === "object" && !Array.isArray(toolCall) ? toolCall : {};
|
|
71
|
+
const fn = toolCallRecord.function && typeof toolCallRecord.function === "object" && !Array.isArray(toolCallRecord.function)
|
|
72
|
+
? toolCallRecord.function
|
|
73
|
+
: {};
|
|
74
|
+
return {
|
|
75
|
+
id: typeof toolCallRecord.id === "string" ? toolCallRecord.id : "",
|
|
76
|
+
type: "function",
|
|
77
|
+
function: {
|
|
78
|
+
name: typeof fn.name === "string" ? fn.name : "",
|
|
79
|
+
arguments: fn.arguments ?? ""
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}),
|
|
86
|
+
...(response.usage && typeof response.usage === "object" && !Array.isArray(response.usage) ? { usage: response.usage } : {})
|
|
87
|
+
};
|
|
88
|
+
}
|
|
36
89
|
const MAX_AGENT_TOOL_ROUNDS = 6;
|
|
37
90
|
const MAX_AGENT_TOOL_CALLS = 12;
|
|
38
91
|
const MAX_CONFIGURED_AGENT_TOOL_CALLS = 48;
|
|
@@ -1223,6 +1276,7 @@ export class AiManager {
|
|
|
1223
1276
|
return this.store.db.all("SELECT * FROM ai_calls WHERE work_id = ? ORDER BY created_at DESC LIMIT 200", workId).map((row) => ({
|
|
1224
1277
|
id: stringValue(row, "id"),
|
|
1225
1278
|
workId: stringValue(row, "work_id"),
|
|
1279
|
+
taskId: row.task_id === null ? null : stringValue(row, "task_id"),
|
|
1226
1280
|
taskType: stringValue(row, "task_type"),
|
|
1227
1281
|
provider: this.getProvider(stringValue(row, "provider_id")),
|
|
1228
1282
|
model: this.getModel(stringValue(row, "model_id")),
|
|
@@ -1243,6 +1297,7 @@ export class AiManager {
|
|
|
1243
1297
|
return paginated(rows.map((row) => ({
|
|
1244
1298
|
id: stringValue(row, "id"),
|
|
1245
1299
|
workId: stringValue(row, "work_id"),
|
|
1300
|
+
taskId: row.task_id === null ? null : stringValue(row, "task_id"),
|
|
1246
1301
|
taskType: stringValue(row, "task_type"),
|
|
1247
1302
|
provider: this.getProvider(stringValue(row, "provider_id")),
|
|
1248
1303
|
model: this.getModel(stringValue(row, "model_id")),
|
|
@@ -1256,6 +1311,55 @@ export class AiManager {
|
|
|
1256
1311
|
completedAt: row.completed_at === null ? null : stringValue(row, "completed_at")
|
|
1257
1312
|
})), pagination);
|
|
1258
1313
|
}
|
|
1314
|
+
getTaskTrace(taskId) {
|
|
1315
|
+
this.store.getTask(taskId);
|
|
1316
|
+
const rows = this.store.db.all(`SELECT call.*, trace.initial_messages_json, trace.rounds_json, trace.created_at AS trace_created_at,
|
|
1317
|
+
trace.updated_at AS trace_updated_at, provider.name AS provider_name,
|
|
1318
|
+
model.display_name AS model_display_name, model.model_id AS external_model_id
|
|
1319
|
+
FROM ai_calls call
|
|
1320
|
+
LEFT JOIN ai_call_traces trace ON trace.call_id = call.id
|
|
1321
|
+
LEFT JOIN providers provider ON provider.id = call.provider_id
|
|
1322
|
+
LEFT JOIN models model ON model.id = call.model_id
|
|
1323
|
+
WHERE call.task_id = ?
|
|
1324
|
+
ORDER BY call.created_at ASC, call.id ASC`, taskId);
|
|
1325
|
+
const calls = rows.map((row) => {
|
|
1326
|
+
const hasTrace = row.initial_messages_json !== null && row.initial_messages_json !== undefined;
|
|
1327
|
+
return {
|
|
1328
|
+
id: stringValue(row, "id"),
|
|
1329
|
+
taskType: stringValue(row, "task_type"),
|
|
1330
|
+
provider: {
|
|
1331
|
+
id: stringValue(row, "provider_id"),
|
|
1332
|
+
name: row.provider_name === null ? "已删除的供应商" : stringValue(row, "provider_name"),
|
|
1333
|
+
deleted: row.provider_name === null
|
|
1334
|
+
},
|
|
1335
|
+
model: {
|
|
1336
|
+
id: stringValue(row, "model_id"),
|
|
1337
|
+
displayName: row.model_display_name === null ? "已删除的模型" : stringValue(row, "model_display_name"),
|
|
1338
|
+
modelId: row.external_model_id === null ? null : stringValue(row, "external_model_id"),
|
|
1339
|
+
deleted: row.model_display_name === null
|
|
1340
|
+
},
|
|
1341
|
+
contextScope: json(stringValue(row, "context_scope_json"), {}),
|
|
1342
|
+
parameters: json(stringValue(row, "parameters_json"), {}),
|
|
1343
|
+
status: stringValue(row, "status"),
|
|
1344
|
+
failure: row.failure === null ? null : stringValue(row, "failure"),
|
|
1345
|
+
inputChars: numberValue(row, "input_chars"),
|
|
1346
|
+
outputChars: numberValue(row, "output_chars"),
|
|
1347
|
+
createdAt: stringValue(row, "created_at"),
|
|
1348
|
+
completedAt: row.completed_at === null ? null : stringValue(row, "completed_at"),
|
|
1349
|
+
trace: hasTrace ? {
|
|
1350
|
+
initialMessages: json(stringValue(row, "initial_messages_json"), []),
|
|
1351
|
+
rounds: json(stringValue(row, "rounds_json"), []),
|
|
1352
|
+
createdAt: stringValue(row, "trace_created_at"),
|
|
1353
|
+
updatedAt: stringValue(row, "trace_updated_at")
|
|
1354
|
+
} : null
|
|
1355
|
+
};
|
|
1356
|
+
});
|
|
1357
|
+
return {
|
|
1358
|
+
taskId,
|
|
1359
|
+
captured: calls.some((call) => call.trace !== null),
|
|
1360
|
+
calls
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1259
1363
|
async runTask(taskId, modelId) {
|
|
1260
1364
|
const task = this.store.getTask(taskId);
|
|
1261
1365
|
const workId = String(task.workId);
|
|
@@ -1310,6 +1414,7 @@ export class AiManager {
|
|
|
1310
1414
|
else {
|
|
1311
1415
|
const generated = await this.generate({
|
|
1312
1416
|
workId,
|
|
1417
|
+
taskId,
|
|
1313
1418
|
taskType: taskType === "book-analysis" ? "book-analysis" : "chapter-analysis",
|
|
1314
1419
|
instruction: "请基于上下文完成分析,给出有原文依据的中文结论。",
|
|
1315
1420
|
scope,
|
|
@@ -1771,8 +1876,20 @@ export class AiManager {
|
|
|
1771
1876
|
});
|
|
1772
1877
|
const callId = id("call");
|
|
1773
1878
|
const timestamp = now();
|
|
1774
|
-
|
|
1775
|
-
|
|
1879
|
+
const traceRounds = [];
|
|
1880
|
+
this.store.db.transaction(() => {
|
|
1881
|
+
this.store.db.run(`INSERT INTO ai_calls (id, work_id, task_id, task_type, provider_id, model_id, context_scope_json, parameters_json,
|
|
1882
|
+
status, input_chars, created_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?)`, callId, input.workId, input.taskId ?? null, input.taskType, stringValue(provider, "id"), stringValue(model, "id"), JSON.stringify(input.scope), JSON.stringify(parameters), context.length + input.instruction.length, timestamp, currentRequestActor()?.userId ?? null);
|
|
1883
|
+
if (input.taskId) {
|
|
1884
|
+
this.store.db.run(`INSERT INTO ai_call_traces (call_id, task_id, initial_messages_json, rounds_json, created_at, updated_at)
|
|
1885
|
+
VALUES (?, ?, ?, '[]', ?, ?)`, callId, input.taskId, JSON.stringify(messages), timestamp, timestamp);
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
const saveTrace = () => {
|
|
1889
|
+
if (!input.taskId)
|
|
1890
|
+
return;
|
|
1891
|
+
this.store.db.run("UPDATE ai_call_traces SET rounds_json = ?, updated_at = ? WHERE call_id = ?", JSON.stringify(traceRounds), now(), callId);
|
|
1892
|
+
};
|
|
1776
1893
|
const callStartedAt = process.hrtime.bigint();
|
|
1777
1894
|
logger.info("ai.call.started", {
|
|
1778
1895
|
callId,
|
|
@@ -1785,8 +1902,10 @@ export class AiManager {
|
|
|
1785
1902
|
instructionChars: input.instruction.length,
|
|
1786
1903
|
toolCount: tools.length
|
|
1787
1904
|
});
|
|
1905
|
+
let activeApiKey = "";
|
|
1788
1906
|
try {
|
|
1789
1907
|
const apiKey = this.decryptKey(provider);
|
|
1908
|
+
activeApiKey = apiKey;
|
|
1790
1909
|
const endpoint = `${normalizeBaseUrl(stringValue(provider, "base_url"))}/chat/completions`;
|
|
1791
1910
|
const timeoutMs = input.taskType === "book-analysis" || input.taskType === "relationship-analysis" ? 300_000 : 60_000;
|
|
1792
1911
|
const maximumAttempts = Math.round(clamp(input.maxAttempts ?? 3, 1, 5));
|
|
@@ -1795,10 +1914,32 @@ export class AiManager {
|
|
|
1795
1914
|
let totalInputTokens = 0;
|
|
1796
1915
|
let totalCachedInputTokens = 0;
|
|
1797
1916
|
const requestCompletion = async (toolChoice) => {
|
|
1917
|
+
const traceRound = {
|
|
1918
|
+
round: traceRounds.length + 1,
|
|
1919
|
+
requestedAt: now(),
|
|
1920
|
+
request: {
|
|
1921
|
+
model: stringValue(model, "model_id"),
|
|
1922
|
+
messages: structuredClone(completionMessages),
|
|
1923
|
+
parameters: structuredClone(parameters),
|
|
1924
|
+
tools: toolChoice === "auto" ? structuredClone(tools) : [],
|
|
1925
|
+
toolChoice
|
|
1926
|
+
},
|
|
1927
|
+
attempts: [],
|
|
1928
|
+
toolExecutions: []
|
|
1929
|
+
};
|
|
1930
|
+
traceRounds.push(traceRound);
|
|
1931
|
+
saveTrace();
|
|
1798
1932
|
let lastFailure = null;
|
|
1799
1933
|
for (let attempt = 1; attempt <= maximumAttempts; attempt += 1) {
|
|
1800
1934
|
let retryable = true;
|
|
1801
1935
|
const attemptStartedAt = process.hrtime.bigint();
|
|
1936
|
+
const traceAttempt = {
|
|
1937
|
+
attempt,
|
|
1938
|
+
startedAt: now(),
|
|
1939
|
+
status: "running"
|
|
1940
|
+
};
|
|
1941
|
+
traceRound.attempts.push(traceAttempt);
|
|
1942
|
+
saveTrace();
|
|
1802
1943
|
logger.info("ai.call.attempt_started", { callId, attempt, maximumAttempts, toolChoice });
|
|
1803
1944
|
try {
|
|
1804
1945
|
const candidate = await this.scheduleProviderRequest(provider, input.signal, async () => {
|
|
@@ -1837,7 +1978,12 @@ export class AiManager {
|
|
|
1837
1978
|
});
|
|
1838
1979
|
if (candidate.ok) {
|
|
1839
1980
|
try {
|
|
1840
|
-
const parsed = JSON.parse(candidate.body);
|
|
1981
|
+
const parsed = redactProviderSecrets(JSON.parse(candidate.body), apiKey);
|
|
1982
|
+
traceAttempt.completedAt = now();
|
|
1983
|
+
traceAttempt.status = "completed";
|
|
1984
|
+
traceAttempt.httpStatus = candidate.status;
|
|
1985
|
+
traceAttempt.response = sanitizeCompletionTraceResponse(parsed);
|
|
1986
|
+
saveTrace();
|
|
1841
1987
|
completionRequestCount += 1;
|
|
1842
1988
|
const cacheUsage = resolveInputCacheUsage(parsed.usage);
|
|
1843
1989
|
if (!cacheUsage)
|
|
@@ -1853,6 +1999,11 @@ export class AiManager {
|
|
|
1853
1999
|
}
|
|
1854
2000
|
}
|
|
1855
2001
|
lastFailure = new Error(`HTTP ${candidate.status}: ${candidate.body.slice(0, 500)}`);
|
|
2002
|
+
traceAttempt.completedAt = now();
|
|
2003
|
+
traceAttempt.status = "failed";
|
|
2004
|
+
traceAttempt.httpStatus = candidate.status;
|
|
2005
|
+
traceAttempt.failure = redactProviderSecret(`HTTP ${candidate.status}: ${candidate.body.slice(0, 2_000)}`, apiKey);
|
|
2006
|
+
saveTrace();
|
|
1856
2007
|
if (candidate.status !== 429 && candidate.status < 500) {
|
|
1857
2008
|
retryable = false;
|
|
1858
2009
|
throw lastFailure;
|
|
@@ -1860,6 +2011,14 @@ export class AiManager {
|
|
|
1860
2011
|
}
|
|
1861
2012
|
catch (error) {
|
|
1862
2013
|
lastFailure = error;
|
|
2014
|
+
if (traceAttempt.status === "running") {
|
|
2015
|
+
traceAttempt.completedAt = now();
|
|
2016
|
+
traceAttempt.status = "failed";
|
|
2017
|
+
traceAttempt.failure = error instanceof Error
|
|
2018
|
+
? redactProviderSecret(error.message.slice(0, 2_000), apiKey)
|
|
2019
|
+
: "AI request failed";
|
|
2020
|
+
saveTrace();
|
|
2021
|
+
}
|
|
1863
2022
|
logger.warn("ai.call.attempt_failed", {
|
|
1864
2023
|
callId,
|
|
1865
2024
|
attempt,
|
|
@@ -1921,6 +2080,8 @@ export class AiManager {
|
|
|
1921
2080
|
const execution = this.executeAgentTool(input.workId, toolCall);
|
|
1922
2081
|
logger.info("ai.tool_call.completed", { callId, toolName: execution.name, status: execution.status, round });
|
|
1923
2082
|
executedToolCalls.push(execution);
|
|
2083
|
+
traceRounds.at(-1)?.toolExecutions.push(execution);
|
|
2084
|
+
saveTrace();
|
|
1924
2085
|
processSteps.push({ id: id("process"), type: "tool", round, toolCall: execution, createdAt: execution.calledAt });
|
|
1925
2086
|
input.onToolCall?.(execution, round);
|
|
1926
2087
|
completionMessages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(execution.result) });
|
|
@@ -1966,7 +2127,7 @@ export class AiManager {
|
|
|
1966
2127
|
return { callId, content, outputTokens, ...(cacheHitPercent === undefined ? {} : { cacheHitPercent }), provider: this.mapProvider(provider), model: this.mapModel(model), context, toolCalls: executedToolCalls, processSteps };
|
|
1967
2128
|
}
|
|
1968
2129
|
catch (error) {
|
|
1969
|
-
const message = error instanceof Error ? error.message : "AI 调用失败";
|
|
2130
|
+
const message = error instanceof Error ? redactProviderSecret(error.message, activeApiKey) : "AI 调用失败";
|
|
1970
2131
|
this.store.db.run("UPDATE ai_calls SET status = 'failed', failure = ?, completed_at = ? WHERE id = ?", message, now(), callId);
|
|
1971
2132
|
logger.error("ai.call.failed", {
|
|
1972
2133
|
callId,
|
|
@@ -2170,6 +2331,7 @@ export class AiManager {
|
|
|
2170
2331
|
const chapter = this.store.getChapter(scope.chapterId);
|
|
2171
2332
|
const generated = await this.generateTaggedJson({
|
|
2172
2333
|
workId,
|
|
2334
|
+
taskId,
|
|
2173
2335
|
taskType: "chapter-analysis",
|
|
2174
2336
|
signal: this.taskSignal(taskId),
|
|
2175
2337
|
instruction: "分析本章并输出 JSON 对象,字段为 summary(1至3句)、events(数组)、characters(数组)、settings(数组)、evidence(数组,每项含 conclusion 和 quote)、uncertainties(数组)。",
|
|
@@ -2189,6 +2351,7 @@ export class AiManager {
|
|
|
2189
2351
|
async runTimelineAnalysis(workId, scope, modelId, taskId) {
|
|
2190
2352
|
const generated = await this.generateTaggedJson({
|
|
2191
2353
|
workId,
|
|
2354
|
+
taskId,
|
|
2192
2355
|
taskType: "timeline-analysis",
|
|
2193
2356
|
signal: this.taskSignal(taskId),
|
|
2194
2357
|
instruction: "抽取大事件候选并输出 JSON 数组。每项字段:name、description、eventType、timeLabel、timeSort(无法确定为 null)、location、impactScope、chapterIds、participantIds、evidence。必须区分发生时间与叙述时间;不确定时使用‘时间待定’。",
|
|
@@ -2228,6 +2391,7 @@ export class AiManager {
|
|
|
2228
2391
|
throw new AppError(409, "CHAPTERS_REQUIRED", "世界观分析范围内没有章节");
|
|
2229
2392
|
const generated = await this.generateTaggedJson({
|
|
2230
2393
|
workId,
|
|
2394
|
+
taskId,
|
|
2231
2395
|
taskType: "book-analysis",
|
|
2232
2396
|
signal: this.taskSignal(taskId),
|
|
2233
2397
|
instruction: [
|
|
@@ -2325,6 +2489,7 @@ export class AiManager {
|
|
|
2325
2489
|
return { candidates: [], callId: null };
|
|
2326
2490
|
const generated = await this.generateTaggedJson({
|
|
2327
2491
|
workId,
|
|
2492
|
+
taskId,
|
|
2328
2493
|
taskType: "book-analysis",
|
|
2329
2494
|
signal: this.taskSignal(taskId),
|
|
2330
2495
|
maxAttempts: 2,
|
|
@@ -2468,6 +2633,7 @@ export class AiManager {
|
|
|
2468
2633
|
async runConsistencyCheck(workId, scope, modelId, taskId) {
|
|
2469
2634
|
const generated = await this.generateTaggedJson({
|
|
2470
2635
|
workId,
|
|
2636
|
+
taskId,
|
|
2471
2637
|
taskType: "consistency-check",
|
|
2472
2638
|
signal: this.taskSignal(taskId),
|
|
2473
2639
|
instruction: "检查设定、人物状态、关系和时间是否冲突,输出 JSON 数组。每项字段:itemType、severity(low/medium/high)、title、description、entityRefs、evidence、suggestion。没有问题时输出 []。",
|
|
@@ -2523,6 +2689,7 @@ export class AiManager {
|
|
|
2523
2689
|
}).join("\n");
|
|
2524
2690
|
const generated = await this.generateTaggedJson({
|
|
2525
2691
|
workId,
|
|
2692
|
+
taskId,
|
|
2526
2693
|
taskType: "book-analysis",
|
|
2527
2694
|
signal: this.taskSignal(taskId),
|
|
2528
2695
|
scope: scope.type === "none" ? scope : { type: "none" },
|
|
@@ -2643,6 +2810,7 @@ export class AiManager {
|
|
|
2643
2810
|
async verifyCharacterTitlePairs(workId, pairs, modelId, taskId) {
|
|
2644
2811
|
const generated = await this.generateTaggedJson({
|
|
2645
2812
|
workId,
|
|
2813
|
+
taskId,
|
|
2646
2814
|
taskType: "book-analysis",
|
|
2647
2815
|
signal: this.taskSignal(taskId),
|
|
2648
2816
|
scope: { type: "none" },
|
|
@@ -2696,6 +2864,7 @@ export class AiManager {
|
|
|
2696
2864
|
const extractChunk = async (text, maxAttempts = 3) => {
|
|
2697
2865
|
const generated = await this.generateTaggedJson({
|
|
2698
2866
|
workId,
|
|
2867
|
+
taskId,
|
|
2699
2868
|
taskType: "book-analysis",
|
|
2700
2869
|
signal: this.taskSignal(taskId),
|
|
2701
2870
|
maxAttempts,
|
|
@@ -3073,6 +3242,7 @@ export class AiManager {
|
|
|
3073
3242
|
const extractChunk = async (text, maxAttempts = 3) => {
|
|
3074
3243
|
const generated = await this.generateTaggedJson({
|
|
3075
3244
|
workId,
|
|
3245
|
+
taskId,
|
|
3076
3246
|
taskType: "relationship-analysis",
|
|
3077
3247
|
signal: this.taskSignal(taskId),
|
|
3078
3248
|
maxAttempts,
|
|
@@ -3210,6 +3380,7 @@ export class AiManager {
|
|
|
3210
3380
|
const aggregationResults = await this.processChunks(evidenceBatches, Math.min(concurrency, 4), async (evidenceBatch) => {
|
|
3211
3381
|
const generated = await this.generateTaggedJson({
|
|
3212
3382
|
workId,
|
|
3383
|
+
taskId,
|
|
3213
3384
|
taskType: "relationship-analysis",
|
|
3214
3385
|
signal: this.taskSignal(taskId),
|
|
3215
3386
|
maxAttempts: 2,
|