@musnows/scriverse 0.7.13 → 0.8.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.
Files changed (44) hide show
  1. package/README.en.md +4 -0
  2. package/README.md +5 -0
  3. package/dist/ai-chat-tab-limit.js +14 -0
  4. package/dist/ai-chat-tab-limit.js.map +1 -0
  5. package/dist/ai-protocol.js +10 -2
  6. package/dist/ai-protocol.js.map +1 -1
  7. package/dist/ai-retry.js +52 -0
  8. package/dist/ai-retry.js.map +1 -0
  9. package/dist/ai.js +525 -117
  10. package/dist/ai.js.map +1 -1
  11. package/dist/app.js +41 -12
  12. package/dist/app.js.map +1 -1
  13. package/dist/cli-contract.js +2 -1
  14. package/dist/cli-contract.js.map +1 -1
  15. package/dist/database.js +127 -2
  16. package/dist/database.js.map +1 -1
  17. package/dist/domain.js +1 -0
  18. package/dist/domain.js.map +1 -1
  19. package/dist/public/ai-chat-tabs.js +75 -0
  20. package/dist/public/ai-request-manager.js +32 -15
  21. package/dist/public/app.js +1151 -252
  22. package/dist/public/background-task-center.d.ts +9 -0
  23. package/dist/public/background-task-center.js +18 -0
  24. package/dist/public/chapter-search.d.ts +6 -0
  25. package/dist/public/chapter-search.js +23 -0
  26. package/dist/public/character-filters.d.ts +3 -1
  27. package/dist/public/character-filters.js +17 -2
  28. package/dist/public/character-version.js +1 -0
  29. package/dist/public/display-labels.d.ts +1 -0
  30. package/dist/public/display-labels.js +5 -1
  31. package/dist/public/index.html +66 -25
  32. package/dist/public/model-config.d.ts +3 -1
  33. package/dist/public/model-config.js +13 -0
  34. package/dist/public/relationship-filters.d.ts +5 -0
  35. package/dist/public/relationship-filters.js +31 -0
  36. package/dist/public/relationship-graph.js +289 -58
  37. package/dist/public/styles.css +170 -32
  38. package/dist/server-runtime.js +6 -0
  39. package/dist/server-runtime.js.map +1 -1
  40. package/dist/store.js +77 -34
  41. package/dist/store.js.map +1 -1
  42. package/dist/version.js +6 -1
  43. package/dist/version.js.map +1 -1
  44. package/package.json +1 -1
package/dist/app.js CHANGED
@@ -9,8 +9,9 @@ import { rm } from "node:fs/promises";
9
9
  import { tmpdir } from "node:os";
10
10
  import { pipeline } from "node:stream/promises";
11
11
  import { z, ZodError } from "zod";
12
- import { AI_PROVIDER_PROTOCOLS } from "./ai-protocol.js";
12
+ import { AI_PROVIDER_PROTOCOLS, MAX_TOKENS_PARAMETERS } from "./ai-protocol.js";
13
13
  import { aiConversationExportContentDisposition, exportAiConversationMarkdown } from "./ai-conversation-export.js";
14
+ import { DEFAULT_AI_CHAT_TAB_LIMIT } from "./ai-chat-tab-limit.js";
14
15
  import { DEFAULT_AI_STREAM_IDLE_TIMEOUT_MS } from "./ai-stream-timeout.js";
15
16
  import { AttachmentStorage } from "./attachment-storage.js";
16
17
  import { AiManager } from "./ai.js";
@@ -20,12 +21,12 @@ import { CredentialVault } from "./credential-vault.js";
20
21
  import { Database } from "./database.js";
21
22
  import { assertSafeDocxArchive } from "./docx-security.js";
22
23
  import { EPUB_MIME_TYPE, epubContentDisposition } from "./epub-export.js";
23
- import { CREATABLE_ANALYSIS_TASK_TYPES, DRAFT_SETTING_MODULES, TASK_TYPES } from "./domain.js";
24
+ import { CHARACTER_GENDERS, CREATABLE_ANALYSIS_TASK_TYPES, DRAFT_SETTING_MODULES, TASK_TYPES } from "./domain.js";
24
25
  import { AppError } from "./errors.js";
25
26
  import { isOfficialGoogleVertexBaseUrl, parseGoogleServiceAccount } from "./google-vertex-auth.js";
26
27
  import { HYBRID_SEARCH_TYPES, MAXIMUM_WORK_SEARCH_QUERY_LENGTH, readableHybridSearchTypes } from "./hybrid-search.js";
27
28
  import { applyImportFileHints, parseNovelText } from "./parser.js";
28
- import { aiConversationTaskTypes, attachmentPermissionModules, RECYCLE_BIN_RETENTION_DAYS, Store, versionedEntityTypes } from "./store.js";
29
+ import { aiConversationTaskTypes, attachmentPermissionModules, RECYCLE_BIN_RETENTION_DAYS, Store, versionedEntityTypes, WORK_AGENT_TOOL_IDS } from "./store.js";
29
30
  import { paginated, parsePagination } from "./pagination.js";
30
31
  import { normalizeUploadFileName } from "./utils.js";
31
32
  import { assertSafeAiEndpoint, assertSafeS3Endpoint, createApiRateLimitMiddleware, createAuthenticationRateLimitMiddleware, createBasicAuthMiddleware, createCaptchaRateLimitMiddleware, createExpensiveApiRateLimitMiddleware, createSameOriginMiddleware, createSecurityHeadersMiddleware, createUploadRateLimitMiddleware, enforceCaseInsensitiveRouting, normalizeApiPath, resolveTrustProxySetting, verifySetupToken } from "./security.js";
@@ -189,7 +190,8 @@ const settingSchema = z.object({
189
190
  const globalReplaceSchema = z.object({
190
191
  find: z.string().min(1).max(500),
191
192
  replacement: z.string().max(200_000),
192
- scope: z.enum(["prose", "settings", "prose-and-settings"])
193
+ scope: z.enum(["prose", "settings", "prose-and-settings"]),
194
+ volumeId: identifier.nullable().optional()
193
195
  }).strict();
194
196
  const draftSchema = z.object({
195
197
  draftType: z.enum(["prose", "setting"]),
@@ -200,6 +202,7 @@ const draftSchema = z.object({
200
202
  }).strict();
201
203
  const characterSchema = z.object({
202
204
  name: nonEmpty.max(200),
205
+ gender: z.enum(CHARACTER_GENDERS).optional(),
203
206
  isDead: z.boolean().optional(),
204
207
  code: z.string().trim().max(200).optional(),
205
208
  aliases: z.array(z.string().trim().min(1).max(200)).max(100).optional(),
@@ -344,6 +347,7 @@ const providerBaseSchema = z.object({
344
347
  baseUrl: z.string().url().refine((value) => value.startsWith("http://") || value.startsWith("https://"), "接口地址必须使用 HTTP 或 HTTPS"),
345
348
  apiKey: z.string().trim().min(1).max(50_000),
346
349
  protocol: z.enum(AI_PROVIDER_PROTOCOLS).optional(),
350
+ maxTokensParameter: z.enum(MAX_TOKENS_PARAMETERS).optional(),
347
351
  status: z.enum(["enabled", "disabled"]).optional(),
348
352
  note: z.string().max(10_000).optional(),
349
353
  concurrencyLimit: z.number().int().min(1).max(100).optional(),
@@ -396,6 +400,7 @@ const modelSchema = z.object({
396
400
  outputNote: z.string().max(10_000).optional(),
397
401
  preset: jsonObject.optional(),
398
402
  thinkingEnabled: z.boolean().optional(),
403
+ thinkingEffort: z.enum(["default", "low", "medium", "high", "xhigh", "max"]).optional(),
399
404
  multimodalEnabled: z.boolean().optional(),
400
405
  imageToolDefault: z.boolean().optional(),
401
406
  enabled: z.boolean().optional(),
@@ -536,11 +541,12 @@ const workAiSettingsSchema = z.object({
536
541
  autoRunBatchLimit: z.number().int().min(1).max(200).optional(),
537
542
  autoRunDailyTaskLimit: z.number().int().min(0).max(10_000).optional(),
538
543
  autoRunFailureThreshold: z.number().int().min(1).max(10).optional(),
544
+ autoRunStabilityDelayMinutes: z.number().int().min(1).max(120).optional(),
539
545
  bookSummaryContextPercent: z.number().int().min(1).max(90).optional(),
540
546
  contextCompactThreshold: z.number().int().min(50).max(90).optional(),
541
547
  agentToolCallLimit: z.number().int().min(5).optional(),
542
548
  agentToolCallGlobalMultiplier: z.number().int().min(1).max(6).optional(),
543
- agentTools: z.array(z.enum(["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections", "search_drafts", "image"])).max(7).optional(),
549
+ agentTools: z.array(z.enum(WORK_AGENT_TOOL_IDS)).max(WORK_AGENT_TOOL_IDS.length).optional(),
544
550
  alwaysIncludeSettingInfo: z.boolean().optional(),
545
551
  titleGenerationModelId: z.string().trim().max(200).optional(),
546
552
  imageToolModelId: identifier.nullable().optional()
@@ -605,8 +611,8 @@ const relationshipAnalysisScopeSchema = z.object({
605
611
  if (scope.volumeIds && new Set(scope.volumeIds).size !== scope.volumeIds.length) {
606
612
  context.addIssue({ code: z.ZodIssueCode.custom, path: ["volumeIds"], message: "分卷不能重复选择" });
607
613
  }
608
- if (scope.includeAllSettings && scope.type !== "book") {
609
- context.addIssue({ code: z.ZodIssueCode.custom, path: ["includeAllSettings"], message: "包含所有设定仅支持全书人物关系分析" });
614
+ if (scope.includeAllSettings && scope.type !== "book" && scope.type !== "chapter") {
615
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["includeAllSettings"], message: "包含所有设定仅支持全书或指定章节人物关系分析" });
610
616
  }
611
617
  if (scope.characterIds && new Set(scope.characterIds).size !== scope.characterIds.length) {
612
618
  context.addIssue({ code: z.ZodIssueCode.custom, path: ["characterIds"], message: "被分析角色不能重复" });
@@ -949,6 +955,13 @@ function redactAiConversation(record, permissions) {
949
955
  const result = {
950
956
  ...scopedRecord
951
957
  };
958
+ if (result.roleplayCharacter) {
959
+ result.agentTools = [
960
+ ...(permissions.characters !== "none" ? ["recall_self"] : []),
961
+ ...(permissions.characters !== "none" && permissions.relationships !== "none" ? ["recall_relationship"] : []),
962
+ "calculate_time"
963
+ ];
964
+ }
952
965
  if ((permissions.prose === "none" || permissions.characters === "none") && Array.isArray(result.messages)) {
953
966
  result.messages = result.messages.map((item) => redactAiConversationMessage(item, permissions));
954
967
  }
@@ -1007,6 +1020,7 @@ function redactVersionSnapshots(value, mapper) {
1007
1020
  }
1008
1021
  export function createRuntime(options) {
1009
1022
  const uploadLimits = options.uploadLimits ?? DEFAULT_IMAGE_UPLOAD_LIMITS;
1023
+ const aiChatTabLimit = options.aiChatTabLimit ?? DEFAULT_AI_CHAT_TAB_LIMIT;
1010
1024
  logger.info("runtime.initializing", {
1011
1025
  databasePath: options.databasePath,
1012
1026
  serveUi: options.serveUi ?? true,
@@ -1124,7 +1138,11 @@ export function createRuntime(options) {
1124
1138
  read: requiredModules,
1125
1139
  write: ["ai-analysis"]
1126
1140
  }, false, actor?.allowAdminAccess ?? false);
1127
- }, attachmentStorage, { interactiveStreamIdleTimeoutMs: options.aiStreamIdleTimeoutMs ?? DEFAULT_AI_STREAM_IDLE_TIMEOUT_MS });
1141
+ }, attachmentStorage, {
1142
+ interactiveStreamIdleTimeoutMs: options.aiStreamIdleTimeoutMs ?? DEFAULT_AI_STREAM_IDLE_TIMEOUT_MS,
1143
+ retryPolicy: options.aiRetryPolicy,
1144
+ retrySleep: options.aiRetrySleep
1145
+ });
1128
1146
  const app = express();
1129
1147
  enforceCaseInsensitiveRouting(app);
1130
1148
  const upload = multer({
@@ -1160,9 +1178,11 @@ export function createRuntime(options) {
1160
1178
  status: "ok",
1161
1179
  bootId,
1162
1180
  version: APP_VERSION,
1181
+ versionLabel: options.betaVersionLabel ?? null,
1163
1182
  protocol: "openai-chat-completions",
1164
1183
  protocols: [...AI_PROVIDER_PROTOCOLS],
1165
1184
  development: options.developmentServer === true,
1185
+ aiChatTabLimit,
1166
1186
  uploadLimits
1167
1187
  });
1168
1188
  });
@@ -2173,9 +2193,16 @@ export function createRuntime(options) {
2173
2193
  }
2174
2194
  data(response, await ai.previewRelationshipSources(request.params.workId, input.scope, input.modelId));
2175
2195
  });
2176
- app.post("/api/works/:workId/tasks", (request, response) => {
2196
+ app.post("/api/works/:workId/tasks", async (request, response) => {
2177
2197
  const input = parse(analysisTaskSchema, request.body);
2178
- data(response, redactTaskCharacterNames(ai.createTask(request.params.workId, input), requestPermissions(request, request.params.workId)), 201);
2198
+ const permissions = requestPermissions(request, request.params.workId);
2199
+ const deniedModules = analysisTaskReadModules(input.taskType, input.scope).filter((module) => permissions[module] === "none");
2200
+ if (deniedModules.length > 0) {
2201
+ throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取创建分析任务所需资料模块的权限", {
2202
+ modules: deniedModules
2203
+ });
2204
+ }
2205
+ data(response, redactTaskCharacterNames(await ai.createTask(request.params.workId, input), permissions), 201);
2179
2206
  });
2180
2207
  app.post("/api/works/:workId/tasks/auto-run", (request, response) => {
2181
2208
  const permissions = requestPermissions(request, request.params.workId);
@@ -2223,7 +2250,7 @@ export function createRuntime(options) {
2223
2250
  allowAdminAccess: request.authMethod !== "api-key"
2224
2251
  } : undefined), requestPermissions(request)));
2225
2252
  });
2226
- app.post("/api/tasks/:taskId/rerun", (request, response) => {
2253
+ app.post("/api/tasks/:taskId/rerun", async (request, response) => {
2227
2254
  const input = parse(z.object({ modelId: identifier.optional() }).strict(), request.body ?? {});
2228
2255
  const task = store.getTask(request.params.taskId);
2229
2256
  const permissions = requestPermissions(request, String(task.workId));
@@ -2233,7 +2260,7 @@ export function createRuntime(options) {
2233
2260
  modules: deniedModules
2234
2261
  });
2235
2262
  }
2236
- data(response, redactTaskCharacterNames(ai.rerunTask(request.params.taskId, input.modelId), requestPermissions(request)), 201);
2263
+ data(response, redactTaskCharacterNames(await ai.rerunTask(request.params.taskId, input.modelId), requestPermissions(request)), 201);
2237
2264
  });
2238
2265
  app.post("/api/tasks/:taskId/cancel", (request, response) => data(response, redactTaskCharacterNames(ai.cancelTask(request.params.taskId), requestPermissions(request))));
2239
2266
  app.post("/api/tasks/:taskId/character-extraction/apply", (request, response) => {
@@ -2353,6 +2380,8 @@ export function createRuntime(options) {
2353
2380
  else
2354
2381
  ai.scheduleAutoRun(workId);
2355
2382
  }
2383
+ if (input.autoRunStabilityDelayMinutes !== undefined)
2384
+ ai.rescheduleChapterAnalysisTasks(workId);
2356
2385
  data(response, updated);
2357
2386
  });
2358
2387
  app.get("/api/works/:workId/ai-conversations", (request, response) => {