mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-11 23:25:50 +00:00
### 修复 - **翰林院(RAG)API Key 污染**: - 修复 `saveSettingsFromUI` 无差别遍历翰林院面板内全部 `[data-setting-key]` 输入(包含被 `profile-sync` 接管隐藏的字段),导致掩码占位符 `••••••••` 被当作真值写回 `settings.rerank.apiKey` / `settings.retrieval.apiKey`,URL / model 也被 Profile 值覆盖到 legacy 字段。修复后会跳过祖先带 `data-profile-hidden` 的输入 - `getRerankSettings` / `getEmbedRetrievalSettings` 同时加入防御性还原:识别历史污染留下的 `••••••••` 时归为空字符串,避免取消 Profile 分配后实际请求带占位符 token 被 401 ---
99 lines
3.5 KiB
JavaScript
99 lines
3.5 KiB
JavaScript
/**
|
||
* core/memory-blocks/executor.js
|
||
*
|
||
* 工作流执行器:拉 context 下的全部块 → Promise.all 并发执行 generator
|
||
* → 把每个块的结果按 placeholder 替换回模板。
|
||
*
|
||
* 核心 API:
|
||
* applyToTemplate(template, opts) 单模板进,字符串出
|
||
* applyToTemplates(templates, opts) 多模板进(数组或对象),结构同形出;
|
||
* 块只执行一次,对每个模板复用结果
|
||
* generateBlockMap(opts) 不替换,返回 { id → value } 给调用方自己玩
|
||
*
|
||
* 中断行为:opts.signal 由调用方控制,传给每个 handler;任一 handler 抛
|
||
* AbortError 时,executor 也抛 AbortError 向上传递(与现有 callAI 体系一致)。
|
||
*/
|
||
|
||
import { getHandler } from './generator-handlers.js';
|
||
import { listByContext } from './registry.js';
|
||
|
||
function escapeForRegex(s) {
|
||
return String(s).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||
}
|
||
|
||
async function runBlock(block, ctx) {
|
||
const handler = getHandler(block.generator?.type);
|
||
if (!handler) {
|
||
console.warn(`[MemoryBlocks] 未注册的 generator 类型 "${block.generator?.type}",块 ${block.id} 已跳过。`);
|
||
return null;
|
||
}
|
||
try {
|
||
const value = await handler(block, ctx);
|
||
if (value === null || value === undefined) return null;
|
||
return { block, value: String(value) };
|
||
} catch (error) {
|
||
if (error?.name === 'AbortError') throw error;
|
||
console.error(`[MemoryBlocks] 块 ${block.id} 生成失败:`, error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function substituteOne(template, results) {
|
||
if (typeof template !== 'string' || !template) return template ?? '';
|
||
let out = template;
|
||
for (const r of results) {
|
||
if (!r) continue;
|
||
const re = new RegExp(escapeForRegex(r.block.placeholder), 'g');
|
||
out = out.replace(re, r.value);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* 执行 context 下的所有块,返回 [ {block, value} | null, ... ]。
|
||
* 内部使用,applyToTemplate(s) 复用。
|
||
*/
|
||
async function executeBlocks({ context, settings, signal, extras } = {}) {
|
||
const blocks = listByContext(context);
|
||
if (blocks.length === 0) return [];
|
||
const ctx = { settings: settings ?? {}, signal, context, extras };
|
||
return await Promise.all(blocks.map(b => runBlock(b, ctx)));
|
||
}
|
||
|
||
export async function applyToTemplate(template, opts = {}) {
|
||
if (typeof template !== 'string' || !template) return template ?? '';
|
||
const results = await executeBlocks(opts);
|
||
return substituteOne(template, results);
|
||
}
|
||
|
||
/**
|
||
* 多模板批处理。templates 可以是:
|
||
* - 字符串数组 → 返回字符串数组
|
||
* - 对象 { key: template } → 返回对象 { key: replaced }
|
||
* - 字符串 → 退化为 applyToTemplate
|
||
*/
|
||
export async function applyToTemplates(templates, opts = {}) {
|
||
const results = await executeBlocks(opts);
|
||
|
||
if (typeof templates === 'string') return substituteOne(templates, results);
|
||
if (Array.isArray(templates)) return templates.map(t => substituteOne(t, results));
|
||
if (templates && typeof templates === 'object') {
|
||
const out = {};
|
||
for (const [k, v] of Object.entries(templates)) out[k] = substituteOne(v, results);
|
||
return out;
|
||
}
|
||
return templates;
|
||
}
|
||
|
||
/**
|
||
* 不替换,只把块结果汇成 Map<id, value>,调用方拿去自由组合。
|
||
*/
|
||
export async function generateBlockMap(opts = {}) {
|
||
const results = await executeBlocks(opts);
|
||
const map = new Map();
|
||
for (const r of results) {
|
||
if (r) map.set(r.block.id, r.value);
|
||
}
|
||
return map;
|
||
}
|