Skip to content

Commit b0f4e4d

Browse files
wehosHongzhi Wenclaude
authored
前端"自主视觉"开关重整为"隐私模式",开启时禁用 user activity tracker (#1024)
* 前端"自主视觉"开关重整为"隐私模式",开启时禁用 user activity tracker - 前端 UI 仅改显示:toggle label 改"隐私模式"+ inverted: true,变量 proactiveVisionEnabled 语义不动;侧弹"基础间隔"改"感知间隔"。8 个 locale + 教学引导 step14 / 屏幕分享 tooltip 文案同步翻新。 - 后端隐私模式 = !proactiveVisionEnabled,新增 utils.preferences is_privacy_mode_enabled / ais_privacy_mode_enabled。 - main_logic/activity/tracker.py:on_user/ai_message 隐私模式下不把 text 写进 buffer(避免切回非隐私模式时旧消息被 enrichment LLM 回放), kickoff_open_threads_compute 直接 return,_activity_guess_loop 每 tick 检查跳过。state machine 时间戳照常更新,只是 LLM 通路全断。 - main_routers/system_router.py:proactive_chat 入口读隐私模式,开了 就把 activity_snapshot 设 None,PR #1015 设计时所有 gating 都已写 过 "if snapshot is not None" fallback —— restricted_screen_only / unfinished_thread / state_section / mark_unfinished_thread_used / _allow_reminiscence 全部退化到 PR #1015 之前的无限制搭话路径。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 按 PR review 修四处问题 - avatar-ui-drag.js / avatar-ui-popup-config.js:popup 重新打开时 re-sync 路径漏了 inverted 语义,会把 checkbox 强行写回 raw proactiveVisionEnabled,开了隐私模式重开 popup 又被翻回去(codex P1) - tracker.py / system_router.py 隐私模式 helper 改 fail-closed:读取 异常时按"隐私开启"处理。正常"用户没开隐私"路径不进 except,没影响 (coderabbit Major × 2) - pt.json / es.json 教程 step14 desc 里的英文 "Screen Share" 换成 本地化术语(葡:"Compartilhamento de tela",西:"Compartir pantalla") 和同 locale 已有的 proactiveVisionChat 翻译对齐(coderabbit Minor × 2) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0cc0306 commit b0f4e4d

15 files changed

Lines changed: 132 additions & 56 deletions

File tree

main_logic/activity/tracker.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,25 @@
7272
_EXTERNAL_SIGNAL_TTL_SECONDS = 30.0
7373

7474

75+
def _privacy_mode_active() -> bool:
76+
"""用户是否开启了隐私模式。开启时整个 tracker 应当短路。
77+
78+
存储在前端 ``proactiveVisionEnabled`` 的反面(详见 utils.preferences)。
79+
异常路径 fail-closed:任何读取异常一律按"隐私模式开启"处理,宁可
80+
短期内 tracker 不可用,也不能让"读不出来"等价于"用户没开隐私"。
81+
正常的"用户没开隐私"路径走 ``is_privacy_mode_enabled`` 返回 False,
82+
不进 except 分支。
83+
"""
84+
try:
85+
from utils.preferences import is_privacy_mode_enabled
86+
return is_privacy_mode_enabled()
87+
except Exception as e:
88+
logger.warning(
89+
'privacy mode check failed, defaulting to enabled (fail-closed): %s', e,
90+
)
91+
return True
92+
93+
7594
class UserActivityTracker:
7695
"""Per-character activity inference engine.
7796
@@ -155,7 +174,10 @@ def on_user_message(self, *, text: str | None = None, now: float | None = None)
155174
ts = now if now is not None else time.time()
156175
self._sm.update_user_message(now=ts)
157176
self._conv_seq += 1
158-
if text:
177+
# 隐私模式:让用户消息文本直接不进 buffer,避免在切回非隐私模式时
178+
# 旧数据被 enrichment LLM 二次曝光。state machine 的时间戳还要更新
179+
# (下游 idle / focused_work 判定依赖),文本扔了即可。
180+
if text and not _privacy_mode_active():
159181
self._user_msg_buffer.append((ts, text.strip()[:1000]))
160182

161183
def on_ai_message(self, *, text: str | None = None, now: float | None = None) -> None:
@@ -173,7 +195,7 @@ def on_ai_message(self, *, text: str | None = None, now: float | None = None) ->
173195
"""
174196
ts = now if now is not None else time.time()
175197
self._sm.update_ai_message(text=text, now=ts)
176-
if text:
198+
if text and not _privacy_mode_active():
177199
self._ai_msg_buffer.append((ts, text.strip()[:1000]))
178200
# AI also opens threads (promises, abandoned mid-sentences) →
179201
# bump _conv_seq so kickoff_open_threads_compute will recompute.
@@ -339,6 +361,9 @@ def kickoff_open_threads_compute(self, lang: str = 'zh') -> None:
339361
* If a previous task is still running → skip (don't queue).
340362
* If conversation buffers are empty → skip (nothing to score).
341363
"""
364+
# 隐私模式下整个 enrichment 通路都关掉。
365+
if _privacy_mode_active():
366+
return
342367
if self._open_threads_computed_at_seq == self._conv_seq:
343368
return
344369
if self._open_threads_task is not None and not self._open_threads_task.done():
@@ -410,6 +435,12 @@ async def _activity_guess_loop(self) -> None:
410435
except asyncio.CancelledError:
411436
return
412437

438+
# 隐私模式:本 tick 不读窗口/进程,也不调 LLM,直接进入下一轮。
439+
# 缓存自然衰减(保留最后一次值,proactive_chat 那边 snapshot 已被
440+
# gating 成 None,缓存不会被消费)。
441+
if _privacy_mode_active():
442+
continue
443+
413444
try:
414445
# Pull a fresh snapshot to compare against.
415446
ts = time.time()

main_routers/system_router.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3355,13 +3355,36 @@ def _proactive_preempted_json(where: str) -> dict:
33553355
# 在 enabled_modes 解析之前拉一次,因为 propensity 可能需要把
33563356
# enabled_modes 收紧到只剩 vision(restricted_screen_only 状态)。
33573357
# 详见 docs/design/user-activity-tracker.md。
3358+
#
3359+
# 隐私模式:用户开了"隐私模式"开关 → 临时禁用整个 user-activity-tracker,
3360+
# 回退到 PR #1015 之前的无限制策略。snapshot 留 None,下游所有 gating
3361+
# 都已在 PR #1015 设计时按 "snapshot is not None" 写过 fallback:
3362+
# - propensity 收紧(restricted_screen_only)→ 不触发
3363+
# - 反思/回忆 _allow_reminiscence → 默认放开
3364+
# - state_section 渲染 → 输出空串
3365+
# - mark_unfinished_thread_used → 不计数
3366+
# 所以这里把 snapshot 直接设 None 就够,等价于"tracker 不存在"。
3367+
from utils.preferences import ais_privacy_mode_enabled
33583368
try:
3359-
activity_snapshot = await mgr._activity_tracker.get_snapshot()
3360-
print(f"[{lanlan_name}] activity snapshot: state={activity_snapshot.state} "
3361-
f"propensity={activity_snapshot.propensity} reasons={activity_snapshot.propensity_reasons}")
3362-
except Exception as _act_err:
3363-
logger.warning(f"[{lanlan_name}] activity snapshot fetch failed: {_act_err}; falling back to open propensity")
3369+
privacy_mode = await ais_privacy_mode_enabled()
3370+
except Exception as _pm_err:
3371+
# fail-closed:读不出来按隐私开启处理。正常"用户没开隐私"是
3372+
# ais_privacy_mode_enabled 返回 False,不进这个 except。
3373+
logger.warning(
3374+
f"[{lanlan_name}] privacy mode check failed, defaulting to enabled: {_pm_err}",
3375+
)
3376+
privacy_mode = True
3377+
if privacy_mode:
3378+
print(f"[{lanlan_name}] 隐私模式开启,跳过 activity tracker,按无限制策略搭话")
33643379
activity_snapshot = None
3380+
else:
3381+
try:
3382+
activity_snapshot = await mgr._activity_tracker.get_snapshot()
3383+
print(f"[{lanlan_name}] activity snapshot: state={activity_snapshot.state} "
3384+
f"propensity={activity_snapshot.propensity} reasons={activity_snapshot.propensity_reasons}")
3385+
except Exception as _act_err:
3386+
logger.warning(f"[{lanlan_name}] activity snapshot fetch failed: {_act_err}; falling back to open propensity")
3387+
activity_snapshot = None
33653388

33663389
# ========== 解析 enabled_modes ==========
33673390
enabled_modes = data.get('enabled_modes', [])

static/avatar-ui-drag.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,11 @@ Live2DManager.prototype.showPopup = function (buttonId, popup) {
680680
}
681681

682682
// 更新 proactive vision checkbox 状态和视觉样式
683+
// 注意:UI 是"隐私模式",underlying 变量 proactiveVisionEnabled 语义相反,
684+
// 故 checked = !proactiveVisionEnabled。和 avatar-ui-popup.js 里 toggle
685+
// 配置 inverted: true 对齐。
683686
if (proactiveVisionCheckbox && typeof window.proactiveVisionEnabled !== 'undefined') {
684-
const newChecked = window.proactiveVisionEnabled;
687+
const newChecked = !window.proactiveVisionEnabled;
685688
if (proactiveVisionCheckbox.checked !== newChecked) {
686689
proactiveVisionCheckbox.checked = newChecked;
687690
}

static/avatar-ui-popup-config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ const _vrmPopupConfig = {
7777
syncCheckbox(document.querySelector(`#${prefix}-focus-mode`), !window.focusModeEnabled);
7878
syncCheckbox(document.querySelector(`#${prefix}-avatar-reaction-bubble`), window.avatarReactionBubbleEnabled);
7979
syncCheckbox(popup.querySelector(`#${prefix}-proactive-chat`), window.proactiveChatEnabled);
80-
syncCheckbox(popup.querySelector(`#${prefix}-proactive-vision`), window.proactiveVisionEnabled);
80+
// proactive-vision 走 inverted("隐私模式" UI 显示),与 avatar-ui-popup.js 对齐
81+
syncCheckbox(popup.querySelector(`#${prefix}-proactive-vision`), !window.proactiveVisionEnabled);
8182
syncCheckbox(popup.querySelector(`#${prefix}-mouse-tracking-toggle`), window.mouseTrackingEnabled);
8283
if (window.CHAT_MODE_CONFIG) {
8384
window.CHAT_MODE_CONFIG.forEach(config => {

static/avatar-ui-popup.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ function createSettingsPopupContent(manager, prefix, popup) {
336336
// 4. 主动搭话和自主视觉(角色设置已移至分隔线下方的导航菜单区域)
337337
const settingsToggles = [
338338
{ id: 'proactive-chat', label: window.t ? window.t('settings.toggles.proactiveChat') : '主动搭话', labelKey: 'settings.toggles.proactiveChat', storageKey: 'proactiveChatEnabled', hasInterval: true, intervalKey: 'proactiveChatInterval', defaultInterval: 15 },
339-
{ id: 'proactive-vision', label: window.t ? window.t('settings.toggles.proactiveVision') : '自主视觉', labelKey: 'settings.toggles.proactiveVision', storageKey: 'proactiveVisionEnabled', hasInterval: true, intervalKey: 'proactiveVisionInterval', defaultInterval: 15 }
339+
{ id: 'proactive-vision', label: window.t ? window.t('settings.toggles.proactiveVision') : '隐私模式', labelKey: 'settings.toggles.proactiveVision', storageKey: 'proactiveVisionEnabled', hasInterval: true, intervalKey: 'proactiveVisionInterval', defaultInterval: 15, inverted: true }
340340
];
341341

342342
settingsToggles.forEach(toggle => {
@@ -1589,7 +1589,7 @@ function createIntervalControl(manager, prefix, toggle) {
15891589
});
15901590

15911591
const labelKey = toggle.id === 'proactive-chat' ? 'settings.interval.chatIntervalBase' : 'settings.interval.visionInterval';
1592-
const defaultLabel = toggle.id === 'proactive-chat' ? '基础间隔' : '读取间隔';
1592+
const defaultLabel = toggle.id === 'proactive-chat' ? '感知间隔' : '读取间隔';
15931593
const labelText = document.createElement('span');
15941594
labelText.textContent = window.t ? window.t(labelKey) : defaultLabel;
15951595
labelText.setAttribute('data-i18n', labelKey);
@@ -1907,7 +1907,7 @@ function createSettingsToggleItem(manager, prefix, toggle) {
19071907
} else if (toggle.id === 'proactive-chat' && typeof window.proactiveChatEnabled !== 'undefined') {
19081908
checkbox.checked = window.proactiveChatEnabled;
19091909
} else if (toggle.id === 'proactive-vision' && typeof window.proactiveVisionEnabled !== 'undefined') {
1910-
checkbox.checked = window.proactiveVisionEnabled;
1910+
checkbox.checked = toggle.inverted ? !window.proactiveVisionEnabled : window.proactiveVisionEnabled;
19111911
} else if (toggle.id === 'fullscreen-tracking' && typeof window.live2dFullscreenTrackingEnabled !== 'undefined') {
19121912
checkbox.checked = window.live2dFullscreenTrackingEnabled;
19131913
}
@@ -2012,11 +2012,12 @@ function createSettingsToggleItem(manager, prefix, toggle) {
20122012
window.stopProactiveChatSchedule();
20132013
}
20142014
} else if (toggle.id === 'proactive-vision') {
2015-
window.proactiveVisionEnabled = isChecked;
2015+
const visionEnabled = toggle.inverted ? !isChecked : isChecked;
2016+
window.proactiveVisionEnabled = visionEnabled;
20162017
if (typeof window.saveNEKOSettings === 'function') {
20172018
window.saveNEKOSettings();
20182019
}
2019-
if (isChecked) {
2020+
if (visionEnabled) {
20202021
if (typeof window.acquireProactiveVisionStream === 'function') {
20212022
window.acquireProactiveVisionStream();
20222023
}

static/locales/en.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,17 +2153,17 @@
21532153
"ru": "Русский"
21542154
},
21552155
"interval": {
2156-
"chatIntervalBase": "Base Interval",
2156+
"chatIntervalBase": "Perception Interval",
21572157
"visionInterval": "Read Interval"
21582158
},
21592159
"toggles": {
21602160
"allowInterrupt": "Allow Interrupt",
21612161
"mergeMessages": "Merge Messages",
21622162
"avatarReactionBubble": "Expression Bubble",
21632163
"proactiveChat": "Proactive Chat",
2164-
"proactiveVision": "Proactive Vision",
2164+
"proactiveVision": "Privacy Mode",
21652165
"proactiveVisionChat": "Screen Share",
2166-
"proactiveVisionChatTooltip": "Requires both Proactive Chat and Proactive Vision to be enabled",
2166+
"proactiveVisionChatTooltip": "Requires Proactive Chat enabled and Privacy Mode disabled",
21672167
"proactiveNewsChat": "News Website",
21682168
"proactiveNewsChatTooltip": "Use trending topics for proactive chat",
21692169
"proactiveVideoChat": "Video Website",
@@ -2563,8 +2563,8 @@
25632563
"desc": "Your Neko initiates conversation proactively. Adjust frequency here~"
25642564
},
25652565
"step14": {
2566-
"title": "👀 Proactive Vision",
2567-
"desc": "Unlike Screen Share which continuously streams during voice sessions, Proactive Vision lets your Neko glance at your screen from time to time on her own. Adjust the interval here~"
2566+
"title": "🔒 Privacy Mode",
2567+
"desc": "When Privacy Mode is off, your Neko will glance at your screen from time to time on her own — unlike Screen Share which continuously streams during voice sessions. Adjust the interval here~"
25682568
},
25692569
"step15": {
25702570
"title": "👤 Character Manager",

static/locales/es.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,17 +2153,17 @@
21532153
"ru": "ruso"
21542154
},
21552155
"interval": {
2156-
"chatIntervalBase": "Intervalo base",
2156+
"chatIntervalBase": "Intervalo de percepción",
21572157
"visionInterval": "Intervalo de lectura"
21582158
},
21592159
"toggles": {
21602160
"allowInterrupt": "Permitir interrupción",
21612161
"mergeMessages": "Fusionar mensajes",
21622162
"avatarReactionBubble": "Burbuja de expresión",
21632163
"proactiveChat": "Chat proactivo",
2164-
"proactiveVision": "Visión Proactiva",
2164+
"proactiveVision": "Modo Privacidad",
21652165
"proactiveVisionChat": "Compartir pantalla",
2166-
"proactiveVisionChatTooltip": "Requiere que estén habilitados tanto el chat proactivo como la visión proactiva",
2166+
"proactiveVisionChatTooltip": "Requiere chat proactivo habilitado y Modo Privacidad deshabilitado",
21672167
"proactiveNewsChat": "Sitio web de noticias",
21682168
"proactiveNewsChatTooltip": "Utilice temas de actualidad para un chat proactivo",
21692169
"proactiveVideoChat": "Sitio web de vídeos",
@@ -2563,8 +2563,8 @@
25632563
"desc": "Tu Neko inicia la conversación de forma proactiva. Ajuste la frecuencia aquí ~"
25642564
},
25652565
"step14": {
2566-
"title": "👀 Visión Proactiva",
2567-
"desc": "A diferencia de Screen Share, que transmite continuamente durante las sesiones de voz, Proactive Vision permite que tu Neko mire tu pantalla de vez en cuando por sí solo. Ajuste el intervalo aquí ~"
2566+
"title": "🔒 Modo Privacidad",
2567+
"desc": "Cuando el Modo Privacidad está desactivado, tu Neko echará un vistazo a tu pantalla de vez en cuando por sí solo, a diferencia del Compartir pantalla que transmite continuamente durante las sesiones de voz. Ajuste el intervalo aquí ~"
25682568
},
25692569
"step15": {
25702570
"title": "👤 Administrador de personajes",

static/locales/ja.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,17 +2153,17 @@
21532153
"ru": "Русский"
21542154
},
21552155
"interval": {
2156-
"chatIntervalBase": "基本間隔",
2156+
"chatIntervalBase": "感知間隔",
21572157
"visionInterval": "読取間隔"
21582158
},
21592159
"toggles": {
21602160
"allowInterrupt": "割り込み許可",
21612161
"mergeMessages": "メッセージ結合",
21622162
"avatarReactionBubble": "表情バブル",
21632163
"proactiveChat": "自発的会話",
2164-
"proactiveVision": "自発的視覚",
2164+
"proactiveVision": "プライバシーモード",
21652165
"proactiveVisionChat": "画面共有",
2166-
"proactiveVisionChatTooltip": "自発的会話と自発的視覚の両方を有効にする必要があります",
2166+
"proactiveVisionChatTooltip": "自発的会話を有効にし、プライバシーモードを無効にする必要があります",
21672167
"proactiveNewsChat": "ニュースサイト",
21682168
"proactiveNewsChatTooltip": "トレンド話題を使用してチャット",
21692169
"proactiveVideoChat": "動画サイト",
@@ -2563,8 +2563,8 @@
25632563
"desc": "ネコが自発的に会話を開始。頻度をここで調整~"
25642564
},
25652565
"step14": {
2566-
"title": "👀 プロアクティブビジョン",
2567-
"desc": "音声セッション中にリアルタイムで配信する画面共有とは異なり、プロアクティブビジョンを有効にするとネコが時々自分から画面をチラッと見ます。間隔をここで調整~"
2566+
"title": "🔒 プライバシーモード",
2567+
"desc": "プライバシーモードをオフにすると、ネコが時々自分から画面をチラッと見ます。音声セッション中にリアルタイムで配信する画面共有とは異なります。間隔をここで調整~"
25682568
},
25692569
"step15": {
25702570
"title": "👤 キャラクターマネージャー",

static/locales/ko.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,17 +2153,17 @@
21532153
"ru": "Русский"
21542154
},
21552155
"interval": {
2156-
"chatIntervalBase": "기본 간격",
2156+
"chatIntervalBase": "인지 간격",
21572157
"visionInterval": "읽기 간격"
21582158
},
21592159
"toggles": {
21602160
"allowInterrupt": "인터럽트 허용",
21612161
"mergeMessages": "메시지 병합",
21622162
"avatarReactionBubble": "표정 말풍선",
21632163
"proactiveChat": "사전 예방적 채팅",
2164-
"proactiveVision": "사전 예방적 비전",
2164+
"proactiveVision": "프라이버시 모드",
21652165
"proactiveVisionChat": "화면 공유",
2166-
"proactiveVisionChatTooltip": "사전 예방적 채팅과 사전 예방적 비전을 모두 활성화해야 합니다",
2166+
"proactiveVisionChatTooltip": "사전 예방적 채팅을 활성화하고 프라이버시 모드를 비활성화해야 합니다",
21672167
"proactiveNewsChat": "뉴스 웹사이트",
21682168
"proactiveNewsChatTooltip": "트렌딩 토픽을 사용하여 채팅",
21692169
"proactiveVideoChat": "동영상 웹사이트",
@@ -2563,8 +2563,8 @@
25632563
"desc": "동반자는 주기적으로 대화를 시작합니다. 여기서 간격을 조정하세요~"
25642564
},
25652565
"step14": {
2566-
"title": "🙌 사전 예방적 비전",
2567-
"desc": "음성 세션 중 실시간으로 전송하는 화면 공유와 달리, 사전 예방적 비전을 활성화하면 동료가 때때로 스스로 화면을 살펴봅니다. 여기서 간격을 조정하세요~"
2566+
"title": "🔒 프라이버시 모드",
2567+
"desc": "프라이버시 모드를 끄면 동료가 때때로 스스로 화면을 살펴봅니다. 음성 세션 중 실시간으로 전송하는 화면 공유와는 다릅니다. 여기서 간격을 조정하세요~"
25682568
},
25692569
"step15": {
25702570
"title": "👤 캐릭터 관리자",

0 commit comments

Comments
 (0)