@musnows/scriverse 0.8.6 → 0.8.8

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.
@@ -10,7 +10,7 @@
10
10
  <link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
11
11
  <link rel="manifest" href="/site.webmanifest">
12
12
  <link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
13
- <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=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=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">
13
+ <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=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=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=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1">
14
14
  </head>
15
15
  <body class="auth-pending">
16
16
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -386,7 +386,18 @@
386
386
  </div>
387
387
  <div class="prompt-composer">
388
388
  <div id="ai-image-attachments" class="ai-image-attachments hidden" aria-label="待发送的图片附件" aria-live="polite"></div>
389
- <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>
389
+ <section id="ai-scene-panel" class="ai-scene-panel hidden" aria-label="旁白与场景">
390
+ <div class="ai-scene-pin-fields">
391
+ <label for="ai-scene-location">地点<input id="ai-scene-location" maxlength="200" autocomplete="off"></label>
392
+ <label for="ai-scene-present">在场人物<input id="ai-scene-present" maxlength="500" autocomplete="off"></label>
393
+ <label for="ai-scene-time">故事时间<input id="ai-scene-time" maxlength="200" autocomplete="off"></label>
394
+ </div>
395
+ <label for="ai-scene-direction">旁白 / 场景<textarea id="ai-scene-direction" rows="3" maxlength="20000" placeholder="写下本轮旁白或场景推进,会随下一条台词一起发送"></textarea></label>
396
+ </section>
397
+ <div class="prompt-composer-leading">
398
+ <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>
399
+ <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>
400
+ </div>
390
401
  <input id="ai-attachment-input" class="ai-attachment-input" type="file" accept="image/png,image/jpeg,.jpg,.jpeg" multiple aria-label="选择图片附件">
391
402
  <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>
392
403
  <div id="ai-mention-menu" class="ai-mention-menu hidden" role="listbox" aria-label="引用角色、设定、章节或上下文能力"></div>
@@ -556,7 +567,7 @@
556
567
  <form id="dynamic-form" method="dialog">
557
568
  <div class="dialog-header">
558
569
  <div><span id="dialog-eyebrow" class="eyebrow">新增</span><h2 id="dialog-title">创建记录</h2><input id="dialog-title-input" class="dialog-title-input hidden" type="text" autocomplete="off" aria-label="记录标题"><p id="dialog-meta" class="dialog-header-meta hidden"></p></div>
559
- <button class="dialog-close" value="cancel" aria-label="关闭" type="submit">×</button>
570
+ <div class="settings-dialog-header-actions"><div id="dialog-context-actions" class="dialog-context-actions"></div><button class="dialog-close" value="cancel" aria-label="关闭" type="submit">×</button></div>
560
571
  </div>
561
572
  <div id="dialog-fields" class="dialog-fields"></div>
562
573
  <div id="dialog-submit-status" class="dialog-submit-status hidden" role="status" aria-live="polite">
@@ -647,6 +658,7 @@
647
658
  </div>
648
659
  </div>
649
660
  <div class="entity-editor-mode-actions">
661
+ <button id="setting-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
650
662
  <button id="setting-editor-edit" class="ghost-button hidden" type="button">编辑设定</button>
651
663
  <button id="setting-editor-confirm" class="ghost-button hidden" type="button">确认候选</button>
652
664
  <button id="setting-editor-deprecate" class="ghost-button hidden" type="button">弃用</button>
@@ -673,6 +685,7 @@
673
685
  <div class="character-editor-title-row"><h1 id="character-editor-title">新建角色</h1><span id="character-editor-version" class="character-version-badge">新档案</span><span id="character-editor-readonly-badge" class="entity-editor-readonly-badge hidden">只读模式</span></div>
674
686
  </div>
675
687
  <div class="character-editor-header-actions">
688
+ <button id="character-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
676
689
  <button id="character-editor-edit" class="primary-button hidden" type="button">编辑角色</button>
677
690
  <button id="character-history-button" class="ghost-button" type="button" aria-controls="character-history-panel" aria-expanded="false">版本历史</button>
678
691
  <button id="character-merge-button" class="ghost-button hidden" type="button">合并角色</button>
@@ -682,7 +695,7 @@
682
695
  <div class="character-editor-workspace">
683
696
  <nav class="character-editor-nav" role="tablist" aria-label="人物档案编辑分区">
684
697
  <button type="button" role="tab" data-character-editor-tab="basic" aria-selected="true">基础资料<small>名称、种族与归属</small></button>
685
- <button type="button" role="tab" data-character-editor-tab="profile" aria-selected="false">人物档案<small>简介、身份与动机</small></button>
698
+ <button type="button" role="tab" data-character-editor-tab="profile" aria-selected="false">人物档案<small>简介、人设摘要、身份与动机</small></button>
686
699
  <button type="button" role="tab" data-character-editor-tab="settings" aria-selected="false">扩展设定<small>属性与长篇章节</small></button>
687
700
  <button type="button" role="tab" data-character-editor-tab="state" aria-selected="false">状态与约束<small>当前状态与锁定项</small></button>
688
701
  <button type="button" role="tab" data-character-editor-tab="relationships" aria-selected="false">人物关系<small>关联人物与关系关键词</small></button>
@@ -708,6 +721,7 @@
708
721
  </div>
709
722
  <div class="character-editor-header-actions">
710
723
  <span id="knowledge-editor-header-note" class="knowledge-editor-header-note">独立资料页</span>
724
+ <button id="knowledge-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
711
725
  <button id="knowledge-editor-edit" class="primary-button hidden" type="button">编辑档案</button>
712
726
  <button id="knowledge-editor-history" class="ghost-button hidden" type="button">版本历史</button>
713
727
  <button id="knowledge-editor-merge" class="ghost-button hidden" type="button">合并档案</button>
@@ -1077,13 +1091,12 @@
1077
1091
  <form id="password-form" class="account-settings-form password-settings-form" method="post" action="/api/auth/password"><label>当前密码<input name="currentPassword" type="password" autocomplete="current-password" maxlength="200" required></label><label>新密码<input name="newPassword" type="password" autocomplete="new-password" minlength="10" maxlength="200" required></label><label>确认新密码<input name="passwordConfirmation" type="password" autocomplete="new-password" minlength="10" maxlength="200" required></label><button class="primary-button" type="submit">更新密码</button></form>
1078
1092
  </section>
1079
1093
  <section class="account-settings-section" aria-labelledby="api-key-settings-title">
1080
- <header class="account-settings-section-header"><h3 id="api-key-settings-title">CLI API Key</h3><p>用于 CLI 登录。服务端只保存摘要,重置后旧 Key 会立即失效,新 Key 仅显示一次。</p></header>
1094
+ <header class="account-settings-section-header"><h3 id="api-key-settings-title">CLI API Key</h3><p>用于 CLI 登录。完整 Key 不会显示在页面上,可随时复制;重置后旧 Key 会立即失效。</p></header>
1081
1095
  <div class="api-key-settings">
1082
1096
  <p id="api-key-status">正在读取 API Key 状态…</p>
1083
- <button id="api-key-reset-button" class="primary-button" type="button">生成 API Key</button>
1084
- <div id="api-key-result" class="api-key-result hidden">
1085
- <label>请立即保存<input id="api-key-value" readonly spellcheck="false"></label>
1086
- <button id="api-key-copy-button" class="ghost-button" type="button"><svg class="message-action-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg>复制</button>
1097
+ <div class="api-key-actions">
1098
+ <button id="api-key-copy-button" class="ghost-button" type="button" hidden aria-label="复制 API Key"><svg class="message-action-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg>复制</button>
1099
+ <button id="api-key-reset-button" class="primary-button" type="button">生成 API Key</button>
1087
1100
  </div>
1088
1101
  </div>
1089
1102
  </section>
@@ -1245,6 +1258,6 @@
1245
1258
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1246
1259
  <script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
1247
1260
  <script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
1248
- <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-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-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=phone-client-entry-v1&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&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-v1&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"></script>
1261
+ <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-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-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-v1&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=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2"></script>
1249
1262
  </body>
1250
1263
  </html>
@@ -1,5 +1,5 @@
1
1
  export const MODEL_PURPOSE_OPTIONS: ReadonlyArray<readonly [string, string]>;
2
- export const MODEL_THINKING_EFFORT_OPTIONS: ReadonlyArray<readonly ["default" | "low" | "medium" | "high" | "xhigh" | "max", string]>;
2
+ export const MODEL_THINKING_EFFORT_OPTIONS: ReadonlyArray<readonly ["default" | "auto" | "low" | "medium" | "high" | "xhigh" | "max", string]>;
3
3
  export const MIN_MODEL_CONTEXT_WINDOW: number;
4
4
  export const RECOMMENDED_MODEL_CONTEXT_WINDOW: number;
5
5
  export type ModelProviderProtocolOption = { value: string; supportsMultimodal?: boolean };
@@ -13,7 +13,7 @@ export type ModelFormValues = {
13
13
  temperature: number;
14
14
  maxTokens: number;
15
15
  thinkingEnabled: boolean;
16
- thinkingEffort: "default" | "low" | "medium" | "high" | "xhigh" | "max";
16
+ thinkingEffort: "default" | "auto" | "low" | "medium" | "high" | "xhigh" | "max";
17
17
  multimodalEnabled: boolean;
18
18
  imageToolDefault: boolean;
19
19
  enabled: boolean;
@@ -23,6 +23,6 @@ export function normalizeModelPurposes(purposes: unknown): string[];
23
23
  export function isKimiModelId(modelId: unknown): boolean;
24
24
  export function modelContextWindowGuidance(value: unknown): { belowMinimum: boolean; showRecommendation: boolean };
25
25
  export function modelFormValues(model?: Record<string, unknown> | null): ModelFormValues;
26
- export function modelPayload(values: ModelFormValues, existingPreset?: Record<string, unknown>): Record<string, unknown> & { thinkingEnabled: boolean; thinkingEffort: "default" | "low" | "medium" | "high" | "xhigh" | "max" };
26
+ export function modelPayload(values: ModelFormValues, existingPreset?: Record<string, unknown>): Record<string, unknown> & { thinkingEnabled: boolean; thinkingEffort: "default" | "auto" | "low" | "medium" | "high" | "xhigh" | "max" };
27
27
  export function modelOptionLabel(model: Record<string, unknown> | null | undefined): string;
28
28
  export function modelThinkingEffortLabel(model: Record<string, unknown> | null | undefined): string;
@@ -11,6 +11,7 @@ export const MODEL_PURPOSE_OPTIONS = Object.freeze([
11
11
 
12
12
  export const MODEL_THINKING_EFFORT_OPTIONS = Object.freeze([
13
13
  ["default", "模型默认"],
14
+ ["auto", "自动(auto)"],
14
15
  ["low", "低(low)"],
15
16
  ["medium", "中(medium)"],
16
17
  ["high", "高(high)"],
@@ -104,5 +105,5 @@ export function modelOptionLabel(model) {
104
105
  export function modelThinkingEffortLabel(model) {
105
106
  if (model?.thinkingEnabled !== true) return "";
106
107
  const thinkingEffort = String(model?.thinkingEffort ?? "").trim().toLowerCase();
107
- return thinkingEffort && thinkingEffort !== "default" ? thinkingEffort : "auto";
108
+ return thinkingEffort && thinkingEffort !== "default" ? thinkingEffort : "default";
108
109
  }
@@ -0,0 +1,100 @@
1
+ const SCENE_DIRECTION_TAG = "scene_direction";
2
+ const USER_MESSAGE_TAG = "user_message";
3
+ const SCENE_PIN_LOCATION_MAX = 200;
4
+ const SCENE_PIN_PRESENT_MAX = 500;
5
+ const SCENE_PIN_TIME_MAX = 200;
6
+
7
+ function escapeXmlText(text) {
8
+ return String(text ?? "").replaceAll("&", "&amp;").replaceAll("<", "&lt;");
9
+ }
10
+
11
+ function unescapeXmlText(text) {
12
+ return String(text ?? "").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
13
+ }
14
+
15
+ function wrapRegion(tag, body, escape = true) {
16
+ const trimmed = String(body ?? "").trim();
17
+ if (!trimmed) return "";
18
+ const content = escape ? escapeXmlText(trimmed) : trimmed;
19
+ return `<${tag}>\n${content}\n</${tag}>`;
20
+ }
21
+
22
+ function unwrapRegion(content, tag) {
23
+ const open = `<${tag}>`;
24
+ const close = `</${tag}>`;
25
+ if (!content.startsWith(open)) return null;
26
+ const closeIndex = content.indexOf(close, open.length);
27
+ if (closeIndex < 0) return null;
28
+ const inner = content.slice(open.length, closeIndex).replace(/^\n/u, "").replace(/\n$/u, "");
29
+ const rest = content.slice(closeIndex + close.length).trim();
30
+ return { inner, rest };
31
+ }
32
+
33
+ export function emptyRoleplayScenePin() {
34
+ return { location: "", present: "", timeLabel: "" };
35
+ }
36
+
37
+ export function normalizeRoleplayScenePin(value) {
38
+ const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
39
+ const clip = (raw, maximum) => (typeof raw === "string" ? raw.trim().slice(0, maximum) : "");
40
+ return {
41
+ location: clip(source.location, SCENE_PIN_LOCATION_MAX),
42
+ present: clip(source.present, SCENE_PIN_PRESENT_MAX),
43
+ timeLabel: clip(source.timeLabel, SCENE_PIN_TIME_MAX)
44
+ };
45
+ }
46
+
47
+ export function roleplayScenePinHasContent(pin) {
48
+ const normalized = normalizeRoleplayScenePin(pin);
49
+ return Boolean(normalized.location || normalized.present || normalized.timeLabel);
50
+ }
51
+
52
+ export function formatRoleplayScenePinText(pin) {
53
+ const normalized = normalizeRoleplayScenePin(pin);
54
+ const lines = [];
55
+ if (normalized.location) lines.push(`地点:${normalized.location}`);
56
+ if (normalized.present) lines.push(`在场:${normalized.present}`);
57
+ if (normalized.timeLabel) lines.push(`故事时间:${normalized.timeLabel}`);
58
+ return lines.join("\n");
59
+ }
60
+
61
+ export function composeRoleplayCurrentUserTurn(sceneDirection, userMessage) {
62
+ return [
63
+ wrapRegion(SCENE_DIRECTION_TAG, sceneDirection, true),
64
+ wrapRegion(USER_MESSAGE_TAG, userMessage, false)
65
+ ].filter(Boolean).join("\n\n");
66
+ }
67
+
68
+ export function composeRoleplayStoredUserContent(sceneDirection, userMessage) {
69
+ const scene = wrapRegion(SCENE_DIRECTION_TAG, sceneDirection, true);
70
+ if (!scene) return String(userMessage ?? "");
71
+ const user = wrapRegion(USER_MESSAGE_TAG, userMessage, false);
72
+ return [scene, user].filter(Boolean).join("\n\n");
73
+ }
74
+
75
+ export function parseRoleplayUserTurn(content) {
76
+ const original = String(content ?? "");
77
+ const trimmed = original.trim();
78
+ const scene = unwrapRegion(trimmed, SCENE_DIRECTION_TAG);
79
+ if (!scene) {
80
+ return { sceneDirection: "", userMessage: original, hasMarkup: false };
81
+ }
82
+ const user = unwrapRegion(scene.rest, USER_MESSAGE_TAG);
83
+ return {
84
+ sceneDirection: unescapeXmlText(scene.inner),
85
+ userMessage: user ? user.inner : scene.rest,
86
+ hasMarkup: true
87
+ };
88
+ }
89
+
90
+ export function roleplayUserTurnDisplayText(content) {
91
+ const parsed = parseRoleplayUserTurn(content);
92
+ if (!parsed.hasMarkup) return content;
93
+ return [parsed.sceneDirection, parsed.userMessage].filter(Boolean).join("\n");
94
+ }
95
+
96
+ export function roleplayUserTurnTitleSource(content) {
97
+ const parsed = parseRoleplayUserTurn(content);
98
+ if (!parsed.hasMarkup) return content;
99
+ return parsed.userMessage || parsed.sceneDirection;
100
+ }
@@ -667,6 +667,9 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
667
667
  .settings-work-note { max-width: 1000px; margin: 18px auto 0; color: var(--muted); font-size: 11px; }
668
668
  .usage-page-content { width: 100%; max-width: 1400px; margin: 0 auto; padding-top: 0; }
669
669
  .usage-overview { min-width: 0; container-name: usage-overview; container-type: inline-size; }
670
+ .usage-overview-header { align-items: flex-start; gap: 16px; }
671
+ .usage-overview-header > div { min-width: 0; }
672
+ .usage-details-button { flex: 0 0 auto; min-height: 32px; padding: 7px 11px; font-size: 11px; }
670
673
  .usage-stat-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
671
674
  .usage-stat {
672
675
  display: grid;
@@ -738,10 +741,41 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
738
741
  .usage-work-table th:first-child, .usage-work-table td:first-child { text-align: left; }
739
742
  .usage-work-table tbody th { max-width: 320px; overflow: hidden; color: var(--ink); font-weight: 600; text-overflow: ellipsis; }
740
743
  .usage-work-table tbody tr:last-child > * { border-bottom: 0; }
744
+ .toast.token-usage-details-toast {
745
+ display: grid;
746
+ grid-template-rows: auto minmax(0, 1fr);
747
+ width: min(1200px, calc(100vw - 48px));
748
+ max-width: none;
749
+ max-height: min(72dvh, 560px);
750
+ padding: 0;
751
+ overflow: hidden;
752
+ border-color: color-mix(in srgb, var(--accent) 42%, var(--line));
753
+ background: var(--panel);
754
+ color: var(--ink);
755
+ pointer-events: auto;
756
+ }
757
+ .token-usage-details-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 11px 12px 10px 14px; border-bottom: 1px solid var(--line); }
758
+ .token-usage-details-heading { display: flex; min-width: 0; align-items: center; gap: 8px; }
759
+ .token-usage-details-heading strong { font-size: 12px; }
760
+ .token-usage-details-heading span { min-width: 0; overflow: hidden; color: var(--accent-dark); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
761
+ .token-usage-details-close { flex: 0 0 auto; min-height: 28px; padding: 5px 9px; font-size: 10px; }
762
+ .token-usage-details-body { display: grid; gap: 10px; min-height: 0; padding: 12px 14px 14px; overflow-y: auto; color: var(--muted); font-size: 11px; line-height: 1.6; overscroll-behavior: contain; }
763
+ .token-usage-details-note { margin: 0; }
764
+ .token-usage-details-table-scroll { min-width: 0; overflow-x: auto; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); }
765
+ .token-usage-details-table { width: 100%; min-width: 820px; border-collapse: collapse; color: var(--ink); font: 10px/1.45 var(--font-latin), var(--font-cjk), monospace; }
766
+ .token-usage-details-table caption { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
767
+ .token-usage-details-table th, .token-usage-details-table td { padding: 9px 10px; border-bottom: 1px solid var(--line); text-align: right; white-space: nowrap; }
768
+ .token-usage-details-table thead th { background: var(--surface-soft); color: var(--muted); font-size: 9px; font-weight: 600; letter-spacing: .03em; }
769
+ .token-usage-details-table th:first-child, .token-usage-details-table td:first-child { text-align: left; }
770
+ .token-usage-details-table tbody th { max-width: 240px; overflow: hidden; text-overflow: ellipsis; font-weight: 600; }
771
+ .token-usage-details-table tfoot th, .token-usage-details-table tfoot td { border-top: 1px solid color-mix(in srgb, var(--accent) 38%, var(--line)); border-bottom: 0; background: color-mix(in srgb, var(--accent) 6%, var(--surface-soft)); font-weight: 700; }
772
+ .token-usage-details-table tbody tr:last-child > * { border-bottom: 0; }
741
773
  @container usage-overview (max-width: 760px) {
742
774
  .usage-stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
743
775
  }
744
776
  @container usage-overview (max-width: 420px) {
777
+ .usage-overview-header { flex-wrap: wrap; }
778
+ .usage-details-button { width: 100%; }
745
779
  .usage-stat-grid { grid-template-columns: minmax(0, 1fr); }
746
780
  }
747
781
  .product-footer { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px 12px; width: 100%; max-width: 1400px; margin: auto auto 0; padding-top: 20px; border-top: 1px solid color-mix(in srgb, var(--line) 76%, transparent); color: var(--muted); font-size: 10px; line-height: 1.5; }
@@ -1206,11 +1240,8 @@ body.work-viewer-mode .character-editor-actions { display: none !important; }
1206
1240
  .password-settings-form { grid-template-columns: repeat(3, minmax(0, 1fr)) auto; }
1207
1241
  .api-key-settings { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 12px; margin-top: 16px; }
1208
1242
  .api-key-settings > p { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.6; }
1209
- .api-key-result { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 10px; }
1210
- .api-key-result.hidden { display: none; }
1211
- .api-key-result label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
1212
- .api-key-result input { width: 100%; min-height: 38px; padding: 9px 10px; background: var(--surface); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 11px; }
1213
- .api-key-result button { display: inline-flex; align-items: center; gap: 6px; min-height: 38px; }
1243
+ .api-key-actions { display: inline-flex; align-items: center; gap: 8px; }
1244
+ .api-key-actions button { display: inline-flex; align-items: center; gap: 6px; min-height: 38px; }
1214
1245
  .book-access-badge { display: inline-flex; width: fit-content; margin-top: 7px; padding: 2px 7px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--accent-dark); font-size: 9px; }
1215
1246
  .book-shelf { display: grid; grid-template-columns: repeat(auto-fill, minmax(205px, 1fr)); gap: clamp(22px, 3vw, 38px); width: 100%; min-width: 0; max-width: 1400px; margin: 0 auto; }
1216
1247
  .book-card { position: relative; min-width: 0; border: 0; background: transparent; text-align: left; }
@@ -1479,9 +1510,29 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1479
1510
  .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 13px; }
1480
1511
  .record-card { position: relative; border: 1px solid var(--line); background: var(--surface-soft); padding: 18px; min-height: 130px; border-radius: 4px; }
1481
1512
  .record-card.has-card-edit h3 { padding-right: 34px; }
1513
+ .record-card.has-favorite-control h3 { padding-right: 70px; }
1482
1514
  .record-card-edit { position: absolute; top: 12px; right: 12px; display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); }
1483
1515
  .record-card-edit:hover, .record-card-edit:focus-visible { border-color: var(--accent); color: var(--accent-dark); outline: none; }
1484
1516
  .record-card-edit svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.7; }
1517
+ .character-favorite-button { position: absolute; top: 12px; right: 48px; display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); }
1518
+ .character-favorite-button:hover, .character-favorite-button:focus-visible { border-color: var(--accent); color: var(--accent-dark); outline: none; }
1519
+ .character-favorite-button.is-favorite { border-color: color-mix(in srgb, var(--accent) 58%, var(--line)); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); color: var(--accent-dark); }
1520
+ .character-favorite-button:disabled { cursor: not-allowed; opacity: .5; }
1521
+ .character-favorite-button svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.6; }
1522
+ .character-favorite-button.is-favorite svg { fill: currentColor; }
1523
+ .record-favorite-button { display: inline-grid; flex: 0 0 auto; place-items: center; width: 28px; height: 28px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); }
1524
+ .record-favorite-button.is-card-control { position: absolute; top: 12px; right: 48px; }
1525
+ .record-favorite-button:hover, .record-favorite-button:focus-visible { border-color: var(--accent); color: var(--accent-dark); outline: none; }
1526
+ .record-favorite-button.is-favorite { border-color: color-mix(in srgb, var(--accent) 58%, var(--line)); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); color: var(--accent-dark); }
1527
+ .record-favorite-button:disabled { cursor: not-allowed; opacity: .5; }
1528
+ .record-favorite-button svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.6; }
1529
+ .record-favorite-button.is-favorite svg { fill: currentColor; }
1530
+ .entity-detail-favorite-button { display: inline-grid; flex: 0 0 36px; place-items: center; width: 36px; min-width: 36px; height: 36px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); }
1531
+ .entity-detail-favorite-button:hover, .entity-detail-favorite-button:focus-visible { border-color: var(--accent); color: var(--accent-dark); outline: none; }
1532
+ .entity-detail-favorite-button.is-favorite { border-color: color-mix(in srgb, var(--accent) 58%, var(--line)); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); color: var(--accent-dark); }
1533
+ .entity-detail-favorite-button:disabled { cursor: not-allowed; opacity: .5; }
1534
+ .entity-detail-favorite-button svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.6; }
1535
+ .entity-detail-favorite-button.is-favorite svg { fill: currentColor; }
1485
1536
  .record-card h3 { font-size: 16px; margin: 5px 0 10px; font-weight: 600; }
1486
1537
  .record-card p { color: var(--muted); font-size: 12px; line-height: 1.55; margin: 0; white-space: pre-wrap; }
1487
1538
  .record-card small { color: var(--accent); font-size: 9px; letter-spacing: .1em; text-transform: uppercase; }
@@ -1517,8 +1568,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1517
1568
  .character-card-heading { display: flex; min-width: 0; align-items: center; gap: 7px; }
1518
1569
  .character-card-heading .character-avatar { width: 32px; height: 32px; font-size: 12px; }
1519
1570
  .character-card-heading h3 { min-width: 0; }
1520
- .record-card.has-card-edit .character-card-heading { padding-right: 34px; }
1571
+ .record-card.has-card-edit .character-card-heading { padding-right: 70px; }
1521
1572
  .record-card.has-card-edit .character-card-heading h3 { padding-right: 0; }
1573
+ .module-row .character-favorite-button { position: static; }
1574
+ .module-row .record-favorite-button { position: static; }
1522
1575
  .character-lock-badge { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 3px; min-height: 20px; padding: 2px 6px; border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line)); border-radius: 10px; background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-dark); font-size: 9px; font-variant-numeric: tabular-nums; line-height: 1; }
1523
1576
  .entity-lifecycle-badge { display: inline-flex; align-items: center; min-height: 20px; margin-left: 7px; padding: 2px 7px; border: 1px solid color-mix(in srgb, var(--accent) 58%, var(--line)); border-radius: 10px; background: color-mix(in srgb, var(--accent) 13%, var(--surface)); color: var(--accent-dark); font-size: 9px; font-weight: 650; line-height: 1; vertical-align: middle; white-space: nowrap; }
1524
1577
  .character-lock-badge svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; }
@@ -1563,6 +1616,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1563
1616
  .card-actions { display: flex; gap: 6px; margin-top: 15px; }
1564
1617
  .card-actions button { border: 1px solid var(--line); background: transparent; border-radius: 3px; font-size: 10px; padding: 5px 8px; }
1565
1618
  .card-actions .record-card-edit { position: static; padding: 0; }
1619
+ .card-actions .record-favorite-button { position: static; padding: 0; }
1566
1620
  .danger-button { border: 1px solid color-mix(in srgb, var(--accent) 52%, var(--line)); border-radius: 4px; background: transparent; color: var(--accent-dark); padding: 8px 13px; font-size: 12px; }
1567
1621
  .danger-button:hover, .danger-button:focus-visible { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, transparent); outline: none; }
1568
1622
  .card-actions .danger-button { border-color: color-mix(in srgb, var(--accent) 52%, var(--line)); color: var(--accent-dark); }
@@ -2716,6 +2770,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2716
2770
  .message-body .markdown-image small { color: var(--muted); font-size: 9px; line-height: 1.45; text-align: center; }
2717
2771
  .message-meta { margin-top: 8px; color: var(--muted); font-size: 9px; }
2718
2772
  .message-meta .ai-stream-character-count { display: inline-block; min-width: 6ch; font-variant-numeric: tabular-nums; text-align: right; }
2773
+ .message-meta .ai-stream-connection-seconds { display: inline-block; min-width: 3ch; font-variant-numeric: tabular-nums; text-align: right; }
2719
2774
  .ai-process-details { margin: 0 0 10px; overflow: hidden; border: 1px solid var(--line); border-radius: 5px; background: color-mix(in srgb, var(--surface-soft) 82%, transparent); }
2720
2775
  .ai-process-details > summary { display: flex; align-items: center; justify-content: space-between; gap: 9px; padding: 8px 10px; color: var(--muted); cursor: pointer; font-size: 10px; list-style: none; }
2721
2776
  .ai-process-details > summary::-webkit-details-marker { display: none; }
@@ -2778,6 +2833,16 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2778
2833
  .prompt-options .ai-roleplay-user-character { grid-column: 1 / -1; }
2779
2834
  .ai-panel.is-roleplaying .ai-roleplay-character { border-color: color-mix(in srgb, var(--accent) 52%, var(--line)); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); color: var(--accent-dark); }
2780
2835
  .prompt-composer { position: relative; }
2836
+ .prompt-composer-leading { position: absolute; bottom: 8px; left: 8px; z-index: 2; display: flex; gap: 6px; align-items: center; }
2837
+ .ai-scene-panel { display: grid; gap: 7px; margin-bottom: 6px; padding: 8px; border: 1px solid var(--line); border-radius: 5px; background: var(--paper); }
2838
+ .ai-scene-panel.hidden { display: none; }
2839
+ .ai-scene-pin-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
2840
+ @media (max-width: 640px) {
2841
+ .ai-scene-pin-fields { grid-template-columns: minmax(0, 1fr); }
2842
+ }
2843
+ .ai-scene-panel label { display: grid; gap: 3px; color: var(--muted); font-size: 9px; }
2844
+ .ai-scene-panel input, .ai-scene-panel textarea { width: 100%; padding: 6px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--ink); font-size: 10px; }
2845
+ .ai-scene-panel textarea { min-height: 64px; resize: vertical; }
2781
2846
  .ai-image-attachments { display: flex; gap: 5px; max-height: 38px; margin-bottom: 6px; overflow-x: auto; overflow-y: hidden; padding: 5px 0 0; }
2782
2847
  .ai-image-attachment { display: inline-flex; flex: 0 0 auto; align-items: center; width: auto; height: 28px; margin: 0; padding: 2px 3px 2px 4px; border: 1px solid color-mix(in srgb, var(--accent) 48%, var(--line)); border-radius: 5px; background: var(--paper); }
2783
2848
  .ai-image-attachment-preview { display: inline-flex; align-items: center; gap: 4px; width: auto; height: 22px; min-width: 0; padding: 0 3px 0 0; border: 0; background: transparent; color: var(--ink); font: 9px/1 var(--font-cjk), sans-serif; white-space: nowrap; cursor: zoom-in; }
@@ -2793,10 +2858,11 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2793
2858
  .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); }
2794
2859
  .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; }
2795
2860
  .ai-attachment-input { position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none; }
2796
- .ai-attachment-button { position: absolute; bottom: 8px; left: 8px; z-index: 2; 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); }
2797
- .ai-attachment-button:hover, .ai-attachment-button:focus-visible { 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; }
2798
- .ai-attachment-button:disabled { cursor: wait; opacity: .56; }
2799
- .ai-image-button-icon { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.7; }
2861
+ .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); }
2862
+ .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; }
2863
+ .ai-attachment-button:disabled, .ai-scene-button:disabled { cursor: wait; opacity: .56; }
2864
+ .ai-scene-button.hidden { display: none; }
2865
+ .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; }
2800
2866
  .ai-mention-menu { position: absolute; z-index: 20; left: 0; right: 0; bottom: calc(100% + 6px); max-height: 230px; overflow-y: auto; padding: 6px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); box-shadow: 0 12px 34px rgba(49,42,32,.18); }
2801
2867
  .ai-mention-option { display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 8px; width: 100%; padding: 7px 8px; border: 0; border-radius: 4px; background: transparent; text-align: left; }
2802
2868
  .ai-mention-option:hover, .ai-mention-option:focus-visible, .ai-mention-option.is-active { background: var(--paper-deep); }
@@ -2879,6 +2945,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2879
2945
  .ai-citation-card.is-expanded pre { display: block; }
2880
2946
  .message-citations { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 7px; }.message-citations span { padding: 3px 6px; border: 1px solid currentColor; border-radius: 10px; font-size: 8px; opacity: .75; }
2881
2947
  .user-message-mentions { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; min-width: 0; margin-top: 8px; padding-top: 7px; border-top: 1px solid color-mix(in srgb, currentColor 28%, transparent); font-size: 8px; line-height: 1.4; }
2948
+ .user-message-scene { margin: 0 0 8px; padding: 0 0 7px; border-bottom: 1px solid color-mix(in srgb, currentColor 28%, transparent); font-size: 10px; line-height: 1.5; }
2949
+ .user-message-scene-label { display: block; margin-bottom: 3px; opacity: .72; letter-spacing: .08em; font-size: 8px; }
2950
+ .user-message-scene-body { white-space: pre-wrap; overflow-wrap: anywhere; }
2951
+ .user-message .message-body:empty { display: none; }
2882
2952
  .user-message-mentions-label { flex: 0 0 auto; opacity: .72; letter-spacing: .08em; }
2883
2953
  .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); }
2884
2954
 
@@ -2907,6 +2977,17 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2907
2977
  .toast-region[data-position="top-right"]:popover-open { top: 78px; right: 24px; bottom: auto; left: auto; }
2908
2978
  .toast-region[data-position="bottom-right"],
2909
2979
  .toast-region[data-position="bottom-right"]:popover-open { top: auto; right: 24px; bottom: 24px; left: auto; }
2980
+ .toast-region.token-usage-details-region,
2981
+ .toast-region.token-usage-details-region:popover-open {
2982
+ top: 50%;
2983
+ right: auto;
2984
+ bottom: auto;
2985
+ left: 50%;
2986
+ width: min(1200px, calc(100vw - 32px));
2987
+ max-width: calc(100vw - 32px);
2988
+ transform: translate(-50%, -50%);
2989
+ }
2990
+ .toast-region.token-usage-details-region .token-usage-details-toast { width: 100%; }
2910
2991
  .toast {
2911
2992
  max-width: 360px;
2912
2993
  padding: 11px 15px;
@@ -3029,6 +3110,13 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3029
3110
  .dialog::backdrop { background: rgba(34,30,25,.42); backdrop-filter: blur(3px); }
3030
3111
  .dialog-header { display: flex; justify-content: space-between; align-items: flex-start; padding: 22px 24px 16px; border-bottom: 1px solid var(--line); }
3031
3112
  .dialog-header h2 { margin: 0; font-size: 23px; font-weight: 500; }
3113
+ #form-dialog .dialog-header { gap: 12px; }
3114
+ #form-dialog .dialog-header > div:first-child { flex: 1 1 auto; min-width: 0; }
3115
+ #form-dialog .settings-dialog-header-actions { flex: 0 0 auto; }
3116
+ #form-dialog .dialog-title-input { width: min(560px, 100%); }
3117
+ .dialog-context-actions { display: flex; align-items: center; gap: 8px; }
3118
+ .dialog-context-actions:empty { display: none; }
3119
+ .dialog-context-actions .ghost-button { min-height: 36px; white-space: nowrap; }
3032
3120
  .dialog-title-input { display: block; width: min(560px, calc(100vw - 150px)); min-width: 0; margin: -5px 0 0; padding: 0 0 4px; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--ink); font-size: 23px; font-weight: 500; line-height: 1.3; }
3033
3121
  .dialog-title-input:focus { border-bottom-color: var(--accent); outline: none; }
3034
3122
  .dialog-title-input::placeholder { color: var(--muted); opacity: .8; }
@@ -3475,7 +3563,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3475
3563
  .character-editor-section > header p { max-width: 430px; margin: 0; color: var(--muted); font-size: 10px; line-height: 1.55; text-align: right; }
3476
3564
  .character-editor-section-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 17px 20px; }
3477
3565
  .character-editor-section-fields > label, .character-editor-section-fields > .form-field { display: grid; align-content: start; gap: 7px; color: var(--muted); font-size: 11px; }
3478
- .character-editor-section-fields > label:has(textarea), .character-editor-section-fields > .structured-list-field, .character-editor-section-fields > .chip-field, .character-editor-section-fields > .markdown-editor-field, .character-editor-field-help { grid-column: 1 / -1; }
3566
+ .character-editor-section-fields > label:has(textarea), .character-editor-section-fields > .form-field:has(textarea), .character-editor-section-fields > .structured-list-field, .character-editor-section-fields > .chip-field, .character-editor-section-fields > .markdown-editor-field, .character-editor-field-help { grid-column: 1 / -1; }
3479
3567
  .character-editor-section-fields input, .character-editor-section-fields textarea, .character-editor-section-fields select { width: 100%; padding: 10px 11px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); font-size: 13px; }
3480
3568
  .character-editor-section-fields textarea { min-height: 120px; resize: vertical; line-height: 1.65; }
3481
3569
  .character-editor-section-fields .section-list-row textarea { min-height: 180px; }
@@ -3756,7 +3844,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3756
3844
  .ai-history-dialog-body { min-height: min(360px, 62vh); padding: 12px 14px 18px; }
3757
3845
  .account-settings-body { padding: 16px; }
3758
3846
  .account-settings-section { padding: 15px; }
3759
- .account-settings-form, .password-settings-form, .api-key-settings, .api-key-result { grid-template-columns: minmax(0, 1fr); }
3847
+ .account-settings-form, .password-settings-form, .api-key-settings { grid-template-columns: minmax(0, 1fr); }
3848
+ .api-key-actions { width: 100%; }
3849
+ .api-key-actions button { flex: 1; }
3760
3850
  .avatar-settings { grid-template-columns: 64px minmax(0, 1fr); }
3761
3851
  .profile-avatar-preview { width: 64px; height: 64px; }
3762
3852
  .character-avatar-editor-preview { width: 64px; height: 64px; }
@@ -3767,6 +3857,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3767
3857
  .account-settings-form .primary-button { width: 100%; }
3768
3858
  .usage-stat-grid { grid-template-columns: minmax(0, 1fr); }
3769
3859
  .usage-stat { min-height: 104px; }
3860
+ .ai-scene-pin-fields { grid-template-columns: minmax(0, 1fr); }
3770
3861
  }
3771
3862
 
3772
3863
  /* 移动端工作台:仅在窄屏覆盖桌面三栏布局,保留桌面端原有尺寸与排列。 */
@@ -4053,6 +4144,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
4053
4144
  .entity-editor-header, .character-editor-header { grid-template-columns: auto minmax(0, 1fr); gap: 8px; padding: 12px var(--mobile-gutter); }
4054
4145
  .entity-editor-mode-actions, .character-editor-header-actions { grid-column: 1 / -1; justify-content: stretch; flex-wrap: wrap; }
4055
4146
  .entity-editor-mode-actions > button, .character-editor-header-actions > button { flex: 1 1 auto; }
4147
+ .entity-editor-mode-actions > .entity-detail-favorite-button, .character-editor-header-actions > .entity-detail-favorite-button { flex: 0 0 40px; width: 40px; min-width: 40px; height: 40px; }
4148
+ .dialog-context-actions .entity-detail-favorite-button { flex-basis: 40px; width: 40px; min-width: 40px; height: 40px; }
4056
4149
  .setting-editor-header { min-height: 0; }
4057
4150
  .setting-editor-content { padding: 12px; }
4058
4151
  .setting-markdown-field { height: 68dvh; }
@@ -4130,6 +4223,10 @@ body { font-size: var(--ui-font-size); }
4130
4223
  .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)); }
4131
4224
  .ai-mention-option small { font-size: calc(8px * var(--ai-font-scale)); }
4132
4225
  .ai-prompt { font-size: var(--ai-font-size); }
4226
+ .ai-scene-panel label { font-size: calc(9px * var(--ai-font-scale)); }
4227
+ .ai-scene-panel input, .ai-scene-panel textarea { font-size: calc(10px * var(--ai-font-scale)); }
4228
+ .user-message-scene { font-size: calc(10px * var(--ai-font-scale)); }
4229
+ .user-message-scene-label { font-size: calc(8px * var(--ai-font-scale)); }
4133
4230
  .ai-prompt-reference { font-size: calc(10px * var(--ai-font-scale)); }
4134
4231
  .ai-context-meter b { font-size: calc(9px * var(--ai-font-scale)); }
4135
4232
  .ai-context-popover-header strong { font-size: calc(12px * var(--ai-font-scale)); }
@@ -0,0 +1,4 @@
1
+ export function resolveToastRegionHost<TDialog extends { open?: boolean }, TFallback>(
2
+ dialogs: ReadonlyArray<TDialog | null | undefined> | null | undefined,
3
+ fallbackHost?: TFallback | null
4
+ ): TDialog | TFallback | null;
@@ -0,0 +1,5 @@
1
+ /** 打开的模态对话框会让文档其余部分 inert;Toast 必须挂到最顶层对话框内才能接收点击。 */
2
+ export function resolveToastRegionHost(dialogs, fallbackHost) {
3
+ const openDialogs = (Array.isArray(dialogs) ? dialogs : []).filter((dialog) => Boolean(dialog?.open));
4
+ return openDialogs.at(-1) ?? fallbackHost ?? null;
5
+ }