Files
kefu/deploy/review-badges-20260917/patch_badges.py
T
2026-09-21 10:34:06 +08:00

153 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pathlib import Path
import os, shutil
ROOTS = (Path('C:/wechat_rpa'), Path('C:/kefu/wechat_rpa'))
def edit(root, name, transform):
path = root / name
old = path.read_text(encoding='utf-8-sig')
new = transform(old)
if new == old:
raise RuntimeError(f'No change: {path}')
backup = root / 'backups/review-badges-20260917' / name
backup.parent.mkdir(parents=True, exist_ok=True)
if not backup.exists(): shutil.copy2(path, backup)
tmp = path.with_name(path.name + '.review-edit')
tmp.write_text(new, encoding='utf-8')
os.replace(tmp, path)
def replace(s, old, new):
if s.count(old) != 1: raise RuntimeError(f'Expected once: {old[:100]} ({s.count(old)})')
return s.replace(old, new, 1)
def gui(s):
s = replace(s, 'from review_state import task_review_state', 'from review_state import task_review_state, pending_attention_counts')
pos = s.index('class QueuePage(')
s = s[:pos] + replace(s[pos:], ' logMessage = Signal(str, str)', ' attentionChanged = Signal(int, int)\n logMessage = Signal(str, str)')
s = replace(s, ' def _refresh_queue(self) -> None:\n', ' def _load_pending_states(self):\n """Refresh counts without rebuilding hidden queue widgets."""\n')
demo = ' self._showing_demo_tasks = not rows and "--qt-smoke-test" in sys.argv\n'
s = replace(s, demo, '')
marker = ' self.pending_states = {\n str(key): dict(state) for key, state in rows if isinstance(state, dict)\n }\n'
s = replace(s, marker, marker + ''' counts = pending_attention_counts(self.pending_states)
self.attentionChanged.emit(counts['review'], counts['human'])
return rows
def _refresh_queue(self) -> None:
rows = self._load_pending_states()
self._showing_demo_tasks = not rows and "--qt-smoke-test" in sys.argv
''')
s = replace(s, ' self.label = label\n', ' self.label = label\n self.attention_count = 0\n')
needle = ' @staticmethod\n def _line(painter: QPainter, *points: tuple[float, float]) -> None:'
s = replace(s, needle, ''' def set_attention_counts(self, review: int, human: int) -> None:
self.attention_count = max(0, review) + max(0, human)
detail = f"待处理 {self.attention_count} 个会话(待审核 {review},需人工 {human})"
self.setToolTip(detail if self.attention_count else "暂无待审核或需人工处理的会话")
self.setAccessibleName(f"{self.label},{detail}" if self.attention_count else self.label)
self.update()
''' + needle)
needle = ' painter.drawText(label_rect, Qt.AlignHCenter | Qt.AlignVCenter, self.label)\n'
s = replace(s, needle, needle + ''' if self.attention_count:
badge_text = str(self.attention_count) if self.attention_count <= 99 else "99+"
painter.setFont(_paint_font(ui_px(11), QFont.Weight.Bold))
height = ui_px(20)
width = max(height, painter.fontMetrics().horizontalAdvance(badge_text) + ui_px(10))
right = min(self.width() - ui_px(2), int(cx + ui_px(32)))
badge = QRect(right - width, ui_px(2), width, height)
painter.setPen(QPen(QColor("#FFFFFF"), ui_px(2)))
painter.setBrush(QColor("#E6374F"))
painter.drawRoundedRect(badge, height / 2, height / 2)
painter.setPen(QColor("#FFFFFF"))
painter.drawText(badge, Qt.AlignCenter, badge_text)
''')
needle = ' def select(self, index: int) -> None:\n'
s = replace(s, needle, ''' def set_attention_counts(self, review: int, human: int) -> None:
for button in self.nav_buttons[:2]:
button.set_attention_counts(review, human)
''' + needle)
needle = ' self.queue_page.logMessage.connect(self.append_log)\n'
s = replace(s, needle, needle + ''' self.queue_page.attentionChanged.connect(self.sidebar.set_attention_counts)
counts = pending_attention_counts(getattr(self.queue_page, "pending_states", {}))
self.sidebar.set_attention_counts(counts['review'], counts['human'])
''')
needle = ' "queueFilter": getattr(self.queue_page, "active_task_filter", "全部") or "全部",\n'
s = replace(s, needle, needle + ' "attention": pending_attention_counts(getattr(self.queue_page, "pending_states", {})),\n')
s = replace(s, ' review_states = [task_review_state(value) for value in (getattr(self.queue_page, "pending_states", {}) or {}).values()]', ' attention = pending_attention_counts(getattr(self.queue_page, "pending_states", {}))')
s = replace(s, 'str(sum(value["awaitingReview"] and not value["needsHuman"] for value in review_states))', 'str(attention["review"])')
s = replace(s, 'str(sum(value["needsHuman"] for value in review_states))', 'str(attention["human"])')
start = s.index(' def _refresh_queue_page(self) -> None:')
end = s.index('\n def ', start + 5)
section = s[start:end]
section = replace(section, ' self._schedule_console_push()', ''' else:
self.queue_page._load_pending_states()
self._schedule_console_push()''')
section = section.replace('队列页开着的时候,让它跟着机器人一起动。', '所有页面更新侧栏计数;仅在队列可见时重建队列内容。')
s = s[:start] + section + s[end:]
return s
def html(s):
for page in ('auto', 'queue'):
start = s.index('data-view="' + page + '"')
end = s.index('</button>', start)
part = s[start:end]
part = replace(part, '</svg></span>', '</svg><span class="nav-attention-badge" hidden aria-hidden="true"></span></span>')
s = s[:start] + part + s[end:]
return s
def js(s):
s = replace(s, 'function syncOrb(){', '''function syncAttentionBadges(){
const counts = STATE.attention || STATE.metrics || {};
const review = Math.max(0, Math.trunc(Number(counts.review) || 0));
const human = Math.max(0, Math.trunc(Number(counts.human) || 0));
const total = review + human;
const detail = total ? `待处理 ${total} 个会话(待审核 ${review},需人工 ${human})` : "暂无待审核或需人工处理的会话";
[["auto", "自动回复"], ["queue", "任务队列"]].forEach(([view, label]) => {
const button = document.querySelector(`.nav-btn[data-view="${view}"]`);
const badge = button && button.querySelector(".nav-attention-badge");
if (!badge) return;
badge.hidden = total === 0;
badge.textContent = total > 99 ? "99+" : String(total);
button.title = detail;
button.setAttribute("aria-label", `${label},${detail}`);
});
}
function syncOrb(){
syncAttentionBadges();''')
return s
for root in ROOTS:
edit(root, 'review_state.py', lambda s:s + '''
def pending_attention_counts(pending):
"""Count conversations, regardless of the visible queue filter or page."""
review = human = 0
for state in (pending or {}).values():
if not isinstance(state, dict):
continue
flags = task_review_state(state)
if flags['needsHuman']:
human += 1
elif flags['awaitingReview']:
review += 1
return {'review': review, 'human': human, 'total': review + human}
''')
edit(root, 'wechat_gui_qt.py', gui)
edit(root, 'assets/ui/zhenyang-ai-console.html', html)
edit(root, 'assets/ui/console-app.js', js)
edit(root, 'assets/ui/console-review.css', lambda s:s + '''
/* Counts remain legible in logical CSS pixels at every display scale. */
#stage .nav-icon { position:relative; }
#stage .nav-attention-badge {
position:absolute; top:-4px; right:-9px; z-index:2; display:flex;
align-items:center; justify-content:center; box-sizing:border-box;
min-width:22px; height:22px; padding:0 5px; border:2px solid white;
border-radius:999px; background:#e6374f; color:white;
font-size:11px; font-weight:700; line-height:18px;
font-variant-numeric:tabular-nums; pointer-events:none;
}
#stage .nav-attention-badge[hidden] { display:none; }
''')
print('Patched badges:', root)