@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,209 @@
1
+ .root {
2
+ width: 100%;
3
+ color: #414352;
4
+ font-size: 14Px;
5
+ font-weight: 400;
6
+ line-height: 24Px;
7
+ word-break: break-word;
8
+ font-family: 'PingFang SC', 'Noto Sans CJK SC', system-ui, -apple-system, sans-serif;
9
+
10
+ :global {
11
+ p {
12
+ margin: 0 0 8Px;
13
+ font-size: 14Px;
14
+ line-height: 24Px;
15
+ color: inherit;
16
+
17
+ &:last-child {
18
+ margin-bottom: 0;
19
+ }
20
+ }
21
+
22
+ h1,
23
+ h2,
24
+ h3,
25
+ h4,
26
+ h5,
27
+ h6 {
28
+ margin: 10Px 0 6Px;
29
+ color: inherit;
30
+ font-weight: 600;
31
+ line-height: 1.4;
32
+
33
+ &:first-child {
34
+ margin-top: 0;
35
+ }
36
+ }
37
+
38
+ h1 {
39
+ font-size: 16Px;
40
+ font-weight: 700;
41
+ }
42
+
43
+ h2 {
44
+ font-size: 15Px;
45
+ font-weight: 700;
46
+ }
47
+
48
+ h3 {
49
+ font-size: 14Px;
50
+ }
51
+
52
+ h4,
53
+ h5,
54
+ h6 {
55
+ font-size: 13Px;
56
+ }
57
+
58
+ ul,
59
+ ol {
60
+ margin: 6Px 0;
61
+ padding-left: 20Px;
62
+ }
63
+
64
+ li {
65
+ margin: 2Px 0;
66
+ line-height: 24Px;
67
+ }
68
+
69
+ strong {
70
+ font-weight: 700;
71
+ }
72
+
73
+ em {
74
+ font-style: italic;
75
+ }
76
+
77
+ a {
78
+ color: #fe780f;
79
+ text-decoration: none;
80
+ }
81
+
82
+ hr {
83
+ margin: 8Px 0;
84
+ border: 0;
85
+ border-top: 0.5Px solid #ebebf0;
86
+ }
87
+
88
+ blockquote {
89
+ margin: 8Px 0;
90
+ padding: 4Px 0 4Px 10Px;
91
+ border-left: 3Px solid rgba(254, 120, 15, 0.4);
92
+ color: #9092a3;
93
+ }
94
+
95
+ code {
96
+ padding: 1Px 4Px;
97
+ border-radius: 4Px;
98
+ background: rgba(42, 43, 51, 0.06);
99
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
100
+ font-size: 12Px;
101
+ }
102
+
103
+ pre {
104
+ margin: 8Px 0;
105
+ padding: 10Px 12Px;
106
+ overflow-x: auto;
107
+ border-radius: 8Px;
108
+ background: #f6f6f8;
109
+
110
+ code {
111
+ padding: 0;
112
+ background: transparent;
113
+ font-size: 12Px;
114
+ line-height: 20Px;
115
+ }
116
+ }
117
+
118
+ table {
119
+ width: 100%;
120
+ margin: 8Px 0;
121
+ border-collapse: separate;
122
+ border-spacing: 0;
123
+ overflow: hidden;
124
+ border: 1Px solid rgba(254, 120, 15, 0.22);
125
+ border-radius: 8Px;
126
+ background: #ffffff;
127
+ }
128
+
129
+ thead {
130
+ background: rgba(249, 115, 22, 0.1);
131
+ }
132
+
133
+ th,
134
+ td {
135
+ padding: 6Px 8Px;
136
+ border-right: 1Px solid rgba(254, 120, 15, 0.22);
137
+ border-bottom: 1Px solid rgba(254, 120, 15, 0.22);
138
+ font-size: 12Px;
139
+ line-height: 18Px;
140
+ text-align: left;
141
+ }
142
+
143
+ th:last-child,
144
+ td:last-child {
145
+ border-right: 0;
146
+ }
147
+
148
+ tr:last-child th,
149
+ tr:last-child td {
150
+ border-bottom: 0;
151
+ }
152
+
153
+ th {
154
+ font-weight: 500;
155
+ }
156
+
157
+ img {
158
+ max-width: 100%;
159
+ height: auto;
160
+ }
161
+ }
162
+ }
163
+
164
+ .thinking {
165
+ color: #9092a3;
166
+ font-size: 13Px;
167
+ line-height: 20Px;
168
+
169
+ :global {
170
+ p,
171
+ li,
172
+ h1,
173
+ h2,
174
+ h3,
175
+ h4,
176
+ h5,
177
+ h6,
178
+ strong,
179
+ em,
180
+ code,
181
+ th,
182
+ td,
183
+ blockquote {
184
+ color: #9092a3;
185
+ }
186
+
187
+ p,
188
+ li {
189
+ font-size: 13Px;
190
+ line-height: 20Px;
191
+ }
192
+ }
193
+ }
194
+
195
+ .cursor {
196
+ display: inline-block;
197
+ width: 2Px;
198
+ height: 1em;
199
+ margin-left: 2Px;
200
+ vertical-align: text-bottom;
201
+ background: #fe780f;
202
+ animation: blink 1s step-end infinite;
203
+ }
204
+
205
+ @keyframes blink {
206
+ 50% {
207
+ opacity: 0;
208
+ }
209
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import styles from './index.less';
5
+
6
+ type MarkdownContentProps = {
7
+ content: string;
8
+ variant?: 'answer' | 'thinking';
9
+ showCursor?: boolean;
10
+ };
11
+
12
+ const MarkdownContent: React.FC<MarkdownContentProps> = ({
13
+ content,
14
+ variant = 'answer',
15
+ showCursor = false,
16
+ }) => {
17
+ if (!content && !showCursor) return null;
18
+
19
+ return (
20
+ <div className={`${styles.root} ${variant === 'thinking' ? styles.thinking : ''}`}>
21
+ {content ? (
22
+ <ReactMarkdown
23
+ remarkPlugins={[remarkGfm]}
24
+ skipHtml
25
+ components={{
26
+ a: ({ href, children }) => (
27
+ <a href={href} target="_blank" rel="noreferrer noopener">
28
+ {children}
29
+ </a>
30
+ ),
31
+ }}
32
+ >
33
+ {content}
34
+ </ReactMarkdown>
35
+ ) : null}
36
+ {showCursor ? <span className={styles.cursor} /> : null}
37
+ </div>
38
+ );
39
+ };
40
+
41
+ export default MarkdownContent;
@@ -0,0 +1,74 @@
1
+ .list {
2
+ padding: 12Px 16Px 8Px;
3
+ }
4
+
5
+ .bottomAnchor {
6
+ height: 12Px;
7
+ }
8
+
9
+ .row {
10
+ display: flex;
11
+ margin-bottom: 12Px;
12
+ }
13
+
14
+ .rowUser {
15
+ justify-content: flex-end;
16
+ padding-left: 48Px;
17
+ }
18
+
19
+ .rowAssistant {
20
+ justify-content: flex-start;
21
+ width: 100%;
22
+ }
23
+
24
+ .bubbleUser {
25
+ max-width: 100%;
26
+ padding: 8Px 12Px;
27
+ border-radius: 12Px 4Px 12Px 12Px;
28
+ background: #fe780f;
29
+ color: #ffffff;
30
+ font-size: 15Px;
31
+ font-weight: 500;
32
+ line-height: 22Px;
33
+ word-break: break-word;
34
+ font-family: 'PingFang SC', 'Noto Sans CJK SC', system-ui, -apple-system, sans-serif;
35
+ }
36
+
37
+ .bubbleAssistant {
38
+ width: 100%;
39
+ box-sizing: border-box;
40
+ padding: 12Px;
41
+ border-radius: 4Px 12Px 12Px 12Px;
42
+ background: #fefbf9;
43
+ color: #2a2b33;
44
+ font-size: 15Px;
45
+ font-weight: 400;
46
+ line-height: 22Px;
47
+ word-break: break-word;
48
+ font-family: 'PingFang SC', 'Noto Sans CJK SC', system-ui, -apple-system, sans-serif;
49
+ }
50
+
51
+ .content {
52
+ white-space: pre-wrap;
53
+ }
54
+
55
+ .hint {
56
+ margin-top: 6Px;
57
+ font-size: 13Px;
58
+ line-height: 18Px;
59
+ color: #9092a3;
60
+ }
61
+
62
+ .hintError {
63
+ margin-top: 6Px;
64
+ font-size: 12Px;
65
+ line-height: 16Px;
66
+ color: #ff3b30;
67
+ }
68
+
69
+ .expertTip {
70
+ margin-top: 6Px;
71
+ font-size: 12Px;
72
+ line-height: 16Px;
73
+ color: #9092a3;
74
+ }
@@ -0,0 +1,195 @@
1
+ /* eslint-disable react/no-multi-comp */
2
+ /* eslint-disable no-nested-ternary */
3
+ import React, { useEffect, useRef } from 'react';
4
+ import type { ChatMessage } from '../../types';
5
+ import { DEFAULT_CHAT_VERSION } from '../../types';
6
+ import { getVibeNoteBridge, vibeNoteCanIUse } from '../../utils/bridge';
7
+ import { resolveMessageContent } from '../../utils/deepThinking';
8
+ import AssistantRichContent from '../AssistantRichContent';
9
+ import DeepThinkingWrapper from '../DeepThinkingWrapper';
10
+ import ThinkingCompleteEntry from '../ThinkingCompleteEntry';
11
+ import TypingIndicator from '../TypingIndicator';
12
+ import styles from './index.less';
13
+
14
+ type MessageListProps = {
15
+ messages: ChatMessage[];
16
+ isStreaming?: boolean;
17
+ };
18
+
19
+ const AssistantMessage: React.FC<{
20
+ message: ChatMessage;
21
+ isStreamingMessage: boolean;
22
+ }> = ({ message, isStreamingMessage }) => {
23
+ const isReplyError = Boolean(message.isReplyError);
24
+ const isStopped = Boolean(message.isStopped);
25
+ const chatVersion = message.chatVersion || DEFAULT_CHAT_VERSION;
26
+ const {
27
+ isDeepThinking,
28
+ isFinishDeepThink,
29
+ thinkingContent,
30
+ answerContent,
31
+ } = resolveMessageContent(
32
+ message.content || '',
33
+ isStreamingMessage,
34
+ isReplyError,
35
+ chatVersion,
36
+ );
37
+
38
+ const showCursor = isStreamingMessage && !isDeepThinking && !isStopped && Boolean(answerContent);
39
+
40
+ return (
41
+ <div className={styles.bubbleAssistant}>
42
+ {isDeepThinking && !isStopped && !isReplyError ? (
43
+ <DeepThinkingWrapper>
44
+ <AssistantRichContent content={thinkingContent} variant="thinking" />
45
+ </DeepThinkingWrapper>
46
+ ) : null}
47
+
48
+ {isFinishDeepThink && thinkingContent ? (
49
+ <ThinkingCompleteEntry thinkingContent={thinkingContent} />
50
+ ) : null}
51
+
52
+ {!isDeepThinking && !answerContent && !isReplyError && !isStopped ? (
53
+ <TypingIndicator />
54
+ ) : null}
55
+
56
+ {answerContent || showCursor ? (
57
+ isReplyError ? (
58
+ <div className={styles.content}>{answerContent}</div>
59
+ ) : (
60
+ <AssistantRichContent content={answerContent} showCursor={showCursor} />
61
+ )
62
+ ) : null}
63
+
64
+ {isStopped ? <div className={styles.hint}>已停止生成</div> : null}
65
+ {isReplyError ? <div className={styles.hintError}>回答异常</div> : null}
66
+ {/* {chatVersion === '2' && !isStreamingMessage && !isReplyError ? (
67
+ <div className={styles.expertTip}>本次使用专家作答</div>
68
+ ) : null} */}
69
+ </div>
70
+ );
71
+ };
72
+
73
+ const MessageList: React.FC<MessageListProps> = ({ messages, isStreaming }) => {
74
+ const listRef = useRef<HTMLDivElement>(null);
75
+ const bottomRef = useRef<HTMLDivElement>(null);
76
+ const snapTimerRef = useRef(0);
77
+
78
+ const getScroller = () => listRef.current?.parentElement ?? null;
79
+
80
+ const snapToBottom = () => {
81
+ const scroller = getScroller();
82
+ if (!scroller) {
83
+ bottomRef.current?.scrollIntoView({ behavior: 'auto', block: 'end' });
84
+ return;
85
+ }
86
+ scroller.scrollTop = scroller.scrollHeight;
87
+ };
88
+
89
+ const scrollToBottom = (smooth = false) => {
90
+ window.clearTimeout(snapTimerRef.current);
91
+ const scroller = getScroller();
92
+ if (!scroller) {
93
+ bottomRef.current?.scrollIntoView({ behavior: smooth ? 'smooth' : 'auto', block: 'end' });
94
+ return;
95
+ }
96
+ const top = scroller.scrollHeight;
97
+ if (smooth) {
98
+ scroller.scrollTo({ top, behavior: 'smooth' });
99
+ snapTimerRef.current = window.setTimeout(snapToBottom, 420);
100
+ return;
101
+ }
102
+ scroller.scrollTop = top;
103
+ };
104
+
105
+ useEffect(() => {
106
+ scrollToBottom(true);
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, [messages, isStreaming]);
109
+
110
+ useEffect(() => {
111
+ let settleTimer = 0;
112
+
113
+ const scrollAfterKeyboard = (delay: number) => {
114
+ window.clearTimeout(settleTimer);
115
+ const run = () => {
116
+ requestAnimationFrame(() => {
117
+ requestAnimationFrame(() => scrollToBottom(true));
118
+ });
119
+ };
120
+ if (delay <= 0) {
121
+ run();
122
+ return;
123
+ }
124
+ settleTimer = window.setTimeout(run, delay);
125
+ };
126
+
127
+ if (vibeNoteCanIUse('event:keyboardShow')) {
128
+ const offKeyboard = getVibeNoteBridge()?.addListener?.('keyboardShow', (event) => {
129
+ const { duration = 0 } = (event?.data as { duration?: number } | undefined) || {};
130
+ scrollAfterKeyboard(Number(duration) || 0);
131
+ });
132
+ return () => {
133
+ window.clearTimeout(settleTimer);
134
+ window.clearTimeout(snapTimerRef.current);
135
+ offKeyboard?.();
136
+ };
137
+ }
138
+
139
+ let lastHeight = window.visualViewport?.height ?? window.innerHeight;
140
+ const onViewportChange = () => {
141
+ const next = window.visualViewport?.height ?? window.innerHeight;
142
+ if (next < lastHeight - 24) {
143
+ scrollAfterKeyboard(320);
144
+ }
145
+ lastHeight = next;
146
+ };
147
+ const onFocusIn = ({ target }: FocusEvent) => {
148
+ if (target instanceof HTMLTextAreaElement || target instanceof HTMLInputElement) {
149
+ scrollAfterKeyboard(320);
150
+ }
151
+ };
152
+ window.visualViewport?.addEventListener('resize', onViewportChange);
153
+ window.addEventListener('resize', onViewportChange);
154
+ document.addEventListener('focusin', onFocusIn);
155
+ return () => {
156
+ window.clearTimeout(settleTimer);
157
+ window.clearTimeout(snapTimerRef.current);
158
+ window.visualViewport?.removeEventListener('resize', onViewportChange);
159
+ window.removeEventListener('resize', onViewportChange);
160
+ document.removeEventListener('focusin', onFocusIn);
161
+ };
162
+ }, []);
163
+
164
+ return (
165
+ <div ref={listRef} className={styles.list}>
166
+ {messages.map((msg, index) => {
167
+ const isUser = msg.role === 'user';
168
+ const isStreamingMessage =
169
+ !isUser &&
170
+ Boolean(isStreaming) &&
171
+ index === messages.length - 1 &&
172
+ !msg.isStopped &&
173
+ !msg.isReplyError;
174
+
175
+ return (
176
+ <div
177
+ key={msg.id}
178
+ className={`${styles.row} ${isUser ? styles.rowUser : styles.rowAssistant}`}
179
+ >
180
+ {isUser ? (
181
+ <div className={styles.bubbleUser}>
182
+ <div className={styles.content}>{msg.content}</div>
183
+ </div>
184
+ ) : (
185
+ <AssistantMessage message={msg} isStreamingMessage={isStreamingMessage} />
186
+ )}
187
+ </div>
188
+ );
189
+ })}
190
+ <div ref={bottomRef} className={styles.bottomAnchor} />
191
+ </div>
192
+ );
193
+ };
194
+
195
+ export default MessageList;
@@ -0,0 +1,56 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: flex-start;
5
+ gap: 8Px;
6
+ width: 100%;
7
+ padding: 0 0 12Px;
8
+ margin-bottom: 4Px;
9
+ border-bottom: 0.5Px solid #ebebeb;
10
+ }
11
+
12
+ .titleRow {
13
+ display: flex;
14
+ align-items: center;
15
+ width: 100%;
16
+ padding: 0;
17
+ border: 0;
18
+ background: transparent;
19
+ cursor: pointer;
20
+ }
21
+
22
+ .icon {
23
+ width: 20Px;
24
+ height: 20Px;
25
+ flex-shrink: 0;
26
+ }
27
+
28
+ .titleText {
29
+ margin-left: 8Px;
30
+ font-size: 14Px;
31
+ line-height: 20Px;
32
+ font-weight: 600;
33
+ background: linear-gradient(90deg, #2a2b33 0%, #fe780f 100%);
34
+ -webkit-background-clip: text;
35
+ background-clip: text;
36
+ color: transparent;
37
+ }
38
+
39
+ .spacer {
40
+ flex: 1;
41
+ }
42
+
43
+ .arrow {
44
+ width: 16Px;
45
+ height: 16Px;
46
+ flex-shrink: 0;
47
+ transition: transform 0.2s ease;
48
+ }
49
+
50
+ .arrowExpanded {
51
+ transform: rotate(180deg);
52
+ }
53
+
54
+ .content {
55
+ width: 100%;
56
+ }
@@ -0,0 +1,65 @@
1
+ /* eslint-disable react/no-multi-comp */
2
+ import React, { useState } from 'react';
3
+ import AssistantRichContent from '../AssistantRichContent';
4
+ import styles from './index.less';
5
+
6
+ type ThinkingCompleteEntryProps = {
7
+ thinkingContent: string;
8
+ };
9
+
10
+ const DeepThinkIcon: React.FC = () => {
11
+ const gradId = `xiaozhi-think-done-${React.useId().replace(/:/g, '')}`;
12
+ return (
13
+ <svg className={styles.icon} viewBox="0 0 1024 1024" fill="none" aria-hidden>
14
+ <path
15
+ d="M433.472 548.672c19.648 41.408 68.736 60.16 110.528 41.856a85.12 85.12 0 0 0 44.8-115.2 85.056 85.056 0 0 0-117.312-38.848 85.12 85.12 0 0 0-38.016 112.192z m398.08-37.504c75.968-136.576 83.648-274.816 18.816-339.648-64.896-64.448-203.968-56.768-340.48 19.2C373.76 116.032 235.968 108.8 171.52 173.248c-64.448 64.384-57.152 201.792 17.92 337.92-75.072 135.68-82.368 273.472-17.92 337.472 20.032 20.032 47.36 34.112 78.912 40.96 70.848 14.912 162.56-5.568 259.008-58.432 97.28 53.76 190.272 75.072 261.568 60.16 31.104-6.848 58.88-20.928 78.912-40.96 20.032-20.096 34.56-47.36 40.96-78.528 15.36-71.232-5.952-163.84-59.712-260.672h0.384z m-599.04-277.376c26.048-25.984 73.856-28.544 142.528-8.064 17.088 5.12 34.112 11.52 51.2 18.752a858.24 858.24 0 0 0-97.28 84.032C297.408 359.68 268.8 392.96 243.2 427.52a468.16 468.16 0 0 1-18.752-51.648c-20.928-68.672-18.368-116.48 8.064-142.464v0.384zM375.04 796.16c-69.12 20.928-116.48 17.92-142.528-8.128-25.984-25.984-28.544-73.344-8.064-142.464 5.12-17.088 11.52-34.176 18.752-51.2a886.592 886.592 0 0 0 182.592 183.04 495.232 495.232 0 0 1-51.2 18.752h0.448z m14.528-163.392A758.4 758.4 0 0 1 288.832 510.72c26.88-41.408 60.608-81.92 100.736-122.048a780.8 780.8 0 0 1 120.32-99.392c41.344 26.88 82.304 60.608 122.432 100.672 39.68 39.68 72.96 79.808 99.84 120.32-26.88 40.576-60.16 80.64-99.84 120.32a779.712 779.712 0 0 1-122.432 100.736 780.8 780.8 0 0 1-120.32-99.456v0.896z m399.744 156.992c-25.984 26.048-73.792 28.608-142.464 8.128a545.28 545.28 0 0 1-53.76-19.648c34.944-25.6 68.224-54.208 99.84-85.76 31.104-30.72 59.264-63.552 84.48-97.728 7.68 17.536 14.08 35.456 19.584 53.376 20.928 68.672 18.368 116.48-8.064 142.464l0.384-0.832z m8.128-415.168c-5.568 17.92-11.968 35.84-19.648 53.376a868.48 868.48 0 0 0-84.864-98.176 856.128 856.128 0 0 0-99.84-85.76c17.92-7.68 35.84-14.464 53.76-20.032 69.12-20.928 116.48-17.92 142.464 8.128 26.048 25.984 28.608 73.344 8.128 142.464z"
16
+ fill={`url(#${gradId})`}
17
+ />
18
+ <defs>
19
+ <linearGradient id={gradId} x1="176" y1="128" x2="848" y2="896">
20
+ <stop stopColor="#2A2B33" />
21
+ <stop offset="1" stopColor="#FE780F" />
22
+ </linearGradient>
23
+ </defs>
24
+ </svg>
25
+ );
26
+ };
27
+
28
+ const ThinkingCompleteEntry: React.FC<ThinkingCompleteEntryProps> = ({ thinkingContent }) => {
29
+ const [expanded, setExpanded] = useState(false);
30
+
31
+ return (
32
+ <div className={styles.root}>
33
+ <button
34
+ type="button"
35
+ className={styles.titleRow}
36
+ onClick={() => setExpanded((prev) => !prev)}
37
+ >
38
+ <DeepThinkIcon />
39
+ <span className={styles.titleText}>已完成深度思考</span>
40
+ <span className={styles.spacer} />
41
+ <svg
42
+ className={`${styles.arrow} ${expanded ? styles.arrowExpanded : ''}`}
43
+ viewBox="0 0 16 16"
44
+ fill="none"
45
+ aria-hidden
46
+ >
47
+ <path
48
+ d="M4 6.5 8 10.5 12 6.5"
49
+ stroke="#FE780F"
50
+ strokeWidth="1.5"
51
+ strokeLinecap="round"
52
+ strokeLinejoin="round"
53
+ />
54
+ </svg>
55
+ </button>
56
+ {expanded ? (
57
+ <div className={styles.content}>
58
+ <AssistantRichContent content={thinkingContent} variant="thinking" />
59
+ </div>
60
+ ) : null}
61
+ </div>
62
+ );
63
+ };
64
+
65
+ export default ThinkingCompleteEntry;
@@ -0,0 +1,41 @@
1
+ .list {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ gap: 8Px;
5
+ margin: 4Px 0;
6
+ }
7
+
8
+ .chip {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: 4Px;
12
+ max-width: 100%;
13
+ padding: 4Px 8Px;
14
+ border: 0.5Px solid #ebebeb;
15
+ border-radius: 8Px;
16
+ background: #ffffff;
17
+ color: #5c5e6e;
18
+ }
19
+
20
+ .chipPending {
21
+ opacity: 0.5;
22
+ }
23
+
24
+ .icon {
25
+ width: 13Px;
26
+ height: 13Px;
27
+ flex-shrink: 0;
28
+ color: #9092a3;
29
+ }
30
+
31
+ .label {
32
+ overflow: hidden;
33
+ font-size: 13Px;
34
+ line-height: 20Px;
35
+ white-space: nowrap;
36
+ text-overflow: ellipsis;
37
+ }
38
+
39
+ .errorCount {
40
+ color: #ff3b30;
41
+ }