@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.
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Owns the reversible RolePlay runtime lifecycle behind one small interface.
3
+ * Persistent roleplay data is deliberately outside this module: disabling the
4
+ * runtime detaches behavior and UI without deleting cards or sessions.
5
+ */
6
+ export class RolePlayLifecycle {
7
+ options;
8
+ runtimeDispose = null;
9
+ enabled = false;
10
+ phase = "disabled";
11
+ error = null;
12
+ queue = Promise.resolve();
13
+ disposed = false;
14
+ constructor(options) {
15
+ this.options = options;
16
+ }
17
+ async initialize() {
18
+ const enabled = await this.options.loadEnabled();
19
+ if (enabled)
20
+ await this.activate(false);
21
+ }
22
+ snapshot() {
23
+ return { enabled: this.enabled, phase: this.phase, error: this.error };
24
+ }
25
+ setEnabled(enabled) {
26
+ const operation = this.queue.then(async () => {
27
+ if (this.disposed)
28
+ throw new Error("RolePlay 生命周期已经释放");
29
+ if (enabled === this.enabled && this.phase !== "failed")
30
+ return;
31
+ if (enabled)
32
+ await this.activate(true);
33
+ else
34
+ await this.deactivate(true);
35
+ });
36
+ this.queue = operation.then(() => undefined, () => undefined);
37
+ return operation.then(() => this.snapshot());
38
+ }
39
+ async dispose() {
40
+ this.disposed = true;
41
+ await this.queue;
42
+ await this.deactivate(false);
43
+ }
44
+ async activate(persist) {
45
+ this.phase = "transitioning";
46
+ this.error = null;
47
+ let dispose = null;
48
+ try {
49
+ dispose = await this.options.startRuntime();
50
+ if (typeof dispose !== "function")
51
+ throw new Error("RolePlay 运行时没有返回卸载函数");
52
+ if (persist)
53
+ await this.options.saveEnabled(true);
54
+ this.runtimeDispose = dispose;
55
+ this.enabled = true;
56
+ this.phase = "active";
57
+ }
58
+ catch (error) {
59
+ if (dispose !== null)
60
+ await dispose();
61
+ this.enabled = false;
62
+ this.phase = "failed";
63
+ this.error = error instanceof Error ? error.message : String(error);
64
+ throw error;
65
+ }
66
+ }
67
+ async deactivate(persist) {
68
+ if (this.runtimeDispose === null) {
69
+ this.enabled = false;
70
+ this.phase = "disabled";
71
+ this.error = null;
72
+ if (persist)
73
+ await this.options.saveEnabled(false);
74
+ return;
75
+ }
76
+ this.phase = "transitioning";
77
+ this.error = null;
78
+ const dispose = this.runtimeDispose;
79
+ try {
80
+ await dispose();
81
+ this.runtimeDispose = null;
82
+ this.enabled = false;
83
+ if (persist)
84
+ await this.options.saveEnabled(false);
85
+ this.phase = "disabled";
86
+ }
87
+ catch (error) {
88
+ this.phase = "failed";
89
+ this.error = error instanceof Error ? error.message : String(error);
90
+ throw error;
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,70 @@
1
+ import { applyVariableUpdate } from "./variable-runtime.js";
2
+ export function supportsExtraModelParsing(updateFormats) {
3
+ return updateFormats.some((format) => typeof format === "string" && format.trim().length > 0);
4
+ }
5
+ function record(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
7
+ }
8
+ function shortText(value, fallback) {
9
+ return typeof value === "string" && value.trim().length > 0 && value.trim().length <= 200 ? value.trim() : fallback;
10
+ }
11
+ export function defaultMvuSessionSettings(defaults) {
12
+ return {
13
+ updateMethod: defaults.supportsExtraModel ? "额外模型解析" : "随 AI 输出",
14
+ automaticRequest: true,
15
+ extraModel: {
16
+ source: "与插头相同",
17
+ provider: defaults.provider,
18
+ model: defaults.model,
19
+ maxTokens: 4096,
20
+ },
21
+ };
22
+ }
23
+ export function normalizeMvuSessionSettings(value, defaults) {
24
+ const base = defaultMvuSessionSettings(defaults);
25
+ const input = record(value);
26
+ const extra = record(input.extraModel);
27
+ const updateMethod = input.updateMethod === "随 AI 输出" || input.updateMethod === "额外模型解析"
28
+ ? input.updateMethod
29
+ : base.updateMethod;
30
+ if (updateMethod === "额外模型解析" && !defaults.supportsExtraModel) {
31
+ throw new Error("当前角色卡未声明可执行的额外模型解析条目");
32
+ }
33
+ const source = extra.source === "自定义" ? "自定义" : "与插头相同";
34
+ const maxTokens = typeof extra.maxTokens === "number" && Number.isInteger(extra.maxTokens) && extra.maxTokens >= 256 && extra.maxTokens <= 32_768
35
+ ? extra.maxTokens
36
+ : base.extraModel.maxTokens;
37
+ return {
38
+ updateMethod,
39
+ automaticRequest: typeof input.automaticRequest === "boolean" ? input.automaticRequest : base.automaticRequest,
40
+ extraModel: {
41
+ source,
42
+ provider: shortText(extra.provider, base.extraModel.provider),
43
+ model: shortText(extra.model, base.extraModel.model),
44
+ maxTokens,
45
+ },
46
+ };
47
+ }
48
+ export function resolveMvuExtraModel(settings, current) {
49
+ return settings.extraModel.source === "自定义"
50
+ ? { provider: settings.extraModel.provider, model: settings.extraModel.model, maxTokens: settings.extraModel.maxTokens }
51
+ : { provider: current.provider, model: current.model, maxTokens: settings.extraModel.maxTokens };
52
+ }
53
+ export function replayMvuReplies(initialState, replies) {
54
+ let state = structuredClone(initialState);
55
+ const events = [];
56
+ let committedReplies = 0;
57
+ let failedReplies = 0;
58
+ replies.forEach((reply, replayIndex) => {
59
+ const result = applyVariableUpdate(state, reply);
60
+ events.push(...result.events.map((event) => ({ ...event, replayIndex })));
61
+ if (result.status === "committed") {
62
+ state = result.state;
63
+ committedReplies += 1;
64
+ }
65
+ else if (result.status === "failed") {
66
+ failedReplies += 1;
67
+ }
68
+ });
69
+ return { state, events, committedReplies, failedReplies };
70
+ }
@@ -0,0 +1,60 @@
1
+ export const PERSONA_AVATARS = ["default", "traveler", "northern-ranger", "jianghu-wanderer"];
2
+ export function personaBindingKey(scope, targetId = "") {
3
+ if (scope === "global")
4
+ return "global:default";
5
+ const normalizedTarget = targetId.trim();
6
+ if (normalizedTarget.length === 0)
7
+ throw new Error(scope === "card" ? "卡片 Persona 绑定缺少 revision" : "Session Persona 绑定缺少 Session ID");
8
+ return `${scope}:${normalizedTarget}`;
9
+ }
10
+ export function normalizePersonaAvatar(value, fallback = "default") {
11
+ return typeof value === "string" && PERSONA_AVATARS.includes(value) ? value : fallback;
12
+ }
13
+ export function validatePersonaDraft(input, fallbackAvatar = "default") {
14
+ const displayName = typeof input.displayName === "string" ? input.displayName.trim() : "";
15
+ const content = typeof input.content === "string" ? input.content.trim() : "";
16
+ if (displayName.length === 0)
17
+ throw new Error("Persona 名称不能为空");
18
+ if (displayName.length > 64)
19
+ throw new Error("Persona 名称不能超过 64 个字符");
20
+ if (content.length > 12_000)
21
+ throw new Error("Persona 描述不能超过 12000 个字符");
22
+ return { displayName, content, avatar: normalizePersonaAvatar(input.avatar, fallbackAvatar) };
23
+ }
24
+ export function personaBindingKeysToClearForSelection(scope, context) {
25
+ if (scope === "session")
26
+ return [];
27
+ const keys = [];
28
+ if ((context.sessionId ?? "").length > 0)
29
+ keys.push(personaBindingKey("session", context.sessionId));
30
+ if (scope === "global" && (context.revisionId ?? "").length > 0)
31
+ keys.push(personaBindingKey("card", context.revisionId));
32
+ return keys;
33
+ }
34
+ export function resolvePersona(personas, bindings, context) {
35
+ const keys = [
36
+ context.sessionId === undefined || context.sessionId.length === 0 ? null : personaBindingKey("session", context.sessionId),
37
+ context.revisionId === undefined || context.revisionId.length === 0 ? null : personaBindingKey("card", context.revisionId),
38
+ personaBindingKey("global"),
39
+ ];
40
+ for (const key of keys) {
41
+ if (key === null)
42
+ continue;
43
+ const binding = bindings.get(key);
44
+ if (binding === undefined)
45
+ continue;
46
+ const persona = personas.get(binding.personaId);
47
+ if (persona !== undefined)
48
+ return { persona, binding };
49
+ }
50
+ return null;
51
+ }
52
+ export function renderPersonaPrompt(displayName, content) {
53
+ const name = displayName.trim();
54
+ const detail = content.trim();
55
+ const lines = name.length === 0 ? [] : [`玩家在当前故事中的名字是 ${name}。`];
56
+ if (detail.length > 0) {
57
+ lines.push("玩家主动启用了以下兼容人设。若酒馆卡已经明确规定玩家身份,以卡片设定为准,不要用人设覆盖卡内约束:", detail);
58
+ }
59
+ return lines.join("\n\n");
60
+ }
@@ -0,0 +1,222 @@
1
+ const MARKER_BY_IDENTIFIER = Object.freeze({
2
+ main: "main-prompt",
3
+ worldInfoBefore: "world-info-before",
4
+ personaDescription: "persona-description",
5
+ charDescription: "character-description",
6
+ charPersonality: "character-personality",
7
+ scenario: "scenario",
8
+ worldInfoAfter: "world-info-after",
9
+ dialogueExamples: "example-messages",
10
+ authorsNote: "authors-note",
11
+ chatHistory: "chat-history",
12
+ jailbreak: "post-history-instructions",
13
+ });
14
+ const IDENTIFIER_BY_MARKER = new Map(Object.entries(MARKER_BY_IDENTIFIER).map(([identifier, marker]) => [marker, identifier]));
15
+ const VALID_MARKERS = new Set(Object.values(MARKER_BY_IDENTIFIER));
16
+ const PINNED_PROMPTS = Object.freeze([
17
+ { identifier: "main", name: "Main Prompt", role: "system", content: "Write {{char}}'s next reply in a fictional chat between {{char}} and {{user}}.", marker: "main-prompt", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
18
+ { identifier: "worldInfoBefore", name: "World Info (before)", role: "system", content: "", marker: "world-info-before", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
19
+ { identifier: "personaDescription", name: "Persona Description", role: "system", content: "", marker: "persona-description", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
20
+ { identifier: "charDescription", name: "Char Description", role: "system", content: "", marker: "character-description", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
21
+ { identifier: "charPersonality", name: "Char Personality", role: "system", content: "", marker: "character-personality", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
22
+ { identifier: "scenario", name: "Scenario", role: "system", content: "", marker: "scenario", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
23
+ { identifier: "enhanceDefinitions", name: "Enhance Definitions", role: "system", content: "If you have more knowledge of {{char}}, add to the character's lore and personality to enhance them but keep the Character Sheet's definitions absolute.", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
24
+ { identifier: "nsfw", name: "Auxiliary Prompt", role: "system", content: "", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
25
+ { identifier: "worldInfoAfter", name: "World Info (after)", role: "system", content: "", marker: "world-info-after", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
26
+ { identifier: "dialogueExamples", name: "Chat Examples", role: "system", content: "", marker: "example-messages", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
27
+ { identifier: "chatHistory", name: "Chat History", role: "system", content: "", marker: "chat-history", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
28
+ { identifier: "jailbreak", name: "Post-History Instructions", role: "system", content: "", marker: "post-history-instructions", systemPrompt: true, injectionPosition: "relative", injectionDepth: 4, injectionOrder: 100 },
29
+ ]);
30
+ const DEFAULT_ORDER = Object.freeze([
31
+ { identifier: "main", enabled: true },
32
+ { identifier: "worldInfoBefore", enabled: true },
33
+ { identifier: "personaDescription", enabled: true },
34
+ { identifier: "charDescription", enabled: true },
35
+ { identifier: "charPersonality", enabled: true },
36
+ { identifier: "scenario", enabled: true },
37
+ { identifier: "enhanceDefinitions", enabled: false },
38
+ { identifier: "nsfw", enabled: true },
39
+ { identifier: "worldInfoAfter", enabled: true },
40
+ { identifier: "dialogueExamples", enabled: true },
41
+ { identifier: "chatHistory", enabled: true },
42
+ { identifier: "jailbreak", enabled: true },
43
+ ]);
44
+ const TOP_LEVEL_KEYS = new Set([
45
+ "temperature", "frequency_penalty", "presence_penalty", "top_p", "openai_max_context", "openai_max_tokens",
46
+ "max_context_unlocked", "wi_format", "worldInfoFormat", "stream_openai", "prompts", "prompt_order", "promptOrder", "settings", "extra", "name", "id", "source", "revision", "createdAt", "updatedAt",
47
+ ]);
48
+ const PROMPT_KEYS = new Set(["identifier", "name", "role", "content", "marker", "system_prompt", "systemPrompt", "injection_position", "injectionPosition", "injection_depth", "injectionDepth", "injection_order", "injectionOrder", "position", "extra"]);
49
+ function record(value) {
50
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
51
+ }
52
+ function finite(value, fallback) {
53
+ const candidate = typeof value === "number" ? value : Number(value);
54
+ return Number.isFinite(candidate) ? candidate : fallback;
55
+ }
56
+ function integer(value, fallback, minimum, maximum = Number.MAX_SAFE_INTEGER) {
57
+ return Math.min(maximum, Math.max(minimum, Math.round(finite(value, fallback))));
58
+ }
59
+ function optionalInteger(value, minimum, maximum = Number.MAX_SAFE_INTEGER) {
60
+ if (value === undefined || value === null || value === "")
61
+ return null;
62
+ const candidate = typeof value === "number" ? value : Number(value);
63
+ return Number.isFinite(candidate) ? Math.min(maximum, Math.max(minimum, Math.round(candidate))) : null;
64
+ }
65
+ function role(value) {
66
+ return value === "user" ? "user" : value === "assistant" || value === "model" ? "assistant" : "system";
67
+ }
68
+ function extraFields(value, known) {
69
+ return Object.fromEntries(Object.entries(value).filter(([key]) => !known.has(key)));
70
+ }
71
+ function promptFrom(value, fallback) {
72
+ const input = record(value);
73
+ const identifier = typeof input.identifier === "string" && input.identifier.trim().length > 0 ? input.identifier.trim() : fallback?.identifier;
74
+ if (identifier === undefined)
75
+ return;
76
+ const explicitMarker = typeof input.marker === "string" && VALID_MARKERS.has(input.marker)
77
+ ? input.marker
78
+ : input.marker === true ? MARKER_BY_IDENTIFIER[identifier] : undefined;
79
+ const marker = fallback?.marker ?? explicitMarker;
80
+ return {
81
+ identifier,
82
+ name: typeof input.name === "string" && input.name.trim().length > 0 ? input.name.trim() : fallback?.name ?? identifier,
83
+ role: role(input.role ?? fallback?.role),
84
+ content: typeof input.content === "string" ? input.content : fallback?.content ?? "",
85
+ ...(marker === undefined ? {} : { marker }),
86
+ systemPrompt: typeof input.system_prompt === "boolean" ? input.system_prompt : typeof input.systemPrompt === "boolean" ? input.systemPrompt : fallback?.systemPrompt ?? false,
87
+ injectionPosition: input.injection_position === 1 || input.injectionPosition === "in-chat" || input.position === "in_chat" || input.position === "in-chat" ? "in-chat" : fallback?.injectionPosition ?? "relative",
88
+ injectionDepth: integer(input.injection_depth ?? input.injectionDepth, fallback?.injectionDepth ?? 4, 0, 10_000),
89
+ injectionOrder: integer(input.injection_order ?? input.injectionOrder, fallback?.injectionOrder ?? 100, -1_000_000, 1_000_000),
90
+ extra: { ...extraFields(input, PROMPT_KEYS), ...record(input.extra) },
91
+ };
92
+ }
93
+ function normalizePromptLibrary(value) {
94
+ const candidates = Array.isArray(value) ? value : [];
95
+ const byId = new Map();
96
+ for (const candidate of candidates) {
97
+ const parsed = promptFrom(candidate);
98
+ if (parsed !== undefined && !byId.has(parsed.identifier))
99
+ byId.set(parsed.identifier, parsed);
100
+ }
101
+ for (const pinned of PINNED_PROMPTS) {
102
+ const existing = byId.get(pinned.identifier);
103
+ byId.set(pinned.identifier, existing === undefined ? { ...pinned, extra: {} } : { ...existing, marker: pinned.marker });
104
+ }
105
+ return [...byId.values()];
106
+ }
107
+ function normalizePromptOrder(value, prompts) {
108
+ const groups = Array.isArray(value) ? value.map(record) : [];
109
+ const personaGroup = groups.find((group) => Number(group.character_id) === 100001);
110
+ const selected = personaGroup ?? groups[0];
111
+ const rawOrder = Array.isArray(selected?.order) ? selected.order : Array.isArray(value) && value.every((item) => "identifier" in record(item)) ? value : [];
112
+ const promptIds = new Set(prompts.map((prompt) => prompt.identifier));
113
+ const seen = new Set();
114
+ const order = rawOrder.flatMap((item) => {
115
+ const candidate = record(item);
116
+ const identifier = typeof candidate.identifier === "string" ? candidate.identifier.trim() : "";
117
+ if (identifier.length === 0 || seen.has(identifier) || !promptIds.has(identifier))
118
+ return [];
119
+ seen.add(identifier);
120
+ return [{ identifier, enabled: candidate.enabled !== false }];
121
+ });
122
+ if (order.length === 0) {
123
+ for (const item of DEFAULT_ORDER) {
124
+ if (promptIds.has(item.identifier)) {
125
+ order.push({ ...item });
126
+ seen.add(item.identifier);
127
+ }
128
+ }
129
+ }
130
+ return order;
131
+ }
132
+ function iso(value, fallback) {
133
+ if (typeof value !== "string")
134
+ return fallback;
135
+ const milliseconds = Date.parse(value);
136
+ return Number.isFinite(milliseconds) ? new Date(milliseconds).toISOString() : fallback;
137
+ }
138
+ export function normalizeTavernPreset(value, options = {}) {
139
+ const input = record(value);
140
+ const now = options.now ?? new Date().toISOString();
141
+ const prompts = normalizePromptLibrary(input.prompts);
142
+ const name = options.name?.trim() || (typeof input.name === "string" ? input.name.trim() : "") || "Imported preset";
143
+ return {
144
+ id: options.id?.trim() || (typeof input.id === "string" ? input.id.trim() : "") || crypto.randomUUID(),
145
+ name,
146
+ source: options.source ?? (input.source === "builtin" || input.source === "created" || input.source === "imported" ? input.source : "imported"),
147
+ revision: integer(input.revision, 1, 1),
148
+ createdAt: iso(input.createdAt, now),
149
+ updatedAt: iso(input.updatedAt, now),
150
+ worldInfoFormat: typeof input.wi_format === "string" ? input.wi_format : typeof input.worldInfoFormat === "string" ? input.worldInfoFormat : "{0}",
151
+ settings: {
152
+ contextTokens: optionalInteger(input.openai_max_context ?? record(input.settings).contextTokens, 256),
153
+ maxReplyTokens: optionalInteger(input.openai_max_tokens ?? record(input.settings).maxReplyTokens, 1),
154
+ stream: typeof input.stream_openai === "boolean" ? input.stream_openai : typeof record(input.settings).stream === "boolean" ? record(input.settings).stream : true,
155
+ temperature: Math.min(5, Math.max(0, finite(input.temperature ?? record(input.settings).temperature, 1))),
156
+ topP: Math.min(1, Math.max(0, finite(input.top_p ?? record(input.settings).topP, 1))),
157
+ frequencyPenalty: Math.min(2, Math.max(-2, finite(input.frequency_penalty ?? record(input.settings).frequencyPenalty, 0))),
158
+ presencePenalty: Math.min(2, Math.max(-2, finite(input.presence_penalty ?? record(input.settings).presencePenalty, 0))),
159
+ maxContextUnlocked: input.max_context_unlocked === true || record(input.settings).maxContextUnlocked === true,
160
+ },
161
+ prompts,
162
+ promptOrder: normalizePromptOrder(input.prompt_order ?? input.promptOrder, prompts),
163
+ extra: { ...extraFields(input, TOP_LEVEL_KEYS), ...record(input.extra) },
164
+ };
165
+ }
166
+ export function createDefaultTavernPreset(now = "2026-08-26T00:00:00.000Z") {
167
+ return normalizeTavernPreset({
168
+ name: "DSH RolePlay Default",
169
+ temperature: 1,
170
+ frequency_penalty: 0,
171
+ presence_penalty: 0,
172
+ top_p: 1,
173
+ max_context_unlocked: false,
174
+ wi_format: "{0}",
175
+ stream_openai: true,
176
+ prompts: PINNED_PROMPTS.map((prompt) => ({
177
+ identifier: prompt.identifier,
178
+ name: prompt.name,
179
+ role: prompt.role,
180
+ content: prompt.content,
181
+ system_prompt: prompt.systemPrompt,
182
+ ...(prompt.marker === undefined ? {} : { marker: true }),
183
+ })),
184
+ prompt_order: [{ character_id: 100001, order: DEFAULT_ORDER }],
185
+ chat_completion_source: "openai",
186
+ }, { id: "dsh-roleplay-default-1", source: "builtin", now });
187
+ }
188
+ export const DEFAULT_TAVERN_PRESET = createDefaultTavernPreset();
189
+ export function exportSillyTavernPreset(preset) {
190
+ return {
191
+ ...preset.extra,
192
+ chat_completion_source: typeof preset.extra.chat_completion_source === "string" ? preset.extra.chat_completion_source : "openai",
193
+ temperature: preset.settings.temperature,
194
+ frequency_penalty: preset.settings.frequencyPenalty,
195
+ presence_penalty: preset.settings.presencePenalty,
196
+ top_p: preset.settings.topP,
197
+ ...(preset.settings.contextTokens === null ? {} : { openai_max_context: preset.settings.contextTokens }),
198
+ ...(preset.settings.maxReplyTokens === null ? {} : { openai_max_tokens: preset.settings.maxReplyTokens }),
199
+ max_context_unlocked: preset.settings.maxContextUnlocked,
200
+ wi_format: preset.worldInfoFormat,
201
+ stream_openai: preset.settings.stream,
202
+ prompts: preset.prompts.map((prompt) => ({
203
+ ...prompt.extra,
204
+ identifier: prompt.identifier,
205
+ name: prompt.name,
206
+ role: prompt.role,
207
+ content: prompt.content,
208
+ system_prompt: prompt.systemPrompt,
209
+ ...(prompt.marker === undefined ? { marker: false } : { marker: true }),
210
+ injection_position: prompt.injectionPosition === "in-chat" ? 1 : 0,
211
+ injection_depth: prompt.injectionDepth,
212
+ injection_order: prompt.injectionOrder,
213
+ })),
214
+ prompt_order: [100000, 100001].map((character_id) => ({ character_id, order: preset.promptOrder.map((item) => ({ ...item })) })),
215
+ };
216
+ }
217
+ export function isPinnedPrompt(identifier) {
218
+ return MARKER_BY_IDENTIFIER[identifier] !== undefined || identifier === "enhanceDefinitions" || identifier === "nsfw";
219
+ }
220
+ export function promptIdentifierForMarker(marker) {
221
+ return IDENTIFIER_BY_MARKER.get(marker) ?? marker;
222
+ }