@musnows/scriverse 1.0.7 → 1.0.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.
@@ -9,7 +9,7 @@
9
9
  <script src="/theme-init.js?v=20260827-reader-prefetch-v1"></script>
10
10
  <link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
11
11
  <link rel="manifest" href="/site.webmanifest">
12
- <link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3&feature=continuation-guard-failure-details-v1&feature=entity-editor-back-icon-v1&feature=toast-stack-v1">
12
+ <link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=ai-composer-compact-controls-v1&feature=ai-context-meter-ring-only-v1&feature=ai-context-cache-hit-v1&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3&feature=entity-editor-back-icon-v1&feature=toast-stack-v1&feature=chapter-header-compact-v4&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1">
13
13
  </head>
14
14
  <body class="auth-pending">
15
15
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -433,14 +433,14 @@
433
433
  <label for="ai-scene-direction">旁白 / 场景<textarea id="ai-scene-direction" rows="3" maxlength="20000" placeholder="写下本轮旁白或场景推进,会随下一条台词一起发送"></textarea></label>
434
434
  </section>
435
435
  <div class="prompt-composer-leading">
436
- <button id="ai-attachment-button" class="ai-attachment-button hidden" type="button" aria-label="添加图片附件" title="添加图片附件"><svg class="ai-image-button-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="3.5" y="4.5" width="17" height="15" rx="2"></rect><circle cx="9" cy="10" r="1.5"></circle><path d="m5.5 17 4.4-4.4 3.3 3.1 2.2-2.2 3.1 3.5"></path></svg></button>
436
+ <button id="ai-attachment-button" class="ai-attachment-button" type="button" aria-label="添加图片附件" title="选择多模态模型后可添加图片附件" disabled><svg class="ai-image-button-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M16 5h6"></path><path d="M19 2v6"></path><path d="M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5"></path><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"></path><circle cx="9" cy="9" r="2"></circle></svg></button>
437
437
  <button id="ai-scene-button" class="ai-scene-button hidden" type="button" aria-label="旁白与场景" title="旁白与场景" aria-expanded="false" aria-controls="ai-scene-panel"><svg class="ai-scene-button-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4.5" y="6.5" width="15" height="13" rx="1.5"></rect><path d="M8 10.5h8M8 14h5.5"></path></svg></button>
438
438
  </div>
439
439
  <input id="ai-attachment-input" class="ai-attachment-input" type="file" accept="image/png,image/jpeg,.jpg,.jpeg" multiple aria-label="选择图片附件">
440
440
  <div id="ai-prompt" class="ai-prompt" contenteditable="true" role="textbox" aria-multiline="true" aria-autocomplete="list" aria-controls="ai-mention-menu" aria-haspopup="listbox" aria-expanded="false" aria-keyshortcuts="Enter" title="Enter 发送,Shift+Enter 换行" data-placeholder="告诉 AI 你想讨论或修改什么……"></div>
441
441
  <div id="ai-mention-menu" class="ai-mention-menu hidden" role="listbox" aria-label="引用角色、设定、章节或上下文能力"></div>
442
442
  <div class="prompt-composer-actions">
443
- <button id="ai-context-meter" class="ai-context-meter is-empty" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="ai-context-popover" aria-live="polite" aria-label="当前上下文用量"><b>—</b></button>
443
+ <button id="ai-context-meter" class="ai-context-meter is-empty" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="ai-context-popover" aria-live="polite" aria-label="当前上下文用量"></button>
444
444
  <section id="ai-context-popover" class="ai-context-popover hidden" role="dialog" aria-labelledby="ai-context-popover-title" aria-describedby="ai-context-popover-description">
445
445
  <header class="ai-context-popover-header"><div><strong id="ai-context-popover-title">Token 分布</strong><small id="ai-context-popover-description">按当前模型上下文窗口计算</small></div><button id="ai-context-popover-close" class="ai-context-popover-close" type="button" aria-label="关闭 Token 分布">×</button></header>
446
446
  <div id="ai-context-distribution" class="ai-context-distribution" role="list" aria-label="当前 Token 分布"></div>
@@ -522,6 +522,7 @@
522
522
  <button type="button" role="menuitemradio" data-chapter-type="设定">设定</button>
523
523
  <button type="button" role="menuitemradio" data-chapter-type="作者的话">作者的话</button>
524
524
  <button type="button" role="menuitemradio" data-chapter-type="其他">其他</button>
525
+ <div id="chapter-type-ai-reference-separator" class="line-citation-menu-separator hidden" role="separator" aria-orientation="horizontal"></div>
525
526
  <button type="button" role="menuitem" data-add-chapter-ai-reference>添加到助手引用</button>
526
527
  <button class="danger-button" type="button" role="menuitem" data-delete-chapter>删除章节</button>
527
528
  </div>
@@ -1426,6 +1427,7 @@
1426
1427
  </dialog>
1427
1428
 
1428
1429
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1429
- <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-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-v2&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-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-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-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-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=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=continuation-guard-failure-details-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=ai-suggestion-editor-boundary-v1&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2&feature=agent-tool-limits-300-v1&feature=server-dev-logo-v1&feature=chapter-directory-ai-reference-v2&feature=ai-error-origin-v1"></script>
1430
+ <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-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-v2&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-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-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-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-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=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-context-meter-ring-only-v1&feature=ai-context-cache-hit-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2&feature=agent-tool-limits-300-v1&feature=server-dev-logo-v1&feature=chapter-directory-ai-reference-v3&feature=ai-error-origin-v1&feature=ai-stream-render-performance-v2&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=ai-inline-message-references-v1"></script>
1431
+
1430
1432
  </body>
1431
1433
  </html>
@@ -142,8 +142,20 @@ function renderMarkdownTable(headers, alignments, rows) {
142
142
  }
143
143
 
144
144
  export function renderMarkdown(value) {
145
+ return renderMarkdownParts(value).map((part) => part.html).join("");
146
+ }
147
+
148
+ export function renderMarkdownParts(value) {
145
149
  const lines = String(value ?? "").replace(/\r\n?/gu, "\n").split("\n");
146
150
  const output = [];
151
+ const offsets = [];
152
+ let offset = 0;
153
+ for (const line of lines) {
154
+ offsets.push(offset);
155
+ offset += line.length + 1;
156
+ }
157
+ let lineIndex = 0;
158
+ const emit = (html, endLine = lineIndex) => output.push({ html, end: offsets[endLine] ?? offset - 1 });
147
159
  let paragraph = [];
148
160
  let list = null;
149
161
  let quote = null;
@@ -151,12 +163,12 @@ export function renderMarkdown(value) {
151
163
 
152
164
  const flushParagraph = () => {
153
165
  if (!paragraph.length) return;
154
- output.push(`<p>${paragraph.map(renderInlineMarkdown).join("<br>")}</p>`);
166
+ emit(`<p>${paragraph.map(renderInlineMarkdown).join("<br>")}</p>`);
155
167
  paragraph = [];
156
168
  };
157
169
  const flushList = () => {
158
170
  if (!list) return;
159
- output.push(`<${list.tag}>${list.items.map((item) => `<li class="markdown-depth-${item.depth}">${renderInlineMarkdown(item.text)}</li>`).join("")}</${list.tag}>`);
171
+ emit(`<${list.tag}>${list.items.map((item) => `<li class="markdown-depth-${item.depth}">${renderInlineMarkdown(item.text)}</li>`).join("")}</${list.tag}>`);
160
172
  list = null;
161
173
  };
162
174
  const flushQuote = () => {
@@ -166,7 +178,7 @@ export function renderMarkdown(value) {
166
178
  const html = content.split(/\n\s*\n/gu)
167
179
  .map((part) => part.split("\n").map(renderInlineMarkdown).join("<br>"))
168
180
  .join("<br><br>");
169
- output.push(`<blockquote>${html}</blockquote>`);
181
+ emit(`<blockquote>${html}</blockquote>`);
170
182
  }
171
183
  quote = null;
172
184
  };
@@ -176,11 +188,11 @@ export function renderMarkdown(value) {
176
188
  flushQuote();
177
189
  };
178
190
 
179
- for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
191
+ for (lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
180
192
  const line = lines[lineIndex];
181
193
  if (codeFence) {
182
194
  if (/^\s*```/u.test(line)) {
183
- output.push(`<pre><code${codeFence.language ? ` class="language-${codeFence.language}"` : ""}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`);
195
+ emit(`<pre><code${codeFence.language ? ` class="language-${codeFence.language}"` : ""}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`, lineIndex + 1);
184
196
  codeFence = null;
185
197
  } else {
186
198
  codeFence.lines.push(line);
@@ -230,12 +242,12 @@ export function renderMarkdown(value) {
230
242
  if (heading) {
231
243
  flushBlocks();
232
244
  const level = heading[1].length;
233
- output.push(`<h${level}>${renderInlineMarkdown(heading[2])}</h${level}>`);
245
+ emit(`<h${level}>${renderInlineMarkdown(heading[2])}</h${level}>`, lineIndex + 1);
234
246
  continue;
235
247
  }
236
248
  if (/^\s*(---+|___+|\*\*\*+)\s*$/u.test(line)) {
237
249
  flushBlocks();
238
- output.push("<hr>");
250
+ emit("<hr>", lineIndex + 1);
239
251
  continue;
240
252
  }
241
253
  const tableHeaders = splitMarkdownTableRow(line);
@@ -251,7 +263,7 @@ export function renderMarkdown(value) {
251
263
  lineIndex += 1;
252
264
  }
253
265
  lineIndex -= 1;
254
- output.push(renderMarkdownTable(tableHeaders, alignments, rows));
266
+ emit(renderMarkdownTable(tableHeaders, alignments, rows), lineIndex + 1);
255
267
  continue;
256
268
  }
257
269
  const listItem = line.match(/^(\s*)([-+*]|\d+\.)\s+(.+)$/u);
@@ -266,7 +278,7 @@ export function renderMarkdown(value) {
266
278
  if (list) flushList();
267
279
  paragraph.push(line);
268
280
  }
269
- if (codeFence) output.push(`<pre><code${codeFence.language ? ` class="language-${codeFence.language}"` : ""}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`);
281
+ if (codeFence) emit(`<pre><code${codeFence.language ? ` class="language-${codeFence.language}"` : ""}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`);
270
282
  flushBlocks();
271
- return output.join("");
283
+ return output;
272
284
  }
@@ -0,0 +1,107 @@
1
+ import { renderMarkdownParts } from "./markdown.js?v=20260912-stream-render-v2";
2
+
3
+ // 保留末尾两个块,等待列表、引用和表格的后续行确定语义后再冻结前缀。
4
+ export function createMarkdownStreamState() {
5
+ let previous = "";
6
+ let committed = 0;
7
+ return {
8
+ update(value) {
9
+ const source = String(value ?? "").replace(/\r\n?/gu, "\n");
10
+ const reset = !source.startsWith(previous);
11
+ if (reset) committed = 0;
12
+ const parts = renderMarkdownParts(source.slice(committed));
13
+ const stableCount = Math.max(0, parts.length - 2);
14
+ if (stableCount) committed += parts[stableCount - 1].end;
15
+ previous = source;
16
+ return { reset, parts, stableCount };
17
+ }
18
+ };
19
+ }
20
+
21
+ // 每个节点都是可让出主线程的边界,长列表和表格也不会作为一个大块集中挂载。
22
+ function* patchMarkdownNode(current, next, parent) {
23
+ if (!current || current.nodeType !== next.nodeType || current.nodeName !== next.nodeName) {
24
+ const replacement = next.cloneNode(false);
25
+ if (current) current.replaceWith(replacement);
26
+ else parent.append(replacement);
27
+ current = replacement;
28
+ }
29
+ if (current.nodeType === 3) {
30
+ if (current.data === next.data) return current;
31
+ if (next.data.startsWith(current.data)) current.appendData(next.data.slice(current.data.length));
32
+ else if (current.data !== next.data) current.data = next.data;
33
+ yield;
34
+ return current;
35
+ }
36
+ if (current.nodeType !== 1) return current;
37
+ for (const attribute of [...current.attributes]) {
38
+ if (!next.hasAttribute(attribute.name)) current.removeAttribute(attribute.name);
39
+ }
40
+ for (const attribute of next.attributes) {
41
+ if (current.getAttribute(attribute.name) !== attribute.value) current.setAttribute(attribute.name, attribute.value);
42
+ }
43
+ yield;
44
+ const children = [...current.childNodes];
45
+ const nextChildren = [...next.childNodes];
46
+ for (const [index, child] of nextChildren.entries()) yield* patchMarkdownNode(children[index], child, current);
47
+ for (const child of children.slice(nextChildren.length)) { child.remove(); yield; }
48
+ return current;
49
+ }
50
+
51
+ export function createStreamingMarkdownRenderer(host, {
52
+ enqueue = (callback) => requestAnimationFrame(callback),
53
+ onRender = () => {},
54
+ now = () => performance.now(),
55
+ budgetMs = 3,
56
+ nodeBudget = 120
57
+ } = {}) {
58
+ const state = createMarkdownStreamState();
59
+ let tail = [];
60
+ let requestedValue = null;
61
+ let appliedValue = null;
62
+ let work = null;
63
+ let queued = false;
64
+ function* apply(value) {
65
+ const update = state.update(value);
66
+ if (update.reset) {
67
+ for (const child of [...host.childNodes]) { child.remove(); yield; }
68
+ tail = [];
69
+ }
70
+ const next = [];
71
+ for (const [index, part] of update.parts.entries()) {
72
+ const previous = tail[index];
73
+ if (previous?.html === part.html) { next.push(previous); continue; }
74
+ const template = host.ownerDocument.createElement("template");
75
+ template.innerHTML = part.html;
76
+ const node = yield* patchMarkdownNode(previous?.node, template.content.firstChild, host);
77
+ next.push({ html: part.html, node });
78
+ }
79
+ for (const item of tail.slice(next.length)) { item.node.remove(); yield; }
80
+ tail = next.slice(update.stableCount);
81
+ appliedValue = value;
82
+ }
83
+ const drain = () => {
84
+ queued = false;
85
+ const started = now();
86
+ if (!work && requestedValue !== appliedValue) work = apply(requestedValue);
87
+ let nodes = 0;
88
+ while (work && nodes < nodeBudget && now() - started < budgetMs) {
89
+ if (work.next().done) { work = null; break; }
90
+ nodes += 1;
91
+ }
92
+ onRender();
93
+ if (work || requestedValue !== appliedValue) schedule();
94
+ };
95
+ const schedule = () => {
96
+ if (queued) return;
97
+ queued = true;
98
+ enqueue(drain);
99
+ };
100
+ return (value) => {
101
+ const normalized = String(value ?? "");
102
+ if (normalized === requestedValue) return false;
103
+ requestedValue = normalized;
104
+ schedule();
105
+ return true;
106
+ };
107
+ }
@@ -27,6 +27,7 @@ export function createStreamTypewriter<FrameHandle = number>(options: {
27
27
  scheduleFrame?: (callback: () => void) => FrameHandle;
28
28
  cancelFrame?: (handle: FrameHandle) => void;
29
29
  reducedMotion?: boolean;
30
+ shouldAnimate?: () => boolean;
30
31
  speedController?: StreamTypewriterSpeedController | null;
31
32
  visibilitySource?: Pick<Document, "visibilityState" | "addEventListener" | "removeEventListener"> | null;
32
33
  }): StreamTypewriter;
@@ -75,23 +75,34 @@ export function createStreamTypewriter({
75
75
  cancelFrame = (handle) => window.cancelAnimationFrame(handle),
76
76
  reducedMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true,
77
77
  speedController = null,
78
+ shouldAnimate = () => true,
78
79
  visibilitySource = typeof document === "undefined" ? null : document
79
80
  }) {
80
81
  if (typeof onRender !== "function") throw new TypeError("onRender must be a function");
81
82
 
82
- const visibleCharacters = [];
83
- const pendingCharacters = [];
83
+ let visibleText = "";
84
+ let visibleCharacterCount = 0;
85
+ let pendingCharacters = [];
86
+ let pendingOffset = 0;
87
+ const pendingCount = () => pendingCharacters.length - pendingOffset;
88
+ const takePending = (count) => {
89
+ const characters = pendingCharacters.slice(pendingOffset, pendingOffset + count);
90
+ visibleText += characters.join("");
91
+ visibleCharacterCount += characters.length;
92
+ pendingOffset += characters.length;
93
+ if (pendingOffset === pendingCharacters.length) { pendingCharacters = []; pendingOffset = 0; }
94
+ };
84
95
  const idleResolvers = [];
85
96
  let scheduledFrame = null;
86
97
  let finishing = false;
87
98
  let listeningForVisibility = false;
88
99
 
89
- const snapshot = () => visibleCharacters.join("");
100
+ const snapshot = () => visibleText;
90
101
  const visibilityChanged = () => {
91
- if (pendingCharacters.length) typewriter.reveal();
102
+ if (pendingCount()) typewriter.reveal();
92
103
  };
93
104
  const resolveIdle = () => {
94
- if (pendingCharacters.length || scheduledFrame !== null) return;
105
+ if (pendingCount() || scheduledFrame !== null) return;
95
106
  if (listeningForVisibility) {
96
107
  visibilitySource.removeEventListener("visibilitychange", visibilityChanged);
97
108
  listeningForVisibility = false;
@@ -101,32 +112,33 @@ export function createStreamTypewriter({
101
112
  };
102
113
  const render = () => {
103
114
  onRender(snapshot(), {
104
- visibleCharacters: visibleCharacters.length,
105
- pendingCharacters: pendingCharacters.length
115
+ visibleCharacters: visibleCharacterCount,
116
+ pendingCharacters: pendingCount()
106
117
  });
107
118
  };
108
119
  const schedule = () => {
109
- if (visibilitySource?.visibilityState === "hidden") {
120
+ if (visibilitySource?.visibilityState === "hidden" || !shouldAnimate()) {
110
121
  typewriter.reveal();
111
122
  return;
112
123
  }
113
- if (scheduledFrame !== null || pendingCharacters.length === 0) return;
124
+ if (scheduledFrame !== null || pendingCount() === 0) return;
114
125
  if (visibilitySource && !listeningForVisibility) {
115
126
  visibilitySource.addEventListener("visibilitychange", visibilityChanged);
116
127
  listeningForVisibility = true;
117
128
  }
118
129
  scheduledFrame = scheduleFrame(() => {
119
130
  scheduledFrame = null;
120
- const batchSize = reducedMotion
121
- ? pendingCharacters.length
131
+ const batchSize = reducedMotion || !shouldAnimate()
132
+ ? pendingCount()
122
133
  : streamTypewriterBatchSize(
123
- pendingCharacters.length,
134
+ pendingCount(),
124
135
  finishing,
125
136
  speedController?.charactersPerSecond?.()
126
137
  );
127
- visibleCharacters.push(...pendingCharacters.splice(0, batchSize));
138
+ const catchUp = pendingCount() > 4096 ? Math.min(2048, Math.ceil(pendingCount() / 8)) : batchSize;
139
+ takePending(Math.max(batchSize, catchUp));
128
140
  render();
129
- if (pendingCharacters.length) schedule();
141
+ if (pendingCount()) schedule();
130
142
  else resolveIdle();
131
143
  });
132
144
  };
@@ -136,7 +148,7 @@ export function createStreamTypewriter({
136
148
  const characters = Array.from(String(value ?? ""));
137
149
  if (!characters.length) return;
138
150
  speedController?.observe?.(characters.length);
139
- pendingCharacters.push(...characters);
151
+ for (const character of characters) pendingCharacters.push(character);
140
152
  schedule();
141
153
  },
142
154
  replace(value) {
@@ -144,15 +156,17 @@ export function createStreamTypewriter({
144
156
  cancelFrame(scheduledFrame);
145
157
  scheduledFrame = null;
146
158
  }
147
- visibleCharacters.splice(0, visibleCharacters.length, ...Array.from(String(value ?? "")));
148
- pendingCharacters.splice(0);
159
+ visibleText = String(value ?? "");
160
+ visibleCharacterCount = Array.from(visibleText).length;
161
+ pendingCharacters = [];
162
+ pendingOffset = 0;
149
163
  finishing = false;
150
164
  render();
151
165
  resolveIdle();
152
166
  return snapshot();
153
167
  },
154
168
  finish() {
155
- if (!pendingCharacters.length && scheduledFrame === null) return Promise.resolve(snapshot());
169
+ if (!pendingCount() && scheduledFrame === null) return Promise.resolve(snapshot());
156
170
  finishing = true;
157
171
  const completed = new Promise((resolve) => idleResolvers.push(resolve));
158
172
  schedule();
@@ -163,7 +177,7 @@ export function createStreamTypewriter({
163
177
  cancelFrame(scheduledFrame);
164
178
  scheduledFrame = null;
165
179
  }
166
- visibleCharacters.push(...pendingCharacters.splice(0));
180
+ takePending(pendingCount());
167
181
  finishing = false;
168
182
  render();
169
183
  resolveIdle();
@@ -1496,11 +1496,11 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1496
1496
 
1497
1497
  .hidden { display: none !important; }
1498
1498
  .editor-view { container-name: editor-workspace; container-type: inline-size; display: grid; grid-template-rows: auto auto minmax(0, 1fr); height: 100%; }
1499
- .editor-toolbar { position: relative; display: grid; grid-row: 1; grid-template-areas: "path path" "title actions"; grid-template-columns: minmax(0, 1fr) auto; align-items: flex-end; column-gap: 20px; row-gap: 6px; padding: 25px 7% 18px; border-bottom: 1px solid var(--line); }
1500
- #chapter-path { grid-area: path; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1501
- .chapter-title { grid-area: title; display: block; width: 100%; min-width: 0; border: 0; border-radius: 0; background: transparent; font-size: 25px; padding: 0; }
1502
- .chapter-title:focus { box-shadow: none; border-bottom: 1px solid var(--accent); }
1503
- .editor-actions { grid-area: actions; display: flex; align-items: center; gap: 8px; }
1499
+ .editor-toolbar { position: relative; display: grid; grid-row: 1; grid-template-areas: "title actions" "path actions"; grid-template-columns: minmax(0, 1fr) auto; align-items: center; column-gap: 16px; row-gap: 2px; padding: 6px 18px; border-bottom: 1px solid var(--line); }
1500
+ #chapter-path { grid-area: path; min-width: 0; margin: 0; color: var(--muted); font-size: 10px; line-height: 14px; letter-spacing: .02em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1501
+ .chapter-title { grid-area: title; display: block; width: 100%; min-width: 0; height: 26px; border: 0; border-radius: 0; background: transparent; font-size: 20px; font-weight: 600; line-height: 26px; padding: 0; text-overflow: ellipsis; }
1502
+ .chapter-title:focus { box-shadow: inset 0 -1px var(--accent); }
1503
+ .editor-actions { grid-area: actions; display: flex; align-items: center; gap: 6px; padding-left: 12px; border-left: 1px solid var(--line); white-space: nowrap; }
1504
1504
  .editor-view .editor-actions > button { min-height: 32px; padding: 6px 10px; font-size: 11px; }
1505
1505
  .chapter-stats { color: var(--muted); font-size: 11px; }
1506
1506
  .chapter-search-panel { position: absolute; z-index: 8; top: calc(100% + 8px); right: 7%; display: grid; width: min(520px, calc(100% - 14%)); gap: 12px; padding: 14px; border: 1px solid var(--line-strong); outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 7px; background: var(--panel); box-shadow: 0 16px 36px rgba(36, 30, 24, .22); }
@@ -1532,9 +1532,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1532
1532
  .chapter-foreshadow-reminder-details dt { color: var(--reminder-color); font-size: 9px; font-weight: 700; }
1533
1533
  .chapter-foreshadow-reminder-details dd { margin: 0; overflow-wrap: anywhere; color: var(--muted); font-size: 10px; line-height: 1.5; white-space: pre-wrap; }
1534
1534
  @container editor-workspace (max-width: 720px) {
1535
- .editor-toolbar { grid-template-areas: "path" "title" "actions"; grid-template-columns: minmax(0, 1fr); gap: 12px; padding-inline: 6%; }
1536
- .chapter-title { min-height: 36px; }
1537
- .editor-actions { justify-content: flex-end; flex-wrap: wrap; }
1535
+ .editor-toolbar { grid-template-areas: "title" "path" "actions"; grid-template-columns: minmax(0, 1fr); }
1536
+ .editor-actions { justify-content: flex-start; flex-wrap: wrap; margin-top: 4px; padding-left: 0; border-left: 0; }
1538
1537
  .chapter-stats { display: none; }
1539
1538
  .chapter-foreshadow-reminder { grid-template-areas: "summary" "actions" "details"; grid-template-columns: minmax(0, 1fr); padding-inline: 6%; }
1540
1539
  .chapter-foreshadow-reminder-actions { justify-content: flex-start; flex-wrap: wrap; }
@@ -3088,13 +3087,6 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3088
3087
  .ai-field-diff-table th:nth-child(2), .ai-field-diff-table th:nth-child(3) { font-size: 8px; }
3089
3088
  }
3090
3089
 
3091
- .guard-card { margin-top: 10px; padding: 9px 10px; border: 1px solid var(--line); border-left: 3px solid var(--green); border-radius: 4px; background: var(--surface-soft); }
3092
- .writing-suggestion-ui { display: contents; }
3093
- .guard-card.warning { border-left-color: #c07b31; }.guard-card.failed { border-left-color: var(--accent); }
3094
- .guard-card strong { display: block; margin-bottom: 5px; font-size: 10px; }.guard-card p { margin: 4px 0; color: var(--muted); font-size: 9px; line-height: 1.45; }.guard-card p b { color: var(--ink); }
3095
- .guard-failure-details { margin-top: 6px; }
3096
- .guard-failure-details summary { width: max-content; max-width: 100%; color: var(--accent-dark); font-size: 9px; line-height: 1.45; cursor: pointer; }
3097
- .guard-failure-details p { margin-top: 6px; padding: 7px 8px; border: 1px solid var(--line); border-radius: 3px; background: var(--surface); white-space: pre-wrap; overflow-wrap: anywhere; }
3098
3090
  .message-actions { display: flex; gap: 7px; margin-top: 10px; }
3099
3091
  .message-actions button { border: 1px solid rgba(139,61,44,.25); background: var(--surface); color: var(--accent-dark); font-size: 10px; padding: 5px 8px; border-radius: 3px; }
3100
3092
  .prompt-box { border-top: 1px solid var(--line); padding-top: 12px; }
@@ -3172,11 +3164,12 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3172
3164
  .ai-image-preview-body { display: grid; place-items: center; min-height: 180px; max-height: calc(88vh - 106px); padding: 16px 20px 20px; overflow: auto; background: var(--paper); }
3173
3165
  .ai-image-preview-body img { display: block; max-width: 100%; max-height: calc(88vh - 150px); width: auto; height: auto; object-fit: contain; border-radius: 3px; }
3174
3166
  .ai-attachment-input { position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none; }
3175
- .ai-attachment-button, .ai-scene-button { display: grid; place-items: center; width: 32px; min-width: 32px; min-height: 32px; height: 32px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); }
3167
+ .ai-attachment-button, .ai-scene-button { display: grid; place-items: center; width: 24px; min-width: 24px; min-height: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); }
3176
3168
  .ai-attachment-button:hover, .ai-attachment-button:focus-visible, .ai-scene-button:hover, .ai-scene-button:focus-visible, .ai-scene-button[aria-expanded="true"], .ai-scene-button.is-active { border-color: color-mix(in srgb, var(--accent) 56%, var(--line)); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); color: var(--accent-dark); outline: 0; }
3177
- .ai-attachment-button:disabled, .ai-scene-button:disabled { cursor: wait; opacity: .56; }
3169
+ .ai-attachment-button:disabled { border-color: var(--line); background: var(--surface); color: var(--muted); cursor: not-allowed; opacity: .56; }
3170
+ .ai-scene-button:disabled { cursor: wait; opacity: .56; }
3178
3171
  .ai-scene-button.hidden { display: none; }
3179
- .ai-image-button-icon, .ai-scene-button-icon { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.7; }
3172
+ .ai-image-button-icon, .ai-scene-button-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.7; }
3180
3173
 
3181
3174
  .character-roleplay-memory-surface { display: grid; grid-column: 1 / -1; gap: 14px; min-width: 0; }
3182
3175
  .roleplay-memory-toolbar { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
@@ -3230,7 +3223,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3230
3223
  .ai-skill-option > span { display: grid; gap: 2px; min-width: 0; }
3231
3224
  .ai-skill-option em { color: var(--muted); font-size: 9px; font-style: normal; line-height: 1.35; overflow-wrap: anywhere; }
3232
3225
  .ai-mention-empty { margin: 0; padding: 10px; color: var(--muted); font-size: 10px; text-align: center; }
3233
- .ai-prompt { display: block; width: 100%; min-height: 104px; margin: 0; padding: 10px 10px 44px; overflow-y: auto; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--ink); font-size: 12px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; cursor: text; }
3226
+ .ai-prompt { display: block; width: 100%; min-height: 104px; margin: 0; padding: 10px 10px 36px; overflow-y: auto; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--ink); font-size: 12px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; cursor: text; }
3234
3227
  .ai-prompt:empty::before { color: var(--muted); content: attr(data-placeholder); pointer-events: none; }
3235
3228
  .ai-prompt:focus { border-color: #a77768; box-shadow: 0 0 0 3px rgba(167,119,104,.12); outline: 0; }
3236
3229
  .ai-prompt-reference { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; margin: 0 2px; padding: 2px 4px 2px 7px; border: 1px solid color-mix(in srgb, var(--accent) 48%, var(--line)); border-radius: 999px; background: color-mix(in srgb, var(--accent) 13%, var(--paper)); color: var(--accent-dark); font-size: 10px; line-height: 1.25; vertical-align: baseline; }
@@ -3238,28 +3231,28 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3238
3231
  .ai-prompt-reference button { width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: inherit; font: 14px/1 var(--font-latin), monospace; }
3239
3232
  .ai-prompt-reference button:hover, .ai-prompt-reference button:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
3240
3233
  .prompt-composer-actions { position: absolute; right: 8px; bottom: 8px; display: flex; gap: 6px; align-items: center; }
3241
- .ai-context-meter { --context-usage: 0; --context-meter-color: var(--green); position: relative; display: grid; flex: 0 0 32px; place-items: center; width: 32px; min-height: 32px; height: 32px; overflow: visible; border: 0; border-radius: 50%; background: conic-gradient(var(--context-meter-color) calc(var(--context-usage) * 1%), rgba(143,132,116,.2) 0); color: var(--ink); font-family: var(--font-latin); line-height: 1; cursor: pointer; }
3242
- .ai-context-meter::before { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--paper); box-shadow: inset 0 0 0 1px rgba(143,132,116,.12); }
3234
+ .ai-context-meter { --context-usage: 0; --context-meter-color: var(--green); position: relative; display: grid; flex: 0 0 24px; place-items: center; width: 24px; min-height: 24px; height: 24px; overflow: visible; border: 0; border-radius: 50%; background: conic-gradient(var(--context-meter-color) calc(var(--context-usage) * 1%), rgba(143,132,116,.2) 0); color: var(--ink); font-family: var(--font-latin); line-height: 1; cursor: pointer; }
3235
+ .ai-context-meter::before { content: ""; position: absolute; inset: 2px; border-radius: 50%; background: var(--paper); box-shadow: inset 0 0 0 1px rgba(143,132,116,.12); }
3243
3236
  .ai-context-meter:hover, .ai-context-meter:focus-visible, .ai-context-meter[aria-expanded="true"] { outline: 0; filter: brightness(.96); }
3244
3237
  .ai-context-meter:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
3245
- .ai-context-meter b { position: relative; z-index: 1; font-size: 9px; font-weight: 600; }.ai-context-meter.is-warning { --context-meter-color: #bf6a35; }.ai-context-meter.is-danger { --context-meter-color: #ad463c; }.ai-context-meter.is-empty { opacity: .62; }
3238
+ .ai-context-meter.is-warning { --context-meter-color: #bf6a35; }.ai-context-meter.is-danger { --context-meter-color: #ad463c; }.ai-context-meter.is-empty { opacity: .62; }
3246
3239
  .ai-context-popover { position: absolute; right: 0; bottom: calc(100% + 10px); z-index: 40; display: grid; width: min(320px, calc(100vw - 32px)); gap: 12px; padding: 14px; border: 1px solid var(--line); border-radius: 7px; background: var(--paper); color: var(--ink); box-shadow: 0 14px 38px rgba(36, 30, 24, .18); }
3247
3240
  .ai-context-popover.hidden { display: none; }
3248
- .ai-context-popover::after { position: absolute; right: 87px; bottom: -6px; width: 10px; height: 10px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--paper); content: ""; transform: rotate(45deg); }
3241
+ .ai-context-popover::after { position: absolute; right: 67px; bottom: -6px; width: 10px; height: 10px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--paper); content: ""; transform: rotate(45deg); }
3249
3242
  .ai-context-popover-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
3250
3243
  .ai-context-popover-header > div { display: grid; gap: 3px; min-width: 0; }
3251
3244
  .ai-context-popover-header strong { font-size: 12px; }
3252
3245
  .ai-context-popover-header small { color: var(--muted); font: 9px/1.4 var(--font-latin), var(--font-cjk), sans-serif; }
3253
3246
  .ai-context-popover-close { flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); font: 17px/1 var(--font-latin), sans-serif; }
3254
3247
  .ai-context-popover-close:hover, .ai-context-popover-close:focus-visible { border-color: var(--accent); color: var(--accent-dark); outline: 0; }
3255
- .ai-model-picker { display: grid; flex: 0 0 32px; place-items: center; width: 32px; min-width: 32px; min-height: 32px; height: 32px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); }
3248
+ .ai-model-picker { display: grid; flex: 0 0 24px; place-items: center; width: 24px; min-width: 24px; min-height: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); }
3256
3249
  .ai-model-picker:hover, .ai-model-picker:focus-visible, .ai-model-picker[aria-expanded="true"] { border-color: color-mix(in srgb, var(--accent) 56%, var(--line)); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); color: var(--accent-dark); outline: 0; }
3257
3250
  .ai-model-picker:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
3258
3251
  .ai-model-picker:disabled { cursor: wait; opacity: .56; }
3259
- .ai-model-picker-icon { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.7; }
3252
+ .ai-model-picker-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.7; }
3260
3253
  .ai-model-popover { position: absolute; right: 0; bottom: calc(100% + 10px); z-index: 41; display: grid; width: min(280px, calc(100vw - 32px)); gap: 7px; padding: 12px; border: 1px solid var(--line); border-radius: 7px; background: var(--paper); color: var(--ink); box-shadow: 0 14px 38px rgba(36, 30, 24, .18); }
3261
3254
  .ai-model-popover.hidden { display: none; }
3262
- .ai-model-popover::after { position: absolute; right: 49px; bottom: -6px; width: 10px; height: 10px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--paper); content: ""; transform: rotate(45deg); }
3255
+ .ai-model-popover::after { position: absolute; right: 37px; bottom: -6px; width: 10px; height: 10px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--paper); content: ""; transform: rotate(45deg); }
3263
3256
  .ai-model-popover > label { color: var(--muted); font-size: 10px; font-weight: 600; }
3264
3257
  .ai-model-native-select { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
3265
3258
  .ai-model-options { display: grid; max-height: 240px; gap: 3px; overflow-y: auto; }
@@ -3287,16 +3280,16 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3287
3280
  .ai-context-distribution-row[data-key="input"] { --distribution-color: var(--green); }
3288
3281
  .ai-context-distribution-row[data-key="output"] { --distribution-color: #718a9f; }
3289
3282
  .ai-context-distribution-row[data-key="left"] { --distribution-color: color-mix(in srgb, var(--muted) 55%, transparent); }
3290
- .ai-send-button { display: grid; flex: 0 0 32px; place-items: center; width: 32px; min-width: 32px; min-height: 32px; height: 32px; padding: 0; border: 0; border-radius: 4px; background: var(--accent); color: #fff; box-shadow: 0 4px 12px rgba(139,61,44,.2); }
3283
+ .ai-send-button { display: grid; flex: 0 0 24px; place-items: center; width: 24px; min-width: 24px; min-height: 24px; height: 24px; padding: 0; border: 0; border-radius: 4px; background: var(--accent); color: #fff; box-shadow: 0 3px 8px rgba(139,61,44,.2); }
3291
3284
  .ai-send-button:hover, .ai-send-button:focus-visible { background: var(--accent-dark); outline: 0; }
3292
- .ai-send-button:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 24%, transparent), 0 4px 12px rgba(139,61,44,.2); }
3285
+ .ai-send-button:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 24%, transparent), 0 3px 8px rgba(139,61,44,.2); }
3293
3286
  .ai-send-button:disabled { cursor: wait; opacity: .62; }
3294
- .ai-send-button-icon { width: 17px; height: 17px; overflow: visible; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; }
3287
+ .ai-send-button-icon { width: 14px; height: 14px; overflow: visible; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; }
3295
3288
  .ai-message-images { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
3296
3289
  .ai-message-image-preview { display: block; width: 68px; height: 68px; padding: 0; overflow: hidden; border: 1px solid color-mix(in srgb, var(--accent) 34%, var(--line)); border-radius: 5px; background: var(--paper); cursor: zoom-in; }
3297
3290
  .ai-message-image-preview:hover, .ai-message-image-preview:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 72%, transparent); outline-offset: 1px; }
3298
3291
  .ai-message-image-preview img { display: block; width: 100%; height: 100%; object-fit: cover; }
3299
- .ai-send-button.is-stop .ai-send-button-icon { width: 15px; height: 15px; fill: currentColor; stroke: none; }
3292
+ .ai-send-button.is-stop .ai-send-button-icon { width: 12px; height: 12px; fill: currentColor; stroke: none; }
3300
3293
  .ai-citations { display: grid; gap: 6px; max-height: 190px; overflow-y: auto; margin-bottom: 8px; }
3301
3294
  .ai-citation-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; border: 1px solid var(--line); border-radius: 5px; background: var(--paper); overflow: hidden; }
3302
3295
  .ai-citation-main { min-width: 0; padding: 8px 9px; border: 0; background: transparent; text-align: left; }
@@ -3328,6 +3321,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3328
3321
  .user-message .message-body:empty { display: none; }
3329
3322
  .user-message-mentions-label { flex: 0 0 auto; opacity: .72; letter-spacing: .08em; }
3330
3323
  .user-message-mention { min-width: 0; max-width: 100%; padding: 2px 6px; overflow-wrap: anywhere; border: 1px solid color-mix(in srgb, currentColor 55%, transparent); border-radius: 999px; background: color-mix(in srgb, currentColor 12%, transparent); }
3324
+ .user-message-inline-mention { display: inline-flex; align-items: center; margin: 0 2px; vertical-align: baseline; font-size: .82em; line-height: 1.35; }
3331
3325
 
3332
3326
  /* Toast 使用 Popover Top Layer;必须覆盖 UA 默认居中/系统边框/Canvas 背景 */
3333
3327
  .toast-region,
@@ -4237,8 +4231,6 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
4237
4231
  @media (max-width: 1100px) {
4238
4232
  .left-panel-body { padding-inline: 10px; }
4239
4233
  .ai-panel { padding-inline: 14px; }
4240
- .editor-toolbar { grid-template-areas: "path" "title" "actions"; grid-template-columns: minmax(0, 1fr); gap: 12px; padding-inline: 6%; }
4241
- .editor-actions { justify-content: flex-end; flex-wrap: wrap; }
4242
4234
  .outline-row { grid-template-columns: 1fr 1fr; }.outline-actions { justify-items: start; }
4243
4235
  .character-editor-workspace, .character-editor-workspace.history-open { grid-template-columns: 150px minmax(0, 1fr); }
4244
4236
  .character-editor-workspace.history-open .character-history-panel { position: absolute; z-index: 3; top: 0; right: 0; bottom: 0; width: min(340px, 44vw); box-shadow: -12px 0 32px rgba(49,42,32,.16); }
@@ -4497,7 +4489,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
4497
4489
  .welcome-view h1 { font-size: clamp(30px, 9vw, 44px); }
4498
4490
  .welcome-grid { grid-template-columns: 1fr; margin-top: 34px; }
4499
4491
 
4500
- .editor-toolbar { padding: 18px var(--mobile-gutter) 14px; }
4492
+ .editor-toolbar { padding: 6px var(--mobile-gutter); }
4501
4493
  .chapter-search-panel { top: calc(100% + 8px); right: var(--mobile-gutter); left: var(--mobile-gutter); width: auto; max-height: calc(100dvh - 120px); overflow-y: auto; }
4502
4494
  .chapter-search-fields { grid-template-columns: minmax(0, 1fr); }
4503
4495
  .chapter-foreshadow-reminder { padding: 10px var(--mobile-gutter); }
@@ -4505,11 +4497,11 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
4505
4497
  .chapter-foreshadow-reminder-counter { grid-column: 1 / -1; justify-self: start; }
4506
4498
  .chapter-foreshadow-reminder-actions button { width: 100%; min-width: 0; min-height: 38px; }
4507
4499
  .chapter-foreshadow-reminder[data-multiple="true"] #chapter-foreshadow-reminder-resolve { grid-column: span 2; }
4508
- .editor-actions { position: static; z-index: auto; right: auto; bottom: auto; left: auto; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; max-width: none; gap: 6px; padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; transform: none; transition: none; }
4500
+ .editor-actions { position: static; z-index: auto; right: auto; bottom: auto; left: auto; display: flex; flex-wrap: wrap; width: 100%; max-width: none; gap: 6px; padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; transform: none; transition: none; }
4509
4501
  .app-shell.left-panel-collapsed .editor-actions { transform: none; pointer-events: auto; }
4510
4502
  .editor-actions .chapter-stats { grid-column: 1 / -1; }
4511
4503
  .editor-view .editor-actions > button { min-width: 0; min-height: 34px; padding-inline: 6px; font-size: 11px; }
4512
- .chapter-title { font-size: 21px; }
4504
+ .chapter-title { font-size: 18px; }
4513
4505
  .chapter-content { padding: 22px 16px 64px 12px; font-size: max(16px, var(--editor-font-size)); }
4514
4506
  .app-shell.editor-typewriter-mode .chapter-content { padding-bottom: max(64px, 45dvh); }
4515
4507
  .chapter-line-numbers { width: 32px; font-size: 11px; }
@@ -4637,7 +4629,6 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
4637
4629
  button.account-button { width: 32px; height: 32px; }
4638
4630
  .top-actions { gap: 3px; }
4639
4631
  .left-primary-actions .file-button { font-size: 10px; }
4640
- .editor-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
4641
4632
  .editor-actions .chapter-stats { display: none; }
4642
4633
  .editor-actions > button { width: auto; }
4643
4634
  .book-shelf { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 12px; max-width: none; }
@@ -4693,7 +4684,7 @@ body { font-size: var(--ui-font-size); }
4693
4684
  .message-body .markdown-image small { font-size: calc(9px * var(--ai-font-scale)); }
4694
4685
  .ai-process-details > summary, .ai-process-step-body, .ai-tool-call-summary { font-size: calc(10px * var(--ai-font-scale)); }
4695
4686
  .ai-process-details > summary > small, .ai-process-step > small { font-size: calc(9px * var(--ai-font-scale)); }
4696
- .guard-card strong, .guard-card p, .message-actions button { font-size: calc(10px * var(--ai-font-scale)); }
4687
+ .message-actions button { font-size: calc(10px * var(--ai-font-scale)); }
4697
4688
  .ai-context-warning strong { font-size: calc(11px * var(--ai-font-scale)); }
4698
4689
  .ai-context-warning p, .ai-context-warning button { font-size: calc(9px * var(--ai-font-scale)); }
4699
4690
  .prompt-options select, .ai-model-popover > label, .ai-model-option-copy strong, .ai-model-options-empty, .ai-mention-option strong, .ai-mention-empty { font-size: calc(10px * var(--ai-font-scale)); }
@@ -4705,7 +4696,6 @@ body { font-size: var(--ui-font-size); }
4705
4696
  .user-message-scene { font-size: calc(10px * var(--ai-font-scale)); }
4706
4697
  .user-message-scene-label { font-size: calc(8px * var(--ai-font-scale)); }
4707
4698
  .ai-prompt-reference { font-size: calc(10px * var(--ai-font-scale)); }
4708
- .ai-context-meter b { font-size: calc(9px * var(--ai-font-scale)); }
4709
4699
  .ai-context-popover-header strong { font-size: calc(12px * var(--ai-font-scale)); }
4710
4700
  .ai-context-popover-header small, .ai-context-distribution-label { font-size: calc(10px * var(--ai-font-scale)); }
4711
4701
  .ai-context-distribution-label small, .ai-context-distribution-label strong { font-size: calc(9px * var(--ai-font-scale)); }