@musnows/scriverse 0.6.7 → 0.6.9

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/store.js CHANGED
@@ -16,7 +16,8 @@ export const WORK_AGENT_TOOL_IDS = [
16
16
  "grep",
17
17
  "search_story_entities",
18
18
  "read_character_sections",
19
- "search_drafts"
19
+ "search_drafts",
20
+ "image"
20
21
  ];
21
22
  const DEFAULT_WORK_AGENT_TOOLS = [...WORK_AGENT_TOOL_IDS];
22
23
  export function normalizeWorkAgentTools(value) {
@@ -681,13 +682,20 @@ export class Store {
681
682
  const row = this.db.get("SELECT * FROM platform_ai_settings WHERE id = 1");
682
683
  return {
683
684
  systemPrompt: String(row?.system_prompt ?? ""),
685
+ imageToolModelId: row?.image_tool_model_id === null || row?.image_tool_model_id === undefined
686
+ ? null
687
+ : String(row.image_tool_model_id),
684
688
  updatedAt: String(row?.updated_at ?? "")
685
689
  };
686
690
  }
687
691
  updatePlatformAiSettings(input) {
688
692
  const timestamp = now();
689
- this.db.run(`INSERT INTO platform_ai_settings (id, system_prompt, updated_at) VALUES (1, ?, ?)
690
- ON CONFLICT(id) DO UPDATE SET system_prompt = excluded.system_prompt, updated_at = excluded.updated_at`, input.systemPrompt ?? String(this.getPlatformAiSettings().systemPrompt), timestamp);
693
+ const current = this.getPlatformAiSettings();
694
+ this.db.run(`INSERT INTO platform_ai_settings (id, system_prompt, image_tool_model_id, updated_at) VALUES (1, ?, ?, ?)
695
+ ON CONFLICT(id) DO UPDATE SET system_prompt = excluded.system_prompt,
696
+ image_tool_model_id = excluded.image_tool_model_id, updated_at = excluded.updated_at`, input.systemPrompt ?? String(current.systemPrompt), input.imageToolModelId === undefined
697
+ ? (current.imageToolModelId === null ? null : String(current.imageToolModelId))
698
+ : input.imageToolModelId, timestamp);
691
699
  return this.getPlatformAiSettings();
692
700
  }
693
701
  getPlatformUiSettings() {
@@ -754,6 +762,9 @@ export class Store {
754
762
  agentToolCallLimit: Math.min(48, Math.max(5, Number(row?.agent_tool_call_limit ?? 12) || 12)),
755
763
  agentToolCallGlobalMultiplier: Math.min(6, Math.max(1, Number(row?.agent_tool_call_global_multiplier ?? 3) || 3)),
756
764
  agentTools: normalizeWorkAgentTools(row?.agent_tools_json),
765
+ imageToolModelId: row?.image_tool_model_id === null || row?.image_tool_model_id === undefined
766
+ ? null
767
+ : String(row.image_tool_model_id),
757
768
  alwaysIncludeSettingInfo: Number(row?.always_include_setting_info ?? 0) === 1,
758
769
  titleGenerationModelId: row?.title_generation_model_id === null || row?.title_generation_model_id === undefined
759
770
  ? null
@@ -779,6 +790,9 @@ export class Store {
779
790
  const nextAgentToolCallLimit = input.agentToolCallLimit ?? Number(current.agentToolCallLimit);
780
791
  const nextAgentToolCallGlobalMultiplier = input.agentToolCallGlobalMultiplier ?? Number(current.agentToolCallGlobalMultiplier);
781
792
  const nextAgentTools = normalizeWorkAgentTools(input.agentTools ?? current.agentTools);
793
+ const nextImageToolModelId = input.imageToolModelId === undefined
794
+ ? (current.imageToolModelId ? String(current.imageToolModelId) : null)
795
+ : input.imageToolModelId?.trim() || null;
782
796
  const nextAlwaysIncludeSettingInfo = input.alwaysIncludeSettingInfo ?? Boolean(current.alwaysIncludeSettingInfo);
783
797
  const nextTitleGenerationModelId = input.titleGenerationModelId === undefined
784
798
  ? (current.titleGenerationModelId ? String(current.titleGenerationModelId) : null)
@@ -788,8 +802,8 @@ export class Store {
788
802
  auto_run_daily_task_limit, auto_run_failure_threshold, auto_run_paused, auto_run_pause_reason,
789
803
  auto_run_resume_at, auto_run_consecutive_failures, book_summary_context_percent,
790
804
  context_compact_threshold, agent_tool_call_limit, agent_tool_call_global_multiplier,
791
- agent_tools_json, title_generation_model_id, always_include_setting_info, updated_at
792
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
805
+ agent_tools_json, title_generation_model_id, image_tool_model_id, always_include_setting_info, updated_at
806
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
793
807
  ON CONFLICT(work_id) DO UPDATE SET
794
808
  system_prompt = excluded.system_prompt,
795
809
  daily_token_quota = excluded.daily_token_quota,
@@ -808,8 +822,9 @@ export class Store {
808
822
  agent_tool_call_global_multiplier = excluded.agent_tool_call_global_multiplier,
809
823
  agent_tools_json = excluded.agent_tools_json,
810
824
  title_generation_model_id = excluded.title_generation_model_id,
825
+ image_tool_model_id = excluded.image_tool_model_id,
811
826
  always_include_setting_info = excluded.always_include_setting_info,
812
- updated_at = excluded.updated_at`, workId, nextPrompt, nextDailyTokenQuota, nextEnabled ? 1 : 0, Math.min(8, Math.max(1, nextConcurrency)), Math.min(200, Math.max(1, nextBatchLimit)), Math.min(10_000, Math.max(0, nextDailyTaskLimit)), Math.min(10, Math.max(1, nextFailureThreshold)), current.autoRunPaused ? 1 : 0, String(current.autoRunPauseReason ?? ""), current.autoRunResumeAt === null ? null : String(current.autoRunResumeAt), Math.max(0, Number(current.autoRunConsecutiveFailures) || 0), Math.min(90, Math.max(1, nextBookSummaryContextPercent)), Math.min(90, Math.max(50, nextContextCompactThreshold)), Math.min(48, Math.max(5, nextAgentToolCallLimit)), Math.min(6, Math.max(1, nextAgentToolCallGlobalMultiplier)), JSON.stringify(nextAgentTools), nextTitleGenerationModelId, nextAlwaysIncludeSettingInfo ? 1 : 0, timestamp);
827
+ updated_at = excluded.updated_at`, workId, nextPrompt, nextDailyTokenQuota, nextEnabled ? 1 : 0, Math.min(8, Math.max(1, nextConcurrency)), Math.min(200, Math.max(1, nextBatchLimit)), Math.min(10_000, Math.max(0, nextDailyTaskLimit)), Math.min(10, Math.max(1, nextFailureThreshold)), current.autoRunPaused ? 1 : 0, String(current.autoRunPauseReason ?? ""), current.autoRunResumeAt === null ? null : String(current.autoRunResumeAt), Math.max(0, Number(current.autoRunConsecutiveFailures) || 0), Math.min(90, Math.max(1, nextBookSummaryContextPercent)), Math.min(90, Math.max(50, nextContextCompactThreshold)), Math.min(48, Math.max(5, nextAgentToolCallLimit)), Math.min(6, Math.max(1, nextAgentToolCallGlobalMultiplier)), JSON.stringify(nextAgentTools), nextTitleGenerationModelId, nextImageToolModelId, nextAlwaysIncludeSettingInfo ? 1 : 0, timestamp);
813
828
  this.audit(workId, "work.ai-settings.updated", "work-ai-settings", workId, {
814
829
  systemPromptChanged: input.systemPrompt !== undefined,
815
830
  dailyTokenQuota: nextDailyTokenQuota,
@@ -823,6 +838,7 @@ export class Store {
823
838
  agentToolCallLimit: Math.min(48, Math.max(5, nextAgentToolCallLimit)),
824
839
  agentToolCallGlobalMultiplier: Math.min(6, Math.max(1, nextAgentToolCallGlobalMultiplier)),
825
840
  agentTools: nextAgentTools,
841
+ imageToolModelId: nextImageToolModelId,
826
842
  alwaysIncludeSettingInfo: nextAlwaysIncludeSettingInfo,
827
843
  titleGenerationModelId: nextTitleGenerationModelId
828
844
  });
@@ -3353,9 +3369,11 @@ export class Store {
3353
3369
  JOIN characters character ON character.id = search.character_id`;
3354
3370
  const rows = [...normalized].length <= 2
3355
3371
  ? this.db.all(`${columns} JOIN character_profile_section_short_terms term ON term.search_id = search.id
3356
- WHERE search.work_id = ? AND term.term = ? ORDER BY character.name, section.sort_order LIMIT ?`, workId, normalized, limit)
3372
+ WHERE search.work_id = ? AND character.merged_into_character_id IS NULL AND term.term = ?
3373
+ ORDER BY character.name, section.sort_order LIMIT ?`, workId, normalized, limit)
3357
3374
  : this.db.all(`${columns} JOIN character_profile_section_search_fts fts ON fts.rowid = search.id
3358
- WHERE search.work_id = ? AND character_profile_section_search_fts MATCH ?
3375
+ WHERE search.work_id = ? AND character.merged_into_character_id IS NULL
3376
+ AND character_profile_section_search_fts MATCH ?
3359
3377
  ORDER BY bm25(character_profile_section_search_fts), character.name, section.sort_order LIMIT ?`, workId, `"${normalized.replaceAll('"', '""')}"`, limit);
3360
3378
  return rows.map((row) => ({ ...this.mapCharacterProfileSection(row), characterName: requiredString(row, "character_name") }));
3361
3379
  }
@@ -3425,6 +3443,16 @@ export class Store {
3425
3443
  throw notFound("附件");
3426
3444
  return this.mapAttachment(row);
3427
3445
  }
3446
+ getSettingAttachment(workId, attachmentId) {
3447
+ const row = this.db.get(`SELECT attachment.*
3448
+ FROM attachments attachment
3449
+ JOIN attachment_references reference ON reference.attachment_id = attachment.id
3450
+ WHERE attachment.id = ? AND attachment.work_id = ? AND reference.work_id = ? AND reference.entity_type = 'setting'
3451
+ LIMIT 1`, attachmentId, workId, workId);
3452
+ if (!row)
3453
+ throw new AppError(404, "SETTING_IMAGE_ATTACHMENT_NOT_FOUND", "图片附件不存在或未被设定正文引用");
3454
+ return this.mapAttachment(row);
3455
+ }
3428
3456
  attachmentModules(attachmentId) {
3429
3457
  this.getAttachment(attachmentId);
3430
3458
  const modules = new Set();
@@ -4377,7 +4405,10 @@ export class Store {
4377
4405
  const conversationId = id("conversation");
4378
4406
  const timestamp = now();
4379
4407
  const agentTools = normalizeWorkAgentTools(this.getWorkAiSettings(workId).agentTools);
4380
- this.db.run("INSERT INTO ai_conversations (id, work_id, task_type, title, agent_tools_json, created_at, updated_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", conversationId, workId, taskType, title.trim() || "新对话", JSON.stringify(agentTools), timestamp, timestamp, currentRequestActor()?.userId ?? null);
4408
+ this.db.transaction(() => {
4409
+ this.db.run("INSERT INTO ai_conversations (id, work_id, task_type, title, agent_tools_json, created_at, updated_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", conversationId, workId, taskType, title.trim() || "新对话", JSON.stringify(agentTools), timestamp, timestamp, currentRequestActor()?.userId ?? null);
4410
+ this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
4411
+ });
4381
4412
  return this.getAiConversation(conversationId);
4382
4413
  }
4383
4414
  listAiConversations(workId) {
@@ -4418,14 +4449,29 @@ export class Store {
4418
4449
  const messages = this.db.all("SELECT * FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId).map((message) => this.mapAiConversationMessage(message));
4419
4450
  return { ...this.mapAiConversation(row), messageCount: messages.length, messages };
4420
4451
  }
4421
- getAiConversationPage(conversationId, pagination) {
4452
+ getAiConversationPage(conversationId, pagination, focusMessageId) {
4422
4453
  const row = this.db.get("SELECT * FROM ai_conversations WHERE id = ?", conversationId);
4423
4454
  if (!row)
4424
4455
  throw notFound("AI 对话");
4425
4456
  const countRow = this.db.get("SELECT COUNT(*) AS count FROM ai_conversation_messages WHERE conversation_id = ?", conversationId);
4426
- const page = paginationSql(pagination);
4457
+ let effectivePagination = pagination;
4458
+ if (focusMessageId) {
4459
+ const focused = this.db.get("SELECT rowid, created_at FROM ai_conversation_messages WHERE conversation_id = ? AND id = ?", conversationId, focusMessageId);
4460
+ if (focused) {
4461
+ const newerCount = this.db.get(`SELECT COUNT(*) AS count FROM ai_conversation_messages
4462
+ WHERE conversation_id = ?
4463
+ AND (created_at > ? OR (created_at = ? AND rowid > ?))`, conversationId, String(focused.created_at ?? ""), String(focused.created_at ?? ""), Number(focused.rowid ?? 0));
4464
+ const focusPage = Math.floor(Number(newerCount?.count ?? 0) / pagination.limit) + 1;
4465
+ effectivePagination = {
4466
+ ...pagination,
4467
+ page: focusPage,
4468
+ offset: (focusPage - 1) * pagination.limit
4469
+ };
4470
+ }
4471
+ }
4472
+ const page = paginationSql(effectivePagination);
4427
4473
  const rows = this.db.all(`SELECT * FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at DESC, rowid DESC${page.sql}`, conversationId, ...page.params);
4428
- const messagesPage = paginated(rows.map((message) => this.mapAiConversationMessage(message)), pagination);
4474
+ const messagesPage = paginated(rows.map((message) => this.mapAiConversationMessage(message)), effectivePagination);
4429
4475
  messagesPage.items.reverse();
4430
4476
  return {
4431
4477
  ...this.mapAiConversation(row),
@@ -4529,7 +4575,10 @@ export class Store {
4529
4575
  const conversation = this.db.get("SELECT id FROM ai_conversations WHERE id = ?", conversationId);
4530
4576
  if (!conversation)
4531
4577
  throw notFound("AI 对话");
4532
- this.db.run("UPDATE ai_conversations SET compacted_summary = ?, compacted_message_count = ?, context_warning_at = NULL, updated_at = ? WHERE id = ?", summary, Math.max(0, compactedMessageCount), now(), conversationId);
4578
+ this.db.transaction(() => {
4579
+ this.db.run("UPDATE ai_conversations SET compacted_summary = ?, compacted_message_count = ?, context_warning_at = NULL, updated_at = ? WHERE id = ?", summary, Math.max(0, compactedMessageCount), now(), conversationId);
4580
+ this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
4581
+ });
4533
4582
  return this.getAiConversation(conversationId);
4534
4583
  }
4535
4584
  setAiConversationTitle(conversationId, title) {
@@ -4537,7 +4586,10 @@ export class Store {
4537
4586
  if (!conversation)
4538
4587
  throw notFound("AI 对话");
4539
4588
  const normalizedTitle = title.replace(/\s+/gu, " ").trim().slice(0, 200) || "新对话";
4540
- this.db.run("UPDATE ai_conversations SET title = ?, updated_at = ? WHERE id = ?", normalizedTitle, now(), conversationId);
4589
+ this.db.transaction(() => {
4590
+ this.db.run("UPDATE ai_conversations SET title = ?, updated_at = ? WHERE id = ?", normalizedTitle, now(), conversationId);
4591
+ this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
4592
+ });
4541
4593
  return this.getAiConversation(conversationId);
4542
4594
  }
4543
4595
  setAiConversationRoleplayCharacter(conversationId, characterId) {
@@ -4640,14 +4692,19 @@ export class Store {
4640
4692
  }
4641
4693
  const messageId = id("message");
4642
4694
  const timestamp = now();
4643
- const title = requiredString(conversation, "title") === "新对话" && input.role === "user"
4695
+ const previousTitle = requiredString(conversation, "title");
4696
+ const title = previousTitle === "新对话" && input.role === "user"
4644
4697
  ? defaultAiConversationTitle(input.content)
4645
- : requiredString(conversation, "title");
4698
+ : previousTitle;
4646
4699
  this.db.transaction(() => {
4647
4700
  this.db.run("INSERT INTO ai_conversation_messages (id, conversation_id, role, content, citations_json, metadata_json, request_id, created_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(conversation_id, request_id) WHERE request_id IS NOT NULL DO NOTHING", messageId, conversationId, input.role, input.content, JSON.stringify(input.citations ?? []), JSON.stringify(input.metadata ?? {}), requestId, timestamp, currentRequestActor()?.userId ?? null);
4648
4701
  const inserted = this.db.get("SELECT id FROM ai_conversation_messages WHERE id = ?", messageId);
4649
- if (inserted)
4702
+ if (inserted) {
4650
4703
  this.db.run("UPDATE ai_conversations SET title = ?, updated_at = ? WHERE id = ?", title, timestamp, conversationId);
4704
+ if (title !== previousTitle)
4705
+ this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
4706
+ this.syncAiHistorySearchShortTermsForSource("message", messageId);
4707
+ }
4651
4708
  });
4652
4709
  const message = requestId
4653
4710
  ? this.db.get("SELECT * FROM ai_conversation_messages WHERE conversation_id = ? AND request_id = ?", conversationId, requestId)
@@ -4681,9 +4738,33 @@ export class Store {
4681
4738
  for (const message of messages.slice(0, targetIndex + 1)) {
4682
4739
  this.db.run("INSERT INTO ai_conversation_messages (id, conversation_id, role, content, citations_json, metadata_json, request_id, created_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", id("message"), forkId, requiredString(message, "role"), requiredString(message, "content"), requiredString(message, "citations_json"), requiredString(message, "metadata_json"), optionalString(message, "request_id"), requiredString(message, "created_at"), currentRequestActor()?.userId ?? null);
4683
4740
  }
4741
+ this.syncAiHistorySearchShortTermsForConversation(forkId);
4684
4742
  });
4685
4743
  return this.getAiConversation(forkId);
4686
4744
  }
4745
+ syncAiHistorySearchShortTermsForSource(sourceType, sourceId) {
4746
+ const row = this.db.get("SELECT id, title, content, search_content FROM ai_history_search WHERE source_type = ? AND source_id = ?", sourceType, sourceId);
4747
+ if (!row)
4748
+ return;
4749
+ const searchId = numberValue(row, "id");
4750
+ const searchContent = sourceType === "message"
4751
+ ? normalizeDocumentSearchText(String(row.content ?? ""))
4752
+ : normalizeDocumentSearchText(`${String(row.title ?? "")}\n${String(row.content ?? "")}`);
4753
+ if (String(row.search_content ?? "") !== searchContent) {
4754
+ this.db.run("UPDATE ai_history_search SET search_content = ? WHERE id = ?", searchContent, searchId);
4755
+ }
4756
+ this.db.run("DELETE FROM ai_history_search_short_terms WHERE search_id = ?", searchId);
4757
+ for (const term of documentShortSearchTerms(searchContent)) {
4758
+ this.db.run("INSERT INTO ai_history_search_short_terms (search_id, term) VALUES (?, ?)", searchId, term);
4759
+ }
4760
+ }
4761
+ syncAiHistorySearchShortTermsForConversation(conversationId) {
4762
+ const rows = this.db.all("SELECT source_type, source_id FROM ai_history_search WHERE conversation_id = ?", conversationId);
4763
+ for (const row of rows) {
4764
+ const sourceType = row.source_type === "message" ? "message" : "conversation";
4765
+ this.syncAiHistorySearchShortTermsForSource(sourceType, String(row.source_id));
4766
+ }
4767
+ }
4687
4768
  mapAiConversation(row) {
4688
4769
  const roleplayCharacterId = optionalString(row, "roleplay_character_id");
4689
4770
  const roleplayCharacter = roleplayCharacterId
@@ -6059,7 +6140,7 @@ export class Store {
6059
6140
  SELECT character.id, character.name, character.aliases_json, character.species, character.is_dead,
6060
6141
  COALESCE(path.path, character.species) AS race_path
6061
6142
  FROM characters character LEFT JOIN character_race_paths path ON path.character_id = character.id
6062
- WHERE character.work_id = ? AND (
6143
+ WHERE character.work_id = ? AND character.merged_into_character_id IS NULL AND (
6063
6144
  character.name LIKE ? ESCAPE '\\' OR character.aliases_json LIKE ? ESCAPE '\\' OR character.species LIKE ? ESCAPE '\\'
6064
6145
  OR EXISTS (SELECT 1 FROM character_race_lineage lineage WHERE lineage.character_id = character.id AND lineage.name LIKE ? ESCAPE '\\')
6065
6146
  ) LIMIT 50`, workId, workId, pattern, pattern, pattern, pattern);