ci: auto build & obfuscate [2026-05-16 19:16:28] (Jenkins #21)

This commit is contained in:
Jenkins CI
2026-05-16 19:16:28 +08:00
parent 4bc6e0a047
commit d9fa3072a2
46 changed files with 4154 additions and 1584 deletions

View File

@@ -6,7 +6,7 @@
* ApiKeyStore密钥存储
*/
import { apiProfileManager, PROFILE_TYPES, SLOTS } from '../utils/config/ApiProfileManager.js';
import { apiProfileManager, PROFILE_TYPES, SLOTS, clearLegacyConfig } 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';
@@ -17,6 +17,12 @@ import { testJqyhApiConnection } from '../core/api/JqyhApi.js';
import { testConcurrentApiConnection } from '../core/api/ConcurrentApi.js';
import { testNgmsApiConnection } from '../core/api/Ngms_api.js';
import { testNccsApiConnection } from '../core/api/NccsApi.js';
import {
getRegistry,
detectVendorSync,
listVendorParamsSync,
getVendorEntry,
} from '../utils/api-vendor.js';
// 槽位 → 真实测试函数映射(发送聊天请求验证连接)
// plotOpt 槽位同时服务剧情优化和 JQYH互斥根据启用状态选择测试函数
@@ -45,11 +51,21 @@ const SLOT_TOGGLES = {
let _editingId = null; // 当前编辑的 Profile IDnull = 新建)
let _currentFilter = 'all'; // 当前类型筛选
let _slotAssignmentPanel = null;
let _slotAssignmentRefreshBound = false;
// ── 入口:绑定整个面板 ────────────────────────────────────────────────────────
export function bindApiConfigPanel(container) {
const $c = $(container);
_slotAssignmentPanel = $c;
if (!_slotAssignmentRefreshBound) {
_slotAssignmentRefreshBound = true;
document.addEventListener('amily2:slotAssigned', () => {
if (_slotAssignmentPanel) renderSlotAssignments(_slotAssignmentPanel);
});
}
// 存储模式
_bindStorageMode($c);
@@ -70,9 +86,11 @@ export function bindApiConfigPanel(container) {
_switchParamSections($c, $(this).val());
});
// 弹窗:接口类型切换Google 自动填 URL
$c.find('#amily2_pf_provider').on('change', function () {
_handleProviderChange($c, $(this).val());
// 弹窗:接口类型切换 —— vendor preset 自动填 defaultUrl + 切换提示框
$c.find('#amily2_pf_provider').on('change', async function () {
const provider = $(this).val();
_handleProviderChange($c, provider);
await _autofillVendorUrl($c, provider);
});
// 弹窗:获取模型列表
@@ -81,6 +99,30 @@ export function bindApiConfigPanel(container) {
// 弹窗:测试连接
$c.find('#amily2_pf_test_conn').on('click', () => _testConnection($c));
// 弹窗URL 变更 → 更新 customParams hint
$c.find('#amily2_pf_url').on('input change blur', () => _updateCustomParamsHint($c));
// 弹窗customParams 文本框实时校验 JSON
$c.find('#amily2_pf_custom_params').on('blur input', () => {
_validateCustomParamsLive($c);
_updateCustomParamsHint($c);
});
$c.on('click', '.amily2_param_hint_btn', function () {
if (this.disabled) return;
_insertParamToCustomParams(
$c,
$(this).data('paramName'),
$(this).data('paramType')
);
});
// 预加载 vendor registry异步UI 不阻塞)
getRegistry().catch(() => { /* 失败已在 api-vendor 内部 fallback无需再处理 */ });
// 旧配置清理按钮
$c.find('#amily2_clear_legacy_config').on('click', () => _handleClearLegacyConfig($c));
// 表单:取消
$c.find('#amily2_profile_modal_cancel').on('click', () => closeModal($c));
@@ -404,6 +446,11 @@ async function openModal($c, id) {
$c.find('#amily2_pf_max_tokens').val(p.maxTokens);
$c.find('#amily2_pf_temperature').val(p.temperature);
$c.find('#amily2_pf_fake_stream').prop('checked', p.fakeStream ?? false);
// customParams 写回成格式化 JSON 字符串
const cp = p.customParams ?? {};
$c.find('#amily2_pf_custom_params').val(
Object.keys(cp).length ? JSON.stringify(cp, null, 2) : ''
);
} else if (p.type === 'embedding') {
$c.find('#amily2_pf_dimensions').val(p.dimensions ?? '');
$c.find('#amily2_pf_encoding_format').val(p.encodingFormat);
@@ -421,9 +468,12 @@ async function openModal($c, id) {
$c.find('#amily2_pf_name, #amily2_pf_url, #amily2_pf_key, #amily2_pf_model').val('');
$c.find('#amily2_pf_provider').val('openai');
_handleProviderChange($c, 'openai');
// 新建模式下自动填充默认 URL编辑模式不调避免覆盖用户已配置的代理 URL
_autofillVendorUrl($c, 'openai');
$c.find('#amily2_pf_max_tokens').val(65500);
$c.find('#amily2_pf_temperature').val(1.0);
$c.find('#amily2_pf_fake_stream').prop('checked', false);
$c.find('#amily2_pf_custom_params').val('');
$c.find('#amily2_pf_dimensions').val('');
$c.find('#amily2_pf_encoding_format').val('float');
$c.find('#amily2_pf_top_n').val(5);
@@ -436,6 +486,10 @@ async function openModal($c, id) {
$c.find('#amily2_pf_model_select').hide().empty();
$c.find('#amily2_pf_model').show();
// 刷新 customParams 旁的 vendor 提示 + 清空错误
_updateCustomParamsHint($c);
_validateCustomParamsLive($c);
const $details = $c.find('#amily2_profile_form_details');
$details.prop('open', true);
$details[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
@@ -464,6 +518,14 @@ async function saveProfile($c) {
data.maxTokens = parseInt($c.find('#amily2_pf_max_tokens').val(), 10) || 65500;
data.temperature = parseFloat($c.find('#amily2_pf_temperature').val()) || 1.0;
data.fakeStream = $c.find('#amily2_pf_fake_stream').prop('checked');
// customParamsJSON 校验失败则中止保存
const cp = _parseCustomParamsOrFail($c);
if (cp === null) {
toastr.error('自定义参数 JSON 解析失败,请修正后再保存。', '保存中止');
return;
}
data.customParams = cp;
} else if (type === 'embedding') {
const dim = $c.find('#amily2_pf_dimensions').val();
data.dimensions = dim ? parseInt(dim, 10) : null;
@@ -705,15 +767,71 @@ async function _testConnection($c) {
// ── Provider 切换 ─────────────────────────────────────────────────────────────
const GOOGLE_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/openai';
/**
* 6 个享受 defaultUrl 自动填充的 vendor preset id。registry 之外的 provider
* sillytavern_backend / sillytavern_preset / custom_oai走各自的特殊逻辑。
*/
const VENDOR_PRESETS = new Set(['anthropic', 'openai', 'google', 'openrouter', 'deepseek', 'xai']);
function _handleProviderChange($c, provider) {
const isGoogle = provider === 'google';
$c.find('#amily2_pf_url_row').toggle(!isGoogle);
$c.find('#amily2_pf_google_note').toggle(isGoogle);
/**
* 处理 provider 变化的"展示侧"逻辑URL row 可见性 + vendor 提示框。
* 不修改 URL 输入值(避免编辑现有 profile 时被覆盖)。
* URL 自动填充由 _autofillVendorUrl 单独负责,仅在用户主动 change 时触发。
*/
async function _handleProviderChange($c, provider) {
const $urlRow = $c.find('#amily2_pf_url_row');
const $note = $c.find('#amily2_pf_vendor_note');
const $noteText = $c.find('#amily2_pf_vendor_note_text');
const $linkWrap = $c.find('#amily2_pf_vendor_note_link_wrap');
const $link = $c.find('#amily2_pf_vendor_note_link');
if (isGoogle) {
$c.find('#amily2_pf_url').val(GOOGLE_API_BASE);
// URL row 一律可见(包括 preset vendor —— 用户可能要切到代理/镜像)
$urlRow.show();
if (VENDOR_PRESETS.has(provider)) {
try {
const entry = await getVendorEntry(provider);
if (entry) {
$noteText.text(`${entry.displayName} — 默认接口地址已自动填写,如需走代理/镜像可在下方修改。`);
if (entry.doc) {
$link.attr('href', entry.doc).text('查看官方文档');
$linkWrap.show();
} else {
$linkWrap.hide();
}
$note.show();
return;
}
} catch (e) {
console.warn('[ApiConfig] vendor entry 加载失败:', e);
}
}
$note.hide();
}
/**
* 用户主动切换 provider 时,把 URL 字段写为该 vendor 的 defaultUrl。
* Custom 模式清空 URLST backend/preset 不动 URL。
* 同时刷新 customParams hint 与校验状态。
*/
async function _autofillVendorUrl($c, provider) {
if (provider === 'custom_oai') {
$c.find('#amily2_pf_url').val('');
_updateCustomParamsHint($c);
return;
}
if (!VENDOR_PRESETS.has(provider)) {
// sillytavern_backend / sillytavern_preset 等不修改 URL
return;
}
try {
const entry = await getVendorEntry(provider);
if (entry?.defaultUrl) {
$c.find('#amily2_pf_url').val(entry.defaultUrl);
_updateCustomParamsHint($c);
}
} catch (e) {
console.warn('[ApiConfig] autofill defaultUrl 失败:', e);
}
}
@@ -741,3 +859,153 @@ function _escapeHtml(str) {
.replace(/>/g, '>')
.replace(/"/g, '"');
}
function _getCustomParamsEditorState($c) {
const raw = ($c.find('#amily2_pf_custom_params').val() || '').trim();
if (!raw) {
return { valid: true, parsed: {}, empty: true };
}
try {
const parsed = JSON.parse(raw);
if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {
return { valid: false, parsed: null, empty: false };
}
return { valid: true, parsed, empty: false };
} catch {
return { valid: false, parsed: null, empty: false };
}
}
function _getDefaultValueForParamType(type) {
const normalized = String(type || '').toLowerCase();
if (normalized.includes('array')) return [];
if (normalized.includes('object')) return {};
if (normalized.includes('integer') || normalized.includes('number')) return 0;
if (normalized.includes('boolean')) return false;
return '';
}
// ── customParams 辅助 ────────────────────────────────────────────────────────
/**
* 根据当前 URL 输入识别 vendor并把已知参数列表渲染到 hint 行。
* registry 还没异步加载完时detectVendorSync 返回 null静默跳过。
*/
function _updateCustomParamsHint($c) {
const $hint = $c.find('#amily2_pf_custom_params_hint');
if (!$hint.length) return;
const apiUrl = $c.find('#amily2_pf_url').val()?.trim() || '';
const vendorId = detectVendorSync(apiUrl);
if (!vendorId) {
$hint.empty();
return;
}
const params = listVendorParamsSync(vendorId);
if (!params.length) {
$hint.empty();
return;
}
const editorState = _getCustomParamsEditorState($c);
getVendorEntry(vendorId).then(entry => {
const label = entry?.displayName || vendorId;
const disabledAttr = editorState.valid ? '' : ' disabled';
const buttons = params.map(param => `
<button type="button"
class="menu_button small_button amily2_param_hint_btn"
data-param-name="${_escapeHtml(param.name)}"
data-param-type="${_escapeHtml(param.type || '')}"
style="margin:2px 6px 2px 0;"
${disabledAttr}>${_escapeHtml(param.name)}</button>
`).join('');
const invalidNote = editorState.valid
? ''
: '<span style="margin-left:6px; color:var(--warning, #d9534f);">请先修复 JSON再插入参数。</span>';
$hint.html(`${_escapeHtml(label)} 已知参数:${buttons}${invalidNote}`);
});
}
/**
* 实时校验 customParams 文本框内容。空 / 合法 JSON object → 清空错误。
* 非 JSON 或非 object → 在 #_error 行显示。仅做提示,不阻断输入。
*/
function _validateCustomParamsLive($c) {
const $err = $c.find('#amily2_pf_custom_params_error');
if (!$err.length) return;
const state = _getCustomParamsEditorState($c);
if (state.empty) {
$err.hide().text('');
return;
}
if (state.valid) {
$err.hide().text('');
return;
}
try {
JSON.parse(($c.find('#amily2_pf_custom_params').val() || '').trim());
$err.show().text('需要是 JSON 对象({} 形式),不能是数组或基本类型。');
} catch (e) {
$err.show().text(`JSON 解析失败:${e.message}`);
}
}
function _insertParamToCustomParams($c, paramName, paramType) {
const state = _getCustomParamsEditorState($c);
if (!state.valid) return;
const next = { ...(state.parsed || {}) };
if (Object.prototype.hasOwnProperty.call(next, paramName)) {
return;
}
next[paramName] = _getDefaultValueForParamType(paramType);
$c.find('#amily2_pf_custom_params').val(JSON.stringify(next, null, 2));
_validateCustomParamsLive($c);
_updateCustomParamsHint($c);
}
/**
* 清除旧配置残留 —— 二次确认 → 调 clearLegacyConfig → 反馈结果。
*/
function _handleClearLegacyConfig($c) {
const confirmed = window.confirm(
'【清除旧配置残留】\n\n' +
'即将删除以下数据:\n' +
'• extension_settings 中各模块的旧 URL / Model / 温度 / maxTokens / 模式等字段\n' +
'• localStorage 中各模块的旧 API Key\n\n' +
'⚠️ 操作不可恢复。如果某个槽位还没分配 profile操作会被阻止。\n\n' +
'确定继续吗?'
);
if (!confirmed) return;
try {
const result = clearLegacyConfig();
if (!result.ok) {
toastr.error(result.error || '清除失败,未知错误。', '清除被阻止');
return;
}
toastr.success(
`已清除 ${result.clearedFields} 个旧字段、${result.clearedKeys} 个旧 API Key。建议刷新页面验证。`,
'清除完成',
{ timeOut: 6000 }
);
} catch (e) {
console.error('[ApiConfig] 清除旧配置失败:', e);
toastr.error(`清除失败: ${e.message}`, '错误');
}
}
/**
* saveProfile 调用:解析 customParams 文本,失败返回 null调用方中止保存
* 空文本视为空对象 {}。
*
* @returns {Object | null}
*/
function _parseCustomParamsOrFail($c) {
const state = _getCustomParamsEditorState($c);
return state.valid ? (state.parsed || {}) : null;
}