feat: add API config system, FilePipe backend, and ConfigManager

- ConfigManager: route sensitive keys (API keys) to localStorage,
  migrate existing values out of extension_settings on startup
- ApiKeyStore: local/cloud storage modes with RSA+AES hybrid encryption
- ApiProfileManager: named connection profiles (chat/embedding/rerank)
  with per-slot type-validated assignments
- FilePipe: complete IndexedDB backend (read/write/delete/list/stat)
- Amily2Bus: inject FilePipe via forPlugin() capability token
- UI: api-config-panel with profile CRUD and slot assignment
- TableSystemService: initial service layer scaffold
- logger.js: XSS fix
This commit is contained in:
2026-03-10 22:07:15 +08:00
parent ed3f52a568
commit 0be6a86e94
17 changed files with 1970 additions and 110 deletions

View File

@@ -1,75 +1,19 @@
import { getContext, extension_settings } from "/scripts/extensions.js";
import { saveChatConditional } from "/script.js";
import { extensionName } from "../utils/settings.js";
import * as TableManager from './table-system/manager.js';
import * as Executor from './table-system/executor.js';
import { renderTables } from '../ui/table-bindings.js';
import { log } from "./table-system/logger.js";
async function handleTableUpdate(messageId) {
TableManager.clearHighlights();
const settings = extension_settings[extensionName];
const tableSystemEnabled = settings.table_system_enabled !== false;
if (!tableSystemEnabled) {
log('【监察系统】表格系统总开关已关闭,跳过所有表格处理。', 'info');
return;
}
const fillingMode = settings.filling_mode || 'main-api';
if (fillingMode === 'secondary-api' || fillingMode === 'optimized') {
log('【监察系统】检测到"分步填表"或"优化中填表"模式已启用主API填表逻辑已自动禁用。', 'info');
return;
}
log(`【监察系统】接到圣旨,开始处理消息 ID: ${messageId}`, 'warn');
const context = getContext();
const message = context.chat[messageId];
if (!message) {
log(`【监察系统】错误:未找到消息 ID: ${messageId},流程中止。`, 'error');
return;
}
if (message.is_user) {
log(`【监察系统】消息 ID: ${messageId} 是用户消息,无需处理。`, 'info');
return;
}
log(`【监察系统】正在处理的奏折内容: "${message.mes.substring(0, 50)}..."`, 'info');
const initialState = TableManager.loadTables(messageId);
log(`【监察系统-步骤1】为消息 ${messageId} 加载了基准状态。`, 'info', initialState);
const { finalState, hasChanges, changes } = Executor.executeCommands(message.mes, initialState);
log(`【监察系统-步骤2】推演完毕。是否有变化: ${hasChanges}`, 'info', finalState);
if (hasChanges) {
if (changes && changes.length > 0) {
changes.forEach(change => {
TableManager.addHighlight(change.tableIndex, change.rowIndex, change.colIndex);
});
}
TableManager.saveStateToMessage(finalState, message);
TableManager.setMemoryState(finalState);
await saveChatConditional();
log(`【监察系统-步骤3】检测到变化已将新状态写入消息 ${messageId} 并保存。`, 'success');
} else {
log(`【监察系统-步骤3】未检测到有效指令或变化无需写入。`, 'info');
}
if (hasChanges) {
renderTables();
}
}
import { processMessageUpdate, fillWithSecondaryApi } from './table-system/TableSystemService.js';
import { processOptimization } from "./summarizer.js";
import { executeAutoHide } from './autoHideManager.js';
import { checkAndTriggerAutoSummary } from './historiographer.js';
import { fillWithSecondaryApi } from './table-system/secondary-filler.js';
import { amilyHelper } from './tavern-helper/main.js';
async function handleTableUpdate(messageId) {
await processMessageUpdate(messageId);
}
export async function onMessageReceived(data) {
window.lastPreOptimizationResult = null;
document.dispatchEvent(new CustomEvent('preOptimizationTextUpdated'));
document.dispatchEvent(new CustomEvent('preOptimizationTextUpdated'));
const context = getContext();
if ((data && data.is_user) || context.isWaitingForUserInput) { return; }
@@ -81,9 +25,10 @@ export async function onMessageReceived(data) {
const latestMessage = chat[chat.length - 1];
if (latestMessage.is_user) { return; }
const tableSystemEnabled = settings.table_system_enabled !== false;
const tableSystemEnabled = settings.table_system_enabled !== false;
await executeAutoHide();
const isOptimizationEnabled = settings.optimizationEnabled && settings.apiUrl;
if (isOptimizationEnabled) {
if (chat.length >= 2 && chat[chat.length - 2].is_user) {
@@ -109,13 +54,14 @@ export async function onMessageReceived(data) {
console.log("[Amily2号-正文优化] 检测到消息并非AI对用户的直接回复已跳过优化。");
}
}
if (tableSystemEnabled) {
const fillingMode = settings.filling_mode || 'main-api';
if (fillingMode === 'secondary-api') {
fillWithSecondaryApi(latestMessage);
}
} else {
log('[分步填表] 表格系统总开关已关闭,跳过分步填表处理。', 'info');
console.log('[分步填表] 表格系统总开关已关闭,跳过分步填表处理。');
}
(async () => {

View File

@@ -0,0 +1,122 @@
/**
* TableSystemService
* 表格系统 Bus 服务 — 统一对外入口
*
* 职责:
* 1. 将原 events.js::handleTableUpdate 的消息处理编排逻辑收归此处
* 2. 通过 Amily2Bus 暴露稳定接口,解耦外部模块的直接依赖
* 3. 向后兼容:保留具名导出,现有直接 import 无需立即修改
*
* Bus 注册名:'TableSystem'
*
* 公开接口query('TableSystem')
* processMessageUpdate(messageId) — 处理 AI 消息的表格更新流程
* fillWithSecondaryApi(msg) — 二次 API 填表
* injectTableData(...) — 向提示词注入表格数据
* generateTableContent() — 生成表格注入内容字符串
* getMemoryState() — 读取当前表格内存状态
* renderTables() — 强制重渲染表格 UI
*/
import { getContext, extension_settings } from "/scripts/extensions.js";
import { saveChatConditional } from "/script.js";
import { extensionName } from "../../utils/settings.js";
// ── table-system 内部模块 ─────────────────────────────────────────────────
// manager.js / logger.js 为受限文件(不修改),此处仅引用其导出
import * as TableManager from './manager.js';
import { executeCommands } from './executor.js';
import { log } from './logger.js';
// 可修改子模块
import { generateTableContent, injectTableData } from './injector.js';
import { fillWithSecondaryApi } from './secondary-filler.js';
// UI 层
import { renderTables } from '../../ui/table-bindings.js';
// ── 核心逻辑 ─────────────────────────────────────────────────────────────
/**
* 处理单条 AI 消息的表格更新流程。
* 原 events.js::handleTableUpdate 的完整逻辑迁移至此。
*
* @param {number} messageId - 消息在 context.chat 中的索引
*/
async function processMessageUpdate(messageId) {
TableManager.clearHighlights();
const settings = extension_settings[extensionName];
const tableSystemEnabled = settings.table_system_enabled !== false;
if (!tableSystemEnabled) {
log('【表格服务】表格系统总开关已关闭,跳过所有表格处理。', 'info');
return;
}
const fillingMode = settings.filling_mode || 'main-api';
if (fillingMode === 'secondary-api' || fillingMode === 'optimized') {
log('【表格服务】检测到"分步填表"或"优化中填表"模式主API填表已自动禁用。', 'info');
return;
}
log(`【表格服务】开始处理消息 ID: ${messageId}`, 'warn');
const context = getContext();
const message = context.chat[messageId];
if (!message) {
log(`【表格服务】错误:未找到消息 ID: ${messageId},流程中止。`, 'error');
return;
}
if (message.is_user) {
log(`【表格服务】消息 ID: ${messageId} 是用户消息,跳过。`, 'info');
return;
}
log(`【表格服务】处理内容: "${message.mes.substring(0, 50)}..."`, 'info');
const initialState = TableManager.loadTables(messageId);
log('【表格服务-步骤1】基准状态已加载。', 'info', initialState);
const { finalState, hasChanges, changes } = executeCommands(message.mes, initialState);
log(`【表格服务-步骤2】推演完毕。是否有变化: ${hasChanges}`, 'info', finalState);
if (hasChanges) {
changes.forEach(change => {
TableManager.addHighlight(change.tableIndex, change.rowIndex, change.colIndex);
});
TableManager.saveStateToMessage(finalState, message);
TableManager.setMemoryState(finalState);
await saveChatConditional();
log('【表格服务-步骤3】状态已写入并保存。', 'success');
renderTables();
} else {
log('【表格服务-步骤3】未检测到有效指令或变化无需写入。', 'info');
}
}
// ── Bus 注册 ──────────────────────────────────────────────────────────────
// 使用 setTimeout 延迟到同步模块初始化完成后再注册,
// 确保 window.Amily2Bus 已由 SL/bus/Amily2Bus.js 完成挂载。
setTimeout(() => {
try {
const _ctx = window.Amily2Bus?.register('TableSystem');
if (!_ctx) {
console.warn('[TableSystem] Amily2Bus 尚未就绪,服务注册跳过。');
return;
}
_ctx.expose({
processMessageUpdate,
fillWithSecondaryApi,
injectTableData,
generateTableContent,
getMemoryState: () => TableManager.getMemoryState(),
renderTables,
});
_ctx.log('TableSystemService', 'info', 'TableSystem 服务已注册到 Bus。');
} catch (e) {
console.error('[TableSystem] Bus 注册失败:', e);
}
}, 0);
// ── 向后兼容具名导出 ──────────────────────────────────────────────────────
// 过渡期保留,现有 import { ... } from '...TableSystemService.js' 无需修改。
export { processMessageUpdate, fillWithSecondaryApi, generateTableContent, injectTableData };

View File

@@ -1 +1,30 @@
const _0x352fc5=_0xb01f;(function(_0x52276c,_0x1fe640){const _0x25137c=_0xb01f,_0x322b57=_0x52276c();while(!![]){try{const _0xb9a91d=parseInt(_0x25137c(0x1d4))/0x1+-parseInt(_0x25137c(0x1d9))/0x2*(parseInt(_0x25137c(0x1c6))/0x3)+parseInt(_0x25137c(0x1c8))/0x4*(-parseInt(_0x25137c(0x1da))/0x5)+-parseInt(_0x25137c(0x1d5))/0x6+-parseInt(_0x25137c(0x1c5))/0x7+parseInt(_0x25137c(0x1c4))/0x8*(-parseInt(_0x25137c(0x1cc))/0x9)+parseInt(_0x25137c(0x1ce))/0xa;if(_0xb9a91d===_0x1fe640)break;else _0x322b57['push'](_0x322b57['shift']());}catch(_0x57e5d4){_0x322b57['push'](_0x322b57['shift']());}}}(_0x13eb,0x61073));function _0xb01f(_0x2b709c,_0x43aa7d){const _0x13eb95=_0x13eb();return _0xb01f=function(_0xb01f68,_0x3325be){_0xb01f68=_0xb01f68-0x1c4;let _0x638bf1=_0x13eb95[_0xb01f68];return _0x638bf1;},_0xb01f(_0x2b709c,_0x43aa7d);}function _0x13eb(){const _0x3421fa=['createElement','\x22></i>\x20','101174LOTkJv','79935LtdznB','3176dnnOAA','861679gOvdAF','33vyMqZa','fa-solid\x20fa-check-circle','28LBJaGM','scrollHeight','fa-solid\x20fa-circle-info','getElementById','7677IWXntE','[内存储司-起居注]\x20','13572940eKjSAe','fa-solid\x20fa-circle-xmark','appendChild','log','hly-log-entry\x20log-','innerHTML','182086ttYsxR','71094AjxVJw','fa-solid\x20fa-triangle-exclamation'];_0x13eb=function(){return _0x3421fa;};return _0x13eb();}const getLogContainer=()=>document[_0x352fc5(0x1cb)]('table-log-display');export function log(_0x1e7922,_0x4de68c='info',_0x2aabe2=null){const _0x17dbe1=_0x352fc5,_0x4fdf31=getLogContainer();if(!_0x4fdf31){const _0xec84ec=console[_0x4de68c]||console[_0x17dbe1(0x1d1)];_0xec84ec(_0x17dbe1(0x1cd)+_0x1e7922,_0x2aabe2||'');return;}const _0x483576={'info':_0x17dbe1(0x1ca),'success':_0x17dbe1(0x1c7),'warn':_0x17dbe1(0x1d6),'error':_0x17dbe1(0x1cf)},_0x5bed08=document[_0x17dbe1(0x1d7)]('p');_0x5bed08['className']=_0x17dbe1(0x1d2)+_0x4de68c,_0x5bed08[_0x17dbe1(0x1d3)]='<i\x20class=\x22'+_0x483576[_0x4de68c]+_0x17dbe1(0x1d8)+_0x1e7922,_0x4fdf31[_0x17dbe1(0x1d0)](_0x5bed08),_0x4fdf31['scrollTop']=_0x4fdf31[_0x17dbe1(0x1c9)];}
const getLogContainer = () => document.getElementById('table-log-display');
export function log(message, type = 'info', data = null) {
const container = getLogContainer();
if (!container) {
// 在容器不可用时,静默地将日志打印到控制台,不再显示警告
const logFunc = console[type] || console.log;
logFunc(`[内存储司-起居注] ${message}`, data || '');
return;
}
const iconMap = {
info: 'fa-solid fa-circle-info',
success: 'fa-solid fa-check-circle',
warn: 'fa-solid fa-triangle-exclamation',
error: 'fa-solid fa-circle-xmark',
};
const logEntry = document.createElement('p');
logEntry.className = `hly-log-entry log-${type}`;
const icon = document.createElement('i');
icon.className = iconMap[type];
logEntry.appendChild(icon);
logEntry.appendChild(document.createTextNode(` ${message}`));
container.appendChild(logEntry);
// Auto-scroll to the bottom
container.scrollTop = container.scrollHeight;
}