yt-chat-components 1.2.8 → 1.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-chat-components",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "main": "build/static/js/bundle.min.js",
5
5
  "module": "build/static/js/bundle.min.js",
6
6
  "types": "build/static/js/index.d.ts",
@@ -6,8 +6,8 @@ import remarkGfm from 'remark-gfm';
6
6
  import rehypeMathjax from 'rehype-mathjax';
7
7
  // import './index.module.css';
8
8
  import upFilePng from '../../../../assets/aicenter/upfile.png';
9
- import {Form, Image, Input, message as messageTip, Select, DatePicker, Button, Spin, Skeleton} from 'antd';
10
- import React, {useState, useRef} from 'react';
9
+ import {Button, Collapse, DatePicker, Form, Image, Input, message as messageTip, Select, Skeleton} from 'antd';
10
+ import React, {useRef, useState} from 'react';
11
11
  import typePdfPng from '../../../../assets/aicenter/type-pdf.png';
12
12
  import typeWordPng from '../../../../assets/aicenter/type-word.png';
13
13
  import typeExcelPng from '../../../../assets/aicenter/type-excel.png';
@@ -18,13 +18,18 @@ import typeRPubPng from '../../../../assets/aicenter/type-rpub.png';
18
18
  import playPng from '../../../../assets/aicenter/play.png';
19
19
  import playRunGif from '../../../../assets/aicenter/play-run.gif';
20
20
  import copyPng from '../../../../assets/aicenter/copy.png';
21
- import { ThoughtChain } from '@ant-design/x';
22
- import type { ThoughtChainProps, ThoughtChainItem } from '@ant-design/x';
23
- import { CheckCircleOutlined, MoreOutlined, InfoCircleOutlined, LoadingOutlined, EllipsisOutlined } from '@ant-design/icons';
21
+ import type {ThoughtChainItem} from '@ant-design/x';
22
+ import {ThoughtChain} from '@ant-design/x';
23
+ import {CheckCircleOutlined, EllipsisOutlined, InfoCircleOutlined, ClockCircleOutlined} from '@ant-design/icons';
24
+ import ChatMessagePlaceholder, {ChatMessagePlaceholderInThought} from "../chatPlaceholder";
24
25
 
25
26
  let speechSynth = window.speechSynthesis;
26
27
  let utterance = null;
27
28
 
29
+ const isEmpty = (o) => {
30
+ return !o || (typeof o === 'object' && Object.keys(o).length === 0) || (typeof o === 'string' && o.trim().length === 0);
31
+ }
32
+
28
33
  export default function ChatMessage({
29
34
  receivingMessage,
30
35
  setReceivingMessage,
@@ -219,7 +224,7 @@ export default function ChatMessage({
219
224
  >
220
225
  {
221
226
  // 其他类型暂不处理
222
- type == MessageType.echart && (
227
+ (type == MessageType.echart || type == MessageType.form) && (
223
228
  <Skeleton.Image active={true} style={{width: '100%', height: '100%'}}/>
224
229
  )
225
230
  }
@@ -261,7 +266,7 @@ export default function ChatMessage({
261
266
  // 渲染 form 组件
262
267
  return renderBotFormMessage(form_config);
263
268
  } catch (error) {
264
- return renderLoading(MessageType.echart, 300);
269
+ return renderLoading(MessageType.form, 300);
265
270
  }
266
271
  }
267
272
  // 默认渲染其他代码块
@@ -281,14 +286,29 @@ export default function ChatMessage({
281
286
  }
282
287
  }
283
288
 
289
+ const renderThinkTitle = (isThinking) => {
290
+ return <div style={{display: 'flex', alignItems: 'center', color: '#989898', fontSize: 14}}>
291
+ {
292
+ isThinking ? [
293
+ <ClockCircleOutlined style={{color: '#82b6dd'}}/>,
294
+ <span style={{marginLeft: 8}}>正在深度思考</span>
295
+ ] : [
296
+ <CheckCircleOutlined style={{color: '#8add82'}} />,
297
+ <span style={{marginLeft: 8}}>已完成深度思考</span>
298
+ ]
299
+ }
300
+ </div>
301
+ }
302
+
284
303
  const renderBotTextMessage = () => {
285
304
  const items = messageItemList.map((item, index) => {
286
- const {id, message, type, name, toolCallInfo, rawInfo} = item;
305
+ const {id, thinkMessage, message, loadingMessage, type, name, toolCallInfo, rawInfo} = item;
306
+ const isLatest = index == messageItemList.length - 1;
287
307
  let status = 'success'
288
308
  // 只有在接收信息的时候有 pending 状态
289
309
  if (receivingMessage && isLatest){
290
310
  // 最后的信息才为 pending
291
- if(messageItemList.length - 1 == index){
311
+ if(isLatest){
292
312
  status = 'pending'
293
313
  }
294
314
  }
@@ -298,15 +318,36 @@ export default function ChatMessage({
298
318
  description: toolCallInfo,
299
319
  icon: getStatusIcon(status),
300
320
  status: status,
301
- content: <Markdown
302
- key={id}
303
- className={'markdown-body prose flex flex-col word-break-break-word'}
304
- remarkPlugins={[remarkGfm]}
305
- rehypePlugins={[rehypeMathjax]}
306
- components={CustomRender}
307
- >
308
- {message}
309
- </Markdown>,
321
+ content: <div style={{display:"flex", flexDirection: 'column'}}>
322
+ {
323
+ thinkMessage && <Collapse
324
+ bordered={false}
325
+ style={{marginBottom: 15, color: '#989898'}}
326
+ size="small"
327
+ defaultActiveKey={'1'}
328
+ expandIconPosition={'end'}
329
+ items={[{key: '1', label: renderThinkTitle(isEmpty(message) && isLatest), children: <Markdown
330
+ className={'think markdown-body prose flex flex-col word-break-break-word'}
331
+ remarkPlugins={[remarkGfm]}
332
+ rehypePlugins={[rehypeMathjax]}
333
+ >
334
+ {thinkMessage}
335
+ </Markdown>}]}
336
+ />
337
+ }
338
+ {/* 有 thinkMessage 的时候没有占位符,否则赞为符输出*/}
339
+ {
340
+ message ? <Markdown
341
+ key={id}
342
+ className={'markdown-body prose flex flex-col word-break-break-word'}
343
+ remarkPlugins={[remarkGfm]}
344
+ rehypePlugins={[rehypeMathjax]}
345
+ components={CustomRender}
346
+ >
347
+ {message}
348
+ </Markdown> : thinkMessage ? "" : <ChatMessagePlaceholderInThought aiStatus={loadingMessage ? loadingMessage : "正在处理数据"} />
349
+ }
350
+ </div>,
310
351
  footer: <div key={id} className="msg_operateBox">
311
352
  <img src={isPlay ? playRunGif : playPng} onClick={() => playVoice(message)} />
312
353
  <img src={copyPng} onClick={() => copyText(message)} />
@@ -21,3 +21,22 @@ export default function ChatMessagePlaceholder({
21
21
  </div>
22
22
  );
23
23
  }
24
+
25
+ export function ChatMessagePlaceholderInThought({
26
+ aiStatus
27
+ }: ChatMessagePlaceholderType) {
28
+ return (
29
+ <div
30
+ className="cl-chat-message-in-thought cl-justify-start"
31
+ >
32
+ <div className={"cl-bot_message-in-thought"}>
33
+ <div className="cl-animate-pulse">
34
+ <div className="plh_textBox">
35
+ <div className="plh_text">{aiStatus ? aiStatus : "深度思考中"}</div>
36
+ <MoreHorizontal />
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ );
42
+ }
@@ -150,6 +150,7 @@ export default function ChatWindow({
150
150
  const [inputContainerHeight, setInputContainerHeight] = useState('50px')
151
151
  let content_id: string = null
152
152
  let content_ns: string = null
153
+ let event_latest: string = null
153
154
  const nowAIContentListRef = useRef(nowAIContentList)
154
155
 
155
156
  let voiceChunks = []; // 临时存储录制的语音片段
@@ -176,7 +177,7 @@ export default function ChatWindow({
176
177
  if (delayMessageList.length > 0) {
177
178
  const data = delayMessageList.shift();
178
179
  const {r_id, form_config} = data;
179
- updateMessageItem("\n```form\n" + JSON.stringify(form_config) + "\n```\n")
180
+ updateMessageItem({chunk:"\n```form\n" + JSON.stringify(form_config) + "\n```\n"})
180
181
 
181
182
  // ?????? done
182
183
  // addMessage({
@@ -193,32 +194,51 @@ export default function ChatWindow({
193
194
  }
194
195
  }
195
196
 
196
- const addMessageItem = (chunk, id, name, status = null) => {
197
- console.log("--- addMessageItem", chunk, status)
197
+ const addMessageItem = ({chunk, id, name, status = null, isThinkChunk = false, loadingMessage = null}) => {
198
+ // console.log("--- addMessageItem", chunk, status, loadingMessage)
198
199
  setNowAIContentList((prevState) => {
200
+ const content = {}
201
+ if(isThinkChunk){
202
+ content["thinkMessage"] = chunk
203
+ content["message"] = ""
204
+ }else {
205
+ content["thinkMessage"] = ""
206
+ content["message"] = chunk
207
+ }
208
+
199
209
  const messageItem : MessageItem = {
200
210
  id,
201
211
  name,
202
- message: chunk,
203
212
  type: MessageType.text,
204
213
  toolCallInfo: status,
214
+ loadingMessage,
215
+ ...content
205
216
  }
206
217
  nowAIContentListRef.current = [...prevState, messageItem]
207
218
  return [...prevState, messageItem]
208
219
  })
209
220
  }
210
221
 
211
- const updateMessageItem = (chunk, status = null) => {
212
- console.log("--- updateMessageItem", chunk, status)
222
+ const updateMessageItem = ({chunk, status = null, isThinkChunk = false, loadingMessage = null}) => {
223
+ // console.log("--- updateMessageItem", chunk, status)
213
224
  setNowAIContentList((prevState) => {
214
225
  const latestMessageItem = prevState[prevState.length - 1]
226
+ const content = {}
227
+ if(isThinkChunk){
228
+ content["thinkMessage"] = latestMessageItem.thinkMessage + chunk
229
+ content["message"] = latestMessageItem.message
230
+ }else {
231
+ content["thinkMessage"] = latestMessageItem.thinkMessage
232
+ content["message"] = latestMessageItem.message + chunk
233
+ }
215
234
 
216
235
  const newMessageItem: MessageItem = {
217
236
  id: latestMessageItem.id === 'WAIT' ? content_id : latestMessageItem.id,
218
237
  name: latestMessageItem.name,
219
- message: latestMessageItem.message + chunk,
220
238
  type: latestMessageItem.type,
221
239
  toolCallInfo: status,
240
+ loadingMessage,
241
+ ...content
222
242
  }
223
243
 
224
244
  const newState = [...prevState]
@@ -228,17 +248,16 @@ export default function ChatWindow({
228
248
  return newState
229
249
  })
230
250
  }
231
-
232
251
 
233
252
  // 流式输出消息,实时显示(token为流式输出内容,end为结束输出,整体输出一次)
234
253
  const handleMessageContent = (event, data) => {
235
- // console.log("--- event, data",event, data)
254
+ console.log("--- event, data",event, data)
236
255
 
237
256
  if (event == 'add_message' && data['sender'] == 'Machine') {
238
257
  getHistoryList();
239
258
  }
240
- else if (event == 'token') {
241
- let { chunk, id, r_id, ns, name } = data
259
+ else if (event == 'token' || event == 't_full_token') {
260
+ let { chunk, id, r_id, ns, name, loading_message } = data
242
261
  if (chunk.includes('```') && !chunk.startsWith('\n')) {
243
262
  // 确保 ``` 前有换行
244
263
  chunk = '\n' + chunk;
@@ -250,14 +269,16 @@ export default function ChatWindow({
250
269
  // 如果刚才在调用工具,则追加信息
251
270
  if (content_id === 'WAIT'){
252
271
  content_id = id
253
- updateMessageItem(chunk)
272
+ updateMessageItem({chunk, loadingMessage: loading_message})
254
273
  }
255
274
  // 新建信息
256
275
  else{
257
276
  content_id = id
258
- addMessageItem(chunk, id, name)
277
+ addMessageItem({chunk, id, name, loadingMessage:loading_message})
259
278
  }
260
- }else {
279
+ }
280
+ // 输出主体没有变化
281
+ else {
261
282
  // 如果 content_id === WAIT 表示 token 发出来之前 agent在调用工具
262
283
  // 但是调用工具的时候已经创建了信息,所以这里更新一下 content_id。这样就可以追加信息
263
284
  if(content_id === 'WAIT'){
@@ -267,19 +288,40 @@ export default function ChatWindow({
267
288
  // id切换表示一句话说完了, 新增信息
268
289
  if (content_id !== id){
269
290
  content_id = id
270
- addMessageItem(chunk, id, name)
291
+
292
+ // 如果 event_latest 是 token,此刻变成了 t_full_token,表示之前都在输出占位符,所以更新信息
293
+ if (event_latest === 'token' && event === 't_full_token'){
294
+ updateMessageItem({chunk, loadingMessage: loading_message})
295
+ } else {
296
+ addMessageItem({chunk, id, name, loadingMessage:loading_message})
297
+ }
271
298
  }else{
272
- updateMessageItem(chunk)
299
+ updateMessageItem({chunk, loadingMessage: loading_message})
273
300
  }
274
301
  }
275
302
 
276
303
  if (lastMessage.current) {
277
304
  lastMessage.current.scrollIntoView({ behavior: 'smooth' });
278
305
  }
306
+
307
+
279
308
  }
280
- else if (event == 'form') {
281
- // 这里添加到延时队列,直接处理可能会把form渲染到token上面
282
- delayMessageList.push(data)
309
+ else if (event == 't_token') {
310
+ let { chunk, id, r_id, ns, name, loading_message } = data
311
+
312
+ // ns切换表示切换了智能体
313
+ if (content_ns !== ns){
314
+ content_ns = ns
315
+ // 新建信息
316
+ content_id = id
317
+ addMessageItem({chunk, id, name, status:null, isThinkChunk:true, loadingMessage: loading_message})
318
+ }else {
319
+ updateMessageItem({chunk, status:null, isThinkChunk:true, loadingMessage: loading_message})
320
+ }
321
+
322
+ if (lastMessage.current) {
323
+ lastMessage.current.scrollIntoView({ behavior: 'smooth' });
324
+ }
283
325
  }
284
326
  else if (event == 'status') {
285
327
  // 更新状态
@@ -288,17 +330,26 @@ export default function ChatWindow({
288
330
  // ns变化表示切换了智能体,表示智能体一上来就调用工具
289
331
  if (content_ns !== ns){
290
332
  content_ns = ns
291
- // 这个时候还没有信息的id,所以 content_id 给特殊值
292
- content_id = "WAIT"
293
- addMessageItem("", "WAIT", name, status)
333
+ // 表示智能体连续调用工具
334
+ if (content_id === 'WAIT') {
335
+ updateMessageItem({chunk:"", status, loadingMessage: loading_message})
336
+ }else{
337
+ // 这个时候还没有信息的id,所以 content_id 给特殊值
338
+ content_id = "WAIT"
339
+ addMessageItem({chunk:"", id: "WAIT", name, status})
340
+ }
294
341
  }
295
342
  // 当前智能体在调用工具
296
343
  else{
297
- updateMessageItem("", status)
344
+ updateMessageItem({chunk:"", status, loadingMessage: loading_message})
298
345
  }
299
346
 
300
347
  setAiStatus(data.status)
301
348
  }
349
+ else if (event == 'form') {
350
+ // 这里添加到延时队列,直接处理可能会把form渲染到token上面
351
+ delayMessageList.push(data)
352
+ }
302
353
  else if (event == 'end') {
303
354
  // const res = {
304
355
  // data: data['result'],
@@ -372,6 +423,8 @@ export default function ChatWindow({
372
423
  content_ns = null
373
424
  content_id = null
374
425
  }
426
+
427
+ event_latest = event
375
428
  };
376
429
 
377
430
  const sendMessageNoStream = (res) => {
@@ -999,7 +1052,7 @@ export default function ChatWindow({
999
1052
  if(messages.length > 0){
1000
1053
  const { messageItemList } = messages[messages.length - 1]
1001
1054
  const latestMessageItem = messageItemList[messageItemList.length - 1]
1002
- isRender = !latestMessageItem.message.includes("```form")
1055
+ isRender = !(latestMessageItem.message || "").includes("```form")
1003
1056
  }
1004
1057
 
1005
1058
  if(!isRender){
@@ -1,13 +1,15 @@
1
1
  export enum MessageType {
2
2
  text,
3
- form,
3
+ form, // 目前只用于渲染loading
4
4
  echart // 目前只用于渲染loading
5
5
  }
6
6
 
7
7
  export type MessageItem = {
8
8
  id:string,
9
9
  name: string;
10
+ thinkMessage: string;
10
11
  message: string;
12
+ loadingMessage?: string;
11
13
  rawInfo?: any;
12
14
  type?: MessageType,
13
15
  toolCallInfo?: string