diff --git a/apps/web/src/i18n/locales/en.json b/apps/web/src/i18n/locales/en.json
index 459ff9b5..326feabe 100644
--- a/apps/web/src/i18n/locales/en.json
+++ b/apps/web/src/i18n/locales/en.json
@@ -559,6 +559,7 @@
"skills": "Skills",
"email": "Email",
"files": "Files",
+ "access": "Access",
"terminal": "Terminal",
"settings": "Settings"
},
@@ -767,6 +768,65 @@
"deleteBotDescription": "Deleting this bot cannot be undone. Proceed with caution.",
"deleteBot": "Delete Bot"
},
+ "access": {
+ "title": "Access Control",
+ "subtitle": "Guest chat trigger access is controlled by allow guest, whitelist, and blacklist together.",
+ "allowGuestDescription": "When enabled, guests may trigger chat by default. Blacklist rules still take precedence.",
+ "saveGuestAccess": "Save Guest Access",
+ "guestAccessSaved": "Guest access updated",
+ "guestRulesTitle": "Rule Summary",
+ "guestRulesDescription": "Owners and members always bypass these rules. The rules below only apply to guest chat triggers.",
+ "whitelistTitle": "Whitelist",
+ "whitelistDescription": "Whitelisted guests can still trigger chat even when guest access is not open.",
+ "blacklistTitle": "Blacklist",
+ "blacklistDescription": "Blacklisted guests are denied chat trigger even when guest access is open.",
+ "userSelector": "Select User",
+ "identitySelector": "Select Platform Identity",
+ "selectUser": "Search and select a user",
+ "selectIdentity": "Search and select a platform identity",
+ "searchUser": "Search users",
+ "searchIdentity": "Search platform identities",
+ "noUserCandidates": "No user candidates",
+ "noIdentityCandidates": "No platform identity candidates",
+ "userId": "User ID",
+ "userIdPlaceholder": "Enter user ID",
+ "channelIdentityId": "Channel Identity ID",
+ "channelIdentityIdPlaceholder": "Enter channel identity ID",
+ "addWhitelist": "Add to Whitelist",
+ "addBlacklist": "Add to Blacklist",
+ "clearSelection": "Clear Selection",
+ "whitelistEmpty": "No whitelist rules yet",
+ "blacklistEmpty": "No blacklist rules yet",
+ "validation": "Fill exactly one subject: user_id or channel_identity_id",
+ "validationConversationRequiresChannel": "Select a source platform before restricting by conversation or thread ID",
+ "validationThreadRequiresConversation": "Thread ID requires a conversation ID",
+ "whitelistSaved": "Whitelist updated",
+ "blacklistSaved": "Blacklist updated",
+ "saveFailed": "Failed to save access rule",
+ "deleteSuccess": "Rule deleted",
+ "deleteFailed": "Failed to delete rule",
+ "sourceScopeTitle": "Source Scope",
+ "sourceScopeDescription": "Optionally restrict a rule to a platform, conversation type, or specific conversation/thread.",
+ "sourceChannel": "Source Platform",
+ "anyChannel": "Any platform",
+ "conversationType": "Conversation Type",
+ "anyConversationType": "Any conversation type",
+ "privateConversationType": "Private",
+ "groupConversationType": "Group",
+ "threadConversationType": "Thread",
+ "observedConversation": "Observed Conversation",
+ "selectObservedConversation": "Select an observed conversation",
+ "searchObservedConversation": "Search observed conversations",
+ "noObservedConversations": "No observed conversations",
+ "selectIdentityFirst": "Select a platform identity first to load observed conversations",
+ "observedConversationHint": "Choosing an observed conversation auto-fills the platform, conversation ID, and thread ID.",
+ "conversationId": "Conversation ID",
+ "conversationIdPlaceholder": "Enter conversation ID",
+ "threadId": "Thread ID",
+ "threadIdPlaceholder": "Enter thread ID",
+ "clearScope": "Clear Scope",
+ "lastObserved": "Last seen"
+ },
"channels": {
"title": "Platforms",
"addChannel": "Add Platform",
diff --git a/apps/web/src/i18n/locales/zh.json b/apps/web/src/i18n/locales/zh.json
index ac848fa2..165e3b61 100644
--- a/apps/web/src/i18n/locales/zh.json
+++ b/apps/web/src/i18n/locales/zh.json
@@ -555,6 +555,7 @@
"skills": "技能",
"email": "邮件",
"files": "文件",
+ "access": "访问控制",
"terminal": "终端",
"settings": "设置"
},
@@ -763,6 +764,65 @@
"deleteBotDescription": "删除此 Bot 后无法恢复,请谨慎操作。",
"deleteBot": "删除 Bot"
},
+ "access": {
+ "title": "访问控制",
+ "subtitle": "游客聊天触发权限由 allow guest、白名单和黑名单共同决定。",
+ "allowGuestDescription": "开启后,游客默认可触发聊天;黑名单仍然会优先阻止。",
+ "saveGuestAccess": "保存游客访问设置",
+ "guestAccessSaved": "游客访问设置已保存",
+ "guestRulesTitle": "规则说明",
+ "guestRulesDescription": "Owner 和成员始终可用;这里的规则只作用于游客的聊天触发。",
+ "whitelistTitle": "白名单",
+ "whitelistDescription": "当未开放游客时,白名单中的游客仍可触发聊天。",
+ "blacklistTitle": "黑名单",
+ "blacklistDescription": "即使已开放游客,黑名单中的游客也会被拒绝触发聊天。",
+ "userSelector": "选择用户",
+ "identitySelector": "选择平台身份",
+ "selectUser": "搜索并选择用户",
+ "selectIdentity": "搜索并选择平台身份",
+ "searchUser": "搜索用户",
+ "searchIdentity": "搜索平台身份",
+ "noUserCandidates": "暂无可选用户",
+ "noIdentityCandidates": "暂无可选平台身份",
+ "userId": "用户 ID",
+ "userIdPlaceholder": "输入用户 ID",
+ "channelIdentityId": "Channel Identity ID",
+ "channelIdentityIdPlaceholder": "输入 channel identity ID",
+ "addWhitelist": "添加到白名单",
+ "addBlacklist": "添加到黑名单",
+ "clearSelection": "清空选择",
+ "whitelistEmpty": "暂无白名单规则",
+ "blacklistEmpty": "暂无黑名单规则",
+ "validation": "请只填写一个主体:user_id 或 channel_identity_id",
+ "validationConversationRequiresChannel": "按会话或线程限制时,请先选择来源平台",
+ "validationThreadRequiresConversation": "填写线程 ID 前必须先填写会话 ID",
+ "whitelistSaved": "白名单已更新",
+ "blacklistSaved": "黑名单已更新",
+ "saveFailed": "保存访问规则失败",
+ "deleteSuccess": "规则已删除",
+ "deleteFailed": "删除规则失败",
+ "sourceScopeTitle": "来源范围",
+ "sourceScopeDescription": "可选地将规则限制到某个平台、会话类型或指定会话/线程。",
+ "sourceChannel": "来源平台",
+ "anyChannel": "任意平台",
+ "conversationType": "会话类型",
+ "anyConversationType": "任意会话类型",
+ "privateConversationType": "私聊",
+ "groupConversationType": "群聊",
+ "threadConversationType": "线程",
+ "observedConversation": "历史会话",
+ "selectObservedConversation": "选择一个历史会话",
+ "searchObservedConversation": "搜索历史会话",
+ "noObservedConversations": "暂无历史会话",
+ "selectIdentityFirst": "请先选择平台身份以加载历史会话",
+ "observedConversationHint": "选择历史会话后会自动填充平台、会话 ID 和线程 ID。",
+ "conversationId": "会话 ID",
+ "conversationIdPlaceholder": "输入会话 ID",
+ "threadId": "线程 ID",
+ "threadIdPlaceholder": "输入线程 ID",
+ "clearScope": "清空范围",
+ "lastObserved": "最近出现"
+ },
"channels": {
"title": "平台",
"addChannel": "添加平台",
diff --git a/apps/web/src/pages/bots/components/bot-access.vue b/apps/web/src/pages/bots/components/bot-access.vue
new file mode 100644
index 00000000..e7031676
--- /dev/null
+++ b/apps/web/src/pages/bots/components/bot-access.vue
@@ -0,0 +1,1170 @@
+
+
+
+
+ {{ $t('bots.access.title') }}
+
+
+ {{ $t('bots.access.subtitle') }}
+
+
+
+
+
+
+
+ {{ $t('bots.settings.allowGuest') }}
+
+
+ {{ $t('bots.access.allowGuestDescription') }}
+
+
+ {{ $t('bots.settings.allowGuestPersonalHint') }}
+
+
+
allowGuestDraft = !!val"
+ />
+
+
+
+
+
+
+
+
+ {{ $t('bots.access.guestRulesTitle') }}
+
+
+ {{ $t('bots.access.guestRulesDescription') }}
+
+
+
+
+
+
+ {{ $t('bots.access.whitelistTitle') }}
+
+
+ {{ $t('bots.access.whitelistDescription') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ initials(option.label) }}
+
+
+
+
+ {{ option.label }}
+
+
+ {{ option.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ initials(option.label) }}
+
+
+
+
+ {{ option.label }}
+
+
+ {{ option.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('bots.access.sourceScopeTitle') }}
+
+
+ {{ $t('bots.access.sourceScopeDescription') }}
+
+
+
+
+
+
+
+ {{ whitelistSelection.sourceChannel || $t('bots.access.anyChannel') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('bots.access.selectIdentityFirst') }}
+
+
+
+ {{ $t('common.loading') }}
+
+
+ {{ $t('bots.access.observedConversationHint') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('common.loading') }}
+
+
+ {{ $t('bots.access.whitelistEmpty') }}
+
+
+
+
+
+
+
+
+ {{ initials(formatRuleLabel(item)) }}
+
+
+
+
+
+
+ {{ formatRuleLabel(item) }}
+
+
+ {{ formatRuleMeta(item) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('bots.access.blacklistTitle') }}
+
+
+ {{ $t('bots.access.blacklistDescription') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ initials(option.label) }}
+
+
+
+
+ {{ option.label }}
+
+
+ {{ option.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ initials(option.label) }}
+
+
+
+
+ {{ option.label }}
+
+
+ {{ option.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('bots.access.sourceScopeTitle') }}
+
+
+ {{ $t('bots.access.sourceScopeDescription') }}
+
+
+
+
+
+
+
+ {{ blacklistSelection.sourceChannel || $t('bots.access.anyChannel') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('bots.access.selectIdentityFirst') }}
+
+
+
+ {{ $t('common.loading') }}
+
+
+ {{ $t('bots.access.observedConversationHint') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('common.loading') }}
+
+
+ {{ $t('bots.access.blacklistEmpty') }}
+
+
+
+
+
+
+
+
+ {{ initials(formatRuleLabel(item)) }}
+
+
+
+
+
+
+ {{ formatRuleLabel(item) }}
+
+
+ {{ formatRuleMeta(item) }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/pages/bots/components/bot-settings.vue b/apps/web/src/pages/bots/components/bot-settings.vue
index 071d8053..c22fec3e 100644
--- a/apps/web/src/pages/bots/components/bot-settings.vue
+++ b/apps/web/src/pages/bots/components/bot-settings.vue
@@ -258,18 +258,6 @@
-
-
-
-
- form.allow_guest = !!val"
- />
-
-
-
-