ci: auto build & obfuscate [2026-04-19 03:01:12] (Jenkins #16)

This commit is contained in:
Jenkins CI
2026-04-19 03:01:12 +08:00
parent 58ff3c3faf
commit 8d590073f4
28 changed files with 761 additions and 384 deletions

View File

@@ -435,6 +435,10 @@ export function bindModalEvents() {
bindAmily2ModalWorldBookSettings();
const container = $("#amily2_drawer_content").length ? $("#amily2_drawer_content") : $("#amily2_chat_optimiser");
const apiConfigButton = container.find('#amily2_open_api_config');
if (apiConfigButton.length && !container.find('#amily2_open_rule_config').length) {
apiConfigButton.after(' <button id="amily2_open_rule_config" class="menu_button wide_button"><i class="fas fa-list-check"></i> 规则配置</button>');
}
// Collapsible sections logic
container.find('.collapsible-legend').each(function() {
@@ -802,7 +806,7 @@ export function bindModalEvents() {
container
.off("click.amily2.chamber_nav")
.on("click.amily2.chamber_nav",
"#amily2_open_text_optimization, #amily2_open_plot_optimization, #amily2_open_additional_features, #amily2_open_rag_palace, #amily2_open_memorisation_forms, #amily2_open_character_world_book, #amily2_open_world_editor, #amily2_open_glossary, #amily2_open_renderer, #amily2_open_super_memory, #amily2_open_auto_char_card, #amily2_open_api_config, #amily2_open_sfigen, #amily2_back_to_main_settings, #amily2_back_to_main_from_hanlinyuan, #amily2_back_to_main_from_forms, #amily2_back_to_main_from_optimization, #amily2_back_to_main_from_text_optimization, #amily2_back_to_main_from_cwb, #amily2_back_to_main_from_world_editor, #amily2_back_to_main_from_glossary, #amily2_renderer_back_button, #amily2_back_to_main_from_super_memory, #amily2_back_to_main_from_api_config, #amily2_sfigen_back_to_main", function () {
"#amily2_open_text_optimization, #amily2_open_plot_optimization, #amily2_open_additional_features, #amily2_open_rag_palace, #amily2_open_memorisation_forms, #amily2_open_character_world_book, #amily2_open_world_editor, #amily2_open_glossary, #amily2_open_renderer, #amily2_open_super_memory, #amily2_open_auto_char_card, #amily2_open_api_config, #amily2_open_rule_config, #amily2_open_sfigen, #amily2_back_to_main_settings, #amily2_back_to_main_from_hanlinyuan, #amily2_back_to_main_from_forms, #amily2_back_to_main_from_optimization, #amily2_back_to_main_from_text_optimization, #amily2_back_to_main_from_cwb, #amily2_back_to_main_from_world_editor, #amily2_back_to_main_from_glossary, #amily2_renderer_back_button, #amily2_back_to_main_from_super_memory, #amily2_back_to_main_from_api_config, #amily2_back_to_main_from_rule_config, #amily2_sfigen_back_to_main", function () {
if (!pluginAuthStatus.authorized) return;
const mainPanel = container.find('.plugin-features');
@@ -817,6 +821,7 @@ export function bindModalEvents() {
const rendererPanel = container.find('#amily2_renderer_panel');
const superMemoryPanel = container.find('#amily2_super_memory_panel');
const apiConfigPanel = container.find('#amily2_api_config_panel');
const ruleConfigPanel = container.find('#amily2_rule_config_panel');
const sfigenPanel = container.find('#amily2_sfigen_panel');
mainPanel.hide();
@@ -831,6 +836,7 @@ export function bindModalEvents() {
rendererPanel.hide();
superMemoryPanel.hide();
apiConfigPanel.hide();
ruleConfigPanel.hide();
sfigenPanel.hide();
switch (this.id) {
@@ -879,6 +885,9 @@ export function bindModalEvents() {
case 'amily2_open_api_config':
apiConfigPanel.show();
break;
case 'amily2_open_rule_config':
ruleConfigPanel.show();
break;
case 'amily2_open_sfigen':
sfigenPanel.show();
break;
@@ -893,6 +902,7 @@ export function bindModalEvents() {
case 'amily2_renderer_back_button':
case 'amily2_back_to_main_from_super_memory':
case 'amily2_back_to_main_from_api_config':
case 'amily2_back_to_main_from_rule_config':
case 'amily2_sfigen_back_to_main':
mainPanel.show();
break;

View File

@@ -7,6 +7,7 @@ import * as ContextUtils from '../core/utils/context-utils.js';
import * as IngestionManager from '../core/ingestion-manager.js';
import { showContentModal, showHtmlModal } from './page-window.js';
import { extractBlocksByTags, applyExclusionRules } from '../core/utils/rag-tag-extractor.js';
import { ruleProfileManager, resolveCondensationRuleConfig } from '../utils/config/RuleProfileManager.js';
import {
filterWorldbooks,
filterWorldbookEntries,
@@ -23,6 +24,27 @@ function escapeTextareaContent(text) {
.replace(/>/g, '&gt;');
}
function escapeAttribute(text) {
return String(text ?? '')
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function _populateHlyRuleProfileSelect(select, slot) {
const profiles = ruleProfileManager.listProfiles();
const assigned = ruleProfileManager.getAssignment(slot) || '';
select.innerHTML = [
'<option value="">— 未分配 —</option>',
...profiles.map(p =>
`<option value="${p.id}" ${p.id === assigned ? 'selected' : ''}>${escapeTextareaContent(p.name || p.id)}</option>`
),
].join('');
}
function setupGlobalEventHandlers() {
window.saveHLYSettings = () => saveSettingsFromUI(false); // false表示非自动保存
@@ -53,6 +75,17 @@ function updateAndSaveSetting(key, value) {
current[keys[keys.length - 1]] = value;
HanlinyuanCore.saveSettings();
if (key === 'condensation.tagExtractionEnabled') {
syncHanlinLinkedRuleProfile('condensation', { tagExtractionEnabled: value });
} else if (key === 'condensation.tags') {
syncHanlinLinkedRuleProfile('condensation', { tags: value });
} else if (key === 'queryPreprocessing.tagExtractionEnabled') {
syncHanlinLinkedRuleProfile('queryPreprocessing', { tagExtractionEnabled: value });
} else if (key === 'queryPreprocessing.tags') {
syncHanlinLinkedRuleProfile('queryPreprocessing', { tags: value });
}
log(`[自动保存] 设置项 '${key}' 已更新为: ${JSON.stringify(value)}`, 'success');
}
@@ -358,18 +391,34 @@ function bindInternalUIEvents() {
librarySelect.addEventListener('change', handleWorldbookSelectionChange);
}
// 为规则配置按钮绑定通用弹窗事件
const condensationRulesBtn = document.getElementById('hly-exclusion-rules-btn');
if (condensationRulesBtn) {
condensationRulesBtn.addEventListener('click', () => showRulesModal('condensation'));
// 浓缩 — 提取规则下拉选单
const condensationRuleSelect = document.getElementById('hly-condensation-rule-profile-select');
if (condensationRuleSelect) {
_populateHlyRuleProfileSelect(condensationRuleSelect, 'condensation');
condensationRuleSelect.addEventListener('change', () => {
ruleProfileManager.setAssignment('condensation', condensationRuleSelect.value || null);
const name = condensationRuleSelect.selectedOptions[0]?.textContent || '';
toastr.info(condensationRuleSelect.value ? `浓缩提取规则已切换为「${name}` : '浓缩提取规则已取消分配');
});
}
// 为“检索预处理”的配置按钮绑定事件
const queryPreprocessingBtn = document.getElementById('hly-query-preprocessing-rules-btn');
if (queryPreprocessingBtn) {
queryPreprocessingBtn.addEventListener('click', () => showRulesModal('queryPreprocessing'));
// 查询预处理 — 提取规则下拉选单
const queryPrepRuleSelect = document.getElementById('hly-query-preprocessing-rule-profile-select');
if (queryPrepRuleSelect) {
_populateHlyRuleProfileSelect(queryPrepRuleSelect, 'queryPreprocessing');
queryPrepRuleSelect.addEventListener('change', () => {
ruleProfileManager.setAssignment('queryPreprocessing', queryPrepRuleSelect.value || null);
const name = queryPrepRuleSelect.selectedOptions[0]?.textContent || '';
toastr.info(queryPrepRuleSelect.value ? `查询预处理规则已切换为「${name}` : '查询预处理规则已取消分配');
});
}
// 规则配置中心保存/删除后自动刷新翰林院下拉选单
document.addEventListener('amily2:ruleProfilesChanged', () => {
if (condensationRuleSelect) _populateHlyRuleProfileSelect(condensationRuleSelect, 'condensation');
if (queryPrepRuleSelect) _populateHlyRuleProfileSelect(queryPrepRuleSelect, 'queryPreprocessing');
});
// 为自定义多选下拉框绑定事件
const multiSelectBtn = document.getElementById('hly-hist-entry-multiselect-btn');
const optionsContainer = document.getElementById('hly-hist-entry-multiselect-options');
@@ -1570,134 +1619,20 @@ API端点: ${settings.retrieval.apiEndpoint}
log(`查询宝库状态失败: ${error.message}`, 'error');
}
}
function showRulesModal(type) {
const settings = HanlinyuanCore.getSettings();
const config = settings[type];
if (!config) {
console.error(`[翰林院-枢纽] 未找到类型为 "${type}" 的配置项。`);
return;
}
const title = type === 'condensation' ? '编辑凝识内容排除规则' : '编辑检索内容排除规则';
const rules = config.exclusionRules || [];
const createRuleRowHtml = (rule = { start: '', end: '' }, index) => `
<div class="hly-exclusion-rule-row" data-index="${index}">
<input type="text" class="hly-imperial-brush" value="${(rule.start || '').replace(/"/g, '"')}" placeholder="开始字符串, 如 <!--">
<span style="margin: 0 5px;">到</span>
<input type="text" class="hly-imperial-brush" value="${(rule.end || '').replace(/"/g, '"')}" placeholder="结束字符串, 如 -->">
<button class="hly-delete-rule-btn" title="删除此规则">&times;</button>
</div>
`;
const rulesHtml = rules.map(createRuleRowHtml).join('');
// 标签提取部分只在“检索预处理”设置中显示
const tagExtractionFieldset = type === 'queryPreprocessing' ? `
<fieldset class="hly-settings-group">
<legend><i class="fas fa-tags"></i> 标签提取</legend>
<div class="hly-control-block" style="flex-direction: row; justify-content: space-between; align-items: center;">
<label for="hly-modal-tag-extraction-enabled">启用标签提取</label>
<label class="hly-toggle-switch">
<input type="checkbox" id="hly-modal-tag-extraction-enabled" ${config.tagExtractionEnabled ? 'checked' : ''}>
<span class="slider"></span>
</label>
</div>
<div id="hly-modal-tag-input-container" class="hly-control-block" style="display: ${config.tagExtractionEnabled ? 'block' : 'none'};">
<label for="hly-modal-tag-input">输入标签 (以逗号分隔):</label>
<textarea id="hly-modal-tag-input" class="hly-imperial-brush" rows="2" placeholder="例如: content,details,摘要">${config.tags || ''}</textarea>
</div>
</fieldset>
` : '';
const modalHtml = `
<div id="hly-rules-modal-container">
${tagExtractionFieldset}
<fieldset class="hly-settings-group">
<legend><i class="fas fa-ban"></i> 内容排除规则</legend>
<p class="hly-notes">在这里定义需要从提取内容中排除的文本片段。例如排除HTML注释可以设置开始字符串为 \`<!--\`,结束字符串为 \`-->\`。</p>
<div id="hly-rules-list">${rulesHtml.length > 0 ? rulesHtml : '<p class="hly-notes" style="text-align:center;">暂无规则</p>'}</div>
<button id="hly-add-rule-btn" class="hly-action-button" style="margin-top: 10px;">
<i class="fas fa-plus"></i> 添加新规则
</button>
</fieldset>
</div>
<style>
.hly-exclusion-rule-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.hly-exclusion-rule-row input { flex-grow: 1; }
.hly-delete-rule-btn { background: #c0392b; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 16px; line-height: 24px; text-align: center; padding: 0; flex-shrink: 0; }
</style>
`;
showHtmlModal(title, modalHtml, {
okText: '保存规则',
onOk: (dialogElement) => {
const newRules = [];
dialogElement.find('.hly-exclusion-rule-row').each(function () {
const start = $(this).find('input').eq(0).val().trim();
const end = $(this).find('input').eq(1).val().trim();
// 规则必须至少有一个开始字符串
if (start) {
newRules.push({ start, end });
}
});
const newConfig = { ...config, exclusionRules: newRules };
// 如果是检索预处理设置,则同时保存标签提取的设置
if (type === 'queryPreprocessing') {
newConfig.tagExtractionEnabled = dialogElement.find('#hly-modal-tag-extraction-enabled').is(':checked');
newConfig.tags = dialogElement.find('#hly-modal-tag-input').val();
}
updateAndSaveSetting(type, newConfig);
toastr.success('规则已保存。', '圣旨已达');
},
onShow: (dialogElement) => {
const rulesList = dialogElement.find('#hly-rules-list');
dialogElement.find('#hly-add-rule-btn').on('click', () => {
const newIndex = rulesList.children('.hly-exclusion-rule-row').length;
const newRowHtml = createRuleRowHtml(undefined, newIndex);
if (rulesList.find('p').length > 0) {
rulesList.html(newRowHtml);
} else {
rulesList.append(newRowHtml);
}
});
rulesList.on('click', '.hly-delete-rule-btn', function () {
$(this).closest('.hly-exclusion-rule-row').remove();
if (rulesList.children().length === 0) {
rulesList.html('<p class="hly-notes" style="text-align:center;">暂无规则</p>');
}
});
// 如果是检索预处理设置则绑定标签提取的UI事件
if (type === 'queryPreprocessing') {
const tagToggle = dialogElement.find('#hly-modal-tag-extraction-enabled');
const tagInputContainer = dialogElement.find('#hly-modal-tag-input-container');
tagToggle.on('change', () => {
tagInputContainer.css('display', tagToggle.is(':checked') ? 'block' : 'none');
});
}
}
});
}
function previewCondensation() {
const resultsEl = document.getElementById('hly-condensation-results');
try {
// 1. 获取UI设置和新规则
const settings = HanlinyuanCore.getSettings();
const exclusionRules = settings.condensation.exclusionRules || [];
const condensationRuleConfig = resolveCondensationRuleConfig(settings);
const exclusionRules = condensationRuleConfig.exclusionRules || [];
const overrideMessageTypes = {
user: document.getElementById('hly-include-user').checked,
ai: document.getElementById('hly-include-ai').checked,
};
const useTagExtraction = document.getElementById('hly-tag-extraction-toggle').checked;
const useTagExtraction = condensationRuleConfig.tagExtractionEnabled;
const tagsToExtract = useTagExtraction
? document.getElementById('hly-tag-input').value.split(',').map(t => t.trim()).filter(Boolean)
? (condensationRuleConfig.tags || '').split(',').map(t => t.trim()).filter(Boolean)
: [];
// 2. 获取原始消息

View File

@@ -5,8 +5,8 @@ import {
saveSettings,
} from "../utils/settings.js";
import { showHtmlModal } from './page-window.js';
import { applyExclusionRules, extractBlocksByTags } from '../core/utils/rag-tag-extractor.js';
import { configManager } from '../utils/config/ConfigManager.js';
import { ruleProfileManager, resolveHistoriographyRuleConfig } from '../utils/config/RuleProfileManager.js';
import {
getAvailableWorldbooks, getLoresForWorldbook,
@@ -17,6 +17,25 @@ import {
import { getNgmsApiSettings, testNgmsApiConnection, fetchNgmsModels } from "../core/api/Ngms_api.js";
function getHistoriographyRuleConfig() {
return resolveHistoriographyRuleConfig(extension_settings[extensionName] || {});
}
function _escapeHtml(text) {
return String(text ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function _populateHistRuleProfileSelect(select) {
const profiles = ruleProfileManager.listProfiles();
const assigned = ruleProfileManager.getAssignment('historiography') || '';
select.innerHTML = [
'<option value="">— 未分配 —</option>',
...profiles.map(p =>
`<option value="${p.id}" ${p.id === assigned ? 'selected' : ''}>${_escapeHtml(p.name || p.id)}</option>`
),
].join('');
}
function setupPromptEditor(type) {
const selector = document.getElementById(
@@ -199,29 +218,19 @@ export function bindHistoriographyEvents() {
saveSettings();
});
// ========== 🏷️ 标签与排除规则绑定 (新增) ==========
const tagExtractionToggle = document.getElementById("historiography-tag-extraction-toggle");
const tagInputContainer = document.getElementById("historiography-tag-input-container");
const tagInput = document.getElementById("historiography-tag-input");
const exclusionRulesBtn = document.getElementById("historiography-exclusion-rules-btn");
tagExtractionToggle.checked = extension_settings[extensionName].historiographyTagExtractionEnabled ?? false;
tagInput.value = extension_settings[extensionName].historiographyTags ?? '';
tagInputContainer.style.display = tagExtractionToggle.checked ? 'block' : 'none';
tagExtractionToggle.addEventListener("change", (event) => {
const isEnabled = event.target.checked;
extension_settings[extensionName].historiographyTagExtractionEnabled = isEnabled;
tagInputContainer.style.display = isEnabled ? 'block' : 'none';
saveSettings();
});
tagInput.addEventListener("change", (event) => {
extension_settings[extensionName].historiographyTags = event.target.value;
saveSettings();
});
exclusionRulesBtn.addEventListener("click", showHistoriographyExclusionRulesModal);
// ========== 提取规则下拉选单 ==========
const histRuleSelect = document.getElementById("historiography-rule-profile-select");
if (histRuleSelect) {
_populateHistRuleProfileSelect(histRuleSelect);
histRuleSelect.addEventListener("change", () => {
ruleProfileManager.setAssignment('historiography', histRuleSelect.value || null);
const name = histRuleSelect.selectedOptions[0]?.textContent || '';
toastr.info(histRuleSelect.value ? `史官提取规则已切换为「${name}` : '史官提取规则已取消分配');
});
document.addEventListener('amily2:ruleProfilesChanged', () => {
_populateHistRuleProfileSelect(histRuleSelect);
});
}
const expeditionExecuteBtn = document.getElementById("amily2_mhb_small_expedition_execute");
@@ -616,62 +625,3 @@ async function loadNgmsTavernPresets() {
}
}
function showHistoriographyExclusionRulesModal() {
const rules = extension_settings[extensionName].historiographyExclusionRules || [];
const createRuleRowHtml = (rule = { start: '', end: '' }, index) => `
<div class="hly-exclusion-rule-row" data-index="${index}">
<input type="text" class="hly-imperial-brush" value="${rule.start}" placeholder="开始字符, 如 <!--">
<span>到</span>
<input type="text" class="hly-imperial-brush" value="${rule.end}" placeholder="结束字符, 如 -->">
<button class="hly-delete-rule-btn" title="删除此规则">&times;</button>
</div>
`;
const rulesHtml = rules.map(createRuleRowHtml).join('');
const modalHtml = `
<div id="historiography-exclusion-rules-container">
<p class="hly-notes">在这里定义需要从提取内容中排除的文本片段。例如排除HTML注释可以设置开始字符为 \`<!--\`,结束字符为 \`-->\`。</p>
<div id="historiography-rules-list">${rulesHtml}</div>
<button id="historiography-add-rule-btn" class="hly-action-button" style="margin-top: 10px;">
<i class="fas fa-plus"></i> 添加新规则
</button>
</div>
<style>
.hly-exclusion-rule-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.hly-exclusion-rule-row input { flex-grow: 1; }
.hly-delete-rule-btn { background: #c0392b; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 16px; line-height: 24px; text-align: center; padding: 0; }
</style>
`;
showHtmlModal('编辑内容排除规则', modalHtml, {
okText: '保存规则',
onOk: (dialogElement) => {
const newRules = [];
dialogElement.find('.hly-exclusion-rule-row').each(function() {
const start = $(this).find('input').eq(0).val().trim();
const end = $(this).find('input').eq(1).val().trim();
if (start && end) {
newRules.push({ start, end });
}
});
extension_settings[extensionName].historiographyExclusionRules = newRules;
saveSettings();
toastr.success('内容排除规则已保存。', '圣旨已达');
},
onShow: (dialogElement) => {
const rulesList = dialogElement.find('#historiography-rules-list');
dialogElement.find('#historiography-add-rule-btn').on('click', () => {
const newIndex = rulesList.children().length;
const newRowHtml = createRuleRowHtml({ start: '', end: '' }, newIndex);
rulesList.append(newRowHtml);
});
rulesList.on('click', '.hly-delete-rule-btn', function() {
$(this).closest('.hly-exclusion-rule-row').remove();
});
}
});
}

151
ui/rule-config-bindings.js Normal file
View File

@@ -0,0 +1,151 @@
import { ruleProfileManager } from '../utils/config/RuleProfileManager.js';
let currentEditingId = null;
function createEmptyProfile() {
return {
id: '',
name: '',
tagExtractionEnabled: false,
tags: '',
exclusionRules: [],
};
}
function createRuleRow(rule = { start: '', end: '' }, index = 0) {
return `
<div class="amily2-rule-row" data-index="${index}">
<input type="text" class="text_pole amily2-rule-start" value="${escapeHtml(rule.start || '')}" placeholder="起始标记">
<input type="text" class="text_pole amily2-rule-end" value="${escapeHtml(rule.end || '')}" placeholder="结束标记">
<button type="button" class="menu_button danger small_button amily2-rule-remove">
<i class="fas fa-trash-alt"></i>
</button>
</div>
`;
}
function escapeHtml(text) {
return String(text ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function renderRules(container, exclusionRules = []) {
const list = container.find('#amily2_rule_profile_rules');
if (!exclusionRules.length) {
list.html('<p class="notes">当前没有排除规则。</p>');
return;
}
list.html(exclusionRules.map((rule, index) => createRuleRow(rule, index)).join(''));
}
function collectProfile(container) {
const exclusionRules = [];
container.find('.amily2-rule-row').each(function () {
const start = $(this).find('.amily2-rule-start').val().trim();
const end = $(this).find('.amily2-rule-end').val().trim();
if (start) {
exclusionRules.push({ start, end });
}
});
return {
id: currentEditingId || '',
name: container.find('#amily2_rule_profile_name').val().trim(),
tagExtractionEnabled: container.find('#amily2_rule_profile_tag_toggle').is(':checked'),
tags: container.find('#amily2_rule_profile_tags').val(),
exclusionRules,
};
}
function renderProfileList(container) {
const list = container.find('#amily2_rule_profile_list');
const profiles = ruleProfileManager.listProfiles();
if (!profiles.length) {
list.html('<p class="notes">还没有规则配置。</p>');
return;
}
list.html(profiles.map(profile => `
<button type="button" class="menu_button wide_button amily2-rule-profile-item" data-id="${profile.id}">
<span>${escapeHtml(profile.name || profile.id)}</span>
</button>
`).join(''));
}
function fillEditor(container, profile) {
const current = profile || createEmptyProfile();
currentEditingId = current.id || null;
container.find('#amily2_rule_profile_name').val(current.name || '');
container.find('#amily2_rule_profile_tag_toggle').prop('checked', !!current.tagExtractionEnabled);
container.find('#amily2_rule_profile_tags').val(current.tags || '');
container.find('#amily2_rule_profile_tags_wrap').toggle(!!current.tagExtractionEnabled);
renderRules(container, current.exclusionRules || []);
}
export function bindRuleConfigPanel(container) {
const $c = $(container);
renderProfileList($c);
fillEditor($c, createEmptyProfile());
$c.off('.ruleConfig');
$c.on('click.ruleConfig', '#amily2_rule_profile_new', () => {
fillEditor($c, createEmptyProfile());
});
$c.on('click.ruleConfig', '.amily2-rule-profile-item', function () {
const profile = ruleProfileManager.getProfile($(this).data('id'));
if (profile) {
fillEditor($c, profile);
}
});
$c.on('change.ruleConfig', '#amily2_rule_profile_tag_toggle', function () {
$c.find('#amily2_rule_profile_tags_wrap').toggle(this.checked);
});
$c.on('click.ruleConfig', '#amily2_rule_profile_add_rule', () => {
const rules = collectProfile($c).exclusionRules;
rules.push({ start: '', end: '' });
renderRules($c, rules);
});
$c.on('click.ruleConfig', '.amily2-rule-remove', function () {
$(this).closest('.amily2-rule-row').remove();
if ($c.find('.amily2-rule-row').length === 0) {
renderRules($c, []);
}
});
$c.on('click.ruleConfig', '#amily2_rule_profile_save', () => {
const profile = collectProfile($c);
if (!profile.name) {
toastr.warning('请先填写规则配置名称。');
return;
}
const saved = ruleProfileManager.saveProfile(profile);
fillEditor($c, saved);
renderProfileList($c);
document.dispatchEvent(new CustomEvent('amily2:ruleProfilesChanged'));
toastr.success('规则配置已保存。');
});
$c.on('click.ruleConfig', '#amily2_rule_profile_delete', () => {
if (!currentEditingId) {
return;
}
if (!confirm('删除当前规则配置?引用它的位置会回退到旧配置。')) {
return;
}
ruleProfileManager.deleteProfile(currentEditingId);
fillEditor($c, createEmptyProfile());
renderProfileList($c);
document.dispatchEvent(new CustomEvent('amily2:ruleProfilesChanged'));
toastr.success('规则配置已删除。');
});
}

View File

@@ -14,6 +14,7 @@ import { fetchNccsModels, testNccsApiConnection } from '../core/api/NccsApi.js';
import { showGraphVisualization } from '../core/relationship-graph/visualizer.js';
import { escapeHTML } from '../utils/utils.js';
import { configManager } from '../utils/config/ConfigManager.js';
import { ruleProfileManager } from '../utils/config/RuleProfileManager.js';
import { bindTableTemplateEditors } from './table/template-bindings.js';
import { bindNccsApiEvents as bindNccsApiSettingsEvents } from './table/nccs-bindings.js';
import { bindChatTableDisplaySetting as bindChatTableDisplaySettings } from './table/chat-display-bindings.js';
@@ -21,6 +22,23 @@ import { bindChatTableDisplaySetting as bindChatTableDisplaySettings } from './t
const isTouchDevice = () => window.matchMedia('(pointer: coarse)').matches;
const getAllTablesContainer = () => document.getElementById('all-tables-container');
/**
* 通用:填充规则配置下拉选单
* @param {HTMLSelectElement} select
* @param {string} slot — RULE_SLOTS 中的功能槽名
*/
function _populateRuleProfileSelect(select, slot) {
const profiles = ruleProfileManager.listProfiles();
const assigned = ruleProfileManager.getAssignment(slot) || '';
const options = [
'<option value="">— 未分配 —</option>',
...profiles.map(p =>
`<option value="${p.id}" ${p.id === assigned ? 'selected' : ''}>${escapeHTML(p.name || p.id)}</option>`
),
];
select.innerHTML = options.join('');
}
function getLiveExtensionSettings() {
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
@@ -782,73 +800,6 @@ export function renderTables() {
}
function openTableRuleEditor() {
const settings = getLiveExtensionSettings();
const tags = settings.table_tags_to_extract || '';
const exclusionRules = settings.table_exclusion_rules || [];
const rulesHtml = exclusionRules.map((rule, index) => `
<div class="exclusion-rule-item" data-index="${index}">
<input type="text" class="text_pole rule-start" value="${rule.start}" placeholder="起始标记">
<span>-</span>
<input type="text" class="text_pole rule-end" value="${rule.end}" placeholder="结束标记">
<button class="menu_button danger small_button remove-rule-btn"><i class="fas fa-trash-alt"></i></button>
</div>
`).join('');
const modalHtml = `
<div id="table-rules-editor" style="display: flex; flex-direction: column; gap: 20px;">
<div>
<label for="table-tags-input"><b>标签提取 (半角逗号分隔)</b></label>
<input type="text" id="table-tags-input" class="text_pole" value="${tags}" placeholder="例如: content,game,time">
<small class="notes">仅提取指定XML标签的内容例如填“content”即提取<content>...</content>中的内容。</small>
</div>
<div>
<label><b>内容排除规则</b></label>
<div id="exclusion-rules-list" style="display: flex; flex-direction: column; gap: 8px; margin-top: 8px;">${rulesHtml}</div>
<button id="add-exclusion-rule-btn" class="menu_button small_button" style="margin-top: 10px;"><i class="fas fa-plus"></i> 添加规则</button>
<small class="notes">移除所有被起始和结束标记包裹的内容(例如 OOC 部分)。</small>
</div>
</div>
`;
const dialog = showHtmlModal('配置独立提取规则', modalHtml, {
onOk: () => {
const newTags = document.getElementById('table-tags-input').value;
updateAndSaveTableSetting('table_tags_to_extract', newTags);
const newExclusionRules = [];
document.querySelectorAll('#exclusion-rules-list .exclusion-rule-item').forEach(item => {
const start = item.querySelector('.rule-start').value.trim();
const end = item.querySelector('.rule-end').value.trim();
if (start && end) {
newExclusionRules.push({ start, end });
}
});
updateAndSaveTableSetting('table_exclusion_rules', newExclusionRules);
toastr.success('独立提取规则已保存。');
},
onShow: (dialogElement) => {
const rulesList = dialogElement.find('#exclusion-rules-list');
dialogElement.find('#add-exclusion-rule-btn').on('click', () => {
const newIndex = rulesList.children().length;
const newItemHtml = `
<div class="exclusion-rule-item" data-index="${newIndex}">
<input type="text" class="text_pole rule-start" value="" placeholder="起始标记">
<span>-</span>
<input type="text" class="text_pole rule-end" value="" placeholder="结束标记">
<button class="menu_button danger small_button remove-rule-btn"><i class="fas fa-trash-alt"></i></button>
</div>`;
rulesList.append(newItemHtml);
});
rulesList.on('click', '.remove-rule-btn', function() {
$(this).closest('.exclusion-rule-item').remove();
});
}
});
}
function openRuleEditor(tableIndex) {
const tables = TableManager.getMemoryState();
@@ -1421,9 +1372,7 @@ export function bindTableEvents(panelElement = null) {
const bufferSlider = document.getElementById('secondary-filler-buffer');
const maxRetriesSlider = document.getElementById('secondary-filler-max-retries'); // 【新增】
const independentRulesContainer = document.getElementById('table-independent-rules-container');
const independentRulesToggle = document.getElementById('table-independent-rules-enabled');
const configureRulesBtn = document.getElementById('table-configure-rules-btn');
const tableRuleProfileSelect = document.getElementById('table-rule-profile-select');
const updateFillingModeUI = () => {
const currentMode = extension_settings[extensionName]?.filling_mode || 'main-api';
@@ -1437,12 +1386,8 @@ export function bindTableEvents(panelElement = null) {
secondaryFillerControls.style.display = isSecondaryMode ? 'block' : 'none';
}
if (independentRulesContainer) {
independentRulesContainer.style.display = 'flex';
}
if (independentRulesToggle && configureRulesBtn) {
configureRulesBtn.style.display = independentRulesToggle.checked ? 'block' : 'none';
if (tableRuleProfileSelect) {
_populateRuleProfileSelect(tableRuleProfileSelect, 'table');
}
};
@@ -1500,18 +1445,18 @@ export function bindTableEvents(panelElement = null) {
});
}
if (independentRulesToggle) {
independentRulesToggle.checked = extension_settings[extensionName]?.table_independent_rules_enabled ?? false;
independentRulesToggle.addEventListener('change', () => {
updateAndSaveTableSetting('table_independent_rules_enabled', independentRulesToggle.checked);
updateFillingModeUI();
});
}
updateFillingModeUI();
if (configureRulesBtn) {
configureRulesBtn.addEventListener('click', openTableRuleEditor);
if (tableRuleProfileSelect) {
_populateRuleProfileSelect(tableRuleProfileSelect, 'table');
tableRuleProfileSelect.addEventListener('change', () => {
ruleProfileManager.setAssignment('table', tableRuleProfileSelect.value || null);
const name = tableRuleProfileSelect.selectedOptions[0]?.textContent || '';
toastr.info(tableRuleProfileSelect.value ? `表格提取规则已切换为「${name}` : '表格提取规则已取消分配');
});
document.addEventListener('amily2:ruleProfilesChanged', () => {
_populateRuleProfileSelect(tableRuleProfileSelect, 'table');
});
}
const renderAll = () => {