ci: auto build & obfuscate [2026-04-09 14:41:31] (Jenkins #13)

This commit is contained in:
Jenkins CI
2026-04-09 14:41:31 +08:00
parent bddda1802f
commit 2291a871eb
14 changed files with 498 additions and 376 deletions

View File

@@ -8,6 +8,7 @@
import { apiProfileManager, PROFILE_TYPES, SLOTS } from '../utils/config/ApiProfileManager.js';
import { apiKeyStore } from '../utils/config/api-key-store/ApiKeyStore.js';
import { configManager } from '../utils/config/ConfigManager.js';
import { getRequestHeaders, saveSettingsDebounced } from '/script.js';
import { extension_settings } from '/scripts/extensions.js';
import { extensionName } from '../utils/settings.js';
@@ -97,6 +98,7 @@ function _bindStorageMode($c) {
const $select = $c.find('#amily2_keystore_mode');
const $cloud = $c.find('#amily2_cloud_key_section');
const $note = $c.find('#amily2_keystore_mode_note');
const $importInput = $c.find('#amily2_import_key_bundle_input');
const MODE_NOTES = {
local: '本地存储API Key 仅存于本设备浏览器,绝不上传服务端。换设备需重新填写。',
@@ -124,6 +126,9 @@ function _bindStorageMode($c) {
try {
await apiKeyStore.setMode(newMode);
if (newMode === 'cloud') {
await configManager.syncSensitiveCache({ force: true });
}
$cloud.toggle(newMode === 'cloud');
$note.text(MODE_NOTES[newMode]);
if (newMode === 'cloud') _refreshFingerprint($c);
@@ -142,6 +147,43 @@ function _bindStorageMode($c) {
_refreshFingerprint($c);
toastr.warning('新密钥对已生成,请重新输入各 Profile 的 API Key。');
});
$c.find('#amily2_export_key_bundle').on('click', async () => {
try {
const bundle = await apiKeyStore.exportPrivateKeyBundle();
_downloadJson(
`amily2-keystore-${_timestampForFilename()}.json`,
bundle
);
toastr.success('私钥包已导出,请妥善保管。');
} catch (e) {
console.error('[ApiConfig] 导出私钥包失败:', e);
toastr.error(e.message || '导出私钥包失败。');
}
});
$c.find('#amily2_import_key_bundle').on('click', () => {
$importInput.val('');
$importInput.trigger('click');
});
$importInput.on('change', async function () {
const file = this.files?.[0];
if (!file) return;
try {
const text = await file.text();
await apiKeyStore.importPrivateKeyBundle(text);
await configManager.syncSensitiveCache({ force: true });
await _refreshFingerprint($c);
toastr.success('私钥包导入成功,已尝试恢复云同步的 API Key 缓存。');
} catch (e) {
console.error('[ApiConfig] 导入私钥包失败:', e);
toastr.error(e.message || '导入私钥包失败。');
} finally {
$importInput.val('');
}
});
}
async function _refreshFingerprint($c) {
@@ -149,6 +191,24 @@ async function _refreshFingerprint($c) {
$c.find('#amily2_keypair_fingerprint').text(fp);
}
function _downloadJson(filename, data) {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
function _timestampForFilename() {
const now = new Date();
const pad = n => String(n).padStart(2, '0');
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
}
// ── Profile 列表渲染 ──────────────────────────────────────────────────────────
export function renderProfileList($c) {

View File

@@ -16,6 +16,13 @@ import {
'use strict';
function escapeTextareaContent(text) {
return String(text ?? '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function setupGlobalEventHandlers() {
window.saveHLYSettings = () => saveSettingsFromUI(false); // false表示非自动保存
@@ -1758,7 +1765,7 @@ function previewCondensation() {
<textarea class="hly-preview-textarea"
data-floor="${item.floor}"
data-is-user="${item.is_user}"
data-send-date="${item.send_date}">${item.content}</textarea>
data-send-date="${item.send_date}">${escapeTextareaContent(item.content)}</textarea>
</div>
</details>
<button class="hly-preview-delete-btn-v2" data-target="${item.id}" title="删除此条">&times;</button>

View File

@@ -14,6 +14,9 @@ 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 { 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';
const isTouchDevice = () => window.matchMedia('(pointer: coarse)').matches;
const getAllTablesContainer = () => document.getElementById('all-tables-container');
@@ -1381,8 +1384,8 @@ function bindWorldBookSettings() {
log('世界书设置已成功绑定。', 'success');
}
export function bindTableEvents() {
const panel = document.getElementById('amily2_memorisation_forms_panel');
export function bindTableEvents(panelElement = null) {
const panel = panelElement || document.getElementById('amily2_memorisation_forms_panel');
if (!panel || panel.dataset.eventsBound) {
return;
}
@@ -1492,7 +1495,12 @@ export function bindTableEvents() {
const renderAll = () => {
renderTables();
bindInjectionSettings();
bindTemplateEditors();
bindTableTemplateEditors({
TableManager,
log,
defaultRuleTemplate: DEFAULT_AI_RULE_TEMPLATE,
defaultFlowTemplate: DEFAULT_AI_FLOW_TEMPLATE,
});
};
renderAll();
@@ -1501,8 +1509,20 @@ export function bindTableEvents() {
bindFloorFillButtons(); // 【新增】绑定楼层填表按钮
bindReorganizeButton(); // 【新增】绑定重新整理按钮
bindClearRecordsButton(); // 【新增】绑定清除记录按钮
bindNccsApiEvents(); // 【新增】绑定Nccs API系统事件
bindChatTableDisplaySetting(); // 【新增】绑定聊天内表格显示开关
bindNccsApiSettingsEvents({
getLiveExtensionSettings,
saveSettingsDebounced,
getContext,
fetchNccsModels,
testNccsApiConnection,
configManager,
log,
}); // 【新增】绑定Nccs API系统事件
bindChatTableDisplaySettings({
getLiveExtensionSettings,
saveSettingsDebounced,
log,
}); // 【新增】绑定聊天内表格显示开关
const navDeck = document.querySelector('#amily2_memorisation_forms_panel .sinan-navigation-deck');
if (navDeck) {
@@ -1956,363 +1976,3 @@ function bindFloorFillButtons() {
}
}
function bindTemplateEditors() {
const ruleEditor = document.getElementById('ai-rule-template-editor');
const ruleSaveBtn = document.getElementById('ai-rule-template-save-btn');
const ruleRestoreBtn = document.getElementById('ai-rule-template-restore-btn');
const flowEditor = document.getElementById('ai-flow-template-editor');
const flowSaveBtn = document.getElementById('ai-flow-template-save-btn');
const flowRestoreBtn = document.getElementById('ai-flow-template-restore-btn');
if (!ruleEditor || !flowEditor || !ruleSaveBtn || !flowSaveBtn) {
log('无法找到指令模板编辑器或其按钮,绑定失败。', 'warn');
return;
}
if (ruleSaveBtn.dataset.templateEventsBound) {
return;
}
ruleEditor.value = TableManager.getBatchFillerRuleTemplate();
flowEditor.value = TableManager.getBatchFillerFlowTemplate();
ruleSaveBtn.addEventListener('click', () => {
TableManager.saveBatchFillerRuleTemplate(ruleEditor.value);
toastr.success('规则提示词已保存。');
log('批量填表-规则提示词已保存。', 'success');
});
flowSaveBtn.addEventListener('click', () => {
TableManager.saveBatchFillerFlowTemplate(flowEditor.value);
toastr.success('流程提示词已保存。');
log('批量填表-流程提示词已保存。', 'success');
});
ruleRestoreBtn.addEventListener('click', () => {
if (confirm('您确定要将规则提示词恢复为默认设置吗?')) {
ruleEditor.value = DEFAULT_AI_RULE_TEMPLATE;
TableManager.saveBatchFillerRuleTemplate(ruleEditor.value);
toastr.info('规则提示词已恢复为默认。');
log('批量填表-规则提示词已恢复默认。', 'info');
}
});
flowRestoreBtn.addEventListener('click', () => {
if (confirm('您确定要将流程提示词恢复为默认设置吗?')) {
flowEditor.value = DEFAULT_AI_FLOW_TEMPLATE;
TableManager.saveBatchFillerFlowTemplate(flowEditor.value);
toastr.info('流程提示词已恢复为默认。');
log('批量填表-流程提示词已恢复默认。', 'info');
}
});
ruleSaveBtn.dataset.templateEventsBound = 'true';
flowSaveBtn.dataset.templateEventsBound = 'true';
log('指令模板编辑器已成功绑定。', 'success');
}
function bindNccsApiEvents() {
const settings = getLiveExtensionSettings();
if (settings.nccsEnabled === undefined) settings.nccsEnabled = false;
if (settings.nccsFakeStreamEnabled === undefined) settings.nccsFakeStreamEnabled = false;
if (settings.nccsApiMode === undefined) settings.nccsApiMode = 'openai_test';
if (settings.nccsApiUrl === undefined) settings.nccsApiUrl = 'https://api.openai.com/v1';
if (settings.nccsModel === undefined) settings.nccsModel = '';
if (settings.nccsTavernProfile === undefined) settings.nccsTavernProfile = '';
const enabledToggle = document.getElementById('nccs-api-enabled');
const enabledFakeStreamToggle = document.getElementById('nccs-api-fakestream-enabled');
const configDiv = document.getElementById('nccs-api-config');
const modeSelect = document.getElementById('nccs-api-mode');
const urlInput = document.getElementById('nccs-api-url');
const keyInput = document.getElementById('nccs-api-key');
const modelInput = document.getElementById('nccs-api-model');
const presetSelect = document.getElementById('nccs-sillytavern-preset');
const testButton = document.getElementById('nccs-test-connection');
const fetchModelsButton = document.getElementById('nccs-fetch-models');
if (!enabledToggle || !configDiv) return;
enabledToggle.checked = settings.nccsEnabled;
enabledFakeStreamToggle.checked = settings.nccsFakeStreamEnabled;
if (modeSelect) modeSelect.value = settings.nccsApiMode;
if (urlInput) urlInput.value = settings.nccsApiUrl;
if (keyInput) keyInput.value = configManager.get('nccsApiKey') || '';
if (modelInput) modelInput.value = settings.nccsModel;
if (presetSelect) presetSelect.value = settings.nccsTavernProfile || '';
const updateConfigVisibility = () => {
configDiv.style.display = enabledToggle.checked ? 'block' : 'none';
};
updateConfigVisibility();
const updateModeBasedVisibility = () => {
if (!modeSelect) return;
const isSillyTavernMode = modeSelect.value === 'sillytavern_preset';
const isOpenAIMode = modeSelect.value === 'openai_test';
const presetContainer = presetSelect?.closest('.amily2_opt_settings_block');
if (presetContainer) {
presetContainer.style.display = isSillyTavernMode ? 'block' : 'none';
}
const fieldsToHideInPresetMode = [
{ element: urlInput, containerId: null },
{ element: keyInput, containerId: null },
{ element: modelInput, containerId: null }
];
fieldsToHideInPresetMode.forEach(({ element }) => {
if (element) {
const container = element.closest('.amily2_opt_settings_block');
if (container) {
container.style.display = isSillyTavernMode ? 'none' : 'block';
}
}
});
const buttonsContainer = testButton?.closest('.nccs-button-row');
if (buttonsContainer) {
buttonsContainer.style.display = 'flex';
}
};
updateModeBasedVisibility();
enabledToggle.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsEnabled = enabledToggle.checked;
saveSettingsDebounced();
updateConfigVisibility();
log(`Nccs API ${enabledToggle.checked ? '已启用' : '已禁用'}`, 'info');
});
enabledFakeStreamToggle.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsFakeStreamEnabled = enabledFakeStreamToggle.checked;
saveSettingsDebounced();
log(`Nccs API FakeStream ${enabledFakeStreamToggle.checked ? 'Enabled' : 'Disabled'}`, 'info');
});
if (modeSelect) {
modeSelect.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsApiMode = modeSelect.value;
saveSettingsDebounced();
updateModeBasedVisibility();
log(`Nccs API模式已切换为: ${modeSelect.value}`, 'info');
});
}
if (urlInput) {
const saveUrl = () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsApiUrl = urlInput.value;
saveSettingsDebounced();
};
urlInput.addEventListener('blur', saveUrl);
}
if (keyInput) {
const saveKey = () => {
configManager.set('nccsApiKey', keyInput.value);
};
keyInput.addEventListener('blur', saveKey);
}
if (modelInput) {
const saveModel = () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsModel = modelInput.value;
saveSettingsDebounced();
};
modelInput.addEventListener('blur', saveModel);
modelInput.addEventListener('input', saveModel);
}
if (presetSelect) {
presetSelect.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsTavernProfile = presetSelect.value;
saveSettingsDebounced();
});
}
if (testButton) {
testButton.addEventListener('click', async () => {
testButton.disabled = true;
testButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 测试中...';
try {
const success = await testNccsApiConnection();
if (success) {
toastr.success('Nccs API连接测试成功');
log('Nccs API连接测试成功', 'success');
} else {
toastr.error('Nccs API连接测试失败请检查配置');
log('Nccs API连接测试失败', 'error');
}
} catch (error) {
toastr.error('Nccs API连接测试出错' + error.message);
log('Nccs API连接测试出错' + error.message, 'error');
} finally {
testButton.disabled = false;
testButton.innerHTML = '<i class="fas fa-plug"></i> 测试连接';
}
});
}
if (fetchModelsButton) {
fetchModelsButton.addEventListener('click', async () => {
fetchModelsButton.disabled = true;
fetchModelsButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 获取中...';
if (urlInput) {
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsApiUrl = urlInput.value;
saveSettingsDebounced();
}
if (keyInput) {
configManager.set('nccsApiKey', keyInput.value);
}
try {
const models = await fetchNccsModels();
if (models && models.length > 0) {
let modelSelect = document.getElementById('nccs-api-model-select');
if (!modelSelect) {
modelSelect = document.createElement('select');
modelSelect.id = 'nccs-api-model-select';
modelSelect.className = 'text_pole';
modelInput.parentNode.insertBefore(modelSelect, modelInput.nextSibling);
}
modelSelect.innerHTML = '<option value="">-- 请选择模型 --</option>';
models.forEach(model => {
const option = document.createElement('option');
option.value = model.id || model.name;
option.textContent = model.name || model.id;
if ((model.id || model.name) === settings.nccsModel) {
option.selected = true;
}
modelSelect.appendChild(option);
});
modelInput.style.display = 'none';
modelSelect.style.display = 'block';
modelSelect.addEventListener('change', () => {
const selectedModel = modelSelect.value;
const currentSettings = getLiveExtensionSettings();
currentSettings.nccsModel = selectedModel;
modelInput.value = selectedModel;
saveSettingsDebounced();
});
toastr.success(`成功获取 ${models.length} 个模型`);
log(`Nccs API获取到 ${models.length} 个模型`, 'success');
} else {
toastr.warning('未获取到可用模型');
log('Nccs API未获取到可用模型', 'warn');
}
} catch (error) {
toastr.error('获取模型失败:' + error.message);
log('Nccs API获取模型失败' + error.message, 'error');
} finally {
fetchModelsButton.disabled = false;
fetchModelsButton.innerHTML = '<i class="fas fa-download"></i> 获取模型';
}
});
}
const loadSillyTavernPresets = async () => {
if (!presetSelect) return;
try {
const context = getContext();
if (!context?.extensionSettings?.connectionManager?.profiles) {
throw new Error('无法获取SillyTavern配置文件列表');
}
const profiles = context.extensionSettings.connectionManager.profiles;
const currentProfileId = settings.nccsTavernProfile;
presetSelect.innerHTML = '';
presetSelect.appendChild(new Option('选择预设', '', false, false));
if (profiles && profiles.length > 0) {
profiles.forEach(profile => {
const isSelected = profile.id === currentProfileId;
const option = new Option(profile.name, profile.id, isSelected, isSelected);
presetSelect.appendChild(option);
});
log(`成功加载 ${profiles.length} 个SillyTavern配置文件`, 'success');
} else {
log('未找到可用的SillyTavern配置文件', 'warn');
}
} catch (error) {
log('加载SillyTavern预设失败' + error.message, 'error');
}
};
if (modeSelect && presetSelect) {
modeSelect.addEventListener('change', () => {
if (modeSelect.value === 'sillytavern_preset') {
loadSillyTavernPresets();
}
});
if (settings.nccsApiMode === 'sillytavern_preset') {
loadSillyTavernPresets();
}
}
log('Nccs API事件绑定完成', 'success');
}
function bindChatTableDisplaySetting() {
const settings = getLiveExtensionSettings();
const showInChatToggle = document.getElementById('show-table-in-chat-toggle');
const continuousRenderToggle = document.getElementById('render-on-every-message-toggle');
if (!showInChatToggle || !continuousRenderToggle) {
log('找不到聊天内表格相关的开关,绑定失败。', 'warn');
return;
}
showInChatToggle.checked = settings.show_table_in_chat === true;
continuousRenderToggle.checked = settings.render_on_every_message === true;
const updateContinuousRenderState = () => {
if (showInChatToggle.checked) {
continuousRenderToggle.disabled = false;
continuousRenderToggle.closest('.control-block-with-switch').style.opacity = '1';
} else {
continuousRenderToggle.disabled = true;
continuousRenderToggle.closest('.control-block-with-switch').style.opacity = '0.5';
}
};
updateContinuousRenderState();
showInChatToggle.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.show_table_in_chat = showInChatToggle.checked;
saveSettingsDebounced();
toastr.info(`聊天内表格显示已${showInChatToggle.checked ? '开启' : '关闭'}`);
updateContinuousRenderState();
});
continuousRenderToggle.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.render_on_every_message = continuousRenderToggle.checked;
saveSettingsDebounced();
toastr.info(`持续渲染最新消息功能已${continuousRenderToggle.checked ? '开启' : '关闭'}。请切换聊天以应用更改。`);
});
log('聊天内表格显示设置及其依赖关系已成功绑定。', 'success');
}

View File

@@ -0,0 +1,48 @@
export function bindChatTableDisplaySetting({
getLiveExtensionSettings,
saveSettingsDebounced,
log,
}) {
const settings = getLiveExtensionSettings();
const showInChatToggle = document.getElementById('show-table-in-chat-toggle');
const continuousRenderToggle = document.getElementById('render-on-every-message-toggle');
if (!showInChatToggle || !continuousRenderToggle) {
log('Chat table display toggles not found, skip binding.', 'warn');
return;
}
showInChatToggle.checked = settings.show_table_in_chat === true;
continuousRenderToggle.checked = settings.render_on_every_message === true;
const updateContinuousRenderState = () => {
const controlBlock = continuousRenderToggle.closest('.control-block-with-switch');
if (showInChatToggle.checked) {
continuousRenderToggle.disabled = false;
if (controlBlock) controlBlock.style.opacity = '1';
return;
}
continuousRenderToggle.disabled = true;
if (controlBlock) controlBlock.style.opacity = '0.5';
};
updateContinuousRenderState();
showInChatToggle.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.show_table_in_chat = showInChatToggle.checked;
saveSettingsDebounced();
toastr.info(`Chat table display ${showInChatToggle.checked ? 'enabled' : 'disabled'}.`);
updateContinuousRenderState();
});
continuousRenderToggle.addEventListener('change', () => {
const currentSettings = getLiveExtensionSettings();
currentSettings.render_on_every_message = continuousRenderToggle.checked;
saveSettingsDebounced();
toastr.info(`Continuous chat render ${continuousRenderToggle.checked ? 'enabled' : 'disabled'}.`);
});
log('Chat table display settings bound.', 'success');
}

242
ui/table/nccs-bindings.js Normal file
View File

@@ -0,0 +1,242 @@
export function bindNccsApiEvents({
getLiveExtensionSettings,
saveSettingsDebounced,
getContext,
fetchNccsModels,
testNccsApiConnection,
configManager,
log,
}) {
const settings = getLiveExtensionSettings();
if (settings.nccsEnabled === undefined) settings.nccsEnabled = false;
if (settings.nccsFakeStreamEnabled === undefined) settings.nccsFakeStreamEnabled = false;
if (settings.nccsApiMode === undefined) settings.nccsApiMode = 'openai_test';
if (settings.nccsApiUrl === undefined) settings.nccsApiUrl = 'https://api.openai.com/v1';
if (settings.nccsModel === undefined) settings.nccsModel = '';
if (settings.nccsTavernProfile === undefined) settings.nccsTavernProfile = '';
const enabledToggle = document.getElementById('nccs-api-enabled');
const enabledFakeStreamToggle = document.getElementById('nccs-api-fakestream-enabled');
const configDiv = document.getElementById('nccs-api-config');
const modeSelect = document.getElementById('nccs-api-mode');
const urlInput = document.getElementById('nccs-api-url');
const keyInput = document.getElementById('nccs-api-key');
const modelInput = document.getElementById('nccs-api-model');
const presetSelect = document.getElementById('nccs-sillytavern-preset');
const testButton = document.getElementById('nccs-test-connection');
const fetchModelsButton = document.getElementById('nccs-fetch-models');
if (!enabledToggle || !enabledFakeStreamToggle || !configDiv) {
return;
}
enabledToggle.checked = settings.nccsEnabled;
enabledFakeStreamToggle.checked = settings.nccsFakeStreamEnabled;
if (modeSelect) modeSelect.value = settings.nccsApiMode;
if (urlInput) urlInput.value = settings.nccsApiUrl;
if (keyInput) keyInput.value = configManager.get('nccsApiKey') || '';
if (modelInput) modelInput.value = settings.nccsModel;
if (presetSelect) presetSelect.value = settings.nccsTavernProfile || '';
const updateConfigVisibility = () => {
configDiv.style.display = enabledToggle.checked ? 'block' : 'none';
};
const updateModeBasedVisibility = () => {
if (!modeSelect) return;
const isPresetMode = modeSelect.value === 'sillytavern_preset';
const presetContainer = presetSelect?.closest('.amily2_opt_settings_block');
if (presetContainer) {
presetContainer.style.display = isPresetMode ? 'block' : 'none';
}
[urlInput, keyInput, modelInput].forEach((element) => {
const container = element?.closest('.amily2_opt_settings_block');
if (container) {
container.style.display = isPresetMode ? 'none' : 'block';
}
});
const buttonsContainer = testButton?.closest('.nccs-button-row');
if (buttonsContainer) {
buttonsContainer.style.display = 'flex';
}
};
const saveSetting = (key, value) => {
const currentSettings = getLiveExtensionSettings();
currentSettings[key] = value;
saveSettingsDebounced();
};
const loadSillyTavernPresets = async () => {
if (!presetSelect) return;
try {
const context = getContext();
const profiles = context?.extensionSettings?.connectionManager?.profiles;
if (!profiles) {
throw new Error('Unable to load SillyTavern presets.');
}
const currentProfileId = getLiveExtensionSettings().nccsTavernProfile;
presetSelect.innerHTML = '';
presetSelect.appendChild(new Option('Select preset', '', false, false));
if (profiles.length === 0) {
log('No SillyTavern presets found.', 'warn');
return;
}
profiles.forEach((profile) => {
const isSelected = profile.id === currentProfileId;
presetSelect.appendChild(new Option(profile.name, profile.id, isSelected, isSelected));
});
log(`Loaded ${profiles.length} SillyTavern presets.`, 'success');
} catch (error) {
log(`Failed to load SillyTavern presets: ${error.message}`, 'error');
}
};
updateConfigVisibility();
updateModeBasedVisibility();
enabledToggle.addEventListener('change', () => {
saveSetting('nccsEnabled', enabledToggle.checked);
updateConfigVisibility();
log(`NCCS API ${enabledToggle.checked ? 'enabled' : 'disabled'}.`, 'info');
});
enabledFakeStreamToggle.addEventListener('change', () => {
saveSetting('nccsFakeStreamEnabled', enabledFakeStreamToggle.checked);
log(`NCCS fake stream ${enabledFakeStreamToggle.checked ? 'enabled' : 'disabled'}.`, 'info');
});
if (modeSelect) {
modeSelect.addEventListener('change', () => {
saveSetting('nccsApiMode', modeSelect.value);
updateModeBasedVisibility();
if (modeSelect.value === 'sillytavern_preset') {
loadSillyTavernPresets();
}
log(`NCCS API mode changed to ${modeSelect.value}.`, 'info');
});
}
if (urlInput) {
urlInput.addEventListener('blur', () => {
saveSetting('nccsApiUrl', urlInput.value);
});
}
if (keyInput) {
keyInput.addEventListener('blur', () => {
configManager.set('nccsApiKey', keyInput.value);
});
}
if (modelInput) {
const saveModel = () => saveSetting('nccsModel', modelInput.value);
modelInput.addEventListener('blur', saveModel);
modelInput.addEventListener('input', saveModel);
}
if (presetSelect) {
presetSelect.addEventListener('change', () => {
saveSetting('nccsTavernProfile', presetSelect.value);
});
}
if (testButton) {
testButton.addEventListener('click', async () => {
testButton.disabled = true;
testButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing...';
try {
const success = await testNccsApiConnection();
if (success) {
toastr.success('NCCS API connection succeeded.');
log('NCCS API connection succeeded.', 'success');
} else {
toastr.error('NCCS API connection failed.');
log('NCCS API connection failed.', 'error');
}
} catch (error) {
toastr.error(`NCCS API test failed: ${error.message}`);
log(`NCCS API test failed: ${error.message}`, 'error');
} finally {
testButton.disabled = false;
testButton.innerHTML = '<i class="fas fa-plug"></i> Test Connection';
}
});
}
if (fetchModelsButton && modelInput) {
fetchModelsButton.addEventListener('click', async () => {
fetchModelsButton.disabled = true;
fetchModelsButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...';
if (urlInput) {
saveSetting('nccsApiUrl', urlInput.value);
}
if (keyInput) {
configManager.set('nccsApiKey', keyInput.value);
}
try {
const models = await fetchNccsModels();
if (!models?.length) {
toastr.warning('No models returned.');
log('No NCCS models returned.', 'warn');
return;
}
let modelSelect = document.getElementById('nccs-api-model-select');
if (!modelSelect) {
modelSelect = document.createElement('select');
modelSelect.id = 'nccs-api-model-select';
modelSelect.className = 'text_pole';
modelInput.parentNode.insertBefore(modelSelect, modelInput.nextSibling);
}
const currentModel = getLiveExtensionSettings().nccsModel;
modelSelect.innerHTML = '<option value="">-- Select model --</option>';
models.forEach((model) => {
const value = model.id || model.name;
const option = document.createElement('option');
option.value = value;
option.textContent = model.name || model.id;
option.selected = value === currentModel;
modelSelect.appendChild(option);
});
modelInput.style.display = 'none';
modelSelect.style.display = 'block';
modelSelect.onchange = () => {
const selectedModel = modelSelect.value;
modelInput.value = selectedModel;
saveSetting('nccsModel', selectedModel);
};
toastr.success(`Loaded ${models.length} models.`);
log(`Loaded ${models.length} NCCS models.`, 'success');
} catch (error) {
toastr.error(`Failed to load models: ${error.message}`);
log(`Failed to load NCCS models: ${error.message}`, 'error');
} finally {
fetchModelsButton.disabled = false;
fetchModelsButton.innerHTML = '<i class="fas fa-download"></i> Fetch Models';
}
});
}
if (modeSelect?.value === 'sillytavern_preset' && presetSelect) {
loadSillyTavernPresets();
}
log('NCCS API settings bound.', 'success');
}

View File

@@ -0,0 +1,64 @@
export function bindTableTemplateEditors({
TableManager,
log,
defaultRuleTemplate,
defaultFlowTemplate,
}) {
const ruleEditor = document.getElementById('ai-rule-template-editor');
const ruleSaveBtn = document.getElementById('ai-rule-template-save-btn');
const ruleRestoreBtn = document.getElementById('ai-rule-template-restore-btn');
const flowEditor = document.getElementById('ai-flow-template-editor');
const flowSaveBtn = document.getElementById('ai-flow-template-save-btn');
const flowRestoreBtn = document.getElementById('ai-flow-template-restore-btn');
if (!ruleEditor || !flowEditor || !ruleSaveBtn || !flowSaveBtn) {
log('Template editors not found, skip binding.', 'warn');
return;
}
if (ruleSaveBtn.dataset.templateEventsBound) {
return;
}
ruleEditor.value = TableManager.getBatchFillerRuleTemplate();
flowEditor.value = TableManager.getBatchFillerFlowTemplate();
ruleSaveBtn.addEventListener('click', () => {
TableManager.saveBatchFillerRuleTemplate(ruleEditor.value);
toastr.success('Rule template saved.');
log('Batch filler rule template saved.', 'success');
});
flowSaveBtn.addEventListener('click', () => {
TableManager.saveBatchFillerFlowTemplate(flowEditor.value);
toastr.success('Flow template saved.');
log('Batch filler flow template saved.', 'success');
});
ruleRestoreBtn.addEventListener('click', () => {
if (!confirm('Restore the default rule template?')) {
return;
}
ruleEditor.value = defaultRuleTemplate;
TableManager.saveBatchFillerRuleTemplate(ruleEditor.value);
toastr.info('Rule template restored.');
log('Batch filler rule template restored.', 'info');
});
flowRestoreBtn.addEventListener('click', () => {
if (!confirm('Restore the default flow template?')) {
return;
}
flowEditor.value = defaultFlowTemplate;
TableManager.saveBatchFillerFlowTemplate(flowEditor.value);
toastr.info('Flow template restored.');
log('Batch filler flow template restored.', 'info');
});
ruleSaveBtn.dataset.templateEventsBound = 'true';
flowSaveBtn.dataset.templateEventsBound = 'true';
log('Template editors bound.', 'success');
}