mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 15:05:51 +00:00
AmilyBus注入初始化
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
|
import { Logger } from '/core/logger.js';
|
||||||
|
import { FilePipe } from '/core/file-pipe.js';
|
||||||
|
|
||||||
class Amily2Bus {
|
class Amily2Bus {
|
||||||
constructor() {
|
constructor() {
|
||||||
/** @type {Logger|null} */
|
/** @type {Logger|null} */
|
||||||
this.Logger = null;
|
this.Logger = new Logger();
|
||||||
/** @type {FilePipe|null} */
|
/** @type {FilePipe|null} */
|
||||||
this.FilePipe = null;
|
this.FilePipe = new FilePipe();
|
||||||
|
|
||||||
// 已注册插件列表,防止重复注册
|
// 已注册插件列表,防止重复注册
|
||||||
this.registry = new Set();
|
this.registry = new Set();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 日志总类,用于记录日志信息
|
* 日志总类,用于记录日志信息
|
||||||
|
* 支持基于位运算的自定义日志级别控制
|
||||||
*/
|
*/
|
||||||
class Logger {
|
class Logger {
|
||||||
|
|
||||||
@@ -8,52 +9,121 @@ class Logger {
|
|||||||
static LOG_HEADER_WARN = '[WARN]';
|
static LOG_HEADER_WARN = '[WARN]';
|
||||||
static LOG_HEADER_ERROR = '[ERROR]';
|
static LOG_HEADER_ERROR = '[ERROR]';
|
||||||
|
|
||||||
static LEVEL_WEIGHTS = {
|
static LOG_LEVEL_CODE = {
|
||||||
'debug': 0,
|
none: 0,
|
||||||
'info': 1,
|
debug: 1 << 0, // 1
|
||||||
'warn': 2,
|
info: 1 << 1, // 2
|
||||||
'error': 3
|
warn: 1 << 2, // 4
|
||||||
|
error: 1 << 3, // 8
|
||||||
|
all: (1 << 4) - 1 // 15
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// 全局默认级别
|
// 全局默认级别 (默认开启 info, warn, error)
|
||||||
this.globalLevel = 'info';
|
this.globalLevel = Logger.LOG_LEVEL_CODE.info | Logger.LOG_LEVEL_CODE.warn | Logger.LOG_LEVEL_CODE.error;
|
||||||
|
|
||||||
// 针对特定插件或模块的配置
|
// 针对特定插件或模块的配置
|
||||||
// 结构示例:
|
// 结构示例:
|
||||||
// {
|
// {
|
||||||
// "PluginA": "debug", // PluginA 下所有模块默认为 debug
|
// "PluginA": 3, // PluginA 下所有模块掩码为 3 (debug | info)
|
||||||
// "PluginB::ModuleX": "error" // 仅 PluginB 下的 ModuleX 为 error
|
// "PluginB::ModuleX": 8 // 仅 PluginB 下的 ModuleX 掩码为 8 (error)
|
||||||
// }
|
// }
|
||||||
this.levelConfig = {};
|
this.levelConfig = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置日志级别
|
* 将输入转换为对应的日志级别掩码
|
||||||
* @param {string} target 目标范围,可以是 'Global'、'PluginName' 或 'PluginName::ModuleName'
|
* @param {number|string|string[]} levelInput
|
||||||
* @param {string} level 'debug' | 'info' | 'warn' | 'error'
|
* @returns {number} 掩码
|
||||||
*/
|
*/
|
||||||
setLevel(target, level) {
|
_parseLevelInput(levelInput) {
|
||||||
if (!Logger.LEVEL_WEIGHTS.hasOwnProperty(level)) {
|
if (typeof levelInput === 'number') {
|
||||||
console.warn(`[Logger] Invalid log level: ${level}`);
|
return levelInput;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof levelInput === 'string') {
|
||||||
|
if (Logger.LOG_LEVEL_CODE.hasOwnProperty(levelInput)) {
|
||||||
|
return Logger.LOG_LEVEL_CODE[levelInput];
|
||||||
|
}
|
||||||
|
// 支持 "debug|info" 这种写法
|
||||||
|
if (levelInput.includes('|')) {
|
||||||
|
return levelInput.split('|').reduce((mask, l) => mask | (Logger.LOG_LEVEL_CODE[l.trim()] || 0), 0);
|
||||||
|
}
|
||||||
|
console.warn(`[Logger] Unknown log level string: ${levelInput}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(levelInput)) {
|
||||||
|
return levelInput.reduce((mask, l) => mask | (Logger.LOG_LEVEL_CODE[l] || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置日志级别(覆盖模式)
|
||||||
|
* @param {string} target 目标范围,可以是 'Global'、'PluginName' 或 'PluginName::ModuleName'
|
||||||
|
* @param {number|string|string[]} level 输入的级别配置
|
||||||
|
*/
|
||||||
|
setLevel(target, level) {
|
||||||
|
const mask = this._parseLevelInput(level);
|
||||||
|
|
||||||
if (target === 'Global') {
|
if (target === 'Global') {
|
||||||
this.globalLevel = level;
|
this.globalLevel = mask;
|
||||||
console.log(`[Logger] Global log level set to: ${level}`);
|
console.log(`[Logger] Global log level mask set to: ${mask.toString(2)}`);
|
||||||
} else {
|
} else {
|
||||||
this.levelConfig[target] = level;
|
this.levelConfig[target] = mask;
|
||||||
console.log(`[Logger] Log level for '${target}' set to: ${level}`);
|
console.log(`[Logger] Log level mask for '${target}' set to: ${mask.toString(2)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定上下文的生效日志级别(级联查找)
|
* 添加日志级别(增量模式)
|
||||||
|
* @param {string} target
|
||||||
|
* @param {number|string|string[]} level
|
||||||
|
*/
|
||||||
|
addLevel(target, level) {
|
||||||
|
const maskToAdd = this._parseLevelInput(level);
|
||||||
|
let currentMask;
|
||||||
|
|
||||||
|
if (target === 'Global') {
|
||||||
|
currentMask = this.globalLevel;
|
||||||
|
this.globalLevel = currentMask | maskToAdd;
|
||||||
|
console.log(`[Logger] Added level to Global. New mask: ${this.globalLevel.toString(2)}`);
|
||||||
|
} else {
|
||||||
|
currentMask = this.levelConfig[target] !== undefined ? this.levelConfig[target] : this.globalLevel;
|
||||||
|
this.levelConfig[target] = currentMask | maskToAdd;
|
||||||
|
console.log(`[Logger] Added level to '${target}'. New mask: ${this.levelConfig[target].toString(2)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除日志级别(减量模式)
|
||||||
|
* @param {string} target
|
||||||
|
* @param {number|string|string[]} level
|
||||||
|
*/
|
||||||
|
removeLevel(target, level) {
|
||||||
|
const maskToRemove = this._parseLevelInput(level);
|
||||||
|
let currentMask;
|
||||||
|
|
||||||
|
if (target === 'Global') {
|
||||||
|
currentMask = this.globalLevel;
|
||||||
|
// 使用 & ~mask 实现移除
|
||||||
|
this.globalLevel = currentMask & ~maskToRemove;
|
||||||
|
console.log(`[Logger] Removed level from Global. New mask: ${this.globalLevel.toString(2)}`);
|
||||||
|
} else {
|
||||||
|
currentMask = this.levelConfig[target] !== undefined ? this.levelConfig[target] : this.globalLevel;
|
||||||
|
this.levelConfig[target] = currentMask & ~maskToRemove;
|
||||||
|
console.log(`[Logger] Removed level from '${target}'. New mask: ${this.levelConfig[target].toString(2)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定上下文的生效日志级别掩码(级联查找)
|
||||||
* @param {string} plugin
|
* @param {string} plugin
|
||||||
* @param {string} origin (Module)
|
* @param {string} origin (Module)
|
||||||
*/
|
*/
|
||||||
_getEffectiveLevel(plugin, origin) {
|
_getEffectiveLevelMask(plugin, origin) {
|
||||||
// 1. 检查精确匹配 "Plugin::Module"
|
// 1. 检查精确匹配 "Plugin::Module"
|
||||||
const specificKey = `${plugin}::${origin}`;
|
const specificKey = `${plugin}::${origin}`;
|
||||||
if (this.levelConfig.hasOwnProperty(specificKey)) {
|
if (this.levelConfig.hasOwnProperty(specificKey)) {
|
||||||
@@ -70,14 +140,14 @@ class Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log(plugin, origin, type, message, inFile = false) {
|
log(plugin, origin, type, message, inFile = false) {
|
||||||
// 获取当前上下文生效的日志级别权重
|
// 获取当前上下文生效的日志级别掩码
|
||||||
const effectiveLevelName = this._getEffectiveLevel(plugin, origin);
|
const effectiveMask = this._getEffectiveLevelMask(plugin, origin);
|
||||||
const currentWeight = Logger.LEVEL_WEIGHTS[effectiveLevelName];
|
|
||||||
|
|
||||||
const weight = Logger.LEVEL_WEIGHTS[type] !== undefined ? Logger.LEVEL_WEIGHTS[type] : 1;
|
// 获取当前日志类型的位码
|
||||||
|
const typeCode = Logger.LOG_LEVEL_CODE[type];
|
||||||
|
|
||||||
// 级别筛选
|
// 级别筛选:位与运算结果为0则表示该级别未开启
|
||||||
if (weight < currentWeight) {
|
if (typeCode === undefined || (effectiveMask & typeCode) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +177,6 @@ class Logger {
|
|||||||
// 2. File Output (via FilePipe)
|
// 2. File Output (via FilePipe)
|
||||||
if (inFile) {
|
if (inFile) {
|
||||||
// Logger 自身也需要作为系统组件注册,获取写入权限
|
// Logger 自身也需要作为系统组件注册,获取写入权限
|
||||||
// 注意:为了性能,建议在 constructor 里注册一次保存起来,这里为了逻辑展示暂时简化
|
|
||||||
if (!this.sysBus) {
|
if (!this.sysBus) {
|
||||||
if (window.Amily2Bus && window.Amily2Bus.register) {
|
if (window.Amily2Bus && window.Amily2Bus.register) {
|
||||||
this.sysBus = window.Amily2Bus.register('SystemLogger');
|
this.sysBus = window.Amily2Bus.register('SystemLogger');
|
||||||
@@ -116,7 +185,6 @@ class Logger {
|
|||||||
|
|
||||||
if (this.sysBus && this.sysBus.file) {
|
if (this.sysBus && this.sysBus.file) {
|
||||||
// 使用注册后的安全接口写入,无需再手动传 'SystemLogger'
|
// 使用注册后的安全接口写入,无需再手动传 'SystemLogger'
|
||||||
// TODO: 这里的路径 'runtime.log' 后续可以做成配置项
|
|
||||||
this.sysBus.file.write('runtime.log', fullMessage + '\n');
|
this.sysBus.file.write('runtime.log', fullMessage + '\n');
|
||||||
} else {
|
} else {
|
||||||
// Fallback: 如果总线未就绪,仅在控制台警告一次,避免死循环
|
// Fallback: 如果总线未就绪,仅在控制台警告一次,避免死循环
|
||||||
|
|||||||
152
core/bus.js
152
core/bus.js
@@ -1,152 +0,0 @@
|
|||||||
import { eventSource, event_types } from '/script.js';
|
|
||||||
|
|
||||||
export const LOG_LEVELS = {
|
|
||||||
DEBUG: 0b0001,
|
|
||||||
INFO: 0b0010,
|
|
||||||
WARN: 0b0100,
|
|
||||||
ERROR: 0b1000,
|
|
||||||
NONE: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
class FilePipe {
|
|
||||||
constructor(pluginName, bus) {
|
|
||||||
this.pluginName = pluginName;
|
|
||||||
this.bus = bus;
|
|
||||||
this.basePath = `extensions/${pluginName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证路径是否合法 (仅允许访问插件自己的目录)
|
|
||||||
* @param {string} path
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
validatePath(path) {
|
|
||||||
// 简单示例:防止 ../ 越权,并确保路径以 basePath 或允许的相对路径开头
|
|
||||||
// 实际上 SillyTavern 的 writeFile API 通常已经限制在特定目录,
|
|
||||||
// 但这里我们做逻辑层的隔离。
|
|
||||||
if (path.includes('..')) {
|
|
||||||
this.bus.log(this.pluginName, 'FilePipe', 'ERROR', `非法路径尝试: ${path}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read(fileName) {
|
|
||||||
if (!this.validatePath(fileName)) throw new Error("Access Denied");
|
|
||||||
// 这里对接实际的文件读取逻辑,例如 fetch 或 ST 的 API
|
|
||||||
// return await readFileContent(this.basePath + '/' + fileName);
|
|
||||||
console.log(`[Mock] Reading ${fileName} for ${this.pluginName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async write(fileName, content) {
|
|
||||||
if (!this.validatePath(fileName)) throw new Error("Access Denied");
|
|
||||||
// 这里对接实际的文件写入逻辑
|
|
||||||
// return await writeFileContent(this.basePath + '/' + fileName, content);
|
|
||||||
console.log(`[Mock] Writing to ${fileName} for ${this.pluginName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Amily2Bus {
|
|
||||||
constructor() {
|
|
||||||
this.plugins = new Map();
|
|
||||||
this.logConfig = {
|
|
||||||
globalLevel: LOG_LEVELS.INFO,
|
|
||||||
filters: {} // e.g., "Amily2.Network": LOG_LEVELS.DEBUG
|
|
||||||
};
|
|
||||||
console.log("[Amily2-Bus] 总线系统初始化...");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册插件
|
|
||||||
* @param {Object} manifest - 必须包含 name 字段
|
|
||||||
* @param {Object} instance - 插件实例或主要对象
|
|
||||||
* @returns {FilePipe|null} - 返回专属的文件管线对象,注册失败返回 null
|
|
||||||
*/
|
|
||||||
register(manifest, instance = {}) {
|
|
||||||
if (!manifest || !manifest.name) {
|
|
||||||
this.log('System', 'Bus', 'ERROR', '注册失败: 无效的 manifest');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = manifest.name;
|
|
||||||
if (this.plugins.has(name)) {
|
|
||||||
this.log('System', 'Bus', 'WARN', `插件 ${name} 已注册,正在覆盖...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.plugins.set(name, {
|
|
||||||
manifest,
|
|
||||||
instance,
|
|
||||||
registeredAt: Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
this.log('System', 'Bus', 'INFO', `插件注册成功: ${name}`);
|
|
||||||
|
|
||||||
// 返回该插件专属的 FilePipe
|
|
||||||
return new FilePipe(name, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取已注册的插件实例
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
getPlugin(name) {
|
|
||||||
return this.plugins.get(name)?.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置日志级别
|
|
||||||
* @param {string} target - "Global" 或 "PluginName.ModuleName"
|
|
||||||
* @param {string|number} level - "DEBUG", "INFO", etc.
|
|
||||||
*/
|
|
||||||
setLogLevel(target, level) {
|
|
||||||
const lvl = typeof level === 'string' ? LOG_LEVELS[level.toUpperCase()] : level;
|
|
||||||
if (target === 'Global') {
|
|
||||||
this.logConfig.globalLevel = lvl;
|
|
||||||
} else {
|
|
||||||
this.logConfig.filters[target] = lvl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 统一日志方法
|
|
||||||
* @param {string} plugin - 插件名
|
|
||||||
* @param {string} module - 模块名
|
|
||||||
* @param {string} level - 级别 (DEBUG, INFO, WARN, ERROR)
|
|
||||||
* @param {string|Object} message - 内容
|
|
||||||
*/
|
|
||||||
log(plugin, module, level, message) {
|
|
||||||
const currentLevel = LOG_LEVELS[level.toUpperCase()] || LOG_LEVELS.INFO;
|
|
||||||
const configKey = `${plugin}.${module}`;
|
|
||||||
|
|
||||||
// 检查特定配置,如果没有则使用全局配置
|
|
||||||
const requiredLevel = this.logConfig.filters[configKey] !== undefined
|
|
||||||
? this.logConfig.filters[configKey]
|
|
||||||
: this.logConfig.globalLevel;
|
|
||||||
|
|
||||||
if (currentLevel < requiredLevel) {
|
|
||||||
return; // 级别不足,忽略
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamp = new Date().toLocaleTimeString();
|
|
||||||
const formattedMsg = typeof message === 'object' ? JSON.stringify(message) : message;
|
|
||||||
|
|
||||||
// 样式化输出
|
|
||||||
let color = '#7f8c8d'; // Default (INFO)
|
|
||||||
if (level === 'WARN') color = '#f39c12';
|
|
||||||
if (level === 'ERROR') color = '#c0392b';
|
|
||||||
if (level === 'DEBUG') color = '#3498db';
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`%c[${timestamp}] [${plugin}::${module}]%c ${formattedMsg}`,
|
|
||||||
`color: ${color}; font-weight: bold;`,
|
|
||||||
`color: inherit;`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单例模式,挂载到 window 方便全局调用
|
|
||||||
if (!window.Amily2Bus) {
|
|
||||||
window.Amily2Bus = new Amily2Bus();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default window.Amily2Bus;
|
|
||||||
Reference in New Issue
Block a user