feat:优化评分网页,可关闭状态看板
This commit is contained in:
@@ -748,6 +748,12 @@ INDEX_HTML_V2 = r"""<!doctype html>
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.header-tools { margin-bottom: 0; justify-content: flex-end; }
|
||||
.global-evaluator {
|
||||
width: 150px;
|
||||
}
|
||||
.global-evaluator {
|
||||
width: 150px;
|
||||
}
|
||||
input, select, textarea, button {
|
||||
font: inherit;
|
||||
border: 1px solid var(--line);
|
||||
@@ -868,6 +874,8 @@ INDEX_HTML_V2 = r"""<!doctype html>
|
||||
<header>
|
||||
<h1>Maisaka 回复效果评分预览</h1>
|
||||
<div class="toolbar header-tools">
|
||||
<span class="meta">评价人</span>
|
||||
<input id="globalEvaluator" class="global-evaluator" placeholder="manual" oninput="saveGlobalEvaluator()" />
|
||||
<button id="browseTab" class="tab-button active" onclick="setMode('browse')">浏览</button>
|
||||
<button id="rateTab" class="tab-button" onclick="setMode('rate')">逐条评分</button>
|
||||
<button class="secondary" onclick="reloadAll()">刷新</button>
|
||||
@@ -1527,6 +1535,33 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
}
|
||||
.message-name { font-weight: 650; color: var(--text); }
|
||||
.message-text { white-space: pre-wrap; word-break: break-word; line-height: 1.45; }
|
||||
.quote-card {
|
||||
border-left: 3px solid var(--accent);
|
||||
background: var(--accent-soft);
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
margin: 0 0 6px;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.quote-card.missing {
|
||||
border-left-color: var(--warn);
|
||||
background: #fff7ed;
|
||||
}
|
||||
.quote-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 3px;
|
||||
font-weight: 650;
|
||||
color: var(--text);
|
||||
}
|
||||
.quote-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.message-attachments {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
@@ -1578,6 +1613,8 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
<header>
|
||||
<h1>Maisaka 回复效果评分预览</h1>
|
||||
<div class="toolbar header-tools">
|
||||
<span class="meta">评价人</span>
|
||||
<input id="globalEvaluator" class="global-evaluator" placeholder="manual" oninput="saveGlobalEvaluator()" />
|
||||
<button id="browseTab" class="tab-button active" onclick="setMode('browse')">浏览</button>
|
||||
<button id="rateTab" class="tab-button" onclick="setMode('rate')">逐条评分</button>
|
||||
<button class="secondary" onclick="reloadAll()">刷新</button>
|
||||
@@ -1632,6 +1669,8 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
let selectedEffect = "";
|
||||
let activeMode = "browse";
|
||||
let selectedFivePointScore = 0;
|
||||
let currentTargetMessageId = "";
|
||||
let currentMessageIndex = new Map();
|
||||
|
||||
async function api(path, options) {
|
||||
const res = await fetch(path, options);
|
||||
@@ -1763,6 +1802,11 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
const reply = record.reply || {};
|
||||
const manual = record._manual || {};
|
||||
const followups = record.followup_messages || [];
|
||||
currentTargetMessageId = String(reply.target_message_id || "");
|
||||
const context = normalizeContextMessages(record.context_snapshot || []);
|
||||
const normalizedFollowups = normalizeFollowupMessages(followups);
|
||||
const botReply = normalizeBotReply(reply);
|
||||
buildCurrentMessageIndex(context, botReply, normalizedFollowups);
|
||||
selectedFivePointScore = Number(manual.manual_score_5 || score100ToFive(manual.manual_score) || 0);
|
||||
document.getElementById("detailPane").innerHTML = `
|
||||
<div class="block">
|
||||
@@ -1784,10 +1828,6 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
id="scoreButton${score}" onclick="selectFivePointScore(${score})">${score}</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
<label>评价人</label>
|
||||
<input id="evaluator" value="${escapeAttr(manual.evaluator || "manual")}" />
|
||||
<label>备注</label>
|
||||
<textarea id="manualNotes">${escapeHtml(manual.notes || "")}</textarea>
|
||||
<div class="toolbar">
|
||||
<button onclick="saveFivePointManual('${escapeAttr(record.session.platform_type_id)}','${escapeAttr(record.effect_id)}', false)">
|
||||
保存人工评分
|
||||
@@ -1796,11 +1836,11 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2>回复内容</h2>
|
||||
${renderBotReplyCard(reply.reply_text || "")}
|
||||
${renderChatMessageCard(botReply)}
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2>后续消息</h2>
|
||||
${renderFollowupCards(followups)}
|
||||
${renderMessageCards(normalizedFollowups, "暂无")}
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2>完整 JSON</h2>
|
||||
@@ -1877,6 +1917,9 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
const followups = record.followup_messages || [];
|
||||
currentTargetMessageId = String(reply.target_message_id || "");
|
||||
const context = normalizeContextMessages(record.context_snapshot || []);
|
||||
const normalizedFollowups = normalizeFollowupMessages(followups);
|
||||
const botReply = normalizeBotReply(reply);
|
||||
buildCurrentMessageIndex(context, botReply, normalizedFollowups);
|
||||
document.getElementById("detailPane").innerHTML = `
|
||||
<div class="rate-top">
|
||||
<div class="toolbar">
|
||||
@@ -1904,11 +1947,11 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2>Bot 回复</h2>
|
||||
${renderBotReplyCard(reply.reply_text || "")}
|
||||
${renderChatMessageCard(botReply)}
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2>后续消息</h2>
|
||||
${renderFollowupCards(followups)}
|
||||
${renderMessageCards(normalizedFollowups, "暂无")}
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2>人工五点评分</h2>
|
||||
@@ -1918,10 +1961,6 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
<button class="score-button" id="scoreButton${score}" onclick="selectFivePointScore(${score})">${score}</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
<label>评价人</label>
|
||||
<input id="ratingEvaluator" value="manual" />
|
||||
<label>备注</label>
|
||||
<textarea id="ratingNotes"></textarea>
|
||||
<div class="toolbar">
|
||||
<button onclick="saveFivePointManual('${escapeAttr(record.session.platform_type_id)}','${escapeAttr(record.effect_id)}', true)">
|
||||
保存并下一条
|
||||
@@ -1950,8 +1989,8 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
effect_id: effectId,
|
||||
manual_score_5: selectedFivePointScore,
|
||||
manual_label: "",
|
||||
evaluator: valueOf("ratingEvaluator") || valueOf("evaluator") || "manual",
|
||||
notes: valueOf("ratingNotes") || valueOf("manualNotes"),
|
||||
evaluator: currentEvaluator(),
|
||||
notes: "",
|
||||
};
|
||||
try {
|
||||
await api("/api/annotations", {
|
||||
@@ -1974,16 +2013,20 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
const items = Array.isArray(context) ? context : [];
|
||||
return items.filter(item => !isToolContextMessage(item)).map((item, index) => {
|
||||
const parsed = parseVisibleText(item.text || "");
|
||||
const rawText = parsed.content || item.text || "";
|
||||
const messageId = item.message_id || parsed.messageId || "";
|
||||
const attachments = Array.isArray(item.attachments) ? item.attachments : [];
|
||||
return {
|
||||
index,
|
||||
role: item.role || parsed.role || "message",
|
||||
source: item.source || "",
|
||||
timestamp: item.timestamp || parsed.time || "",
|
||||
name: parsed.name || roleName(item.role, item.source),
|
||||
messageId: parsed.messageId || "",
|
||||
text: cleanMessageText(parsed.content || item.text || ""),
|
||||
attachments: Array.isArray(item.attachments) ? item.attachments : [],
|
||||
isTarget: parsed.messageId && String(parsed.messageId) === String(selectedTargetMessageId()),
|
||||
messageId,
|
||||
text: cleanMessageText(rawText, attachments),
|
||||
quoteTargetIds: quoteTargetIdsFromMessage(item, rawText),
|
||||
attachments,
|
||||
isTarget: messageId && String(messageId) === String(selectedTargetMessageId()),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -2000,9 +2043,23 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
|
||||
function parseVisibleText(text) {
|
||||
const value = String(text || "");
|
||||
const pattern = /^(?<time>\d{1,2}:\d{2}:\d{2})?(?:\[msg_id:(?<messageId>[^\]]+)\])?(?:\[(?<name>[^\]]+)\])?(?<content>[\s\S]*)$/;
|
||||
const plannerPattern = /^\s*\[时间\](?<time>[^\n]*)\n\[用户名\](?<name>[^\n]*)\n(?:\[用户群昵称\][^\n]*\n)?(?:\[msg_id\](?<plannerMessageId>[^\n]*)\n)?\[发言内容\](?<plannerContent>[\s\S]*)$/;
|
||||
const plannerMatch = value.match(plannerPattern);
|
||||
if (plannerMatch && plannerMatch.groups) {
|
||||
return {
|
||||
time: (plannerMatch.groups.time || "").trim(),
|
||||
messageId: (plannerMatch.groups.plannerMessageId || "").trim(),
|
||||
name: (plannerMatch.groups.name || "").trim(),
|
||||
content: (plannerMatch.groups.plannerContent || "").trim(),
|
||||
};
|
||||
}
|
||||
|
||||
const pattern = /^\s*(?<time>\d{1,2}:\d{2}:\d{2})?(?:\[msg_id(?::|\])(?<messageId>[^\]\n]+)\]?)?(?:\[(?<name>[^\]]+)\])?(?<content>[\s\S]*)$/;
|
||||
const match = value.match(pattern);
|
||||
if (!match || !match.groups) return { content: value };
|
||||
if (!match.groups.time && !match.groups.messageId && match.groups.name && !(match.groups.content || "").trim()) {
|
||||
return { content: value };
|
||||
}
|
||||
return {
|
||||
time: match.groups.time || "",
|
||||
messageId: match.groups.messageId || "",
|
||||
@@ -2015,8 +2072,71 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
return currentTargetMessageId;
|
||||
}
|
||||
|
||||
function renderMessageCards(messages) {
|
||||
if (!messages.length) return `<div class="empty">暂无上下文</div>`;
|
||||
function normalizeBotReply(reply) {
|
||||
const metadata = reply.reply_metadata || {};
|
||||
const sentIds = Array.isArray(metadata.sent_message_ids) ? metadata.sent_message_ids : [];
|
||||
return {
|
||||
side: "bot",
|
||||
role: "assistant",
|
||||
source: "guided_reply",
|
||||
name: "Bot",
|
||||
timestamp: "本次回复",
|
||||
messageId: sentIds[0] || "",
|
||||
messageIds: sentIds,
|
||||
text: cleanMessageText(reply.reply_text || "", []),
|
||||
quoteTargetIds: [],
|
||||
attachments: [],
|
||||
isTarget: false,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeFollowupMessages(followups) {
|
||||
if (!followups || !followups.length) return [];
|
||||
return followups.map(message => {
|
||||
const rawText = message.visible_text || message.plain_text || "";
|
||||
const parsed = parseVisibleText(rawText);
|
||||
const attachments = Array.isArray(message.attachments) ? message.attachments : [];
|
||||
const messageId = message.message_id || parsed.messageId || "";
|
||||
return {
|
||||
side: "user",
|
||||
role: "user",
|
||||
source: "followup",
|
||||
name: `${userName(message) || parsed.name}${message.is_target_user ? " · 目标用户" : ""}`,
|
||||
timestamp: message.timestamp || "",
|
||||
messageId,
|
||||
text: cleanMessageText(parsed.content || rawText, attachments),
|
||||
quoteTargetIds: quoteTargetIdsFromMessage(message, rawText),
|
||||
attachments,
|
||||
isTarget: message.is_target_user,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function buildCurrentMessageIndex(contextMessages, botReply, followupMessages) {
|
||||
currentMessageIndex = new Map();
|
||||
[...contextMessages, botReply, ...followupMessages].forEach(message => {
|
||||
const ids = [message.messageId, ...(Array.isArray(message.messageIds) ? message.messageIds : [])]
|
||||
.map(id => String(id || "").trim())
|
||||
.filter(Boolean);
|
||||
ids.forEach(id => {
|
||||
if (!currentMessageIndex.has(id)) currentMessageIndex.set(id, message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function quoteTargetIdsFromMessage(message, text) {
|
||||
const structuredIds = Array.isArray(message.quote_target_ids) ? message.quote_target_ids : [];
|
||||
const textIds = [];
|
||||
String(text || "").replace(/\[引用回复\]\(([^)]+)\)/g, (_, id) => {
|
||||
const normalizedId = String(id || "").trim();
|
||||
if (normalizedId) textIds.push(normalizedId);
|
||||
return "";
|
||||
});
|
||||
return [...new Set([...structuredIds, ...textIds].map(id => String(id || "").trim()).filter(Boolean))];
|
||||
}
|
||||
|
||||
function renderMessageCards(messages, emptyText = "暂无上下文") {
|
||||
if (!messages.length) return `<div class="empty">${escapeHtml(emptyText)}</div>`;
|
||||
return messages.map(message => {
|
||||
const side = isBotContextMessage(message) ? "bot" : "user";
|
||||
return renderChatMessageCard({
|
||||
@@ -2027,43 +2147,13 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
timestamp: message.timestamp,
|
||||
messageId: message.messageId,
|
||||
text: message.text,
|
||||
quoteTargetIds: message.quoteTargetIds || [],
|
||||
attachments: message.attachments,
|
||||
isTarget: message.isTarget,
|
||||
});
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function renderBotReplyCard(text) {
|
||||
return renderChatMessageCard({
|
||||
side: "bot",
|
||||
role: "assistant",
|
||||
source: "guided_reply",
|
||||
name: "Bot",
|
||||
timestamp: "本次回复",
|
||||
messageId: "",
|
||||
text,
|
||||
attachments: [],
|
||||
isTarget: false,
|
||||
});
|
||||
}
|
||||
|
||||
function renderFollowupCards(followups) {
|
||||
if (!followups || !followups.length) return `<div class="empty">暂无</div>`;
|
||||
return followups.map(message => `
|
||||
${renderChatMessageCard({
|
||||
side: "user",
|
||||
role: "user",
|
||||
source: "followup",
|
||||
name: `${userName(message)}${message.is_target_user ? " · 目标用户" : ""}`,
|
||||
timestamp: message.timestamp || "",
|
||||
messageId: message.message_id || "",
|
||||
text: cleanMessageText(message.visible_text || message.plain_text || ""),
|
||||
attachments: Array.isArray(message.attachments) ? message.attachments : [],
|
||||
isTarget: message.is_target_user,
|
||||
})}
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function renderChatMessageCard(message) {
|
||||
const messageIdText = message.messageId ? ` · ${escapeHtml(message.messageId)}` : "";
|
||||
const textHtml = message.text ? `<div class="message-text">${escapeHtml(message.text)}</div>` : "";
|
||||
@@ -2074,6 +2164,7 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
<span class="message-name">${escapeHtml(message.name || "消息")}</span>
|
||||
<span>${escapeHtml(message.timestamp || "")}${messageIdText}</span>
|
||||
</div>
|
||||
${renderQuoteCards(message.quoteTargetIds || [])}
|
||||
${textHtml}
|
||||
${renderAttachments(message.attachments || [])}
|
||||
</div>
|
||||
@@ -2081,6 +2172,35 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderQuoteCards(quoteTargetIds) {
|
||||
if (!quoteTargetIds || !quoteTargetIds.length) return "";
|
||||
return quoteTargetIds.map(targetId => {
|
||||
const quoted = currentMessageIndex.get(String(targetId || ""));
|
||||
if (!quoted) {
|
||||
return `
|
||||
<div class="quote-card missing">
|
||||
<div class="quote-title">
|
||||
<span>引用回复</span>
|
||||
<span>${escapeHtml(targetId)}</span>
|
||||
</div>
|
||||
<div class="quote-text">未在本记录的上下文或后续消息中找到这条消息</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
const quotedName = quoted.name || roleName(quoted.role, quoted.source);
|
||||
const quotedText = quoted.text || attachmentSummary(quoted.attachments || []) || "无文本内容";
|
||||
return `
|
||||
<div class="quote-card">
|
||||
<div class="quote-title">
|
||||
<span>引用 ${escapeHtml(quotedName)}</span>
|
||||
<span>${escapeHtml(targetId)}</span>
|
||||
</div>
|
||||
<div class="quote-text">${escapeHtml(quotedText)}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function renderAttachments(attachments) {
|
||||
const shown = (attachments || []).filter(item => attachmentUrl(item));
|
||||
if (!shown.length) return "";
|
||||
@@ -2089,7 +2209,6 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
${shown.map(item => `
|
||||
<div>
|
||||
<img class="message-image" src="${escapeAttr(attachmentUrl(item))}" alt="${escapeAttr(item.content || item.kind || "图片")}" loading="lazy" />
|
||||
${item.content ? `<div class="message-image-caption">${escapeHtml(item.content)}</div>` : ""}
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>
|
||||
@@ -2104,11 +2223,32 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
return "";
|
||||
}
|
||||
|
||||
function cleanMessageText(text) {
|
||||
return String(text || "")
|
||||
.replace(/\[图片\]/g, "")
|
||||
.replace(/\[表情包?\]/g, "")
|
||||
.trim();
|
||||
function cleanMessageText(text, attachments = []) {
|
||||
let normalized = stripVisibleMessagePrefix(String(text || "")).replace(/\[引用回复\]\([^)]+\)/g, "");
|
||||
const shownAttachments = (attachments || []).filter(item => attachmentUrl(item));
|
||||
if (shownAttachments.length) {
|
||||
normalized = normalized
|
||||
.replace(/\[图片\]/g, "")
|
||||
.replace(/\[表情包?\]/g, "");
|
||||
for (const attachment of shownAttachments) {
|
||||
const content = String(attachment.content || "").trim();
|
||||
if (!content) continue;
|
||||
normalized = normalized.split(content).join("");
|
||||
}
|
||||
}
|
||||
return normalized.trim();
|
||||
}
|
||||
|
||||
function stripVisibleMessagePrefix(text) {
|
||||
const parsed = parseVisibleText(text);
|
||||
if (parsed.content && parsed.content !== text) return parsed.content;
|
||||
return String(text || "");
|
||||
}
|
||||
|
||||
function attachmentSummary(attachments) {
|
||||
const count = Array.isArray(attachments) ? attachments.length : 0;
|
||||
if (!count) return "";
|
||||
return count === 1 ? "[图片]" : `[${count} 张图片]`;
|
||||
}
|
||||
|
||||
function isBotContextMessage(message) {
|
||||
@@ -2152,6 +2292,28 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
return element ? element.value : "";
|
||||
}
|
||||
|
||||
function currentEvaluator() {
|
||||
return valueOf("globalEvaluator").trim() || "manual";
|
||||
}
|
||||
|
||||
function saveGlobalEvaluator() {
|
||||
try {
|
||||
localStorage.setItem("replyEffectEvaluator", currentEvaluator());
|
||||
} catch (_err) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function restoreGlobalEvaluator() {
|
||||
const input = document.getElementById("globalEvaluator");
|
||||
if (!input) return;
|
||||
try {
|
||||
input.value = localStorage.getItem("replyEffectEvaluator") || "manual";
|
||||
} catch (_err) {
|
||||
input.value = "manual";
|
||||
}
|
||||
}
|
||||
|
||||
function scoreText(v) {
|
||||
return v === null || v === undefined || v === "" ? "N/A" : Number(v).toFixed(1);
|
||||
}
|
||||
@@ -2174,6 +2336,7 @@ INDEX_HTML_V3 = r"""<!doctype html>
|
||||
return escapeHtml(value).replace(/`/g, "`");
|
||||
}
|
||||
|
||||
restoreGlobalEvaluator();
|
||||
reloadAll();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user