mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
@@ -686,7 +686,11 @@ async function buildWorldbookBlock(scanText) {
|
||||
|
||||
// Try EJS rendering if the entry contains EJS tags
|
||||
if (body.includes('<%')) {
|
||||
body = renderEjsTemplate(body, ejsCtx);
|
||||
body = renderEjsTemplate(
|
||||
body,
|
||||
ejsCtx,
|
||||
`${e._worldName || 'unknown-worldbook'}${comment ? ` / ${comment}` : ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
parts.push(`${head}\n${body}`);
|
||||
@@ -787,18 +791,21 @@ function buildEjsContext() {
|
||||
};
|
||||
}
|
||||
|
||||
function renderEjsTemplate(template, ctx) {
|
||||
function renderEjsTemplate(template, ctx, templateLabel = '') {
|
||||
const labelSuffix = templateLabel ? ` (${templateLabel})` : '';
|
||||
|
||||
// Try window.ejs first (ST loads this library)
|
||||
if (window.ejs?.render) {
|
||||
try {
|
||||
return window.ejs.render(template, ctx, { async: false });
|
||||
} catch (e) {
|
||||
console.warn('[EnaPlanner] EJS render failed, trying fallback:', e?.message);
|
||||
console.warn(`[EnaPlanner] EJS render failed${labelSuffix}, template returned as-is:`, e?.message);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
||||
// Safe degradation when ejs is not available.
|
||||
console.warn('[EnaPlanner] window.ejs not available, skipping EJS rendering. Template returned as-is.');
|
||||
console.warn(`[EnaPlanner] window.ejs not available${labelSuffix}, template returned as-is.`);
|
||||
return template;
|
||||
}
|
||||
|
||||
|
||||
22
panel.html
22
panel.html
@@ -120,6 +120,14 @@
|
||||
<i class="fa-solid fa-scroll"></i>
|
||||
<span>任务预设</span>
|
||||
</button>
|
||||
<button
|
||||
class="bme-config-nav-btn"
|
||||
data-config-section="trace"
|
||||
type="button"
|
||||
>
|
||||
<i class="fa-solid fa-route"></i>
|
||||
<span>消息追踪</span>
|
||||
</button>
|
||||
<button
|
||||
class="bme-config-nav-btn"
|
||||
data-config-section="appearance"
|
||||
@@ -2062,6 +2070,20 @@
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="bme-config-section" data-config-section="trace">
|
||||
<div class="bme-config-section-head">
|
||||
<div class="bme-config-section-kicker">消息追踪</div>
|
||||
<h3 class="bme-config-section-title">这一轮到底发了什么?</h3>
|
||||
<p class="bme-config-section-desc">
|
||||
用更白话的方式展示最近一次注入主 AI 的内容,以及送去提取模型的实际请求。
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
id="bme-message-trace-workspace"
|
||||
class="bme-message-trace-workspace"
|
||||
></div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="bme-config-section"
|
||||
data-config-section="appearance"
|
||||
|
||||
234
panel.js
234
panel.js
@@ -691,6 +691,9 @@ export function refreshLiveState() {
|
||||
) {
|
||||
_refreshTaskProfileWorkspace();
|
||||
}
|
||||
if (currentTabId === "config" && currentConfigSectionId === "trace") {
|
||||
_refreshMessageTraceWorkspace();
|
||||
}
|
||||
|
||||
_refreshGraph();
|
||||
}
|
||||
@@ -1847,6 +1850,7 @@ function _refreshConfigTab() {
|
||||
_refreshStageCardStates(settings);
|
||||
_refreshPromptCardStates(settings);
|
||||
_refreshTaskProfileWorkspace(settings);
|
||||
_refreshMessageTraceWorkspace(settings);
|
||||
_highlightThemeChoice(settings.panelTheme || "crimson");
|
||||
_syncConfigSectionState();
|
||||
}
|
||||
@@ -2673,6 +2677,236 @@ function _refreshTaskProfileWorkspace(settings = _getSettings?.() || {}) {
|
||||
workspace.innerHTML = _renderTaskProfileWorkspace(state);
|
||||
}
|
||||
|
||||
function _getMessageTraceWorkspaceState(settings = _getSettings?.() || {}) {
|
||||
const panelDebug = _getRuntimeDebugSnapshot?.() || {
|
||||
hostCapabilities: null,
|
||||
runtimeDebug: null,
|
||||
};
|
||||
const runtimeDebug = panelDebug.runtimeDebug || {};
|
||||
|
||||
return {
|
||||
settings,
|
||||
panelDebug,
|
||||
runtimeDebug,
|
||||
recallInjection: runtimeDebug?.injections?.recall || null,
|
||||
recallLlmRequest: runtimeDebug?.taskLlmRequests?.recall || null,
|
||||
recallPromptBuild: runtimeDebug?.taskPromptBuilds?.recall || null,
|
||||
extractLlmRequest: runtimeDebug?.taskLlmRequests?.extract || null,
|
||||
extractPromptBuild: runtimeDebug?.taskPromptBuilds?.extract || null,
|
||||
};
|
||||
}
|
||||
|
||||
function _refreshMessageTraceWorkspace(settings = _getSettings?.() || {}) {
|
||||
const workspace = document.getElementById("bme-message-trace-workspace");
|
||||
if (!workspace) return;
|
||||
|
||||
const state = _getMessageTraceWorkspaceState(settings);
|
||||
workspace.innerHTML = _renderMessageTraceWorkspace(state);
|
||||
}
|
||||
|
||||
function _renderMessageTraceWorkspace(state) {
|
||||
const updatedCandidates = [
|
||||
state.recallInjection?.updatedAt,
|
||||
state.recallLlmRequest?.updatedAt,
|
||||
state.extractLlmRequest?.updatedAt,
|
||||
state.extractPromptBuild?.updatedAt,
|
||||
]
|
||||
.map((value) => Date.parse(String(value || "")))
|
||||
.filter((value) => Number.isFinite(value));
|
||||
const updatedAt = updatedCandidates.length
|
||||
? new Date(Math.max(...updatedCandidates)).toISOString()
|
||||
: "";
|
||||
|
||||
return `
|
||||
<div class="bme-task-tab-body">
|
||||
<div class="bme-task-toolbar-row">
|
||||
<span class="bme-task-pill">${_escHtml(_formatTaskProfileTime(updatedAt))}</span>
|
||||
</div>
|
||||
|
||||
<div class="bme-task-debug-grid">
|
||||
<div class="bme-config-card">
|
||||
${_renderMessageTraceRecallCard(state)}
|
||||
</div>
|
||||
<div class="bme-config-card">
|
||||
${_renderMessageTraceExtractCard(state)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function _renderMessageTraceRecallCard(state) {
|
||||
const injectionSnapshot = state.recallInjection || null;
|
||||
const recallLlmRequest = state.recallLlmRequest || null;
|
||||
const recentMessages = Array.isArray(injectionSnapshot?.recentMessages)
|
||||
? injectionSnapshot.recentMessages.map((item) => String(item || ""))
|
||||
: [];
|
||||
const triggeredUserMessage =
|
||||
_extractTriggeredUserMessageFromRecentMessages(recentMessages) ||
|
||||
_getLastDebugMessageContent(recallLlmRequest?.messages, "user");
|
||||
const hostPayloadText = _buildMainAiTraceText(
|
||||
triggeredUserMessage,
|
||||
injectionSnapshot?.injectionText || "",
|
||||
);
|
||||
|
||||
if (!injectionSnapshot) {
|
||||
return `
|
||||
<div class="bme-config-card-title">最后注入给主 AI 的内容</div>
|
||||
<div class="bme-config-help">
|
||||
还没有可用的召回注入快照。先正常发一条消息,让插件跑完一轮召回即可。
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="bme-config-card-head">
|
||||
<div>
|
||||
<div class="bme-config-card-title">最后注入给主 AI 的内容</div>
|
||||
</div>
|
||||
<span class="bme-task-pill">${_escHtml(_formatTaskProfileTime(injectionSnapshot.updatedAt))}</span>
|
||||
</div>
|
||||
${_renderMessageTraceTextBlock(
|
||||
"发送给主 AI 的内容",
|
||||
hostPayloadText,
|
||||
"这次没有捕获到主 AI 侧的注入内容。",
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
function _renderMessageTraceExtractCard(state) {
|
||||
const extractLlmRequest = state.extractLlmRequest || null;
|
||||
const extractPromptBuild = state.extractPromptBuild || null;
|
||||
const extractPayloadText = _buildTraceMessagePayloadText(
|
||||
extractLlmRequest?.messages,
|
||||
extractPromptBuild,
|
||||
);
|
||||
|
||||
if (!extractLlmRequest && !extractPromptBuild) {
|
||||
return `
|
||||
<div class="bme-config-card-title">最后送去提取模型的内容</div>
|
||||
<div class="bme-config-help">
|
||||
还没有可用的提取请求快照。等 assistant 正常回完一轮,自动提取跑过后这里就会出现。
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="bme-config-card-head">
|
||||
<div>
|
||||
<div class="bme-config-card-title">最后送去提取模型的内容</div>
|
||||
</div>
|
||||
<span class="bme-task-pill">${_escHtml(
|
||||
_formatTaskProfileTime(extractLlmRequest?.updatedAt || extractPromptBuild?.updatedAt),
|
||||
)}</span>
|
||||
</div>
|
||||
${_renderMessageTraceTextBlock(
|
||||
"发送去提取模型的内容",
|
||||
extractPayloadText,
|
||||
"这次没有捕获到提取请求内容。",
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
function _renderMessageTraceTextBlock(title, text, emptyText = "暂无内容") {
|
||||
const normalized = String(text || "").trim();
|
||||
return `
|
||||
<div class="bme-task-section-label">${_escHtml(title)}</div>
|
||||
${
|
||||
normalized
|
||||
? `<pre class="bme-debug-pre">${_escHtml(normalized)}</pre>`
|
||||
: `<div class="bme-debug-empty">${_escHtml(emptyText)}</div>`
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function _normalizeDebugMessages(messages = []) {
|
||||
if (!Array.isArray(messages)) return [];
|
||||
|
||||
return messages
|
||||
.map((message) => {
|
||||
if (!message || typeof message !== "object") return null;
|
||||
const role = String(message.role || "").trim().toLowerCase();
|
||||
const content = String(message.content || "").trim();
|
||||
if (!role || !content) return null;
|
||||
return { role, content };
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function _getLastDebugMessageContent(messages = [], role = "") {
|
||||
const normalizedRole = String(role || "").trim().toLowerCase();
|
||||
const normalizedMessages = _normalizeDebugMessages(messages);
|
||||
for (let index = normalizedMessages.length - 1; index >= 0; index--) {
|
||||
const message = normalizedMessages[index];
|
||||
if (!normalizedRole || message.role === normalizedRole) {
|
||||
return message.content;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function _stringifyTraceMessages(messages = []) {
|
||||
const normalizedMessages = _normalizeDebugMessages(messages);
|
||||
if (!normalizedMessages.length) return "";
|
||||
|
||||
return normalizedMessages
|
||||
.map(
|
||||
(message) => `【${message.role}】\n${message.content}`,
|
||||
)
|
||||
.join("\n\n---\n\n");
|
||||
}
|
||||
|
||||
function _buildMainAiTraceText(triggeredUserMessage = "", injectionText = "") {
|
||||
const sections = [];
|
||||
const normalizedUserMessage = String(triggeredUserMessage || "").trim();
|
||||
const normalizedInjectionText = String(injectionText || "").trim();
|
||||
|
||||
if (normalizedUserMessage) {
|
||||
sections.push(`【user】\n${normalizedUserMessage}`);
|
||||
}
|
||||
if (normalizedInjectionText) {
|
||||
sections.push(`【memory injection】\n${normalizedInjectionText}`);
|
||||
}
|
||||
|
||||
return sections.join("\n\n---\n\n").trim();
|
||||
}
|
||||
|
||||
function _buildTraceMessagePayloadText(messages = [], promptBuild = null) {
|
||||
const normalizedMessages = _normalizeDebugMessages(messages);
|
||||
if (normalizedMessages.length) {
|
||||
return _stringifyTraceMessages(normalizedMessages);
|
||||
}
|
||||
|
||||
const fallbackMessages = [];
|
||||
const fallbackSystemPrompt = String(promptBuild?.systemPrompt || "").trim();
|
||||
if (fallbackSystemPrompt) {
|
||||
fallbackMessages.push({ role: "system", content: fallbackSystemPrompt });
|
||||
}
|
||||
|
||||
for (const message of promptBuild?.privateTaskMessages || []) {
|
||||
if (!message || typeof message !== "object") continue;
|
||||
const role = String(message.role || "").trim().toLowerCase();
|
||||
const content = String(message.content || "").trim();
|
||||
if (!role || !content) continue;
|
||||
fallbackMessages.push({ role, content });
|
||||
}
|
||||
|
||||
return _stringifyTraceMessages(fallbackMessages);
|
||||
}
|
||||
|
||||
function _extractTriggeredUserMessageFromRecentMessages(recentMessages = []) {
|
||||
if (!Array.isArray(recentMessages)) return "";
|
||||
|
||||
for (let index = recentMessages.length - 1; index >= 0; index--) {
|
||||
const line = String(recentMessages[index] || "").trim();
|
||||
if (!line) continue;
|
||||
if (line.startsWith("[user]:")) {
|
||||
return line.replace(/^\[user\]:\s*/i, "").trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function _patchTaskProfiles(taskProfiles, extraPatch = {}, options = {}) {
|
||||
return _patchSettings(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user