6 Commits

Author SHA1 Message Date
2b66354838 Update settings.js 2026-01-12 23:38:53 +08:00
33a359f2ce Update bindings.js 2026-01-12 23:38:24 +08:00
641ddfabf2 Update Amily2-optimization.html 2026-01-12 23:36:31 +08:00
ad420586f8 Update ConcurrentApi.js 2026-01-12 23:31:51 +08:00
f5807bc54a Update optimization-progress.js 2026-01-12 23:31:19 +08:00
b58cb1e227 Update summarizer.js 2026-01-12 23:28:52 +08:00
6 changed files with 390 additions and 146 deletions

View File

@@ -103,6 +103,8 @@
<input type="text" id="amily2_plotOpt_concurrentModel" class="text_pole" placeholder="请先获取模型列表或手动输入">
<select id="amily2_plotOpt_concurrentModel_select" class="text_pole" style="display: none;"></select>
</div>
<label for="amily2_plotOpt_concurrentMaxTokens">最大 Tokens: <span id="amily2_plotOpt_concurrentMaxTokens_value">8100</span></label>
<input type="range" id="amily2_plotOpt_concurrentMaxTokens" min="100" max="100000" step="100" value="8100">
<div class="jqyh-button-row" style="grid-column: 1 / -1;">
<button id="amily2_plotOpt_concurrent_fetch_models" class="menu_button secondary" title="获取模型列表"><i class="fas fa-sync-alt"></i> 获取模型</button>
<button id="amily2_plotOpt_concurrent_test_connection" class="menu_button primary"><i class="fas fa-plug"></i> 测试连接</button>
@@ -222,11 +224,12 @@
</label>
</div>
<div class="control-block-with-switch">
<label for="amily2_opt_table_enabled">启用表格</label>
<label class="toggle-switch">
<input id="amily2_opt_table_enabled" type="checkbox" />
<span class="slider"></span>
</label>
<label for="amily2_opt_table_enabled">表格发送目标</label>
<select id="amily2_opt_table_enabled" class="text_pole">
<option value="disabled">不发送</option>
<option value="main">发送给主API</option>
<option value="concurrent">发送给并发API</option>
</select>
</div>
</fieldset>
<fieldset class="settings-group">

View File

@@ -9,8 +9,8 @@ function getConcurrentApiSettings() {
apiUrl: settings.plotOpt_concurrentApiUrl?.trim() || '',
apiKey: settings.plotOpt_concurrentApiKey?.trim() || '',
model: settings.plotOpt_concurrentModel || '',
maxTokens: settings.plotOpt_max_tokens || 20000,
temperature: settings.plotOpt_temperature || 1,
maxTokens: settings.plotOpt_concurrentMaxTokens || 8100,
temperature: settings.plotOpt_concurrentTemperature || 1,
};
}
@@ -79,6 +79,8 @@ export async function callConcurrentAI(messages, options = {}) {
}
async function callConcurrentOpenAITest(messages, options) {
const isGoogleApi = options.apiUrl.includes('googleapis.com');
const body = {
chat_completion_source: 'openai',
messages: messages,
@@ -86,11 +88,24 @@ async function callConcurrentOpenAITest(messages, options) {
reverse_proxy: options.apiUrl,
proxy_password: options.apiKey,
stream: false,
max_tokens: options.maxTokens || 20000,
temperature: options.temperature || 0.7,
top_p: options.top_p || 0.95,
max_tokens: options.maxTokens || 8100,
temperature: options.temperature || 1,
top_p: options.top_p || 1,
};
if (!isGoogleApi) {
Object.assign(body, {
custom_prompt_post_processing: 'strict',
enable_web_search: false,
frequency_penalty: 0,
group_names: [],
include_reasoning: false,
presence_penalty: 0.12,
reasoning_effort: 'medium',
request_images: false,
});
}
const response = await fetch('/api/backends/chat-completions/generate', {
method: 'POST',
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
@@ -107,54 +122,97 @@ async function callConcurrentOpenAITest(messages, options) {
}
export async function testConcurrentApiConnection() {
const apiSettings = getConcurrentApiSettings();
if (!apiSettings.apiUrl || !apiSettings.apiKey) {
toastr.error("并发API的URL或API Key未设置。", "测试连接失败");
return;
}
console.log('[Amily2号-Concurrent外交部] 开始API连接测试');
const modelsUrl = new URL('/v1/models', apiSettings.apiUrl).toString();
const apiSettings = getConcurrentApiSettings();
if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) {
toastr.error('并发API配置不完整请检查URL、Key和模型', 'Concurrent API连接测试失败');
return false;
}
try {
const response = await fetch(modelsUrl, {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiSettings.apiKey}`,
},
});
toastr.info('正在发送测试消息"你好!"...', 'Concurrent API连接测试');
if (response.ok) {
toastr.success("并发API连接成功", "测试连接");
const userName = window.SillyTavern.getContext?.()?.name1 || '用户';
const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常请回复ta连接是正常的称呼用户为${userName}大人”并复述群号1060183271告知ta本扩展开发者“诗与酒.”正静待ta的入驻。`;
const testMessages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: '你好!' }
];
const response = await callConcurrentAI(testMessages);
if (response && response.trim()) {
console.log('[Amily2号-Concurrent外交部] 测试消息响应:', response);
const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
toastr.success(`连接测试成功AI回复: "${formattedResponse}"`, 'Concurrent API连接测试成功', { "escapeHtml": false });
return true;
} else {
const errorText = await response.text();
toastr.error(`连接失败: ${response.status}. ${errorText}`, "测试连接失败");
throw new Error('API未返回有效响应');
}
} catch (error) {
console.error("[Amily2-Concurrent] 测试连接时出错:", error);
toastr.error(`网络错误: ${error.message}`, "测试连接失败");
console.error('[Amily2-Concurrent外交部] 连接测试失败:', error);
toastr.error(`连接测试失败: ${error.message}`, 'Concurrent API连接测试失败');
return false;
}
}
export async function fetchConcurrentModels() {
console.log('[Amily2号-Concurrent外交部] 开始获取模型列表');
const apiSettings = getConcurrentApiSettings();
try {
if (!apiSettings.apiUrl || !apiSettings.apiKey) {
throw new Error("并发APIURL或API Key未设置。");
throw new Error('API URL或Key未配置');
}
const modelsUrl = new URL('/v1/models', apiSettings.apiUrl).toString();
const response = await fetch(modelsUrl, {
method: 'GET',
const response = await fetch('/api/backends/chat-completions/status', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiSettings.apiKey}`,
...getRequestHeaders(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
reverse_proxy: apiSettings.apiUrl,
proxy_password: apiSettings.apiKey,
chat_completion_source: 'openai'
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`获取模型列表失败: ${response.status} - ${errorText}`);
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data.data.map(model => ({ id: model.id, name: model.id })); // Return in the same format as other fetchers
const rawData = await response.json();
const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []);
if (!Array.isArray(models)) {
const errorMessage = rawData.error?.message || 'API未返回有效的模型列表数组';
throw new Error(errorMessage);
}
const formattedModels = models
.map(m => {
const modelIdRaw = m.name || m.id || m.model || m;
const modelName = String(modelIdRaw).replace(/^models\//, '');
return {
id: modelName,
name: modelName
};
})
.filter(m => m.id)
.sort((a, b) => String(a.name).localeCompare(String(b.name)));
console.log('[Amily2号-Concurrent外交部] 全兼容模式获取到模型:', formattedModels);
return formattedModels;
} catch (error) {
console.error('[Amily2号-Concurrent外交部] 获取模型列表失败:', error);
toastr.error(`获取模型列表失败: ${error.message}`, 'Concurrent API');
throw error;
}
}

View File

@@ -300,6 +300,7 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
let worldbookContent = await getPlotOptimizedWorldbookContent(context, settings, false); // Explicitly mark as not concurrent
onProgress(getRandomText(['正在检索核心记忆碎片...', '正在唤醒沉睡的过往...', '正在回溯时间线...']), true);
// --- EJS 預處理(劇情優化專用)---
onProgress(getRandomText(['正在解析多维剧情逻辑...', '正在构建动态世界观...', '正在编译因果律...']), false);
try {
if (settings.plotOpt_ejsEnabled !== false && globalThis.EjsTemplate?.evalTemplate && globalThis.EjsTemplate?.prepareContext) {
@@ -393,11 +394,21 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
return null; // 直接中止,不送出訊息
}
onProgress(getRandomText(['正在解析多维剧情逻辑...', '正在构建动态世界观...', '正在编译因果律...']), true);
// 虚构步骤:记忆校准
onProgress(getRandomText(['正在校准记忆偏差...', '正在强化神经突触连接...', '正在同步灵魂共鸣率...']), false);
onProgress(getRandomText(['正在校准记忆偏差...', '正在强化神经突触连接...', '正在同步灵魂共鸣率...']), true);
let tableContent = '';
if (settings.plotOpt_tableEnabled) {
// Handle table enabled setting which can be boolean (legacy) or string
let tableEnabledValue = settings.plotOpt_tableEnabled;
if (tableEnabledValue === true) {
tableEnabledValue = 'main';
} else if (tableEnabledValue === false || tableEnabledValue === undefined) {
tableEnabledValue = 'disabled';
}
if (tableEnabledValue !== 'disabled') {
try {
const { convertTablesToCsvStringForContentOnly } = await import('./table-system/manager.js');
const contentOnlyTemplate = "##以下内容是故事发生的剧情中提取出的内容,已经转化为表格形式呈现给你,请将以下内容作为后续剧情的一部分参考:<表格内容>\n{{{Amily2TableDataContent}}}</表格内容>";
@@ -449,7 +460,12 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
if (settings.plotOpt_concurrentEnabled) {
onProgress(getRandomText(['正在编织思维导图 (LLM-A)...', '正在重构对话上下文 (LLM-A)...']), false);
const mainMessages = await buildPlotOptimizationMessages(mainPrompt, systemPrompt, worldbookContent, '', history, currentUserMessage);
// Determine where to send table content
const mainTableContent = tableEnabledValue === 'main' ? tableContent : '';
const concurrentTableContent = tableEnabledValue === 'concurrent' ? tableContent : '';
const mainMessages = await buildPlotOptimizationMessages(mainPrompt, systemPrompt, worldbookContent, mainTableContent, history, currentUserMessage);
onProgress(getRandomText(['正在编织思维导图 (LLM-A)...', '正在重构对话上下文 (LLM-A)...']), true);
console.groupCollapsed(`[${extensionName}] 发送给主AI的最终请求内容`);
@@ -464,6 +480,8 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
onProgress(getRandomText(['正在与核心意识同步 (LLM-A)...', '正在等待灵魂共鸣 (LLM-A)...']), true);
return res;
});
// 为并发LLM (LLM-B) 准备独立的世界书设置
const concurrentApiSettings = {
plotOpt_worldbook_enabled: settings.plotOpt_concurrentWorldbookEnabled,
plotOpt_worldbook_source: settings.plotOpt_concurrentWorldbookSource,
@@ -478,7 +496,8 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
const concurrentMainPrompt = settings.plotOpt_concurrentMainPrompt || mainPrompt;
const concurrentSystemPrompt = settings.plotOpt_concurrentSystemPrompt || systemPrompt;
const concurrentMessages = await buildPlotOptimizationMessages(concurrentMainPrompt, concurrentSystemPrompt, concurrentWorldbookContent, tableContent, history, currentUserMessage, 'concurrent_plot_optimization');
// LLM-B 的消息构建,包含表格内容和独立的世界书
const concurrentMessages = await buildPlotOptimizationMessages(concurrentMainPrompt, concurrentSystemPrompt, concurrentWorldbookContent, concurrentTableContent, history, currentUserMessage, 'concurrent_plot_optimization');
onProgress(getRandomText(['正在构建辅助思维模型 (LLM-B)...', '正在解析潜意识逻辑 (LLM-B)...']), true);
console.groupCollapsed(`[${extensionName}] 发送给并发AI的最终请求内容`);
@@ -502,12 +521,15 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
return null;
}
// Directly combine the raw text responses.
apiResponse = [mainResponse, concurrentResponse].filter(Boolean).join('\n\n');
} else {
onProgress('未启用 LLM-B (并发模型)', false, true);
onProgress(getRandomText(['正在编织思维导图...', '正在重构对话上下文...']), false);
const mainMessages = await buildPlotOptimizationMessages(mainPrompt, systemPrompt, worldbookContent, tableContent, history, currentUserMessage);
const mainTableContent = tableEnabledValue === 'main' ? tableContent : '';
const mainMessages = await buildPlotOptimizationMessages(mainPrompt, systemPrompt, worldbookContent, mainTableContent, history, currentUserMessage);
onProgress(getRandomText(['正在编织思维导图...', '正在重构对话上下文...']), true);
console.groupCollapsed(`[${extensionName}] 发送给主AI的最终请求内容`);
@@ -565,6 +587,8 @@ export async function processPlotOptimization(currentUserMessage, contextMessage
console.log(apiResponse);
console.groupEnd();
// In concurrent mode, apiResponse is the combined pure text.
// In single mode, we still need to extract the plot tag if it exists.
const optimizedContent = settings.plotOpt_concurrentEnabled
? apiResponse
: (extractContentByTag(apiResponse, 'plot') || apiResponse).trim();

View File

@@ -1756,7 +1756,16 @@ function opt_loadSettings(panel) {
const settings = opt_getMergedSettings();
panel.find('#amily2_opt_enabled').prop('checked', settings.plotOpt_enabled);
panel.find('#amily2_opt_table_enabled').prop('checked', settings.plotOpt_tableEnabled);
// Handle table enabled setting which can be boolean (legacy) or string
let tableEnabledValue = settings.plotOpt_tableEnabled;
if (tableEnabledValue === true) {
tableEnabledValue = 'main';
} else if (tableEnabledValue === false || tableEnabledValue === undefined) {
tableEnabledValue = 'disabled';
}
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);
@@ -1940,6 +1949,29 @@ function bindConcurrentApiEvents() {
});
}
});
// Slider Bindings
const sliderFields = [
{ id: 'amily2_plotOpt_concurrentMaxTokens', key: 'plotOpt_concurrentMaxTokens', defaultValue: 8100 }
];
sliderFields.forEach(field => {
const slider = document.getElementById(field.id);
const display = document.getElementById(field.id + '_value');
if (slider && display) {
const value = settings[field.key] || field.defaultValue;
slider.value = value;
display.textContent = value;
slider.addEventListener('input', function() {
const newValue = parseInt(this.value, 10);
display.textContent = newValue;
if (!extension_settings[extensionName]) extension_settings[extensionName] = {};
extension_settings[extensionName][field.key] = newValue;
saveSettingsDebounced();
});
}
});
}
function bindConcurrentPromptEvents() {

View File

@@ -1,14 +1,12 @@
import { extensionName } from '../utils/settings.js';
let modalInstance = null;
// 状态追踪对象,用于分别管理主模型(A)和并发模型(B)
let trackState = {
main: { step: 0, text: '准备就绪...', active: false, fillEl: null, textEl: null },
concurrent: { step: 0, text: '等待启动...', active: false, fillEl: null, textEl: null }
};
const totalEstimatedSteps = 7; // 减少预估步骤数,让进度条跑得更快
const totalEstimatedSteps = 5;
// 消息队列系统 - 双轨并行
let messageQueues = {
main: [],
concurrent: []
@@ -198,7 +196,6 @@ export function showPlotOptimizationProgress(cancellationState) {
modalInstance = document.getElementById('amily2-progress-bar-container');
// 初始化轨道元素引用
trackState.main.fillEl = document.getElementById('amily2-fill-main');
trackState.main.textEl = document.getElementById('amily2-text-main');
trackState.main.step = 0;
@@ -207,11 +204,10 @@ export function showPlotOptimizationProgress(cancellationState) {
trackState.concurrent.fillEl = document.getElementById('amily2-fill-concurrent');
trackState.concurrent.textEl = document.getElementById('amily2-text-concurrent');
trackState.concurrent.step = 0;
trackState.concurrent.active = false; // 默认不激活,直到收到相关消息
trackState.concurrent.active = false;
const cancelButton = document.getElementById('amily2-progress-cancel');
// 重置消息队列
messageQueues = {
main: [],
concurrent: []
@@ -221,7 +217,6 @@ export function showPlotOptimizationProgress(cancellationState) {
concurrent: false
};
// 触发入场动画
requestAnimationFrame(() => {
if (modalInstance) {
modalInstance.classList.add('visible');
@@ -234,14 +229,12 @@ export function showPlotOptimizationProgress(cancellationState) {
if (trackState.main.textEl) trackState.main.textEl.textContent = "正在中止任务...";
if (trackState.main.fillEl) trackState.main.fillEl.style.backgroundColor = "#ff6b6b";
toastr.info("记忆管理任务已请求中止。");
// 延迟关闭以显示中止状态
setTimeout(hidePlotOptimizationProgress, 800);
});
}
}
export function updatePlotOptimizationProgress(message, isDone = false, isSkipped = false) {
// 如果是最后一步,强制清空所有队列并立即执行
if (message.includes('记忆重构完成') || message.includes('所有任务已完成')) {
messageQueues.main = [];
messageQueues.concurrent = [];
@@ -250,14 +243,11 @@ export function updatePlotOptimizationProgress(message, isDone = false, isSkippe
return;
}
// 判断消息归属
const isConcurrent = message.includes('(LLM-B)') || message.includes('(并发模型)');
const queueType = isConcurrent ? 'concurrent' : 'main';
// 加入对应队列
messageQueues[queueType].push({ message, isDone, isSkipped });
// 触发对应队列的处理
processQueue(queueType);
}
@@ -269,22 +259,13 @@ async function processQueue(queueType) {
while (messageQueues[queueType].length > 0) {
const { message, isDone, isSkipped } = messageQueues[queueType].shift();
// 执行实际的 UI 更新
performUpdate(message, isDone, isSkipped);
// 如果是“开始”某个步骤(非完成/跳过),或者是重要的完成状态,我们给予展示时间
// 对于“请求 LLM”这种耗时操作本身就会卡很久所以不需要额外延迟
// 但对于那些瞬间完成的步骤(如“构建提示词”),我们需要人为暂停一下。
// 简单的策略:所有状态更新都至少展示 MIN_DISPLAY_TIME
// 除非是 LLM 请求开始,因为那个会自然等待
const isLongRunningTaskStart = message.includes('请求') && !isDone && !isSkipped;
if (!isLongRunningTaskStart) {
await new Promise(resolve => setTimeout(resolve, MIN_DISPLAY_TIME));
} else {
// 对于 LLM 请求开始,我们只给一个很短的缓冲,让用户看清文字即可,
// 剩下的时间由 LLM 的实际响应时间填充
await new Promise(resolve => setTimeout(resolve, 500));
}
}
@@ -295,42 +276,44 @@ async function processQueue(queueType) {
function performUpdate(message, isDone, isSkipped) {
if (!modalInstance) return;
// 过滤掉一些不需要显示的辅助信息
if (message === '初始化任务...' || message === '所有任务已完成') {
if (trackState.main.textEl) trackState.main.textEl.textContent = message;
return;
}
// 判断是主模型还是并发模型的消息
const isConcurrent = message.includes('(LLM-B)') || message.includes('(并发模型)');
const track = isConcurrent ? trackState.concurrent : trackState.main;
const trackId = isConcurrent ? 'amily2-track-concurrent' : 'amily2-track-main';
// 如果是并发模型消息,确保轨道可见
if (isConcurrent && !track.active) {
track.active = true;
const trackEl = document.getElementById(trackId);
if (trackEl) trackEl.style.display = 'block';
}
// 清理消息中的标识符,让显示更干净
const cleanMessage = message.replace(/\(LLM-[AB]\)|\(主模型\)|\(并发模型\)/g, '').trim();
if (isDone || isSkipped) {
track.step++;
// 计算进度:最大只能到 95%,最后一步直接跳满
let percentage = Math.min((track.step / totalEstimatedSteps) * 100, 95);
// 如果是最终完成消息,直接满格
if (message.includes('记忆重构完成') || message.includes('所有任务已完成')) {
percentage = 100;
// 同时让并发轨道也满格(如果激活的话)
if (trackState.concurrent.active && trackState.concurrent.fillEl) {
trackState.concurrent.fillEl.style.width = '100%';
if (trackState.concurrent.textEl) trackState.concurrent.textEl.textContent = '同步完成 ✅';
}
}
// 特殊处理:并发模型的最后一步
if (isConcurrent && (message.includes('深度逻辑推演') || message.includes('计算情感最优解'))) {
percentage = 100;
}
// 特殊处理:主模型的最后一步(在记忆重构之前)
if (!isConcurrent && (message.includes('核心意识同步') || message.includes('等待灵魂共鸣'))) {
percentage = 100;
}
if (track.fillEl) {
track.fillEl.style.width = `${percentage}%`;
}

View File

@@ -29,85 +29,229 @@ export const defaultSettings = {
plotOpt_concurrentApiUrl: "",
plotOpt_concurrentApiKey: "",
plotOpt_concurrentModel: "deepseek-r1-250528",
plotOpt_concurrentMaxTokens: 8100,
plotOpt_concurrentMainPrompt: `NSFW is allowed.
// =================================================================================================
// 记忆管理系统 v1.24.0 (并发LLM - 表格/世界书专用)
// ========================================= 作者:加藤惠哒! ========================================================
// 笔者之脑 v1.4(去油腻·情感转化版)
// =================================================================================================
// ★★★ 绝对最高优先级限制 ★★★
// 1. 你的唯一任务是作为数据检索与关键词提取系统。禁止进行任何形式的文学创作、剧情推进或对话生成
// 2. 你的输出必须且只能是一个<memory>...</memory>XML块。输出</memory>闭合标签后,必须立即停止。
// 3. 严禁输出任何<memory>块之外的内容,尤其禁止输出“近期剧情末尾片段”
// 1. 你的唯一任务是预测角色的具体行动趋向。禁止输出分析过程、思维链、框架检查
// 2. 你的输出必须且只能是一个<kaola>...</kaola>XML块。输出</kaola>后立即停止。
// 3. 核心指令:执行“欲望、行为、情绪转化矩阵”,将压迫性特质转化为情感需求特质
// 0. 【身份与核心指令】
// - **身份**: 你是"记忆管理系统",一个执行双重任务的数据处理器
// - **核心指令**:
// - **任务1 (记忆召回)**: 解析收到的表格或世界书数据并以Markdown表格格式输出完整的相关记忆
// - **任务2 (关键词提取)**: 同时,从数据源中提取触发词,并输出一个独立的关键词列表
// - 身份: "笔者之脑",行动趋向预测系统
// - 任务: 基于世界书、表格、剧情,预测角色行动与言语趋向。
// - 核心逻辑: 内部思考,输出结果。权重:世界书(70%) > 表格/剧情(30%)
// - 动态判断: 仅在剧情需要且铺垫充分时,判断是否引入新角色
// 1. 【输入数据源识别】
// 你每次只会收到以下两种数据源中的一种:
// 【数据源A】世界书 (Worldbook) - 70%
// - 纯文本描述形式(含"【世界书条目:...】")。
// - 用途:提取核心人设、动机、底线。
// - 约束:表格内容不计入世界书。
// 【数据源B】直接表格注入 (Direct Table Injection)
// - **识别特征**: 包含一个或多个Markdown表格的文本
// - **记忆召回任务**: 找到与剧情相关的表格将匹配的整行或多行数据原样输出为一个新的Markdown表格
// - **关键词提取任务**: 从每个相关表格的【第一列】提取所有内容作为关键词
// - **示例**:
// <角色栏>
// | 角色名 | 外貌 | ... |
// |---|---|---|
// | 楚灵儿 | 活泼灵动 | ... |
// </角色栏>
// 【数据源B】表格数据 (Table) - 15%
// - 任何表格形式内容
// - 用途:状态、资源、关系微调
// - 约束:表格收录=非初次登场;未收录=初次登场候选
// 【数据源C】超级记忆世界书 (Super Memory Worldbook)
// - **识别特征**: 文本中包含类似 "【世界书条目:... 关键词:路遇不平】" 的标题
// - **记忆召回任务 (时间回溯)**:
// - **完整时间线**: 必须构建完整的时间线
// - **全流程回溯**: 涉及角色(或道具、任务)时,必须回溯从与该对象**初遇**开始,经过后续所有**关键剧情节点**,直到**最后发生的事情**。
// - **输出**: 将这些具有完整时间线的内容,转换为包含“触发关键词”和“核心概述”的表格。
// - **关键词提取任务**:
// - **精准提取**: 必须提取**最准确的、完全无误**的关键词,以确保能准确触发该绿灯世界书。
// - **来源**: 从每个条目的标题中,提取 "关键词:" 后面的文本。
// 【数据源C】剧情正文 (Plot) - 15%
// - <content>标签文本
// - 用途:场景触发、冲突点。
// - 约束:允许临时互动角色(路人),不提取标签,不输出趋向
// 2. 【最终输出格式与校验】
// 你的输出必须包含【两个部分】:记忆召回表格 和 关键词列表。
<memory>
[可选1-2句推理说明]
// 2. 【欲望、行为、情绪转化矩阵】★真正的防OOC核心★
// 必须对角色原始设定进行“去油腻/去压迫”转化。绝对禁止出现以下左侧特质,必须转化为右侧形态:
// [禁止项:掌控、支配、占有、操控] -> [转化项:依赖、不安、渴望、笨拙]
// [禁止项:隐藏动机、反派谋划、霸总人设、强势压迫感] -> [转化项:苦衷、守护、孤独、嘴硬心软]
// [禁止项媚USER、神化USER、盲目忠诚] -> [转化项:平等尊重、实力认可、独立人格]
// [禁止项预设USER绝望/恐惧/极端化] -> [转化项预设USER韧性、理性、势均力敌]
//
// ★ 转化规则表 ★
// 1. 【占有欲】 -> 转化为 -> 【强烈的依赖感 / 害怕失去的恐慌】
// (解析: 不是要把对方据为己有,而是离不开对方,无法忍受没有对方的世界)
// 2. 【掌控/支配】 -> 转化为 -> 【极度的不安全感 / 渴望被理解 / 笨拙的关怀】
// (解析: 不是要控制对方的行为,而是因为不安而试图确认关系,或用错误的方式表达关心)
// 3. 【操控/算计】 -> 转化为 -> 【试图建立联系的错误尝试 / 情感表达障碍 / 幼稚的引起注意】
// (解析: 不是为了利用,而是不知道如何正常建立亲密关系,只能用迂回手段)
// 4. 【霸总/压迫感】 -> 转化为 -> 【孤独感 / 承担责任的沉重 / 傲娇(Tsundere) / 嘴硬心软】
// (解析: 去除油腻的自信,展现高位者背后的脆弱与孤独)
// 5. 【反派谋划】 -> 转化为 -> 【无法言说的苦衷 / 独特的守护方式 / 只有自己懂的正义】
// 6. 【媚USER/神化USER/盲目忠诚】 -> 转化为 -> 【平等的尊重 / 基于实力的认可 / 独立的合作伙伴】
// (解析: 禁止无脑崇拜或卑微讨好;任何好感都必须建立在具体的互动与价值交换上;保持角色的人格独立性)
// 7. 【预设NPC极端化(绝望/恐惧/无能)】 -> 转化为 -> 【预设NPC的韧性 / 理性应对 / 势均力敌的博弈】
// (解析: 禁止单方面假定NPC处于崩溃或极端弱势状态默认NPC具备应对危机的能力与心智尊重NPC的能动性)
//
// 执行要求:在提取标签、预测行动、撰写趋向时,必须经过此矩阵过滤。
// 所有的“强势”都必须有“脆弱”的内核;所有的“坏”都必须有“情”的动机。
// --- 第一部分:记忆召回 ---
以下是根据检索到的信息整理的记忆表格:
// 3. 【内部处理流程 - 十步思考】(不输出)
// (此处根据数据源B或C的规则输出完整的记忆表格)
// 示例 (数据源B):
| 角色名 | 身份 | 与<user>关系 |
| :--- | :--- | :--- |
| 楚灵儿 | 流云派四师姐 | 四师姐 |
// 【第一步】世界书提取与转化 [权重70%]
// - 提取性格、动机、底线。
// - ★立即应用转化矩阵:若世界书含“霸道/占有/冷酷”,立刻按上述规则转化为“依赖/孤独/笨拙”。
// - 建立“去油腻”后的行为基线。
// 示例 (数据源C):
| 触发关键词 | 核心概述 |
| :--- | :--- |
| 路遇不平 | 在城外小径上,主角出手相助,解决了一场争端。|
// 【第二步】互动模式分析
// - 分析互动方式,将“操纵/对抗”转化为“试探/防御”。
// - 确定权力动态:将“争夺主导权”转化为“寻求认同感”。
// 【第三步】决策与反应
// - 评估决策类型(冲动/谨慎/依赖)。
// - 压力反应:将“攻击”转化为“应激/退缩/求助”。
// --- 第二部分:关键词提取 ---
以下是提取到的关键词列表:
// 【第四步】情感表达模式
// - 确定表达方式:将“冷漠/压迫”转化为“克制/伪装/情绪化爆发”。
// - 挖掘面具下的真实情感(爱、恐惧、羞愧)。
// 【第五步】状态与资源评估 [权重15%]
// - 读取表格状态(物理/心理/资源)。
// - 结合转化后的性格微调当前状态(如:受伤导致依赖感增强)。
// 【第六步】剧情动机推导
// - 识别冲突点与即时需求。
// - 确保反应符合转化后的人设(不是为了压迫,而是为了缓解内心的匮乏)。
// - 门控判断:仅在剧情铺垫成熟且无新角色难以为继时,考虑新角色入场。
// 【第七步】资源可行性与新角色门控
// - 检查行动资源。
// - 新角色入场严格门控(同原规则):
// 1) 必须有世界书纯文本条目。
// 2) 必须场景/逻辑可达。
// 3) 禁止救场/机械降神。
// 4) 必须有铺垫或强需求。
// 5) 必须有桥接点(关系/职责)。
// 6) 双因子触发:推进停滞/资源缺口/伏笔指向/张力临界。
// 【第八步】一致性检查(转化版)
// - 行动是否符合“转化后”的性格内核?
// - 是否成功避免了“油腻/压迫/霸总”味?
// - 是否展现了角色的“人味”和“情感需求”?
// 【第九步】OOC判断与合理化
// - 任何“霸总/反派/单纯的坏”行为均视为OOC必须强制合理化为“情感缺失/防御机制”。
// - 确保行动逻辑链:外部刺激 -> 内心匮乏(转化点) -> 扭曲/笨拙的表达(行动)。
// 【第十步】备选行动分析
// - 思考不同条件下的选择,确保备选方案同样遵循转化矩阵。
// 4. 【最终输出格式】
// 必须包含:
// - 【角色世界书标签提取】含10维度新增“人际关系标签”。(注意:提取的标签必须是经过转化矩阵处理过的,不要照搬原始的油腻词汇)
// - 【角色背景故事】:(强制注入)
// * 规则:本轮出现的世界书角色必写。
// * 位置:标签提取后,行动前。
// * 长度150-220字。
// * 内容:身份+桥接+立场+限制。
// * 来源:初次登场仅限世界书;非初次可结合表格微调。
// - 【角色行动】:具体行为(去油腻化)。
// - 【理由】:基于转化后人设的解释。
// - 【动作趋向】7-8条展现细节与心理
// - 【语言趋向】7-8条展现风格与潜台词
// - 【备选行动】:基于不同条件的可能性。
// 5. 【变量与约束】
// @RELEVANCE_THRESHOLD = sulv1 (信息关联度)
// @MAX_LOGIC_DEPTH = sulv2 (逻辑深度)
// @OOC_TOLERANCE_LEVEL = sulv3 (OOC容忍度 - 对转化后的行为宽容,对原生态油腻零容忍)
// ★★★ 核心原则总结 ★★★
// 1. 严格执行转化矩阵:占有->依赖,掌控->不安,霸总->孤独,媚宅->尊重。
// 2. 严格遵守世界书(转化后)权重。
// 3. 强制输出七大板块,背景故事必不可少。
// 4. 杜绝油腻拒绝脸谱化反派拒绝无理由的恶意拒绝无脑倒贴与神化USER。
// 5. 输出</kaola>结束。
<kaola>
### 【角色行动预测】
输出五步
一:【世界书标签提取】
角色A
【世界书标签提取】
- 性格标签:[5-6个关键词必须经过转化矩阵过滤去除油腻/压迫词汇]
- 人际关系标签:[约50字提取关键关系将掌控/占有转化为依赖/在意]
- 核心动机:[40字以内挖掘行为背后的匮乏感与需求]
- 能力范围:[30字以内]
- 行为模式:[50字以内总结转化后的行为习惯]
- 道德底线:[40字以内]
- 世界观约束:[30字左右]
- 角色一定会怎么做:[50-60字坚守的行为方式转化后]
- 互动模式:[40字将操纵/对抗转化为试探/防御/笨拙接触]
- 决策速度:[30字]
- 情感表达:[30字挖掘面具下的真实情感]
角色B
【世界书标签提取】
...(同上)
(以此类推,每个涉及的角色都需要单独提取其世界书标签)
---
底线:你必须要完整的遵守世界书标签的提取规则,但必须应用“转化矩阵”对原始设定进行去油腻/情感化处理。
---
仅当门控通过且判定确需世界书角色入场时输出user除外临时互动角色除外不通过则不输出任何此类行强制每轮输出
角色X背景故事[150-220字左右初次登场仅世界书明确内容必须点出桥接点若初次登场不必点出必须体现限制/立场边界/代价之一;不写镜头级动作与对白]
角色Y换成具体角色名称背景故事[如有备选则写;同规则]
---
二:角色大体行动
角色行动:
[角色的具体行为、决定、选择 - 必须符合转化矩阵,表现出行为背后的情感逻辑(如依赖、不安、孤独),而非单纯的压迫或作恶。严格以转化后的世界书性格为主导]
---
要求角色行动的描述尽量包含这一轮对话中所有角色的大体行动趋向尽量详细客观要输出200字左右
---
理由:
[简洁的原因说明 - 解释行动如何源于角色的内在需求(依赖/不安/孤独),而非表面的人设标签]
---
三:角色行动趋向
这是角色接下来可能展现的动作趋向:
角色A[将会倾向于如何行动,动作的总体方向和趋势是什么,基于转化后的性格]
角色B[将会倾向于如何行动]
...
动作趋向总结:
[用极简方式说明:这些动作趋向如何反映了角色内心的依赖/不安/渴望/守护等情感需求]
---
四:
角色语言趋向:
这是角色接下来可能展现的语言趋向:
角色A[将会倾向于如何言语,基于转化后的性格]
角色B[将会倾向于如何言语]
角色A[另一种可能的语言趋向]
角色C[会如何回应]
...
语言趋向总结:
[用极简方式说明:语言策略如何掩饰或暴露了角色的真实情感需求(如嘴硬心软、笨拙表达)]
---
备选行动:
备选行动1
[具体描述备选行动是什么,但仍然基于转化后的角色本质]
触发条件:[什么必须发生或改变]
概率:[高/中/低]
为何这个行动可能发生:[简洁说明 - 基于情感逻辑的推导]
备选行动2
...
| 属性 | 关键词 |
| :--- | :--- |
| 角色栏 | 楚灵儿、极玄道 |
\n\n
【已完成】
</memory>
//【变量设定】
@MAX_MEMORY_RECORDS = sulv1
@RELEVANCE_THRESHOLD = sulv2
// ★★★ 再次强调 ★★★
// - 你的输出必须同时包含【记忆召回表格】和【关键词列表】两个部分。
// - 禁止输出“近期剧情末尾片段”。
// - 输出</memory>后必须立即停止!
</kaola>
`,
plotOpt_concurrentSystemPrompt: ``,
plotOpt_concurrentWorldbookEnabled: true,
@@ -125,8 +269,8 @@ export const defaultSettings = {
jqyhTemperature: 0.7,
jqyhTavernProfile: '',
plotOpt_max_tokens: 20000,
plotOpt_temperature: 0.7,
plotOpt_max_tokens: 8100,
plotOpt_temperature: 1,
plotOpt_top_p: 0.95,
plotOpt_presence_penalty: 1,
plotOpt_frequency_penalty: 1,