@musnows/scriverse 0.3.0 → 0.3.1
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.en.md +1 -1
- package/README.md +1 -1
- package/dist/ai.js +182 -8
- package/dist/ai.js.map +1 -1
- package/dist/app.js +41 -8
- package/dist/app.js.map +1 -1
- package/dist/database.js +108 -10
- package/dist/database.js.map +1 -1
- package/dist/http-logging.js +76 -0
- package/dist/http-logging.js.map +1 -0
- package/dist/logger.js +110 -0
- package/dist/logger.js.map +1 -0
- package/dist/public/app.js +211 -31
- package/dist/public/index.html +4 -4
- package/dist/public/styles.css +44 -2
- package/dist/request-context.js +10 -3
- package/dist/request-context.js.map +1 -1
- package/dist/security.js +12 -0
- package/dist/security.js.map +1 -1
- package/dist/server-runtime.js +22 -9
- package/dist/server-runtime.js.map +1 -1
- package/dist/server.js +12 -1
- package/dist/server.js.map +1 -1
- package/dist/store.js +80 -4
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +33 -7
- package/dist/user-auth.js.map +1 -1
- package/dist/utils.js +17 -0
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -76,6 +76,24 @@ function analysisTaskStatusLabel(status) {
|
|
|
76
76
|
})[String(status)] ?? String(status);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function canEditWork(work = state.work) {
|
|
80
|
+
return ["admin", "owner", "editor"].includes(String(work?.accessRole));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function applyWorkAccessMode() {
|
|
84
|
+
const viewOnly = Boolean(state.work) && !canEditWork();
|
|
85
|
+
$("#app").classList.toggle("view-only-mode", viewOnly);
|
|
86
|
+
document.body.classList.toggle("work-viewer-mode", viewOnly);
|
|
87
|
+
$("#chapter-title").readOnly = viewOnly;
|
|
88
|
+
$("#chapter-content").readOnly = viewOnly;
|
|
89
|
+
$("#chapter-title").setAttribute("aria-readonly", String(viewOnly));
|
|
90
|
+
$("#chapter-content").setAttribute("aria-readonly", String(viewOnly));
|
|
91
|
+
if (viewOnly) {
|
|
92
|
+
cancelChapterAutoSave();
|
|
93
|
+
state.dirty = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
const $ = (selector) => document.querySelector(selector);
|
|
80
98
|
const esc = (value) => String(value ?? "").replace(/[&<>'"]/g, (character) => ({ "&": "&", "<": "<", ">": ">", "'": "'", '"': """ })[character]);
|
|
81
99
|
const maximumAvatarFileSize = 5 * 1024 * 1024;
|
|
@@ -127,6 +145,15 @@ let onboardingStep = 0;
|
|
|
127
145
|
let onboardingAutoScheduled = false;
|
|
128
146
|
let onboardingPositionFrame = null;
|
|
129
147
|
let onboardingSteps = [];
|
|
148
|
+
let loadedAiModelsWorkId = null;
|
|
149
|
+
let loadedAiReferencesWorkId = null;
|
|
150
|
+
let loadedAiConversationsWorkId = null;
|
|
151
|
+
let aiModelsLoadPromise = null;
|
|
152
|
+
let aiModelsLoadWorkId = null;
|
|
153
|
+
let aiReferencesLoadPromise = null;
|
|
154
|
+
let aiReferencesLoadWorkId = null;
|
|
155
|
+
let aiConversationsLoadPromise = null;
|
|
156
|
+
let aiConversationsLoadWorkId = null;
|
|
130
157
|
|
|
131
158
|
const shelfOnboardingSteps = [
|
|
132
159
|
{ selector: "#home-button", eyebrow: "作品入口", title: "这里是你的创作书架", description: "点击左上角的叙界标志,可以随时回到书架,在不同作品之间切换。", placement: "bottom" },
|
|
@@ -799,12 +826,14 @@ function attachMessageIdentity(message, messageId) {
|
|
|
799
826
|
const AI_TOOL_DISPLAY_NAMES = {
|
|
800
827
|
story_index: "作品目录与章节概要",
|
|
801
828
|
read_chapters: "读取章节",
|
|
829
|
+
grep: "查询正文关键字",
|
|
802
830
|
query_story_knowledge: "查询作品知识"
|
|
803
831
|
};
|
|
804
832
|
|
|
805
833
|
const AI_TOOL_DESCRIPTIONS = {
|
|
806
834
|
story_index: "分页读取当前作品的卷章目录和章节概要。",
|
|
807
835
|
read_chapters: "读取指定章节的概要、正文或两者。",
|
|
836
|
+
grep: "查询正文关键字所在的完整段落及章节信息。",
|
|
808
837
|
query_story_knowledge: "按关键词查询设定、人物、组织、时间线等作品知识。"
|
|
809
838
|
};
|
|
810
839
|
|
|
@@ -977,8 +1006,12 @@ function renderAiConversationHistory() {
|
|
|
977
1006
|
}
|
|
978
1007
|
|
|
979
1008
|
async function loadAiConversations(openLatest = true) {
|
|
980
|
-
|
|
981
|
-
|
|
1009
|
+
const workId = state.work?.id;
|
|
1010
|
+
if (!workId) return;
|
|
1011
|
+
const conversations = await api(`/api/works/${workId}/ai-conversations`);
|
|
1012
|
+
if (state.work?.id !== workId) return;
|
|
1013
|
+
state.aiConversations = conversations;
|
|
1014
|
+
loadedAiConversationsWorkId = workId;
|
|
982
1015
|
renderAiConversationHistory();
|
|
983
1016
|
if (openLatest && state.aiConversations.length) await openAiConversation(state.aiConversations[0].id, false);
|
|
984
1017
|
else {
|
|
@@ -987,6 +1020,22 @@ async function loadAiConversations(openLatest = true) {
|
|
|
987
1020
|
}
|
|
988
1021
|
}
|
|
989
1022
|
|
|
1023
|
+
async function ensureAiConversationsLoaded() {
|
|
1024
|
+
const workId = state.work?.id;
|
|
1025
|
+
if (!workId || loadedAiConversationsWorkId === workId) return;
|
|
1026
|
+
if (aiConversationsLoadPromise && aiConversationsLoadWorkId === workId) return aiConversationsLoadPromise;
|
|
1027
|
+
aiConversationsLoadWorkId = workId;
|
|
1028
|
+
aiConversationsLoadPromise = loadAiConversations(false);
|
|
1029
|
+
try {
|
|
1030
|
+
await aiConversationsLoadPromise;
|
|
1031
|
+
} finally {
|
|
1032
|
+
if (aiConversationsLoadWorkId === workId) {
|
|
1033
|
+
aiConversationsLoadPromise = null;
|
|
1034
|
+
aiConversationsLoadWorkId = null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
990
1039
|
async function openAiConversation(conversationId, hideHistory = true) {
|
|
991
1040
|
const conversation = await api(`/api/ai-conversations/${conversationId}`);
|
|
992
1041
|
state.aiConversationId = conversation.id;
|
|
@@ -1409,14 +1458,35 @@ async function initializeAuthentication() {
|
|
|
1409
1458
|
return true;
|
|
1410
1459
|
}
|
|
1411
1460
|
|
|
1461
|
+
function raiseToastRegion() {
|
|
1462
|
+
const region = $("#toast-region");
|
|
1463
|
+
if (typeof region.showPopover !== "function") return;
|
|
1464
|
+
if (region.matches(":popover-open")) region.hidePopover();
|
|
1465
|
+
region.showPopover();
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1412
1468
|
function toast(message, type = "info") {
|
|
1469
|
+
const region = $("#toast-region");
|
|
1413
1470
|
const element = document.createElement("div");
|
|
1414
1471
|
element.className = `toast ${type}`;
|
|
1415
1472
|
element.textContent = message;
|
|
1416
|
-
|
|
1417
|
-
|
|
1473
|
+
region.append(element);
|
|
1474
|
+
raiseToastRegion();
|
|
1475
|
+
setTimeout(() => {
|
|
1476
|
+
element.remove();
|
|
1477
|
+
if (!region.childElementCount && typeof region.hidePopover === "function" && region.matches(":popover-open")) {
|
|
1478
|
+
region.hidePopover();
|
|
1479
|
+
}
|
|
1480
|
+
}, 3600);
|
|
1418
1481
|
}
|
|
1419
1482
|
|
|
1483
|
+
document.addEventListener("toggle", (event) => {
|
|
1484
|
+
const target = event.target;
|
|
1485
|
+
if (target instanceof HTMLDialogElement && target.open && $("#toast-region").childElementCount) {
|
|
1486
|
+
raiseToastRegion();
|
|
1487
|
+
}
|
|
1488
|
+
}, true);
|
|
1489
|
+
|
|
1420
1490
|
function setSaveState(text, dirty = false) {
|
|
1421
1491
|
state.dirty = dirty;
|
|
1422
1492
|
$("#save-state").textContent = text;
|
|
@@ -1442,7 +1512,7 @@ function cancelChapterAutoSave() {
|
|
|
1442
1512
|
}
|
|
1443
1513
|
|
|
1444
1514
|
function scheduleChapterAutoSave(delay = chapterAutoSaveDelay) {
|
|
1445
|
-
if (!state.chapter) return;
|
|
1515
|
+
if (!state.chapter || !canEditWork()) return;
|
|
1446
1516
|
cancelChapterAutoSave();
|
|
1447
1517
|
setSaveState("等待自动保存", true);
|
|
1448
1518
|
chapterAutoSaveTimer = setTimeout(() => {
|
|
@@ -1452,6 +1522,7 @@ function scheduleChapterAutoSave(delay = chapterAutoSaveDelay) {
|
|
|
1452
1522
|
}
|
|
1453
1523
|
|
|
1454
1524
|
async function persistChapter({ automatic = false } = {}) {
|
|
1525
|
+
if (!canEditWork()) return null;
|
|
1455
1526
|
if (!state.chapter) {
|
|
1456
1527
|
if (!automatic) toast("请先选择章节", "error");
|
|
1457
1528
|
return null;
|
|
@@ -1564,7 +1635,7 @@ async function initializePage() {
|
|
|
1564
1635
|
|
|
1565
1636
|
if (requestedWork) {
|
|
1566
1637
|
state.module = route.view === "module" ? route.module : "editor";
|
|
1567
|
-
await selectWork(requestedWork.id);
|
|
1638
|
+
await selectWork(requestedWork.id, route.view === "editor" ? route.chapterId : null);
|
|
1568
1639
|
}
|
|
1569
1640
|
|
|
1570
1641
|
if (route.view === "editor") {
|
|
@@ -1696,11 +1767,23 @@ function renderMembers(members) {
|
|
|
1696
1767
|
const work = memberDialogWork ?? state.work;
|
|
1697
1768
|
const canManage = ["admin", "owner"].includes(String(work?.accessRole));
|
|
1698
1769
|
$("#members-list").innerHTML = members.map((member) => `<article class="access-row">
|
|
1699
|
-
<div class="access-person">${userAvatarHtml(member, "access-avatar")}<div class="access-person-copy"><strong>${esc(member.displayName)} · @${esc(member.username)}</strong><small>${member.role === "owner" ? "作品创建者" : "协作者"}${member.status === "disabled" ? " · 已停用" : ""}</small></div></div>
|
|
1700
|
-
<span
|
|
1770
|
+
<div class="access-person">${userAvatarHtml(member, "access-avatar")}<div class="access-person-copy"><strong>${esc(member.displayName)} · @${esc(member.username)}</strong><small>${member.role === "owner" ? "作品创建者" : member.role === "viewer" ? "查看者" : "协作者"}${member.status === "disabled" ? " · 已停用" : ""}</small></div></div>
|
|
1771
|
+
${member.role === "owner" ? "<span>所有者</span>" : `<select data-member-role="${esc(member.userId)}" aria-label="${esc(member.displayName)}的作品权限" ${canManage ? "" : "disabled"}><option value="viewer" ${member.role === "viewer" ? "selected" : ""}>仅查看</option><option value="editor" ${member.role === "editor" ? "selected" : ""}>可编辑</option></select>`}
|
|
1701
1772
|
${member.role === "owner" || !canManage ? "<span></span>" : `<button type="button" data-remove-member="${esc(member.userId)}">移除</button>`}
|
|
1702
1773
|
</article>`).join("");
|
|
1703
1774
|
bindUserAvatarFallbacks($("#members-list"));
|
|
1775
|
+
$("#members-list").querySelectorAll("[data-member-role]").forEach((select) => select.addEventListener("change", async () => {
|
|
1776
|
+
if (!work) return;
|
|
1777
|
+
const previousRole = members.find((member) => member.userId === select.dataset.memberRole)?.role ?? "viewer";
|
|
1778
|
+
try {
|
|
1779
|
+
const updated = await api(`/api/works/${encodeURIComponent(work.id)}/members/${encodeURIComponent(select.dataset.memberRole)}`, { method: "PATCH", body: { role: select.value } });
|
|
1780
|
+
renderMembers(updated);
|
|
1781
|
+
toast("成员权限已更新");
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
select.value = previousRole;
|
|
1784
|
+
toast(error.message, "error");
|
|
1785
|
+
}
|
|
1786
|
+
}));
|
|
1704
1787
|
$("#members-list").querySelectorAll("[data-remove-member]").forEach((button) => button.addEventListener("click", async () => {
|
|
1705
1788
|
if (!work) return;
|
|
1706
1789
|
try {
|
|
@@ -1888,9 +1971,9 @@ function renderShelf() {
|
|
|
1888
1971
|
<span class="book-cover-fallback">${esc(Array.from(work.title)[0] ?? "书")}</span>
|
|
1889
1972
|
${work.coverUrl ? `<img src="${esc(work.coverUrl)}" alt="${esc(work.title)} 封面">` : ""}
|
|
1890
1973
|
</span>
|
|
1891
|
-
<span class="book-info"><strong>${esc(work.title)}</strong><small>${esc(work.author || "未署名")} · ${work.chapterCount} 章 · ${work.wordCount} 字</small><span>${esc(work.description || "尚未填写作品简介")}</span><em class="book-access-badge">${work.accessRole === "editor" ? "协作作品" : work.accessRole === "admin" ? "管理员访问" : "我的作品"}</em></span>
|
|
1974
|
+
<span class="book-info"><strong>${esc(work.title)}</strong><small>${esc(work.author || "未署名")} · ${work.chapterCount} 章 · ${work.wordCount} 字</small><span>${esc(work.description || "尚未填写作品简介")}</span><em class="book-access-badge">${work.accessRole === "viewer" ? "仅查看" : work.accessRole === "editor" ? "协作作品" : work.accessRole === "admin" ? "管理员访问" : "我的作品"}</em></span>
|
|
1892
1975
|
</button>
|
|
1893
|
-
|
|
1976
|
+
${canEditWork(work) ? `<button class="book-card-settings" type="button" data-edit-work="${esc(work.id)}" aria-label="作品设置" title="作品设置">设置</button>` : ""}
|
|
1894
1977
|
</article>`).join("")}
|
|
1895
1978
|
<button class="book-card book-add-card" id="book-add-card" type="button" aria-label="新建作品" data-testid="book-add-card"><span>+</span><strong>新建作品</strong><small>从零开始或导入 TXT / DOCX</small></button>`;
|
|
1896
1979
|
shelf.querySelectorAll("[data-open-work]").forEach((button) => button.addEventListener("click", () => selectWork(button.dataset.openWork)));
|
|
@@ -1901,11 +1984,20 @@ function renderShelf() {
|
|
|
1901
1984
|
$("#book-add-card").addEventListener("click", openWorkDialog);
|
|
1902
1985
|
}
|
|
1903
1986
|
|
|
1904
|
-
async function selectWork(workId) {
|
|
1987
|
+
async function selectWork(workId, preferredChapterId = null) {
|
|
1905
1988
|
const discarding = state.work?.id !== workId && state.dirty;
|
|
1906
1989
|
if (discarding && !confirmDiscardChanges()) return false;
|
|
1907
1990
|
const nextWork = await api(`/api/works/${workId}`);
|
|
1908
1991
|
if (state.work?.id !== nextWork.id) {
|
|
1992
|
+
loadedAiModelsWorkId = null;
|
|
1993
|
+
loadedAiReferencesWorkId = null;
|
|
1994
|
+
loadedAiConversationsWorkId = null;
|
|
1995
|
+
state.models = [];
|
|
1996
|
+
state.characters = [];
|
|
1997
|
+
state.settings = [];
|
|
1998
|
+
if (aiContextUsageTimer !== null) clearTimeout(aiContextUsageTimer);
|
|
1999
|
+
aiContextUsageTimer = null;
|
|
2000
|
+
aiContextUsageRequest += 1;
|
|
1909
2001
|
state.aiCitations = [];
|
|
1910
2002
|
state.aiReferences = [];
|
|
1911
2003
|
state.aiPromptSent = false;
|
|
@@ -1916,6 +2008,8 @@ async function selectWork(workId) {
|
|
|
1916
2008
|
renderAiQuickActions();
|
|
1917
2009
|
resetAiFeed();
|
|
1918
2010
|
$("#ai-conversation-title").textContent = "新对话";
|
|
2011
|
+
$("#ai-model").innerHTML = '<option value="">使用创作助手时加载模型</option>';
|
|
2012
|
+
setAiContextMeter(null);
|
|
1919
2013
|
renderAiConversationHistory();
|
|
1920
2014
|
}
|
|
1921
2015
|
if (discarding) setSaveState("就绪");
|
|
@@ -1927,15 +2021,14 @@ async function selectWork(workId) {
|
|
|
1927
2021
|
settingsReturnContext = null;
|
|
1928
2022
|
state.work = nextWork;
|
|
1929
2023
|
state.chapter = null;
|
|
2024
|
+
applyWorkAccessMode();
|
|
1930
2025
|
updateDocumentTitle(state.work);
|
|
1931
2026
|
$("#work-meta").textContent = `${state.work.title}${state.work.author ? ` · ${state.work.author}` : ""} · ${state.work.wordCount} 字`;
|
|
1932
2027
|
$("#top-search-button").disabled = false;
|
|
1933
2028
|
renderTree();
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
await
|
|
1937
|
-
const firstChapter = state.work.volumes.flatMap((volume) => volume.chapters)[0];
|
|
1938
|
-
if (state.module === "editor" && firstChapter) await selectChapter(firstChapter.id);
|
|
2029
|
+
const chapters = state.work.volumes.flatMap((volume) => volume.chapters);
|
|
2030
|
+
const targetChapter = chapters.find((chapter) => chapter.id === preferredChapterId) ?? chapters[0];
|
|
2031
|
+
if (state.module === "editor" && targetChapter) await selectChapter(targetChapter.id);
|
|
1939
2032
|
else if (state.module === "editor") showWelcome(true);
|
|
1940
2033
|
else await showModule(state.module);
|
|
1941
2034
|
return true;
|
|
@@ -1964,6 +2057,7 @@ function renderTree() {
|
|
|
1964
2057
|
renderTree();
|
|
1965
2058
|
});
|
|
1966
2059
|
button.addEventListener("contextmenu", (event) => {
|
|
2060
|
+
if (!canEditWork()) return;
|
|
1967
2061
|
event.preventDefault();
|
|
1968
2062
|
openVolumeDialog(state.work.volumes.find((volume) => volume.id === button.dataset.volumeToggle));
|
|
1969
2063
|
});
|
|
@@ -1971,6 +2065,7 @@ function renderTree() {
|
|
|
1971
2065
|
$("#novel-tree").querySelectorAll("[data-chapter-id]").forEach((button) => {
|
|
1972
2066
|
button.addEventListener("click", () => selectChapter(button.dataset.chapterId));
|
|
1973
2067
|
button.addEventListener("contextmenu", (event) => {
|
|
2068
|
+
if (!canEditWork()) return;
|
|
1974
2069
|
event.preventDefault();
|
|
1975
2070
|
openChapterTypeMenu(button.dataset.chapterId, event.clientX, event.clientY);
|
|
1976
2071
|
});
|
|
@@ -2020,7 +2115,8 @@ async function selectChapter(chapterId) {
|
|
|
2020
2115
|
scheduleChapterLineNumbers();
|
|
2021
2116
|
$("#chapter-insight").classList.add("hidden");
|
|
2022
2117
|
updateChapterStats();
|
|
2023
|
-
if (
|
|
2118
|
+
if (!canEditWork()) setSaveState("仅查看");
|
|
2119
|
+
else if (spacingChanged) scheduleChapterAutoSave(120);
|
|
2024
2120
|
else setSaveState("已保存");
|
|
2025
2121
|
renderTree();
|
|
2026
2122
|
replacePageRoute({ view: "editor", workId: state.work.id, chapterId: state.chapter.id });
|
|
@@ -2077,6 +2173,7 @@ const moduleMeta = {
|
|
|
2077
2173
|
|
|
2078
2174
|
async function showModule(module) {
|
|
2079
2175
|
if (!state.work) return showWelcome();
|
|
2176
|
+
if (!canEditWork() && ["tasks", "ai-settings"].includes(module)) module = "editor";
|
|
2080
2177
|
if (module !== "editor" && state.module === "editor" && !confirmDiscardChanges()) return;
|
|
2081
2178
|
if (module !== "editor" && state.module === "editor" && state.dirty) setSaveState("已放弃修改");
|
|
2082
2179
|
state.module = module;
|
|
@@ -2098,7 +2195,7 @@ async function showModule(module) {
|
|
|
2098
2195
|
$("#module-title").textContent = meta[1];
|
|
2099
2196
|
$("#module-description").textContent = meta[2];
|
|
2100
2197
|
$("#module-create-button").textContent = meta[3];
|
|
2101
|
-
$("#module-create-button").classList.toggle("hidden", module === "ai-settings");
|
|
2198
|
+
$("#module-create-button").classList.toggle("hidden", module === "ai-settings" || !canEditWork());
|
|
2102
2199
|
$("#module-content").innerHTML = '<div class="empty-state">正在载入……</div>';
|
|
2103
2200
|
try {
|
|
2104
2201
|
if (module === "settings") await renderSettings();
|
|
@@ -2189,6 +2286,7 @@ function bindEntityHistoryButtons(refresh) {
|
|
|
2189
2286
|
|
|
2190
2287
|
async function renderSettings() {
|
|
2191
2288
|
const records = await api(`/api/works/${state.work.id}/settings`);
|
|
2289
|
+
state.settings = records;
|
|
2192
2290
|
$("#module-content").innerHTML = records.length ? `<div class="card-grid">${records.map((item) => `
|
|
2193
2291
|
<article class="record-card"><small>${esc(item.category)} · ${item.locked ? "已锁定" : esc(item.status)}</small>
|
|
2194
2292
|
<h3>${esc(item.title)}</h3><p>${esc(item.content)}</p>
|
|
@@ -2587,8 +2685,12 @@ async function renderBookAiSettings() {
|
|
|
2587
2685
|
api(`/api/works/${state.work.id}/task-defaults`)
|
|
2588
2686
|
]);
|
|
2589
2687
|
const host = $("#module-content");
|
|
2590
|
-
const agentTools = new Set(settings.agentTools ?? ["story_index", "read_chapters", "query_story_knowledge"]);
|
|
2688
|
+
const agentTools = new Set(settings.agentTools ?? ["story_index", "read_chapters", "grep", "query_story_knowledge"]);
|
|
2591
2689
|
host.innerHTML = `<section class="config-section"><div class="config-section-header"><div><h2>本书系统提示词</h2><p>会追加在内置系统提示词和平台全局系统提示词之后,只影响《${esc(state.work.title)}》的 AI 请求。</p></div></div><div class="field-label"><textarea id="work-system-prompt" rows="8" aria-label="本书系统提示词" placeholder="例如:叙事使用第三人称,哥斯拉不得离开地球。">${esc(settings.systemPrompt)}</textarea></div><div class="card-actions"><button id="save-work-system-prompt" class="primary-button">保存本书提示词</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>全书概要引用配额</h2><p>引用全书概要时按分卷保留覆盖,并优先加入与当前问题相关的章节概要;该比例控制概要可使用的上下文预算。</p></div></div><div class="field-label"><label class="book-summary-context-percent-field">上下文占比(%)<input id="book-summary-context-percent" type="number" min="1" max="90" value="${esc(String(settings.bookSummaryContextPercent ?? 50))}" aria-label="全书概要引用上下文占比"></label></div><div class="card-actions"><button id="save-book-summary-context-percent" class="primary-button">保存概要配额</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>对话长期记忆</h2><p>对话历史使用独立预算;达到阈值时先提醒,继续发送会把较早消息整理成带来源的结构化长期记忆,并尽量保留最近八条原文。</p></div></div><div class="field-label"><label class="context-compact-threshold-field">整理提醒阈值(%)<input id="context-compact-threshold" type="number" min="50" max="90" value="${esc(String(settings.contextCompactThreshold ?? 85))}" aria-label="对话长期记忆整理提醒阈值"></label></div><div class="card-actions"><button id="save-context-compact-threshold" class="primary-button">保存整理阈值</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>AI 查询工具</h2><p>工具默认可用,作为已有上下文的补充。关闭后模型不会看到对应能力;所有工具只读且有数量、篇幅与调用轮次限制。</p></div></div><div class="ai-agent-tools"><label><input name="agent-tool" type="checkbox" value="story_index" ${agentTools.has("story_index") ? "checked" : ""}><span><strong>作品目录与章节概要</strong><small>分页获取卷章、章节 ID 和当前概要,不返回正文。</small></span></label><label><input name="agent-tool" type="checkbox" value="read_chapters" ${agentTools.has("read_chapters") ? "checked" : ""}><span><strong>读取章节</strong><small>按章节 ID 获取概要或正文,每次最多 3 章。</small></span></label><label><input name="agent-tool" type="checkbox" value="query_story_knowledge" ${agentTools.has("query_story_knowledge") ? "checked" : ""}><span><strong>查询作品知识</strong><small>按关键词查询设定、人物、组织、时间线、关系、大纲和伏笔。</small></span></label></div><div class="card-actions"><button id="save-agent-tools" class="primary-button">保存工具设置</button></div></section>${renderTaskDefaults(models, providers, taskDefaults)}`;
|
|
2690
|
+
host.querySelector('input[name="agent-tool"][value="query_story_knowledge"]').closest("label").insertAdjacentHTML(
|
|
2691
|
+
"beforebegin",
|
|
2692
|
+
`<label><input name="agent-tool" type="checkbox" value="grep" ${agentTools.has("grep") ? "checked" : ""}><span><strong>查询正文关键字</strong><small>从段落索引查询关键字,默认返回前 20 条完整段落和章节信息。</small></span></label>`
|
|
2693
|
+
);
|
|
2592
2694
|
$("#save-work-system-prompt").addEventListener("click", async () => {
|
|
2593
2695
|
const button = $("#save-work-system-prompt");
|
|
2594
2696
|
button.disabled = true;
|
|
@@ -2655,8 +2757,12 @@ async function renderBookAiSettings() {
|
|
|
2655
2757
|
}
|
|
2656
2758
|
|
|
2657
2759
|
async function loadModels() {
|
|
2658
|
-
|
|
2659
|
-
|
|
2760
|
+
const workId = state.work?.id;
|
|
2761
|
+
if (!workId) return;
|
|
2762
|
+
const models = await api(`/api/works/${workId}/models`);
|
|
2763
|
+
if (state.work?.id !== workId) return;
|
|
2764
|
+
state.models = models;
|
|
2765
|
+
loadedAiModelsWorkId = workId;
|
|
2660
2766
|
const select = $("#ai-model");
|
|
2661
2767
|
select.innerHTML = state.models.length
|
|
2662
2768
|
? state.models.map((model) => `<option value="${esc(model.id)}" ${model.enabled ? "" : "disabled"}>${esc(modelOptionLabel(model))}</option>`).join("")
|
|
@@ -2664,6 +2770,27 @@ async function loadModels() {
|
|
|
2664
2770
|
scheduleAiContextUsage();
|
|
2665
2771
|
}
|
|
2666
2772
|
|
|
2773
|
+
async function ensureAiModelsLoaded() {
|
|
2774
|
+
const workId = state.work?.id;
|
|
2775
|
+
if (!workId || loadedAiModelsWorkId === workId) return;
|
|
2776
|
+
if (aiModelsLoadPromise && aiModelsLoadWorkId === workId) return aiModelsLoadPromise;
|
|
2777
|
+
const select = $("#ai-model");
|
|
2778
|
+
select.innerHTML = '<option value="">正在加载模型……</option>';
|
|
2779
|
+
aiModelsLoadWorkId = workId;
|
|
2780
|
+
aiModelsLoadPromise = loadModels();
|
|
2781
|
+
try {
|
|
2782
|
+
await aiModelsLoadPromise;
|
|
2783
|
+
} catch (error) {
|
|
2784
|
+
if (state.work?.id === workId) select.innerHTML = '<option value="">模型加载失败,点击重试</option>';
|
|
2785
|
+
throw error;
|
|
2786
|
+
} finally {
|
|
2787
|
+
if (aiModelsLoadWorkId === workId) {
|
|
2788
|
+
aiModelsLoadPromise = null;
|
|
2789
|
+
aiModelsLoadWorkId = null;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2667
2794
|
let aiContextUsageTimer = null;
|
|
2668
2795
|
let aiContextUsageRequest = 0;
|
|
2669
2796
|
|
|
@@ -2772,11 +2899,32 @@ async function refreshAiContextUsage() {
|
|
|
2772
2899
|
}
|
|
2773
2900
|
|
|
2774
2901
|
async function loadAiReferences() {
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
api(`/api/works/${
|
|
2902
|
+
const workId = state.work?.id;
|
|
2903
|
+
if (!workId) return;
|
|
2904
|
+
const [characters, settings] = await Promise.all([
|
|
2905
|
+
api(`/api/works/${workId}/characters`),
|
|
2906
|
+
api(`/api/works/${workId}/settings`)
|
|
2779
2907
|
]);
|
|
2908
|
+
if (state.work?.id !== workId) return;
|
|
2909
|
+
state.characters = characters;
|
|
2910
|
+
state.settings = settings;
|
|
2911
|
+
loadedAiReferencesWorkId = workId;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
async function ensureAiReferencesLoaded() {
|
|
2915
|
+
const workId = state.work?.id;
|
|
2916
|
+
if (!workId || loadedAiReferencesWorkId === workId) return;
|
|
2917
|
+
if (aiReferencesLoadPromise && aiReferencesLoadWorkId === workId) return aiReferencesLoadPromise;
|
|
2918
|
+
aiReferencesLoadWorkId = workId;
|
|
2919
|
+
aiReferencesLoadPromise = loadAiReferences();
|
|
2920
|
+
try {
|
|
2921
|
+
await aiReferencesLoadPromise;
|
|
2922
|
+
} finally {
|
|
2923
|
+
if (aiReferencesLoadWorkId === workId) {
|
|
2924
|
+
aiReferencesLoadPromise = null;
|
|
2925
|
+
aiReferencesLoadWorkId = null;
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2780
2928
|
}
|
|
2781
2929
|
|
|
2782
2930
|
function field(name, label, type = "text", value = "", options = []) {
|
|
@@ -2938,7 +3086,7 @@ function openWorkSettingsDialog(work) {
|
|
|
2938
3086
|
if (!work) return;
|
|
2939
3087
|
const canManageAccess = ["admin", "owner"].includes(String(work.accessRole));
|
|
2940
3088
|
const accessField = `<section class="work-access-field" aria-labelledby="work-access-title">
|
|
2941
|
-
<div><strong id="work-access-title">可访问人</strong><small
|
|
3089
|
+
<div><strong id="work-access-title">可访问人</strong><small>可以分别授予成员仅查看或共同编辑权限。</small></div>
|
|
2942
3090
|
${canManageAccess ? '<button id="work-access-manage" class="ghost-button" type="button">添加或管理可访问人</button>' : '<small>仅作品创建者或系统管理员可以调整访问权限。</small>'}
|
|
2943
3091
|
</section>`;
|
|
2944
3092
|
openDialog("作品信息",
|
|
@@ -3192,6 +3340,12 @@ async function openCharacterDialog(item) {
|
|
|
3192
3340
|
$("#character-history-button").title = item ? "查看、比较和回滚历史版本" : "创建人物档案后即可查看版本历史";
|
|
3193
3341
|
setCharacterHistoryVisible(false);
|
|
3194
3342
|
renderCharacterEditorFields(item);
|
|
3343
|
+
const viewOnly = !canEditWork();
|
|
3344
|
+
if (viewOnly) {
|
|
3345
|
+
$("#character-editor-eyebrow").textContent = "人物档案";
|
|
3346
|
+
$("#character-editor-fields").querySelectorAll("input, textarea").forEach((control) => { control.readOnly = true; });
|
|
3347
|
+
$("#character-editor-fields").querySelectorAll("select, input[type='checkbox']").forEach((control) => { control.disabled = true; });
|
|
3348
|
+
}
|
|
3195
3349
|
document.querySelectorAll("[data-character-editor-tab]").forEach((button) => {
|
|
3196
3350
|
button.onclick = () => activateCharacterEditorTab(button.dataset.characterEditorTab);
|
|
3197
3351
|
});
|
|
@@ -3199,6 +3353,7 @@ async function openCharacterDialog(item) {
|
|
|
3199
3353
|
const form = $("#character-editor-form");
|
|
3200
3354
|
form.onsubmit = async (event) => {
|
|
3201
3355
|
event.preventDefault();
|
|
3356
|
+
if (!canEditWork()) return;
|
|
3202
3357
|
const submit = $("#character-editor-submit");
|
|
3203
3358
|
submit.disabled = true;
|
|
3204
3359
|
try {
|
|
@@ -3391,6 +3546,11 @@ function openModelDialog(providerId, item = null) {
|
|
|
3391
3546
|
|
|
3392
3547
|
async function sendAi() {
|
|
3393
3548
|
if (!state.work || !state.chapter) return toast("请先选择章节", "error");
|
|
3549
|
+
try {
|
|
3550
|
+
await Promise.all([ensureAiModelsLoaded(), ensureAiConversationsLoaded()]);
|
|
3551
|
+
} catch (error) {
|
|
3552
|
+
return toast(`创作助手加载失败:${error.message}`, "error");
|
|
3553
|
+
}
|
|
3394
3554
|
const modelId = $("#ai-model").value;
|
|
3395
3555
|
if (!modelId) return toast("请先在 AI 管理中配置并选择模型", "error");
|
|
3396
3556
|
const instruction = aiPromptText().trim();
|
|
@@ -3936,10 +4096,11 @@ $("#member-invite-form").addEventListener("submit", async (event) => {
|
|
|
3936
4096
|
const work = memberDialogWork ?? state.work;
|
|
3937
4097
|
if (!work || !userId) return;
|
|
3938
4098
|
try {
|
|
3939
|
-
const
|
|
4099
|
+
const role = $("#member-role-select").value;
|
|
4100
|
+
const members = await api(`/api/works/${encodeURIComponent(work.id)}/members`, { method: "POST", body: { userId, role } });
|
|
3940
4101
|
renderMembers(members);
|
|
3941
4102
|
await fillMemberCandidates(members);
|
|
3942
|
-
toast("协作者已邀请");
|
|
4103
|
+
toast(role === "viewer" ? "仅查看成员已邀请" : "协作者已邀请");
|
|
3943
4104
|
} catch (error) { toast(error.message, "error"); }
|
|
3944
4105
|
});
|
|
3945
4106
|
$("#platform-new-provider").addEventListener("click", () => openProviderDialog());
|
|
@@ -4055,9 +4216,22 @@ $("#module-nav").addEventListener("click", (event) => {
|
|
|
4055
4216
|
});
|
|
4056
4217
|
$("#module-more-button").addEventListener("click", () => setModuleNavExpanded(!moduleNavExpanded));
|
|
4057
4218
|
$("#module-create-button").addEventListener("click", () => ({ settings: openSettingDialog, characters: openCharacterDialog, races: openRaceDialog, organizations: openOrganizationDialog, timeline: openTimelineDialog, outlines: openForeshadowDialog, relationships: openRelationshipDialog, reviews: openReviewDialog, tasks: openTaskDialog })[state.module]?.());
|
|
4058
|
-
$("#ai-prompt").addEventListener("input", () => {
|
|
4219
|
+
$("#ai-prompt").addEventListener("input", async () => {
|
|
4059
4220
|
updateAiMentionMenu();
|
|
4060
4221
|
scheduleAiContextUsage();
|
|
4222
|
+
if (!findAiMention(aiPromptTextBeforeCursor())) return;
|
|
4223
|
+
try {
|
|
4224
|
+
await ensureAiReferencesLoaded();
|
|
4225
|
+
updateAiMentionMenu();
|
|
4226
|
+
} catch (error) {
|
|
4227
|
+
toast(`引用数据加载失败:${error.message}`, "error");
|
|
4228
|
+
}
|
|
4229
|
+
});
|
|
4230
|
+
$("#ai-prompt").addEventListener("focus", () => {
|
|
4231
|
+
ensureAiModelsLoaded().catch((error) => toast(`模型加载失败:${error.message}`, "error"));
|
|
4232
|
+
});
|
|
4233
|
+
$("#ai-model").addEventListener("focus", () => {
|
|
4234
|
+
ensureAiModelsLoaded().catch((error) => toast(`模型加载失败:${error.message}`, "error"));
|
|
4061
4235
|
});
|
|
4062
4236
|
$("#ai-model").addEventListener("change", scheduleAiContextUsage);
|
|
4063
4237
|
$("#ai-task").addEventListener("change", scheduleAiContextUsage);
|
|
@@ -4208,8 +4382,14 @@ $("#ai-context-new-conversation").addEventListener("click", async () => {
|
|
|
4208
4382
|
}
|
|
4209
4383
|
});
|
|
4210
4384
|
$("#ai-context-dismiss").addEventListener("click", hideAiContextWarning);
|
|
4211
|
-
$("#ai-history-toggle").addEventListener("click", () => {
|
|
4212
|
-
|
|
4385
|
+
$("#ai-history-toggle").addEventListener("click", async () => {
|
|
4386
|
+
if ($("#ai-history-dialog").open) return setAiHistoryVisible(false);
|
|
4387
|
+
try {
|
|
4388
|
+
await ensureAiConversationsLoaded();
|
|
4389
|
+
setAiHistoryVisible(true);
|
|
4390
|
+
} catch (error) {
|
|
4391
|
+
toast(`对话历史加载失败:${error.message}`, "error");
|
|
4392
|
+
}
|
|
4213
4393
|
});
|
|
4214
4394
|
$("#ai-history-close").addEventListener("click", () => setAiHistoryVisible(false));
|
|
4215
4395
|
$("#ai-history-dialog").addEventListener("close", () => {
|
package/dist/public/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<script src="/theme-init.js?v=20260713-dark-mode"></script>
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<link rel="stylesheet" href="/styles.css?v=
|
|
12
|
+
<link rel="stylesheet" href="/styles.css?v=20260719-viewer-toast-layer">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="auth-pending">
|
|
15
15
|
<section id="auth-view" class="auth-view" aria-labelledby="auth-title">
|
|
@@ -263,7 +263,7 @@
|
|
|
263
263
|
</aside>
|
|
264
264
|
</div>
|
|
265
265
|
|
|
266
|
-
<div id="toast-region" class="toast-region" data-position="bottom-right" aria-live="polite"></div>
|
|
266
|
+
<div id="toast-region" class="toast-region" data-position="bottom-right" aria-live="polite" popover="manual"></div>
|
|
267
267
|
<div id="chapter-type-menu" class="chapter-type-menu hidden" role="menu" aria-label="设置章节类型" data-testid="chapter-type-menu">
|
|
268
268
|
<strong>标记章节</strong>
|
|
269
269
|
<div>
|
|
@@ -476,7 +476,7 @@
|
|
|
476
476
|
<dialog id="members-dialog" class="dialog wide-dialog" aria-labelledby="members-dialog-title">
|
|
477
477
|
<div class="dialog-header"><div><span id="members-dialog-eyebrow" class="eyebrow">作品权限</span><h2 id="members-dialog-title">可访问人</h2></div><button id="members-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
|
|
478
478
|
<div class="access-dialog-body">
|
|
479
|
-
<form id="member-invite-form" class="member-invite-form"><label>邀请已注册用户<select id="member-user-select" required></select></label><button class="primary-button" type="submit"
|
|
479
|
+
<form id="member-invite-form" class="member-invite-form"><label>邀请已注册用户<select id="member-user-select" required></select></label><label>访问权限<select id="member-role-select" required><option value="viewer">仅查看</option><option value="editor">可编辑</option></select></label><button class="primary-button" type="submit">发送邀请</button></form>
|
|
480
480
|
<div id="members-list" class="access-list"></div>
|
|
481
481
|
</div>
|
|
482
482
|
</dialog>
|
|
@@ -542,6 +542,6 @@
|
|
|
542
542
|
</section>
|
|
543
543
|
</dialog>
|
|
544
544
|
|
|
545
|
-
<script type="module" src="/app.js?v=20260719-
|
|
545
|
+
<script type="module" src="/app.js?v=20260719-ai-grep-lazy-viewer-toast"></script>
|
|
546
546
|
</body>
|
|
547
547
|
</html>
|
package/dist/public/styles.css
CHANGED
|
@@ -424,7 +424,7 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
424
424
|
cursor: not-allowed;
|
|
425
425
|
opacity: .46;
|
|
426
426
|
}
|
|
427
|
-
.member-invite-form { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 10px; margin: 0; }
|
|
427
|
+
.member-invite-form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(130px, .3fr) auto; align-items: end; gap: 10px; margin: 0; }
|
|
428
428
|
.member-invite-form label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
429
429
|
.member-invite-form select {
|
|
430
430
|
width: 100%;
|
|
@@ -437,6 +437,47 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
437
437
|
font-size: 13px;
|
|
438
438
|
}
|
|
439
439
|
.member-invite-form .primary-button { min-height: 38px; white-space: nowrap; }
|
|
440
|
+
.app-shell.view-only-mode:not(.shelf-mode) { grid-template-columns: var(--left-panel-width) minmax(480px, 1fr); }
|
|
441
|
+
.app-shell.view-only-mode:not(.shelf-mode) .ai-panel { display: none; }
|
|
442
|
+
.app-shell.view-only-mode:not(.shelf-mode) .left-primary-actions .file-button,
|
|
443
|
+
.app-shell.view-only-mode:not(.shelf-mode) #new-volume-button,
|
|
444
|
+
.app-shell.view-only-mode:not(.shelf-mode) #new-chapter-button,
|
|
445
|
+
.app-shell.view-only-mode:not(.shelf-mode) #module-create-button,
|
|
446
|
+
.app-shell.view-only-mode:not(.shelf-mode) [data-work-settings],
|
|
447
|
+
.app-shell.view-only-mode:not(.shelf-mode) [data-module="tasks"],
|
|
448
|
+
.app-shell.view-only-mode:not(.shelf-mode) [data-module="ai-settings"],
|
|
449
|
+
.app-shell.view-only-mode:not(.shelf-mode) #tidy-blank-lines-button,
|
|
450
|
+
.app-shell.view-only-mode:not(.shelf-mode) #save-button { display: none !important; }
|
|
451
|
+
.app-shell.view-only-mode:not(.shelf-mode) .left-primary-actions { justify-content: flex-end; }
|
|
452
|
+
.app-shell.view-only-mode:not(.shelf-mode) #chapter-title[readonly],
|
|
453
|
+
.app-shell.view-only-mode:not(.shelf-mode) #chapter-content[readonly] { cursor: default; }
|
|
454
|
+
body.work-viewer-mode [data-setting-status],
|
|
455
|
+
body.work-viewer-mode [data-edit-setting],
|
|
456
|
+
body.work-viewer-mode [data-edit-character],
|
|
457
|
+
body.work-viewer-mode [data-edit-race],
|
|
458
|
+
body.work-viewer-mode [data-edit-organization],
|
|
459
|
+
body.work-viewer-mode [data-edit-timeline-track],
|
|
460
|
+
body.work-viewer-mode [data-add-event-track],
|
|
461
|
+
body.work-viewer-mode [data-edit-event],
|
|
462
|
+
body.work-viewer-mode [data-split-event],
|
|
463
|
+
body.work-viewer-mode [data-event-select],
|
|
464
|
+
body.work-viewer-mode #create-timeline-track,
|
|
465
|
+
body.work-viewer-mode #merge-events,
|
|
466
|
+
body.work-viewer-mode [data-edit-foreshadow],
|
|
467
|
+
body.work-viewer-mode [data-edit-outline],
|
|
468
|
+
body.work-viewer-mode [data-edit-relationship],
|
|
469
|
+
body.work-viewer-mode [data-review-id],
|
|
470
|
+
body.work-viewer-mode .task-auto-run-panel,
|
|
471
|
+
body.work-viewer-mode [data-run-task],
|
|
472
|
+
body.work-viewer-mode [data-cancel-task],
|
|
473
|
+
body.work-viewer-mode [data-restore-version],
|
|
474
|
+
body.work-viewer-mode [data-entity-version-restore],
|
|
475
|
+
body.work-viewer-mode [data-character-restore],
|
|
476
|
+
body.work-viewer-mode [data-item-list-add],
|
|
477
|
+
body.work-viewer-mode [data-item-list-remove],
|
|
478
|
+
body.work-viewer-mode [data-structured-list-add],
|
|
479
|
+
body.work-viewer-mode [data-structured-list-remove],
|
|
480
|
+
body.work-viewer-mode .character-editor-actions { display: none !important; }
|
|
440
481
|
.search-form { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 10px; margin: 0; }
|
|
441
482
|
.search-form label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
442
483
|
.search-form input {
|
|
@@ -1185,7 +1226,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1185
1226
|
.ai-citation-card.is-expanded pre { display: block; }
|
|
1186
1227
|
.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; }
|
|
1187
1228
|
|
|
1188
|
-
.toast-region { position: fixed;
|
|
1229
|
+
.toast-region { position: fixed; inset: auto 24px 24px auto; z-index: 2147483647; display: grid; max-width: calc(100vw - 48px); margin: 0; padding: 0; gap: 8px; overflow: visible; border: 0; background: transparent; color: inherit; pointer-events: none; }
|
|
1189
1230
|
.toast-region[data-position="top-right"] { top: 78px; bottom: auto; }
|
|
1190
1231
|
.toast-region[data-position="bottom-right"] { top: auto; bottom: 24px; }
|
|
1191
1232
|
.toast { max-width: 360px; background: var(--toast-bg); color: var(--toast-fg); padding: 11px 15px; border-radius: 4px; box-shadow: var(--shadow); font-size: 12px; animation: toast-appear-top .18s ease; }
|
|
@@ -1348,6 +1389,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1348
1389
|
.relationship-table .relationship-actions button + button { margin-left: 5px; }
|
|
1349
1390
|
|
|
1350
1391
|
@media (max-width: 540px) {
|
|
1392
|
+
.member-invite-form { grid-template-columns: minmax(0, 1fr); }
|
|
1351
1393
|
.onboarding-popover { width: calc(100vw - 24px); }
|
|
1352
1394
|
.onboarding-popover-header { padding-inline: 15px; }
|
|
1353
1395
|
.onboarding-popover-body { padding: 16px 17px 18px; }
|
package/dist/request-context.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
const
|
|
2
|
+
const requestStorage = new AsyncLocalStorage();
|
|
3
|
+
export function runWithRequestContext(context, operation) {
|
|
4
|
+
return requestStorage.run(context, operation);
|
|
5
|
+
}
|
|
3
6
|
export function runWithRequestActor(actor, operation) {
|
|
4
|
-
|
|
7
|
+
const current = requestStorage.getStore();
|
|
8
|
+
return requestStorage.run({ ...(current?.requestId ? { requestId: current.requestId } : {}), actor }, operation);
|
|
5
9
|
}
|
|
6
10
|
export function currentRequestActor() {
|
|
7
|
-
return
|
|
11
|
+
return requestStorage.getStore()?.actor ?? null;
|
|
12
|
+
}
|
|
13
|
+
export function currentRequestContext() {
|
|
14
|
+
return requestStorage.getStore() ?? null;
|
|
8
15
|
}
|
|
9
16
|
//# sourceMappingURL=request-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../src/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../src/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAerD,MAAM,cAAc,GAAG,IAAI,iBAAiB,EAAyB,CAAC;AAEtE,MAAM,UAAU,qBAAqB,CAAI,OAAuB,EAAE,SAAkB;IAClF,OAAO,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAI,KAA0B,EAAE,SAAkB;IACnF,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,CAAC;IAC1C,OAAO,cAAc,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;AACnH,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,cAAc,CAAC,QAAQ,EAAE,EAAE,KAAK,IAAI,IAAI,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,cAAc,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC;AAC3C,CAAC"}
|