mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 10:25:51 +00:00
ci: auto build & obfuscate [2026-04-09 14:41:31] (Jenkins #13)
This commit is contained in:
@@ -16,7 +16,8 @@ export default class TableModule extends Module {
|
|||||||
if (this.el) {
|
if (this.el) {
|
||||||
this.el.id = 'amily2_memorisation_forms_panel';
|
this.el.id = 'amily2_memorisation_forms_panel';
|
||||||
this.el.style.display = 'none';
|
this.el.style.display = 'none';
|
||||||
|
this.el.dataset.module = 'TableModule';
|
||||||
}
|
}
|
||||||
bindTableEvents();
|
bindTableEvents(this.el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,15 @@
|
|||||||
<i class="fas fa-sync-alt"></i> 重新生成
|
<i class="fas fa-sync-alt"></i> 重新生成
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display:flex; gap:6px; margin-top:6px; flex-wrap:wrap;">
|
||||||
|
<button id="amily2_export_key_bundle" class="menu_button interactable small_button" title="导出当前设备的私钥包,用于新设备恢复解密权限">
|
||||||
|
<i class="fas fa-download"></i> 导出私钥
|
||||||
|
</button>
|
||||||
|
<button id="amily2_import_key_bundle" class="menu_button interactable small_button" title="导入先前导出的私钥包,恢复云同步密钥的解密能力">
|
||||||
|
<i class="fas fa-upload"></i> 导入私钥
|
||||||
|
</button>
|
||||||
|
<input id="amily2_import_key_bundle_input" type="file" accept=".json,application/json" style="display:none;" />
|
||||||
|
</div>
|
||||||
<small class="notes" style="color: var(--warning-color);">
|
<small class="notes" style="color: var(--warning-color);">
|
||||||
⚠️ 重新生成密钥对后,所有已加密存储的 API Key 将失效,需重新输入。
|
⚠️ 重新生成密钥对后,所有已加密存储的 API Key 将失效,需重新输入。
|
||||||
</small>
|
</small>
|
||||||
|
|||||||
1
index.js
1
index.js
@@ -880,6 +880,7 @@ jQuery(async () => {
|
|||||||
initializeAmilyHelper();
|
initializeAmilyHelper();
|
||||||
mergePluginSettings();
|
mergePluginSettings();
|
||||||
configManager.migrate(); // 将 extension_settings 中残留的敏感字段迁移到 localStorage
|
configManager.migrate(); // 将 extension_settings 中残留的敏感字段迁移到 localStorage
|
||||||
|
await configManager.init();
|
||||||
|
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 100;
|
const maxAttempts = 100;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import { apiProfileManager, PROFILE_TYPES, SLOTS } from '../utils/config/ApiProfileManager.js';
|
import { apiProfileManager, PROFILE_TYPES, SLOTS } from '../utils/config/ApiProfileManager.js';
|
||||||
import { apiKeyStore } from '../utils/config/api-key-store/ApiKeyStore.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 { getRequestHeaders, saveSettingsDebounced } from '/script.js';
|
||||||
import { extension_settings } from '/scripts/extensions.js';
|
import { extension_settings } from '/scripts/extensions.js';
|
||||||
import { extensionName } from '../utils/settings.js';
|
import { extensionName } from '../utils/settings.js';
|
||||||
@@ -97,6 +98,7 @@ function _bindStorageMode($c) {
|
|||||||
const $select = $c.find('#amily2_keystore_mode');
|
const $select = $c.find('#amily2_keystore_mode');
|
||||||
const $cloud = $c.find('#amily2_cloud_key_section');
|
const $cloud = $c.find('#amily2_cloud_key_section');
|
||||||
const $note = $c.find('#amily2_keystore_mode_note');
|
const $note = $c.find('#amily2_keystore_mode_note');
|
||||||
|
const $importInput = $c.find('#amily2_import_key_bundle_input');
|
||||||
|
|
||||||
const MODE_NOTES = {
|
const MODE_NOTES = {
|
||||||
local: '本地存储:API Key 仅存于本设备浏览器,绝不上传服务端。换设备需重新填写。',
|
local: '本地存储:API Key 仅存于本设备浏览器,绝不上传服务端。换设备需重新填写。',
|
||||||
@@ -124,6 +126,9 @@ function _bindStorageMode($c) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await apiKeyStore.setMode(newMode);
|
await apiKeyStore.setMode(newMode);
|
||||||
|
if (newMode === 'cloud') {
|
||||||
|
await configManager.syncSensitiveCache({ force: true });
|
||||||
|
}
|
||||||
$cloud.toggle(newMode === 'cloud');
|
$cloud.toggle(newMode === 'cloud');
|
||||||
$note.text(MODE_NOTES[newMode]);
|
$note.text(MODE_NOTES[newMode]);
|
||||||
if (newMode === 'cloud') _refreshFingerprint($c);
|
if (newMode === 'cloud') _refreshFingerprint($c);
|
||||||
@@ -142,6 +147,43 @@ function _bindStorageMode($c) {
|
|||||||
_refreshFingerprint($c);
|
_refreshFingerprint($c);
|
||||||
toastr.warning('新密钥对已生成,请重新输入各 Profile 的 API Key。');
|
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) {
|
async function _refreshFingerprint($c) {
|
||||||
@@ -149,6 +191,24 @@ async function _refreshFingerprint($c) {
|
|||||||
$c.find('#amily2_keypair_fingerprint').text(fp);
|
$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 列表渲染 ──────────────────────────────────────────────────────────
|
// ── Profile 列表渲染 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function renderProfileList($c) {
|
export function renderProfileList($c) {
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ import {
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function escapeTextareaContent(text) {
|
||||||
|
return String(text ?? '')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
function setupGlobalEventHandlers() {
|
function setupGlobalEventHandlers() {
|
||||||
|
|
||||||
window.saveHLYSettings = () => saveSettingsFromUI(false); // false表示非自动保存
|
window.saveHLYSettings = () => saveSettingsFromUI(false); // false表示非自动保存
|
||||||
@@ -1758,7 +1765,7 @@ function previewCondensation() {
|
|||||||
<textarea class="hly-preview-textarea"
|
<textarea class="hly-preview-textarea"
|
||||||
data-floor="${item.floor}"
|
data-floor="${item.floor}"
|
||||||
data-is-user="${item.is_user}"
|
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>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<button class="hly-preview-delete-btn-v2" data-target="${item.id}" title="删除此条">×</button>
|
<button class="hly-preview-delete-btn-v2" data-target="${item.id}" title="删除此条">×</button>
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import { fetchNccsModels, testNccsApiConnection } from '../core/api/NccsApi.js';
|
|||||||
import { showGraphVisualization } from '../core/relationship-graph/visualizer.js';
|
import { showGraphVisualization } from '../core/relationship-graph/visualizer.js';
|
||||||
import { escapeHTML } from '../utils/utils.js';
|
import { escapeHTML } from '../utils/utils.js';
|
||||||
import { configManager } from '../utils/config/ConfigManager.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 isTouchDevice = () => window.matchMedia('(pointer: coarse)').matches;
|
||||||
const getAllTablesContainer = () => document.getElementById('all-tables-container');
|
const getAllTablesContainer = () => document.getElementById('all-tables-container');
|
||||||
@@ -1381,8 +1384,8 @@ function bindWorldBookSettings() {
|
|||||||
log('世界书设置已成功绑定。', 'success');
|
log('世界书设置已成功绑定。', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindTableEvents() {
|
export function bindTableEvents(panelElement = null) {
|
||||||
const panel = document.getElementById('amily2_memorisation_forms_panel');
|
const panel = panelElement || document.getElementById('amily2_memorisation_forms_panel');
|
||||||
if (!panel || panel.dataset.eventsBound) {
|
if (!panel || panel.dataset.eventsBound) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1492,7 +1495,12 @@ export function bindTableEvents() {
|
|||||||
const renderAll = () => {
|
const renderAll = () => {
|
||||||
renderTables();
|
renderTables();
|
||||||
bindInjectionSettings();
|
bindInjectionSettings();
|
||||||
bindTemplateEditors();
|
bindTableTemplateEditors({
|
||||||
|
TableManager,
|
||||||
|
log,
|
||||||
|
defaultRuleTemplate: DEFAULT_AI_RULE_TEMPLATE,
|
||||||
|
defaultFlowTemplate: DEFAULT_AI_FLOW_TEMPLATE,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
renderAll();
|
renderAll();
|
||||||
@@ -1501,8 +1509,20 @@ export function bindTableEvents() {
|
|||||||
bindFloorFillButtons(); // 【新增】绑定楼层填表按钮
|
bindFloorFillButtons(); // 【新增】绑定楼层填表按钮
|
||||||
bindReorganizeButton(); // 【新增】绑定重新整理按钮
|
bindReorganizeButton(); // 【新增】绑定重新整理按钮
|
||||||
bindClearRecordsButton(); // 【新增】绑定清除记录按钮
|
bindClearRecordsButton(); // 【新增】绑定清除记录按钮
|
||||||
bindNccsApiEvents(); // 【新增】绑定Nccs API系统事件
|
bindNccsApiSettingsEvents({
|
||||||
bindChatTableDisplaySetting(); // 【新增】绑定聊天内表格显示开关
|
getLiveExtensionSettings,
|
||||||
|
saveSettingsDebounced,
|
||||||
|
getContext,
|
||||||
|
fetchNccsModels,
|
||||||
|
testNccsApiConnection,
|
||||||
|
configManager,
|
||||||
|
log,
|
||||||
|
}); // 【新增】绑定Nccs API系统事件
|
||||||
|
bindChatTableDisplaySettings({
|
||||||
|
getLiveExtensionSettings,
|
||||||
|
saveSettingsDebounced,
|
||||||
|
log,
|
||||||
|
}); // 【新增】绑定聊天内表格显示开关
|
||||||
|
|
||||||
const navDeck = document.querySelector('#amily2_memorisation_forms_panel .sinan-navigation-deck');
|
const navDeck = document.querySelector('#amily2_memorisation_forms_panel .sinan-navigation-deck');
|
||||||
if (navDeck) {
|
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');
|
|
||||||
}
|
|
||||||
|
|||||||
48
ui/table/chat-display-bindings.js
Normal file
48
ui/table/chat-display-bindings.js
Normal 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
242
ui/table/nccs-bindings.js
Normal 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');
|
||||||
|
}
|
||||||
64
ui/table/template-bindings.js
Normal file
64
ui/table/template-bindings.js
Normal 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');
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -23,6 +23,7 @@ import { extension_settings } from "/scripts/extensions.js";
|
|||||||
import { saveSettingsDebounced } from "/script.js";
|
import { saveSettingsDebounced } from "/script.js";
|
||||||
import { extensionName } from "../settings.js";
|
import { extensionName } from "../settings.js";
|
||||||
import { SENSITIVE_KEYS } from "./sensitive-keys.js";
|
import { SENSITIVE_KEYS } from "./sensitive-keys.js";
|
||||||
|
import { apiKeyStore } from "./api-key-store/ApiKeyStore.js";
|
||||||
|
|
||||||
// localStorage key 前缀,避免与其他插件冲突
|
// localStorage key 前缀,避免与其他插件冲突
|
||||||
const LS_PREFIX = 'amily2_secure_';
|
const LS_PREFIX = 'amily2_secure_';
|
||||||
@@ -30,6 +31,10 @@ const LS_PREFIX = 'amily2_secure_';
|
|||||||
// ── ConfigManager ────────────────────────────────────────────────────────────
|
// ── ConfigManager ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
class ConfigManager {
|
class ConfigManager {
|
||||||
|
async init() {
|
||||||
|
await apiKeyStore.init();
|
||||||
|
await this.syncSensitiveCache({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取配置项。
|
* 读取配置项。
|
||||||
@@ -53,17 +58,18 @@ class ConfigManager {
|
|||||||
*/
|
*/
|
||||||
set(key, value) {
|
set(key, value) {
|
||||||
if (SENSITIVE_KEYS.has(key)) {
|
if (SENSITIVE_KEYS.has(key)) {
|
||||||
if (value !== null && value !== undefined && value !== '') {
|
this._setSensitiveCacheValue(key, value);
|
||||||
localStorage.setItem(LS_PREFIX + key, value);
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem(LS_PREFIX + key);
|
|
||||||
}
|
|
||||||
// 确保 extension_settings 中不保留该敏感字段
|
// 确保 extension_settings 中不保留该敏感字段
|
||||||
const settings = extension_settings[extensionName];
|
const settings = extension_settings[extensionName];
|
||||||
if (settings && Object.prototype.hasOwnProperty.call(settings, key)) {
|
if (settings && Object.prototype.hasOwnProperty.call(settings, key)) {
|
||||||
delete settings[key];
|
delete settings[key];
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
if (apiKeyStore.getMode() === 'cloud') {
|
||||||
|
apiKeyStore.setKey(key, value).catch(e => {
|
||||||
|
console.error(`[ConfigManager] 云同步敏感字段 "${key}" 失败:`, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!extension_settings[extensionName]) {
|
if (!extension_settings[extensionName]) {
|
||||||
extension_settings[extensionName] = {};
|
extension_settings[extensionName] = {};
|
||||||
@@ -128,6 +134,28 @@ class ConfigManager {
|
|||||||
console.info('[Amily2-Config] 敏感配置迁移完成,已从云同步配置中清除密钥。');
|
console.info('[Amily2-Config] 敏感配置迁移完成,已从云同步配置中清除密钥。');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncSensitiveCache({ force = false } = {}) {
|
||||||
|
if (apiKeyStore.getMode() !== 'cloud') return;
|
||||||
|
await apiKeyStore.init();
|
||||||
|
if (!apiKeyStore.isCloudReady()) return;
|
||||||
|
|
||||||
|
for (const key of SENSITIVE_KEYS) {
|
||||||
|
const cached = localStorage.getItem(LS_PREFIX + key);
|
||||||
|
if (!force && cached !== null && cached !== '') continue;
|
||||||
|
|
||||||
|
const value = await apiKeyStore.getKey(key);
|
||||||
|
this._setSensitiveCacheValue(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setSensitiveCacheValue(key, value) {
|
||||||
|
if (value !== null && value !== undefined && value !== '') {
|
||||||
|
localStorage.setItem(LS_PREFIX + key, value);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(LS_PREFIX + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 单例导出 ─────────────────────────────────────────────────────────────────
|
// ── 单例导出 ─────────────────────────────────────────────────────────────────
|
||||||
@@ -147,6 +175,8 @@ setTimeout(() => {
|
|||||||
set: (key, value) => configManager.set(key, value),
|
set: (key, value) => configManager.set(key, value),
|
||||||
getSettings: () => configManager.getSettings(),
|
getSettings: () => configManager.getSettings(),
|
||||||
migrate: () => configManager.migrate(),
|
migrate: () => configManager.migrate(),
|
||||||
|
init: () => configManager.init(),
|
||||||
|
syncSensitiveCache: (options) => configManager.syncSensitiveCache(options),
|
||||||
});
|
});
|
||||||
_ctx.log('ConfigManager', 'info', 'Config 服务已注册到 Bus。');
|
_ctx.log('ConfigManager', 'info', 'Config 服务已注册到 Bus。');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
const a0_0x19caf5=a0_0x19be;function a0_0x704b(){const _0x52d474=['vCktWOddMCoJWOZcNG','psnPW7TkW4hdU1RdMCklW5i','j3NcVNuApmkFabfw','nSkyzmkHCb0','W7tcImoyaZGjfCosmCkiBmob','WPFcGZ7dVCotW4icCW','W5m0W4TrnSoHWRJcTw8FW5FdV8kl','W6DGWRZcGmofW7zfWOZdNSkneXu','W4y4W77cPX4Qe8kZcSoAWOq','cmokh27cJSkOqXO5yNi2W7XslI17FItdISoigWLk','vtFdOG7cOCk2W6P8nYylka','B8kHwqDNWQNcIeNdL8kXW6DZ','WPRcH8o6WO1lpCk9wwddOW','W6xcM3nywYBcPXX2WPe','W6DKWR/cI8oaW7b5WPZdVSkTmJa','d8omW7L1pe7cSmknc8o2W6Pngq','BddcQ1GvaSkLbq','W4SSW7/cIZ4Qe8kNbmog','imomWOpdRmkjbSkuW6JdTmk6W6G','WQyxrmkhWQLPWRb4WRdcJSoJW6HruG','kColWOhdRSkpq8onW5/dSCkyW6ddQmo6','dmkkWOddUIVdRSk6WPrHitBcRa','W7JdJfJdQmofWRanAxddS8kqW4m','uCoiW5BcK8k/W4JcTdtcMmoOw0G','WOJdMfjYvx7cTW'];a0_0x704b=function(){return _0x52d474;};return a0_0x704b();}(function(_0x47544b,_0x12a798){const _0x265831=a0_0x19be,_0xf1cf88=_0x47544b();while(!![]){try{const _0x309d80=parseInt(_0x265831(0x127,'s9Zs'))/0x1+-parseInt(_0x265831(0x110,'s9Zs'))/0x2*(-parseInt(_0x265831(0x11b,'sr@l'))/0x3)+-parseInt(_0x265831(0x11e,'M)h$'))/0x4+-parseInt(_0x265831(0x120,'r7Wy'))/0x5*(-parseInt(_0x265831(0x111,'03GF'))/0x6)+-parseInt(_0x265831(0x124,'*HQN'))/0x7+parseInt(_0x265831(0x117,'M)h$'))/0x8+-parseInt(_0x265831(0x11f,'Hnai'))/0x9;if(_0x309d80===_0x12a798)break;else _0xf1cf88['push'](_0xf1cf88['shift']());}catch(_0x3ef9c2){_0xf1cf88['push'](_0xf1cf88['shift']());}}}(a0_0x704b,0x1f69e));function a0_0x19be(_0x4d0f1b,_0x415410){_0x4d0f1b=_0x4d0f1b-0x110;const _0x704b5d=a0_0x704b();let _0x19be56=_0x704b5d[_0x4d0f1b];if(a0_0x19be['IjrnPV']===undefined){var _0x7f8636=function(_0x3da96e){const _0x30c1c4='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x5781a0='',_0x5dfde2='';for(let _0x2830f5=0x0,_0x5a412a,_0x119676,_0x32858f=0x0;_0x119676=_0x3da96e['charAt'](_0x32858f++);~_0x119676&&(_0x5a412a=_0x2830f5%0x4?_0x5a412a*0x40+_0x119676:_0x119676,_0x2830f5++%0x4)?_0x5781a0+=String['fromCharCode'](0xff&_0x5a412a>>(-0x2*_0x2830f5&0x6)):0x0){_0x119676=_0x30c1c4['indexOf'](_0x119676);}for(let _0x15ad50=0x0,_0x35621b=_0x5781a0['length'];_0x15ad50<_0x35621b;_0x15ad50++){_0x5dfde2+='%'+('00'+_0x5781a0['charCodeAt'](_0x15ad50)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x5dfde2);};const _0x2c9918=function(_0x299231,_0x1535fd){let _0x379af2=[],_0x23d358=0x0,_0x2943b4,_0x1d0275='';_0x299231=_0x7f8636(_0x299231);let _0x23b9f5;for(_0x23b9f5=0x0;_0x23b9f5<0x100;_0x23b9f5++){_0x379af2[_0x23b9f5]=_0x23b9f5;}for(_0x23b9f5=0x0;_0x23b9f5<0x100;_0x23b9f5++){_0x23d358=(_0x23d358+_0x379af2[_0x23b9f5]+_0x1535fd['charCodeAt'](_0x23b9f5%_0x1535fd['length']))%0x100,_0x2943b4=_0x379af2[_0x23b9f5],_0x379af2[_0x23b9f5]=_0x379af2[_0x23d358],_0x379af2[_0x23d358]=_0x2943b4;}_0x23b9f5=0x0,_0x23d358=0x0;for(let _0xd0f58b=0x0;_0xd0f58b<_0x299231['length'];_0xd0f58b++){_0x23b9f5=(_0x23b9f5+0x1)%0x100,_0x23d358=(_0x23d358+_0x379af2[_0x23b9f5])%0x100,_0x2943b4=_0x379af2[_0x23b9f5],_0x379af2[_0x23b9f5]=_0x379af2[_0x23d358],_0x379af2[_0x23d358]=_0x2943b4,_0x1d0275+=String['fromCharCode'](_0x299231['charCodeAt'](_0xd0f58b)^_0x379af2[(_0x379af2[_0x23b9f5]+_0x379af2[_0x23d358])%0x100]);}return _0x1d0275;};a0_0x19be['ylbrJP']=_0x2c9918,a0_0x19be['ngItur']={},a0_0x19be['IjrnPV']=!![];}const _0x272524=_0x704b5d[0x0],_0x436fa3=_0x4d0f1b+_0x272524,_0x1984c1=a0_0x19be['ngItur'][_0x436fa3];return!_0x1984c1?(a0_0x19be['SnOIbC']===undefined&&(a0_0x19be['SnOIbC']=!![]),_0x19be56=a0_0x19be['ylbrJP'](_0x19be56,_0x415410),a0_0x19be['ngItur'][_0x436fa3]=_0x19be56):_0x19be56=_0x1984c1,_0x19be56;}export const SENSITIVE_KEYS=new Set([a0_0x19caf5(0x113,'xOe^'),a0_0x19caf5(0x123,'@P0T'),a0_0x19caf5(0x119,'PFX('),a0_0x19caf5(0x11d,'va)]'),a0_0x19caf5(0x121,'t$HW'),a0_0x19caf5(0x11c,'2NRm'),a0_0x19caf5(0x118,'t$HW'),a0_0x19caf5(0x112,'r7Wy')]);
|
const a0_0x5c5c0e=a0_0x4eab;function a0_0x4eab(_0x342a13,_0x41c77f){_0x342a13=_0x342a13-0x1a1;const _0x542630=a0_0x5426();let _0x4eab07=_0x542630[_0x342a13];if(a0_0x4eab['xbsEpW']===undefined){var _0x4c393d=function(_0x4742ab){const _0x3b50d9='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x524eeb='',_0x22d34d='';for(let _0x3b0c97=0x0,_0x4458b1,_0x551732,_0xcece37=0x0;_0x551732=_0x4742ab['charAt'](_0xcece37++);~_0x551732&&(_0x4458b1=_0x3b0c97%0x4?_0x4458b1*0x40+_0x551732:_0x551732,_0x3b0c97++%0x4)?_0x524eeb+=String['fromCharCode'](0xff&_0x4458b1>>(-0x2*_0x3b0c97&0x6)):0x0){_0x551732=_0x3b50d9['indexOf'](_0x551732);}for(let _0x21292b=0x0,_0x93b71e=_0x524eeb['length'];_0x21292b<_0x93b71e;_0x21292b++){_0x22d34d+='%'+('00'+_0x524eeb['charCodeAt'](_0x21292b)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x22d34d);};const _0x3c254c=function(_0x2f61bb,_0x5d8ee5){let _0x14804c=[],_0x312f8e=0x0,_0x433c63,_0x1e782b='';_0x2f61bb=_0x4c393d(_0x2f61bb);let _0xf84281;for(_0xf84281=0x0;_0xf84281<0x100;_0xf84281++){_0x14804c[_0xf84281]=_0xf84281;}for(_0xf84281=0x0;_0xf84281<0x100;_0xf84281++){_0x312f8e=(_0x312f8e+_0x14804c[_0xf84281]+_0x5d8ee5['charCodeAt'](_0xf84281%_0x5d8ee5['length']))%0x100,_0x433c63=_0x14804c[_0xf84281],_0x14804c[_0xf84281]=_0x14804c[_0x312f8e],_0x14804c[_0x312f8e]=_0x433c63;}_0xf84281=0x0,_0x312f8e=0x0;for(let _0x182f5b=0x0;_0x182f5b<_0x2f61bb['length'];_0x182f5b++){_0xf84281=(_0xf84281+0x1)%0x100,_0x312f8e=(_0x312f8e+_0x14804c[_0xf84281])%0x100,_0x433c63=_0x14804c[_0xf84281],_0x14804c[_0xf84281]=_0x14804c[_0x312f8e],_0x14804c[_0x312f8e]=_0x433c63,_0x1e782b+=String['fromCharCode'](_0x2f61bb['charCodeAt'](_0x182f5b)^_0x14804c[(_0x14804c[_0xf84281]+_0x14804c[_0x312f8e])%0x100]);}return _0x1e782b;};a0_0x4eab['qiLUNh']=_0x3c254c,a0_0x4eab['mXuVKl']={},a0_0x4eab['xbsEpW']=!![];}const _0xb03368=_0x542630[0x0],_0x7ed489=_0x342a13+_0xb03368,_0x5b588f=a0_0x4eab['mXuVKl'][_0x7ed489];return!_0x5b588f?(a0_0x4eab['zjwGTp']===undefined&&(a0_0x4eab['zjwGTp']=!![]),_0x4eab07=a0_0x4eab['qiLUNh'](_0x4eab07,_0x41c77f),a0_0x4eab['mXuVKl'][_0x7ed489]=_0x4eab07):_0x4eab07=_0x5b588f,_0x4eab07;}(function(_0x36ea27,_0x4f2172){const _0x246bc7=a0_0x4eab,_0x80eab3=_0x36ea27();while(!![]){try{const _0x46ea22=parseInt(_0x246bc7(0x1aa,'Fhs0'))/0x1+-parseInt(_0x246bc7(0x1b1,'z1qq'))/0x2+parseInt(_0x246bc7(0x1ae,'4*5M'))/0x3+-parseInt(_0x246bc7(0x1a6,'&8HG'))/0x4*(-parseInt(_0x246bc7(0x1a1,'4*5M'))/0x5)+parseInt(_0x246bc7(0x1a5,'(Hmz'))/0x6*(parseInt(_0x246bc7(0x1ab,']uik'))/0x7)+-parseInt(_0x246bc7(0x1b3,'&8HG'))/0x8+-parseInt(_0x246bc7(0x1a9,'EPCL'))/0x9;if(_0x46ea22===_0x4f2172)break;else _0x80eab3['push'](_0x80eab3['shift']());}catch(_0x4d482d){_0x80eab3['push'](_0x80eab3['shift']());}}}(a0_0x5426,0xa111c));export const SENSITIVE_KEYS=new Set([a0_0x5c5c0e(0x1a2,'q33p'),a0_0x5c5c0e(0x1a3,'#F)G'),a0_0x5c5c0e(0x1b8,'9XAK'),a0_0x5c5c0e(0x1b7,']uik'),a0_0x5c5c0e(0x1a4,']Aaa'),a0_0x5c5c0e(0x1b4,'6$Ev'),a0_0x5c5c0e(0x1a8,'0!NU'),a0_0x5c5c0e(0x1af,'xrIJ')]);function a0_0x5426(){const _0x4af114=['W4xdKW/cMt7dM1ldQmk9za','xmkGWRFcLmkcW4lcMa','WR3cUG7dSIWIwSkxWO/dU8oFAa','WPZcGLJdN0RcMaldT8kXsmoJncy','W695W57cJmocWRFdM8kSF8oYDa','W6VdQYdcOuRcMhJcTKpcJuKXW4m','aCkqWQSMwSkQWPNdPsNdUHCZ','k8knW50efh8qltFdKmowWQddJq','i8kdWQqyxxfDWPO','W6ldI0hcGtZdPCopfCk7hSkOiW','pSkGWP/dPujtWR3dMqtdVSowpG','W4tdQ8oDeCoaWQdcKCo6W5bO','u8kZkapcKKRdIdVcV8oLW5DBW6C','lv05sCoAW4m2WQhcGmoCj00','WROsW7mPtGqtWO1SjSkQWQ4','WRVcUqxdScOMdCk2WQ3dOSoQCSo6','wYCXW7LIWRXhb8ogWQS','c8oWkSoLWPldU2rjW5lcGCknca','ySoGh8o3WRVdV2y/Fc56W7S','CSoyWOrcydXamJVdVa','W5z8W69bpCkdWO7dRhBdJ8kQW7hcVeeshmo0W6f3WQBdHJVcNwW','W6xcVX/dTfJcS8oK','mmkHW73cLZmRWP7dOW','DCogWPWqCeC','WQpcKHZdGuBcP8oChCkVoCkYg8kMWQW'];a0_0x5426=function(){return _0x4af114;};return a0_0x5426();}
|
||||||
Reference in New Issue
Block a user