@musnows/scriverse 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/ai.js +248 -250
- package/dist/ai.js.map +1 -1
- package/dist/app.js +184 -39
- package/dist/app.js.map +1 -1
- package/dist/attachment-storage.js +28 -12
- package/dist/attachment-storage.js.map +1 -1
- package/dist/backup-encryption.js +129 -0
- package/dist/backup-encryption.js.map +1 -0
- package/dist/collaboration-presence.js +200 -18
- package/dist/collaboration-presence.js.map +1 -1
- package/dist/database.js +162 -3
- package/dist/database.js.map +1 -1
- package/dist/docx-export.js +1 -1
- package/dist/docx-export.js.map +1 -1
- package/dist/domain.js +18 -0
- package/dist/domain.js.map +1 -1
- package/dist/image-metadata.js +17 -2
- package/dist/image-metadata.js.map +1 -1
- package/dist/presence-store.js +160 -0
- package/dist/presence-store.js.map +1 -0
- package/dist/public/ai-context-meter.js +27 -0
- package/dist/public/ai-mentions.js +14 -0
- package/dist/public/app.js +1017 -291
- package/dist/public/background-task-center.js +1 -1
- package/dist/public/chapter-editor-virtualization.js +52 -0
- package/dist/public/index.html +43 -21
- package/dist/public/presence-client-id.d.ts +10 -0
- package/dist/public/presence-client-id.js +31 -0
- package/dist/public/s3-backup-ui.d.ts +10 -0
- package/dist/public/s3-backup-ui.js +29 -0
- package/dist/public/setting-filters.d.ts +16 -0
- package/dist/public/setting-filters.js +19 -0
- package/dist/public/styles.css +37 -7
- package/dist/s3-backup.js +197 -12
- package/dist/s3-backup.js.map +1 -1
- package/dist/security.js +82 -20
- package/dist/security.js.map +1 -1
- package/dist/server-runtime.js +36 -18
- package/dist/server-runtime.js.map +1 -1
- package/dist/store.js +75 -17
- package/dist/store.js.map +1 -1
- package/dist/upload-limits.js +35 -0
- package/dist/upload-limits.js.map +1 -0
- package/dist/user-auth.js +11 -1
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const activeTaskStatuses = new Set(["pending", "running"]);
|
|
2
|
-
const terminalTaskStatuses = new Set(["review", "completed", "partial", "expired", "cancelled"]);
|
|
2
|
+
const terminalTaskStatuses = new Set(["review", "completed", "partial", "failed", "expired", "cancelled"]);
|
|
3
3
|
|
|
4
4
|
export function backgroundTaskActivityCount(taskPage, relationshipIndex) {
|
|
5
5
|
const pendingCount = Number(taskPage?.stats?.pendingCount ?? 0);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const lineMarker = "\u200b";
|
|
2
|
+
|
|
3
|
+
export function buildChapterLineMirror(value) {
|
|
4
|
+
const lines = String(value ?? "").replace(/\r\n?/gu, "\n").split("\n");
|
|
5
|
+
const offsets = [];
|
|
6
|
+
const parts = [];
|
|
7
|
+
let offset = 0;
|
|
8
|
+
lines.forEach((line, index) => {
|
|
9
|
+
offsets.push(offset);
|
|
10
|
+
parts.push(lineMarker, line);
|
|
11
|
+
offset += lineMarker.length + line.length;
|
|
12
|
+
if (index < lines.length - 1) {
|
|
13
|
+
parts.push("\n");
|
|
14
|
+
offset += 1;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return { lines, offsets, text: parts.join("") };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function findChapterLineWindow(lineCount, getBounds, viewportStart, viewportEnd) {
|
|
21
|
+
const count = Math.max(0, Math.floor(Number(lineCount) || 0));
|
|
22
|
+
if (count === 0) return { start: 0, end: 0 };
|
|
23
|
+
const startTarget = Number.isFinite(Number(viewportStart)) ? Number(viewportStart) : 0;
|
|
24
|
+
const endTarget = Math.max(startTarget, Number.isFinite(Number(viewportEnd)) ? Number(viewportEnd) : startTarget);
|
|
25
|
+
|
|
26
|
+
let low = 0;
|
|
27
|
+
let high = count - 1;
|
|
28
|
+
let start = count - 1;
|
|
29
|
+
while (low <= high) {
|
|
30
|
+
const middle = Math.floor((low + high) / 2);
|
|
31
|
+
if (getBounds(middle).bottom >= startTarget) {
|
|
32
|
+
start = middle;
|
|
33
|
+
high = middle - 1;
|
|
34
|
+
} else {
|
|
35
|
+
low = middle + 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
low = start;
|
|
40
|
+
high = count - 1;
|
|
41
|
+
let end = start;
|
|
42
|
+
while (low <= high) {
|
|
43
|
+
const middle = Math.floor((low + high) / 2);
|
|
44
|
+
if (getBounds(middle).top <= endTarget) {
|
|
45
|
+
end = middle;
|
|
46
|
+
low = middle + 1;
|
|
47
|
+
} else {
|
|
48
|
+
high = middle - 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { start, end: end + 1 };
|
|
52
|
+
}
|
package/dist/public/index.html
CHANGED
|
@@ -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=
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260812-agent-chat-font-size-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<button id="auth-login-tab" type="button" role="tab" aria-selected="true">登录</button>
|
|
24
24
|
<button id="auth-register-tab" type="button" role="tab" aria-selected="false" aria-disabled="true" disabled>注册已禁用</button>
|
|
25
25
|
</div>
|
|
26
|
-
<form id="login-form" class="auth-form">
|
|
26
|
+
<form id="login-form" class="auth-form" method="post" action="/api/auth/login">
|
|
27
27
|
<label>用户名<input name="username" autocomplete="username" maxlength="100" required></label>
|
|
28
28
|
<label>密码<input id="login-password" name="password" type="password" autocomplete="current-password" maxlength="200" required aria-describedby="login-lock-hint"></label>
|
|
29
29
|
<p id="login-lock-hint" class="auth-security-hint">登录和注册采用验证码与来源限速防护,不会因他人输错密码而锁定账户。</p>
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<p id="login-captcha-hint" class="auth-captcha-hint">点击按钮显示验证码,显示后点击图片可刷新</p>
|
|
39
39
|
<button class="primary-button" type="submit">登录</button>
|
|
40
40
|
</form>
|
|
41
|
-
<form id="register-form" class="auth-form hidden">
|
|
41
|
+
<form id="register-form" class="auth-form hidden" method="post" action="/api/auth/register">
|
|
42
42
|
<label>用户名<input name="username" autocomplete="username" minlength="3" maxlength="40" pattern="[A-Za-z0-9_.\-\u4e00-\u9fff]+" required></label>
|
|
43
43
|
<label id="register-setup-token-field" class="hidden">初始化令牌<input name="setupToken" type="password" autocomplete="off" minlength="32" maxlength="500" aria-describedby="register-setup-token-hint"><small id="register-setup-token-hint">请输入服务端 APP_SETUP_TOKEN 的值。</small></label>
|
|
44
44
|
<label>密码<span class="password-field"><input id="register-password" name="password" type="password" autocomplete="new-password" minlength="10" maxlength="200" required><button class="password-toggle" type="button" data-password-toggle="register-password" aria-label="显示密码" aria-pressed="false" title="显示密码"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M2.5 12s3.3-5.5 9.5-5.5S21.5 12 21.5 12 18.2 17.5 12 17.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="2.8"/></svg></button></span></label>
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
<nav id="module-nav" class="module-nav" aria-label="作品模块">
|
|
127
127
|
<button type="button" data-module="editor" class="active"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>正文</button>
|
|
128
128
|
<button type="button" data-module="drafts"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20h4l11-11a2.8 2.8 0 0 0-4-4L4 16v4Z"/><path d="m13.5 6.5 4 4"/></svg>想法</button>
|
|
129
|
-
<button type="button" data-module="settings"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/></svg
|
|
129
|
+
<button type="button" data-module="settings"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/></svg>设定</button>
|
|
130
130
|
<button type="button" data-module="characters"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>角色</button>
|
|
131
131
|
<button type="button" data-module="organizations"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>组织</button>
|
|
132
132
|
<button type="button" data-module="timeline"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>时间轴</button>
|
|
@@ -330,7 +330,7 @@
|
|
|
330
330
|
<select id="ai-model" aria-label="实际使用模型"></select>
|
|
331
331
|
</div>
|
|
332
332
|
<div class="prompt-composer">
|
|
333
|
-
<div id="ai-prompt" class="ai-prompt" contenteditable="true" role="textbox" aria-multiline="true" aria-keyshortcuts="Enter" title="Enter 发送,Shift+Enter 换行" data-placeholder="告诉 AI 你想讨论或修改什么……"></div>
|
|
333
|
+
<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>
|
|
334
334
|
<div id="ai-mention-menu" class="ai-mention-menu hidden" role="listbox" aria-label="引用角色、设定、章节或上下文能力"></div>
|
|
335
335
|
<div class="prompt-composer-actions">
|
|
336
336
|
<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>
|
|
@@ -381,7 +381,7 @@
|
|
|
381
381
|
</div>
|
|
382
382
|
<input id="new-import-file" class="hidden" type="file" accept=".txt,.docx">
|
|
383
383
|
<input id="cover-file" class="hidden" type="file" accept="image/png,image/jpeg,image/webp">
|
|
384
|
-
<input id="avatar-file" class="hidden" type="file" accept="image/png,image/jpeg,image/webp">
|
|
384
|
+
<input id="avatar-file" class="hidden" type="file" accept="image/png,image/jpeg,image/webp,image/gif">
|
|
385
385
|
|
|
386
386
|
<dialog id="background-task-dialog" class="dialog wide-dialog background-task-dialog" aria-labelledby="background-task-dialog-title">
|
|
387
387
|
<form method="dialog">
|
|
@@ -448,7 +448,7 @@
|
|
|
448
448
|
</dialog>
|
|
449
449
|
|
|
450
450
|
<dialog id="chapter-batch-dialog" class="dialog chapter-batch-dialog" aria-labelledby="chapter-batch-title">
|
|
451
|
-
<form id="chapter-batch-form">
|
|
451
|
+
<form id="chapter-batch-form" method="post">
|
|
452
452
|
<div class="dialog-header">
|
|
453
453
|
<div><span class="eyebrow">正文工具</span><h2 id="chapter-batch-title">章节批量管理</h2><p class="dialog-header-meta">选择多个章节后统一移动、修改属性或软删除。</p></div>
|
|
454
454
|
<button id="chapter-batch-close" class="dialog-close" type="button" aria-label="关闭章节批量管理">×</button>
|
|
@@ -508,11 +508,12 @@
|
|
|
508
508
|
</dialog>
|
|
509
509
|
|
|
510
510
|
<section id="entity-editor-view" class="entity-editor-view hidden" aria-label="全屏资料编辑">
|
|
511
|
-
<form id="setting-editor-form" class="entity-editor-page hidden" aria-label="设定编辑器">
|
|
511
|
+
<form id="setting-editor-form" class="entity-editor-page hidden" aria-label="设定编辑器" method="post">
|
|
512
512
|
<header class="entity-editor-header setting-editor-header">
|
|
513
513
|
<button id="setting-editor-back" class="entity-editor-back" type="button">返回设定库</button>
|
|
514
514
|
<div class="entity-editor-heading">
|
|
515
515
|
<span id="setting-editor-eyebrow" class="eyebrow">新建设定</span>
|
|
516
|
+
<span id="setting-editor-version" class="character-version-badge">新档案</span>
|
|
516
517
|
<span id="setting-editor-readonly-badge" class="entity-editor-readonly-badge hidden">只读模式</span>
|
|
517
518
|
<input id="setting-editor-name" class="setting-editor-title-input" name="title" maxlength="200" placeholder="新建设定" aria-label="设定标题">
|
|
518
519
|
<div class="setting-editor-header-fields">
|
|
@@ -541,7 +542,7 @@
|
|
|
541
542
|
</div>
|
|
542
543
|
</form>
|
|
543
544
|
|
|
544
|
-
<form id="character-editor-form" class="entity-editor-page hidden" aria-labelledby="character-editor-title">
|
|
545
|
+
<form id="character-editor-form" class="entity-editor-page hidden" aria-labelledby="character-editor-title" method="post">
|
|
545
546
|
<header class="character-editor-header">
|
|
546
547
|
<button id="character-editor-close" class="entity-editor-back" type="button">返回角色列表</button>
|
|
547
548
|
<div>
|
|
@@ -575,7 +576,7 @@
|
|
|
575
576
|
</footer>
|
|
576
577
|
</form>
|
|
577
578
|
|
|
578
|
-
<form id="knowledge-editor-form" class="entity-editor-page hidden" aria-labelledby="knowledge-editor-title">
|
|
579
|
+
<form id="knowledge-editor-form" class="entity-editor-page hidden" aria-labelledby="knowledge-editor-title" method="post">
|
|
579
580
|
<header class="character-editor-header knowledge-editor-header">
|
|
580
581
|
<button id="knowledge-editor-close" class="entity-editor-back" type="button">返回列表</button>
|
|
581
582
|
<div>
|
|
@@ -595,7 +596,7 @@
|
|
|
595
596
|
<main id="knowledge-editor-fields" class="character-editor-fields"></main>
|
|
596
597
|
</div>
|
|
597
598
|
<footer class="character-editor-actions knowledge-editor-actions">
|
|
598
|
-
<p id="knowledge-editor-footer-note"
|
|
599
|
+
<p id="knowledge-editor-footer-note">保存后继续留在当前页面。</p>
|
|
599
600
|
<div><button id="knowledge-editor-cancel" class="ghost-button" type="button">返回列表</button><button id="knowledge-editor-submit" class="primary-button" type="submit">保存档案</button></div>
|
|
600
601
|
</footer>
|
|
601
602
|
</form>
|
|
@@ -640,7 +641,7 @@
|
|
|
640
641
|
</dialog>
|
|
641
642
|
|
|
642
643
|
<dialog id="replace-dialog" class="dialog replace-dialog" aria-labelledby="replace-dialog-title" aria-describedby="replace-dialog-description">
|
|
643
|
-
<form id="replace-form">
|
|
644
|
+
<form id="replace-form" method="post">
|
|
644
645
|
<div class="dialog-header">
|
|
645
646
|
<div><span class="eyebrow">内容工具</span><h2 id="replace-dialog-title">全局替换</h2><p id="replace-dialog-description" class="dialog-header-meta">只处理已保存内容;每个被修改的章节或设定都会保留一个可恢复版本。</p></div>
|
|
646
647
|
<button id="replace-dialog-close" class="dialog-close" aria-label="关闭全局替换" type="button">×</button>
|
|
@@ -734,7 +735,7 @@
|
|
|
734
735
|
<strong>字体预览 Typography 0123</strong>
|
|
735
736
|
<span>中文默认使用黑体,English letters always use monospace.</span>
|
|
736
737
|
<span>第二段用于预览正文的行距与段落密度。</span>
|
|
737
|
-
<span id="font-size-preview">界面 14 px · Agent 对话
|
|
738
|
+
<span id="font-size-preview">界面 14 px · Agent 对话 12 px</span>
|
|
738
739
|
</div>
|
|
739
740
|
</div>
|
|
740
741
|
<div class="dialog-actions">
|
|
@@ -789,7 +790,7 @@
|
|
|
789
790
|
</dialog>
|
|
790
791
|
|
|
791
792
|
<dialog id="writing-progress-dialog" class="dialog wide-dialog writing-progress-dialog" aria-labelledby="writing-progress-title">
|
|
792
|
-
<form id="writing-goal-form">
|
|
793
|
+
<form id="writing-goal-form" method="post">
|
|
793
794
|
<div class="dialog-header"><div><span class="eyebrow">创作节奏</span><h2 id="writing-progress-title">写作目标与字数趋势</h2><p class="dialog-header-meta">趋势根据正文版本历史重建,软删除章节不会继续计入。</p></div><button id="writing-progress-close" class="dialog-close" aria-label="关闭写作目标" type="button">×</button></div>
|
|
794
795
|
<div class="writing-progress-body">
|
|
795
796
|
<div class="writing-progress-stats"><article><small>当前总字数</small><strong id="writing-current-words">0</strong></article><article><small>今日新增</small><strong id="writing-today-words">0</strong></article><article><small>每日目标完成</small><strong id="writing-daily-completion">0%</strong></article><article><small>总目标完成</small><strong id="writing-total-completion">0%</strong></article></div>
|
|
@@ -802,7 +803,7 @@
|
|
|
802
803
|
|
|
803
804
|
<dialog id="platform-ui-settings-dialog" class="dialog" aria-labelledby="platform-ui-settings-title">
|
|
804
805
|
<div class="dialog-header"><div><span class="eyebrow">系统管理员</span><h2 id="platform-ui-settings-title">界面与分页</h2></div><div class="settings-dialog-header-actions"><button id="platform-ui-settings-return" class="ghost-button settings-parent-button" type="button">返回设置</button><button id="platform-ui-settings-close" class="dialog-close" aria-label="关闭" type="button">×</button></div></div>
|
|
805
|
-
<form id="platform-ui-settings-form">
|
|
806
|
+
<form id="platform-ui-settings-form" method="post">
|
|
806
807
|
<div class="dialog-fields">
|
|
807
808
|
<label>Toast 提示位置<select id="toast-position" name="toastPosition" aria-label="Toast 提示位置"><option value="bottom-right">右下角(默认)</option><option value="top-right">右上角</option></select></label>
|
|
808
809
|
<small>该设置对所有用户生效,保存后的提示会立即显示在新位置。</small>
|
|
@@ -836,6 +837,17 @@
|
|
|
836
837
|
<div class="settings-dialog-header-actions"><button id="s3-backup-settings-return" class="ghost-button settings-parent-button" type="button">返回设置</button><button id="s3-backup-close" class="dialog-close" aria-label="关闭 S3 系统备份" type="button">×</button></div>
|
|
837
838
|
</div>
|
|
838
839
|
<div class="s3-backup-body">
|
|
840
|
+
<fieldset class="s3-backup-encryption" aria-describedby="s3-backup-encryption-description">
|
|
841
|
+
<legend>备份加密</legend>
|
|
842
|
+
<div class="s3-backup-encryption-control">
|
|
843
|
+
<label><input id="s3-backup-encryption-toggle" type="checkbox"><span><strong>使用端到端加密</strong><small id="s3-backup-encryption-description">开启后,数据库快照、CredentialVault 恢复密钥和新上传图片都会先在本机加密。</small></span></label>
|
|
844
|
+
<span id="s3-backup-encryption-status" class="s3-backup-status">正在读取状态</span>
|
|
845
|
+
</div>
|
|
846
|
+
<div id="s3-backup-encryption-warning" class="s3-backup-encryption-warning hidden" role="note">
|
|
847
|
+
<strong>当前备份为明文</strong>
|
|
848
|
+
<p>请将所有 S3 目标桶设置为私有桶,避免数据库、密钥与图片被公开访问。</p>
|
|
849
|
+
</div>
|
|
850
|
+
</fieldset>
|
|
839
851
|
<section class="s3-backup-overview" aria-labelledby="s3-backup-overview-title">
|
|
840
852
|
<div><h3 id="s3-backup-overview-title">备份目标</h3><p id="s3-backup-summary">正在读取备份配置……</p></div>
|
|
841
853
|
<div class="s3-backup-overview-actions"><button id="s3-backup-run-all" class="ghost-button" type="button">立即备份已启用目标</button><button id="s3-backup-add" class="primary-button" type="button">新增目标</button></div>
|
|
@@ -849,7 +861,7 @@
|
|
|
849
861
|
</dialog>
|
|
850
862
|
|
|
851
863
|
<dialog id="s3-backup-target-dialog" class="dialog editor-dialog s3-backup-target-dialog" aria-labelledby="s3-backup-target-title">
|
|
852
|
-
<form id="s3-backup-target-form">
|
|
864
|
+
<form id="s3-backup-target-form" method="post" action="/api/platform/backups/targets">
|
|
853
865
|
<div class="dialog-header"><div><span class="eyebrow">系统备份目标</span><h2 id="s3-backup-target-title">新增 S3 目标</h2><p class="dialog-header-meta">AK 与 SK 会使用系统主密钥加密保存,读取配置时不会回显。</p></div><button id="s3-backup-target-close" class="dialog-close" aria-label="关闭 S3 目标编辑" type="button">×</button></div>
|
|
854
866
|
<div class="dialog-fields s3-backup-target-fields">
|
|
855
867
|
<label>配置名称<input id="s3-backup-name" name="name" maxlength="100" required autocomplete="off" placeholder="例如:异地 MinIO"></label>
|
|
@@ -873,6 +885,16 @@
|
|
|
873
885
|
</form>
|
|
874
886
|
</dialog>
|
|
875
887
|
|
|
888
|
+
<dialog id="s3-backup-key-dialog" class="dialog s3-backup-key-dialog" aria-labelledby="s3-backup-key-title" aria-describedby="s3-backup-key-description">
|
|
889
|
+
<div class="dialog-header"><div><span class="eyebrow">仅显示这一次</span><h2 id="s3-backup-key-title">立即保存备份加密密钥</h2><p id="s3-backup-key-description" class="dialog-header-meta">密钥丢失后,任何人都无法恢复加密备份。请先复制或下载并存放到 Scriverse 设备之外。</p></div></div>
|
|
890
|
+
<div class="s3-backup-key-body">
|
|
891
|
+
<div class="s3-backup-encryption-warning" role="alert"><strong>确认已保存后才会开启加密</strong><p>Scriverse 不会在后续设置页面再次展示这把密钥。刷新页面会放弃此次开启,下次可重新生成;关闭已启用的加密不会清除密钥,以便历史备份继续可恢复。开启加密不会回写桶内已有的明文图片对象,请继续将所有 S3 桶保持为私有桶。</p></div>
|
|
892
|
+
<label>备份加密密钥<textarea id="s3-backup-key-value" rows="3" readonly spellcheck="false" aria-label="备份加密密钥"></textarea></label>
|
|
893
|
+
<div class="s3-backup-key-actions"><button id="s3-backup-key-copy" class="ghost-button" type="button">复制密钥</button><button id="s3-backup-key-download" class="ghost-button" type="button">下载密钥文件</button></div>
|
|
894
|
+
</div>
|
|
895
|
+
<div class="dialog-actions"><button id="s3-backup-key-confirm" class="primary-button" type="button">我已保存</button></div>
|
|
896
|
+
</dialog>
|
|
897
|
+
|
|
876
898
|
<dialog id="account-dialog" class="dialog account-dialog" aria-labelledby="account-dialog-title">
|
|
877
899
|
<div class="dialog-header"><div><span class="eyebrow">个人账户</span><h2 id="account-dialog-title">账户设置</h2></div><button id="account-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
|
|
878
900
|
<div class="account-settings-body">
|
|
@@ -880,14 +902,14 @@
|
|
|
880
902
|
<header class="account-settings-section-header"><h3 id="profile-settings-title">个人资料</h3><p>用于作品协作和历史记录中的作者标识。</p></header>
|
|
881
903
|
<div class="avatar-settings">
|
|
882
904
|
<div id="profile-avatar-preview" class="user-avatar profile-avatar-preview" role="img" aria-label="当前头像"><span class="user-avatar-fallback">作</span></div>
|
|
883
|
-
<div class="avatar-settings-copy"><strong>头像图片</strong><small>支持 PNG、JPEG、WebP,文件不超过
|
|
905
|
+
<div class="avatar-settings-copy"><strong>头像图片</strong><small>支持 PNG、JPEG、WebP、GIF,文件不超过 2 MB。选择后可框选正方形选区再裁剪上传。</small></div>
|
|
884
906
|
<div class="avatar-settings-actions"><button id="avatar-upload-button" class="ghost-button" type="button">上传头像</button><button id="avatar-remove-button" class="ghost-button hidden" type="button">移除头像</button></div>
|
|
885
907
|
</div>
|
|
886
|
-
<form id="profile-form" class="account-settings-form"><label>显示名称<input id="profile-display-name" name="displayName" maxlength="80" required></label><button class="primary-button" type="submit">保存名称</button></form>
|
|
908
|
+
<form id="profile-form" class="account-settings-form" method="post"><label>显示名称<input id="profile-display-name" name="displayName" maxlength="80" required></label><button class="primary-button" type="submit">保存名称</button></form>
|
|
887
909
|
</section>
|
|
888
910
|
<section class="account-settings-section" aria-labelledby="password-settings-title">
|
|
889
911
|
<header class="account-settings-section-header"><h3 id="password-settings-title">修改密码</h3><p>新密码至少需要 10 个字符。</p></header>
|
|
890
|
-
<form id="password-form" class="account-settings-form password-settings-form"><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>
|
|
912
|
+
<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>
|
|
891
913
|
</section>
|
|
892
914
|
<section class="account-settings-section" aria-labelledby="api-key-settings-title">
|
|
893
915
|
<header class="account-settings-section-header"><h3 id="api-key-settings-title">CLI API Key</h3><p>用于 CLI 登录。服务端只保存摘要,重置后旧 Key 会立即失效,新 Key 仅显示一次。</p></header>
|
|
@@ -937,7 +959,7 @@
|
|
|
937
959
|
<dialog id="members-dialog" class="dialog wide-dialog" aria-labelledby="members-dialog-title">
|
|
938
960
|
<div class="dialog-header"><div><span id="members-dialog-eyebrow" class="eyebrow">作品权限</span><h2 id="members-dialog-title">成员模块权限</h2></div><div class="settings-dialog-header-actions"><button id="members-settings-return" class="ghost-button settings-parent-button hidden" type="button">返回设置</button><button id="members-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div></div>
|
|
939
961
|
<div class="access-dialog-body">
|
|
940
|
-
<form id="member-permission-form" class="member-permission-form">
|
|
962
|
+
<form id="member-permission-form" class="member-permission-form" method="post">
|
|
941
963
|
<label class="member-person-field">选择成员<select id="member-user-select" required></select><small>先选择已注册用户,再逐项配置模块权限。</small></label>
|
|
942
964
|
<fieldset id="member-permission-fieldset" disabled>
|
|
943
965
|
<legend>模块读写权限</legend>
|
|
@@ -1026,6 +1048,6 @@
|
|
|
1026
1048
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1027
1049
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
1028
1050
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
1029
|
-
<script type="module" src="/app.js?v=
|
|
1051
|
+
<script type="module" src="/app.js?v=20260812-image-upload-limit-v3"></script>
|
|
1030
1052
|
</body>
|
|
1031
1053
|
</html>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type PresenceClientIdStorage = Pick<Storage, "getItem" | "removeItem" | "setItem">;
|
|
2
|
+
|
|
3
|
+
export const PRESENCE_CLIENT_ID_BEFORE_RELOGIN_KEY: string;
|
|
4
|
+
|
|
5
|
+
export function createPresenceClientId(storage?: PresenceClientIdStorage | null): string;
|
|
6
|
+
|
|
7
|
+
export function stagePresenceClientIdForRelogin(
|
|
8
|
+
storage: PresenceClientIdStorage | null | undefined,
|
|
9
|
+
clientId: string
|
|
10
|
+
): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const PRESENCE_CLIENT_ID_BEFORE_RELOGIN_KEY = "scriverse-presence-client-id-before-relogin-v1";
|
|
2
|
+
|
|
3
|
+
function generatePresenceClientId() {
|
|
4
|
+
if (typeof globalThis.crypto.randomUUID === "function") return globalThis.crypto.randomUUID();
|
|
5
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
6
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
7
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
8
|
+
const value = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
9
|
+
return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createPresenceClientId(storage) {
|
|
13
|
+
try {
|
|
14
|
+
const stagedClientId = storage?.getItem(PRESENCE_CLIENT_ID_BEFORE_RELOGIN_KEY) ?? "";
|
|
15
|
+
if (stagedClientId) {
|
|
16
|
+
storage?.removeItem(PRESENCE_CLIENT_ID_BEFORE_RELOGIN_KEY);
|
|
17
|
+
return stagedClientId;
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// 浏览器禁用存储时退化为当前页面的新客户端标识
|
|
21
|
+
}
|
|
22
|
+
return generatePresenceClientId();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function stagePresenceClientIdForRelogin(storage, clientId) {
|
|
26
|
+
try {
|
|
27
|
+
storage?.setItem(PRESENCE_CLIENT_ID_BEFORE_RELOGIN_KEY, clientId);
|
|
28
|
+
} catch {
|
|
29
|
+
// 浏览器禁用存储时保持现有刷新行为
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -15,3 +15,13 @@ export function collectS3BackupRunTransitions<T extends S3BackupRunSnapshot>(
|
|
|
15
15
|
maximumSnapshots?: number
|
|
16
16
|
): { snapshots: Map<string, string>; failures: T[] };
|
|
17
17
|
export function s3BackupFailureToast(run: Pick<S3BackupRunSnapshot, "targetName" | "errorMessage">): string;
|
|
18
|
+
export function s3BackupEncryptionPresentation(state?: {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
keyConfiguredAt?: string | null;
|
|
21
|
+
} | null): {
|
|
22
|
+
label: string;
|
|
23
|
+
statusClass: string;
|
|
24
|
+
description: string;
|
|
25
|
+
showPrivateBucketWarning: boolean;
|
|
26
|
+
};
|
|
27
|
+
export function s3BackupEncryptionKeyFile(key: unknown): string;
|
|
@@ -26,3 +26,32 @@ export function s3BackupFailureToast(run) {
|
|
|
26
26
|
const reason = String(run?.errorMessage || "S3 服务请求失败").trim();
|
|
27
27
|
return `S3 备份目标“${run?.targetName || "未命名目标"}”失败:${reason}`;
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
export function s3BackupEncryptionPresentation(state) {
|
|
31
|
+
if (state?.enabled) {
|
|
32
|
+
return {
|
|
33
|
+
label: "已开启",
|
|
34
|
+
statusClass: "is-enabled",
|
|
35
|
+
description: "新生成的数据库快照、恢复密钥和图片会以 AES-256-GCM 信封密文上传。",
|
|
36
|
+
showPrivateBucketWarning: false
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (state?.keyConfiguredAt) {
|
|
40
|
+
return {
|
|
41
|
+
label: "已关闭",
|
|
42
|
+
statusClass: "",
|
|
43
|
+
description: "新备份恢复为明文上传;原密钥仍保留,以便解密历史备份。",
|
|
44
|
+
showPrivateBucketWarning: true
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
label: "未开启",
|
|
49
|
+
statusClass: "",
|
|
50
|
+
description: "备份将以明文上传,请确保所有 S3 目标桶均为私有桶。",
|
|
51
|
+
showPrivateBucketWarning: true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function s3BackupEncryptionKeyFile(key) {
|
|
56
|
+
return `${String(key).trim()}\n`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type SettingLockFilter = "all" | "locked" | "unlocked";
|
|
2
|
+
|
|
3
|
+
export interface SettingFilterOptions {
|
|
4
|
+
keyword?: string;
|
|
5
|
+
category?: string;
|
|
6
|
+
lockState?: SettingLockFilter;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SettingFilterRecord {
|
|
10
|
+
title?: unknown;
|
|
11
|
+
category?: unknown;
|
|
12
|
+
contentPreview?: unknown;
|
|
13
|
+
locked?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function filterSettings<T extends SettingFilterRecord>(records: readonly T[], filters?: SettingFilterOptions): T[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function normalizedText(value) {
|
|
2
|
+
return String(value ?? "").toLocaleLowerCase("zh-CN");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function filterSettings(records, { keyword = "", category = "", lockState = "all" } = {}) {
|
|
6
|
+
const normalizedKeyword = normalizedText(keyword).trim();
|
|
7
|
+
const normalizedCategory = String(category ?? "").trim();
|
|
8
|
+
const normalizedLockState = lockState === "locked" || lockState === "unlocked" ? lockState : "all";
|
|
9
|
+
|
|
10
|
+
return records.filter((record) => {
|
|
11
|
+
const matchesKeyword = !normalizedKeyword
|
|
12
|
+
|| normalizedText(record?.title).includes(normalizedKeyword)
|
|
13
|
+
|| normalizedText(record?.contentPreview).includes(normalizedKeyword);
|
|
14
|
+
const matchesCategory = !normalizedCategory || String(record?.category ?? "") === normalizedCategory;
|
|
15
|
+
const matchesLockState = normalizedLockState === "all"
|
|
16
|
+
|| (normalizedLockState === "locked" ? Boolean(record?.locked) : !record?.locked);
|
|
17
|
+
return matchesKeyword && matchesCategory && matchesLockState;
|
|
18
|
+
});
|
|
19
|
+
}
|
package/dist/public/styles.css
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
--font-cjk: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "Heiti SC";
|
|
66
66
|
--ui-font-size: 14px;
|
|
67
67
|
--ui-font-scale: 1;
|
|
68
|
-
--ai-font-size:
|
|
68
|
+
--ai-font-size: 12px;
|
|
69
69
|
--ai-font-scale: 1;
|
|
70
70
|
--editor-font-size: 17px;
|
|
71
71
|
--editor-line-height: 1.55;
|
|
@@ -796,6 +796,17 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
796
796
|
}
|
|
797
797
|
.dialog.s3-backup-dialog { width: min(900px, 94vw); }
|
|
798
798
|
.s3-backup-body { display: grid; gap: 18px; max-height: calc(88vh - 86px); overflow-y: auto; padding: 18px 24px 24px; }
|
|
799
|
+
.s3-backup-encryption { display: grid; gap: 12px; min-width: 0; margin: 0; padding: 15px 16px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-soft); }
|
|
800
|
+
.s3-backup-encryption legend { padding: 0 6px; color: var(--ink); font-size: 13px; font-weight: 600; }
|
|
801
|
+
.s3-backup-encryption-control { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
|
802
|
+
.s3-backup-encryption-control > label { display: flex; align-items: flex-start; gap: 9px; min-width: 0; color: var(--ink); cursor: pointer; }
|
|
803
|
+
.s3-backup-encryption-control input { flex: 0 0 auto; width: 17px; height: 17px; margin-top: 1px; }
|
|
804
|
+
.s3-backup-encryption-control label > span { display: grid; gap: 4px; min-width: 0; }
|
|
805
|
+
.s3-backup-encryption-control strong { font-size: 12px; font-weight: 600; }
|
|
806
|
+
.s3-backup-encryption-control small { color: var(--muted); font-size: 10px; line-height: 1.55; }
|
|
807
|
+
.s3-backup-encryption-warning { padding: 10px 12px; border: 1px solid color-mix(in srgb, #bf6a35 48%, var(--line)); border-radius: 5px; background: color-mix(in srgb, #bf6a35 10%, var(--surface)); }
|
|
808
|
+
.s3-backup-encryption-warning strong { color: var(--ink); font-size: 11px; }
|
|
809
|
+
.s3-backup-encryption-warning p { margin: 3px 0 0; color: var(--muted); font-size: 10px; line-height: 1.55; }
|
|
799
810
|
.s3-backup-overview { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 15px 16px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-soft); }
|
|
800
811
|
.s3-backup-overview h3, .s3-backup-history h3 { margin: 0; font-size: 15px; font-weight: 600; }
|
|
801
812
|
.s3-backup-overview p, .s3-backup-history header p { margin: 4px 0 0; color: var(--muted); font-size: 10px; line-height: 1.55; }
|
|
@@ -841,6 +852,12 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
841
852
|
.s3-backup-options span { display: grid; gap: 3px; }
|
|
842
853
|
.s3-backup-options strong { font-size: 10px; font-weight: 600; }
|
|
843
854
|
.s3-backup-options small { color: var(--muted); font-size: 9px; line-height: 1.45; }
|
|
855
|
+
.dialog.s3-backup-key-dialog { width: min(620px, 94vw); }
|
|
856
|
+
.s3-backup-key-body { display: grid; gap: 14px; padding: 18px 24px 4px; }
|
|
857
|
+
.s3-backup-key-body > label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
858
|
+
.s3-backup-key-body textarea { width: 100%; min-height: 82px; resize: none; padding: 10px 12px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface); color: var(--ink); font: 12px/1.6 var(--font-latin), monospace; overflow-wrap: anywhere; }
|
|
859
|
+
.s3-backup-key-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
860
|
+
.s3-backup-key-actions button { min-height: 34px; }
|
|
844
861
|
.member-permission-form { display: grid; gap: 12px; margin: 0; padding: 14px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-soft); }
|
|
845
862
|
.member-person-field { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
846
863
|
.member-person-field small, .member-permission-help { color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
@@ -1415,6 +1432,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1415
1432
|
.draft-type-filter-field { display: grid; min-width: 0; }
|
|
1416
1433
|
.draft-type-filter-field > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
1417
1434
|
.draft-type-filter-field select { width: 100%; min-width: 0; min-height: 38px; padding: 8px 10px; background: var(--surface); color: var(--ink); font-size: 11px; }
|
|
1435
|
+
.setting-filter-toolbar { grid-template-columns: minmax(0, 1.2fr) repeat(2, minmax(0, .8fr)) auto; }
|
|
1436
|
+
.setting-filter-field { display: grid; min-width: 0; }
|
|
1437
|
+
.setting-filter-field > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
1438
|
+
.setting-filter-field input, .setting-filter-field select { width: 100%; min-width: 0; min-height: 38px; padding: 8px 10px; background: var(--surface); color: var(--ink); font-size: 11px; }
|
|
1418
1439
|
.draft-binding-field { min-width: 0; }
|
|
1419
1440
|
.draft-binding-field > label { display: grid; gap: 7px; }
|
|
1420
1441
|
.character-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
@@ -1615,13 +1636,13 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1615
1636
|
}
|
|
1616
1637
|
.task-status-badge.is-running .task-status-indicator::after { animation: task-status-running-ripple 1.8s ease-out infinite; }
|
|
1617
1638
|
.task-status-badge.is-review, .task-status-badge.is-completed { --task-status-color: var(--green); }
|
|
1618
|
-
.task-status-badge.is-partial { --task-status-color: var(--accent); }
|
|
1639
|
+
.task-status-badge.is-partial, .task-status-badge.is-failed { --task-status-color: var(--accent); }
|
|
1619
1640
|
.task-status-badge.is-expired { --task-status-color: color-mix(in srgb, var(--muted) 72%, var(--accent)); border-style: dashed; }
|
|
1620
1641
|
.task-status-badge.is-cancelled, .task-status-badge.is-unknown { --task-status-color: var(--muted); }
|
|
1621
1642
|
.task-status-badge.is-cancelled .task-status-indicator, .task-status-badge.is-unknown .task-status-indicator { background: transparent; border: 1px solid currentColor; box-shadow: none; }
|
|
1622
1643
|
.task-status-badge.is-state-change { animation: task-status-enter .42s cubic-bezier(.22, .8, .32, 1) both; }
|
|
1623
1644
|
.task-status-badge.is-running.is-state-change { animation: task-status-enter .42s cubic-bezier(.22, .8, .32, 1) both, task-status-running-breathe 1.8s ease-in-out .42s infinite; }
|
|
1624
|
-
.task-status-badge.is-partial.is-state-change { animation: task-status-attention .46s ease-out both; }
|
|
1645
|
+
.task-status-badge.is-partial.is-state-change, .task-status-badge.is-failed.is-state-change { animation: task-status-attention .46s ease-out both; }
|
|
1625
1646
|
.task-progress-cell { width: 128px; }
|
|
1626
1647
|
.task-progress { display: grid; grid-template-columns: 36px minmax(58px, 1fr); align-items: center; gap: 8px; min-width: 102px; }
|
|
1627
1648
|
.task-progress-value { color: var(--ink); font-family: var(--font-latin), monospace; font-size: 11px; font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
@@ -1629,7 +1650,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1629
1650
|
.task-progress-fill { position: relative; display: block; width: var(--task-progress); height: 100%; overflow: hidden; border-radius: inherit; background: var(--muted); transition: width .35s ease; }
|
|
1630
1651
|
.task-progress.is-running .task-progress-fill { background: var(--task-running); }
|
|
1631
1652
|
.task-progress.is-review .task-progress-fill, .task-progress.is-completed .task-progress-fill { background: var(--green); }
|
|
1632
|
-
.task-progress.is-partial .task-progress-fill { background: var(--accent); }
|
|
1653
|
+
.task-progress.is-partial .task-progress-fill, .task-progress.is-failed .task-progress-fill { background: var(--accent); }
|
|
1633
1654
|
.task-progress.is-running .task-progress-fill::after {
|
|
1634
1655
|
content: "";
|
|
1635
1656
|
position: absolute;
|
|
@@ -1963,7 +1984,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1963
1984
|
.mind-edge-detail { position: absolute; z-index: 5; left: 16px; right: 16px; bottom: 14px; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; align-items: start; padding: 10px 12px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-strong); box-shadow: var(--shadow); font-size: 11px; }
|
|
1964
1985
|
.mind-edge-detail b { white-space: nowrap; }.mind-edge-detail span { color: var(--muted); line-height: 1.55; }
|
|
1965
1986
|
.relationship-empty-note { margin-top: -12px; padding: 12px 14px; border: 1px dashed var(--line); color: var(--muted); font-size: 11px; }
|
|
1966
|
-
.relationship-map-dialog { position:
|
|
1987
|
+
.relationship-map-dialog { position: fixed; inset: 0; width: 100vw; max-width: none; height: 100dvh; max-height: none; margin: 0; padding: 0; overflow: hidden; border: 0; border-radius: 0; background: var(--panel); box-shadow: none; }
|
|
1967
1988
|
.relationship-map-dialog::backdrop { background: rgba(34,30,25,.52); backdrop-filter: blur(3px); }
|
|
1968
1989
|
.relationship-map-floating-close { position: absolute; z-index: 12; top: 25px; right: 25px; width: 34px; height: 34px; border: 1px solid var(--line); border-radius: 50%; background: var(--surface-strong); color: var(--ink); font-size: 20px; }
|
|
1969
1990
|
.relationship-map-expanded-host { height: 100%; padding: 18px; }
|
|
@@ -2437,7 +2458,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2437
2458
|
.prompt-composer { position: relative; }
|
|
2438
2459
|
.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); }
|
|
2439
2460
|
.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; }
|
|
2440
|
-
.ai-mention-option:hover, .ai-mention-option:focus-visible { background: var(--paper-deep); }
|
|
2461
|
+
.ai-mention-option:hover, .ai-mention-option:focus-visible, .ai-mention-option.is-active { background: var(--paper-deep); }
|
|
2441
2462
|
.ai-mention-option small { color: var(--accent); font-size: 8px; }
|
|
2442
2463
|
.ai-mention-option strong { overflow: hidden; font-size: 10px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
|
2443
2464
|
.ai-mention-empty { margin: 0; padding: 10px; color: var(--muted); font-size: 10px; text-align: center; }
|
|
@@ -2487,6 +2508,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2487
2508
|
.ai-citation-card pre { display: none; grid-column: 1 / -1; max-height: 160px; overflow: auto; margin: 0; padding: 9px; border-top: 1px solid var(--line); background: var(--surface); font: 10px/1.55 var(--font-cjk), sans-serif; white-space: pre-wrap; }
|
|
2488
2509
|
.ai-citation-card.is-expanded pre { display: block; }
|
|
2489
2510
|
.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; }
|
|
2511
|
+
.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; }
|
|
2512
|
+
.user-message-mentions-label { flex: 0 0 auto; opacity: .72; letter-spacing: .08em; }
|
|
2513
|
+
.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); }
|
|
2490
2514
|
|
|
2491
2515
|
/* Toast 使用 Popover Top Layer;必须覆盖 UA 默认居中/系统边框/Canvas 背景 */
|
|
2492
2516
|
.toast-region,
|
|
@@ -2531,6 +2555,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2531
2555
|
background: var(--toast-error-bg);
|
|
2532
2556
|
color: var(--toast-error-fg);
|
|
2533
2557
|
}
|
|
2558
|
+
.toast.warning { border-color: color-mix(in srgb, #f0bd78 42%, transparent); background: #65451f; color: #fff7e8; }
|
|
2534
2559
|
.toast-confirmation {
|
|
2535
2560
|
display: grid;
|
|
2536
2561
|
gap: 8px;
|
|
@@ -2860,6 +2885,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2860
2885
|
.character-filter-toolbar { grid-template-columns: minmax(0, 1fr); }
|
|
2861
2886
|
.character-filter-toolbar-actions { justify-content: flex-start; }
|
|
2862
2887
|
.draft-filter-toolbar { grid-template-columns: minmax(0, 1fr); }
|
|
2888
|
+
.setting-filter-toolbar { grid-template-columns: minmax(0, 1fr); }
|
|
2863
2889
|
}
|
|
2864
2890
|
.entity-editor-open { overflow: hidden; }
|
|
2865
2891
|
.preview-record-card { cursor: pointer; }
|
|
@@ -3527,7 +3553,7 @@ body { font-size: var(--ui-font-size); }
|
|
|
3527
3553
|
.ai-context-distribution-label small, .ai-context-distribution-label strong { font-size: calc(9px * var(--ai-font-scale)); }
|
|
3528
3554
|
.ai-send-button { font-size: calc(10px * var(--ai-font-scale)); }
|
|
3529
3555
|
.ai-citation-main strong { font-size: calc(10px * var(--ai-font-scale)); }
|
|
3530
|
-
.ai-citation-main small, .ai-citation-main span, .message-citations span { font-size: calc(9px * var(--ai-font-scale)); }
|
|
3556
|
+
.ai-citation-main small, .ai-citation-main span, .message-citations span, .user-message-mentions { font-size: calc(9px * var(--ai-font-scale)); }
|
|
3531
3557
|
.ai-history-item strong { font-size: calc(14px * var(--ai-font-scale)); }
|
|
3532
3558
|
.ai-history-item small { font-size: calc(10px * var(--ai-font-scale)); }
|
|
3533
3559
|
.ai-tool-call-info dt, .ai-tool-call-detail h3 { font-size: calc(9px * var(--ai-font-scale)); }
|
|
@@ -3541,6 +3567,8 @@ body { font-size: var(--ui-font-size); }
|
|
|
3541
3567
|
|
|
3542
3568
|
@media (max-width: 640px) {
|
|
3543
3569
|
.s3-backup-body { gap: 14px; padding: 14px 16px 18px; }
|
|
3570
|
+
.s3-backup-encryption-control { align-items: stretch; flex-direction: column; }
|
|
3571
|
+
.s3-backup-encryption-control > .s3-backup-status { width: fit-content; }
|
|
3544
3572
|
.s3-backup-overview, .s3-backup-history > header { align-items: stretch; flex-direction: column; }
|
|
3545
3573
|
.s3-backup-overview-actions { display: grid; grid-template-columns: minmax(0, 1fr); }
|
|
3546
3574
|
.s3-backup-overview-actions button { width: 100%; }
|
|
@@ -3553,4 +3581,6 @@ body { font-size: var(--ui-font-size); }
|
|
|
3553
3581
|
.s3-backup-run-card details { grid-column: 1 / -1; }
|
|
3554
3582
|
.s3-backup-target-fields { grid-template-columns: minmax(0, 1fr) !important; padding-inline: 16px !important; }
|
|
3555
3583
|
.s3-backup-target-fields > * { grid-column: 1 !important; }
|
|
3584
|
+
.s3-backup-key-body { padding-inline: 16px; }
|
|
3585
|
+
.s3-backup-key-actions { display: grid; grid-template-columns: minmax(0, 1fr); }
|
|
3556
3586
|
}
|