Files
ST-Amily2-Chat-Optimisation…/MiZheSi/index.js
2025-09-07 06:10:58 +08:00

294 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { eventSource, event_types, main_api, stopGeneration } from '/script.js';
import { renderExtensionTemplateAsync } from '/scripts/extensions.js';
import { POPUP_RESULT, POPUP_TYPE, Popup } from '/scripts/popup.js';
import { t } from '/scripts/i18n.js';
import { extensionName } from '../utils/settings.js';
import { getTokenCountAsync } from '/scripts/tokenizers.js';
window.MiZheSi_Global = {
isEnabled: () => inspectEnabled,
};
const miZheSiPath = `third-party/${extensionName}/MiZheSi`;
const STORAGE_KEY = 'amily2_miZheSiEnabled';
if (!('GENERATE_AFTER_COMBINE_PROMPTS' in event_types) || !('CHAT_COMPLETION_PROMPT_READY' in event_types)) {
toastr.error('【密折司】错误您的SillyTavern版本过旧缺少必要的事件支持。请更新至最新版本。');
throw new Error('【密折司】缺少必要的事件支持。');
}
let inspectEnabled = false;
function addLaunchButton() {
const enabledText = '关闭Amliy2号密折司';
const disabledText = '开启Amliy2号密折司';
const iconClass = 'fa-solid fa-scroll';
const getText = () => inspectEnabled ? enabledText : disabledText;
const launchButton = document.createElement('div');
launchButton.id = 'miZheSiLaunchButton';
launchButton.classList.add('list-group-item', 'flex-container', 'flexGap5', 'interactable');
launchButton.tabIndex = 0;
launchButton.title = '切换【密折司】状态';
const icon = document.createElement('i');
icon.className = iconClass;
launchButton.appendChild(icon);
const textSpan = document.createElement('span');
textSpan.textContent = getText();
launchButton.appendChild(textSpan);
const extensionsMenu = document.getElementById('extensionsMenu');
if (!extensionsMenu) {
console.error('【密折司】无法找到左下角扩展菜单 (extensionsMenu)。');
return;
}
if (document.getElementById(launchButton.id)) {
return;
}
extensionsMenu.appendChild(launchButton);
launchButton.addEventListener('click', () => {
toggleInspectNext();
textSpan.textContent = getText();
launchButton.classList.toggle('active', inspectEnabled);
});
launchButton.classList.toggle('active', inspectEnabled);
}
function toggleInspectNext() {
inspectEnabled = !inspectEnabled;
toastr.info(`【密折司】已${inspectEnabled ? '开启' : '关闭'}`);
localStorage.setItem(STORAGE_KEY, String(inspectEnabled));
}
async function showPromptInspector(input) {
const template = $(await renderExtensionTemplateAsync(miZheSiPath, 'template'));
const container = template.find('#mizhesi-editor-container');
let isJsonMode = false;
const titleHeader = template.find('.mizhesi-header h3');
const charCountDisplay = $('<span id="mizhesi-char-count" style="font-size: 14px; color: #FFD700; margin-left: 15px; font-weight: normal;"></span>');
titleHeader.append(charCountDisplay);
const updateTotalCharCount = async () => {
let totalTokens = 0;
let totalChars = 0;
if (isJsonMode) {
const textareas = template.find('.mizhesi-message-textarea');
for (const textarea of textareas) {
const text = $(textarea).val();
totalTokens += await getTokenCountAsync(text);
totalChars += text.length;
}
} else {
const text = template.find('#mizhesi-plain-text-editor').val();
totalTokens = await getTokenCountAsync(text);
totalChars = text.length;
}
charCountDisplay.text(`(总 ${totalTokens} Tokens / ${totalChars} 字)`);
};
try {
const chat = JSON.parse(input);
if (Array.isArray(chat)) {
isJsonMode = true;
container.empty(); // 清空容器
for (const message of chat) {
const block = $(`
<div class="mizhesi-message-block" data-role="${message.role}">
<div class="mizhesi-message-header">
<span class="mizhesi-injection-icons" style="display: inline-flex; gap: 5px; margin-right: 10px; align-items: center;"></span>
<span class="mizhesi-line-char-count" style="font-weight: normal; color: #FFD700; margin-right: 10px;"></span>
<span class="mizhesi-role">${message.role}</span>
</div>
<div class="mizhesi-message-content">
<textarea class="mizhesi-message-textarea"></textarea>
</div>
</div>
`);
let content = message.content;
const iconsContainer = block.find('.mizhesi-injection-icons');
if (content.includes('%%HANLINYUAN_RAG_INJECTION%%')) {
content = content.replace('%%HANLINYUAN_RAG_INJECTION%%', '');
iconsContainer.append('<i class="fa-solid fa-brain" title="翰林院 RAG 注入" style="color: #66ccff;"></i>');
}
if (content.includes('%%AMILY2_TABLE_INJECTION%%')) {
content = content.replace('%%AMILY2_TABLE_INJECTION%%', '');
iconsContainer.append('<i class="fa-solid fa-table-cells" title="表格系统注入" style="color: #99cc33;"></i>');
}
const textarea = block.find('textarea');
textarea.val(content);
container.append(block);
const lineCharCountDisplay = block.find('.mizhesi-line-char-count');
const updateLineCharCount = async () => {
const text = textarea.val();
const lineTokens = await getTokenCountAsync(text);
const lineChars = text.length;
lineCharCountDisplay.text(`(${lineTokens} Tokens / ${lineChars} 字)`);
};
await updateLineCharCount(); // 初始化行字数
textarea.on('input', async () => {
await updateLineCharCount();
await updateTotalCharCount();
});
block.find('.mizhesi-message-header').on('click', function(e) {
if ($(e.target).is('.mizhesi-line-char-count, .mizhesi-injection-icons, .mizhesi-injection-icons *')) {
e.stopPropagation(); // 防止点击字数或图标时折叠
return;
}
const content = $(this).siblings('.mizhesi-message-content');
const parentBlock = $(this).closest('.mizhesi-message-block');
parentBlock.toggleClass('expanded');
content.slideToggle('fast');
});
}
} else {
throw new Error("Input is not a chat array.");
}
} catch (e) {
isJsonMode = false;
const textArea = $('<textarea id="mizhesi-plain-text-editor" style="width: 100%; height: 100%; box-sizing: border-box;"></textarea>');
textArea.val(input);
container.empty().append(textArea);
textArea.on('input', async () => await updateTotalCharCount());
}
await updateTotalCharCount(); // 初始化总字数
const searchInput = template.find('#mizhesi-search-input');
const searchButton = template.find('#mizhesi-search-button');
const clearButton = template.find('#mizhesi-clear-button');
const performSearch = () => {
const searchTerm = searchInput.val().trim();
if (!searchTerm) return;
clearHighlights();
let firstMatch = null;
const textareas = template.find('.mizhesi-message-textarea, #mizhesi-plain-text-editor');
textareas.each(function() {
const textarea = $(this);
const content = textarea.val();
const regex = new RegExp(searchTerm.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
if (regex.test(content)) {
textarea.addClass('mizhesi-highlight-border');
if (!firstMatch) {
firstMatch = textarea;
}
const block = textarea.closest('.mizhesi-message-block');
if (block.length && !block.hasClass('expanded')) {
block.addClass('expanded');
block.find('.mizhesi-message-content').slideDown('fast');
}
}
});
if (firstMatch) {
firstMatch[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
toastr.info('【密折司】未找到匹配项。');
}
};
const clearHighlights = () => {
template.find('.mizhesi-highlight-border').removeClass('mizhesi-highlight-border');
};
searchButton.on('click', performSearch);
searchInput.on('keypress', (e) => {
if (e.which === 13) { // Enter key
performSearch();
}
});
clearButton.on('click', clearHighlights);
const customButton = {
text: '取消生成',
result: POPUP_RESULT.CANCELLED,
appendAtEnd: true,
action: async () => {
await stopGeneration();
await popup.complete(POPUP_RESULT.CANCELLED);
},
};
const popup = new Popup(template, POPUP_TYPE.CONFIRM, '', {
wide: true,
large: true,
okButton: '确认修改',
cancelButton: '放弃修改',
customButtons: [customButton]
});
const result = await popup.show();
if (!result) {
return input; // 用户取消,返回原始输入
}
if (isJsonMode) {
const newChat = [];
template.find('.mizhesi-message-block').each(function() {
const role = $(this).data('role');
const content = $(this).find('textarea').val();
newChat.push({ role, content });
});
return JSON.stringify(newChat, null, 4);
} else {
return template.find('#mizhesi-plain-text-editor').val();
}
}
function isChatCompletion() {
return main_api === 'openai';
}
eventSource.on(event_types.GENERATE_AFTER_COMBINE_PROMPTS, async (data) => {
if (!inspectEnabled || data.dryRun || isChatCompletion()) return;
if (typeof data.prompt !== 'string') return;
const result = await showPromptInspector(data.prompt);
if (result !== data.prompt) {
data.prompt = result;
console.log('【密折司】奏章已按御笔修改 (Text Gen)。');
}
});
eventSource.on(event_types.CHAT_COMPLETION_PROMPT_READY, async (data) => {
if (!inspectEnabled || data.dryRun || !isChatCompletion()) return;
if (!Array.isArray(data.chat)) return;
const originalJson = JSON.stringify(data.chat, null, 4);
const resultJson = await showPromptInspector(originalJson);
if (resultJson === originalJson) return;
try {
const modifiedChat = JSON.parse(resultJson);
data.chat.splice(0, data.chat.length, ...modifiedChat);
console.log('【密折司】奏章已按御笔修改 (Chat Completion)。');
} catch (e) {
console.error('【密折司】解析修改后的JSON奏章失败:', e);
toastr.error('【密折司】解析JSON失败本次修改未生效。');
}
});
addLaunchButton();