mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
825 lines
22 KiB
JavaScript
825 lines
22 KiB
JavaScript
// ST-BME: 任务预设与兼容迁移层
|
|
|
|
const TASK_TYPES = [
|
|
"extract",
|
|
"recall",
|
|
"compress",
|
|
"synopsis",
|
|
"reflection",
|
|
"consolidation",
|
|
];
|
|
|
|
const TASK_TYPE_META = {
|
|
extract: {
|
|
label: "提取",
|
|
description: "从当前对话批次中抽取结构化记忆。",
|
|
},
|
|
recall: {
|
|
label: "召回",
|
|
description: "根据上下文筛选最相关的记忆节点。",
|
|
},
|
|
compress: {
|
|
label: "压缩",
|
|
description: "合并并压缩高层节点内容。",
|
|
},
|
|
synopsis: {
|
|
label: "概要",
|
|
description: "生成阶段性的全局剧情提要。",
|
|
},
|
|
reflection: {
|
|
label: "反思",
|
|
description: "沉淀长期趋势、触发点与建议。",
|
|
},
|
|
consolidation: {
|
|
label: "整合",
|
|
description: "分析新旧记忆的冲突、去重与进化。",
|
|
},
|
|
};
|
|
|
|
const BUILTIN_BLOCK_DEFINITIONS = [
|
|
{
|
|
sourceKey: "taskName",
|
|
name: "任务名",
|
|
role: "system",
|
|
description: "当前任务类型标识。",
|
|
},
|
|
{
|
|
sourceKey: "systemInstruction",
|
|
name: "系统说明",
|
|
role: "system",
|
|
description: "任务系统级说明或通用约束。",
|
|
},
|
|
{
|
|
sourceKey: "outputRules",
|
|
name: "输出规则",
|
|
role: "system",
|
|
description: "用于声明 JSON 或结构化输出要求。",
|
|
},
|
|
{
|
|
sourceKey: "schema",
|
|
name: "Schema",
|
|
role: "system",
|
|
description: "节点类型或字段定义。",
|
|
},
|
|
{
|
|
sourceKey: "recentMessages",
|
|
name: "最近消息",
|
|
role: "user",
|
|
description: "最近对话上下文或历史片段。",
|
|
},
|
|
{
|
|
sourceKey: "userMessage",
|
|
name: "用户消息",
|
|
role: "user",
|
|
description: "当前用户输入内容。",
|
|
},
|
|
{
|
|
sourceKey: "candidateNodes",
|
|
name: "候选节点",
|
|
role: "user",
|
|
description: "召回或整合阶段的候选节点列表。",
|
|
},
|
|
{
|
|
sourceKey: "graphStats",
|
|
name: "图统计",
|
|
role: "user",
|
|
description: "图谱状态或当前图概览。",
|
|
},
|
|
{
|
|
sourceKey: "currentRange",
|
|
name: "当前范围",
|
|
role: "user",
|
|
description: "当前处理的消息或楼层范围。",
|
|
},
|
|
{
|
|
sourceKey: "nodeContent",
|
|
name: "节点内容",
|
|
role: "user",
|
|
description: "待压缩或待处理的节点正文。",
|
|
},
|
|
{
|
|
sourceKey: "eventSummary",
|
|
name: "事件摘要",
|
|
role: "user",
|
|
description: "近期事件线摘要。",
|
|
},
|
|
{
|
|
sourceKey: "characterSummary",
|
|
name: "角色摘要",
|
|
role: "user",
|
|
description: "近期角色状态摘要。",
|
|
},
|
|
{
|
|
sourceKey: "threadSummary",
|
|
name: "主线摘要",
|
|
role: "user",
|
|
description: "活跃主线或当前线程摘要。",
|
|
},
|
|
{
|
|
sourceKey: "contradictionSummary",
|
|
name: "矛盾摘要",
|
|
role: "user",
|
|
description: "近期冲突或矛盾信息。",
|
|
},
|
|
];
|
|
|
|
const DEFAULT_TASK_PROFILE_VERSION = 1;
|
|
const DEFAULT_PROFILE_ID = "default";
|
|
|
|
const LEGACY_PROMPT_FIELD_MAP = {
|
|
extract: "extractPrompt",
|
|
recall: "recallPrompt",
|
|
compress: "compressPrompt",
|
|
synopsis: "synopsisPrompt",
|
|
reflection: "reflectionPrompt",
|
|
consolidation: "consolidationPrompt",
|
|
};
|
|
|
|
function nowIso() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function cloneJson(value) {
|
|
return JSON.parse(JSON.stringify(value ?? null));
|
|
}
|
|
|
|
function createUniqueId(prefix = "profile") {
|
|
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
}
|
|
|
|
function normalizeRole(role) {
|
|
const value = String(role || "system").trim().toLowerCase();
|
|
if (["system", "user", "assistant"].includes(value)) {
|
|
return value;
|
|
}
|
|
return "system";
|
|
}
|
|
|
|
function normalizeInjectionMode(mode) {
|
|
const value = String(mode || "append").trim().toLowerCase();
|
|
if (["append", "prepend", "relative"].includes(value)) {
|
|
return value;
|
|
}
|
|
return "append";
|
|
}
|
|
|
|
function normalizePromptBlock(taskType, block = {}, index = 0) {
|
|
const fallbackType = String(block?.type || "custom");
|
|
return {
|
|
id: String(block?.id || createPromptBlockId(taskType)),
|
|
name: typeof block?.name === "string" ? block.name : "",
|
|
type: fallbackType,
|
|
enabled: block?.enabled !== false,
|
|
role: normalizeRole(block?.role),
|
|
sourceKey: typeof block?.sourceKey === "string" ? block.sourceKey : "",
|
|
sourceField: typeof block?.sourceField === "string" ? block.sourceField : "",
|
|
content: typeof block?.content === "string" ? block.content : "",
|
|
injectionMode: normalizeInjectionMode(block?.injectionMode),
|
|
order: Number.isFinite(Number(block?.order)) ? Number(block.order) : index,
|
|
};
|
|
}
|
|
|
|
function normalizeRegexLocalRule(rule = {}, taskType = "task", index = 0) {
|
|
return {
|
|
id: String(rule?.id || createRegexRuleId(taskType)),
|
|
script_name: String(
|
|
rule?.script_name || rule?.scriptName || `本地规则 ${index + 1}`,
|
|
),
|
|
enabled: rule?.enabled !== false,
|
|
find_regex: String(rule?.find_regex || rule?.findRegex || ""),
|
|
replace_string: String(
|
|
rule?.replace_string ?? rule?.replaceString ?? "",
|
|
),
|
|
trim_strings: Array.isArray(rule?.trim_strings)
|
|
? rule.trim_strings.map((item) => String(item || ""))
|
|
: typeof rule?.trim_strings === "string"
|
|
? rule.trim_strings
|
|
: "",
|
|
source: {
|
|
user_input:
|
|
rule?.source?.user_input === undefined
|
|
? true
|
|
: Boolean(rule.source.user_input),
|
|
ai_output:
|
|
rule?.source?.ai_output === undefined
|
|
? true
|
|
: Boolean(rule.source.ai_output),
|
|
},
|
|
destination: {
|
|
prompt:
|
|
rule?.destination?.prompt === undefined
|
|
? true
|
|
: Boolean(rule.destination.prompt),
|
|
display: Boolean(rule?.destination?.display),
|
|
},
|
|
min_depth: Number.isFinite(Number(rule?.min_depth))
|
|
? Number(rule.min_depth)
|
|
: 0,
|
|
max_depth: Number.isFinite(Number(rule?.max_depth))
|
|
? Number(rule.max_depth)
|
|
: 9999,
|
|
};
|
|
}
|
|
|
|
function normalizeTaskProfilesState(taskProfiles = {}) {
|
|
return ensureTaskProfiles({ taskProfiles });
|
|
}
|
|
|
|
function getDefaultProfileDescription(taskType) {
|
|
return TASK_TYPE_META[taskType]?.description || "";
|
|
}
|
|
|
|
export function createPromptBlockId(taskType = "task") {
|
|
return createUniqueId(`${taskType}-block`);
|
|
}
|
|
|
|
export function createRegexRuleId(taskType = "task") {
|
|
return createUniqueId(`${taskType}-rule`);
|
|
}
|
|
|
|
export function createProfileId(taskType = "task") {
|
|
return createUniqueId(`${taskType}-profile`);
|
|
}
|
|
|
|
export function createDefaultTaskProfiles() {
|
|
const profiles = {};
|
|
for (const taskType of TASK_TYPES) {
|
|
profiles[taskType] = {
|
|
activeProfileId: DEFAULT_PROFILE_ID,
|
|
profiles: [createDefaultTaskProfile(taskType)],
|
|
};
|
|
}
|
|
return profiles;
|
|
}
|
|
|
|
export function createDefaultTaskProfile(taskType) {
|
|
const legacyPromptField = LEGACY_PROMPT_FIELD_MAP[taskType];
|
|
return {
|
|
id: DEFAULT_PROFILE_ID,
|
|
name: "默认预设",
|
|
taskType,
|
|
version: DEFAULT_TASK_PROFILE_VERSION,
|
|
builtin: true,
|
|
enabled: true,
|
|
description: getDefaultProfileDescription(taskType),
|
|
promptMode: "legacy-compatible",
|
|
updatedAt: nowIso(),
|
|
blocks: [
|
|
{
|
|
id: "legacy-system",
|
|
name: "兼容主提示词",
|
|
type: "legacyPrompt",
|
|
enabled: true,
|
|
role: "system",
|
|
sourceField: legacyPromptField,
|
|
sourceKey: "",
|
|
content: "",
|
|
injectionMode: "append",
|
|
order: 0,
|
|
},
|
|
],
|
|
generation: {
|
|
max_context_tokens: null,
|
|
max_completion_tokens: null,
|
|
reply_count: null,
|
|
stream: false,
|
|
temperature: null,
|
|
top_p: null,
|
|
top_k: null,
|
|
top_a: null,
|
|
min_p: null,
|
|
seed: null,
|
|
frequency_penalty: null,
|
|
presence_penalty: null,
|
|
repetition_penalty: null,
|
|
squash_system_messages: null,
|
|
reasoning_effort: null,
|
|
request_thoughts: null,
|
|
enable_function_calling: null,
|
|
enable_web_search: null,
|
|
character_name_prefix: null,
|
|
wrap_user_messages_in_quotes: null,
|
|
},
|
|
regex: {
|
|
enabled: false,
|
|
inheritStRegex: true,
|
|
sources: {
|
|
global: true,
|
|
preset: true,
|
|
character: true,
|
|
},
|
|
stages: {
|
|
finalPrompt: true,
|
|
"input.userMessage": false,
|
|
"input.recentMessages": false,
|
|
"input.candidateText": false,
|
|
"input.finalPrompt": false,
|
|
rawResponse: false,
|
|
beforeParse: false,
|
|
"output.rawResponse": false,
|
|
"output.beforeParse": false,
|
|
},
|
|
localRules: [],
|
|
},
|
|
metadata: {
|
|
migratedFromLegacy: false,
|
|
legacyPromptField,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function createCustomPromptBlock(taskType, overrides = {}) {
|
|
return normalizePromptBlock(taskType, {
|
|
id: createPromptBlockId(taskType),
|
|
name: "自定义块",
|
|
type: "custom",
|
|
enabled: true,
|
|
role: "system",
|
|
sourceKey: "",
|
|
sourceField: "",
|
|
content: "",
|
|
injectionMode: "append",
|
|
order: 0,
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
export function createBuiltinPromptBlock(taskType, sourceKey = "", overrides = {}) {
|
|
const definition =
|
|
BUILTIN_BLOCK_DEFINITIONS.find((item) => item.sourceKey === sourceKey) ||
|
|
BUILTIN_BLOCK_DEFINITIONS[0];
|
|
return normalizePromptBlock(taskType, {
|
|
id: createPromptBlockId(taskType),
|
|
name: definition?.name || "内置块",
|
|
type: "builtin",
|
|
enabled: true,
|
|
role: definition?.role || "system",
|
|
sourceKey: definition?.sourceKey || sourceKey,
|
|
sourceField: "",
|
|
content: "",
|
|
injectionMode: "append",
|
|
order: 0,
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
export function createLocalRegexRule(taskType, overrides = {}) {
|
|
return normalizeRegexLocalRule(
|
|
{
|
|
id: createRegexRuleId(taskType),
|
|
script_name: "本地规则",
|
|
enabled: true,
|
|
find_regex: "",
|
|
replace_string: "",
|
|
trim_strings: "",
|
|
source: {
|
|
user_input: true,
|
|
ai_output: true,
|
|
},
|
|
destination: {
|
|
prompt: true,
|
|
display: false,
|
|
},
|
|
min_depth: 0,
|
|
max_depth: 9999,
|
|
...overrides,
|
|
},
|
|
taskType,
|
|
0,
|
|
);
|
|
}
|
|
|
|
export function ensureTaskProfiles(settings = {}) {
|
|
const existing = settings.taskProfiles;
|
|
const defaults = createDefaultTaskProfiles();
|
|
|
|
if (!existing || typeof existing !== "object") {
|
|
return defaults;
|
|
}
|
|
|
|
const normalized = {};
|
|
for (const taskType of TASK_TYPES) {
|
|
const current = existing[taskType] || {};
|
|
const defaultBucket = defaults[taskType];
|
|
const profiles =
|
|
Array.isArray(current.profiles) && current.profiles.length > 0
|
|
? current.profiles.map((profile) =>
|
|
normalizeTaskProfile(taskType, profile, settings),
|
|
)
|
|
: defaultBucket.profiles;
|
|
|
|
const activeProfileId =
|
|
typeof current.activeProfileId === "string" &&
|
|
profiles.some((profile) => profile.id === current.activeProfileId)
|
|
? current.activeProfileId
|
|
: profiles[0]?.id || DEFAULT_PROFILE_ID;
|
|
|
|
normalized[taskType] = {
|
|
activeProfileId,
|
|
profiles,
|
|
};
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
export function normalizeTaskProfile(taskType, profile = {}, settings = {}) {
|
|
const base = createDefaultTaskProfile(taskType);
|
|
const legacyPromptField = LEGACY_PROMPT_FIELD_MAP[taskType];
|
|
const blocks =
|
|
Array.isArray(profile.blocks) && profile.blocks.length > 0
|
|
? profile.blocks.map((block, index) =>
|
|
normalizePromptBlock(taskType, block, index),
|
|
)
|
|
: base.blocks.map((block, index) =>
|
|
normalizePromptBlock(taskType, block, index),
|
|
);
|
|
|
|
return {
|
|
...base,
|
|
...profile,
|
|
id: String(profile?.id || base.id),
|
|
name: String(profile?.name || base.name),
|
|
taskType,
|
|
builtin:
|
|
profile?.builtin === undefined
|
|
? profile?.id === DEFAULT_PROFILE_ID
|
|
: Boolean(profile?.builtin),
|
|
enabled: profile?.enabled !== false,
|
|
description:
|
|
typeof profile?.description === "string"
|
|
? profile.description
|
|
: base.description,
|
|
promptMode: String(profile?.promptMode || base.promptMode),
|
|
updatedAt:
|
|
typeof profile?.updatedAt === "string" && profile.updatedAt
|
|
? profile.updatedAt
|
|
: nowIso(),
|
|
blocks,
|
|
generation: {
|
|
...base.generation,
|
|
...(profile?.generation || {}),
|
|
},
|
|
regex: {
|
|
...base.regex,
|
|
...(profile?.regex || {}),
|
|
sources: {
|
|
...base.regex.sources,
|
|
...(profile?.regex?.sources || {}),
|
|
},
|
|
stages: {
|
|
...base.regex.stages,
|
|
...(profile?.regex?.stages || {}),
|
|
},
|
|
localRules: Array.isArray(profile?.regex?.localRules)
|
|
? profile.regex.localRules.map((rule, index) =>
|
|
normalizeRegexLocalRule(rule, taskType, index),
|
|
)
|
|
: [],
|
|
},
|
|
metadata: {
|
|
...base.metadata,
|
|
...(profile?.metadata || {}),
|
|
legacyPromptField,
|
|
legacyPromptSnapshot:
|
|
typeof settings?.[legacyPromptField] === "string"
|
|
? settings[legacyPromptField]
|
|
: "",
|
|
},
|
|
};
|
|
}
|
|
|
|
export function migrateLegacyTaskProfiles(settings = {}) {
|
|
const alreadyMigrated =
|
|
Number(settings.taskProfilesVersion) >= DEFAULT_TASK_PROFILE_VERSION;
|
|
const nextTaskProfiles = ensureTaskProfiles(settings);
|
|
let changed = !alreadyMigrated;
|
|
|
|
for (const taskType of TASK_TYPES) {
|
|
const legacyField = LEGACY_PROMPT_FIELD_MAP[taskType];
|
|
const legacyPrompt =
|
|
typeof settings?.[legacyField] === "string" ? settings[legacyField] : "";
|
|
const bucket = nextTaskProfiles[taskType];
|
|
if (!bucket || !Array.isArray(bucket.profiles) || bucket.profiles.length === 0) {
|
|
nextTaskProfiles[taskType] = {
|
|
activeProfileId: DEFAULT_PROFILE_ID,
|
|
profiles: [createDefaultTaskProfile(taskType)],
|
|
};
|
|
changed = true;
|
|
continue;
|
|
}
|
|
|
|
const firstProfile = bucket.profiles[0];
|
|
if (
|
|
firstProfile?.id === DEFAULT_PROFILE_ID &&
|
|
firstProfile?.metadata?.migratedFromLegacy !== true &&
|
|
legacyPrompt
|
|
) {
|
|
firstProfile.metadata = {
|
|
...(firstProfile.metadata || {}),
|
|
migratedFromLegacy: true,
|
|
legacyPromptField: legacyField,
|
|
legacyPromptSnapshot: legacyPrompt,
|
|
};
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return {
|
|
changed,
|
|
taskProfilesVersion: DEFAULT_TASK_PROFILE_VERSION,
|
|
taskProfiles: nextTaskProfiles,
|
|
};
|
|
}
|
|
|
|
export function getActiveTaskProfile(settings = {}, taskType) {
|
|
const taskProfiles = ensureTaskProfiles(settings);
|
|
const bucket = taskProfiles?.[taskType];
|
|
if (!bucket?.profiles?.length) {
|
|
return createDefaultTaskProfile(taskType);
|
|
}
|
|
return (
|
|
bucket.profiles.find((profile) => profile.id === bucket.activeProfileId) ||
|
|
bucket.profiles[0]
|
|
);
|
|
}
|
|
|
|
export function getLegacyPromptForTask(settings = {}, taskType) {
|
|
const field = LEGACY_PROMPT_FIELD_MAP[taskType];
|
|
return typeof settings?.[field] === "string" ? settings[field] : "";
|
|
}
|
|
|
|
export function getLegacyPromptFieldForTask(taskType) {
|
|
return LEGACY_PROMPT_FIELD_MAP[taskType] || "";
|
|
}
|
|
|
|
export function getTaskTypeMeta(taskType) {
|
|
return {
|
|
id: taskType,
|
|
label: TASK_TYPE_META[taskType]?.label || taskType,
|
|
description: TASK_TYPE_META[taskType]?.description || "",
|
|
};
|
|
}
|
|
|
|
export function getTaskTypeOptions() {
|
|
return TASK_TYPES.map((taskType) => getTaskTypeMeta(taskType));
|
|
}
|
|
|
|
export function getTaskTypes() {
|
|
return [...TASK_TYPES];
|
|
}
|
|
|
|
export function getBuiltinBlockDefinitions() {
|
|
return BUILTIN_BLOCK_DEFINITIONS.map((definition) => ({ ...definition }));
|
|
}
|
|
|
|
export function cloneTaskProfile(profile = {}, options = {}) {
|
|
const taskType = String(options.taskType || profile.taskType || "extract");
|
|
const cloned = normalizeTaskProfile(taskType, cloneJson(profile));
|
|
const nextName = String(options.name || "").trim() || `${cloned.name} 副本`;
|
|
const nextProfile = {
|
|
...cloned,
|
|
id: createProfileId(taskType),
|
|
taskType,
|
|
name: nextName,
|
|
builtin: false,
|
|
updatedAt: nowIso(),
|
|
blocks: (Array.isArray(cloned.blocks) ? cloned.blocks : []).map(
|
|
(block, index) =>
|
|
normalizePromptBlock(
|
|
taskType,
|
|
{
|
|
...block,
|
|
id: createPromptBlockId(taskType),
|
|
order: index,
|
|
},
|
|
index,
|
|
),
|
|
),
|
|
regex: {
|
|
...(cloned.regex || {}),
|
|
localRules: Array.isArray(cloned?.regex?.localRules)
|
|
? cloned.regex.localRules.map((rule, index) =>
|
|
normalizeRegexLocalRule(
|
|
{
|
|
...rule,
|
|
id: createRegexRuleId(taskType),
|
|
},
|
|
taskType,
|
|
index,
|
|
),
|
|
)
|
|
: [],
|
|
},
|
|
metadata: {
|
|
...(cloned.metadata || {}),
|
|
clonedFromId: cloned.id || "",
|
|
clonedAt: nowIso(),
|
|
},
|
|
};
|
|
|
|
return nextProfile;
|
|
}
|
|
|
|
export function upsertTaskProfile(
|
|
taskProfiles = {},
|
|
taskType,
|
|
profile,
|
|
options = {},
|
|
) {
|
|
const normalizedState = normalizeTaskProfilesState(taskProfiles);
|
|
const bucket = normalizedState[taskType] || {
|
|
activeProfileId: DEFAULT_PROFILE_ID,
|
|
profiles: [],
|
|
};
|
|
const normalizedProfile = normalizeTaskProfile(taskType, {
|
|
...(profile || {}),
|
|
updatedAt: nowIso(),
|
|
});
|
|
const nextProfiles = [...bucket.profiles];
|
|
const existingIndex = nextProfiles.findIndex(
|
|
(item) => item.id === normalizedProfile.id,
|
|
);
|
|
|
|
if (existingIndex >= 0) {
|
|
nextProfiles.splice(existingIndex, 1, normalizedProfile);
|
|
} else if (normalizedProfile.id === DEFAULT_PROFILE_ID) {
|
|
nextProfiles.unshift(normalizedProfile);
|
|
} else {
|
|
nextProfiles.push(normalizedProfile);
|
|
}
|
|
|
|
normalizedState[taskType] = {
|
|
activeProfileId:
|
|
options.setActive === false
|
|
? bucket.activeProfileId
|
|
: normalizedProfile.id,
|
|
profiles: nextProfiles.map((item, index) =>
|
|
normalizeTaskProfile(taskType, {
|
|
...item,
|
|
blocks: Array.isArray(item.blocks)
|
|
? item.blocks.map((block, blockIndex) => ({
|
|
...block,
|
|
order: Number.isFinite(Number(block?.order))
|
|
? Number(block.order)
|
|
: blockIndex,
|
|
}))
|
|
: [],
|
|
builtin: item.id === DEFAULT_PROFILE_ID ? true : item.builtin,
|
|
updatedAt:
|
|
item.id === normalizedProfile.id ? normalizedProfile.updatedAt : item.updatedAt,
|
|
}),
|
|
),
|
|
};
|
|
|
|
return normalizedState;
|
|
}
|
|
|
|
export function setActiveTaskProfileId(taskProfiles = {}, taskType, profileId) {
|
|
const normalizedState = normalizeTaskProfilesState(taskProfiles);
|
|
const bucket = normalizedState[taskType];
|
|
if (!bucket?.profiles?.some((profile) => profile.id === profileId)) {
|
|
return normalizedState;
|
|
}
|
|
normalizedState[taskType] = {
|
|
...bucket,
|
|
activeProfileId: profileId,
|
|
};
|
|
return normalizedState;
|
|
}
|
|
|
|
export function deleteTaskProfile(taskProfiles = {}, taskType, profileId) {
|
|
if (!profileId) return normalizeTaskProfilesState(taskProfiles);
|
|
|
|
const normalizedState = normalizeTaskProfilesState(taskProfiles);
|
|
const bucket = normalizedState[taskType];
|
|
if (!bucket?.profiles?.length) {
|
|
return normalizedState;
|
|
}
|
|
|
|
const remaining = bucket.profiles.filter((profile) => profile.id !== profileId);
|
|
if (remaining.length === 0) {
|
|
normalizedState[taskType] = {
|
|
activeProfileId: DEFAULT_PROFILE_ID,
|
|
profiles: [createDefaultTaskProfile(taskType)],
|
|
};
|
|
return normalizedState;
|
|
}
|
|
|
|
normalizedState[taskType] = {
|
|
activeProfileId: remaining.some(
|
|
(profile) => profile.id === bucket.activeProfileId,
|
|
)
|
|
? bucket.activeProfileId
|
|
: remaining[0].id,
|
|
profiles: remaining,
|
|
};
|
|
return normalizedState;
|
|
}
|
|
|
|
export function restoreDefaultTaskProfile(taskProfiles = {}, taskType) {
|
|
const normalizedState = normalizeTaskProfilesState(taskProfiles);
|
|
const bucket = normalizedState[taskType] || {
|
|
activeProfileId: DEFAULT_PROFILE_ID,
|
|
profiles: [],
|
|
};
|
|
const defaultProfile = createDefaultTaskProfile(taskType);
|
|
const remaining = (bucket.profiles || []).filter(
|
|
(profile) => profile.id !== DEFAULT_PROFILE_ID,
|
|
);
|
|
|
|
normalizedState[taskType] = {
|
|
activeProfileId: DEFAULT_PROFILE_ID,
|
|
profiles: [defaultProfile, ...remaining],
|
|
};
|
|
|
|
return normalizedState;
|
|
}
|
|
|
|
export function exportTaskProfile(taskProfiles = {}, taskType, profileId = "") {
|
|
const normalizedState = normalizeTaskProfilesState(taskProfiles);
|
|
const bucket = normalizedState[taskType];
|
|
const profile =
|
|
bucket?.profiles?.find((item) => item.id === profileId) ||
|
|
bucket?.profiles?.[0];
|
|
|
|
if (!profile) {
|
|
throw new Error(`Task profile not found: ${taskType}/${profileId}`);
|
|
}
|
|
|
|
return {
|
|
format: "st-bme-task-profile",
|
|
version: DEFAULT_TASK_PROFILE_VERSION,
|
|
taskType,
|
|
exportedAt: nowIso(),
|
|
profile: cloneJson(profile),
|
|
};
|
|
}
|
|
|
|
export function importTaskProfile(
|
|
taskProfiles = {},
|
|
rawInput,
|
|
preferredTaskType = "",
|
|
) {
|
|
const parsed =
|
|
typeof rawInput === "string" ? JSON.parse(rawInput) : cloneJson(rawInput);
|
|
const candidate =
|
|
parsed?.profile && typeof parsed.profile === "object"
|
|
? parsed.profile
|
|
: parsed;
|
|
const importedTaskType = String(
|
|
preferredTaskType || parsed?.taskType || candidate?.taskType || "",
|
|
).trim();
|
|
|
|
if (!TASK_TYPES.includes(importedTaskType)) {
|
|
throw new Error(`Unsupported task type: ${importedTaskType || "(empty)"}`);
|
|
}
|
|
|
|
const bucket = normalizeTaskProfilesState(taskProfiles)[importedTaskType];
|
|
const baseName = String(candidate?.name || "").trim() || "导入预设";
|
|
const importedProfile = normalizeTaskProfile(importedTaskType, {
|
|
...candidate,
|
|
id: createProfileId(importedTaskType),
|
|
taskType: importedTaskType,
|
|
name: baseName,
|
|
builtin: false,
|
|
updatedAt: nowIso(),
|
|
metadata: {
|
|
...(candidate?.metadata || {}),
|
|
importedAt: nowIso(),
|
|
},
|
|
blocks: Array.isArray(candidate?.blocks) && candidate.blocks.length > 0
|
|
? candidate.blocks.map((block, index) => ({
|
|
...block,
|
|
id: createPromptBlockId(importedTaskType),
|
|
order: index,
|
|
}))
|
|
: createDefaultTaskProfile(importedTaskType).blocks,
|
|
regex: {
|
|
...(candidate?.regex || {}),
|
|
localRules: Array.isArray(candidate?.regex?.localRules)
|
|
? candidate.regex.localRules.map((rule) => ({
|
|
...rule,
|
|
id: createRegexRuleId(importedTaskType),
|
|
}))
|
|
: [],
|
|
},
|
|
});
|
|
|
|
const nextTaskProfiles = upsertTaskProfile(
|
|
{
|
|
...normalizeTaskProfilesState(taskProfiles),
|
|
[importedTaskType]: bucket,
|
|
},
|
|
importedTaskType,
|
|
importedProfile,
|
|
{ setActive: true },
|
|
);
|
|
|
|
return {
|
|
taskProfiles: nextTaskProfiles,
|
|
taskType: importedTaskType,
|
|
profile: importedProfile,
|
|
};
|
|
}
|