@musnows/scriverse 0.5.12 → 0.6.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/store.js CHANGED
@@ -123,6 +123,10 @@ export const versionedEntityTypes = [
123
123
  "chapter-outline",
124
124
  "foreshadow"
125
125
  ];
126
+ export function defaultAiConversationTitle(prompt) {
127
+ const normalized = prompt.replace(/\s+/gu, " ").trim();
128
+ return Array.from(normalized).slice(0, 15).join("") || "新对话";
129
+ }
126
130
  function isRecord(value) {
127
131
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
128
132
  }
@@ -689,6 +693,9 @@ export class Store {
689
693
  agentTools: json(String(row?.agent_tools_json ?? '["story_index","read_chapters","search_story_entities","grep","read_character_sections","search_drafts"]'), ["story_index", "read_chapters", "search_story_entities", "grep", "read_character_sections", "search_drafts"])
690
694
  .map((tool) => tool === "query_story_knowledge" ? "search_story_entities" : tool)
691
695
  .filter((tool, index, tools) => tools.indexOf(tool) === index),
696
+ titleGenerationModelId: row?.title_generation_model_id === null || row?.title_generation_model_id === undefined
697
+ ? null
698
+ : String(row.title_generation_model_id),
692
699
  updatedAt: String(row?.updated_at ?? "")
693
700
  };
694
701
  }
@@ -705,12 +712,15 @@ export class Store {
705
712
  const nextBookSummaryContextPercent = input.bookSummaryContextPercent ?? Number(current.bookSummaryContextPercent);
706
713
  const nextContextCompactThreshold = input.contextCompactThreshold ?? Number(current.contextCompactThreshold);
707
714
  const nextAgentTools = input.agentTools ?? current.agentTools;
715
+ const nextTitleGenerationModelId = input.titleGenerationModelId === undefined
716
+ ? (current.titleGenerationModelId ? String(current.titleGenerationModelId) : null)
717
+ : input.titleGenerationModelId?.trim() || null;
708
718
  this.db.run(`INSERT INTO work_ai_settings (
709
719
  work_id, system_prompt, auto_run_enabled, auto_run_concurrency, auto_run_batch_limit,
710
720
  auto_run_daily_task_limit, auto_run_failure_threshold, auto_run_paused, auto_run_pause_reason,
711
721
  auto_run_resume_at, auto_run_consecutive_failures, book_summary_context_percent,
712
- context_compact_threshold, agent_tools_json, updated_at
713
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
722
+ context_compact_threshold, agent_tools_json, title_generation_model_id, updated_at
723
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
714
724
  ON CONFLICT(work_id) DO UPDATE SET
715
725
  system_prompt = excluded.system_prompt,
716
726
  auto_run_enabled = excluded.auto_run_enabled,
@@ -725,7 +735,8 @@ export class Store {
725
735
  book_summary_context_percent = excluded.book_summary_context_percent,
726
736
  context_compact_threshold = excluded.context_compact_threshold,
727
737
  agent_tools_json = excluded.agent_tools_json,
728
- updated_at = excluded.updated_at`, workId, nextPrompt, 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)), JSON.stringify(nextAgentTools), timestamp);
738
+ title_generation_model_id = excluded.title_generation_model_id,
739
+ updated_at = excluded.updated_at`, workId, nextPrompt, 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)), JSON.stringify(nextAgentTools), nextTitleGenerationModelId, timestamp);
729
740
  this.audit(workId, "work.ai-settings.updated", "work-ai-settings", workId, {
730
741
  systemPromptChanged: input.systemPrompt !== undefined,
731
742
  autoRunEnabled: nextEnabled,
@@ -735,7 +746,8 @@ export class Store {
735
746
  autoRunFailureThreshold: Math.min(10, Math.max(1, nextFailureThreshold)),
736
747
  bookSummaryContextPercent: Math.min(90, Math.max(1, nextBookSummaryContextPercent)),
737
748
  contextCompactThreshold: Math.min(90, Math.max(50, nextContextCompactThreshold)),
738
- agentTools: nextAgentTools
749
+ agentTools: nextAgentTools,
750
+ titleGenerationModelId: nextTitleGenerationModelId
739
751
  });
740
752
  return this.getWorkAiSettings(workId);
741
753
  }
@@ -4127,7 +4139,7 @@ export class Store {
4127
4139
  throw notFound("AI 对话");
4128
4140
  if (requiredString(conversation, "work_id") !== workId)
4129
4141
  throw new AppError(400, "CONVERSATION_WORK_MISMATCH", "AI 对话不属于当前作品");
4130
- const rows = this.db.all("SELECT id, role, content FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId);
4142
+ const rows = this.db.all("SELECT id, role, content, metadata_json FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId);
4131
4143
  const compactedMessageCount = Math.min(rows.length, Math.max(0, numberValue(conversation, "compacted_message_count")));
4132
4144
  return {
4133
4145
  workId,
@@ -4140,10 +4152,23 @@ export class Store {
4140
4152
  .map((message) => ({
4141
4153
  id: requiredString(message, "id"),
4142
4154
  role: requiredString(message, "role") === "assistant" ? "assistant" : "user",
4143
- content: requiredString(message, "content")
4155
+ content: requiredString(message, "content"),
4156
+ metadata: json(requiredString(message, "metadata_json"), {})
4144
4157
  }))
4145
4158
  };
4146
4159
  }
4160
+ getAiConversationTitleContext(conversationId, workId) {
4161
+ const conversation = this.db.get("SELECT title, work_id FROM ai_conversations WHERE id = ?", conversationId);
4162
+ if (!conversation)
4163
+ throw notFound("AI 对话");
4164
+ if (requiredString(conversation, "work_id") !== workId)
4165
+ throw new AppError(400, "CONVERSATION_WORK_MISMATCH", "AI 对话不属于当前作品");
4166
+ const messages = this.db.all("SELECT role, content FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId).map((message) => ({
4167
+ role: requiredString(message, "role") === "assistant" ? "assistant" : "user",
4168
+ content: requiredString(message, "content")
4169
+ }));
4170
+ return { title: requiredString(conversation, "title"), messages };
4171
+ }
4147
4172
  setAiConversationContextWarning(conversationId, pending) {
4148
4173
  const conversation = this.db.get("SELECT id FROM ai_conversations WHERE id = ?", conversationId);
4149
4174
  if (!conversation)
@@ -4157,6 +4182,14 @@ export class Store {
4157
4182
  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);
4158
4183
  return this.getAiConversation(conversationId);
4159
4184
  }
4185
+ setAiConversationTitle(conversationId, title) {
4186
+ const conversation = this.db.get("SELECT id FROM ai_conversations WHERE id = ?", conversationId);
4187
+ if (!conversation)
4188
+ throw notFound("AI 对话");
4189
+ const normalizedTitle = title.replace(/\s+/gu, " ").trim().slice(0, 200) || "新对话";
4190
+ this.db.run("UPDATE ai_conversations SET title = ?, updated_at = ? WHERE id = ?", normalizedTitle, now(), conversationId);
4191
+ return this.getAiConversation(conversationId);
4192
+ }
4160
4193
  addAiConversationMessage(conversationId, input) {
4161
4194
  const conversation = this.db.get("SELECT * FROM ai_conversations WHERE id = ?", conversationId);
4162
4195
  if (!conversation)
@@ -4170,7 +4203,7 @@ export class Store {
4170
4203
  const messageId = id("message");
4171
4204
  const timestamp = now();
4172
4205
  const title = requiredString(conversation, "title") === "新对话" && input.role === "user"
4173
- ? input.content.replace(/\s+/gu, " ").trim().slice(0, 36) || "新对话"
4206
+ ? defaultAiConversationTitle(input.content)
4174
4207
  : requiredString(conversation, "title");
4175
4208
  this.db.transaction(() => {
4176
4209
  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);