@tencent-rtc/mcp 1.7.3 → 1.7.4

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 (29) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +13 -13
  3. package/knowledge/path-meta.js +23 -1
  4. package/package.json +1 -1
  5. package/resource/index/manifest.json +1 -1
  6. package/resource/knowledge/callkit/best-practice/android/video-chat-dating.md +497 -0
  7. package/resource/knowledge/callkit/best-practice/flutter/android-keep-call-alive-background.md +79 -0
  8. package/resource/knowledge/callkit/best-practice/flutter/background-incoming-call-no-notification.md +99 -0
  9. package/resource/knowledge/callkit/best-practice/flutter/ios-keep-call-alive-background.md +93 -0
  10. package/resource/knowledge/callkit/best-practice/flutter/ios-startup-crash-version-voip.md +68 -0
  11. package/resource/knowledge/callkit/core-sdk/miniprogram/call-engine.md +386 -0
  12. package/resource/knowledge/callkit/core-sdk/miniprogram/call-event.md +315 -0
  13. package/resource/knowledge/callkit/faq/uniapp-miniprogram.md +204 -0
  14. package/resource/knowledge/callkit/preset/harmonyos/api.md +362 -0
  15. package/resource/knowledge/callkit/preset/harmonyos/integration.md +318 -0
  16. package/resource/knowledge/callkit/preset/miniprogram/api-uniapp.md +430 -0
  17. package/resource/knowledge/callkit/preset/miniprogram/api.md +293 -0
  18. package/resource/knowledge/callkit/preset/miniprogram/integration.md +294 -0
  19. package/resource/knowledge/callkit/preset/miniprogram/uniapp-integration.md +224 -0
  20. package/resource/knowledge/callkit/preset/uni-app/api.md +339 -0
  21. package/resource/knowledge/callkit/preset/uni-app/integration.md +243 -0
  22. package/resource/knowledge/chat/preset/react/h5/integration.md +964 -0
  23. package/resource/knowledge/chat/preset/vue/h5/integration.md +717 -0
  24. package/resource/knowledge/rtcengine/web/plugin/23-advanced-support-detection.md +6 -6
  25. package/resource/knowledge/rtcengine/web/plugin/31-advanced-plugins-overview.md +8 -6
  26. package/resource/knowledge/rtcengine/web/plugin/42-advanced-realtime-transcriber.md +30 -10
  27. package/server.js +1 -1
  28. package/utils/report-cls-client.js +1 -1
  29. package/utils/report-es-client.js +1 -1
@@ -0,0 +1,964 @@
1
+ TUIKit is a React component library built on top of the Tencent Cloud Chat SDK. It provides common UI components covering conversations, chat, groups, and more. This guide walks you through quickly integrating TUIKit and implementing core features.
2
+
3
+ >
4
+
5
+ >
6
+
7
+ ## Key Concepts
8
+
9
+ chat-uikit-react consists of core UI components including ConversationListH5, MessageListH5, ChatHeaderH5, MessageInputH5, ChatSettingH5, SearchH5, and ContactH5. Each component is responsible for rendering different content.
10
+ 1. ConversationListH5 provides the conversation list component.
11
+
12
+ 2. Chat provides the conversation container component.
13
+
14
+ 3. MessageListH5 provides the message list component for conversations.
15
+
16
+ 4. ChatHeaderH5 provides the conversation header component.
17
+
18
+ 5. MessageInputH5 provides the message input component.
19
+
20
+ 6. ChatSettingH5 provides the management component for one-to-one and group chats.
21
+
22
+ 7. SearchH5 provides the cloud search component.
23
+
24
+ 8. ContactH5 provides the contacts component.
25
+
26
+ ## Prerequisites
27
+ - Node.js v18 or later, LTS version recommended
28
+
29
+ - React^18.2 || React^19.0.0
30
+
31
+ - TypeScript@^5.0.0
32
+
33
+ ## Quick Start with the Complete Example Project
34
+
35
+ If you want to quickly see the full result, or need a code reference during integration, you can clone our ready-made example project. The example project has completed Chat UIKit dependency installation, component imports, initialization configuration, and basic page integration — ideal as an integration reference.
36
+ ``` bash
37
+ git clone https://gitee.com/tencent-cloud-uikit/rtc-chat-web.git
38
+ cd chat-mobile-react
39
+ npm install
40
+ npm run dev
41
+ ```
42
+
43
+ > **Note:**
44
+ > Example project: [View on Gitee](https://gitee.com/tencent-cloud-uikit/rtc-chat-web)。
45
+ >
46
+ > The example project helps you quickly experience the integration flow. If you need to integrate into an existing project, continue reading the steps below.
47
+ >
48
+
49
+ ## Create a Project
50
+
51
+ Use Vite to create a new React project named chat-integration-react-h5 and follow the scaffold prompts to initialize it. After the initial project starts, remove the scaffold's sample files (default styles in src/App.css, src/index.css, src/assets, icons in public, etc.), keeping only src/main.tsx and index.html.
52
+
53
+ > **Warning:**
54
+ > 1. It is recommended to use npm for installation, as npm will automatically download required peerDependencies.
55
+ > 2. `npm create vite@latest` uses the latest Vite version, which may conflict with older Node.js versions. Please check your environment configuration.
56
+
57
+ [shell]
58
+ ``` bash
59
+ npm create vite@latest
60
+
61
+ ◇ Project name:
62
+ │ chat-integration-react-h5
63
+ │
64
+ ◇ Select a framework:
65
+ │ React
66
+ │
67
+ ◇ Select a variant:
68
+ │ TypeScript + React Compiler
69
+ │
70
+ ◇ Use ESLint instead of Oxlint?
71
+ │ Yes (ESLint)
72
+ │
73
+ ◇ Install with npm and start now?
74
+ │ Yes
75
+ ```
76
+
77
+ ## Download and Import Components
78
+
79
+ ### Step 1: Install Dependencies
80
+
81
+ Download [chat-uikit-react](https://www.npmjs.com/package/@tencentcloud/chat-uikit-react) via npm and use it in your project.
82
+
83
+ [shell]
84
+ ``` bash
85
+ npm install @tencentcloud/chat-uikit-react @tencentcloud/uikit-base-component-react
86
+ ```
87
+
88
+ ### Step 2: Import Components
89
+ 1. Configure the mobile viewport.
90
+
91
+ The first step for a great mobile experience: in the project root, set the default viewport in index.html to full-screen coverage with locked scaling, making the page feel more like a native app.
92
+
93
+ ``` java
94
+ <!DOCTYPE html>
95
+ <html lang="zh-CN">
96
+ <head>
97
+ <meta charset="UTF-8" />
98
+ <!-- H5 viewport: 覆盖刘海屏区域,锁定缩放,营造类原生应用的体验 -->
99
+ <meta
100
+ name="viewport"
101
+ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
102
+ />
103
+ <title>Chat UIKit H5 Quickstart (React)</title>
104
+ </head>
105
+ <body>
106
+ <div id="root"></div>
107
+ <script type="module" src="/src/main.tsx"></script>
108
+ </body>
109
+ </html>
110
+ ```
111
+ 2. Configure global base styles.
112
+
113
+ Create **src/styles.css** and set the global base styles.
114
+
115
+ ``` css
116
+ /* ------- 移动端基础布局 ------- */
117
+ * {
118
+ box-sizing: border-box;
119
+ }
120
+
121
+ html,
122
+ body,
123
+ #root {
124
+ width: 100%;
125
+ height: 100%;
126
+ margin: 0;
127
+ }
128
+
129
+ body {
130
+ /* 使用 dvh 来处理 iOS Safari 地址栏. */
131
+ min-height: 100vh;
132
+ min-height: 100dvh;
133
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
134
+ color: #1f2329;
135
+ background: #f5f6f8;
136
+ /* 移除移动版 WebKit 上的点击亮块。 */
137
+ -webkit-tap-highlight-color: transparent;
138
+ }
139
+
140
+ button {
141
+ font: inherit;
142
+ cursor: pointer;
143
+ }
144
+
145
+ /* 16px 可防止 iOS 在输入框获得焦点时自动放大屏幕。 */
146
+ input {
147
+ font-size: 16px;
148
+ }
149
+ ```
150
+ 3. Configure the app entry point.
151
+
152
+ Write the following into **src/main.tsx** to import styles at the app entry.
153
+
154
+ ``` typescript
155
+ import { createRoot } from 'react-dom/client';
156
+ import App from './App';
157
+ import './styles.css';
158
+
159
+ createRoot(document.getElementById('root')!).render(
160
+ <App />,
161
+ );
162
+ ```
163
+ 4. Configure the home page.
164
+
165
+ Write the following into **src/App.tsx** to route between the login page and chat page.
166
+
167
+ ``` typescript
168
+ import { useState } from 'react';
169
+ import { LoginStatus, UIKitProvider, useLoginState } from '@tencentcloud/chat-uikit-react';
170
+ import { LoginView } from './LoginView';
171
+ import { ChatLayout } from './ChatLayout';
172
+ import { languageResources } from './i18n';
173
+
174
+ function App() {
175
+ const { status } = useLoginState();
176
+ const isLogined = status === LoginStatus.SUCCESS;
177
+
178
+ // 语言在此处进行维护,并通过 UIKitProvider 的 `language` 属性进行配置。
179
+ // 这是控制语言的推荐方式
180
+ // 更新这个单一数据源,它驱动整个 UIKit。
181
+ const [language, setLanguage] = useState('zh-CN');
182
+
183
+ return (
184
+ <UIKitProvider language={language} languageResources={languageResources}>
185
+ {isLogined
186
+ ? <ChatLayout onChangeLanguage={setLanguage} />
187
+ : <LoginView language={language} onChangeLanguage={setLanguage} />}
188
+ </UIKitProvider>
189
+ );
190
+ }
191
+
192
+ export default App;
193
+ ```
194
+ 5. Configure the login page and chat main page.
195
+
196
+ Create **src/LoginView.tsx** and **src/LoginView.css** and write the following code to build the login page.
197
+
198
+ [src/LoginView.tsx]
199
+ ``` typescript
200
+ import { useState, type SubmitEvent } from 'react';
201
+ import { useLoginState, useUIKit } from '@tencentcloud/chat-uikit-react';
202
+ import './LoginView.css';
203
+
204
+ // 语言切换器中显示的语言。标签使用本来的语言,确保用户
205
+ // 不受当前 UI 语言影响,始终能识别自己的选项。
206
+ const LANGUAGES = [
207
+ { value: 'zh-CN', label: '中文' },
208
+ { value: 'en-US', label: 'English' },
209
+ ];
210
+
211
+ interface LoginViewProps {
212
+ language: string;
213
+ onChangeLanguage: (language: string) => void;
214
+ }
215
+
216
+ export function LoginView({ language, onChangeLanguage }: LoginViewProps) {
217
+ const { login } = useLoginState();
218
+ const { t } = useUIKit();
219
+ const [sdkAppId, setSdkAppId] = useState<number>();
220
+ const [userId, setUserId] = useState('');
221
+ const [userSig, setUserSig] = useState('');
222
+ const [loading, setLoading] = useState(false);
223
+ const [error, setError] = useState('');
224
+
225
+ const handleSubmit = async (e: SubmitEvent) => {
226
+ e.preventDefault();
227
+ setError('');
228
+ if (!sdkAppId || !userId || !userSig) {
229
+ setError(t('demo.error.required'));
230
+ return;
231
+ }
232
+
233
+ setLoading(true);
234
+ try {
235
+ await login({ SDKAppID: Number(sdkAppId), userID: userId, userSig });
236
+ // On success the login gate in App.tsx switches to ChatLayout automatically.
237
+ } catch (err) {
238
+ setError(err instanceof Error ? err.message : t('demo.error.loginFailed'));
239
+ } finally {
240
+ setLoading(false);
241
+ }
242
+ };
243
+
244
+ return (
245
+ <div className="login">
246
+ <div className="login__lang" role="group" aria-label="Language">
247
+ {LANGUAGES.map(item => (
248
+ <button
249
+ key={item.value}
250
+ type="button"
251
+ className={item.value === language ? 'login__lang-btn login__lang-btn--active' : 'login__lang-btn'}
252
+ onClick={() => onChangeLanguage(item.value)}
253
+ >
254
+ {item.label}
255
+ </button>
256
+ ))}
257
+ </div>
258
+
259
+ <form className="login__card" onSubmit={handleSubmit}>
260
+ <h1 className="login__title">{t('demo.appTitle')}</h1>
261
+
262
+ <label className="login__field">
263
+ <span>{t('demo.field.sdkAppId')}</span>
264
+ <input
265
+ type="number"
266
+ placeholder={t('demo.placeholder.sdkAppId')}
267
+ value={sdkAppId || ''}
268
+ onChange={e => setSdkAppId(Number(e.target.value))}
269
+ />
270
+ </label>
271
+
272
+ <label className="login__field">
273
+ <span>{t('demo.field.userId')}</span>
274
+ <input
275
+ type="text"
276
+ placeholder={t('demo.placeholder.userId')}
277
+ value={userId}
278
+ onChange={e => setUserId(e.target.value)}
279
+ />
280
+ </label>
281
+
282
+ <label className="login__field">
283
+ <span>{t('demo.field.userSig')}</span>
284
+ <input
285
+ type="text"
286
+ placeholder={t('demo.placeholder.userSig')}
287
+ value={userSig}
288
+ onChange={e => setUserSig(e.target.value)}
289
+ />
290
+ </label>
291
+
292
+ {error && <p className="login__error">{error}</p>}
293
+
294
+ <button className="login__submit" type="submit" disabled={loading}>
295
+ {loading ? t('demo.loggingIn') : t('demo.login')}
296
+ </button>
297
+ </form>
298
+ </div>
299
+ );
300
+ }
301
+ ```
302
+
303
+ [src/LoginView.css]
304
+ ``` css
305
+ .login {
306
+ position: relative;
307
+ display: flex;
308
+ width: 100%;
309
+ height: 100dvh;
310
+ align-items: center;
311
+ justify-content: center;
312
+ padding: max(20px, env(safe-area-inset-top)) 20px max(20px, env(safe-area-inset-bottom));
313
+ /* Soft natural-white backdrop with a subtle top-down gradient for depth. */
314
+ background: linear-gradient(180deg, #ffffff 0%, #f3f5f8 100%);
315
+ }
316
+
317
+ /* Segmented language switcher, pinned to the top-right. */
318
+ .login__lang {
319
+ position: absolute;
320
+ top: calc(16px + env(safe-area-inset-top));
321
+ right: 16px;
322
+ display: inline-flex;
323
+ padding: 3px;
324
+ border-radius: 999px;
325
+ background: #eef0f3;
326
+ }
327
+
328
+ .login__lang-btn {
329
+ min-width: 56px;
330
+ padding: 6px 12px;
331
+ border: 0;
332
+ border-radius: 999px;
333
+ background: transparent;
334
+ color: #6b7280;
335
+ font-size: 13px;
336
+ font-weight: 500;
337
+ transition: background 0.2s, color 0.2s;
338
+ }
339
+
340
+ .login__lang-btn--active {
341
+ background: #fff;
342
+ color: #1f2329;
343
+ box-shadow: 0 1px 2px rgb(16 24 40 / 10%);
344
+ }
345
+
346
+ .login__card {
347
+ display: flex;
348
+ width: 100%;
349
+ max-width: 360px;
350
+ flex-direction: column;
351
+ gap: 16px;
352
+ padding: 28px 24px;
353
+ border-radius: 16px;
354
+ background: #fff;
355
+ /* Layered, low-opacity shadow for a soft, natural lift off the white bg. */
356
+ border: 1px solid rgb(16 24 40 / 4%);
357
+ box-shadow:
358
+ 0 1px 2px rgb(16 24 40 / 4%),
359
+ 0 12px 32px rgb(16 24 40 / 8%);
360
+ }
361
+
362
+ .login__title {
363
+ margin: 0 0 4px;
364
+ font-size: 20px;
365
+ font-weight: 600;
366
+ text-align: center;
367
+ }
368
+
369
+ .login__field {
370
+ display: flex;
371
+ flex-direction: column;
372
+ gap: 6px;
373
+ font-size: 13px;
374
+ color: #6b7280;
375
+ }
376
+
377
+ .login__field input {
378
+ padding: 12px 14px;
379
+ border: 1px solid #e8eaed;
380
+ border-radius: 10px;
381
+ outline: none;
382
+ appearance: none;
383
+ }
384
+
385
+ .login__field input:focus {
386
+ border-color: #1464ff;
387
+ }
388
+
389
+ .login__error {
390
+ margin: 0;
391
+ color: #e34d59;
392
+ font-size: 13px;
393
+ }
394
+
395
+ .login__submit {
396
+ padding: 13px;
397
+ border: 0;
398
+ border-radius: 10px;
399
+ background: #1464ff;
400
+ color: #fff;
401
+ font-size: 16px;
402
+ font-weight: 600;
403
+ }
404
+
405
+ .login__submit:disabled {
406
+ opacity: 0.6;
407
+ }
408
+ ```
409
+
410
+ Create **src/ChatLayout.tsx** and **src/ChatLayout.css** and write the following code:
411
+
412
+ [src/ChatLayout.tsx]
413
+ ``` typescript
414
+ import { useEffect, useRef, useState, type ReactNode } from 'react';
415
+ import {
416
+ Chat,
417
+ ChatHeaderH5,
418
+ ChatSettingH5,
419
+ ContactInfoH5,
420
+ ContactListH5,
421
+ ConversationListH5,
422
+ MessageInputH5,
423
+ MessageListH5,
424
+ SearchH5,
425
+ VariantType,
426
+ useChatContext,
427
+ useLoginState,
428
+ useUIKit,
429
+ } from '@tencentcloud/chat-uikit-react';
430
+ import { IconBulletpoint, IconSearch } from '@tencentcloud/uikit-base-component-react';
431
+ import './ChatLayout.css';
432
+
433
+ type MobileView = 'conversations' | 'chat' | 'contacts' | 'contactInfo' | 'search' | 'setting';
434
+
435
+ interface ChatLayoutProps {
436
+ onChangeLanguage: (language: string) => void;
437
+ }
438
+
439
+ /** // 全屏视图;仅当前激活的视图可见(保持挂载以保留状态)。 */
440
+ function Screen({ active, children }: { active: boolean; children: ReactNode }) {
441
+ return (
442
+ <section className="screen" style={{ zIndex: active ? 1 : 0, visibility: active ? 'visible' : 'hidden' }}>
443
+ {children}
444
+ </section>
445
+ );
446
+ }
447
+
448
+ export function ChatLayout({ onChangeLanguage }: ChatLayoutProps) {
449
+
450
+ const { activeConversationID, activeConversation, sendMessage, setActiveConversation } = useChatContext();
451
+ const { loginUserInfo, logout } = useLoginState();
452
+ const { t, language } = useUIKit();
453
+ const [activeView, setActiveView] = useState<MobileView>('conversations');
454
+ // 记住搜索是从哪里打开的,使其返回按钮能回到原处。
455
+ const [previousView, setPreviousView] = useState<MobileView>('chat');
456
+ const defaultOpenConversationID = 'C2Cadministrator';
457
+
458
+ const activeViewRef = useRef(activeView);
459
+ const prevConversationIDRef = useRef(activeConversationID);
460
+ // 仅发送一次欢迎语
461
+ const sendWelcomeMessageOnce = useRef(false);
462
+
463
+ useEffect(() => {
464
+ activeViewRef.current = activeView;
465
+ }, [activeView]);
466
+
467
+ useEffect(() => {
468
+ const changed = prevConversationIDRef.current !== activeConversationID;
469
+ prevConversationIDRef.current = activeConversationID;
470
+ if (changed && activeConversationID && activeViewRef.current === 'conversations') {
471
+ setActiveView('chat');
472
+ }
473
+ }, [activeConversationID]);
474
+
475
+ // 应用创建(组件挂载)时仅发送一次欢迎语;切换会话不会重复发送。
476
+ useEffect(() => {
477
+ const timer = setTimeout(() => {
478
+ if (activeConversation?.conversationID === defaultOpenConversationID && !sendWelcomeMessageOnce.current) {
479
+ sendMessage({
480
+ type: 'textMessage',
481
+ text: `欢迎体验 Chat React 移动端WEB应用 Demo~
482
+
483
+ 您可以按照以下顺序体验 IM 的核心功能:
484
+ 1. 在输入框内发送一条文本消息。
485
+ 2. 输入框上的工具栏支持,语音通话、视频通话、图片、视频、文件发送等扩展功能。
486
+ 3. 如果您想添加好友,可以前往联系人页面点击"添加好友/群聊"。
487
+ `,
488
+ });
489
+ sendWelcomeMessageOnce.current = true;
490
+ }
491
+ }, 1000);
492
+ return () => clearTimeout(timer);
493
+ // eslint-disable-next-line react-hooks/exhaustive-deps
494
+ }, [activeConversation?.conversationID]);
495
+
496
+ const openFromChat = (view: 'search' | 'setting') => {
497
+ setPreviousView(activeView);
498
+ setActiveView(view);
499
+ };
500
+
501
+ const handleChatBack = () => {
502
+ setActiveConversation(undefined);
503
+ setActiveView('conversations');
504
+ };
505
+
506
+ return (
507
+ <div className="app-shell">
508
+ <Screen active={activeView === 'conversations'}>
509
+ <header className="topbar">
510
+ <span className="topbar__title">
511
+ {t('demo.chats')}{loginUserInfo?.userId ? ` · ${loginUserInfo.userId}` : ''}
512
+ </span>
513
+ <div className="chat-pane__actions">
514
+ <button
515
+ className="topbar__text-btn"
516
+ type="button"
517
+ onClick={() => onChangeLanguage(language === 'zh-CN' ? 'en-US' : 'zh-CN')}
518
+ >
519
+ {language === 'zh-CN' ? 'EN' : '中'}
520
+ </button>
521
+ <button className="topbar__text-btn" type="button" onClick={logout}>{t('demo.logout')}</button>
522
+ </div>
523
+ </header>
524
+ <div className="screen__body">
525
+ <ConversationListH5
526
+ style={{ height: '100%' }}
527
+ onSelectConversation={() => setActiveView('chat')}
528
+ />
529
+ </div>
530
+ <nav className="tabbar">
531
+ <button className="tabbar__item tabbar__item--active" type="button">{t('demo.chats')}</button>
532
+ <button className="tabbar__item" type="button" onClick={() => setActiveView('contacts')}>{t('demo.contacts')}</button>
533
+ </nav>
534
+ </Screen>
535
+
536
+ <Screen active={activeView === 'contacts'}>
537
+ <header className="topbar">
538
+ <span className="topbar__title">{t('demo.contacts')}</span>
539
+ </header>
540
+ <div className="screen__body">
541
+ <ContactListH5 onContactItemClick={() => setActiveView('contactInfo')} />
542
+ </div>
543
+ <nav className="tabbar">
544
+ <button className="tabbar__item" type="button" onClick={() => setActiveView('conversations')}>{t('demo.chats')}</button>
545
+ <button className="tabbar__item tabbar__item--active" type="button">{t('demo.contacts')}</button>
546
+ </nav>
547
+ </Screen>
548
+
549
+ <Screen active={activeView === 'contactInfo'}>
550
+ <div className="screen__body screen__body--surface">
551
+ <ContactInfoH5
552
+ onClose={() => setActiveView('contacts')}
553
+ onSendMessage={() => setActiveView('chat')}
554
+ onEnterGroup={() => setActiveView('chat')}
555
+ />
556
+ </div>
557
+ </Screen>
558
+
559
+ <Screen active={activeView === 'chat'}>
560
+ <Chat className="chat-pane">
561
+ <ChatHeaderH5
562
+ className="chat-pane__header"
563
+ enableUserStatus
564
+ onBack={handleChatBack}
565
+ ChatHeaderRight={(
566
+ <div className="chat-pane__actions">
567
+ <button className="icon-btn" type="button" aria-label={t('demo.search')} onClick={() => openFromChat('search')}>
568
+ <IconSearch size="20px" />
569
+ </button>
570
+ <button className="icon-btn" type="button" aria-label={t('demo.setting')} onClick={() => openFromChat('setting')}>
571
+ <IconBulletpoint size="20px" />
572
+ </button>
573
+ </div>
574
+ )}
575
+ />
576
+ <MessageListH5 />
577
+ <MessageInputH5 />
578
+ </Chat>
579
+ </Screen>
580
+
581
+ <Screen active={activeView === 'search'}>
582
+ <div className="screen__body">
583
+ <SearchH5
584
+ variant={VariantType.EMBEDDED}
585
+ onBack={() => setActiveView(previousView)}
586
+ onResultItemClick={(_item, type) => {
587
+ if (type === 'chat_message') {
588
+ setActiveView('chat');
589
+ }
590
+ }}
591
+ />
592
+ </div>
593
+ </Screen>
594
+
595
+ <Screen active={activeView === 'setting'}>
596
+ <div className="screen__body screen__body--surface">
597
+ <ChatSettingH5 onBack={() => setActiveView('chat')} />
598
+ </div>
599
+ </Screen>
600
+ </div>
601
+ );
602
+ }
603
+ ```
604
+
605
+ [src/ChatLayout.css]
606
+ ``` css
607
+ /* ------- Shell + stacked screens ------- */
608
+ .app-shell {
609
+ position: relative;
610
+ width: 100vw;
611
+ height: 100vh;
612
+ height: 100dvh;
613
+ overflow: hidden;
614
+ background: #fff;
615
+ }
616
+
617
+ /* Each full-screen view is stacked; only the active one is visible. */
618
+ .screen {
619
+ position: absolute;
620
+ inset: 0;
621
+ display: flex;
622
+ flex-direction: column;
623
+ background: #fff;
624
+ }
625
+
626
+ .screen__body {
627
+ flex: 1;
628
+ min-height: 0;
629
+ overflow: hidden;
630
+ }
631
+
632
+ .screen__body--surface {
633
+ overflow: auto;
634
+ background: #f5f6f8;
635
+ }
636
+
637
+ /* ------- Top bar ------- */
638
+ .topbar {
639
+ display: flex;
640
+ flex: 0 0 auto;
641
+ min-height: 52px;
642
+ align-items: center;
643
+ justify-content: space-between;
644
+ padding: calc(8px + env(safe-area-inset-top)) 16px 8px;
645
+ border-bottom: 1px solid #e8eaed;
646
+ }
647
+
648
+ .topbar__title {
649
+ overflow: hidden;
650
+ font-size: 17px;
651
+ font-weight: 600;
652
+ text-overflow: ellipsis;
653
+ white-space: nowrap;
654
+ }
655
+
656
+ .topbar__text-btn {
657
+ border: 0;
658
+ background: transparent;
659
+ color: #1464ff;
660
+ font-size: 14px;
661
+ font-weight: 500;
662
+ }
663
+
664
+ /* ------- Tab bar ------- */
665
+ .tabbar {
666
+ display: flex;
667
+ flex: 0 0 auto;
668
+ gap: 8px;
669
+ padding: 6px 12px calc(6px + env(safe-area-inset-bottom));
670
+ border-top: 1px solid #e8eaed;
671
+ }
672
+
673
+ .tabbar__item {
674
+ flex: 1;
675
+ min-height: 40px;
676
+ border: 0;
677
+ border-radius: 12px;
678
+ background: transparent;
679
+ color: #6b7280;
680
+ font-size: 14px;
681
+ font-weight: 600;
682
+ }
683
+
684
+ .tabbar__item--active {
685
+ background: #eef4ff;
686
+ color: #1464ff;
687
+ }
688
+
689
+ /* ------- Chat pane ------- *
690
+ * The library's <Chat> root is already `display:flex; flex-direction:column;
691
+ * height:100%`, so it fills the screen on its own. The demo only arranges the
692
+ * three slotted children: header (fixed) / message list (grow + scroll) /
693
+ * input (fixed). `min-height:0` on the list breaks the flexbox min-height
694
+ * chain so it scrolls instead of stretching the pane.
695
+ */
696
+ .chat-pane__header {
697
+ flex: 0 0 auto;
698
+ padding-top: env(safe-area-inset-top);
699
+ border-bottom: 1px solid #e8eaed;
700
+ }
701
+
702
+ .chat-pane > :nth-child(2) {
703
+ flex: 1;
704
+ min-height: 0;
705
+ }
706
+
707
+ .chat-pane > :last-child {
708
+ flex: 0 0 auto;
709
+ padding-bottom: env(safe-area-inset-bottom);
710
+ border-top: 1px solid #e8eaed;
711
+ }
712
+
713
+ .chat-pane__actions {
714
+ display: flex;
715
+ align-items: center;
716
+ gap: 4px;
717
+ }
718
+
719
+ .icon-btn {
720
+ display: inline-flex;
721
+ width: 36px;
722
+ height: 36px;
723
+ align-items: center;
724
+ justify-content: center;
725
+ border: 0;
726
+ border-radius: 10px;
727
+ background: transparent;
728
+ color: #1f2329;
729
+ }
730
+
731
+ .icon-btn:active {
732
+ background: #eef0f3;
733
+ }
734
+ ```
735
+
736
+ 6. Configure internationalization.
737
+
738
+ Create **src/i18n.ts** and write the following:
739
+
740
+ ``` typescript
741
+ // 本 demo 自身字符串的翻译(聊天组件的翻译由 UIKit 自身
742
+ // 通过 `language` prop 处理)。键名以 `demo.` 作为命名空间,
743
+ // 确保不会与库的 i18next 资源发生冲突。
744
+ //
745
+ // `languageResources` 传递给 <UIKitProvider>;可在任意位置
746
+ // 通过 `const { t } = useUIKit()` 和 `t('demo.chats')` 读取字符串。
747
+ type TranslationTree = { [key: string]: string | TranslationTree };
748
+ interface LanguageResource {
749
+ lng: string;
750
+ translation: TranslationTree;
751
+ }
752
+
753
+ const en = {
754
+ demo: {
755
+ appTitle: 'Chat UIKit H5',
756
+ field: { sdkAppId: 'SDKAppID', userId: 'UserID', userSig: 'UserSig' },
757
+ placeholder: {
758
+ sdkAppId: 'Your SDKAppID',
759
+ userId: 'e.g. user_001',
760
+ userSig: 'Test UserSig from IM console',
761
+ },
762
+ error: {
763
+ required: 'Please fill in SDKAppID, UserID and UserSig.',
764
+ loginFailed: 'Login failed, please check your credentials.',
765
+ },
766
+ login: 'Login',
767
+ loggingIn: 'Logging in…',
768
+ chats: 'Chats',
769
+ contacts: 'Contacts',
770
+ logout: 'Logout',
771
+ search: 'Search',
772
+ setting: 'Setting',
773
+ },
774
+ };
775
+
776
+ const zh = {
777
+ demo: {
778
+ appTitle: 'Chat UIKit H5',
779
+ field: { sdkAppId: 'SDKAppID', userId: '用户 ID', userSig: '用户签名' },
780
+ placeholder: {
781
+ sdkAppId: '请输入 SDKAppID',
782
+ userId: '例如 user_001',
783
+ userSig: '控制台获取的测试 UserSig',
784
+ },
785
+ error: {
786
+ required: '请填写 SDKAppID、用户 ID 和用户签名。',
787
+ loginFailed: '登录失败,请检查登录凭据。',
788
+ },
789
+ login: '登录',
790
+ loggingIn: '登录中…',
791
+ chats: '会话',
792
+ contacts: '通讯录',
793
+ logout: '退出登录',
794
+ search: '搜索',
795
+ setting: '设置',
796
+ },
797
+ };
798
+
799
+ export const languageResources: LanguageResource[] = [
800
+ { lng: 'en-US', translation: en },
801
+ { lng: 'zh-CN', translation: zh },
802
+ ];
803
+ ```
804
+
805
+ ### Step 3: Obtain SDKAppID, userID, and userSig
806
+ | Parameter | Type | Description |
807
+ | --- | --- | --- |
808
+ | SDKAppID | Number | The unique identifier for your Tencent Cloud IM application. You can create a new application in the <a href="https://console.cloud.tencent.com/im">IM Console</a> to obtain the SDKAppID.<br><strong>Note:</strong><br>Each independent app should have its own SDKAppID. Messages between different SDKAppIDs are naturally isolated. |
809
+ | userID | String | The unique user identifier you define. Only allows letters (a-z, A-Z), digits (0-9), underscores, and hyphens. |
810
+ | userSig | String | The authentication ticket for logging into IM. It is essentially ciphertext generated by encrypting information such as the UserID.<br><strong>Note:</strong><br>- Development: For quick testing, you can obtain UserSig from the <a href="https://console.cloud.tencent.com/im">IM Console</a>.<br>- Production: Integrate the UserSig calculation on your server. See <a href="https://cloud.tencent.com/document/product/269/32688">Server-side UserSig Generation</a>. |
811
+
812
+ > **Warning:**
813
+ > The sample code in this guide uses UserSig obtained from the [IM Console](https://console.cloud.tencent.com/im), which is **only suitable for local testing and debugging**. For production, see [Server-side UserSig Generation](https://cloud.tencent.com/document/product/269/32688).
814
+ >
815
+
816
+ - SDKAppID: In [IM Console > Application Management](https://console.cloud.tencent.com/im), click **Create New Application** to get the SDKAppID.
817
+
818
+ - userID: Go to [IM Console > Chat > Account Management](https://console.cloud.tencent.com/im/account-management), switch to your target application, and create 2-3 accounts for testing.
819
+
820
+ - userSig: Go to [IM Console > Developer Tools > UserSig Generation](https://console.cloud.tencent.com/im/tool-usersig), switch to your target application, enter the userID, and generate the userSig.
821
+
822
+ ## Run and Test
823
+
824
+ Fill in the login credentials and run the project to start experiencing it.
825
+
826
+ [shell]
827
+ ``` bash
828
+ npm run dev
829
+ ```
830
+
831
+ > **Warning:**
832
+ > - userID and userSig have a one-to-one correspondence. See UserSig Generation for details.
833
+ > - If the project fails to start, check whether the development environment requirements are met.
834
+
835
+ ## Integrate Advanced Features
836
+
837
+ ### Audio/Video Calls
838
+
839
+ > **Note:**
840
+ > TUICallKit is a Tencent Cloud audio/video calling UI component. By integrating it, you can experience audio/video calling in your chat app with just a few lines of code.
841
+ >
842
+ > For more details, see: Audio/Video Calling - Activate Service.
843
+ >
844
+
845
+ 1. Install the `@trtc/calls-uikit-react` dependency.
846
+
847
+ ``` bash
848
+ npm install @trtc/calls-uikit-react
849
+ ```
850
+ 2. Import TUICallKit from `@trtc/calls-uikit-react` and mount it to the DOM.
851
+
852
+ Add the following code to the `src/App.tsx` file:
853
+
854
+ ``` typescript
855
+ import { useState } from 'react';
856
+ import { LoginStatus, UIKitProvider, useLoginState } from '@tencentcloud/chat-uikit-react';
857
+ import { TUICallKit } from '@trtc/calls-uikit-react';
858
+ import { LoginView } from './LoginView';
859
+ import { ChatLayout } from './ChatLayout';
860
+ import { languageResources } from './i18n';
861
+
862
+ function App() {
863
+ const { status } = useLoginState();
864
+ const isLogined = status === LoginStatus.SUCCESS;
865
+ // `language` drives both the UIKit's own strings and our demo strings.
866
+ const [language, setLanguage] = useState('en-US');
867
+
868
+ return (
869
+ <UIKitProvider theme="light" language={language} languageResources={languageResources}>
870
+ {isLogined
871
+ ? (
872
+ <>
873
+ <ChatLayout onChangeLanguage={setLanguage} />
874
+ {/* Audio/video calls. Mount once at the app root; it shares the chat
875
+ login session, so no extra login is needed. The call buttons are
876
+ already built into the chat header. */}
877
+ <div className="call-kit">
878
+ <TUICallKit />
879
+ </div>
880
+ </>
881
+ )
882
+ : <LoginView language={language} onChangeLanguage={setLanguage} />}
883
+ </UIKitProvider>
884
+ );
885
+ }
886
+
887
+ export default App;
888
+ ```
889
+
890
+ ### Cloud Search
891
+
892
+ > **Note:**
893
+ > Search is an essential feature for customer service, social, online education, telemedicine, OA, and other scenarios. It helps users quickly find groups, users, and messages, improving product experience and user engagement.
894
+ >
895
+ > Due to the limitations of Web platform local storage, **local search is not available**. To better meet search needs, **cloud search** is provided. **Cloud search supports global search and in-conversation search, including searching for groups, users, and messages.**
896
+ >
897
+ > This is a value-added service. You need to purchase the cloud search plugin. Click [Purchase](https://console.cloud.tencent.com/im/plugin/TUICloudSearch).
898
+ >
899
+
900
+ Cloud search is integrated by default in Step 2. To disable cloud search:
901
+ 1. Comment out the `ChatHeaderRight` prop in `ChatHeader` and the in-conversation search sidebar related code.
902
+
903
+ ``` typescript
904
+ <ChatHeaderH5
905
+ className="chat-pane__header"
906
+ enableUserStatus
907
+ onBack={handleChatBack}
908
+ ChatHeaderRight={(
909
+ <div className="chat-pane__actions">
910
+ {/* <button className="icon-btn" type="button" aria-label={t('demo.search')} onClick={() => openFromChat('search')}>
911
+ <IconSearch size="20px" />
912
+ </button> */}
913
+ <button className="icon-btn" type="button" aria-label={t('demo.setting')} onClick={() => openFromChat('setting')}>
914
+ <IconBulletpoint size="20px" />
915
+ </button>
916
+ </div>
917
+ )}
918
+ />
919
+ ```
920
+ 2. Disable cloud search on the `ConversationList` component.
921
+
922
+ ``` java
923
+ <ConversationListH5 enableSearch={false} enableCreate={false} />
924
+ ```
925
+
926
+ ## FAQ
927
+
928
+ ### What is UserSig? How to generate UserSig?
929
+
930
+ UserSig is the authentication ticket for logging into IM. It is essentially ciphertext generated by encrypting information such as the UserID. The recommended approach is to integrate UserSig calculation on your server and provide a project-facing API. For more details, see [Server-side UserSig Generation](https://cloud.tencent.com/document/product/269/32688).
931
+
932
+ > **Warning:**
933
+ > The sample code in this guide uses UserSig obtained from the [IM Console](https://console.cloud.tencent.com/im), which is **only suitable for local testing and debugging**. For production, see [Server-side UserSig Generation](https://cloud.tencent.com/document/product/269/32688).
934
+ >
935
+
936
+ ### Is React 17 supported?
937
+
938
+ React v17.x is not supported. Only React v18.2+ and above are supported.
939
+
940
+ ### Can I use third-party component libraries like Ant-Design?
941
+
942
+ You can use other component libraries for the glue code between core components, as shown in the example code — for instance, you can wrap `<ChatSettingH5 />` in a full-screen drawer component. However, components already built into the core components cannot be modified at this time.
943
+ ``` typescript
944
+ import { Drawer } from 'antd';
945
+
946
+ const [isChatSettingShow, setIsChatSettingShow] = useState(false);
947
+
948
+ function onChatSettingClose() {
949
+ setIsChatSettingShow(false);
950
+ }
951
+
952
+ <Drawer
953
+ title="设置"
954
+ open={isChatSettingShow}
955
+ onClose={onChatSettingClose}
956
+ >
957
+ <ChatSettingH5 />
958
+ </Drawer>
959
+ ```
960
+
961
+ ### Emoji Packs
962
+
963
+ To respect copyrights, the IM Demo/TUIKit project does not include large emoji sticker assets by default. Before going live, please replace them with your own designed or licensed emoji packs. The **default yellow-face emoji pack shown below is copyrighted by Tencent Cloud**. You can use it for free by upgrading to the [IM Enterprise plan](https://buy.cloud.tencent.com/avc).
964
+