mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 09:15:50 +00:00
Compare commits
4 Commits
40c3d6c735
...
60befb69ea
| Author | SHA1 | Date | |
|---|---|---|---|
| 60befb69ea | |||
| ab161e475d | |||
| 859588461d | |||
| 3043c3d80a |
@@ -190,6 +190,11 @@
|
|||||||
<input type="range" id="amily2_ngms_temperature" min="0" max="2" step="0.1" value="0.7" />
|
<input type="range" id="amily2_ngms_temperature" min="0" max="2" step="0.1" value="0.7" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group" style="display: flex; align-items: center; gap: 10px;">
|
||||||
|
<label for="amily2_ngms_fakestream_enabled" style="margin-bottom: 0;">启用流式支持 (防超时)</label>
|
||||||
|
<input type="checkbox" id="amily2_ngms_fakestream_enabled" style="width: auto;" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 测试按钮组 - 水平排列 -->
|
<!-- 测试按钮组 - 水平排列 -->
|
||||||
<div class="ngms-button-row" style="display: flex; gap: 10px; justify-content: center; margin-top: 15px;">
|
<div class="ngms-button-row" style="display: flex; gap: 10px; justify-content: center; margin-top: 15px;">
|
||||||
<button id="amily2_ngms_test_connection" class="menu_button primary small_button interactable">
|
<button id="amily2_ngms_test_connection" class="menu_button primary small_button interactable">
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function getNccsApiSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =================================================================================================
|
// =================================================================================================
|
||||||
// 核心调用入口 (Legacy Mode with Stream Support)
|
// 核心调用入口 (Legacy First Mode)
|
||||||
// =================================================================================================
|
// =================================================================================================
|
||||||
|
|
||||||
export async function callNccsAI(messages, options = {}) {
|
export async function callNccsAI(messages, options = {}) {
|
||||||
@@ -63,17 +63,25 @@ export async function callNccsAI(messages, options = {}) {
|
|||||||
const settings = getNccsApiSettings();
|
const settings = getNccsApiSettings();
|
||||||
const finalOptions = {
|
const finalOptions = {
|
||||||
...settings,
|
...settings,
|
||||||
...options,
|
...options
|
||||||
// 显式合并流式开关
|
|
||||||
stream: options.useFakeStream ?? settings.useFakeStream ?? false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 确保 stream 标志位存在
|
||||||
|
finalOptions.stream = finalOptions.useFakeStream ?? false;
|
||||||
|
|
||||||
if (finalOptions.apiMode !== 'sillytavern_preset') {
|
if (finalOptions.apiMode !== 'sillytavern_preset') {
|
||||||
if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) {
|
if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// [限制] 预设模式暂不支持流式
|
||||||
|
if (finalOptions.stream) {
|
||||||
|
console.warn("[Amily2-Nccs] 预设模式目前尚不支持流式处理方案,已自动切换为标准模式。");
|
||||||
|
toastr.warning("SillyTavern预设模式目前暂不支持流式处理(假流式),已为您切换为标准请求模式。该功能将在后续版本中支持。", "Nccs-外交部");
|
||||||
|
finalOptions.stream = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -134,14 +142,16 @@ async function fetchFakeStream(url, opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!fullContent && buffer) {
|
if (!fullContent && buffer) {
|
||||||
try { return JSON.parse(buffer).choices?.[0]?.message?.content || buffer; }
|
try {
|
||||||
catch { return buffer; }
|
const data = JSON.parse(buffer);
|
||||||
|
return data.choices?.[0]?.message?.content || data.content || buffer;
|
||||||
|
} catch { return buffer; }
|
||||||
}
|
}
|
||||||
return fullContent;
|
return fullContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================================================================
|
// =================================================================================================
|
||||||
// Legacy Implementations (直接增加流式适配)
|
// Legacy Implementations
|
||||||
// =================================================================================================
|
// =================================================================================================
|
||||||
|
|
||||||
function normalizeApiResponse(responseData) {
|
function normalizeApiResponse(responseData) {
|
||||||
@@ -207,44 +217,24 @@ async function callNccsSillyTavernPreset(messages, options) {
|
|||||||
await amilyHelper.triggerSlash(`/profile await=true "${targetProfile.name.replace(/"/g, '\\"')}"`);
|
await amilyHelper.triggerSlash(`/profile await=true "${targetProfile.name.replace(/"/g, '\\"')}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.stream) {
|
|
||||||
// [关键复刻] 完全继承 Profile 属性并注入运行时元数据
|
|
||||||
const body = {
|
|
||||||
...targetProfile,
|
|
||||||
messages: messages,
|
|
||||||
stream: true,
|
|
||||||
max_tokens: options.maxTokens || targetProfile.max_tokens || 2000,
|
|
||||||
user_name: context.name1 || 'User',
|
|
||||||
char_name: context.name2 || 'AI',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 补全 URL 映射 (ST 后端对不同 source 字段名要求不同)
|
|
||||||
const rawUrl = body['api-url'] || body['api_url'] || body.custom_url || body.url;
|
|
||||||
if (rawUrl) {
|
|
||||||
if (body.chat_completion_source === 'custom') body.custom_url = rawUrl;
|
|
||||||
else body.reverse_proxy = rawUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchOpts = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(body)
|
|
||||||
};
|
|
||||||
return await fetchFakeStream('/api/backends/chat-completions/generate', fetchOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!context.ConnectionManagerRequestService) throw new Error('ConnectionManagerRequestService unavailable');
|
if (!context.ConnectionManagerRequestService) throw new Error('ConnectionManagerRequestService unavailable');
|
||||||
const result = await context.ConnectionManagerRequestService.sendRequest(targetProfile.id, messages, options.maxTokens || 4000);
|
|
||||||
|
const result = await context.ConnectionManagerRequestService.sendRequest(
|
||||||
|
targetProfile.id,
|
||||||
|
messages,
|
||||||
|
options.maxTokens || 4000
|
||||||
|
);
|
||||||
|
|
||||||
return normalizeApiResponse(result);
|
return normalizeApiResponse(result);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
// Restore profile
|
||||||
const current = await amilyHelper.triggerSlash('/profile');
|
const current = await amilyHelper.triggerSlash('/profile');
|
||||||
if (originalProfile && originalProfile !== current) {
|
if (originalProfile && originalProfile !== current) {
|
||||||
await amilyHelper.triggerSlash(`/profile await=true "${originalProfile.replace(/"/g, '\\"')}"`);
|
await amilyHelper.triggerSlash(`/profile await=true "${originalProfile.replace(/"/g, '\\"')}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchNccsModels() {
|
export async function fetchNccsModels() {
|
||||||
console.log('[Amily2号-Nccs外交部] 开始获取模型列表');
|
console.log('[Amily2号-Nccs外交部] 开始获取模型列表');
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export function getNgmsApiSettings() {
|
|||||||
model: extension_settings[extensionName]?.ngmsModel || '',
|
model: extension_settings[extensionName]?.ngmsModel || '',
|
||||||
maxTokens: extension_settings[extensionName]?.ngmsMaxTokens || 4000,
|
maxTokens: extension_settings[extensionName]?.ngmsMaxTokens || 4000,
|
||||||
temperature: extension_settings[extensionName]?.ngmsTemperature || 0.7,
|
temperature: extension_settings[extensionName]?.ngmsTemperature || 0.7,
|
||||||
tavernProfile: extension_settings[extensionName]?.ngmsTavernProfile || ''
|
tavernProfile: extension_settings[extensionName]?.ngmsTavernProfile || '',
|
||||||
|
useFakeStream: extension_settings[extensionName]?.ngmsFakeStreamEnabled || false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,12 +74,22 @@ export async function callNgmsAI(messages, options = {}) {
|
|||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 确保 stream 标志位存在
|
||||||
|
finalOptions.stream = finalOptions.useFakeStream ?? apiSettings.useFakeStream ?? false;
|
||||||
|
|
||||||
if (finalOptions.apiMode !== 'sillytavern_preset') {
|
if (finalOptions.apiMode !== 'sillytavern_preset') {
|
||||||
if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) {
|
if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) {
|
||||||
console.warn("[Amily2-Ngms外交部] API配置不完整,无法调用AI");
|
console.warn("[Amily2-Ngms外交部] API配置不完整,无法调用AI");
|
||||||
toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Ngms-外交部");
|
toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Ngms-外交部");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// [限制] 预设模式暂不支持流式
|
||||||
|
if (finalOptions.stream) {
|
||||||
|
console.warn("[Amily2-Ngms] 预设模式目前尚不支持流式处理方案,已自动切换为标准模式。");
|
||||||
|
toastr.warning("SillyTavern预设模式目前暂不支持流式处理(假流式),已为您切换为标准请求模式。该功能将在后续版本中支持。", "Ngms-外交部");
|
||||||
|
finalOptions.stream = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.groupCollapsed(`[Amily2号-Ngms统一API调用] ${new Date().toLocaleTimeString()}`);
|
console.groupCollapsed(`[Amily2号-Ngms统一API调用] ${new Date().toLocaleTimeString()}`);
|
||||||
@@ -87,6 +98,7 @@ export async function callNgmsAI(messages, options = {}) {
|
|||||||
model: finalOptions.model,
|
model: finalOptions.model,
|
||||||
maxTokens: finalOptions.maxTokens,
|
maxTokens: finalOptions.maxTokens,
|
||||||
temperature: finalOptions.temperature,
|
temperature: finalOptions.temperature,
|
||||||
|
stream: finalOptions.stream,
|
||||||
messagesCount: messages.length
|
messagesCount: messages.length
|
||||||
});
|
});
|
||||||
console.log("【消息内容】:", messages);
|
console.log("【消息内容】:", messages);
|
||||||
@@ -139,6 +151,54 @@ export async function callNgmsAI(messages, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchFakeStream(url, opts) {
|
||||||
|
const res = await fetch(url, opts);
|
||||||
|
if (!res.ok) {
|
||||||
|
const errorText = await res.text();
|
||||||
|
throw new Error(`Stream HTTP ${res.status}: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = res.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let fullContent = "";
|
||||||
|
let buffer = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
buffer += decoder.decode(value, { stream: true });
|
||||||
|
const lines = buffer.split('\n');
|
||||||
|
buffer = lines.pop();
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed === 'data: [DONE]') continue;
|
||||||
|
if (trimmed.startsWith('data: ')) {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(trimmed.substring(6));
|
||||||
|
const delta = json.choices?.[0]?.delta?.content;
|
||||||
|
if (delta) fullContent += delta;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[NgmsApi] SSE Parse Error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fullContent && buffer) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(buffer);
|
||||||
|
return data.choices?.[0]?.message?.content || data.content || buffer;
|
||||||
|
} catch { return buffer; }
|
||||||
|
}
|
||||||
|
return fullContent;
|
||||||
|
}
|
||||||
|
|
||||||
async function callNgmsOpenAITest(messages, options) {
|
async function callNgmsOpenAITest(messages, options) {
|
||||||
const isGoogleApi = options.apiUrl.includes('googleapis.com');
|
const isGoogleApi = options.apiUrl.includes('googleapis.com');
|
||||||
|
|
||||||
@@ -148,7 +208,7 @@ async function callNgmsOpenAITest(messages, options) {
|
|||||||
model: options.model,
|
model: options.model,
|
||||||
reverse_proxy: options.apiUrl,
|
reverse_proxy: options.apiUrl,
|
||||||
proxy_password: options.apiKey,
|
proxy_password: options.apiKey,
|
||||||
stream: false,
|
stream: !!options.stream,
|
||||||
max_tokens: options.maxTokens || 30000,
|
max_tokens: options.maxTokens || 30000,
|
||||||
temperature: options.temperature || 1,
|
temperature: options.temperature || 1,
|
||||||
top_p: options.top_p || 1,
|
top_p: options.top_p || 1,
|
||||||
@@ -167,11 +227,17 @@ async function callNgmsOpenAITest(messages, options) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/backends/chat-completions/generate', {
|
const fetchOpts = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
|
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (options.stream) {
|
||||||
|
return await fetchFakeStream('/api/backends/chat-completions/generate', fetchOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/backends/chat-completions/generate', fetchOpts);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
|
|||||||
@@ -408,6 +408,7 @@ function bindNgmsApiEvents() {
|
|||||||
|
|
||||||
// Ngms API 开关控制
|
// Ngms API 开关控制
|
||||||
const ngmsToggle = document.getElementById('amily2_ngms_enabled');
|
const ngmsToggle = document.getElementById('amily2_ngms_enabled');
|
||||||
|
const ngmsFakeStreamToggle = document.getElementById('amily2_ngms_fakestream_enabled');
|
||||||
const ngmsContent = document.getElementById('amily2_ngms_content');
|
const ngmsContent = document.getElementById('amily2_ngms_content');
|
||||||
|
|
||||||
if (ngmsToggle && ngmsContent) {
|
if (ngmsToggle && ngmsContent) {
|
||||||
@@ -421,6 +422,13 @@ function bindNgmsApiEvents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ngmsFakeStreamToggle) {
|
||||||
|
ngmsFakeStreamToggle.checked = extension_settings[extensionName].ngmsFakeStreamEnabled ?? false;
|
||||||
|
ngmsFakeStreamToggle.addEventListener('change', function() {
|
||||||
|
updateAndSaveSetting('ngmsFakeStreamEnabled', this.checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// API模式切换
|
// API模式切换
|
||||||
const apiModeSelect = document.getElementById('amily2_ngms_api_mode');
|
const apiModeSelect = document.getElementById('amily2_ngms_api_mode');
|
||||||
const compatibleConfig = document.getElementById('amily2_ngms_compatible_config');
|
const compatibleConfig = document.getElementById('amily2_ngms_compatible_config');
|
||||||
|
|||||||
Reference in New Issue
Block a user