@riemannre3/dsh-roleplay 0.1.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/lib/client.js ADDED
@@ -0,0 +1,2327 @@
1
+ // Generated single-file DSH client entry.
2
+ /**
3
+ * Reconcile DSH's resident client Session after a Host runtime removes and
4
+ * republishes the same durable id. rc.7 through 0.1.1-rc.2 keep the old
5
+ * Session instance and its sticky `removed` bit; a fresh page rebuilds the
6
+ * client object layer from the still-authoritative Host Session.
7
+ */
8
+ function recoverRestoredClientSession(sessions, options) {
9
+ const timeoutMs = options.timeoutMs ?? 2_000;
10
+ return new Promise((resolve) => {
11
+ let settled = false;
12
+ let unsubscribe = () => { };
13
+ let timer;
14
+ const finish = (result) => {
15
+ if (settled)
16
+ return;
17
+ settled = true;
18
+ unsubscribe();
19
+ if (timer !== undefined)
20
+ clearTimeout(timer);
21
+ resolve(result);
22
+ };
23
+ const inspect = () => {
24
+ const snapshot = sessions.list.getSnapshot();
25
+ if (snapshot.current !== options.sessionId && !snapshot.ids.includes(options.sessionId))
26
+ return;
27
+ const binding = sessions.binding(options.sessionId);
28
+ if (binding === undefined)
29
+ return;
30
+ if (binding.session.getSnapshot().removed === true) {
31
+ options.reload();
32
+ finish("reloaded");
33
+ return;
34
+ }
35
+ finish("ready");
36
+ };
37
+ unsubscribe = sessions.list.subscribe(inspect);
38
+ timer = setTimeout(() => finish("missing"), timeoutMs);
39
+ inspect();
40
+ });
41
+ }
42
+
43
+ const rawHtmlDocument = /^\s*<(?:!doctype|html|body|style|div|section|details)\b/iu;
44
+ const htmlFragment = /^\s*<(?:!doctype|[a-z][\w:-]*)(?:\s|>)[\s\S]*>\s*$/iu;
45
+ const fencedHtml = /```html\s*\r?\n([\s\S]*?)\r?\n```/giu;
46
+ const fencedUntypedHtmlDocument = /```[ \t]*\r?\n(\s*<(?:!doctype|html)\b[\s\S]*?)\r?\n```/giu;
47
+ const htmlTag = /<!--[\s\S]*?-->|<![^>]*>|<\/?([a-z][\w:-]*)\b(?:\s+(?:"[^"]*"|'[^']*'|[^'"<>])*)?\s*\/?>/giu;
48
+ const rawTextElement = /<(script|style|template)\b(?:\s+(?:"[^"]*"|'[^']*'|[^'"<>])*)?\s*>[\s\S]*?<\/\1\s*>/giu;
49
+ const voidHtmlTags = new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
50
+ const crossBoundaryContainerTags = new Set(["article", "aside", "body", "details", "dialog", "div", "fieldset", "footer", "form", "header", "html", "main", "nav", "ol", "section", "table", "tbody", "tfoot", "thead", "ul"]);
51
+ function leavesOpenHtmlContainer(value) {
52
+ const stack = [];
53
+ const structuralHtml = value.replace(rawTextElement, "");
54
+ for (const match of structuralHtml.matchAll(new RegExp(htmlTag.source, htmlTag.flags))) {
55
+ const token = match[0];
56
+ const tag = match[1]?.toLowerCase();
57
+ if (tag === undefined || token.startsWith("<!--") || token.startsWith("<!"))
58
+ continue;
59
+ if (token.startsWith("</")) {
60
+ const matchingIndex = stack.lastIndexOf(tag);
61
+ if (matchingIndex >= 0)
62
+ stack.splice(matchingIndex);
63
+ continue;
64
+ }
65
+ if (token.endsWith("/>") || voidHtmlTags.has(tag))
66
+ continue;
67
+ stack.push(tag);
68
+ }
69
+ return stack.some((tag) => crossBoundaryContainerTags.has(tag) || tag.includes("-"));
70
+ }
71
+ function escapeCrossBoundaryProse(value) {
72
+ // `<content>` is a common card control wrapper, not player prose. In the
73
+ // original shared message DOM the browser consumes it as markup; do the same
74
+ // without allowing arbitrary prose HTML to become executable in the iframe.
75
+ const visible = value
76
+ .replace(/(?:^|\r?\n)[ \t]*<\/?content\b[^>]*>[ \t]*(?=\r?\n|$)/giu, "\n")
77
+ .trim();
78
+ if (visible.length === 0)
79
+ return "";
80
+ const escaped = visible
81
+ .replaceAll("&", "&amp;")
82
+ .replaceAll("<", "&lt;")
83
+ .replaceAll(">", "&gt;")
84
+ .replaceAll('"', "&quot;")
85
+ .replaceAll("'", "&#39;");
86
+ return `<div data-dsh-cross-boundary-prose style="white-space: pre-wrap">${escaped}</div>`;
87
+ }
88
+ function stitchCrossBoundaryWrapper(blocks) {
89
+ const openingIndex = blocks.findIndex((block, index) => block.kind === "html"
90
+ && index < blocks.length - 1
91
+ && leavesOpenHtmlContainer(block.content));
92
+ if (openingIndex < 0)
93
+ return [...blocks];
94
+ const content = blocks.slice(openingIndex)
95
+ .map((block) => block.kind === "html" ? block.content : escapeCrossBoundaryProse(block.content))
96
+ .filter((value) => value.length > 0)
97
+ .join("\n");
98
+ return [...blocks.slice(0, openingIndex), { kind: "html", content }];
99
+ }
100
+ // Message iframes are expanded by the Host, so their previous viewport height
101
+ // must never participate in the next measurement. In particular,
102
+ // documentElement.scrollHeight is pinned to an already-tall iframe and cannot
103
+ // shrink after a card switches from a long page to a short page.
104
+ function measureRichFrameContentHeight(metrics) {
105
+ const finite = (value) => Number.isFinite(value) ? value : 0;
106
+ const bodyTop = Math.min(0, finite(metrics.bodyRectTop));
107
+ const bodyBottom = Math.max(finite(metrics.bodyScrollHeight), finite(metrics.bodyOffsetHeight), finite(metrics.bodyRectBottom) - bodyTop);
108
+ // A viewport-relative card can keep body at exactly the current iframe
109
+ // height while moving the rest of its UI into overflow:auto descendants.
110
+ // Add that clipped extent so repeated resize receipts grow the viewport
111
+ // until the outer DSH conversation owns the vertical scroll surface.
112
+ const nestedOverflow = Math.max(0, finite(metrics.nestedScrollableOverflowHeight ?? 0));
113
+ return Math.max(72, Math.ceil(bodyBottom + nestedOverflow));
114
+ }
115
+ // Real cards can legitimately be many screens tall, so the Host owns one
116
+ // generous outer-scroll surface. Still cap a forged or broken resize receipt
117
+ // before it can create a billion-pixel layout in the conversation.
118
+ function clampRichFrameHeight(value) {
119
+ if (!Number.isFinite(value))
120
+ return 72;
121
+ return Math.max(72, Math.min(40_000, Math.ceil(value)));
122
+ }
123
+ // The accepted SillyTavern reference profile exposes EjsTemplate as enabled.
124
+ // Real-card environment checks read this exact public context path. Keeping the
125
+ // compatibility value in one builder prevents the iframe bootstrap from
126
+ // drifting into an incomplete, card-specific mock.
127
+ function buildSillyTavernCompatibilityContext(messageCount, currentMessageId) {
128
+ const safeCount = Number.isFinite(messageCount) ? Math.max(0, Math.trunc(messageCount)) : 0;
129
+ const safeMessageId = Number.isFinite(currentMessageId) ? Math.trunc(currentMessageId) : -1;
130
+ return {
131
+ chat: Array.from({ length: safeCount }, () => ({})),
132
+ chatId: safeMessageId,
133
+ extensionSettings: { EjsTemplate: { enabled: true } },
134
+ };
135
+ }
136
+ function alignProjectedRoles(flowRoles, projectedRoles) {
137
+ const aligned = Array.from({ length: flowRoles.length }, () => null);
138
+ let projectedIndex = 0;
139
+ for (let flowIndex = 0; flowIndex < flowRoles.length && projectedIndex < projectedRoles.length; flowIndex += 1) {
140
+ if (flowRoles[flowIndex] !== projectedRoles[projectedIndex])
141
+ continue;
142
+ aligned[flowIndex] = projectedIndex;
143
+ projectedIndex += 1;
144
+ }
145
+ return projectedIndex === projectedRoles.length ? aligned : null;
146
+ }
147
+ function splitRichMessage(text) {
148
+ if (rawHtmlDocument.test(text))
149
+ return [{ kind: "html", content: text.trim() }];
150
+ // Some real Tavern cards prepend a fenced <style> block to an opening whose
151
+ // document root is a custom element (for example <welcome>). Treat that
152
+ // combination as one frontend document so the style and markup share an
153
+ // iframe instead of leaking hundreds of HTML lines into native prose.
154
+ const fencedDocumentCount = [...text.matchAll(new RegExp(fencedHtml.source, fencedHtml.flags))].length
155
+ + [...text.matchAll(new RegExp(fencedUntypedHtmlDocument.source, fencedUntypedHtmlDocument.flags))].length;
156
+ if (fencedDocumentCount <= 1) {
157
+ const stitched = text
158
+ .replace(fencedHtml, (_match, content) => content)
159
+ // SillyTavern cards in the wild also fence complete HTML documents without
160
+ // an `html` language label. Only unwrap documents whose first tag proves
161
+ // their intent; ordinary untyped code fences remain prose/code.
162
+ .replace(fencedUntypedHtmlDocument, (_match, content) => content)
163
+ .trim();
164
+ if (htmlFragment.test(stitched))
165
+ return [{ kind: "html", content: stitched }];
166
+ }
167
+ const blocks = [];
168
+ const expression = new RegExp(fencedHtml.source, fencedHtml.flags);
169
+ let cursor = 0;
170
+ for (const match of text.matchAll(expression)) {
171
+ const index = match.index ?? cursor;
172
+ const before = text.slice(cursor, index).trim();
173
+ if (before.length > 0)
174
+ blocks.push({ kind: "prose", content: before });
175
+ blocks.push({ kind: "html", content: match[1] ?? "" });
176
+ cursor = index + match[0].length;
177
+ }
178
+ const after = text.slice(cursor).trim();
179
+ if (after.length > 0)
180
+ blocks.push({ kind: "prose", content: after });
181
+ if (blocks.length === 0)
182
+ blocks.push({ kind: "prose", content: text });
183
+ // Some Tavern display Regex rules deliberately open a visual wrapper when a
184
+ // control tag starts and rely on the shared SillyTavern message DOM to close
185
+ // it at the end of the message. An iframe boundary would otherwise close the
186
+ // wrapper before the following prose. Compose only that structural case;
187
+ // balanced authored fragments keep the ordinary native prose/iframe flow.
188
+ return stitchCrossBoundaryWrapper(blocks);
189
+ }
190
+ // The authored documents run in a sandboxed data document with a unique
191
+ // origin. Rewrite only the well-known SillyTavern parent-window access points
192
+ // used by the accepted real cards; ordinary parent.postMessage remains
193
+ // untouched for resize notices.
194
+ function adaptRealCardFrontendHtml(value) {
195
+ return value
196
+ .replaceAll("window.parent.document", "window.__dshCompatDocument")
197
+ .replaceAll("window.top.document", "window.__dshCompatDocument")
198
+ .replaceAll("parent.document", "window.__dshCompatDocument")
199
+ .replaceAll("top.document", "window.__dshCompatDocument")
200
+ .replaceAll("window.parent?.SillyTavern", "window.__dshSillyTavern")
201
+ .replaceAll("window.top?.SillyTavern", "window.__dshSillyTavern")
202
+ .replaceAll("window.parent.SillyTavern", "window.__dshSillyTavern")
203
+ .replaceAll("window.top.SillyTavern", "window.__dshSillyTavern")
204
+ .replaceAll("window.parent?.TavernHelper", "window.__dshTavernHelper")
205
+ .replaceAll("window.top?.TavernHelper", "window.__dshTavernHelper")
206
+ .replaceAll("window.parent.TavernHelper", "window.__dshTavernHelper")
207
+ .replaceAll("window.top.TavernHelper", "window.__dshTavernHelper")
208
+ .replaceAll("window.parent?.Mvu", "window.__dshMvu")
209
+ .replaceAll("window.top?.Mvu", "window.__dshMvu")
210
+ .replaceAll("window.parent.Mvu", "window.__dshMvu")
211
+ .replaceAll("window.top.Mvu", "window.__dshMvu")
212
+ // Bundled card code sometimes aliases the frame first (`const host =
213
+ // window.top`) and only reads SillyTavern APIs afterwards. Keep resize
214
+ // postMessages pointed at the host, then collapse any remaining top/parent
215
+ // aliases to the compatibility globals installed inside the data document.
216
+ .replaceAll("window.parent.postMessage", "parent.postMessage")
217
+ .replace(/window\.(?:parent|top)(?![\w$])/gu, "window");
218
+ }
219
+
220
+ "use strict";
221
+ const clientWindow = window;
222
+ clientWindow.__ModuleLoader__.load({
223
+ id: "@riemannre3/dsh-roleplay",
224
+ factory: (requireModule) => {
225
+ const module = { exports: {} };
226
+ const React = requireModule("react");
227
+ const ReactDOM = requireModule("react-dom");
228
+ const h = React.createElement;
229
+ const { IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14, IconAgentPresetOutline16, IconChecklistOutline14, IconCheckOutline14, IconCloseOutline16, IconCopyOutline16, IconDataOutline16, IconEllipsisOutline16, IconFolderOpenOutline16, IconListPenOutline16, IconNewChatOutline16, IconPlusOutline16, IconProjectAddOutline16, IconSearchOutline16, IconSkillOutline16, IconUserOutline16, MarkdownText, } = requireModule("@deepseek-ai/dsh-client-ui-primitives");
230
+ const initialState = () => ({
231
+ sidebarMode: "tavern",
232
+ screen: "library",
233
+ cards: [],
234
+ expandedCardId: null,
235
+ selectedCardId: null,
236
+ selectedSaveId: null,
237
+ searchQuery: "",
238
+ userName: "旅行者",
239
+ openingId: "",
240
+ presetId: "dsh-default",
241
+ advancedOpen: false,
242
+ compatibilityOpen: false,
243
+ importResult: null,
244
+ importing: false,
245
+ starting: false,
246
+ busySaveId: null,
247
+ busyCardId: null,
248
+ startError: null,
249
+ startedSessionId: null,
250
+ startedCardId: null,
251
+ startedSaveId: null,
252
+ activeSessionId: null,
253
+ capabilityTargetKey: null,
254
+ capabilityLoading: false,
255
+ capabilityError: null,
256
+ capabilitySnapshot: null,
257
+ runtimeDiagnostics: []
258
+ });
259
+ let state = initialState();
260
+ let runtimeContext = null;
261
+ const listeners = new Set();
262
+ const publish = (next) => {
263
+ state = next;
264
+ for (const listener of listeners)
265
+ listener();
266
+ };
267
+ const patchState = (changes) => publish({ ...state, ...changes });
268
+ const subscribe = (listener) => {
269
+ listeners.add(listener);
270
+ return () => listeners.delete(listener);
271
+ };
272
+ const getSnapshot = () => state;
273
+ const usePrototypeState = () => React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
274
+ const selectedCard = (snapshot) => snapshot.cards.find((card) => card.id === snapshot.selectedCardId) ?? null;
275
+ function hostCard(value) {
276
+ return {
277
+ id: `imported-${value.revisionId.slice(0, 16)}`,
278
+ revisionId: value.revisionId,
279
+ title: value.title,
280
+ source: `${value.sourceFormat === "chara_card_v3" ? "角色卡 V3" : "角色卡 V2"}${value.creator ? ` · ${value.creator}` : ""}`,
281
+ summary: value.summary || "卡片已导入;没有可展示的简介。",
282
+ coverUrl: typeof value.sourceName === "string" && value.sourceName.toLocaleLowerCase().endsWith(".png") ? value.originalUrl : "",
283
+ playability: value.playability,
284
+ statusText: value.statusText,
285
+ statusDetail: value.statusDetail,
286
+ openings: value.openings.map((opening) => ({ ...opening, message: "" })),
287
+ sessions: value.sessions.map((session) => ({ ...session, lastActive: "已保存" })),
288
+ preset: "default",
289
+ imported: true,
290
+ compatibilityRows: value.compatibilityRows,
291
+ sourceDigest: value.revisionId,
292
+ };
293
+ }
294
+ async function loadCapabilitySnapshot(target) {
295
+ const key = target.sessionId === undefined ? `revision:${target.revisionId ?? ""}` : `session:${target.sessionId}`;
296
+ if (key.endsWith(":"))
297
+ return;
298
+ patchState({ capabilityTargetKey: key, capabilityLoading: true, capabilityError: null });
299
+ try {
300
+ const params = new URLSearchParams();
301
+ if (target.sessionId !== undefined)
302
+ params.set("sessionId", target.sessionId);
303
+ else
304
+ params.set("revision", target.revisionId ?? "");
305
+ const response = await fetch(`/dsh-re3-rp/capability-snapshot?${params.toString()}`, { cache: "no-store" });
306
+ const body = await response.json();
307
+ if (!response.ok || body.card === undefined)
308
+ throw new Error(typeof body.error === "string" ? body.error : "无法读取酒馆能力");
309
+ if (state.capabilityTargetKey !== key)
310
+ return;
311
+ patchState({ capabilityLoading: false, capabilityError: null, capabilitySnapshot: body });
312
+ }
313
+ catch (error) {
314
+ if (state.capabilityTargetKey !== key)
315
+ return;
316
+ patchState({ capabilityLoading: false, capabilityError: error instanceof Error ? error.message : "无法读取酒馆能力", capabilitySnapshot: null });
317
+ }
318
+ }
319
+ function syncActiveTavernSession(sessionId, revisionId) {
320
+ const card = state.cards.find((candidate) => candidate.revisionId === revisionId);
321
+ if (state.activeSessionId !== sessionId || state.selectedCardId !== card?.id) {
322
+ patchState({
323
+ activeSessionId: sessionId,
324
+ selectedCardId: card?.id ?? state.selectedCardId,
325
+ expandedCardId: card?.id ?? state.expandedCardId,
326
+ selectedSaveId: sessionId,
327
+ });
328
+ }
329
+ void loadCapabilitySnapshot({ sessionId });
330
+ }
331
+ function clearActiveTavernSession(sessionId) {
332
+ if (state.activeSessionId !== sessionId)
333
+ return;
334
+ const card = state.cards.find((candidate) => candidate.id === state.selectedCardId);
335
+ patchState({ activeSessionId: null });
336
+ if (card?.revisionId !== undefined)
337
+ void loadCapabilitySnapshot({ revisionId: card.revisionId });
338
+ }
339
+ function recordRuntimeDiagnostic(sessionId, token, message) {
340
+ const next = [...state.runtimeDiagnostics, { sessionId, token, message, capturedAt: new Date().toISOString() }].slice(-40);
341
+ patchState({ runtimeDiagnostics: next });
342
+ }
343
+ async function refreshHostCards() {
344
+ const response = await fetch("/dsh-re3-rp/cards", { cache: "no-store" });
345
+ if (!response.ok)
346
+ throw new Error("无法读取已保存的酒馆卡");
347
+ const body = await response.json();
348
+ const importedCards = (body.cards ?? []).map(hostCard);
349
+ const importedIds = new Set(importedCards.map((card) => card.id));
350
+ const cards = [...importedCards, ...state.cards.filter((card) => !importedIds.has(card.id) && !card.imported)];
351
+ const selected = cards.find((card) => card.id === state.selectedCardId) ?? cards[0];
352
+ publish({ ...state, cards, selectedCardId: selected?.id ?? null, expandedCardId: state.expandedCardId ?? selected?.id ?? null });
353
+ if (state.activeSessionId === null && selected?.revisionId !== undefined)
354
+ void loadCapabilitySnapshot({ revisionId: selected.revisionId });
355
+ }
356
+ function openLibrary() {
357
+ patchState({ sidebarMode: "tavern", screen: "library", startError: null });
358
+ }
359
+ async function moveCardInLibrary(card, direction) {
360
+ if (card.revisionId === undefined || state.busyCardId !== null)
361
+ return;
362
+ const currentIndex = state.cards.findIndex((candidate) => candidate.id === card.id);
363
+ const targetIndex = currentIndex + direction;
364
+ if (currentIndex < 0 || targetIndex < 0 || targetIndex >= state.cards.length)
365
+ return;
366
+ const ordered = [...state.cards];
367
+ [ordered[currentIndex], ordered[targetIndex]] = [ordered[targetIndex], ordered[currentIndex]];
368
+ const revisionIds = ordered.flatMap((candidate) => candidate.revisionId === undefined ? [] : [candidate.revisionId]);
369
+ patchState({ busyCardId: card.id, startError: null });
370
+ try {
371
+ const response = await fetch("/dsh-re3-rp/cards", {
372
+ method: "PATCH",
373
+ headers: { "Content-Type": "application/json" },
374
+ body: JSON.stringify({ revisionIds }),
375
+ });
376
+ const body = await response.json();
377
+ if (!response.ok)
378
+ throw new Error(body.error ?? "无法调整酒馆卡顺序");
379
+ publish({ ...state, cards: ordered, busyCardId: null });
380
+ }
381
+ catch (error) {
382
+ patchState({ busyCardId: null, startError: error instanceof Error ? error.message : "无法调整酒馆卡顺序" });
383
+ }
384
+ }
385
+ async function deleteCardFromLibrary(card) {
386
+ if (card.revisionId === undefined || state.busyCardId !== null)
387
+ return;
388
+ const confirmed = window.confirm(`从卡库删除“${card.title}”?\n\n已有 DSH Session 和原件会保留;重新导入同一原件可以恢复这张卡。`);
389
+ if (!confirmed)
390
+ return;
391
+ patchState({ busyCardId: card.id, startError: null });
392
+ try {
393
+ const response = await fetch("/dsh-re3-rp/cards", {
394
+ method: "DELETE",
395
+ headers: { "Content-Type": "application/json" },
396
+ body: JSON.stringify({ revisionId: card.revisionId }),
397
+ });
398
+ const body = await response.json();
399
+ if (!response.ok)
400
+ throw new Error(body.error ?? "无法从卡库删除酒馆卡");
401
+ await refreshHostCards();
402
+ patchState({ busyCardId: null, importResult: `已从卡库删除“${card.title}”;保留 ${body.preservedSessions ?? card.sessions.length} 个已有 Session。` });
403
+ }
404
+ catch (error) {
405
+ patchState({ busyCardId: null, startError: error instanceof Error ? error.message : "无法从卡库删除酒馆卡" });
406
+ }
407
+ }
408
+ function toggleCard(card) {
409
+ patchState({
410
+ screen: "library",
411
+ expandedCardId: state.expandedCardId === card.id ? null : card.id,
412
+ selectedCardId: card.id,
413
+ startError: null
414
+ });
415
+ if (card.revisionId !== undefined)
416
+ void loadCapabilitySnapshot({ revisionId: card.revisionId });
417
+ }
418
+ function prepareNewGame(card) {
419
+ if (card.playability === "blocked")
420
+ return;
421
+ patchState({
422
+ screen: "setup",
423
+ selectedCardId: card.id,
424
+ expandedCardId: card.id,
425
+ openingId: card.openings[0]?.id ?? "",
426
+ presetId: card.preset === "recommended" || card.preset === "required" ? "deepseek-v4f" : "dsh-default",
427
+ advancedOpen: false,
428
+ compatibilityOpen: false,
429
+ startError: null
430
+ });
431
+ }
432
+ function inspectCompatibility(card) {
433
+ patchState({
434
+ screen: "setup",
435
+ selectedCardId: card.id,
436
+ expandedCardId: card.id,
437
+ openingId: card.openings[0]?.id ?? "",
438
+ compatibilityOpen: true,
439
+ advancedOpen: false,
440
+ startError: null,
441
+ });
442
+ }
443
+ async function openSavedConversation(card, save) {
444
+ if (runtimeContext === null || state.busySaveId !== null || save.sessionId === undefined)
445
+ return;
446
+ patchState({ busySaveId: save.id, startError: null, selectedCardId: card.id, expandedCardId: card.id, selectedSaveId: save.id });
447
+ runtimeContext.sessions.open(save.sessionId);
448
+ patchState({
449
+ screen: "library",
450
+ busySaveId: null,
451
+ startedSessionId: save.sessionId,
452
+ startedCardId: card.id,
453
+ startedSaveId: save.id,
454
+ selectedCardId: card.id,
455
+ selectedSaveId: save.id,
456
+ expandedCardId: card.id,
457
+ });
458
+ if (card.revisionId === undefined)
459
+ void loadCapabilitySnapshot({ sessionId: save.sessionId });
460
+ else
461
+ syncActiveTavernSession(save.sessionId, card.revisionId);
462
+ }
463
+ async function importFiles(files) {
464
+ if (files.length === 0 || state.importing)
465
+ return;
466
+ patchState({ importing: true, importResult: `正在导入并建立 ${files.length} 个原件的运行时索引…`, startError: null });
467
+ try {
468
+ const importedCards = [];
469
+ const errors = [];
470
+ for (const file of files) {
471
+ const response = await fetch("/dsh-re3-rp/import", {
472
+ method: "POST",
473
+ headers: { "Content-Type": "application/octet-stream", "X-Dsh-Re3-Rp-Filename": encodeURIComponent(file.name) },
474
+ body: file,
475
+ });
476
+ const result = await response.json();
477
+ if (response.ok && result.card !== undefined)
478
+ importedCards.push(hostCard(result.card));
479
+ else
480
+ errors.push(`${file.name}:${result.error ?? "无法导入"}`);
481
+ }
482
+ if (importedCards.length === 0)
483
+ throw new Error(errors.join(";") || "没有识别出角色卡");
484
+ const importedIds = new Set(importedCards.map((card) => card.id));
485
+ const cards = [...importedCards, ...state.cards.filter((card) => !importedIds.has(card.id))];
486
+ const selected = importedCards[0];
487
+ publish({
488
+ ...state,
489
+ cards,
490
+ selectedCardId: selected?.id ?? state.selectedCardId,
491
+ expandedCardId: selected?.id ?? state.expandedCardId,
492
+ selectedSaveId: selected === undefined ? state.selectedSaveId : null,
493
+ screen: "library",
494
+ importing: false,
495
+ importResult: `已持久化 ${importedCards.length} 张角色卡及其原件、开场、世界书和脚本索引${errors.length > 0 ? `;${errors.join(";")}` : ""}。启用脚本只在对应 Session 的隔离运行域中启动。`,
496
+ });
497
+ if (selected?.revisionId !== undefined)
498
+ void loadCapabilitySnapshot({ revisionId: selected.revisionId });
499
+ }
500
+ catch (error) {
501
+ patchState({ importing: false, importResult: null, startError: error instanceof Error ? error.message : "无法检查这些文件" });
502
+ }
503
+ }
504
+ async function startPlay(card, selectedOpeningId) {
505
+ if (card.playability === "blocked" || state.userName.trim() === "" || runtimeContext === null || state.starting)
506
+ return;
507
+ patchState({ starting: true, startError: null });
508
+ const opening = card.openings.find((item) => item.id === selectedOpeningId) ?? card.openings[0];
509
+ const save = { id: `play-${Date.now()}`, title: opening?.label ?? "新的故事", messageCount: 0, lastActive: "刚刚", openingId: opening?.id ?? "" };
510
+ if (card.revisionId === undefined) {
511
+ patchState({ starting: false, startError: "这张卡还没有 Host revision,请重新导入原件" });
512
+ return;
513
+ }
514
+ try {
515
+ const response = await fetch("/dsh-re3-rp/sessions", {
516
+ method: "POST",
517
+ headers: { "Content-Type": "application/json" },
518
+ body: JSON.stringify({ revisionId: card.revisionId, openingId: opening?.id, userName: state.userName }),
519
+ });
520
+ const result = await response.json();
521
+ if (!response.ok || result.sessionId === undefined)
522
+ throw new Error(result.error ?? "无法创建卡片会话");
523
+ save.sessionId = result.sessionId;
524
+ save.id = result.sessionId;
525
+ save.messageCount = 1;
526
+ const cards = state.cards.map((item) => item.id === card.id ? { ...item, sessions: [save, ...item.sessions] } : item);
527
+ publish({ ...state, cards, screen: "library", expandedCardId: card.id, selectedCardId: card.id, selectedSaveId: save.id, starting: false, startedSessionId: result.sessionId, startedCardId: card.id, startedSaveId: save.id });
528
+ syncActiveTavernSession(result.sessionId, card.revisionId);
529
+ runtimeContext.sessions.open(result.sessionId);
530
+ }
531
+ catch (error) {
532
+ patchState({ starting: false, startError: error instanceof Error ? error.message : "无法创建卡片会话" });
533
+ }
534
+ }
535
+ function StatusLine({ card }) {
536
+ return h("span", { className: `tavern-status-line is-${card.playability}` }, h("i", { "aria-hidden": "true" }), h("strong", null, card.statusText));
537
+ }
538
+ function SessionRow({ card, save }) {
539
+ const snapshot = usePrototypeState();
540
+ const selected = snapshot.selectedSaveId === save.id && snapshot.expandedCardId === card.id;
541
+ const busy = snapshot.busySaveId === save.id;
542
+ return h("button", { type: "button", className: `tavern-session-row${selected ? " is-selected" : ""}`, "data-tavern-save-id": save.id, disabled: snapshot.busySaveId !== null, onClick: () => void openSavedConversation(card, save) }, h("span", { className: "tavern-session-icon" }, h(IconNewChatOutline16, { size: 15 })), h("span", { className: "tavern-session-copy" }, h("strong", null, busy ? "正在打开…" : save.title), h("small", null, `${save.messageCount} 条消息 · ${save.lastActive}`)), h(IconEllipsisOutline16, { className: "tavern-row-more", size: 15 }));
543
+ }
544
+ function CardBranch({ card }) {
545
+ const snapshot = usePrototypeState();
546
+ const [menuOpen, setMenuOpen] = React.useState(false);
547
+ const menuRef = React.useRef(null);
548
+ const expanded = snapshot.expandedCardId === card.id;
549
+ const cardIndex = snapshot.cards.findIndex((candidate) => candidate.id === card.id);
550
+ const busy = snapshot.busyCardId === card.id;
551
+ React.useEffect(() => {
552
+ if (!menuOpen)
553
+ return;
554
+ const close = (event) => { if (menuRef.current?.contains(event.target) !== true)
555
+ setMenuOpen(false); };
556
+ const escape = (event) => { if (event.key === "Escape")
557
+ setMenuOpen(false); };
558
+ document.addEventListener("pointerdown", close);
559
+ document.addEventListener("keydown", escape);
560
+ return () => { document.removeEventListener("pointerdown", close); document.removeEventListener("keydown", escape); };
561
+ }, [menuOpen]);
562
+ return h("section", { className: `tavern-card-branch${expanded ? " is-expanded" : ""}`, "data-card-id": card.id, "aria-busy": busy }, h("button", { type: "button", className: "tavern-card-row", title: "右键管理卡片", "aria-expanded": expanded, onClick: () => { setMenuOpen(false); toggleCard(card); }, onContextMenu: (event) => { event.preventDefault(); event.stopPropagation(); setMenuOpen(true); } }, card.coverUrl === "" ? h("span", { className: "tavern-card-thumb tavern-card-placeholder", "aria-label": "这张 JSON 卡没有随卡封面" }, card.title.slice(0, 1)) : h("img", { className: "tavern-card-thumb", src: card.coverUrl, alt: "" }), h("span", { className: "tavern-card-row-copy" }, h("strong", null, card.title), h("small", null, card.source), h("span", { className: "tavern-card-meta" }, h(StatusLine, { card }), h("small", null, `${card.sessions.length} 个会话`))), expanded ? h(IconChevronDownOutline14, { className: "tavern-chevron", size: 14 }) : h(IconChevronRightOutline14, { className: "tavern-chevron", size: 14 })), menuOpen ? h("div", { ref: menuRef, className: "tavern-card-context-menu", role: "menu", "aria-label": `${card.title} 卡片操作` }, h("button", { type: "button", role: "menuitem", disabled: busy || cardIndex <= 0, onClick: () => { setMenuOpen(false); void moveCardInLibrary(card, -1); } }, "上移"), h("button", { type: "button", role: "menuitem", disabled: busy || cardIndex < 0 || cardIndex >= snapshot.cards.length - 1, onClick: () => { setMenuOpen(false); void moveCardInLibrary(card, 1); } }, "下移"), h("span", { role: "separator" }), h("button", { type: "button", role: "menuitem", className: "is-danger", disabled: busy || card.revisionId === undefined, onClick: () => { setMenuOpen(false); void deleteCardFromLibrary(card); } }, "从卡库删除")) : null, expanded ? h("div", { className: "tavern-session-tree" }, h("button", { type: "button", className: "tavern-report-row", onClick: (event) => { event.stopPropagation(); inspectCompatibility(card); } }, h(IconSearchOutline16, { size: 15 }), h("span", null, "查看兼容报告")), h("button", { type: "button", className: "tavern-new-game-row", disabled: card.playability === "blocked" || snapshot.starting, onClick: (event) => { event.stopPropagation(); prepareNewGame(card); } }, h(IconPlusOutline16, { size: 16 }), h("span", null, card.playability === "blocked" ? "暂不能新建游戏" : snapshot.starting ? "正在准备…" : "新游戏")), ...card.sessions.map((save) => h(SessionRow, { card, save, key: save.id }))) : null);
563
+ }
564
+ function LibraryScreen() {
565
+ const snapshot = usePrototypeState();
566
+ const inputRef = React.useRef(null);
567
+ const query = snapshot.searchQuery.trim().toLocaleLowerCase("zh-CN");
568
+ const cards = query === "" ? snapshot.cards : snapshot.cards.filter((card) => card.title.toLocaleLowerCase("zh-CN").includes(query));
569
+ return h("div", { className: "tavern-panel-screen", "data-tavern-screen": "library" }, h("div", { className: "tavern-toolbar" }, h("label", { className: "tavern-search" }, h(IconSearchOutline16, { size: 16 }), h("input", { value: snapshot.searchQuery, placeholder: "搜索酒馆卡", onChange: (event) => patchState({ searchQuery: event.target.value }) })), h("input", { ref: inputRef, className: "tavern-file-input", type: "file", multiple: true, accept: ".png,.json,image/png,application/json", onChange: (event) => { const files = Array.from(event.target.files ?? []); if (files.length > 0)
570
+ void importFiles(files); event.target.value = ""; } }), h("button", { type: "button", className: "tavern-import-button", disabled: snapshot.importing, onClick: () => inputRef.current?.click() }, h(IconProjectAddOutline16, { size: 16 }), h("span", null, snapshot.importing ? "检查中…" : "导入"))), h("div", { className: "tavern-library-label" }, h("strong", null, "酒馆卡"), h("small", null, `${snapshot.cards.length} 张`)), snapshot.importResult ? h("div", { className: "tavern-inline-notice", role: "status" }, snapshot.importResult, h("small", null, "原件与运行时索引保存在当前隔离 DSH_HOME;不会执行卡内脚本")) : null, snapshot.startError ? h("div", { className: "tavern-start-error", role: "alert" }, snapshot.startError) : null, h("div", { className: "tavern-card-list" }, cards.length > 0 ? cards.map((card) => h(CardBranch, { card, key: card.id })) : h("p", { className: "tavern-empty" }, "没有找到酒馆卡")));
571
+ }
572
+ function SetupScreen() {
573
+ const snapshot = usePrototypeState();
574
+ const card = selectedCard(snapshot);
575
+ if (card === null)
576
+ return h(LibraryScreen);
577
+ const blocked = card.playability === "blocked";
578
+ const selectedOpening = card.openings.find((opening) => opening.id === snapshot.openingId) ?? card.openings[0];
579
+ return h("div", { className: "tavern-panel-screen tavern-setup-screen", "data-tavern-screen": "setup" }, h("button", { type: "button", className: "tavern-back-button", onClick: openLibrary }, h(IconChevronLeftOutline14, { size: 14 }), h("span", null, card.title)), h("div", { className: "tavern-card-summary" }, card.coverUrl === "" ? h("span", { className: "tavern-card-thumb tavern-card-placeholder", "aria-label": "这张 JSON 卡没有随卡封面" }, card.title.slice(0, 1)) : h("img", { className: "tavern-card-thumb", src: card.coverUrl, alt: "" }), h("div", null, h("h2", null, snapshot.compatibilityOpen ? "兼容报告" : "新游戏"), h("p", null, card.summary))), h("div", { className: `tavern-compatibility is-${card.playability}` }, h(StatusLine, { card }), h("p", null, card.statusDetail), h("button", { type: "button", onClick: () => patchState({ compatibilityOpen: !snapshot.compatibilityOpen }) }, snapshot.compatibilityOpen ? "收起兼容明细" : "查看兼容明细"), snapshot.compatibilityOpen ? h("div", { className: "tavern-technical-note" }, card.compatibilityRows === undefined ? "这是 #101 留下的界面样例;导入真实 PNG/JSON 后,这里会逐项显示静态证据。" : h("ul", null, ...card.compatibilityRows.map((row, index) => h("li", { key: `${row.capability}-${index}` }, h("strong", null, `${row.capability} · ${row.disposition}`), h("span", null, row.evidence))), card.sourceDigest === undefined ? null : h("p", null, `原件 SHA-256:${card.sourceDigest}`))) : null), snapshot.startError ? h("div", { className: "tavern-start-error", role: "alert" }, snapshot.startError) : null, blocked ? h("div", { className: "tavern-panel-footer is-blocked" }, h("p", null, "解决必需依赖后才能创建会话")) : h("div", { className: "tavern-form-section" }, h("label", null, h("span", null, "玩家称呼"), h("input", { value: snapshot.userName, maxLength: 80, onChange: (event) => patchState({ userName: event.target.value }) })), h("fieldset", null, h("legend", null, "选择开场"), ...card.openings.map((opening) => h("label", { className: `tavern-opening${opening.id === selectedOpening?.id ? " is-selected" : ""}`, key: opening.id }, h("input", { type: "radio", name: `opening-${card.id}`, checked: opening.id === selectedOpening?.id, onChange: () => patchState({ openingId: opening.id }) }), h("span", null, h("strong", null, opening.label), h("small", null, opening.preview))))), selectedOpening === undefined ? null : h("div", { className: "tavern-opening-preview" }, h("span", null, "将从这里开始"), h("p", null, selectedOpening.preview)), h("div", { className: "tavern-panel-footer" }, h("p", null, "创建独立 Session;进入第一轮后开场锁定。"), h("button", { type: "button", className: "tavern-start-button", disabled: snapshot.starting || snapshot.userName.trim() === "" || selectedOpening === undefined, onClick: () => void startPlay(card, snapshot.openingId) }, snapshot.starting ? "正在创建…" : "开始游戏"))));
580
+ }
581
+ function trajectoryRowKey(row) {
582
+ const key = row.getAttribute("data-trajectory-row-key");
583
+ if (key === null)
584
+ return undefined;
585
+ try {
586
+ return decodeURIComponent(key);
587
+ }
588
+ catch {
589
+ return undefined;
590
+ }
591
+ }
592
+ function trajectoryRow(operation) {
593
+ for (const row of Array.from(document.querySelectorAll("tr[data-trajectory-row-key]")))
594
+ if (trajectoryRowKey(row) === operation.trajectoryRowKey)
595
+ return row;
596
+ return null;
597
+ }
598
+ function TavernTrajectorySurfaceAdapter(props) {
599
+ const sessionId = props.sessionId;
600
+ const [audit, setAudit] = React.useState(null);
601
+ const sessionRevision = props.useSession((snapshot) => `${snapshot.nodes.length}:${snapshot.running}:${snapshot.openState}`);
602
+ React.useEffect(() => {
603
+ const controller = new AbortController();
604
+ void fetch(`/dsh-re3-rp/surface-audit?sessionId=${encodeURIComponent(sessionId)}&optional=1`, { cache: "no-store", signal: controller.signal })
605
+ .then(async (response) => response.status === 204 ? null : response.ok ? await response.json() : null)
606
+ .then((value) => { if (!controller.signal.aborted)
607
+ setAudit(value); })
608
+ .catch(() => { if (!controller.signal.aborted)
609
+ setAudit(null); });
610
+ return () => controller.abort();
611
+ }, [sessionId, sessionRevision]);
612
+ React.useEffect(() => {
613
+ if (audit === null)
614
+ return;
615
+ const assemblies = audit.operations.filter((operation) => operation.kind === "assembly");
616
+ let disposed = false;
617
+ const hideAssemblyRows = () => {
618
+ if (disposed)
619
+ return;
620
+ for (const operation of assemblies) {
621
+ const row = trajectoryRow(operation);
622
+ if (row === null)
623
+ continue;
624
+ row.dataset.tavernAssemblyRow = "true";
625
+ row.hidden = true;
626
+ }
627
+ };
628
+ const observer = new MutationObserver(hideAssemblyRows);
629
+ observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-selected", "data-trajectory-row-key"] });
630
+ hideAssemblyRows();
631
+ return () => {
632
+ disposed = true;
633
+ observer.disconnect();
634
+ document.querySelectorAll("tr[data-tavern-assembly-row]").forEach((row) => {
635
+ row.hidden = false;
636
+ row.removeAttribute("data-tavern-assembly-row");
637
+ });
638
+ };
639
+ }, [audit, sessionId]);
640
+ return null;
641
+ }
642
+ function estimateTokens(content) {
643
+ return Math.max(1, Math.ceil(Array.from(content).length / 2));
644
+ }
645
+ function compactNumber(value) {
646
+ return value >= 1000 ? `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}k` : value.toLocaleString();
647
+ }
648
+ function blockSourceLabel(blockId, kind, depth) {
649
+ if (kind === "chat")
650
+ return { label: "聊天历史", detail: "原始对话消息" };
651
+ if (kind === "example")
652
+ return { label: "示例对话", detail: "Example Messages" };
653
+ if (kind === "worldbook" && depth !== undefined)
654
+ return { label: "世界书 · 深度插入", detail: `@ Depth ${depth}` };
655
+ const labels = {
656
+ "main-prompt": ["主提示词", "Main Prompt"],
657
+ "world-info-before": ["世界书 · 角色定义前", "Before Char Defs"],
658
+ "persona-description": ["玩家设定", "Persona Description"],
659
+ "character-description": ["角色描述", "Character Description"],
660
+ "character-personality": ["角色性格", "Character Personality"],
661
+ scenario: ["场景", "Scenario"],
662
+ "world-info-after": ["世界书 · 角色定义后", "After Char Defs"],
663
+ "example-messages": ["示例对话", "Example Messages"],
664
+ "authors-note": ["作者注释", "Author's Note"],
665
+ "chat-history": ["聊天历史", "Chat History"],
666
+ "post-history-instructions": ["历史后指令", "Post-History Instructions"],
667
+ };
668
+ const match = blockId === undefined ? undefined : labels[blockId];
669
+ return match === undefined ? { label: kind === "worldbook" ? "世界书" : "预设模块", detail: blockId ?? kind } : { label: match[0], detail: match[1] };
670
+ }
671
+ function TavernContextView(props) {
672
+ const sessionId = props.sessionId;
673
+ const [audit, setAudit] = React.useState(null);
674
+ const [selectedSeq, setSelectedSeq] = React.useState(null);
675
+ const [detail, setDetail] = React.useState(null);
676
+ const [selectedRowId, setSelectedRowId] = React.useState(null);
677
+ const [inspectorTab, setInspectorTab] = React.useState("content");
678
+ const [query, setQuery] = React.useState("");
679
+ const [onlyChanges, setOnlyChanges] = React.useState(false);
680
+ const [copiedId, setCopiedId] = React.useState(null);
681
+ const [loading, setLoading] = React.useState(true);
682
+ const sessionRevision = props.useSession((snapshot) => `${snapshot.nodes.length}:${snapshot.running}:${snapshot.openState}`);
683
+ React.useEffect(() => {
684
+ const controller = new AbortController();
685
+ setLoading(true);
686
+ void fetch(`/dsh-re3-rp/surface-audit?sessionId=${encodeURIComponent(sessionId)}&optional=1`, { cache: "no-store", signal: controller.signal })
687
+ .then(async (response) => response.status === 204 ? null : response.ok ? await response.json() : null)
688
+ .then((value) => {
689
+ if (controller.signal.aborted)
690
+ return;
691
+ setAudit(value);
692
+ const requests = value?.operations.filter((operation) => operation.kind === "assembly" && operation.assembly?.stage === "request") ?? [];
693
+ const fallback = requests.at(-1) ?? value?.operations.filter((operation) => operation.kind === "assembly").at(-1);
694
+ setSelectedSeq((current) => current !== null && requests.some((operation) => operation.seq === current) ? current : fallback?.seq ?? null);
695
+ if (fallback === undefined)
696
+ setLoading(false);
697
+ })
698
+ .catch(() => { if (!controller.signal.aborted) {
699
+ setAudit(null);
700
+ setLoading(false);
701
+ } });
702
+ return () => controller.abort();
703
+ }, [sessionId, sessionRevision]);
704
+ React.useEffect(() => {
705
+ if (selectedSeq === null) {
706
+ setDetail(null);
707
+ setLoading(false);
708
+ return;
709
+ }
710
+ const controller = new AbortController();
711
+ setLoading(true);
712
+ void fetch(`/dsh-re3-rp/surface-event?sessionId=${encodeURIComponent(sessionId)}&seq=${selectedSeq}`, { cache: "no-store", signal: controller.signal })
713
+ .then(async (response) => response.ok ? await response.json() : null)
714
+ .then((value) => { if (!controller.signal.aborted) {
715
+ setDetail(value);
716
+ setSelectedRowId(null);
717
+ setInspectorTab("content");
718
+ setLoading(false);
719
+ } })
720
+ .catch(() => { if (!controller.signal.aborted) {
721
+ setDetail(null);
722
+ setLoading(false);
723
+ } });
724
+ return () => controller.abort();
725
+ }, [sessionId, selectedSeq]);
726
+ const requests = audit?.operations.filter((operation) => operation.kind === "assembly" && operation.assembly?.stage === "request") ?? [];
727
+ const selectedIndex = Math.max(0, requests.findIndex((operation) => operation.seq === selectedSeq));
728
+ const assembly = detail?.assembly ?? null;
729
+ const addedIds = new Set(assembly?.summary.addedEntryIds ?? []);
730
+ const rows = (assembly?.messages ?? []).map((message, index) => {
731
+ const entryIds = message.source.entryIds ?? [];
732
+ const entryIdSet = new Set(entryIds);
733
+ const entryLabels = assembly?.activation.filter((item) => entryIdSet.has(item.id)).map((item) => item.label) ?? [];
734
+ const basePresentation = blockSourceLabel(message.source.blockId, message.source.kind, message.source.depth);
735
+ const presentation = message.source.kind !== "worldbook" ? basePresentation : {
736
+ label: entryLabels.length === 1 ? `世界书 · ${entryLabels[0]}` : `世界书 · ${entryLabels.length} 条激活`,
737
+ detail: [basePresentation.detail, ...entryLabels.slice(0, 3)].join(" · ") + (entryLabels.length > 3 ? ` 等 ${entryLabels.length} 条` : ""),
738
+ };
739
+ return {
740
+ id: `${index}:${message.source.blockId ?? message.source.kind}:${message.source.depth ?? ""}`,
741
+ role: message.role,
742
+ content: message.content,
743
+ sourceKind: message.source.kind,
744
+ sourceLabel: presentation.label,
745
+ sourceDetail: presentation.detail,
746
+ entryIds,
747
+ changed: entryIds.some((id) => addedIds.has(id)),
748
+ depth: message.source.depth,
749
+ };
750
+ });
751
+ const normalizedQuery = query.trim().toLocaleLowerCase();
752
+ const visibleRows = rows.filter((row) => (!onlyChanges || row.changed) && (normalizedQuery.length === 0 || `${row.sourceLabel}\n${row.sourceDetail}\n${row.role}\n${row.content}`.toLocaleLowerCase().includes(normalizedQuery)));
753
+ const activeOperation = requests[selectedIndex];
754
+ const selectedRow = rows.find((row) => row.id === selectedRowId) ?? null;
755
+ const selectRequest = (index) => {
756
+ const operation = requests[index];
757
+ if (operation !== undefined)
758
+ setSelectedSeq(operation.seq);
759
+ };
760
+ const copyRow = async (row) => {
761
+ try {
762
+ await navigator.clipboard.writeText(row.content);
763
+ setCopiedId(row.id);
764
+ window.setTimeout(() => setCopiedId((current) => current === row.id ? null : current), 1200);
765
+ }
766
+ catch {
767
+ setCopiedId(null);
768
+ }
769
+ };
770
+ const activationFor = (row) => {
771
+ const ids = new Set(row.entryIds);
772
+ return (assembly?.activation ?? []).filter((item) => ids.has(item.id));
773
+ };
774
+ const reasonLabel = (reason) => ({ constant: "固定激活", keyword: "关键词命中", regex: "正则命中", "primary-match": "关键词命中", "secondary-match": "次关键词命中", recursive: "递归激活", "recursive-keyword": "递归命中", "probability-miss": "概率未通过", disabled: "已禁用" }[reason] ?? reason);
775
+ if (loading && assembly === null)
776
+ return h("section", { className: "tavern-context-view is-loading", "data-tavern-context-view": "true" }, h("div", { className: "tavern-context-empty" }, "正在读取酒馆装配…"));
777
+ if (assembly === null)
778
+ return h("section", { className: "tavern-context-view", "data-tavern-context-view": "true" }, h("div", { className: "tavern-context-empty" }, "发送第一句话后,这里会显示本轮酒馆上下文的装配顺序。"));
779
+ return h("section", { className: "tavern-context-view", "data-tavern-context-view": "true", "aria-label": "酒馆上下文装配" }, h("header", { className: "tavern-context-toolbar" }, h("div", { className: "tavern-context-request-nav" }, h("button", { type: "button", title: "上一次请求", "aria-label": "上一次请求", disabled: selectedIndex <= 0, onClick: () => selectRequest(selectedIndex - 1) }, h(IconChevronLeftOutline14, { size: 14 })), h("div", { className: "tavern-context-request-title" }, h("strong", null, requests.length === 0 ? "已准备" : `请求 ${selectedIndex + 1} / ${requests.length}`), h("span", null, activeOperation?.time === null || activeOperation?.time === undefined ? "酒馆语义装配" : new Date(activeOperation.time).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }))), h("button", { type: "button", title: "下一次请求", "aria-label": "下一次请求", disabled: selectedIndex >= requests.length - 1, onClick: () => selectRequest(selectedIndex + 1) }, h(IconChevronRightOutline14, { size: 14 }))), h("div", { className: "tavern-context-toolbar-meta" }, h("span", { className: "tavern-context-model", title: detail?.runtime?.provider ?? "" }, detail?.runtime?.model ?? "当前模型")), h("label", { className: "tavern-context-search" }, h(IconSearchOutline16, { size: 16 }), h("input", { value: query, onChange: (event) => setQuery(event.target.value), placeholder: "搜索本轮上下文", "aria-label": "搜索本轮上下文" })), h("label", { className: "tavern-context-change-filter" }, h("input", { type: "checkbox", checked: onlyChanges, onChange: (event) => setOnlyChanges(event.target.checked) }), h("span", { className: "tavern-context-check" }, onlyChanges ? h(IconCheckOutline14, { size: 12 }) : null), h("span", null, "只看本轮变化"))), h("div", { className: `tavern-context-workbench${selectedRow === null ? "" : " has-inspector"}` }, h("div", { className: "tavern-context-table", role: "table", "aria-label": "本轮酒馆语义消息栈" }, h("div", { className: "tavern-context-table-head", role: "row" }, h("span", { role: "columnheader" }, "顺序"), h("span", { role: "columnheader" }, "角色"), h("span", { role: "columnheader" }, "来源 / 类型"), h("span", { role: "columnheader" }, "Tokens"), h("span", { role: "columnheader" }, "预览"), h("span", { role: "columnheader" }, "操作")), ...(visibleRows.length === 0 ? [h("div", { className: "tavern-context-no-results", key: "empty" }, onlyChanges ? "这一轮没有变化的世界书条目。" : "没有找到匹配的上下文。")] : visibleRows.map((row) => {
780
+ const index = rows.indexOf(row);
781
+ const selected = selectedRowId === row.id;
782
+ return h("article", { className: `tavern-context-row${selected ? " is-selected" : ""}${row.sourceKind === "worldbook" ? " is-worldbook" : ""}${row.changed ? " is-changed" : ""}`, role: "row", key: row.id }, h("button", { type: "button", className: "tavern-context-row-main", "aria-selected": selected, onClick: () => { setSelectedRowId(row.id); setInspectorTab("content"); } }, h("span", { className: "tavern-context-order", role: "cell" }, String(index + 1).padStart(2, "0")), h("span", { className: `tavern-context-role is-${row.role}`, role: "cell" }, row.role.toUpperCase()), h("span", { className: "tavern-context-source", role: "cell" }, h("strong", null, row.sourceLabel), h("small", null, row.sourceDetail)), h("span", { className: "tavern-context-tokens", role: "cell" }, `≈ ${compactNumber(estimateTokens(row.content))}`), h("span", { className: "tavern-context-preview", role: "cell" }, row.content.replace(/\s+/gu, " ").trim()), h("span", { className: "tavern-context-expand", role: "cell", "aria-hidden": "true" }, h(IconChevronRightOutline14, { size: 14 }))), h("button", { type: "button", className: "tavern-context-row-copy", onClick: () => void copyRow(row), title: "复制这一条", "aria-label": `复制第 ${index + 1} 条上下文` }, copiedId === row.id ? h(IconCheckOutline14, { size: 14 }) : h(IconCopyOutline16, { size: 15 })));
783
+ }))), selectedRow === null ? null : h("aside", { className: "tavern-context-inspector", "aria-label": `${selectedRow.sourceLabel}详情` }, h("header", { className: "tavern-context-inspector-header" }, h("div", null, h("span", { className: `tavern-context-role is-${selectedRow.role}` }, selectedRow.role.toUpperCase()), h("strong", null, selectedRow.sourceLabel), h("small", null, selectedRow.sourceDetail)), h("button", { type: "button", onClick: () => setSelectedRowId(null), title: "关闭详情", "aria-label": "关闭详情" }, h(IconCloseOutline16, { size: 14 }))), h("div", { className: "tavern-context-inspector-tabs", role: "tablist", "aria-label": "提示词详情" }, h("button", { type: "button", role: "tab", "aria-selected": inspectorTab === "content", onClick: () => setInspectorTab("content") }, "正文"), h("button", { type: "button", role: "tab", "aria-selected": inspectorTab === "evidence", onClick: () => setInspectorTab("evidence") }, "激活依据")), inspectorTab === "content" ? h("div", { className: "tavern-context-inspector-content", role: "tabpanel" }, h("button", { type: "button", className: "tavern-context-inspector-copy", onClick: () => void copyRow(selectedRow) }, copiedId === selectedRow.id ? h(IconCheckOutline14, { size: 14 }) : h(IconCopyOutline16, { size: 15 }), copiedId === selectedRow.id ? "已复制" : "复制"), h("pre", null, selectedRow.content)) : h("div", { className: "tavern-context-inspector-evidence", role: "tabpanel" }, (() => {
784
+ const activations = activationFor(selectedRow);
785
+ return activations.length === 0 ? h("p", null, selectedRow.sourceKind === "chat" ? "这是一条原始对话消息,按聊天历史顺序进入。" : `这部分由当前预设固定放入消息栈:${selectedRow.sourceDetail}。`) :
786
+ h("div", { className: "tavern-context-activation-cards" }, ...activations.map((item) => h("article", { key: item.id }, h("div", null, h("strong", null, item.label), h("span", null, reasonLabel(item.reason))), h("p", null, [item.position === "at_depth" ? `Depth ${item.depth}` : item.position.replaceAll("_", " "), `Order ${item.order}`, ...item.matchedPrimaryKeys.map((key) => `命中“${key}”`), ...item.recursiveParents.map((id) => `由 #${id} 递归激活`)].join(" · ")))));
787
+ })()))));
788
+ }
789
+ const capabilityPositionLabel = (position) => ({
790
+ before_char: "角色定义前", after_char: "角色定义后", an_top: "作者注释前", an_bottom: "作者注释后",
791
+ at_depth: "指定深度", before_examples: "示例对话前", after_examples: "示例对话后", outlet: "命名出口",
792
+ }[position] ?? position);
793
+ const capabilityEntryMode = (entry) => entry.constant ? "常驻" : entry.useRegex ? "正则关键词" : "关键词";
794
+ function openNativeSettings() {
795
+ const candidates = Array.from(document.querySelectorAll('button,a,[role="button"]'));
796
+ const target = candidates.find((candidate) => {
797
+ if (candidate.closest(".tavern-capability-panel") !== null)
798
+ return false;
799
+ const label = `${candidate.getAttribute("aria-label") ?? ""} ${candidate.getAttribute("title") ?? ""} ${candidate.textContent ?? ""}`.trim();
800
+ return label === "设置" || label.startsWith("设置 ") || label.endsWith(" 设置");
801
+ });
802
+ target?.click();
803
+ return target !== undefined;
804
+ }
805
+ function CapabilityTabs({ value, onChange, items, label }) {
806
+ return h("div", { className: "tavern-capability-tabs", role: "tablist", "aria-label": label }, ...items.map((item) => h("button", { type: "button", role: "tab", "aria-selected": value === item.id, onClick: () => onChange(item.id), key: item.id }, item.label)));
807
+ }
808
+ function CapabilityPanelHeader({ title, meta }) {
809
+ return h("header", { className: "tavern-capability-panel-header" }, h("div", null, h("strong", null, title), h("span", null, meta)));
810
+ }
811
+ function WorldbookCapabilityPanel({ snapshot }) {
812
+ const [mode, setMode] = React.useState(snapshot.context === null ? "all" : "active");
813
+ const [query, setQuery] = React.useState("");
814
+ const [entryId, setEntryId] = React.useState(snapshot.card.worldbook[0]?.id ?? "");
815
+ const [visibleLimit, setVisibleLimit] = React.useState(120);
816
+ const activations = snapshot.context?.assembly?.activation ?? [];
817
+ const activationById = new Map(activations.map((item) => [String(item.id), item]));
818
+ const activeCount = activations.filter((item) => item.activated === true).length;
819
+ const normalizedQuery = query.trim().toLocaleLowerCase("zh-CN");
820
+ const visible = snapshot.card.worldbook.filter((entry) => {
821
+ if (mode === "active" && activationById.get(entry.id)?.activated !== true)
822
+ return false;
823
+ return normalizedQuery === "" || `${entry.comment} ${entry.content} ${entry.keys.join(" ")}`.toLocaleLowerCase("zh-CN").includes(normalizedQuery);
824
+ });
825
+ React.useEffect(() => setVisibleLimit(120), [snapshot.card.revisionId, mode, normalizedQuery]);
826
+ const rendered = visible.slice(0, visibleLimit);
827
+ const selected = rendered.find((entry) => entry.id === entryId) ?? rendered[0];
828
+ const activation = selected === undefined ? undefined : activationById.get(selected.id);
829
+ return h(React.Fragment, null, h(CapabilityPanelHeader, { title: "世界书", meta: `${snapshot.card.title} · ${snapshot.card.worldbook.length} 条资料` }), h("section", { className: "tavern-worldbook-overview", "aria-label": "世界书概览" }, h("div", null, h("strong", null, String(snapshot.card.worldbook.length)), h("span", null, "全部条目")), h("div", null, h("strong", null, String(activeCount)), h("span", null, "本轮命中")), h("div", null, h("strong", null, String(visible.length)), h("span", null, query.trim() === "" ? "当前范围" : "搜索结果"))), h("div", { className: "tavern-worldbook-toolbar" }, h(CapabilityTabs, { value: mode, onChange: setMode, label: "世界书范围", items: [{ id: "active", label: "本轮命中" }, { id: "all", label: "全部条目" }] }), h("label", { className: "tavern-capability-search" }, h(IconSearchOutline16, { size: 16 }), h("input", { value: query, placeholder: "搜索标题、正文或关键词", onChange: (event) => setQuery(event.target.value) }))), h("div", { className: "tavern-worldbook-workbench" }, h("section", { className: "tavern-worldbook-library", "aria-label": "世界书条目列表" }, h("header", null, h("strong", null, mode === "active" ? "本轮资料" : "资料目录"), h("span", null, `${visible.length} 条`)), h("div", { className: "tavern-capability-list", role: "listbox", "aria-label": mode === "active" ? "本轮命中的世界书" : "全部世界书" }, ...(visible.length === 0 ? [h("p", { className: "tavern-capability-empty", key: "empty" }, snapshot.card.worldbook.length === 0 ? "这张卡没有世界书条目。" : mode === "active" ? "这一轮还没有命中资料。发送一句话后再回来看看。" : "没有找到匹配的资料。")] : rendered.map((entry) => {
830
+ const preview = entry.content.trim().replace(/\s+/gu, " ");
831
+ return h("button", { type: "button", role: "option", "aria-selected": selected?.id === entry.id, onClick: () => setEntryId(entry.id), key: entry.id }, h("span", { className: "tavern-worldbook-entry-title" }, entry.comment || "未命名条目"), h("p", null, preview || "(空内容)"), h("footer", null, h("em", { className: entry.enabled ? "is-enabled" : "is-disabled" }, entry.enabled ? capabilityEntryMode(entry) : "已停用"), h("small", null, capabilityPositionLabel(entry.position))));
832
+ }))), rendered.length < visible.length ? h("footer", { className: "tavern-worldbook-load-more" }, h("span", null, `已显示 ${rendered.length} / ${visible.length}`), h("button", { type: "button", onClick: () => setVisibleLimit((value) => Math.min(value + 120, visible.length)) }, "继续加载")) : null), selected === undefined ? null : h("article", { className: "tavern-capability-inspector" }, h("header", null, h("div", null, h("small", null, `世界书条目 · #${selected.id}`), h("h3", null, selected.comment || "未命名条目")), h("span", { className: selected.enabled ? "is-enabled" : "is-disabled" }, selected.enabled ? "已启用" : "已停用")), h("section", { className: "tavern-worldbook-content" }, h("h4", null, "正文"), h("div", { className: "tavern-capability-prose" }, selected.content.trim() || "(空内容)")), h("section", { className: "tavern-worldbook-keywords" }, h("h4", null, "触发关键词"), h("div", null, ...(selected.keys.length > 0 ? selected.keys.map((key) => h("span", { key }, key)) : [h("span", { className: "is-muted", key: "empty" }, selected.constant ? "常驻条目,无需关键词" : "没有设置关键词")]))), h("section", { className: "tavern-worldbook-facts" }, h("div", null, h("span", null, "插入位置"), h("strong", null, `${capabilityPositionLabel(selected.position)} · ${selected.role}${selected.position === "at_depth" ? ` · Depth ${selected.depth}` : ""}`)), h("div", null, h("span", null, "执行顺序"), h("strong", null, String(selected.order))), h("div", null, h("span", null, "本轮状态"), h("strong", null, activation?.activated === true ? `${activation.reason} · pass ${activation.pass}` : snapshot.context === null ? "等待真实请求" : "本轮未命中")), h("div", null, h("span", null, "作用范围"), h("strong", null, selected.sourceEnabled === selected.enabled || selected.sourceEnabled === undefined ? "卡片原件" : "当前 Session 覆盖"))))));
833
+ }
834
+ function PresetCapabilityPanel({ snapshot }) {
835
+ const sessionId = snapshot.session?.id ?? "";
836
+ const importRef = React.useRef(null);
837
+ const [remote, setRemote] = React.useState(null);
838
+ const [draft, setDraft] = React.useState(null);
839
+ const [selectedPromptId, setSelectedPromptId] = React.useState("");
840
+ const [promptQuery, setPromptQuery] = React.useState("");
841
+ const [unusedPromptQuery, setUnusedPromptQuery] = React.useState("");
842
+ const [addPromptOpen, setAddPromptOpen] = React.useState(false);
843
+ const [draggedPromptId, setDraggedPromptId] = React.useState("");
844
+ const [dropTarget, setDropTarget] = React.useState(null);
845
+ const [renaming, setRenaming] = React.useState(false);
846
+ const [renameName, setRenameName] = React.useState("");
847
+ const [busy, setBusy] = React.useState(false);
848
+ const [dirty, setDirty] = React.useState(false);
849
+ const [notice, setNotice] = React.useState("");
850
+ const [error, setError] = React.useState("");
851
+ const adopt = (value, preferredId = "") => {
852
+ const presetId = preferredId || value.sessionPresetId || value.activePresetId;
853
+ const selected = value.presets.find((preset) => preset.id === presetId) ?? value.presets[0] ?? null;
854
+ setRemote(value);
855
+ setDraft(selected === null ? null : structuredClone(selected));
856
+ const selectedOrderIds = new Set(selected?.promptOrder.map((item) => item.identifier) ?? []);
857
+ setSelectedPromptId((current) => selectedOrderIds.has(current) ? current : selected?.promptOrder[0]?.identifier ?? "");
858
+ setUnusedPromptQuery("");
859
+ setAddPromptOpen(false);
860
+ setDraggedPromptId("");
861
+ setDropTarget(null);
862
+ setRenaming(false);
863
+ setDirty(false);
864
+ };
865
+ React.useEffect(() => {
866
+ const controller = new AbortController();
867
+ setBusy(true);
868
+ setError("");
869
+ void fetch(`/dsh-re3-rp/presets?sessionId=${encodeURIComponent(sessionId)}`, { cache: "no-store", signal: controller.signal })
870
+ .then(async (response) => {
871
+ const value = await response.json();
872
+ if (!response.ok || value.ok !== true)
873
+ throw new Error(value.error || "无法读取预设");
874
+ if (!controller.signal.aborted)
875
+ adopt(value);
876
+ })
877
+ .catch((reason) => { if (!controller.signal.aborted)
878
+ setError(reason instanceof Error ? reason.message : "无法读取预设"); })
879
+ .finally(() => { if (!controller.signal.aborted)
880
+ setBusy(false); });
881
+ return () => controller.abort();
882
+ }, [sessionId]);
883
+ const post = async (payload) => {
884
+ setBusy(true);
885
+ setError("");
886
+ setNotice("");
887
+ try {
888
+ const response = await fetch("/dsh-re3-rp/presets", { method: "POST", headers: { "content-type": "application/json; charset=utf-8" }, body: JSON.stringify(payload) });
889
+ const value = await response.json();
890
+ if (!response.ok || value.ok !== true)
891
+ throw new Error(value.error || "预设操作失败");
892
+ return value;
893
+ }
894
+ finally {
895
+ setBusy(false);
896
+ }
897
+ };
898
+ const updateDraft = (mutate) => {
899
+ setDraft((current) => {
900
+ if (current === null)
901
+ return current;
902
+ const next = structuredClone(current);
903
+ mutate(next);
904
+ return next;
905
+ });
906
+ setDirty(true);
907
+ setNotice("");
908
+ };
909
+ const selectPreset = async (presetId) => {
910
+ try {
911
+ const value = await post({ action: sessionId.length > 0 ? "bind" : "activate", presetId, sessionId });
912
+ adopt(value, presetId);
913
+ setNotice(sessionId.length > 0 ? "已切换;下一次回复使用这个预设。" : "已设为新 Session 的默认预设。");
914
+ }
915
+ catch (reason) {
916
+ setError(reason instanceof Error ? reason.message : "无法切换预设");
917
+ }
918
+ };
919
+ const save = async () => {
920
+ if (draft === null)
921
+ return;
922
+ try {
923
+ const value = await post({ action: "save", sessionId, preset: draft });
924
+ adopt(value, value.savedPresetId || draft.id);
925
+ setNotice(draft.source === "builtin" ? "已另存为可编辑副本,并绑定当前 Session。" : "预设已保存;下一次请求使用新 revision。");
926
+ }
927
+ catch (reason) {
928
+ setError(reason instanceof Error ? reason.message : "无法保存预设");
929
+ }
930
+ };
931
+ const createPreset = async () => {
932
+ if (draft === null)
933
+ return;
934
+ try {
935
+ const value = await post({ action: "create", sessionId, basePresetId: draft.id, name: `${draft.name} 副本` });
936
+ adopt(value, value.createdPresetId);
937
+ setNotice("已创建可编辑副本并绑定当前 Session。");
938
+ }
939
+ catch (reason) {
940
+ setError(reason instanceof Error ? reason.message : "无法创建预设");
941
+ }
942
+ };
943
+ const importPreset = async (file) => {
944
+ try {
945
+ const parsed = JSON.parse(await file.text());
946
+ const value = await post({ action: "import", sessionId, name: file.name, preset: parsed });
947
+ adopt(value, value.importedPresetId);
948
+ setNotice(`已真实导入 ${file.name};未识别字段会原样保留到导出。`);
949
+ }
950
+ catch (reason) {
951
+ setError(reason instanceof Error ? reason.message : "预设 JSON 无法导入");
952
+ }
953
+ finally {
954
+ if (importRef.current !== null)
955
+ importRef.current.value = "";
956
+ }
957
+ };
958
+ const deletePreset = async () => {
959
+ if (draft === null || draft.source === "builtin" || !window.confirm(`删除预设“${draft.name}”?`))
960
+ return;
961
+ try {
962
+ const value = await post({ action: "delete", sessionId, presetId: draft.id });
963
+ adopt(value);
964
+ setNotice("预设已删除。");
965
+ }
966
+ catch (reason) {
967
+ setError(reason instanceof Error ? reason.message : "无法删除预设");
968
+ }
969
+ };
970
+ const movePrompt = (identifier, delta) => updateDraft((value) => {
971
+ const index = value.promptOrder.findIndex((item) => item.identifier === identifier);
972
+ const target = Math.max(0, Math.min(value.promptOrder.length - 1, index + delta));
973
+ if (index < 0 || target === index)
974
+ return;
975
+ const [item] = value.promptOrder.splice(index, 1);
976
+ value.promptOrder.splice(target, 0, item);
977
+ });
978
+ const reorderPrompt = (sourceIdentifier, targetIdentifier, position) => {
979
+ if (sourceIdentifier === targetIdentifier)
980
+ return;
981
+ updateDraft((value) => {
982
+ const sourceIndex = value.promptOrder.findIndex((item) => item.identifier === sourceIdentifier);
983
+ if (sourceIndex < 0)
984
+ return;
985
+ const [item] = value.promptOrder.splice(sourceIndex, 1);
986
+ const targetIndex = value.promptOrder.findIndex((candidate) => candidate.identifier === targetIdentifier);
987
+ if (item === undefined || targetIndex < 0)
988
+ return;
989
+ value.promptOrder.splice(targetIndex + (position === "after" ? 1 : 0), 0, item);
990
+ });
991
+ setSelectedPromptId(sourceIdentifier);
992
+ };
993
+ const addPrompt = () => {
994
+ const identifier = crypto.randomUUID();
995
+ updateDraft((value) => {
996
+ value.prompts.push({ identifier, name: "新提示词", role: "system", content: "", systemPrompt: false, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100, extra: {} });
997
+ value.promptOrder.push({ identifier, enabled: true });
998
+ });
999
+ setSelectedPromptId(identifier);
1000
+ setAddPromptOpen(false);
1001
+ };
1002
+ const insertUnusedPrompt = (identifier) => {
1003
+ if (identifier.length === 0)
1004
+ return;
1005
+ updateDraft((value) => { if (!value.promptOrder.some((item) => item.identifier === identifier))
1006
+ value.promptOrder.push({ identifier, enabled: true }); });
1007
+ setSelectedPromptId(identifier);
1008
+ setAddPromptOpen(false);
1009
+ setUnusedPromptQuery("");
1010
+ };
1011
+ if (remote === null || draft === null)
1012
+ return h(React.Fragment, null, h(CapabilityPanelHeader, { title: "对话补全预设", meta: "SillyTavern Chat Completion 兼容运行时" }), h("div", { className: `tavern-capability-loading${error ? " is-error" : ""}` }, error || "正在读取真实预设…"));
1013
+ const definitions = new Map(draft.prompts.map((prompt) => [prompt.identifier, prompt]));
1014
+ const ordered = draft.promptOrder.map((item, index) => ({ item, index, prompt: definitions.get(item.identifier) })).filter((row) => row.prompt !== undefined);
1015
+ const orderedIds = new Set(draft.promptOrder.map((item) => item.identifier));
1016
+ const library = draft.prompts.filter((prompt) => !orderedIds.has(prompt.identifier));
1017
+ const normalizedPromptQuery = promptQuery.trim().toLocaleLowerCase();
1018
+ const filteredOrdered = normalizedPromptQuery.length === 0 ? ordered : ordered.filter(({ prompt }) => `${prompt.name} ${prompt.role} ${prompt.marker ?? ""}`.toLocaleLowerCase().includes(normalizedPromptQuery));
1019
+ const normalizedUnusedPromptQuery = unusedPromptQuery.trim().toLocaleLowerCase();
1020
+ const filteredUnusedPrompts = normalizedUnusedPromptQuery.length === 0 ? library : library.filter((prompt) => `${prompt.name} ${prompt.role} ${prompt.marker ?? ""}`.toLocaleLowerCase().includes(normalizedUnusedPromptQuery));
1021
+ const selectedPrompt = definitions.get(selectedPromptId) ?? ordered[0]?.prompt ?? null;
1022
+ const selectedOrder = draft.promptOrder.find((item) => item.identifier === selectedPrompt?.identifier);
1023
+ const selectedOrderedIndex = ordered.findIndex(({ prompt }) => prompt.identifier === selectedPrompt?.identifier);
1024
+ const pinnedIds = new Set(["main", "worldInfoBefore", "personaDescription", "charDescription", "charPersonality", "scenario", "enhanceDefinitions", "nsfw", "worldInfoAfter", "dialogueExamples", "chatHistory", "jailbreak"]);
1025
+ const contentEditable = selectedPrompt !== null && (selectedPrompt.marker === undefined || selectedPrompt.marker === "main-prompt" || selectedPrompt.marker === "post-history-instructions");
1026
+ const slider = (key, label, minimum, maximum, step) => {
1027
+ const value = Number(draft.settings[key]);
1028
+ return h("label", { className: "tavern-preset-slider" }, h("span", null, h("strong", null, label)), h("div", null, h("input", { type: "range", min: minimum, max: maximum, step, value, onChange: (event) => updateDraft((next) => { next.settings[key] = Number(event.target.value); }) }), h("input", { type: "number", min: minimum, max: maximum, step, value, onChange: (event) => updateDraft((next) => { next.settings[key] = Number(event.target.value); }) })));
1029
+ };
1030
+ return h(React.Fragment, null, h("div", { className: "tavern-preset-workbench", "data-tavern-preset-runtime": "true" }, h("header", { className: "tavern-preset-toolbar" }, h("div", { className: "tavern-preset-title-block" }, h("h2", null, "对话补全预设"), h("span", null, `${draft.source === "builtin" ? "官方内置" : draft.source === "imported" ? "酒馆 JSON 导入" : "DSH 创建"} · ${sessionId.length > 0 ? "当前 Session" : "新 Session 默认"}`)), h("div", { className: "tavern-preset-select-wrap" }, h("select", { value: draft.id, disabled: busy || dirty, "aria-label": "当前预设", title: dirty ? "请先保存或重新选择以放弃修改" : "切换预设", onChange: (event) => void selectPreset(event.target.value) }, ...remote.presets.map((preset) => h("option", { value: preset.id, key: preset.id }, preset.name))), h("span", { className: `tavern-preset-revision${dirty ? " is-dirty" : ""}` }, dirty ? "有未保存修改" : `已保存 revision ${draft.revision}`)), h("div", { className: "tavern-preset-actions" }, h("button", { type: "button", className: "is-primary", disabled: busy || !dirty, onClick: () => void save(), title: draft.source === "builtin" ? "另存为新预设" : "保存预设" }, h(IconCheckOutline14, { size: 14 }), draft.source === "builtin" ? "另存为" : "保存"), h("input", { ref: importRef, type: "file", accept: ".json,application/json", hidden: true, onChange: (event) => { const file = event.target.files?.[0]; if (file !== undefined)
1031
+ void importPreset(file); } }))), h("nav", { className: "tavern-preset-secondary-actions", "aria-label": "预设操作" }, h("button", { type: "button", disabled: busy || draft.source === "builtin", onClick: () => { setRenameName(draft.name); setRenaming(true); } }, h(IconListPenOutline16, { size: 14 }), "重命名"), h("button", { type: "button", disabled: busy, onClick: () => void createPreset() }, h(IconCopyOutline16, { size: 14 }), "副本"), h("button", { type: "button", disabled: busy, onClick: () => importRef.current?.click() }, h(IconProjectAddOutline16, { size: 14 }), "导入"), h("a", { href: `/dsh-re3-rp/presets?download=${encodeURIComponent(draft.id)}`, title: "导出 SillyTavern 兼容 JSON" }, h(IconFolderOpenOutline16, { size: 14 }), "导出"), h("button", { type: "button", className: "is-danger", disabled: busy || draft.source === "builtin", onClick: () => void deletePreset() }, h(IconCloseOutline16, { size: 14 }), "删除")), renaming ? h("form", { className: "tavern-preset-rename", onSubmit: (event) => { event.preventDefault(); updateDraft((value) => { value.name = renameName.trim() || value.name; }); setRenaming(false); } }, h("label", null, "重命名预设", h("input", { value: renameName, autoFocus: true, onChange: (event) => setRenameName(event.target.value) })), h("button", { type: "submit" }, "完成"), h("button", { type: "button", onClick: () => setRenaming(false) }, "取消")) : null, notice ? h("p", { className: "tavern-preset-notice", role: "status" }, notice) : null, error ? h("p", { className: "tavern-preset-error", role: "alert" }, error) : null, h("details", { className: "tavern-preset-advanced" }, h("summary", null, h("strong", null, "高级设置")), h("div", { className: "tavern-preset-advanced-body" }, h("p", { className: "tavern-preset-runtime-note" }, "DeepSeek 默认:温度 1 · Top P 1。当前 DSH 仅保存 Top P 以兼容 SillyTavern JSON;实际请求由当前提供方支持的参数决定。"), h("div", { className: "tavern-preset-samplers" }, slider("temperature", "温度", 0, 2, 0.01), slider("topP", "Top P", 0, 1, 0.01)), h("button", { type: "button", className: "tavern-preset-reset-sampling", onClick: () => updateDraft((value) => { value.settings.temperature = 1; value.settings.topP = 1; }) }, "恢复 DeepSeek 默认值"))), h("section", { className: "tavern-preset-prompts" }, h("nav", { className: "tavern-preset-prompt-nav", "aria-label": "提示词" }, h("header", null, h("div", null, h("h3", null, "提示词顺序"), h("p", null, `${ordered.filter(({ item }) => item.enabled).length} 个启用 · 共 ${ordered.length} 个`)), h("button", { type: "button", className: "tavern-preset-add-trigger", onClick: () => setAddPromptOpen((open) => !open), title: "添加提示词", "aria-label": "添加提示词", "aria-expanded": addPromptOpen }, h(IconPlusOutline16, { size: 14 }), "添加")), addPromptOpen ? h("section", { className: "tavern-preset-add-popover", "aria-label": "添加提示词" }, h("header", null, h("div", null, h("strong", null, "添加提示词"), h("span", null, `${library.length} 条尚未加入顺序`)), h("button", { type: "button", onClick: () => setAddPromptOpen(false), title: "关闭", "aria-label": "关闭添加提示词" }, h(IconCloseOutline16, { size: 14 }))), h("button", { type: "button", className: "tavern-preset-create-prompt", onClick: addPrompt }, h(IconPlusOutline16, { size: 14 }), h("span", null, h("strong", null, "新建提示词"), h("small", null, "创建空白提示词并加入顺序"))), library.length > 0 ? h(React.Fragment, null, h("label", { className: "tavern-preset-unused-search" }, h(IconSearchOutline16, { size: 13 }), h("input", { type: "search", value: unusedPromptQuery, placeholder: "搜索尚未加入的提示词", autoFocus: true, onChange: (event) => setUnusedPromptQuery(event.target.value) })), h("div", { className: "tavern-preset-unused-list" }, ...(filteredUnusedPrompts.length === 0
1032
+ ? [h("p", { className: "tavern-preset-list-empty", key: "empty" }, "没有匹配的提示词")]
1033
+ : filteredUnusedPrompts.map((prompt) => h("button", { type: "button", key: prompt.identifier, onClick: () => insertUnusedPrompt(prompt.identifier) }, h("span", null, h("strong", null, prompt.name || "未命名提示词"), h("small", null, prompt.role)), h("em", null, "加入"))))))
1034
+ : h("p", { className: "tavern-preset-list-empty" }, "所有提示词都已加入顺序")) : null, h("label", { className: "tavern-preset-search" }, h(IconSearchOutline16, { size: 14 }), h("input", { type: "search", value: promptQuery, placeholder: "搜索提示词", onChange: (event) => setPromptQuery(event.target.value) })), h("div", { className: "tavern-preset-prompt-list" }, ...(filteredOrdered.length === 0 ? [h("p", { className: "tavern-preset-list-empty", key: "empty" }, "没有匹配的提示词")] : filteredOrdered.map(({ item, index, prompt }) => h("article", {
1035
+ className: `${selectedPrompt?.identifier === prompt.identifier ? " is-selected" : ""}${item.enabled ? "" : " is-disabled"}${draggedPromptId === prompt.identifier ? " is-dragging" : ""}${dropTarget?.identifier === prompt.identifier ? ` is-drop-${dropTarget.position}` : ""}`,
1036
+ key: prompt.identifier,
1037
+ draggable: true,
1038
+ onDragStart: (event) => { event.dataTransfer.effectAllowed = "move"; event.dataTransfer.setData("text/plain", prompt.identifier); setDraggedPromptId(prompt.identifier); setDropTarget(null); },
1039
+ onDragEnd: () => { setDraggedPromptId(""); setDropTarget(null); },
1040
+ onDragOver: (event) => { if (draggedPromptId.length === 0 || draggedPromptId === prompt.identifier)
1041
+ return; event.preventDefault(); event.dataTransfer.dropEffect = "move"; const bounds = event.currentTarget.getBoundingClientRect(); setDropTarget({ identifier: prompt.identifier, position: event.clientY < bounds.top + bounds.height / 2 ? "before" : "after" }); },
1042
+ onDrop: (event) => { event.preventDefault(); const sourceIdentifier = draggedPromptId || event.dataTransfer.getData("text/plain"); const position = dropTarget?.identifier === prompt.identifier ? dropTarget.position : "before"; reorderPrompt(sourceIdentifier, prompt.identifier, position); setDraggedPromptId(""); setDropTarget(null); },
1043
+ }, h("span", { className: "tavern-preset-order-handle", title: `拖动第 ${index + 1} 条提示词排序`, "aria-hidden": "true" }, h("span", null, String(index + 1).padStart(2, "0")), h(IconEllipsisOutline16, { size: 13 })), h("button", { type: "button", className: "tavern-preset-prompt-main", onClick: () => setSelectedPromptId(prompt.identifier) }, h("strong", null, prompt.name || "未命名提示词"), h("small", null, `${prompt.marker ? "动态标记" : prompt.injectionPosition === "in-chat" ? `In-chat · Depth ${prompt.injectionDepth}` : "相对顺序"} · ${prompt.role}`)), h("label", { className: "tavern-preset-row-toggle", title: item.enabled ? "已启用" : "已停用" }, h("input", { type: "checkbox", checked: item.enabled, onChange: (event) => updateDraft((value) => { const row = value.promptOrder.find((candidate) => candidate.identifier === prompt.identifier); if (row !== undefined)
1044
+ row.enabled = event.target.checked; }) }), h("span", null)), h("div", { className: "tavern-preset-move" }, h("button", { type: "button", disabled: index === 0, title: "上移", "aria-label": "上移", onClick: () => movePrompt(prompt.identifier, -1) }, h(IconChevronLeftOutline14, { size: 12 })), h("button", { type: "button", disabled: index === ordered.length - 1, title: "下移", "aria-label": "下移", onClick: () => movePrompt(prompt.identifier, 1) }, h(IconChevronRightOutline14, { size: 12 })))))))), selectedPrompt === null ? h("div", { className: "tavern-preset-prompt-empty" }, "选择一条提示词进行编辑。") : h("aside", { className: "tavern-preset-prompt-editor" }, h("header", null, h("div", null, h("small", null, `提示词顺序 / ${selectedOrderedIndex + 1}`), h("strong", null, selectedPrompt.name || "未命名提示词")), h("div", { className: "tavern-preset-editor-actions" }, selectedOrder !== undefined ? h("label", { className: "tavern-preset-editor-toggle" }, h("input", { type: "checkbox", checked: selectedOrder.enabled, onChange: (event) => updateDraft((value) => { const row = value.promptOrder.find((candidate) => candidate.identifier === selectedPrompt.identifier); if (row !== undefined)
1045
+ row.enabled = event.target.checked; }) }), h("span", null), selectedOrder.enabled ? "已启用" : "已停用") : null, h("button", { type: "button", disabled: selectedOrderedIndex <= 0, title: "上一条", "aria-label": "上一条", onClick: () => setSelectedPromptId(ordered[selectedOrderedIndex - 1]?.prompt.identifier ?? selectedPrompt.identifier) }, h(IconChevronLeftOutline14, { size: 14 })), h("button", { type: "button", disabled: selectedOrderedIndex < 0 || selectedOrderedIndex >= ordered.length - 1, title: "下一条", "aria-label": "下一条", onClick: () => setSelectedPromptId(ordered[selectedOrderedIndex + 1]?.prompt.identifier ?? selectedPrompt.identifier) }, h(IconChevronRightOutline14, { size: 14 })))), h("label", null, "名称", h("input", { value: selectedPrompt.name, onChange: (event) => updateDraft((value) => { const prompt = value.prompts.find((candidate) => candidate.identifier === selectedPrompt.identifier); if (prompt !== undefined)
1046
+ prompt.name = event.target.value; }) })), h("div", { className: "tavern-preset-editor-grid" }, h("label", null, "角色", h("select", { value: selectedPrompt.role, onChange: (event) => updateDraft((value) => { const prompt = value.prompts.find((candidate) => candidate.identifier === selectedPrompt.identifier); if (prompt !== undefined)
1047
+ prompt.role = event.target.value; }) }, h("option", { value: "system" }, "System"), h("option", { value: "user" }, "User"), h("option", { value: "assistant" }, "Assistant"))), h("label", null, "位置", h("select", { value: selectedPrompt.injectionPosition, disabled: selectedPrompt.marker !== undefined, onChange: (event) => updateDraft((value) => { const prompt = value.prompts.find((candidate) => candidate.identifier === selectedPrompt.identifier); if (prompt !== undefined)
1048
+ prompt.injectionPosition = event.target.value; }) }, h("option", { value: "relative" }, "相对顺序"), h("option", { value: "in-chat" }, "In-chat"))), selectedPrompt.injectionPosition === "in-chat" ? h("label", null, "Depth", h("input", { type: "number", min: 0, value: selectedPrompt.injectionDepth, onChange: (event) => updateDraft((value) => { const prompt = value.prompts.find((candidate) => candidate.identifier === selectedPrompt.identifier); if (prompt !== undefined)
1049
+ prompt.injectionDepth = Number(event.target.value); }) })) : null, selectedPrompt.injectionPosition === "in-chat" ? h("label", null, "Order", h("input", { type: "number", value: selectedPrompt.injectionOrder, onChange: (event) => updateDraft((value) => { const prompt = value.prompts.find((candidate) => candidate.identifier === selectedPrompt.identifier); if (prompt !== undefined)
1050
+ prompt.injectionOrder = Number(event.target.value); }) })) : null), contentEditable ? h("label", { className: "tavern-preset-content" }, h("span", null, "内容", h("small", null, "支持酒馆变量与模板语法")), h("textarea", { value: selectedPrompt.content, rows: 10, placeholder: "在此输入提示词内容…", onChange: (event) => updateDraft((value) => { const prompt = value.prompts.find((candidate) => candidate.identifier === selectedPrompt.identifier); if (prompt !== undefined)
1051
+ prompt.content = event.target.value; }) })) : h("div", { className: "tavern-preset-marker-note" }, h("strong", null, "正文由 Session 动态生成"), h("p", null, "这里在真实请求时替换为角色卡、世界书、Persona、示例或聊天历史;名称、角色、顺序和启用状态仍属于预设。")), h("details", { className: "tavern-preset-editor-more" }, h("summary", null, "更多选项"), h("div", null, !pinnedIds.has(selectedPrompt.identifier) && selectedOrder !== undefined ? h("button", { type: "button", className: "tavern-preset-remove-prompt", onClick: () => { updateDraft((value) => { value.promptOrder = value.promptOrder.filter((item) => item.identifier !== selectedPrompt.identifier); }); setSelectedPromptId(ordered.find((row) => row.prompt.identifier !== selectedPrompt.identifier)?.prompt.identifier ?? ""); } }, "从顺序中移除") : null, h("details", { className: "tavern-preset-card-overrides" }, h("summary", null, "当前角色卡覆盖项"), h("div", null, h("label", null, "System Prompt"), h("pre", null, snapshot.card.systemPrompt.trim() || "(原件未填写,使用预设 Main Prompt)"), h("label", null, "Post-history Instructions"), h("pre", null, snapshot.card.postHistoryInstructions.trim() || "(原件未填写,使用预设 Post-History Instructions)"))))), h("footer", { className: "tavern-preset-editor-footer" }, h("span", { className: dirty ? "is-dirty" : "" }, dirty ? "未保存的更改" : "所有更改已保存"), h("button", { type: "button", disabled: busy || !dirty, onClick: () => adopt(remote, draft.id) }, "还原"), h("button", { type: "button", className: "is-primary", disabled: busy || !dirty, onClick: () => void save() }, "保存更改"))))));
1052
+ }
1053
+ function VariableTreeNode({ name, value, depth = 0 }) {
1054
+ const composite = typeof value === "object" && value !== null;
1055
+ if (!composite)
1056
+ return h("div", { className: "tavern-variable-leaf" }, h("span", null, name), h("code", null, JSON.stringify(value)));
1057
+ const entries = Array.isArray(value) ? value.map((child, index) => [String(index), child]) : Object.entries(value);
1058
+ return h("details", { className: "tavern-variable-branch", open: depth < 1 }, h("summary", null, h("strong", null, name), h("span", null, Array.isArray(value) ? `${entries.length} 项` : `${entries.length} 个字段`)), h("div", null, ...entries.map(([key, child]) => h(VariableTreeNode, { name: key, value: child, depth: depth + 1, key }))));
1059
+ }
1060
+ function FrontendVariableSection({ snapshot }) {
1061
+ const [tab, setTab] = React.useState("tree");
1062
+ const variables = snapshot.frontend.variables;
1063
+ const events = variables?.events ?? [];
1064
+ const lastCommitted = [...events].reverse().find((event) => event.phase === "reply" && event.committed === true);
1065
+ const lastDigest = lastCommitted?.stateDigestAfter;
1066
+ const diffEvents = lastDigest === undefined ? [] : events.filter((event) => event.phase === "reply" && event.stateDigestAfter === lastDigest);
1067
+ return h(FrontendSettingSection, { title: "变量状态", meta: variables === null ? "等待当前 Session" : `${variables.initializationStatus === "initialized" ? "已初始化" : "初始化失败"} · MVU`, open: variables !== null }, variables === null ? h("p", { className: "tavern-capability-empty" }, snapshot.card.runtime.variableFormatCount === 0 ? "这张卡没有识别到 MVU / TavernHelper 变量格式。" : "打开这张卡的 Session 后读取变量树。") : h(React.Fragment, null, h("section", { className: "tavern-capability-summary" }, h("dl", null, h("dt", null, "作用域"), h("dd", null, "当前 Session"), h("dt", null, "初始化"), h("dd", null, variables.initializationStatus), h("dt", null, "状态摘要"), h("dd", null, variables.digest.slice(0, 12)), h("dt", null, "Split MVU"), h("dd", null, snapshot.session?.splitMvu?.status ?? "未启用"))), h(CapabilityTabs, { value: tab, onChange: setTab, label: "MVU 查看方式", items: [{ id: "tree", label: "变量树" }, { id: "diff", label: "本轮差异" }, { id: "raw", label: "原始 JSON" }] }), tab === "tree" ? h("div", { className: "tavern-variable-tree" }, h(VariableTreeNode, { name: "stat_data", value: variables.state })) : tab === "diff" ? h("div", { className: "tavern-variable-diff" }, diffEvents.length === 0 ? h("p", { className: "tavern-capability-empty" }, "还没有已提交的本轮变量差异。") : h("ol", null, ...diffEvents.map((event, index) => h("li", { key: `${event.sequence}-${index}` }, h("strong", null, event.operation ?? event.type), h("span", null, event.path ?? event.diagnostic?.message ?? "状态已提交"))))) : h("pre", { className: "tavern-raw-json" }, JSON.stringify(variables.state, null, 2))));
1068
+ }
1069
+ function RegexCapabilityPanel({ snapshot }) {
1070
+ const [tab, setTab] = React.useState("settings");
1071
+ const [query, setQuery] = React.useState("");
1072
+ const [ruleId, setRuleId] = React.useState(snapshot.regex.scripts[0]?.id ?? "");
1073
+ const normalizedQuery = query.trim().toLocaleLowerCase("zh-CN");
1074
+ const visibleRules = snapshot.regex.scripts.map((item, index) => ({ item, index })).filter(({ item }) => normalizedQuery === "" || `${item.name ?? ""} ${item.id} ${item.pattern} ${item.replacement}`.toLocaleLowerCase("zh-CN").includes(normalizedQuery));
1075
+ const selectedEntry = visibleRules.find(({ item }) => item.id === ruleId) ?? visibleRules[0];
1076
+ const rule = selectedEntry?.item;
1077
+ const ruleTitle = (item, index) => item.name?.trim() || `正则规则 ${index + 1}`;
1078
+ const depthLabel = (item) => item.minDepth === null && item.maxDepth === null ? "全部消息深度" : `${item.minDepth ?? "不限"} → ${item.maxDepth ?? "不限"}`;
1079
+ const placementLabel = (item) => item.placements.includes(1) ? "user + assistant" : "assistant";
1080
+ return h(React.Fragment, null, h(CapabilityPanelHeader, { title: "正则", meta: `${snapshot.card.title} · ${snapshot.regex.scripts.length} 条当前卡规则` }), h(CapabilityTabs, { value: tab, onChange: setTab, label: "正则页面", items: [{ id: "settings", label: "规则设置" }, { id: "evidence", label: `运行证据${snapshot.regex.matches.length > 0 ? ` · ${snapshot.regex.matches.length}` : ""}` }] }), tab === "settings" ? h("div", { className: "tavern-regex-settings" }, h("section", { className: "tavern-regex-overview", "aria-label": "正则概览" }, h("div", null, h("strong", null, String(snapshot.regex.scripts.length)), h("span", null, "当前卡规则")), h("div", null, h("strong", null, String(snapshot.regex.matches.length)), h("span", null, "发生变换的消息")), h("div", null, h("strong", null, snapshot.session === null ? "卡片" : "Session"), h("span", null, "当前证据范围"))), h("div", { className: "tavern-regex-toolbar" }, h("label", { className: "tavern-capability-search" }, h(IconSearchOutline16, { size: 16 }), h("input", { value: query, placeholder: "搜索规则名称、表达式或替换内容", onChange: (event) => setQuery(event.target.value) })), h("button", { type: "button", className: "tavern-regex-debug-button", onClick: () => setTab("evidence") }, h(IconChecklistOutline14, { size: 15 }), h("span", null, "调试工具"))), h("div", { className: "tavern-regex-scopes" }, h("section", { className: "tavern-regex-scope is-unavailable" }, h("header", null, h("div", null, h("strong", null, "全局正则"), h("span", null, "影响所有酒馆卡")), h("em", null, "未接入")), h("p", null, "全局规则属于 DSH 设置;当前插件不复制第二套全局配置。")), h("section", { className: "tavern-regex-scope is-unavailable" }, h("header", null, h("div", null, h("strong", null, "预设正则"), h("span", null, "随当前生成预设装配")), h("em", null, "未导入")), h("p", null, "当前 Session 没有可管理的外部 Regex 预设。")), h("section", { className: "tavern-regex-scope is-current" }, h("header", null, h("div", null, h("strong", null, "当前卡正则"), h("span", null, "来自卡片原件,仅改变消息显示投影")), h("em", null, `${snapshot.regex.scripts.length} 条可执行`)), snapshot.regex.scripts.length === 0 ? h("p", { className: "tavern-capability-empty" }, "这张卡没有可安全执行的 assistant 消息展示 Regex。") : visibleRules.length === 0 ? h("p", { className: "tavern-capability-empty" }, "没有找到匹配的规则。") : h("div", { className: "tavern-regex-rule-list" }, ...visibleRules.map(({ item, index }) => {
1081
+ const expanded = item.id === rule?.id;
1082
+ const panelId = `tavern-regex-rule-${index}`;
1083
+ return h("article", { className: expanded ? "is-expanded" : "", key: item.id }, h("button", { type: "button", "aria-expanded": expanded, "aria-controls": panelId, onClick: () => setRuleId(item.id) }, h("span", { className: "tavern-regex-order" }, String(index + 1).padStart(2, "0")), h("span", { className: "tavern-regex-rule-copy" }, h("strong", null, ruleTitle(item, index)), h("small", null, `${placementLabel(item)} · ${depthLabel(item)}`)), h("span", { className: "tavern-regex-enabled" }, "已启用"), expanded ? h(IconChevronDownOutline14, { size: 15 }) : h(IconChevronRightOutline14, { size: 15 })), expanded ? h("div", { className: "tavern-regex-rule-detail", id: panelId }, h("div", { className: "tavern-regex-rule-facts" }, h("div", null, h("span", null, "来源"), h("strong", null, "当前卡扩展")), h("div", null, h("span", null, "执行顺序"), h("strong", null, `第 ${index + 1} 条`)), h("div", null, h("span", null, "DSH 作用层"), h("strong", null, "屏幕显示投影")), h("div", null, h("span", null, "原件声明"), h("strong", null, item.promptOnly === true ? "显示 + Prompt" : "显示")), h("div", null, h("span", null, "正式消息"), h("strong", null, "Session 保持原文")), h("div", null, h("span", null, "编辑时"), h("strong", null, item.runOnEdit ? "允许运行" : "不运行"))), h("label", null, "查找表达式"), h("pre", null, `/${item.pattern}/${item.flags}`), h("label", null, "替换内容"), h("pre", null, item.replacement || "(空字符串:隐藏匹配内容)"), h("small", { className: "tavern-regex-rule-id" }, `规则 ID · ${item.id}`)) : null);
1084
+ }))), h("aside", { className: "tavern-regex-boundary" }, h("strong", null, "当前管理边界"), h("p", null, "卡片原件保持不可变;这里展示真实顺序和生效范围。全局、预设、新建、导入与批量改写在对应后端接入前不会伪装成可用按钮。")))) :
1085
+ h("div", { className: "tavern-regex-evidence" }, h("section", { className: "tavern-regex-evidence-summary" }, h("header", null, h("div", null, h("strong", null, "运行边界"), h("span", null, "同一条消息在不同层面可能不同"))), h("div", { className: "tavern-regex-plane-grid" }, h("article", null, h("span", null, "卡片原件"), h("strong", null, `${snapshot.regex.scripts.length} 条规则`), h("small", null, "不可变来源")), h("article", null, h("span", null, "Session 持久消息"), h("strong", null, "保持原文"), h("small", null, "不写回替换结果")), h("article", null, h("span", null, "最终 Prompt"), h("strong", null, "当前未应用"), h("small", null, "展示 Regex 不进入请求")), h("article", { className: snapshot.regex.matches.length > 0 ? "is-active" : "" }, h("span", null, "屏幕显示"), h("strong", null, `${snapshot.regex.matches.length} 条变化`), h("small", null, "可重建只读投影"))), h("p", null, "下方是整条消息经过当前卡 Regex 流水线后的最终差异,不把它冒充成逐规则命中记录。")), h("section", { className: "tavern-regex-match-section" }, h("header", null, h("div", null, h("strong", null, "本轮处理结果"), h("span", null, snapshot.session === null ? "等待 Session" : `${snapshot.regex.matches.length} 条消息发生变化`))), h("div", { className: "tavern-regex-matches" }, ...(snapshot.regex.matches.length === 0 ? [h("p", { className: "tavern-capability-empty", key: "empty" }, snapshot.session === null ? "打开 Session 后显示实际 Regex 结果。" : "当前可见消息没有发生 Regex 变换。")] : snapshot.regex.matches.map((match) => h("article", { key: match.seq }, h("header", null, h("strong", null, `消息 ${match.seq}`), h("span", null, `${match.role} · 流水线结果`)), h("div", null, h("section", null, h("h4", null, "Session 原文"), h("pre", null, match.before)), h("section", null, h("h4", null, "屏幕显示"), h("pre", null, match.after))))))))));
1086
+ }
1087
+ function frontendContainerLabel(container) {
1088
+ if (container === "message-html")
1089
+ return "消息内 HTML / CSS";
1090
+ if (container === "message-iframe")
1091
+ return "消息内交互 iframe";
1092
+ if (container === "standalone")
1093
+ return "独立卡内前端";
1094
+ if (container === "required-asset")
1095
+ return "远程资源前端";
1096
+ return "未声明独立容器";
1097
+ }
1098
+ function frontendRuntimeLabel(runtimeClass) {
1099
+ if (runtimeClass === "message_html_css")
1100
+ return "消息样式";
1101
+ if (runtimeClass === "message_iframe")
1102
+ return "消息交互";
1103
+ if (runtimeClass === "background_script_and_message_iframe")
1104
+ return "后台脚本与消息交互";
1105
+ if (runtimeClass === "standalone_app")
1106
+ return "独立前端";
1107
+ if (runtimeClass === "cross_origin_required_asset")
1108
+ return "远程资源前端";
1109
+ if (runtimeClass === "formal_message_mixed_projection")
1110
+ return "正式消息混合呈现";
1111
+ if (runtimeClass === "generated_multi_fragment_projection")
1112
+ return "多段消息呈现";
1113
+ return runtimeClass ?? "按实际回复内容识别";
1114
+ }
1115
+ function FrontendSettingSection({ id, title, meta, open = false, children }) {
1116
+ return h("details", { className: "tavern-frontend-setting-section", id, open }, h("summary", null, h("div", null, h("strong", null, title), meta === undefined ? null : h("span", null, meta))), h("div", { className: "tavern-frontend-setting-body" }, children));
1117
+ }
1118
+ function FrontendCapabilityPanel({ snapshot, diagnostics }) {
1119
+ const permissionKey = `dsh-re3-rp:companion-permission:${snapshot.card.revisionId}`;
1120
+ const [authorized, setAuthorized] = React.useState(window.localStorage.getItem(permissionKey) === "enabled");
1121
+ const [mvuSettings, setMvuSettings] = React.useState(snapshot.session?.mvuSettings ?? null);
1122
+ const [mvuBusy, setMvuBusy] = React.useState("");
1123
+ const [mvuNotice, setMvuNotice] = React.useState(null);
1124
+ const hasVariables = snapshot.frontend.variables !== null || snapshot.card.runtime.variableFormatCount > 0;
1125
+ const hasRuntime = snapshot.frontend.definition !== null || snapshot.frontend.companionScripts.length > 0 || hasVariables;
1126
+ const hasScripts = snapshot.frontend.companionScripts.length > 0;
1127
+ const container = snapshot.frontend.definition?.container;
1128
+ const hostedFrontend = container === "standalone" || container === "required-asset";
1129
+ const visibleStatus = !hasRuntime ? snapshot.session === null ? "等待 Session 检测" : "未检测到卡内前端" : hasScripts && !authorized ? "等待脚本授权" : "前端可运行";
1130
+ const uniqueErrors = diagnostics.filter((item, index, values) => values.findIndex((candidate) => candidate.message === item.message) === index);
1131
+ const compatibilityCalls = snapshot.frontend.compatibilityCalls;
1132
+ React.useEffect(() => setAuthorized(window.localStorage.getItem(permissionKey) === "enabled"), [permissionKey]);
1133
+ React.useEffect(() => setMvuSettings(snapshot.session?.mvuSettings ?? null), [snapshot.session?.mvuSettings]);
1134
+ const setPermission = (enabled) => {
1135
+ if (enabled)
1136
+ window.localStorage.setItem(permissionKey, "enabled");
1137
+ else
1138
+ window.localStorage.removeItem(permissionKey);
1139
+ setAuthorized(enabled);
1140
+ window.dispatchEvent(new Event("dsh-re3-rp:companion-permission"));
1141
+ };
1142
+ const scripts = hasScripts
1143
+ ? snapshot.frontend.companionScripts.map((script) => h("details", { className: "tavern-frontend-script", key: script.id }, h("summary", null, h("strong", null, script.name), h("span", null, `${script.source.length.toLocaleString()} 字符`)), h("pre", null, script.source)))
1144
+ : [h("p", { className: "tavern-frontend-empty", key: "empty" }, "这张卡没有常驻 TavernHelper 脚本。")];
1145
+ const compatibilityCallRows = compatibilityCalls.slice().reverse().map((call) => h("li", { key: `${call.sequence}:${call.operationId}` }, h("div", null, h("strong", null, `${call.surface}.${call.method}`), h("span", null, call.effect)), h("p", null, call.dshAction), h("small", null, new Date(call.capturedAt).toLocaleTimeString("zh-CN"))));
1146
+ const runMvuControl = async (action, settings) => {
1147
+ if (snapshot.session === null || mvuBusy.length > 0)
1148
+ return;
1149
+ setMvuBusy(action);
1150
+ setMvuNotice(null);
1151
+ try {
1152
+ const response = await fetch("/dsh-re3-rp/mvu-control", {
1153
+ method: "POST",
1154
+ headers: { "Content-Type": "application/json" },
1155
+ body: JSON.stringify({ sessionId: snapshot.session.id, action, settings }),
1156
+ });
1157
+ const body = await response.json();
1158
+ if (!response.ok)
1159
+ throw new Error(body.error ?? "MVU 操作失败");
1160
+ setMvuNotice({ kind: "ok", text: action === "updateSettings" ? "MVU 设置已保存" : "操作完成,变量状态已从 Session 重新读取" });
1161
+ await loadCapabilitySnapshot({ sessionId: snapshot.session.id });
1162
+ }
1163
+ catch (error) {
1164
+ setMvuNotice({ kind: "error", text: error instanceof Error ? error.message : "MVU 操作失败" });
1165
+ }
1166
+ finally {
1167
+ setMvuBusy("");
1168
+ }
1169
+ };
1170
+ const openTool = (target) => {
1171
+ if (target === "context") {
1172
+ window.dispatchEvent(new CustomEvent("dsh-re3-rp:open-capability", { detail: "context" }));
1173
+ return;
1174
+ }
1175
+ const element = document.getElementById(target === "variables" ? "tavern-frontend-variables" : "tavern-frontend-diagnostics");
1176
+ if (element !== null) {
1177
+ element.open = true;
1178
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
1179
+ }
1180
+ };
1181
+ return h(React.Fragment, null, h(CapabilityPanelHeader, { title: "前端", meta: `${snapshot.card.title} · 界面、交互与变量状态` }), h("div", { className: "tavern-frontend-settings" }, h("section", { className: `tavern-frontend-overview ${hasScripts && !authorized ? "is-pending" : hasRuntime ? "is-ready" : "is-idle"}` }, h("div", { className: "tavern-frontend-overview-copy" }, h("span", null, "界面、交互与变量状态"), h("strong", null, visibleStatus), h("small", null, `Revision ${snapshot.card.revisionId.slice(0, 7)} · ${frontendRuntimeLabel(snapshot.frontend.definition?.runtimeClass)}`)), h("div", { className: "tavern-frontend-overview-facts" }, h("div", null, h("strong", null, String(snapshot.frontend.companionScripts.length)), h("span", null, "卡内脚本")), h("div", null, h("strong", null, String(snapshot.frontend.events.length)), h("span", null, "运行事件")), h("div", null, h("strong", null, String(uniqueErrors.length)), h("span", null, "运行错误")))), h(FrontendSettingSection, { title: "当前角色卡配置", meta: frontendContainerLabel(container), open: true }, h("dl", { className: "tavern-frontend-facts" }, h("dt", null, "消息 HTML / CSS"), h("dd", null, container === "message-html" ? "当前卡已声明" : "随实际回复内容识别"), h("dt", null, "消息 iframe"), h("dd", null, container === "message-iframe" ? "当前卡已声明交互容器" : "按回复中的交互片段挂载"), h("dt", null, "后台脚本 iframe"), h("dd", null, hasScripts ? `${snapshot.frontend.companionScripts.length} 个脚本 · ${authorized ? "已运行" : "等待授权"}` : "当前卡未携带"), h("dt", null, "独立前端"), h("dd", null, hostedFrontend ? frontendContainerLabel(container) : "当前卡未声明"), h("dt", null, "MVU 变量状态"), h("dd", null, hasVariables ? snapshot.frontend.variables === null ? "已识别,等待 Session" : "已接入当前 Session" : "当前卡未识别"), h("dt", null, "显示位置"), h("dd", null, "实际游戏 UI 位于 DSH 原生对话消息内"))), h(FrontendSettingSection, { title: "变量更新方式", meta: mvuSettings?.updateMethod ?? "等待 Session", open: snapshot.session !== null }, mvuSettings === null ? h("p", { className: "tavern-frontend-empty" }, "打开 Session 后可设置 MVU 更新方式。") : h(React.Fragment, null, h("label", { className: "tavern-mvu-field" }, h("span", null, "变量更新方式"), h("select", { value: mvuSettings.updateMethod, disabled: mvuBusy.length > 0, onChange: (event) => setMvuSettings({ ...mvuSettings, updateMethod: event.currentTarget.value }) }, h("option", { value: "随 AI 输出" }, "随 AI 输出"), h("option", { value: "额外模型解析", disabled: snapshot.session?.supportsExtraModel !== true }, "额外模型解析"))), mvuSettings.updateMethod === "额外模型解析" ? h("div", { className: "tavern-mvu-advanced" }, h("label", { className: "tavern-frontend-permission" }, h("input", { type: "checkbox", checked: mvuSettings.automaticRequest, onChange: (event) => setMvuSettings({ ...mvuSettings, automaticRequest: event.currentTarget.checked }) }), h("span", null, h("strong", null, "自动请求"), h("small", null, "剧情回复没有携带变量更新时,自动调用额外模型解析。"))), h("label", { className: "tavern-mvu-field" }, h("span", null, "模型来源"), h("select", { value: mvuSettings.extraModel.source, onChange: (event) => setMvuSettings({ ...mvuSettings, extraModel: { ...mvuSettings.extraModel, source: event.currentTarget.value } }) }, h("option", { value: "与插头相同" }, "与插头相同"), h("option", { value: "自定义" }, "自定义"))), mvuSettings.extraModel.source === "自定义" ? h("div", { className: "tavern-mvu-model-grid" }, h("label", { className: "tavern-mvu-field" }, h("span", null, "Provider"), h("input", { value: mvuSettings.extraModel.provider, onChange: (event) => setMvuSettings({ ...mvuSettings, extraModel: { ...mvuSettings.extraModel, provider: event.currentTarget.value } }) })), h("label", { className: "tavern-mvu-field" }, h("span", null, "Model"), h("input", { value: mvuSettings.extraModel.model, onChange: (event) => setMvuSettings({ ...mvuSettings, extraModel: { ...mvuSettings.extraModel, model: event.currentTarget.value } }) }))) : null, h("label", { className: "tavern-mvu-field" }, h("span", null, "最大输出 Token"), h("input", { type: "number", min: 256, max: 32768, step: 256, value: mvuSettings.extraModel.maxTokens, onChange: (event) => setMvuSettings({ ...mvuSettings, extraModel: { ...mvuSettings.extraModel, maxTokens: Number(event.currentTarget.value) } }) })), h("p", { className: "tavern-frontend-note" }, "模型凭据沿用 DSH 的模型配置;这里不另存 API Key。")) : null, h("div", { className: "tavern-mvu-actions" }, h("button", { type: "button", disabled: mvuBusy.length > 0, onClick: () => void runMvuControl("updateSettings", mvuSettings) }, mvuBusy === "updateSettings" ? "正在保存…" : "保存设置")), mvuNotice === null ? null : h("p", { className: `tavern-mvu-notice is-${mvuNotice.kind}`, role: "status" }, mvuNotice.text))), h("div", { id: "tavern-frontend-variables" }, h(FrontendVariableSection, { snapshot })), h(FrontendSettingSection, { title: "修复按钮", meta: "只重建当前 Session 的变量投影" }, h("div", { className: "tavern-mvu-actions" }, h("button", { type: "button", disabled: snapshot.session === null || mvuBusy.length > 0, onClick: () => void runMvuControl("reprocessVariables") }, "重新处理变量"), h("button", { type: "button", disabled: snapshot.session === null || mvuBusy.length > 0, onClick: () => void runMvuControl("reloadInitialVariables") }, "重新读取初始变量"), h("button", { type: "button", disabled: snapshot.session === null || mvuSettings?.updateMethod !== "额外模型解析" || mvuBusy.length > 0, onClick: () => void runMvuControl("retryExtraModelParsing") }, "重试额外模型解析")), h("p", { className: "tavern-frontend-note" }, "操作会从 DSH Session 的正式消息重新计算变量;校验失败时保留原状态。")), h(FrontendSettingSection, { title: "工具", meta: "查看真实请求、变量和运行记录" }, h("div", { className: "tavern-mvu-actions" }, h("button", { type: "button", onClick: () => openTool("context") }, "提示词查看器"), h("button", { type: "button", onClick: () => openTool("variables") }, "变量管理器"), h("button", { type: "button", onClick: () => openTool("diagnostics") }, "日志查看器"))), h(FrontendSettingSection, { title: "安全与权限", meta: hasScripts ? authorized ? "当前版本已允许" : "需要你的确认" : "无需授权", open: hasScripts }, h("label", { className: "tavern-frontend-permission" }, h("input", { type: "checkbox", checked: hasScripts ? authorized : true, disabled: !hasScripts, onChange: (event) => setPermission(event.currentTarget.checked) }), h("span", null, h("strong", null, hasScripts ? "允许此版本运行卡内脚本" : "当前卡没有需要授权的脚本"), h("small", null, hasScripts ? `授权只绑定 Revision ${snapshot.card.revisionId.slice(0, 7)};卡片更新后需要重新确认。` : "消息 HTML / CSS 仍在隔离的消息容器中呈现。"))), hasScripts ? h("p", { className: "tavern-frontend-note" }, "卡内脚本可以读取本 Session 的投影状态,并通过受控 Bridge 提交允许的操作。关闭后,后台脚本 iframe 会立即卸载。") : null), h(FrontendSettingSection, { title: "交互与提交", meta: `${snapshot.frontend.events.length} 个事件 · ${snapshot.frontend.receiptCount} 个已确认操作` }, h("div", { className: "tavern-frontend-metrics" }, h("div", null, h("span", null, "前端运行事件"), h("strong", null, String(snapshot.frontend.events.length))), h("div", null, h("span", null, "Host 已确认操作"), h("strong", null, String(snapshot.frontend.receiptCount)))), h("h4", null, "Bridge 能力"), snapshot.frontend.capabilities.length === 0
1182
+ ? h("p", { className: "tavern-frontend-empty" }, "当前卡没有声明专用 Bridge capability;脚本仍受上方版本授权边界限制。")
1183
+ : h("div", { className: "tavern-chip-list" }, ...snapshot.frontend.capabilities.map((capability) => h("span", { key: capability }, capability)))), h(FrontendSettingSection, { title: "酒馆助手与 MVU 调用", meta: `${compatibilityCalls.length} 次真实调用`, open: compatibilityCalls.length > 0 }, compatibilityCalls.length === 0
1184
+ ? h("p", { className: "tavern-frontend-empty" }, snapshot.session === null ? "打开 Session 后记录卡内脚本实际调用。" : "当前 Session 尚未观察到酒馆助手或 MVU 兼容调用。")
1185
+ : h("ol", { className: "tavern-compatibility-call-list" }, ...compatibilityCallRows)), h(FrontendSettingSection, { id: "tavern-frontend-diagnostics", title: "运行诊断", meta: uniqueErrors.length === 0 ? "未发现错误" : `${uniqueErrors.length} 条错误`, open: uniqueErrors.length > 0 }, uniqueErrors.length === 0
1186
+ ? h("p", { className: "tavern-frontend-empty" }, snapshot.session === null ? "打开 Session 后开始采集卡内前端运行错误。" : "本次页面生命周期尚未收到卡内前端运行错误。")
1187
+ : h("ul", { className: "tavern-runtime-errors" }, ...uniqueErrors.map((error) => h("li", { key: `${error.token}:${error.message}` }, error.message)))), h(FrontendSettingSection, { title: "脚本与资源", meta: `${snapshot.frontend.companionScripts.length} 个脚本` }, h("div", { className: "tavern-frontend-script-list" }, ...scripts), h("div", { className: "tavern-frontend-resource" }, h("h4", null, "前端资源"), h("p", null, snapshot.frontend.definition?.frontendEntry ?? (snapshot.frontend.capabilities.includes("asset.resolve") ? "通过 Host asset.resolve 解析 required 资源" : "当前卡没有声明 required 远程资源"))), h("details", { className: "tavern-frontend-state" }, h("summary", null, "查看前端状态摘要"), h("pre", null, snapshot.frontend.state === null ? "(没有前端状态)" : JSON.stringify(snapshot.frontend.state, null, 2))))));
1188
+ }
1189
+ function CardDocument({ text, empty }) {
1190
+ return h("article", { className: "tavern-card-document" }, h(MarkdownText, { text: text.trim() || empty }));
1191
+ }
1192
+ function CardCapabilityPanel({ snapshot }) {
1193
+ const prototype = usePrototypeState();
1194
+ const [tab, setTab] = React.useState("description");
1195
+ const [openingIndex, setOpeningIndex] = React.useState(0);
1196
+ const [menuOpen, setMenuOpen] = React.useState(false);
1197
+ const card = snapshot.card;
1198
+ const hostCardValue = prototype.cards.find((candidate) => candidate.revisionId === card.revisionId) ?? null;
1199
+ const coverUrl = hostCardValue?.coverUrl || (card.sourceName.toLocaleLowerCase().endsWith(".png") ? card.originalUrl : "");
1200
+ const sourceLabel = card.sourceName.toLocaleLowerCase().endsWith(".png") ? "PNG" : "JSON";
1201
+ const subtitle = card.tags.filter((tag) => tag.trim() !== "").slice(0, 2).join(" · ") || "酒馆卡 · 角色扮演";
1202
+ const canStart = hostCardValue !== null && card.playability !== "blocked";
1203
+ const navItems = [
1204
+ { id: "description", label: "角色描述" },
1205
+ { id: "openings", label: `开场白 ${card.openings.length}` },
1206
+ { id: "personality", label: "性格" },
1207
+ { id: "scenario", label: "场景" },
1208
+ ];
1209
+ const selectedOpening = card.openings[Math.min(openingIndex, Math.max(card.openings.length - 1, 0))];
1210
+ const panel = tab === "description" ? h(CardDocument, { text: card.description, empty: "(原件中未填写角色描述)" }) :
1211
+ tab === "personality" ? h(CardDocument, { text: card.personality, empty: "(原件中未单独填写性格)" }) :
1212
+ tab === "scenario" ? h(CardDocument, { text: card.scenario, empty: "(原件中未单独填写场景)" }) :
1213
+ tab === "openings" ? h("section", { className: "tavern-card-openings", "aria-label": "开场白" }, card.openings.length === 0 ? h("p", { className: "tavern-card-empty" }, "这张卡没有可用开场白。") : h(React.Fragment, null, h("nav", { className: "tavern-card-opening-tabs", "aria-label": "开场白列表" }, ...card.openings.map((opening, index) => h("button", { type: "button", "aria-pressed": index === openingIndex, onClick: () => setOpeningIndex(index), key: opening.id }, `开场 ${index + 1}`))), h(CardDocument, { text: selectedOpening?.message ?? "", empty: "(空白开场)" }))) :
1214
+ h("section", { className: "tavern-card-advanced", "aria-label": "兼容报告与原件" }, h("header", null, h("button", { type: "button", className: "tavern-card-back-to-description", onClick: () => setTab("description") }, h(IconChevronLeftOutline14, { size: 14 }), "返回角色描述"), h("h2", null, "兼容报告与原件")), h("section", { className: `tavern-card-playability is-${card.playability}` }, h("strong", null, card.statusText), h("p", null, card.statusDetail)), h("div", { className: "tavern-card-compatibility-rows" }, ...card.compatibilityRows.map((row, index) => h("article", { key: `${row.capability}-${index}` }, h("div", null, h("strong", null, row.capability), h("span", null, row.disposition)), h("p", null, row.evidence)))), h("section", { className: "tavern-card-source" }, h("h3", null, "原件来源"), h("dl", null, h("dt", null, "文件"), h("dd", null, card.sourceName), h("dt", null, "格式"), h("dd", null, card.sourceFormat), h("dt", null, "Revision"), h("dd", null, card.revisionId), h("dt", null, "导入时间"), h("dd", null, card.importedAt)), h("a", { className: "tavern-native-jump", href: card.originalUrl, target: "_blank", rel: "noreferrer" }, "打开原始文件")));
1215
+ return h(React.Fragment, null, h(CapabilityPanelHeader, { title: "卡片", meta: `${sourceLabel} · ${card.openings.length} 个开场` }), h("section", { className: "tavern-card-hero", "aria-label": "当前酒馆卡" }, coverUrl === "" ? h("span", { className: "tavern-card-hero-cover is-placeholder", "aria-label": "这张 JSON 卡没有随卡封面" }, card.title.slice(0, 1)) : h("img", { className: "tavern-card-hero-cover", src: coverUrl, alt: `${card.title} 封面` }), h("div", { className: "tavern-card-hero-copy" }, h("div", { className: "tavern-card-hero-title" }, h("h1", null, card.title), h("div", { className: "tavern-card-more" }, h("button", { type: "button", "aria-haspopup": "menu", "aria-expanded": menuOpen, onClick: () => setMenuOpen(!menuOpen) }, "更多…"), menuOpen ? h("div", { className: "tavern-card-more-menu", role: "menu" }, h("button", { type: "button", role: "menuitem", onClick: () => { setTab("advanced"); setMenuOpen(false); } }, "兼容报告"), h("a", { role: "menuitem", href: card.originalUrl, target: "_blank", rel: "noreferrer", onClick: () => setMenuOpen(false) }, "打开原件")) : null)), h("p", null, subtitle), h("button", { type: "button", className: "tavern-card-start-session", disabled: !canStart, onClick: () => { if (hostCardValue !== null)
1216
+ prepareNewGame(hostCardValue); } }, card.playability === "blocked" ? "暂不可开始" : "开始新对话"))), h("nav", { className: "tavern-card-nav", role: "tablist", "aria-label": "卡片内容" }, ...navItems.map((item) => h("button", { type: "button", role: "tab", "aria-selected": tab === item.id, onClick: () => setTab(item.id), key: item.id }, item.label))), h("div", { className: `tavern-card-view is-${tab}`, role: "tabpanel" }, panel));
1217
+ }
1218
+ function PersonaCapabilityPanel({ snapshot }) {
1219
+ const sessionId = snapshot.session?.id ?? "";
1220
+ const revisionId = snapshot.card.revisionId;
1221
+ const [library, setLibrary] = React.useState(null);
1222
+ const [query, setQuery] = React.useState("");
1223
+ const [selectedId, setSelectedId] = React.useState(null);
1224
+ const [displayName, setDisplayName] = React.useState("");
1225
+ const [content, setContent] = React.useState("");
1226
+ const [avatar, setAvatar] = React.useState("default");
1227
+ const [dirty, setDirty] = React.useState(false);
1228
+ const [busy, setBusy] = React.useState(false);
1229
+ const [notice, setNotice] = React.useState(null);
1230
+ const [error, setError] = React.useState(null);
1231
+ const avatarIds = ["default", "traveler", "northern-ranger", "jianghu-wanderer"];
1232
+ const avatarUrl = (value) => `/dsh-re3-rp/persona-avatar?avatar=${encodeURIComponent(value)}`;
1233
+ const queryString = () => {
1234
+ const params = new URLSearchParams({ revision: revisionId });
1235
+ if (sessionId.length > 0)
1236
+ params.set("sessionId", sessionId);
1237
+ return params.toString();
1238
+ };
1239
+ const chooseLibraryPersona = (next, preferred) => {
1240
+ const personaId = preferred ?? next.effective?.personaId ?? next.personas[0]?.id ?? null;
1241
+ setLibrary(next);
1242
+ setSelectedId(personaId);
1243
+ };
1244
+ const refreshSnapshot = () => {
1245
+ void loadCapabilitySnapshot(sessionId.length > 0 ? { sessionId } : { revisionId });
1246
+ };
1247
+ React.useEffect(() => {
1248
+ const controller = new AbortController();
1249
+ setError(null);
1250
+ void fetch(`/dsh-re3-rp/personas?${queryString()}`, { cache: "no-store", signal: controller.signal })
1251
+ .then(async (response) => {
1252
+ const body = await response.json();
1253
+ if (!response.ok)
1254
+ throw new Error(body.error ?? "无法读取 Persona");
1255
+ if (!controller.signal.aborted)
1256
+ chooseLibraryPersona(body, body.effective?.personaId);
1257
+ })
1258
+ .catch((caught) => { if (!controller.signal.aborted)
1259
+ setError(caught instanceof Error ? caught.message : "无法读取 Persona"); });
1260
+ return () => controller.abort();
1261
+ }, [revisionId, sessionId]);
1262
+ const selected = library?.personas.find((persona) => persona.id === selectedId) ?? null;
1263
+ React.useEffect(() => {
1264
+ if (selected === null)
1265
+ return;
1266
+ setDisplayName(selected.displayName);
1267
+ setContent(selected.content);
1268
+ setAvatar(selected.avatar);
1269
+ setDirty(false);
1270
+ setNotice(null);
1271
+ setError(null);
1272
+ }, [selected?.id, selected?.updatedAt]);
1273
+ const post = async (payload) => {
1274
+ const response = await fetch("/dsh-re3-rp/personas", {
1275
+ method: "POST",
1276
+ headers: { "Content-Type": "application/json" },
1277
+ body: JSON.stringify({ ...payload, revisionId, ...(sessionId.length === 0 ? {} : { sessionId }) }),
1278
+ });
1279
+ const body = await response.json();
1280
+ if (!response.ok)
1281
+ throw new Error(body.error ?? "Persona 操作失败");
1282
+ return body;
1283
+ };
1284
+ const runAction = async (action) => {
1285
+ setBusy(true);
1286
+ setError(null);
1287
+ setNotice(null);
1288
+ try {
1289
+ await action();
1290
+ }
1291
+ catch (caught) {
1292
+ setError(caught instanceof Error ? caught.message : "Persona 操作失败");
1293
+ }
1294
+ finally {
1295
+ setBusy(false);
1296
+ }
1297
+ };
1298
+ const createPersona = () => {
1299
+ void runAction(async () => {
1300
+ const next = await post({ action: "create" });
1301
+ chooseLibraryPersona(next, next.createdPersonaId ?? null);
1302
+ setNotice("已创建 Persona,可以继续填写并保存。");
1303
+ });
1304
+ };
1305
+ const savePersona = () => {
1306
+ if (selected === null)
1307
+ return;
1308
+ void runAction(async () => {
1309
+ const next = await post({ action: "update", personaId: selected.id, displayName, content, avatar });
1310
+ chooseLibraryPersona(next, selected.id);
1311
+ setDirty(false);
1312
+ setNotice("修改已保存,已绑定的后续生成会使用新版内容。");
1313
+ refreshSnapshot();
1314
+ });
1315
+ };
1316
+ const bindPersona = (scope) => {
1317
+ if (selected === null)
1318
+ return;
1319
+ void runAction(async () => {
1320
+ const next = await post({ action: "bind", personaId: selected.id, scope });
1321
+ chooseLibraryPersona(next, selected.id);
1322
+ setNotice(scope === "session" ? "已切换到当前 Session。" : scope === "card" ? "已切换到当前酒馆卡;当前 Session 覆盖已取消。" : "已切换为全局默认;当前酒馆卡与 Session 覆盖已取消。");
1323
+ refreshSnapshot();
1324
+ });
1325
+ };
1326
+ const clearEffectiveBinding = () => {
1327
+ if (library?.effective === null || library?.effective === undefined)
1328
+ return;
1329
+ void runAction(async () => {
1330
+ const next = await post({ action: "clear", scope: library.effective.scope });
1331
+ chooseLibraryPersona(next, selectedId);
1332
+ setNotice("已取消当前生效的 Persona 绑定。");
1333
+ refreshSnapshot();
1334
+ });
1335
+ };
1336
+ const visible = (library?.personas ?? []).filter((persona) => `${persona.displayName}\n${persona.content}`.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
1337
+ const effectivePersona = library?.personas.find((persona) => persona.id === library.effective?.personaId) ?? null;
1338
+ const bindingLabel = (scope) => scope === "global" ? "全局默认" : scope === "card" ? "当前酒馆卡" : "当前 Session";
1339
+ const personaTokenSource = `${displayName}\n${content}`;
1340
+ const nonAsciiCharacters = Array.from(personaTokenSource).filter((character) => (character.codePointAt(0) ?? 0) > 0x7f).length;
1341
+ const asciiCharacters = personaTokenSource.length - nonAsciiCharacters;
1342
+ const tokenEstimate = Math.max(1, nonAsciiCharacters + Math.ceil(asciiCharacters / 4));
1343
+ const listRows = library === null
1344
+ ? [h("p", { className: "tavern-persona-empty", key: "loading" }, error ?? "正在读取…")]
1345
+ : visible.length === 0
1346
+ ? [h("p", { className: "tavern-persona-empty", key: "empty" }, library.personas.length === 0 ? "还没有 Persona。\n点击上方新建。" : "没有匹配的 Persona。")]
1347
+ : visible.map((persona) => h("button", {
1348
+ type: "button",
1349
+ key: persona.id,
1350
+ role: "option",
1351
+ "aria-selected": persona.id === selectedId,
1352
+ onClick: () => setSelectedId(persona.id),
1353
+ }, h("img", { src: avatarUrl(persona.avatar), alt: "" }), h("span", null, persona.displayName), library.effective?.personaId === persona.id ? h(IconCheckOutline14, { size: 14 }) : null));
1354
+ const scopeButtons = ["global", "card", "session"].map((scope) => h("button", {
1355
+ type: "button",
1356
+ key: scope,
1357
+ disabled: busy || (scope === "session" && sessionId.length === 0),
1358
+ "aria-pressed": library?.effective?.scope === scope && library.effective.personaId === selected?.id,
1359
+ onClick: () => bindPersona(scope),
1360
+ }, bindingLabel(scope)));
1361
+ const editorContent = selected === null
1362
+ ? h("div", { className: "tavern-persona-empty is-roomy" }, library?.personas.length === 0 ? "新建 Persona 后,可在这里填写名字与描述。" : "选择一个 Persona 进行编辑。")
1363
+ : h(React.Fragment, null, h("div", { className: "tavern-persona-identity" }, h("button", { type: "button", className: "tavern-persona-avatar", title: "更换头像", onClick: () => { setAvatar(avatarIds[(avatarIds.indexOf(avatar) + 1) % avatarIds.length]); setDirty(true); } }, h("img", { src: avatarUrl(avatar), alt: `${displayName || "Persona"} 头像` })), h("label", null, h("span", null, "名称"), h("input", { value: displayName, maxLength: 64, onChange: (event) => { setDisplayName(event.target.value); setDirty(true); } }))), h("label", { className: "tavern-persona-description" }, h("span", null, "描述(角色设定、性格、背景等)"), h("textarea", { value: content, maxLength: 12000, placeholder: "例如:你是一位习惯独自旅行的江湖客……", onChange: (event) => { setContent(event.target.value); setDirty(true); } })), h("div", { className: "tavern-persona-save-row" }, h("span", null, `约 ${tokenEstimate} Token`), h("div", null, h("small", { className: dirty ? "is-dirty" : "is-saved" }, dirty ? null : h(IconCheckOutline14, { size: 14 }), dirty ? "尚未保存" : "已保存"), h("button", { type: "button", disabled: busy || !dirty || displayName.trim().length === 0, onClick: savePersona }, "保存"))), h("section", { className: "tavern-persona-binding" }, h("header", null, h("strong", null, "生效范围"), library?.effective == null ? null : h("button", { type: "button", disabled: busy, onClick: clearEffectiveBinding }, "取消当前绑定")), h("div", { className: "tavern-persona-scope", role: "group", "aria-label": "Persona 生效范围" }, ...scopeButtons), h("div", { className: "tavern-persona-session" }, h("span", null, "当前 Session"), h("strong", null, sessionId.length === 0 ? "打开 Session 后可单独绑定" : snapshot.card.title)), h("p", { className: "tavern-persona-priority" }, "优先级:当前 Session > 当前酒馆卡 > 全局默认"), h("p", { className: "tavern-persona-guidance" }, "卡片设定优先;主动启用后才注入详细人设。"), h("p", { className: effectivePersona === null ? "tavern-persona-effective is-empty" : "tavern-persona-effective" }, effectivePersona === null ? "未选择 Persona,将沿用开局填写的玩家名字。" : `当前生效:${effectivePersona.displayName} · ${bindingLabel(library.effective.scope)}`)), notice === null ? null : h("p", { className: "tavern-persona-notice", role: "status" }, notice), error === null ? null : h("p", { className: "tavern-persona-error", role: "alert" }, error));
1364
+ const libraryPane = h("aside", { className: "tavern-persona-library", "aria-label": "Persona 列表" }, h("button", { type: "button", className: "tavern-persona-create", disabled: busy, onClick: createPersona }, h(IconPlusOutline16, { size: 16 }), h("span", null, "新建")), h("label", { className: "tavern-persona-search" }, h(IconSearchOutline16, { size: 15 }), h("input", { value: query, placeholder: "搜索 Persona", onChange: (event) => setQuery(event.target.value) })), h("div", { className: "tavern-persona-list", role: "listbox", "aria-label": "可用 Persona" }, ...listRows));
1365
+ return h(React.Fragment, null, h(CapabilityPanelHeader, { title: "Persona", meta: "管理玩家身份与当前会话绑定" }), h("div", { className: "tavern-persona-workbench" }, libraryPane, h("section", { className: "tavern-persona-editor" }, editorContent)));
1366
+ }
1367
+ function CapabilityPanel({ capability, snapshot, diagnostics }) {
1368
+ if (capability === "worldbook")
1369
+ return h(WorldbookCapabilityPanel, { snapshot });
1370
+ if (capability === "preset")
1371
+ return h(PresetCapabilityPanel, { snapshot });
1372
+ if (capability === "regex")
1373
+ return h(RegexCapabilityPanel, { snapshot });
1374
+ if (capability === "frontend")
1375
+ return h(FrontendCapabilityPanel, { snapshot, diagnostics });
1376
+ if (capability === "card")
1377
+ return h(CardCapabilityPanel, { snapshot });
1378
+ return h(PersonaCapabilityPanel, { snapshot });
1379
+ }
1380
+ function CapabilityRail() {
1381
+ const stateSnapshot = usePrototypeState();
1382
+ const [capability, setCapability] = React.useState("worldbook");
1383
+ const [panelOpen, setPanelOpen] = React.useState(false);
1384
+ React.useEffect(() => {
1385
+ const open = (event) => {
1386
+ const next = event.detail;
1387
+ if (["worldbook", "preset", "regex", "frontend", "card", "persona"].includes(next)) {
1388
+ setCapability(next);
1389
+ setPanelOpen(true);
1390
+ }
1391
+ };
1392
+ window.addEventListener("dsh-re3-rp:open-capability", open);
1393
+ return () => window.removeEventListener("dsh-re3-rp:open-capability", open);
1394
+ }, []);
1395
+ if (stateSnapshot.sidebarMode !== "tavern")
1396
+ return null;
1397
+ const data = stateSnapshot.capabilitySnapshot;
1398
+ const loadedTargetKey = data === null
1399
+ ? ""
1400
+ : data.session === null
1401
+ ? `revision:${data.card.revisionId}`
1402
+ : `session:${data.session.id}`;
1403
+ const loadingDifferentTarget = stateSnapshot.capabilityLoading && loadedTargetKey !== stateSnapshot.capabilityTargetKey;
1404
+ const items = [
1405
+ { id: "worldbook", label: "世界书", icon: IconDataOutline16, group: 0 },
1406
+ { id: "preset", label: "预设", icon: IconAgentPresetOutline16, group: 0 },
1407
+ { id: "regex", label: "Regex", icon: IconSkillOutline16, group: 1 },
1408
+ { id: "frontend", label: "前端", icon: IconProjectAddOutline16, group: 1 },
1409
+ { id: "card", label: "卡片", icon: IconFolderOpenOutline16, group: 2 },
1410
+ { id: "persona", label: "Persona", icon: IconUserOutline16, group: 2 },
1411
+ ];
1412
+ const selectCapability = (next) => {
1413
+ if (next === capability && panelOpen) {
1414
+ setPanelOpen(false);
1415
+ return;
1416
+ }
1417
+ setCapability(next);
1418
+ setPanelOpen(true);
1419
+ };
1420
+ const diagnostics = stateSnapshot.runtimeDiagnostics.filter((item) => item.sessionId === data?.session?.id);
1421
+ const railButtons = items.map((item, index) => h("button", {
1422
+ type: "button",
1423
+ className: `${capability === item.id && panelOpen ? "is-active" : ""}${index > 0 && item.group !== items[index - 1]?.group ? " is-group-start" : ""}`,
1424
+ "aria-pressed": capability === item.id && panelOpen,
1425
+ onClick: () => selectCapability(item.id),
1426
+ key: item.id,
1427
+ }, h(item.icon, { size: 20 }), h("span", null, item.label)));
1428
+ return h(React.Fragment, null, h("aside", { className: `tavern-capability-panel ${panelOpen ? "is-open" : "is-closed"}`, id: "tavern-capability-panel", "data-capability": capability, "aria-hidden": !panelOpen, "aria-label": `${items.find((item) => item.id === capability)?.label ?? "酒馆"}能力面板` }, loadingDifferentTarget ? h("div", { className: "tavern-capability-loading", "aria-live": "polite" }, "正在读取真实卡片与 Session 数据…") : stateSnapshot.capabilityError !== null ? h("div", { className: "tavern-capability-loading is-error", role: "alert" }, stateSnapshot.capabilityError) : data === null ? h("div", { className: "tavern-capability-loading" }, "选择一张酒馆卡后查看能力。") : h(CapabilityPanel, { capability, snapshot: data, diagnostics, key: `${capability}:${data.card.revisionId}:${data.session?.id ?? "card"}` })), h("nav", { className: "tavern-card-capability-rail", "aria-label": "酒馆能力", "data-panel-open": String(panelOpen) }, h("button", { type: "button", className: "tavern-capability-handle", "aria-controls": "tavern-capability-panel", "aria-expanded": panelOpen, "aria-label": panelOpen ? "收起能力窗口" : "展开能力窗口", title: panelOpen ? "收起能力窗口" : "展开能力窗口", onClick: () => setPanelOpen(!panelOpen) }, panelOpen ? h(IconChevronRightOutline14, { size: 18 }) : h(IconChevronLeftOutline14, { size: 18 })), ...railButtons));
1429
+ }
1430
+ function TavernOpeningSwitcher(props) {
1431
+ const sessionId = props.sessionId;
1432
+ const [openingState, setOpeningState] = React.useState(undefined);
1433
+ const [expanded, setExpanded] = React.useState(false);
1434
+ const [busy, setBusy] = React.useState(false);
1435
+ const [error, setError] = React.useState(null);
1436
+ React.useEffect(() => {
1437
+ const controller = new AbortController();
1438
+ void fetch(`/dsh-re3-rp/opening?sessionId=${encodeURIComponent(sessionId)}&optional=1`, { cache: "no-store", signal: controller.signal })
1439
+ .then(async (response) => response.status === 204 ? null : response.ok ? await response.json() : null)
1440
+ .then((value) => { if (!controller.signal.aborted)
1441
+ setOpeningState(value); })
1442
+ .catch(() => { if (!controller.signal.aborted)
1443
+ setOpeningState(null); });
1444
+ return () => controller.abort();
1445
+ }, [sessionId, props.session]);
1446
+ React.useLayoutEffect(() => {
1447
+ if (openingState === null || openingState === undefined || openingState.currentIndex === 0)
1448
+ return;
1449
+ const applyOpeningRender = () => {
1450
+ const openingFlow = document.querySelector('[data-chat-flow-key$=":assistant-step1:1"]');
1451
+ const paragraph = openingFlow?.querySelector("p");
1452
+ const original = paragraph?.parentElement;
1453
+ if (original === undefined || original.dataset.tavernOpeningSource === "true")
1454
+ return;
1455
+ original.dataset.tavernOpeningSource = "true";
1456
+ original.hidden = true;
1457
+ const replacement = document.createElement("div");
1458
+ replacement.className = "tavern-opening-message";
1459
+ replacement.dataset.tavernOpeningRender = sessionId;
1460
+ replacement.textContent = openingState.currentMessage;
1461
+ original.before(replacement);
1462
+ };
1463
+ const clearOpeningRender = () => {
1464
+ document.querySelectorAll(`[data-tavern-opening-render="${CSS.escape(sessionId)}"]`).forEach((node) => node.remove());
1465
+ document.querySelectorAll('[data-tavern-opening-source="true"]').forEach((node) => {
1466
+ node.hidden = false;
1467
+ delete node.dataset.tavernOpeningSource;
1468
+ });
1469
+ };
1470
+ clearOpeningRender();
1471
+ applyOpeningRender();
1472
+ const observer = new MutationObserver(applyOpeningRender);
1473
+ observer.observe(document.body, { childList: true, subtree: true });
1474
+ return () => { observer.disconnect(); clearOpeningRender(); };
1475
+ }, [sessionId, openingState?.currentIndex, openingState?.currentMessage]);
1476
+ const choose = async (openingId) => {
1477
+ if (openingState === null || openingState === undefined || openingState.locked || busy || openingId === openingState.openingId)
1478
+ return;
1479
+ setBusy(true);
1480
+ setError(null);
1481
+ try {
1482
+ const response = await fetch("/dsh-re3-rp/opening", {
1483
+ method: "POST",
1484
+ headers: { "Content-Type": "application/json" },
1485
+ body: JSON.stringify({ sessionId, openingId }),
1486
+ });
1487
+ const result = await response.json();
1488
+ if (!response.ok)
1489
+ throw new Error(result.error ?? "无法切换开场");
1490
+ setOpeningState(result);
1491
+ setExpanded(false);
1492
+ await refreshHostCards();
1493
+ }
1494
+ catch (cause) {
1495
+ setError(cause instanceof Error ? cause.message : "无法切换开场");
1496
+ }
1497
+ finally {
1498
+ setBusy(false);
1499
+ }
1500
+ };
1501
+ if (openingState === null || openingState === undefined || openingState.openings.length <= 1)
1502
+ return null;
1503
+ const adjacent = (offset) => openingState.openings[(openingState.currentIndex + offset + openingState.openings.length) % openingState.openings.length].id;
1504
+ return h("section", { className: "tavern-opening-dock", "data-tavern-opening-switcher": openingState.locked ? "locked" : "draft", "aria-label": "开场选择" }, h("div", { className: "tavern-opening-dock-panel" }, h("div", { className: "tavern-opening-dock-toolbar" }, h("span", { className: "tavern-opening-toolbar-spacer", "aria-hidden": "true" }), h("div", { className: "tavern-opening-navigation" }, h("button", { type: "button", title: "上一个开场", "aria-label": "上一个开场", disabled: openingState.locked || busy, onClick: () => void choose(adjacent(-1)) }, h(IconChevronLeftOutline14, { size: 14 })), h("span", { className: "tavern-opening-current" }, openingState.locked ? "开场已锁定" : `开场 ${openingState.currentIndex + 1} / ${openingState.openings.length}`), h("button", { type: "button", title: "下一个开场", "aria-label": "下一个开场", disabled: openingState.locked || busy, onClick: () => void choose(adjacent(1)) }, h(IconChevronRightOutline14, { size: 14 }))), h("button", { type: "button", className: `tavern-opening-disclosure${expanded ? " is-expanded" : ""}`, title: expanded ? "收起开场列表" : "展开开场列表", "aria-label": expanded ? "收起开场列表" : "展开开场列表", "aria-expanded": expanded, disabled: busy, onClick: () => setExpanded(!expanded) }, h(IconChevronDownOutline14, { size: 14 }))), expanded ? h("div", { className: "tavern-opening-menu", role: "listbox", "aria-label": "选择备选开场" }, ...openingState.openings.map((opening) => h("button", { type: "button", role: "option", "aria-selected": opening.id === openingState.openingId, disabled: openingState.locked || busy, key: opening.id, onClick: () => void choose(opening.id) }, h("span", { className: "tavern-opening-option-copy" }, h("strong", null, `${opening.index + 1}.`), h("small", null, opening.preview))))) : null, error === null ? null : h("p", { className: "tavern-opening-error", role: "alert" }, error)));
1505
+ }
1506
+ function nativeRichSrcDoc(content, token, variableState, frontendStorage, cardTitle, messageCount) {
1507
+ const adapted = adaptRealCardFrontendHtml(content)
1508
+ .replaceAll("window.parent.frontendTestHost", "window.__dshGameHost")
1509
+ .replaceAll("window.parent.frontendTestStateHost", "window.__dshGameStateHost");
1510
+ const encodedToken = JSON.stringify(token).replace(/<\//gu, "<\\/");
1511
+ const encodedState = JSON.stringify(variableState).replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
1512
+ const encodedStorage = JSON.stringify(frontendStorage).replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
1513
+ const encodedTitle = JSON.stringify(cardTitle).replace(/</gu, "\\u003c");
1514
+ const encodedHeightMeasure = measureRichFrameContentHeight.toString().replace(/<\//gu, "<\\/");
1515
+ const encodedContextBuilder = buildSillyTavernCompatibilityContext.toString().replace(/<\//gu, "<\\/");
1516
+ const baseHref = new URL(".", window.location.href).href.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1517
+ const messageId = Number(token.split(":", 1)[0]) || -1;
1518
+ const bootstrap = `<script>(()=>{
1519
+ const token=${encodedToken};
1520
+ const cardTitle=${encodedTitle};
1521
+ let currentMessageId=${messageId};
1522
+ let messageCount=${messageCount};
1523
+ const companion=token.startsWith('companion:');
1524
+ let cardState=${encodedState};
1525
+ const storageEntries=new Map(Object.entries(${encodedStorage}));
1526
+ let nextId=0;
1527
+ const pending=new Map();
1528
+ const measureRichFrameContentHeight=${encodedHeightMeasure};
1529
+ const buildSillyTavernCompatibilityContext=${encodedContextBuilder};
1530
+ const clone=value=>value===undefined?undefined:JSON.parse(JSON.stringify(value));
1531
+ const merge=(left,right)=>{const out=clone(left||{});for(const [key,value] of Object.entries(right||{})){out[key]=value&&typeof value==='object'&&!Array.isArray(value)&&out[key]&&typeof out[key]==='object'&&!Array.isArray(out[key])?merge(out[key],value):clone(value)}return out};
1532
+ const pathParts=path=>(Array.isArray(path)?path:String(path??'').split('[').join('.').split(']').join('').split('.')).map(part=>String(part).replace(/^['"]|['"]$/gu,'')).filter(Boolean);
1533
+ const safePath=parts=>parts.every(part=>part!=='__proto__'&&part!=='prototype'&&part!=='constructor');
1534
+ const lodash=Object.freeze({
1535
+ get(object,path,defaultValue){const parts=pathParts(path);if(!safePath(parts))return defaultValue;let cursor=object;for(const part of parts){if(cursor==null)return defaultValue;cursor=cursor[part]}return cursor===undefined?defaultValue:cursor},
1536
+ has(object,path){const parts=pathParts(path);if(!safePath(parts))return false;let cursor=object;for(const part of parts){if(cursor==null||!Object.prototype.hasOwnProperty.call(Object(cursor),part))return false;cursor=cursor[part]}return true},
1537
+ set(object,path,value){const parts=pathParts(path);if(object==null||parts.length===0||!safePath(parts))return object;let cursor=object;for(let index=0;index<parts.length-1;index++){const part=parts[index];const next=parts[index+1];if(cursor[part]==null||typeof cursor[part]!=='object')cursor[part]=/^\d+$/u.test(next)?[]:{};cursor=cursor[part]}cursor[parts.at(-1)]=value;return object},
1538
+ unset(object,path){const parts=pathParts(path);if(object==null||parts.length===0||!safePath(parts))return false;let cursor=object;for(const part of parts.slice(0,-1)){if(cursor==null||typeof cursor!=='object')return true;cursor=cursor[part]}if(cursor==null||typeof cursor!=='object')return true;return delete cursor[parts.at(-1)]},
1539
+ omit(object,paths){const out=clone(object||{});for(const path of Array.isArray(paths)?paths:[paths])lodash.unset(out,path);return out},
1540
+ cloneDeep(value){return clone(value)},
1541
+ isPlainObject(value){if(value===null||typeof value!=='object')return false;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null},
1542
+ isEqual(left,right){if(Object.is(left,right))return true;if(typeof left!==typeof right||left===null||right===null||typeof left!=='object')return false;if(Array.isArray(left)!==Array.isArray(right))return false;const leftKeys=Object.keys(left);const rightKeys=Object.keys(right);return leftKeys.length===rightKeys.length&&leftKeys.every(key=>Object.prototype.hasOwnProperty.call(right,key)&&lodash.isEqual(left[key],right[key]))}
1543
+ });
1544
+ const selectNodes=selector=>{const selected=String(selector).includes('option:selected');const normalized=selected?String(selector).split('option:selected').join('option'):String(selector);const nodes=[...document.querySelectorAll(normalized)];return selected?nodes.filter(node=>node.selected):nodes};
1545
+ const miniEventRegistry=new WeakMap();
1546
+ class MiniQuery{
1547
+ constructor(nodes){this.nodes=[...new Set(nodes.filter(Boolean))];this.length=this.nodes.length;this.nodes.forEach((node,index)=>{this[index]=node})}
1548
+ each(callback){this.nodes.forEach((node,index)=>callback.call(node,index,node));return this}
1549
+ on(events,selectorOrHandler,maybeHandler){const delegated=typeof selectorOrHandler==='string';const handler=delegated?maybeHandler:selectorOrHandler;for(const eventName of String(events).split(' ').filter(Boolean)){const [type,...namespaceParts]=eventName.split('.');if(!type)continue;const namespace=namespaceParts.join('.');this.each((_index,node)=>{const wrapped=event=>{try{if(!('originalEvent' in event))Object.defineProperty(event,'originalEvent',{value:event})}catch{}if(!delegated)return handler.call(node,event);const target=event.target?.closest?.(selectorOrHandler);if(target&&node.contains(target))return handler.call(target,event)};node.addEventListener(type,wrapped);const records=miniEventRegistry.get(node)||[];records.push({type,namespace,handler,wrapped});miniEventRegistry.set(node,records)})}return this}
1550
+ off(events){const filters=events===undefined?[]:String(events).split(' ').filter(Boolean).map(value=>{const [type,...parts]=value.split('.');return{type,namespace:parts.join('.')}});return this.each((_index,node)=>{const records=miniEventRegistry.get(node)||[];const keep=[];for(const record of records){const matched=filters.length===0||filters.some(filter=>(!filter.type||filter.type===record.type)&&(!filter.namespace||filter.namespace===record.namespace));if(matched)node.removeEventListener(record.type,record.wrapped);else keep.push(record)}miniEventRegistry.set(node,keep)})}
1551
+ trigger(eventName){return this.each((_index,node)=>node.dispatchEvent(new Event(String(eventName),{bubbles:true,cancelable:true})))}
1552
+ click(handler){return handler===undefined?this.trigger('click'):this.on('click',handler)}
1553
+ change(handler){return handler===undefined?this.trigger('change'):this.on('change',handler)}
1554
+ html(value){if(value===undefined)return this.nodes[0]?.innerHTML;return this.each((_index,node)=>{node.innerHTML=String(value??'')})}
1555
+ text(value){if(value===undefined)return this.nodes[0]?.textContent??'';return this.each((_index,node)=>{node.textContent=String(value??'')})}
1556
+ val(value){const node=this.nodes[0];if(value===undefined)return node?.value;return this.each((_index,item)=>{item.value=String(value??'')})}
1557
+ attr(name,value){if(value===undefined&&typeof name==='string')return this.nodes[0]?.getAttribute(name);if(name&&typeof name==='object')return this.each((_index,node)=>{for(const [key,item] of Object.entries(name))node.setAttribute(key,String(item))});return this.each((_index,node)=>node.setAttribute(String(name),String(value)))}
1558
+ prop(name,value){if(value===undefined)return this.nodes[0]?.[name];return this.each((_index,node)=>{node[name]=value})}
1559
+ data(name,value){const node=this.nodes[0];const key=String(name).replace(/-([a-z])/gu,(_match,letter)=>letter.toUpperCase());if(value!==undefined)return this.each((_index,item)=>{item.dataset[key]=String(value)});const raw=node?.dataset?.[key];if(raw===undefined)return undefined;if(raw==='true'||raw==='false')return raw==='true';if(raw!==''&&!Number.isNaN(Number(raw)))return Number(raw);try{return JSON.parse(raw)}catch{return raw}}
1560
+ css(name,value){if(name&&typeof name==='object')return this.each((_index,node)=>{for(const [key,item] of Object.entries(name)){if(String(key).includes('-'))node.style.setProperty(key,String(item));else node.style[key]=String(item)}});if(value===undefined)return getComputedStyle(this.nodes[0]||document.documentElement).getPropertyValue(String(name));return this.each((_index,node)=>{if(String(name).includes('-'))node.style.setProperty(String(name),String(value));else node.style[name]=String(value)})}
1561
+ addClass(names){const parts=String(names).split(' ').filter(Boolean);return this.each((_index,node)=>node.classList.add(...parts))}
1562
+ removeClass(names){const parts=String(names??'').split(' ').filter(Boolean);return this.each((_index,node)=>parts.length?node.classList.remove(...parts):node.removeAttribute('class'))}
1563
+ hasClass(name){return this.nodes[0]?.classList.contains(String(name))===true}
1564
+ hide(){return this.css('display','none')}
1565
+ show(){return this.each((_index,node)=>{node.style.display='';if(getComputedStyle(node).display==='none')node.style.display='block'})}
1566
+ remove(){return this.each((_index,node)=>node.remove())}
1567
+ is(selector){const node=this.nodes[0];if(!node)return false;if(selector===':checked')return node.checked===true;if(selector===':visible')return getComputedStyle(node).display!=='none'&&getComputedStyle(node).visibility!=='hidden';return node.matches(String(selector))}
1568
+ closest(selector){return new MiniQuery(this.nodes.map(node=>node.closest?.(selector)))}
1569
+ siblings(selector){const nodes=this.nodes.flatMap(node=>[...(node.parentElement?.children||[])].filter(item=>item!==node&&(!selector||item.matches(selector))));return new MiniQuery(nodes)}
1570
+ find(selector){return new MiniQuery(this.nodes.flatMap(node=>[...node.querySelectorAll(selector)]))}
1571
+ next(selector){return new MiniQuery(this.nodes.map(node=>node.nextElementSibling).filter(node=>node&&(!selector||node.matches(selector))))}
1572
+ append(value){return this.each((_index,node)=>{if(typeof value==='string')node.insertAdjacentHTML('beforeend',value);else if(value?.nodeType)node.append(value.cloneNode(true))})}
1573
+ prepend(value){return this.each((_index,node)=>{if(typeof value==='string')node.insertAdjacentHTML('afterbegin',value);else if(value?.nodeType)node.prepend(value.cloneNode(true))})}
1574
+ toggle(force){return this.each((_index,node)=>{const show=force===undefined?getComputedStyle(node).display==='none':Boolean(force);node.style.display=show?'':'none'})}
1575
+ slideToggle(){return this.toggle()}
1576
+ scrollTop(value){const node=this.nodes[0];if(value===undefined)return node?.scrollTop??0;return this.each((_index,item)=>{item.scrollTop=Number(value)||0})}
1577
+ }
1578
+ function miniDollar(input){if(typeof input==='function'){const run=()=>input(miniDollar);if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',run,{once:true});else queueMicrotask(run);return new MiniQuery([])}if(input instanceof MiniQuery)return input;if(typeof input==='string')return new MiniQuery(selectNodes(input));if(input==null)return new MiniQuery([]);return new MiniQuery(Array.isArray(input)?input:[input])}
1579
+ const operation=kind=>'card-ui:'+kind+':'+(crypto.randomUUID?.()||Date.now()+':'+(++nextId));
1580
+ function send(action,payload={}){const id=++nextId;parent.postMessage({source:'dsh-re3-rp-rich-frame',token,kind:'request',id,action,payload},'*');return new Promise((resolve,reject)=>pending.set(id,{resolve,reject}));}
1581
+ const observeCompatibilityCall=(surface,method)=>{void send('reportCompatibilityCall',{surface,method,operationId:operation('compat-call')}).catch(()=>{})};
1582
+ let storageTimer;
1583
+ const persistStorage=()=>{clearTimeout(storageTimer);storageTimer=setTimeout(()=>{void send('replaceCardStorage',{entries:Object.fromEntries(storageEntries)}).catch(()=>{})},40)};
1584
+ const memoryStorage=Object.freeze({get length(){return storageEntries.size},key(index){return [...storageEntries.keys()][Number(index)]??null},getItem(key){const value=storageEntries.get(String(key));return value===undefined?null:value},setItem(key,value){storageEntries.set(String(key),String(value));persistStorage()},removeItem(key){storageEntries.delete(String(key));persistStorage()},clear(){storageEntries.clear();persistStorage()}});
1585
+ try{Object.defineProperty(window,'localStorage',{value:memoryStorage,configurable:false})}catch{}
1586
+ addEventListener('message',event=>{const data=event.data;if(event.source!==parent||data?.source!=='dsh-re3-rp-rich-host'||data.token!==token||data.kind!=='response')return;const task=pending.get(data.id);if(!task)return;pending.delete(data.id);if(data.ok)task.resolve(data.result);else task.reject(Object.assign(new Error(data.error?.message||'Bridge unavailable'),{code:data.error?.code||'bridge_unavailable'}));});
1587
+ let lastReportedHeight=0;
1588
+ const visibleFixedNode=node=>{const style=getComputedStyle(node);const rect=node.getBoundingClientRect();return style.position==='fixed'&&style.display!=='none'&&style.visibility!=='hidden'&&style.pointerEvents!=='none'&&rect.width>0&&rect.height>0};
1589
+ const fixedCompanionNodes=()=>[...document.body.querySelectorAll('*')].filter(visibleFixedNode);
1590
+ const hasFixedAncestor=node=>{let parent=node.parentElement;while(parent&&parent!==document.body){if(getComputedStyle(parent).position==='fixed')return true;parent=parent.parentElement}return false};
1591
+ const fixedNodeScore=node=>{const rect=node.getBoundingClientRect();const z=Number.parseInt(getComputedStyle(node).zIndex,10)||0;return z*100000000+rect.width*rect.height};
1592
+ const discoverCompanionTarget=()=>{const explicit=document.querySelector('[data-dsh-companion-root]');if(explicit)return explicit;const viewportArea=Math.max(1,innerWidth*innerHeight);return fixedCompanionNodes().filter(node=>!hasFixedAncestor(node)).filter(node=>{const rect=node.getBoundingClientRect();const viewportCoverage=rect.width*rect.height/viewportArea;return viewportCoverage<0.98&&(rect.width<innerWidth*0.95||rect.height<innerHeight*0.95)}).sort((left,right)=>fixedNodeScore(right)-fixedNodeScore(left))[0]};
1593
+ const discoverCompanionDragLayer=target=>{const explicit=document.querySelector('[data-dsh-drag-overlay]');if(explicit)return explicit;return fixedCompanionNodes().filter(node=>node!==target).filter(node=>{const rect=node.getBoundingClientRect();return rect.width>=innerWidth*0.9&&rect.height>=innerHeight*0.9}).sort((left,right)=>fixedNodeScore(right)-fixedNodeScore(left))[0]};
1594
+ const report=()=>{if(companion){const target=discoverCompanionTarget();const rect=target?.getBoundingClientRect();const dragOverlay=discoverCompanionDragLayer(target);const dragRect=dragOverlay?.getBoundingClientRect();const active=target!==null&&target!==undefined&&rect!==undefined&&visibleFixedNode(target);const dragging=dragOverlay!==null&&dragOverlay!==undefined&&dragRect!==undefined&&visibleFixedNode(dragOverlay);const x=active?Math.floor(rect.left):0;const y=active?Math.floor(rect.top):0;const width=active?Math.ceil(rect.width):0;const height=active?Math.ceil(rect.height):0;const signature=(active?'on:':'off:')+(dragging?'drag:':'rest:')+x+':'+y+':'+width+':'+height;if(signature===lastReportedHeight)return;lastReportedHeight=signature;parent.postMessage({source:'dsh-re3-rp-rich-frame',token,kind:'overlay',active,dragging,x,y,width,height},'*');return}const body=document.body;const rect=body.getBoundingClientRect();const nestedScrollableOverflowHeight=[...body.querySelectorAll('*')].reduce((total,node)=>{const clientHeight=Number(node.clientHeight)||0;const scrollHeight=Number(node.scrollHeight)||0;if(scrollHeight<=clientHeight)return total;const nodeRect=node.getBoundingClientRect();if(nodeRect.width<=0&&nodeRect.height<=0)return total;const style=getComputedStyle(node);if(style.overflowY==='auto'||style.overflowY==='scroll')return total+scrollHeight-clientHeight;return total},0);const height=measureRichFrameContentHeight({bodyScrollHeight:body.scrollHeight,bodyOffsetHeight:body.offsetHeight,bodyRectTop:rect.top,bodyRectBottom:rect.bottom,nestedScrollableOverflowHeight});if(height===lastReportedHeight)return;lastReportedHeight=height;parent.postMessage({source:'dsh-re3-rp-rich-frame',token,kind:'resize',height},'*')};
1595
+ const scheduleReport=()=>requestAnimationFrame(report);
1596
+ addEventListener('load',scheduleReport);new ResizeObserver(scheduleReport).observe(document.body);new MutationObserver(scheduleReport).observe(document.body,{attributes:true,childList:true,subtree:true});
1597
+ if(companion)setInterval(report,120);
1598
+ const setDraft=text=>send('setDraft',{text:String(text??'')});
1599
+ const submitTurn=text=>send('submitTurn',{text:String(text??''),operationId:operation('turn')});
1600
+ let draft='';
1601
+ const textarea={get value(){return draft},set value(value){draft=String(value??'')},focus(){},dispatchEvent(event){if(event?.type==='input'||event?.type==='change')void setDraft(draft);return true}};
1602
+ const sendButton={click(){if(draft.trim())void submitTurn(draft)}};
1603
+ const compatDocument=Object.freeze({querySelector(selector){return selector==='#send_textarea'?textarea:selector==='#send_but'?sendButton:null},getElementById(id){return id==='send_textarea'?textarea:id==='send_but'?sendButton:null}});
1604
+ async function triggerSlash(command){observeCompatibilityCall('TavernHelper','triggerSlash');const source=String(command??'');const set=/^\\/setinput\\s+([\\s\\S]*)$/u.exec(source);if(set)return setDraft(set[1]);const submit=/^\\/send\\s+([\\s\\S]*?)(?:\\s*\\|\\/trigger\\s*)?$/u.exec(source);if(submit)return submitTurn(submit[1]);throw Object.assign(new Error('Unsupported Tavern slash command'),{code:'capability_denied'});}
1605
+ async function generate(config={}){observeCompatibilityCall('TavernHelper','generate');const operationId=operation('generate');const source=config&&typeof config==='object'?config:{};const signal=source.signal;const payload={...source,operationId};delete payload.signal;const cancel=()=>{void send('cancelGenerate',{operationId}).catch(()=>{})};if(signal?.aborted)throw Object.assign(new Error('Auxiliary generation cancelled'),{code:'generation_cancelled'});signal?.addEventListener?.('abort',cancel,{once:true});try{const result=await send('generate',payload);return String(result?.text??'')}finally{signal?.removeEventListener?.('abort',cancel)}}
1606
+ async function refreshState(){const result=await send('getCardState');cardState=clone(result.state||{});return clone(cardState)}
1607
+ const cardEvents=new EventTarget();
1608
+ const tavernEvents=Object.freeze({MESSAGE_RECEIVED:'message_received'});
1609
+ const mvuEvents=Object.freeze({VARIABLE_UPDATE_ENDED:'mvu:variable-update-ended'});
1610
+ const eventOn=(name,handler)=>{observeCompatibilityCall('TavernHelper','eventOn');const listener=event=>handler(event.detail);cardEvents.addEventListener(String(name),listener);return()=>cardEvents.removeEventListener(String(name),listener)};
1611
+ const eventEmit=(name,payload)=>{observeCompatibilityCall('TavernHelper','eventEmit');return cardEvents.dispatchEvent(new CustomEvent(String(name),{detail:payload}))};
1612
+ async function replaceVariables(value){observeCompatibilityCall('TavernHelper','replaceVariables');cardState=clone(value||{});const result=await send('replaceCardState',{state:cardState,operationId:operation('replace-state')});cardState=clone(result.state||cardState);eventEmit(mvuEvents.VARIABLE_UPDATE_ENDED,{stat_data:clone(cardState)});return clone(cardState)}
1613
+ async function insertOrAssignVariables(value){observeCompatibilityCall('TavernHelper','insertOrAssignVariables');cardState=merge(cardState,value||{});const result=await send('replaceCardState',{state:cardState,operationId:operation('merge-state')});cardState=clone(result.state||cardState);eventEmit(mvuEvents.VARIABLE_UPDATE_ENDED,{stat_data:clone(cardState)});return clone(cardState)}
1614
+ async function getChatMessages(){observeCompatibilityCall('TavernHelper','getChatMessages');try{await refreshState()}catch{}const stat_data=clone(cardState);return[{data:{stat_data},extra:{stat_data}}]}
1615
+ async function setChatMessages(messages){observeCompatibilityCall('TavernHelper','setChatMessages');return send('selectOpening',{messages,operationId:operation('opening')})}
1616
+ const getVariables=()=>{observeCompatibilityCall('TavernHelper','getVariables');return clone(cardState)};
1617
+ const getAllVariables=()=>{observeCompatibilityCall('TavernHelper','getAllVariables');return{stat_data:clone(cardState)}};
1618
+ const mvu=Object.freeze({events:mvuEvents,get variables(){observeCompatibilityCall('MVU','variables');return clone(cardState)},getMvuData(){observeCompatibilityCall('MVU','getMvuData');return{stat_data:clone(cardState)}},async replaceMvuData(value){observeCompatibilityCall('MVU','replaceMvuData');await replaceVariables(value?.stat_data||{});return{stat_data:clone(cardState)}}});
1619
+ const globalName=name=>{const value=String(name);if(!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value))throw new Error('Invalid global name: '+value);return value};
1620
+ const initializeGlobal=(name,value)=>{observeCompatibilityCall('TavernHelper','initializeGlobal');const key=globalName(name);window[key]=value;const registered=new Set(String(document.documentElement.dataset.dshGlobalRegistered||'').split(',').filter(Boolean));registered.add(key);document.documentElement.dataset.dshGlobalRegistered=[...registered].join(',');if(companion){try{parent.postMessage({source:'dsh-re3-rp-rich-frame',token,kind:'global-registered',name:key,value:structuredClone(value)},'*')}catch{}}return value};
1621
+ const waitGlobalInitialized=async name=>{observeCompatibilityCall('TavernHelper','waitGlobalInitialized');if(String(name)==='Mvu')return mvu;for(let attempt=0;attempt<300;attempt++){if(String(name) in window)return window[String(name)];if(!companion){try{const remote=await send('getCompanionGlobal',{name:String(name)});if(remote?.found){const key=globalName(name);window[key]=remote.value;return remote.value}}catch{}}await new Promise(resolve=>setTimeout(resolve,100))}throw new Error('Global unavailable: '+String(name))};
1622
+ const errorCatched=callback=>async function(...args){try{return await callback.apply(this,args)}catch(error){console.error(error);return undefined}};
1623
+ const getWorldbook=name=>{observeCompatibilityCall('TavernHelper','getWorldbook');return send('getWorldbook',{name})};
1624
+ async function updateWorldbookWith(name,updater){observeCompatibilityCall('TavernHelper','updateWorldbookWith');const before=await getWorldbook(name);const after=await updater(clone(before));const updates=(after||[]).map(entry=>({id:String(entry.id??entry.uid??''),name:String(entry.name??entry.comment??''),enabled:entry.enabled===true}));await send('updateWorldbook',{updates,operationId:operation('worldbook')});return after}
1625
+ async function replaceWorldbook(name,entries){observeCompatibilityCall('TavernHelper','replaceWorldbook');const updates=(entries||[]).map(entry=>({id:String(entry.id??entry.uid??''),name:String(entry.name??entry.comment??''),enabled:entry.enabled===true}));await send('updateWorldbook',{updates,operationId:operation('worldbook')});return entries}
1626
+ const getCharWorldbookNames=()=>{observeCompatibilityCall('TavernHelper','getCharWorldbookNames');return{primary:cardTitle,additional:[]}};
1627
+ const getWorldbookNames=()=>{observeCompatibilityCall('TavernHelper','getWorldbookNames');return[cardTitle]};
1628
+ const rebindCharWorldbooks=async(_target,binding)=>{observeCompatibilityCall('TavernHelper','rebindCharWorldbooks');return binding};
1629
+ const createWorldbookEntries=async(_name,entries)=>{observeCompatibilityCall('TavernHelper','createWorldbookEntries');return entries};
1630
+ const helper=Object.freeze({getAllVariables,getVariables,replaceVariables,insertOrAssignVariables,getChatMessages,setChatMessages,generate,getWorldbook,getWorldbookNames,getCharWorldbookNames,updateWorldbookWith,replaceWorldbook,rebindCharWorldbooks,createWorldbookEntries,eventOn,eventEmit,initializeGlobal,waitGlobalInitialized,errorCatched});
1631
+ const sillyContext=buildSillyTavernCompatibilityContext(messageCount,currentMessageId);
1632
+ const silly=Object.freeze({get chat(){return sillyContext.chat},get chatId(){return sillyContext.chatId},get extensionSettings(){return sillyContext.extensionSettings},getContext:()=>{observeCompatibilityCall('SillyTavern','getContext');return sillyContext},app:{chat:{sendMessage:text=>{observeCompatibilityCall('SillyTavern','sendMessage');return submitTurn(text)}}}});
1633
+ const audioSettings={bgm:{enabled:false}};
1634
+ const getAudioSettings=channel=>clone(audioSettings[String(channel)]||{});
1635
+ const setAudioSettings=(channel,value)=>{audioSettings[String(channel)]=merge(audioSettings[String(channel)]||{},value||{});return getAudioSettings(channel)};
1636
+ Object.assign(window,{_:lodash,$:miniDollar,jQuery:miniDollar,__dshCompatDocument:compatDocument,__dshSillyTavern:silly,__dshTavernHelper:helper,__dshMvu:mvu,TavernHelper:helper,SillyTavern:silly,Mvu:mvu,tavern_events:tavernEvents,triggerSlash,_triggerSlash:triggerSlash,generate,initializeGlobal,waitGlobalInitialized,errorCatched,eventOn,eventEmit,getAllVariables,getVariables,replaceVariables,insertOrAssignVariables,getChatMessages,setChatMessages,getCurrentMessageId:()=>currentMessageId,getWorldbook,getWorldbookNames,getCharWorldbookNames,updateWorldbookWith,replaceWorldbook,rebindCharWorldbooks,createWorldbookEntries,getTavernVersion:()=> '1.18.0',getTavernHelperVersion:()=> '4.9.3-dsh.1',getAudioSettings,setAudioSettings,replaceAudioList:async()=>{},playAudio:async()=>{},toastr:{success(){},error(){},warning(){},info(){}}});
1637
+ addEventListener('message',event=>{const data=event.data;if(event.source!==parent||data?.source!=='dsh-re3-rp-rich-host'||data.token!==token||data.kind!=='projection')return;cardState=clone(data.state||{});messageCount=Number(data.messageCount)||messageCount;currentMessageId=Number(data.currentMessageId)||currentMessageId;sillyContext.chat=Array.from({length:Math.max(0,messageCount)},()=>({}));sillyContext.chatId=currentMessageId;eventEmit(tavernEvents.MESSAGE_RECEIVED,{message_id:currentMessageId});eventEmit(mvuEvents.VARIABLE_UPDATE_ENDED,{stat_data:clone(cardState)});scheduleReport()});
1638
+ addEventListener('message',event=>{const data=event.data;if(event.source!==parent||data?.source!=='dsh-re3-rp-rich-host'||data.token!==token||data.kind!=='global-published'||typeof data.name!=='string')return;try{const key=globalName(data.name);window[key]=data.value;document.documentElement.dataset.dshGlobalResolved=key}catch{}});
1639
+ addEventListener('message',event=>{const data=event.data;if(!companion||event.source!==parent||data?.source!=='dsh-re3-rp-rich-host'||data.token!==token||data.kind!=='global-read')return;const name=String(data.name??'');let found=false;let value;try{if(name in window){value=window[name];found=true}else if(/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)){value=eval(name);found=true}value=found?structuredClone(value):undefined}catch{found=false;value=undefined}parent.postMessage({source:'dsh-re3-rp-rich-frame',token,kind:'global-value',id:data.id,requesterToken:data.requesterToken,found,value},'*')});
1640
+ window.__dshGameHost=Object.freeze({submitTurn:payload=>send('submitTurn',payload)});
1641
+ window.__dshGameStateHost=Object.freeze({async getProjection(){const value=await send('getProjection');return{...value.state,state_digest:value.stateDigest}},subscribe(){return()=>{}},submitStateAction:payload=>send('submitStateAction',payload)});
1642
+ addEventListener('click',async event=>{const button=event.target.closest?.('[data-mm-action="ring-east-bell"]');if(!button)return;const status=button.closest('[data-mm-marker]')?.querySelector('[data-mm-status]');button.disabled=true;if(status)status.textContent='正在提交正式行动…';try{const receipt=await window.__dshGameHost.submitTurn({text:'我握住铜钥匙,拉响东塔警铃。',operationId:'mm-watch-u1'});if(status){status.dataset.committed=String(receipt.committed===true);status.textContent=receipt.committed?'行动与新回复已进入同一聊天。':'宿主未确认提交。'}}catch(error){if(status){status.dataset.error=error.code||'bridge_unavailable';status.textContent='提交失败:'+status.dataset.error}}finally{button.disabled=false;report()}});
1643
+ })();<\/script>`;
1644
+ // TavernHelper cards commonly compile Vue applications as external-global
1645
+ // bundles. SillyTavern provides these globals at page scope; each DSH card
1646
+ // document has an isolated realm, so provide the same generic runtime set
1647
+ // before the card bootstrap executes. This is deliberately card-agnostic:
1648
+ // authored frontends still decide whether to use any of these libraries.
1649
+ const commonFrontendGlobals = `<script src="https://testingcf.jsdelivr.net/npm/vue@3.5.21/dist/vue.global.prod.js"><\/script><script src="https://testingcf.jsdelivr.net/npm/vue-router@4.5.1/dist/vue-router.global.prod.js"><\/script><script src="https://testingcf.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"><\/script><script>if(window.jsyaml&&!window.YAML)window.YAML=Object.freeze({...window.jsyaml,parse:window.jsyaml.load,stringify:window.jsyaml.dump});<\/script>`;
1650
+ const runtimeDiagnostics = `<script>(()=>{const token=${encodedToken};const record=value=>{const message=String(value);const write=()=>{const node=document.createElement('pre');node.hidden=true;node.dataset.dshRuntimeError='';node.textContent=message;document.body.appendChild(node)};document.body?write():addEventListener('DOMContentLoaded',write,{once:true});parent.postMessage({source:'dsh-re3-rp-rich-frame',token,kind:'runtime-error',message},'*')};const originalError=console.error.bind(console);console.error=(...values)=>{originalError(...values);record(values.map(value=>value?.stack||value).join(' '))};addEventListener('error',event=>record(event.message||event.error||'Card script error'),true);addEventListener('unhandledrejection',event=>record(event.reason||'Unhandled card rejection'))})();<\/script>`;
1651
+ return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><base href="${baseHref}"><meta name="viewport" content="width=device-width,initial-scale=1"><style>html{height:auto!important;min-height:0!important;margin:0;overflow-x:hidden!important;overflow-y:hidden!important;background:transparent;color-scheme:light dark}body{height:auto!important;min-height:0!important;margin:0;overflow-x:hidden!important;overflow-y:visible!important;background:transparent;font-family:system-ui,-apple-system,"Segoe UI","PingFang SC",sans-serif}</style>${runtimeDiagnostics}${commonFrontendGlobals}</head><body>${bootstrap}${adapted}</body></html>`;
1652
+ }
1653
+ function NativeRichFrame(props) {
1654
+ const source = `data:text/html;charset=utf-8,${encodeURIComponent(nativeRichSrcDoc(props.content, props.token, props.variableState, props.frontendStorage, props.cardTitle, props.messageCount))}`;
1655
+ return h("iframe", {
1656
+ className: "tavern-native-rich-frame",
1657
+ title: "卡片生成的消息前端",
1658
+ // A data document keeps a unique opaque origin while allow-same-origin
1659
+ // restores the inline handlers and Web Storage semantics used by real
1660
+ // Tavern card UIs. The card still cannot read the DSH parent document.
1661
+ // Real Tavern setup UIs commonly gate their final submit behind
1662
+ // confirm(). Keep the frame isolated from the Host while allowing that
1663
+ // explicit player confirmation to unblock the narrow Bridge action.
1664
+ sandbox: "allow-scripts allow-same-origin allow-modals",
1665
+ scrolling: "no",
1666
+ src: source,
1667
+ referrerPolicy: "no-referrer",
1668
+ "data-tavern-rich-token": props.token,
1669
+ ref: (frame) => props.register(props.token, frame),
1670
+ });
1671
+ }
1672
+ function companionSrcDoc(scripts, token, frontendStorage, cardTitle) {
1673
+ const encodedSources = JSON.stringify(scripts.map((script) => ({ id: script.id, name: script.name, source: script.source })))
1674
+ .replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
1675
+ const runner = `<script>(async()=>{document.documentElement.dataset.dshCompanionRunner='started';const scripts=${encodedSources};for(const script of scripts){const url=URL.createObjectURL(new Blob([script.source],{type:'text/javascript'}));try{await import(url)}catch(error){console.error('[DSH Re3 RPHelper] '+script.name,error);const diagnostic=document.createElement('pre');diagnostic.hidden=true;diagnostic.dataset.dshRuntimeError='';diagnostic.textContent='[DSH Re3 RPHelper] '+script.name+': '+(error?.stack||error);document.body.appendChild(diagnostic)}finally{URL.revokeObjectURL(url)}}document.documentElement.dataset.dshCompanionRunner='complete'})();<\/script>`;
1676
+ return nativeRichSrcDoc(runner, token, {}, frontendStorage, cardTitle, 0);
1677
+ }
1678
+ function NativeCompanionRuntime(props) {
1679
+ const token = `companion:${props.sessionId}:${props.revisionId}`;
1680
+ const frameRef = React.useRef(null);
1681
+ // Real TavernHelper bundles regularly exceed one megabyte. Percent-
1682
+ // encoding those bundles into a data URL crosses browser URL limits and
1683
+ // fails as an empty frame without a useful error. srcdoc carries the same
1684
+ // isolated document without a URL-size ceiling.
1685
+ const sourceDocument = companionSrcDoc(props.scripts, token, props.frontendStorage, props.cardTitle);
1686
+ const pushProjection = () => {
1687
+ const currentMessageId = props.messages.filter((message) => message.role === "assistant").at(-1)?.seq ?? -1;
1688
+ frameRef.current?.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token, kind: "projection", state: props.variableState, messageCount: props.messages.length, currentMessageId }, "*");
1689
+ };
1690
+ React.useEffect(() => { pushProjection(); }, [props.variableState, props.messages.length]);
1691
+ return h("iframe", {
1692
+ className: "tavern-native-companion-frame",
1693
+ title: "卡片 TavernHelper 伴随前端",
1694
+ sandbox: "allow-scripts allow-modals",
1695
+ srcDoc: sourceDocument,
1696
+ referrerPolicy: "no-referrer",
1697
+ "data-tavern-companion-runtime": props.revisionId,
1698
+ onLoad: pushProjection,
1699
+ ref: (frame) => { frameRef.current = frame; props.register(token, frame); },
1700
+ });
1701
+ }
1702
+ function NativeHostedFrontend(props) {
1703
+ return h("iframe", {
1704
+ className: `tavern-native-hosted-frame is-${props.frontend.container}`,
1705
+ title: "卡片声明的宿主前端",
1706
+ sandbox: "allow-scripts allow-same-origin",
1707
+ src: props.frontend.entryUrl,
1708
+ referrerPolicy: "no-referrer",
1709
+ "data-tavern-hosted-frontend": props.frontend.caseId,
1710
+ });
1711
+ }
1712
+ function NativeRichMessage(props) {
1713
+ const blocks = splitRichMessage(props.message.text);
1714
+ const content = blocks.map((block, index) => block.kind === "prose"
1715
+ ? h(MarkdownText, { text: block.content, key: `prose:${index}` })
1716
+ : h(NativeRichFrame, { content: block.content, token: `${props.message.seq}:${index}`, variableState: props.variableState, frontendStorage: props.frontendStorage, cardTitle: props.cardTitle, messageCount: props.messageCount, register: props.register, key: `html:${index}` }));
1717
+ if (props.frontend !== undefined)
1718
+ content.push(h(NativeHostedFrontend, { frontend: props.frontend, key: "hosted-frontend" }));
1719
+ return h("div", { className: "tavern-native-message" }, ...content);
1720
+ }
1721
+ function TavernNativeMessageAdapter(props) {
1722
+ const sessionId = props.sessionId;
1723
+ const [projectionState, setProjectionState] = React.useState({ sessionId, value: undefined });
1724
+ const projection = projectionState.sessionId === sessionId ? projectionState.value : undefined;
1725
+ const [, refreshPermission] = React.useReducer((value) => value + 1, 0);
1726
+ const anchorRef = React.useRef(null);
1727
+ const mountsRef = React.useRef(new Map());
1728
+ const framesRef = React.useRef(new Map());
1729
+ const [projectionRevision, refreshProjection] = React.useReducer((value) => value + 1, 0);
1730
+ const activeRevisionId = projection?.revisionId ?? "";
1731
+ const register = React.useCallback((token, frame) => {
1732
+ if (frame === null)
1733
+ framesRef.current.delete(token);
1734
+ else
1735
+ framesRef.current.set(token, { frame, sessionId, revisionId: activeRevisionId });
1736
+ }, [sessionId, activeRevisionId]);
1737
+ const sessionRevision = props.useSession((snapshot) => `${snapshot.nodes.length}:${snapshot.running}:${snapshot.openState}`);
1738
+ React.useEffect(() => {
1739
+ const controller = new AbortController();
1740
+ void fetch(`/dsh-re3-rp/conversation-projection?sessionId=${encodeURIComponent(sessionId)}&optional=1`, { cache: "no-store", signal: controller.signal })
1741
+ .then(async (response) => response.status === 204 ? null : response.ok ? await response.json() : null)
1742
+ .then((value) => {
1743
+ if (controller.signal.aborted)
1744
+ return;
1745
+ setProjectionState({ sessionId, value });
1746
+ if (value === null)
1747
+ clearActiveTavernSession(sessionId);
1748
+ else
1749
+ syncActiveTavernSession(sessionId, value.revisionId);
1750
+ })
1751
+ .catch(() => {
1752
+ if (controller.signal.aborted)
1753
+ return;
1754
+ setProjectionState({ sessionId, value: null });
1755
+ clearActiveTavernSession(sessionId);
1756
+ });
1757
+ return () => controller.abort();
1758
+ }, [sessionId, sessionRevision, projectionRevision]);
1759
+ React.useEffect(() => {
1760
+ const refresh = (event) => {
1761
+ if (event.detail?.sessionId === sessionId)
1762
+ refreshProjection();
1763
+ };
1764
+ window.addEventListener("dsh-re3-rp:projection-changed", refresh);
1765
+ return () => window.removeEventListener("dsh-re3-rp:projection-changed", refresh);
1766
+ }, [sessionId]);
1767
+ React.useEffect(() => {
1768
+ const refresh = () => refreshPermission();
1769
+ window.addEventListener("dsh-re3-rp:companion-permission", refresh);
1770
+ return () => window.removeEventListener("dsh-re3-rp:companion-permission", refresh);
1771
+ }, []);
1772
+ React.useLayoutEffect(() => {
1773
+ const scroll = anchorRef.current?.closest('[data-conversation-scroll]');
1774
+ if (scroll === null || projection === undefined)
1775
+ return;
1776
+ const mounts = mountsRef.current;
1777
+ const frames = framesRef.current;
1778
+ const companionGlobals = new Map();
1779
+ const hiddenThoughts = new Map();
1780
+ let scanFrame;
1781
+ const restore = (mount) => {
1782
+ mount.original.style.removeProperty("display");
1783
+ mount.root.unmount();
1784
+ mount.display.remove();
1785
+ mounts.delete(mount.item);
1786
+ delete mount.item.dataset.tavernRichMessage;
1787
+ };
1788
+ const restoreThought = (thought) => {
1789
+ const display = hiddenThoughts.get(thought);
1790
+ if (display === undefined)
1791
+ return;
1792
+ if (display.length === 0)
1793
+ thought.style.removeProperty("display");
1794
+ else
1795
+ thought.style.display = display;
1796
+ hiddenThoughts.delete(thought);
1797
+ };
1798
+ const scan = () => {
1799
+ if (projection === null) {
1800
+ for (const mount of [...mounts.values()])
1801
+ restore(mount);
1802
+ return;
1803
+ }
1804
+ const flowItems = Array.from(scroll.querySelectorAll('[data-chat-flow-kind="user"], [data-chat-flow-kind="assistant-step"]'));
1805
+ const liveThoughts = new Set();
1806
+ for (const item of flowItems) {
1807
+ if (item.dataset.chatFlowKind !== "assistant-step")
1808
+ continue;
1809
+ for (const thought of Array.from(item.querySelectorAll('[data-variant="think"]'))) {
1810
+ liveThoughts.add(thought);
1811
+ if (!hiddenThoughts.has(thought))
1812
+ hiddenThoughts.set(thought, thought.style.display);
1813
+ thought.style.display = "none";
1814
+ }
1815
+ }
1816
+ for (const thought of [...hiddenThoughts.keys()])
1817
+ if (!liveThoughts.has(thought) || !thought.isConnected)
1818
+ restoreThought(thought);
1819
+ const aligned = alignProjectedRoles(flowItems.map((item) => item.dataset.chatFlowKind === "user" ? "user" : "assistant"), projection.messages.map((message) => message.role));
1820
+ if (aligned === null)
1821
+ return;
1822
+ const liveItems = new Set();
1823
+ const hostedMessageSeq = projection.frontend?.entryUrl === undefined ? undefined : projection.messages.find((candidate) => candidate.role === "assistant")?.seq;
1824
+ flowItems.forEach((item, index) => {
1825
+ if (item.dataset.chatFlowKind !== "assistant-step")
1826
+ return;
1827
+ const projectedIndex = aligned[index];
1828
+ const message = projectedIndex === null ? undefined : projection.messages[projectedIndex];
1829
+ const hostedFrontend = message?.seq === hostedMessageSeq ? projection.frontend ?? undefined : undefined;
1830
+ if (message === undefined || (!splitRichMessage(message.text).some((block) => block.kind === "html") && hostedFrontend === undefined && message.rawText === undefined))
1831
+ return;
1832
+ const original = item.firstElementChild;
1833
+ if (original === null)
1834
+ return;
1835
+ liveItems.add(item);
1836
+ const signature = `${message.seq}:${message.text}:${hostedFrontend?.entryUrl ?? ""}`;
1837
+ let mount = mounts.get(item);
1838
+ if (mount === undefined) {
1839
+ const display = document.createElement("div");
1840
+ display.className = "tavern-native-rich-display";
1841
+ item.insertBefore(display, original.nextSibling);
1842
+ mount = { item, original, display, root: ReactDOM.createRoot(display), signature: "" };
1843
+ mounts.set(item, mount);
1844
+ item.dataset.tavernRichMessage = "true";
1845
+ }
1846
+ if (mount.signature === signature)
1847
+ return;
1848
+ mount.signature = signature;
1849
+ original.style.removeProperty("display");
1850
+ mount.root.render(h(NativeRichMessage, { message, frontend: hostedFrontend, variableState: projection.variableState ?? {}, frontendStorage: projection.frontendStorage ?? {}, cardTitle: projection.title, messageCount: projection.messages.length, register }));
1851
+ requestAnimationFrame(() => { if (mounts.get(item) === mount)
1852
+ original.style.display = "none"; });
1853
+ });
1854
+ for (const mount of [...mounts.values()])
1855
+ if (!liveItems.has(mount.item) || !mount.item.isConnected)
1856
+ restore(mount);
1857
+ };
1858
+ const schedule = () => {
1859
+ if (scanFrame !== undefined)
1860
+ return;
1861
+ scanFrame = requestAnimationFrame(() => { scanFrame = undefined; scan(); });
1862
+ };
1863
+ const onMessage = (event) => {
1864
+ const message = event.data;
1865
+ if (message?.source !== "dsh-re3-rp-rich-frame" || typeof message.token !== "string")
1866
+ return;
1867
+ const registration = frames.get(message.token);
1868
+ if (registration === undefined || registration.sessionId !== sessionId || registration.revisionId !== activeRevisionId || event.source !== registration.frame.contentWindow)
1869
+ return;
1870
+ const frame = registration.frame;
1871
+ if (message.kind === "runtime-error" && typeof message.message === "string") {
1872
+ recordRuntimeDiagnostic(sessionId, message.token, message.message);
1873
+ return;
1874
+ }
1875
+ if (message.kind === "global-registered" && message.token.startsWith("companion:") && typeof message.name === "string") {
1876
+ let globals = companionGlobals.get(message.token);
1877
+ if (globals === undefined) {
1878
+ globals = new Map();
1879
+ companionGlobals.set(message.token, globals);
1880
+ }
1881
+ globals.set(message.name, message.value);
1882
+ for (const [requesterToken, requester] of frames.entries()) {
1883
+ if (requesterToken.startsWith("companion:") || requester.sessionId !== sessionId || requester.revisionId !== activeRevisionId)
1884
+ continue;
1885
+ requester.frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: requesterToken, kind: "global-published", name: message.name, value: message.value }, "*");
1886
+ }
1887
+ return;
1888
+ }
1889
+ if (message.kind === "global-value" && message.token.startsWith("companion:") && typeof message.requesterToken === "string") {
1890
+ const requester = frames.get(message.requesterToken);
1891
+ if (requester === undefined || requester.sessionId !== sessionId || requester.revisionId !== activeRevisionId)
1892
+ return;
1893
+ requester.frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: message.requesterToken, kind: "response", id: message.id, ok: true, result: { found: message.found === true, value: message.value } }, "*");
1894
+ return;
1895
+ }
1896
+ if (message.kind === "overlay" && message.token.startsWith("companion:")) {
1897
+ frame.style.pointerEvents = message.active === false ? "none" : "auto";
1898
+ frame.style.width = `${window.innerWidth}px`;
1899
+ frame.style.height = `${window.innerHeight}px`;
1900
+ if (message.active === false)
1901
+ frame.style.clipPath = "inset(0 100% 100% 0)";
1902
+ else if (message.dragging === true)
1903
+ frame.style.clipPath = "none";
1904
+ else {
1905
+ const left = Math.max(0, Math.min(window.innerWidth, Math.floor(Number(message.x) || 0)));
1906
+ const top = Math.max(0, Math.min(window.innerHeight, Math.floor(Number(message.y) || 0)));
1907
+ const width = Math.max(1, Math.min(window.innerWidth - left, Math.ceil(Number(message.width) || 1)));
1908
+ const height = Math.max(1, Math.min(window.innerHeight - top, Math.ceil(Number(message.height) || 1)));
1909
+ frame.style.clipPath = `inset(${top}px ${Math.max(0, window.innerWidth - left - width)}px ${Math.max(0, window.innerHeight - top - height)}px ${left}px)`;
1910
+ }
1911
+ return;
1912
+ }
1913
+ if (message.kind === "resize") {
1914
+ // The token/source checks above make this a Host-owned resize
1915
+ // receipt. Match TavernHelper's auto-height contract so the outer
1916
+ // DSH conversation, rather than a clipped inner pane, owns scroll.
1917
+ frame.style.height = `${clampRichFrameHeight(Number(message.height))}px`;
1918
+ return;
1919
+ }
1920
+ if (message.kind !== "request" || typeof message.id !== "number")
1921
+ return;
1922
+ if (message.action === "setDraft") {
1923
+ const composer = document.querySelector("textarea[data-phase]");
1924
+ if (composer === null || typeof message.payload?.text !== "string") {
1925
+ frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: message.token, kind: "response", id: message.id, ok: false, error: { code: "bridge_unavailable", message: "找不到当前 DSH 输入框" } }, "*");
1926
+ return;
1927
+ }
1928
+ const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
1929
+ if (setter !== undefined)
1930
+ setter.call(composer, message.payload.text);
1931
+ else
1932
+ composer.value = message.payload.text;
1933
+ composer.dispatchEvent(new Event("input", { bubbles: true }));
1934
+ composer.focus();
1935
+ frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: message.token, kind: "response", id: message.id, ok: true, result: { draftSet: true } }, "*");
1936
+ return;
1937
+ }
1938
+ if (message.action === "getCompanionGlobal" && typeof message.payload?.name === "string") {
1939
+ const companionEntry = [...frames.entries()].find(([candidateToken, candidate]) => candidateToken.startsWith(`companion:${sessionId}:`) && candidate.revisionId === activeRevisionId);
1940
+ if (companionEntry === undefined) {
1941
+ frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: message.token, kind: "response", id: message.id, ok: false, error: { code: "bridge_unavailable", message: "卡内常驻脚本尚未就绪" } }, "*");
1942
+ return;
1943
+ }
1944
+ const [companionToken, companionRegistration] = companionEntry;
1945
+ const globals = companionGlobals.get(companionToken);
1946
+ if (globals?.has(message.payload.name)) {
1947
+ frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: message.token, kind: "response", id: message.id, ok: true, result: { found: true, value: globals.get(message.payload.name) } }, "*");
1948
+ return;
1949
+ }
1950
+ companionRegistration.frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: companionToken, kind: "global-read", id: message.id, requesterToken: message.token, name: message.payload.name }, "*");
1951
+ return;
1952
+ }
1953
+ if (!["reportCompatibilityCall", "selectOpening", "generate", "cancelGenerate", "submitTurn", "getProjection", "submitStateAction", "getCardState", "replaceCardState", "replaceCardStorage", "getWorldbook", "updateWorldbook"].includes(message.action))
1954
+ return;
1955
+ void fetch("/dsh-re3-rp/bridge", { method: "POST", headers: { "content-type": "application/json; charset=utf-8" }, body: JSON.stringify({ sessionId, method: message.action, payload: message.payload ?? {}, operationId: message.payload?.operationId }) })
1956
+ .then(async (response) => {
1957
+ const body = await response.json().catch(() => ({}));
1958
+ if (!response.ok || body.ok !== true)
1959
+ throw Object.assign(new Error(body?.error?.message ?? "Bridge unavailable"), { code: body?.error?.code ?? "bridge_unavailable" });
1960
+ if (message.action === "reportCompatibilityCall")
1961
+ void loadCapabilitySnapshot({ sessionId });
1962
+ if (message.action === "selectOpening")
1963
+ window.dispatchEvent(new CustomEvent("dsh-re3-rp:projection-changed", { detail: { sessionId } }));
1964
+ frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: message.token, kind: "response", id: message.id, ok: true, result: body.result }, "*");
1965
+ })
1966
+ .catch((error) => frame.contentWindow?.postMessage({ source: "dsh-re3-rp-rich-host", token: message.token, kind: "response", id: message.id, ok: false, error: { code: error?.code ?? "bridge_unavailable", message: error?.message ?? "Bridge unavailable" } }, "*"));
1967
+ };
1968
+ window.addEventListener("message", onMessage);
1969
+ const observer = new MutationObserver(schedule);
1970
+ observer.observe(scroll, { childList: true, subtree: true });
1971
+ scan();
1972
+ return () => {
1973
+ observer.disconnect();
1974
+ window.removeEventListener("message", onMessage);
1975
+ if (scanFrame !== undefined)
1976
+ cancelAnimationFrame(scanFrame);
1977
+ for (const mount of [...mounts.values()])
1978
+ restore(mount);
1979
+ for (const thought of [...hiddenThoughts.keys()])
1980
+ restoreThought(thought);
1981
+ frames.clear();
1982
+ };
1983
+ }, [projection, sessionId]);
1984
+ if (projection === null)
1985
+ return h("span", { ref: anchorRef, hidden: true, "data-tavern-native-message-adapter": "inactive" });
1986
+ const anchor = h("span", { ref: anchorRef, hidden: true, "data-tavern-native-message-adapter": projection === undefined ? "loading" : "active" });
1987
+ if (projection === undefined || (projection.companionScripts?.length ?? 0) === 0)
1988
+ return anchor;
1989
+ const permissionKey = `dsh-re3-rp:companion-permission:${projection.revisionId}`;
1990
+ const companionEnabled = window.localStorage.getItem(permissionKey) === "enabled";
1991
+ const setCompanionPermission = (enabled) => {
1992
+ if (enabled)
1993
+ window.localStorage.setItem(permissionKey, "enabled");
1994
+ else
1995
+ window.localStorage.removeItem(permissionKey);
1996
+ refreshPermission();
1997
+ window.dispatchEvent(new Event("dsh-re3-rp:companion-permission"));
1998
+ };
1999
+ if (!companionEnabled)
2000
+ return h(React.Fragment, null, h("aside", { className: "tavern-companion-permission", role: "alert" }, h("strong", null, `此卡包含 ${projection.companionScripts?.length ?? 0} 个可执行脚本`), h("span", null, "启用后脚本可联网,并可通过受限 Bridge 修改本卡状态、世界书、草稿或提交回合。仅对可信卡启用。"), h("button", { type: "button", onClick: () => setCompanionPermission(true) }, "启用此版本卡内脚本")), anchor);
2001
+ return h(React.Fragment, null, h(NativeCompanionRuntime, { sessionId, revisionId: projection.revisionId, scripts: projection.companionScripts ?? [], variableState: projection.variableState ?? {}, frontendStorage: projection.frontendStorage ?? {}, cardTitle: projection.title, messages: projection.messages, register }), anchor);
2002
+ }
2003
+ function TavernModeTabs() {
2004
+ const snapshot = usePrototypeState();
2005
+ return h("div", { className: "tavern-tabs", role: "tablist", "aria-label": "侧栏内容" }, h("button", { type: "button", role: "tab", title: "工作区", "aria-label": "工作区", "aria-selected": snapshot.sidebarMode === "workspace", onClick: () => patchState({ sidebarMode: "workspace", screen: "library", startError: null }) }, h(IconFolderOpenOutline16, { size: 16 }), h("span", { className: "tavern-tab-label" }, "工作区")), h("button", { type: "button", role: "tab", title: "酒馆", "aria-label": "酒馆", "aria-selected": snapshot.sidebarMode === "tavern", onClick: openLibrary }, h(IconUserOutline16, { size: 16 }), h("span", { className: "tavern-tab-label" }, "酒馆")));
2006
+ }
2007
+ function TavernSidebar() {
2008
+ const snapshot = usePrototypeState();
2009
+ return h("section", { className: "tavern-sidebar-content", "data-tavern-panel": "true", "aria-label": "酒馆卡与会话" }, h(TavernModeTabs), snapshot.screen === "library" ? h(LibraryScreen) : h(SetupScreen));
2010
+ }
2011
+ function applySidebarMode(slot, host, mode) {
2012
+ slot.dataset.tavernMode = mode;
2013
+ for (const child of Array.from(slot.children)) {
2014
+ // Narrative owns data-nwh-native-workspace and data-nwh-sidebar-mode-host;
2015
+ // exclude its host so Tavern only labels the native DSH Workspace browser.
2016
+ const isNativeWorkspace = child !== host && !child.hasAttribute("data-nwh-sidebar-mode-host");
2017
+ if (isNativeWorkspace)
2018
+ child.setAttribute("data-tavern-native-workspace", "true");
2019
+ else
2020
+ child.removeAttribute("data-tavern-native-workspace");
2021
+ }
2022
+ }
2023
+ function useSidebarModeHost(mode) {
2024
+ const [host, setHost] = React.useState(null);
2025
+ const modeRef = React.useRef(mode);
2026
+ modeRef.current = mode;
2027
+ React.useLayoutEffect(() => {
2028
+ let slot = null;
2029
+ let mount = null;
2030
+ const attach = () => {
2031
+ const nextSlot = document.querySelector('[data-slot="sidebar.workspaces"]');
2032
+ if (nextSlot === null)
2033
+ return false;
2034
+ if (slot !== nextSlot || mount?.isConnected !== true) {
2035
+ if (slot !== null) {
2036
+ delete slot.dataset.tavernMode;
2037
+ for (const child of Array.from(slot.children))
2038
+ delete child.dataset.tavernNativeWorkspace;
2039
+ }
2040
+ mount?.remove();
2041
+ slot = nextSlot;
2042
+ mount = document.createElement("div");
2043
+ mount.dataset.tavernSidebarHost = "true";
2044
+ slot.prepend(mount);
2045
+ setHost(mount);
2046
+ }
2047
+ applySidebarMode(slot, mount, modeRef.current);
2048
+ return true;
2049
+ };
2050
+ const observer = new MutationObserver(attach);
2051
+ observer.observe(document.body, { childList: true, subtree: true });
2052
+ attach();
2053
+ return () => {
2054
+ observer.disconnect();
2055
+ if (slot !== null) {
2056
+ delete slot.dataset.tavernMode;
2057
+ for (const child of Array.from(slot.children))
2058
+ delete child.dataset.tavernNativeWorkspace;
2059
+ }
2060
+ mount?.remove();
2061
+ setHost(null);
2062
+ };
2063
+ }, []);
2064
+ React.useLayoutEffect(() => {
2065
+ if (host?.parentElement !== null && host?.parentElement !== undefined)
2066
+ applySidebarMode(host.parentElement, host, mode);
2067
+ }, [host, mode]);
2068
+ return host;
2069
+ }
2070
+ function SidebarIntegration() {
2071
+ const snapshot = usePrototypeState();
2072
+ const host = useSidebarModeHost(snapshot.sidebarMode);
2073
+ if (host === null)
2074
+ return null;
2075
+ return h(React.Fragment, null, ReactDOM.createPortal(snapshot.sidebarMode === "tavern" ? h(TavernSidebar) : h(TavernModeTabs), host), ReactDOM.createPortal(h(CapabilityRail), document.body));
2076
+ }
2077
+ const STYLE = `
2078
+ [data-slot="conversation.view"]:has(>.tavern-context-view){display:flex;min-height:0;flex:1;overflow:hidden}
2079
+ .tavern-context-view{display:flex;box-sizing:border-box;width:100%;height:100%;min-height:0;flex-direction:column;overflow:hidden;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#20242a);font-family:inherit}.tavern-context-view.is-loading{justify-content:center}.tavern-context-empty{margin:auto;padding:28px;color:var(--dsw-alias-label-tertiary,#858c96);font-size:12px;text-align:center}
2080
+ .tavern-context-toolbar{display:grid;grid-template-columns:minmax(178px,230px) minmax(280px,1fr) minmax(170px,250px);grid-template-rows:42px 24px;align-items:center;column-gap:14px;min-height:82px;box-sizing:border-box;border-bottom:1px solid var(--dsw-alias-border-l1,#e2e5e9);padding:8px 22px;background:color-mix(in srgb,var(--dsw-alias-bg-base,#fff) 96%,var(--dsw-alias-bg-subtle,#f6f7f9) 4%)}.tavern-context-request-nav{display:grid;grid-column:1;grid-row:1;grid-template-columns:28px minmax(86px,auto) 28px;align-items:center;justify-content:start;gap:3px}.tavern-context-request-nav>button{display:grid;width:28px;height:28px;place-items:center;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#646d79);cursor:pointer}.tavern-context-request-nav>button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eef0f3)}.tavern-context-request-nav>button:disabled{cursor:default;opacity:.3}.tavern-context-request-title{display:flex;min-width:0;flex-direction:column;align-items:center;gap:2px}.tavern-context-request-title strong{font-size:12px;font-weight:650;white-space:nowrap}.tavern-context-request-title span{color:var(--dsw-alias-label-tertiary,#858c96);font-size:9px;white-space:nowrap}
2081
+ .tavern-context-toolbar-meta{display:flex;min-width:0;grid-column:2;grid-row:1;align-items:center;justify-content:center;gap:6px}.tavern-context-toolbar-meta>span{overflow:hidden;border:1px solid var(--dsw-alias-border-l1,#dfe3e8);border-radius:7px;padding:5px 8px;background:var(--dsw-alias-bg-subtle,#f8f9fa);color:var(--dsw-alias-label-secondary,#626b78);font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-context-model{max-width:150px}.tavern-context-preset{max-width:205px}.tavern-context-token-total{flex:none;font-variant-numeric:tabular-nums}.tavern-context-search{display:flex;min-width:0;height:32px;box-sizing:border-box;grid-column:3;grid-row:1;align-items:center;gap:7px;border:1px solid var(--dsw-alias-border-l2,#d5d9df);border-radius:8px;padding:0 9px;color:var(--dsw-alias-label-tertiary,#858c96);background:var(--dsw-alias-bg-base,#fff)}.tavern-context-search:focus-within{border-color:#8daadd;box-shadow:0 0 0 2px rgba(75,119,192,.1)}.tavern-context-search input{min-width:0;width:100%;border:0;outline:0;background:transparent;color:var(--dsw-alias-label-primary,#20242a);font:400 10px/1 inherit}
2082
+ .tavern-context-change-filter{display:flex;grid-column:1;grid-row:2;align-items:center;gap:6px;color:var(--dsw-alias-label-secondary,#626b78);cursor:pointer;font-size:9px;white-space:nowrap}.tavern-context-change-filter input{position:absolute;opacity:0;pointer-events:none}.tavern-context-check{display:grid;width:15px;height:15px;box-sizing:border-box;place-items:center;border:1px solid var(--dsw-alias-border-l2,#cfd4db);border-radius:4px;background:var(--dsw-alias-bg-base,#fff);color:#fff}.tavern-context-change-filter input:checked+.tavern-context-check{border-color:#3475d5;background:#3475d5}
2083
+ .tavern-context-table{min-height:0;flex:1;overflow:auto}.tavern-context-table-head,.tavern-context-row-main{display:grid;grid-template-columns:54px 92px minmax(190px,260px) 78px minmax(240px,1fr) 68px;align-items:center}.tavern-context-table-head{position:sticky;z-index:3;top:0;height:34px;border-bottom:1px solid var(--dsw-alias-border-l1,#e2e5e9);background:color-mix(in srgb,var(--dsw-alias-bg-subtle,#f6f7f9) 90%,var(--dsw-alias-bg-base,#fff) 10%);color:var(--dsw-alias-label-tertiary,#7f8792);font-size:9px}.tavern-context-table-head>span{box-sizing:border-box;padding:0 10px}.tavern-context-row{position:relative;border-bottom:1px solid var(--dsw-alias-border-l1,#e5e7ea);background:var(--dsw-alias-bg-base,#fff)}.tavern-context-row.is-worldbook::before{position:absolute;z-index:2;top:0;bottom:0;left:0;width:3px;background:#8b6cc4;content:""}.tavern-context-row.is-changed{background:color-mix(in srgb,#8b6cc4 4%,var(--dsw-alias-bg-base,#fff) 96%)}
2084
+ .tavern-context-row-main{width:100%;min-height:52px;border:0;padding:0;outline:none;background:transparent;color:inherit;cursor:pointer;font:inherit;text-align:left}.tavern-context-row-main:hover{background:var(--dsw-alias-interactive-bg-hover,#f3f4f6)}.tavern-context-row-main:focus-visible{outline:2px solid #7894c8;outline-offset:-2px}.tavern-context-row-main>span{min-width:0;box-sizing:border-box;padding:0 10px}.tavern-context-order{color:var(--dsw-alias-label-tertiary,#8b929b);font:500 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace;text-align:center}.tavern-context-role{justify-self:start;border-radius:5px;padding:3px 6px!important;font-size:9px;font-weight:700;letter-spacing:.02em}.tavern-context-role.is-system{background:#edf0f4;color:#536070}.tavern-context-role.is-user{background:#eaf2ff;color:#3972c9}.tavern-context-role.is-assistant{background:#f2edff;color:#7758b3}.tavern-context-source{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:1px 7px}.tavern-context-source strong{overflow:hidden;font-size:11px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tavern-context-source small{grid-column:1;color:var(--dsw-alias-label-tertiary,#858c96);font-size:9px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-context-source em{grid-column:2;grid-row:1/3;align-self:center;border-radius:999px;padding:2px 5px;background:#eee8f8;color:#7656aa;font-size:8px;font-style:normal;white-space:nowrap}.tavern-context-tokens{color:var(--dsw-alias-label-secondary,#68717e);font:500 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace;text-align:right}.tavern-context-preview{overflow:hidden;color:var(--dsw-alias-label-secondary,#5e6672);font-size:10px;text-overflow:ellipsis;white-space:nowrap}.tavern-context-expand{display:grid;place-items:center;padding-right:28px!important;color:var(--dsw-alias-label-tertiary,#858c96)}.tavern-context-expand svg{transition:transform .16s ease}.tavern-context-expand.is-expanded svg{transform:rotate(180deg)}.tavern-context-row-copy{position:absolute;z-index:2;top:13px;right:8px;display:grid;width:26px;height:26px;place-items:center;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary,#7e8793);cursor:pointer}.tavern-context-row-copy:hover{background:var(--dsw-alias-interactive-bg-hover,#e9ebee);color:var(--dsw-alias-label-primary,#20242a)}
2085
+ .tavern-context-row-detail{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(250px,.85fr);gap:12px;border-top:1px solid var(--dsw-alias-border-l1,#e3e6ea);padding:12px 18px 16px 54px;background:color-mix(in srgb,var(--dsw-alias-bg-subtle,#f6f7f9) 72%,var(--dsw-alias-bg-base,#fff) 28%)}.tavern-context-content,.tavern-context-inspector{min-width:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l1,#dfe3e8);border-radius:9px;background:var(--dsw-alias-bg-base,#fff)}.tavern-context-content>header,.tavern-context-inspector>header{display:flex;height:34px;box-sizing:border-box;align-items:center;justify-content:space-between;gap:8px;border-bottom:1px solid var(--dsw-alias-border-l1,#e5e7ea);padding:0 10px}.tavern-context-content>header strong,.tavern-context-inspector>header strong{font-size:10px;font-weight:650}.tavern-context-content>header button{display:flex;align-items:center;gap:5px;border:0;border-radius:6px;padding:4px 6px;background:transparent;color:var(--dsw-alias-label-secondary,#626b78);cursor:pointer;font:500 9px/1 inherit}.tavern-context-content>header button:hover{background:var(--dsw-alias-interactive-bg-hover,#eef0f3)}.tavern-context-content pre{max-height:min(310px,28vh);margin:0;overflow:auto;padding:11px 12px;color:var(--dsw-alias-label-primary,#25292f);font:10px/1.62 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.tavern-context-inspector>header span{color:var(--dsw-alias-label-tertiary,#858c96);font-size:9px}.tavern-context-inspector-empty{margin:0;padding:12px;color:var(--dsw-alias-label-secondary,#68717e);font-size:9px;line-height:1.55}.tavern-context-activation-cards{display:grid;max-height:min(310px,28vh);gap:6px;overflow:auto;padding:8px}.tavern-context-activation-cards article{border:1px solid var(--dsw-alias-border-l1,#e2e5e9);border-left:3px solid #8b6cc4;border-radius:7px;padding:8px 9px;background:var(--dsw-alias-bg-base,#fff)}.tavern-context-activation-cards article>div{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-context-activation-cards strong{overflow:hidden;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.tavern-context-activation-cards span{flex:none;color:#7656aa;font-size:8px}.tavern-context-activation-cards p{margin:5px 0 0;color:var(--dsw-alias-label-secondary,#68717e);font-size:8px;line-height:1.45}.tavern-context-no-results{padding:34px;color:var(--dsw-alias-label-tertiary,#858c96);font-size:11px;text-align:center}
2086
+ @media(max-width:980px){.tavern-context-toolbar{grid-template-columns:minmax(170px,210px) minmax(120px,1fr) minmax(170px,220px)}.tavern-context-table-head,.tavern-context-row-main{grid-template-columns:42px 82px minmax(150px,220px) 66px minmax(160px,1fr) 54px}}@media(max-width:720px){.tavern-context-toolbar{grid-template-columns:1fr auto;grid-template-rows:auto auto auto;padding:8px 12px}.tavern-context-request-nav{grid-column:1;grid-row:1}.tavern-context-change-filter{grid-column:2;grid-row:1}.tavern-context-toolbar-meta{grid-column:1/-1;grid-row:2;justify-content:flex-start}.tavern-context-search{grid-column:1/-1;grid-row:3}.tavern-context-table-head,.tavern-context-row-main{grid-template-columns:36px 76px minmax(130px,1fr) 58px 46px}.tavern-context-table-head>span:nth-child(5),.tavern-context-preview{display:none}}
2087
+ .tavern-context-toolbar{grid-template-columns:minmax(178px,230px) minmax(120px,1fr) minmax(170px,250px)}.tavern-context-toolbar-meta{gap:0}.tavern-context-model{max-width:180px}
2088
+ .tavern-context-workbench{position:relative;display:grid;min-height:0;flex:1;grid-template-columns:minmax(0,1fr);overflow:hidden}.tavern-context-workbench.has-inspector{grid-template-columns:minmax(0,1fr) minmax(310px,360px)}.tavern-context-workbench>.tavern-context-table{min-width:0;min-height:0;flex:none;overflow:auto}.tavern-context-workbench.has-inspector .tavern-context-table-head,.tavern-context-workbench.has-inspector .tavern-context-row-main{grid-template-columns:42px 76px minmax(135px,1fr) 58px 48px}.tavern-context-workbench.has-inspector .tavern-context-table-head>span:nth-child(5),.tavern-context-workbench.has-inspector .tavern-context-preview{display:none}
2089
+ .tavern-context-row.is-selected{background:color-mix(in srgb,#4c79c9 8%,var(--dsw-alias-bg-base,#fff) 92%)}.tavern-context-row.is-selected .tavern-context-expand{color:#3f70c0}.tavern-context-source{grid-template-columns:minmax(0,1fr);gap:1px}.tavern-context-source small{grid-column:1}.tavern-context-expand svg{transition:none}
2090
+ .tavern-context-workbench>.tavern-context-inspector{display:flex;min-width:0;min-height:0;flex-direction:column;overflow:hidden;border:0;border-left:1px solid var(--dsw-alias-border-l1,#dfe3e8);border-radius:0;background:color-mix(in srgb,var(--dsw-alias-bg-base,#fff) 97%,#edf3fc 3%);box-shadow:-8px 0 24px rgba(34,48,72,.04)}.tavern-context-inspector-header{display:flex;min-height:70px;box-sizing:border-box;align-items:flex-start;justify-content:space-between;gap:12px;border-bottom:1px solid var(--dsw-alias-border-l1,#e2e5e9);padding:15px 14px 12px 16px}.tavern-context-inspector-header>div{display:grid;min-width:0;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:5px 8px}.tavern-context-inspector-header strong{overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.tavern-context-inspector-header small{grid-column:2;color:var(--dsw-alias-label-tertiary,#858c96);font-size:9px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-context-inspector-header>button{display:grid;width:26px;height:26px;flex:none;place-items:center;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary,#68717e);cursor:pointer;font:400 20px/1 inherit}.tavern-context-inspector-header>button:hover{background:var(--dsw-alias-interactive-bg-hover,#eef0f3)}
2091
+ .tavern-context-inspector-tabs{display:flex;height:39px;flex:none;gap:20px;border-bottom:1px solid var(--dsw-alias-border-l1,#e2e5e9);padding:0 16px}.tavern-context-inspector-tabs button{position:relative;border:0;padding:0;background:transparent;color:var(--dsw-alias-label-secondary,#68717e);cursor:pointer;font:500 10px/39px inherit}.tavern-context-inspector-tabs button[aria-selected=true]{color:#245fb7;font-weight:650}.tavern-context-inspector-tabs button[aria-selected=true]::after{position:absolute;right:0;bottom:-1px;left:0;height:2px;border-radius:2px;background:#3475d5;content:""}.tavern-context-inspector-content,.tavern-context-inspector-evidence{position:relative;min-height:0;flex:1;overflow:auto}.tavern-context-inspector-content pre{max-height:none;margin:0;padding:20px 18px 60px;color:var(--dsw-alias-label-primary,#25292f);font:11px/1.75 inherit;white-space:pre-wrap;overflow-wrap:anywhere}.tavern-context-inspector-copy{position:sticky;z-index:2;top:12px;float:right;display:flex;height:28px;align-items:center;gap:5px;margin:12px 12px 0 8px;border:1px solid var(--dsw-alias-border-l1,#dfe3e8);border-radius:7px;padding:0 8px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-secondary,#626b78);cursor:pointer;font:500 9px/1 inherit}.tavern-context-inspector-evidence>p{margin:0;padding:20px 18px;color:var(--dsw-alias-label-secondary,#68717e);font-size:10px;line-height:1.65}.tavern-context-workbench .tavern-context-activation-cards{max-height:none;gap:8px;padding:14px}.tavern-context-workbench .tavern-context-activation-cards article{border-radius:8px;padding:10px 11px}.tavern-context-workbench .tavern-context-activation-cards p{font-size:9px;line-height:1.55}
2092
+ @media(max-width:980px){.tavern-context-workbench.has-inspector{grid-template-columns:minmax(0,1fr) 310px}}@media(max-width:720px){.tavern-context-workbench.has-inspector{display:block}.tavern-context-workbench.has-inspector .tavern-context-table-head,.tavern-context-workbench.has-inspector .tavern-context-row-main{grid-template-columns:36px 76px minmax(130px,1fr) 58px 46px}.tavern-context-inspector{position:absolute;z-index:8;inset:0 0 0 auto;width:min(88%,360px);box-shadow:-12px 0 32px rgba(25,39,64,.18)}}
2093
+ /* Scheme 02: align the Tavern context viewer with DSH Trajectory while retaining a little more room for Chinese text. */
2094
+ .tavern-context-view{color:#0f1115;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Helvetica,Arial,sans-serif}
2095
+ .tavern-context-toolbar{grid-template-columns:190px minmax(120px,1fr) 250px;grid-template-rows:56px 27px;column-gap:12px;min-height:83px;border-bottom:1px solid #dfe2e5;padding:0 18px;background:#fff}.tavern-context-request-nav{grid-template-columns:28px minmax(92px,auto) 28px;gap:4px}.tavern-context-request-nav>button{border-radius:4px;color:#777f87}.tavern-context-request-title{gap:1px}.tavern-context-request-title strong{color:#0f1115;font-size:12px;font-weight:600}.tavern-context-request-title span{color:#8f949a;font:10px/1.2 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-context-toolbar-meta>span{border:1px solid #dfe2e5;border-radius:7px;padding:5px 9px;background:#fafafa;color:#61666b;font-size:10px}.tavern-context-model{max-width:180px}.tavern-context-search{height:30px;border-color:#dcdfe2;border-radius:6px;padding:0 9px;color:#8b9095}.tavern-context-search input{color:#0f1115;font-size:12px}.tavern-context-change-filter{height:27px;align-self:stretch;color:#61666b;font-size:10px}.tavern-context-check{width:14px;height:14px;border-color:#d5d8dc;border-radius:3px}
2096
+ .tavern-context-table-head,.tavern-context-row-main{grid-template-columns:50px 92px minmax(180px,270px) 70px minmax(190px,1fr) 52px}.tavern-context-table-head{height:30px;border-bottom-color:#dfe2e5;background:#fafafa;color:#8b9095;font-size:10px}.tavern-context-table-head>span,.tavern-context-row-main>span{padding:0 9px}.tavern-context-row{border-bottom-color:#eff0f1;background:#fff}.tavern-context-row.is-worldbook::before{display:none}.tavern-context-row.is-changed{background:#fff}.tavern-context-row.is-selected{background:#e8eaed}.tavern-context-row.is-selected::before{position:absolute;z-index:2;top:0;bottom:0;left:0;display:block;width:3px;background:#3478f6;content:""}.tavern-context-row-main{height:38px;min-height:38px}.tavern-context-row-main:hover{background:#f4f5f6}.tavern-context-row-main:focus-visible{outline-color:#3478f6}.tavern-context-order{color:#8f949a;font-size:10px;font-weight:400}.tavern-context-role{border-radius:4px;padding:3px 7px!important;font-size:10px;font-weight:650;letter-spacing:0}.tavern-context-role.is-system{background:#eef0f2;color:#3f4b56}.tavern-context-role.is-user{background:#e8f0ff;color:#3371d6}.tavern-context-role.is-assistant{background:#f2ecfa;color:#7b58b5}.tavern-context-source{gap:0}.tavern-context-source strong{font-size:12px;font-weight:600}.tavern-context-source small{color:#8f949a;font-size:9px;line-height:1.25}.tavern-context-tokens{color:#61666b;font-size:10px;font-weight:400}.tavern-context-preview{color:#394047;font-size:12px}.tavern-context-expand{padding-right:20px!important;color:#777f87}.tavern-context-row-copy{top:6px;right:4px;width:26px;height:26px;border-radius:4px;color:#777f87}
2097
+ .tavern-context-workbench.has-inspector{grid-template-columns:minmax(0,1fr) 386px}.tavern-context-workbench.has-inspector .tavern-context-table-head,.tavern-context-workbench.has-inspector .tavern-context-row-main{grid-template-columns:46px 82px minmax(145px,1fr) 58px 42px}.tavern-context-workbench>.tavern-context-inspector{border-left:1px solid #dfe2e5;background:#fdfdfd;box-shadow:none}.tavern-context-inspector-header{min-height:54px;align-items:center;border-bottom-color:#dfe2e5;padding:0 16px}.tavern-context-inspector-header>div{grid-template-columns:auto minmax(0,1fr);gap:2px 9px}.tavern-context-inspector-header strong{font-size:13px;font-weight:600}.tavern-context-inspector-header small{color:#61666b;font:10px/1.2 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-context-inspector-header>button{width:28px;height:28px;border-radius:4px;color:#61666b}.tavern-context-inspector-tabs{height:42px;gap:28px;border-bottom-color:#dfe2e5;padding:0 16px}.tavern-context-inspector-tabs button{height:42px;color:#61666b;font:400 13px/42px inherit}.tavern-context-inspector-tabs button[aria-selected=true]{color:#3478f6;font-weight:400}.tavern-context-inspector-tabs button[aria-selected=true]::after{background:#3478f6}.tavern-context-inspector-content pre{padding:16px 18px 60px;color:#0f1115;font-family:inherit;font-size:14px;font-weight:400;line-height:1.72}.tavern-context-inspector-copy{top:10px;height:26px;margin:10px 12px 0 8px;border:0;border-radius:4px;padding:0 6px;background:transparent;color:#61666b;font-size:10px}.tavern-context-inspector-evidence>p{padding:16px 18px;color:#61666b;font-size:12px;line-height:1.65}.tavern-context-workbench .tavern-context-activation-cards{gap:10px;padding:16px 18px}.tavern-context-workbench .tavern-context-activation-cards article{border:0;border-left:3px solid #2aa65a;border-radius:0;padding:8px 11px;background:#f7faf8}.tavern-context-workbench .tavern-context-activation-cards strong{font-size:12px}.tavern-context-workbench .tavern-context-activation-cards span{color:#2a7d49;font-size:9px}.tavern-context-workbench .tavern-context-activation-cards p{color:#61666b;font-size:11px;line-height:1.5}
2098
+ @media(max-width:980px){.tavern-context-toolbar{grid-template-columns:180px minmax(100px,1fr) 220px}.tavern-context-workbench.has-inspector{grid-template-columns:minmax(0,1fr) 340px}}@media(max-width:720px){.tavern-context-toolbar{grid-template-columns:1fr auto;grid-template-rows:auto auto auto;padding:8px 12px}.tavern-context-workbench.has-inspector{display:block}.tavern-context-workbench.has-inspector .tavern-context-table-head,.tavern-context-workbench.has-inspector .tavern-context-row-main{grid-template-columns:36px 76px minmax(130px,1fr) 58px 46px}.tavern-context-inspector{position:absolute;z-index:8;inset:0 0 0 auto;width:min(88%,360px);box-shadow:-12px 0 32px rgba(25,39,64,.18)}}
2099
+ [data-tavern-sidebar-host="true"],.tavern-sidebar-content{box-sizing:border-box;min-width:0;color:var(--dsw-alias-label-primary,#17191d);font-family:inherit}
2100
+ [data-tavern-sidebar-host="true"]{flex:none;padding:2px 8px 8px 4px}[data-slot="sidebar.workspaces"][data-tavern-mode="tavern"]>[data-tavern-sidebar-host="true"]{display:flex;flex:1;min-height:0;padding-bottom:0}[data-slot="sidebar.workspaces"][data-tavern-mode="tavern"]>[data-tavern-native-workspace="true"]{display:none!important}[data-slot="sidebar.workspaces"][data-tavern-mode="tavern"][data-nwh-mode="chat"]>[data-nwh-sidebar-mode-host="true"]{display:none!important}.tavern-sidebar-content{display:flex;flex:1;min-height:0;flex-direction:column;animation:tavern-content-in .16s ease-out}@keyframes tavern-content-in{from{opacity:.45;transform:translateX(4px)}to{opacity:1;transform:none}}
2101
+ .tavern-panel-screen{min-height:0;flex:1;overflow:auto;padding:10px 4px 14px}.tavern-tabs{display:flex;flex:none;height:40px;border-bottom:1px solid var(--dsw-alias-border-l1,#e4e6e9);gap:22px;padding:0 8px}.tavern-tabs button{position:relative;display:flex;align-items:center;gap:6px;border:0;padding:0;background:transparent;color:var(--dsw-alias-label-secondary,#727985);cursor:pointer;font:500 14px/40px inherit}.tavern-tabs button[aria-selected=true]{color:#1757b8;font-weight:650}.tavern-tabs button[aria-selected=true]::after{position:absolute;right:0;bottom:-1px;left:0;height:2px;border-radius:2px;background:#2d69c9;content:""}
2102
+ [data-sidebar-collapsed] [data-tavern-sidebar-host="true"]{padding:0 0 10px}[data-sidebar-collapsed] [data-slot="sidebar.workspaces"][data-tavern-mode="tavern"]>[data-tavern-sidebar-host="true"]{flex:none;min-height:auto;padding-bottom:10px}[data-sidebar-collapsed] .tavern-sidebar-content{flex:none}[data-sidebar-collapsed] .tavern-panel-screen{display:none}[data-sidebar-collapsed] .tavern-tabs{width:36px;height:auto;box-sizing:border-box;flex-direction:column;gap:2px;border:1px solid var(--dsw-alias-border-l2,#d7dbe1);border-radius:10px;padding:3px;background:color-mix(in srgb,var(--dsw-specific-sidebar-fill,#f7f8fa) 82%,var(--dsw-alias-bg-base,#fff) 18%)}[data-sidebar-collapsed] .tavern-tabs button{display:grid;width:28px;height:28px;place-items:center;border-radius:7px;padding:0;line-height:1}[data-sidebar-collapsed] .tavern-tabs button:hover{background:var(--dsw-alias-interactive-bg-hover,#eef0f3);color:var(--dsw-alias-label-primary,#17191d)}[data-sidebar-collapsed] .tavern-tabs button[aria-selected=true]{background:var(--dsw-alias-bg-base,#fff);box-shadow:0 1px 4px rgba(25,36,60,.1)}[data-sidebar-collapsed] .tavern-tabs button[aria-selected=true]::after{display:none}[data-sidebar-collapsed] .tavern-tab-label{display:none}
2103
+ .tavern-toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;margin:14px 2px 15px}.tavern-search{display:flex;align-items:center;gap:7px;height:34px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2,#d6d9de);border-radius:8px;padding:0 9px;color:var(--dsw-alias-label-tertiary,#858b95);background:var(--dsw-alias-bg-base,#fff)}.tavern-search:focus-within{border-color:#8daadd;box-shadow:0 0 0 2px rgba(75,119,192,.1)}.tavern-search input{min-width:0;width:100%;border:0;outline:0;background:transparent;color:inherit;font:400 12px/1 inherit}.tavern-import-button{display:flex;align-items:center;gap:5px;height:34px;border:0;border-radius:8px;padding:0 8px;background:transparent;color:inherit;cursor:pointer;font:550 12px/1 inherit}.tavern-import-button:hover{background:var(--dsw-alias-interactive-bg-hover,#f0f1f3)}.tavern-import-button:disabled{cursor:wait;opacity:.55}.tavern-file-input{display:none}.tavern-library-label{display:flex;align-items:center;justify-content:space-between;margin:0 5px 4px}.tavern-library-label strong{font-size:12px}.tavern-library-label small{color:var(--dsw-alias-label-tertiary,#8a909a);font-size:10px}
2104
+ .tavern-inline-notice,.tavern-start-error{margin:8px 2px;border-radius:7px;padding:8px 9px;font-size:10px;line-height:1.45}.tavern-inline-notice{display:flex;flex-direction:column;background:var(--dsw-alias-interactive-bg-hover,#f0f1f3);color:var(--dsw-alias-label-secondary,#555d69)}.tavern-inline-notice small{margin-top:2px;color:var(--dsw-alias-label-tertiary,#858b95)}.tavern-start-error{background:#fff1f1;color:#923b3b}
2105
+ .tavern-card-list{display:flex;flex-direction:column;gap:6px}.tavern-card-branch{position:relative;overflow:visible;border:1px solid transparent;border-radius:10px}.tavern-card-branch.is-expanded{border-color:var(--dsw-alias-border-l1,#e3e6eb);background:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,#edf2f9) 48%,transparent)}.tavern-card-row{display:grid;grid-template-columns:54px minmax(0,1fr) 16px;align-items:center;gap:9px;width:100%;border:0;border-radius:9px;padding:8px;background:transparent;color:inherit;cursor:pointer;text-align:left}.tavern-card-row:hover{background:var(--dsw-alias-interactive-bg-hover,#f3f4f5)}.tavern-card-thumb{display:block;width:54px;height:54px;object-fit:cover;border:1px solid var(--dsw-alias-border-l2,#d6d9de);border-radius:9px;background:#eef0f3}.tavern-card-placeholder{display:grid;box-sizing:border-box;place-items:center;background:linear-gradient(145deg,#e8edf5,#d8e0ec);color:#52647f;font-size:20px;font-weight:700}.tavern-card-row-copy{display:flex;min-width:0;flex-direction:column}.tavern-card-row-copy>strong{overflow:hidden;font-size:12px;line-height:1.4;text-overflow:ellipsis;white-space:nowrap}.tavern-card-row-copy>small{margin-top:1px;color:var(--dsw-alias-label-tertiary,#868c96);font-size:10px}.tavern-card-meta{display:flex;align-items:flex-end;justify-content:space-between;gap:4px}.tavern-card-meta>small{color:var(--dsw-alias-label-tertiary,#878d97);font-size:9px;white-space:nowrap}.tavern-chevron{color:var(--dsw-alias-label-tertiary,#8f959f)}.tavern-status-line{display:flex;align-items:center;gap:5px;margin-top:4px;color:#1e8a55;font-size:9px;min-width:0}.tavern-status-line i{width:5px;height:5px;flex:none;border-radius:50%;background:currentColor}.tavern-status-line strong{overflow:hidden;font-weight:550;text-overflow:ellipsis;white-space:nowrap}.tavern-status-line.is-degraded{color:#98731f}.tavern-status-line.is-blocked{color:#b33d49}
2106
+ .tavern-card-context-menu{position:absolute;z-index:20;top:10px;right:10px;display:flex;width:136px;box-sizing:border-box;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#d5dae1);border-radius:9px;padding:5px;background:var(--dsw-alias-bg-base,#fff);box-shadow:0 10px 28px rgba(25,37,58,.2)}.tavern-card-context-menu button{appearance:none;display:flex;height:32px;align-items:center;border:0;border-radius:6px;padding:0 9px;background:transparent;color:var(--dsw-alias-label-primary,#30363d);cursor:pointer;font-family:inherit;font-size:11px;font-weight:500;line-height:1;text-align:left}.tavern-card-context-menu button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eef2f7)}.tavern-card-context-menu button:disabled{cursor:not-allowed;opacity:.35}.tavern-card-context-menu button.is-danger{color:#a43d48}.tavern-card-context-menu>[role=separator]{height:1px;margin:4px;background:var(--dsw-alias-border-l1,#e5e8ec)}
2107
+ .tavern-session-tree{position:relative;margin:0 7px 8px 10px;padding-left:14px}.tavern-session-tree::before{position:absolute;top:0;bottom:10px;left:0;border-left:1px dashed var(--dsw-alias-border-l2,#cfd5dd);content:""}.tavern-report-row,.tavern-new-game-row,.tavern-session-row{position:relative;display:grid;align-items:center;width:100%;border:0;border-radius:7px;background:transparent;color:inherit;cursor:pointer;text-align:left}.tavern-report-row::before,.tavern-new-game-row::before,.tavern-session-row::before{position:absolute;left:-14px;width:10px;border-top:1px dashed var(--dsw-alias-border-l2,#cfd5dd);content:""}.tavern-report-row,.tavern-new-game-row{grid-template-columns:22px 1fr;gap:4px;height:34px;color:var(--dsw-alias-label-secondary,#59616d);font:550 12px/1 inherit}.tavern-report-row:hover,.tavern-new-game-row:hover{background:rgba(255,255,255,.7);color:#215eb7}.tavern-new-game-row:disabled{cursor:not-allowed;color:var(--dsw-alias-label-tertiary,#9aa0aa)}.tavern-session-row{grid-template-columns:22px minmax(0,1fr) 16px;gap:4px;min-height:46px;padding:5px 6px}.tavern-session-row:hover{background:rgba(255,255,255,.72)}.tavern-session-row.is-selected{background:#e7eef9;color:#172f55}.tavern-session-row:disabled{cursor:wait}.tavern-session-icon{display:grid;place-items:center;color:var(--dsw-alias-label-secondary,#677080)}.tavern-session-copy{display:flex;min-width:0;flex-direction:column}.tavern-session-copy strong{overflow:hidden;font-size:12px;line-height:1.35;text-overflow:ellipsis;white-space:nowrap}.tavern-session-copy small{margin-top:2px;color:var(--dsw-alias-label-tertiary,#838b97);font-size:10px}.tavern-row-more{color:var(--dsw-alias-label-tertiary,#8b929c);opacity:0}.tavern-session-row:hover .tavern-row-more,.tavern-session-row.is-selected .tavern-row-more{opacity:1}.tavern-empty{margin:25px 0;color:var(--dsw-alias-label-tertiary,#8b929c);font-size:11px;text-align:center}
2108
+ .tavern-back-button{display:flex;align-items:center;gap:5px;max-width:100%;border:0;padding:3px 2px;background:transparent;color:var(--dsw-alias-label-secondary,#5a6270);cursor:pointer;font:550 11px/1.3 inherit}.tavern-back-button span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-card-summary{display:grid;grid-template-columns:64px 1fr;gap:12px;margin:18px 2px 14px}.tavern-card-summary h2{margin:1px 0 0;font-size:16px}.tavern-card-summary p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#626a77);font-size:10px;line-height:1.5}.tavern-compatibility{border:1px solid var(--dsw-alias-border-l1,#e2e4e7);border-radius:8px;padding:10px 11px;background:var(--dsw-alias-bg-subtle,#fafafa)}.tavern-compatibility>.tavern-status-line{margin-top:0}.tavern-compatibility>p{margin:6px 0 0;color:var(--dsw-alias-label-secondary,#606875);font-size:10px;line-height:1.5}.tavern-compatibility>button{margin:7px 0 0;border:0;padding:0;background:transparent;color:var(--dsw-alias-label-tertiary,#7e858f);cursor:pointer;font:500 10px/1 inherit}.tavern-technical-note{margin-top:8px;border-top:1px solid var(--dsw-alias-border-l1,#e4e6e9);padding-top:8px;color:var(--dsw-alias-label-tertiary,#838995);font-size:9px;line-height:1.45}.tavern-technical-note ul{display:flex;flex-direction:column;gap:7px;margin:0;padding:0;list-style:none}.tavern-technical-note li{display:flex;flex-direction:column;gap:1px}.tavern-technical-note li strong{color:var(--dsw-alias-label-secondary,#626a77);font-size:9px}.tavern-technical-note p{overflow-wrap:anywhere;margin:8px 0 0;border-top:1px dashed var(--dsw-alias-border-l2,#d4d7dc);padding-top:6px}
2109
+ .tavern-form-section{display:flex;flex-direction:column;gap:13px;margin-top:16px}.tavern-form-section>label,.tavern-form-section fieldset{display:flex;flex-direction:column;gap:6px;border:0;margin:0;padding:0}.tavern-form-section>label>span,.tavern-form-section legend{color:var(--dsw-alias-label-secondary,#565e6b);font-size:10px;font-weight:600}.tavern-form-section input:not([type=radio]),.tavern-form-section select,.tavern-form-section textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2,#d5d8dd);border-radius:7px;padding:8px 9px;background:var(--dsw-alias-bg-base,#fff);color:inherit;font:400 11px/1.45 inherit;outline:none}.tavern-form-section input:focus,.tavern-form-section select:focus,.tavern-form-section textarea:focus{border-color:#8ca9d8;box-shadow:0 0 0 2px rgba(91,135,205,.12)}.tavern-opening{display:grid;grid-template-columns:16px 1fr;gap:6px;border:1px solid var(--dsw-alias-border-l1,#e2e4e7);border-radius:7px;padding:8px;background:var(--dsw-alias-bg-base,#fff);cursor:pointer}.tavern-opening.is-selected{border-color:#9db5d9;background:#f6f8fc}.tavern-opening input{margin:2px 0 0}.tavern-opening span{display:flex;min-width:0;flex-direction:column}.tavern-opening strong{font-size:10px}.tavern-opening small{display:-webkit-box;margin-top:3px;overflow:hidden;color:var(--dsw-alias-label-secondary,#68707c);font-size:9px;line-height:1.4;-webkit-box-orient:vertical;-webkit-line-clamp:2}.tavern-opening-preview{border-radius:7px;padding:9px 10px;background:var(--dsw-alias-interactive-bg-hover,#f1f2f4)}.tavern-opening-preview span{font-size:10px;font-weight:600}.tavern-opening-preview p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#626a77);font-size:9px;line-height:1.5}.tavern-advanced-button{height:32px;border:1px dashed var(--dsw-alias-border-l2,#cfd3d9);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#646c78);cursor:pointer;font:500 10px/1 inherit}
2110
+ .tavern-panel-footer{position:sticky;bottom:-14px;display:flex;align-items:center;justify-content:space-between;gap:10px;margin:18px -4px -14px;border-top:1px solid var(--dsw-alias-border-l1,#e3e5e8);padding:10px 12px;background:color-mix(in srgb,var(--dsw-alias-bg-base,#fff) 96%,transparent);backdrop-filter:blur(8px)}.tavern-panel-footer.is-blocked{position:static;margin-top:16px;background:var(--dsw-alias-bg-subtle,#fafafa);backdrop-filter:none}.tavern-panel-footer p{margin:0;color:var(--dsw-alias-label-tertiary,#7e858f);font-size:9px}.tavern-start-button{flex:none;height:34px;border:0;border-radius:7px;padding:0 13px;background:#215eb7;color:#fff;cursor:pointer;font:600 11px/1 inherit}.tavern-start-button:hover{background:#194f9e}.tavern-start-button:disabled{background:var(--dsw-alias-interactive-bg-hover,#e4e6e9);color:var(--dsw-alias-label-tertiary,#999ea6);cursor:not-allowed}
2111
+ .tavern-opening-dock{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance,20px) * 2);max-width:calc(var(--dsh-composer-card-max-width,850px) - 24px);margin:0 auto -6px;padding:0 12px;position:relative;z-index:2}.tavern-opening-dock-panel{overflow:hidden;border:1px solid var(--dsw-alias-border-l1,#dfe3e8);border-radius:14px 14px 8px 8px;background:color-mix(in srgb,var(--dsw-alias-bg-subtle,#f7f8fa) 82%,var(--dsw-alias-bg-base,#fff) 18%);box-shadow:0 1px 3px rgba(25,36,60,.045)}.tavern-opening-dock-toolbar{display:grid;grid-template-columns:34px minmax(0,1fr) 34px;align-items:center;min-height:40px;padding:2px 8px}.tavern-opening-navigation{display:grid;grid-template-columns:28px auto 28px;align-items:center;justify-content:center;gap:3px}.tavern-opening-navigation button,.tavern-opening-disclosure{display:grid;width:28px;height:28px;place-items:center;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#626b78);cursor:pointer}.tavern-opening-navigation button:hover:not(:disabled),.tavern-opening-disclosure:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eef0f3)}.tavern-opening-navigation button:disabled,.tavern-opening-disclosure:disabled{cursor:default;opacity:.42}.tavern-opening-current{min-width:84px;padding:0 4px;color:var(--dsw-alias-label-primary,#20242a);font-size:12px;font-weight:550;text-align:center;white-space:nowrap}.tavern-opening-disclosure{justify-self:end}.tavern-opening-disclosure svg{transition:transform .16s ease}.tavern-opening-disclosure.is-expanded svg{transform:rotate(180deg)}.tavern-opening-menu{max-height:210px;overflow:auto;border-top:1px solid var(--dsw-alias-border-l1,#e1e4e8);padding:5px;background:var(--dsw-alias-bg-base,#fff)}.tavern-opening-menu button{display:flex;width:100%;box-sizing:border-box;flex-direction:column;gap:2px;border:0;border-radius:7px;padding:7px 9px;background:transparent;color:inherit;cursor:pointer;text-align:left}.tavern-opening-menu button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eef0f3)}.tavern-opening-menu button[aria-selected=true]{background:var(--dsw-alias-interactive-bg-hover,#eef0f3);color:var(--dsw-alias-label-primary,#20242a)}.tavern-opening-menu span{font-size:12px;font-weight:500}.tavern-opening-menu small{overflow:hidden;color:var(--dsw-alias-label-secondary,#68717e);font-size:11px;line-height:1.4;text-overflow:ellipsis;white-space:nowrap}.tavern-opening-error{margin:0;border-top:1px solid var(--dsw-alias-border-l1,#e5e7ea);padding:6px 12px;color:#a43d48;background:#fff4f4;font-size:10px;line-height:1.35}
2112
+ .tavern-opening-dock{z-index:0}.tavern-opening-dock-panel{border-bottom:0;border-radius:14px 14px 0 0;box-shadow:0 -1px 3px rgba(25,36,60,.035)}
2113
+ .tavern-opening-option-copy{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:baseline;gap:5px;min-width:0}.tavern-opening-option-copy strong{font-size:12px;font-weight:600}.tavern-opening-menu .tavern-opening-option-copy small{display:block;min-width:0;font-size:11px;font-weight:400}
2114
+ .tavern-opening-message{color:inherit;font:inherit;line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere}
2115
+ .tavern-surface-update-event.is-narrow{align-items:flex-start;flex-direction:column;justify-content:center;gap:1px}.tavern-surface-update-event.is-narrow strong{width:100%;flex:none}.tavern-surface-update-event.is-narrow>span{font-size:9px}
2116
+ .tavern-surface-version-meta{display:block;margin-top:3px;color:#3d72c8;font:500 10px/1.35 inherit}.tavern-surface-update-event{position:absolute;inset:0;display:flex;box-sizing:border-box;align-items:center;justify-content:space-between;gap:8px;padding:0 12px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#25292f);pointer-events:none}.tavern-surface-update-event strong{min-width:0;flex:1;overflow:hidden;font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tavern-surface-update-event>span{flex:none;color:var(--dsw-alias-label-secondary,#68717e);font:500 10px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap}td[data-tavern-surface-update-cell="true"]{position:relative;min-height:42px;color:transparent!important}tr[data-selected="true"] .tavern-surface-update-event,tr[data-tavern-relation="current"] .tavern-surface-update-event{background:color-mix(in srgb,var(--dsw-alias-interactive-bg-selected,#e9f0fb) 76%,var(--dsw-alias-bg-base,#fff) 24%)}tr[data-tavern-relation="history"]{opacity:.78}tr[data-tavern-locate-pulse="true"]{animation:tavern-surface-locate .6s ease-out}@keyframes tavern-surface-locate{0%,100%{outline-color:transparent}35%{outline:2px solid #6e86ca;outline-offset:-2px}}.tavern-surface-version-stem{position:fixed;z-index:9998;width:1px;pointer-events:none;background:repeating-linear-gradient(180deg,#b9c5d8 0 3px,transparent 3px 7px)}#trajectory-detail-panel[data-tavern-surface-detail-active="true"]>:not([data-tavern-surface-detail]){display:none!important}.tavern-surface-detail{box-sizing:border-box;margin:0;padding:13px 15px 16px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#25292f);animation:tavern-surface-detail-in .2s ease-out}@keyframes tavern-surface-detail-in{from{opacity:.35;transform:translateY(-3px)}to{opacity:1;transform:none}}.tavern-surface-detail-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;border-bottom:1px solid var(--dsw-alias-border-l1,#e1e4e8);padding-bottom:11px}.tavern-surface-detail-heading strong{font-size:13px;font-weight:650}.tavern-surface-detail-heading p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#68717e);font-size:10px;line-height:1.5}.tavern-surface-version-history{position:relative;display:grid;gap:0;margin:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#e1e4e8);padding-bottom:8px}.tavern-surface-version-history>*{display:flex;flex-direction:column;gap:2px;border:0;border-left:2px solid transparent;padding:6px 9px;background:transparent;color:inherit;text-align:left}.tavern-surface-version-history button{cursor:pointer}.tavern-surface-version-history button:hover{background:var(--dsw-alias-interactive-bg-hover,#f3f4f6)}.tavern-surface-version-history .is-history{color:var(--dsw-alias-label-secondary,#68717e)}.tavern-surface-version-history .is-current{border-left-color:#5685dc;background:color-mix(in srgb,var(--dsw-alias-interactive-bg-selected,#e9f0fb) 54%,transparent);color:#2f68c5}.tavern-surface-version-history strong{font-size:10px;font-weight:600}.tavern-surface-version-history span{font-size:9px;line-height:1.4}.tavern-surface-change-summary{margin:0 0 7px;color:var(--dsw-alias-label-secondary,#68717e);font-size:9px;font-weight:550}.tavern-surface-comparison{display:grid;gap:8px}.tavern-surface-comparison article{min-width:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l1,#dfe3e8);border-radius:8px;background:var(--dsw-alias-bg-subtle,#f8f9fa)}.tavern-surface-comparison header{border-bottom:1px solid var(--dsw-alias-border-l1,#e3e6ea);padding:6px 8px;color:var(--dsw-alias-label-secondary,#68717e);font-size:9px;font-weight:600}.tavern-surface-comparison pre{max-height:178px;margin:0;overflow:auto;padding:8px;color:var(--dsw-alias-label-primary,#25292f);font:9px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.tavern-surface-comparison article:first-child{opacity:.78}.tavern-surface-version-chain{margin:8px 0 0;color:var(--dsw-alias-label-tertiary,#7e858f);font:9px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-surface-technical{margin-top:9px;color:var(--dsw-alias-label-secondary,#68717e);font-size:9px}.tavern-surface-technical summary{cursor:pointer}.tavern-surface-technical pre{overflow:auto;border-radius:7px;padding:7px;background:var(--dsw-alias-bg-subtle,#f5f6f8);font:9px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace}@media (prefers-reduced-motion:reduce){tr[data-tavern-locate-pulse="true"],.tavern-surface-detail{animation:none!important}}
2117
+ .tavern-assembly-event{position:absolute;inset:0;display:grid;box-sizing:border-box;grid-template-columns:24px minmax(0,1fr) auto;align-items:center;gap:8px;padding:0 12px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#25292f);pointer-events:none}.tavern-assembly-icon{display:grid;width:20px;height:20px;place-items:center;border-radius:6px;background:color-mix(in srgb,#5685dc 12%,transparent);color:#5685dc;font-size:14px}.tavern-assembly-copy{display:flex;min-width:0;flex-direction:column;gap:2px}.tavern-assembly-copy strong{overflow:hidden;font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tavern-assembly-copy>span{overflow:hidden;color:var(--dsw-alias-label-secondary,#68717e);font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-assembly-delta{color:var(--dsw-alias-label-secondary,#68717e);font:550 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace}td[data-tavern-assembly-cell="true"]{position:relative;min-height:42px;color:transparent!important}tr[data-selected="true"] .tavern-assembly-event{background:color-mix(in srgb,var(--dsw-alias-interactive-bg-selected,#e9f0fb) 76%,var(--dsw-alias-bg-base,#fff) 24%)}tr[data-tavern-relation="history"]{background:color-mix(in srgb,#5685dc 8%,transparent)}#trajectory-detail-panel[data-tavern-assembly-detail-active="true"]>:not([data-tavern-assembly-detail]){display:none!important}.tavern-assembly-detail{box-sizing:border-box;height:100%;overflow:auto;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#25292f);animation:tavern-surface-detail-in .2s ease-out}.tavern-assembly-detail-heading{padding:14px 16px 11px;border-bottom:1px solid var(--dsw-alias-border-l1,#e1e4e8)}.tavern-assembly-detail-heading strong{font-size:13px;font-weight:650}.tavern-assembly-detail-heading p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#68717e);font-size:10px}.tavern-assembly-tabs{display:flex;height:38px;align-items:end;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#e1e4e8);padding:0 16px}.tavern-assembly-tabs button{height:38px;border:0;border-bottom:2px solid transparent;padding:0 1px;background:transparent;color:var(--dsw-alias-label-secondary,#68717e);cursor:pointer;font:500 11px/1 inherit}.tavern-assembly-tabs button.is-active{border-bottom-color:#3978df;color:#3978df}.tavern-assembly-tab-body{padding:14px 16px 20px}.tavern-assembly-stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.tavern-assembly-stat{display:flex;min-width:0;flex-direction:column;gap:3px;border:1px solid var(--dsw-alias-border-l1,#e1e4e8);border-radius:9px;padding:10px;background:var(--dsw-alias-bg-subtle,#f7f8fa)}.tavern-assembly-stat strong{font-size:17px;font-weight:600}.tavern-assembly-stat span{color:var(--dsw-alias-label-secondary,#68717e);font-size:9px}.tavern-assembly-section{margin-top:16px}.tavern-assembly-section h4{margin:0 0 8px;font-size:10px;font-weight:650}.tavern-assembly-changes{display:flex;flex-wrap:wrap;gap:5px}.tavern-assembly-changes span{border-radius:999px;padding:3px 7px;background:var(--dsw-alias-bg-subtle,#f1f3f5);font-size:9px}.tavern-assembly-changes .is-added{background:#edf8f1;color:#27844d}.tavern-assembly-changes .is-removed{background:#fff0f0;color:#aa4751}.tavern-assembly-changes .is-quiet{color:var(--dsw-alias-label-secondary,#68717e)}.tavern-assembly-previous{margin-top:10px;border:0;padding:0;background:transparent;color:#3978df;cursor:pointer;font:500 9px/1 inherit}.tavern-activation-list,.tavern-layout-list,.tavern-request-list{display:grid;gap:7px}.tavern-activation-item,.tavern-layout-block,.tavern-request-message{min-width:0;border:1px solid var(--dsw-alias-border-l1,#e1e4e8);border-radius:8px;background:var(--dsw-alias-bg-base,#fff)}.tavern-activation-item{border-left:3px solid #55aa73;padding:8px 9px}.tavern-activation-item.is-filtered{border-left-color:#c6cbd2;opacity:.72}.tavern-activation-item>div{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-activation-item strong{overflow:hidden;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.tavern-activation-item span,.tavern-activation-item p{color:var(--dsw-alias-label-secondary,#68717e);font-size:8px}.tavern-activation-item p{margin:5px 0 0;line-height:1.45}.tavern-layout-block{display:grid;grid-template-columns:24px minmax(0,1fr) auto;align-items:center;gap:8px;padding:8px}.tavern-layout-block.is-disabled{opacity:.48}.tavern-layout-index{color:var(--dsw-alias-label-tertiary,#8a919b);font:500 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-layout-block strong{font-size:10px}.tavern-layout-block p{display:-webkit-box;margin:3px 0 0;overflow:hidden;color:var(--dsw-alias-label-secondary,#68717e);font-size:8px;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}.tavern-layout-meta{color:var(--dsw-alias-label-tertiary,#858c96);font-size:8px;white-space:nowrap}.tavern-layout-injection{margin:-3px 8px 0 31px;border-left:2px solid #8ba9dd;padding:4px 8px;color:#3d72c8;background:color-mix(in srgb,#5685dc 7%,transparent);font-size:8px}.tavern-request-message{overflow:hidden}.tavern-request-message header{display:flex;align-items:center;gap:7px;border-bottom:1px solid var(--dsw-alias-border-l1,#e1e4e8);padding:6px 8px;color:var(--dsw-alias-label-secondary,#68717e);font-size:8px}.tavern-request-message header span:first-child{border-radius:4px;padding:2px 5px;font-weight:650}.tavern-request-message header .is-system{background:#edf0f4;color:#536070}.tavern-request-message header .is-user{background:#eaf2ff;color:#3972c9}.tavern-request-message header .is-assistant{background:#f2edff;color:#7758b3}.tavern-request-message pre{max-height:155px;margin:0;overflow:auto;padding:8px;color:var(--dsw-alias-label-primary,#25292f);font:9px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}@media (prefers-reduced-motion:reduce){.tavern-assembly-detail,tr[data-tavern-locate-pulse="true"]{animation:none!important}}
2118
+ tr[data-tavern-assembly-row="true"]{display:none!important}
2119
+ @media (prefers-reduced-motion:reduce){.tavern-sidebar-content{animation:none}.tavern-capability-panel,body:has(.tavern-card-capability-rail){transition:none!important}}@media (max-width:760px){.tavern-toolbar{grid-template-columns:minmax(0,1fr) auto}}
2120
+ .tavern-native-rich-display{min-width:0;width:100%}.tavern-native-message{display:flex;min-width:0;flex-direction:column;gap:16px;color:var(--dsw-alias-label-primary,#25292f);font-size:16px;line-height:28px}.tavern-native-rich-frame{display:block;box-sizing:border-box;width:100%;height:72px;border:0;background:transparent}.tavern-native-hosted-frame{display:block;box-sizing:border-box;width:100%;height:min(520px,60vh);border:1px solid var(--dsw-alias-border-l1,#dfe3e8);border-radius:14px;background:var(--dsw-alias-bg-subtle,#f7f8fa)}.tavern-native-hosted-frame.is-required-asset{height:360px}
2121
+ .tavern-native-companion-frame{position:fixed;z-index:90;top:0;left:0;display:block;width:100vw;height:100vh;border:0;clip-path:inset(0 100% 100% 0);pointer-events:none;background:transparent;opacity:1;filter:none;mix-blend-mode:normal;color-scheme:light dark}
2122
+ .tavern-companion-permission{position:fixed;z-index:98;top:72px;left:16px;display:grid;max-width:360px;gap:8px;padding:14px;border:1px solid #e4a11b;border-radius:12px;background:#fff8e8;color:#3a2b12;box-shadow:0 12px 32px #0003;font-size:13px;line-height:20px}.tavern-companion-permission button{cursor:pointer;justify-self:start;border:0;border-radius:999px;padding:8px 14px;background:#1f6feb;color:#fff;font-weight:700}
2123
+ :root{--tavern-capability-rail-width:64px;--tavern-capability-panel-width:clamp(460px,35vw,560px)}
2124
+ body:has(.tavern-card-capability-rail){box-sizing:border-box;padding-right:var(--tavern-capability-rail-width);transition:padding-right .2s cubic-bezier(.2,.7,.2,1)}body:has(.tavern-capability-panel.is-open){padding-right:calc(var(--tavern-capability-rail-width) + var(--tavern-capability-panel-width))}
2125
+ .tavern-card-capability-rail,.tavern-capability-panel{color:#17191d;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
2126
+ .tavern-card-capability-rail{position:fixed;z-index:125;top:0;right:0;bottom:0;display:flex;width:var(--tavern-capability-rail-width);box-sizing:border-box;flex-direction:column;align-items:stretch;border-left:1px solid #dfe2e5;background:#fafbfc}.tavern-card-capability-rail>button{position:relative;display:flex;height:49px;box-sizing:border-box;flex:none;flex-direction:column;align-items:center;justify-content:center;gap:3px;border:0;border-radius:0;background:transparent;color:#5f6770;cursor:pointer;font:500 10px/1 inherit}.tavern-card-capability-rail>button:hover{background:#f0f3f7;color:#2468c7}.tavern-card-capability-rail>button.is-active{background:#edf4ff;color:#1767d8}.tavern-card-capability-rail>button.is-active::before{position:absolute;top:7px;bottom:7px;left:0;width:2px;border-radius:0 2px 2px 0;background:#2877eb;content:""}.tavern-card-capability-rail>button.is-group-start{margin-top:7px;border-top:1px solid #e2e5e9;padding-top:7px}.tavern-card-capability-rail>.tavern-capability-handle{height:42px;margin-bottom:5px;border-bottom:1px solid #e2e5e9}.tavern-card-capability-rail>.tavern-capability-handle::before{display:none}
2127
+ .tavern-capability-panel{position:fixed;z-index:124;top:0;right:var(--tavern-capability-rail-width);bottom:0;display:flex;width:var(--tavern-capability-panel-width);min-width:0;box-sizing:border-box;flex-direction:column;overflow:hidden;border-left:1px solid #dfe2e5;background:#fff;box-shadow:-12px 0 32px rgba(26,38,58,.08);transform:translateX(calc(100% + var(--tavern-capability-rail-width)));visibility:hidden;pointer-events:none;transition:transform .2s cubic-bezier(.2,.7,.2,1),visibility 0s linear .2s}.tavern-capability-panel.is-open{transform:translateX(0);visibility:visible;pointer-events:auto;transition:transform .2s cubic-bezier(.2,.7,.2,1),visibility 0s}.tavern-capability-panel-header{display:flex;min-height:64px;box-sizing:border-box;flex:none;align-items:center;border-bottom:1px solid #e1e4e8;padding:12px 18px}.tavern-capability-panel-header>div{display:flex;min-width:0;flex-direction:column;gap:4px}.tavern-capability-panel-header strong{font-size:17px;font-weight:650}.tavern-capability-panel-header span{overflow:hidden;color:#747c86;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.tavern-capability-loading{display:grid;min-height:0;flex:1;place-items:center;padding:28px;color:#747c86;font-size:12px;text-align:center}.tavern-capability-loading.is-error{color:#a94450}
2128
+ .tavern-capability-tabs{display:flex;height:42px;flex:none;align-items:flex-end;gap:22px;overflow-x:auto;border-bottom:1px solid #e1e4e8;padding:0 18px}.tavern-capability-tabs button{position:relative;height:42px;flex:none;border:0;padding:0;background:transparent;color:#69717b;cursor:pointer;font:500 11px/42px inherit}.tavern-capability-tabs button[aria-selected=true]{color:#1767d8}.tavern-capability-tabs button[aria-selected=true]::after{position:absolute;right:0;bottom:-1px;left:0;height:2px;background:#2877eb;content:""}.tavern-capability-search{display:flex;height:36px;box-sizing:border-box;flex:none;align-items:center;gap:7px;margin:12px 14px;border:1px solid #d9dde2;border-radius:7px;padding:0 10px;color:#858c95}.tavern-capability-search:focus-within{border-color:#8aa8d9;box-shadow:0 0 0 2px #3478f617}.tavern-capability-search input{min-width:0;flex:1;border:0;outline:0;background:transparent;color:#17191d;font:400 11px/1 inherit}
2129
+ .tavern-worldbook-workbench,.tavern-regex-workbench{display:grid;min-height:0;flex:1;grid-template-columns:minmax(155px,40%) minmax(0,1fr);overflow:hidden;border-top:1px solid #eef0f2}.tavern-capability-list{min-height:0;overflow:auto;border-right:1px solid #e1e4e8;background:#fafbfc}.tavern-capability-list>button{position:relative;display:flex;width:100%;min-width:0;min-height:48px;box-sizing:border-box;flex-direction:column;justify-content:center;gap:3px;border:0;border-bottom:1px solid #eceef1;padding:7px 12px;background:transparent;color:#25292f;cursor:pointer;text-align:left}.tavern-capability-list>button:hover{background:#f1f4f8}.tavern-capability-list>button[aria-selected=true]{background:#eaf2ff;color:#1f61be}.tavern-capability-list>button span,.tavern-capability-list>button small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-capability-list>button span{font-size:11px;font-weight:600}.tavern-capability-list>button small{color:#7c848e;font-size:9px}.tavern-capability-empty{margin:0;padding:24px;color:#7c848e;font-size:11px;line-height:1.6;text-align:center}.tavern-capability-empty.is-roomy{display:grid;min-height:0;flex:1;place-items:center}.tavern-capability-inspector{min-width:0;min-height:0;overflow:auto;padding:18px}.tavern-capability-inspector>header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.tavern-capability-inspector>header small{color:#8a919a;font:8px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-capability-inspector h3{margin:4px 0 0;font-size:14px}.tavern-capability-inspector>header>span{border-radius:999px;padding:3px 7px;background:#eef0f2;color:#727a84;font-size:8px}.tavern-capability-inspector>header>span.is-enabled{background:#eaf7ef;color:#26794a}.tavern-capability-prose{margin:15px 0;border-left:3px solid #6f91c9;padding:10px 11px;background:#f7f8fa;font-size:11px;line-height:1.65;white-space:pre-wrap;overflow-wrap:anywhere}.tavern-capability-inspector dl,.tavern-capability-summary dl,.tavern-capability-sections dl{display:grid;grid-template-columns:94px minmax(0,1fr);gap:0;margin:0;border-top:1px solid #e3e6e9;font-size:10px}.tavern-capability-inspector dt,.tavern-capability-inspector dd,.tavern-capability-summary dt,.tavern-capability-summary dd,.tavern-capability-sections dt,.tavern-capability-sections dd{margin:0;border-bottom:1px solid #e8eaed;padding:8px}.tavern-capability-inspector dt,.tavern-capability-summary dt,.tavern-capability-sections dt{color:#7a828c}.tavern-capability-inspector dd,.tavern-capability-summary dd,.tavern-capability-sections dd{overflow-wrap:anywhere;color:#343a41}
2130
+ .tavern-worldbook-overview{display:grid;flex:none;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;border-bottom:1px solid #e8ebef;padding:12px 16px;background:#f7f9fc}.tavern-worldbook-overview>div{display:flex;min-width:0;flex-direction:column;gap:2px;border:1px solid #e2e6eb;border-radius:9px;padding:9px 10px;background:#fff}.tavern-worldbook-overview strong{color:#20252b;font-size:17px;line-height:1.15}.tavern-worldbook-overview span{overflow:hidden;color:#7b838d;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-worldbook-toolbar{flex:none;border-bottom:1px solid #e3e7eb;padding:10px 16px 12px;background:#fff}.tavern-worldbook-toolbar .tavern-capability-tabs{height:34px;align-items:center;gap:2px;border:0;border-radius:8px;padding:3px;background:#eff2f6}.tavern-worldbook-toolbar .tavern-capability-tabs>button{height:28px;flex:1;border:0;border-radius:6px;padding:0 10px;color:#68717c;font-size:10px;line-height:28px}.tavern-worldbook-toolbar .tavern-capability-tabs>button[aria-selected=true]{background:#fff;color:#1e65c6;box-shadow:0 1px 3px #15253d1f}.tavern-worldbook-toolbar .tavern-capability-tabs>button[aria-selected=true]::after{display:none}.tavern-worldbook-toolbar .tavern-capability-search{margin:10px 0 0}.tavern-worldbook-workbench{grid-template-columns:minmax(190px,43%) minmax(0,1fr);border-top:0;background:#f5f7fa}.tavern-worldbook-library{display:flex;min-width:0;min-height:0;flex-direction:column;border-right:1px solid #e1e5ea;background:#f4f6f9}.tavern-worldbook-library>header{display:flex;flex:none;align-items:center;justify-content:space-between;padding:12px 12px 7px;color:#30363d}.tavern-worldbook-library>header strong{font-size:11px}.tavern-worldbook-library>header span{color:#858d97;font-size:9px}.tavern-worldbook-library>.tavern-capability-list{flex:1;border:0;padding:0 8px 12px;background:transparent}.tavern-worldbook-library .tavern-capability-list>button{min-height:82px;gap:5px;margin:5px 0;border:1px solid #e1e5ea;border-radius:9px;padding:10px;background:#fff;box-shadow:0 1px 2px #182a4212}.tavern-worldbook-library .tavern-capability-list>button:hover{border-color:#c7d7eb;background:#fff}.tavern-worldbook-library .tavern-capability-list>button[aria-selected=true]{border-color:#7ba7e2;background:#f7fbff;box-shadow:0 0 0 1px #80ace73d,0 2px 6px #1f4d8917;color:#205da9}.tavern-worldbook-entry-title{font-size:11px!important}.tavern-worldbook-library .tavern-capability-list>button p{display:-webkit-box;min-height:28px;margin:0;overflow:hidden;color:#68727d;font-size:9.5px;line-height:1.45;-webkit-box-orient:vertical;-webkit-line-clamp:2}.tavern-worldbook-library .tavern-capability-list>button footer{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-worldbook-library .tavern-capability-list>button footer em{overflow:hidden;border-radius:999px;padding:2px 6px;background:#edf5ff;color:#376ba9;font-size:8px;font-style:normal;text-overflow:ellipsis;white-space:nowrap}.tavern-worldbook-library .tavern-capability-list>button footer em.is-disabled{background:#f0f1f3;color:#818892}.tavern-worldbook-library .tavern-capability-list>button footer small{min-width:0;color:#88919b;font-size:8px}.tavern-worldbook-load-more{display:flex;flex:none;align-items:center;justify-content:space-between;gap:8px;border-top:1px solid #e0e5ea;padding:8px 10px;background:#fff}.tavern-worldbook-load-more span{color:#7f8892;font-size:8px}.tavern-worldbook-load-more button{border:1px solid #cad8e9;border-radius:7px;padding:5px 8px;background:#f6faff;color:#2867b4;cursor:pointer;font:600 9px/1 inherit}.tavern-worldbook-load-more button:hover{background:#edf5ff}.tavern-worldbook-workbench>.tavern-capability-inspector{padding:20px 20px 48px;background:#fff}.tavern-worldbook-workbench>.tavern-capability-inspector>header{padding-bottom:16px;border-bottom:1px solid #e6e9ed}.tavern-worldbook-workbench>.tavern-capability-inspector>header small{color:#85909c;font:9px/1.3 inherit}.tavern-worldbook-workbench>.tavern-capability-inspector h3{margin-top:5px;color:#20252b;font-size:16px;line-height:1.35}.tavern-worldbook-content,.tavern-worldbook-keywords{margin-top:18px}.tavern-worldbook-content h4,.tavern-worldbook-keywords h4{margin:0 0 7px;color:#68727d;font-size:9px;font-weight:700;letter-spacing:.08em}.tavern-worldbook-content .tavern-capability-prose{margin:0;border:1px solid #e4e8ed;border-radius:9px;padding:13px 14px;background:#f8fafc;color:#303840;font-size:11px;line-height:1.75}.tavern-worldbook-keywords>div{display:flex;flex-wrap:wrap;gap:5px}.tavern-worldbook-keywords>div>span{max-width:100%;overflow:hidden;border:1px solid #dce6f3;border-radius:999px;padding:4px 8px;background:#f4f8fd;color:#3a689d;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-worldbook-keywords>div>span.is-muted{border-color:#e5e7ea;background:#f6f7f8;color:#868d96}.tavern-worldbook-facts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:18px}.tavern-worldbook-facts>div{display:flex;min-width:0;flex-direction:column;gap:4px;border:1px solid #e5e8ec;border-radius:8px;padding:9px 10px;background:#fafbfc}.tavern-worldbook-facts span{color:#838b95;font-size:8px}.tavern-worldbook-facts strong{overflow-wrap:anywhere;color:#343b43;font-size:9px;line-height:1.45}
2131
+ .tavern-capability-summary{flex:none;border-bottom:1px solid #e1e4e8;padding:13px 16px}.tavern-capability-summary dl{margin-bottom:10px}.tavern-native-jump{display:inline-flex;min-height:30px;box-sizing:border-box;align-items:center;justify-content:center;border:1px solid #b8c9e4;border-radius:7px;padding:0 11px;background:#f5f8fd;color:#2465bd;cursor:pointer;font:550 10px/1 inherit;text-decoration:none}.tavern-native-jump:hover{background:#eaf2ff}.tavern-native-jump.is-danger{border-color:#e5b7bb;background:#fff7f7;color:#a94450}.tavern-capability-warning{margin:8px 0 0;color:#a66b13;font-size:9px}.tavern-context-block-list,.tavern-final-request,.tavern-capability-sections,.tavern-card-profile,.tavern-opening-documents,.tavern-compatibility-list,.tavern-variable-tree,.tavern-variable-diff,.tavern-regex-matches,.tavern-script-list{min-height:0;flex:1;overflow:auto}.tavern-context-block-list{padding:8px 12px}.tavern-context-block-list article{display:grid;grid-template-columns:30px minmax(0,1fr) auto;align-items:center;gap:8px;border-bottom:1px solid #eceef1;padding:9px 4px}.tavern-context-block-list article.is-disabled{opacity:.48}.tavern-context-block-list article>span{color:#8a9199;font:9px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-context-block-list strong{font-size:11px}.tavern-context-block-list p{display:-webkit-box;margin:3px 0 0;overflow:hidden;color:#66707b;font-size:9px;line-height:1.4;-webkit-box-orient:vertical;-webkit-line-clamp:2}.tavern-context-block-list small{color:#858c95;font-size:8px;white-space:nowrap}.tavern-final-request{padding:12px 14px 40px}.tavern-final-request section{margin-bottom:12px;border:1px solid #e1e4e8;border-radius:8px;overflow:hidden}.tavern-final-request h3{margin:0;border-bottom:1px solid #e5e7ea;padding:7px 9px;background:#f7f8fa;font-size:9px;text-transform:uppercase}.tavern-final-request pre,.tavern-capability-sections pre,.tavern-capability-inspector pre,.tavern-card-profile pre,.tavern-script-list pre,.tavern-regex-matches pre,.tavern-raw-json{margin:0;overflow:auto;padding:10px;color:#25292f;background:#fafbfc;font:9px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
2132
+ .tavern-capability-sections{padding:14px 16px 48px}.tavern-capability-sections>section{margin-bottom:18px}.tavern-capability-sections h3,.tavern-card-profile h3{margin:0 0 9px;font-size:12px}.tavern-capability-sections p,.tavern-card-profile p{margin:7px 0;color:#5f6873;font-size:10px;line-height:1.65;white-space:pre-wrap;overflow-wrap:anywhere}.tavern-capability-sections label,.tavern-capability-inspector>label{display:block;margin:10px 0 5px;color:#737b85;font-size:9px;font-weight:600}.tavern-preset-row{display:flex;align-items:center;gap:8px;border:1px solid #e1e4e8;border-radius:8px;padding:9px}.tavern-preset-row>div{display:flex;min-width:0;flex-direction:column;gap:2px}.tavern-preset-row strong{font-size:10px}.tavern-preset-row span{color:#7b838d;font-size:8px}
2133
+
2134
+ .tavern-variable-tree{padding:12px 14px 50px}.tavern-variable-branch{margin:4px 0;border-left:1px solid #dfe3e8;padding-left:10px}.tavern-variable-branch summary{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:5px 0;cursor:pointer}.tavern-variable-branch summary strong{font:600 10px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-variable-branch summary span{color:#858c95;font-size:8px}.tavern-variable-branch>div{padding-left:7px}.tavern-variable-leaf{display:grid;grid-template-columns:minmax(90px,.8fr) minmax(0,1.2fr);gap:8px;border-bottom:1px dotted #e2e5e8;padding:5px 0;font-size:9px}.tavern-variable-leaf span{overflow-wrap:anywhere;color:#59636e}.tavern-variable-leaf code{overflow-wrap:anywhere;color:#2b61a8}.tavern-variable-diff{padding:12px 16px}.tavern-variable-diff ol{margin:0;padding-left:20px}.tavern-variable-diff li{margin-bottom:7px;font-size:9px}.tavern-variable-diff strong,.tavern-variable-diff span{display:block}.tavern-variable-diff span{margin-top:2px;color:#6f7882}.tavern-raw-json{min-height:0;flex:1;padding:16px!important}
2135
+ .tavern-regex-workbench .tavern-capability-inspector>pre{margin-top:5px}.tavern-regex-matches{padding:12px}.tavern-regex-matches>article{margin-bottom:12px;border:1px solid #dfe3e8;border-radius:8px;overflow:hidden}.tavern-regex-matches>article>header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e5e7ea;padding:7px 9px;background:#f7f8fa;font-size:9px}.tavern-regex-matches>article>div{display:grid;grid-template-columns:1fr 1fr}.tavern-regex-matches section+section{border-left:1px solid #e5e7ea}.tavern-regex-matches h4{margin:0;padding:6px 8px;color:#6e7781;font-size:8px}.tavern-regex-matches pre{max-height:220px}.tavern-runtime-errors{margin:0;padding-left:18px;color:#a94450;font-size:9px;line-height:1.5}.tavern-script-list{padding:12px 14px}.tavern-script-list details{margin-bottom:8px;border:1px solid #dfe3e8;border-radius:8px;overflow:hidden}.tavern-script-list summary{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 10px;cursor:pointer}.tavern-script-list summary strong{font-size:10px}.tavern-script-list summary span{color:#818994;font-size:8px}.tavern-script-list pre{max-height:420px;border-top:1px solid #e5e7ea}.tavern-chip-list{display:flex;flex-wrap:wrap;gap:5px}.tavern-chip-list span{border-radius:999px;padding:4px 7px;background:#edf3fc;color:#2d67ba;font:8px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
2136
+ .tavern-regex-settings,.tavern-regex-evidence{min-height:0;flex:1;overflow:auto;background:#f5f7fa}.tavern-regex-overview{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;border-bottom:1px solid #e3e7eb;padding:12px 16px;background:#f7f9fc}.tavern-regex-overview>div{display:flex;min-width:0;flex-direction:column;gap:3px;border:1px solid #e2e6eb;border-radius:9px;padding:9px 10px;background:#fff}.tavern-regex-overview strong{overflow:hidden;color:#20252b;font-size:16px;line-height:1.15;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-overview span{overflow:hidden;color:#7c848d;font-size:8px;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;border-bottom:1px solid #e3e7eb;padding:10px 16px;background:#fff}.tavern-regex-toolbar .tavern-capability-search{height:34px;margin:0}.tavern-regex-debug-button{display:inline-flex;height:34px;align-items:center;gap:6px;border:1px solid #cbd9eb;border-radius:7px;padding:0 10px;background:#f6f9fd;color:#2b65ad;cursor:pointer;font:600 9px/1 inherit}.tavern-regex-debug-button:hover{border-color:#9ebbe0;background:#edf4fd}.tavern-regex-scopes{padding:12px 14px 48px}.tavern-regex-scope{margin-bottom:10px;border:1px solid #dfe4e9;border-radius:10px;background:#fff;box-shadow:0 1px 2px #1b2d4510;overflow:hidden}.tavern-regex-scope>header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 12px}.tavern-regex-scope>header>div{display:flex;min-width:0;flex-direction:column;gap:3px}.tavern-regex-scope>header strong{color:#2b3138;font-size:11px}.tavern-regex-scope>header span{overflow:hidden;color:#7d8690;font-size:8.5px;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-scope>header em{flex:none;border-radius:999px;padding:3px 7px;background:#eef1f4;color:#747d87;font-size:8px;font-style:normal}.tavern-regex-scope>p{margin:0;border-top:1px solid #edf0f2;padding:9px 12px;color:#7d858e;font-size:9px;line-height:1.55}.tavern-regex-scope.is-unavailable{background:#fafbfc}.tavern-regex-scope.is-unavailable>header{opacity:.75}.tavern-regex-scope.is-current{border-color:#cad9ec}.tavern-regex-scope.is-current>header{background:#f7faff}.tavern-regex-scope.is-current>header em{background:#e7f1ff;color:#2464b5}.tavern-regex-rule-list{border-top:1px solid #e4e9ef}.tavern-regex-rule-list>article+article{border-top:1px solid #e8ecf0}.tavern-regex-rule-list>article>button{display:grid;width:100%;min-height:58px;box-sizing:border-box;grid-template-columns:28px minmax(0,1fr) auto 16px;align-items:center;gap:8px;border:0;padding:8px 10px;background:#fff;color:#30363d;cursor:pointer;text-align:left}.tavern-regex-rule-list>article>button:hover{background:#f7faff}.tavern-regex-rule-list>article.is-expanded>button{background:#eef5ff;color:#205fae}.tavern-regex-order{display:grid;width:25px;height:25px;place-items:center;border-radius:7px;background:#f0f2f5;color:#737c86;font:650 8px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-regex-rule-list>article.is-expanded .tavern-regex-order{background:#dcecff;color:#1d61b7}.tavern-regex-rule-copy{display:flex;min-width:0;flex-direction:column;gap:4px}.tavern-regex-rule-copy strong,.tavern-regex-rule-copy small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-rule-copy strong{font-size:10.5px}.tavern-regex-rule-copy small{color:#7a838d;font-size:8px}.tavern-regex-enabled{border-radius:999px;padding:3px 7px;background:#eaf7ef;color:#277949;font-size:8px;white-space:nowrap}.tavern-regex-rule-detail{border-top:1px solid #dfe8f2;padding:12px;background:#fbfdff}.tavern-regex-rule-facts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-bottom:12px}.tavern-regex-rule-facts>div{display:flex;min-width:0;flex-direction:column;gap:3px;border:1px solid #e5eaf0;border-radius:7px;padding:7px 8px;background:#fff}.tavern-regex-rule-facts span{color:#828b95;font-size:7.5px}.tavern-regex-rule-facts strong{overflow-wrap:anywhere;color:#343b43;font-size:8.5px;line-height:1.4}.tavern-regex-rule-detail>label{display:block;margin:10px 0 5px;color:#6f7882;font-size:8px;font-weight:650}.tavern-regex-rule-detail>pre{max-height:180px;margin:0;overflow:auto;border:1px solid #e2e7ed;border-radius:7px;padding:9px;background:#f6f8fa;color:#293039;font:8.5px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.tavern-regex-rule-id{display:block;margin-top:10px;overflow:hidden;color:#9098a1;font:7.5px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-boundary{margin-top:12px;border-left:3px solid #d69a34;border-radius:0 8px 8px 0;padding:9px 10px;background:#fff9ee}.tavern-regex-boundary strong{color:#684814;font-size:9px}.tavern-regex-boundary p{margin:4px 0 0;color:#806333;font-size:8.5px;line-height:1.55}.tavern-regex-evidence{padding-bottom:48px}.tavern-regex-evidence-summary{border-bottom:1px solid #e1e6eb;padding:14px 16px;background:#fff}.tavern-regex-evidence-summary>header,.tavern-regex-match-section>header{display:flex;align-items:center;justify-content:space-between;gap:10px}.tavern-regex-evidence-summary>header>div,.tavern-regex-match-section>header>div{display:flex;min-width:0;flex-direction:column;gap:3px}.tavern-regex-evidence-summary>header strong,.tavern-regex-match-section>header strong{font-size:11px}.tavern-regex-evidence-summary>header span,.tavern-regex-match-section>header span{color:#7d858f;font-size:8px}.tavern-regex-plane-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:11px}.tavern-regex-plane-grid>article{display:flex;min-width:0;flex-direction:column;gap:3px;border:1px solid #e2e7ec;border-radius:8px;padding:9px;background:#fafbfc}.tavern-regex-plane-grid>article.is-active{border-color:#aac7ec;background:#f1f7ff}.tavern-regex-plane-grid span{color:#7c858f;font-size:7.5px}.tavern-regex-plane-grid strong{overflow:hidden;color:#343b43;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-plane-grid small{color:#8b939c;font-size:7.5px}.tavern-regex-evidence-summary>p{margin:10px 0 0;color:#69737d;font-size:8.5px;line-height:1.55}.tavern-regex-match-section{padding:14px}.tavern-regex-match-section>.tavern-regex-matches{padding:10px 0 0}.tavern-regex-matches>article{background:#fff;box-shadow:0 1px 2px #182a4210}.tavern-regex-matches>article>header{padding:8px 10px}.tavern-regex-matches>article>header span{color:#7f8892}.tavern-regex-matches pre{font-size:8.5px;line-height:1.6}
2137
+
2138
+ .tavern-frontend-settings{min-height:0;flex:1;overflow:auto;padding:14px 14px 48px;background:#f6f8fa}.tavern-frontend-overview{margin-bottom:10px;border:1px solid #dfe4ea;border-left:3px solid #9aa4af;border-radius:10px;background:#fff;box-shadow:0 1px 2px #182a4212}.tavern-frontend-overview.is-ready{border-left-color:#2f9e61}.tavern-frontend-overview.is-pending{border-left-color:#d5932f}.tavern-frontend-overview-copy{display:flex;flex-direction:column;gap:4px;padding:14px 15px 13px}.tavern-frontend-overview-copy>span{color:#7c858f;font-size:10px;font-weight:650}.tavern-frontend-overview-copy>strong{color:#22272e;font-size:17px;line-height:1.25}.tavern-frontend-overview-copy>small{overflow:hidden;color:#6f7883;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.tavern-frontend-overview-facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid #e8ebef;background:#fafbfd}.tavern-frontend-overview-facts>div{display:flex;min-width:0;flex-direction:column;gap:2px;padding:10px 12px}.tavern-frontend-overview-facts>div+div{border-left:1px solid #e8ebef}.tavern-frontend-overview-facts strong{color:#262c33;font-size:15px}.tavern-frontend-overview-facts span{color:#858e98;font-size:9px}.tavern-frontend-setting-section{margin-bottom:8px;border:1px solid #dfe4ea;border-radius:10px;overflow:hidden;background:#fff}.tavern-frontend-setting-section>summary{display:flex;min-height:48px;box-sizing:border-box;align-items:center;padding:10px 13px;cursor:pointer;color:#2c3239}.tavern-frontend-setting-section>summary::marker{color:#77818c}.tavern-frontend-setting-section>summary>div{display:flex;min-width:0;flex:1;align-items:center;justify-content:space-between;gap:12px}.tavern-frontend-setting-section>summary strong{font-size:12px;font-weight:650}.tavern-frontend-setting-section>summary span{overflow:hidden;color:#7d8691;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-frontend-setting-section[open]>summary{border-bottom:1px solid #e7eaee;background:#fbfcfd}.tavern-frontend-setting-body{padding:13px 14px}.tavern-frontend-facts{display:grid;grid-template-columns:116px minmax(0,1fr);gap:0;margin:0;font-size:10px}.tavern-frontend-facts dt,.tavern-frontend-facts dd{margin:0;border-bottom:1px solid #eceef1;padding:9px 4px}.tavern-frontend-facts dt{color:#7b848e}.tavern-frontend-facts dd{overflow-wrap:anywhere;color:#30373e}.tavern-frontend-permission{display:flex!important;align-items:flex-start;gap:10px;margin:0!important;color:#2d333a!important}.tavern-frontend-permission input{width:16px;height:16px;flex:none;margin:1px 0 0;accent-color:#2877eb}.tavern-frontend-permission>span{display:flex;min-width:0;flex-direction:column;gap:4px}.tavern-frontend-permission strong{font-size:11px}.tavern-frontend-permission small{color:#737d88;font-size:9.5px;font-weight:400;line-height:1.55}.tavern-frontend-note,.tavern-frontend-empty,.tavern-frontend-resource p{margin:10px 0 0;color:#66717c;font-size:10px;line-height:1.6}.tavern-frontend-metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.tavern-frontend-metrics>div{display:flex;min-width:0;flex-direction:column;gap:4px;border:1px solid #e5e8ec;border-radius:8px;padding:10px;background:#fafbfd}.tavern-frontend-metrics span{color:#7b858f;font-size:9px}.tavern-frontend-metrics strong{color:#2d343b;font-size:15px}.tavern-frontend-setting-body h4{margin:13px 0 7px;color:#68727d;font-size:9px;font-weight:700;letter-spacing:.06em}.tavern-frontend-script-list{display:flex;flex-direction:column;gap:7px}.tavern-frontend-script{border:1px solid #e1e5e9;border-radius:8px;overflow:hidden}.tavern-frontend-script>summary{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 10px;cursor:pointer}.tavern-frontend-script>summary strong{overflow:hidden;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.tavern-frontend-script>summary span{flex:none;color:#818a94;font-size:9px}.tavern-frontend-script pre{max-height:360px;border-top:1px solid #e6e9ed}.tavern-frontend-resource{margin-top:12px;border-top:1px solid #e8ebee;padding-top:1px}.tavern-frontend-state{margin-top:10px;border-top:1px solid #e8ebee;padding-top:9px}.tavern-frontend-state>summary{cursor:pointer;color:#3969a7;font-size:10px}.tavern-frontend-state>pre{max-height:260px;margin-top:8px!important;border:1px solid #e4e7eb;border-radius:7px}.tavern-frontend-settings .tavern-runtime-errors{margin:0;font-size:10px}.tavern-frontend-settings .tavern-chip-list{margin-top:6px}
2139
+ .tavern-card-profile{padding:14px 16px 48px}.tavern-card-profile section{margin-bottom:20px}.tavern-card-profile p{color:#343a41;font-size:11px}.tavern-opening-documents{padding:12px 14px}.tavern-opening-documents article{margin-bottom:10px;border:1px solid #dfe3e8;border-radius:8px;overflow:hidden}.tavern-opening-documents header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e5e7ea;padding:8px 10px;background:#f7f8fa}.tavern-opening-documents header strong{font-size:10px}.tavern-opening-documents header span{color:#2d6cc7;font-size:8px}.tavern-opening-documents p{margin:0;padding:11px;color:#343a41;font-size:11px;line-height:1.7;white-space:pre-wrap}.tavern-compatibility-list{padding:12px 14px}.tavern-compatibility-list>section,.tavern-compatibility-list>article{margin-bottom:8px;border:1px solid #dfe3e8;border-radius:8px;padding:10px}.tavern-compatibility-list>section.is-ready{border-left:3px solid #25a45b}.tavern-compatibility-list>section.is-degraded{border-left:3px solid #e3a52b}.tavern-compatibility-list>section.is-blocked{border-left:3px solid #c94d59}.tavern-compatibility-list header{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-compatibility-list strong{font-size:10px}.tavern-compatibility-list span{color:#6e7781;font-size:8px}.tavern-compatibility-list p{margin:5px 0 0;color:#606a75;font-size:9px;line-height:1.5}.tavern-capability-boundary{border-left:3px solid #e3a52b;padding:9px 10px;background:#fffaf0}.tavern-capability-boundary strong{font-size:10px}
2140
+ @media(max-width:1050px){:root{--tavern-capability-panel-width:440px}.tavern-worldbook-workbench{grid-template-columns:180px minmax(0,1fr)}.tavern-regex-workbench{grid-template-columns:155px minmax(0,1fr)}}@media(max-width:760px){:root{--tavern-capability-panel-width:calc(100vw - var(--tavern-capability-rail-width))}body:has(.tavern-card-capability-rail),body:has(.tavern-capability-panel){padding-right:var(--tavern-capability-rail-width)}.tavern-capability-panel{box-shadow:-16px 0 40px #0002}.tavern-worldbook-workbench{grid-template-columns:170px minmax(0,1fr)}.tavern-regex-workbench{grid-template-columns:145px minmax(0,1fr)}}
2141
+ `;
2142
+ const FRONTEND_CALL_STYLE = `
2143
+ .tavern-compatibility-call-list{display:flex;flex-direction:column;gap:7px;margin:0;padding:0;list-style:none}
2144
+ .tavern-compatibility-call-list li{border:1px solid #e2e6eb;border-radius:8px;padding:9px 10px;background:#fafbfd}
2145
+ .tavern-compatibility-call-list li>div{display:flex;align-items:center;justify-content:space-between;gap:8px}
2146
+ .tavern-compatibility-call-list strong{overflow-wrap:anywhere;color:#2e3741;font:600 9.5px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace}
2147
+ .tavern-compatibility-call-list span{flex:none;border-radius:999px;padding:3px 6px;background:#edf3fc;color:#2d67ba;font-size:8px}
2148
+ .tavern-compatibility-call-list p{margin:6px 0 0;color:#596570;font-size:9px;line-height:1.5}
2149
+ .tavern-compatibility-call-list small{display:block;margin-top:5px;color:#8a929b;font-size:8px}
2150
+ .tavern-mvu-field{display:flex!important;align-items:center;justify-content:space-between;gap:12px;margin:0 0 10px!important;color:#30373e!important;font-size:10px!important}.tavern-mvu-field>span{flex:none}.tavern-mvu-field select,.tavern-mvu-field input{width:min(230px,60%);box-sizing:border-box;border:1px solid #d9dee4;border-radius:7px;padding:7px 9px;background:#fff;color:#28313a;font:10px/1.3 inherit}.tavern-mvu-advanced{margin:10px 0;border:1px solid #e4e8ec;border-radius:8px;padding:11px;background:#fafbfd}.tavern-mvu-model-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}.tavern-mvu-model-grid .tavern-mvu-field{display:block!important}.tavern-mvu-model-grid .tavern-mvu-field>span{display:block;margin-bottom:5px}.tavern-mvu-model-grid .tavern-mvu-field input{width:100%}.tavern-mvu-actions{display:flex;flex-wrap:wrap;gap:7px}.tavern-mvu-actions button{border:1px solid #cad7e6;border-radius:7px;padding:7px 10px;background:#f5f9ff;color:#2d63a5;cursor:pointer;font:600 9px/1.3 inherit}.tavern-mvu-actions button:hover:not(:disabled){border-color:#9eb9da;background:#edf5ff}.tavern-mvu-actions button:disabled{cursor:not-allowed;opacity:.48}.tavern-mvu-notice{margin:9px 0 0;font-size:9px}.tavern-mvu-notice.is-ok{color:#287949}.tavern-mvu-notice.is-error{color:#aa3f4b}
2151
+ .tavern-frontend-settings{min-height:0;flex:1;overflow:auto;padding:14px 14px 48px;background:#f6f8fa}.tavern-frontend-overview{margin-bottom:10px;border:1px solid #dfe4ea;border-left:3px solid #9aa4af;border-radius:10px;background:#fff;box-shadow:0 1px 2px #182a4212}.tavern-frontend-overview.is-ready{border-left-color:#2f9e61}.tavern-frontend-overview.is-pending{border-left-color:#d5932f}.tavern-frontend-overview-copy{display:flex;flex-direction:column;gap:4px;padding:14px 15px 13px}.tavern-frontend-overview-copy>span{color:#7c858f;font-size:9px;font-weight:650}.tavern-frontend-overview-copy>strong{color:#22272e;font-size:16px;line-height:1.25}.tavern-frontend-overview-copy>small{overflow:hidden;color:#6f7883;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-frontend-overview-facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid #e8ebef;background:#fafbfd}.tavern-frontend-overview-facts>div{display:flex;min-width:0;flex-direction:column;gap:2px;padding:10px 12px}.tavern-frontend-overview-facts>div+div{border-left:1px solid #e8ebef}.tavern-frontend-overview-facts strong{color:#262c33;font-size:14px}.tavern-frontend-overview-facts span{color:#858e98;font-size:8px}.tavern-frontend-setting-section{margin-bottom:8px;border:1px solid #dfe4ea;border-radius:10px;overflow:hidden;background:#fff}.tavern-frontend-setting-section>summary{display:flex;min-height:48px;box-sizing:border-box;align-items:center;padding:10px 13px;cursor:pointer;color:#2c3239}.tavern-frontend-setting-section>summary::marker{color:#77818c}.tavern-frontend-setting-section>summary>div{display:flex;min-width:0;flex:1;align-items:center;justify-content:space-between;gap:12px}.tavern-frontend-setting-section>summary strong{font-size:11px;font-weight:650}.tavern-frontend-setting-section>summary span{overflow:hidden;color:#7d8691;font-size:8px;text-overflow:ellipsis;white-space:nowrap}.tavern-frontend-setting-section[open]>summary{border-bottom:1px solid #e7eaee;background:#fbfcfd}.tavern-frontend-setting-body{padding:13px 14px}.tavern-frontend-facts{display:grid;grid-template-columns:116px minmax(0,1fr);gap:0;margin:0;font-size:9px}.tavern-frontend-facts dt,.tavern-frontend-facts dd{margin:0;border-bottom:1px solid #eceef1;padding:8px 4px}.tavern-frontend-facts dt{color:#7b848e}.tavern-frontend-facts dd{overflow-wrap:anywhere;color:#30373e}.tavern-frontend-permission{display:flex!important;align-items:flex-start;gap:10px;margin:0!important;color:#2d333a!important}.tavern-frontend-permission input{width:16px;height:16px;flex:none;margin:1px 0 0;accent-color:#2877eb}.tavern-frontend-permission>span{display:flex;min-width:0;flex-direction:column;gap:4px}.tavern-frontend-permission strong{font-size:10px}.tavern-frontend-permission small{color:#737d88;font-size:8.5px;font-weight:400;line-height:1.55}.tavern-frontend-note,.tavern-frontend-empty,.tavern-frontend-resource p{margin:10px 0 0;color:#66717c;font-size:9px;line-height:1.6}.tavern-frontend-metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.tavern-frontend-metrics>div{display:flex;min-width:0;flex-direction:column;gap:4px;border:1px solid #e5e8ec;border-radius:8px;padding:10px;background:#fafbfd}.tavern-frontend-metrics span{color:#7b858f;font-size:8px}.tavern-frontend-metrics strong{color:#2d343b;font-size:14px}.tavern-frontend-setting-body h4{margin:13px 0 7px;color:#68727d;font-size:8px;font-weight:700;letter-spacing:.06em}.tavern-frontend-script-list{display:flex;flex-direction:column;gap:7px}.tavern-frontend-script{border:1px solid #e1e5e9;border-radius:8px;overflow:hidden}.tavern-frontend-script>summary{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 10px;cursor:pointer}.tavern-frontend-script>summary strong{overflow:hidden;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-frontend-script>summary span{flex:none;color:#818a94;font-size:8px}.tavern-frontend-script pre{max-height:360px;border-top:1px solid #e6e9ed}.tavern-frontend-resource{margin-top:12px;border-top:1px solid #e8ebee;padding-top:1px}.tavern-frontend-state{margin-top:10px;border-top:1px solid #e8ebee;padding-top:9px}.tavern-frontend-state>summary{cursor:pointer;color:#3969a7;font-size:9px}.tavern-frontend-state>pre{max-height:260px;margin-top:8px!important;border:1px solid #e4e7eb;border-radius:7px}.tavern-frontend-settings .tavern-runtime-errors{margin:0}.tavern-frontend-settings .tavern-chip-list{margin-top:6px}
2152
+ .tavern-card-hero{display:grid;flex:none;grid-template-columns:96px minmax(0,1fr);align-items:center;gap:14px;border-bottom:1px solid #edf0f3;padding:16px 18px}.tavern-card-hero-cover{display:block;width:96px;height:96px;border-radius:9px;object-fit:cover;background:#f1f3f6}.tavern-card-hero-cover.is-placeholder{display:grid;place-items:center;color:#6c7480;font-size:28px;font-weight:650}.tavern-card-hero-copy{display:flex;min-width:0;align-items:flex-start;flex-direction:column}.tavern-card-hero-title{display:flex;width:100%;min-width:0;align-items:center;justify-content:space-between;gap:8px}.tavern-card-hero-title h1{min-width:0;margin:0;overflow:hidden;color:#1d2228;font-size:18px;font-weight:680;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.tavern-card-hero-copy>p{max-width:100%;margin:5px 0 0;overflow:hidden;color:#727b86;font-size:10px;text-overflow:ellipsis;white-space:nowrap}.tavern-card-start-session{min-height:34px;margin-top:10px;border:0;border-radius:7px;padding:0 14px;background:#176ff2;color:#fff;cursor:pointer;font:600 11px/1 inherit}.tavern-card-start-session:hover{background:#0e63e3}.tavern-card-start-session:focus-visible,.tavern-card-nav button:focus-visible,.tavern-card-more>button:focus-visible,.tavern-card-opening-tabs button:focus-visible,.tavern-card-back-to-description:focus-visible{outline:2px solid #82abe6;outline-offset:2px}.tavern-card-start-session:disabled{cursor:not-allowed;background:#b7c3d1;color:#f7f9fb}.tavern-card-more{position:relative;flex:none}.tavern-card-more>button{height:28px;border:1px solid #dfe3e8;border-radius:6px;padding:0 9px;background:#fff;color:#59636f;cursor:pointer;font:550 10px/1 inherit}.tavern-card-more>button:hover,.tavern-card-more>button[aria-expanded=true]{border-color:#b9c9df;background:#f5f8fc;color:#2464b5}.tavern-card-more-menu{position:absolute;z-index:5;top:34px;right:0;display:grid;width:124px;overflow:hidden;border:1px solid #dfe3e8;border-radius:8px;background:#fff;box-shadow:0 10px 28px rgba(25,39,64,.16)}.tavern-card-more-menu>*{display:flex;min-height:36px;box-sizing:border-box;align-items:center;border:0;padding:0 11px;background:#fff;color:#303841;cursor:pointer;font:500 10px/1 inherit;text-align:left;text-decoration:none}.tavern-card-more-menu>*+*{border-top:1px solid #edf0f2}.tavern-card-more-menu>*:hover{background:#f3f6fa;color:#1f61b7}.tavern-card-nav{display:flex;height:44px;flex:none;align-items:flex-end;justify-content:flex-start;gap:20px;overflow-x:auto;border-bottom:1px solid #e1e5e9;padding:0 18px}.tavern-card-nav button{position:relative;height:44px;min-width:max-content;border:0;padding:0 2px;background:transparent;color:#69727d;cursor:pointer;font:550 11px/44px inherit;white-space:nowrap}.tavern-card-nav button[aria-selected=true]{color:#1767d8}.tavern-card-nav button[aria-selected=true]::after{position:absolute;right:2px;bottom:-1px;left:2px;height:2px;background:#2877eb;content:""}.tavern-card-view{min-height:0;flex:1;overflow:auto;background:var(--dsw-alias-bg-base,#fff)}.tavern-card-document{box-sizing:border-box;min-height:100%;padding:18px 20px 48px;color:var(--dsw-alias-label-primary,#25292f);font-size:13px;line-height:1.78;overflow-wrap:anywhere}.tavern-card-document>:first-child,.tavern-card-document>div>:first-child{margin-top:0}.tavern-card-document>:last-child,.tavern-card-document>div>:last-child{margin-bottom:0}.tavern-card-document h1,.tavern-card-document h2,.tavern-card-document h3,.tavern-card-document h4{margin:1.25em 0 .55em;color:var(--dsw-alias-label-primary,#20252b);font-weight:650;line-height:1.4}.tavern-card-document h1{font-size:17px}.tavern-card-document h2{font-size:15px}.tavern-card-document h3,.tavern-card-document h4{font-size:13px}.tavern-card-document p{margin:.65em 0}.tavern-card-document ul,.tavern-card-document ol{margin:.65em 0;padding-left:1.6em}.tavern-card-document li+li{margin-top:.25em}.tavern-card-document blockquote{margin:.9em 0;border-left:3px solid #a9b9cd;padding:3px 0 3px 12px;color:var(--dsw-alias-label-secondary,#66717e)}.tavern-card-document pre{max-width:100%;overflow:auto;border:1px solid #e0e5ea;border-radius:7px;padding:10px 11px;background:#f6f8fa;color:#2e363f;font:11px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap}.tavern-card-document code{border-radius:4px;padding:1px 4px;background:#f0f2f5;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-card-document a{color:#1767d8}.tavern-card-document table{width:100%;border-collapse:collapse;font-size:11px}.tavern-card-document th,.tavern-card-document td{border:1px solid #e0e4e8;padding:6px 7px;text-align:left}.tavern-card-openings{min-height:100%}.tavern-card-opening-tabs{position:sticky;z-index:2;top:0;display:flex;gap:6px;overflow-x:auto;border-bottom:1px solid #e4e8ec;padding:10px 18px;background:color-mix(in srgb,var(--dsw-alias-bg-base,#fff) 96%,transparent)}.tavern-card-opening-tabs button{min-width:max-content;height:28px;border:1px solid #dfe3e8;border-radius:6px;padding:0 9px;background:#fff;color:#69727d;cursor:pointer;font:550 10px/1 inherit}.tavern-card-opening-tabs button[aria-pressed=true]{border-color:#99b8e2;background:#edf5ff;color:#2465b7}.tavern-card-empty{margin:0;padding:24px 20px;color:#7d8690;font-size:11px}.tavern-card-advanced{padding:18px 20px 40px}.tavern-card-advanced>header{display:flex;flex-direction:column;gap:10px;margin-bottom:14px}.tavern-card-advanced h2{margin:0;color:#242a31;font-size:16px;line-height:1.35}.tavern-card-back-to-description{display:inline-flex;align-self:flex-start;align-items:center;gap:4px;border:0;padding:0;background:transparent;color:#3971b3;cursor:pointer;font:550 10px/1 inherit}.tavern-card-playability{margin-bottom:12px;border-left:3px solid #2f9e61;border-radius:0 8px 8px 0;padding:11px 12px;background:#f3faf6}.tavern-card-playability.is-degraded{border-left-color:#d69a34;background:#fff9ee}.tavern-card-playability.is-blocked{border-left-color:#c94d59;background:#fff4f5}.tavern-card-playability strong{font-size:10.5px}.tavern-card-playability p{margin:5px 0 0;color:#5e6873;font-size:9px;line-height:1.6}.tavern-card-compatibility-rows{border-top:1px solid #e2e6ea}.tavern-card-compatibility-rows>article{padding:11px 2px;border-bottom:1px solid #e7eaed}.tavern-card-compatibility-rows>article>div{display:flex;align-items:center;justify-content:space-between;gap:10px}.tavern-card-compatibility-rows strong{font-size:10px}.tavern-card-compatibility-rows span{color:#3971b3;font-size:8px}.tavern-card-compatibility-rows p{margin:5px 0 0;color:#69737d;font-size:9px;line-height:1.55}.tavern-card-source{margin-top:19px}.tavern-card-source h3{margin:0 0 8px;font-size:11px}.tavern-card-source dl{display:grid;grid-template-columns:84px minmax(0,1fr);margin:0 0 12px;border-top:1px solid #e2e6ea;font-size:9px}.tavern-card-source dt,.tavern-card-source dd{margin:0;border-bottom:1px solid #e7eaed;padding:8px 4px}.tavern-card-source dt{color:#7a838d}.tavern-card-source dd{overflow-wrap:anywhere;color:#343b43}.tavern-capability-boundary{border-left:3px solid #e3a52b;padding:9px 10px;background:#fffaf0}.tavern-capability-boundary strong{font-size:10px}
2153
+ .tavern-card-more-menu>*{appearance:none;font-family:inherit;font-size:13px;font-weight:500;line-height:1}
2154
+ .tavern-frontend-overview-copy>span{font-size:10px}.tavern-frontend-overview-copy>strong{font-size:17px}.tavern-frontend-overview-copy>small{font-size:10px}.tavern-frontend-overview-facts strong{font-size:15px}.tavern-frontend-overview-facts span{font-size:9px}.tavern-frontend-setting-section>summary strong{font-size:12px}.tavern-frontend-setting-section>summary span{font-size:9px}.tavern-frontend-facts{font-size:10px}.tavern-frontend-facts dt,.tavern-frontend-facts dd{padding:9px 4px}.tavern-frontend-permission strong{font-size:11px}.tavern-frontend-permission small{font-size:9.5px}.tavern-frontend-note,.tavern-frontend-empty,.tavern-frontend-resource p{font-size:10px}.tavern-frontend-metrics span{font-size:9px}.tavern-frontend-metrics strong{font-size:15px}.tavern-frontend-setting-body h4{font-size:9px}.tavern-frontend-script>summary strong{font-size:10px}.tavern-frontend-script>summary span{font-size:9px}.tavern-frontend-state>summary{font-size:10px}
2155
+
2156
+ .tavern-persona-workbench{display:grid;min-height:0;flex:1;grid-template-columns:minmax(150px,34%) minmax(0,1fr);overflow:hidden;background:#fff}.tavern-persona-library{display:flex;min-width:0;min-height:0;flex-direction:column;border-right:1px solid #e1e5ea;background:#f6f8fb}.tavern-persona-create{display:flex;height:36px;flex:none;align-items:center;justify-content:center;gap:6px;margin:12px 10px 8px;border:1px solid #1767d8;border-radius:7px;background:#1767d8;color:#fff;cursor:pointer;font:600 10px/1 inherit}.tavern-persona-create:hover{background:#0e58c0}.tavern-persona-create:disabled{cursor:wait;opacity:.58}.tavern-persona-search{display:flex;height:34px;box-sizing:border-box;flex:none;align-items:center;gap:6px;margin:0 10px 8px;border:1px solid #dce1e7;border-radius:7px;padding:0 8px;background:#fff;color:#8b929b}.tavern-persona-search:focus-within{border-color:#8aa8d9;box-shadow:0 0 0 2px #3478f617}.tavern-persona-search input{min-width:0;flex:1;border:0;outline:0;background:transparent;color:#242a31;font:400 9px/1 inherit}.tavern-persona-list{min-height:0;flex:1;overflow:auto;padding:0 7px 10px}.tavern-persona-list>button{display:grid;width:100%;height:54px;box-sizing:border-box;grid-template-columns:34px minmax(0,1fr) 16px;align-items:center;gap:7px;margin:4px 0;border:1px solid transparent;border-radius:8px;padding:6px;background:transparent;color:#303740;cursor:pointer;text-align:left}.tavern-persona-list>button:hover{background:#edf2f8}.tavern-persona-list>button[aria-selected=true]{border-color:#76a5e6;background:#f5f9ff;color:#1e62bc;box-shadow:0 0 0 1px #6fa4e933}.tavern-persona-list img{width:34px;height:34px;border-radius:50%;object-fit:cover}.tavern-persona-list>button>span{overflow:hidden;font-size:10px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tavern-persona-list>button>svg{color:#1767d8}.tavern-persona-empty{margin:0;padding:20px 10px;color:#808994;font-size:9px;line-height:1.65;text-align:center;white-space:pre-line}.tavern-persona-empty.is-roomy{display:grid;min-height:240px;place-items:center}.tavern-persona-editor{min-width:0;min-height:0;overflow:auto;padding:18px 16px 48px}.tavern-persona-identity{display:grid;grid-template-columns:68px minmax(0,1fr);align-items:center;gap:12px}.tavern-persona-avatar{width:68px;height:68px;overflow:hidden;border:1px solid #dbe1e7;border-radius:50%;padding:0;background:#fff;cursor:pointer}.tavern-persona-avatar:hover{border-color:#76a5e6;box-shadow:0 0 0 3px #3478f614}.tavern-persona-avatar img{display:block;width:100%;height:100%;object-fit:cover}.tavern-persona-identity label,.tavern-persona-description{display:flex;min-width:0;flex-direction:column;gap:6px}.tavern-persona-identity label>span,.tavern-persona-description>span{color:#5d6671;font-size:9px;font-weight:650}.tavern-persona-identity input,.tavern-persona-description textarea{width:100%;box-sizing:border-box;border:1px solid #dce1e7;border-radius:7px;outline:0;background:#fff;color:#252b32;font:400 11px/1.55 inherit}.tavern-persona-identity input{height:36px;padding:0 10px}.tavern-persona-description{margin-top:16px}.tavern-persona-description textarea{min-height:190px;resize:vertical;padding:10px 11px}.tavern-persona-identity input:focus,.tavern-persona-description textarea:focus{border-color:#7ba6df;box-shadow:0 0 0 2px #3478f617}.tavern-persona-save-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:8px}.tavern-persona-save-row>span{color:#7f8791;font-size:8px}.tavern-persona-save-row>div{display:flex;align-items:center;gap:9px}.tavern-persona-save-row small{font-size:8px}.tavern-persona-save-row small.is-saved{color:#28794c}.tavern-persona-save-row small.is-dirty{color:#a56a12}.tavern-persona-save-row button{height:32px;border:1px solid #1767d8;border-radius:7px;padding:0 16px;background:#1767d8;color:#fff;cursor:pointer;font:600 9px/1 inherit}.tavern-persona-save-row button:disabled{border-color:#d8dde3;background:#e8ebef;color:#979ea7;cursor:not-allowed}.tavern-persona-binding{margin-top:18px;border-top:1px solid #e5e8ec;padding-top:16px}.tavern-persona-binding>header{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-persona-binding>header strong{font-size:10px}.tavern-persona-binding>header button{border:0;padding:0;background:transparent;color:#687789;cursor:pointer;font:500 8px/1 inherit}.tavern-persona-binding>header button:hover{color:#1e62bc}.tavern-persona-scope{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));margin-top:9px;border:1px solid #dce1e7;border-radius:7px;overflow:hidden}.tavern-persona-scope button{height:32px;border:0;border-right:1px solid #e1e5e9;background:#f7f8fa;color:#5f6873;cursor:pointer;font:500 8.5px/1 inherit}.tavern-persona-scope button:last-child{border-right:0}.tavern-persona-scope button[aria-pressed=true]{background:#eaf3ff;color:#1767d8;font-weight:650;box-shadow:inset 0 0 0 1px #74a5e8}.tavern-persona-scope button:disabled{cursor:not-allowed;opacity:.45}.tavern-persona-session{display:flex;min-width:0;flex-direction:column;gap:4px;margin-top:12px;border:1px solid #e2e6eb;border-radius:7px;padding:9px 10px;background:#fafbfc}.tavern-persona-session span{color:#838b95;font-size:8px}.tavern-persona-session strong{overflow:hidden;color:#343b43;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-persona-priority{margin:10px 0 0;color:#7b848e;font-size:8px}.tavern-persona-guidance{margin:10px 0 0;border-left:3px solid #6e9edc;padding:8px 9px;background:#f1f7ff;color:#356eaf;font-size:8.5px;line-height:1.55}.tavern-persona-effective{margin:9px 0 0;padding:8px 9px;background:#edf7f1;color:#28754a;font-size:8.5px;line-height:1.5}.tavern-persona-effective.is-empty{background:#f4f5f7;color:#78818b}.tavern-persona-notice,.tavern-persona-error{margin:10px 0 0;font-size:8.5px;line-height:1.5}.tavern-persona-notice{color:#28754a}.tavern-persona-error{color:#a94450}
2157
+ .tavern-card-profile{padding:14px 16px 48px}.tavern-card-profile section{margin-bottom:20px}.tavern-card-profile p{color:#343a41;font-size:11px}.tavern-opening-documents{padding:12px 14px}.tavern-opening-documents article{margin-bottom:10px;border:1px solid #dfe3e8;border-radius:8px;overflow:hidden}.tavern-opening-documents header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e5e7ea;padding:8px 10px;background:#f7f8fa}.tavern-opening-documents header strong{font-size:10px}.tavern-opening-documents header span{color:#2d6cc7;font-size:8px}.tavern-opening-documents p{margin:0;padding:11px;color:#343a41;font-size:11px;line-height:1.7;white-space:pre-wrap}.tavern-compatibility-list{padding:12px 14px}.tavern-compatibility-list>section,.tavern-compatibility-list>article{margin-bottom:8px;border:1px solid #dfe3e8;border-radius:8px;padding:10px}.tavern-compatibility-list>section.is-ready{border-left:3px solid #25a45b}.tavern-compatibility-list>section.is-degraded{border-left:3px solid #e3a52b}.tavern-compatibility-list>section.is-blocked{border-left:3px solid #c94d59}.tavern-compatibility-list header{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-compatibility-list strong{font-size:10px}.tavern-compatibility-list span{color:#6e7781;font-size:8px}.tavern-compatibility-list p{margin:5px 0 0;color:#606a75;font-size:9px;line-height:1.5}.tavern-capability-boundary{border-left:3px solid #e3a52b;padding:9px 10px;background:#fffaf0}.tavern-capability-boundary strong{font-size:10px}
2158
+ .tavern-capability-panel[data-capability="persona"] .tavern-capability-panel-header{min-height:76px;padding:15px 18px}.tavern-capability-panel[data-capability="persona"] .tavern-capability-panel-header strong{font-size:20px}.tavern-capability-panel[data-capability="persona"] .tavern-capability-panel-header span{font-size:11px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-workbench{grid-template-columns:minmax(165px,36%) minmax(0,1fr)}
2159
+ .tavern-capability-panel[data-capability="persona"] .tavern-persona-create{height:42px;margin:14px 10px 10px;font-size:13px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-search{height:40px;margin-bottom:10px;padding:0 10px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-search input{font-size:12px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-list>button{height:64px;grid-template-columns:42px minmax(0,1fr) 18px;gap:9px;padding:7px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-list img{width:42px;height:42px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-list>button>span{font-size:12px}
2160
+ .tavern-capability-panel[data-capability="persona"] .tavern-persona-editor{padding:22px 18px 60px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-identity{grid-template-columns:78px minmax(0,1fr);gap:14px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-avatar{width:78px;height:78px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-identity label>span,.tavern-capability-panel[data-capability="persona"] .tavern-persona-description>span{font-size:11px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-identity input,.tavern-capability-panel[data-capability="persona"] .tavern-persona-description textarea{font-size:13px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-identity input{height:42px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-description{margin-top:18px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-description textarea{min-height:230px;padding:12px 13px;line-height:1.65}
2161
+ .tavern-capability-panel[data-capability="persona"] .tavern-persona-save-row{margin-top:10px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-save-row>span,.tavern-capability-panel[data-capability="persona"] .tavern-persona-save-row small{font-size:10px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-save-row button{height:36px;padding:0 18px;font-size:11px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-binding{margin-top:22px;padding-top:18px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-binding>header strong{font-size:12px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-binding>header button{font-size:10px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-scope{margin-top:10px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-scope button{height:38px;font-size:10.5px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-session{margin-top:14px;padding:11px 12px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-session span{font-size:9.5px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-session strong{font-size:11px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-priority{margin-top:12px;font-size:10px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-guidance,.tavern-capability-panel[data-capability="persona"] .tavern-persona-effective{margin-top:11px;padding:10px 11px;font-size:10px}.tavern-capability-panel[data-capability="persona"] .tavern-persona-notice,.tavern-capability-panel[data-capability="persona"] .tavern-persona-error{font-size:10px}
2162
+ .tavern-capability-panel[data-capability="persona"] .tavern-persona-list>button{border-color:#e1e5ea;outline:0;background:#fff;box-shadow:0 1px 2px #182a4212}.tavern-capability-panel[data-capability="persona"] .tavern-persona-list>button:focus-visible{outline:0;box-shadow:0 0 0 2px #2877eb55}.tavern-capability-panel[data-capability="persona"] .tavern-persona-save-row small{display:inline-flex;align-items:center;gap:4px}
2163
+ .tavern-capability-panel[data-capability="persona"] .tavern-persona-list>button[aria-selected=true]{border-color:#76a5e6;background:#f5f9ff;box-shadow:0 0 0 1px #6fa4e933,0 2px 5px #182a4214}
2164
+ @media(max-width:1050px){:root{--tavern-capability-panel-width:440px}.tavern-worldbook-workbench{grid-template-columns:180px minmax(0,1fr)}.tavern-regex-workbench{grid-template-columns:155px minmax(0,1fr)}}@media(max-width:760px){:root{--tavern-capability-panel-width:calc(100vw - var(--tavern-capability-rail-width))}body:has(.tavern-card-capability-rail),body:has(.tavern-capability-panel){padding-right:var(--tavern-capability-rail-width)}.tavern-capability-panel{box-shadow:-16px 0 40px #0002}.tavern-worldbook-workbench{grid-template-columns:170px minmax(0,1fr)}.tavern-regex-workbench{grid-template-columns:145px minmax(0,1fr)}}
2165
+
2166
+ body:has(.tavern-capability-panel[data-capability="preset"]){--tavern-capability-panel-width:clamp(700px,58vw,960px)}.tavern-preset-workbench{min-height:0;flex:1;overflow:auto;background:#f4f6f8;padding-bottom:48px}.tavern-preset-workbench button,.tavern-preset-workbench input,.tavern-preset-workbench select,.tavern-preset-workbench textarea{box-sizing:border-box;font:inherit}.tavern-preset-toolbar{position:sticky;z-index:3;top:0;display:flex;align-items:flex-end;justify-content:space-between;gap:14px;border-bottom:1px solid #dfe4e9;padding:12px 16px;background:#fff}.tavern-preset-select-wrap{display:grid;min-width:280px;flex:1;grid-template-columns:minmax(160px,1fr) minmax(140px,.8fr);gap:7px}.tavern-preset-select-wrap>label{grid-column:1/-1;color:#747d87;font-size:8px;font-weight:700;letter-spacing:.06em}.tavern-preset-select-wrap select,.tavern-preset-select-wrap input,.tavern-preset-library select,.tavern-preset-prompt-editor input,.tavern-preset-prompt-editor select,.tavern-preset-budget-grid input[type=number],.tavern-preset-slider input[type=number]{height:32px;min-width:0;border:1px solid #d5dae0;border-radius:7px;padding:0 9px;background:#fff;color:#262b31;font-size:10px;outline:none}.tavern-preset-select-wrap select:focus,.tavern-preset-select-wrap input:focus,.tavern-preset-library select:focus,.tavern-preset-prompt-editor input:focus,.tavern-preset-prompt-editor select:focus,.tavern-preset-prompt-editor textarea:focus{border-color:#6e9cdc;box-shadow:0 0 0 2px #357bd51a}.tavern-preset-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:5px}.tavern-preset-actions button,.tavern-preset-action-link,.tavern-preset-prompts>header button,.tavern-preset-library button{display:inline-flex;height:31px;box-sizing:border-box;align-items:center;justify-content:center;gap:5px;border:1px solid #cfd6de;border-radius:7px;padding:0 9px;background:#fff;color:#44505d;cursor:pointer;font-size:9px;font-weight:600;text-decoration:none}.tavern-preset-actions button:hover:not(:disabled),.tavern-preset-action-link:hover,.tavern-preset-prompts>header button:hover,.tavern-preset-library button:hover:not(:disabled){border-color:#9db8dd;background:#f3f7fd;color:#2366bd}.tavern-preset-actions button:disabled,.tavern-preset-library button:disabled{cursor:not-allowed;opacity:.45}.tavern-preset-actions .is-danger{color:#a34851}.tavern-preset-binding{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;border-bottom:1px solid #e1e6eb;padding:8px 16px;background:#fafbfc;color:#6e7781;font-size:8.5px}.tavern-preset-binding strong{color:#4b5560;font-size:9px}.tavern-preset-binding.is-session{background:#f2f7ff;color:#4b6d98}.tavern-preset-binding.is-session strong{color:#245eaa}.tavern-preset-binding em{color:#a46b18;font-style:normal;font-weight:650}.tavern-preset-notice,.tavern-preset-error{margin:0;border-bottom:1px solid;padding:8px 16px;font-size:9px;line-height:1.45}.tavern-preset-notice{border-color:#cfe6d7;background:#f2fbf5;color:#287148}.tavern-preset-error{border-color:#edc9cd;background:#fff5f5;color:#a13d48}.tavern-preset-generation,.tavern-preset-prompts{margin:14px 16px 0;border:1px solid #dfe4e9;border-radius:10px;background:#fff;box-shadow:0 1px 2px #182a4210}.tavern-preset-generation{padding:14px}.tavern-preset-generation>header,.tavern-preset-prompts>header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.tavern-preset-generation h3,.tavern-preset-prompts h3{margin:0;color:#252b32;font-size:13px}.tavern-preset-generation p,.tavern-preset-prompts p{margin:4px 0 0;color:#78818b;font-size:8.5px;line-height:1.5}.tavern-preset-switch,.tavern-preset-stream{display:flex;align-items:center;gap:7px;color:#4f5964;font-size:9px;font-weight:600}.tavern-preset-switch input,.tavern-preset-stream input,.tavern-preset-prompt-list input{position:absolute;width:1px;height:1px;opacity:0}.tavern-preset-switch>span,.tavern-preset-stream>span{position:relative;width:30px;height:17px;flex:none;border-radius:999px;background:#c9cfd6;transition:background .15s ease}.tavern-preset-switch>span::after,.tavern-preset-stream>span::after{position:absolute;top:2px;left:2px;width:13px;height:13px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #0003;content:"";transition:transform .15s ease}.tavern-preset-switch input:checked+span,.tavern-preset-stream input:checked+span{background:#3378d3}.tavern-preset-switch input:checked+span::after,.tavern-preset-stream input:checked+span::after{transform:translateX(13px)}.tavern-preset-budget-grid{display:grid;grid-template-columns:minmax(0,1.45fr) minmax(180px,.55fr);gap:12px;margin-top:14px}.tavern-preset-budget-grid>label,.tavern-preset-slider{display:flex;min-width:0;flex-direction:column;gap:7px;color:#4e5863;font-size:9px;font-weight:600}.tavern-preset-budget-grid>label>span,.tavern-preset-slider>span{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-preset-budget-grid em,.tavern-preset-slider em{color:#2f76ce;font-size:7.5px;font-style:normal;font-weight:600}.tavern-preset-budget-grid>label>div,.tavern-preset-slider>div{display:grid;grid-template-columns:minmax(80px,1fr) 92px;align-items:center;gap:8px}.tavern-preset-budget-grid input[type=range],.tavern-preset-slider input[type=range]{width:100%;accent-color:#357bd5}.tavern-preset-stream{margin-top:14px;border-top:1px solid #e8ebef;padding-top:12px}.tavern-preset-stream>div{display:flex;flex-direction:column;gap:2px}.tavern-preset-stream small{color:#7a838d;font-size:8px;font-weight:400}.tavern-preset-samplers{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;margin-top:14px;border-top:1px solid #e8ebef;padding-top:14px}.tavern-preset-slider.is-stored em{color:#91691d}.tavern-preset-prompts{overflow:hidden}.tavern-preset-prompts>header{align-items:center;border-bottom:1px solid #e4e8ec;padding:12px 14px}.tavern-preset-library{display:flex;align-items:center;gap:7px;border-bottom:1px solid #e3e7eb;padding:8px 12px;background:#f8fafc}.tavern-preset-library select{flex:1}.tavern-preset-prompt-layout{display:grid;min-height:430px;grid-template-columns:minmax(280px,.9fr) minmax(300px,1.1fr)}.tavern-preset-prompt-list{max-height:560px;overflow:auto;border-right:1px solid #e1e5e9;background:#f5f7f9}.tavern-preset-prompt-list article{display:grid;min-height:52px;grid-template-columns:27px 23px minmax(0,1fr) 25px;align-items:center;gap:5px;border-bottom:1px solid #e3e7eb;padding:5px 7px;background:#fff;color:#353c44}.tavern-preset-prompt-list article:hover{background:#f8fbff}.tavern-preset-prompt-list article.is-selected{position:relative;background:#eef5ff;color:#1f5fae}.tavern-preset-prompt-list article.is-selected::before{position:absolute;inset:0 auto 0 0;width:3px;background:#367bd2;content:""}.tavern-preset-prompt-list article.is-disabled{opacity:.55}.tavern-preset-order{color:#8b939d;font:600 8px/1 ui-monospace,SFMono-Regular,Consolas,monospace;text-align:center}.tavern-preset-prompt-list label{display:grid;width:17px;height:17px;place-items:center;border:1px solid #bcc5ce;border-radius:4px;background:#fff}.tavern-preset-prompt-list label span{display:none;width:8px;height:4px;border-bottom:2px solid #fff;border-left:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.tavern-preset-prompt-list label:has(input:checked){border-color:#3277d0;background:#3277d0}.tavern-preset-prompt-list label:has(input:checked) span{display:block}.tavern-preset-prompt-main{display:flex;min-width:0;flex-direction:column;gap:3px;border:0;padding:5px 3px;background:transparent;color:inherit;cursor:pointer;text-align:left}.tavern-preset-prompt-main strong,.tavern-preset-prompt-main small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-preset-prompt-main strong{font-size:10px}.tavern-preset-prompt-main small{color:#7d8690;font-size:7.5px}.tavern-preset-move{display:flex;flex-direction:column}.tavern-preset-move button{width:22px;height:19px;border:0;border-radius:4px;background:transparent;color:#7b8590;cursor:pointer;font-size:10px;line-height:1}.tavern-preset-move button:hover:not(:disabled){background:#e5edf8;color:#2569bd}.tavern-preset-move button:disabled{opacity:.28}.tavern-preset-prompt-editor{min-width:0;padding:15px 16px 24px;background:#fff}.tavern-preset-prompt-editor>header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:14px}.tavern-preset-prompt-editor>header>div{display:flex;min-width:0;flex-direction:column;gap:3px}.tavern-preset-prompt-editor>header small{color:#818a94;font-size:8px}.tavern-preset-prompt-editor>header strong{overflow:hidden;font-size:13px;text-overflow:ellipsis;white-space:nowrap}.tavern-preset-prompt-editor>header em{border-radius:999px;padding:3px 7px;background:#eef0f3;color:#737c86;font-size:8px;font-style:normal}.tavern-preset-prompt-editor>label,.tavern-preset-editor-grid label,.tavern-preset-content{display:flex;min-width:0;flex-direction:column;gap:5px;color:#69737e;font-size:8.5px;font-weight:650}.tavern-preset-editor-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-top:10px}.tavern-preset-content{margin-top:11px}.tavern-preset-prompt-editor textarea{width:100%;resize:vertical;border:1px solid #d5dae0;border-radius:8px;padding:10px;background:#fbfcfd;color:#303840;font:9px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace;outline:none}.tavern-preset-marker-note{margin-top:12px;border-left:3px solid #4d86d1;padding:9px 10px;background:#f2f7fd}.tavern-preset-marker-note strong{color:#315f99;font-size:9px}.tavern-preset-marker-note p{margin:4px 0 0;color:#687a90;font-size:8.5px;line-height:1.55}.tavern-preset-remove-prompt{margin-top:12px;border:1px solid #e3bfc3;border-radius:7px;padding:6px 9px;background:#fff8f8;color:#a34852;cursor:pointer;font-size:8.5px}.tavern-preset-prompt-empty{display:grid;place-items:center;padding:24px;color:#7f8892;font-size:9px}.tavern-preset-card-overrides{margin:12px 16px 0;border:1px solid #dfe4e9;border-radius:9px;background:#fff}.tavern-preset-card-overrides summary{padding:10px 12px;color:#4f5964;cursor:pointer;font-size:9px;font-weight:650}.tavern-preset-card-overrides>div{border-top:1px solid #e5e9ed;padding:10px 12px}.tavern-preset-card-overrides label{display:block;margin:8px 0 5px;color:#7b848e;font-size:8px;font-weight:650}.tavern-preset-card-overrides pre{max-height:150px;overflow:auto;margin:0;border:1px solid #e4e8ec;border-radius:7px;padding:8px;background:#f8fafb;color:#343b43;font:8px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap}
2167
+ .tavern-variable-tree{padding:12px 14px 50px}.tavern-variable-branch{margin:4px 0;border-left:1px solid #dfe3e8;padding-left:10px}.tavern-variable-branch summary{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:5px 0;cursor:pointer}.tavern-variable-branch summary strong{font:600 10px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-variable-branch summary span{color:#858c95;font-size:8px}.tavern-variable-branch>div{padding-left:7px}.tavern-variable-leaf{display:grid;grid-template-columns:minmax(90px,.8fr) minmax(0,1.2fr);gap:8px;border-bottom:1px dotted #e2e5e8;padding:5px 0;font-size:9px}.tavern-variable-leaf span{overflow-wrap:anywhere;color:#59636e}.tavern-variable-leaf code{overflow-wrap:anywhere;color:#2b61a8}.tavern-variable-diff{padding:12px 16px}.tavern-variable-diff ol{margin:0;padding-left:20px}.tavern-variable-diff li{margin-bottom:7px;font-size:9px}.tavern-variable-diff strong,.tavern-variable-diff span{display:block}.tavern-variable-diff span{margin-top:2px;color:#6f7882}.tavern-raw-json{min-height:0;flex:1;padding:16px!important}
2168
+ .tavern-regex-workbench .tavern-capability-inspector>pre{margin-top:5px}.tavern-regex-matches{padding:12px}.tavern-regex-matches>article{margin-bottom:12px;border:1px solid #dfe3e8;border-radius:8px;overflow:hidden}.tavern-regex-matches>article>header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e5e7ea;padding:7px 9px;background:#f7f8fa;font-size:9px}.tavern-regex-matches>article>div{display:grid;grid-template-columns:1fr 1fr}.tavern-regex-matches section+section{border-left:1px solid #e5e7ea}.tavern-regex-matches h4{margin:0;padding:6px 8px;color:#6e7781;font-size:8px}.tavern-regex-matches pre{max-height:220px}.tavern-runtime-errors{margin:0;padding-left:18px;color:#a94450;font-size:9px;line-height:1.5}.tavern-script-list{padding:12px 14px}.tavern-script-list details{margin-bottom:8px;border:1px solid #dfe3e8;border-radius:8px;overflow:hidden}.tavern-script-list summary{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 10px;cursor:pointer}.tavern-script-list summary strong{font-size:10px}.tavern-script-list summary span{color:#818994;font-size:8px}.tavern-script-list pre{max-height:420px;border-top:1px solid #e5e7ea}.tavern-chip-list{display:flex;flex-wrap:wrap;gap:5px}.tavern-chip-list span{border-radius:999px;padding:4px 7px;background:#edf3fc;color:#2d67ba;font:8px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
2169
+ .tavern-regex-settings,.tavern-regex-evidence{min-height:0;flex:1;overflow:auto;background:#f5f7fa}.tavern-regex-overview{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;border-bottom:1px solid #e3e7eb;padding:12px 16px;background:#f7f9fc}.tavern-regex-overview>div{display:flex;min-width:0;flex-direction:column;gap:3px;border:1px solid #e2e6eb;border-radius:9px;padding:9px 10px;background:#fff}.tavern-regex-overview strong{overflow:hidden;color:#20252b;font-size:16px;line-height:1.15;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-overview span{overflow:hidden;color:#7c848d;font-size:8px;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;border-bottom:1px solid #e3e7eb;padding:10px 16px;background:#fff}.tavern-regex-toolbar .tavern-capability-search{height:34px;margin:0}.tavern-regex-debug-button{display:inline-flex;height:34px;align-items:center;gap:6px;border:1px solid #cbd9eb;border-radius:7px;padding:0 10px;background:#f6f9fd;color:#2b65ad;cursor:pointer;font:600 9px/1 inherit}.tavern-regex-debug-button:hover{border-color:#9ebbe0;background:#edf4fd}.tavern-regex-scopes{padding:12px 14px 48px}.tavern-regex-scope{margin-bottom:10px;border:1px solid #dfe4e9;border-radius:10px;background:#fff;box-shadow:0 1px 2px #1b2d4510;overflow:hidden}.tavern-regex-scope>header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 12px}.tavern-regex-scope>header>div{display:flex;min-width:0;flex-direction:column;gap:3px}.tavern-regex-scope>header strong{color:#2b3138;font-size:11px}.tavern-regex-scope>header span{overflow:hidden;color:#7d8690;font-size:8.5px;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-scope>header em{flex:none;border-radius:999px;padding:3px 7px;background:#eef1f4;color:#747d87;font-size:8px;font-style:normal}.tavern-regex-scope>p{margin:0;border-top:1px solid #edf0f2;padding:9px 12px;color:#7d858e;font-size:9px;line-height:1.55}.tavern-regex-scope.is-unavailable{background:#fafbfc}.tavern-regex-scope.is-unavailable>header{opacity:.75}.tavern-regex-scope.is-current{border-color:#cad9ec}.tavern-regex-scope.is-current>header{background:#f7faff}.tavern-regex-scope.is-current>header em{background:#e7f1ff;color:#2464b5}.tavern-regex-rule-list{border-top:1px solid #e4e9ef}.tavern-regex-rule-list>article+article{border-top:1px solid #e8ecf0}.tavern-regex-rule-list>article>button{display:grid;width:100%;min-height:58px;box-sizing:border-box;grid-template-columns:28px minmax(0,1fr) auto 16px;align-items:center;gap:8px;border:0;padding:8px 10px;background:#fff;color:#30363d;cursor:pointer;text-align:left}.tavern-regex-rule-list>article>button:hover{background:#f7faff}.tavern-regex-rule-list>article.is-expanded>button{background:#eef5ff;color:#205fae}.tavern-regex-order{display:grid;width:25px;height:25px;place-items:center;border-radius:7px;background:#f0f2f5;color:#737c86;font:650 8px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-regex-rule-list>article.is-expanded .tavern-regex-order{background:#dcecff;color:#1d61b7}.tavern-regex-rule-copy{display:flex;min-width:0;flex-direction:column;gap:4px}.tavern-regex-rule-copy strong,.tavern-regex-rule-copy small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-rule-copy strong{font-size:10.5px}.tavern-regex-rule-copy small{color:#7a838d;font-size:8px}.tavern-regex-enabled{border-radius:999px;padding:3px 7px;background:#eaf7ef;color:#277949;font-size:8px;white-space:nowrap}.tavern-regex-rule-detail{border-top:1px solid #dfe8f2;padding:12px;background:#fbfdff}.tavern-regex-rule-facts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-bottom:12px}.tavern-regex-rule-facts>div{display:flex;min-width:0;flex-direction:column;gap:3px;border:1px solid #e5eaf0;border-radius:7px;padding:7px 8px;background:#fff}.tavern-regex-rule-facts span{color:#828b95;font-size:7.5px}.tavern-regex-rule-facts strong{overflow-wrap:anywhere;color:#343b43;font-size:8.5px;line-height:1.4}.tavern-regex-rule-detail>label{display:block;margin:10px 0 5px;color:#6f7882;font-size:8px;font-weight:650}.tavern-regex-rule-detail>pre{max-height:180px;margin:0;overflow:auto;border:1px solid #e2e7ed;border-radius:7px;padding:9px;background:#f6f8fa;color:#293039;font:8.5px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.tavern-regex-rule-id{display:block;margin-top:10px;overflow:hidden;color:#9098a1;font:7.5px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-boundary{margin-top:12px;border-left:3px solid #d69a34;border-radius:0 8px 8px 0;padding:9px 10px;background:#fff9ee}.tavern-regex-boundary strong{color:#684814;font-size:9px}.tavern-regex-boundary p{margin:4px 0 0;color:#806333;font-size:8.5px;line-height:1.55}.tavern-regex-evidence{padding-bottom:48px}.tavern-regex-evidence-summary{border-bottom:1px solid #e1e6eb;padding:14px 16px;background:#fff}.tavern-regex-evidence-summary>header,.tavern-regex-match-section>header{display:flex;align-items:center;justify-content:space-between;gap:10px}.tavern-regex-evidence-summary>header>div,.tavern-regex-match-section>header>div{display:flex;min-width:0;flex-direction:column;gap:3px}.tavern-regex-evidence-summary>header strong,.tavern-regex-match-section>header strong{font-size:11px}.tavern-regex-evidence-summary>header span,.tavern-regex-match-section>header span{color:#7d858f;font-size:8px}.tavern-regex-plane-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:11px}.tavern-regex-plane-grid>article{display:flex;min-width:0;flex-direction:column;gap:3px;border:1px solid #e2e7ec;border-radius:8px;padding:9px;background:#fafbfc}.tavern-regex-plane-grid>article.is-active{border-color:#aac7ec;background:#f1f7ff}.tavern-regex-plane-grid span{color:#7c858f;font-size:7.5px}.tavern-regex-plane-grid strong{overflow:hidden;color:#343b43;font-size:9px;text-overflow:ellipsis;white-space:nowrap}.tavern-regex-plane-grid small{color:#8b939c;font-size:7.5px}.tavern-regex-evidence-summary>p{margin:10px 0 0;color:#69737d;font-size:8.5px;line-height:1.55}.tavern-regex-match-section{padding:14px}.tavern-regex-match-section>.tavern-regex-matches{padding:10px 0 0}.tavern-regex-matches>article{background:#fff;box-shadow:0 1px 2px #182a4210}.tavern-regex-matches>article>header{padding:8px 10px}.tavern-regex-matches>article>header span{color:#7f8892}.tavern-regex-matches pre{font-size:8.5px;line-height:1.6}
2170
+ .tavern-card-profile{padding:14px 16px 48px}.tavern-card-profile section{margin-bottom:20px}.tavern-card-profile p{color:#343a41;font-size:11px}.tavern-opening-documents{padding:12px 14px}.tavern-opening-documents article{margin-bottom:10px;border:1px solid #dfe3e8;border-radius:8px;overflow:hidden}.tavern-opening-documents header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e5e7ea;padding:8px 10px;background:#f7f8fa}.tavern-opening-documents header strong{font-size:10px}.tavern-opening-documents header span{color:#2d6cc7;font-size:8px}.tavern-opening-documents p{margin:0;padding:11px;color:#343a41;font-size:11px;line-height:1.7;white-space:pre-wrap}.tavern-compatibility-list{padding:12px 14px}.tavern-compatibility-list>section,.tavern-compatibility-list>article{margin-bottom:8px;border:1px solid #dfe3e8;border-radius:8px;padding:10px}.tavern-compatibility-list>section.is-ready{border-left:3px solid #25a45b}.tavern-compatibility-list>section.is-degraded{border-left:3px solid #e3a52b}.tavern-compatibility-list>section.is-blocked{border-left:3px solid #c94d59}.tavern-compatibility-list header{display:flex;align-items:center;justify-content:space-between;gap:8px}.tavern-compatibility-list strong{font-size:10px}.tavern-compatibility-list span{color:#6e7781;font-size:8px}.tavern-compatibility-list p{margin:5px 0 0;color:#606a75;font-size:9px;line-height:1.5}.tavern-capability-boundary{border-left:3px solid #e3a52b;padding:9px 10px;background:#fffaf0}.tavern-capability-boundary strong{font-size:10px}
2171
+ @media(max-width:1050px){:root{--tavern-capability-panel-width:440px}body:has(.tavern-capability-panel.is-open[data-capability="preset"]){--tavern-capability-panel-width:calc(100vw - var(--tavern-capability-rail-width))}.tavern-worldbook-workbench{grid-template-columns:180px minmax(0,1fr)}.tavern-regex-workbench{grid-template-columns:155px minmax(0,1fr)}.tavern-preset-toolbar{align-items:stretch;flex-direction:column}.tavern-preset-select-wrap{width:100%}.tavern-preset-actions{justify-content:flex-start}}@media(max-width:760px){:root{--tavern-capability-panel-width:calc(100vw - var(--tavern-capability-rail-width))}body:has(.tavern-card-capability-rail){padding-right:var(--tavern-capability-rail-width)}.tavern-capability-panel{box-shadow:-16px 0 40px #0002}.tavern-worldbook-workbench{grid-template-columns:170px minmax(0,1fr)}.tavern-regex-workbench{grid-template-columns:145px minmax(0,1fr)}.tavern-preset-budget-grid,.tavern-preset-samplers,.tavern-preset-prompt-layout{grid-template-columns:1fr}.tavern-preset-prompt-list{max-height:360px;border-right:0;border-bottom:1px solid #e1e5e9}.tavern-preset-select-wrap{min-width:0;grid-template-columns:1fr}.tavern-preset-select-wrap>label{grid-column:auto}.tavern-preset-binding{grid-template-columns:1fr}.tavern-preset-generation>header{align-items:flex-start;flex-direction:column}}
2172
+ .tavern-preset-advanced{margin:12px 16px 0;border:1px solid #dfe4e9;border-radius:9px;background:#fff;box-shadow:0 1px 2px #182a4210}.tavern-preset-advanced>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;color:#4f5964;cursor:pointer;list-style:none}.tavern-preset-advanced>summary::-webkit-details-marker{display:none}.tavern-preset-advanced>summary::after{width:6px;height:6px;flex:none;border-right:1.5px solid #7b8590;border-bottom:1.5px solid #7b8590;content:"";transform:rotate(45deg);transition:transform .15s ease}.tavern-preset-advanced[open]>summary::after{transform:rotate(225deg)}.tavern-preset-advanced>summary strong{font-size:10px}.tavern-preset-advanced-body{display:flex;align-items:flex-end;gap:10px;border-top:1px solid #e6e9ed;padding:10px 12px}.tavern-preset-advanced-body .tavern-preset-samplers{min-width:0;flex:1;margin:0;border:0;padding:0;grid-template-columns:repeat(2,minmax(0,1fr))}.tavern-preset-reset-sampling{height:30px;flex:none;margin:0;border:1px solid #ced9e7;border-radius:7px;padding:0 9px;background:#f7faff;color:#3168aa;cursor:pointer;font-size:8.5px;font-weight:600}
2173
+ .tavern-preset-workbench{display:flex;overflow:hidden;padding:0;flex-direction:column;background:#f7f8fa}.tavern-preset-toolbar{position:relative;z-index:5;align-items:center;gap:10px;padding:10px 14px}.tavern-preset-select-wrap{display:flex;min-width:0;align-items:center;gap:8px}.tavern-preset-select-wrap select{width:min(330px,55%);height:34px}.tavern-preset-revision{overflow:hidden;border:1px solid #dfe4e9;border-radius:6px;padding:6px 8px;background:#f6f7f9;color:#6f7882;font-size:8px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tavern-preset-revision.is-dirty{border-color:#d5b66f;background:#fff9ea;color:#8a6319}.tavern-preset-actions{position:relative;flex:none;flex-wrap:nowrap}.tavern-preset-actions>button,.tavern-preset-more>summary{display:inline-flex;height:34px;box-sizing:border-box;align-items:center;justify-content:center;gap:5px;border:1px solid #ccd4dd;border-radius:7px;padding:0 11px;background:#fff;color:#44505d;cursor:pointer;font-size:9px;font-weight:650;list-style:none}.tavern-preset-actions .is-primary,.tavern-preset-editor-footer .is-primary{border-color:#2f73ca;background:#3278d3;color:#fff}.tavern-preset-actions .is-primary:hover:not(:disabled),.tavern-preset-editor-footer .is-primary:hover:not(:disabled){border-color:#245fae;background:#2869be;color:#fff}.tavern-preset-more{position:relative}.tavern-preset-more>summary{width:34px;padding:0}.tavern-preset-more>summary::-webkit-details-marker{display:none}.tavern-preset-menu{position:absolute;z-index:10;top:39px;right:0;display:flex;width:138px;overflow:hidden;flex-direction:column;border:1px solid #d7dde4;border-radius:8px;padding:5px;background:#fff;box-shadow:0 10px 26px #182a4224}.tavern-preset-menu button,.tavern-preset-menu a{display:flex;height:32px;align-items:center;gap:8px;border:0;border-radius:5px;padding:0 9px;background:transparent;color:#3f4852;cursor:pointer;font-size:9px;font-weight:550;text-decoration:none}.tavern-preset-menu button:hover:not(:disabled),.tavern-preset-menu a:hover{background:#f0f5fc;color:#2468be}.tavern-preset-menu .is-danger{color:#a7444d}.tavern-preset-rename{display:flex;align-items:flex-end;gap:7px;border-bottom:1px solid #e1e5ea;padding:8px 14px;background:#fbfcfd}.tavern-preset-rename label{display:flex;min-width:0;flex:1;flex-direction:column;gap:4px;color:#6f7882;font-size:8px;font-weight:650}.tavern-preset-rename input{height:31px;border:1px solid #a9c4e7;border-radius:7px;padding:0 9px;background:#fff;outline:none;box-shadow:0 0 0 2px #357bd512}.tavern-preset-rename button{height:31px;border:1px solid #d0d7df;border-radius:7px;padding:0 9px;background:#fff;color:#45515e;cursor:pointer;font-size:8.5px}.tavern-preset-binding{grid-template-columns:auto minmax(0,1fr);padding:7px 14px}.tavern-preset-notice,.tavern-preset-error{padding:7px 14px}.tavern-preset-advanced{flex:none;margin:8px 14px 0;border-radius:7px;box-shadow:none}.tavern-preset-advanced>summary{padding:9px 11px}.tavern-preset-prompts{display:grid;min-height:0;flex:1;grid-template-columns:216px minmax(0,1fr);overflow:hidden;margin:8px 14px 14px;border-radius:8px;box-shadow:none}.tavern-preset-prompt-nav{display:flex;min-width:0;min-height:0;overflow:hidden;flex-direction:column;border-right:1px solid #e0e5ea;background:#fbfcfd}.tavern-preset-prompt-nav>header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:13px 12px 8px}.tavern-preset-prompt-nav h3{font-size:12px}.tavern-preset-prompt-nav>header button{display:grid;width:28px;height:28px;flex:none;place-items:center;border:1px solid #d2d9e1;border-radius:7px;background:#fff;color:#4f5b68;cursor:pointer}.tavern-preset-search{display:flex;height:31px;box-sizing:border-box;align-items:center;gap:6px;margin:0 10px;border:1px solid #d9dfe6;border-radius:7px;padding:0 8px;background:#fff;color:#8a939d}.tavern-preset-search input{width:100%;height:auto;border:0;padding:0;background:transparent;color:#343c45;font-size:8.5px;outline:none}.tavern-preset-prompt-tabs{display:grid;grid-template-columns:repeat(2,1fr);margin:8px 10px 0;border-bottom:1px solid #dfe4e9}.tavern-preset-prompt-tabs button{position:relative;height:31px;border:0;background:transparent;color:#747d87;cursor:pointer;font-size:9px;font-weight:650}.tavern-preset-prompt-tabs button.is-active{color:#2368bd}.tavern-preset-prompt-tabs button.is-active::after{position:absolute;right:0;bottom:-1px;left:0;height:2px;background:#3278d3;content:""}.tavern-preset-prompt-tabs span{margin-left:3px;color:#929aa3;font-size:7px}.tavern-preset-prompt-list,.tavern-preset-library-list{min-height:0;max-height:none;overflow:auto;border:0;background:#fbfcfd}.tavern-preset-prompt-list article{min-height:47px;grid-template-columns:23px minmax(0,1fr) 30px 22px;gap:3px;padding:4px 6px;background:#fbfcfd}.tavern-preset-prompt-list article.is-selected{background:#edf5ff}.tavern-preset-prompt-main{gap:2px;padding:4px 2px}.tavern-preset-prompt-main strong{font-size:9px}.tavern-preset-prompt-main small{font-size:7px}.tavern-preset-row-toggle{position:relative!important;display:block!important;width:28px!important;height:16px!important;border:0!important;border-radius:999px!important;background:transparent!important}.tavern-preset-row-toggle input,.tavern-preset-editor-toggle input{position:absolute;width:1px;height:1px;opacity:0}.tavern-preset-row-toggle>span,.tavern-preset-editor-toggle>span{position:relative;display:block!important;width:28px!important;height:16px!important;border:0!important;border-radius:999px!important;background:#c9d0d8!important;transition:background .15s ease}.tavern-preset-row-toggle>span::after,.tavern-preset-editor-toggle>span::after{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #0003;content:"";transition:transform .15s ease}.tavern-preset-row-toggle:has(input:checked)>span,.tavern-preset-editor-toggle:has(input:checked)>span{background:#3278d3!important}.tavern-preset-row-toggle:has(input:checked)>span::after,.tavern-preset-editor-toggle:has(input:checked)>span::after{transform:translateX(12px)}.tavern-preset-move{gap:0}.tavern-preset-move button{width:20px;height:20px}.tavern-preset-move button:first-child svg{transform:rotate(90deg)}.tavern-preset-move button:last-child svg{transform:rotate(90deg)}.tavern-preset-library-list article{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;border-bottom:1px solid #e5e9ed;padding:5px 7px}.tavern-preset-library-list article.is-selected{background:#edf5ff}.tavern-preset-library-list article>button:first-child{display:flex;min-width:0;flex-direction:column;gap:2px;border:0;padding:7px 4px;background:transparent;color:#39424c;cursor:pointer;text-align:left}.tavern-preset-library-list strong,.tavern-preset-library-list small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-preset-library-list strong{font-size:9px}.tavern-preset-library-list small{color:#818a94;font-size:7px}.tavern-preset-insert{border:1px solid #cbd7e6;border-radius:6px;padding:5px 7px;background:#fff;color:#3169aa;cursor:pointer;font-size:8px}.tavern-preset-list-empty{margin:0;padding:24px 12px;color:#8a939d;font-size:8.5px;text-align:center}.tavern-preset-prompt-editor{position:relative;display:flex;min-width:0;min-height:0;overflow:auto;flex-direction:column;padding:16px 18px 64px;background:#fff}.tavern-preset-prompt-editor>header{align-items:center;margin-bottom:18px}.tavern-preset-prompt-editor>header strong{font-size:16px;line-height:1.25}.tavern-preset-editor-actions{display:flex;align-items:center;gap:5px}.tavern-preset-editor-actions>button{display:grid;width:29px;height:29px;place-items:center;border:1px solid #d4dae1;border-radius:7px;background:#fff;color:#56616d;cursor:pointer}.tavern-preset-editor-actions>button:disabled{cursor:not-allowed;opacity:.35}.tavern-preset-editor-toggle{display:flex;align-items:center;gap:5px;margin:0 5px 0 0!important;color:#66717c!important;font-size:8px!important}.tavern-preset-prompt-editor>label,.tavern-preset-editor-grid label,.tavern-preset-content{font-size:9px}.tavern-preset-prompt-editor input,.tavern-preset-prompt-editor select{height:35px;font-size:9.5px}.tavern-preset-editor-grid{gap:11px;margin-top:12px}.tavern-preset-content{min-height:210px;flex:1;margin-top:14px}.tavern-preset-content>span{display:flex;align-items:baseline;justify-content:space-between}.tavern-preset-content>span small{color:#8b949e;font-size:7.5px;font-weight:500}.tavern-preset-prompt-editor textarea{min-height:190px;flex:1;resize:none;padding:12px;background:#fff;font:9px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace}.tavern-preset-marker-note{margin-top:14px}.tavern-preset-editor-more{margin-top:14px;border-top:1px solid #e4e8ed}.tavern-preset-editor-more>summary{padding:11px 2px;color:#59646f;cursor:pointer;font-size:8.5px;font-weight:650}.tavern-preset-editor-more>div{padding-bottom:8px}.tavern-preset-card-overrides{margin:4px 0 0;box-shadow:none}.tavern-preset-editor-footer{position:absolute;right:0;bottom:0;left:0;display:flex;height:52px;box-sizing:border-box;align-items:center;justify-content:flex-end;gap:7px;border-top:1px solid #e0e5ea;padding:9px 14px;background:#fff}.tavern-preset-editor-footer>span{margin-right:auto;color:#7e8791;font-size:8px}.tavern-preset-editor-footer>span.is-dirty{color:#996c18}.tavern-preset-editor-footer>button{height:31px;border:1px solid #d0d7df;border-radius:7px;padding:0 10px;background:#fff;color:#45515e;cursor:pointer;font-size:8.5px;font-weight:650}.tavern-preset-editor-footer>button:disabled{cursor:not-allowed;opacity:.45}
2174
+ .tavern-preset-search{height:38px;flex:none;gap:7px;border-radius:8px;padding:0 10px;color:#7e8893}.tavern-preset-search input{font-size:11px}.tavern-preset-prompt-tabs{flex:none}.tavern-preset-prompt-tabs button{height:38px;font-size:11.5px}.tavern-preset-prompt-tabs span{font-size:9.5px}.tavern-preset-prompt-main strong{font-size:10.5px}.tavern-preset-prompt-main small{font-size:8px}.tavern-preset-row-toggle>span,.tavern-preset-editor-toggle>span{transform:none!important}.tavern-preset-prompt-editor textarea{font-family:inherit;font-size:12px;line-height:1.7}
2175
+ .tavern-preset-prompt-nav{position:relative}.tavern-preset-prompt-nav>header .tavern-preset-add-trigger{display:flex;width:auto;height:30px;gap:4px;padding:0 8px;font-size:9.5px;font-weight:650}.tavern-preset-prompt-list{margin-top:8px;border-top:1px solid #e1e5ea}.tavern-preset-add-popover{position:absolute;z-index:6;top:54px;right:8px;left:8px;display:flex;max-height:calc(100% - 62px);box-sizing:border-box;overflow:hidden;flex-direction:column;border:1px solid #ced6df;border-radius:9px;padding:8px;background:#fff;box-shadow:0 10px 28px #182a4230}.tavern-preset-add-popover>header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:2px 2px 8px}.tavern-preset-add-popover>header>div{display:flex;min-width:0;flex-direction:column;gap:2px}.tavern-preset-add-popover>header strong{font-size:10.5px}.tavern-preset-add-popover>header span{color:#7e8791;font-size:8px}.tavern-preset-add-popover>header>button{display:grid;width:25px;height:25px;flex:none;place-items:center;border:0;border-radius:6px;background:transparent;color:#65717d;cursor:pointer}.tavern-preset-create-prompt{display:flex;min-height:42px;flex:none;align-items:center;gap:8px;border:1px solid #c9d8ea;border-radius:7px;padding:6px 9px;background:#f4f8fe;color:#2868b8;cursor:pointer;text-align:left}.tavern-preset-create-prompt>span{display:flex;min-width:0;flex-direction:column;gap:2px}.tavern-preset-create-prompt strong{font-size:9.5px}.tavern-preset-create-prompt small{color:#6f7d8d;font-size:7.5px}.tavern-preset-unused-search{display:flex;height:34px;flex:none;align-items:center;gap:6px;margin-top:8px;border:1px solid #d8dfe6;border-radius:7px;padding:0 8px;color:#828c97}.tavern-preset-unused-search input{width:100%;min-width:0;border:0;background:transparent;color:#343c45;font-size:9.5px;outline:none}.tavern-preset-unused-list{min-height:0;overflow:auto;margin-top:6px;border-top:1px solid #edf0f3}.tavern-preset-unused-list>button{display:flex;width:100%;align-items:center;justify-content:space-between;gap:7px;border:0;border-bottom:1px solid #edf0f3;padding:8px 4px;background:#fff;color:#343d47;cursor:pointer;text-align:left}.tavern-preset-unused-list>button:hover{background:#f3f7fc}.tavern-preset-unused-list>button>span{display:flex;min-width:0;flex-direction:column;gap:2px}.tavern-preset-unused-list strong,.tavern-preset-unused-list small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tavern-preset-unused-list strong{font-size:9.5px}.tavern-preset-unused-list small{color:#7d8791;font-size:7.5px}.tavern-preset-unused-list em{flex:none;color:#2d6fc3;font-size:8px;font-style:normal;font-weight:650}
2176
+ .tavern-preset-prompt-list article{grid-template-columns:32px minmax(0,1fr) 30px 22px;cursor:grab}.tavern-preset-prompt-list article:active{cursor:grabbing}.tavern-preset-order-handle{display:flex;height:100%;align-items:center;justify-content:center;gap:2px;color:#87929d;cursor:grab;font:7px/1 ui-monospace,SFMono-Regular,Consolas,monospace;user-select:none}.tavern-preset-order-handle svg{color:#9aa4ae;transform:rotate(90deg)}.tavern-preset-prompt-list article.is-dragging{opacity:.35!important}.tavern-preset-prompt-list article.is-drop-before{box-shadow:inset 0 2px #3278d3}.tavern-preset-prompt-list article.is-drop-after{box-shadow:inset 0 -2px #3278d3}
2177
+ .tavern-preset-secondary-actions{display:flex;min-height:46px;box-sizing:border-box;flex:none;align-items:center;gap:6px;border-bottom:1px solid #e1e5ea;padding:6px 14px;background:#fbfcfd}.tavern-preset-secondary-actions button,.tavern-preset-secondary-actions a{display:inline-flex;height:32px;box-sizing:border-box;align-items:center;justify-content:center;gap:6px;border:1px solid #d2d9e1;border-radius:7px;padding:0 10px;background:#fff;color:#46515d;cursor:pointer;font-size:9px;font-weight:650;text-decoration:none}.tavern-preset-secondary-actions button:hover:not(:disabled),.tavern-preset-secondary-actions a:hover{border-color:#9db8dd;background:#f3f7fd;color:#2366bd}.tavern-preset-secondary-actions button:disabled{cursor:not-allowed;opacity:.42}.tavern-preset-secondary-actions .is-danger{margin-left:auto;border-color:#e2c7ca;color:#a7444d}.tavern-preset-secondary-actions .is-danger:hover:not(:disabled){border-color:#d7aeb3;background:#fff5f5;color:#9f303c}
2178
+ @media(max-width:1050px){.tavern-preset-toolbar{align-items:center;flex-direction:row}.tavern-preset-actions{justify-content:flex-end}.tavern-preset-select-wrap{width:auto}.tavern-preset-binding{grid-template-columns:auto minmax(0,1fr)}}@media(max-width:640px){.tavern-preset-revision{display:none}.tavern-preset-prompts{grid-template-columns:185px minmax(310px,1fr);overflow:auto}.tavern-preset-prompt-editor{min-width:310px}.tavern-preset-select-wrap select{width:100%}}
2179
+ .tavern-preset-title-block{display:flex;width:126px;min-width:0;flex:none;flex-direction:column;gap:2px}.tavern-preset-title-block h2{overflow:hidden;margin:0;color:#20262d;font-size:13px;line-height:1.25;text-overflow:ellipsis;white-space:nowrap}.tavern-preset-title-block span{overflow:hidden;color:#7f8892;font-size:7.5px;text-overflow:ellipsis;white-space:nowrap}.tavern-preset-select-wrap{flex:1}.tavern-preset-select-wrap select{width:min(250px,62%)}
2180
+ @media(max-width:640px){.tavern-preset-title-block{width:108px}.tavern-preset-title-block span{display:none}}
2181
+ .tavern-frontend-overview-copy>span{font-size:10px}.tavern-frontend-overview-copy>strong{font-size:17px}.tavern-frontend-overview-copy>small{font-size:10px}.tavern-frontend-overview-facts strong{font-size:15px}.tavern-frontend-overview-facts span{font-size:9px}.tavern-frontend-setting-section>summary strong{font-size:12px}.tavern-frontend-setting-section>summary span{font-size:9px}.tavern-frontend-facts{font-size:10px}.tavern-frontend-facts dt,.tavern-frontend-facts dd{padding:9px 4px}.tavern-frontend-permission strong{font-size:11px}.tavern-frontend-permission small{font-size:9.5px}.tavern-frontend-note,.tavern-frontend-empty,.tavern-frontend-resource p{font-size:10px}.tavern-frontend-metrics span{font-size:9px}.tavern-frontend-metrics strong{font-size:15px}.tavern-frontend-setting-body h4{font-size:9px}.tavern-frontend-script>summary strong{font-size:10px}.tavern-frontend-script>summary span{font-size:9px}.tavern-frontend-state>summary{font-size:10px}
2182
+ `;
2183
+ const CONTROL_STYLE = `
2184
+ .dsh-roleplay-control{width:100%;box-sizing:border-box;list-style:none;border:1px solid var(--dsw-alias-border-l2,#e1e5ea);border-radius:12px;background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-primary,#20242a);transition:border-color .16s,background .16s}.dsh-roleplay-control:hover{border-color:var(--dsw-alias-label-dimmed,#a8afb8)}.dsh-roleplay-control.is-open{border-color:var(--dsw-alias-label-dimmed,#a8afb8);background:var(--dsw-alias-bg-layer-2,#fafbfc)}
2185
+ .dsh-roleplay-control-header{display:flex;width:100%;box-sizing:border-box;appearance:none;align-items:center;gap:12px;border:0;border-radius:12px;padding:14px 16px;background:transparent;color:inherit;cursor:pointer;font:inherit;text-align:left}.dsh-roleplay-control-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2877eb);outline-offset:-2px}.dsh-roleplay-control-heading{display:flex;min-width:0;flex:1;flex-direction:column;gap:4px}.dsh-roleplay-control-heading strong{color:var(--dsw-alias-label-primary,#20242a);font-size:15px;font-weight:600;line-height:1.4}.dsh-roleplay-control-heading span{color:var(--dsw-alias-label-tertiary,#838b95);font-size:13px;line-height:1.5}.dsh-roleplay-control-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#838b95);transition:transform .16s}.dsh-roleplay-control.is-open .dsh-roleplay-control-chevron{transform:rotate(180deg)}
2186
+ .dsh-roleplay-control-body{margin:0 16px;border-top:1px solid var(--dsw-alias-border-l2,#e1e5ea);padding:14px 0 12px}.dsh-roleplay-control-row{display:flex;align-items:flex-start;justify-content:space-between;gap:24px}.dsh-roleplay-control-copy{min-width:0}.dsh-roleplay-control-copy strong{font-size:13px;line-height:1.5}.dsh-roleplay-control-copy p{max-width:520px;margin:5px 0 0;color:var(--dsw-alias-label-secondary,#68717c);font-size:12px;line-height:1.6}.dsh-roleplay-control-toggle{display:flex;flex:none;align-items:center;gap:9px;margin-top:1px;font-size:13px;font-weight:650;cursor:pointer}.dsh-roleplay-control-toggle input{width:18px;height:18px;accent-color:var(--dsw-alias-brand-primary,#2877eb)}.dsh-roleplay-control-note{display:block;margin-top:14px;border-top:1px solid var(--dsw-alias-border-l2,#e1e5ea);padding-top:12px;color:var(--dsw-alias-label-tertiary,#838b95);font-size:12px;line-height:1.55}.dsh-roleplay-control [role=alert]{margin:10px 0 0;color:var(--dsw-alias-label-error,#b43e49);font-size:12px}
2187
+ `;
2188
+ module.exports.inject = ["slots", "sessions"];
2189
+ module.exports.apply = (ctx) => {
2190
+ const controlDisposers = [];
2191
+ let runtimeDispose = null;
2192
+ let lastRolePlaySessionId = null;
2193
+ let sessionRecovery = null;
2194
+ const currentRolePlaySessionId = () => {
2195
+ const current = ctx.sessions.list.getSnapshot().current;
2196
+ if (current === undefined)
2197
+ return null;
2198
+ if (state.activeSessionId === current)
2199
+ return current;
2200
+ return state.cards.some((card) => card.sessions.some((save) => save.sessionId === current)) ? current : null;
2201
+ };
2202
+ const recoverCurrentRolePlaySession = () => {
2203
+ const sessionId = currentRolePlaySessionId() ?? lastRolePlaySessionId;
2204
+ if (sessionId === null)
2205
+ return null;
2206
+ if (sessionRecovery !== null)
2207
+ return sessionRecovery;
2208
+ sessionRecovery = recoverRestoredClientSession(ctx.sessions, {
2209
+ sessionId,
2210
+ reload: () => window.location.reload(),
2211
+ }).then((result) => {
2212
+ if (result === "ready")
2213
+ lastRolePlaySessionId = null;
2214
+ return result;
2215
+ }).finally(() => { sessionRecovery = null; });
2216
+ return sessionRecovery;
2217
+ };
2218
+ const startRuntime = () => {
2219
+ if (runtimeDispose !== null)
2220
+ return;
2221
+ runtimeContext = ctx;
2222
+ void refreshHostCards()
2223
+ .then(() => recoverCurrentRolePlaySession())
2224
+ .catch((error) => patchState({ startError: error instanceof Error ? error.message : "无法读取已保存的酒馆卡" }));
2225
+ const disposers = [];
2226
+ if (typeof document !== "undefined") {
2227
+ const style = document.createElement("style");
2228
+ style.dataset.plugin = "dsh-roleplay-runtime";
2229
+ style.textContent = `${STYLE}\n${FRONTEND_CALL_STYLE}`;
2230
+ document.head.appendChild(style);
2231
+ disposers.push(() => style.remove());
2232
+ }
2233
+ disposers.push(ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({ name: "sidebar.footer.action", id: "dsh-roleplay-integration", order: 15 }, SidebarIntegration)));
2234
+ disposers.push(ctx.slots.inject("conversation.view", () => ctx.slots.register({ name: "conversation.view", id: "tavern", order: 30, label: () => "酒馆" }, TavernContextView)));
2235
+ disposers.push(ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({ name: "conversation.composer.dock", id: "dsh-roleplay-native-message-adapter", order: 90 }, TavernNativeMessageAdapter)));
2236
+ disposers.push(ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({ name: "conversation.input.dock", id: "dsh-roleplay-trajectory-surface-adapter", order: 19 }, TavernTrajectorySurfaceAdapter)));
2237
+ disposers.push(ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({ name: "conversation.input.dock", id: "dsh-roleplay-opening-switcher", order: 20 }, TavernOpeningSwitcher)));
2238
+ runtimeDispose = () => {
2239
+ const failures = [];
2240
+ lastRolePlaySessionId = currentRolePlaySessionId() ?? lastRolePlaySessionId;
2241
+ for (const dispose of disposers.reverse()) {
2242
+ try {
2243
+ dispose();
2244
+ }
2245
+ catch (error) {
2246
+ failures.push(error);
2247
+ }
2248
+ }
2249
+ listeners.clear();
2250
+ state = initialState();
2251
+ runtimeContext = null;
2252
+ runtimeDispose = null;
2253
+ if (failures.length === 1)
2254
+ throw failures[0];
2255
+ if (failures.length > 1)
2256
+ throw new AggregateError(failures, "RolePlay 客户端运行时卸载失败");
2257
+ };
2258
+ };
2259
+ const syncRuntime = (enabled) => {
2260
+ if (enabled)
2261
+ startRuntime();
2262
+ else
2263
+ runtimeDispose?.();
2264
+ };
2265
+ const RolePlaySettingsCard = () => {
2266
+ const [open, setOpen] = React.useState(false);
2267
+ const [enabled, setEnabled] = React.useState(null);
2268
+ const [busy, setBusy] = React.useState(false);
2269
+ const [error, setError] = React.useState("");
2270
+ const read = async () => {
2271
+ const response = await fetch("/dsh-re3-rp/control", { cache: "no-store" });
2272
+ const body = await response.json();
2273
+ if (!response.ok || body.ok !== true)
2274
+ throw new Error(body.error || "无法读取插件状态");
2275
+ setEnabled(body.enabled === true);
2276
+ syncRuntime(body.enabled === true);
2277
+ };
2278
+ React.useEffect(() => { void read().catch((reason) => setError(reason instanceof Error ? reason.message : String(reason))); }, []);
2279
+ const update = async (next) => {
2280
+ setBusy(true);
2281
+ setError("");
2282
+ try {
2283
+ const response = await fetch("/dsh-re3-rp/control", {
2284
+ method: "POST",
2285
+ headers: { "content-type": "application/json; charset=utf-8" },
2286
+ body: JSON.stringify({ enabled: next }),
2287
+ });
2288
+ const body = await response.json();
2289
+ if (!response.ok || body.ok !== true)
2290
+ throw new Error(body.error || "无法切换插件状态");
2291
+ setEnabled(body.enabled === true);
2292
+ syncRuntime(body.enabled === true);
2293
+ }
2294
+ catch (reason) {
2295
+ setError(reason instanceof Error ? reason.message : String(reason));
2296
+ }
2297
+ finally {
2298
+ setBusy(false);
2299
+ }
2300
+ };
2301
+ return h("li", { className: `dsh-roleplay-control${open ? " is-open" : ""}` }, h("button", { type: "button", className: "dsh-roleplay-control-header", "aria-expanded": open, onClick: () => setOpen(!open) }, h("span", { className: "dsh-roleplay-control-heading" }, h("strong", null, "DSH RolePlay"), h("span", null, "管理酒馆体验与运行时扩展。")), h(IconChevronDownOutline14, { className: "dsh-roleplay-control-chevron", size: 14 })), open ? h("div", { className: "dsh-roleplay-control-body" }, h("div", { className: "dsh-roleplay-control-row" }, h("div", { className: "dsh-roleplay-control-copy" }, h("strong", null, "启用 DSH RolePlay"), h("p", null, "关闭后会卸载酒馆侧栏、对话适配、输入区扩展、Host 路由和运行时资源。角色卡与 Session 数据会保留,重新开启即可恢复。")), h("label", { className: "dsh-roleplay-control-toggle" }, h("input", { type: "checkbox", checked: enabled === true, disabled: enabled === null || busy, onChange: (event) => { void update(event.currentTarget.checked); } }), busy ? "切换中…" : enabled === true ? "已开启" : "已关闭")), error.length > 0 ? h("div", { role: "alert" }, error) : null, h("small", { className: "dsh-roleplay-control-note" }, "这里的关闭是可逆的运行时卸载;如需从磁盘删除 npm 包,请使用 DSH 的插件移除命令。")) : null);
2302
+ };
2303
+ if (typeof document !== "undefined") {
2304
+ const style = document.createElement("style");
2305
+ style.dataset.plugin = "dsh-roleplay-control";
2306
+ style.textContent = CONTROL_STYLE;
2307
+ document.head.appendChild(style);
2308
+ controlDisposers.push(() => style.remove());
2309
+ }
2310
+ controlDisposers.push(ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({ name: "settings.plugin.item", key: "dsh-roleplay" }, RolePlaySettingsCard)));
2311
+ void fetch("/dsh-re3-rp/control", { cache: "no-store" })
2312
+ .then(async (response) => ({ response, body: await response.json() }))
2313
+ .then(({ response, body }) => {
2314
+ if (!response.ok || body.ok !== true)
2315
+ throw new Error(body.error || "无法读取插件状态");
2316
+ syncRuntime(body.enabled === true);
2317
+ })
2318
+ .catch((error) => patchState({ startError: error instanceof Error ? error.message : "无法读取插件状态" }));
2319
+ return () => {
2320
+ runtimeDispose?.();
2321
+ for (const dispose of controlDisposers.reverse())
2322
+ dispose();
2323
+ };
2324
+ };
2325
+ return module.exports;
2326
+ }
2327
+ });