diff --git a/wechat_plain/scripts/auto-reply/README.md b/wechat_plain/scripts/auto-reply/README.md
index 0807f47..b9f60bd 100644
--- a/wechat_plain/scripts/auto-reply/README.md
+++ b/wechat_plain/scripts/auto-reply/README.md
@@ -88,7 +88,7 @@ python wechat_reply_orchestrator.py --drain # 手动跑一轮编排
1. **原生引用气泡 / 群@昵称前缀**:现发纯文本;微信原生引用(A11y 长按→引用)是环节5 增强。
2. **日志轮转**:watcher/orchestrator log 24/7 会长(ponytail 已标)。
3. **lifelog 人物增强**(二期):回链 `com.lemon.lifelog` 的 Person 库让回复带人物记忆。
-4. **auto_send 开关搬进 viewer UI**:目前在 policy_config.json,可加到 /whitelist 页。
+4. ~~auto_send 开关搬进 viewer UI~~ ✅ **已做(2026-07-12)**:`/whitelist` 页顶部开关卡片,`GET/POST /api/auto_send` 读写 `policy_config.json`。
## 相关文档
- `../../../md/wechat-auto-reply-requirements.md` — 需求 + 逐环节设计/状态
diff --git a/wechat_plain/scripts/auto-reply/viewer_templates/whitelist.html b/wechat_plain/scripts/auto-reply/viewer_templates/whitelist.html
index 60e3523..384b09e 100644
--- a/wechat_plain/scripts/auto-reply/viewer_templates/whitelist.html
+++ b/wechat_plain/scripts/auto-reply/viewer_templates/whitelist.html
@@ -36,9 +36,37 @@ body{overflow-y:auto!important;height:auto!important;}
.btn-confirm{background:#27ae60;color:white;border:none;border-radius:8px;padding:9px 20px;font-size:14px;cursor:pointer;}
.btn-confirm:disabled{opacity:.5;cursor:not-allowed;}
.loading{text-align:center;padding:30px;color:#999;}
+/* 总开关卡片 */
+.as-card{background:white;border-radius:8px;padding:14px 16px;margin-bottom:14px;box-shadow:0 1px 2px rgba(0,0,0,0.05);}
+.as-row{display:flex;align-items:center;gap:12px;}
+.as-txt{flex:1;}
+.as-title{font-weight:600;font-size:15px;color:#2c3e50;}
+.as-state{font-size:12px;margin-top:2px;}
+.as-state.on{color:#27ae60;font-weight:600;}
+.as-state.off{color:#999;}
+.switch{position:relative;display:inline-block;width:52px;height:30px;flex-shrink:0;}
+.switch input{opacity:0;width:0;height:0;}
+.slider{position:absolute;cursor:pointer;inset:0;background:#ccc;border-radius:30px;transition:.2s;}
+.slider:before{content:"";position:absolute;height:24px;width:24px;left:3px;bottom:3px;background:white;border-radius:50%;transition:.2s;}
+.switch input:checked+.slider{background:#27ae60;}
+.switch input:checked+.slider:before{transform:translateX(22px);}
+.switch input:disabled+.slider{opacity:.5;cursor:not-allowed;}
+.as-warn{font-size:12px;color:#b9770e;background:#fef9e7;border-radius:6px;padding:8px 10px;margin-top:10px;line-height:1.5;}
+
+
+
+
开=白名单里的联系人/群直接自动发(钱/承诺/约时间/隐私等敏感词仍强制转人工,绝不误发)。关=一切都进「审批」等你手动点发。建议审核模式跑几天、草稿质量稳了再开。
+
+
白名单里的联系人 / 群,消息可被自动回复(仍受敏感词等安全规则拦截,命中转人工)。不在名单里的一律进「自动回复审批」等你手动发。
@@ -75,6 +103,30 @@ function load(){
}
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('失败');});}
+function renderAuto(on){
+ var st=document.getElementById('asState'),tg=document.getElementById('asToggle');
+ tg.checked=!!on;tg.disabled=false;
+ st.textContent=on?'已开启 · 白名单自动发':'已关闭 · 全部人工审批';
+ st.className='as-state '+(on?'on':'off');
+}
+function loadAuto(){
+ fetch('/api/auto_send').then(function(r){return r.json()}).then(function(j){
+ if(j.ok)renderAuto(j.enabled);
+ else{document.getElementById('asState').textContent='读取失败';}
+ }).catch(function(){document.getElementById('asState').textContent='读取失败';});
+}
+function toggleAuto(cb){
+ var want=cb.checked;cb.disabled=true;
+ if(want&&!confirm('确定开启自动发送?白名单里的消息将无需你确认直接发出(敏感词仍会拦截转人工)。')){
+ cb.checked=false;cb.disabled=false;return;
+ }
+ fetch('/api/auto_send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({enabled:want})})
+ .then(function(r){return r.json()}).then(function(j){
+ if(j.ok)renderAuto(j.enabled);
+ else{alert('失败: '+(j.error||''));cb.checked=!want;cb.disabled=false;}
+ }).catch(function(){alert('网络失败');cb.checked=!want;cb.disabled=false;});
+}
+
function openPicker(){
selected={};updateCnt();
document.getElementById('q').value='';
@@ -120,5 +172,6 @@ function confirmAdd(){
});
}
load();
+loadAuto();
{% endblock %}
diff --git a/wechat_plain/scripts/auto-reply/wechat-viewer-changes.md b/wechat_plain/scripts/auto-reply/wechat-viewer-changes.md
index cb02a4c..a08e42a 100644
--- a/wechat_plain/scripts/auto-reply/wechat-viewer-changes.md
+++ b/wechat_plain/scripts/auto-reply/wechat-viewer-changes.md
@@ -34,6 +34,14 @@ CREATE TABLE reply_queue ( -- 策略判定后落地
| `GET /api/contacts` | 弹窗默认列表:会话过的联系人+群(排除公众号),去重带昵称,最近排序 |
| `GET /api/contacts/search?q=` | 按名字/wxid 搜全量 rcontact+群(`wechat_db.search_talker`) |
| `POST /api/whitelist`(升级) | 支持批量 `{items:[{wxid,name,kind}]}`(弹窗多选) |
+| `GET /api/auto_send` | 读自动发总开关(`policy_config.json` 的 `auto_send_enabled`) |
+| `POST /api/auto_send` | 设总开关 `{enabled:bool}`;只翻转该字段保留其余 |
+
+**自动发总开关搬进 UI(2026-07-12)**:`/whitelist` 页顶部加开关卡片(读 `/api/auto_send`,
+拨动 POST 回写)。开=白名单可自动发(敏感词仍强制转人工),关=全部进审批。开启时前端二次确认。
+`policy_config.json` 是 root 属主 644,viewer(termux 用户)可直接读、写时经 `_write_auto_send`
+写 tmp 再 `su cp` 回(沿用本 app 的 `su` 模式,只改 `auto_send_enabled` 不动 block_keywords 等)。
+orchestrator 每轮 `load_config()` 读文件即时生效,无需重启。
**白名单选人弹窗(2026-07-10 升级)**:`/whitelist` 页「添加」不再手填 wxid,改为弹窗:
默认列出你会话过的联系人/群(带昵称),顶部搜索框可查全量通讯录/群,复选框**多选**,