@zihanw/pi-forge 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -105
- package/README.zh-CN.md +215 -104
- package/dist/compiler.d.ts +10 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +490 -0
- package/dist/compiler.js.map +1 -0
- package/dist/extension-registry.d.ts +25 -0
- package/dist/extension-registry.d.ts.map +1 -0
- package/dist/extension-registry.js +6 -0
- package/dist/extension-registry.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +364 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +14 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +139 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/loader.d.ts +8 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +345 -0
- package/dist/loader.js.map +1 -0
- package/dist/macro-engine.d.ts +23 -0
- package/dist/macro-engine.d.ts.map +1 -0
- package/dist/macro-engine.js +262 -0
- package/dist/macro-engine.js.map +1 -0
- package/dist/payload-capture.d.ts +15 -0
- package/dist/payload-capture.d.ts.map +1 -0
- package/dist/payload-capture.js +86 -0
- package/dist/payload-capture.js.map +1 -0
- package/dist/payload-command.d.ts +10 -0
- package/dist/payload-command.d.ts.map +1 -0
- package/dist/payload-command.js +112 -0
- package/dist/payload-command.js.map +1 -0
- package/dist/policy.d.ts +6 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +38 -0
- package/dist/policy.js.map +1 -0
- package/dist/preset-command.d.ts +14 -0
- package/dist/preset-command.d.ts.map +1 -0
- package/dist/preset-command.js +223 -0
- package/dist/preset-command.js.map +1 -0
- package/dist/preview.d.ts +12 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +179 -0
- package/dist/preview.js.map +1 -0
- package/dist/regex.d.ts +7 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +432 -0
- package/dist/regex.js.map +1 -0
- package/dist/render-helpers.d.ts +44 -0
- package/dist/render-helpers.d.ts.map +1 -0
- package/dist/render-helpers.js +128 -0
- package/dist/render-helpers.js.map +1 -0
- package/dist/runtime-state.d.ts +24 -0
- package/dist/runtime-state.d.ts.map +1 -0
- package/dist/runtime-state.js +13 -0
- package/dist/runtime-state.js.map +1 -0
- package/dist/sillytavern-importer/items.d.ts +3 -0
- package/dist/sillytavern-importer/items.d.ts.map +1 -0
- package/dist/sillytavern-importer/items.js +88 -0
- package/dist/sillytavern-importer/items.js.map +1 -0
- package/dist/sillytavern-importer/macros.d.ts +15 -0
- package/dist/sillytavern-importer/macros.d.ts.map +1 -0
- package/dist/sillytavern-importer/macros.js +141 -0
- package/dist/sillytavern-importer/macros.js.map +1 -0
- package/dist/sillytavern-importer/prompt-order.d.ts +6 -0
- package/dist/sillytavern-importer/prompt-order.d.ts.map +1 -0
- package/dist/sillytavern-importer/prompt-order.js +38 -0
- package/dist/sillytavern-importer/prompt-order.js.map +1 -0
- package/dist/sillytavern-importer/regex.d.ts +3 -0
- package/dist/sillytavern-importer/regex.d.ts.map +1 -0
- package/dist/sillytavern-importer/regex.js +275 -0
- package/dist/sillytavern-importer/regex.js.map +1 -0
- package/dist/sillytavern-importer/report.d.ts +21 -0
- package/dist/sillytavern-importer/report.d.ts.map +1 -0
- package/dist/sillytavern-importer/report.js +166 -0
- package/dist/sillytavern-importer/report.js.map +1 -0
- package/dist/sillytavern-importer/types.d.ts +106 -0
- package/dist/sillytavern-importer/types.d.ts.map +1 -0
- package/dist/sillytavern-importer/types.js +2 -0
- package/dist/sillytavern-importer/types.js.map +1 -0
- package/dist/sillytavern-importer.d.ts +5 -0
- package/dist/sillytavern-importer.d.ts.map +1 -0
- package/dist/sillytavern-importer.js +117 -0
- package/dist/sillytavern-importer.js.map +1 -0
- package/dist/slot-renderers.d.ts +25 -0
- package/dist/slot-renderers.d.ts.map +1 -0
- package/dist/slot-renderers.js +334 -0
- package/dist/slot-renderers.js.map +1 -0
- package/dist/stack-migration.d.ts +29 -0
- package/dist/stack-migration.d.ts.map +1 -0
- package/dist/stack-migration.js +126 -0
- package/dist/stack-migration.js.map +1 -0
- package/dist/storage.d.ts +6 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +23 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +173 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/web-editor/index.d.ts +3 -0
- package/dist/web-editor/index.d.ts.map +1 -0
- package/dist/web-editor/index.js +2 -0
- package/dist/web-editor/index.js.map +1 -0
- package/dist/web-editor/page.d.ts +2 -0
- package/dist/web-editor/page.d.ts.map +1 -0
- package/dist/web-editor/page.js +3331 -0
- package/dist/web-editor/page.js.map +1 -0
- package/dist/web-editor/server.d.ts +4 -0
- package/dist/web-editor/server.d.ts.map +1 -0
- package/dist/web-editor/server.js +263 -0
- package/dist/web-editor/server.js.map +1 -0
- package/dist/web-editor/types.d.ts +125 -0
- package/dist/web-editor/types.d.ts.map +1 -0
- package/dist/web-editor/types.js +2 -0
- package/dist/web-editor/types.js.map +1 -0
- package/dist/web-host.d.ts +29 -0
- package/dist/web-host.d.ts.map +1 -0
- package/dist/web-host.js +174 -0
- package/dist/web-host.js.map +1 -0
- package/examples/custom-system-status-extension/README.md +78 -0
- package/examples/custom-system-status-extension/index.ts +176 -0
- package/examples/custom-system-status-extension/prompt-stack.json +39 -0
- package/examples/default-prompt-stack.json +52 -93
- package/examples/image-reader-prompt-stack.json +124 -0
- package/examples/reviewer-prompt-stack.json +115 -0
- package/examples/sillytavern-dm-writer-prompt-stack.json +190 -0
- package/package.json +30 -9
- package/src/compiler.ts +321 -420
- package/src/extension-registry.ts +33 -0
- package/src/index.ts +310 -1110
- package/src/lifecycle.ts +171 -0
- package/src/loader.ts +141 -56
- package/src/macro-engine.ts +358 -0
- package/src/payload-capture.ts +85 -0
- package/src/payload-command.ts +138 -0
- package/src/policy.ts +42 -0
- package/src/preset-command.ts +271 -0
- package/src/preview.ts +226 -0
- package/src/regex.ts +500 -0
- package/src/render-helpers.ts +169 -0
- package/src/runtime-state.ts +36 -0
- package/src/sillytavern-importer/items.ts +98 -0
- package/src/sillytavern-importer/macros.ts +159 -0
- package/src/sillytavern-importer/prompt-order.ts +54 -0
- package/src/sillytavern-importer/regex.ts +270 -0
- package/src/sillytavern-importer/report.ts +202 -0
- package/src/sillytavern-importer/types.ts +120 -0
- package/src/sillytavern-importer.ts +57 -482
- package/src/slot-renderers.ts +414 -0
- package/src/stack-migration.ts +159 -0
- package/src/storage.ts +28 -0
- package/src/types.ts +83 -52
- package/src/web-editor/index.ts +2 -0
- package/src/web-editor/page.ts +3330 -0
- package/src/web-editor/server.ts +294 -0
- package/src/web-editor/types.ts +98 -0
- package/src/web-host.ts +232 -0
- package/src/web-editor.ts +0 -1491
|
@@ -1,149 +1,28 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
interface StPromptOrderEntry {
|
|
27
|
-
character_id: number;
|
|
28
|
-
order?: StOrderItem[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface StOrderItem {
|
|
32
|
-
identifier: string;
|
|
33
|
-
enabled: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface StConversionItem {
|
|
37
|
-
def: StPromptDef;
|
|
38
|
-
orderEnabled: boolean;
|
|
39
|
-
orderIndex: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ── Marker identifiers ─────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
const MARKER_CHAT_HISTORY = "chatHistory";
|
|
45
|
-
const MARKER_SKIP = new Set([
|
|
46
|
-
"worldInfoBefore",
|
|
47
|
-
"worldInfoAfter",
|
|
48
|
-
"charDescription",
|
|
49
|
-
"charPersonality",
|
|
50
|
-
"personaDescription",
|
|
51
|
-
"scenario",
|
|
52
|
-
"dialogueExamples",
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
// ── ST macros that pi-forge handles natively ───────────────────────────────
|
|
56
|
-
|
|
57
|
-
const NATIVE_MACROS = new Set([
|
|
58
|
-
"char", // → static variable
|
|
59
|
-
"user", // → static variable
|
|
60
|
-
"lastusermessage", // → handled by pi-forge runtime
|
|
61
|
-
"cwd",
|
|
62
|
-
"date",
|
|
63
|
-
"time",
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
const MACRO_DISPLAY_NAMES: Record<string, string> = {
|
|
67
|
-
lastusermessage: "lastUserMessage",
|
|
68
|
-
groupnotmuted: "groupNotMuted",
|
|
69
|
-
notchar: "notChar",
|
|
70
|
-
charifnotgroup: "charIfNotGroup",
|
|
71
|
-
mesexamples: "mesExamples",
|
|
72
|
-
mesexamplesraw: "mesExamplesRaw",
|
|
73
|
-
charprompt: "charPrompt",
|
|
74
|
-
charinstruction: "charInstruction",
|
|
75
|
-
charversion: "charVersion",
|
|
76
|
-
charfirstmessage: "charFirstMessage",
|
|
77
|
-
wibefore: "wiBefore",
|
|
78
|
-
wiafter: "wiAfter",
|
|
79
|
-
lorebefore: "loreBefore",
|
|
80
|
-
loreafter: "loreAfter",
|
|
81
|
-
anchorbefore: "anchorBefore",
|
|
82
|
-
anchorafter: "anchorAfter",
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// ── ST macros that need manual migration ───────────────────────────────────
|
|
86
|
-
|
|
87
|
-
const MACRO_NEEDS_MIGRATION: Record<string, string> = {
|
|
88
|
-
"setvar": "pi-forge {{setvar::name::value}} / {{setsessionvar::name::value}} — scoping differs",
|
|
89
|
-
"getvar": "pi-forge {{getvar::name}} — lookup order: turn → session → static",
|
|
90
|
-
"addvar": "use pi-forge {{getvar::name}} + math, or setvar with computed value",
|
|
91
|
-
"getglobalvar": "use pi-forge session variables via {{getsessionvar::name}}",
|
|
92
|
-
"setglobalvar": "use pi-forge {{setsessionvar::name::value}}",
|
|
93
|
-
"random": "use pi-forge conditionals (Priority 2) or precompute",
|
|
94
|
-
"pick": "use pi-forge conditionals (Priority 2) or precompute",
|
|
95
|
-
"roll": "use pi-forge conditionals (Priority 2) or precompute",
|
|
96
|
-
"if": "use pi-forge conditionals (Priority 2)",
|
|
97
|
-
"original": "SillyTavern-specific — no direct equivalent; set as session variable if needed",
|
|
98
|
-
"outlet": "SillyTavern extension outlet — no pi-forge equivalent",
|
|
99
|
-
"group": "SillyTavern group chat — no pi-forge equivalent",
|
|
100
|
-
"groupnotmuted": "SillyTavern group chat — no pi-forge equivalent",
|
|
101
|
-
"notchar": "SillyTavern group chat — no pi-forge equivalent",
|
|
102
|
-
"charifnotgroup": "SillyTavern group chat — no pi-forge equivalent",
|
|
103
|
-
"description": "ST character card field — set as session variable or static variable if needed",
|
|
104
|
-
"personality": "ST character card field — set as session variable or static variable if needed",
|
|
105
|
-
"scenario": "ST character card field — set as session variable or static variable if needed",
|
|
106
|
-
"persona": "ST persona field — set as session variable or static variable if needed",
|
|
107
|
-
"mesexamples": "ST character dialogue examples — add inline or as context file if needed",
|
|
108
|
-
"mesexamplesraw": "ST character dialogue examples — add inline or as context file if needed",
|
|
109
|
-
"charprompt": "ST character prompt override — merge into system prompt blocks if needed",
|
|
110
|
-
"charinstruction": "ST character instruction override — merge into post-history blocks if needed",
|
|
111
|
-
"charversion": "ST character version — set as static variable if needed",
|
|
112
|
-
"charfirstmessage": "ST character first message — add as static variable if needed",
|
|
113
|
-
"system": "ST context template system prompt — handled by pi-forge system prompt replacement",
|
|
114
|
-
"wibefore": "ST world info — no pi-forge equivalent; merge relevant lore into static blocks",
|
|
115
|
-
"wiafter": "ST world info — no pi-forge equivalent; merge relevant lore into static blocks",
|
|
116
|
-
"lorebefore": "ST world info — no pi-forge equivalent; merge relevant lore into static blocks",
|
|
117
|
-
"loreafter": "ST world info — no pi-forge equivalent; merge relevant lore into static blocks",
|
|
118
|
-
"anchorbefore": "ST extension injection point — no pi-forge equivalent",
|
|
119
|
-
"anchorafter": "ST extension injection point — no pi-forge equivalent",
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// ── Macros we can strip entirely (produce no output) ───────────────────────
|
|
123
|
-
|
|
124
|
-
const COMMENT_MACRO_RE = /\{\{\s*\/\/[\s\S]*?\}\}/g;
|
|
125
|
-
const TRIM_MACRO_RE = /\{\{\s*trim\s*\}\}/gi;
|
|
126
|
-
|
|
127
|
-
// ── Public result types ────────────────────────────────────────────────────
|
|
128
|
-
|
|
129
|
-
export interface SillyTavernImportResult {
|
|
130
|
-
stack: PromptStack;
|
|
131
|
-
report: string;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export interface SillyTavernImportError {
|
|
135
|
-
error: string;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export type SillyTavernImportOutcome = SillyTavernImportResult | SillyTavernImportError;
|
|
139
|
-
|
|
140
|
-
export interface SillyTavernConvertOptions {
|
|
141
|
-
sourceName?: string;
|
|
142
|
-
sourcePath?: string;
|
|
143
|
-
characterId?: number;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ── Importer ───────────────────────────────────────────────────────────────
|
|
3
|
+
import { convertPromptItems } from "./sillytavern-importer/items.ts";
|
|
4
|
+
import { detectMacros } from "./sillytavern-importer/macros.ts";
|
|
5
|
+
import {
|
|
6
|
+
buildConversionItems,
|
|
7
|
+
buildPromptMap,
|
|
8
|
+
findMissingIdentifiers,
|
|
9
|
+
selectCharacterEntry,
|
|
10
|
+
} from "./sillytavern-importer/prompt-order.ts";
|
|
11
|
+
import { summarizeRegexScripts } from "./sillytavern-importer/regex.ts";
|
|
12
|
+
import { buildSillyTavernImportReport } from "./sillytavern-importer/report.ts";
|
|
13
|
+
import type {
|
|
14
|
+
SillyTavernConvertOptions,
|
|
15
|
+
SillyTavernImportOutcome,
|
|
16
|
+
StPreset,
|
|
17
|
+
} from "./sillytavern-importer/types.ts";
|
|
18
|
+
import type { PromptStack } from "./types.ts";
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
SillyTavernConvertOptions,
|
|
22
|
+
SillyTavernImportError,
|
|
23
|
+
SillyTavernImportOutcome,
|
|
24
|
+
SillyTavernImportResult,
|
|
25
|
+
} from "./sillytavern-importer/types.ts";
|
|
147
26
|
|
|
148
27
|
export function importSillyTavernPreset(
|
|
149
28
|
filePath: string,
|
|
@@ -181,12 +60,12 @@ export function convertSillyTavernPreset(
|
|
|
181
60
|
const sourcePath = options.sourcePath ?? sourceName;
|
|
182
61
|
const allPrompts = preset.prompts ?? [];
|
|
183
62
|
const promptOrder = preset.prompt_order ?? [];
|
|
63
|
+
const regexScripts = summarizeRegexScripts(preset);
|
|
184
64
|
|
|
185
65
|
if (allPrompts.length === 0) {
|
|
186
66
|
return { error: "Preset has no prompts array." };
|
|
187
67
|
}
|
|
188
68
|
|
|
189
|
-
// Select character_id
|
|
190
69
|
const selectedEntry = selectCharacterEntry(promptOrder, options.characterId);
|
|
191
70
|
if (!selectedEntry) {
|
|
192
71
|
const ids = promptOrder.map((entry) => entry.character_id);
|
|
@@ -198,127 +77,27 @@ export function convertSillyTavernPreset(
|
|
|
198
77
|
};
|
|
199
78
|
}
|
|
200
79
|
|
|
201
|
-
|
|
202
|
-
const conversionItems
|
|
203
|
-
const promptMap = new Map<string, StPromptDef>();
|
|
204
|
-
for (const def of allPrompts) {
|
|
205
|
-
promptMap.set(def.identifier, def);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
let orderIndex = 0;
|
|
209
|
-
for (const orderItem of selectedEntry.order ?? []) {
|
|
210
|
-
const def = promptMap.get(orderItem.identifier);
|
|
211
|
-
if (def) {
|
|
212
|
-
conversionItems.push({ def, orderEnabled: orderItem.enabled, orderIndex: orderIndex++ });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// First pass: detect macros across all content
|
|
80
|
+
const promptMap = buildPromptMap(allPrompts);
|
|
81
|
+
const conversionItems = buildConversionItems(selectedEntry, promptMap);
|
|
217
82
|
const macroUsage = detectMacros(conversionItems);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const reportNotes: string[] = [];
|
|
221
|
-
let nextId = 1;
|
|
222
|
-
const items: PromptStackItem[] = [];
|
|
223
|
-
let usesLastUserMessage = false;
|
|
224
|
-
const disabledCount = { marker: 0, empty: 0, orderDisable: 0 };
|
|
225
|
-
|
|
226
|
-
for (const { def, orderEnabled, orderIndex } of conversionItems) {
|
|
227
|
-
const id = String(nextId++);
|
|
228
|
-
|
|
229
|
-
// Marker items
|
|
230
|
-
if (def.marker) {
|
|
231
|
-
if (def.identifier === MARKER_CHAT_HISTORY) {
|
|
232
|
-
items.push({
|
|
233
|
-
kind: "slot",
|
|
234
|
-
id,
|
|
235
|
-
name: def.name || "Chat History",
|
|
236
|
-
enabled: orderEnabled,
|
|
237
|
-
slot: "chat-history",
|
|
238
|
-
source: { previousId: def.identifier, name: def.name },
|
|
239
|
-
});
|
|
240
|
-
} else if (MARKER_SKIP.has(def.identifier)) {
|
|
241
|
-
disabledCount.marker++;
|
|
242
|
-
}
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Clean content: strip comments, handle trim
|
|
247
|
-
let content = def.content ?? "";
|
|
248
|
-
if (!content.trim()) {
|
|
249
|
-
disabledCount.empty++;
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Strip ST comment macros
|
|
254
|
-
content = content.replace(COMMENT_MACRO_RE, "");
|
|
255
|
-
|
|
256
|
-
// Strip {{trim}} — it's a formatting hint for newline removal, not meaningful content
|
|
257
|
-
content = content.replace(TRIM_MACRO_RE, "");
|
|
258
|
-
|
|
259
|
-
// After stripping comments/trim, check if content is still meaningful
|
|
260
|
-
if (!content.trim()) {
|
|
261
|
-
disabledCount.empty++;
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Detect {{lastUserMessage}} usage, case-insensitively.
|
|
266
|
-
if (/\{\{\s*lastUserMessage\b/i.test(content)) {
|
|
267
|
-
usesLastUserMessage = true;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const role = normalizeRole(def.role);
|
|
271
|
-
if (!role) {
|
|
272
|
-
disabledCount.empty++;
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!orderEnabled) {
|
|
277
|
-
disabledCount.orderDisable++;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
items.push({
|
|
281
|
-
kind: "block",
|
|
282
|
-
id,
|
|
283
|
-
name: def.name,
|
|
284
|
-
enabled: orderEnabled,
|
|
285
|
-
role,
|
|
286
|
-
content: cleanContent(content),
|
|
287
|
-
source: {
|
|
288
|
-
previousId: def.identifier,
|
|
289
|
-
previousName: def.name,
|
|
290
|
-
orderIndex,
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Diagnostic: any prompts in the order that don't exist in the prompts array
|
|
296
|
-
const missingIdentifiers: string[] = [];
|
|
297
|
-
for (const orderItem of selectedEntry.order ?? []) {
|
|
298
|
-
if (!promptMap.has(orderItem.identifier)) {
|
|
299
|
-
missingIdentifiers.push(orderItem.identifier);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Build stack name from file
|
|
83
|
+
const { items, disabledCount, usesLastUserMessage: itemUsesLastUserMessage } = convertPromptItems(conversionItems);
|
|
84
|
+
const missingIdentifiers = findMissingIdentifiers(selectedEntry, promptMap);
|
|
304
85
|
const fileName = basename(sourceName).replace(/\.json$/i, "") || "imported";
|
|
305
86
|
const styleName = preset.names_behavior === 1 ? "names" : preset.names_behavior === 2 ? "nonames" : "default";
|
|
306
|
-
const stackId = fileName
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const variables: Record<string, string> = {};
|
|
310
|
-
if (macroUsage.detected.has("char")) variables.char = "{{char}}";
|
|
311
|
-
if (macroUsage.detected.has("user")) variables.user = "{{user}}";
|
|
87
|
+
const stackId = normalizeStackId(fileName);
|
|
88
|
+
const variables = buildStaticVariables(macroUsage.detected);
|
|
89
|
+
let usesLastUserMessage = itemUsesLastUserMessage;
|
|
312
90
|
|
|
313
91
|
const stack: PromptStack = {
|
|
314
92
|
schemaVersion: 1,
|
|
315
93
|
type: "pi-forge.prompt-stack",
|
|
316
94
|
id: stackId,
|
|
317
|
-
name: preset
|
|
95
|
+
name: preset.preset_name || preset.name || fileName,
|
|
318
96
|
autoActivate: false,
|
|
319
97
|
mode: "replace",
|
|
320
98
|
variables: Object.keys(variables).length > 0 ? variables : undefined,
|
|
321
99
|
context: { allowDuplicateChatHistory: false },
|
|
100
|
+
regex: regexScripts && regexScripts.rules.length > 0 ? { schemaVersion: 1, rules: regexScripts.rules } : undefined,
|
|
322
101
|
items,
|
|
323
102
|
import: {
|
|
324
103
|
source: "sillytavern",
|
|
@@ -328,7 +107,6 @@ export function convertSillyTavernPreset(
|
|
|
328
107
|
},
|
|
329
108
|
};
|
|
330
109
|
|
|
331
|
-
// Add chat-history option if {{lastUserMessage}} is used after history.
|
|
332
110
|
if (macroUsage.detected.has("lastusermessage")) usesLastUserMessage = true;
|
|
333
111
|
if (usesLastUserMessage) {
|
|
334
112
|
const chatHistoryItem = items.find(
|
|
@@ -339,239 +117,36 @@ export function convertSillyTavernPreset(
|
|
|
339
117
|
}
|
|
340
118
|
}
|
|
341
119
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
if (macroUsage.trimStripped > 0) {
|
|
360
|
-
reportLines.push(`- **Trim markers removed**: ${macroUsage.trimStripped} {{trim}} markers (ST formatting hint)`);
|
|
361
|
-
}
|
|
362
|
-
reportLines.push("");
|
|
363
|
-
|
|
364
|
-
// Skipped items
|
|
365
|
-
if (disabledCount.marker > 0 || disabledCount.empty > 0 || disabledCount.orderDisable > 0) {
|
|
366
|
-
reportLines.push("## Skipped items");
|
|
367
|
-
reportLines.push("");
|
|
368
|
-
if (disabledCount.marker > 0) {
|
|
369
|
-
reportLines.push(`- ${disabledCount.marker} marker items (charDescription, worldInfo, etc.) — handled by Pi/ST runtime, not needed in prompt stack`);
|
|
370
|
-
}
|
|
371
|
-
if (disabledCount.empty > 0) {
|
|
372
|
-
reportLines.push(`- ${disabledCount.empty} items with empty or missing content`);
|
|
373
|
-
}
|
|
374
|
-
if (disabledCount.orderDisable > 0) {
|
|
375
|
-
reportLines.push(`- ${disabledCount.orderDisable} items disabled in prompt_order`);
|
|
376
|
-
}
|
|
377
|
-
reportLines.push("");
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Missing identifiers
|
|
381
|
-
if (missingIdentifiers.length > 0) {
|
|
382
|
-
reportLines.push("## ⚠️ Missing identifiers in prompts array");
|
|
383
|
-
reportLines.push("");
|
|
384
|
-
for (const id of missingIdentifiers) {
|
|
385
|
-
reportLines.push(`- \`${id}\` (referenced in prompt_order but not found in prompts)`);
|
|
386
|
-
}
|
|
387
|
-
reportLines.push("");
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Auto-populated variables
|
|
391
|
-
if (Object.keys(variables).length > 0) {
|
|
392
|
-
reportLines.push("## Auto-populated variables");
|
|
393
|
-
reportLines.push("");
|
|
394
|
-
reportLines.push("These ST built-in macros were auto-populated as static variables with placeholder values:");
|
|
395
|
-
reportLines.push("");
|
|
396
|
-
for (const [key, val] of Object.entries(variables)) {
|
|
397
|
-
reportLines.push(`- \`${key}\` = \`${val}\` — replace with your character/persona name`);
|
|
398
|
-
}
|
|
399
|
-
reportLines.push("");
|
|
400
|
-
reportLines.push("Use `/state set <name> <value>` or edit the stack JSON to set real values.");
|
|
401
|
-
reportLines.push("");
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Macros needing migration
|
|
405
|
-
const migrationEntries = Object.entries(macroUsage.migrationNeeded).sort(([, a], [, b]) => b - a);
|
|
406
|
-
if (migrationEntries.length > 0) {
|
|
407
|
-
reportLines.push("## ⚠️ Macros needing manual migration");
|
|
408
|
-
reportLines.push("");
|
|
409
|
-
reportLines.push("These ST macros appear in the preset but have no direct pi-forge equivalent:");
|
|
410
|
-
reportLines.push("");
|
|
411
|
-
for (const [name, count] of migrationEntries) {
|
|
412
|
-
const note = MACRO_NEEDS_MIGRATION[name] ?? "no mapping available";
|
|
413
|
-
reportLines.push(`- **\`{{${displayMacroName(name)}}}\`** (${count} occurrence${count > 1 ? "s" : ""}) — ${note}`);
|
|
414
|
-
}
|
|
415
|
-
reportLines.push("");
|
|
416
|
-
|
|
417
|
-
// Specific guidance for heavy variable usage
|
|
418
|
-
if (macroUsage.migrationNeeded.setvar || macroUsage.migrationNeeded.getvar) {
|
|
419
|
-
reportLines.push("### Variable system migration");
|
|
420
|
-
reportLines.push("");
|
|
421
|
-
reportLines.push("ST uses `setvar`/`getvar` for ephemeral state. pi-forge has a different variable model:");
|
|
422
|
-
reportLines.push("");
|
|
423
|
-
reportLines.push("- **Static variables** — set in the stack JSON's `variables` object (character names, fixed config)");
|
|
424
|
-
reportLines.push("- **Session state** — persists across turns, set by the user via `/state set` or by the agent via `forge_state_set` for `agent.*` names");
|
|
425
|
-
reportLines.push("- **Turn variables** — ephemeral, set via `{{setvar::name::value}}` macros in blocks");
|
|
426
|
-
reportLines.push("");
|
|
427
|
-
reportLines.push("Review ST `setvar`/`getvar` calls and migrate to the appropriate pi-forge scope.");
|
|
428
|
-
reportLines.push("");
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Native macros handled
|
|
433
|
-
const nativeDetected = [...macroUsage.detected].filter((m) => NATIVE_MACROS.has(m));
|
|
434
|
-
if (nativeDetected.length > 0) {
|
|
435
|
-
reportLines.push("## Handled macros");
|
|
436
|
-
reportLines.push("");
|
|
437
|
-
for (const name of nativeDetected) {
|
|
438
|
-
const displayName = displayMacroName(name);
|
|
439
|
-
if (name === "char" || name === "user") {
|
|
440
|
-
reportLines.push(`- \`{{${displayName}}}\` → auto-populated as static variable`);
|
|
441
|
-
} else if (name === "lastusermessage") {
|
|
442
|
-
reportLines.push(`- \`{{${displayName}}}\` → handled by pi-forge runtime (chat-history slot)`);
|
|
443
|
-
} else {
|
|
444
|
-
reportLines.push(`- \`{{${displayName}}}\` → handled natively by pi-forge`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
reportLines.push("");
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Item mapping table
|
|
451
|
-
reportLines.push(`## Item mapping`);
|
|
452
|
-
reportLines.push("");
|
|
453
|
-
reportLines.push(`| # | ST Identifier | ST Name | pi-forge ID | Role | Enabled | Kind |`);
|
|
454
|
-
reportLines.push(`|---|--------------|---------|-------------|------|---------|------|`);
|
|
455
|
-
|
|
456
|
-
for (const item of items) {
|
|
457
|
-
const stId = (item.source as Record<string, unknown> | undefined)?.previousId ?? "—";
|
|
458
|
-
const stName = item.name ?? "—";
|
|
459
|
-
const enabled = item.enabled !== false ? "✓" : "✗";
|
|
460
|
-
const kind = item.kind;
|
|
461
|
-
reportLines.push(`| ${item.id} | ${stId} | ${stName} | ${item.id} | ${item.role ?? "—"} | ${enabled} | ${kind} |`);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
reportLines.push("");
|
|
465
|
-
reportLines.push("## General notes");
|
|
466
|
-
reportLines.push("");
|
|
467
|
-
|
|
468
|
-
if (usesLastUserMessage) {
|
|
469
|
-
reportLines.push("- Auto-detected `{{lastUserMessage}}` in post-history content. `chat-history` slot set with `includeLastUserMessage: false`.");
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (macroUsage.commentsStripped > 0 || macroUsage.trimStripped > 0) {
|
|
473
|
-
reportLines.push(`- ${macroUsage.commentsStripped} ST comment blocks and ${macroUsage.trimStripped} TRIM markers were stripped during import.`);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
reportLines.push("- All items are assigned sequential numeric IDs. Original SillyTavern identifiers are preserved in `source.previousId`.");
|
|
477
|
-
reportLines.push("- Marker items for world info, character description, persona, scenario, and dialogue examples are omitted — these are handled by the SillyTavern frontend and have no direct pi-forge equivalent.");
|
|
478
|
-
reportLines.push(`- The stack is set with \`autoActivate: false\`. Use \`/preset use ${stackId}\` to activate.`);
|
|
479
|
-
reportLines.push("");
|
|
480
|
-
reportLines.push("## Suggested next steps");
|
|
481
|
-
reportLines.push("");
|
|
482
|
-
reportLines.push("1. Set real values for auto-populated variables with `/state set`.");
|
|
483
|
-
reportLines.push("2. Review items with migration-needed macros and rewrite for pi-forge's macro system.");
|
|
484
|
-
reportLines.push("3. Consider adding a `variables` slot for agent state visibility.");
|
|
485
|
-
reportLines.push(`4. Run \`/preset validate ${stackId}\` to check for issues.`);
|
|
120
|
+
const report = buildSillyTavernImportReport({
|
|
121
|
+
fileName,
|
|
122
|
+
sourcePath,
|
|
123
|
+
characterId: selectedEntry.character_id,
|
|
124
|
+
stackId,
|
|
125
|
+
styleName: `${styleName} (ST value ${preset.names_behavior ?? "?"})`,
|
|
126
|
+
totalPrompts: allPrompts.length,
|
|
127
|
+
orderItemCount: selectedEntry.order?.length ?? 0,
|
|
128
|
+
items,
|
|
129
|
+
macroUsage,
|
|
130
|
+
disabledCount,
|
|
131
|
+
missingIdentifiers,
|
|
132
|
+
regexScripts,
|
|
133
|
+
variables,
|
|
134
|
+
usesLastUserMessage,
|
|
135
|
+
});
|
|
486
136
|
|
|
487
137
|
return {
|
|
488
138
|
stack,
|
|
489
|
-
report
|
|
139
|
+
report,
|
|
490
140
|
};
|
|
491
141
|
}
|
|
492
142
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
interface MacroDetection {
|
|
496
|
-
detected: Set<string>;
|
|
497
|
-
commentsStripped: number;
|
|
498
|
-
trimStripped: number;
|
|
499
|
-
migrationNeeded: Record<string, number>;
|
|
143
|
+
function normalizeStackId(fileName: string): string {
|
|
144
|
+
return fileName.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase() || "imported";
|
|
500
145
|
}
|
|
501
146
|
|
|
502
|
-
function
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
// Regex to find {{macroName...}} — captures the first word/identifier
|
|
509
|
-
const macroFindRe = /\{\{(?!\/\/)([a-zA-Z_][a-zA-Z0-9_-]*)/gi;
|
|
510
|
-
|
|
511
|
-
for (const { def } of conversionItems) {
|
|
512
|
-
const content = def.content ?? "";
|
|
513
|
-
if (!content) continue;
|
|
514
|
-
|
|
515
|
-
// Count comments
|
|
516
|
-
const commentMatches = content.match(COMMENT_MACRO_RE);
|
|
517
|
-
if (commentMatches) commentsStripped += commentMatches.length;
|
|
518
|
-
|
|
519
|
-
// Count trims
|
|
520
|
-
const trimMatches = content.match(TRIM_MACRO_RE);
|
|
521
|
-
if (trimMatches) trimStripped += trimMatches.length;
|
|
522
|
-
|
|
523
|
-
// Work on a copy with comments stripped so we don't flag // as a macro
|
|
524
|
-
const cleaned = content.replace(COMMENT_MACRO_RE, "").replace(TRIM_MACRO_RE, "");
|
|
525
|
-
|
|
526
|
-
let match: RegExpExecArray | null;
|
|
527
|
-
macroFindRe.lastIndex = 0;
|
|
528
|
-
while ((match = macroFindRe.exec(cleaned)) !== null) {
|
|
529
|
-
const name = match[1].toLowerCase();
|
|
530
|
-
detected.add(name);
|
|
531
|
-
|
|
532
|
-
if (MACRO_NEEDS_MIGRATION[name]) {
|
|
533
|
-
migrationNeeded[name] = (migrationNeeded[name] ?? 0) + 1;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
return { detected, commentsStripped, trimStripped, migrationNeeded };
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// ── Content cleaning ──────────────────────────────────────────────────────
|
|
542
|
-
|
|
543
|
-
function displayMacroName(name: string): string {
|
|
544
|
-
return MACRO_DISPLAY_NAMES[name] ?? name;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function cleanContent(content: string): string {
|
|
548
|
-
// Collapse multiple blank lines (max 1 blank line between paragraphs)
|
|
549
|
-
return content.replace(/\n{3,}/g, "\n\n").trim();
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
553
|
-
|
|
554
|
-
function selectCharacterEntry(
|
|
555
|
-
order: StPromptOrderEntry[],
|
|
556
|
-
preferredId?: number,
|
|
557
|
-
): StPromptOrderEntry | undefined {
|
|
558
|
-
if (order.length === 0) return undefined;
|
|
559
|
-
|
|
560
|
-
if (preferredId !== undefined) {
|
|
561
|
-
return order.find((entry) => entry.character_id === preferredId);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (order.length === 1) return order[0];
|
|
565
|
-
|
|
566
|
-
// Multiple entries, no preference — can't auto-select
|
|
567
|
-
return undefined;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
function normalizeRole(role: string | undefined): "system" | "user" | "assistant" | undefined {
|
|
571
|
-
if (!role) return undefined;
|
|
572
|
-
const lower = role.trim().toLowerCase();
|
|
573
|
-
if (lower === "system") return "system";
|
|
574
|
-
if (lower === "user") return "user";
|
|
575
|
-
if (lower === "assistant") return "assistant";
|
|
576
|
-
return undefined;
|
|
147
|
+
function buildStaticVariables(detectedMacros: Set<string>): Record<string, string> {
|
|
148
|
+
const variables: Record<string, string> = {};
|
|
149
|
+
if (detectedMacros.has("char")) variables.char = "{{char}}";
|
|
150
|
+
if (detectedMacros.has("user")) variables.user = "{{user}}";
|
|
151
|
+
return variables;
|
|
577
152
|
}
|