Fix prompt preview semantics for display regex rules

This commit is contained in:
Youzini-afk
2026-04-07 01:12:28 +08:00
parent f606fbfb8e
commit e0ebe07a55
3 changed files with 71 additions and 17 deletions

View File

@@ -3890,7 +3890,11 @@ function _renderRegexReuseRuleList(rules = [], emptyText = "无") {
const flags = [
rule.promptOnly ? "promptOnly" : "",
rule.markdownOnly ? "markdownOnly" : "",
rule.promptReplaceAsEmpty ? "请求阶段按空字符串替换" : "",
rule.promptStageMode === "clear"
? "请求阶段: 美化/展示 -> 清空"
: rule.promptStageMode === "replace"
? "请求阶段: 正常替换"
: "请求阶段: 不参与",
rule.reason ? `原因: ${rule.reason}` : "",
].filter(Boolean);
const replaceText = rule.promptReplaceAsEmpty
@@ -3983,10 +3987,10 @@ function _buildRegexReusePopupContent(snapshot = {}) {
raw=${Number(source.rawRuleCount || 0)} / active=${Number(source.activeRuleCount || 0)}
${source.reason ? `<br>${_escHtml(source.reason)}` : ""}
</div>
<div class="bme-task-section-label">本来源当前生效规则</div>
${_renderRegexReuseRuleList(source.rules, "该来源当前没有进入任务链的复用规则")}
<div class="bme-task-section-label">被跳过的规则</div>
${_renderRegexReuseRuleList(source.ignoredRules, "没有额外跳过的规则")}
<div class="bme-task-section-label">本来源规则总览</div>
${_renderRegexReuseRuleList(source.previewRules || source.rules, "该来源当前没有可展示的规则")}
<div class="bme-task-section-label">未纳入最终任务链</div>
${_renderRegexReuseRuleList(source.ignoredRules, "没有额外被排除的规则")}
</details>
`).join("")
: `<div class="bme-task-empty">当前没有可展示的酒馆正则来源。</div>`
@@ -3996,7 +4000,7 @@ function _buildRegexReusePopupContent(snapshot = {}) {
<div class="bme-config-card">
<div class="bme-config-card-title">汇总后的复用规则</div>
<div class="bme-config-card-subtitle">
这是经过来源开关、allowlist 和去重后,准备进入当前任务链的 Tavern 规则集合。
这是经过来源开关、allowlist 和去重后,进入 ST-BME 任务链的 Tavern 规则集合。展示/美化类规则在请求阶段会按空字符串替换。
</div>
${_renderRegexReuseRuleList(activeRules, "当前没有复用到任何酒馆正则")}
</div>

View File

@@ -169,7 +169,7 @@ function normalizeRule(raw = {}, fallbackSource = "local", index = 0) {
sourceFlags,
destinationFlags: {
prompt: destination
? isTavernRule && beautificationReplace
? isTavernRule && (raw.markdownOnly === true || beautificationReplace)
? true
: Boolean(destination.prompt)
: isTavernRule && raw.markdownOnly === true
@@ -397,7 +397,8 @@ function getPlacementLabels(placement = []) {
function summarizeRule(rule, reason = "") {
const normalized = rule && typeof rule === "object" ? rule : {};
const promptReplaceAsEmpty = Boolean(normalized.beautificationReplace);
const promptReplaceAsEmpty =
Boolean(normalized.markdownOnly) || Boolean(normalized.beautificationReplace);
const sourceFlags =
normalized.sourceFlags && typeof normalized.sourceFlags === "object"
? normalized.sourceFlags
@@ -434,6 +435,24 @@ function summarizeRule(rule, reason = "") {
};
}
function summarizeRuleForPromptPreview(rule, stageConfig = {}, reason = "") {
const summary = summarizeRule(rule, reason);
const promptStageApplies = shouldApplyRuleForStage(
rule,
"input.finalPrompt",
stageConfig,
);
return {
...summary,
promptStageApplies,
promptStageMode: promptStageApplies
? summary.promptReplaceAsEmpty
? "clear"
: "replace"
: "skip",
};
}
function collectViaApi(sourceType, regexHost = null) {
const getter = regexHost?.getTavernRegexes;
if (typeof getter !== "function") {
@@ -505,6 +524,8 @@ function collectTavernRulesDetailed(regexConfig = {}) {
enabled && allowed ? (Array.isArray(rawItems) ? rawItems : []) : [];
const activeRules = [];
const ignoredRules = [];
const ignoredPreviewRules = [];
const previewRules = [];
if (!enabled) {
sources.push({
@@ -518,6 +539,10 @@ function collectTavernRulesDetailed(regexConfig = {}) {
reason || (shouldReuse ? "当前任务已关闭该来源" : "当前任务未启用复用酒馆正则"),
rawRuleCount: Array.isArray(rawItems) ? rawItems.length : 0,
activeRuleCount: 0,
previewRules: Array.isArray(rawItems)
? rawItems.map((item, index) => normalizeRule(item, type, index))
: [],
ignoredPreviewRules: [],
rules: [],
ignoredRules: [],
});
@@ -526,24 +551,31 @@ function collectTavernRulesDetailed(regexConfig = {}) {
if (!allowed && Array.isArray(rawItems)) {
for (let index = 0; index < rawItems.length; index++) {
const normalized = normalizeRule(rawItems[index], type, index);
previewRules.push(normalized);
ignoredPreviewRules.push({ ...normalized, reason: "not-allowed" });
ignoredRules.push(
summarizeRule(normalizeRule(rawItems[index], type, index), "not-allowed"),
summarizeRule(normalized, "not-allowed"),
);
}
}
for (let index = 0; index < effectiveItems.length; index++) {
const normalized = normalizeRule(effectiveItems[index], type, index);
previewRules.push(normalized);
if (!normalized.enabled) {
ignoredPreviewRules.push({ ...normalized, reason: "disabled" });
ignoredRules.push(summarizeRule(normalized, "disabled"));
continue;
}
if (!normalized.findRegex) {
ignoredPreviewRules.push({ ...normalized, reason: "missing-find-regex" });
ignoredRules.push(summarizeRule(normalized, "missing-find-regex"));
continue;
}
const key = `${type}:${normalized.id}:${normalized.findRegex}`;
if (seen.has(key)) {
ignoredPreviewRules.push({ ...normalized, reason: "duplicate" });
ignoredRules.push(summarizeRule(normalized, "duplicate"));
continue;
}
@@ -562,6 +594,8 @@ function collectTavernRulesDetailed(regexConfig = {}) {
reason,
rawRuleCount: Array.isArray(rawItems) ? rawItems.length : 0,
activeRuleCount: activeRules.length,
previewRules,
ignoredPreviewRules,
rules: activeRules,
ignoredRules,
});
@@ -676,7 +710,7 @@ function shouldApplyRuleForTaskContext(rule, stage = "") {
const isOutputStage = OUTPUT_STAGES.has(normalizedStage);
if (rule.markdownOnly) {
return isPromptStage && rule.beautificationReplace === true;
return isPromptStage;
}
if (isFinalPromptStage) {
@@ -726,7 +760,7 @@ function applyOneRule(input, rule, stage = "") {
let replacement = rule.replaceString || "";
if (
PROMPT_STAGES.has(stage) &&
rule.beautificationReplace
(rule.markdownOnly || rule.beautificationReplace)
) {
replacement = "";
}
@@ -820,6 +854,12 @@ export function inspectTaskRegexReuse(settings = {}, taskType = "") {
const profile = getActiveTaskProfile(settings, taskType);
const regexConfig = profile?.regex || {};
const detailed = collectTavernRulesDetailed(regexConfig);
const stageConfig = normalizeTaskRegexStages(regexConfig.stages || {});
const mapPreviewRules = (rules = []) =>
(Array.isArray(rules) ? rules : []).map((rule) =>
summarizeRuleForPromptPreview(rule, stageConfig, rule?.reason || ""),
);
return {
taskType: String(taskType || ""),
@@ -836,9 +876,16 @@ export function inspectTaskRegexReuse(settings = {}, taskType = "") {
localRuleCount: Array.isArray(regexConfig.localRules)
? regexConfig.localRules.length
: 0,
sources: detailed.sources,
sources: detailed.sources.map((source) => ({
...source,
previewRules: mapPreviewRules(source.previewRules),
rules: mapPreviewRules(source.previewRules),
ignoredRules: mapPreviewRules(source.ignoredPreviewRules),
})),
host: detailed.host,
activeRuleCount: detailed.rules.length,
activeRules: detailed.rules.map((rule) => summarizeRule(rule)),
activeRules: detailed.rules.map((rule) =>
summarizeRuleForPromptPreview(rule, stageConfig),
),
};
}

View File

@@ -506,6 +506,7 @@ try {
assert.equal(markdownRule?.promptReplaceAsEmpty, true);
assert.equal(markdownRule?.effectivePromptReplaceString, "");
assert.deepEqual(markdownRule?.placementLabels, ["用户输入"]);
assert.equal(markdownRule?.promptStageMode, "clear");
const markdownOnlyFinalPromptSettings = buildSettings({
sources: {
global: true,
@@ -588,15 +589,15 @@ try {
destinationBeautifySettings,
"extract",
"input.finalPrompt",
"Decor Plain",
"DecorPlain",
destinationDebug,
"user",
),
" Plain",
"",
);
assert.deepEqual(
destinationDebug.entries[0].appliedRules.map((item) => item.id),
["destination-display-only-beautify"],
["destination-display-only-beautify", "destination-display-only-text"],
);
const destinationInspect = inspectTaskRegexReuse(
destinationBeautifySettings,
@@ -610,7 +611,9 @@ try {
);
assert.deepEqual(destinationBeautifyRule?.placementLabels, ["用户输入"]);
assert.equal(destinationBeautifyRule?.promptReplaceAsEmpty, true);
assert.equal(destinationTextRule?.promptReplaceAsEmpty, false);
assert.equal(destinationBeautifyRule?.promptStageMode, "clear");
assert.equal(destinationTextRule?.promptReplaceAsEmpty, true);
assert.equal(destinationTextRule?.promptStageMode, "clear");
setTestContext({
extensionSettings: {
regex: [