@musnows/scriverse 0.5.1 → 0.5.3
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/LICENSE +661 -21
- package/README.en.md +8 -0
- package/README.md +8 -0
- package/dist/ai.js +1259 -163
- package/dist/ai.js.map +1 -1
- package/dist/app.js +138 -19
- package/dist/app.js.map +1 -1
- package/dist/database.js +340 -0
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +171 -56
- package/dist/public/display-labels.js +1 -0
- package/dist/public/index.html +13 -7
- package/dist/public/styles.css +74 -6
- package/dist/relationship-search.js +196 -0
- package/dist/relationship-search.js.map +1 -0
- package/dist/store.js +909 -13
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +23 -6
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
package/dist/app.js
CHANGED
|
@@ -28,7 +28,7 @@ import { currentRequestActor, runWithRequestActor } from "./request-context.js";
|
|
|
28
28
|
import { APP_VERSION } from "./version.js";
|
|
29
29
|
import { fullWorkModulePermissions, proseReplacementPermissionModules } from "./work-permissions.js";
|
|
30
30
|
import { CollaborationPresence, editorPageKey, entityEditorPageKey, modulePageKey, pageLabelForKey, presencePageKinds } from "./collaboration-presence.js";
|
|
31
|
-
import { clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, setSessionCookie, UserAuthService } from "./user-auth.js";
|
|
31
|
+
import { clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, relationshipAnalysisReadModules, setSessionCookie, UserAuthService } from "./user-auth.js";
|
|
32
32
|
const nonEmpty = z.string().trim().min(1);
|
|
33
33
|
const identifier = z.string().trim().min(1).max(200);
|
|
34
34
|
const optionalStrings = z.array(z.string()).optional();
|
|
@@ -358,7 +358,7 @@ const contextSchema = z.object({
|
|
|
358
358
|
});
|
|
359
359
|
const analysisTaskTypeSchema = z.enum(["structure", "chapter-analysis", "character-extraction", "character-summary", "character-identity-audit", "timeline-analysis", "worldview-analysis", "setting-extraction", "consistency-check", "report-update", "book-analysis"]);
|
|
360
360
|
const relationshipAnalysisScopeSchema = z.object({
|
|
361
|
-
type: z.enum(["chapter", "book"]),
|
|
361
|
+
type: z.enum(["chapter", "book", "settings"]),
|
|
362
362
|
chapterId: identifier.optional(),
|
|
363
363
|
includeAllSettings: z.boolean().optional(),
|
|
364
364
|
additionalPrompt: z.string().trim().max(10_000).optional(),
|
|
@@ -379,8 +379,8 @@ const relationshipAnalysisScopeSchema = z.object({
|
|
|
379
379
|
}
|
|
380
380
|
});
|
|
381
381
|
const analysisTaskSchema = z.union([
|
|
382
|
-
z.object({ taskType: z.literal("relationship-analysis"), scope: relationshipAnalysisScopeSchema.optional() }).strict(),
|
|
383
|
-
z.object({ taskType: analysisTaskTypeSchema, scope: jsonObject.optional() }).strict().superRefine((input, context) => {
|
|
382
|
+
z.object({ taskType: z.literal("relationship-analysis"), scope: relationshipAnalysisScopeSchema.optional(), modelId: identifier.optional() }).strict(),
|
|
383
|
+
z.object({ taskType: analysisTaskTypeSchema, scope: jsonObject.optional(), modelId: identifier.optional() }).strict().superRefine((input, context) => {
|
|
384
384
|
if (input.scope?.includeAllSettings !== undefined) {
|
|
385
385
|
context.addIssue({ code: z.ZodIssueCode.custom, path: ["scope", "includeAllSettings"], message: "包含所有设定仅支持人物关系分析" });
|
|
386
386
|
}
|
|
@@ -434,16 +434,84 @@ function redactRaceMembers(record, permissions) {
|
|
|
434
434
|
function redactOrganizationMembers(record, permissions) {
|
|
435
435
|
return permissions.characters === "none" ? { ...record, memberIds: [], members: [] } : record;
|
|
436
436
|
}
|
|
437
|
+
const taskResultPermissionModules = {
|
|
438
|
+
"chapter-analysis": "prose",
|
|
439
|
+
"character-extraction": "characters",
|
|
440
|
+
"character-summary": "characters",
|
|
441
|
+
"character-identity-audit": "reviews",
|
|
442
|
+
"timeline-analysis": "timeline",
|
|
443
|
+
"relationship-analysis": "relationships",
|
|
444
|
+
"worldview-analysis": "settings",
|
|
445
|
+
"setting-extraction": "settings",
|
|
446
|
+
"consistency-check": "reviews",
|
|
447
|
+
"book-analysis": "prose",
|
|
448
|
+
structure: "outlines",
|
|
449
|
+
"report-update": "prose"
|
|
450
|
+
};
|
|
451
|
+
function requiredTaskResultModule(taskType) {
|
|
452
|
+
return taskResultPermissionModules[String(taskType)];
|
|
453
|
+
}
|
|
437
454
|
function redactTaskCharacterNames(record, permissions) {
|
|
438
455
|
const { scopeSummaryWithoutCharacterNames, ...result } = record;
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
result.scopeSummary
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
456
|
+
const proseRestricted = permissions.prose === "none";
|
|
457
|
+
const proseScope = recordValue(result.scope);
|
|
458
|
+
const selectionScopeRestricted = proseRestricted
|
|
459
|
+
&& (proseScope?.type === "selection" || String(result.scopeSummary ?? "").startsWith("选定内容:"));
|
|
460
|
+
if (selectionScopeRestricted) {
|
|
461
|
+
if (proseScope) {
|
|
462
|
+
const { selection: _selection, ...redactedScope } = proseScope;
|
|
463
|
+
result.scope = redactedScope;
|
|
464
|
+
}
|
|
465
|
+
result.scopeSummary = "选定内容(正文读取权限受限)";
|
|
466
|
+
if (Array.isArray(result.scopeDetails)) {
|
|
467
|
+
result.scopeDetails = result.scopeDetails.map((item) => {
|
|
468
|
+
const detail = recordValue(item);
|
|
469
|
+
return detail?.type === "selection" ? { type: "selection", restricted: true } : item;
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const characterRestricted = permissions.characters === "none";
|
|
474
|
+
if (characterRestricted) {
|
|
475
|
+
if (!selectionScopeRestricted && typeof scopeSummaryWithoutCharacterNames === "string")
|
|
476
|
+
result.scopeSummary = scopeSummaryWithoutCharacterNames;
|
|
477
|
+
const scope = recordValue(result.scope);
|
|
478
|
+
if (scope) {
|
|
479
|
+
const { targetCharacters: _targetCharacters, ...redactedScope } = scope;
|
|
480
|
+
result.scope = redactedScope;
|
|
481
|
+
}
|
|
482
|
+
const taskResult = recordValue(result.result);
|
|
483
|
+
if (taskResult) {
|
|
484
|
+
const redactedTaskResult = { ...taskResult };
|
|
485
|
+
if (Array.isArray(taskResult.relationshipResults)) {
|
|
486
|
+
redactedTaskResult.relationshipResults = taskResult.relationshipResults.map((value) => {
|
|
487
|
+
const relationship = recordValue(value);
|
|
488
|
+
if (!relationship)
|
|
489
|
+
return value;
|
|
490
|
+
const { fromCharacterName: _fromCharacterName, toCharacterName: _toCharacterName, ...redactedRelationship } = relationship;
|
|
491
|
+
return redactedRelationship;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
const analysisTarget = recordValue(taskResult.analysisTarget);
|
|
495
|
+
if (analysisTarget) {
|
|
496
|
+
const { characterNames: _characterNames, ...redactedAnalysisTarget } = analysisTarget;
|
|
497
|
+
redactedTaskResult.analysisTarget = redactedAnalysisTarget;
|
|
498
|
+
}
|
|
499
|
+
result.result = redactedTaskResult;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const resultSummary = recordValue(result.resultSummary);
|
|
503
|
+
const characterSensitiveTask = ["character-extraction", "character-summary", "character-identity-audit", "relationship-analysis"]
|
|
504
|
+
.includes(String(result.taskType));
|
|
505
|
+
const requiredResultModule = requiredTaskResultModule(result.taskType);
|
|
506
|
+
const resultRestricted = requiredResultModule !== undefined && permissions[requiredResultModule] === "none";
|
|
507
|
+
if (resultSummary && (resultRestricted || (characterRestricted && characterSensitiveTask))) {
|
|
508
|
+
result.resultSummary = {
|
|
509
|
+
...resultSummary,
|
|
510
|
+
analysisContent: `${String(resultSummary.title ?? "AI 分析")};范围:${String(result.scopeSummary ?? "未指定")}`,
|
|
511
|
+
summary: "当前账号缺少相关资料的读取权限,无法展示对应的可读结论。",
|
|
512
|
+
sections: [],
|
|
513
|
+
restricted: true
|
|
514
|
+
};
|
|
447
515
|
}
|
|
448
516
|
return result;
|
|
449
517
|
}
|
|
@@ -502,7 +570,24 @@ export function createRuntime(options) {
|
|
|
502
570
|
return auth.workModulePermissions(request.authUser, resolvedWorkId, request.authMethod !== "api-key") ?? fullWorkModulePermissions();
|
|
503
571
|
};
|
|
504
572
|
const captcha = new ImageCaptchaService({ revealAnswer: options.revealCaptchaAnswer === true });
|
|
505
|
-
const ai = new AiManager(store, new CredentialVault(options.masterSecret), options.fetchImpl ?? fetch, options.security ? (url) => assertSafeAiEndpoint(url, options.security?.allowPrivateAiEndpoints) : undefined)
|
|
573
|
+
const ai = new AiManager(store, new CredentialVault(options.masterSecret), options.fetchImpl ?? fetch, options.security ? (url) => assertSafeAiEndpoint(url, options.security?.allowPrivateAiEndpoints) : undefined, (task, actor) => {
|
|
574
|
+
if (task.taskType !== "relationship-analysis")
|
|
575
|
+
return;
|
|
576
|
+
const requiredModules = relationshipAnalysisReadModules(task.scope);
|
|
577
|
+
if (requiredModules.length === 0)
|
|
578
|
+
return;
|
|
579
|
+
const creator = actor ? null : database.get("SELECT created_by_user_id FROM analysis_tasks WHERE id = ?", String(task.id));
|
|
580
|
+
const userId = actor?.userId ?? (typeof creator?.created_by_user_id === "string" ? creator.created_by_user_id : null);
|
|
581
|
+
if (!userId)
|
|
582
|
+
return;
|
|
583
|
+
const user = auth.getUser(userId);
|
|
584
|
+
if (user.status !== "active")
|
|
585
|
+
throw new AppError(403, "WORK_ACCESS_DENIED", "任务创建者已无法访问这部作品");
|
|
586
|
+
auth.assertWorkAccess(user, String(task.workId), {
|
|
587
|
+
read: requiredModules,
|
|
588
|
+
write: ["ai-analysis"]
|
|
589
|
+
}, false, actor?.allowAdminAccess ?? false);
|
|
590
|
+
});
|
|
506
591
|
const app = express();
|
|
507
592
|
const upload = multer({
|
|
508
593
|
storage: multer.memoryStorage(),
|
|
@@ -1349,20 +1434,54 @@ export function createRuntime(options) {
|
|
|
1349
1434
|
});
|
|
1350
1435
|
app.post("/api/works/:workId/tasks", (request, response) => {
|
|
1351
1436
|
const input = parse(analysisTaskSchema, request.body);
|
|
1352
|
-
data(response, redactTaskCharacterNames(
|
|
1437
|
+
data(response, redactTaskCharacterNames(ai.createTask(request.params.workId, input), requestPermissions(request, request.params.workId)), 201);
|
|
1353
1438
|
});
|
|
1354
1439
|
app.post("/api/works/:workId/tasks/auto-run", (request, response) => {
|
|
1440
|
+
const permissions = requestPermissions(request, request.params.workId);
|
|
1441
|
+
for (const taskId of store.listOldestPendingTaskIds(request.params.workId, store.countPendingTasks(request.params.workId))) {
|
|
1442
|
+
const task = store.getTask(taskId);
|
|
1443
|
+
if (task.taskType !== "relationship-analysis")
|
|
1444
|
+
continue;
|
|
1445
|
+
const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
|
|
1446
|
+
if (deniedModules.length > 0) {
|
|
1447
|
+
throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取待运行定向人物关系分析所需资料模块的权限", {
|
|
1448
|
+
taskId,
|
|
1449
|
+
modules: deniedModules
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1355
1453
|
data(response, ai.startAutoRunBatch(request.params.workId));
|
|
1356
1454
|
});
|
|
1455
|
+
app.get("/api/tasks/:taskId/detail", (request, response) => data(response, redactTaskCharacterNames(store.getTaskDetail(request.params.taskId), requestPermissions(request))));
|
|
1456
|
+
app.get("/api/tasks/:taskId/result", (request, response) => {
|
|
1457
|
+
const task = store.getTaskResultPayload(request.params.taskId);
|
|
1458
|
+
const permissions = requestPermissions(request);
|
|
1459
|
+
const requiredModule = requiredTaskResultModule(task.taskType);
|
|
1460
|
+
if (requiredModule && permissions[requiredModule] === "none") {
|
|
1461
|
+
throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取该分析结果对应资料模块的权限");
|
|
1462
|
+
}
|
|
1463
|
+
const redacted = redactTaskCharacterNames(task, permissions);
|
|
1464
|
+
data(response, { taskId: task.id, result: redacted.result });
|
|
1465
|
+
});
|
|
1357
1466
|
app.get("/api/tasks/:taskId", (request, response) => data(response, redactTaskCharacterNames(store.getTask(request.params.taskId), requestPermissions(request))));
|
|
1358
1467
|
app.get("/api/tasks/:taskId/trace", (request, response) => data(response, ai.getTaskTrace(request.params.taskId)));
|
|
1359
|
-
app.get("/api/tasks/:taskId/trace/calls/:callId", (request, response) =>
|
|
1360
|
-
const full = parse(z.enum(["true", "false"]).default("false"), request.query.full ?? "false") === "true";
|
|
1361
|
-
data(response, ai.getTaskTraceCall(request.params.taskId, request.params.callId, full));
|
|
1362
|
-
});
|
|
1468
|
+
app.get("/api/tasks/:taskId/trace/calls/:callId", (request, response) => data(response, ai.getTaskTraceCall(request.params.taskId, request.params.callId)));
|
|
1363
1469
|
app.post("/api/tasks/:taskId/run", async (request, response) => {
|
|
1364
1470
|
const input = parse(z.object({ modelId: identifier.optional() }), request.body ?? {});
|
|
1365
|
-
|
|
1471
|
+
const task = store.getTask(request.params.taskId);
|
|
1472
|
+
if (task.taskType === "relationship-analysis") {
|
|
1473
|
+
const permissions = requestPermissions(request, String(task.workId));
|
|
1474
|
+
const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
|
|
1475
|
+
if (deniedModules.length > 0) {
|
|
1476
|
+
throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次定向人物关系分析所需资料模块的权限", {
|
|
1477
|
+
modules: deniedModules
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
data(response, redactTaskCharacterNames(await ai.runTask(request.params.taskId, input.modelId, request.authUser ? {
|
|
1482
|
+
userId: request.authUser.userId,
|
|
1483
|
+
allowAdminAccess: request.authMethod !== "api-key"
|
|
1484
|
+
} : undefined), requestPermissions(request)));
|
|
1366
1485
|
});
|
|
1367
1486
|
app.post("/api/tasks/:taskId/cancel", (request, response) => data(response, redactTaskCharacterNames(ai.cancelTask(request.params.taskId), requestPermissions(request))));
|
|
1368
1487
|
app.get("/api/platform/ai/providers", (request, response) => {
|