@talknote/create-talknote-h5 0.1.4 → 0.1.5

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 (102) hide show
  1. package/bin/create-talknote-h5.js +14 -0
  2. package/package.json +1 -1
  3. package/template/.agents/skills/add-h5-page/SKILL.md +116 -0
  4. package/template/.eslintrc.js +2 -0
  5. package/template/README.md +143 -117
  6. package/template/_gitignore +0 -3
  7. package/template/config/config.ts +49 -4
  8. package/template/config/proxy.ts +28 -31
  9. package/template/docker/nginx/error_page.conf +51 -0
  10. package/template/docker/nginx/nginx.conf +54 -0
  11. package/template/docker/nginx/webApp.conf +13 -0
  12. package/template/docker/webApp.Dockerfile +12 -0
  13. package/template/package.json +6 -2
  14. package/template/packages/xiaozhi-chat/README.md +53 -0
  15. package/template/packages/xiaozhi-chat/XiaozhiChatPage.less +50 -0
  16. package/template/packages/xiaozhi-chat/XiaozhiChatPage.tsx +172 -0
  17. package/template/packages/xiaozhi-chat/assets/images/bg.jpg +0 -0
  18. package/template/packages/xiaozhi-chat/assets/images/icon-arrow-back.png +0 -0
  19. package/template/packages/xiaozhi-chat/assets/images/icon-close.png +0 -0
  20. package/template/packages/xiaozhi-chat/assets/images/icon-voice.png +0 -0
  21. package/template/packages/xiaozhi-chat/assets/images/keyboard.png +0 -0
  22. package/template/packages/xiaozhi-chat/assets/images/send-icon.png +0 -0
  23. package/template/packages/xiaozhi-chat/assets/images/stop-icon.png +0 -0
  24. package/template/packages/xiaozhi-chat/components/AssistantRichContent/index.less +3 -0
  25. package/template/packages/xiaozhi-chat/components/AssistantRichContent/index.tsx +44 -0
  26. package/template/packages/xiaozhi-chat/components/ChatHeader/index.less +84 -0
  27. package/template/packages/xiaozhi-chat/components/ChatHeader/index.tsx +44 -0
  28. package/template/packages/xiaozhi-chat/components/ChatInput/HoldToTalk.less +59 -0
  29. package/template/packages/xiaozhi-chat/components/ChatInput/HoldToTalk.tsx +167 -0
  30. package/template/packages/xiaozhi-chat/components/ChatInput/index.less +189 -0
  31. package/template/packages/xiaozhi-chat/components/ChatInput/index.tsx +203 -0
  32. package/template/packages/xiaozhi-chat/components/DeepThinkingWrapper/index.less +80 -0
  33. package/template/packages/xiaozhi-chat/components/DeepThinkingWrapper/index.tsx +54 -0
  34. package/template/packages/xiaozhi-chat/components/MarkdownContent/index.less +209 -0
  35. package/template/packages/xiaozhi-chat/components/MarkdownContent/index.tsx +41 -0
  36. package/template/packages/xiaozhi-chat/components/MessageList/index.less +74 -0
  37. package/template/packages/xiaozhi-chat/components/MessageList/index.tsx +195 -0
  38. package/template/packages/xiaozhi-chat/components/ThinkingCompleteEntry/index.less +56 -0
  39. package/template/packages/xiaozhi-chat/components/ThinkingCompleteEntry/index.tsx +65 -0
  40. package/template/packages/xiaozhi-chat/components/ToolCallList/index.less +41 -0
  41. package/template/packages/xiaozhi-chat/components/ToolCallList/index.tsx +51 -0
  42. package/template/packages/xiaozhi-chat/components/TypingIndicator/index.less +35 -0
  43. package/template/packages/xiaozhi-chat/components/TypingIndicator/index.tsx +12 -0
  44. package/template/packages/xiaozhi-chat/components/Welcome/index.less +105 -0
  45. package/template/packages/xiaozhi-chat/components/Welcome/index.tsx +84 -0
  46. package/template/packages/xiaozhi-chat/hooks/useChatSession.ts +235 -0
  47. package/template/packages/xiaozhi-chat/hooks/useRealtimeTranscription.ts +226 -0
  48. package/template/packages/xiaozhi-chat/hooks/useTypewriter.ts +215 -0
  49. package/template/packages/xiaozhi-chat/hooks/useViewportHeight.ts +43 -0
  50. package/template/packages/xiaozhi-chat/hooks/useVoiceRecording.ts +128 -0
  51. package/template/packages/xiaozhi-chat/index.ts +11 -0
  52. package/template/packages/xiaozhi-chat/package.json +34 -0
  53. package/template/packages/xiaozhi-chat/services/chatApi.ts +585 -0
  54. package/template/packages/xiaozhi-chat/types.ts +80 -0
  55. package/template/packages/xiaozhi-chat/utils/bridge.ts +131 -0
  56. package/template/packages/xiaozhi-chat/utils/deepThinking.ts +200 -0
  57. package/template/packages/xiaozhi-chat/utils/parseAssistantContent.ts +233 -0
  58. package/template/packages/xiaozhi-chat/utils/typewriter.ts +265 -0
  59. package/template/scripts/dev.js +6 -7
  60. package/template/src/app.tsx +1 -73
  61. package/template/src/assets/error-network.png +0 -0
  62. package/template/src/assets/error.png +0 -0
  63. package/template/src/assets/success.png +0 -0
  64. package/template/src/assets/welcome.png +0 -0
  65. package/template/src/components/Header/index.less +70 -0
  66. package/template/src/components/Header/index.tsx +46 -0
  67. package/template/src/components/TabBar/index.less +13 -0
  68. package/template/src/components/TabBar/index.tsx +58 -0
  69. package/template/src/global.css +27 -0
  70. package/template/src/layouts/BlankLayout/index.less +5 -0
  71. package/template/src/layouts/BlankLayout/index.tsx +14 -0
  72. package/template/src/layouts/HeaderLayout/index.less +25 -0
  73. package/template/src/layouts/HeaderLayout/index.tsx +42 -0
  74. package/template/src/layouts/LayoutOutlet.tsx +7 -0
  75. package/template/src/layouts/SSOLoginLayout/index.less +28 -0
  76. package/template/src/layouts/SSOLoginLayout/index.tsx +92 -0
  77. package/template/src/layouts/TabBarLayout/index.less +21 -0
  78. package/template/src/layouts/TabBarLayout/index.tsx +29 -0
  79. package/template/src/layouts/index.tsx +30 -0
  80. package/template/src/layouts/usePageHeaderProps.ts +9 -0
  81. package/template/src/pages/index/index.css +5 -5
  82. package/template/src/pages/index/index.tsx +15 -2
  83. package/template/src/pages/list/index.tsx +5 -5
  84. package/template/src/pages/test/index.tsx +12 -0
  85. package/template/src/pages/xiaozhi-chat/index.less +4 -0
  86. package/template/src/pages/xiaozhi-chat/index.tsx +26 -0
  87. package/template/src/routes.ts +49 -0
  88. package/template/src/services/api.ts +7 -2
  89. package/template/src/theme/variables.css +5 -0
  90. package/template/src/utils/ensureSsoLogin.ts +28 -0
  91. package/template/src/utils/goBack.ts +10 -0
  92. package/template/src/utils/request.ts +106 -0
  93. package/template/tsconfig.json +4 -1
  94. package/template/typings.d.ts +15 -0
  95. package/template/yarn.lock +2168 -61
  96. package/template/src/assets/demoIcon/home.png +0 -0
  97. package/template/src/assets/demoIcon/home1.png +0 -0
  98. package/template/src/assets/demoIcon/list.png +0 -0
  99. package/template/src/assets/demoIcon/list1.png +0 -0
  100. package/template/src/assets/demoIcon/setting.png +0 -0
  101. package/template/src/assets/demoIcon/setting1.png +0 -0
  102. package/template/src/assets/yay.jpg +0 -0
@@ -0,0 +1,54 @@
1
+ user nginx;
2
+ worker_processes 1;
3
+
4
+ error_log /var/log/nginx/error.log notice;
5
+ pid /tmp/nginx.pid;
6
+
7
+ # load_module modules/ngx_http_headers_more_filter_module.so;
8
+
9
+ worker_rlimit_nofile 65535;
10
+ events {
11
+ worker_connections 2048;
12
+ }
13
+
14
+
15
+ http {
16
+ include /etc/nginx/mime.types;
17
+ default_type application/octet-stream;
18
+
19
+ log_format main '$remote_addr [$time_local] "$request" '
20
+ '$status $body_bytes_sent $request_time "$http_referer" '
21
+ '"$http_user_agent" $http_x_forwarded_for';
22
+
23
+ access_log /var/log/nginx/access.log main;
24
+ error_log /var/log/nginx/error.log notice;
25
+
26
+ server_tokens off;
27
+ # 完全隐藏 Server 响应头(server_tokens 只能隐藏版本号,不能隐藏响应头)
28
+ # more_clear_headers Server;
29
+
30
+ client_max_body_size 1G;
31
+
32
+ client_header_timeout 60s;
33
+ client_body_timeout 60s;
34
+ send_timeout 60s;
35
+ keepalive_timeout 65s;
36
+
37
+ sendfile on;
38
+ tcp_nopush on;
39
+ tcp_nodelay on;
40
+
41
+ limit_conn_zone $binary_remote_addr zone=perip:10m;
42
+ limit_conn perip 1000;
43
+ limit_rate 0;
44
+
45
+ gzip on;
46
+ gzip_min_length 1k;
47
+ gzip_comp_level 2;
48
+ gzip_http_version 1.0;
49
+ gzip_types text/plain text/css text/xml application/javascript application/json application/xml image/svg+xml;
50
+ gzip_proxied any;
51
+ gzip_vary on;
52
+
53
+ include /etc/nginx/conf.d/*.conf;
54
+ }
@@ -0,0 +1,13 @@
1
+ server {
2
+ listen 8080;
3
+ server_name _;
4
+ root /srv/http;
5
+
6
+ # Require cache revalidation by default
7
+ location / {
8
+ try_files $uri $uri/ /index.html;
9
+ add_header Cache-Control "no-cache";
10
+ }
11
+
12
+ include /etc/nginx/includes/error_page.conf;
13
+ }
@@ -0,0 +1,12 @@
1
+ FROM hub-nj.iwhalecloud.com/lingxi/nginx:1
2
+
3
+ # 与 Tomcat 镜像保持一致
4
+ EXPOSE 8080
5
+
6
+ COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf
7
+ COPY docker/nginx/error_page.conf /etc/nginx/includes/error_page.conf
8
+ COPY docker/nginx/webApp.conf /etc/nginx/conf.d/default.conf
9
+
10
+ COPY --chown=nginx:nginx dist /srv/http
11
+
12
+ USER 101
@@ -15,12 +15,16 @@
15
15
  "test": "jest"
16
16
  },
17
17
  "dependencies": {
18
- "@talknote/h5-bridge":"^0.0.5",
18
+ "@talknote/h5-bridge": "^0.0.5",
19
+ "@talknote/xiaozhi-chat": "file:packages/xiaozhi-chat",
19
20
  "@alita/flow": "^3.1.1",
20
21
  "alita": "^3.2.27",
21
22
  "antd-mobile": "^5.15.1",
23
+ "lucide-react": "^1.38.0",
22
24
  "react": "^18.2.0",
23
- "react-dom": "^18.2.0"
25
+ "react-dom": "^18.2.0",
26
+ "react-markdown": "^8.0.7",
27
+ "remark-gfm": "^3.0.1"
24
28
  },
25
29
  "devDependencies": {
26
30
  "patch-package": "^8.0.0",
@@ -0,0 +1,53 @@
1
+ # @talknote/xiaozhi-chat
2
+
3
+ Ask AI H5 简易问答子包,位于模板工程 `packages/xiaozhi-chat`,供主工程引入使用。
4
+
5
+ ## 能力
6
+
7
+ - 自绘顶栏(`vibe-element="header"`)
8
+ - 三段布局:头固定 / 中滚动 / 底固定;键盘弹出通过 `visualViewport` 只压缩中间区
9
+ - 欢迎文案、消息列表、键盘 + 按住说话(`window.vibeNoteBridge`)
10
+ - 快捷菜单插槽 `shortcutSlot`
11
+ - 欢迎页首次提问先创建会话 `/nes/bote/chat/createBoteSession`,再按 chatVersion 2(专家)SSE 流式回复(可停止)`/nes/bote/chat/execute`
12
+ - 专家模式:按分割线/标题拆分思考过程,解析 `<toolCall>` / `<toolCallResult>`,Markdown 打字机输出
13
+
14
+ ## 主工程引入
15
+
16
+ `package.json`:
17
+
18
+ ```json
19
+ {
20
+ "dependencies": {
21
+ "@talknote/xiaozhi-chat": "file:packages/xiaozhi-chat"
22
+ }
23
+ }
24
+ ```
25
+
26
+ `config/config.ts` alias(保证 less / 图片可编译):
27
+
28
+ ```ts
29
+ alias: {
30
+ '@talknote/xiaozhi-chat': path.join(__dirname, '../packages/xiaozhi-chat'),
31
+ }
32
+ ```
33
+
34
+ 页面薄壳 `src/pages/xiaozhi-chat/index.tsx`:
35
+
36
+ ```tsx
37
+ import { XiaozhiChatPage } from '@talknote/xiaozhi-chat';
38
+
39
+ export default () => (
40
+ <div style={{ height: '100%', overflow: 'hidden' }}>
41
+ <XiaozhiChatPage />
42
+ </div>
43
+ );
44
+ ```
45
+
46
+ 然后执行 `yarn` 链接本地子包。
47
+
48
+ ## Peer 依赖
49
+
50
+ - `react` / `react-dom`
51
+ - `antd-mobile`(Toast)
52
+
53
+ 录音与返回走 `window.vibeNoteBridge`,不依赖 `@talknote/h5-bridge`。
@@ -0,0 +1,50 @@
1
+ .page {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ width: 100%;
6
+ height: 100%;
7
+ max-height: 100%;
8
+ min-height: 0;
9
+ overflow: hidden;
10
+ background: #ffffff;
11
+ }
12
+
13
+ .pageViewportFallback {
14
+ height: var(--xiaozhi-vh, 100%);
15
+ max-height: var(--xiaozhi-vh, 100%);
16
+ }
17
+
18
+ .bg {
19
+ position: absolute;
20
+ inset: 0;
21
+ width: 100%;
22
+ height: 100%;
23
+ object-fit: cover;
24
+ pointer-events: none;
25
+ z-index: 0;
26
+ }
27
+
28
+ .headerWrap {
29
+ position: relative;
30
+ z-index: 2;
31
+ flex-shrink: 0;
32
+ }
33
+
34
+ .body {
35
+ position: relative;
36
+ z-index: 1;
37
+ flex: 1 1 auto;
38
+ min-height: 0;
39
+ overflow-x: hidden;
40
+ overflow-y: auto;
41
+ -webkit-overflow-scrolling: touch;
42
+ overscroll-behavior: contain;
43
+ }
44
+
45
+ .footer {
46
+ position: relative;
47
+ z-index: 2;
48
+ flex-shrink: 0;
49
+ background: transparent;
50
+ }
@@ -0,0 +1,172 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import bgImage from './assets/images/bg.jpg';
3
+ import ChatHeader from './components/ChatHeader';
4
+ import ChatInput from './components/ChatInput';
5
+ import MessageList from './components/MessageList';
6
+ import Welcome from './components/Welcome';
7
+ import { useChatSession } from './hooks/useChatSession';
8
+ import { useViewportHeight } from './hooks/useViewportHeight';
9
+ import type { XiaozhiChatPageProps } from './types';
10
+ import { hideVibeNoteKeyboard, setKeyboardAvoidMode, vibeNoteCanIUse } from './utils/bridge';
11
+ import styles from './XiaozhiChatPage.less';
12
+
13
+ const lockPageToWebViewFrame = () => {
14
+ const nodes = [document.documentElement, document.body, document.getElementById('root')].filter(
15
+ (el): el is HTMLElement => Boolean(el),
16
+ );
17
+ const prev = nodes.map(el => ({
18
+ el,
19
+ height: el.style.height,
20
+ minHeight: el.style.minHeight,
21
+ maxHeight: el.style.maxHeight,
22
+ overflow: el.style.overflow,
23
+ }));
24
+ nodes.forEach(el => {
25
+ el.style.setProperty('height', '100%', 'important');
26
+ el.style.setProperty('min-height', '0', 'important');
27
+ el.style.setProperty('max-height', '100%', 'important');
28
+ el.style.setProperty('overflow', 'hidden', 'important');
29
+ });
30
+ const vk = (navigator as Navigator & { virtualKeyboard?: { overlaysContent: boolean } }).virtualKeyboard;
31
+ if (vk) {
32
+ vk.overlaysContent = true;
33
+ }
34
+ return () => {
35
+ document.documentElement.style.transform = '';
36
+ if (vk) {
37
+ vk.overlaysContent = false;
38
+ }
39
+ prev.forEach(({ el, height, minHeight, maxHeight, overflow }) => {
40
+ if (height) {
41
+ el.style.height = height;
42
+ } else {
43
+ el.style.removeProperty('height');
44
+ }
45
+ if (minHeight) {
46
+ el.style.minHeight = minHeight;
47
+ } else {
48
+ el.style.removeProperty('min-height');
49
+ }
50
+ if (maxHeight) {
51
+ el.style.maxHeight = maxHeight;
52
+ } else {
53
+ el.style.removeProperty('max-height');
54
+ }
55
+ if (overflow) {
56
+ el.style.overflow = overflow;
57
+ } else {
58
+ el.style.removeProperty('overflow');
59
+ }
60
+ });
61
+ };
62
+ };
63
+
64
+ const XiaozhiChatPage: React.FC<XiaozhiChatPageProps> = ({
65
+ shortcutSlot,
66
+ welcomeTitle,
67
+ welcomeSubtitle,
68
+ placeholder,
69
+ title = '问小智',
70
+ onBack,
71
+ }) => {
72
+ const [useViewportFallback, setUseViewportFallback] = useState(false);
73
+ useViewportHeight('--xiaozhi-vh', { enabled: useViewportFallback });
74
+ const { messages, isStreaming, showWelcome, send, stop, clearChat } = useChatSession();
75
+ const [inputText, setInputText] = useState('');
76
+
77
+ useEffect(() => {
78
+ let cancelled = false;
79
+ const unlock = lockPageToWebViewFrame();
80
+ const apply = async () => {
81
+ try {
82
+ const ok = await setKeyboardAvoidMode('resize');
83
+ if (!cancelled && !ok) {
84
+ setUseViewportFallback(true);
85
+ }
86
+ } catch {
87
+ if (!cancelled) {
88
+ setUseViewportFallback(true);
89
+ }
90
+ }
91
+ };
92
+ void apply();
93
+ const timer = window.setTimeout(() => {
94
+ if (!cancelled && !vibeNoteCanIUse('setKeyboardAvoidMode')) {
95
+ setUseViewportFallback(true);
96
+ }
97
+ }, 800);
98
+ return () => {
99
+ cancelled = true;
100
+ window.clearTimeout(timer);
101
+ void setKeyboardAvoidMode('adjust');
102
+ unlock();
103
+ };
104
+ }, []);
105
+
106
+ const handleSend = useCallback(
107
+ (text: string) => {
108
+ setInputText('');
109
+ void send(text);
110
+ },
111
+ [send],
112
+ );
113
+
114
+ const handleVoiceResult = useCallback(
115
+ (text: string) => {
116
+ setInputText('');
117
+ void send(text);
118
+ },
119
+ [send],
120
+ );
121
+
122
+ const handleNewChat = useCallback(() => {
123
+ setInputText('');
124
+ clearChat();
125
+ }, [clearChat]);
126
+
127
+ return (
128
+ <div
129
+ className={`${styles.page} ${useViewportFallback ? styles.pageViewportFallback : ''}`}
130
+ vibe-element="safe-area-view"
131
+ vibe-edges="top"
132
+ >
133
+ <img src={bgImage} alt="" className={styles.bg} />
134
+
135
+ <div className={styles.headerWrap}>
136
+ <ChatHeader
137
+ title={title}
138
+ showNewChat={!showWelcome}
139
+ onNewChat={handleNewChat}
140
+ onBack={onBack}
141
+ />
142
+ </div>
143
+
144
+ <div className={styles.body} onPointerDown={hideVibeNoteKeyboard}>
145
+ {showWelcome ? (
146
+ <Welcome
147
+ title={welcomeTitle}
148
+ subtitle={welcomeSubtitle}
149
+ onQuestionPress={handleSend}
150
+ />
151
+ ) : (
152
+ <MessageList messages={messages} isStreaming={isStreaming} />
153
+ )}
154
+ </div>
155
+
156
+ <div className={styles.footer}>
157
+ <ChatInput
158
+ value={inputText}
159
+ onChange={setInputText}
160
+ onSend={handleSend}
161
+ onStop={stop}
162
+ isStreaming={isStreaming}
163
+ placeholder={placeholder}
164
+ shortcutSlot={shortcutSlot}
165
+ onVoiceResult={handleVoiceResult}
166
+ />
167
+ </div>
168
+ </div>
169
+ );
170
+ };
171
+
172
+ export default XiaozhiChatPage;
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { parseAssistantContentSegments } from '../../utils/parseAssistantContent';
3
+ import MarkdownContent from '../MarkdownContent';
4
+ import ToolCallList from '../ToolCallList';
5
+ import styles from './index.less';
6
+
7
+ type AssistantRichContentProps = {
8
+ content: string;
9
+ variant?: 'answer' | 'thinking';
10
+ showCursor?: boolean;
11
+ };
12
+
13
+ const AssistantRichContent: React.FC<AssistantRichContentProps> = ({
14
+ content,
15
+ variant = 'answer',
16
+ showCursor = false,
17
+ }) => {
18
+ const segments = parseAssistantContentSegments(content || '');
19
+ if (!segments.length && !showCursor) return null;
20
+
21
+ return (
22
+ <div className={styles.root}>
23
+ {segments.map((segment, index) => {
24
+ const isLast = index === segments.length - 1;
25
+ if (segment.type === 'toolCalls') {
26
+ return <ToolCallList key={`tool-${index}`} groups={segment.groups} />;
27
+ }
28
+ return (
29
+ <MarkdownContent
30
+ key={`md-${index}`}
31
+ content={segment.content}
32
+ variant={variant}
33
+ showCursor={showCursor && isLast}
34
+ />
35
+ );
36
+ })}
37
+ {showCursor && (segments.length === 0 || segments[segments.length - 1]?.type === 'toolCalls') ? (
38
+ <MarkdownContent content="" showCursor />
39
+ ) : null}
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export default AssistantRichContent;
@@ -0,0 +1,84 @@
1
+ /* 使用 Px 避免 alita px→rem 把 RN 字号压小;数值对齐原生 dp */
2
+ /* App 内 vibe-element=header 注入 fixed + 安全区;标题绝对居中,右侧按钮距右边 100 避让胶囊 */
3
+ .header {
4
+ box-sizing: border-box;
5
+ position: sticky;
6
+ top: 0;
7
+ z-index: 100;
8
+ display: flex;
9
+ flex-direction: row;
10
+ align-items: center;
11
+ justify-content: flex-start;
12
+ width: 100%;
13
+ min-height: 44Px;
14
+ padding-left: 12Px;
15
+ padding-right: 12Px;
16
+ background: transparent;
17
+ flex-shrink: 0;
18
+ }
19
+
20
+ .backBtn {
21
+ position: relative;
22
+ z-index: 2;
23
+ width: 36Px;
24
+ height: 36Px;
25
+ border: none;
26
+ border-radius: 18Px;
27
+ background: rgba(255, 255, 255, 0.5);
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ padding: 0;
32
+ flex-shrink: 0;
33
+ }
34
+
35
+ .backIcon {
36
+ width: 20Px;
37
+ height: 20Px;
38
+ object-fit: contain;
39
+ }
40
+
41
+ .title {
42
+ position: absolute;
43
+ left: 50%;
44
+ bottom: 0;
45
+ height: 44Px;
46
+ transform: translateX(-50%);
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ max-width: 40%;
51
+ overflow: hidden;
52
+ text-overflow: ellipsis;
53
+ white-space: nowrap;
54
+ pointer-events: none;
55
+ font-size: 17Px;
56
+ font-weight: 600;
57
+ line-height: 22Px;
58
+ color: #2a2b33;
59
+ font-family: 'PingFang SC', 'Noto Sans CJK SC', system-ui, -apple-system, sans-serif;
60
+ }
61
+
62
+ .right {
63
+ position: absolute;
64
+ right: 100Px;
65
+ bottom: 0;
66
+ height: 44Px;
67
+ z-index: 2;
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: flex-end;
71
+ }
72
+
73
+ .newChatBtn {
74
+ height: 36Px;
75
+ padding: 0 16Px;
76
+ border: none;
77
+ border-radius: 99Px;
78
+ background: rgba(255, 255, 255, 0.5);
79
+ font-size: 14Px;
80
+ font-weight: 500;
81
+ line-height: 20Px;
82
+ color: #414352;
83
+ white-space: nowrap;
84
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { getVibeNoteBridge } from '../../utils/bridge';
3
+ import iconClose from '../../assets/images/icon-close.png';
4
+ import styles from './index.less';
5
+
6
+ type ChatHeaderProps = {
7
+ title?: string;
8
+
9
+ /** 无消息时可不显示「新对话」 */
10
+ showNewChat?: boolean;
11
+ onNewChat?: () => void;
12
+ onBack?: () => void;
13
+ };
14
+
15
+ const defaultBack = () => {
16
+ if (window.history.length > 1) {
17
+ window.history.back();
18
+ return;
19
+ }
20
+ void getVibeNoteBridge()?.goBack?.();
21
+ };
22
+
23
+ const ChatHeader: React.FC<ChatHeaderProps> = ({
24
+ title = '问小智',
25
+ showNewChat = false,
26
+ onNewChat,
27
+ onBack = defaultBack,
28
+ }) => (
29
+ <header vibe-element="header" className={styles.header}>
30
+ <button type="button" className={styles.backBtn} onClick={onBack} aria-label="返回">
31
+ <img src={iconClose} alt="" className={styles.backIcon} />
32
+ </button>
33
+ <div className={styles.title}>{title}</div>
34
+ {/* <div className={styles.right}>
35
+ {showNewChat && onNewChat ? (
36
+ <button type="button" className={styles.newChatBtn} onClick={onNewChat}>
37
+ 新对话
38
+ </button>
39
+ ) : null}
40
+ </div> */}
41
+ </header>
42
+ );
43
+
44
+ export default ChatHeader;
@@ -0,0 +1,59 @@
1
+ .wrap {
2
+ position: relative;
3
+ flex: 1;
4
+ min-width: 0;
5
+ min-height: 48Px;
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ }
10
+
11
+ .tip {
12
+ position: absolute;
13
+ left: 50%;
14
+ bottom: calc(100% + 10Px);
15
+ transform: translateX(-50%);
16
+ white-space: nowrap;
17
+ padding: 6Px 10Px;
18
+ border-radius: 8Px;
19
+ font-size: 12Px;
20
+ line-height: 16Px;
21
+ color: #ffffff;
22
+ background: rgba(42, 43, 51, 0.85);
23
+ pointer-events: none;
24
+ z-index: 2;
25
+ font-family: 'PingFang SC', 'Noto Sans CJK SC', system-ui, -apple-system, sans-serif;
26
+ }
27
+
28
+ .tipCancel {
29
+ background: #ff615c;
30
+ }
31
+
32
+ .button {
33
+ width: 100%;
34
+ min-height: 48Px;
35
+ border: none;
36
+ border-radius: 8Px;
37
+ background: #ffffff;
38
+ color: #2a2b33;
39
+ font-size: 15Px;
40
+ font-weight: 700;
41
+ line-height: 22Px;
42
+ text-align: center;
43
+ touch-action: none;
44
+ user-select: none;
45
+ -webkit-user-select: none;
46
+ font-family: 'PingFang SC', 'Noto Sans CJK SC', system-ui, -apple-system, sans-serif;
47
+ }
48
+
49
+ .button:disabled {
50
+ opacity: 0.5;
51
+ }
52
+
53
+ .buttonActive {
54
+ color: #fe780f;
55
+ }
56
+
57
+ .buttonCancel {
58
+ color: #ff615c;
59
+ }