9 Commits

Author SHA1 Message Date
Jenkins CI
59c4adc1c0 release: v2.2.4 [2026-05-30 13:03:07]
### 新功能
- **Function Call 填表**:
  - FC 首次请求时对 DeepSeek 系模型自动附加 `thinking: { type: "disabled" }`,避免思考模式与 tool_choice 冲突
  - 操作列表为空时在日志面板输出原始响应 JSON,便于区分"AI 判断无需变更"、"格式校验全部不通过"和"JSON 解析失败"三种情况
### 修复
- **剧情优化**:移除剧情优化页面遗留的 Jqyh 直连配置字段(URL / Key / Model),统一走 API 连接配置功能分配槽位
- **表格**:
  - 补全 `batch-filling-threshold` 批处理阈值的持久化绑定(页面刷新后不再还原为默认值 30)
  - 修复分步填表并发锁与 async/await 时序问题
  - 修复外层多余 `try...finally` 导致的插件加载报错
- **Rerank**:
  - 修复选择连接配置后报"API Key 未配置"的问题(`apiMode` 现从设置读取而非硬编码 `custom`)
  - 补全 `hly-rerank-api-mode` 加载绑定及默认值
- **翰林院 RAG**:补全 `priorityRetrieval.sources` 各来源条目的缺失键,修复设置面板回填 TypeError
- **二次填表**:
  - 修复 `secondary-filler.js` 把哈希/重试次数写入非持久化的 `msg.metadata` 字段(ST 标准位是 `msg.extra`),导致刷新后去重与重试计数失效
  - 修复扫描深度重复计入 `bufferSize`(`contextLimit + buffer + batch + redundancy` → `contextLimit + batch + redundancy`),避免越过预期窗口
  - SWIPED 事件改走扫描路径,不再用 `targetMessage` bypass 强填最末条,`保留缓冲区(bufferSize)` 设置在滑动场景下正确生效(手动"回退重填"按钮仍保留 bypass,意图明确)
  - 修复 FC(Function Call)路径下成功填表与"AI 判断无需修改"两种结果均未写回 `amily2_process_hash` 与 `saveChat()` 的问题——之前导致 FC 模式去重完全失效,最旧的未处理楼层会被每次扫描重复发给 AI;现统一回写路径为 `markTargetsProcessed`
  - FC 空操作时同步输出原始响应 JSON 到控制台(与批量回填日志面板保持一致),便于区分"无需变更"/"格式校验失败"/"JSON 解析失败"
2026-05-30 13:03:07 +08:00
Jenkins CI
e66544f774 release: v2.2.4 [2026-05-30 12:44:56]
### 新功能
- **Function Call 填表**:
  - FC 首次请求时对 DeepSeek 系模型自动附加 `thinking: { type: "disabled" }`,避免思考模式与 tool_choice 冲突
  - 操作列表为空时在日志面板输出原始响应 JSON,便于区分"AI 判断无需变更"、"格式校验全部不通过"和"JSON 解析失败"三种情况
### 修复
- **剧情优化**:移除剧情优化页面遗留的 Jqyh 直连配置字段(URL / Key / Model),统一走 API 连接配置功能分配槽位
- **表格**:
  - 补全 `batch-filling-threshold` 批处理阈值的持久化绑定(页面刷新后不再还原为默认值 30)
  - 修复分步填表并发锁与 async/await 时序问题
  - 修复外层多余 `try...finally` 导致的插件加载报错
- **Rerank**:
  - 修复选择连接配置后报"API Key 未配置"的问题(`apiMode` 现从设置读取而非硬编码 `custom`)
  - 补全 `hly-rerank-api-mode` 加载绑定及默认值
- **翰林院 RAG**:补全 `priorityRetrieval.sources` 各来源条目的缺失键,修复设置面板回填 TypeError
- **二次填表**:
  - 修复 `secondary-filler.js` 把哈希/重试次数写入非持久化的 `msg.metadata` 字段(ST 标准位是 `msg.extra`),导致刷新后去重与重试计数失效
  - 修复扫描深度重复计入 `bufferSize`(`contextLimit + buffer + batch + redundancy` → `contextLimit + batch + redundancy`),避免越过预期窗口
  - SWIPED 事件改走扫描路径,不再用 `targetMessage` bypass 强填最末条,`保留缓冲区(bufferSize)` 设置在滑动场景下正确生效(手动"回退重填"按钮仍保留 bypass,意图明确)
2026-05-30 12:44:56 +08:00
Jenkins CI
d6b3b00c86 release: v2.2.4 [2026-05-30 12:16:52]
### 新功能
- **Function Call 填表**:
  - FC 首次请求时对 DeepSeek 系模型自动附加 `thinking: { type: "disabled" }`,避免思考模式与 tool_choice 冲突
  - 操作列表为空时在日志面板输出原始响应 JSON,便于区分"AI 判断无需变更"、"格式校验全部不通过"和"JSON 解析失败"三种情况
### 修复
- **剧情优化**:移除剧情优化页面遗留的 Jqyh 直连配置字段(URL / Key / Model),统一走 API 连接配置功能分配槽位
- **表格**:
  - 补全 `batch-filling-threshold` 批处理阈值的持久化绑定(页面刷新后不再还原为默认值 30)
  - 修复分步填表并发锁与 async/await 时序问题
  - 修复外层多余 `try...finally` 导致的插件加载报错
- **Rerank**:
  - 修复选择连接配置后报"API Key 未配置"的问题(`apiMode` 现从设置读取而非硬编码 `custom`)
  - 补全 `hly-rerank-api-mode` 加载绑定及默认值
- **翰林院 RAG**:补全 `priorityRetrieval.sources` 各来源条目的缺失键,修复设置面板回填 TypeError
2026-05-30 12:16:52 +08:00
Jenkins CI
a8c3ad9027 release: v2.2.4 [2026-05-30 11:32:49]
### 新功能
- **Function Call 填表**:
  - FC 首次请求时对 DeepSeek 系模型自动附加 `thinking: { type: "disabled" }`,避免思考模式与 tool_choice 冲突
  - 操作列表为空时在日志面板输出原始响应 JSON,便于区分"AI 判断无需变更"、"格式校验全部不通过"和"JSON 解析失败"三种情况
### 修复
- **剧情优化**:移除剧情优化页面遗留的 Jqyh 直连配置字段(URL / Key / Model),统一走 API 连接配置功能分配槽位
- **表格**:
  - 补全 `batch-filling-threshold` 批处理阈值的持久化绑定(页面刷新后不再还原为默认值 30)
  - 修复分步填表并发锁与 async/await 时序问题
  - 修复外层多余 `try...finally` 导致的插件加载报错
- **Rerank**:
  - 修复选择连接配置后报"API Key 未配置"的问题(`apiMode` 现从设置读取而非硬编码 `custom`)
  - 补全 `hly-rerank-api-mode` 加载绑定及默认值
- **翰林院 RAG**:补全 `priorityRetrieval.sources` 各来源条目的缺失键,修复设置面板回填 TypeError
2026-05-30 11:32:49 +08:00
Jenkins CI
0e11f85031 release: v2.2.3 [2026-05-29 21:31:05]
### 新功能
- Function Call 填表开关下方新增公益站风险提示横幅:部分公益站会屏蔽 tools 参数,请确认支持情况避免被意外封禁
### 修复
- **Function Call 填表**:
  - 修复 ST 代理以 HTTP 200 + error body 形式返回错误、导致降级重试机制从未触发的问题
  - 修复思考模式模型(如 DeepSeek v4-flash)因 tool_choice 不兼容返回 Bad Request 后正确降级并重试
  - 重试时自动追加强制调用指令,防止思考模型绕过工具直接输出文本造成无效二次开销
- **超级记忆 / 翰林院**:
  - 修复 `getRagSettings()` 读写顶层路径而非嵌套路径,导致打开超级记忆面板后向量化、归档等开关在重载时被全默认值覆盖的问题
  - 修复自动归档失效问题
  - 修复归档管理器在同一事件中被三次触发的回归问题
  - 修复翰林院设置旧版迁移逻辑异常
2026-05-29 21:31:05 +08:00
Jenkins CI
9bc2f694b0 release: v2.2.3 [2026-05-29 13:07:39]
### 新功能
- Function Call 填表开关下方新增公益站风险提示横幅:部分公益站会屏蔽 tools 参数,请确认支持情况避免被意外封禁
### 修复
- **Function Call 填表**:
  - 修复 ST 代理以 HTTP 200 + error body 形式返回错误、导致降级重试机制从未触发的问题
  - 修复思考模式模型(如 DeepSeek v4-flash)因 tool_choice 不兼容返回 Bad Request 后正确降级并重试
  - 重试时自动追加强制调用指令,防止思考模型绕过工具直接输出文本造成无效二次开销
- **超级记忆 / 翰林院**:
  - 修复 `getRagSettings()` 读写顶层路径而非嵌套路径,导致打开超级记忆面板后向量化、归档等开关在重载时被全默认值覆盖的问题
  - 修复自动归档失效问题
  - 修复归档管理器在同一事件中被三次触发的回归问题
  - 修复翰林院设置旧版迁移逻辑异常
2026-05-29 13:07:39 +08:00
Jenkins CI
08e1dbde85 release: v2.2.3 [2026-05-27 23:06:48]
### 新功能
- Function Call 填表开关下方新增公益站风险提示横幅:部分公益站会屏蔽 tools 参数,请确认支持情况避免被意外封禁
### 修复
- **Function Call 填表**:
  - 修复 ST 代理以 HTTP 200 + error body 形式返回错误、导致降级重试机制从未触发的问题
  - 修复思考模式模型(如 DeepSeek v4-flash)因 tool_choice 不兼容返回 Bad Request 后正确降级并重试
  - 重试时自动追加强制调用指令,防止思考模型绕过工具直接输出文本造成无效二次开销
- **超级记忆 / 翰林院**:
  - 修复 `getRagSettings()` 读写顶层路径而非嵌套路径,导致打开超级记忆面板后向量化、归档等开关在重载时被全默认值覆盖的问题
  - 修复自动归档失效问题
  - 修复归档管理器在同一事件中被三次触发的回归问题
  - 修复翰林院设置旧版迁移逻辑异常
2026-05-27 23:06:48 +08:00
Jenkins CI
42e0bdec19 release: v2.2.3 [2026-05-27 21:24:56]
### 新功能
- Function Call 填表开关下方新增公益站风险提示横幅:部分公益站会屏蔽 tools 参数,请确认支持情况避免被意外封禁
### 修复
- **Function Call 填表**:
  - 修复 ST 代理以 HTTP 200 + error body 形式返回错误、导致降级重试机制从未触发的问题
  - 修复思考模式模型(如 DeepSeek v4-flash)因 tool_choice 不兼容返回 Bad Request 后正确降级并重试
  - 重试时自动追加强制调用指令,防止思考模型绕过工具直接输出文本造成无效二次开销
- **超级记忆 / 翰林院**:
  - 修复 `getRagSettings()` 读写顶层路径而非嵌套路径,导致打开超级记忆面板后向量化、归档等开关在重载时被全默认值覆盖的问题
  - 修复自动归档失效问题
  - 修复归档管理器在同一事件中被三次触发的回归问题
  - 修复翰林院设置旧版迁移逻辑异常
2026-05-27 21:24:56 +08:00
Jenkins CI
3e217e8ed8 release: v2.2.2 [2026-05-27 19:39:34]
### 新功能
- **Function Call 填表模式**:在填表设置中新增独立开关,启用后支持通过 OpenAI 兼容接口(DeepSeek / OpenRouter / 各类中转等)直接返回结构化操作列表,绕过 `<Amily2Edit>` 文本解析路径,填表更稳定
  - 遇到不支持 `tool_choice` 的接口时自动降级重试
  - 对思考模型注入强制调用指令,防止绕过工具直接输出文本
  - 全部走 ST 后端代理,修复 CSP 拦截直连外部 URL 的问题
- **主界面新增提示词链编辑器入口**,同时调换了记忆管理与角色世界书的按钮位置
- **规则中心**新增"自动排除用户楼层"选项
### 修复
- 提示词链按钮点击无响应(改为事件委托方式绑定)
- 拖拽组件微抖误触发(加 5px 移动阈值过滤)
- 填表检查窗若干问题修复;翰林院(批量回填)修复;防抖逻辑落地
- 角色世界书入口添加使用警告弹窗(强制 10 秒倒计时),提示该功能长期未维护
- ApiProfile `fakeStream` 字段保存丢失问题
- 正文优化默认改为关闭状态
- NGMS / NCCS API 配置槽位标签修正(NGMS→总结,NCCS→填表)
- API Profile 面板选择逻辑统一重构,修复多处旧字段覆盖新配置的问题
- 世界书控制参数兼容性修复(排除递归、插入位置、扫描深度等,适配 ST 1.17.0+)
2026-05-27 19:39:34 +08:00
25 changed files with 295 additions and 480 deletions

View File

@@ -26,3 +26,51 @@
- NGMS / NCCS API 配置槽位标签修正NGMS→总结NCCS→填表
- API Profile 面板选择逻辑统一重构,修复多处旧字段覆盖新配置的问题
- 世界书控制参数兼容性修复(排除递归、插入位置、扫描深度等,适配 ST 1.17.0+
---
## v2.2.3
### 新功能
- Function Call 填表开关下方新增公益站风险提示横幅:部分公益站会屏蔽 tools 参数,请确认支持情况避免被意外封禁
### 修复
- **Function Call 填表**
- 修复 ST 代理以 HTTP 200 + error body 形式返回错误、导致降级重试机制从未触发的问题
- 修复思考模式模型(如 DeepSeek v4-flash因 tool_choice 不兼容返回 Bad Request 后正确降级并重试
- 重试时自动追加强制调用指令,防止思考模型绕过工具直接输出文本造成无效二次开销
- **超级记忆 / 翰林院**
- 修复 `getRagSettings()` 读写顶层路径而非嵌套路径,导致打开超级记忆面板后向量化、归档等开关在重载时被全默认值覆盖的问题
- 修复自动归档失效问题
- 修复归档管理器在同一事件中被三次触发的回归问题
- 修复翰林院设置旧版迁移逻辑异常
---
## v2.2.4
### 新功能
- **Function Call 填表**
- FC 首次请求时对 DeepSeek 系模型自动附加 `thinking: { type: "disabled" }`,避免思考模式与 tool_choice 冲突
- 操作列表为空时在日志面板输出原始响应 JSON便于区分"AI 判断无需变更"、"格式校验全部不通过"和"JSON 解析失败"三种情况
### 修复
- **剧情优化**:移除剧情优化页面遗留的 Jqyh 直连配置字段URL / Key / Model统一走 API 连接配置功能分配槽位
- **表格**
- 补全 `batch-filling-threshold` 批处理阈值的持久化绑定(页面刷新后不再还原为默认值 30
- 修复分步填表并发锁与 async/await 时序问题
- 修复外层多余 `try...finally` 导致的插件加载报错
- **Rerank**
- 修复选择连接配置后报"API Key 未配置"的问题(`apiMode` 现从设置读取而非硬编码 `custom`
- 补全 `hly-rerank-api-mode` 加载绑定及默认值
- **翰林院 RAG**:补全 `priorityRetrieval.sources` 各来源条目的缺失键,修复设置面板回填 TypeError
- **二次填表**
- 修复 `secondary-filler.js` 把哈希/重试次数写入非持久化的 `msg.metadata` 字段ST 标准位是 `msg.extra`),导致刷新后去重与重试计数失效
- 修复扫描深度重复计入 `bufferSize``contextLimit + buffer + batch + redundancy``contextLimit + batch + redundancy`),避免越过预期窗口
- SWIPED 事件改走扫描路径,不再用 `targetMessage` bypass 强填最末条,`保留缓冲区(bufferSize)` 设置在滑动场景下正确生效(手动"回退重填"按钮仍保留 bypass意图明确
- 修复 FCFunction Call路径下成功填表与"AI 判断无需修改"两种结果均未写回 `amily2_process_hash``saveChat()` 的问题——之前导致 FC 模式去重完全失效,最旧的未处理楼层会被每次扫描重复发给 AI现统一回写路径为 `markTargetsProcessed`
- FC 空操作时同步输出原始响应 JSON 到控制台(与批量回填日志面板保持一致),便于区分"无需变更"/"格式校验失败"/"JSON 解析失败"

View File

@@ -36,47 +36,12 @@
<!-- API Settings Tab -->
<div id="sinan-api-settings-tab" class="sinan-tab-pane active">
<fieldset class="settings-group">
<legend>Jqyh API</legend>
<div class="control-block-with-switch">
<label for="amily2_jqyh_enabled"><strong>启用 Jqyh API</strong></label>
<label class="toggle-switch">
<input id="amily2_jqyh_enabled" type="checkbox" />
<span class="slider"></span>
</label>
</div>
<div id="amily2_jqyh_content" style="display: none;" class="inline-settings-grid">
<label for="amily2_jqyh_api_mode">API 模式</label>
<select id="amily2_jqyh_api_mode" class="text_pole">
<option value="openai_test">全兼容模式</option>
<option value="sillytavern_preset">SillyTavern 预设</option>
</select>
<div id="amily2_jqyh_compatible_config" class="inline-settings-grid" style="grid-column: 1 / -1;">
<label for="amily2_jqyh_api_url">API URL</label>
<input type="text" id="amily2_jqyh_api_url" class="text_pole" placeholder="例如: https://api.openai.com/v1">
<label for="amily2_jqyh_api_key">API Key</label>
<input type="password" id="amily2_jqyh_api_key" class="text_pole" placeholder="请输入您的 API Key">
<label for="amily2_jqyh_model">模型</label>
<div class="amily2_opt_preset_selector_wrapper">
<input type="text" id="amily2_jqyh_model" class="text_pole" placeholder="请先获取模型列表或手动输入">
<select id="amily2_jqyh_model_select" class="text_pole" style="display: none;"></select>
</div>
<div class="jqyh-button-row" style="grid-column: 1 / -1;">
<button id="amily2_jqyh_fetch_models" class="menu_button secondary" title="获取模型列表"><i class="fas fa-sync-alt"></i> 获取模型</button>
<button id="amily2_jqyh_test_connection" class="menu_button primary"><i class="fas fa-plug"></i> 测试连接</button>
</div>
</div>
<div id="amily2_jqyh_preset_config" class="inline-settings-grid" style="display: none; grid-column: 1 / -1;">
<label for="amily2_jqyh_tavern_profile">选择酒馆预设</label>
<select id="amily2_jqyh_tavern_profile" class="text_pole"></select>
</div>
<label for="amily2_jqyh_max_tokens">最大 Tokens: <span id="amily2_jqyh_max_tokens_value">4000</span></label>
<input type="number" class="text_pole" id="amily2_jqyh_max_tokens" min="100" max="100000" value="4000">
<label for="amily2_jqyh_temperature">温度: <span id="amily2_jqyh_temperature_value">0.7</span></label>
<input type="number" class="text_pole" id="amily2_jqyh_temperature" min="0" max="2" value="0.7">
</div>
<legend>剧情优化 API</legend>
<p class="notes" style="margin: 0;">
剧情优化所用的连接配置统一在
<strong>API 连接配置 → 功能分配 → 剧情优化 / JQYH</strong>
中指定,无需在此单独填写。
</p>
</fieldset>
<fieldset class="settings-group">

View File

@@ -304,7 +304,10 @@
<span class="slider"></span>
</label>
</div>
<p class="notes" style="margin-bottom: 10px;">仅支持 openai 直连接口tableFilling 槽位)。启用后跳过 &lt;Amily2Edit&gt; 文本解析,由模型直接返回操作列表。</p>
<p class="notes" style="margin-bottom: 6px;">仅支持 openai 直连接口tableFilling 槽位)。启用后跳过 &lt;Amily2Edit&gt; 文本解析,由模型直接返回操作列表。</p>
<div style="background: rgba(255, 160, 0, 0.12); border-left: 3px solid #ffa000; border-radius: 3px; padding: 6px 10px; margin-bottom: 10px; font-size: 0.85em; color: #ffcc80;">
⚠️ 部分公益站因禁止用于跑代码会屏蔽 tools 参数,请确认公益站是否支持 tools 调用,避免被意外封禁。
</div>
<hr class="section-divider" style="margin: 10px 0;">

View File

@@ -485,8 +485,7 @@ export async function getApiSettings(slot = 'main') {
apiProvider: apiMode,
apiUrl: settings.plotOpt_apiUrl?.trim() || '',
apiKey: configManager.get('plotOpt_apiKey') || '',
model: document.getElementById('amily2_opt_model')?.value?.trim()
|| settings.plotOpt_model || '',
model: settings.plotOpt_model || '',
maxTokens: settings.plotOpt_max_tokens ?? 65500,
temperature: settings.plotOpt_temperature ?? 1.0,
tavernProfile: '',
@@ -986,7 +985,11 @@ export async function callAIForTools(messages, tool, options = {}) {
return null;
}
const buildFCBody = (withToolChoice, overrideMessages) => ({
// deepseek.com 域名或模型名含 deepseek 时,第一次调用主动关闭思考模式,
// 让 tool_choice 强制走 Function Call思考模式下 tool_choice 会报错/失败)
const isDeepSeek = /deepseek/i.test(finalOptions.apiUrl || '') || /deepseek/i.test(finalOptions.model || '');
const buildFCBody = (withToolChoice, overrideMessages, extraParams = {}) => ({
chat_completion_source: 'openai',
reverse_proxy: finalOptions.apiUrl,
proxy_password: finalOptions.apiKey,
@@ -996,15 +999,16 @@ export async function callAIForTools(messages, tool, options = {}) {
temperature: finalOptions.temperature ?? 1,
stream: false,
...(finalOptions.customParams || {}),
...extraParams,
tools: [tool],
...(withToolChoice ? { tool_choice: { type: 'function', function: { name: tool.function.name } } } : {}),
});
const doFCRequest = async (withToolChoice, overrideMessages) => {
const doFCRequest = async (withToolChoice, overrideMessages, extraParams) => {
const response = await fetch('/api/backends/chat-completions/generate', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(buildFCBody(withToolChoice, overrideMessages)),
body: JSON.stringify(buildFCBody(withToolChoice, overrideMessages, extraParams)),
});
if (!response.ok) {
const errorText = await response.text();
@@ -1027,7 +1031,10 @@ export async function callAIForTools(messages, tool, options = {}) {
let data;
try {
// 走 ST 后端代理,避免浏览器 CSP 拦截直连外部 URL
data = await doFCRequest(true);
// DeepSeek 思考模式与 tool_choice 不兼容,第一次请求时主动关闭思考模式
const firstAttemptExtra = isDeepSeek ? { thinking: { type: 'disabled' } } : {};
if (isDeepSeek) console.log('[Amily2-外交部] 检测到 DeepSeek 端点,首次 FC 请求附加 thinking:disabled');
data = await doFCRequest(true, undefined, firstAttemptExtra);
} catch (firstError) {
// 首次失败(含 ST 代理吞掉错误码场景)无条件去掉 tool_choice 重试一次
// 思考模式模型支持 tools 但不支持强制 tool_choice追加强制指令防止模型直接输出文本

View File

@@ -10,8 +10,16 @@ export function initializeArchiveManager() {
console.log('[归档管理器] 已启动,正在监控表格状态...');
}
/** Bus 直调路径:由 super-memory/manager.js 的 pushUpdate 调用,接受纯 payload 对象。 */
export function handleArchiveUpdate(payload) {
return handleArchivePayload(payload);
}
async function handleTableUpdate(event) {
const { tableName, data, role } = event.detail;
return handleArchivePayload(event.detail);
}
async function handleArchivePayload({ tableName, data, role }) {
const settings = getSettings();
if (!settings.archive || !settings.archive.enabled) return;
@@ -24,7 +32,8 @@ async function handleTableUpdate(event) {
if (isArchiving) return;
let hasNotice = false;
let realRows = data;
if (data.length > 0 && data[0][2] && data[0][2].includes('已自动归档')) {
hasNotice = true;
realRows = data.slice(1);

View File

@@ -49,12 +49,13 @@ export async function getEmbedRetrievalSettings() {
export async function getRerankSettings() {
const profile = await getSlotProfile('ragRerank');
if (profile) {
const manualSettings = getSettings().rerank || {};
return {
url: profile.apiUrl,
apiKey: profile.apiKey ?? '',
model: profile.model,
top_n: getSettings().rerank?.top_n ?? 10,
apiMode: 'custom',
top_n: manualSettings.top_n ?? 10,
apiMode: manualSettings.apiMode ?? 'custom',
};
}
return getSettings().rerank || {};

View File

@@ -341,7 +341,26 @@ function getSettings() {
}
}
}
// 旧版设置 rerank.priorityRetrieval 可能只有 enabled 字段而缺少 sources补全
if (s.rerank?.priorityRetrieval && !s.rerank.priorityRetrieval.sources) {
s.rerank.priorityRetrieval.sources = structuredClone(ragDefaultSettings.rerank.priorityRetrieval.sources);
}
// 确保 sources 中每个来源条目完整(新增来源 / 新增字段时旧用户不会缺失)
if (s.rerank?.priorityRetrieval?.sources) {
const defaultSources = ragDefaultSettings.rerank.priorityRetrieval.sources;
for (const sourceName in defaultSources) {
if (!s.rerank.priorityRetrieval.sources[sourceName]) {
s.rerank.priorityRetrieval.sources[sourceName] = structuredClone(defaultSources[sourceName]);
} else {
const existing = s.rerank.priorityRetrieval.sources[sourceName];
for (const key in defaultSources[sourceName]) {
if (existing[key] === undefined) existing[key] = defaultSources[sourceName][key];
}
}
}
}
return s;
}

View File

@@ -65,8 +65,9 @@ export const defaultSettings = {
},
rerank: {
enabled: false,
apiMode: 'custom',
url: 'https://api.siliconflow.cn/v1',
apiKey: '',
apiKey: '',
model: 'Pro/BAAI/bge-reranker-v2-m3',
top_n: 5,
hybrid_alpha: 0.7,

View File

@@ -15,7 +15,6 @@ import { resolveHistoriographyRuleConfig } from "../utils/config/RuleProfileMana
import { getPresetPrompts, getMixedOrder } from '../PresetSettings/index.js';
import { callAI, generateRandomSeed } from './api.js';
import { callJqyhAI } from './api/JqyhApi.js';
import { callConcurrentAI } from './api/ConcurrentApi.js';
export async function processOptimization(latestMessage, previousMessages) {
@@ -480,7 +479,7 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
onProgress(getRandomText(['正在检索辅助记忆 (LLM-B)...', '正在扫描平行世界线 (LLM-B)...']), false);
onProgress(getRandomText(['正在与核心意识同步 (LLM-A)...', '正在等待灵魂共鸣 (LLM-A)...']), false);
const promise1 = (settings.jqyhEnabled ? callJqyhAI(mainMessages) : callAI(mainMessages, { slot: 'plotOpt' })).then(res => {
const promise1 = callAI(mainMessages, { slot: 'plotOpt' }).then(res => {
onProgress(getRandomText(['正在与核心意识同步 (LLM-A)...', '正在等待灵魂共鸣 (LLM-A)...']), true);
return res;
});
@@ -554,7 +553,7 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
attempt++;
console.log(`[${extensionName}] 剧情优化第 ${attempt} 次尝试...`);
const rawResponse = settings.jqyhEnabled ? await callJqyhAI(mainMessages) : await callAI(mainMessages, { slot: 'plotOpt' });
const rawResponse = await callAI(mainMessages, { slot: 'plotOpt' });
if (cancellationState.isCancelled) {
console.log(`[${extensionName}] 优化任务在API调用后被中止。`);

View File

@@ -9,10 +9,11 @@ const RAG_MODULE_NAME = 'hanlinyuan-rag-core';
function getRagSettings() {
if (!extension_settings[extensionName]) extension_settings[extensionName] = {};
if (!extension_settings[RAG_MODULE_NAME]) {
extension_settings[RAG_MODULE_NAME] = structuredClone(ragDefaultSettings);
const root = extension_settings[extensionName];
if (!root[RAG_MODULE_NAME]) {
root[RAG_MODULE_NAME] = structuredClone(ragDefaultSettings);
}
return extension_settings[RAG_MODULE_NAME];
return root[RAG_MODULE_NAME];
}
export function bindSuperMemoryEvents() {

View File

@@ -6,6 +6,7 @@ import { syncToLorebook, ensureMemoryBook, updateTransientHint, getMemoryBookNam
import { getMemoryState, loadMemoryState, saveMemoryState } from "../table-system/manager.js";
import { TABLE_UPDATED_EVENT } from "../table-system/events-schema.js";
import { eventSource, event_types } from "/script.js";
import { handleArchiveUpdate } from "../archive-manager.js";
/* ── [AMILY2-MODIFIED] ── pipeline integration: awaitSync() export ── */
let isInitialized = false;
@@ -110,10 +111,15 @@ export function pushUpdate(payload) {
updateQueue.push({ tableName, data, role, headers, rowStatuses });
_syncPromise = processQueue();
// Bus 路径下 document event 不再分发,需直接通知归档管理器
handleArchiveUpdate(payload);
}
/** CustomEvent 降级路径Bus 未就绪时的兜底监听器) */
function handleTableUpdate(event) {
// Bus 已就绪时 pushUpdate 已由 dispatchTableUpdate 直调,跳过避免重复处理
if (window.Amily2Bus?.query('SuperMemory')?.pushUpdate) return;
pushUpdate(event.detail);
}

View File

@@ -291,7 +291,17 @@ async function runBatchAttempt(batchNum, attemptNum) {
if (!argsString) throw new Error('Function Call 返回为空。');
const ops = parseToolCallArgs(argsString);
if (ops.length === 0) {
log(`批次 ${batchNum} 的 Function Call 返回操作列表为空AI 判断此批次无需变更。`, 'warn');
let parseHint = '';
try {
const rawParsed = JSON.parse(argsString);
const rawOpsLen = rawParsed?.operations?.length ?? 0;
if (rawOpsLen > 0) {
parseHint = `(响应含 ${rawOpsLen} 条操作,但全部未通过格式校验)`;
}
} catch {
parseHint = '(响应 JSON 解析失败)';
}
log(`批次 ${batchNum} FC 操作列表为空${parseHint},原始响应:\n${argsString}`, 'warn');
toastr.info('AI 判断此批次无需修改。', `批次 ${batchNum}`);
} else {
await updateTableFromOps(ops, { immediateDelete: true });
@@ -417,7 +427,9 @@ export function startBatchFilling() {
manualStopRequested = false;
const context = getContext();
chatHistoryLength = context.chat.length;
threshold = parseInt(document.getElementById('batch-filling-threshold')?.value, 10) || 30;
threshold = extension_settings[extensionName]?.batch_filling_threshold
?? parseInt(/** @type {HTMLInputElement|null} */ (document.getElementById('batch-filling-threshold'))?.value, 10)
?? 30;
const ruleTemplate = getBatchFillerRuleTemplate();
const flowTemplate = getBatchFillerFlowTemplate();
@@ -562,7 +574,17 @@ export async function startFloorRangeFilling(startFloor, endFloor) {
if (!argsString) throw new Error('Function Call 返回为空。');
const ops = parseToolCallArgs(argsString);
if (ops.length === 0) {
log(`楼层 ${startFloor}-${endFloor} Function Call 返回操作列表为空,无需变更。`, 'warn');
let parseHint = '';
try {
const rawParsed = JSON.parse(argsString);
const rawOpsLen = rawParsed?.operations?.length ?? 0;
if (rawOpsLen > 0) {
parseHint = `(响应含 ${rawOpsLen} 条操作,但全部未通过格式校验)`;
}
} catch {
parseHint = '(响应 JSON 解析失败)';
}
log(`楼层 ${startFloor}-${endFloor} FC 操作列表为空${parseHint},原始响应:\n${argsString}`, 'warn');
toastr.info('AI 判断此楼层范围无需修改。', `楼层 ${startFloor}-${endFloor}`);
} else {
await updateTableFromOps(ops, { immediateDelete: true });

View File

@@ -1021,7 +1021,7 @@ export async function rollbackAndRefill() {
const lastMessage = context.chat[context.chat.length - 1];
try {
await fillWithSecondaryApi(lastMessage, true);
await fillWithSecondaryApi(lastMessage, true, { targetMessage: lastMessage });
log('回退并重新填表操作完成。', 'success');
} catch (error) {
log(`回退重填过程中发生错误: ${error.message}`, 'error');

View File

@@ -18,6 +18,7 @@ import { showTableFillReviewModal } from '../../ui/page-window.js';
const CONTINUE_PROMPT_SECONDARY = '上一条回复不完整或缺少 <Amily2Edit> 指令块。请直接从中断处继续生成剩余内容,不要重复已输出的文本,也不要添加任何解释或寒暄,确保最终输出中包含完整的 <Amily2Edit>...</Amily2Edit> 指令块。';
let secondaryFillerDebounceTimer = null;
let secondaryFillerRunning = false;
async function callSecondaryModel(messages) {
const settings = extension_settings[extensionName] || {};
@@ -38,23 +39,30 @@ async function requestSecondaryContinuation(baseMessages, partialResponse) {
return `${partialResponse || ''}${continued}`;
}
function commitSecondaryFillResult(rawContent, targetMessages) {
updateTableFromText(rawContent);
async function markTargetsProcessed(targetMessages, { skipTableSave = false } = {}) {
if (!targetMessages || targetMessages.length === 0) return;
const memoryState = getMemoryState();
const lastProcessedMsg = targetMessages[targetMessages.length - 1].msg;
for (const target of targetMessages) {
if (!target.msg.metadata) target.msg.metadata = {};
target.msg.metadata.Amily2_Process_Hash = target.hash;
if (!target.msg.extra) target.msg.extra = {};
target.msg.extra.amily2_process_hash = target.hash;
}
if (saveStateToMessage(memoryState, lastProcessedMsg)) {
renderTables();
updateOrInsertTableInChat();
if (!skipTableSave) {
const memoryState = getMemoryState();
if (saveStateToMessage(memoryState, lastProcessedMsg)) {
renderTables();
updateOrInsertTableInChat();
}
}
saveChat();
await saveChat();
}
async function commitSecondaryFillResult(rawContent, targetMessages) {
await updateTableFromText(rawContent);
await markTargetsProcessed(targetMessages);
}
@@ -110,7 +118,12 @@ async function getWorldBookContext() {
return content.trim() ? `<世界书>\n${content.trim()}\n</世界书>` : '';
}
export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
export async function fillWithSecondaryApi(latestMessage, forceRun = false, opts = {}) {
if (secondaryFillerRunning) {
log('分步填表正在进行中,跳过本次触发。', 'warn');
return;
}
secondaryFillerRunning = true;
const settings = extension_settings[extensionName] || {};
// 【V2.1.1】分步填表触发延迟 / 防抖:自动触发时若配置了延迟,则延后执行,
@@ -122,7 +135,7 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
}
secondaryFillerDebounceTimer = setTimeout(() => {
secondaryFillerDebounceTimer = null;
fillWithSecondaryApi(latestMessage, forceRun);
fillWithSecondaryApi(latestMessage, forceRun, opts);
}, delay);
console.log(`[Amily2-副API] 分步填表已按防抖延迟 ${delay}ms 调度。`);
return;
@@ -160,29 +173,18 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
try {
const bufferSize = parseInt(settings.secondary_filler_buffer || 0, 10);
const batchSize = parseInt(settings.secondary_filler_batch || 0, 10);
const batchSize = parseInt(settings.secondary_filler_batch || 0, 10);
const contextLimit = parseInt(settings.secondary_filler_context || 2, 10);
// 【V1.7.7 修复】限制最大回溯深度,防止更新后无限填补旧历史
// 响应用户反馈:扫描深度 = 上下文 + 填表批次 + 保留楼层 + 冗余量(10)
// redundancy (冗余量): 额外扫描 10 层作为安全缓冲,防止因消息索引计算偏差导致漏掉边缘消息
// 扫描深度 = 上下文 + 填表批次 + 冗余量(10)
// bufferSize保留楼层仅用于限定尾部边界 validEndIndex
// 不再回流到扫描起点,避免重复影响范围
const redundancy = 10;
const maxScanDepth = contextLimit + batchSize + bufferSize + redundancy;
const maxScanDepth = contextLimit + batchSize + redundancy;
const chat = context.chat;
const totalMessages = chat.length;
const validEndIndex = totalMessages - 1 - bufferSize;
// 计算扫描的起始索引不小于0
const scanStartIndex = Math.max(0, validEndIndex - maxScanDepth);
if (validEndIndex < 0) {
console.log(`[Amily2-副API] 消息数量不足以超出保留区(${bufferSize}),跳过。`);
return;
}
let targetMessages = [];
let needsProcessing = false;
const getContentHash = (content) => {
let hash = 0, i, chr;
@@ -190,45 +192,74 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
for (i = 0; i < content.length; i++) {
chr = content.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
hash |= 0;
}
return hash;
};
// 【修复】改为正向扫描,优先处理最老的未处理消息,防止遗留消息被挤出扫描区
for (let i = scanStartIndex; i <= validEndIndex; i++) {
const msg = chat[i];
if (msg.is_user) continue;
let targetMessages = [];
const currentHash = getContentHash(msg.mes);
const savedHash = msg.metadata?.Amily2_Process_Hash;
const isUnprocessed = !savedHash;
const isChanged = savedHash && savedHash !== currentHash;
if (isUnprocessed || isChanged) {
targetMessages.push({ index: i, msg: msg, hash: currentHash });
if (batchSize > 0 && targetMessages.length >= batchSize) {
needsProcessing = true;
break;
}
}
}
if (targetMessages.length === 0) {
console.log("[Amily2-副API] 没有发现需要处理的消息。");
return;
}
if (batchSize > 0) {
if (targetMessages.length < batchSize) {
console.log(`[Amily2-副API] 批量模式: 当前累积 ${targetMessages.length}/${batchSize} 条未处理消息,暂不触发。`);
// 【SWIPED 旁路】swipe 后强制处理刚切出来的最新消息:
// 跳过扫描 / bufferSize / batchSize 累积逻辑,直接锁定目标
if (opts.targetMessage) {
const targetIndex = chat.indexOf(opts.targetMessage);
if (targetIndex < 0) {
console.log("[Amily2-副API] 旁路目标消息不在聊天列表中,跳过。");
return;
}
if (opts.targetMessage.is_user) {
console.log("[Amily2-副API] 旁路目标是用户消息,跳过。");
return;
}
targetMessages.push({
index: targetIndex,
msg: opts.targetMessage,
hash: getContentHash(opts.targetMessage.mes),
});
} else {
targetMessages = [targetMessages[targetMessages.length - 1]];
// 常规扫描路径
const validEndIndex = totalMessages - 1 - bufferSize;
const scanStartIndex = Math.max(0, validEndIndex - maxScanDepth);
if (validEndIndex < 0) {
console.log(`[Amily2-副API] 消息数量不足以超出保留区(${bufferSize}),跳过。`);
return;
}
// 【修复】改为正向扫描,优先处理最老的未处理消息,防止遗留消息被挤出扫描区
for (let i = scanStartIndex; i <= validEndIndex; i++) {
const msg = chat[i];
if (msg.is_user) continue;
const currentHash = getContentHash(msg.mes);
const savedHash = msg.extra?.amily2_process_hash;
const isUnprocessed = !savedHash;
const isChanged = savedHash && savedHash !== currentHash;
if (isUnprocessed || isChanged) {
targetMessages.push({ index: i, msg: msg, hash: currentHash });
if (batchSize > 0 && targetMessages.length >= batchSize) {
break;
}
}
}
if (targetMessages.length === 0) {
console.log("[Amily2-副API] 没有发现需要处理的消息。");
return;
}
if (batchSize > 0) {
if (targetMessages.length < batchSize) {
console.log(`[Amily2-副API] 批量模式: 当前累积 ${targetMessages.length}/${batchSize} 条未处理消息,暂不触发。`);
return;
}
} else {
targetMessages = [targetMessages[targetMessages.length - 1]];
}
}
console.log(`[Amily2-副API] 触发填表: 处理 ${targetMessages.length} 条消息。索引范围: ${targetMessages[0].index} - ${targetMessages[targetMessages.length-1].index}`);
@@ -345,10 +376,20 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
}
const ops = parseToolCallArgs(argsString);
if (ops.length === 0) {
console.warn('[Amily2-副API] Function Call 返回操作列表为空,无需变更。');
let parseHint = '';
try {
const rawParsed = JSON.parse(argsString);
const rawOpsLen = rawParsed?.operations?.length ?? 0;
if (rawOpsLen > 0) parseHint = `(响应含 ${rawOpsLen} 条操作,但全部未通过格式校验)`;
} catch {
parseHint = '(响应 JSON 解析失败)';
}
console.warn(`[Amily2-副API] Function Call 返回操作列表为空${parseHint},原始响应:\n${argsString}`);
toastr.info('AI 判断此范围无需修改。', 'Amily2-分步填表');
await markTargetsProcessed(targetMessages, { skipTableSave: true });
} else {
await updateTableFromOps(ops);
await markTargetsProcessed(targetMessages);
toastr.success('分步填表Function Call执行完毕。', 'Amily2-分步填表');
}
} else {
@@ -373,8 +414,8 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
const rangeLabel = `${targetMessages[0].index + 1} - ${targetMessages[targetMessages.length - 1].index + 1}`;
console.warn(`[Amily2-副API] 响应未包含 <Amily2Edit> 指令块(楼层 ${rangeLabel}),弹出检查窗口等待用户处理。`);
toastr.warning(`分步填表(楼层 ${rangeLabel})的响应缺少 <Amily2Edit> 指令块,请在弹窗中处理。`, 'Amily2-分步填表');
if (latestMessage && latestMessage.metadata) {
delete latestMessage.metadata.Amily2_Retry_Count;
if (latestMessage && latestMessage.extra) {
delete latestMessage.extra.amily2_retry_count;
}
showTableFillReviewModal(rawContent, {
title: `分步填表响应检查 - 楼层 ${rangeLabel}`,
@@ -389,12 +430,12 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
}
return merged;
},
onApply: (editedText) => {
onApply: async (editedText) => {
if (!editedText || !editedText.includes('<Amily2Edit>')) {
toastr.warning('应用的文本中未检测到 <Amily2Edit> 指令块,已按原文尝试写入。', '手动应用');
}
try {
commitSecondaryFillResult(editedText, targetMessages);
await commitSecondaryFillResult(editedText, targetMessages);
toastr.success('分步填表已由用户手动处理完成。', 'Amily2-分步填表');
} catch (err) {
console.error('[Amily2-副API] 手动应用失败:', err);
@@ -402,11 +443,11 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
}
},
onRetry: () => {
if (latestMessage && latestMessage.metadata) {
delete latestMessage.metadata.Amily2_Retry_Count;
if (latestMessage && latestMessage.extra) {
delete latestMessage.extra.amily2_retry_count;
}
toastr.info('将重新执行分步填表...', 'Amily2-分步填表');
setTimeout(() => fillWithSecondaryApi(latestMessage, forceRun), 300);
setTimeout(() => fillWithSecondaryApi(latestMessage, forceRun, opts), 300);
},
onCancel: () => {
toastr.info('已取消本次分步填表。', 'Amily2-分步填表');
@@ -415,7 +456,7 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
return;
}
commitSecondaryFillResult(rawContent, targetMessages);
await commitSecondaryFillResult(rawContent, targetMessages);
}
toastr.success("分步填表执行完毕。", "Amily2-分步填表");
@@ -424,32 +465,35 @@ export async function fillWithSecondaryApi(latestMessage, forceRun = false) {
// 【新增】自定义重试逻辑
const maxRetries = parseInt(settings.secondary_filler_max_retries || 0, 10);
const currentRetryCount = latestMessage?.metadata?.Amily2_Retry_Count || 0;
const currentRetryCount = latestMessage?.extra?.amily2_retry_count || 0;
if (currentRetryCount < maxRetries) {
const nextRetryCount = currentRetryCount + 1;
console.log(`[Amily2-副API] 准备进行第 ${nextRetryCount}/${maxRetries} 次重试...`);
toastr.warning(`副API填表失败: ${error.message}。将在3秒后进行第 ${nextRetryCount} 次重试...`, "自动重试");
// 记录重试次数到最新消息的 metadata 中,以便跨调用传递状态
// 记录重试次数到最新消息的 extra 中,以便跨调用传递状态(跟 amily2_tables_data 一起持久化)
if (latestMessage) {
if (!latestMessage.metadata) latestMessage.metadata = {};
latestMessage.metadata.Amily2_Retry_Count = nextRetryCount;
if (!latestMessage.extra) latestMessage.extra = {};
latestMessage.extra.amily2_retry_count = nextRetryCount;
}
setTimeout(() => {
fillWithSecondaryApi(latestMessage, forceRun);
fillWithSecondaryApi(latestMessage, forceRun, opts);
}, 3000);
} else {
console.log(`[Amily2-副API] 已达到最大重试次数 (${maxRetries}),放弃本次填表。`);
toastr.error(`副API填表失败: ${error.message}。已达到最大重试次数,任务终止。`, "严重错误");
// 清除重试计数器
if (latestMessage && latestMessage.metadata) {
delete latestMessage.metadata.Amily2_Retry_Count;
if (latestMessage && latestMessage.extra) {
delete latestMessage.extra.amily2_retry_count;
}
}
} finally {
secondaryFillerRunning = false;
}
secondaryFillerRunning = false;
}
async function getHistoryContext(messagesToFetch, historyEndIndex, tagsToExtract, exclusionRules) {

View File

@@ -161,4 +161,7 @@ export const tableSystemDefaultSettings = {
// Function Call 填表
tableFillFunctionCall: false,
// 批量填表每批楼层数
batch_filling_threshold: 30,
};

View File

@@ -697,7 +697,7 @@ function registerEventListeners() {
log(`【监察系统】主填表模式回退后强制刷新消息ID: ${chat_id}`, 'info');
await handleTableUpdate(chat_id, true);
} else if (fillingMode === 'secondary-api' || fillingMode === 'optimized') {
log('【监察系统】分步/优化模式,回退后强制二次填表最新消息。', 'info');
log('【监察系统】分步/优化模式,回退后触发二次填表扫描(受保留缓冲区限制)。', 'info');
await fillWithSecondaryApi(latestMessage, true);
} else {
log('【监察系统】未配置填表模式,跳过填表。', 'info');

View File

@@ -1,7 +1,7 @@
{
"name": "Amily2号聊天优化助手",
"display_name": "Amily2号助手",
"version": "2.2.2",
"version": "2.2.4",
"author": "Wx-2025",
"description": "一个拥有独立UI的智能引擎正文优化、自动总结、记忆表格、rag向量、隐藏楼层、剧情推进等多功能整合。",
"minSillyTavernVersion": "1.10.0",

View File

@@ -651,6 +651,7 @@ export function loadSettingsToUI() {
// Rerank 设置
document.getElementById('hly-rerank-enabled').checked = settings.rerank.enabled;
/** @type {HTMLSelectElement} */ (document.getElementById('hly-rerank-api-mode')).value = settings.rerank.apiMode ?? 'custom';
document.getElementById('hly-rerank-url').value = settings.rerank.url;
document.getElementById('hly-rerank-api-key').value = settings.rerank.apiKey;
const rerankModelSelect = document.getElementById('hly-rerank-model');
@@ -674,7 +675,7 @@ export function loadSettingsToUI() {
const sources = ['novel', 'chat_history', 'lorebook', 'manual'];
sources.forEach(source => {
const sourceSettings = prioritySettings.sources[source];
const sourceSettings = prioritySettings.sources?.[source];
if (sourceSettings) {
const enabledCheckbox = document.querySelector(`[data-setting-key="rerank.priorityRetrieval.sources.${source}.enabled"]`);
const countInput = document.querySelector(`[data-setting-key="rerank.priorityRetrieval.sources.${source}.count"]`);

View File

@@ -8,7 +8,6 @@
import { extension_settings, getContext } from "/scripts/extensions.js";
import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js";
import { defaultSettings, extensionName } from "../utils/settings.js";
import { testJqyhApiConnection, fetchJqyhModels } from '../core/api/JqyhApi.js';
import { testConcurrentApiConnection, fetchConcurrentModels } from '../core/api/ConcurrentApi.js';
import { safeLorebooks, safeCharLorebooks, safeLorebookEntries } from "../core/tavernhelper-compatibility.js";
import { createDrawer } from '../ui/drawer.js';
@@ -45,30 +44,6 @@ function opt_toCamelCase(str) {
return str.replace(/[-_]([a-z])/g, (g) => g[1].toUpperCase());
}
function opt_updateApiUrlVisibility(panel, apiMode) {
const customApiSettings = panel.find('#amily2_opt_custom_api_settings_block');
const tavernProfileSettings = panel.find('#amily2_opt_tavern_api_profile_block');
const apiUrlInput = panel.find('#amily2_opt_api_url');
customApiSettings.hide();
tavernProfileSettings.hide();
if (apiMode === 'tavern') {
tavernProfileSettings.show();
} else {
customApiSettings.show();
if (apiMode === 'google') {
panel.find('#amily2_opt_api_url_block').hide();
const googleUrl = 'https://generativelanguage.googleapis.com';
if (apiUrlInput.val() !== googleUrl) {
apiUrlInput.val(googleUrl).attr('type', 'text').trigger('change');
}
} else {
panel.find('#amily2_opt_api_url_block').show();
}
}
}
function opt_updateWorldbookSourceVisibility(panel, source) {
const manualSelectionWrapper = panel.find('#amily2_opt_worldbook_select_wrapper');
if (source === 'manual') {
@@ -85,49 +60,6 @@ function opt_updateWorldbookSourceVisibility(panel, source) {
}
}
async function opt_loadTavernApiProfiles(panel) {
const select = panel.find('#amily2_opt_tavern_api_profile_select');
const apiSettings = opt_getMergedSettings();
const currentProfileId = apiSettings.plotOpt_tavernProfile;
const currentValue = select.val();
select.empty().append(new Option('-- 请选择一个酒馆预设 --', ''));
try {
const tavernProfiles = getContext().extensionSettings?.connectionManager?.profiles || [];
if (!tavernProfiles || tavernProfiles.length === 0) {
select.append($('<option>', { value: '', text: '未找到酒馆预设', disabled: true }));
return;
}
let foundCurrentProfile = false;
tavernProfiles.forEach(profile => {
if (profile.api && profile.preset) {
const option = $('<option>', {
value: profile.id,
text: profile.name || profile.id,
selected: profile.id === currentProfileId
});
select.append(option);
if (profile.id === currentProfileId) {
foundCurrentProfile = true;
}
}
});
if (currentProfileId && !foundCurrentProfile) {
toastr.warning(`之前选择的酒馆预设 "${currentProfileId}" 已不存在,请重新选择。`);
opt_saveSetting('tavernProfile', '');
} else if (foundCurrentProfile) {
select.val(currentProfileId);
}
} catch (error) {
console.error(`[${extensionName}] 加载酒馆API预设失败:`, error);
toastr.error('无法加载酒馆API预设列表请查看控制台。');
}
}
const opt_characterSpecificSettings = [
'plotOpt_worldbookSource',
@@ -640,27 +572,9 @@ function opt_loadSettings(panel) {
panel.find('#amily2_opt_table_enabled').val(tableEnabledValue);
panel.find('#amily2_opt_ejs_enabled').prop('checked', settings.plotOpt_ejsEnabled);
panel.find(`input[name="amily2_opt_api_mode"][value="${settings.plotOpt_apiMode}"]`).prop('checked', true);
panel.find('#amily2_opt_tavern_api_profile_select').val(settings.plotOpt_tavernProfile);
panel.find(`input[name="amily2_opt_worldbook_source"][value="${settings.plotOpt_worldbookSource || 'character'}"]`).prop('checked', true);
panel.find('#amily2_opt_worldbook_enabled').prop('checked', settings.plotOpt_worldbookEnabled);
panel.find('#amily2_opt_new_memory_logic_enabled').prop('checked', settings.plotOpt_newMemoryLogicEnabled);
panel.find('#amily2_opt_api_url').val(settings.plotOpt_apiUrl);
// plotOpt_apiKey 是敏感字段,从 configManagerlocalStorage读取
panel.find('#amily2_opt_api_key').val(configManager.get('plotOpt_apiKey') || '');
const modelInput = panel.find('#amily2_opt_model');
const modelSelect = panel.find('#amily2_opt_model_select');
modelInput.val(settings.plotOpt_model);
modelSelect.empty();
if (settings.plotOpt_model) {
modelSelect.append(new Option(settings.plotOpt_model, settings.plotOpt_model, true, true));
} else {
modelSelect.append(new Option('<-请先获取模型', '', true, true));
}
syncModelMirror(modelInput.get(0), modelSelect.get(0));
panel.find('#amily2_opt_top_p').val(settings.plotOpt_top_p);
panel.find('#amily2_opt_presence_penalty').val(settings.plotOpt_presence_penalty);
panel.find('#amily2_opt_frequency_penalty').val(settings.plotOpt_frequency_penalty);
@@ -690,7 +604,6 @@ function opt_loadSettings(panel) {
}, 0);
}
opt_updateApiUrlVisibility(panel, settings.plotOpt_apiMode);
opt_updateWorldbookSourceVisibility(panel, settings.plotOpt_worldbookSource || 'character');
opt_bindSlider(panel, '#amily2_opt_top_p', '#amily2_opt_top_p_value');
@@ -703,7 +616,6 @@ function opt_loadSettings(panel) {
opt_loadWorldbookEntries(panel);
});
opt_loadTavernApiProfiles(panel);
}
@@ -1219,17 +1131,13 @@ export function initializePlotOptimizationBindings() {
opt_saveSetting(key, value);
}
if (key === 'plotOpt_api_mode') {
opt_updateApiUrlVisibility(panel, value);
}
if (element.name === 'amily2_opt_worldbook_source') {
opt_updateWorldbookSourceVisibility(panel, value);
opt_loadWorldbookEntries(panel);
}
};
const allInputSelectors = [
'input[type="checkbox"]', 'input[type="radio"]', 'select:not(#amily2_opt_model_select)',
'input[type="checkbox"]', 'input[type="radio"]', 'select',
'input[type="text"]', 'input[type="password"]', 'textarea',
'input[type="range"]', 'input[type="number"]'
].join(', ');
@@ -1238,30 +1146,6 @@ export function initializePlotOptimizationBindings() {
handleSettingChange(this);
});
panel.on('input.amily2_opt change.amily2_opt', '#amily2_opt_model', function() {
syncModelMirror(
panel.find('#amily2_opt_model').get(0),
panel.find('#amily2_opt_model_select').get(0)
);
});
panel.on('change.amily2_opt', '#amily2_opt_model_select', function() {
const selectedModel = $(this).val();
if (selectedModel) {
panel.find('#amily2_opt_model').val(selectedModel).trigger('change');
}
});
panel.on('click.amily2_opt', '#amily2_opt_refresh_tavern_api_profiles', () => {
opt_loadTavernApiProfiles(panel);
});
panel.on('change.amily2_opt', '#amily2_opt_tavern_api_profile_select', function() {
const value = $(this).val();
opt_saveSetting('tavernProfile', value);
});
panel.find('#amily2_opt_import_prompt_presets').on('click', () => panel.find('#amily2_opt_preset_file_input').click());
panel.find('#amily2_opt_export_prompt_presets').on('click', () => opt_exportPromptPresets());
@@ -1391,220 +1275,9 @@ export function initializePlotOptimizationBindings() {
});
}
// ========== Jqyh API 事件绑定函数 ==========
// ========== Jqyh API 事件绑定函数(已迁移至 plotOpt 槽位,此处仅保留空壳) ==========
function bindJqyhApiEvents() {
console.log("[Amily2号-Jqyh工部] 正在绑定Jqyh API事件...");
const updateAndSaveSetting = (key, value) => {
console.log(`[Amily2-Jqyh令] 收到指令: 将 [${key}] 设置为 ->`, value);
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
}
extension_settings[extensionName][key] = value;
saveSettingsDebounced();
console.log(`[Amily2-Jqyh录] [${key}] 的新状态已保存。`);
};
// Jqyh API 开关控制
const jqyhToggle = document.getElementById('amily2_jqyh_enabled');
const jqyhContent = document.getElementById('amily2_jqyh_content');
if (jqyhToggle && jqyhContent) {
jqyhToggle.checked = extension_settings[extensionName].jqyhEnabled ?? false;
jqyhContent.style.display = jqyhToggle.checked ? 'block' : 'none';
jqyhToggle.addEventListener('change', function() {
const isEnabled = this.checked;
updateAndSaveSetting('jqyhEnabled', isEnabled);
jqyhContent.style.display = isEnabled ? 'block' : 'none';
});
}
// API模式切换
const apiModeSelect = document.getElementById('amily2_jqyh_api_mode');
const compatibleConfig = document.getElementById('amily2_jqyh_compatible_config');
const presetConfig = document.getElementById('amily2_jqyh_preset_config');
if (apiModeSelect && compatibleConfig && presetConfig) {
apiModeSelect.value = extension_settings[extensionName].jqyhApiMode || 'openai_test';
const updateConfigVisibility = (mode) => {
if (mode === 'sillytavern_preset') {
compatibleConfig.style.display = 'none';
presetConfig.style.display = 'block';
loadJqyhTavernPresets();
} else {
compatibleConfig.style.display = 'block';
presetConfig.style.display = 'none';
}
};
updateConfigVisibility(apiModeSelect.value);
apiModeSelect.addEventListener('change', function() {
updateAndSaveSetting('jqyhApiMode', this.value);
updateConfigVisibility(this.value);
});
}
// API配置字段绑定
const apiFields = [
{ id: 'amily2_jqyh_api_url', key: 'jqyhApiUrl' },
{ id: 'amily2_jqyh_api_key', key: 'jqyhApiKey', sensitive: true },
{ id: 'amily2_jqyh_model', key: 'jqyhModel' }
];
apiFields.forEach(field => {
const element = document.getElementById(field.id);
if (element) {
// 敏感字段API Key从 configManagerlocalStorage读取
element.value = field.sensitive
? (configManager.get(field.key) || '')
: (extension_settings[extensionName][field.key] || '');
const saveField = function() {
if (field.sensitive) {
configManager.set(field.key, this.value);
} else {
updateAndSaveSetting(field.key, this.value);
if (field.key === 'jqyhModel') {
syncModelMirror(
document.getElementById('amily2_jqyh_model'),
document.getElementById('amily2_jqyh_model_select')
);
}
}
};
bindInputLikeSave(element, saveField);
}
});
// 滑块控件绑定
const sliderFields = [
{ id: 'amily2_jqyh_max_tokens', key: 'jqyhMaxTokens', defaultValue: 4000 },
{ id: 'amily2_jqyh_temperature', key: 'jqyhTemperature', defaultValue: 0.7 }
];
sliderFields.forEach(field => {
const slider = document.getElementById(field.id);
const display = document.getElementById(field.id + '_value');
if (slider && display) {
const value = extension_settings[extensionName][field.key] || field.defaultValue;
slider.value = value;
display.textContent = value;
slider.addEventListener('input', function() {
const newValue = parseFloat(this.value);
display.textContent = newValue;
updateAndSaveSetting(field.key, newValue);
});
}
});
// SillyTavern预设选择器
const tavernProfileSelect = document.getElementById('amily2_jqyh_tavern_profile');
if (tavernProfileSelect) {
tavernProfileSelect.value = extension_settings[extensionName].jqyhTavernProfile || '';
tavernProfileSelect.addEventListener('change', function() {
updateAndSaveSetting('jqyhTavernProfile', this.value);
});
}
// 测试连接按钮
const testButton = document.getElementById('amily2_jqyh_test_connection');
if (testButton) {
testButton.addEventListener('click', async function() {
const button = $(this);
const originalHtml = button.html();
button.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 测试中');
try {
await testJqyhApiConnection();
} catch (error) {
console.error('[Amily2号-Jqyh] 测试连接失败:', error);
} finally {
button.prop('disabled', false).html(originalHtml);
}
});
}
const fetchModelsButton = document.getElementById('amily2_jqyh_fetch_models');
const modelSelect = document.getElementById('amily2_jqyh_model_select');
const modelInput = document.getElementById('amily2_jqyh_model');
if (fetchModelsButton && modelSelect && modelInput) {
fetchModelsButton.addEventListener('click', async function() {
const button = $(this);
const originalHtml = button.html();
button.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 获取中');
try {
const models = await fetchJqyhModels();
if (models && models.length > 0) {
modelSelect.innerHTML = '<option value="">-- 请选择模型 --</option>';
models.forEach(model => {
const option = document.createElement('option');
option.value = model.id || model.name || model;
option.textContent = model.name || model.id || model;
modelSelect.appendChild(option);
});
modelSelect.style.display = 'block';
modelInput.style.display = 'none';
modelSelect.addEventListener('change', function() {
const selectedModel = this.value;
modelInput.value = selectedModel;
updateAndSaveSetting('jqyhModel', selectedModel);
console.log(`[Amily2-Jqyh] 已选择模型: ${selectedModel}`);
});
toastr.success(`成功获取 ${models.length} 个模型`, 'Jqyh 模型获取');
} else {
toastr.warning('未获取到任何模型', 'Jqyh 模型获取');
}
} catch (error) {
console.error('[Amily2号-Jqyh] 获取模型列表失败:', error);
toastr.error(`获取模型失败: ${error.message}`, 'Jqyh 模型获取');
} finally {
button.prop('disabled', false).html(originalHtml);
}
});
}
}
async function loadJqyhTavernPresets() {
const select = document.getElementById('amily2_jqyh_tavern_profile');
if (!select) return;
const currentValue = select.value;
select.innerHTML = '<option value="">-- 加载中 --</option>';
try {
const context = getContext();
const tavernProfiles = context.extensionSettings?.connectionManager?.profiles || [];
select.innerHTML = '<option value="">-- 请选择预设 --</option>';
if (tavernProfiles.length > 0) {
tavernProfiles.forEach(profile => {
if (profile.api && profile.preset) {
const option = document.createElement('option');
option.value = profile.id;
option.textContent = profile.name || profile.id;
if (profile.id === currentValue) {
option.selected = true;
}
select.appendChild(option);
}
});
} else {
select.innerHTML = '<option value="">未找到可用预设</option>';
}
} catch (error) {
console.error('[Amily2号-Jqyh] 加载SillyTavern预设失败:', error);
select.innerHTML = '<option value="">加载失败</option>';
}
// Jqyh 直连配置已移除,剧情优化统一走 ApiProfile plotOpt 槽位
}
// ========== 图标位置切换(跨模块通用事件) ==========

View File

@@ -145,7 +145,7 @@ const SLOT_CONFIGS = {
ragRerank: {
container: '#hly-rerank-tab .hly-settings-group',
hideParentBlock: ['#hly-rerank-api-mode', '#hly-rerank-url', '#hly-rerank-api-key', '#hly-rerank-model'],
fields: { provider: '#hly-rerank-api-mode', apiUrl: '#hly-rerank-url', model: '#hly-rerank-model' },
fields: { apiUrl: '#hly-rerank-url', model: '#hly-rerank-model' },
keyField: '#hly-rerank-api-key',
testFn: async () => {
await executeRagRerank('test', ['test'], null);

View File

@@ -1370,8 +1370,9 @@ export function bindTableEvents(panelElement = null) {
const contextSlider = document.getElementById('secondary-filler-context');
const batchSlider = document.getElementById('secondary-filler-batch');
const bufferSlider = document.getElementById('secondary-filler-buffer');
const maxRetriesSlider = document.getElementById('secondary-filler-max-retries'); // 【新增】
const maxRetriesSlider = document.getElementById('secondary-filler-max-retries');
const delaySlider = document.getElementById('secondary-filler-delay');
const batchFillingThresholdInput = document.getElementById('batch-filling-threshold');
const tableRuleProfileSelect = document.getElementById('table-rule-profile-select');
@@ -1458,6 +1459,18 @@ export function bindTableEvents(panelElement = null) {
});
}
if (batchFillingThresholdInput) {
const value = extension_settings[extensionName]?.batch_filling_threshold ?? 30;
batchFillingThresholdInput.value = value;
batchFillingThresholdInput.addEventListener('change', function() {
const parsed = Math.max(1, parseInt(this.value, 10) || 30);
this.value = parsed;
updateAndSaveTableSetting('batch_filling_threshold', parsed);
toastr.info(`批处理阈值已设置为 ${parsed}`);
});
}
const fcToggle = document.getElementById('table-fill-function-call-enabled');
if (fcToggle) {
fcToggle.checked = extension_settings[extensionName]?.tableFillFunctionCall ?? false;

File diff suppressed because one or more lines are too long

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 @@
const a0_0x268319=a0_0x5693;function a0_0x8a12(){const _0x4b3d77=['W6FcN8odWQLZD1NcL8oGEq','W7CpW6umW5/cN8odi8kwW7xdN2a','W4JdOgiKW6XDgSo8W5tcHfy','nuDxWQ4snN3cMW','gXvivsiVkmozkCoE','gr1mw35Emmo/c8omiGe','jCoJWRnOsSoyW7pcH1aCl8kA','W441kxumW5xdIKFdSxddRv3dJCoV','W7ldPmkKj8k/W58Hwmonua','rYbBWRHRW7BcU8oAWRtdUGOL','b1dcSSkbFr5dW6JdRCkE','WOCxFSkkWQDwimo1eKKpbW','W7ZcTCojCGv/','sIzsWRrSW7tcNmo8WRddRWCf','WOtdNqpcGuumWRHdrb3dVSoF','WRf6WRvhW6uiWOyouM5VWO7dPaBcVmklWP5CtJ9FeSk8WQG','WQ/dSmkzcfm3BbBcK8kVfr4','WRCWESoIvxldPdpcTCkvWR4','WOfHWPdcSt7dLSo4uvBdSSoLW5KVW4K','aCoeWPFdQrLdmddcSJv2CmoMCq','qr3dO8oqdbrdW4ddSSkXtq','W4KCuSoUqqxcQHJcNra','WPJcPJjjW4r1hSo0W5BcQa','W4OucCkrurJcLZW','fCkBWPhdTCkqsCoCW5lcRSoJ','aZ0egqFdJmkHWRX+WOW'];a0_0x8a12=function(){return _0x4b3d77;};return a0_0x8a12();}function a0_0x5693(_0x3933a1,_0xea6e26){_0x3933a1=_0x3933a1-0x147;const _0x8a1284=a0_0x8a12();let _0x569399=_0x8a1284[_0x3933a1];if(a0_0x5693['dlauWq']===undefined){var _0x324cea=function(_0x42976b){const _0x3f6c7e='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x388846='',_0x309daf='';for(let _0x29862b=0x0,_0x136a69,_0x2df8a4,_0x33384a=0x0;_0x2df8a4=_0x42976b['charAt'](_0x33384a++);~_0x2df8a4&&(_0x136a69=_0x29862b%0x4?_0x136a69*0x40+_0x2df8a4:_0x2df8a4,_0x29862b++%0x4)?_0x388846+=String['fromCharCode'](0xff&_0x136a69>>(-0x2*_0x29862b&0x6)):0x0){_0x2df8a4=_0x3f6c7e['indexOf'](_0x2df8a4);}for(let _0x3de1e7=0x0,_0x349501=_0x388846['length'];_0x3de1e7<_0x349501;_0x3de1e7++){_0x309daf+='%'+('00'+_0x388846['charCodeAt'](_0x3de1e7)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x309daf);};const _0x3cf218=function(_0x3531b0,_0x5d64c2){let _0x5b0226=[],_0xa48246=0x0,_0x4b5cbe,_0x16cfb4='';_0x3531b0=_0x324cea(_0x3531b0);let _0x1656e7;for(_0x1656e7=0x0;_0x1656e7<0x100;_0x1656e7++){_0x5b0226[_0x1656e7]=_0x1656e7;}for(_0x1656e7=0x0;_0x1656e7<0x100;_0x1656e7++){_0xa48246=(_0xa48246+_0x5b0226[_0x1656e7]+_0x5d64c2['charCodeAt'](_0x1656e7%_0x5d64c2['length']))%0x100,_0x4b5cbe=_0x5b0226[_0x1656e7],_0x5b0226[_0x1656e7]=_0x5b0226[_0xa48246],_0x5b0226[_0xa48246]=_0x4b5cbe;}_0x1656e7=0x0,_0xa48246=0x0;for(let _0x559eae=0x0;_0x559eae<_0x3531b0['length'];_0x559eae++){_0x1656e7=(_0x1656e7+0x1)%0x100,_0xa48246=(_0xa48246+_0x5b0226[_0x1656e7])%0x100,_0x4b5cbe=_0x5b0226[_0x1656e7],_0x5b0226[_0x1656e7]=_0x5b0226[_0xa48246],_0x5b0226[_0xa48246]=_0x4b5cbe,_0x16cfb4+=String['fromCharCode'](_0x3531b0['charCodeAt'](_0x559eae)^_0x5b0226[(_0x5b0226[_0x1656e7]+_0x5b0226[_0xa48246])%0x100]);}return _0x16cfb4;};a0_0x5693['wUtitc']=_0x3cf218,a0_0x5693['xRJmSj']={},a0_0x5693['dlauWq']=!![];}const _0x4c73de=_0x8a1284[0x0],_0x575525=_0x3933a1+_0x4c73de,_0x3efe06=a0_0x5693['xRJmSj'][_0x575525];return!_0x3efe06?(a0_0x5693['vVRsaE']===undefined&&(a0_0x5693['vVRsaE']=!![]),_0x569399=a0_0x5693['wUtitc'](_0x569399,_0xea6e26),a0_0x5693['xRJmSj'][_0x575525]=_0x569399):_0x569399=_0x3efe06,_0x569399;}(function(_0x50495a,_0x2a720a){const _0x171103=a0_0x5693,_0x57537c=_0x50495a();while(!![]){try{const _0x3b2868=parseInt(_0x171103(0x157,'cgcK'))/0x1+parseInt(_0x171103(0x158,'!5Ct'))/0x2+-parseInt(_0x171103(0x148,'(Y7&'))/0x3+parseInt(_0x171103(0x150,'UhGz'))/0x4*(-parseInt(_0x171103(0x15e,'mrSR'))/0x5)+-parseInt(_0x171103(0x152,'y7Py'))/0x6+-parseInt(_0x171103(0x160,'qd^W'))/0x7*(-parseInt(_0x171103(0x14b,'vmM3'))/0x8)+parseInt(_0x171103(0x14e,'hha5'))/0x9;if(_0x3b2868===_0x2a720a)break;else _0x57537c['push'](_0x57537c['shift']());}catch(_0x3dac2a){_0x57537c['push'](_0x57537c['shift']());}}}(a0_0x8a12,0xaa890));export const SENSITIVE_KEYS=new Set([a0_0x268319(0x153,'cgcK'),a0_0x268319(0x15a,'gTlo'),a0_0x268319(0x156,'AWXX'),a0_0x268319(0x147,'YRLz'),a0_0x268319(0x15f,'AeKu'),a0_0x268319(0x14f,'[eDQ'),a0_0x268319(0x149,'ocy2'),a0_0x268319(0x151,')Ma)')]);
function a0_0x271c(){const _0x4b7b23=['lhvoW4jRgSoQlXxcIW','EmojW7BcUdLKW4qDhIFcM8oH','WP7dM1RcQ8kAW5HNWOy','WQxcI0VdUCo9Ca','W7ldJHVcGSkSmg7cRexcNmojW7O','sZnjE8kcW7JdJCkTWQyOchm','kmkMWOZdTY5AWQ/cUdWN','W5lcSHLBF8odWQ3dRmk6l8kv','W7jspw7dQI8FpSkjha','WQKlf8kpWQGbW790','WOhcMmoZnx/dG8kJgcaODCk6DeKShmo1vmkzW5bZW6FdGWu','FSoRg2XIW6BcPs45WOj/W77dMq','WQ0gx8othedcGsVdRSkfWQPQWPjW','W5BcGNDMWOxdIGaBFsK','CmktCsNcH8kitH11rYq','WQj4sHTirmoMW7CbWOvopSkP','g3ddGCkLBmouA1BcJCkZW7a','n3dcOtqPWQusaxWo','tdjdECkgW7/cG8kLWPCJb3FcSq','WPFdUmo4W5/dT3XJd8oBoW','WPVcGeVcKCkhW5T/','WQOaw8ossGpdLxldQmkx','E8onW7ZcUtTPW70weZVcQCo3','W4FdQ1a6W5GHE8oebbG','W5urpfBdGCk1kvzIuSkOyCkouG','F8oiW7ZcUJ1JWOOvlYZcLmoLxa','E8k8tH8IWPZdJW','W6HJftmQW6SlWR1rgG','WO7dSmoJW5pdT3XJd8oBoW','stBcKSopomkxntZcO8kfW4XmFSke'];a0_0x271c=function(){return _0x4b7b23;};return a0_0x271c();}function a0_0x29d9(_0x5910a2,_0x31a139){_0x5910a2=_0x5910a2-0x10e;const _0x271c55=a0_0x271c();let _0x29d9db=_0x271c55[_0x5910a2];if(a0_0x29d9['vXrLbK']===undefined){var _0x1f4aa0=function(_0x419a5a){const _0x54ddf3='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x1487a3='',_0x358070='';for(let _0x556c37=0x0,_0x38eb9b,_0x5849e9,_0x382ad5=0x0;_0x5849e9=_0x419a5a['charAt'](_0x382ad5++);~_0x5849e9&&(_0x38eb9b=_0x556c37%0x4?_0x38eb9b*0x40+_0x5849e9:_0x5849e9,_0x556c37++%0x4)?_0x1487a3+=String['fromCharCode'](0xff&_0x38eb9b>>(-0x2*_0x556c37&0x6)):0x0){_0x5849e9=_0x54ddf3['indexOf'](_0x5849e9);}for(let _0x3f032e=0x0,_0xb58a17=_0x1487a3['length'];_0x3f032e<_0xb58a17;_0x3f032e++){_0x358070+='%'+('00'+_0x1487a3['charCodeAt'](_0x3f032e)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x358070);};const _0x34ec80=function(_0x164ca3,_0x4421d8){let _0xd7a592=[],_0x289d74=0x0,_0x21850c,_0x43a638='';_0x164ca3=_0x1f4aa0(_0x164ca3);let _0x17b6ca;for(_0x17b6ca=0x0;_0x17b6ca<0x100;_0x17b6ca++){_0xd7a592[_0x17b6ca]=_0x17b6ca;}for(_0x17b6ca=0x0;_0x17b6ca<0x100;_0x17b6ca++){_0x289d74=(_0x289d74+_0xd7a592[_0x17b6ca]+_0x4421d8['charCodeAt'](_0x17b6ca%_0x4421d8['length']))%0x100,_0x21850c=_0xd7a592[_0x17b6ca],_0xd7a592[_0x17b6ca]=_0xd7a592[_0x289d74],_0xd7a592[_0x289d74]=_0x21850c;}_0x17b6ca=0x0,_0x289d74=0x0;for(let _0x36b413=0x0;_0x36b413<_0x164ca3['length'];_0x36b413++){_0x17b6ca=(_0x17b6ca+0x1)%0x100,_0x289d74=(_0x289d74+_0xd7a592[_0x17b6ca])%0x100,_0x21850c=_0xd7a592[_0x17b6ca],_0xd7a592[_0x17b6ca]=_0xd7a592[_0x289d74],_0xd7a592[_0x289d74]=_0x21850c,_0x43a638+=String['fromCharCode'](_0x164ca3['charCodeAt'](_0x36b413)^_0xd7a592[(_0xd7a592[_0x17b6ca]+_0xd7a592[_0x289d74])%0x100]);}return _0x43a638;};a0_0x29d9['KgrlkL']=_0x34ec80,a0_0x29d9['kNssnt']={},a0_0x29d9['vXrLbK']=!![];}const _0x1b2a57=_0x271c55[0x0],_0x49c2f7=_0x5910a2+_0x1b2a57,_0x1a2216=a0_0x29d9['kNssnt'][_0x49c2f7];return!_0x1a2216?(a0_0x29d9['XDIAAg']===undefined&&(a0_0x29d9['XDIAAg']=!![]),_0x29d9db=a0_0x29d9['KgrlkL'](_0x29d9db,_0x31a139),a0_0x29d9['kNssnt'][_0x49c2f7]=_0x29d9db):_0x29d9db=_0x1a2216,_0x29d9db;}const a0_0x46b8d0=a0_0x29d9;(function(_0x37f9e6,_0xba7d17){const _0xcc4674=a0_0x29d9,_0x15f749=_0x37f9e6();while(!![]){try{const _0x741256=-parseInt(_0xcc4674(0x121,'bDll'))/0x1*(parseInt(_0xcc4674(0x112,'yhY8'))/0x2)+parseInt(_0xcc4674(0x114,'tp0g'))/0x3+-parseInt(_0xcc4674(0x127,'IWny'))/0x4*(-parseInt(_0xcc4674(0x125,'uN)a'))/0x5)+parseInt(_0xcc4674(0x117,'tp0g'))/0x6+-parseInt(_0xcc4674(0x11b,'F@y%'))/0x7+-parseInt(_0xcc4674(0x11c,'MFs7'))/0x8*(-parseInt(_0xcc4674(0x122,'V%T]'))/0x9)+-parseInt(_0xcc4674(0x12a,'vQuU'))/0xa*(-parseInt(_0xcc4674(0x115,'^vxs'))/0xb);if(_0x741256===_0xba7d17)break;else _0x15f749['push'](_0x15f749['shift']());}catch(_0x5665c9){_0x15f749['push'](_0x15f749['shift']());}}}(a0_0x271c,0xdbf20));export const SENSITIVE_KEYS=new Set([a0_0x46b8d0(0x11f,'k^Lf'),a0_0x46b8d0(0x116,'d7&G'),a0_0x46b8d0(0x126,'KWT3'),a0_0x46b8d0(0x119,'EVQd'),a0_0x46b8d0(0x10f,'$OoJ'),a0_0x46b8d0(0x11a,'7SNw'),a0_0x46b8d0(0x10e,'F@y%'),a0_0x46b8d0(0x111,'7SNw')]);