mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 10:25:51 +00:00
release: v2.2.2 [2026-05-27 11:10:55]
### 新功能 - **Function Call 填表模式**:在填表设置中新增独立开关,启用后支持通过 OpenAI 兼容接口(DeepSeek / OpenRouter / 各类中转等)直接返回结构化操作列表,绕过 `<Amily2Edit>` 文本解析路径,填表更稳定 - 遇到不支持 `tool_choice` 的接口时自动降级重试 - 对思考模型注入强制调用指令,防止绕过工具直接输出文本 - 全部走 ST 后端代理,修复 CSP 拦截直连外部 URL 的问题 - **主界面新增提示词链编辑器入口**,同时调换了记忆管理与角色世界书的按钮位置 - **规则中心**新增"自动排除用户楼层"选项 ### 修复 - 提示词链按钮点击无响应(改为事件委托方式绑定) - 拖拽组件微抖误触发(加 5px 移动阈值过滤) - 填表检查窗若干问题修复;翰林院(批量回填)修复;防抖逻辑落地 - 角色世界书入口添加使用警告弹窗(强制 10 秒倒计时),提示该功能长期未维护 - ApiProfile `fakeStream` 字段保存丢失问题 - 正文优化默认改为关闭状态 - NGMS / NCCS API 配置槽位标签修正(NGMS→总结,NCCS→填表) - API Profile 面板选择逻辑统一重构,修复多处旧字段覆盖新配置的问题 - 世界书控制参数兼容性修复(排除递归、插入位置、扫描深度等,适配 ST 1.17.0+)
This commit is contained in:
@@ -10,8 +10,9 @@ import { setAvailableModels, populateModelDropdown, getLatestUpdateInfo } from "
|
||||
import { fixCommand, testReplyChecker } from "../core/commands.js";
|
||||
import { messageFormatting } from '/script.js';
|
||||
import { executeManualCommand } from '../core/autoHideManager.js';
|
||||
import { showContentModal, showHtmlModal } from './page-window.js';
|
||||
import { showContentModal, showHtmlModal, showCwbWarningModal } from './page-window.js';
|
||||
import { openAutoCharCardWindow } from '../core/auto-char-card/ui-bindings.js';
|
||||
import { showPresetSettings } from '../PresetSettings/prese_ui.js';
|
||||
|
||||
function displayDailyAuthCode() {
|
||||
const displayEl = document.getElementById('amily2_daily_code_display');
|
||||
@@ -806,7 +807,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_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 () {
|
||||
"#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_open_preset_editor, #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');
|
||||
@@ -874,7 +875,10 @@ export function bindModalEvents() {
|
||||
memorisationFormsPanel.show();
|
||||
break;
|
||||
case 'amily2_open_character_world_book':
|
||||
characterWorldBookPanel.show();
|
||||
showCwbWarningModal(
|
||||
() => characterWorldBookPanel.show(),
|
||||
() => mainPanel.show()
|
||||
);
|
||||
break;
|
||||
case 'amily2_open_world_editor':
|
||||
worldEditorPanel.show();
|
||||
@@ -891,6 +895,10 @@ export function bindModalEvents() {
|
||||
case 'amily2_open_sfigen':
|
||||
sfigenPanel.show();
|
||||
break;
|
||||
case 'amily2_open_preset_editor':
|
||||
showPresetSettings();
|
||||
mainPanel.show();
|
||||
return;
|
||||
case 'amily2_back_to_main_settings':
|
||||
case 'amily2_back_to_main_from_hanlinyuan':
|
||||
case 'amily2_back_to_main_from_forms':
|
||||
|
||||
@@ -77,16 +77,6 @@ function updateAndSaveSetting(key, 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');
|
||||
}
|
||||
|
||||
@@ -390,15 +380,7 @@ function bindInternalUIEvents() {
|
||||
}
|
||||
|
||||
// 注入设置的UI逻辑已由 initializeUnifiedInjectionEditor 函数统一处理。
|
||||
|
||||
// 【新增】为“标签提取”复选框绑定事件
|
||||
const tagExtractionToggle = document.getElementById('hly-tag-extraction-toggle');
|
||||
const tagInputContainer = document.getElementById('hly-tag-input-container');
|
||||
if (tagExtractionToggle && tagInputContainer) {
|
||||
tagExtractionToggle.addEventListener('change', () => {
|
||||
tagInputContainer.style.display = tagExtractionToggle.checked ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
// 标签提取开关/输入框已在 2.1.0 重构中移除,改为规则配置下拉选单管理。
|
||||
|
||||
// 为“书库选择”下拉框绑定联动事件
|
||||
const librarySelect = document.getElementById('hly-hist-select-library');
|
||||
@@ -664,17 +646,8 @@ export function loadSettingsToUI() {
|
||||
histMaxRetriesEl.value = settings.historiographyMaxRetries ?? 2;
|
||||
}
|
||||
|
||||
// 注:hly-tag-extraction-toggle / hly-tag-input / hly-tag-input-container 已从 HTML 移除,
|
||||
// 标签提取规则改由 RuleProfileManager 管理。此处保留兼容性 null 检查,避免抛错吞掉后续段落加载。
|
||||
const tagExtractionToggle = document.getElementById('hly-tag-extraction-toggle');
|
||||
const tagInput = document.getElementById('hly-tag-input');
|
||||
const tagInputContainer = document.getElementById('hly-tag-input-container');
|
||||
|
||||
if (tagExtractionToggle) tagExtractionToggle.checked = settings.condensation.tagExtractionEnabled;
|
||||
if (tagInput) tagInput.value = settings.condensation.tags;
|
||||
if (tagInputContainer && tagExtractionToggle) {
|
||||
tagInputContainer.style.display = tagExtractionToggle.checked ? 'block' : 'none';
|
||||
}
|
||||
// 标签提取开关/输入框已在 2.1.0 重构中移除(改为规则配置下拉选单),
|
||||
// 这里不再回填对应 DOM,避免因元素已不存在导致 loadSettingsToUI 中断。
|
||||
|
||||
// Rerank 设置
|
||||
document.getElementById('hly-rerank-enabled').checked = settings.rerank.enabled;
|
||||
|
||||
@@ -161,9 +161,151 @@ export function showSummaryModal(summaryText, callbacks) {
|
||||
regenerateButton.on('click', () => {
|
||||
if (onRegenerate) {
|
||||
dialogElement[0].close();
|
||||
onRegenerate(dialogElement);
|
||||
onRegenerate(dialogElement);
|
||||
}
|
||||
});
|
||||
|
||||
dialogElement.find('.popup-controls').prepend(regenerateButton);
|
||||
}
|
||||
|
||||
|
||||
export function showTableFillReviewModal(rawResponse, callbacks = {}) {
|
||||
const {
|
||||
title = '填表响应检查',
|
||||
subtitle = 'AI未返回有效的 <Amily2Edit> 指令块。您可以在下方查看/编辑原始响应,并选择后续处理方式。',
|
||||
onApply,
|
||||
onContinue,
|
||||
onRetry,
|
||||
onCancel,
|
||||
} = callbacks;
|
||||
|
||||
const modalHtml = `
|
||||
<div class="amily2-fill-review-modal">
|
||||
<div class="notes" style="margin-bottom: 10px; color: #ffb74d; line-height: 1.6;">
|
||||
<i class="fas fa-exclamation-triangle"></i> ${escapeHtml(subtitle)}
|
||||
</div>
|
||||
<textarea class="text_pole amily2-fill-review-text"
|
||||
style="width: 100%; height: 45vh; resize: vertical; font-family: var(--monoFontFamily, monospace); font-size: 12px; white-space: pre; overflow-wrap: normal; overflow-x: auto;"
|
||||
>${escapeHtml(rawResponse || '')}</textarea>
|
||||
<div class="notes" style="margin-top: 8px; font-size: 0.85em; opacity: 0.8; line-height: 1.6;">
|
||||
<div><b>继续补全</b>:让 AI 基于当前文本继续生成剩余内容,结果会追加到文本框后。</div>
|
||||
<div><b>重新填表</b>:舍弃当前响应并重新向 AI 请求同一批次的填表。</div>
|
||||
<div><b>手动应用</b>:将文本框中的当前内容直接作为最终结果写入表格(跳过格式校验)。</div>
|
||||
<div><b>取消</b>:放弃本次填表,任务暂停。</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const dialogElement = showHtmlModal(title, modalHtml, {
|
||||
okText: '手动应用',
|
||||
cancelText: '取消',
|
||||
showCancel: true,
|
||||
onOk: (dialog) => {
|
||||
const editedText = dialog.find('.amily2-fill-review-text').val();
|
||||
if (onApply) {
|
||||
onApply(editedText);
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const textarea = dialogElement.find('.amily2-fill-review-text');
|
||||
|
||||
if (typeof onContinue === 'function') {
|
||||
const continueButton = $('<button class="menu_button interactable" style="margin-right: auto;"><i class="fas fa-forward"></i> 继续补全</button>');
|
||||
continueButton.on('click', async () => {
|
||||
const currentText = textarea.val();
|
||||
textarea.prop('disabled', true);
|
||||
continueButton.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 正在请求补全...');
|
||||
try {
|
||||
const continued = await onContinue(currentText);
|
||||
if (typeof continued === 'string' && continued.length > 0) {
|
||||
textarea.val(continued);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Amily2 填表检查] 补全请求失败:', err);
|
||||
if (window.toastr) toastr.error(`补全失败: ${err.message || err}`, '继续补全');
|
||||
} finally {
|
||||
textarea.prop('disabled', false);
|
||||
continueButton.prop('disabled', false).html('<i class="fas fa-forward"></i> 继续补全');
|
||||
}
|
||||
});
|
||||
dialogElement.find('.popup-controls').prepend(continueButton);
|
||||
}
|
||||
|
||||
if (typeof onRetry === 'function') {
|
||||
const retryButton = $('<button class="menu_button secondary interactable"><i class="fas fa-redo"></i> 重新填表</button>');
|
||||
retryButton.on('click', () => {
|
||||
dialogElement[0].close();
|
||||
dialogElement.remove();
|
||||
onRetry();
|
||||
});
|
||||
const okBtn = dialogElement.find('.popup-button-ok');
|
||||
if (okBtn.length) {
|
||||
retryButton.insertBefore(okBtn);
|
||||
} else {
|
||||
dialogElement.find('.popup-controls').append(retryButton);
|
||||
}
|
||||
}
|
||||
|
||||
return dialogElement;
|
||||
}
|
||||
|
||||
const CWB_WARNING_COUNTDOWN = 10;
|
||||
|
||||
/**
|
||||
* 角色世界书入口警告弹窗,强制倒计时后才可继续。
|
||||
* @param {Function} onProceed - 用户点击"继续使用"时的回调
|
||||
* @param {Function} onClose - 用户点击"关闭退出"时的回调(含弹窗关闭前直接离开)
|
||||
*/
|
||||
export function showCwbWarningModal(onProceed, onClose) {
|
||||
const dialogHtml = `
|
||||
<dialog class="popup wide_dialogue_popup">
|
||||
<div class="popup-body">
|
||||
<h3 style="margin-top:0; color:#e8a838; border-bottom:1px solid rgba(255,255,255,0.2); padding-bottom:10px;">
|
||||
<i class="fas fa-exclamation-triangle" style="color:#e8a838;"></i> 注意 — 角色世界书功能维护状态
|
||||
</h3>
|
||||
<div style="line-height:1.8; padding:12px 4px; color:var(--SmartThemeBodyColor);">
|
||||
该功能长期未进行维护且其实现可被表格及其他功能替代,若非必须一般不建议使用,如确认希望使用,请明确该功能无法获得有效技术支持。
|
||||
</div>
|
||||
<div class="popup-controls" style="gap:8px;">
|
||||
<button class="cwb-warning-close menu_button secondary interactable">关闭退出</button>
|
||||
<button class="cwb-warning-proceed menu_button menu_button_primary interactable" disabled>
|
||||
继续使用(<span class="cwb-countdown">${CWB_WARNING_COUNTDOWN}</span>)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>`;
|
||||
|
||||
const $dialog = $(dialogHtml).appendTo('body');
|
||||
|
||||
const close = (cb) => {
|
||||
clearInterval(timer);
|
||||
$dialog[0].close();
|
||||
$dialog.remove();
|
||||
cb?.();
|
||||
};
|
||||
|
||||
$dialog.find('.cwb-warning-close').on('click', () => close(onClose));
|
||||
|
||||
$dialog.find('.cwb-warning-proceed').on('click', function () {
|
||||
if (!this.disabled) close(onProceed);
|
||||
});
|
||||
|
||||
let remaining = CWB_WARNING_COUNTDOWN;
|
||||
const timer = setInterval(() => {
|
||||
remaining -= 1;
|
||||
$dialog.find('.cwb-countdown').text(remaining);
|
||||
if (remaining <= 0) {
|
||||
clearInterval(timer);
|
||||
const $btn = $dialog.find('.cwb-warning-proceed');
|
||||
$btn.prop('disabled', false).html('继续使用');
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
$dialog[0].showModal();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ function createEmptyProfile() {
|
||||
tagExtractionEnabled: false,
|
||||
tags: '',
|
||||
exclusionRules: [],
|
||||
excludeUserMessages: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,6 +58,7 @@ function collectProfile(container) {
|
||||
tagExtractionEnabled: container.find('#amily2_rule_profile_tag_toggle').is(':checked'),
|
||||
tags: container.find('#amily2_rule_profile_tags').val(),
|
||||
exclusionRules,
|
||||
excludeUserMessages: container.find('#amily2_rule_profile_exclude_user').is(':checked'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,6 +85,7 @@ function fillEditor(container, profile) {
|
||||
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);
|
||||
container.find('#amily2_rule_profile_exclude_user').prop('checked', !!current.excludeUserMessages);
|
||||
renderRules(container, current.exclusionRules || []);
|
||||
}
|
||||
|
||||
|
||||
@@ -1371,6 +1371,7 @@ export function bindTableEvents(panelElement = null) {
|
||||
const batchSlider = document.getElementById('secondary-filler-batch');
|
||||
const bufferSlider = document.getElementById('secondary-filler-buffer');
|
||||
const maxRetriesSlider = document.getElementById('secondary-filler-max-retries'); // 【新增】
|
||||
const delaySlider = document.getElementById('secondary-filler-delay');
|
||||
|
||||
const tableRuleProfileSelect = document.getElementById('table-rule-profile-select');
|
||||
|
||||
@@ -1438,13 +1439,34 @@ export function bindTableEvents(panelElement = null) {
|
||||
if (maxRetriesSlider) {
|
||||
const value = extension_settings[extensionName]?.secondary_filler_max_retries ?? 2;
|
||||
maxRetriesSlider.value = value;
|
||||
|
||||
|
||||
maxRetriesSlider.addEventListener('change', function() {
|
||||
updateAndSaveTableSetting('secondary_filler_max_retries', parseInt(this.value, 10));
|
||||
toastr.info(`最大重试次数已设置为 ${this.value}。`);
|
||||
});
|
||||
}
|
||||
|
||||
if (delaySlider) {
|
||||
const value = extension_settings[extensionName]?.secondary_filler_delay ?? 0;
|
||||
delaySlider.value = value;
|
||||
|
||||
delaySlider.addEventListener('change', function() {
|
||||
const parsed = Math.max(0, parseInt(this.value, 10) || 0);
|
||||
this.value = parsed;
|
||||
updateAndSaveTableSetting('secondary_filler_delay', parsed);
|
||||
toastr.info(`触发延迟已设置为 ${parsed} 毫秒。`);
|
||||
});
|
||||
}
|
||||
|
||||
const fcToggle = document.getElementById('table-fill-function-call-enabled');
|
||||
if (fcToggle) {
|
||||
fcToggle.checked = extension_settings[extensionName]?.tableFillFunctionCall ?? false;
|
||||
fcToggle.addEventListener('change', function() {
|
||||
updateAndSaveTableSetting('tableFillFunctionCall', this.checked);
|
||||
toastr.info(`Function Call 填表已${this.checked ? '启用' : '禁用'}。`);
|
||||
});
|
||||
}
|
||||
|
||||
updateFillingModeUI();
|
||||
|
||||
if (tableRuleProfileSelect) {
|
||||
|
||||
Reference in New Issue
Block a user