Add global task regex migration and UI

This commit is contained in:
Hao19911125
2026-04-09 10:06:29 +08:00
parent e1f7988aed
commit 3533aeab18
6 changed files with 859 additions and 133 deletions

View File

@@ -146,6 +146,7 @@ import {
} from "./ui/panel-bridge.js"; } from "./ui/panel-bridge.js";
import { import {
migrateLegacyTaskProfiles, migrateLegacyTaskProfiles,
migratePerTaskRegexToGlobal,
} from "./prompting/prompt-profiles.js"; } from "./prompting/prompt-profiles.js";
import { inspectTaskRegexReuse } from "./prompting/task-regex.js"; import { inspectTaskRegexReuse } from "./prompting/task-regex.js";
import { import {
@@ -2885,6 +2886,11 @@ function getSettings() {
const migrated = migrateLegacyTaskProfiles(mergedSettings); const migrated = migrateLegacyTaskProfiles(mergedSettings);
mergedSettings.taskProfilesVersion = migrated.taskProfilesVersion; mergedSettings.taskProfilesVersion = migrated.taskProfilesVersion;
mergedSettings.taskProfiles = migrated.taskProfiles; mergedSettings.taskProfiles = migrated.taskProfiles;
const regexMigration = migratePerTaskRegexToGlobal(mergedSettings);
if (regexMigration.changed) {
mergedSettings.globalTaskRegex = regexMigration.settings.globalTaskRegex;
mergedSettings.taskProfiles = regexMigration.settings.taskProfiles;
}
extension_settings[MODULE_NAME] = mergedSettings; extension_settings[MODULE_NAME] = mergedSettings;
globalThis.__stBmeDebugLoggingEnabled = Boolean( globalThis.__stBmeDebugLoggingEnabled = Boolean(
mergedSettings.debugLoggingEnabled, mergedSettings.debugLoggingEnabled,

View File

@@ -806,6 +806,71 @@ export function normalizeTaskRegexStages(stages = {}) {
return normalized; return normalized;
} }
export function createDefaultGlobalTaskRegex() {
return {
enabled: true,
inheritStRegex: true,
sources: {
global: true,
preset: true,
character: true,
},
stages: normalizeTaskRegexStages(DEFAULT_TASK_REGEX_STAGES),
localRules: [],
};
}
export function dedupeRegexRules(rules = [], taskType = "task") {
const sourceRules = Array.isArray(rules) ? rules : [];
const deduped = [];
const seen = new Set();
for (let index = 0; index < sourceRules.length; index++) {
const normalized = normalizeRegexLocalRule(sourceRules[index], taskType, index);
const key = JSON.stringify({
enabled: normalized.enabled !== false,
find_regex: normalized.find_regex,
replace_string: normalized.replace_string,
trim_strings: normalized.trim_strings,
source: {
user_input: normalized.source?.user_input !== false,
ai_output: normalized.source?.ai_output !== false,
},
destination: {
prompt: normalized.destination?.prompt !== false,
display: Boolean(normalized.destination?.display),
},
min_depth: normalized.min_depth,
max_depth: normalized.max_depth,
});
if (seen.has(key)) continue;
seen.add(key);
deduped.push(normalized);
}
return deduped;
}
export function normalizeGlobalTaskRegex(config = {}, taskType = "global") {
const defaults = createDefaultGlobalTaskRegex();
const source =
config && typeof config === "object" && !Array.isArray(config) ? config : {};
return {
enabled: source.enabled !== false,
inheritStRegex: source.inheritStRegex !== false,
sources: {
...defaults.sources,
...(source.sources && typeof source.sources === "object" ? source.sources : {}),
},
stages: {
...normalizeTaskRegexStages(defaults.stages),
...normalizeTaskRegexStages(source.stages || {}),
},
localRules: dedupeRegexRules(source.localRules, taskType),
};
}
export function isTaskRegexStageEnabled(stages = {}, stageKey = "") { export function isTaskRegexStageEnabled(stages = {}, stageKey = "") {
const normalizedStages = normalizeTaskRegexStages(stages); const normalizedStages = normalizeTaskRegexStages(stages);
const normalizedStageKey = normalizeRegexStageKey(stageKey); const normalizedStageKey = normalizeRegexStageKey(stageKey);
@@ -829,6 +894,20 @@ export function isTaskRegexStageEnabled(stages = {}, stageKey = "") {
return normalizedStages[normalizedStageKey] !== false; return normalizedStages[normalizedStageKey] !== false;
} }
function buildRegexConfigSignature(config = {}, taskType = "global") {
const normalized = normalizeGlobalTaskRegex(config, taskType);
return JSON.stringify({
enabled: normalized.enabled !== false,
inheritStRegex: normalized.inheritStRegex !== false,
sources: {
global: normalized.sources?.global !== false,
preset: normalized.sources?.preset !== false,
character: normalized.sources?.character !== false,
},
stages: normalizeTaskRegexStages(normalized.stages || {}),
});
}
function normalizeTaskProfilesState(taskProfiles = {}) { function normalizeTaskProfilesState(taskProfiles = {}) {
return ensureTaskProfiles({ taskProfiles }); return ensureTaskProfiles({ taskProfiles });
} }
@@ -1358,6 +1437,141 @@ export function migrateLegacyTaskProfiles(settings = {}) {
}; };
} }
export function migratePerTaskRegexToGlobal(settings = {}) {
const taskProfiles = ensureTaskProfiles(settings);
const defaultGlobalRegex = normalizeGlobalTaskRegex(
createDefaultGlobalTaskRegex(),
"global",
);
const existingGlobalRegex = normalizeGlobalTaskRegex(
settings.globalTaskRegex || {},
"global",
);
const existingGlobalConfigSignature = buildRegexConfigSignature(
existingGlobalRegex,
"global",
);
const defaultGlobalConfigSignature = buildRegexConfigSignature(
defaultGlobalRegex,
"global",
);
const profilesWithLegacyRegex = [];
for (const taskType of TASK_TYPES) {
const bucket = taskProfiles[taskType];
const defaultProfileRegex = normalizeGlobalTaskRegex(
createDefaultTaskProfile(taskType).regex || {},
taskType,
);
const defaultProfileConfigSignature = buildRegexConfigSignature(
defaultProfileRegex,
taskType,
);
for (const profile of Array.isArray(bucket?.profiles) ? bucket.profiles : []) {
const normalizedProfileRegex = normalizeGlobalTaskRegex(
profile?.regex || {},
taskType,
);
const profileConfigSignature = buildRegexConfigSignature(
normalizedProfileRegex,
taskType,
);
const hasRules = normalizedProfileRegex.localRules.length > 0;
const hasConfigDiff = profileConfigSignature !== defaultProfileConfigSignature;
if (!hasRules && !hasConfigDiff) continue;
profilesWithLegacyRegex.push({
taskType,
profileId: String(profile?.id || ""),
regex: normalizedProfileRegex,
configSignature: profileConfigSignature,
hasConfigDiff,
});
}
}
if (profilesWithLegacyRegex.length === 0) {
return {
changed: false,
settings: {
...settings,
taskProfiles,
},
};
}
const configCandidates = profilesWithLegacyRegex.filter(
(item) => item.hasConfigDiff,
);
const uniqueCandidateSignatures = [
...new Set(configCandidates.map((item) => item.configSignature)),
];
if (uniqueCandidateSignatures.length > 1) {
console.warn(
"[ST-BME] 检测到多个任务预设存在冲突的旧正则配置,已按顺序采用第一份并统一迁移。",
configCandidates.map((item) => ({
taskType: item.taskType,
profileId: item.profileId,
})),
);
}
const selectedConfig =
existingGlobalConfigSignature !== defaultGlobalConfigSignature
? existingGlobalRegex
: configCandidates[0]?.regex || defaultGlobalRegex;
const mergedLocalRules = dedupeRegexRules(
[
...(Array.isArray(existingGlobalRegex.localRules)
? existingGlobalRegex.localRules
: []),
...profilesWithLegacyRegex.flatMap((item) =>
Array.isArray(item.regex?.localRules) ? item.regex.localRules : [],
),
],
"global",
);
const nextGlobalRegex = {
...normalizeGlobalTaskRegex(selectedConfig, "global"),
localRules: mergedLocalRules,
};
const nextTaskProfiles = {};
for (const taskType of TASK_TYPES) {
const bucket = taskProfiles[taskType] || {
activeProfileId: DEFAULT_PROFILE_ID,
profiles: [createDefaultTaskProfile(taskType)],
};
const legacyProfileIds = new Set(
profilesWithLegacyRegex
.filter((item) => item.taskType === taskType)
.map((item) => item.profileId),
);
nextTaskProfiles[taskType] = {
...bucket,
profiles: (Array.isArray(bucket.profiles) ? bucket.profiles : []).map((profile) =>
legacyProfileIds.has(String(profile?.id || ""))
? normalizeTaskProfile(taskType, {
...profile,
regex: {},
})
: normalizeTaskProfile(taskType, profile),
),
};
}
return {
changed: true,
settings: {
...settings,
globalTaskRegex: nextGlobalRegex,
taskProfiles: nextTaskProfiles,
},
};
}
export function getActiveTaskProfile(settings = {}, taskType) { export function getActiveTaskProfile(settings = {}, taskType) {
const taskProfiles = ensureTaskProfiles(settings); const taskProfiles = ensureTaskProfiles(settings);
const bucket = taskProfiles?.[taskType]; const bucket = taskProfiles?.[taskType];

View File

@@ -8,6 +8,7 @@ import { getHostAdapter } from "../host/adapter/index.js";
import { import {
getActiveTaskProfile, getActiveTaskProfile,
isTaskRegexStageEnabled, isTaskRegexStageEnabled,
normalizeGlobalTaskRegex,
normalizeTaskRegexStages, normalizeTaskRegexStages,
} from "./prompt-profiles.js"; } from "./prompt-profiles.js";
@@ -1026,6 +1027,29 @@ function applyHostRegexReuseFallback(
}; };
} }
function resolveTaskRegexConfig(settings = {}, taskType = "") {
const hasGlobalRegex =
settings?.globalTaskRegex &&
typeof settings.globalTaskRegex === "object" &&
!Array.isArray(settings.globalTaskRegex);
if (hasGlobalRegex) {
return {
profile: null,
regexConfig: normalizeGlobalTaskRegex(
settings.globalTaskRegex || {},
"global",
),
};
}
const profile = getActiveTaskProfile(settings, taskType);
return {
profile,
regexConfig: normalizeGlobalTaskRegex(profile?.regex || {}, taskType || "task"),
};
}
export function applyHostRegexReuse( export function applyHostRegexReuse(
settings = {}, settings = {},
taskType, taskType,
@@ -1041,8 +1065,7 @@ export function applyHostRegexReuse(
const normalizedTaskType = String(taskType || "").trim(); const normalizedTaskType = String(taskType || "").trim();
const normalizedSourceType = normalizeHostRegexSourceType(sourceType); const normalizedSourceType = normalizeHostRegexSourceType(sourceType);
const normalizedFormatterOptions = normalizeHostFormatterOptions(formatterOptions); const normalizedFormatterOptions = normalizeHostFormatterOptions(formatterOptions);
const profile = getActiveTaskProfile(settings, normalizedTaskType); const { regexConfig } = resolveTaskRegexConfig(settings, taskType);
const regexConfig = profile?.regex || {};
const regexHost = getRegexHost(); const regexHost = getRegexHost();
const executionState = buildHostRegexExecutionState(regexHost); const executionState = buildHostRegexExecutionState(regexHost);
@@ -1193,8 +1216,7 @@ export function applyTaskRegex(
debugCollector = null, debugCollector = null,
role = "system", role = "system",
) { ) {
const profile = getActiveTaskProfile(settings, taskType); const { regexConfig } = resolveTaskRegexConfig(settings, taskType);
const regexConfig = profile?.regex || {};
const input = typeof text === "string" ? text : ""; const input = typeof text === "string" ? text : "";
if (!regexConfig.enabled) { if (!regexConfig.enabled) {
@@ -1252,8 +1274,7 @@ export function applyTaskRegex(
} }
export function inspectTaskRegexReuse(settings = {}, taskType = "") { export function inspectTaskRegexReuse(settings = {}, taskType = "") {
const profile = getActiveTaskProfile(settings, taskType); const { profile, regexConfig } = resolveTaskRegexConfig(settings, taskType);
const regexConfig = profile?.regex || {};
const detailed = collectTavernRulesDetailed(regexConfig); const detailed = collectTavernRulesDetailed(regexConfig);
const stageConfig = normalizeTaskRegexStages(regexConfig.stages || {}); const stageConfig = normalizeTaskRegexStages(regexConfig.stages || {});
const localRules = collectLocalRules(regexConfig); const localRules = collectLocalRules(regexConfig);
@@ -1292,3 +1313,6 @@ export function inspectTaskRegexReuse(settings = {}, taskType = "") {
), ),
}; };
} }

View File

@@ -1,4 +1,7 @@
import { createDefaultTaskProfiles } from "../prompting/prompt-profiles.js"; import {
createDefaultGlobalTaskRegex,
createDefaultTaskProfiles,
} from "../prompting/prompt-profiles.js";
function clampIntValue(value, fallback = 0, min = 0, max = 9999) { function clampIntValue(value, fallback = 0, min = 0, max = 9999) {
const numeric = Number(value); const numeric = Number(value);
@@ -110,6 +113,7 @@ export const defaultSettings = {
reflectionPrompt: "", reflectionPrompt: "",
taskProfilesVersion: 3, taskProfilesVersion: 3,
taskProfiles: createDefaultTaskProfiles(), taskProfiles: createDefaultTaskProfiles(),
globalTaskRegex: createDefaultGlobalTaskRegex(),
// ====== v2 增强设置 ====== // ====== v2 增强设置 ======
enableConsolidation: true, enableConsolidation: true,

View File

@@ -112,6 +112,10 @@ export function createGenerationRecallHarness(options = {}) {
taskProfilesVersion: settings?.taskProfilesVersion || 0, taskProfilesVersion: settings?.taskProfilesVersion || 0,
taskProfiles: settings?.taskProfiles || {}, taskProfiles: settings?.taskProfiles || {},
}), }),
migratePerTaskRegexToGlobal: (settings = {}) => ({
changed: false,
settings,
}),
refreshPanelLiveStateController: () => { refreshPanelLiveStateController: () => {
context.refreshPanelCalls += 1; context.refreshPanelCalls += 1;
}, },

View File

@@ -20,10 +20,12 @@ import {
} from "../llm/llm-preset-utils.js"; } from "../llm/llm-preset-utils.js";
import { import {
cloneTaskProfile, cloneTaskProfile,
createDefaultGlobalTaskRegex,
createBuiltinPromptBlock, createBuiltinPromptBlock,
createCustomPromptBlock, createCustomPromptBlock,
createLocalRegexRule, createLocalRegexRule,
DEFAULT_TASK_BLOCKS, DEFAULT_TASK_BLOCKS,
dedupeRegexRules,
ensureTaskProfiles, ensureTaskProfiles,
exportTaskProfile as serializeTaskProfile, exportTaskProfile as serializeTaskProfile,
getBuiltinBlockDefinitions, getBuiltinBlockDefinitions,
@@ -31,6 +33,7 @@ import {
getTaskTypeOptions, getTaskTypeOptions,
importTaskProfile as parseImportedTaskProfile, importTaskProfile as parseImportedTaskProfile,
isTaskRegexStageEnabled, isTaskRegexStageEnabled,
normalizeGlobalTaskRegex,
normalizeTaskRegexStages, normalizeTaskRegexStages,
restoreDefaultTaskProfile, restoreDefaultTaskProfile,
setActiveTaskProfileId, setActiveTaskProfileId,
@@ -67,7 +70,6 @@ function getDefaultPromptText(taskType = "") {
const TASK_PROFILE_TABS = [ const TASK_PROFILE_TABS = [
{ id: "generation", label: "生成参数" }, { id: "generation", label: "生成参数" },
{ id: "prompt", label: "Prompt 编排" }, { id: "prompt", label: "Prompt 编排" },
{ id: "regex", label: "正则" },
{ id: "debug", label: "调试预览" }, { id: "debug", label: "调试预览" },
]; ];
@@ -226,6 +228,8 @@ let currentTaskProfileTaskType = "extract";
let currentTaskProfileTabId = "generation"; let currentTaskProfileTabId = "generation";
let currentTaskProfileBlockId = ""; let currentTaskProfileBlockId = "";
let currentTaskProfileRuleId = ""; let currentTaskProfileRuleId = "";
let showGlobalRegexPanel = false;
let currentGlobalRegexRuleId = "";
let currentCognitionOwnerKey = ""; let currentCognitionOwnerKey = "";
let currentGraphView = "graph"; let currentGraphView = "graph";
let fetchedMemoryLLMModels = []; let fetchedMemoryLLMModels = [];
@@ -4784,16 +4788,58 @@ function _bindTaskProfileWorkspace() {
try { try {
const text = await file.text(); const text = await file.text();
const settings = _getSettings?.() || {}; const settings = _getSettings?.() || {};
const imported = parseImportedTaskProfile( const parsed = JSON.parse(text);
settings.taskProfiles || {}, let nextGlobalTaskRegex = _normalizeGlobalRegexDraft(
text, settings.globalTaskRegex || {},
); );
const importedGlobalMerge = _mergeImportedGlobalRegex(
nextGlobalTaskRegex,
parsed?.globalTaskRegex,
);
nextGlobalTaskRegex = importedGlobalMerge.globalTaskRegex;
let imported = parseImportedTaskProfile(
settings.taskProfiles || {},
parsed,
);
const legacyRuleMerge = _mergeProfileRegexRulesIntoGlobal(
nextGlobalTaskRegex,
imported.profile,
);
nextGlobalTaskRegex = legacyRuleMerge.globalTaskRegex;
if (legacyRuleMerge.clearedLegacyRules) {
imported = {
...imported,
profile: legacyRuleMerge.profile,
taskProfiles: upsertTaskProfile(
imported.taskProfiles,
imported.taskType,
legacyRuleMerge.profile,
{ setActive: true },
),
};
}
currentTaskProfileTaskType = imported.taskType || currentTaskProfileTaskType; currentTaskProfileTaskType = imported.taskType || currentTaskProfileTaskType;
currentTaskProfileBlockId = imported.profile?.blocks?.[0]?.id || ""; currentTaskProfileBlockId = imported.profile?.blocks?.[0]?.id || "";
currentTaskProfileRuleId = currentTaskProfileRuleId =
imported.profile?.regex?.localRules?.[0]?.id || ""; imported.profile?.regex?.localRules?.[0]?.id || "";
_patchTaskProfiles(imported.taskProfiles); _patchSettings(
toastr.success("预设导入成功", "ST-BME"); {
taskProfilesVersion: 3,
taskProfiles: imported.taskProfiles,
globalTaskRegex: nextGlobalTaskRegex,
},
{
refreshTaskWorkspace: true,
},
);
const mergedRuleCount =
importedGlobalMerge.mergedRuleCount + legacyRuleMerge.mergedRuleCount;
toastr.success(
mergedRuleCount > 0
? `预设导入成功,${mergedRuleCount} 条正则规则已合并到通用正则规则`
: "预设导入成功",
"ST-BME",
);
} catch (error) { } catch (error) {
console.error("[ST-BME] 导入任务预设失败:", error); console.error("[ST-BME] 导入任务预设失败:", error);
toastr.error(`预设导入失败: ${error?.message || error}`, "ST-BME"); toastr.error(`预设导入失败: ${error?.message || error}`, "ST-BME");
@@ -4817,14 +4863,41 @@ function _bindTaskProfileWorkspace() {
} }
const settings = _getSettings?.() || {}; const settings = _getSettings?.() || {};
let mergedProfiles = settings.taskProfiles || {}; let mergedProfiles = settings.taskProfiles || {};
let nextGlobalTaskRegex = _normalizeGlobalRegexDraft(
settings.globalTaskRegex || {},
);
const importedGlobalMerge = _mergeImportedGlobalRegex(
nextGlobalTaskRegex,
parsed?.globalTaskRegex,
);
nextGlobalTaskRegex = importedGlobalMerge.globalTaskRegex;
let importedCount = 0; let importedCount = 0;
let mergedLegacyRuleCount = 0;
for (const [taskType, entry] of Object.entries(parsed.profiles)) { for (const [taskType, entry] of Object.entries(parsed.profiles)) {
try { try {
const imported = parseImportedTaskProfile( let imported = parseImportedTaskProfile(
mergedProfiles, mergedProfiles,
entry, entry,
taskType, taskType,
); );
const legacyRuleMerge = _mergeProfileRegexRulesIntoGlobal(
nextGlobalTaskRegex,
imported.profile,
);
nextGlobalTaskRegex = legacyRuleMerge.globalTaskRegex;
mergedLegacyRuleCount += legacyRuleMerge.mergedRuleCount;
if (legacyRuleMerge.clearedLegacyRules) {
imported = {
...imported,
profile: legacyRuleMerge.profile,
taskProfiles: upsertTaskProfile(
imported.taskProfiles,
imported.taskType,
legacyRuleMerge.profile,
{ setActive: true },
),
};
}
mergedProfiles = imported.taskProfiles; mergedProfiles = imported.taskProfiles;
importedCount++; importedCount++;
} catch (innerError) { } catch (innerError) {
@@ -4835,8 +4908,24 @@ function _bindTaskProfileWorkspace() {
toastr.warning("没有成功导入任何预设", "ST-BME"); toastr.warning("没有成功导入任何预设", "ST-BME");
return; return;
} }
_patchTaskProfiles(mergedProfiles); _patchSettings(
toastr.success(`已导入 ${importedCount} 个任务预设`, "ST-BME"); {
taskProfilesVersion: 3,
taskProfiles: mergedProfiles,
globalTaskRegex: nextGlobalTaskRegex,
},
{
refreshTaskWorkspace: true,
},
);
const mergedRuleCount =
importedGlobalMerge.mergedRuleCount + mergedLegacyRuleCount;
toastr.success(
mergedRuleCount > 0
? `已导入 ${importedCount} 个任务预设,并合并 ${mergedRuleCount} 条通用正则规则`
: `已导入 ${importedCount} 个任务预设`,
"ST-BME",
);
} catch (error) { } catch (error) {
console.error("[ST-BME] 导入全部预设失败:", error); console.error("[ST-BME] 导入全部预设失败:", error);
toastr.error(`导入全部预设失败: ${error?.message || error}`, "ST-BME"); toastr.error(`导入全部预设失败: ${error?.message || error}`, "ST-BME");
@@ -4851,6 +4940,7 @@ function _bindTaskProfileWorkspace() {
function _handleTaskProfileWorkspaceInput(event) { function _handleTaskProfileWorkspaceInput(event) {
const target = event.target; const target = event.target;
if (!(target instanceof HTMLElement)) return; if (!(target instanceof HTMLElement)) return;
const isGlobalRegexPanel = _isGlobalRegexPanelTarget(target);
if (target.matches("[data-block-field]")) { if (target.matches("[data-block-field]")) {
_persistSelectedBlockField(target, false); _persistSelectedBlockField(target, false);
@@ -4880,13 +4970,18 @@ function _handleTaskProfileWorkspaceInput(event) {
target.matches("[data-regex-rule-source]") || target.matches("[data-regex-rule-source]") ||
target.matches("[data-regex-rule-destination]") target.matches("[data-regex-rule-destination]")
) { ) {
if (isGlobalRegexPanel) {
_persistSelectedGlobalRegexRuleField(target, false);
} else {
_persistSelectedRegexRuleField(target, false); _persistSelectedRegexRuleField(target, false);
} }
} }
}
function _handleTaskProfileWorkspaceChange(event) { function _handleTaskProfileWorkspaceChange(event) {
const target = event.target; const target = event.target;
if (!(target instanceof HTMLElement)) return; if (!(target instanceof HTMLElement)) return;
const isGlobalRegexPanel = _isGlobalRegexPanelTarget(target);
if (target.id === "bme-task-profile-select") { if (target.id === "bme-task-profile-select") {
const settings = _getSettings?.() || {}; const settings = _getSettings?.() || {};
@@ -4912,17 +5007,29 @@ function _handleTaskProfileWorkspaceChange(event) {
} }
if (target.matches("[data-regex-field]")) { if (target.matches("[data-regex-field]")) {
if (isGlobalRegexPanel) {
_persistGlobalRegexField(target, false);
} else {
_persistRegexConfigField(target, false); _persistRegexConfigField(target, false);
}
return; return;
} }
if (target.matches("[data-regex-source]")) { if (target.matches("[data-regex-source]")) {
if (isGlobalRegexPanel) {
_persistGlobalRegexSourceField(target, false);
} else {
_persistRegexSourceField(target, false); _persistRegexSourceField(target, false);
}
return; return;
} }
if (target.matches("[data-regex-stage]")) { if (target.matches("[data-regex-stage]")) {
if (isGlobalRegexPanel) {
_persistGlobalRegexStageField(target, false);
} else {
_persistRegexStageField(target, false); _persistRegexStageField(target, false);
}
return; return;
} }
@@ -4931,12 +5038,20 @@ function _handleTaskProfileWorkspaceChange(event) {
target.matches("[data-regex-rule-source]") || target.matches("[data-regex-rule-source]") ||
target.matches("[data-regex-rule-destination]") target.matches("[data-regex-rule-destination]")
) { ) {
if (isGlobalRegexPanel) {
_persistSelectedGlobalRegexRuleField(target, true);
} else {
_persistSelectedRegexRuleField(target, true); _persistSelectedRegexRuleField(target, true);
} }
} }
}
function _getTaskProfileWorkspaceState(settings = _getSettings?.() || {}) { function _getTaskProfileWorkspaceState(settings = _getSettings?.() || {}) {
const taskProfiles = ensureTaskProfiles(settings); const taskProfiles = ensureTaskProfiles(settings);
const globalTaskRegex = _normalizeGlobalRegexDraft(settings.globalTaskRegex || {});
const globalRegexRules = Array.isArray(globalTaskRegex.localRules)
? globalTaskRegex.localRules
: [];
const taskTypeOptions = getTaskTypeOptions(); const taskTypeOptions = getTaskTypeOptions();
const runtimeDebug = _getRuntimeDebugSnapshot?.() || { const runtimeDebug = _getRuntimeDebugSnapshot?.() || {
hostCapabilities: null, hostCapabilities: null,
@@ -4970,10 +5085,16 @@ function _getTaskProfileWorkspaceState(settings = _getSettings?.() || {}) {
if (!regexRules.some((rule) => rule.id === currentTaskProfileRuleId)) { if (!regexRules.some((rule) => rule.id === currentTaskProfileRuleId)) {
currentTaskProfileRuleId = regexRules[0]?.id || ""; currentTaskProfileRuleId = regexRules[0]?.id || "";
} }
if (!globalRegexRules.some((rule) => rule.id === currentGlobalRegexRuleId)) {
currentGlobalRegexRuleId = globalRegexRules[0]?.id || "";
}
return { return {
settings, settings,
taskProfiles, taskProfiles,
globalTaskRegex,
globalRegexRules,
showGlobalRegex: showGlobalRegexPanel,
taskTypeOptions, taskTypeOptions,
taskType: currentTaskProfileTaskType, taskType: currentTaskProfileTaskType,
taskTabId: currentTaskProfileTabId, taskTabId: currentTaskProfileTabId,
@@ -4985,6 +5106,8 @@ function _getTaskProfileWorkspaceState(settings = _getSettings?.() || {}) {
regexRules, regexRules,
selectedRule: selectedRule:
regexRules.find((rule) => rule.id === currentTaskProfileRuleId) || null, regexRules.find((rule) => rule.id === currentTaskProfileRuleId) || null,
selectedGlobalRegexRule:
globalRegexRules.find((rule) => rule.id === currentGlobalRegexRuleId) || null,
builtinBlockDefinitions: getBuiltinBlockDefinitions(), builtinBlockDefinitions: getBuiltinBlockDefinitions(),
runtimeDebug, runtimeDebug,
}; };
@@ -5636,6 +5759,10 @@ async function _handleTaskProfileWorkspaceClick(event) {
currentTaskProfileRuleId = ""; currentTaskProfileRuleId = "";
_refreshTaskProfileWorkspace(); _refreshTaskProfileWorkspace();
return; return;
case "toggle-global-regex":
showGlobalRegexPanel = !showGlobalRegexPanel;
_refreshTaskProfileWorkspace();
return;
case "switch-task-tab": case "switch-task-tab":
currentTaskProfileTabId = currentTaskProfileTabId =
actionEl.dataset.taskTab || currentTaskProfileTabId; actionEl.dataset.taskTab || currentTaskProfileTabId;
@@ -5655,7 +5782,11 @@ async function _handleTaskProfileWorkspaceClick(event) {
_refreshTaskProfileWorkspace(); _refreshTaskProfileWorkspace();
return; return;
case "select-regex-rule": case "select-regex-rule":
if (_isGlobalRegexPanelTarget(actionEl)) {
currentGlobalRegexRuleId = actionEl.dataset.ruleId || "";
} else {
currentTaskProfileRuleId = actionEl.dataset.ruleId || ""; currentTaskProfileRuleId = actionEl.dataset.ruleId || "";
}
_refreshTaskProfileWorkspace(); _refreshTaskProfileWorkspace();
return; return;
case "add-custom-block": case "add-custom-block":
@@ -5748,20 +5879,25 @@ async function _handleTaskProfileWorkspaceClick(event) {
return; return;
} }
case "export-profile": case "export-profile":
_downloadTaskProfile(state.taskProfiles, currentTaskProfileTaskType, selectedProfile); _downloadTaskProfile(
state.taskProfiles,
currentTaskProfileTaskType,
selectedProfile,
state.globalTaskRegex,
);
return; return;
case "import-profile": case "import-profile":
document.getElementById("bme-task-profile-import")?.click(); document.getElementById("bme-task-profile-import")?.click();
return; return;
case "export-all-profiles": case "export-all-profiles":
_downloadAllTaskProfiles(state.taskProfiles); _downloadAllTaskProfiles(state.taskProfiles, state.globalTaskRegex);
return; return;
case "import-all-profiles": case "import-all-profiles":
document.getElementById("bme-task-profile-import-all")?.click(); document.getElementById("bme-task-profile-import-all")?.click();
return; return;
case "restore-all-profiles": { case "restore-all-profiles": {
const confirmed = window.confirm( const confirmed = window.confirm(
"这会将全部 6 个任务的默认预设恢复为出厂状态。已保存的自定义预设不受影响。是否继续?", "这会将全部 6 个任务的默认预设恢复为出厂状态。已保存的自定义预设不受影响,通用正则规则也不受影响。是否继续?",
); );
if (!confirmed) return; if (!confirmed) return;
const taskTypes = getTaskTypeOptions().map((t) => t.id); const taskTypes = getTaskTypeOptions().map((t) => t.id);
@@ -5815,6 +5951,33 @@ async function _handleTaskProfileWorkspaceClick(event) {
case "delete-regex-rule": case "delete-regex-rule":
_deleteRegexRule(actionEl.dataset.ruleId); _deleteRegexRule(actionEl.dataset.ruleId);
return; return;
case "add-global-regex-rule":
_updateGlobalTaskRegex((draft) => {
const localRules = Array.isArray(draft.localRules) ? draft.localRules : [];
const nextRule = createLocalRegexRule("global", {
script_name: `通用规则 ${localRules.length + 1}`,
});
draft.localRules = [...localRules, nextRule];
return { selectRuleId: nextRule.id };
});
return;
case "delete-global-regex-rule":
_deleteGlobalRegexRule(actionEl.dataset.ruleId);
return;
case "select-global-regex-rule":
currentGlobalRegexRuleId = actionEl.dataset.ruleId || "";
_refreshTaskProfileWorkspace();
return;
case "restore-global-regex-defaults": {
const confirmed = window.confirm(
"这会将通用正则规则恢复为默认配置。是否继续?",
);
if (!confirmed) return;
currentGlobalRegexRuleId = "";
_patchGlobalTaskRegex(createDefaultGlobalTaskRegex(), { refresh: true });
toastr.success("通用正则规则已恢复默认", "ST-BME");
return;
}
default: default:
return; return;
} }
@@ -5853,6 +6016,14 @@ function _renderTaskProfileWorkspace(state) {
.join("")} .join("")}
</div> </div>
<div class="bme-task-action-bar-right"> <div class="bme-task-action-bar-right">
<button
class="bme-config-secondary-btn bme-bulk-profile-btn ${state.showGlobalRegex ? "active" : ""}"
data-task-action="toggle-global-regex"
type="button"
title="打开或收起通用正则规则面板"
>
<i class="fa-solid fa-filter"></i><span>通用正则</span>
</button>
<button class="bme-config-secondary-btn bme-bulk-profile-btn bme-task-btn-danger" data-task-action="restore-all-profiles" type="button" title="恢复全部 6 个任务的默认预设"> <button class="bme-config-secondary-btn bme-bulk-profile-btn bme-task-btn-danger" data-task-action="restore-all-profiles" type="button" title="恢复全部 6 个任务的默认预设">
<i class="fa-solid fa-arrows-rotate"></i><span>恢复全部</span> <i class="fa-solid fa-arrows-rotate"></i><span>恢复全部</span>
</button> </button>
@@ -5865,6 +6036,8 @@ function _renderTaskProfileWorkspace(state) {
</div> </div>
</div> </div>
${state.showGlobalRegex ? _renderGlobalRegexPanel(state) : ""}
<div class="bme-task-master-detail"> <div class="bme-task-master-detail">
<div class="bme-task-profile-editor"> <div class="bme-task-profile-editor">
<div class="bme-task-editor-header"> <div class="bme-task-editor-header">
@@ -5921,8 +6094,6 @@ function _renderTaskProfileWorkspace(state) {
${ ${
state.taskTabId === "generation" state.taskTabId === "generation"
? _renderTaskGenerationTab(state) ? _renderTaskGenerationTab(state)
: state.taskTabId === "regex"
? _renderTaskRegexTab(state)
: state.taskTabId === "debug" : state.taskTabId === "debug"
? _renderTaskDebugTab(state) ? _renderTaskDebugTab(state)
: _renderTaskPromptTab(state) : _renderTaskPromptTab(state)
@@ -5933,7 +6104,6 @@ function _renderTaskProfileWorkspace(state) {
</div> </div>
`; `;
} }
function _renderTaskPromptTab(state) { function _renderTaskPromptTab(state) {
return ` return `
<div class="bme-task-editor-grid"> <div class="bme-task-editor-grid">
@@ -6026,30 +6196,61 @@ function _renderTaskGenerationTab(state) {
`; `;
} }
function _renderTaskRegexTab(state) { function _renderTaskRegexTab(state, options = {}) {
const regex = state.profile.regex || {}; const regex = options.regex || state.profile?.regex || {};
const regexRules = Array.isArray(options.regexRules)
? options.regexRules
: state.regexRules;
const selectedRule =
options.selectedRule === undefined ? state.selectedRule : options.selectedRule;
const normalizedStages = normalizeTaskRegexStages(regex.stages || {}); const normalizedStages = normalizeTaskRegexStages(regex.stages || {});
const selectAction = options.selectAction || "select-regex-rule";
const deleteAction = options.deleteAction || "delete-regex-rule";
const addAction = options.addAction || "add-regex-rule";
const addButtonLabel = options.addButtonLabel || "+ 新增规则";
const wrapperClassName = options.wrapperClassName
? ` ${options.wrapperClassName}`
: "";
const sectionTitle = options.sectionTitle || "复用与阶段";
const sectionSubtitle =
options.sectionSubtitle ||
"任务预设可复用酒馆正则,并叠加当前任务自己的附加规则。";
const rulesTitle = options.rulesTitle || "本地附加规则";
const rulesSubtitle =
options.rulesSubtitle ||
"本地规则只作用于当前任务预设,不会污染宿主酒馆配置。";
const emptyText = options.emptyText || "当前预设还没有本地正则规则。";
const defaultNamePrefix = options.defaultNamePrefix || "本地规则";
const headerExtraActions = options.extraHeaderActions || "";
const editorState = {
...state,
selectedRule,
};
return ` return `
<div class="bme-task-tab-body"> <div class="bme-task-tab-body${wrapperClassName}">
<div class="bme-task-regex-top"> <div class="bme-task-regex-top">
<div class="bme-config-card"> <div class="bme-config-card">
<div class="bme-config-card-head"> <div class="bme-config-card-head">
<div> <div>
<div class="bme-config-card-title">复用与阶段</div> <div class="bme-config-card-title">${_escHtml(sectionTitle)}</div>
<div class="bme-config-card-subtitle"> <div class="bme-config-card-subtitle">
任务预设可复用酒馆正则,并叠加当前任务自己的附加规则。 ${_escHtml(sectionSubtitle)}
</div> </div>
</div> </div>
<div class="bme-task-inline-actions">
<button class="bme-config-secondary-btn" data-task-action="inspect-tavern-regex" type="button"> <button class="bme-config-secondary-btn" data-task-action="inspect-tavern-regex" type="button">
查看当前复用规则 查看当前复用规则
</button> </button>
${headerExtraActions}
</div>
</div> </div>
<div class="bme-task-toggle-list"> <div class="bme-task-toggle-list">
<label class="bme-toggle-item"> <label class="bme-toggle-item">
<span class="bme-toggle-copy"> <span class="bme-toggle-copy">
<span class="bme-toggle-title">启用任务正则</span> <span class="bme-toggle-title">启用任务正则</span>
<span class="bme-toggle-desc">关闭后当前预设不执行任何任务级正则。</span> <span class="bme-toggle-desc">关闭后当前配置不执行任何任务级正则。</span>
</span> </span>
<input <input
type="checkbox" type="checkbox"
@@ -6119,24 +6320,30 @@ function _renderTaskRegexTab(state) {
<div class="bme-config-card"> <div class="bme-config-card">
<div class="bme-config-card-head"> <div class="bme-config-card-head">
<div> <div>
<div class="bme-config-card-title">本地附加规则</div> <div class="bme-config-card-title">${_escHtml(rulesTitle)}</div>
<div class="bme-config-card-subtitle"> <div class="bme-config-card-subtitle">
本地规则只作用于当前任务预设,不会污染宿主酒馆配置。 ${_escHtml(rulesSubtitle)}
</div> </div>
</div> </div>
<button class="bme-config-secondary-btn" data-task-action="add-regex-rule" type="button"> <button class="bme-config-secondary-btn" data-task-action="${_escAttr(addAction)}" type="button">
+ 新增规则 ${_escHtml(addButtonLabel)}
</button> </button>
</div> </div>
<div class="bme-task-list"> <div class="bme-task-list">
${state.regexRules.length ${regexRules.length
? state.regexRules ? regexRules
.map((rule, index) => _renderRegexRuleListItem(rule, index, state)) .map((rule, index) =>
_renderRegexRuleListItem(rule, index, editorState, {
selectAction,
deleteAction,
defaultNamePrefix,
})
)
.join("") .join("")
: ` : `
<div class="bme-task-empty"> <div class="bme-task-empty">
当前预设还没有本地正则规则。 ${_escHtml(emptyText)}
</div> </div>
`} `}
</div> </div>
@@ -6144,12 +6351,41 @@ function _renderTaskRegexTab(state) {
</div> </div>
<div class="bme-config-card"> <div class="bme-config-card">
${_renderRegexRuleEditor(state)} ${_renderRegexRuleEditor(editorState)}
</div> </div>
</div> </div>
`; `;
} }
function _renderGlobalRegexPanel(state) {
return _renderTaskRegexTab(
{
...state,
selectedRule: state.selectedGlobalRegexRule,
},
{
regex: state.globalTaskRegex,
regexRules: state.globalRegexRules,
selectedRule: state.selectedGlobalRegexRule,
addAction: "add-global-regex-rule",
selectAction: "select-global-regex-rule",
deleteAction: "delete-global-regex-rule",
addButtonLabel: "+ 新增通用规则",
wrapperClassName: "bme-global-regex-panel",
sectionTitle: "通用正则设置",
sectionSubtitle: "所有任务共享同一套任务正则开关、复用来源、执行阶段与附加规则。",
rulesTitle: "通用附加规则",
rulesSubtitle: "这里维护所有任务共享的附加规则。",
emptyText: "当前还没有通用正则规则。",
defaultNamePrefix: "通用规则",
extraHeaderActions: `
<button class="bme-config-secondary-btn bme-task-btn-danger" data-task-action="restore-global-regex-defaults" type="button">
恢复默认
</button>
`,
},
);
}
function _formatRegexReuseSourceState(source = {}) { function _formatRegexReuseSourceState(source = {}) {
const states = []; const states = [];
states.push(source.enabled ? "已启用" : "已关闭"); states.push(source.enabled ? "已启用" : "已关闭");
@@ -7238,19 +7474,23 @@ function _renderGenerationField(field, value, state = {}) {
`; `;
} }
function _renderRegexRuleListItem(rule, index, state) { function _renderRegexRuleListItem(rule, index, state, options = {}) {
const isSelected = rule.id === state.selectedRule?.id; const isSelected = rule.id === state.selectedRule?.id;
const selectAction = options.selectAction || "select-regex-rule";
const deleteAction = options.deleteAction || "delete-regex-rule";
const defaultNamePrefix = options.defaultNamePrefix || "本地规则";
return ` return `
<div class="bme-task-list-entry"> <div class="bme-task-list-entry">
<button <button
class="bme-task-list-item ${isSelected ? "active" : ""}" class="bme-task-list-item ${isSelected ? "active" : ""}"
data-task-action="select-regex-rule" data-task-action="${_escAttr(selectAction)}"
data-rule-id="${_escAttr(rule.id)}" data-rule-id="${_escAttr(rule.id)}"
type="button" type="button"
> >
<span class="bme-task-list-index">#${index + 1}</span> <span class="bme-task-list-index">#${index + 1}</span>
<span class="bme-task-list-copy"> <span class="bme-task-list-copy">
<span class="bme-task-list-title">${_escHtml(rule.script_name || `本地规则 ${index + 1}`)}</span> <span class="bme-task-list-title">${_escHtml(rule.script_name || `${defaultNamePrefix} ${index + 1}`)}</span>
<span class="bme-task-list-meta"> <span class="bme-task-list-meta">
${rule.enabled ? "启用" : "停用"} · ${_escHtml(rule.find_regex || "(未填写 find_regex)")} ${rule.enabled ? "启用" : "停用"} · ${_escHtml(rule.find_regex || "(未填写 find_regex)")}
</span> </span>
@@ -7259,7 +7499,7 @@ function _renderRegexRuleListItem(rule, index, state) {
<div class="bme-task-inline-actions"> <div class="bme-task-inline-actions">
<button <button
class="bme-config-secondary-btn bme-task-mini-btn" class="bme-config-secondary-btn bme-task-mini-btn"
data-task-action="delete-regex-rule" data-task-action="${_escAttr(deleteAction)}"
data-rule-id="${_escAttr(rule.id)}" data-rule-id="${_escAttr(rule.id)}"
type="button" type="button"
> >
@@ -7269,7 +7509,6 @@ function _renderRegexRuleListItem(rule, index, state) {
</div> </div>
`; `;
} }
function _renderRegexRuleEditor(state) { function _renderRegexRuleEditor(state) {
const rule = state.selectedRule; const rule = state.selectedRule;
if (!rule) { if (!rule) {
@@ -7627,6 +7866,110 @@ function _persistSelectedRegexRuleField(target, refresh) {
); );
} }
function _deleteGlobalRegexRule(ruleId) {
if (!ruleId) return;
_updateGlobalTaskRegex((draft) => {
const localRules = Array.isArray(draft.localRules) ? [...draft.localRules] : [];
const index = localRules.findIndex((item) => item.id === ruleId);
if (index < 0) return null;
localRules.splice(index, 1);
draft.localRules = localRules;
return {
selectRuleId:
localRules[Math.max(0, index - 1)]?.id || localRules[0]?.id || "",
};
});
}
function _persistGlobalRegexField(target, refresh) {
const key = target.dataset.regexField;
if (!key) return;
_updateGlobalTaskRegex(
(draft) => {
draft[key] =
target instanceof HTMLInputElement && target.type === "checkbox"
? Boolean(target.checked)
: target.value;
},
{ refresh },
);
}
function _persistGlobalRegexSourceField(target, refresh) {
const sourceKey = target.dataset.regexSource;
if (!sourceKey) return;
_updateGlobalTaskRegex(
(draft) => {
draft.sources = {
...(draft.sources || {}),
[sourceKey]: Boolean(target.checked),
};
},
{ refresh },
);
}
function _persistGlobalRegexStageField(target, refresh) {
const stageKey = target.dataset.regexStage;
if (!stageKey) return;
_updateGlobalTaskRegex(
(draft) => {
draft.stages = {
...(draft.stages || {}),
[stageKey]: Boolean(target.checked),
};
},
{ refresh },
);
}
function _persistSelectedGlobalRegexRuleField(target, refresh) {
_updateGlobalTaskRegex(
(draft) => {
const localRules = Array.isArray(draft.localRules) ? [...draft.localRules] : [];
const rule = localRules.find((item) => item.id === currentGlobalRegexRuleId);
if (!rule) return null;
if (target.dataset.regexRuleField) {
const field = target.dataset.regexRuleField;
if (target instanceof HTMLInputElement && target.type === "checkbox") {
rule[field] = Boolean(target.checked);
} else if (["min_depth", "max_depth"].includes(field)) {
const parsed = Number.parseInt(String(target.value || "").trim(), 10);
rule[field] = Number.isFinite(parsed) ? parsed : 0;
} else if (field === "trim_strings") {
rule[field] = String(target.value || "");
} else {
rule[field] = String(target.value || "");
}
}
if (target.dataset.regexRuleSource) {
const sourceKey = target.dataset.regexRuleSource;
rule.source = {
...(rule.source || {}),
[sourceKey]: Boolean(target.checked),
};
}
if (target.dataset.regexRuleDestination) {
const destinationKey = target.dataset.regexRuleDestination;
rule.destination = {
...(rule.destination || {}),
[destinationKey]: Boolean(target.checked),
};
}
draft.localRules = localRules;
return { selectRuleId: rule.id };
},
{ refresh },
);
}
function _updateCurrentTaskProfile(mutator, options = {}) { function _updateCurrentTaskProfile(mutator, options = {}) {
const settings = _getSettings?.() || {}; const settings = _getSettings?.() || {};
const taskProfiles = ensureTaskProfiles(settings); const taskProfiles = ensureTaskProfiles(settings);
@@ -7751,9 +8094,139 @@ function _parseTaskWorkspaceValue(target, valueType = "text") {
return String(target.value || "").trim(); return String(target.value || "").trim();
} }
function _downloadTaskProfile(taskProfiles, taskType, profile) { function _isGlobalRegexPanelTarget(target) {
return target instanceof HTMLElement && Boolean(target.closest(".bme-global-regex-panel"));
}
function _normalizeGlobalRegexDraft(regex = {}) {
const normalized = normalizeGlobalTaskRegex(regex || {}, "global");
return {
...normalized,
sources: {
...(normalized.sources || {}),
},
stages: {
...normalizeTaskRegexStages(normalized.stages || {}),
},
localRules: Array.isArray(normalized.localRules)
? normalized.localRules.map((rule, index) =>
createLocalRegexRule("global", {
...rule,
id: String(rule?.id || `global-rule-${index + 1}`),
}),
)
: [],
};
}
function _mergeImportedGlobalRegex(currentGlobalRegex = {}, importedGlobalRegex = null) {
const current = _normalizeGlobalRegexDraft(currentGlobalRegex);
if (
!importedGlobalRegex ||
typeof importedGlobalRegex !== "object" ||
Array.isArray(importedGlobalRegex)
) {
return {
globalTaskRegex: current,
mergedRuleCount: 0,
replacedConfig: false,
};
}
const imported = _normalizeGlobalRegexDraft(importedGlobalRegex);
const mergedRules = dedupeRegexRules(
[
...(Array.isArray(current.localRules) ? current.localRules : []),
...(Array.isArray(imported.localRules) ? imported.localRules : []),
],
"global",
);
return {
globalTaskRegex: {
...imported,
localRules: mergedRules,
},
mergedRuleCount: Math.max(
0,
mergedRules.length -
(Array.isArray(current.localRules) ? current.localRules.length : 0),
),
replacedConfig: true,
};
}
function _mergeProfileRegexRulesIntoGlobal(currentGlobalRegex = {}, profile = null) {
const current = _normalizeGlobalRegexDraft(currentGlobalRegex);
const legacyRules = Array.isArray(profile?.regex?.localRules)
? profile.regex.localRules
: [];
if (legacyRules.length === 0) {
return {
globalTaskRegex: current,
mergedRuleCount: 0,
profile,
clearedLegacyRules: false,
};
}
const mergedRules = dedupeRegexRules(
[...(current.localRules || []), ...legacyRules],
"global",
);
return {
globalTaskRegex: {
...current,
localRules: mergedRules,
},
mergedRuleCount: Math.max(0, mergedRules.length - current.localRules.length),
profile: {
...(profile || {}),
regex: {},
},
clearedLegacyRules: true,
};
}
function _patchGlobalTaskRegex(globalTaskRegex, options = {}) {
return _patchSettings(
{
globalTaskRegex: _normalizeGlobalRegexDraft(globalTaskRegex),
},
{
refreshTaskWorkspace: options.refresh !== false,
},
);
}
function _updateGlobalTaskRegex(mutator, options = {}) {
const settings = _getSettings?.() || {};
const draft = _normalizeGlobalRegexDraft(_cloneJson(settings.globalTaskRegex || {}));
const mutationResult = mutator?.(draft, { settings });
if (mutationResult === null) return null;
const result = mutationResult || {};
const nextRegex = _normalizeGlobalRegexDraft(result.globalTaskRegex || draft);
if (Object.prototype.hasOwnProperty.call(result, "selectRuleId")) {
currentGlobalRegexRuleId = result.selectRuleId || "";
}
return _patchSettings(
{
globalTaskRegex: nextRegex,
...(result.extraSettingsPatch || {}),
},
{
refreshTaskWorkspace:
result.refresh === undefined ? options.refresh !== false : result.refresh,
},
);
}
function _downloadTaskProfile(taskProfiles, taskType, profile, globalTaskRegex = {}) {
try { try {
const payload = serializeTaskProfile(taskProfiles, taskType, profile?.id || ""); const payload = serializeTaskProfile(taskProfiles, taskType, profile?.id || "");
payload.globalTaskRegex = _normalizeGlobalRegexDraft(globalTaskRegex || {});
const fileName = _sanitizeFileName( const fileName = _sanitizeFileName(
`st-bme-${taskType}-${profile?.name || "profile"}.json`, `st-bme-${taskType}-${profile?.name || "profile"}.json`,
); );
@@ -7774,12 +8247,11 @@ function _downloadTaskProfile(taskProfiles, taskType, profile) {
toastr.error(`预设导出失败: ${error?.message || error}`, "ST-BME"); toastr.error(`预设导出失败: ${error?.message || error}`, "ST-BME");
} }
} }
function _sanitizeFileName(fileName = "profile.json") { function _sanitizeFileName(fileName = "profile.json") {
return String(fileName || "profile.json").replace(/[<>:"/\\|?*\x00-\x1f]/g, "-"); return String(fileName || "profile.json").replace(/[<>:"/\\|?*\x00-\x1f]/g, "-");
} }
function _downloadAllTaskProfiles(taskProfiles) { function _downloadAllTaskProfiles(taskProfiles, globalTaskRegex = {}) {
try { try {
const taskTypes = getTaskTypeOptions().map((t) => t.id); const taskTypes = getTaskTypeOptions().map((t) => t.id);
const profiles = {}; const profiles = {};
@@ -7799,6 +8271,7 @@ function _downloadAllTaskProfiles(taskProfiles) {
format: "st-bme-all-task-profiles", format: "st-bme-all-task-profiles",
version: 1, version: 1,
exportedAt: new Date().toISOString(), exportedAt: new Date().toISOString(),
globalTaskRegex: _normalizeGlobalRegexDraft(globalTaskRegex || {}),
profiles, profiles,
}; };
const blob = new Blob([JSON.stringify(payload, null, 2)], { const blob = new Blob([JSON.stringify(payload, null, 2)], {
@@ -7818,7 +8291,6 @@ function _downloadAllTaskProfiles(taskProfiles) {
toastr.error(`导出全部预设失败: ${error?.message || error}`, "ST-BME"); toastr.error(`导出全部预设失败: ${error?.message || error}`, "ST-BME");
} }
} }
function _cloneJson(value) { function _cloneJson(value) {
return JSON.parse(JSON.stringify(value ?? null)); return JSON.parse(JSON.stringify(value ?? null));
} }
@@ -8356,3 +8828,5 @@ function _getNodeSnippet(node) {
function _isMobile() { function _isMobile() {
return window.innerWidth <= 768; return window.innerWidth <= 768;
} }