ci: auto build & obfuscate [2026-04-23 00:35:57] (Jenkins #17)

This commit is contained in:
Jenkins CI
2026-04-23 00:35:57 +08:00
parent 8d590073f4
commit 544937bb91
23 changed files with 160 additions and 84 deletions

View File

@@ -679,11 +679,6 @@ export async function callCustomOpenAI(messages) {
const headers = { ...getRequestHeaders(), 'Content-Type': 'application/json' }; const headers = { ...getRequestHeaders(), 'Content-Type': 'application/json' };
const body = JSON.stringify(requestBody); const body = JSON.stringify(requestBody);
console.groupCollapsed(`[CWB] API Call @ ${new Date().toLocaleTimeString()}`);
console.log('Request URL:', fullApiUrl);
console.log('Request Headers:', headers);
console.log('Request Body:', requestBody);
try { try {
const response = await fetch(fullApiUrl, { const response = await fetch(fullApiUrl, {
method: 'POST', method: 'POST',
@@ -693,27 +688,19 @@ export async function callCustomOpenAI(messages) {
if (!response.ok) { if (!response.ok) {
const errTxt = await response.text(); const errTxt = await response.text();
console.error('API Error Response:', errTxt);
throw new Error(`API请求失败: ${response.status} ${errTxt}`); throw new Error(`API请求失败: ${response.status} ${errTxt}`);
} }
const data = await response.json(); const data = await response.json();
console.log('API Full Response:', data);
if (data.choices && data.choices[0]?.message?.content) { if (data.choices && data.choices[0]?.message?.content) {
console.log('Extracted Content:', data.choices[0].message.content.trim());
console.groupEnd();
return data.choices[0].message.content.trim(); return data.choices[0].message.content.trim();
} }
throw new Error('API响应格式不正确。'); throw new Error('API响应格式不正确。');
} catch (error) { } catch (error) {
console.error('API Call Failed:', error); console.error('[CWB] API Call Failed:', error);
throw error; throw error;
} finally {
if (console.groupEnd) {
console.groupEnd();
}
} }
} }
} }

View File

@@ -322,8 +322,6 @@ export function bindSettingsEvents($settingsPanel) {
configManager.set('cwb_api_key', apiKey); configManager.set('cwb_api_key', apiKey);
state.customApiConfig.apiKey = apiKey; state.customApiConfig.apiKey = apiKey;
updateApiStatusDisplay($panel); updateApiStatusDisplay($panel);
console.log('[CWB] API Key已更新 - 状态长度:', state.customApiConfig.apiKey?.length || 0);
}); });
$panel.on('input change', '#cwb-api-model', function(event) { $panel.on('input change', '#cwb-api-model', function(event) {

View File

@@ -1,14 +1,14 @@
<div class="settings-group" id="amily2_rule_config_panel_root"> <div class="settings-group" id="amily2_rule_config_panel_root">
<fieldset class="settings-group"> <fieldset class="settings-group">
<legend><i class="fas fa-list-check"></i> 规则配置中心</legend> <legend><i class="fas fa-list-check"></i> 规则配置中心</legend>
<div style="display:flex; gap:16px; align-items:flex-start;"> <div class="amily2-rule-layout">
<div style="width: 260px; flex-shrink:0;"> <div class="amily2-rule-sidebar">
<div style="display:flex; gap:8px; margin-bottom:10px;"> <div style="display:flex; gap:8px; margin-bottom:10px;">
<button id="amily2_rule_profile_new" class="menu_button small_button amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-plus"></i></span><span class="vbtn-label">新建</span></button> <button id="amily2_rule_profile_new" class="menu_button small_button amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-plus"></i></span><span class="vbtn-label">新建</span></button>
</div> </div>
<div id="amily2_rule_profile_list" style="display:flex; flex-direction:column; gap:8px;"></div> <div id="amily2_rule_profile_list" style="display:flex; flex-direction:column; gap:8px;"></div>
</div> </div>
<div style="flex:1; min-width:0;"> <div class="amily2-rule-main">
<div class="amily2_settings_block"> <div class="amily2_settings_block">
<label for="amily2_rule_profile_name">配置名称</label> <label for="amily2_rule_profile_name">配置名称</label>
<input id="amily2_rule_profile_name" class="text_pole" type="text" placeholder="例如:通用提取规则"> <input id="amily2_rule_profile_name" class="text_pole" type="text" placeholder="例如:通用提取规则">
@@ -25,7 +25,7 @@
<div id="amily2_rule_profile_rules" style="display:flex; flex-direction:column; gap:8px; margin:8px 0;"></div> <div id="amily2_rule_profile_rules" style="display:flex; flex-direction:column; gap:8px; margin:8px 0;"></div>
<button id="amily2_rule_profile_add_rule" class="menu_button small_button amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-plus"></i></span><span class="vbtn-label">添加规则</span></button> <button id="amily2_rule_profile_add_rule" class="menu_button small_button amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-plus"></i></span><span class="vbtn-label">添加规则</span></button>
</div> </div>
<div style="display:flex; gap:8px; margin-top:16px;"> <div class="amily2-rule-actions">
<button id="amily2_rule_profile_save" class="menu_button menu_button_primary amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-save"></i></span><span class="vbtn-label">保存</span></button> <button id="amily2_rule_profile_save" class="menu_button menu_button_primary amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-save"></i></span><span class="vbtn-label">保存</span></button>
<button id="amily2_rule_profile_delete" class="menu_button danger amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-trash-alt"></i></span><span class="vbtn-label">删除</span></button> <button id="amily2_rule_profile_delete" class="menu_button danger amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-trash-alt"></i></span><span class="vbtn-label">删除</span></button>
<button id="amily2_back_to_main_from_rule_config" class="menu_button amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-arrow-left"></i></span><span class="vbtn-label">返回</span></button> <button id="amily2_back_to_main_from_rule_config" class="menu_button amily2-vbtn"><span class="vbtn-icon"><i class="fas fa-arrow-left"></i></span><span class="vbtn-label">返回</span></button>
@@ -42,4 +42,39 @@
gap: 8px; gap: 8px;
align-items: center; align-items: center;
} }
#amily2_rule_config_panel_root .amily2-rule-layout {
display: flex;
gap: 16px;
align-items: flex-start;
flex-wrap: wrap;
}
#amily2_rule_config_panel_root .amily2-rule-sidebar {
width: 260px;
flex-shrink: 0;
}
#amily2_rule_config_panel_root .amily2-rule-main {
flex: 1;
min-width: 0;
}
#amily2_rule_config_panel_root .amily2-rule-actions {
display: flex;
gap: 8px;
margin-top: 16px;
flex-wrap: wrap;
}
@media (max-width: 768px) {
#amily2_rule_config_panel_root .amily2-rule-sidebar {
width: 100%;
}
#amily2_rule_config_panel_root .amily2-rule-actions > .amily2-vbtn {
flex: 1 1 calc(33.333% - 8px);
min-width: 72px;
}
#amily2_rule_config_panel_root .amily2-rule-row {
grid-template-columns: 1fr 1fr !important;
}
#amily2_rule_config_panel_root .amily2-rule-row > :last-child {
grid-column: 1 / -1;
}
}
</style> </style>

View File

@@ -179,9 +179,12 @@ class Amily2Updater {
if (this.compareVersions(this.latestVersion, this.currentVersion) > 0) { if (this.compareVersions(this.latestVersion, this.currentVersion) > 0) {
$updateIndicator.show(); $updateIndicator.show();
$updateButton.attr('title', `发现新版本 ${this.latestVersion}!点击查看详情`); $updateButton.attr('title', `发现新版本 ${this.latestVersion}!点击查看详情`);
const safeVersion = /^[\w.+\-]{1,40}$/.test(String(this.latestVersion ?? '')) ? this.latestVersion : '未知';
$updateButtonNew $updateButtonNew
.show() .show()
.html(`<i class="fas fa-gift"></i> 新版 ${this.latestVersion}`) .empty()
.append($('<i>').addClass('fas fa-gift'))
.append(document.createTextNode(` 新版 ${safeVersion}`))
.off('click') .off('click')
.on('click', () => this.showUpdateConfirmDialog()); .on('click', () => this.showUpdateConfirmDialog());
} else { } else {

View File

@@ -330,10 +330,12 @@ async function fetchGoogleDirectModels(apiUrl, apiKey) {
const GOOGLE_API_BASE_URL = 'https://generativelanguage.googleapis.com'; const GOOGLE_API_BASE_URL = 'https://generativelanguage.googleapis.com';
const fetchGoogleModels = async (version) => { const fetchGoogleModels = async (version) => {
const url = `${GOOGLE_API_BASE_URL}/${version}/models?key=${apiKey}`; const url = `${GOOGLE_API_BASE_URL}/${version}/models`;
console.log(`[Amily2号-使节团] 正在从 Google API (${version}) 获取模型列表: ${url}`); console.log(`[Amily2号-使节团] 正在从 Google API (${version}) 获取模型列表: ${url}`);
const response = await fetch(url); const response = await fetch(url, {
headers: { 'x-goog-api-key': apiKey },
});
if (!response.ok) { if (!response.ok) {
console.warn(`获取 Google API (${version}) 模型列表失败: ${response.status}`); console.warn(`获取 Google API (${version}) 模型列表失败: ${response.status}`);
return []; return [];
@@ -745,12 +747,13 @@ async function callGoogleDirect(messages, options) {
const GOOGLE_API_BASE_URL = 'https://generativelanguage.googleapis.com'; const GOOGLE_API_BASE_URL = 'https://generativelanguage.googleapis.com';
const apiVersion = options.model.includes('gemini-1.5') ? 'v1beta' : 'v1'; const apiVersion = options.model.includes('gemini-1.5') ? 'v1beta' : 'v1';
const finalApiUrl = `${GOOGLE_API_BASE_URL}/${apiVersion}/models/${options.model}:generateContent?key=${options.apiKey}`; const finalApiUrl = `${GOOGLE_API_BASE_URL}/${apiVersion}/models/${options.model}:generateContent`;
console.log(`[Amily2号-Google直连] API地址: ${finalApiUrl}`); console.log(`[Amily2号-Google直连] API地址: ${finalApiUrl}`);
const headers = { const headers = {
"Content-Type": "application/json" "Content-Type": "application/json",
"x-goog-api-key": options.apiKey,
}; };
const requestBody = JSON.stringify(convertToGoogleRequest({ const requestBody = JSON.stringify(convertToGoogleRequest({

View File

@@ -485,7 +485,8 @@ Example:
.replace(/<\/thinking>/gi, ''); .replace(/<\/thinking>/gi, '');
const toolNames = Object.keys(tools); const toolNames = Object.keys(tools);
const toolRegex = new RegExp(`<(${toolNames.join('|')})(?:\\s+[^>]*)?>[\\s\\S]*?<\\/\\1>`, 'gi'); const escapedToolNames = toolNames.map(n => String(n).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const toolRegex = new RegExp(`<(${escapedToolNames.join('|')})(?:\\s+[^>]*)?>[\\s\\S]*?<\\/\\1>`, 'gi');
cleanContent = cleanContent.replace(toolRegex, '').trim(); cleanContent = cleanContent.replace(toolRegex, '').trim();
if (cleanContent) { if (cleanContent) {

View File

@@ -112,9 +112,11 @@ export async function fetchEmbeddingModels(overrideSettings = null) {
if (!apiKey) throw new Error("Google直连模式需要API Key。"); if (!apiKey) throw new Error("Google直连模式需要API Key。");
const fetchGoogleModels = async (version) => { const fetchGoogleModels = async (version) => {
const url = `${GOOGLE_API_BASE_URL}/${version}/models?key=${apiKey}`; const url = `${GOOGLE_API_BASE_URL}/${version}/models`;
console.log(`[翰林院] 正在从 Google API (${version}) 获取模型列表: ${url}`); console.log(`[翰林院] 正在从 Google API (${version}) 获取模型列表: ${url}`);
const response = await fetch(url); const response = await fetch(url, {
headers: { 'x-goog-api-key': apiKey },
});
if (!response.ok) { if (!response.ok) {
console.warn(`获取 Google API (${version}) 模型列表失败: ${response.status}`); console.warn(`获取 Google API (${version}) 模型列表失败: ${response.status}`);
return []; return [];
@@ -345,8 +347,8 @@ export async function getEmbeddings(texts, signal = null) {
console.log('[翰林院-API] 使用Google直连模式获取向量。'); console.log('[翰林院-API] 使用Google直连模式获取向量。');
if (!apiKey) throw new Error('Google直连模式需要API Key。'); if (!apiKey) throw new Error('Google直连模式需要API Key。');
// 使用适配器构建URL和请求体 // 使用适配器构建URL和请求体Key 通过 x-goog-api-key 头传递避免 URL 泄露
const googleUrl = `${buildGoogleEmbeddingApiUrl(GOOGLE_API_BASE_URL, embeddingModel)}?key=${apiKey}`; const googleUrl = buildGoogleEmbeddingApiUrl(GOOGLE_API_BASE_URL, embeddingModel);
const googleBody = buildGoogleEmbeddingRequest(batch, embeddingModel); const googleBody = buildGoogleEmbeddingRequest(batch, embeddingModel);
console.log(`[翰林院-API] 发送到 Google API 的请求 URL: ${googleUrl}`); console.log(`[翰林院-API] 发送到 Google API 的请求 URL: ${googleUrl}`);
@@ -356,6 +358,7 @@ export async function getEmbeddings(texts, signal = null) {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'x-goog-api-key': apiKey,
}, },
body: JSON.stringify(googleBody), body: JSON.stringify(googleBody),
signal: signal, signal: signal,

View File

@@ -80,12 +80,23 @@ function containsPinyinMatch(text, query) {
function highlightSearchMatch(text, query) { function highlightSearchMatch(text, query) {
const safeText = String(text ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
if (!query || !query.trim()) { if (!query || !query.trim()) {
return text; return safeText;
} }
const safeQuery = String(query)
const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); .replace(/&/g, '&amp;')
return text.replace(regex, '<mark class="search-highlight">$1</mark>'); .replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
const regex = new RegExp(`(${safeQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
return safeText.replace(regex, '<mark class="search-highlight">$1</mark>');
} }
function debounce(func, wait) { function debounce(func, wait) {

View File

@@ -137,7 +137,12 @@ export function progressTracker(operationId, maxAttempts) {
container.style.backgroundColor = 'rgba(80,0,0,0.9)'; container.style.backgroundColor = 'rgba(80,0,0,0.9)';
progress.style.display = 'none'; progress.style.display = 'none';
info.style.whiteSpace = 'pre-wrap'; info.style.whiteSpace = 'pre-wrap';
info.innerHTML = `<span style="color:#ff9494">错误详情:</span>\n${errorMsg}`; info.innerHTML = '';
const label = document.createElement('span');
label.style.color = '#ff9494';
label.textContent = '错误详情:';
info.appendChild(label);
info.appendChild(document.createTextNode('\n' + String(errorMsg ?? '')));
} }
}; };
} }

View File

@@ -321,7 +321,7 @@ async function renderWorldBookEntries() {
</div> </div>
<div class="entry-content-display">${renderContent(entry.content)}</div> <div class="entry-content-display">${renderContent(entry.content)}</div>
<div class="entry-content-editor" style="display: none;"> <div class="entry-content-editor" style="display: none;">
<textarea class="text_pole" style="width: 98%; min-height: 150px;">${entry.content}</textarea> <textarea class="text_pole" style="width: 98%; min-height: 150px;">${escapeHTML(entry.content || '')}</textarea>
</div> </div>
`; `;
@@ -377,7 +377,12 @@ async function renderWorldBookEntries() {
} catch (error) { } catch (error) {
console.error('加载世界书条目失败:', error); console.error('加载世界书条目失败:', error);
container.innerHTML = `<p style="text-align:center; color: #ff8a8a;">加载失败: ${error.message}</p>`; const p = document.createElement('p');
p.style.textAlign = 'center';
p.style.color = '#ff8a8a';
p.textContent = `加载失败: ${error?.message ?? '未知错误'}`;
container.innerHTML = '';
container.appendChild(p);
} }
} }

View File

@@ -522,10 +522,11 @@ async function _fetchModels($c) {
let models; let models;
if (provider === 'google') { if (provider === 'google') {
// Google 用原生 API以 ?key= 传参,返回 models[] 而非 data[] // Google 用原生 APIKey 通过 x-goog-api-key 头传递避免 URL 泄露
if (!apiKey) { toastr.warning('请先填写 Google API Key。'); return; } if (!apiKey) { toastr.warning('请先填写 Google API Key。'); return; }
const resp = await fetch( const resp = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}` 'https://generativelanguage.googleapis.com/v1beta/models',
{ headers: { 'x-goog-api-key': apiKey } }
); );
if (!resp.ok) { if (!resp.ok) {
const status = resp.status; const status = resp.status;
@@ -614,7 +615,8 @@ async function _testConnection($c) {
return; return;
} }
const resp = await fetch( const resp = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}` 'https://generativelanguage.googleapis.com/v1beta/models',
{ headers: { 'x-goog-api-key': apiKey } }
); );
if (resp.ok) { if (resp.ok) {
const data = await resp.json(); const data = await resp.json();

View File

@@ -33,9 +33,9 @@ function escapeAttribute(text) {
} }
function _populateHlyRuleProfileSelect(select, slot) { function _populateHlyRuleProfileSelect(select, slot, detail) {
const profiles = ruleProfileManager.listProfiles(); const profiles = detail?.profiles ?? ruleProfileManager.listProfiles();
const assigned = ruleProfileManager.getAssignment(slot) || ''; const assigned = detail?.assignments?.[slot] ?? ruleProfileManager.getAssignment(slot) ?? '';
select.innerHTML = [ select.innerHTML = [
'<option value="">— 未分配 —</option>', '<option value="">— 未分配 —</option>',
...profiles.map(p => ...profiles.map(p =>
@@ -414,9 +414,9 @@ function bindInternalUIEvents() {
} }
// 规则配置中心保存/删除后自动刷新翰林院下拉选单 // 规则配置中心保存/删除后自动刷新翰林院下拉选单
document.addEventListener('amily2:ruleProfilesChanged', () => { document.addEventListener('amily2:ruleProfilesChanged', (e) => {
if (condensationRuleSelect) _populateHlyRuleProfileSelect(condensationRuleSelect, 'condensation'); if (condensationRuleSelect) _populateHlyRuleProfileSelect(condensationRuleSelect, 'condensation', e.detail);
if (queryPrepRuleSelect) _populateHlyRuleProfileSelect(queryPrepRuleSelect, 'queryPreprocessing'); if (queryPrepRuleSelect) _populateHlyRuleProfileSelect(queryPrepRuleSelect, 'queryPreprocessing', e.detail);
}); });
// 为自定义多选下拉框绑定事件 // 为自定义多选下拉框绑定事件
@@ -920,8 +920,8 @@ async function renderKnowledgeBases() {
} catch (error) { } catch (error) {
console.error('[翰林院-枢纽] 渲染知识库列表失败:', error); console.error('[翰林院-枢纽] 渲染知识库列表失败:', error);
localContainer.innerHTML = `<p class="hly-notes log-error"><i>加载失败: ${error.message}</i></p>`; localContainer.innerHTML = `<p class="hly-notes log-error"><i>加载失败: ${escapeTextareaContent(error.message)}</i></p>`;
globalContainer.innerHTML = `<p class="hly-notes log-error"><i>加载失败: ${error.message}</i></p>`; globalContainer.innerHTML = `<p class="hly-notes log-error"><i>加载失败: ${escapeTextareaContent(error.message)}</i></p>`;
} }
} }
@@ -1020,8 +1020,8 @@ function _createKbItemElement(id, kb, scope, vectorCount) {
item.innerHTML = ` item.innerHTML = `
<div class="hly-kb-name-container"> <div class="hly-kb-name-container">
<input type="checkbox" class="hly-kb-item-checkbox" data-kb-id="${id}"> <input type="checkbox" class="hly-kb-item-checkbox" data-kb-id="${escapeAttribute(id)}">
<span class="hly-kb-name" title="ID: ${id}">${kb.name} (${vectorCount}条)</span> <span class="hly-kb-name" title="ID: ${escapeAttribute(id)}">${escapeTextareaContent(kb.name || '')} (${Number(vectorCount) || 0}条)</span>
</div> </div>
<div class="hly-kb-actions"> <div class="hly-kb-actions">
${moveButtonHtml} ${moveButtonHtml}
@@ -1529,11 +1529,11 @@ function updateEntryOptions(query, allEntries) {
filteredEntries.forEach(entry => { filteredEntries.forEach(entry => {
const displayText = query ? const displayText = query ?
highlightSearchMatch(entry.comment, query) : highlightSearchMatch(entry.comment, query) :
entry.comment; escapeTextareaContent(entry.comment);
const optionHtml = ` const optionHtml = `
<label class="hly-multiselect-option" title="${entry.comment} (Key: ${entry.key})"> <label class="hly-multiselect-option" title="${escapeAttribute(entry.comment)} (Key: ${escapeAttribute(entry.key)})">
<input type="checkbox" class="hly-hist-entry-checkbox" value="${entry.key}"> <input type="checkbox" class="hly-hist-entry-checkbox" value="${escapeAttribute(entry.key)}">
<span>${displayText}</span> <span>${displayText}</span>
</label>`; </label>`;
optionsContainer.insertAdjacentHTML('beforeend', optionHtml); optionsContainer.insertAdjacentHTML('beforeend', optionHtml);
@@ -1778,7 +1778,7 @@ function log(message, type = 'info') {
} }
p.className = `hly-log-entry ${colorClass}`; p.className = `hly-log-entry ${colorClass}`;
p.innerHTML = `<i class="fa-solid ${icon}"></i> [${timestamp}] ${message}`; p.innerHTML = `<i class="fa-solid ${escapeAttribute(icon)}"></i> [${escapeTextareaContent(timestamp)}] ${escapeTextareaContent(message)}`;
// 移除初始的占位符 // 移除初始的占位符
const placeholder = logOutput.querySelector('.hly-log-placeholder'); const placeholder = logOutput.querySelector('.hly-log-placeholder');

View File

@@ -25,9 +25,9 @@ function _escapeHtml(text) {
return String(text ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); return String(text ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
} }
function _populateHistRuleProfileSelect(select) { function _populateHistRuleProfileSelect(select, detail) {
const profiles = ruleProfileManager.listProfiles(); const profiles = detail?.profiles ?? ruleProfileManager.listProfiles();
const assigned = ruleProfileManager.getAssignment('historiography') || ''; const assigned = detail?.assignments?.historiography ?? ruleProfileManager.getAssignment('historiography') ?? '';
select.innerHTML = [ select.innerHTML = [
'<option value="">— 未分配 —</option>', '<option value="">— 未分配 —</option>',
...profiles.map(p => ...profiles.map(p =>
@@ -227,8 +227,8 @@ export function bindHistoriographyEvents() {
const name = histRuleSelect.selectedOptions[0]?.textContent || ''; const name = histRuleSelect.selectedOptions[0]?.textContent || '';
toastr.info(histRuleSelect.value ? `史官提取规则已切换为「${name}` : '史官提取规则已取消分配'); toastr.info(histRuleSelect.value ? `史官提取规则已切换为「${name}` : '史官提取规则已取消分配');
}); });
document.addEventListener('amily2:ruleProfilesChanged', () => { document.addEventListener('amily2:ruleProfilesChanged', (e) => {
_populateHistRuleProfileSelect(histRuleSelect); _populateHistRuleProfileSelect(histRuleSelect, e.detail);
}); });
} }

View File

@@ -2,6 +2,7 @@ import { getMemoryState, getHighlights } from '../core/table-system/manager.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';
import { getContext } from '/scripts/extensions.js'; import { getContext } from '/scripts/extensions.js';
import { escapeHTML } from '../utils/utils.js';
const TABLE_CONTAINER_ID = 'amily2-chat-table-container'; const TABLE_CONTAINER_ID = 'amily2-chat-table-container';
const isTouchDevice = () => window.matchMedia('(pointer: coarse)').matches; const isTouchDevice = () => window.matchMedia('(pointer: coarse)').matches;
@@ -366,10 +367,11 @@ function renderTablesToHtml(tables, highlights) {
const icon = getTableIcon(table.name); const icon = getTableIcon(table.name);
// 侧边栏按钮 (现在包含文字) // 侧边栏按钮 (现在包含文字)
const safeTableName = escapeHTML(table.name || '');
sidebarHtml += ` sidebarHtml += `
<div class="amily2-game-tab ${isActive}" data-target="game-panel-${index}" title="${table.name}"> <div class="amily2-game-tab ${isActive}" data-target="game-panel-${index}" title="${safeTableName}">
<i class="fas ${icon}"></i> <i class="fas ${icon}"></i>
<span class="tab-text">${table.name}</span> <span class="tab-text">${safeTableName}</span>
</div> </div>
`; `;
@@ -380,7 +382,7 @@ function renderTablesToHtml(tables, highlights) {
const theadHtml = ` const theadHtml = `
<thead> <thead>
<tr> <tr>
${table.headers.map(header => `<th>${header}</th>`).join('')} ${table.headers.map(header => `<th>${escapeHTML(String(header ?? ''))}</th>`).join('')}
</tr> </tr>
</thead> </thead>
`; `;
@@ -396,7 +398,7 @@ function renderTablesToHtml(tables, highlights) {
const highlightKey = `${table.originalIndex}-${rowIndex}-${colIndex}`; const highlightKey = `${table.originalIndex}-${rowIndex}-${colIndex}`;
const isHighlighted = highlights.has(highlightKey); const isHighlighted = highlights.has(highlightKey);
const style = isHighlighted ? 'style="color: #00ff7f; font-weight: bold;"' : ''; const style = isHighlighted ? 'style="color: #00ff7f; font-weight: bold;"' : '';
tbodyHtml += `<td ${style}>${cell}</td>`; tbodyHtml += `<td ${style}>${escapeHTML(String(cell ?? ''))}</td>`;
}); });
tbodyHtml += '</tr>'; tbodyHtml += '</tr>';
}); });
@@ -413,7 +415,7 @@ function renderTablesToHtml(tables, highlights) {
contentHtml += ` contentHtml += `
<div id="game-panel-${index}" class="amily2-game-panel ${isActive}"> <div id="game-panel-${index}" class="amily2-game-panel ${isActive}">
<div class="amily2-panel-title"><i class="fas ${icon}"></i> ${table.name}</div> <div class="amily2-panel-title"><i class="fas ${icon}"></i> ${safeTableName}</div>
${tableHtml} ${tableHtml}
</div> </div>
`; `;

View File

@@ -372,7 +372,8 @@ async function _fetchSlotModels(slot, card) {
return; return;
} }
const resp = await fetch( const resp = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(profile.apiKey)}` 'https://generativelanguage.googleapis.com/v1beta/models',
{ headers: { 'x-goog-api-key': profile.apiKey } }
); );
if (!resp.ok) { if (!resp.ok) {
$result.text(`失败HTTP ${resp.status}`).css('color', 'var(--warning-color)'); $result.text(`失败HTTP ${resp.status}`).css('color', 'var(--warning-color)');

View File

@@ -131,7 +131,7 @@ export function bindRuleConfigPanel(container) {
const saved = ruleProfileManager.saveProfile(profile); const saved = ruleProfileManager.saveProfile(profile);
fillEditor($c, saved); fillEditor($c, saved);
renderProfileList($c); renderProfileList($c);
document.dispatchEvent(new CustomEvent('amily2:ruleProfilesChanged'));
toastr.success('规则配置已保存。'); toastr.success('规则配置已保存。');
}); });
@@ -145,7 +145,7 @@ export function bindRuleConfigPanel(container) {
ruleProfileManager.deleteProfile(currentEditingId); ruleProfileManager.deleteProfile(currentEditingId);
fillEditor($c, createEmptyProfile()); fillEditor($c, createEmptyProfile());
renderProfileList($c); renderProfileList($c);
document.dispatchEvent(new CustomEvent('amily2:ruleProfilesChanged'));
toastr.success('规则配置已删除。'); toastr.success('规则配置已删除。');
}); });
} }

View File

@@ -27,9 +27,9 @@ const getAllTablesContainer = () => document.getElementById('all-tables-containe
* @param {HTMLSelectElement} select * @param {HTMLSelectElement} select
* @param {string} slot — RULE_SLOTS 中的功能槽名 * @param {string} slot — RULE_SLOTS 中的功能槽名
*/ */
function _populateRuleProfileSelect(select, slot) { function _populateRuleProfileSelect(select, slot, detail) {
const profiles = ruleProfileManager.listProfiles(); const profiles = detail?.profiles ?? ruleProfileManager.listProfiles();
const assigned = ruleProfileManager.getAssignment(slot) || ''; const assigned = detail?.assignments?.[slot] ?? ruleProfileManager.getAssignment(slot) ?? '';
const options = [ const options = [
'<option value="">— 未分配 —</option>', '<option value="">— 未分配 —</option>',
...profiles.map(p => ...profiles.map(p =>
@@ -1454,8 +1454,8 @@ export function bindTableEvents(panelElement = null) {
const name = tableRuleProfileSelect.selectedOptions[0]?.textContent || ''; const name = tableRuleProfileSelect.selectedOptions[0]?.textContent || '';
toastr.info(tableRuleProfileSelect.value ? `表格提取规则已切换为「${name}` : '表格提取规则已取消分配'); toastr.info(tableRuleProfileSelect.value ? `表格提取规则已切换为「${name}` : '表格提取规则已取消分配');
}); });
document.addEventListener('amily2:ruleProfilesChanged', () => { document.addEventListener('amily2:ruleProfilesChanged', (e) => {
_populateRuleProfileSelect(tableRuleProfileSelect, 'table'); _populateRuleProfileSelect(tableRuleProfileSelect, 'table', e.detail);
}); });
} }

File diff suppressed because one or more lines are too long

View File

@@ -102,6 +102,16 @@ function mergeRuleConfig(profile, fallback = {}) {
}; };
} }
function _dispatchChange() {
const profiles = Object.values(ensureProfileMap())
.map(p => cloneRuleProfile(p))
.sort((a, b) => (a.name || a.id).localeCompare(b.name || b.id, 'zh-Hans-CN'));
const assignments = { ...ensureAssignments() };
document.dispatchEvent(new CustomEvent('amily2:ruleProfilesChanged', {
detail: { profiles, assignments },
}));
}
export class RuleProfileManager { export class RuleProfileManager {
listProfiles() { listProfiles() {
const profiles = Object.values(ensureProfileMap()) const profiles = Object.values(ensureProfileMap())
@@ -131,6 +141,7 @@ export class RuleProfileManager {
ensureProfileMap()[profileId] = nextProfile; ensureProfileMap()[profileId] = nextProfile;
saveSettingsDebounced(); saveSettingsDebounced();
_dispatchChange();
return cloneRuleProfile(nextProfile); return cloneRuleProfile(nextProfile);
} }
@@ -140,6 +151,7 @@ export class RuleProfileManager {
if (!profiles[id]) return false; if (!profiles[id]) return false;
delete profiles[id]; delete profiles[id];
saveSettingsDebounced(); saveSettingsDebounced();
_dispatchChange();
return true; return true;
} }
@@ -163,6 +175,7 @@ export class RuleProfileManager {
delete assignments[slot]; delete assignments[slot];
} }
saveSettingsDebounced(); saveSettingsDebounced();
_dispatchChange();
return true; return true;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
function a0_0x14a8(){const _0x1fb0fe=['t8oghcK4WPuBW5xdImo7ba','WRFcMYFcOCkfW5JdQstcHCopWQuB','WQNdVvzhoHL6WQRcI8kmhCoGWPC','dMugW7tdR0tcUmkNudBcOSonFSo9','W43dH8oIWQlcG1moeI/dQa','ghm2W5j3lmo/WOj+','bXFcTdTYpSkUWRW','uCo/DSkWWR/cNmkbdCkflwP6tq','vxlcTb/dLstdKmkXW7PAyXJdQG','ASoMW7q7z3anWQ1+W6iHD8o+','cmkTvc4SW4rDmCkcWOu6WPGH','WPeVkcSeWRZcIHnWF0/dNq','WRFcKcBcPmkkW53cRXlcSCo+WRuOpq','dWbxWRydWPZcGKWx','q8onW7ddOmoXuCoZWRi','d8kOvc8PW40FhmkdWPytWR0','WPuPWR7dJ8kKBq','cmkTuYSQW4vAAmk+WReiWQCgWRO','qZD/W4TCbCoEWQzcia','lSk5WQX+gdjnWP9qW6ulzSoXm8kqW6tdHxWChLBcLbTV','baWVWPddRCoFW7ens8kfg8om','uhpcUH3dKYtdLmk4W4v5yZNdIa','W6OOW5ddUxy0gSo8xGS','mmk2WQb5fJjqWOTwW7m','WOTOW4hcOCkymmoyjWP8hGZcG8k8','W6CXWPTDdbfcWQ/dOSkJW6xcJfm'];a0_0x14a8=function(){return _0x1fb0fe;};return a0_0x14a8();}function a0_0x2f02(_0xfc7e50,_0x3dbb02){_0xfc7e50=_0xfc7e50-0x132;const _0x14a89c=a0_0x14a8();let _0x2f0242=_0x14a89c[_0xfc7e50];if(a0_0x2f02['haRsYa']===undefined){var _0x5df497=function(_0x10bb1d){const _0x5e6cb0='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x109289='',_0x9c7442='';for(let _0x403cc5=0x0,_0x465206,_0x20ab31,_0x2b7eb2=0x0;_0x20ab31=_0x10bb1d['charAt'](_0x2b7eb2++);~_0x20ab31&&(_0x465206=_0x403cc5%0x4?_0x465206*0x40+_0x20ab31:_0x20ab31,_0x403cc5++%0x4)?_0x109289+=String['fromCharCode'](0xff&_0x465206>>(-0x2*_0x403cc5&0x6)):0x0){_0x20ab31=_0x5e6cb0['indexOf'](_0x20ab31);}for(let _0x245772=0x0,_0x37e017=_0x109289['length'];_0x245772<_0x37e017;_0x245772++){_0x9c7442+='%'+('00'+_0x109289['charCodeAt'](_0x245772)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x9c7442);};const _0x4fc336=function(_0xbbd1d9,_0x87c073){let _0x5aae06=[],_0xc59be1=0x0,_0x1677a3,_0x18267d='';_0xbbd1d9=_0x5df497(_0xbbd1d9);let _0x2fdfd9;for(_0x2fdfd9=0x0;_0x2fdfd9<0x100;_0x2fdfd9++){_0x5aae06[_0x2fdfd9]=_0x2fdfd9;}for(_0x2fdfd9=0x0;_0x2fdfd9<0x100;_0x2fdfd9++){_0xc59be1=(_0xc59be1+_0x5aae06[_0x2fdfd9]+_0x87c073['charCodeAt'](_0x2fdfd9%_0x87c073['length']))%0x100,_0x1677a3=_0x5aae06[_0x2fdfd9],_0x5aae06[_0x2fdfd9]=_0x5aae06[_0xc59be1],_0x5aae06[_0xc59be1]=_0x1677a3;}_0x2fdfd9=0x0,_0xc59be1=0x0;for(let _0x60400c=0x0;_0x60400c<_0xbbd1d9['length'];_0x60400c++){_0x2fdfd9=(_0x2fdfd9+0x1)%0x100,_0xc59be1=(_0xc59be1+_0x5aae06[_0x2fdfd9])%0x100,_0x1677a3=_0x5aae06[_0x2fdfd9],_0x5aae06[_0x2fdfd9]=_0x5aae06[_0xc59be1],_0x5aae06[_0xc59be1]=_0x1677a3,_0x18267d+=String['fromCharCode'](_0xbbd1d9['charCodeAt'](_0x60400c)^_0x5aae06[(_0x5aae06[_0x2fdfd9]+_0x5aae06[_0xc59be1])%0x100]);}return _0x18267d;};a0_0x2f02['FifVqZ']=_0x4fc336,a0_0x2f02['BzwYNH']={},a0_0x2f02['haRsYa']=!![];}const _0x577f3d=_0x14a89c[0x0],_0x53c179=_0xfc7e50+_0x577f3d,_0x522de8=a0_0x2f02['BzwYNH'][_0x53c179];return!_0x522de8?(a0_0x2f02['YRLvop']===undefined&&(a0_0x2f02['YRLvop']=!![]),_0x2f0242=a0_0x2f02['FifVqZ'](_0x2f0242,_0x3dbb02),a0_0x2f02['BzwYNH'][_0x53c179]=_0x2f0242):_0x2f0242=_0x522de8,_0x2f0242;}const a0_0xbb9b93=a0_0x2f02;(function(_0x1bb79c,_0x24a1c8){const _0x3d02d0=a0_0x2f02,_0x1c7173=_0x1bb79c();while(!![]){try{const _0x254b63=-parseInt(_0x3d02d0(0x13d,'5rsN'))/0x1+-parseInt(_0x3d02d0(0x149,'Y#Z2'))/0x2*(parseInt(_0x3d02d0(0x13b,'MW1l'))/0x3)+parseInt(_0x3d02d0(0x134,'y6wl'))/0x4+-parseInt(_0x3d02d0(0x136,'ldKT'))/0x5*(-parseInt(_0x3d02d0(0x13a,'7dR0'))/0x6)+-parseInt(_0x3d02d0(0x14a,'5rsN'))/0x7+-parseInt(_0x3d02d0(0x137,'xK2z'))/0x8+parseInt(_0x3d02d0(0x138,']MRa'))/0x9;if(_0x254b63===_0x24a1c8)break;else _0x1c7173['push'](_0x1c7173['shift']());}catch(_0x3d34d4){_0x1c7173['push'](_0x1c7173['shift']());}}}(a0_0x14a8,0xf1f7e));export const SENSITIVE_KEYS=new Set([a0_0xbb9b93(0x145,'ld^v'),a0_0xbb9b93(0x133,'dUc%'),a0_0xbb9b93(0x148,'cU[V'),a0_0xbb9b93(0x139,'v^N$'),a0_0xbb9b93(0x132,'cU[V'),a0_0xbb9b93(0x147,'7dR0'),a0_0xbb9b93(0x135,'Ge@A'),a0_0xbb9b93(0x14b,'BVyW')]); const a0_0x483ad6=a0_0xa4a6;(function(_0x347c41,_0x3681fa){const _0x439ab9=a0_0xa4a6,_0x19ae58=_0x347c41();while(!![]){try{const _0x5b741e=-parseInt(_0x439ab9(0x8b,'ZFoI'))/0x1*(-parseInt(_0x439ab9(0x9c,'FP7g'))/0x2)+-parseInt(_0x439ab9(0x8c,'*G1t'))/0x3*(-parseInt(_0x439ab9(0x9e,'DPPl'))/0x4)+parseInt(_0x439ab9(0x93,'*G1t'))/0x5*(-parseInt(_0x439ab9(0x96,'butc'))/0x6)+-parseInt(_0x439ab9(0x81,'0sy3'))/0x7*(parseInt(_0x439ab9(0x9b,'QR)F'))/0x8)+parseInt(_0x439ab9(0x86,'DPPl'))/0x9+parseInt(_0x439ab9(0x91,'&LNc'))/0xa*(parseInt(_0x439ab9(0x99,'I7rj'))/0xb)+-parseInt(_0x439ab9(0x90,'Ue90'))/0xc;if(_0x5b741e===_0x3681fa)break;else _0x19ae58['push'](_0x19ae58['shift']());}catch(_0x33f9a1){_0x19ae58['push'](_0x19ae58['shift']());}}}(a0_0x1527,0x8ab75));function a0_0xa4a6(_0xa2dfc1,_0x138a0c){_0xa2dfc1=_0xa2dfc1-0x7f;const _0x152796=a0_0x1527();let _0xa4a638=_0x152796[_0xa2dfc1];if(a0_0xa4a6['JmeJrD']===undefined){var _0x517737=function(_0x5a8e29){const _0x319d63='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x4e0805='',_0x57921f='';for(let _0x997e7d=0x0,_0x2df684,_0x5ad720,_0x433629=0x0;_0x5ad720=_0x5a8e29['charAt'](_0x433629++);~_0x5ad720&&(_0x2df684=_0x997e7d%0x4?_0x2df684*0x40+_0x5ad720:_0x5ad720,_0x997e7d++%0x4)?_0x4e0805+=String['fromCharCode'](0xff&_0x2df684>>(-0x2*_0x997e7d&0x6)):0x0){_0x5ad720=_0x319d63['indexOf'](_0x5ad720);}for(let _0x33166c=0x0,_0x28e4f2=_0x4e0805['length'];_0x33166c<_0x28e4f2;_0x33166c++){_0x57921f+='%'+('00'+_0x4e0805['charCodeAt'](_0x33166c)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x57921f);};const _0x6d6410=function(_0x4f3ad6,_0x28cd52){let _0x3a4fa6=[],_0x494034=0x0,_0x4f28b1,_0x1ec03f='';_0x4f3ad6=_0x517737(_0x4f3ad6);let _0x1acd8b;for(_0x1acd8b=0x0;_0x1acd8b<0x100;_0x1acd8b++){_0x3a4fa6[_0x1acd8b]=_0x1acd8b;}for(_0x1acd8b=0x0;_0x1acd8b<0x100;_0x1acd8b++){_0x494034=(_0x494034+_0x3a4fa6[_0x1acd8b]+_0x28cd52['charCodeAt'](_0x1acd8b%_0x28cd52['length']))%0x100,_0x4f28b1=_0x3a4fa6[_0x1acd8b],_0x3a4fa6[_0x1acd8b]=_0x3a4fa6[_0x494034],_0x3a4fa6[_0x494034]=_0x4f28b1;}_0x1acd8b=0x0,_0x494034=0x0;for(let _0x400606=0x0;_0x400606<_0x4f3ad6['length'];_0x400606++){_0x1acd8b=(_0x1acd8b+0x1)%0x100,_0x494034=(_0x494034+_0x3a4fa6[_0x1acd8b])%0x100,_0x4f28b1=_0x3a4fa6[_0x1acd8b],_0x3a4fa6[_0x1acd8b]=_0x3a4fa6[_0x494034],_0x3a4fa6[_0x494034]=_0x4f28b1,_0x1ec03f+=String['fromCharCode'](_0x4f3ad6['charCodeAt'](_0x400606)^_0x3a4fa6[(_0x3a4fa6[_0x1acd8b]+_0x3a4fa6[_0x494034])%0x100]);}return _0x1ec03f;};a0_0xa4a6['YEbknG']=_0x6d6410,a0_0xa4a6['QNQDqw']={},a0_0xa4a6['JmeJrD']=!![];}const _0x10211e=_0x152796[0x0],_0x2a793d=_0xa2dfc1+_0x10211e,_0x2bc5e3=a0_0xa4a6['QNQDqw'][_0x2a793d];return!_0x2bc5e3?(a0_0xa4a6['lYBLiN']===undefined&&(a0_0xa4a6['lYBLiN']=!![]),_0xa4a638=a0_0xa4a6['YEbknG'](_0xa4a638,_0x138a0c),a0_0xa4a6['QNQDqw'][_0x2a793d]=_0xa4a638):_0xa4a638=_0x2bc5e3,_0xa4a638;}function a0_0x1527(){const _0x21a110=['FSootSo+W67cRspcJZldV0FdTq','WOv7W59+W6BdKSodwIBcIa','uSo6W6FdHmkTCmovAa','WRxdUN/dOSkFbGe','fmkBfSoIWQCAcCoOWR9nW5hdVgO','WObJymkihSoVBWv+DW','ggWVW4rMuCkpW4dcGbyhW47dLCkvDGaXW43cMX7cKSoqy1q','lCoSWQzFumkPWRtcJSk6WOiJgCkHW5W','WQBcUmkeW7ZcSSocg8oL','ofBcQ8oWkSkehmkFWRD4WQnj','WRVcHWNcKmkdmcJdMmkUWQK','C8kBk1FdP2bcyNmYE8oaWQeQ','WOJcPgqbWPPsWPW','WPpdM0XKzenpgCkkmq','r8oOef3cP8oFqSk1WOdcOa','ESojs8oZW63cR3BcIYZdVNtdKSoo','W54TWOm1WPtcLmkCzYJcTSoJW7yv','BtjZW73cJCkxWRddTfK9WOj+W6Kk','hGddJqnBmvXdxMZdLgS','DqxdNb08cuhcHdeMzGW','WQ4zFqxdNLxcOJ8','WQieW5OeuXmWDColxCorf8oh','tSoGDaZcQCobDCkU','W4BdKSk1bmofWOtdPCoOWQJcMG','zCkPWOWIW5JdUSonWOC','nK4fldX7','W4tdTc7dKCoMrCknWOdcQCkyW7hdJ8ol','Ac3dH8okWOldPgqdWPCg','nNOYWQxdVSoFW6/cH0Op','WQOpW58duHm1wSoDq8oyl8o9','WPhdNWCmDubfiG','p3WPWPldNSoFW6/cK0qtWPC'];a0_0x1527=function(){return _0x21a110;};return a0_0x1527();}export const SENSITIVE_KEYS=new Set([a0_0x483ad6(0x82,'zzAg'),a0_0x483ad6(0x94,'Lp4^'),a0_0x483ad6(0x8f,']o&H'),a0_0x483ad6(0x8a,'I7rj'),a0_0x483ad6(0x80,'MLS]'),a0_0x483ad6(0x85,'x5GK'),a0_0x483ad6(0x88,'x5GK'),a0_0x483ad6(0x84,'Vzpf')]);

View File

@@ -58,21 +58,28 @@ function replaceContentByTag(xmlString, tagName, newContent) {
export { extractContentByTag, replaceContentByTag, extractFullTagBlock, opt_extractContentByTag, opt_replaceContentByTag, opt_extractFullTagBlock }; export { extractContentByTag, replaceContentByTag, extractFullTagBlock, opt_extractContentByTag, opt_replaceContentByTag, opt_extractFullTagBlock };
function escapeRegex(s) {
return String(s ?? '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function opt_extractContentByTag(text, tagName) { function opt_extractContentByTag(text, tagName) {
const regex = new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`); const safe = escapeRegex(tagName);
const regex = new RegExp(`<${safe}[^>]*>([\\s\\S]*?)<\\/${safe}>`);
const match = text.match(regex); const match = text.match(regex);
return match ? match[1] : null; return match ? match[1] : null;
} }
function opt_extractFullTagBlock(text, tagName) { function opt_extractFullTagBlock(text, tagName) {
const regex = new RegExp(`(<${tagName}[^>]*>[\\s\\S]*?<\\/${tagName}>)`); const safe = escapeRegex(tagName);
const regex = new RegExp(`(<${safe}[^>]*>[\\s\\S]*?<\\/${safe}>)`);
const match = text.match(regex); const match = text.match(regex);
return match ? match[0] : null; return match ? match[0] : null;
} }
function opt_replaceContentByTag(originalText, tagName, newContent) { function opt_replaceContentByTag(originalText, tagName, newContent) {
const regex = new RegExp(`(<${tagName}[^>]*>)([\\s\\S]*?)(<\\/${tagName}>)`); const safe = escapeRegex(tagName);
const regex = new RegExp(`(<${safe}[^>]*>)([\\s\\S]*?)(<\\/${safe}>)`);
const match = originalText.match(regex); const match = originalText.match(regex);
if (match) { if (match) {