Commit 4f519e39f16cdbe60f64620bee3cc92c24c0e69a

Authored by yibo
1 parent b683af4a

feat(wechat-auto-reply): auto_send 总开关搬进 viewer /whitelist UI

- app.py 加 GET/POST /api/auto_send:读写 policy_config.json 的
  auto_send_enabled(root 属主 644,写经 tmp+su cp,只翻该字段保留 block_keywords)
- whitelist.html 顶部加开关卡片(开=白名单自动发/敏感词仍拦,关=全审批),开启二次确认
- orchestrator 每轮 load_config() 即时生效,无需重启
- 真机验证:读接口 enabled=false、POST true/false 文件正确翻转其余字段完整、/whitelist 200
- README 待收尾 #4 标完成

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wechat_plain/scripts/auto-reply/README.md
... ... @@ -88,7 +88,7 @@ python wechat_reply_orchestrator.py --drain # 手动跑一轮编排
88 88 1. **原生引用气泡 / 群@昵称前缀**:现发纯文本;微信原生引用(A11y 长按→引用)是环节5 增强。
89 89 2. **日志轮转**:watcher/orchestrator log 24/7 会长(ponytail 已标)。
90 90 3. **lifelog 人物增强**(二期):回链 `com.lemon.lifelog` 的 Person 库让回复带人物记忆。
91   -4. **auto_send 开关搬进 viewer UI**:目前在 policy_config.json,可加到 /whitelist 页。
  91 +4. ~~auto_send 开关搬进 viewer UI~~ ✅ **已做(2026-07-12)**:`/whitelist` 页顶部开关卡片,`GET/POST /api/auto_send` 读写 `policy_config.json`。
92 92  
93 93 ## 相关文档
94 94 - `../../../md/wechat-auto-reply-requirements.md` — 需求 + 逐环节设计/状态
... ...
wechat_plain/scripts/auto-reply/viewer_templates/whitelist.html
... ... @@ -36,9 +36,37 @@ body{overflow-y:auto!important;height:auto!important;}
36 36 .btn-confirm{background:#27ae60;color:white;border:none;border-radius:8px;padding:9px 20px;font-size:14px;cursor:pointer;}
37 37 .btn-confirm:disabled{opacity:.5;cursor:not-allowed;}
38 38 .loading{text-align:center;padding:30px;color:#999;}
  39 +/* 总开关卡片 */
  40 +.as-card{background:white;border-radius:8px;padding:14px 16px;margin-bottom:14px;box-shadow:0 1px 2px rgba(0,0,0,0.05);}
  41 +.as-row{display:flex;align-items:center;gap:12px;}
  42 +.as-txt{flex:1;}
  43 +.as-title{font-weight:600;font-size:15px;color:#2c3e50;}
  44 +.as-state{font-size:12px;margin-top:2px;}
  45 +.as-state.on{color:#27ae60;font-weight:600;}
  46 +.as-state.off{color:#999;}
  47 +.switch{position:relative;display:inline-block;width:52px;height:30px;flex-shrink:0;}
  48 +.switch input{opacity:0;width:0;height:0;}
  49 +.slider{position:absolute;cursor:pointer;inset:0;background:#ccc;border-radius:30px;transition:.2s;}
  50 +.slider:before{content:"";position:absolute;height:24px;width:24px;left:3px;bottom:3px;background:white;border-radius:50%;transition:.2s;}
  51 +.switch input:checked+.slider{background:#27ae60;}
  52 +.switch input:checked+.slider:before{transform:translateX(22px);}
  53 +.switch input:disabled+.slider{opacity:.5;cursor:not-allowed;}
  54 +.as-warn{font-size:12px;color:#b9770e;background:#fef9e7;border-radius:6px;padding:8px 10px;margin-top:10px;line-height:1.5;}
39 55 </style>
40 56 <div class="wl-container">
41 57 <div class="wl-header"><h1>⭐ 回复白名单</h1><button class="btn-add" onclick="openPicker()">➕ 添加</button></div>
  58 +
  59 +<div class="as-card">
  60 + <div class="as-row">
  61 + <div class="as-txt">
  62 + <div class="as-title">自动发送总开关</div>
  63 + <div class="as-state off" id="asState">加载中…</div>
  64 + </div>
  65 + <label class="switch"><input type="checkbox" id="asToggle" onchange="toggleAuto(this)" disabled><span class="slider"></span></label>
  66 + </div>
  67 + <div class="as-warn">开=白名单里的联系人/群<b>直接自动发</b>(钱/承诺/约时间/隐私等敏感词仍强制转人工,绝不误发)。关=一切都进「审批」等你手动点发。建议审核模式跑几天、草稿质量稳了再开。</div>
  68 +</div>
  69 +
42 70 <div class="wl-hint">白名单里的联系人 / 群,消息可被<b>自动回复</b>(仍受敏感词等安全规则拦截,命中转人工)。不在名单里的一律进「自动回复审批」等你手动发。</div>
43 71 <div id="list"></div>
44 72 </div>
... ... @@ -75,6 +103,30 @@ function load(){
75 103 }
76 104 function del(id){fetch('/api/whitelist/'+id+'/delete',{method:'POST'}).then(function(r){return r.json()}).then(function(j){if(j.ok)load();else alert('失败');});}
77 105  
  106 +function renderAuto(on){
  107 + var st=document.getElementById('asState'),tg=document.getElementById('asToggle');
  108 + tg.checked=!!on;tg.disabled=false;
  109 + st.textContent=on?'已开启 · 白名单自动发':'已关闭 · 全部人工审批';
  110 + st.className='as-state '+(on?'on':'off');
  111 +}
  112 +function loadAuto(){
  113 + fetch('/api/auto_send').then(function(r){return r.json()}).then(function(j){
  114 + if(j.ok)renderAuto(j.enabled);
  115 + else{document.getElementById('asState').textContent='读取失败';}
  116 + }).catch(function(){document.getElementById('asState').textContent='读取失败';});
  117 +}
  118 +function toggleAuto(cb){
  119 + var want=cb.checked;cb.disabled=true;
  120 + if(want&&!confirm('确定开启自动发送?白名单里的消息将无需你确认直接发出(敏感词仍会拦截转人工)。')){
  121 + cb.checked=false;cb.disabled=false;return;
  122 + }
  123 + fetch('/api/auto_send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({enabled:want})})
  124 + .then(function(r){return r.json()}).then(function(j){
  125 + if(j.ok)renderAuto(j.enabled);
  126 + else{alert('失败: '+(j.error||''));cb.checked=!want;cb.disabled=false;}
  127 + }).catch(function(){alert('网络失败');cb.checked=!want;cb.disabled=false;});
  128 +}
  129 +
78 130 function openPicker(){
79 131 selected={};updateCnt();
80 132 document.getElementById('q').value='';
... ... @@ -120,5 +172,6 @@ function confirmAdd(){
120 172 });
121 173 }
122 174 load();
  175 +loadAuto();
123 176 </script>
124 177 {% endblock %}
... ...
wechat_plain/scripts/auto-reply/wechat-viewer-changes.md
... ... @@ -34,6 +34,14 @@ CREATE TABLE reply_queue ( -- 策略判定后落地
34 34 | `GET /api/contacts` | 弹窗默认列表:会话过的联系人+群(排除公众号),去重带昵称,最近排序 |
35 35 | `GET /api/contacts/search?q=` | 按名字/wxid 搜全量 rcontact+群(`wechat_db.search_talker`) |
36 36 | `POST /api/whitelist`(升级) | 支持批量 `{items:[{wxid,name,kind}]}`(弹窗多选) |
  37 +| `GET /api/auto_send` | 读自动发总开关(`policy_config.json` 的 `auto_send_enabled`) |
  38 +| `POST /api/auto_send` | 设总开关 `{enabled:bool}`;只翻转该字段保留其余 |
  39 +
  40 +**自动发总开关搬进 UI(2026-07-12)**:`/whitelist` 页顶部加开关卡片(读 `/api/auto_send`,
  41 +拨动 POST 回写)。开=白名单可自动发(敏感词仍强制转人工),关=全部进审批。开启时前端二次确认。
  42 +`policy_config.json` 是 root 属主 644,viewer(termux 用户)可直接读、写时经 `_write_auto_send`
  43 +写 tmp 再 `su cp` 回(沿用本 app 的 `su` 模式,只改 `auto_send_enabled` 不动 block_keywords 等)。
  44 +orchestrator 每轮 `load_config()` 读文件即时生效,无需重启。
37 45  
38 46 **白名单选人弹窗(2026-07-10 升级)**:`/whitelist` 页「添加」不再手填 wxid,改为弹窗:
39 47 默认列出你会话过的联系人/群(带昵称),顶部搜索框可查全量通讯录/群,复选框**多选**,
... ...