mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 13:55:51 +00:00
ci: auto build & obfuscate [2026-04-19 03:01:12] (Jenkins #16)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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, '>');
|
||||
}
|
||||
|
||||
function escapeAttribute(text) {
|
||||
return String(text ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
|
||||
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="删除此规则">×</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. 获取原始消息
|
||||
|
||||
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
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="删除此规则">×</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
151
ui/rule-config-bindings.js
Normal 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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
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('规则配置已删除。');
|
||||
});
|
||||
}
|
||||
@@ -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 = () => {
|
||||
|
||||
Reference in New Issue
Block a user