mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 12:45:51 +00:00
Compare commits
3 Commits
398649c754
...
3ba4e39193
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ba4e39193 | |||
| 8f2b91c36c | |||
| 56017782bb |
@@ -114,13 +114,49 @@ export default class ModelCaller {
|
|||||||
// 难点:ST 的 ConnectionManagerRequestService 不暴露流。
|
// 难点:ST 的 ConnectionManagerRequestService 不暴露流。
|
||||||
// 策略:切换 Profile 后,手动向生成接口发送请求。
|
// 策略:切换 Profile 后,手动向生成接口发送请求。
|
||||||
const url = '/api/backends/chat-completions/generate';
|
const url = '/api/backends/chat-completions/generate';
|
||||||
// Preset 模式下只需要最小载荷
|
|
||||||
const payload = requestBody.toMinimalPayload();
|
// [修复]: 手动合并 Profile 中的关键参数,否则后端不会自动应用预设配置
|
||||||
|
// 我们需要模拟 ST 前端发送请求时的行为,把预设参数填进去
|
||||||
|
const profilePayload = {
|
||||||
|
// 基础模型参数
|
||||||
|
model: targetProfile.openai_model || targetProfile.model,
|
||||||
|
temperature: targetProfile.temperature,
|
||||||
|
frequency_penalty: targetProfile.frequency_penalty,
|
||||||
|
presence_penalty: targetProfile.presence_penalty,
|
||||||
|
top_p: targetProfile.top_p,
|
||||||
|
top_k: targetProfile.top_k,
|
||||||
|
min_p: targetProfile.min_p,
|
||||||
|
repetition_penalty: targetProfile.repetition_penalty,
|
||||||
|
|
||||||
|
// 关键:OpenAI 源标记
|
||||||
|
chat_completion_source: targetProfile.chat_completion_source || 'openai',
|
||||||
|
|
||||||
|
// 代理设置 (如果预设里有)
|
||||||
|
reverse_proxy: targetProfile.reverse_proxy,
|
||||||
|
proxy_password: targetProfile.proxy_password,
|
||||||
|
|
||||||
|
// 其他可能影响生成的参数
|
||||||
|
custom_prompt_post_processing: targetProfile.custom_prompt_post_processing ?? 'strict',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 合并顺序:基础Payload(msg) < Profile预设 < 显式Params覆盖
|
||||||
|
// toMinimalPayload 包含: messages, stream, max_tokens, ...params
|
||||||
|
// 我们需要把 profilePayload 塞在中间,被 params 覆盖
|
||||||
|
const minimal = requestBody.toMinimalPayload();
|
||||||
|
|
||||||
|
// 剔除 minimal 中可能已经存在的 undefined 属性,避免覆盖 profile 的有效值
|
||||||
|
// 但实际上 minimal 中的 ...params 是用户强指定的,应该覆盖 profile
|
||||||
|
|
||||||
|
const finalPayload = {
|
||||||
|
...profilePayload,
|
||||||
|
...minimal, // 包含 messages, stream, max_tokens
|
||||||
|
...options.params // 再次确保显式参数优先级最高 (minimal里其实已经含了,这里双保险)
|
||||||
|
};
|
||||||
|
|
||||||
const fetchOpts = {
|
const fetchOpts = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...getRequestHeaders(), ...this.defaultHeaders },
|
headers: { ...getRequestHeaders(), ...this.defaultHeaders },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(finalPayload)
|
||||||
};
|
};
|
||||||
return await this._fetchFakeStream(url, fetchOpts);
|
return await this._fetchFakeStream(url, fetchOpts);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
4
TODO.md
4
TODO.md
@@ -13,6 +13,10 @@
|
|||||||
|
|
||||||
以下为待开发内容
|
以下为待开发内容
|
||||||
|
|
||||||
|
- **项目框架重构 (Project Refactoring)**:
|
||||||
|
- 现状:大量功能模块(如 `NccsApi.js`)存在手动组装参数、逻辑耦合度高、代码风格不统一("能跑就行"遗留债)等问题。
|
||||||
|
- 目标:系统性重构项目架构,统一使用 Builder 模式(如 `Options.builder`),解耦业务逻辑与配置管理,提升代码可维护性和优雅度。
|
||||||
|
|
||||||
## 未修复
|
## 未修复
|
||||||
|
|
||||||
以下为示例(预计三个版本后移除)
|
以下为示例(预计三个版本后移除)
|
||||||
|
|||||||
@@ -333,13 +333,12 @@
|
|||||||
|
|
||||||
<div class="amily2_opt_settings_block" style="margin-bottom: 10px;">
|
<div class="amily2_opt_settings_block" style="margin-bottom: 10px;">
|
||||||
<label for="nccs-temperature">Temperature: <span id="nccs-temperature-value">0.7</span></label>
|
<label for="nccs-temperature">Temperature: <span id="nccs-temperature-value">0.7</span></label>
|
||||||
<input type="checkbox" id="nccs-api-fakestream-enabled" data-setting-key="nccsFakeStreamEnabled"
|
<input type="range" id="nccs-temperature" min="0" max="2" step="0.1" value="0.7">
|
||||||
data-type="boolean">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="amily2_opt_settings_block" style="margin-bottom: 10px;">
|
<div class="amily2_opt_settings_block" style="margin-bottom: 10px;">
|
||||||
<label for="nccs-temperature">Temperature: <span id="nccs-temperature-value">0.7</span></label>
|
<label for="nccs-api-fakestream-enabled">启用流式支持: </label>
|
||||||
<input type="range" id="nccs-temperature" min="0" max="2" step="0.1" value="0.7">
|
<input type="checkbox" id="nccs-api-fakestream-enabled" data-setting-key="nccsFakeStreamEnabled" data-type="boolean">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="amily2_opt_settings_block" style="margin-bottom: 10px;">
|
<div class="amily2_opt_settings_block" style="margin-bottom: 10px;">
|
||||||
|
|||||||
@@ -38,13 +38,15 @@ if (window.Amily2Bus) {
|
|||||||
|
|
||||||
export function getNccsApiSettings() {
|
export function getNccsApiSettings() {
|
||||||
return {
|
return {
|
||||||
|
nccsEnabled: extension_settings[extensionName]?.nccsEnabled || false,
|
||||||
apiMode: extension_settings[extensionName]?.nccsApiMode || 'openai_test',
|
apiMode: extension_settings[extensionName]?.nccsApiMode || 'openai_test',
|
||||||
apiUrl: extension_settings[extensionName]?.nccsApiUrl?.trim() || '',
|
apiUrl: extension_settings[extensionName]?.nccsApiUrl?.trim() || '',
|
||||||
apiKey: extension_settings[extensionName]?.nccsApiKey?.trim() || '',
|
apiKey: extension_settings[extensionName]?.nccsApiKey?.trim() || '',
|
||||||
model: extension_settings[extensionName]?.nccsModel || '',
|
model: extension_settings[extensionName]?.nccsModel || '',
|
||||||
maxTokens: extension_settings[extensionName]?.nccsMaxTokens || 4000,
|
maxTokens: extension_settings[extensionName]?.nccsMaxTokens || 4000,
|
||||||
temperature: extension_settings[extensionName]?.nccsTemperature || 0.7,
|
temperature: extension_settings[extensionName]?.nccsTemperature || 0.7,
|
||||||
tavernProfile: extension_settings[extensionName]?.nccsTavernProfile || ''
|
tavernProfile: extension_settings[extensionName]?.nccsTavernProfile || '',
|
||||||
|
useFakeStream: extension_settings[extensionName]?.nccsFakeStreamEnabled || false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,43 +60,69 @@ export async function callNccsAI(messages, options = {}) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiSettings = getNccsApiSettings();
|
const settings = getNccsApiSettings();
|
||||||
const finalOptions = {
|
|
||||||
...apiSettings,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
|
|
||||||
if (finalOptions.apiMode !== 'sillytavern_preset') {
|
// 0. 全局开关检查
|
||||||
if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) {
|
if (settings.nccsEnabled === false) {
|
||||||
|
// 暂不阻断,仅作为配置读取,保持兼容性
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 基础配置确定 (options 覆盖 settings)
|
||||||
|
const activeMode = options.apiMode || settings.apiMode;
|
||||||
|
const activeUrl = options.apiUrl || settings.apiUrl;
|
||||||
|
const activeKey = options.apiKey || settings.apiKey;
|
||||||
|
const activeModel = options.model || settings.model;
|
||||||
|
const activeProfile = options.tavernProfile || settings.tavernProfile;
|
||||||
|
const activeMaxTokens = options.maxTokens ?? settings.maxTokens;
|
||||||
|
const activeTemperature = options.temperature ?? settings.temperature;
|
||||||
|
const activeFakeStream = options.useFakeStream ?? settings.useFakeStream;
|
||||||
|
|
||||||
|
if (activeMode !== 'sillytavern_preset') {
|
||||||
|
if (!activeUrl || !activeModel || !activeKey) {
|
||||||
console.warn("[Amily2-Nccs外交部] API配置不完整,无法调用AI");
|
console.warn("[Amily2-Nccs外交部] API配置不完整,无法调用AI");
|
||||||
toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Nccs-外交部");
|
toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Nccs-外交部");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [兼容性修复] 自动收集 options 中的额外参数到 params,防止 ModelCaller 丢失 top_p 等参数
|
||||||
|
const standardKeys = [
|
||||||
|
'apiMode', 'apiUrl', 'apiKey', 'model',
|
||||||
|
'maxTokens', 'temperature', 'tavernProfile', 'useFakeStream',
|
||||||
|
'params'
|
||||||
|
];
|
||||||
|
const extraParams = {};
|
||||||
|
Object.keys(options).forEach(key => {
|
||||||
|
if (!standardKeys.includes(key)) {
|
||||||
|
extraParams[key] = options[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 合并显式的 options.params 和 收集到的 extraParams
|
||||||
|
const finalParams = { ...extraParams, ...(options.params || {}) };
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 尝试路径 A: 新版 Amily2Bus ModelCaller (支持 FakeStream)
|
// 尝试路径 A: 新版 Amily2Bus ModelCaller (支持 FakeStream)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
if (nccsCtx && nccsCtx.model) {
|
if (nccsCtx && nccsCtx.model) {
|
||||||
try {
|
try {
|
||||||
nccsCtx.log('Main', 'info', `[v2 尝试通过 ModelCaller 调用 (${finalOptions.useFakeStream ? 'FakeStream' : 'Standard'})...`);
|
nccsCtx.log('Main', 'info', `[v2] 尝试通过 ModelCaller 调用 (${activeFakeStream ? 'FakeStream' : 'Standard'})...`);
|
||||||
|
|
||||||
const Options = nccsCtx.model.Options;
|
const builder = nccsCtx.model.Options.builder()
|
||||||
const builder = Options.builder()
|
.setFakeStream(activeFakeStream)
|
||||||
.setFakeStream(finalOptions.useFakeStream)
|
.setMaxTokens(activeMaxTokens)
|
||||||
.setMaxTokens(finalOptions.maxTokens)
|
.setTemperature(activeTemperature)
|
||||||
.setTemperature(finalOptions.temperature)
|
.setParams(finalParams);
|
||||||
.setParams(options.params || {});
|
|
||||||
|
|
||||||
if (finalOptions.apiMode === 'sillytavern_preset') {
|
if (activeMode === 'sillytavern_preset') {
|
||||||
builder.setMode('preset')
|
builder.setMode('preset')
|
||||||
.setPresetId(finalOptions.tavernProfile)
|
.setPresetId(activeProfile)
|
||||||
.setModel(finalOptions.model);
|
.setModel(activeModel);
|
||||||
} else {
|
} else {
|
||||||
builder.setMode('direct')
|
builder.setMode('direct')
|
||||||
.setApiUrl(finalOptions.apiUrl)
|
.setApiUrl(activeUrl)
|
||||||
.setApiKey(finalOptions.apiKey)
|
.setApiKey(activeKey)
|
||||||
.setModel(finalOptions.model);
|
.setModel(activeModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发起请求
|
// 发起请求
|
||||||
@@ -121,21 +149,34 @@ export async function callNccsAI(messages, options = {}) {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// 尝试路径 B: 旧版 Legacy 方法 (Fallback)
|
// 尝试路径 B: 旧版 Legacy 方法 (Fallback)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
// 构建 Legacy 兼容对象
|
||||||
|
const legacyOptions = {
|
||||||
|
apiMode: activeMode,
|
||||||
|
apiUrl: activeUrl,
|
||||||
|
apiKey: activeKey,
|
||||||
|
model: activeModel,
|
||||||
|
tavernProfile: activeProfile,
|
||||||
|
maxTokens: activeMaxTokens,
|
||||||
|
temperature: activeTemperature,
|
||||||
|
useFakeStream: activeFakeStream,
|
||||||
|
...finalParams // 将额外参数直接展平回 legacyOptions 根目录
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.groupCollapsed(`[Amily2-Nccs] 降级使用 Legacy API 调用`);
|
console.groupCollapsed(`[Amily2-Nccs] 降级使用 Legacy API 调用`);
|
||||||
console.log("Fallback Mode Active");
|
console.log("Fallback Mode Active");
|
||||||
|
|
||||||
let responseContent;
|
let responseContent;
|
||||||
|
|
||||||
switch (finalOptions.apiMode) {
|
switch (activeMode) {
|
||||||
case 'openai_test':
|
case 'openai_test':
|
||||||
responseContent = await callNccsOpenAITest(messages, finalOptions);
|
responseContent = await callNccsOpenAITest(messages, legacyOptions);
|
||||||
break;
|
break;
|
||||||
case 'sillytavern_preset':
|
case 'sillytavern_preset':
|
||||||
responseContent = await callNccsSillyTavernPreset(messages, finalOptions);
|
responseContent = await callNccsSillyTavernPreset(messages, legacyOptions);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error(`未支持的 API 模式: ${finalOptions.apiMode}`);
|
console.error(`未支持的 API 模式: ${activeMode}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,4 +155,8 @@ export const tableSystemDefaultSettings = {
|
|||||||
table_independent_rules_enabled: false,
|
table_independent_rules_enabled: false,
|
||||||
table_tags_to_extract: '',
|
table_tags_to_extract: '',
|
||||||
table_exclusion_rules: [],
|
table_exclusion_rules: [],
|
||||||
|
|
||||||
|
// Nccs API 设置
|
||||||
|
nccsEnabled: false,
|
||||||
|
nccsFakeStreamEnabled: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Amily2号聊天优化助手",
|
"name": "Amily2号聊天优化助手",
|
||||||
"display_name": "Amily2号助手",
|
"display_name": "Amily2号助手",
|
||||||
"version": "1.8.3",
|
"version": "1.8.3-a",
|
||||||
"author": "Wx-2025",
|
"author": "Wx-2025",
|
||||||
"description": "一个拥有独立UI的智能引擎,正文优化、自动总结、记忆表格、rag向量、隐藏楼层、剧情推进等多功能整合。",
|
"description": "一个拥有独立UI的智能引擎,正文优化、自动总结、记忆表格、rag向量、隐藏楼层、剧情推进等多功能整合。",
|
||||||
"minSillyTavernVersion": "1.10.0",
|
"minSillyTavernVersion": "1.10.0",
|
||||||
|
|||||||
@@ -1980,7 +1980,7 @@ function bindNccsApiEvents() {
|
|||||||
const settings = extension_settings[extensionName];
|
const settings = extension_settings[extensionName];
|
||||||
|
|
||||||
if (settings.nccsEnabled === undefined) settings.nccsEnabled = false;
|
if (settings.nccsEnabled === undefined) settings.nccsEnabled = false;
|
||||||
if (settings.nccsFakeStreamEnabled === undefined) settings.nccsEnabled = false;
|
if (settings.nccsFakeStreamEnabled === undefined) settings.nccsFakeStreamEnabled = false;
|
||||||
if (settings.nccsApiMode === undefined) settings.nccsApiMode = 'openai_test';
|
if (settings.nccsApiMode === undefined) settings.nccsApiMode = 'openai_test';
|
||||||
if (settings.nccsApiUrl === undefined) settings.nccsApiUrl = 'https://api.openai.com/v1';
|
if (settings.nccsApiUrl === undefined) settings.nccsApiUrl = 'https://api.openai.com/v1';
|
||||||
if (settings.nccsApiKey === undefined) settings.nccsApiKey = '';
|
if (settings.nccsApiKey === undefined) settings.nccsApiKey = '';
|
||||||
@@ -2068,6 +2068,12 @@ function bindNccsApiEvents() {
|
|||||||
log(`Nccs API ${enabledToggle.checked ? '已启用' : '已禁用'}`, 'info');
|
log(`Nccs API ${enabledToggle.checked ? '已启用' : '已禁用'}`, 'info');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
enabledFakeStreamToggle.addEventListener('change', () => {
|
||||||
|
settings.nccsFakeStreamEnabled = enabledFakeStreamToggle.checked;
|
||||||
|
saveSettingsDebounced();
|
||||||
|
log(`Nccs API FakeStream ${enabledFakeStreamToggle.checked ? 'Enabled' : 'Disabled'}`, 'info');
|
||||||
|
});
|
||||||
|
|
||||||
if (modeSelect) {
|
if (modeSelect) {
|
||||||
modeSelect.addEventListener('change', () => {
|
modeSelect.addEventListener('change', () => {
|
||||||
settings.nccsApiMode = modeSelect.value;
|
settings.nccsApiMode = modeSelect.value;
|
||||||
|
|||||||
Reference in New Issue
Block a user