release: v2.2.4 [2026-05-30 12:16:52]

### 新功能
- **Function Call 填表**:
  - FC 首次请求时对 DeepSeek 系模型自动附加 `thinking: { type: "disabled" }`,避免思考模式与 tool_choice 冲突
  - 操作列表为空时在日志面板输出原始响应 JSON,便于区分"AI 判断无需变更"、"格式校验全部不通过"和"JSON 解析失败"三种情况
### 修复
- **剧情优化**:移除剧情优化页面遗留的 Jqyh 直连配置字段(URL / Key / Model),统一走 API 连接配置功能分配槽位
- **表格**:
  - 补全 `batch-filling-threshold` 批处理阈值的持久化绑定(页面刷新后不再还原为默认值 30)
  - 修复分步填表并发锁与 async/await 时序问题
  - 修复外层多余 `try...finally` 导致的插件加载报错
- **Rerank**:
  - 修复选择连接配置后报"API Key 未配置"的问题(`apiMode` 现从设置读取而非硬编码 `custom`)
  - 补全 `hly-rerank-api-mode` 加载绑定及默认值
- **翰林院 RAG**:补全 `priorityRetrieval.sources` 各来源条目的缺失键,修复设置面板回填 TypeError
This commit is contained in:
Jenkins CI
2026-05-30 12:16:52 +08:00
parent a8c3ad9027
commit d6b3b00c86
7 changed files with 90 additions and 72 deletions

View File

@@ -1021,7 +1021,7 @@ export async function rollbackAndRefill() {
const lastMessage = context.chat[context.chat.length - 1];
try {
await fillWithSecondaryApi(lastMessage, true);
await fillWithSecondaryApi(lastMessage, true, { targetMessage: lastMessage });
log('回退并重新填表操作完成。', 'success');
} catch (error) {
log(`回退重填过程中发生错误: ${error.message}`, 'error');

View File

@@ -46,8 +46,8 @@ async function commitSecondaryFillResult(rawContent, targetMessages) {
const lastProcessedMsg = targetMessages[targetMessages.length - 1].msg;
for (const target of targetMessages) {
if (!target.msg.metadata) target.msg.metadata = {};
target.msg.metadata.Amily2_Process_Hash = target.hash;
if (!target.msg.extra) target.msg.extra = {};
target.msg.extra.amily2_process_hash = target.hash;
}
if (saveStateToMessage(memoryState, lastProcessedMsg)) {
@@ -111,7 +111,7 @@ async function getWorldBookContext() {
return content.trim() ? `<世界书>\n${content.trim()}\n</世界书>` : '';
}
export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
export async function fillWithSecondaryApi(latestMessage, forceRun = false, opts = {}) {
if (secondaryFillerRunning) {
log('分步填表正在进行中,跳过本次触发。', 'warn');
return;
@@ -128,7 +128,7 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
}
secondaryFillerDebounceTimer = setTimeout(() => {
secondaryFillerDebounceTimer = null;
fillWithSecondaryApi(latestMessage, forceRun);
fillWithSecondaryApi(latestMessage, forceRun, opts);
}, delay);
console.log(`[Amily2-副API] 分步填表已按防抖延迟 ${delay}ms 调度。`);
return;
@@ -166,29 +166,18 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
try {
const bufferSize = parseInt(settings.secondary_filler_buffer || 0, 10);
const batchSize = parseInt(settings.secondary_filler_batch || 0, 10);
const batchSize = parseInt(settings.secondary_filler_batch || 0, 10);
const contextLimit = parseInt(settings.secondary_filler_context || 2, 10);
// 【V1.7.7 修复】限制最大回溯深度,防止更新后无限填补旧历史
// 响应用户反馈:扫描深度 = 上下文 + 填表批次 + 保留楼层 + 冗余量(10)
// redundancy (冗余量): 额外扫描 10 层作为安全缓冲,防止因消息索引计算偏差导致漏掉边缘消息
// 扫描深度 = 上下文 + 填表批次 + 冗余量(10)
// bufferSize保留楼层仅用于限定尾部边界 validEndIndex
// 不再回流到扫描起点,避免重复影响范围
const redundancy = 10;
const maxScanDepth = contextLimit + batchSize + bufferSize + redundancy;
const maxScanDepth = contextLimit + batchSize + redundancy;
const chat = context.chat;
const totalMessages = chat.length;
const validEndIndex = totalMessages - 1 - bufferSize;
// 计算扫描的起始索引不小于0
const scanStartIndex = Math.max(0, validEndIndex - maxScanDepth);
if (validEndIndex < 0) {
console.log(`[Amily2-副API] 消息数量不足以超出保留区(${bufferSize}),跳过。`);
return;
}
let targetMessages = [];
let needsProcessing = false;
const getContentHash = (content) => {
let hash = 0, i, chr;
@@ -196,45 +185,74 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
for (i = 0; i < content.length; i++) {
chr = content.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
hash |= 0;
}
return hash;
};
// 【修复】改为正向扫描,优先处理最老的未处理消息,防止遗留消息被挤出扫描区
for (let i = scanStartIndex; i <= validEndIndex; i++) {
const msg = chat[i];
if (msg.is_user) continue;
let targetMessages = [];
const currentHash = getContentHash(msg.mes);
const savedHash = msg.metadata?.Amily2_Process_Hash;
const isUnprocessed = !savedHash;
const isChanged = savedHash && savedHash !== currentHash;
if (isUnprocessed || isChanged) {
targetMessages.push({ index: i, msg: msg, hash: currentHash });
if (batchSize > 0 && targetMessages.length >= batchSize) {
needsProcessing = true;
break;
}
}
}
if (targetMessages.length === 0) {
console.log("[Amily2-副API] 没有发现需要处理的消息。");
return;
}
if (batchSize > 0) {
if (targetMessages.length < batchSize) {
console.log(`[Amily2-副API] 批量模式: 当前累积 ${targetMessages.length}/${batchSize} 条未处理消息,暂不触发。`);
// 【SWIPED 旁路】swipe 后强制处理刚切出来的最新消息:
// 跳过扫描 / bufferSize / batchSize 累积逻辑,直接锁定目标
if (opts.targetMessage) {
const targetIndex = chat.indexOf(opts.targetMessage);
if (targetIndex < 0) {
console.log("[Amily2-副API] 旁路目标消息不在聊天列表中,跳过。");
return;
}
if (opts.targetMessage.is_user) {
console.log("[Amily2-副API] 旁路目标是用户消息,跳过。");
return;
}
targetMessages.push({
index: targetIndex,
msg: opts.targetMessage,
hash: getContentHash(opts.targetMessage.mes),
});
} else {
targetMessages = [targetMessages[targetMessages.length - 1]];
// 常规扫描路径
const validEndIndex = totalMessages - 1 - bufferSize;
const scanStartIndex = Math.max(0, validEndIndex - maxScanDepth);
if (validEndIndex < 0) {
console.log(`[Amily2-副API] 消息数量不足以超出保留区(${bufferSize}),跳过。`);
return;
}
// 【修复】改为正向扫描,优先处理最老的未处理消息,防止遗留消息被挤出扫描区
for (let i = scanStartIndex; i <= validEndIndex; i++) {
const msg = chat[i];
if (msg.is_user) continue;
const currentHash = getContentHash(msg.mes);
const savedHash = msg.extra?.amily2_process_hash;
const isUnprocessed = !savedHash;
const isChanged = savedHash && savedHash !== currentHash;
if (isUnprocessed || isChanged) {
targetMessages.push({ index: i, msg: msg, hash: currentHash });
if (batchSize > 0 && targetMessages.length >= batchSize) {
break;
}
}
}
if (targetMessages.length === 0) {
console.log("[Amily2-副API] 没有发现需要处理的消息。");
return;
}
if (batchSize > 0) {
if (targetMessages.length < batchSize) {
console.log(`[Amily2-副API] 批量模式: 当前累积 ${targetMessages.length}/${batchSize} 条未处理消息,暂不触发。`);
return;
}
} else {
targetMessages = [targetMessages[targetMessages.length - 1]];
}
}
console.log(`[Amily2-副API] 触发填表: 处理 ${targetMessages.length} 条消息。索引范围: ${targetMessages[0].index} - ${targetMessages[targetMessages.length-1].index}`);
@@ -379,8 +397,8 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
const rangeLabel = `${targetMessages[0].index + 1} - ${targetMessages[targetMessages.length - 1].index + 1}`;
console.warn(`[Amily2-副API] 响应未包含 <Amily2Edit> 指令块(楼层 ${rangeLabel}),弹出检查窗口等待用户处理。`);
toastr.warning(`分步填表(楼层 ${rangeLabel})的响应缺少 <Amily2Edit> 指令块,请在弹窗中处理。`, 'Amily2-分步填表');
if (latestMessage && latestMessage.metadata) {
delete latestMessage.metadata.Amily2_Retry_Count;
if (latestMessage && latestMessage.extra) {
delete latestMessage.extra.amily2_retry_count;
}
showTableFillReviewModal(rawContent, {
title: `分步填表响应检查 - 楼层 ${rangeLabel}`,
@@ -408,11 +426,11 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
}
},
onRetry: () => {
if (latestMessage && latestMessage.metadata) {
delete latestMessage.metadata.Amily2_Retry_Count;
if (latestMessage && latestMessage.extra) {
delete latestMessage.extra.amily2_retry_count;
}
toastr.info('将重新执行分步填表...', 'Amily2-分步填表');
setTimeout(() => fillWithSecondaryApi(latestMessage, forceRun), 300);
setTimeout(() => fillWithSecondaryApi(latestMessage, forceRun, opts), 300);
},
onCancel: () => {
toastr.info('已取消本次分步填表。', 'Amily2-分步填表');
@@ -430,29 +448,29 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
// 【新增】自定义重试逻辑
const maxRetries = parseInt(settings.secondary_filler_max_retries || 0, 10);
const currentRetryCount = latestMessage?.metadata?.Amily2_Retry_Count || 0;
const currentRetryCount = latestMessage?.extra?.amily2_retry_count || 0;
if (currentRetryCount < maxRetries) {
const nextRetryCount = currentRetryCount + 1;
console.log(`[Amily2-副API] 准备进行第 ${nextRetryCount}/${maxRetries} 次重试...`);
toastr.warning(`副API填表失败: ${error.message}。将在3秒后进行第 ${nextRetryCount} 次重试...`, "自动重试");
// 记录重试次数到最新消息的 metadata 中,以便跨调用传递状态
// 记录重试次数到最新消息的 extra 中,以便跨调用传递状态(跟 amily2_tables_data 一起持久化)
if (latestMessage) {
if (!latestMessage.metadata) latestMessage.metadata = {};
latestMessage.metadata.Amily2_Retry_Count = nextRetryCount;
if (!latestMessage.extra) latestMessage.extra = {};
latestMessage.extra.amily2_retry_count = nextRetryCount;
}
setTimeout(() => {
fillWithSecondaryApi(latestMessage, forceRun);
fillWithSecondaryApi(latestMessage, forceRun, opts);
}, 3000);
} else {
console.log(`[Amily2-副API] 已达到最大重试次数 (${maxRetries}),放弃本次填表。`);
toastr.error(`副API填表失败: ${error.message}。已达到最大重试次数,任务终止。`, "严重错误");
// 清除重试计数器
if (latestMessage && latestMessage.metadata) {
delete latestMessage.metadata.Amily2_Retry_Count;
if (latestMessage && latestMessage.extra) {
delete latestMessage.extra.amily2_retry_count;
}
}
} finally {