@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.
- package/CODEBUDDY.md +0 -24
- package/README.md +12 -0
- package/README.zh.md +12 -0
- package/bin/cli.js +18 -5
- package/knowledge-base/chat/web/index.yaml +168 -0
- package/knowledge-base/chat/web/path-d-signals.yaml +4 -0
- package/knowledge-base/docs/chat/debug/GenerateTestUserSig.js +23 -0
- package/knowledge-base/docs/chat/debug/lib-generate-test-usersig.min.js +1 -0
- package/knowledge-base/docs/chat/gen-usersig.md +129 -0
- package/knowledge-base/docs/chat/product.md +61 -0
- package/knowledge-base/docs/chat/restapi.md +227 -0
- package/knowledge-base/docs/chat/sdk/android/faq.md +7 -0
- package/knowledge-base/docs/chat/sdk/android/index.md +94 -0
- package/knowledge-base/docs/chat/sdk/flutter/faq.md +7 -0
- package/knowledge-base/docs/chat/sdk/flutter/index.md +83 -0
- package/knowledge-base/docs/chat/sdk/ios/faq.md +7 -0
- package/knowledge-base/docs/chat/sdk/ios/index.md +94 -0
- package/knowledge-base/docs/chat/sdk/web/faq.md +8 -0
- package/knowledge-base/docs/chat/sdk/web/index.md +97 -0
- package/knowledge-base/docs/chat/uikit/android/index.md +28 -0
- package/knowledge-base/docs/chat/uikit/flutter/index.md +17 -0
- package/knowledge-base/docs/chat/uikit/ios/index.md +28 -0
- package/knowledge-base/docs/chat/uikit/react/index.md +6 -0
- package/knowledge-base/docs/chat/uikit/uniapp/index.md +6 -0
- package/knowledge-base/docs/chat/uikit/vue3/index.md +6 -0
- package/knowledge-base/docs/chat/webhook.md +73 -0
- package/knowledge-base/products.yaml +2 -2
- package/knowledge-base/slices/chat/web/at-mention.md +417 -0
- package/knowledge-base/slices/chat/web/conversation-actions.md +178 -0
- package/knowledge-base/slices/chat/web/conversation-list.md +368 -0
- package/knowledge-base/slices/chat/web/detect-integration.md +36 -0
- package/knowledge-base/slices/chat/web/detect-style.md +88 -0
- package/knowledge-base/slices/chat/web/direct-chat-entry.md +210 -0
- package/knowledge-base/slices/chat/web/login-auth.md +244 -0
- package/knowledge-base/slices/chat/web/message-base-actions.md +273 -0
- package/knowledge-base/slices/chat/web/message-input.md +209 -0
- package/knowledge-base/slices/chat/web/message-list.md +532 -0
- package/knowledge-base/slices/chat/web/send-custom-message.md +189 -0
- package/knowledge-base/slices/chat/web/send-media.md +214 -0
- package/knowledge-base/slices/chat/web/state-api-skeleton.md +60 -0
- package/knowledge-base/slices/chat/web/style-guide.md +210 -0
- package/package.json +5 -1
- package/skills/trtc/SKILL.md +57 -23
- package/skills/trtc/hooks/topic_phase_gate.py +5 -0
- package/skills/trtc/tests/test_reporting_v2_docs_query.py +175 -0
- package/skills/trtc/tools/kb.py +130 -0
- package/skills/trtc/tools/reporting.py +23 -2
- package/skills/trtc/tools/reporting_v2.py +501 -0
- package/skills/trtc-ai-service/README.ja.md +209 -0
- package/skills/trtc-ai-service/README.md +23 -9
- package/skills/trtc-ai-service/README.zh-CN.md +23 -9
- package/skills/trtc-ai-service/SKILL.md +4 -4
- package/skills/trtc-chat/.docs-query.yaml +9 -0
- package/skills/trtc-chat/SKILL.md +186 -0
- package/skills/trtc-chat/docs/SKILL.md +31 -0
- package/skills/trtc-chat/flows/maintenance.md +19 -0
- package/skills/trtc-chat/flows/onboarding.md +79 -0
- package/skills/trtc-chat/references/01-detect-project.md +136 -0
- package/skills/trtc-chat/references/02-path-a-questions.md +182 -0
- package/skills/trtc-chat/references/02-path-a-scaffold-template.md +59 -0
- package/skills/trtc-chat/references/02-path-a-script.md +266 -0
- package/skills/trtc-chat/references/02-path-a-templates.md +159 -0
- package/skills/trtc-chat/references/03-path-b-script.md +226 -0
- package/skills/trtc-chat/references/04-path-c-script.md +95 -0
- package/skills/trtc-chat/references/04-uikit-redirect.md +32 -0
- package/skills/trtc-chat/references/05-path-d-script.md +735 -0
- package/skills/trtc-chat/references/05-slice-loading.md +139 -0
- package/skills/trtc-chat/references/06-a-defensive-coding.md +139 -0
- package/skills/trtc-chat/references/06-hard-rules.md +190 -0
- package/skills/trtc-chat/references/08-state-config.md +231 -0
- package/skills/trtc-chat/references/09-troubleshoot.md +91 -0
- package/skills/trtc-chat/references/10-references-index.md +63 -0
- package/skills/trtc-chat/references/11-what-to-do-next-template.md +338 -0
- package/skills/trtc-chat/references/12-page-composition.md +73 -0
- package/skills/trtc-chat/references/13-reporting.md +166 -0
- package/skills/trtc-chat/references/14-official-docs.md +169 -0
- package/skills/trtc-chat/references/execution-units.yaml +27 -0
- package/skills/trtc-chat/references/vue3.md +104 -0
- package/skills/trtc-chat/tests/test_chat_bundle_contract.py +330 -0
- package/skills/trtc-chat/tools/__init__.py +5 -0
- package/skills/trtc-chat/tools/_delegate.py +32 -0
- package/skills/trtc-chat/tools/flow.py +8 -0
- package/skills/trtc-chat/tools/kb.py +8 -0
- package/skills/trtc-chat/tools/reporting_v2.py +8 -0
- package/skills/trtc-chat/tools/session.py +8 -0
- package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
- package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: chat/conversation-list
|
|
3
|
+
name: 会话列表
|
|
4
|
+
product: chat
|
|
5
|
+
platform: web
|
|
6
|
+
description: 会话列表(加载 / 渲染 + 置顶 / 删除 / 免打扰 基础操作)
|
|
7
|
+
applies-to: [tuikit-atomicx-vue3]
|
|
8
|
+
sdk-version: "tuikit-atomicx-vue3 >=6.0.0"
|
|
9
|
+
depends-on-stores: []
|
|
10
|
+
trigger-keywords: [会话列表, conversation list, 最近联系人, 置顶, pin, 删除会话, 免打扰, mute, do not disturb, 发起单聊, 发起群聊, 创建会话, 创建群组, 冷启动]
|
|
11
|
+
prerequisites: [login-auth]
|
|
12
|
+
tags: [chat, vue3]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. 这个 slice 处理什么
|
|
16
|
+
|
|
17
|
+
路径 A 基础功能。在 `src/components/chat/ConversationList.vue` 通过 `useConversationListStore()` 拉取并渲染会话列表。**只覆盖:加载 / 渲染 + 置顶 / 删除 / 免打扰**。
|
|
18
|
+
|
|
19
|
+
> SDK 一次性返回全部会话,不分页。本 slice 不暴露 `hasMoreConversations` / `loadMoreConversations`。
|
|
20
|
+
> 其他操作(标星 / 标已读未读 / 草稿 / 清空消息)见 `features/conversation-actions.md`。
|
|
21
|
+
|
|
22
|
+
## 2. AI 思考清单
|
|
23
|
+
|
|
24
|
+
- 是否要分组?(默认无分组传 `undefined`,按需传 `conversationGroup` 字符串)
|
|
25
|
+
- 点击会话后怎么切换?(`emit('select', id)` / 路由跳转 / 全局 store)
|
|
26
|
+
- 是否需要显示草稿提示 / 群标识 / 免打扰图标?
|
|
27
|
+
- 操作触发方式?(右键菜单 / 长按 / 侧滑 / 悬浮按钮)
|
|
28
|
+
|
|
29
|
+
## 3. SDK API 必读
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { useConversationListStore } from 'tuikit-atomicx-vue3/chat'
|
|
33
|
+
import { ReceiveMessageOption, ConversationType, MessageType } from 'tuikit-atomicx-vue3/chat'
|
|
34
|
+
import type { ConversationInfo } from 'tuikit-atomicx-vue3/chat'
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
conversationList, // ComputedRef<ConversationInfo[]>
|
|
38
|
+
totalUnreadCount, // ComputedRef<number>
|
|
39
|
+
loadConversations, // (option?) => Promise
|
|
40
|
+
getConversationInfo, // (conversationID: string) => Promise<ConversationInfo> — 获取/创建单条会话
|
|
41
|
+
pinConversation, // (id, pin: boolean) => Promise
|
|
42
|
+
deleteConversation, // (id) => Promise
|
|
43
|
+
setReceiveMessageOpt, // (id, opt: ReceiveMessageOption) => Promise
|
|
44
|
+
} = useConversationListStore(/* 可选 conversationGroup?: string */)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
创建群组(§ 4.7 发起群聊用):
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { useGroupStore } from 'tuikit-atomicx-vue3/chat'
|
|
51
|
+
import { GroupType } from 'tuikit-atomicx-vue3/chat'
|
|
52
|
+
|
|
53
|
+
const { createGroup } = useGroupStore()
|
|
54
|
+
// createGroup(params: GroupCreateParams) => Promise<string> // 返回值直接是 groupID
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`GroupCreateParams` 完整字段:
|
|
58
|
+
|
|
59
|
+
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
60
|
+
|---|---|---|---|---|
|
|
61
|
+
| `groupName` | `string` | ✅ | — | 群名称 |
|
|
62
|
+
| `groupType` | `GroupType` | 否 | `GroupType.Work` | 群类型枚举(Work / Public / Meeting / AVChatRoom) |
|
|
63
|
+
| `groupID` | `string` | 否 | SDK 自动生成 | 自定义群 ID,不填则由服务端生成 |
|
|
64
|
+
| `avatarURL` | `string` | 否 | — | 群头像 URL |
|
|
65
|
+
| `memberList` | `string[]` | 否 | `[]` | 初始成员 userID 列表 |
|
|
66
|
+
|
|
67
|
+
- ❗ `groupType` 必须用 `GroupType` 枚举,不准写字符串
|
|
68
|
+
- ❌ `groupType: 'Public'`
|
|
69
|
+
- ✅ `groupType: GroupType.Public`
|
|
70
|
+
- ❗ § 4.7 发起群聊默认使用 `GroupType.Public`(非默认的 Work,因为 Work 群不允许主动加入)
|
|
71
|
+
|
|
72
|
+
`ConversationInfo` 关键字段:
|
|
73
|
+
|
|
74
|
+
| 字段 | 类型 | 用途 |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `conversationID` | `string` | 唯一 ID(含 `C2C` / `GROUP` 前缀) |
|
|
77
|
+
| `type` | `ConversationType \| undefined` | `Group` / `C2C`,渲染图标 |
|
|
78
|
+
| `groupType` | `GroupType \| undefined` | 群类型(Work / Public / Meeting / AVChatRoom),仅群聊有值 |
|
|
79
|
+
| `title` | `string \| undefined` | 已合成的会话名 |
|
|
80
|
+
| `avatarURL` | `string \| undefined` | 已合成的头像 URL |
|
|
81
|
+
| `isPinned` | `boolean` | 是否置顶 |
|
|
82
|
+
| `unreadCount` | `number` | 未读数 |
|
|
83
|
+
| `draft` | `string \| undefined` | 草稿(非空时优先于 lastMessage 显示) |
|
|
84
|
+
| `lastMessage` | `MessageInfo \| undefined` | 最后一条消息 |
|
|
85
|
+
| `receiveOption` | `ReceiveMessageOption` | 免打扰状态 |
|
|
86
|
+
| `groupAtInfoList` | `GroupAtInfo[] \| undefined` | 群 @ 信息列表(含 @ 自己 / @ 所有人),有值时在徽标旁显示 @ 提示 |
|
|
87
|
+
| `conversationMarkList` | `ConversationMarkType[]` | 会话标记列表(Star / Unread 等枚举值);空数组表示无标记 |
|
|
88
|
+
| `conversationGroupList` | `string[]` | 所属会话分组名列表;空数组表示未分组 |
|
|
89
|
+
|
|
90
|
+
lastMessage 预览取值:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
draft 非空 → "[Draft] " + draft
|
|
94
|
+
否则按 lastMessage.messageType 映射:
|
|
95
|
+
Text → .messagePayload.text | Image → [Image] | Video → [Video]
|
|
96
|
+
Audio → [Audio] | File → [File] | Face → [Sticker]
|
|
97
|
+
Custom → [Custom] | Merged → [Chat Record] | Tips → [Notice]
|
|
98
|
+
Stream → .messagePayload.markdown | 兜底 → [Message]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 4. Hard rules
|
|
102
|
+
|
|
103
|
+
> ❗ 所有 SDK 异步调用必须遵循 `references/06-a-defensive-coding.md` 防御编程规范(try/catch/finally、formatError、错误反馈形式、状态锁)。本节规则是该规范的专属补充,不替代它。
|
|
104
|
+
> ❗ 写 UI 代码前必须先 `read_file _base/style-guide.md`,按其规范生成样式,不准跳过。
|
|
105
|
+
|
|
106
|
+
### 4.1 数据 / 加载
|
|
107
|
+
|
|
108
|
+
- ❗ 登录完成后再调 `loadConversations()`(`onMounted` + `try/finally` 控制 loading 态)
|
|
109
|
+
- ❗ 直接用 `conversationList` ComputedRef 渲染,不准赋给本地 ref
|
|
110
|
+
- ❌ `const list = ref(conversationList.value)` — 丢响应性
|
|
111
|
+
- ❗ 不准手动 `chat.on(...)` / `setInterval` 轮询,store 已自动同步
|
|
112
|
+
- ❗ 不准写"加载更多"按钮(不分页)
|
|
113
|
+
- ❗ 三态显式区分:loading / empty / 有数据
|
|
114
|
+
|
|
115
|
+
### 4.2 头像渲染
|
|
116
|
+
|
|
117
|
+
- ❗ 优先级:`conv.avatarURL` 非空 → `<img>`(必须有 `@error` 兜底)→ 字母头像(取首个 Unicode 码点,背景色按 ID 哈希稳定)
|
|
118
|
+
- ❌ 不做 `@error` 兜底,静默显示破图
|
|
119
|
+
- ❌ 用外网占位图(`placehold.co` / `dicebear`)
|
|
120
|
+
- ❌ `title[0]` 直接切(emoji 会坏),应 `[...title][0]?.toUpperCase()`
|
|
121
|
+
- ❌ 背景色 `Math.random()`(刷新闪烁)
|
|
122
|
+
|
|
123
|
+
### 4.3 置顶 / 删除
|
|
124
|
+
|
|
125
|
+
- ❗ `pinConversation(id, !conv.isPinned)` 第二参必须为布尔
|
|
126
|
+
- ❌ 漏传第二参 / 传字符串
|
|
127
|
+
- ❗ `deleteConversation(id)` 后不准手动 `splice`,store 自动同步
|
|
128
|
+
- ❌ `conversationList.value.splice(idx, 1)`
|
|
129
|
+
- ❗ 删除必须二次确认,按钮做危险态:
|
|
130
|
+
- 有 UI 库 → 用 UI 库的 Dialog 组件
|
|
131
|
+
- 无 UI 库 → 自建最小 Dialog(有 Tailwind/SCSS 复用已有 CSS 方案;无 CSS 方案时用 `confirm()` 并标 `// TODO: 替换为 Dialog 组件`)
|
|
132
|
+
- ❗ 已置顶项必须有视觉提示(pin 图标 / 底色变化)
|
|
133
|
+
- ❗ 必须暴露至少一种触发入口,不准只写函数不给 UI
|
|
134
|
+
|
|
135
|
+
### 4.4 选中时清未读数
|
|
136
|
+
|
|
137
|
+
- ❗ `clearConversationUnreadCount` 从 `useConversationListStore()` 解构(与 § 3 同一个 store 实例,不需要额外 import)
|
|
138
|
+
- ❗ 选中会话时内部调用 `clearConversationUnreadCount(conversationID)` 清零未读
|
|
139
|
+
- ❗ 相同 conversationID 连续触发需防抖(判断 ID 未变则跳过,不重复调用)
|
|
140
|
+
- ❗ 此调用在 `emit('select', conversationID)` 之前完成,父组件不感知
|
|
141
|
+
- ❌ 把 clearConversationUnreadCount 暴露给父组件调用
|
|
142
|
+
- ❌ 不做防抖,快速连点同一会话多次调接口
|
|
143
|
+
- ❌ 从其他 store / 自行 import SDK 方法调用
|
|
144
|
+
|
|
145
|
+
### 4.5 免打扰
|
|
146
|
+
|
|
147
|
+
- ❗ 状态判定必须用枚举:`conv.receiveOption !== ReceiveMessageOption.Receive`
|
|
148
|
+
- ❌ 魔法数字 `conv.receiveOption !== 1`
|
|
149
|
+
- ❌ 字符串 `'mute'` / 布尔 `true`
|
|
150
|
+
- ❗ 切换传 `ReceiveMessageOption` 枚举值
|
|
151
|
+
- ❗ 必须有视觉反馈(铃铛图标 / 菜单文案切换)
|
|
152
|
+
- ❗ 免打扰下未读徽标**必须变灰**(不能仍是高饱和主色)
|
|
153
|
+
- ❗ 免打扰不改变 `unreadCount` 数字,只是 UI 降级
|
|
154
|
+
|
|
155
|
+
### 4.6 冷启动(首次会话列表为空)
|
|
156
|
+
|
|
157
|
+
- ❗ 登录成功 + `loadConversations()` 后 `conversationList.length === 0` → 自动与 `administrator` 创建一条 C2C 会话
|
|
158
|
+
- ❗ 使用 `useConversationListStore().getConversationInfo('C2Cadministrator')` 触发会话创建
|
|
159
|
+
- ❗ 仅当列表确实为空时触发,已有会话不重复创建
|
|
160
|
+
- ❗ 不发消息,仅建立会话关系(让用户看到非空列表即可)
|
|
161
|
+
- ❌ 自动发一条"欢迎消息"(侵入用户数据)
|
|
162
|
+
|
|
163
|
+
### 4.7 创建会话入口(列表头部)
|
|
164
|
+
|
|
165
|
+
- ❗ 会话列表顶部必须有两个创建入口:**发起单聊** + **发起群聊**
|
|
166
|
+
- ❗ 这是基础件套的一部分,不需要额外命中 feature slice
|
|
167
|
+
|
|
168
|
+
#### 发起单聊
|
|
169
|
+
|
|
170
|
+
- ❗ **必须以 Dialog 弹窗形式呈现**,不准 inline 展开在列表区域内——inline 表单破坏会话列表布局
|
|
171
|
+
- 弹窗内容:userID 输入框 + 确定按钮 + 取消按钮
|
|
172
|
+
- 用户点确定 → 调 `useConversationListStore().getConversationInfo('C2C' + userID)` → 创建/获取会话 → emit select 跳转
|
|
173
|
+
- ❗ **失败时用 Toast 提示**(居中/偏上),不准在列表区域内 inline 显示 errorMsg——errorMsg 会遮挡会话列表
|
|
174
|
+
- ❌ 没有取消按钮
|
|
175
|
+
- ❌ inline 展开在列表顶部
|
|
176
|
+
- ❌ 没有输入入口,只能等别人先发消息
|
|
177
|
+
|
|
178
|
+
#### 发起群聊
|
|
179
|
+
|
|
180
|
+
- ❗ **必须以 Dialog 弹窗形式呈现**,不准 inline 展开在列表区域内
|
|
181
|
+
- 调 `useGroupStore().createGroup(params)` 创建群组
|
|
182
|
+
- 参数默认值:
|
|
183
|
+
- `groupType`: `GroupType.Public`
|
|
184
|
+
- `groupName`: `'group_test'`(用户可修改)
|
|
185
|
+
- `groupID`: 不填(IM 后台自动生成)
|
|
186
|
+
- `memberList`: 由用户输入(内容可以不填,不填则群内只有群主自己)
|
|
187
|
+
- ❗ `groupType` 必须传 `GroupType` 枚举值
|
|
188
|
+
- ❌ `groupType: 'Public'`(字符串会报错)
|
|
189
|
+
- 创建成功后自动跳转到该群会话(`emit('select', 'GROUP' + groupID)`)
|
|
190
|
+
- UI 形式:Dialog 弹窗,包含:
|
|
191
|
+
- 群名输入框(默认值 `group_test`,用户可修改)
|
|
192
|
+
- 成员 userID 输入框(**UI 必须有此输入框**;多个 userID 英文逗号分隔;内容可以不填,不填则群内只有群主自己)
|
|
193
|
+
- 确认按钮 → 调 `createGroup` → 跳转到群会话
|
|
194
|
+
- ❌ 没有创建群聊入口
|
|
195
|
+
|
|
196
|
+
## 5. 反例库
|
|
197
|
+
|
|
198
|
+
> § 4 各条已内联主要反例。本节补充跨规则复合错误:
|
|
199
|
+
|
|
200
|
+
- ❌ 顶部 `const list = (await loadConversations()).data` 赋给本地 ref → 同时违反 4.1(丢响应性)+ 后续操作(pin/delete/mute)不会反映到 UI
|
|
201
|
+
- ❌ 切换免打扰时顺手清了 `unreadCount`(免打扰只是 UI 降级,不改数据)
|
|
202
|
+
|
|
203
|
+
## 6. UI 自由度
|
|
204
|
+
|
|
205
|
+
> 样式由 AI 自由发挥,遵循项目已有 CSS 方案(见 `_base/detect-style.md`)。
|
|
206
|
+
|
|
207
|
+
✅ 完全自由:列表样式 / 头像形状 / 字母头像配色 / 触发方式 / 徽标位置 / 加载态插画
|
|
208
|
+
⚠️ 必须遵循项目现状:UI 库 / CSS 方案 / 同类组件命名
|
|
209
|
+
⚠️ § 4 强约束不可放宽
|
|
210
|
+
|
|
211
|
+
## 7. 参考实现
|
|
212
|
+
|
|
213
|
+
```vue
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
import { ref, onMounted } from 'vue'
|
|
216
|
+
import { useConversationListStore, useGroupStore } from 'tuikit-atomicx-vue3/chat'
|
|
217
|
+
import { ConversationType, ReceiveMessageOption, MessageType, GroupType } from 'tuikit-atomicx-vue3/chat'
|
|
218
|
+
import type { ConversationInfo } from 'tuikit-atomicx-vue3/chat'
|
|
219
|
+
|
|
220
|
+
const emit = defineEmits<{ select: [id: string] }>()
|
|
221
|
+
|
|
222
|
+
const {
|
|
223
|
+
conversationList,
|
|
224
|
+
loadConversations,
|
|
225
|
+
getConversationInfo,
|
|
226
|
+
pinConversation,
|
|
227
|
+
deleteConversation,
|
|
228
|
+
setReceiveMessageOpt,
|
|
229
|
+
} = useConversationListStore()
|
|
230
|
+
|
|
231
|
+
const { createGroup } = useGroupStore()
|
|
232
|
+
|
|
233
|
+
const loading = ref(true)
|
|
234
|
+
|
|
235
|
+
onMounted(async () => {
|
|
236
|
+
try {
|
|
237
|
+
await loadConversations()
|
|
238
|
+
// § 4.6 冷启动:列表为空时自动创建 administrator 会话
|
|
239
|
+
if (conversationList.value.length === 0) {
|
|
240
|
+
await getConversationInfo('C2Cadministrator')
|
|
241
|
+
}
|
|
242
|
+
} finally { loading.value = false }
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// ── 创建会话入口(§ 4.7) ──
|
|
246
|
+
|
|
247
|
+
async function startC2C(userID: string) {
|
|
248
|
+
if (!userID.trim()) return
|
|
249
|
+
await getConversationInfo('C2C' + userID)
|
|
250
|
+
emit('select', 'C2C' + userID)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function startGroup(groupName = 'group_test', memberList: string[] = []) {
|
|
254
|
+
const groupID = await createGroup({
|
|
255
|
+
groupType: GroupType.Public,
|
|
256
|
+
groupName,
|
|
257
|
+
...(memberList.length ? { memberList } : {}),
|
|
258
|
+
})
|
|
259
|
+
emit('select', 'GROUP' + groupID)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── 列表操作 ──
|
|
263
|
+
|
|
264
|
+
function getAvatarFallback(conv: ConversationInfo): string {
|
|
265
|
+
const title = conv.title ?? conv.conversationID
|
|
266
|
+
return [...title][0]?.toUpperCase() ?? '?'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getLastMessagePreview(conv: ConversationInfo): string {
|
|
270
|
+
if (conv.draft) return `[Draft] ${conv.draft}`
|
|
271
|
+
const msg = conv.lastMessage
|
|
272
|
+
if (!msg) return ''
|
|
273
|
+
switch (msg.messageType) {
|
|
274
|
+
case MessageType.Text: return msg.messagePayload.text
|
|
275
|
+
case MessageType.Image: return '[Image]'
|
|
276
|
+
case MessageType.Video: return '[Video]'
|
|
277
|
+
case MessageType.Audio: return '[Audio]'
|
|
278
|
+
case MessageType.File: return '[File]'
|
|
279
|
+
case MessageType.Face: return '[Sticker]'
|
|
280
|
+
case MessageType.Custom: return '[Custom]'
|
|
281
|
+
case MessageType.Merged: return '[Chat Record]'
|
|
282
|
+
case MessageType.Tips: return '[Notice]'
|
|
283
|
+
case MessageType.Stream: return msg.messagePayload.markdown || '[Stream]'
|
|
284
|
+
default: return '[Message]'
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const isMuted = (c: ConversationInfo) => c.receiveOption !== ReceiveMessageOption.Receive
|
|
289
|
+
|
|
290
|
+
async function handlePin(conv: ConversationInfo) {
|
|
291
|
+
await pinConversation(conv.conversationID, !conv.isPinned)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function handleDelete(conv: ConversationInfo) {
|
|
295
|
+
// 有 UI 库 → 用 UI 库 Dialog;有 Tailwind → 自建最小 Dialog(见 style-guide.md §3 弹窗规范)
|
|
296
|
+
// 无任何 CSS 方案(极少情况)→ confirm() 并标 TODO
|
|
297
|
+
const confirmed = await showConfirmDialog(`确认删除会话「${conv.title ?? conv.conversationID}」?`)
|
|
298
|
+
if (!confirmed) return
|
|
299
|
+
await deleteConversation(conv.conversationID)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function handleMute(conv: ConversationInfo) {
|
|
303
|
+
await setReceiveMessageOpt(
|
|
304
|
+
conv.conversationID,
|
|
305
|
+
isMuted(conv) ? ReceiveMessageOption.Receive : ReceiveMessageOption.NotNotify,
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
</script>
|
|
309
|
+
|
|
310
|
+
<template>
|
|
311
|
+
<div class="conv-list">
|
|
312
|
+
<!-- § 4.7 创建会话入口 -->
|
|
313
|
+
<div class="conv-list-header">
|
|
314
|
+
<!-- AI 自由选择 UI 形态:按钮 / icon / dropdown -->
|
|
315
|
+
<!-- startC2C: 弹出输入 userID 的 Dialog -->
|
|
316
|
+
<!-- startGroup: 弹出填写群名(默认 group_test)+ 成员 userID 的 Dialog -->
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div v-if="loading">Loading...</div>
|
|
320
|
+
<div v-else-if="conversationList.length === 0">No conversations yet</div>
|
|
321
|
+
<template v-else>
|
|
322
|
+
<div
|
|
323
|
+
v-for="conv in conversationList"
|
|
324
|
+
:key="conv.conversationID"
|
|
325
|
+
class="conv-item"
|
|
326
|
+
:class="{ pinned: conv.isPinned, muted: isMuted(conv) }"
|
|
327
|
+
@click="emit('select', conv.conversationID)"
|
|
328
|
+
>
|
|
329
|
+
<img v-if="conv.avatarURL" :src="conv.avatarURL" @error="($event.target as HTMLImageElement).style.display='none'" />
|
|
330
|
+
<span v-else class="avatar-fallback">{{ getAvatarFallback(conv) }}</span>
|
|
331
|
+
|
|
332
|
+
<div class="conv-body">
|
|
333
|
+
<span class="conv-title">{{ conv.title ?? conv.conversationID }}</span>
|
|
334
|
+
<p class="conv-preview">{{ getLastMessagePreview(conv) }}</p>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<span v-if="conv.unreadCount > 0" class="badge" :class="{ 'badge-muted': isMuted(conv) }">
|
|
338
|
+
{{ conv.unreadCount > 99 ? '99+' : conv.unreadCount }}
|
|
339
|
+
</span>
|
|
340
|
+
</div>
|
|
341
|
+
</template>
|
|
342
|
+
</div>
|
|
343
|
+
</template>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 可改
|
|
347
|
+
|
|
348
|
+
- 样式 / class 名 / Tailwind / 项目 CSS 方案
|
|
349
|
+
- 触发方式(右键 / 长按 / 侧滑 / 悬浮按钮)
|
|
350
|
+
- 二次确认形式(Dialog 组件的样式和触发方式)
|
|
351
|
+
- 字母头像配色板
|
|
352
|
+
- 图标库选型(铃铛 / pin 图标)
|
|
353
|
+
- 创建会话入口的 UI 形态(按钮 / icon / dropdown / Dialog)
|
|
354
|
+
- 创建群聊表单的布局和样式
|
|
355
|
+
|
|
356
|
+
### 不可改
|
|
357
|
+
|
|
358
|
+
- `useConversationListStore()` 解构出的 API 名
|
|
359
|
+
- `useGroupStore().createGroup()` 创建群聊的 API 名
|
|
360
|
+
- 冷启动目标为 `C2Cadministrator`(§ 4.6)
|
|
361
|
+
- 创建单聊用 `getConversationInfo('C2C' + userID)`(§ 4.7)
|
|
362
|
+
- 创建群聊默认 `GroupType.Public`(§ 4.7)
|
|
363
|
+
- 头像兜底链顺序(§ 4.2)
|
|
364
|
+
- 删除二次确认(§ 4.3)
|
|
365
|
+
- `pinConversation` 第二参为布尔
|
|
366
|
+
- `setReceiveMessageOpt` 传枚举(§ 4.5)
|
|
367
|
+
- 免打扰下徽标变灰
|
|
368
|
+
- 不加 `chat.on(...)` / 不 `setInterval`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: chat/detect-integration
|
|
3
|
+
name: 集成信号探测
|
|
4
|
+
product: chat
|
|
5
|
+
platform: web
|
|
6
|
+
description: 集成信号探测口径(仅看是否依赖 tuikit-atomicx-vue3)
|
|
7
|
+
applies-to: [tuikit-atomicx-vue3]
|
|
8
|
+
sdk-version: "tuikit-atomicx-vue3 >=6.0.0"
|
|
9
|
+
depends-on-stores: []
|
|
10
|
+
trigger-keywords: []
|
|
11
|
+
prerequisites: []
|
|
12
|
+
tags: [chat, vue3]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# 集成信号探测口径
|
|
16
|
+
|
|
17
|
+
> dispatcher Step 1 探测时硬编码 `read_file`,不通过 trigger-keywords 命中。
|
|
18
|
+
> 与 `chat/references/01-detect-project.md` § 2 互为参照。
|
|
19
|
+
|
|
20
|
+
## 探测策略
|
|
21
|
+
|
|
22
|
+
最小跑通:**只看 `package.json` 是否依赖 `tuikit-atomicx-vue3`**,不区分 UIKit,不单独识别 `tuikit-atomicx-vue3`。
|
|
23
|
+
|
|
24
|
+
## 规则
|
|
25
|
+
|
|
26
|
+
- ❗ 必须实际 `read_file package.json`,不准凭训练数据猜
|
|
27
|
+
- ❌ 没读就直接判定"已集成"
|
|
28
|
+
- ❗ 唯一判定信号:`tuikit-atomicx-vue3` 在 `dependencies` 或 `devDependencies`
|
|
29
|
+
|
|
30
|
+
## 判定结果
|
|
31
|
+
|
|
32
|
+
| 命中 | 判定 | 走向 |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| deps 含 `tuikit-atomicx-vue3` | 已集成 | 路径 B |
|
|
35
|
+
| 不含,但目录非空 | 未集成 | 路径 A |
|
|
36
|
+
| 空目录 / 新项目 | 未集成 | 路径 A(先经新建引导) |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: chat/detect-style
|
|
3
|
+
name: CSS 与 UI 库检测
|
|
4
|
+
product: chat
|
|
5
|
+
platform: web
|
|
6
|
+
description: 项目 CSS 方案与 UI 库检测(识别已有体系 + 可复用组件)
|
|
7
|
+
applies-to: [tuikit-atomicx-vue3]
|
|
8
|
+
sdk-version: "tuikit-atomicx-vue3 >=6.0.0"
|
|
9
|
+
depends-on-stores: []
|
|
10
|
+
trigger-keywords: []
|
|
11
|
+
prerequisites: []
|
|
12
|
+
tags: [chat, vue3]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# 项目 CSS 方案检测
|
|
16
|
+
|
|
17
|
+
> dispatcher Step 1 探测时硬编码 `read_file`,不通过 trigger-keywords 命中。
|
|
18
|
+
|
|
19
|
+
## 目标
|
|
20
|
+
|
|
21
|
+
识别项目已有的 CSS 体系和 UI 组件库,确保生成代码遵循项目现有方案,不引入冲突。
|
|
22
|
+
|
|
23
|
+
## 1. CSS 方案识别
|
|
24
|
+
|
|
25
|
+
| 探测信号 | 判定 | 后续行为 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `tailwindcss` / `@tailwindcss/*` 在 deps | Tailwind | 用 Tailwind utility class 写样式 |
|
|
28
|
+
| `unocss` 在 deps | UnoCSS | 按 UnoCSS 语法写 |
|
|
29
|
+
| `element-plus` 在 deps | Element Plus | 用其组件 + 遵循其变量体系 |
|
|
30
|
+
| `naive-ui` 在 deps | Naive UI | 用其组件 |
|
|
31
|
+
| `vant` 在 deps | Vant | 用其组件 |
|
|
32
|
+
| `ant-design-vue` 在 deps | Ant Design Vue | 用其组件 |
|
|
33
|
+
| `vuetify` 在 deps | Vuetify | 用其组件 |
|
|
34
|
+
| `*.module.css` / `*.module.scss` 存在 | CSS Modules | 用 CSS Modules 写 |
|
|
35
|
+
| `<style scoped>` 为主 | Vue Scoped | 用 scoped style |
|
|
36
|
+
| 都没识别到 | 无 CSS 方案 | 空项目默认装 Tailwind |
|
|
37
|
+
|
|
38
|
+
- ❗ 探测到已有 CSS 方案 → 生成代码直接使用该方案
|
|
39
|
+
- ❌ 项目用 Element Plus,AI 手写 `<button>` 不用 `<el-button>`
|
|
40
|
+
- ❗ 已有方案不可叠加另一套
|
|
41
|
+
- ❌ 项目用 UnoCSS,路径 A 还装 Tailwind(冲突)
|
|
42
|
+
- ❌ 项目用 Vuetify,还引入 Tailwind(冲突)
|
|
43
|
+
- ❗ 空项目(无任何 CSS 方案)→ 路径 A 默认装 Tailwind
|
|
44
|
+
|
|
45
|
+
## 2. UI 组件库识别
|
|
46
|
+
|
|
47
|
+
探测 `package.json` deps 中的 UI 库:
|
|
48
|
+
|
|
49
|
+
- 有 UI 库 → 优先使用其提供的组件(Button / Dialog / Card 等)
|
|
50
|
+
- 无 UI 库 → AI 自由实现组件
|
|
51
|
+
|
|
52
|
+
## 3. 可复用组件扫描
|
|
53
|
+
|
|
54
|
+
- ❗ 扫描 `src/components/ui/*` 或项目约定组件目录
|
|
55
|
+
- ❗ 能复用必须复用
|
|
56
|
+
- ❌ 项目有 `BaseAvatar.vue`,聊天里另写 `ChatAvatar.vue`
|
|
57
|
+
- ❌ 项目有 `AppDialog.vue`,自己用 `<div class="modal">`
|
|
58
|
+
- ❗ 复用时遵循该组件 API 约定(props / slot / emit)
|
|
59
|
+
- ❗ 结果列入 `config.json.reusable_components`
|
|
60
|
+
|
|
61
|
+
## 4. 命名约定识别
|
|
62
|
+
|
|
63
|
+
- 扫 `src/components/*` 的命名(kebab-case / PascalCase)
|
|
64
|
+
- 扫 `<script setup>` vs Options API
|
|
65
|
+
- TypeScript / JavaScript
|
|
66
|
+
|
|
67
|
+
## 5. 探测输出
|
|
68
|
+
|
|
69
|
+
写入 `config.json`:
|
|
70
|
+
|
|
71
|
+
```jsonc
|
|
72
|
+
{
|
|
73
|
+
"css_scheme": "tailwind", // tailwind | unocss | css-modules | scoped | plain
|
|
74
|
+
"ui_library": "element-plus", // element-plus | naive-ui | vant | ant-design-vue | vuetify | null
|
|
75
|
+
"naming": "PascalCase",
|
|
76
|
+
"script_style": "setup-script",
|
|
77
|
+
"reusable_components": [
|
|
78
|
+
"src/components/ui/card.vue",
|
|
79
|
+
"src/components/ui/dialog.vue"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 6. 反例(跨规则复合错误)
|
|
85
|
+
|
|
86
|
+
- ❌ 识别到项目用 Element Plus,但不用 `<el-card>` 自己手写 card 组件 → 违反 § 1 + § 2
|
|
87
|
+
- ❌ 项目有 Tailwind,路径 A 又装了一遍 Tailwind → 违反 § 1 "不可叠加"
|
|
88
|
+
- ❌ 项目有可复用的 `BaseDialog.vue`,聊天模块另起 `ChatDialog.vue` → 违反 § 3
|