yt-chat-components 0.1.0 → 0.2.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,14 +1,17 @@
1
1
  {
2
2
  "name": "yt-chat-components",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
7
+ "peerDependencies": {
8
+ "react": "^17.0.0 || ^18.0.0",
9
+ "react-dom": "^17.0.0 || ^18.0.0"
10
+ },
7
11
  "dependencies": {
8
12
  "antd": "5.24.2",
9
13
  "@ant-design/icons": "5.0.0",
10
14
  "lodash": "^4.17.21",
11
- "@r2wc/react-to-web-component": "^2.0.2",
12
15
  "@testing-library/jest-dom": "^5.16.5",
13
16
  "@testing-library/react": "^13.4.0",
14
17
  "@testing-library/user-event": "^13.5.0",
@@ -16,9 +19,7 @@
16
19
  "@types/node": "^16.18.37",
17
20
  "@types/react": "^18.2.14",
18
21
  "@types/react-dom": "^18.2.6",
19
- "antd": "5.24.2",
20
22
  "axios": "^1.4.0",
21
- "lodash": "^4.17.21",
22
23
  "lucide-react": "^0.256.0",
23
24
  "react": "^18.3.1",
24
25
  "react-dom": "^18.3.1",
package/public/index.html CHANGED
@@ -5,33 +5,35 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Document</title>
7
7
  </head>
8
- <!--<body style="width: 100vw; height: 100vh; position: relative; margin: unset ">-->
9
- <!-- <yt-chat-->
10
- <!-- right="100"-->
11
- <!-- bottom="100"-->
12
- <!-- width="50"-->
13
- <!-- height="50"-->
14
- <!-- title="菜鸟驿站"-->
15
- <!-- icon-url="https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/smartSchool/appCreator/school/ccit/user/xc//image/ebfaf4da-c1d9-46fb-a0b1-f159e95cffc2_AI招生咨询小助手.png"-->
16
- <!-- host-url="https://ai-api.yuntu.cn"-->
17
- <!-- user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'-->
18
- <!-- flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"-->
19
- <!-- />-->
20
- <!--</body>-->
8
+ <!-- <body style="width: 100vw; height: 100vh; position: relative; margin: unset ">-->
9
+ <!-- <yt-chat-->
10
+ <!-- right="100"-->
11
+ <!-- bottom="100"-->
12
+ <!-- width="50"-->
13
+ <!-- height="50"-->
14
+ <!-- title="菜鸟驿站"-->
15
+ <!-- icon-url="https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/smartSchool/appCreator/school/ccit/user/xc//image/ebfaf4da-c1d9-46fb-a0b1-f159e95cffc2_AI招生咨询小助手.png"-->
16
+ <!-- host-url="https://ai-api.yuntu.cn"-->
17
+ <!-- user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'-->
18
+ <!-- flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"-->
19
+ <!-- />-->
20
+ <!-- </body>-->
21
21
 
22
- <!--<body style="width: 100vw; height: 100vh; margin:0 ">-->
23
- <!--<yt-page-chat-->
24
- <!-- title="菜鸟驿站"-->
25
- <!-- host-url="https://ai-api.yuntu.cn"-->
26
- <!-- flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"-->
27
- <!-- user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'-->
28
- <!-- tags='["tag1", "tag2", "tag3"]'-->
29
- <!-- box-style='{"height":"100%"}'-->
30
- <!--/>-->
31
- <!--</body>-->
22
+ <body style="width: 100vw; height: 100vh; margin:0 ">
23
+ <yt-page-chat
24
+ title="菜鸟驿站"
25
+ host-url="https://ai-api.yuntu.cn"
26
+ flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"
27
+ user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'
28
+ tags='["tag1", "tag2", "tag3"]'
29
+ box-style='{"height":"100%"}'
30
+ scene-id="e6fb45ea-3415-44e1-a1c8-5e98963bf512"
31
+ is-show-side-right=true
32
+ />
33
+ </body>
32
34
 
33
35
  <body style="width: 100vw; height: 100vh; position: relative; margin: unset ">
34
- <yt-split-modal-chat
36
+ <yt-split-modal-chat
35
37
  right="100"
36
38
  bottom="100"
37
39
  width="50"
@@ -42,6 +44,6 @@
42
44
  user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'
43
45
  flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"
44
46
  scene-id="e6fb45ea-3415-44e1-a1c8-5e98963bf512"
45
- />
47
+ />
46
48
  </body>
47
49
  </html>
@@ -24,7 +24,7 @@ import luyinPng from '../../../assets/aicenter/luyin.png';
24
24
  import { RightOutlined } from '@ant-design/icons';
25
25
  import { Image, message as messageTip, Tooltip } from 'antd';
26
26
  import { isEmpty, isFunction } from 'lodash';
27
- import {MoreHorizontal} from "lucide-react";
27
+ import btn_answer from '../../../assets/aicenter/btn_answer.png';
28
28
 
29
29
 
30
30
  let mediaRecorder = null; // 语音对象,用于录音
@@ -122,6 +122,8 @@ export default function ChatWindow({
122
122
  const [showRightArrow, setShowRightArrow] = useState(false); // 控制右侧箭头显示
123
123
  const isStreamOutput = false;//是否流式输出(手动开关)
124
124
  const [recordState, setRecordState] = useState(false); // 录音状态。true为正在录音,false为停止录音
125
+ const [tagList, setTagList] = useState([]); // 问题标签列表
126
+
125
127
  let voiceChunks = []; // 临时存储录制的语音片段
126
128
  // 滚动事件处理,选择文件时,文件内容超出显示框时,显示左右箭头
127
129
  const handleScroll = () => {
@@ -555,8 +557,9 @@ export default function ChatWindow({
555
557
 
556
558
  useEffect(() => {
557
559
  // after a slight delay
560
+ setTagList(tags);
558
561
  setTimeout(() => {
559
- inputRef.current?.focus();
562
+ // inputRef.current?.focus();
560
563
  }, 100);
561
564
  }, [messages, open]);
562
565
 
@@ -599,191 +602,206 @@ export default function ChatWindow({
599
602
  ref={ref}
600
603
  className="cl-window"
601
604
  >
602
- <div className="cl-header">
603
- {window_title}
604
- <div className="cl-header-subtitle">
605
- {online ? (
606
- <>
607
- <div className="cl-online-message"></div>
608
- {online_message}
609
- </>
610
- ) : (
611
- <>
612
- <div className="cl-offline-message"></div>
613
- {offline_message}
614
- </>
605
+ <div className="cl-middle-container">
606
+ <div className="cl-header">
607
+ <div className="header-title"><span className="diamond"/>{window_title}</div>
608
+ <div className="cl-header-subtitle"/>
609
+ {!isEmpty(dropDownList) && (
610
+ <div className="cl-drop-down">
611
+ <div className="drop-down-title">Hi,欢迎使用{window_title},您可以这样问我:</div>
612
+ <div className="drop-down-list">
613
+ {dropDownList.map(({ backgroundImg, title }) => (
614
+ <div className="drop-down-item-card" key={title}>
615
+ <div className="drop-down-item-title">“{title}”</div>
616
+ <div className="drop-down-item-bottom">
617
+ <div
618
+ className="drop-down-item-bottom-button"
619
+ onClick={() => {
620
+ setDropDownList(undefined);
621
+ handleClick(title);
622
+ }}
623
+ >
624
+ <img src={btn_answer}/>
625
+ {/*<RightOutlined style={{ height: '0.8rem', width: '0.4rem' }} />*/}
626
+ </div>
627
+ <img className="drop-down-item-bottom-img" src={backgroundImg} />
628
+ </div>
629
+ </div>
630
+ ))}
631
+ </div>
632
+ </div>
615
633
  )}
616
634
  </div>
617
- {!isEmpty(dropDownList) && (
618
- <div className="cl-drop-down">
619
- <div className="drop-down-title">Hi,欢迎使用{window_title},您可以这样问我:</div>
620
- <div className="drop-down-list">
621
- {dropDownList.map(({ backgroundImg, title }) => (
622
- <div className="drop-down-item-card" key={title}>
623
- <div className="drop-down-item-title">“{title}”</div>
624
- <div className="drop-down-item-bottom">
625
- <div
626
- className="drop-down-item-bottom-button"
627
- onClick={() => {
628
- setDropDownList(undefined);
629
- handleClick(title);
630
- }}
631
- >
632
- 去提问&nbsp;
633
- <RightOutlined style={{ height: '0.8rem', width: '0.4rem' }} />
634
- </div>
635
- <img className="drop-down-item-bottom-img" src={backgroundImg} />
636
- </div>
637
- </div>
638
- ))}
639
- </div>
640
- </div>
641
- )}
642
- &nbsp;
643
- </div>
644
- <div
645
- className="cl-messages_container"
646
- // style={{ maxWidth: '100%', minHeight:'300px', height:'700px', paddingBottom: '56px' }}
647
- >
648
- {messages.map((message, index) => (
649
- <ChatMessage
650
- bot_message_style={bot_message_style}
651
- user_message_style={user_message_style}
652
- error_message_style={error_message_style}
653
- key={index}
654
- host_url={hostUrl}
655
- message={message}
656
- isSend={message.isSend}
657
- error={message.error}
658
- />
659
- ))}
660
- {sendingMessage && (nowAIContent?
661
- <ChatMessage
662
- bot_message_style={bot_message_style}
663
- user_message_style={user_message_style}
664
- error_message_style={error_message_style}
665
- key={1688574569}
666
- host_url={hostUrl}
667
- message={{ message: nowAIContent}}
668
- isSend={false}
669
- />:<ChatMessagePlaceholder bot_message_style={bot_message_style} />
670
- )}
671
- <div ref={lastMessage}></div>
672
- </div>
673
-
674
- <div style={input_container_style} className="cl-input_container">
675
- <div className="w_file_preview" ref={scrollContainerRef} onScroll={handleScroll}>
676
- <div className="w_toLeftBox" style={{ display: showLeftArrow ? 'flex' : 'none' }}>
677
- <div className="w_toLeft" onClick={() => handleScrollRight('left')}>
678
- <img src={toLeftPng} />
679
- </div>
680
- </div>
681
- <div className="w_toRightBox" style={{ display: showRightArrow ? 'flex' : 'none' }}>
682
- <div className="w_toRight" onClick={() => handleScrollRight('right')}>
683
- <img src={toRightPng} />
684
- </div>
685
- </div>
686
- {fileList.map((item, index) => {
687
- return (
688
- <div key={item.filePath} className="w_fileBox">
689
- {item.fileType == 'image' && (
690
- <Image
691
- height={40}
692
- width={40}
693
- src={item.fileUrl}
694
- className="w_upImg"
695
- preview={{
696
- mask: <span className="custom-mask"></span>,
697
- }}
698
- />
699
- )}
700
- {getFileTypeByUrl(item.file.name) == 'pdf' && (
701
- <img style={{ width: 40, height: 40 }} src={typePdfPng} />
702
- )}
703
- {getFileTypeByUrl(item.file.name) == 'word' && (
704
- <img style={{ width: 40, height: 40 }} src={typeWordPng} />
705
- )}
706
- {getFileTypeByUrl(item.file.name) == 'excel' && (
707
- <img style={{ width: 40, height: 40 }} src={typeExcelPng} />
708
- )}
709
- {getFileTypeByUrl(item.file.name) == 'markdown' && (
710
- <img style={{ width: 40, height: 40 }} src={typeMarkdownPng} />
711
- )}
712
- {getFileTypeByUrl(item.file.name) == 'txt' && (
713
- <img style={{ width: 40, height: 40 }} src={typeTextPng} />
714
- )}
715
- {getFileTypeByUrl(item.file.name) == 'mobi' && (
716
- <img style={{ width: 40, height: 40 }} src={typeMobiPng} />
717
- )}
718
- {getFileTypeByUrl(item.file.name) == 'rpub' && (
719
- <img style={{ width: 40, height: 40 }} src={typeRPubPng} />
720
- )}
721
- {getFileTypeByUrl(item.file.name) == 'file' && (
722
- <img style={{ width: 40, height: 40 }} src={upFilePng} />
723
- )}
724
- <div className="w_fileInfoBox">
725
- <div className="w_fileInfoFileName">{item.file.name}</div>
726
- <div className="w_fileInfoMeta">{`${item.fileType} · ${getFileSize(
727
- item.file.size,
728
- )}`}</div>
729
- </div>
730
- <div
731
- className="w_fileRemove"
635
+ <div
636
+ className="cl-messages_container"
637
+ // style={{ maxWidth: '100%', minHeight:'300px', height:'700px', paddingBottom: '56px' }}
638
+ >
639
+ {messages.map((message, index) => (
640
+ <ChatMessage
641
+ bot_message_style={bot_message_style}
642
+ user_message_style={user_message_style}
643
+ error_message_style={error_message_style}
644
+ key={index}
645
+ host_url={hostUrl}
646
+ message={message}
647
+ isSend={message.isSend}
648
+ error={message.error}
649
+ />
650
+ ))}
651
+ {sendingMessage && (nowAIContent?
652
+ <ChatMessage
653
+ bot_message_style={bot_message_style}
654
+ user_message_style={user_message_style}
655
+ error_message_style={error_message_style}
656
+ key={1688574569}
657
+ host_url={hostUrl}
658
+ message={{ message: nowAIContent}}
659
+ isSend={false}
660
+ />:<ChatMessagePlaceholder bot_message_style={bot_message_style} />
661
+ )}
662
+ <div ref={lastMessage}></div>
663
+ </div>
664
+ <div
665
+ className="w_tagListClass"
666
+ style={{
667
+ bottom: fileList.length > 0 ? '130px' : '80px',
668
+ display: tagList.length > 0 ? '' : 'none',
669
+ }}
670
+ >
671
+ {tagList.map((item, index) => (
672
+ <div
673
+ key={index}
674
+ className="w_tagItemBox"
732
675
  onClick={() => {
733
- removeFile(item, index);
676
+ handleClick(item?.name);
734
677
  }}
735
- >
736
- <img src={closexPng} />
678
+ >
679
+ <div>
680
+ <img
681
+ style={item?.img ? {} : { display: 'none' }}
682
+ src={'https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/'+ item?.img}
683
+ className="w_tagImgh"
684
+ alt="Image"
685
+ />
737
686
  </div>
687
+ <div className="w_tagItemText">{item?.name}</div>
738
688
  </div>
739
- );
740
- })}
689
+ ))}
741
690
  </div>
742
- <div className="w_inputBox">
691
+ <div style={input_container_style} className="cl-input_container">
692
+ <div className="w_file_preview" ref={scrollContainerRef} onScroll={handleScroll}>
693
+ <div className="w_toLeftBox" style={{ display: showLeftArrow ? 'flex' : 'none' }}>
694
+ <div className="w_toLeft" onClick={() => handleScrollRight('left')}>
695
+ <img src={toLeftPng} />
696
+ </div>
697
+ </div>
698
+ <div className="w_toRightBox" style={{ display: showRightArrow ? 'flex' : 'none' }}>
699
+ <div className="w_toRight" onClick={() => handleScrollRight('right')}>
700
+ <img src={toRightPng} />
701
+ </div>
702
+ </div>
703
+ {fileList.map((item, index) => {
704
+ return (
705
+ <div key={item.filePath} className="w_fileBox">
706
+ {item.fileType == 'image' && (
707
+ <Image
708
+ height={40}
709
+ width={40}
710
+ src={item.fileUrl}
711
+ className="w_upImg"
712
+ preview={{
713
+ mask: <span className="custom-mask"></span>,
714
+ }}
715
+ />
716
+ )}
717
+ {getFileTypeByUrl(item.file.name) == 'pdf' && (
718
+ <img style={{ width: 40, height: 40 }} src={typePdfPng} />
719
+ )}
720
+ {getFileTypeByUrl(item.file.name) == 'word' && (
721
+ <img style={{ width: 40, height: 40 }} src={typeWordPng} />
722
+ )}
723
+ {getFileTypeByUrl(item.file.name) == 'excel' && (
724
+ <img style={{ width: 40, height: 40 }} src={typeExcelPng} />
725
+ )}
726
+ {getFileTypeByUrl(item.file.name) == 'markdown' && (
727
+ <img style={{ width: 40, height: 40 }} src={typeMarkdownPng} />
728
+ )}
729
+ {getFileTypeByUrl(item.file.name) == 'txt' && (
730
+ <img style={{ width: 40, height: 40 }} src={typeTextPng} />
731
+ )}
732
+ {getFileTypeByUrl(item.file.name) == 'mobi' && (
733
+ <img style={{ width: 40, height: 40 }} src={typeMobiPng} />
734
+ )}
735
+ {getFileTypeByUrl(item.file.name) == 'rpub' && (
736
+ <img style={{ width: 40, height: 40 }} src={typeRPubPng} />
737
+ )}
738
+ {getFileTypeByUrl(item.file.name) == 'file' && (
739
+ <img style={{ width: 40, height: 40 }} src={upFilePng} />
740
+ )}
741
+ <div className="w_fileInfoBox">
742
+ <div className="w_fileInfoFileName">{item.file.name}</div>
743
+ <div className="w_fileInfoMeta">{`${item.fileType} · ${getFileSize(
744
+ item.file.size,
745
+ )}`}</div>
746
+ </div>
747
+ <div
748
+ className="w_fileRemove"
749
+ onClick={() => {
750
+ removeFile(item, index);
751
+ }}
752
+ >
753
+ <img src={closexPng} />
754
+ </div>
755
+ </div>
756
+ );
757
+ })}
758
+ </div>
759
+ <div className="w_inputBox">
743
760
  <textarea
744
- value={value}
745
- onChange={(e) => setValue(e.target.value)}
746
- onKeyDown={(e) => {
747
- if (e.key === 'Enter' && !e.shiftKey) handleClick();
748
- }}
749
- disabled={sendingMessage}
750
- placeholder={
751
- sendingMessage
752
- ? placeholder_sending || '思考中...'
753
- : placeholder || '请输入您的问题...'
754
- }
755
- style={{...input_style,resize:"none"}}
756
- ref={inputRef}
757
- className="cl-input-element"
761
+ value={value}
762
+ onChange={(e) => setValue(e.target.value)}
763
+ onKeyDown={(e) => {
764
+ if (e.key === 'Enter' && !e.shiftKey) handleClick();
765
+ }}
766
+ disabled={sendingMessage}
767
+ placeholder={
768
+ sendingMessage
769
+ ? placeholder_sending || '思考中...'
770
+ : placeholder || '请输入您的问题...'
771
+ }
772
+ style={{...input_style,resize:"none"}}
773
+ ref={inputRef}
774
+ className="cl-input-element"
758
775
  />
759
- <Tooltip title={recordState ? "点击结束录音" : "点击开始录音"}>
760
- <div
761
- className="w_send_voice_box"
762
- style={sendingMessage ? { cursor: 'not-allowed' } : {}}
763
- onClick={startRecord}
764
- >
765
- <img src={recordState ? soundWavePng : luyinPng} style={{ width: 23 }} className={recordState ? "w_recordIng" : ''}></img>
766
- </div>
767
- </Tooltip>
768
- {/*<Tooltip title="支持PDF / Word / Excel / Markdown / txt / mobi / rpub">*/}
769
- <Tooltip title="支持图片格式">
770
- <div
771
- className="w_send_file_box"
772
- style={sendingMessage ? { cursor: 'not-allowed' } : {}}
773
- onClick={uploadFile}
776
+ <Tooltip title={recordState ? "点击结束录音" : "点击开始录音"}>
777
+ <div
778
+ className="w_send_voice_box"
779
+ style={sendingMessage ? { cursor: 'not-allowed' } : {}}
780
+ onClick={startRecord}
781
+ >
782
+ <img src={recordState ? soundWavePng : luyinPng} style={{ width: 23 }} className={recordState ? "w_recordIng" : ''}></img>
783
+ </div>
784
+ </Tooltip>
785
+ {/*<Tooltip title="支持PDF / Word / Excel / Markdown / txt / mobi / rpub">*/}
786
+ <Tooltip title="支持图片格式">
787
+ <div
788
+ className="w_send_file_box"
789
+ style={sendingMessage ? { cursor: 'not-allowed' } : {}}
790
+ onClick={uploadFile}
791
+ >
792
+ <img src={fileuploadPng} style={{ width: 23 }}></img>
793
+ </div>
794
+ </Tooltip>
795
+ <button
796
+ disabled={sendingMessage}
797
+ style={{ ...(sendingMessage ? { cursor: 'not-allowed' } : {}), padding: '0 13px', background:'transparent', display:'flex', alignItems:'center', justifyContent:'center' }}
798
+ onClick={() => {
799
+ handleClick('');
800
+ }}
774
801
  >
775
- <img src={fileuploadPng} style={{ width: 23 }}></img>
776
- </div>
777
- </Tooltip>
778
- <button
779
- disabled={sendingMessage}
780
- style={{ ...(sendingMessage ? { cursor: 'not-allowed' } : {}), padding: '0 13px', background:'transparent', display:'flex', alignItems:'center', justifyContent:'center' }}
781
- onClick={() => {
782
- handleClick('');
783
- }}
784
- >
785
- <img src={sendmessagePng} style={{ width: 55 }}></img>
786
- </button>
802
+ <img src={sendmessagePng} style={{ width: 55 }}></img>
803
+ </button>
804
+ </div>
787
805
  </div>
788
806
  </div>
789
807
  </div>