@tencent-rtc/trtc-agent-skills 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CODEBUDDY.md +0 -24
  2. package/README.md +12 -0
  3. package/README.zh.md +12 -0
  4. package/bin/cli.js +18 -5
  5. package/knowledge-base/chat/web/index.yaml +168 -0
  6. package/knowledge-base/chat/web/path-d-signals.yaml +4 -0
  7. package/knowledge-base/docs/chat/debug/GenerateTestUserSig.js +23 -0
  8. package/knowledge-base/docs/chat/debug/lib-generate-test-usersig.min.js +1 -0
  9. package/knowledge-base/docs/chat/gen-usersig.md +129 -0
  10. package/knowledge-base/docs/chat/product.md +61 -0
  11. package/knowledge-base/docs/chat/restapi.md +227 -0
  12. package/knowledge-base/docs/chat/sdk/android/faq.md +7 -0
  13. package/knowledge-base/docs/chat/sdk/android/index.md +94 -0
  14. package/knowledge-base/docs/chat/sdk/flutter/faq.md +7 -0
  15. package/knowledge-base/docs/chat/sdk/flutter/index.md +83 -0
  16. package/knowledge-base/docs/chat/sdk/ios/faq.md +7 -0
  17. package/knowledge-base/docs/chat/sdk/ios/index.md +94 -0
  18. package/knowledge-base/docs/chat/sdk/web/faq.md +8 -0
  19. package/knowledge-base/docs/chat/sdk/web/index.md +97 -0
  20. package/knowledge-base/docs/chat/uikit/android/index.md +28 -0
  21. package/knowledge-base/docs/chat/uikit/flutter/index.md +17 -0
  22. package/knowledge-base/docs/chat/uikit/ios/index.md +28 -0
  23. package/knowledge-base/docs/chat/uikit/react/index.md +6 -0
  24. package/knowledge-base/docs/chat/uikit/uniapp/index.md +6 -0
  25. package/knowledge-base/docs/chat/uikit/vue3/index.md +6 -0
  26. package/knowledge-base/docs/chat/webhook.md +73 -0
  27. package/knowledge-base/products.yaml +2 -2
  28. package/knowledge-base/slices/chat/web/at-mention.md +417 -0
  29. package/knowledge-base/slices/chat/web/conversation-actions.md +178 -0
  30. package/knowledge-base/slices/chat/web/conversation-list.md +368 -0
  31. package/knowledge-base/slices/chat/web/detect-integration.md +36 -0
  32. package/knowledge-base/slices/chat/web/detect-style.md +88 -0
  33. package/knowledge-base/slices/chat/web/direct-chat-entry.md +210 -0
  34. package/knowledge-base/slices/chat/web/login-auth.md +244 -0
  35. package/knowledge-base/slices/chat/web/message-base-actions.md +273 -0
  36. package/knowledge-base/slices/chat/web/message-input.md +209 -0
  37. package/knowledge-base/slices/chat/web/message-list.md +532 -0
  38. package/knowledge-base/slices/chat/web/send-custom-message.md +189 -0
  39. package/knowledge-base/slices/chat/web/send-media.md +214 -0
  40. package/knowledge-base/slices/chat/web/state-api-skeleton.md +60 -0
  41. package/knowledge-base/slices/chat/web/style-guide.md +210 -0
  42. package/package.json +5 -1
  43. package/skills/trtc/SKILL.md +57 -23
  44. package/skills/trtc/hooks/topic_phase_gate.py +5 -0
  45. package/skills/trtc/tests/test_reporting_v2_docs_query.py +175 -0
  46. package/skills/trtc/tools/kb.py +130 -0
  47. package/skills/trtc/tools/reporting.py +23 -2
  48. package/skills/trtc/tools/reporting_v2.py +501 -0
  49. package/skills/trtc-ai-service/README.ja.md +209 -0
  50. package/skills/trtc-ai-service/README.md +23 -9
  51. package/skills/trtc-ai-service/README.zh-CN.md +23 -9
  52. package/skills/trtc-ai-service/SKILL.md +4 -4
  53. package/skills/trtc-chat/.docs-query.yaml +9 -0
  54. package/skills/trtc-chat/SKILL.md +186 -0
  55. package/skills/trtc-chat/docs/SKILL.md +31 -0
  56. package/skills/trtc-chat/flows/maintenance.md +19 -0
  57. package/skills/trtc-chat/flows/onboarding.md +79 -0
  58. package/skills/trtc-chat/references/01-detect-project.md +136 -0
  59. package/skills/trtc-chat/references/02-path-a-questions.md +182 -0
  60. package/skills/trtc-chat/references/02-path-a-scaffold-template.md +59 -0
  61. package/skills/trtc-chat/references/02-path-a-script.md +266 -0
  62. package/skills/trtc-chat/references/02-path-a-templates.md +159 -0
  63. package/skills/trtc-chat/references/03-path-b-script.md +226 -0
  64. package/skills/trtc-chat/references/04-path-c-script.md +95 -0
  65. package/skills/trtc-chat/references/04-uikit-redirect.md +32 -0
  66. package/skills/trtc-chat/references/05-path-d-script.md +735 -0
  67. package/skills/trtc-chat/references/05-slice-loading.md +139 -0
  68. package/skills/trtc-chat/references/06-a-defensive-coding.md +139 -0
  69. package/skills/trtc-chat/references/06-hard-rules.md +190 -0
  70. package/skills/trtc-chat/references/08-state-config.md +231 -0
  71. package/skills/trtc-chat/references/09-troubleshoot.md +91 -0
  72. package/skills/trtc-chat/references/10-references-index.md +63 -0
  73. package/skills/trtc-chat/references/11-what-to-do-next-template.md +338 -0
  74. package/skills/trtc-chat/references/12-page-composition.md +73 -0
  75. package/skills/trtc-chat/references/13-reporting.md +166 -0
  76. package/skills/trtc-chat/references/14-official-docs.md +169 -0
  77. package/skills/trtc-chat/references/execution-units.yaml +27 -0
  78. package/skills/trtc-chat/references/vue3.md +104 -0
  79. package/skills/trtc-chat/tests/test_chat_bundle_contract.py +330 -0
  80. package/skills/trtc-chat/tools/__init__.py +5 -0
  81. package/skills/trtc-chat/tools/_delegate.py +32 -0
  82. package/skills/trtc-chat/tools/flow.py +8 -0
  83. package/skills/trtc-chat/tools/kb.py +8 -0
  84. package/skills/trtc-chat/tools/reporting_v2.py +8 -0
  85. package/skills/trtc-chat/tools/session.py +8 -0
  86. package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
  87. package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
  88. package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  89. package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
  90. package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
  91. package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
  92. package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
  93. package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
  94. package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
@@ -0,0 +1,210 @@
1
+ ---
2
+ id: chat/direct-chat-entry
3
+ name: 直连对话入口
4
+ product: chat
5
+ platform: web
6
+ description: Direct Chat 入口组件(静默登录 + 固定会话 + 三态管理 + 返回行为 + 集成方式)
7
+ applies-to: [tuikit-atomicx-vue3]
8
+ sdk-version: "tuikit-atomicx-vue3 >=6.0.0"
9
+ depends-on-stores: [LoginStore, MessageListStore, MessageInputStore]
10
+ trigger-keywords: [直连对话, direct chat, 客服入口, 在线客服页, 单点对话, 固定会话]
11
+ prerequisites: [login-auth, message-list, message-input]
12
+ tags: ['LoginStore', 'MessageListStore', 'MessageInputStore']
13
+ ---
14
+
15
+ ## 1. 这个 slice 处理什么
16
+
17
+ Direct Chat 模式的**入口组件**。用户点击入口(Footer 按钮 / 悬浮按钮 / 路由跳转)→ 本组件自动登录 → 直接渲染指定会话的消息列表 + 输入框。
18
+
19
+ > 本 slice 是 Direct Chat 模式的**组装层**——内部复用 `login-auth` 的 `loginIM` composable + `message-list` 的 `MessageList` 组件 + `message-input` 的 `MessageInput` 组件,不重复它们的逻辑。
20
+
21
+ **不在本 slice 处理**:多会话列表(那是 Full Chat 模式的 `conversation-list`);登录页 UI(Direct Chat 没有独立登录页)。
22
+
23
+ ## 2. AI 思考清单(写代码前必须想清楚)
24
+
25
+ - 对话对象是谁?(从 `directChatConfig.targetID` + `targetType` 取,拼出 `C2C${id}` 或 `GROUP${id}`)
26
+ - 入口集成方式?(独立路由页 / 右下角悬浮弹窗 / 底部 Sheet / 嵌入侧边栏)
27
+ - 登录凭据从哪来?(业务 token 换 userSig 接口 / `.env.local` 开发期直接用)
28
+ - Header 需要什么?(返回按钮 + 标题 + 在线状态?)
29
+ - 登录失败 / 网络断开时的 UX?(就地提示 + 返回按钮,不跳登录页)
30
+
31
+ ## 3. SDK API 必读(绝对真理)
32
+
33
+ 本 slice 不引入新 API,复用已有 slice 的 API:
34
+
35
+ - `loginIM` / `loginStatus` / `onEvent` → 来自 `login-auth.md` § 3
36
+ - `useMessageListStore(conversationID)` → 来自 `message-list.md` § 3
37
+ - `useMessageInputStore(conversationID)` → 来自 `message-input.md` § 3
38
+
39
+ 唯一新增的是 **conversationID 拼接逻辑**:
40
+
41
+ ```ts
42
+ // targetID 直接写在文件顶部常量,不通过 env 管理(减少配置负担)
43
+ // ⚠️ 替换为实际客服 userID 或 groupID(用户在 A.2 Q.3b 中填写的值)
44
+ const TARGET_ID = 'administrator'
45
+ const targetType = directChatConfig.targetType // 'C2C' | 'GROUP'
46
+ const conversationID = `${targetType}${TARGET_ID}`
47
+ ```
48
+
49
+ ## 4. Hard rules(AI 必须遵守)
50
+
51
+ ### 4.1 静默登录
52
+
53
+ - ❗ `onMounted` 自动调 `loginIM()`,不等用户手动触发
54
+ - ❌ 弹出登录表单让用户输入
55
+ - ❗ 登录凭据从业务登录态获取(生产)或 `.env.local`(开发期),不准让用户在这个界面手动输入 userSig
56
+ - ❗ 登录过程中显示 `connecting` 态(loading spinner + "正在连接...")
57
+
58
+ ### 4.2 固定 conversationID
59
+
60
+ - ❗ conversationID 从 `directChatConfig` 拼出,不准让用户选
61
+ - ❌ 弹出会话选择器
62
+ - ❗ `C2C` 前缀 + `targetID`(单聊)或 `GROUP` 前缀 + `targetID`(群聊),不准拼错
63
+ - ❌ `conversationID = targetID`(缺少 C2C/GROUP 前缀)
64
+
65
+ ### 4.3 三态管理
66
+
67
+ - ❗ 必须显式区分三态:`connecting` → `connected` → `error`
68
+
69
+ | 状态 | UI |
70
+ |---|---|
71
+ | `connecting` | 居中 loading + "正在连接..." |
72
+ | `connected` | MessageList + MessageInput |
73
+ | `error` | 错误提示 + 重试按钮 + 返回按钮 |
74
+
75
+ - ❌ 没有 loading 态,白屏后突然出现消息
76
+ - ❌ 登录失败白屏无反馈
77
+
78
+ ### 4.4 返回行为
79
+
80
+ - ❗ 必须有返回按钮(Header 左侧)
81
+ - ❗ 点击返回:路由页 → `router.back()`;弹窗 → `emit('close')`
82
+ - ❌ 无返回按钮,用户被困在对话页
83
+
84
+ ### 4.5 被踢下线
85
+
86
+ - ❗ 被踢下线时跳回来源页面(`router.back()` 或 `emit('close')`),不跳登录页
87
+ - ❌ `router.push('/login')`(Direct Chat 没有登录页)
88
+ - ❗ 跳回前 Toast 提示"您的账号在其他设备登录"
89
+
90
+ ### 4.6 集成方式
91
+
92
+ - ❗ 按 `directChatConfig.entryPosition` 决定组件形态:
93
+ - `route` → 独立路由页(`/customer-service` 等)
94
+ - `floating` → 右下角 fixed 弹窗
95
+ - `sidebar` → 右侧 Drawer
96
+ - `footer-button` → 替换/改造 Footer 中的按钮,点击后展开对话区
97
+ - ❗ 非 `route` 方式时,组件必须支持 `v-if` 控制显隐 + 关闭后 cleanup(logout 或保持连接由业务决定)
98
+
99
+ ## 5. 反例库
100
+
101
+ > § 4 各条已内联反例。本节补充跨规则复合错误:
102
+
103
+ - ❌ 静默登录写在组件外(`App.vue` onMounted 全局登录)+ conversationID 写死在 message-list 里 → 导致切换对话对象时需要改两处,且全局登录会在不需要聊天的页面也触发
104
+ - ❌ 用 `conversation-list` 的 `loadConversations()` 拉会话再取第一条的 ID → 违反 Direct Chat "不需要会话列表"的核心约束
105
+
106
+ ## 6. UI 自由度
107
+
108
+ > 样式由 AI 自由发挥,遵循项目已有 CSS 方案(见 `_base/detect-style.md`)。
109
+
110
+ ✅ 完全自由:Header 样式 / loading 动画 / 错误提示样式 / 弹窗形态 / 返回按钮图标
111
+ ⚠️ 必须遵循项目现状:UI 库 / CSS 方案 / 已有 Dialog/Drawer 组件
112
+ ⚠️ § 4 强约束不可放宽
113
+
114
+ ## 7. 参考实现(明确哪些可改)
115
+
116
+ ```vue
117
+ <script setup lang="ts">
118
+ import { ref, onMounted, onUnmounted } from 'vue'
119
+ import { useLoginStore } from 'tuikit-atomicx-vue3/chat'
120
+ import MessageList from '@/components/chat/MessageList.vue'
121
+ import MessageInput from '@/components/chat/MessageInput.vue'
122
+
123
+ const props = defineProps<{
124
+ targetID: string // 对话对象 userID 或 groupID
125
+ targetType: 'C2C' | 'GROUP'
126
+ }>()
127
+
128
+ const emit = defineEmits<{ close: [] }>()
129
+
130
+ const { login, loginStatus, onEvent } = useLoginStore()
131
+
132
+ // ── 三态 ──
133
+ type ConnectionState = 'connecting' | 'connected' | 'error'
134
+ const state = ref<ConnectionState>('connecting')
135
+ const errorMsg = ref('')
136
+
137
+ // ── conversationID ──
138
+ const conversationID = `${props.targetType}${props.targetID}`
139
+
140
+ // ── 静默登录 ──
141
+ onMounted(async () => {
142
+ try {
143
+ const userID = props.userID // Direct Chat 由父组件传入或从业务上下文获取
144
+ const { SDKAppID, userSig } = (window as any).genTestUserSig(userID)
145
+ await login({ sdkAppID: SDKAppID, userID, userSig })
146
+ state.value = 'connected'
147
+ } catch (err: any) {
148
+ state.value = 'error'
149
+ errorMsg.value = err?.message ?? '连接失败'
150
+ }
151
+ })
152
+
153
+ // ── 被踢下线 ──
154
+ let unsubscribe: (() => void) | null = null
155
+ onMounted(() => {
156
+ unsubscribe = onEvent((event) => {
157
+ if (event.type === 'kickedOffline') {
158
+ emit('close') // 或 router.back()
159
+ }
160
+ })
161
+ })
162
+ onUnmounted(() => { unsubscribe?.() })
163
+
164
+ function handleRetry() {
165
+ state.value = 'connecting'
166
+ // 重新调 login...
167
+ }
168
+ </script>
169
+
170
+ <template>
171
+ <div class="direct-chat">
172
+ <header class="direct-chat-header">
173
+ <button @click="emit('close')">← 返回</button>
174
+ <span>在线客服</span>
175
+ </header>
176
+
177
+ <div v-if="state === 'connecting'" class="state-connecting">
178
+ 正在连接...
179
+ </div>
180
+
181
+ <div v-else-if="state === 'error'" class="state-error">
182
+ <p>{{ errorMsg }}</p>
183
+ <button @click="handleRetry">重试</button>
184
+ <button @click="emit('close')">返回</button>
185
+ </div>
186
+
187
+ <template v-else>
188
+ <MessageList :conversationID="conversationID" />
189
+ <MessageInput :conversationID="conversationID" />
190
+ </template>
191
+ </div>
192
+ </template>
193
+ ```
194
+
195
+ ### 可改
196
+
197
+ - Header 样式 / 标题文案 / 在线状态展示
198
+ - loading / error 的 UI 形态
199
+ - 集成方式(route / floating / sidebar / sheet)
200
+ - 登录凭据获取方式(env / 后端接口)
201
+ - 关闭后是否 logout(业务决定)
202
+
203
+ ### 不可改
204
+
205
+ - `onMounted` 静默登录(不准弹登录表单)
206
+ - conversationID 从 `targetType + targetID` 拼出
207
+ - 三态显式区分(connecting / connected / error)
208
+ - 必须有返回按钮
209
+ - 被踢下线跳回来源(不跳登录页)
210
+ - `onEvent` 必须存 unsubscribe + `onUnmounted` 释放
@@ -0,0 +1,244 @@
1
+ ---
2
+ id: chat/login-auth
3
+ name: SDK 登录鉴权
4
+ product: chat
5
+ platform: web
6
+ description: SDK 登录鉴权(tuikit-atomicx-vue3 useLoginStore 单例 composable)+ UserSig 来源;路径 A 同时覆盖默认登录页
7
+ applies-to: [tuikit-atomicx-vue3]
8
+ sdk-version: "tuikit-atomicx-vue3 >=6.0.0"
9
+ depends-on-stores: [LoginStore]
10
+ trigger-keywords: [登录, login, 鉴权, userSig, 初始化, init, sdkAppID, kickedOffline, 被踢下线, logout, 登出, 登录页, login page]
11
+ prerequisites: []
12
+ tags: ['LoginStore']
13
+ ---
14
+
15
+ ## 1. 这个 slice 处理什么
16
+
17
+ 路径 A 基础功能之(登录鉴权)。在 `src/im/login.ts`(或项目等价位置)封装 `useLoginStore` 的 `login` / `logout` / `loginStatus` / 被踢下线,给上层 UI 和后续 slice 提供:
18
+
19
+ - `loginIM({ userID, userSig })` —— 上层登录页调用入口
20
+ - `logoutIM()` —— 登出入口
21
+ - `useLoginGuard()` —— 路由守卫用的 `loginStatus` ComputedRef 透传
22
+ - `setupKickedOfflineHandler()` —— 被踢下线事件订阅
23
+
24
+ **额外覆盖**:路径 A(0→1 项目)需同时生成最小登录页 `src/views/Login.vue`,见 § 8。
25
+
26
+ **不在本 slice 处理**:UserSig 后端接口实现;路径 B 的登录页改造(用户主动要求才做)。
27
+
28
+ ## 2. AI 思考清单(写代码前必须想清楚)
29
+
30
+ - **chatMode = full / direct?**(A.1 用户已选)→ full 含登录页;direct 仅 composable(静默自动登录,无独立登录页)
31
+ - `kickedOffline` 提示用什么 UI?优先用 Toast / Dialog;无 UI 库时降级原生 `alert()`(标注 TODO)
32
+ - **Direct Chat 额外**:登录失败时就地显示"连接失败 + 返回按钮"(不跳登录页);被踢下线跳回来源页面(`router.back()`)
33
+
34
+ ## 3. SDK API 必读(绝对真理)
35
+
36
+ ```ts
37
+ import { useLoginStore } from 'tuikit-atomicx-vue3/chat'
38
+ const {
39
+ loginStatus, // ComputedRef<'unlogin' | 'logined'>
40
+ loginUserInfo, // ComputedRef<UserProfile | null>
41
+ login, // (params: LoginParams) => Promise<void>
42
+ logout, // () => Promise<void>
43
+ setSelfInfo, // (profile: Partial<UserProfile>) => Promise<void>
44
+ getChat, // () => ChatSDK(兜底原生实例)
45
+ onEvent, // (listener: (e: LoginEvent) => void) => Unsubscribe
46
+ } = useLoginStore()
47
+ ```
48
+
49
+ `LoginParams` 关键字段(完整见 `_shared/store-types.md`):
50
+
51
+ | 字段 | 类型 | 必填 | 说明 |
52
+ |---|---|---|---|
53
+ | `sdkAppID` | `number` | 是 | 腾讯云控制台拿 |
54
+ | `userID` | `string` | 是 | 业务侧用户 ID |
55
+ | `userSig` | `string` | 是 | 鉴权后端签发 |
56
+ | `scene` | `string` | 否 | 默认 `'5000'`,必须传入 |
57
+ | `proxyServer` / `fileUploadProxy` / `fileDownloadProxy` | `string` | 否 | 私有化部署专用 |
58
+
59
+ `LoginEvent`(目前 1 种,监听用 `switch` 预留扩展):
60
+
61
+ ```ts
62
+ type LoginEvent = { type: 'kickedOffline' }
63
+ ```
64
+
65
+ ## 4. Hard rules(AI 必须遵守)
66
+
67
+ > ❗ 所有 SDK 异步调用必须遵循 `references/06-a-defensive-coding.md` 防御编程规范(try/catch/finally、formatError、错误反馈形式、状态锁)。本节规则是该规范的专属补充,不替代它。
68
+ > ❗ 写 UI 代码前必须先 `read_file _base/style-guide.md`,按其规范生成样式,不准跳过。
69
+
70
+ ### 4.1 实例化
71
+
72
+ - ❗ 统一使用 `useLoginStore()`,是**单例 composable**,任意位置调用同一实例
73
+ - ❌ `useLoginStore.create(...)` / `LoginStore.create(...)` — 那是多实例 store 的 API
74
+ - ❌ `import { LoginStore } from 'tuikit-atomicx-vue3/chat'`(`LoginStore` 不是 hook,应用 `useLoginStore`)
75
+ - ❌ `onScopeDispose(() => store.destroy())` — 单例由 SDK 管理,不准手动销毁
76
+
77
+ ### 4.2 调用顺序
78
+
79
+ - ❗ `await login(...)` 必须 resolve 后才能调其他 store(ConversationList / MessageList 等)
80
+ - ❌ `login(...).then(...)` 然后 then 外面立即调 `loadMessages()`
81
+ - ❗ 登录态判定唯一信号:`loginStatus.value === 'logined'`
82
+ - ❌ 自建 `const isLoggedIn = ref(false)`(断响应式)
83
+ - ❌ `if (loginUserInfo.value)` 判断(登录中可能短暂 null)
84
+
85
+ ### 4.3 凭证安全 + env
86
+
87
+ - ❗ `userSig` 绝不持久化(不写 localStorage / sessionStorage / cookie / git)
88
+ - ❌ `localStorage.setItem('userSig', sig)`
89
+ - ❗ 不准在前端持有 `SDKSecretKey`
90
+ - ❗ SDKAppID 开发期从 `window.genTestUserSig(userID).SDKAppID` 获取(依赖 `index.html` 中的 `<script>` 加载),生产期从后端接口返回
91
+ - ❗ `TOKEN_ENDPOINT` 直接写在 `src/im/login.ts` 顶部常量中,不通过 env 读取
92
+ - ❌ `process.env.VITE_TRTC_CHAT_TOKEN_ENDPOINT`(前端不用 process.env,会报 ReferenceError)
93
+ - ❌ `import.meta.env.VITE_TRTC_CHAT_TOKEN_ENDPOINT`(增加配置负担,直接写常量更简单)
94
+ - ✅ `const TOKEN_ENDPOINT = 'https://...'`(空字符串 = 开发期,填写地址 = 生产期)
95
+ - ❗ `login()` 必须传 `scene: '5000'`
96
+ - ❌ `await login({ sdkAppID, userID, userSig })`(缺少 scene 参数)
97
+ - ✅ `await login({ sdkAppID, userID, userSig, scene: '5000' /* 方便排查跟踪问题 */ })`
98
+ - ❗ debug 文件通过 `index.html` 的 `<script>` 加载,不能 ESM import(见 `gen-usersig.md` §4)
99
+
100
+ ### 4.4 被踢下线
101
+
102
+ - ❗ 必须在应用顶层(`App.vue` / 路由根布局)订阅 `onEvent`,处理 `kickedOffline`:清态→跳登录页→提示用户
103
+ - ❗ `onEvent` 返回 `Unsubscribe`,只订阅一次
104
+ - ❌ 在每个页面 `onMounted` 里都订阅(重复触发 + 页面卸载漏听)
105
+
106
+ ### 4.5 与原生 SDK 兜底
107
+
108
+ - ❗ 只有 `tuikit-atomicx-vue3` 未封装的能力才用 `getChat()`
109
+ - ❌ `getChat()` 后自己再 `chat.login()`(重复登录)
110
+
111
+ ### 4.6 路由守卫(刷新保护)
112
+
113
+ - ❗ Full Chat 模式必须在 router 中加全局前置守卫:刷新后 `loginStatus` 非 `'logined'` 时跳回 `/login`,防止直接进 `/chat` 因未登录报错
114
+ - ❗ 守卫通过 `useLoginGuard()` 取 `loginStatus`,不直接调 `useLoginStore()`
115
+ - ❌ 在 `router.beforeEach` 里直接 `useLoginStore()`(Pinia/composable 在路由守卫中可能未初始化)
116
+ - ❗ Direct Chat 模式不加此守卫(入口组件自带 connecting / connected / error 三态管理)
117
+
118
+ ## 6. UI 自由度
119
+
120
+ > 本 slice § 1~§ 7 主要写 `src/im/login.ts`,纯逻辑。
121
+ > 路径 A 配套登录页是自由发挥,规则见 § 8。
122
+
123
+ ## 7. 参考实现
124
+
125
+ > 路径:`src/im/login.ts`(项目已有等价位置就跟随)
126
+
127
+ ```ts
128
+ // src/im/login.ts
129
+ import { useLoginStore } from 'tuikit-atomicx-vue3/chat'
130
+ import type { LoginParams } from 'tuikit-atomicx-vue3/chat'
131
+
132
+ // ⚠️ 上线前填入后端接口地址,并删除 public/debug/ 目录和 index.html 中的两行 <script>
133
+ const TOKEN_ENDPOINT = ''
134
+
135
+ async function resolveCredentials(userID: string): Promise<{ SDKAppID: number; userSig: string }> {
136
+ if (TOKEN_ENDPOINT) {
137
+ const res = await fetch(`${TOKEN_ENDPOINT}?userID=${encodeURIComponent(userID)}`, { credentials: 'include' })
138
+ if (!res.ok) throw new Error(`Token endpoint ${res.status}`)
139
+ return res.json() // 后端返回 { SDKAppID, userSig }
140
+ }
141
+ // 开发期:依赖 index.html 中的 <script> 加载 debug 文件
142
+ return (window as any).genTestUserSig(userID)
143
+ }
144
+
145
+ export async function loginIM(userID: string) {
146
+ const { SDKAppID, userSig } = await resolveCredentials(userID)
147
+ const { login } = useLoginStore()
148
+ await login({ sdkAppID: SDKAppID, userID, userSig, scene: '5000' /* 方便排查跟踪问题 */ })
149
+ }
150
+
151
+ export async function logoutIM() {
152
+ const { logout } = useLoginStore()
153
+ await logout()
154
+ }
155
+
156
+ export function useLoginGuard() {
157
+ const { loginStatus, loginUserInfo } = useLoginStore()
158
+ return { loginStatus, loginUserInfo }
159
+ }
160
+
161
+ export function setupKickedOfflineHandler(onKicked: () => void) {
162
+ const { onEvent } = useLoginStore()
163
+ return onEvent((event) => {
164
+ switch (event.type) {
165
+ case 'kickedOffline':
166
+ onKicked()
167
+ break
168
+ }
169
+ })
170
+ }
171
+ ```
172
+
173
+ ```vue
174
+ <!-- App.vue 顶层订阅(节选) -->
175
+ <script setup lang="ts">
176
+ import { useRouter } from 'vue-router'
177
+ import { setupKickedOfflineHandler } from '@/im/login'
178
+
179
+ const router = useRouter()
180
+ setupKickedOfflineHandler(() => {
181
+ alert('您的账号在其他设备登录,已被强制下线') // TODO: 替换为 Toast / Dialog 组件
182
+ router.replace('/login')
183
+ })
184
+ </script>
185
+ ```
186
+
187
+ ```ts
188
+ // src/router/index.ts — 路由守卫(Full Chat 模式必须加,Direct Chat 不加)
189
+ import { createRouter, createWebHistory } from 'vue-router'
190
+ import { useLoginGuard } from '@/im/login'
191
+
192
+ const router = createRouter({
193
+ history: createWebHistory(),
194
+ routes: [
195
+ { path: '/login', component: () => import('@/views/Login.vue') },
196
+ { path: '/chat', component: () => import('@/components/chat/ChatPage.vue') },
197
+ ]
198
+ })
199
+
200
+ router.beforeEach((to) => {
201
+ if (to.path === '/login') return true
202
+ const { loginStatus } = useLoginGuard()
203
+ if (loginStatus.value !== 'logined') {
204
+ return '/login'
205
+ }
206
+ })
207
+
208
+ export default router
209
+ ```
210
+
211
+ ### 可改
212
+
213
+ - 文件路径(跟项目现状)
214
+ - `loginIM` 内部加埋点 / 错误码映射
215
+ - `setupKickedOfflineHandler` 的 UI 提示形式
216
+ - `useLoginGuard` 改名 / 加返回字段
217
+ - 路由守卫的跳转目标路径(`/login` 改为项目实际登录路由)
218
+
219
+ ### 不可改
220
+
221
+ - `useLoginStore()` 的调用方式和解构 API 名
222
+ - `loginStatus` 必须保持 ComputedRef 形态向上透传
223
+ - `userSig` 不可持久化
224
+ - `kickedOffline` 订阅必须在 App 根
225
+ - 路由守卫通过 `useLoginGuard()` 获取 `loginStatus`(不直接调 `useLoginStore()`)
226
+ - Full Chat 模式必须有路由守卫(§ 4.6)
227
+
228
+ ## 8. 路径 A 配套登录页(仅 0→1 项目)
229
+
230
+ > 路径 B 跳过本节,除非用户主动要求。
231
+
232
+ ### 8.1 处理什么
233
+
234
+ 在 `src/views/Login.vue` 提供最小登录页:输入 `userID` → 调 `loginIM()` → 跳 `/chat`。AI 自由生成整页代码和样式。
235
+
236
+ ### 8.2 约束
237
+
238
+ - ❗ 表单只有 `userID`;UserSig 来源走 § 4.3 约定,不在表单出现
239
+ - ❗ 提交按钮 `loginIM` resolve 前 disabled
240
+ - ❗ reject 时显示人类可读错误(错误码→文案见 `09-troubleshoot.md`)
241
+ - ❗ 成功后跳 `/chat` 或切到 `<ChatPage />`
242
+ - ❌ 登录页加 SDKAppID / UserSig 输入框(开发者凭据不是用户输入项)
243
+ - ❌ 登录页和 ChatPage 写同一文件用 `v-if` 切换
244
+ - ❌ 登录页里另起登录逻辑(必须复用 `loginIM`)