Improve injection preview readability

This commit is contained in:
Youzini-afk
2026-04-07 20:54:15 +08:00
parent 2d45abc704
commit dd6baa8dad
2 changed files with 387 additions and 3 deletions

259
panel.js
View File

@@ -1207,9 +1207,7 @@ async function _refreshInjectionPreview() {
try {
const { estimateTokens } = await import("./injector.js");
const totalTokens = estimateTokens(injection);
const preview = document.createElement("div");
preview.className = "bme-injection-preview";
preview.textContent = injection;
const preview = _buildInjectionPreviewNode(injection);
container.replaceChildren(preview);
if (tokenEl) tokenEl.textContent = `${totalTokens} tokens`;
} catch (error) {
@@ -1222,6 +1220,261 @@ async function _refreshInjectionPreview() {
}
}
function _buildInjectionPreviewNode(injectionText = "") {
const parsed = _parseInjectionPreview(String(injectionText || ""));
if (!parsed.sections.length) {
const preview = document.createElement("div");
preview.className = "bme-injection-preview";
preview.textContent = injectionText;
return preview;
}
const root = document.createElement("div");
root.className = "bme-injection-rich";
const hint = document.createElement("div");
hint.className = "bme-injection-rich__hint";
hint.textContent = "这里是结构化预览,便于阅读;实际发给模型的仍是原始注入文本。";
root.appendChild(hint);
for (const section of parsed.sections) {
const card = document.createElement("section");
card.className = `bme-injection-card ${_getInjectionSectionFlavor(section.title)}`;
const title = document.createElement("div");
title.className = "bme-injection-card__title";
title.textContent = section.title;
card.appendChild(title);
if (section.note) {
const note = document.createElement("div");
note.className = "bme-injection-card__note";
note.textContent = section.note;
card.appendChild(note);
}
for (const block of section.blocks) {
if (block.type === "table") {
card.appendChild(_buildInjectionTableNode(block));
} else if (block.type === "text" && block.text) {
const text = document.createElement("div");
text.className = "bme-injection-card__text";
text.textContent = block.text;
card.appendChild(text);
}
}
root.appendChild(card);
}
return root;
}
function _parseInjectionPreview(injectionText = "") {
const lines = String(injectionText || "").replace(/\r/g, "").split("\n");
const sections = [];
let index = 0;
let currentSection = null;
function ensureSection(title = "Memory") {
if (!currentSection) {
currentSection = {
title,
note: "",
blocks: [],
};
sections.push(currentSection);
}
return currentSection;
}
while (index < lines.length) {
const rawLine = lines[index] ?? "";
const line = rawLine.trim();
if (!line) {
index += 1;
continue;
}
const sectionMatch = line.match(/^\[(Memory\s*-\s*.+)]$/i);
if (sectionMatch) {
currentSection = {
title: sectionMatch[1],
note: "",
blocks: [],
};
sections.push(currentSection);
index += 1;
const noteCandidate = (lines[index] ?? "").trim();
if (
noteCandidate &&
!noteCandidate.startsWith("[") &&
!noteCandidate.endsWith(":") &&
!noteCandidate.startsWith("|") &&
!noteCandidate.startsWith("## ")
) {
currentSection.note = noteCandidate;
index += 1;
}
continue;
}
const section = ensureSection();
if (line.endsWith(":") && String(lines[index + 1] || "").trim().startsWith("|")) {
const tableName = line.slice(0, -1).trim();
const tableLines = [];
index += 1;
while (index < lines.length) {
const tableLine = String(lines[index] || "");
if (!tableLine.trim().startsWith("|")) {
break;
}
tableLines.push(tableLine.trim());
index += 1;
}
const parsedTable = _parseInjectionTable(tableName, tableLines);
if (parsedTable) {
section.blocks.push(parsedTable);
}
continue;
}
const textLines = [];
while (index < lines.length) {
const candidate = String(lines[index] || "").trim();
if (!candidate) {
index += 1;
if (textLines.length > 0) {
break;
}
continue;
}
if (
/^\[(Memory\s*-\s*.+)]$/i.test(candidate) ||
(candidate.endsWith(":") && String(lines[index + 1] || "").trim().startsWith("|"))
) {
break;
}
textLines.push(candidate);
index += 1;
}
if (textLines.length > 0) {
section.blocks.push({
type: "text",
text: textLines.join("\n"),
});
}
}
return { sections };
}
function _parseInjectionTable(tableName, tableLines = []) {
if (!Array.isArray(tableLines) || tableLines.length < 2) {
return null;
}
const headerCells = _splitInjectionTableRow(tableLines[0]);
if (!headerCells.length) {
return null;
}
const rows = tableLines
.slice(2)
.map((row) => _splitInjectionTableRow(row))
.filter((cells) => cells.length > 0);
return {
type: "table",
name: tableName,
headers: headerCells,
rows,
};
}
function _splitInjectionTableRow(row = "") {
const text = String(row || "").trim();
if (!text.startsWith("|")) {
return [];
}
const inner = text.replace(/^\|/, "").replace(/\|$/, "");
const cells = [];
let current = "";
let escaped = false;
for (const ch of inner) {
if (escaped) {
current += ch;
escaped = false;
continue;
}
if (ch === "\\") {
escaped = true;
continue;
}
if (ch === "|") {
cells.push(current.trim());
current = "";
continue;
}
current += ch;
}
cells.push(current.trim());
return cells.map((cell) => cell.replace(/\\\|/g, "|").trim());
}
function _buildInjectionTableNode(table) {
const wrap = document.createElement("div");
wrap.className = "bme-injection-table-wrap";
const name = document.createElement("div");
name.className = "bme-injection-table-name";
name.textContent = table.name;
wrap.appendChild(name);
const tableEl = document.createElement("table");
tableEl.className = "bme-injection-table";
const thead = document.createElement("thead");
const headRow = document.createElement("tr");
for (const header of table.headers) {
const th = document.createElement("th");
th.textContent = header;
headRow.appendChild(th);
}
thead.appendChild(headRow);
tableEl.appendChild(thead);
const tbody = document.createElement("tbody");
for (const row of table.rows) {
const tr = document.createElement("tr");
const normalizedCells = table.headers.map((_, idx) => row[idx] ?? "");
for (const cell of normalizedCells) {
const td = document.createElement("td");
td.textContent = cell;
tr.appendChild(td);
}
tbody.appendChild(tr);
}
tableEl.appendChild(tbody);
wrap.appendChild(tableEl);
return wrap;
}
function _getInjectionSectionFlavor(title = "") {
const normalized = String(title || "").toLowerCase();
if (normalized.includes("character pov")) return "character-pov";
if (normalized.includes("user pov")) return "user-pov";
if (normalized.includes("current region")) return "objective-current";
if (normalized.includes("global")) return "objective-global";
return "generic";
}
// ==================== 图谱 ====================
/** SillyTavern 用户显示名name1用于图谱分区误标为角色的用户 POV 强制归用户区 */

131
style.css
View File

@@ -909,6 +909,137 @@
overflow-y: auto;
}
.bme-injection-rich {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 100%;
overflow-y: auto;
}
.bme-injection-rich__hint {
font-size: 11px;
line-height: 1.5;
color: var(--bme-on-surface-dim);
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 10px;
padding: 10px 12px;
}
.bme-injection-card {
display: flex;
flex-direction: column;
gap: 10px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.015));
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 12px;
padding: 12px;
}
.bme-injection-card.character-pov {
border-color: rgba(255, 113, 145, 0.26);
background: linear-gradient(180deg, rgba(255, 113, 145, 0.08), rgba(255, 255, 255, 0.02));
}
.bme-injection-card.user-pov {
border-color: rgba(104, 183, 255, 0.24);
background: linear-gradient(180deg, rgba(104, 183, 255, 0.08), rgba(255, 255, 255, 0.02));
}
.bme-injection-card.objective-current {
border-color: rgba(111, 230, 179, 0.22);
background: linear-gradient(180deg, rgba(111, 230, 179, 0.07), rgba(255, 255, 255, 0.02));
}
.bme-injection-card.objective-global {
border-color: rgba(255, 211, 102, 0.18);
background: linear-gradient(180deg, rgba(255, 211, 102, 0.06), rgba(255, 255, 255, 0.02));
}
.bme-injection-card__title {
font-size: 13px;
line-height: 1.35;
font-weight: 800;
color: var(--bme-on-surface);
}
.bme-injection-card__note {
font-size: 11px;
line-height: 1.55;
color: var(--bme-on-surface-dim);
padding: 8px 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.035);
}
.bme-injection-card__text {
font-size: 12px;
line-height: 1.65;
color: var(--bme-on-surface);
white-space: pre-wrap;
word-break: break-word;
}
.bme-injection-table-wrap {
display: flex;
flex-direction: column;
gap: 8px;
overflow-x: auto;
}
.bme-injection-table-name {
font-family: "Cascadia Code", "Fira Code", monospace;
font-size: 10px;
line-height: 1.4;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--bme-on-surface-dim);
}
.bme-injection-table {
width: 100%;
min-width: 560px;
border-collapse: collapse;
table-layout: fixed;
background: rgba(0, 0, 0, 0.18);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
overflow: hidden;
}
.bme-injection-table th,
.bme-injection-table td {
padding: 8px 9px;
text-align: left;
vertical-align: top;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
border-right: 1px solid rgba(255, 255, 255, 0.05);
font-size: 11px;
line-height: 1.55;
color: var(--bme-on-surface);
word-break: break-word;
overflow-wrap: anywhere;
}
.bme-injection-table th:last-child,
.bme-injection-table td:last-child {
border-right: none;
}
.bme-injection-table tr:last-child td {
border-bottom: none;
}
.bme-injection-table th {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--bme-on-surface-dim);
background: rgba(255, 255, 255, 0.05);
}
.bme-injection-section-label {
font-size: 10px;
font-weight: 700;