@musnows/scriverse 0.8.3 → 0.8.4

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.
@@ -3255,6 +3255,10 @@ function renderAiRoleplayUserCharacterSelect() {
3255
3255
 
3256
3256
  const aiConversationOptionLockedMessage = "会话选项在会话开始后不支持修改,若需要修改,请新建会话";
3257
3257
 
3258
+ function aiConversationModelLocked() {
3259
+ return typeof state.aiConversationModelId === "string" && state.aiConversationModelId.trim().length > 0;
3260
+ }
3261
+
3258
3262
  function selectedAiModelLabel() {
3259
3263
  return $("#ai-model").selectedOptions[0]?.textContent?.trim() || "尚未选择模型";
3260
3264
  }
@@ -3263,20 +3267,21 @@ function syncAiModelPicker() {
3263
3267
  const select = $("#ai-model");
3264
3268
  const button = $("#ai-model-picker");
3265
3269
  const interactionBusy = aiInteractionBusy();
3270
+ const modelLocked = aiConversationModelLocked();
3266
3271
  const selectedLabel = selectedAiModelLabel();
3267
- const label = state.aiPromptSent
3272
+ const label = modelLocked
3268
3273
  ? `当前模型:${selectedLabel}。${aiConversationOptionLockedMessage}`
3269
3274
  : `选择实际使用模型:${selectedLabel}`;
3270
3275
  select.disabled = interactionBusy;
3271
- select.title = state.aiPromptSent ? aiConversationOptionLockedMessage : "";
3276
+ select.title = modelLocked ? aiConversationOptionLockedMessage : "";
3272
3277
  button.disabled = interactionBusy;
3273
3278
  button.title = label;
3274
3279
  button.setAttribute("aria-label", label);
3275
3280
  if (interactionBusy) setAiModelPickerVisible(false);
3276
3281
  }
3277
3282
 
3278
- function notifyAiConversationOptionLocked(select) {
3279
- if (!state.aiPromptSent) return false;
3283
+ function notifyAiConversationOptionLocked(select, locked = state.aiPromptSent) {
3284
+ if (!locked) return false;
3280
3285
  const now = Date.now();
3281
3286
  const lastToastAt = Number(select.dataset.lockedToastAt ?? 0);
3282
3287
  if (now - lastToastAt > 500) toast(aiConversationOptionLockedMessage);
@@ -3284,9 +3289,16 @@ function notifyAiConversationOptionLocked(select) {
3284
3289
  return true;
3285
3290
  }
3286
3291
 
3292
+ function notifyAiConversationModelLocked(control) {
3293
+ return notifyAiConversationOptionLocked(control, aiConversationModelLocked());
3294
+ }
3295
+
3287
3296
  function blockLockedAiConversationOptionInteraction(event) {
3288
3297
  const select = event.currentTarget;
3289
- if (!notifyAiConversationOptionLocked(select)) return;
3298
+ const locked = select.id === "ai-model"
3299
+ ? notifyAiConversationModelLocked(select)
3300
+ : notifyAiConversationOptionLocked(select);
3301
+ if (!locked) return;
3290
3302
  event.preventDefault();
3291
3303
  event.stopPropagation();
3292
3304
  }
@@ -15032,13 +15044,9 @@ function createAiStreamCharacterCount(value) {
15032
15044
  return count;
15033
15045
  }
15034
15046
 
15035
- function renderAiStreamingCharacterProgress(meta, visibleCharacters, receivedCharacters) {
15047
+ function renderAiStreamingCharacterProgress(meta, visibleCharacters) {
15036
15048
  const visible = Math.max(0, Number(visibleCharacters) || 0);
15037
- const received = Math.max(visible, Number(receivedCharacters) || 0);
15038
- const children = ["正在生成 · ", createAiStreamCharacterCount(visible)];
15039
- if (received > visible) children.push(" / ", createAiStreamCharacterCount(received));
15040
- children.push(" 字");
15041
- meta.replaceChildren(...children);
15049
+ meta.replaceChildren("正在生成 · ", createAiStreamCharacterCount(visible), " 字");
15042
15050
  }
15043
15051
 
15044
15052
  async function streamChat(requestHolder, body, idempotencyKey) {
@@ -15067,8 +15075,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
15067
15075
  onRender: (text, progress) => {
15068
15076
  if (!aiRequestTargetsCurrentState(requestHolder.snapshot) || !mountAssistantMessage()) return;
15069
15077
  content.innerHTML = renderMarkdown(text);
15070
- const receivedCharacters = progress.visibleCharacters + progress.pendingCharacters;
15071
- renderAiStreamingCharacterProgress(meta, progress.visibleCharacters, receivedCharacters);
15078
+ renderAiStreamingCharacterProgress(meta, progress.visibleCharacters);
15072
15079
  scrollAiFeedToBottom(feed);
15073
15080
  }
15074
15081
  });
@@ -15178,7 +15185,6 @@ async function streamChat(requestHolder, body, idempotencyKey) {
15178
15185
  streamedText += delta;
15179
15186
  streamedPendingText += delta;
15180
15187
  typewriter.append(delta);
15181
- meta.textContent = "正在生成回复……";
15182
15188
  } else if (eventName === "process_step") {
15183
15189
  mountAssistantMessage();
15184
15190
  const step = { ...payload };
@@ -16996,7 +17002,7 @@ $("#ai-model").addEventListener("focus", () => {
16996
17002
  ensureAiModelsLoaded().catch((error) => toast(`模型加载失败:${error.message}`, "error"));
16997
17003
  });
16998
17004
  $("#ai-model").addEventListener("change", (event) => {
16999
- if (state.aiPromptSent) {
17005
+ if (aiConversationModelLocked()) {
17000
17006
  event.currentTarget.value = state.aiConversationModelId ?? event.currentTarget.value;
17001
17007
  syncAiModelPicker();
17002
17008
  return toast(aiConversationOptionLockedMessage);
@@ -17007,7 +17013,7 @@ $("#ai-model").addEventListener("change", (event) => {
17007
17013
  });
17008
17014
  $("#ai-model-picker").addEventListener("click", async (event) => {
17009
17015
  const button = event.currentTarget;
17010
- if (notifyAiConversationOptionLocked(button)) return;
17016
+ if (notifyAiConversationModelLocked(button)) return;
17011
17017
  const willOpen = $("#ai-model-popover").classList.contains("hidden");
17012
17018
  setAiContextDistributionVisible(false);
17013
17019
  setAiModelPickerVisible(willOpen);
@@ -1210,6 +1210,6 @@
1210
1210
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1211
1211
  <script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
1212
1212
  <script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
1213
- <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v1&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v1&feature=volume-detail-icon-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v1&feature=phone-client-entry-v1"></script>
1213
+ <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v1&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v1&feature=volume-detail-icon-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=phone-client-entry-v1"></script>
1214
1214
  </body>
1215
1215
  </html>
package/dist/store.js CHANGED
@@ -5582,12 +5582,18 @@ export class Store {
5582
5582
  throw notFound("AI 对话");
5583
5583
  if (requiredString(conversation, "work_id") !== workId)
5584
5584
  throw new AppError(400, "CONVERSATION_WORK_MISMATCH", "AI 对话不属于当前作品");
5585
- const message = this.db.get(`SELECT metadata_json FROM ai_conversation_messages
5585
+ return this.aiConversationLockedModelId(conversationId);
5586
+ }
5587
+ aiConversationLockedModelId(conversationId) {
5588
+ const messages = this.db.all(`SELECT metadata_json FROM ai_conversation_messages
5586
5589
  WHERE conversation_id = ? AND role = 'user'
5587
- ORDER BY created_at, rowid
5588
- LIMIT 1`, conversationId);
5589
- const metadata = message ? json(requiredString(message, "metadata_json"), {}) : {};
5590
- return typeof metadata.modelId === "string" && metadata.modelId.trim() ? metadata.modelId.trim() : null;
5590
+ ORDER BY created_at, rowid`, conversationId);
5591
+ for (const message of messages) {
5592
+ const metadata = json(requiredString(message, "metadata_json"), {});
5593
+ if (typeof metadata.modelId === "string" && metadata.modelId.trim())
5594
+ return metadata.modelId.trim();
5595
+ }
5596
+ return null;
5591
5597
  }
5592
5598
  getAiConversationInjectedEntities(conversationId, workId) {
5593
5599
  const conversation = this.db.get("SELECT work_id, injected_entities_json FROM ai_conversations WHERE id = ?", conversationId);
@@ -6070,7 +6076,11 @@ export class Store {
6070
6076
  ? JSON.stringify(normalizeWorkAgentTools(this.getWorkAiSettings(workId).agentTools))
6071
6077
  : String(conversation.agent_tools_json), injectedEntitiesJson, systemClockText, timestamp, timestamp, currentRequestActor()?.userId ?? null);
6072
6078
  for (const message of messages.slice(0, targetIndex + 1)) {
6073
- 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);
6079
+ const role = requiredString(message, "role");
6080
+ const inheritedMetadata = json(requiredString(message, "metadata_json"), {});
6081
+ if (role === "user")
6082
+ delete inheritedMetadata.modelId;
6083
+ 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, role, requiredString(message, "content"), requiredString(message, "citations_json"), JSON.stringify(inheritedMetadata), optionalString(message, "request_id"), requiredString(message, "created_at"), currentRequestActor()?.userId ?? null);
6074
6084
  }
6075
6085
  if (normalizedRequestId) {
6076
6086
  this.db.run("INSERT INTO ai_conversation_forks (source_conversation_id, source_message_id, request_id, conversation_id, created_at) VALUES (?, ?, ?, ?, ?)", conversationId, messageId, normalizedRequestId, forkId, timestamp);
@@ -6110,16 +6120,7 @@ export class Store {
6110
6120
  mapAiConversation(row) {
6111
6121
  const roleplayCharacterId = optionalString(row, "roleplay_character_id");
6112
6122
  const roleplayUserCharacterId = optionalString(row, "roleplay_user_character_id");
6113
- const firstUserMessage = this.db.get(`SELECT metadata_json FROM ai_conversation_messages
6114
- WHERE conversation_id = ? AND role = 'user'
6115
- ORDER BY created_at, rowid
6116
- LIMIT 1`, requiredString(row, "id"));
6117
- const firstUserMetadata = firstUserMessage
6118
- ? json(requiredString(firstUserMessage, "metadata_json"), {})
6119
- : {};
6120
- const lockedModelId = typeof firstUserMetadata.modelId === "string" && firstUserMetadata.modelId.trim()
6121
- ? firstUserMetadata.modelId.trim()
6122
- : null;
6123
+ const lockedModelId = this.aiConversationLockedModelId(requiredString(row, "id"));
6123
6124
  const roleplayCharacter = roleplayCharacterId
6124
6125
  ? this.db.get("SELECT id, name, code FROM characters WHERE id = ? AND work_id = ?", roleplayCharacterId, requiredString(row, "work_id"))
6125
6126
  : undefined;