From 1c7d885f0692c246ce333296de7b31cd7f1e740b Mon Sep 17 00:00:00 2001 From: YumemiDream <1803068130@qq.com> Date: Sat, 6 Jun 2026 14:45:53 +0800 Subject: [PATCH 1/2] Remove misleading jargon occurrence sort --- web_res/static/html/dashboard.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/web_res/static/html/dashboard.html b/web_res/static/html/dashboard.html index d61e32b4..6e047578 100644 --- a/web_res/static/html/dashboard.html +++ b/web_res/static/html/dashboard.html @@ -2530,7 +2530,6 @@

黑话与批次

- @@ -6563,8 +6562,6 @@

${escapeHtml(item.title)}

items.reverse(); } else if (state.jargon.sort === 'name') { items.sort((a, b) => (a.term || a.word || '').localeCompare(b.term || b.word || '')); - } else if (state.jargon.sort === 'occurrences') { - items.sort((a, b) => (b.occurrences || 0) - (a.occurrences || 0)); } state.jargon.items = items; @@ -6617,7 +6614,7 @@

${escapeHtml(item.title)}

['候选', jargonStats.total_candidates], ['确认', jargonStats.confirmed_jargon], ['完成', jargonStats.completed_inference], - ['出现', jargonStats.total_occurrences], + ['命中', jargonStats.total_occurrences], ].map(([label, value]) => `
${label}
@@ -6635,7 +6632,7 @@

${escapeHtml(item.title)}

const meta = [ item.group_id ? `群组 ${item.group_id}` : null, item.is_confirmed !== undefined ? (item.is_confirmed ? '已确认' : '待确认') : null, - item.occurrences !== undefined ? `出现 ${formatCount(item.occurrences)}` : null, + item.occurrences !== undefined ? `命中 ${formatCount(item.occurrences)}` : null, item.created_at ? `创建 ${formatTime(item.created_at)}` : null, item.updated_at ? `更新 ${formatTime(item.updated_at)}` : null, ].filter(Boolean).join(' · '); From d153989adf11c12e90dbf046d53f3fa78e3583d8 Mon Sep 17 00:00:00 2001 From: YumemiDream <1803068130@qq.com> Date: Sat, 6 Jun 2026 14:51:55 +0800 Subject: [PATCH 2/2] refactor: rework review queue with internal sidebar workspace - Add a dedicated review workspace layout with sticky sidebar containing 6 category buttons: persona / style / jargon / persona-state / reviewed / batches - Each button shows live count badges; counts update whenever persona, style, jargon, persona-state, backup or batch data is loaded - Add setReviewTab(tab) / updateReviewNavCounts(counts) helpers and bind button clicks + page-revisit restore (state.reviews.tab) - Add new integration test test_dashboard_review_queue_uses_sidebar_categories to lock in the new structure --- tests/integration/test_webui_static_assets.py | 15 + web_res/static/html/dashboard.html | 599 ++++++++++++++---- 2 files changed, 495 insertions(+), 119 deletions(-) diff --git a/tests/integration/test_webui_static_assets.py b/tests/integration/test_webui_static_assets.py index e435fded..87658249 100644 --- a/tests/integration/test_webui_static_assets.py +++ b/tests/integration/test_webui_static_assets.py @@ -72,6 +72,21 @@ def test_dashboard_review_details_use_backend_structured_fields(): assert "renderContextExamples(item)" in text +def test_dashboard_review_queue_uses_sidebar_categories(): + text = (PLUGIN_ROOT / "web_res" / "static" / "html" / "dashboard.html").read_text(encoding="utf-8") + + assert "review-sidebar" in text + assert "review-workspace" in text + assert 'data-review-tab="persona"' in text + assert 'data-review-tab="style"' in text + assert 'data-review-tab="jargon"' in text + assert 'data-review-tab="persona-state"' in text + assert 'data-review-tab="reviewed"' in text + assert 'data-review-tab="batches"' in text + assert "setReviewTab" in text + assert "updateReviewNavCounts" in text + + def test_dashboard_review_deletes_use_inline_confirmation(): text = (PLUGIN_ROOT / "web_res" / "static" / "html" / "dashboard.html").read_text(encoding="utf-8") diff --git a/web_res/static/html/dashboard.html b/web_res/static/html/dashboard.html index 6e047578..fe671a20 100644 --- a/web_res/static/html/dashboard.html +++ b/web_res/static/html/dashboard.html @@ -769,6 +769,164 @@ gap: 16px; } + .review-workspace { + display: grid; + grid-template-columns: 280px minmax(0, 1fr); + gap: 16px; + align-items: start; + } + + .review-sidebar { + position: sticky; + top: 16px; + padding: 14px; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--panel); + box-shadow: var(--shadow-sm); + } + + .review-sidebar-head { + padding: 2px 4px 12px; + border-bottom: 1px solid var(--border); + margin-bottom: 12px; + } + + .review-sidebar-kicker { + color: var(--muted); + font-size: 11px; + font-weight: 700; + } + + .review-sidebar-head h3 { + margin: 4px 0 2px; + font-size: var(--text-lg); + line-height: 1.25; + } + + .review-sidebar-head p { + margin: 0; + color: var(--muted); + font-size: 12px; + } + + .review-nav { + display: grid; + gap: 8px; + } + + .review-nav-group { + margin: 14px 4px 8px; + color: var(--muted); + font-size: 11px; + font-weight: 700; + } + + .review-nav-btn { + width: 100%; + min-height: 58px; + display: grid; + grid-template-columns: 32px minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + padding: 10px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: var(--text); + cursor: pointer; + text-align: left; + } + + .review-nav-btn:hover { + border-color: var(--border); + background: var(--surface-hover); + } + + .review-nav-btn.active { + border-color: var(--accent); + background: var(--selection); + box-shadow: inset 3px 0 0 var(--accent); + } + + .review-nav-icon { + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--panel-soft); + color: var(--accent); + } + + .review-nav-icon .material-icons { + font-size: 18px; + } + + .review-nav-text { + min-width: 0; + } + + .review-nav-title { + display: block; + font-size: 13px; + font-weight: 700; + line-height: 1.2; + } + + .review-nav-desc { + display: block; + margin-top: 3px; + color: var(--muted); + font-size: 12px; + line-height: 1.25; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .review-nav-badge { + min-width: 28px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: var(--radius-full); + background: var(--panel-soft); + color: var(--muted); + font-size: 12px; + font-weight: 700; + } + + .review-nav-btn.active .review-nav-badge { + border-color: transparent; + background: var(--accent); + color: #fff; + } + + .review-main { + min-width: 0; + } + + .review-tab-panel { + display: none; + gap: 16px; + } + + .review-tab-panel.active { + display: grid; + } + + .review-split-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(300px, 0.85fr); + gap: 16px; + } + .panel { background: var(--panel); border: 1px solid var(--border); @@ -2007,6 +2165,15 @@ grid-template-columns: 1fr; } + .review-workspace, + .review-split-grid { + grid-template-columns: 1fr; + } + + .review-sidebar { + position: static; + } + .settings-head { flex-direction: column; align-items: stretch; @@ -2046,6 +2213,14 @@ grid-template-columns: 1fr; } + .review-sidebar { + padding: 12px; + } + + .review-nav-btn { + min-height: 54px; + } + .panel-head { flex-direction: column; } @@ -2456,134 +2631,210 @@

审查队列

审查操作就绪。
-
-
-
-

当前人格状态

- -- -
-
-
-
-
-
- -
-
-

人格备份

- -- -
-
-
-
-
-
- -
-
-
-

待审人格

- 0 -
-
-
-
-
- -
-
-

风格审查

- 0 -
-
-
-
-
-
+
+ + +
+
+
+
+

待审人格

+ 0 +
+
+
+
+
+
-
-
-
-

黑话与批次

- -- -
-
- - - -
-
-
-
+ + +
+
+ + -
- -- -
- - +
+ + +
-
- +
@@ -2872,6 +3123,9 @@

手动安装依赖

mirror: localStorage.getItem('sl-pip-mirror') || 'default', }, actionBusy: new Set(), + reviews: { + tab: 'persona', + }, personaState: { groupId: 'default', }, @@ -3867,6 +4121,15 @@

手动安装依赖

return map[target] || 'home'; } + function getReviewTabForInsightTarget(target) { + const map = { + reviews: 'persona', + jargon: 'jargon', + batches: 'batches', + }; + return map[target] || null; + } + function jumpToInsightTarget(target) { const targetId = getInsightTargetId(target); if (!targetId) { @@ -3874,6 +4137,10 @@

手动安装依赖

} navigateToPage(getInsightTargetPage(target), { keepScroll: true }); + const reviewTab = getReviewTabForInsightTarget(target); + if (reviewTab) { + setReviewTab(reviewTab); + } window.setTimeout(() => { const element = $(targetId); if (!element) { @@ -4078,6 +4345,72 @@

手动安装依赖

return `置信 ${formatPercent(score <= 1 ? score * 100 : score)}`; } + function setReviewTab(tab) { + const validTabs = ['persona', 'style', 'jargon', 'persona-state', 'reviewed', 'batches']; + const nextTab = validTabs.includes(tab) ? tab : 'persona'; + state.reviews.tab = nextTab; + + document.querySelectorAll('[data-review-tab]').forEach((button) => { + const active = button.dataset.reviewTab === nextTab; + button.classList.toggle('active', active); + button.setAttribute('aria-selected', active ? 'true' : 'false'); + }); + + document.querySelectorAll('[data-review-panel]').forEach((panel) => { + const active = panel.dataset.reviewPanel === nextTab; + panel.classList.toggle('active', active); + panel.hidden = !active; + }); + } + + function updateReviewNavCounts(counts = {}) { + const setText = (id, value) => { + const target = $(id); + if (target) { + target.textContent = value; + } + }; + + const dashboardData = state.data || {}; + const persona = dashboardData.persona || {}; + const personaUpdates = safeArray(persona.updates); + const style = dashboardData.style || {}; + const personaReviewed = dashboardData.personaReviewed || {}; + const personaBackups = dashboardData.personaBackups || {}; + const jargonStats = extractJargonStats(dashboardData.jargonStats); + const styleBacklogDefault = safeNumber(style.total); + const personaTotal = safeNumber(persona.total); + const personaIncludesStyle = personaUpdates.some((item) => item && item.review_source === 'style_learning'); + const personaBacklogDefault = personaIncludesStyle ? Math.max(0, personaTotal - styleBacklogDefault) : personaTotal; + const pendingJargonDefault = Math.max( + 0, + safeNumber(jargonStats.total_candidates ?? jargonStats.totalCandidates) + - safeNumber(jargonStats.confirmed_jargon ?? jargonStats.confirmedJargon), + ); + const backupTotalDefault = personaBackups.available === false + ? 0 + : safeNumber(personaBackups.total || safeArray(personaBackups.backups).length); + const valueOrDefault = (value, fallback) => ( + value === undefined || value === null ? fallback : safeNumber(value) + ); + + const personaBacklog = valueOrDefault(counts.personaBacklog, personaBacklogDefault); + const styleBacklog = valueOrDefault(counts.styleBacklog, styleBacklogDefault); + const pendingJargon = valueOrDefault(counts.pendingJargon, pendingJargonDefault); + const reviewedTotal = valueOrDefault(counts.reviewedTotal, safeNumber(personaReviewed.total)); + const backupTotal = valueOrDefault(counts.backupTotal, backupTotalDefault); + const batchTotal = valueOrDefault(counts.batchTotal, state.batch.total); + const pendingTotal = personaBacklog + styleBacklog + pendingJargon; + + setText('reviewSidebarSummary', `待处理 ${formatCount(pendingTotal)} 条`); + setText('reviewNavPersonaCount', formatCount(personaBacklog)); + setText('reviewNavStyleCount', formatCount(styleBacklog)); + setText('reviewNavJargonCount', formatCount(pendingJargon)); + setText('reviewNavPersonaStateCount', formatCount(backupTotal)); + setText('reviewNavReviewedCount', formatCount(reviewedTotal)); + setText('reviewNavBatchCount', formatCount(batchTotal)); + } + function actionButton(action, id, icon, label, tone = '') { if (id === undefined || id === null || id === '') { return ''; @@ -4354,6 +4687,9 @@

手动安装依赖

const personaTotal = safeNumber(persona.total); const personaPayloadIncludesStyle = personaUpdates.some((item) => item && item.review_source === 'style_learning'); const personaBacklog = personaPayloadIncludesStyle ? Math.max(0, personaTotal - styleBacklog) : personaTotal; + const totalJargonCandidates = safeNumber(jargonStats.total_candidates ?? jargonStats.totalCandidates); + const confirmedJargon = safeNumber(jargonStats.confirmed_jargon ?? jargonStats.confirmedJargon); + const pendingJargon = Math.max(0, totalJargonCandidates - confirmedJargon); const backlog = personaBacklog + styleBacklog; const overallHealth = textOrDash(health.overall || 'unknown'); @@ -4468,6 +4804,17 @@

手动安装依赖

$('styleList').innerHTML = renderStyleQueue(styleUpdatesFromPersona.length ? styleUpdatesFromPersona : style.reviews); $('reviewedPersonaList').innerHTML = renderReviewedPersonaHistory(personaReviewed.updates); renderPersonaStatePanel(personaState, personaBackups); + updateReviewNavCounts({ + personaBacklog, + styleBacklog, + pendingJargon, + reviewedTotal: personaReviewed.total || 0, + backupTotal: personaBackups.available === false + ? 0 + : (personaBackups.total || safeArray(personaBackups.backups).length), + batchTotal: state.batch.total, + }); + setReviewTab(state.reviews.tab); $('trendHint').textContent = `最近 ${Object.keys(trends.daily_messages || {}).length || 7} 天`; @@ -6690,6 +7037,13 @@

${escapeHtml(item.title)}

$('jargonNextBtn').disabled = page >= totalPages; $('jargonHint').textContent = `候选 ${formatCount((jargonStats.total_candidates ?? jargonStats.totalCandidates) || 0)} · 第 ${page}/${totalPages} 页`; + updateReviewNavCounts({ + pendingJargon: Math.max( + 0, + safeNumber(jargonStats.total_candidates ?? jargonStats.totalCandidates) + - safeNumber(jargonStats.confirmed_jargon ?? jargonStats.confirmedJargon), + ), + }); } function renderBatchPanel() { @@ -6727,6 +7081,7 @@

${escapeHtml(item.title)}

$('batchPrevBtn').disabled = page <= 1; $('batchNextBtn').disabled = page >= totalPages; $('batchHint').textContent = `共 ${formatCount(total)} 个批次`; + updateReviewNavCounts({ batchTotal: total }); } function bindEvents() { @@ -6869,6 +7224,12 @@

${escapeHtml(item.title)}

return; } + const reviewTab = event.target.closest('[data-review-tab]'); + if (reviewTab) { + setReviewTab(reviewTab.dataset.reviewTab); + return; + } + const graphTab = event.target.closest('[data-graph-type]'); if (graphTab) { state.graph.type = graphTab.dataset.graphType === 'knowledge' ? 'knowledge' : 'memory';