@piedata/pieui 1.0.1 → 1.1.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.
Files changed (131) hide show
  1. package/dist/cli.js +11 -9
  2. package/dist/components/Buttons/AjaxButtonCard/ui/AjaxButtonCard.d.ts +2 -2
  3. package/dist/components/Buttons/AjaxButtonCard/ui/AjaxButtonCard.d.ts.map +1 -1
  4. package/dist/components/Chats/ChatCard/ui/components/Markdown.d.ts +0 -1
  5. package/dist/components/Chats/ChatCard/ui/components/Markdown.d.ts.map +1 -1
  6. package/dist/components/Containers/AjaxGroupCard/ui/AjaxGroupCard.d.ts +2 -2
  7. package/dist/components/Containers/AjaxGroupCard/ui/AjaxGroupCard.d.ts.map +1 -1
  8. package/dist/components/Containers/SequenceCard/ui/SequenceCard.d.ts.map +1 -1
  9. package/dist/components/Containers/UnionCard/ui/UnionCard.d.ts.map +1 -1
  10. package/dist/components/PieCard/index.d.ts.map +1 -1
  11. package/dist/components/PieRoot/index.d.ts +1 -2
  12. package/dist/components/PieRoot/index.d.ts.map +1 -1
  13. package/dist/components/PieRoot/types/index.d.ts +3 -0
  14. package/dist/components/PieRoot/types/index.d.ts.map +1 -1
  15. package/dist/components/PieTelegramRoot/index.d.ts.map +1 -1
  16. package/dist/components/UI/index.d.ts.map +1 -1
  17. package/dist/components/index.esm.js +14 -26
  18. package/dist/components/index.js +23 -36
  19. package/dist/index.d.ts +1 -3
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.esm.js +20 -32
  22. package/dist/index.js +33 -46
  23. package/dist/providers/CentrifugeIOInitProvider.d.ts.map +1 -1
  24. package/dist/providers/SocketIOInitProvider.d.ts.map +1 -1
  25. package/dist/types/index.d.ts +7 -0
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/util/ajaxCommonUtils.d.ts.map +1 -1
  28. package/dist/util/centrifuge.d.ts +1 -1
  29. package/dist/util/centrifuge.d.ts.map +1 -1
  30. package/dist/util/initializeComponents.d.ts.map +1 -1
  31. package/dist/util/pieConfig.d.ts +12 -0
  32. package/dist/util/pieConfig.d.ts.map +1 -0
  33. package/dist/util/registry.d.ts.map +1 -1
  34. package/dist/util/socket.d.ts +1 -1
  35. package/dist/util/socket.d.ts.map +1 -1
  36. package/dist/util/useIsSupported.d.ts +1 -1
  37. package/dist/util/useIsSupported.d.ts.map +1 -1
  38. package/dist/util/useWebApp.d.ts +2 -2
  39. package/dist/util/useWebApp.d.ts.map +1 -1
  40. package/dist/util/waitForSidAvailable.d.ts +1 -2
  41. package/dist/util/waitForSidAvailable.d.ts.map +1 -1
  42. package/dist/util/webrtcClient.d.ts.map +1 -1
  43. package/package.json +21 -18
  44. package/src/components/Buttons/AjaxButtonCard/index.ts +1 -0
  45. package/src/components/Buttons/AjaxButtonCard/types/index.ts +17 -0
  46. package/src/components/Buttons/AjaxButtonCard/ui/AjaxButtonCard.tsx +38 -0
  47. package/src/components/Chats/ChatCard/index.ts +1 -0
  48. package/src/components/Chats/ChatCard/types/annyang.d.ts +11 -0
  49. package/src/components/Chats/ChatCard/types/index.ts +59 -0
  50. package/src/components/Chats/ChatCard/ui/ChatCard.tsx +130 -0
  51. package/src/components/Chats/ChatCard/ui/components/AttachFileButton.tsx +46 -0
  52. package/src/components/Chats/ChatCard/ui/components/AttachedFileView.tsx +29 -0
  53. package/src/components/Chats/ChatCard/ui/components/ChatCardInput.tsx +177 -0
  54. package/src/components/Chats/ChatCard/ui/components/ChatOption.tsx +25 -0
  55. package/src/components/Chats/ChatCard/ui/components/Markdown.tsx +25 -0
  56. package/src/components/Chats/ChatCard/ui/components/MessageAvatar.tsx +17 -0
  57. package/src/components/Chats/ChatCard/ui/components/MessageCard.tsx +80 -0
  58. package/src/components/Chats/ChatCard/ui/components/MessageContent.tsx +27 -0
  59. package/src/components/Chats/ChatCard/ui/components/MessagesBoard.tsx +61 -0
  60. package/src/components/Chats/ChatCard/ui/components/Options.tsx +20 -0
  61. package/src/components/Chats/ChatCard/ui/components/ResizableTextarea.tsx +59 -0
  62. package/src/components/Chats/ChatCard/ui/components/SendButton.tsx +37 -0
  63. package/src/components/Chats/ChatCard/ui/components/VoiceListeningButton.tsx +35 -0
  64. package/src/components/Chats/ChatCard/ui/components/icons/AttachFileIcon.tsx +18 -0
  65. package/src/components/Chats/ChatCard/ui/components/icons/AttachedFileIcon.tsx +18 -0
  66. package/src/components/Chats/ChatCard/ui/components/icons/CancelFileIcon.tsx +14 -0
  67. package/src/components/Chats/ChatCard/ui/components/icons/DefaultAvatar.tsx +10 -0
  68. package/src/components/Chats/ChatCard/ui/components/icons/SendIcon.tsx +18 -0
  69. package/src/components/Chats/ChatCard/ui/components/icons/VoiceRecordIcon.tsx +15 -0
  70. package/src/components/Containers/AjaxGroupCard/index.ts +1 -0
  71. package/src/components/Containers/AjaxGroupCard/types/index.ts +17 -0
  72. package/src/components/Containers/AjaxGroupCard/ui/AjaxGroupCard.tsx +96 -0
  73. package/src/components/Containers/SequenceCard/index.ts +1 -0
  74. package/src/components/Containers/SequenceCard/types/index.ts +10 -0
  75. package/src/components/Containers/SequenceCard/ui/SequenceCard.tsx +32 -0
  76. package/src/components/Containers/UnionCard/index.ts +1 -0
  77. package/src/components/Containers/UnionCard/types/index.ts +8 -0
  78. package/src/components/Containers/UnionCard/ui/UnionCard.tsx +27 -0
  79. package/src/components/PieCard/index.tsx +149 -0
  80. package/src/components/PieCard/types/index.ts +18 -0
  81. package/src/components/PieRoot/index.tsx +154 -0
  82. package/src/components/PieRoot/types/index.ts +14 -0
  83. package/src/components/PieTelegramRoot/index.tsx +161 -0
  84. package/src/components/UI/index.tsx +70 -0
  85. package/src/components/index.ts +6 -0
  86. package/src/index.ts +15 -0
  87. package/src/providers/CentrifugeIOInitProvider.tsx +42 -0
  88. package/src/providers/SocketIOInitProvider.tsx +52 -0
  89. package/src/types/index.ts +139 -0
  90. package/src/util/ajaxCommonUtils.ts +137 -0
  91. package/src/util/centrifuge.ts +33 -0
  92. package/src/util/fallback.tsx +6 -0
  93. package/src/util/initializeComponents.ts +84 -0
  94. package/src/util/lazy.ts +25 -0
  95. package/src/util/mitt.ts +11 -0
  96. package/src/util/pieConfig.ts +43 -0
  97. package/src/util/registry.ts +81 -0
  98. package/src/util/socket.ts +24 -0
  99. package/src/util/sx2radium.ts +15 -0
  100. package/src/util/tailwindCommonUtils.ts +6 -0
  101. package/src/util/useIsSupported.ts +17 -0
  102. package/src/util/useOpenAIWebRTC.ts +176 -0
  103. package/src/util/useWebApp.ts +32 -0
  104. package/src/util/waitForSidAvailable.ts +21 -0
  105. package/src/util/webrtcClient.ts +247 -0
  106. package/dist/cli.d.ts +0 -3
  107. package/dist/cli.d.ts.map +0 -1
  108. package/dist/components/PieBaseRoot/index.d.ts +0 -5
  109. package/dist/components/PieBaseRoot/index.d.ts.map +0 -1
  110. package/dist/components/PieBaseRoot/types/index.d.ts +0 -6
  111. package/dist/components/PieBaseRoot/types/index.d.ts.map +0 -1
  112. package/dist/components/PieStaticRoot/index.d.ts +0 -5
  113. package/dist/components/PieStaticRoot/index.d.ts.map +0 -1
  114. package/dist/components/PieStaticRoot/types/index.d.ts +0 -7
  115. package/dist/components/PieStaticRoot/types/index.d.ts.map +0 -1
  116. package/dist/config/constant.d.ts +0 -10
  117. package/dist/config/constant.d.ts.map +0 -1
  118. package/dist/util/axiosWithCache.d.ts +0 -3
  119. package/dist/util/axiosWithCache.d.ts.map +0 -1
  120. package/dist/util/globalForm.d.ts +0 -3
  121. package/dist/util/globalForm.d.ts.map +0 -1
  122. package/dist/util/queryClient.d.ts +0 -3
  123. package/dist/util/queryClient.d.ts.map +0 -1
  124. package/dist/util/useBodyStyles.d.ts +0 -5
  125. package/dist/util/useBodyStyles.d.ts.map +0 -1
  126. package/dist/util/useImage.d.ts +0 -5
  127. package/dist/util/useImage.d.ts.map +0 -1
  128. package/dist/util/useScreenSize.d.ts +0 -6
  129. package/dist/util/useScreenSize.d.ts.map +0 -1
  130. package/dist/util/useTitle.d.ts +0 -2
  131. package/dist/util/useTitle.d.ts.map +0 -1
@@ -0,0 +1,177 @@
1
+ import { CSSProperties, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
2
+ import ResizableTextarea from './ResizableTextarea'
3
+ // import annyang from 'annyang'
4
+ // import '../../types/annyang.d.ts'
5
+ import SendButton from './SendButton'
6
+ import { Option } from '../../types'
7
+ import AttachFileButton from './AttachFileButton'
8
+ import AttachedFileView from './AttachedFileView'
9
+ import VoiceListeningButton from './VoiceListeningButton'
10
+ import Options from './Options'
11
+
12
+ export interface ChatCardInputHandle {
13
+ clear: () => void
14
+ setValue: (newValue: string) => void
15
+ setOptions: (newOptions: Option[]) => void
16
+ }
17
+
18
+ const ChatCardInput = forwardRef<
19
+ ChatCardInputHandle,
20
+ {
21
+ name: string
22
+ defaultOptions: Option[]
23
+ defaultValue: string
24
+ isArea: boolean
25
+ placeholder: string
26
+ fileAccept: string
27
+ optionsPosition: 'top' | 'bottom'
28
+ icons: Record<'voiceRecordingIcon' | 'sendIcon' | 'cancelIcon' | 'attachFileIcon', string>
29
+ handleOptionClick: (option: string) => void
30
+ handleSendMessage: () => void
31
+ sx: CSSProperties
32
+ }
33
+ >(
34
+ (
35
+ {
36
+ name,
37
+ defaultValue,
38
+ defaultOptions,
39
+ isArea,
40
+ placeholder,
41
+ fileAccept,
42
+ optionsPosition,
43
+ icons,
44
+ handleOptionClick,
45
+ handleSendMessage,
46
+ sx,
47
+ },
48
+ ref,
49
+ ) => {
50
+ const fileInputRef = useRef<HTMLInputElement>(null!)
51
+ const [selectedFile, setSelectedFile] = useState<File | null>(null)
52
+ const [value, setValue] = useState<string>(defaultValue)
53
+ const [options, setOptions] = useState<Option[]>(defaultOptions)
54
+ const [isListening, setIsListening] = useState(false)
55
+
56
+ useImperativeHandle(ref, () => ({
57
+ clear: () => {
58
+ setValue('')
59
+ setSelectedFile(null)
60
+ if (fileInputRef.current) {
61
+ fileInputRef.current.value = ''
62
+ }
63
+ },
64
+ setValue: (newValue: string) => setValue(newValue),
65
+ setOptions: (newOptions: Array<Option>) => setOptions(newOptions),
66
+ }))
67
+
68
+ useEffect(() => {
69
+ setValue(defaultValue)
70
+ }, [defaultValue])
71
+
72
+ // useEffect(() => {
73
+ // if (annyang) {
74
+ // annyang.addCallback('result', (phrases: string[]) => {
75
+ // setValue(phrases[0])
76
+ // })
77
+ // return () => {
78
+ // annyang.abort()
79
+ // annyang.removeCallback()
80
+ // }
81
+ // }
82
+ // }, [])
83
+
84
+ const toggleListening = () => {
85
+ // if (!annyang) return
86
+ // if (isListening) {
87
+ // annyang.abort()
88
+ // setIsListening(false)
89
+ // handleSendMessage()
90
+ // } else {
91
+ // annyang.start({ autoRestart: true, continuous: true })
92
+ // setValue('')
93
+ // setIsListening(true)
94
+ // }
95
+ }
96
+
97
+ return (
98
+ <div
99
+ className='flex flex-col items-center gap-[0.1rem]'
100
+ id={name + '_chat_input'}
101
+ style={sx}
102
+ >
103
+ {options && optionsPosition === 'top' && (
104
+ <Options options={options} handleOptionClick={handleOptionClick} />
105
+ )}
106
+ <div className='stretch relative flex size-full flex-1 flex-row items-stretch gap-3 last:mb-2 md:mx-4 md:flex-col md:last:mb-6 lg:mx-auto'>
107
+ <div className='flex w-full grow flex-row items-center rounded-md bg-transparent'>
108
+ {selectedFile ? (
109
+ <AttachedFileView
110
+ name={name}
111
+ selectedFile={selectedFile}
112
+ onDropFile={() => {
113
+ setSelectedFile(null)
114
+ if (fileInputRef.current) fileInputRef.current.value = ''
115
+ }}
116
+ />
117
+ ) : !isArea ? (
118
+ <input
119
+ // ref={fileInputRef as any}
120
+ name={name}
121
+ value={value}
122
+ onChange={(e) => setValue(e.target.value)}
123
+ onKeyDown={(e) => {
124
+ if (e.key === 'Enter') {
125
+ e.preventDefault()
126
+ handleSendMessage()
127
+ }
128
+ }}
129
+ tabIndex={0}
130
+ placeholder={placeholder}
131
+ className='m-0 w-full resize-none border-0 bg-transparent outline-none'
132
+ style={{ maxHeight: 200, height: '100%', overflowY: 'hidden' }}
133
+ />
134
+ ) : (
135
+ <ResizableTextarea
136
+ // ref={fileInputRef as any}
137
+ name={name}
138
+ value={value}
139
+ onChange={(e) => setValue(e.target.value)}
140
+ onKeyDown={(e) => {
141
+ if (e.key === 'Enter' && !e.shiftKey) {
142
+ e.preventDefault()
143
+ handleSendMessage()
144
+ }
145
+ }}
146
+ tabIndex={0}
147
+ rows={2}
148
+ placeholder={placeholder}
149
+ className='m-0 w-full resize-none border-0 bg-transparent p-0 pl-2 pr-7 outline-none md:pl-0'
150
+ style={{ maxHeight: 200, height: '100%', minHeight: 24 }}
151
+ />
152
+ )}
153
+
154
+ <VoiceListeningButton
155
+ isListening={isListening}
156
+ toggleListening={toggleListening}
157
+ icons={icons}
158
+ />
159
+ <AttachFileButton
160
+ name={name}
161
+ fileInputRef={fileInputRef}
162
+ accept={fileAccept}
163
+ onSelectFile={setSelectedFile}
164
+ icons={icons}
165
+ />
166
+ <SendButton onClick={handleSendMessage} icons={icons} />
167
+ </div>
168
+ </div>
169
+ {options && optionsPosition === 'bottom' && (
170
+ <Options options={options} handleOptionClick={handleOptionClick} />
171
+ )}
172
+ </div>
173
+ )
174
+ },
175
+ )
176
+
177
+ export default ChatCardInput
@@ -0,0 +1,25 @@
1
+ import { Option } from '../../types'
2
+
3
+ const ChatOption = ({
4
+ option,
5
+ onClickOption,
6
+ }: {
7
+ option: Option
8
+ onClickOption: (title: string) => void
9
+ }) => {
10
+ return (
11
+ <div
12
+ className='flex w-fit cursor-pointer flex-row place-content-center items-center gap-1 rounded-md border border-black bg-white px-2 py-1 text-black'
13
+ onClick={() => {
14
+ onClickOption(option.title)
15
+ }}
16
+ style={option.sx}
17
+ >
18
+ {option.iconPosition === 'start' && <img src={option.iconUrl} alt=''></img>}
19
+ {option.title}
20
+ {option.iconPosition === 'end' && <img src={option.iconUrl} alt=''></img>}
21
+ </div>
22
+ )
23
+ }
24
+
25
+ export default ChatOption
@@ -0,0 +1,25 @@
1
+ // import ReactMarkdown from 'react-markdown'
2
+ // import rehypeHighlight from 'rehype-highlight'
3
+ // import 'highlight.js/styles/github.css'
4
+ import { useEffect, useState } from 'react'
5
+
6
+ function MarkdownRender({ children }: { children: string }) {
7
+ const [content, setContent] = useState<string>(children)
8
+
9
+ useEffect(() => {
10
+ setContent(children)
11
+ }, [children])
12
+
13
+ return (
14
+ <div className='max-w-full first:mt-0'>
15
+ {/*<ReactMarkdown*/}
16
+ {/* // rehypePlugins={[rehypeHighlight]}*/}
17
+ {/*>*/}
18
+ {/* {content}*/}
19
+ {/*</ReactMarkdown>*/}
20
+ {content}
21
+ </div>
22
+ )
23
+ }
24
+
25
+ export default MarkdownRender
@@ -0,0 +1,17 @@
1
+ import DefaultAvatar from './icons/DefaultAvatar'
2
+
3
+ function MessageAvatar({ username, avatar }: { username: string; avatar: string | null }) {
4
+ return (
5
+ <div className='w-[30px]'>
6
+ <div className='relative flex size-[30px] items-center justify-center rounded-sm p-1 text-white'>
7
+ {avatar ? (
8
+ <img src={avatar} className='absolute inset-0 rounded' alt={username} />
9
+ ) : (
10
+ <DefaultAvatar />
11
+ )}
12
+ </div>
13
+ </div>
14
+ )
15
+ }
16
+
17
+ export default MessageAvatar
@@ -0,0 +1,80 @@
1
+ import parse from 'html-react-parser'
2
+ import { useState, useEffect } from 'react'
3
+ import MarkdownRender from './Markdown'
4
+ import UI from '../../../../UI'
5
+ import MessageAvatar from './MessageAvatar'
6
+ import ChatOption from './ChatOption'
7
+ import { Message } from '../../types'
8
+ import { SetUiAjaxConfigurationType } from '../../../../../types'
9
+
10
+ const MessageCard = ({
11
+ message,
12
+ handleOptionClick,
13
+ setUiAjaxConfiguration,
14
+ }: {
15
+ message: Message
16
+ handleOptionClick: (option: string) => void
17
+ setUiAjaxConfiguration?: SetUiAjaxConfigurationType
18
+ }) => {
19
+ const [copied, setCopied] = useState(false)
20
+
21
+ useEffect(() => {
22
+ const timeout = setTimeout(() => {
23
+ if (copied) setCopied(false)
24
+ }, 1000)
25
+
26
+ return () => clearTimeout(timeout)
27
+ }, [copied])
28
+
29
+ return (
30
+ <div className='group w-full border-b border-black/10' id={message.id}>
31
+ <div
32
+ className={`flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-3xl xl:max-w-5xl ${message.align === 'center' ? 'm-auto' : ''} ${message.align === 'right' ? 'ml-auto justify-end' : ''} ${message.align === 'left' ? 'mr-auto' : ''} `}
33
+ >
34
+ {(message.align === 'left' || message.align === 'center') && (
35
+ <div className='relative flex shrink-0 flex-col items-end'>
36
+ <MessageAvatar username={message.username} avatar={message.avatar} />
37
+ </div>
38
+ )}
39
+ <div className='relative flex w-[calc(100%-50px)] flex-col gap-1 md:gap-3 lg:w-[calc(100%-115px)]'>
40
+ <div
41
+ className={`markdown light prose w-full break-words dark:prose-invert first:mt-0 ${message.align === 'right' ? 'flex justify-end self-end' : ''}`}
42
+ >
43
+ {typeof message.content === 'string' ? (
44
+ message.parseMode.toLowerCase() === 'markdown' ? (
45
+ <MarkdownRender key={Date.now() + Math.random()}>
46
+ {message.content}
47
+ </MarkdownRender>
48
+ ) : message.parseMode.toLowerCase() === 'html' ? (
49
+ parse(message.content)
50
+ ) : (
51
+ message.content
52
+ )
53
+ ) : (
54
+ <UI
55
+ uiConfig={message.content}
56
+ setUiAjaxConfiguration={setUiAjaxConfiguration}
57
+ />
58
+ )}
59
+ </div>
60
+ <div className='flex flex-row flex-wrap justify-start gap-1'>
61
+ {message.options.map((option, idx) => (
62
+ <ChatOption
63
+ key={idx}
64
+ onClickOption={handleOptionClick}
65
+ option={option}
66
+ />
67
+ ))}
68
+ </div>
69
+ </div>
70
+ {message.align === 'right' && (
71
+ <div className='relative flex shrink-0 flex-col items-end'>
72
+ <MessageAvatar username={message.username} avatar={message.avatar} />
73
+ </div>
74
+ )}
75
+ </div>
76
+ </div>
77
+ )
78
+ }
79
+
80
+ export default MessageCard
@@ -0,0 +1,27 @@
1
+ import clsx from 'clsx'
2
+
3
+ const MessageContent = ({
4
+ name,
5
+ content,
6
+ isSent,
7
+ }: {
8
+ name: string
9
+ content: string
10
+ isSent: boolean
11
+ }) => {
12
+ return (
13
+ <div className='px-6 py-4'>
14
+ <div
15
+ className={clsx(
16
+ 'text-xl font-medium text-black',
17
+ isSent ? 'text-right' : 'text-left',
18
+ )}
19
+ >
20
+ {isSent ? 'You' : name}
21
+ </div>
22
+ <p className='text-gray-500'>{content}</p>
23
+ </div>
24
+ )
25
+ }
26
+
27
+ export default MessageContent
@@ -0,0 +1,61 @@
1
+ import MessageCard from './MessageCard'
2
+ import { CSSProperties, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
3
+ import { Message } from '../../types'
4
+ import { SetUiAjaxConfigurationType } from '../../../../../types'
5
+
6
+ export interface MessagesBoardHandle {
7
+ addMessage: (message: Message) => void
8
+ setMessages: (messages: Array<Message>) => void
9
+ scrollToBottom: () => void
10
+ }
11
+
12
+ const MessagesBoard = forwardRef<
13
+ MessagesBoardHandle,
14
+ {
15
+ name: string
16
+ handleOptionClick: (option: string) => void
17
+ defaultMessages: Message[]
18
+ sx: CSSProperties
19
+ setUiAjaxConfiguration?: SetUiAjaxConfigurationType
20
+ }
21
+ >(({ name, handleOptionClick, defaultMessages, sx, setUiAjaxConfiguration }, ref) => {
22
+ const [messages, setMessages] = useState<Message[]>(defaultMessages)
23
+ const containerRef = useRef<HTMLDivElement>(null)
24
+
25
+ const scrollToBottom = () => {
26
+ if (containerRef.current) {
27
+ containerRef.current.scrollTop = containerRef.current.scrollHeight
28
+ }
29
+ }
30
+
31
+ useEffect(() => {
32
+ scrollToBottom()
33
+ }, [messages])
34
+
35
+ useImperativeHandle(ref, () => ({
36
+ setMessages: (currentMessages: Array<Message>) => setMessages(currentMessages),
37
+ addMessage: (message: Message) =>
38
+ setMessages((currentMessages) => [...currentMessages, message]),
39
+ scrollToBottom: scrollToBottom,
40
+ }))
41
+
42
+ return (
43
+ <div
44
+ id={name + '_messages'}
45
+ className='flex flex-col items-center overflow-y-scroll'
46
+ style={sx}
47
+ ref={containerRef}
48
+ >
49
+ {messages.map((message) => (
50
+ <MessageCard
51
+ key={message.id}
52
+ message={message}
53
+ handleOptionClick={handleOptionClick}
54
+ setUiAjaxConfiguration={setUiAjaxConfiguration}
55
+ />
56
+ ))}
57
+ </div>
58
+ )
59
+ })
60
+
61
+ export default MessagesBoard
@@ -0,0 +1,20 @@
1
+ import { Option } from '../../types'
2
+ import ChatOption from './ChatOption'
3
+
4
+ const Options = ({
5
+ options,
6
+ handleOptionClick,
7
+ }: {
8
+ options: Option[]
9
+ handleOptionClick: (option: string) => void
10
+ }) => {
11
+ return (
12
+ <div className='flex w-full flex-row flex-wrap justify-start gap-[5px]'>
13
+ {options.map((option: Option, idx: number) => {
14
+ return <ChatOption key={idx} option={option} onClickOption={handleOptionClick} />
15
+ })}
16
+ </div>
17
+ )
18
+ }
19
+
20
+ export default Options
@@ -0,0 +1,59 @@
1
+ import {
2
+ DetailedHTMLProps,
3
+ FocusEventHandler,
4
+ KeyboardEventHandler,
5
+ TextareaHTMLAttributes,
6
+ useRef,
7
+ useState,
8
+ } from 'react'
9
+
10
+ export default function ResizableTextarea(
11
+ props: DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>,
12
+ ) {
13
+ const [canResize, setCanResize] = useState<boolean>(false)
14
+ const textareaRef = useRef<HTMLTextAreaElement | null>(null)
15
+
16
+ const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
17
+ if (e.key === 'Enter' && e.shiftKey) {
18
+ setCanResize(true)
19
+ if (textareaRef.current && typeof window !== 'undefined') {
20
+ const style = window.getComputedStyle(textareaRef.current)
21
+ const lineHeight = parseFloat(style.lineHeight)
22
+ textareaRef.current.style.height =
23
+ textareaRef.current.scrollHeight + lineHeight + 'px'
24
+ }
25
+ }
26
+ if (e.key === 'Backspace') {
27
+ setCanResize(true)
28
+ if (textareaRef.current && typeof window !== 'undefined') {
29
+ const style = window.getComputedStyle(textareaRef.current)
30
+ const lineHeight = parseFloat(style.lineHeight)
31
+ if (textareaRef.current.scrollHeight > lineHeight) {
32
+ textareaRef.current.style.height =
33
+ textareaRef.current.scrollHeight - lineHeight + 'px'
34
+ }
35
+ }
36
+ }
37
+ props.onKeyDown && props.onKeyDown(e)
38
+ }
39
+
40
+ const handleKeyUp: KeyboardEventHandler<HTMLTextAreaElement> = () => {
41
+ setCanResize(false)
42
+ }
43
+
44
+ const handleBlur: FocusEventHandler<HTMLTextAreaElement> = (e) => {
45
+ setCanResize(false)
46
+ props.onBlur && props.onBlur(e)
47
+ }
48
+
49
+ return (
50
+ <textarea
51
+ ref={textareaRef}
52
+ {...props}
53
+ onKeyUp={handleKeyUp}
54
+ onKeyDown={handleKeyDown}
55
+ onBlur={handleBlur}
56
+ style={{ resize: canResize ? 'vertical' : 'none', overflowY: 'auto', ...props.style }}
57
+ />
58
+ )
59
+ }
@@ -0,0 +1,37 @@
1
+ import { useRef, useCallback, MouseEventHandler } from 'react'
2
+ import SendIcon from './icons/SendIcon'
3
+ import { ChatButtonProps } from '../../types'
4
+
5
+
6
+ const SendButton = ({ type = 'button', onClick, icons }: ChatButtonProps) => {
7
+ const buttonRef = useRef<HTMLButtonElement | null>(null)
8
+
9
+ const animateSendButton = useCallback(() => {
10
+ const btn = buttonRef.current
11
+ if (btn) {
12
+ btn.style.transform = 'scale(0.8)'
13
+ setTimeout(() => {
14
+ btn.style.transform = 'scale(1)'
15
+ }, 600)
16
+ }
17
+ }, [])
18
+
19
+ const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
20
+ if (onClick) onClick(e)
21
+ animateSendButton()
22
+ }
23
+
24
+ return (
25
+ <button
26
+ ref={buttonRef}
27
+ type={type}
28
+ onClick={handleClick}
29
+ className='mr-1.5 rounded-md p-1 text-gray-500 ring-0 hover:bg-gray-100 disabled:opacity-40 disabled:hover:bg-transparent'
30
+ style={{ transition: 'transform 300ms ease' }}
31
+ >
32
+ {icons.sendIcon ? <img src={icons.sendIcon} alt='' /> : <SendIcon />}
33
+ </button>
34
+ )
35
+ }
36
+
37
+ export default SendButton
@@ -0,0 +1,35 @@
1
+ import CancelFileIcon from './icons/CancelFileIcon'
2
+ import VoiceRecordIcon from './icons/VoiceRecordIcon'
3
+ import { ChatIconsType } from '../../types'
4
+
5
+ const VoiceListeningButton = ({
6
+ isListening,
7
+ toggleListening,
8
+ icons,
9
+ }: {
10
+ toggleListening: () => void
11
+ isListening: boolean
12
+ icons: ChatIconsType
13
+ }) => {
14
+ return (
15
+ <button
16
+ className='rounded-md p-1 text-gray-500 ring-0 hover:bg-gray-100 disabled:opacity-40 disabled:hover:bg-transparent'
17
+ type='button'
18
+ onClick={toggleListening}
19
+ >
20
+ {isListening ? (
21
+ icons.cancelIcon ? (
22
+ <img src={icons.cancelIcon} alt='' />
23
+ ) : (
24
+ <CancelFileIcon />
25
+ )
26
+ ) : icons.voiceRecordingIcon ? (
27
+ <img src={icons.voiceRecordingIcon} alt='' />
28
+ ) : (
29
+ <VoiceRecordIcon />
30
+ )}
31
+ </button>
32
+ )
33
+ }
34
+
35
+ export default VoiceListeningButton
@@ -0,0 +1,18 @@
1
+ const AttachFileIcon = () => (
2
+ <svg
3
+ xmlns='http://www.w3.org/2000/svg'
4
+ fill='none'
5
+ viewBox='0 0 24 24'
6
+ strokeWidth='1.5'
7
+ stroke='currentColor'
8
+ className='size-5'
9
+ >
10
+ <path
11
+ strokeLinecap='round'
12
+ strokeLinejoin='round'
13
+ d='M18.375 12.739l-7.693 7.693a4.5 4.5 0 01-6.364-6.364l10.94-10.94A3 3 0 1119.5 7.372L8.552 18.32m.009-.01l-.01.01m5.699-9.941l-7.81 7.81a1.5 1.5 0 002.112 2.13'
14
+ />
15
+ </svg>
16
+ )
17
+
18
+ export default AttachFileIcon
@@ -0,0 +1,18 @@
1
+ const AttachedFileIcon = () => (
2
+ <svg
3
+ xmlns='http://www.w3.org/2000/svg'
4
+ fill='none'
5
+ viewBox='0 0 24 24'
6
+ strokeWidth='1.5'
7
+ stroke='currentColor'
8
+ className='size-5 text-gray-500'
9
+ >
10
+ <path
11
+ strokeLinecap='round'
12
+ strokeLinejoin='round'
13
+ d='M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z'
14
+ />
15
+ </svg>
16
+ )
17
+
18
+ export default AttachedFileIcon
@@ -0,0 +1,14 @@
1
+ const CancelFileIcon = () => (
2
+ <svg
3
+ xmlns='http://www.w3.org/2000/svg'
4
+ fill='none'
5
+ viewBox='0 0 24 24'
6
+ strokeWidth='1.5'
7
+ stroke='currentColor'
8
+ className='size-5'
9
+ >
10
+ <path strokeLinecap='round' strokeLinejoin='round' d='M6 18L18 6M6 6l12 12' />
11
+ </svg>
12
+ )
13
+
14
+ export default CancelFileIcon
@@ -0,0 +1,10 @@
1
+ const DefaultAvatarIcon = () => (
2
+ <svg width='30' height='30' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'>
3
+ <path
4
+ d='m 8 1 c -1.65625 0 -3 1.34375 -3 3 s 1.34375 3 3 3 s 3 -1.34375 3 -3 s -1.34375 -3 -3 -3 z m -1.5 7 c -2.492188 0 -4.5 2.007812 -4.5 4.5 v 0.5 c 0 1.109375 0.890625 2 2 2 h 8 c 1.109375 0 2 -0.890625 2 -2 v -0.5 c 0 -2.492188 -2.007812 -4.5 -4.5 -4.5 z m 0 0'
5
+ fill='#2e3436'
6
+ />
7
+ </svg>
8
+ )
9
+
10
+ export default DefaultAvatarIcon
@@ -0,0 +1,18 @@
1
+ const SendIcon = () => (
2
+ <svg
3
+ xmlns='http://www.w3.org/2000/svg'
4
+ fill='none'
5
+ viewBox='0 0 24 24'
6
+ strokeWidth='1.5'
7
+ stroke='currentColor'
8
+ className='size-5'
9
+ >
10
+ <path
11
+ strokeLinecap='round'
12
+ strokeLinejoin='round'
13
+ d='M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5'
14
+ />
15
+ </svg>
16
+ )
17
+
18
+ export default SendIcon
@@ -0,0 +1,15 @@
1
+ const VoiceRecordIcon = () => (
2
+ <svg
3
+ xmlns='http://www.w3.org/2000/svg'
4
+ fill='none'
5
+ viewBox='0 0 24 24'
6
+ strokeWidth='1.5'
7
+ stroke='currentColor'
8
+ className='size-5'
9
+ >
10
+ <path d='M8 5C8 2.79086 9.79086 1 12 1C14.2091 1 16 2.79086 16 5V12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12V5Z' />
11
+ <path d='M6.25 11.8438V12C6.25 13.525 6.8558 14.9875 7.93414 16.0659C9.01247 17.1442 10.475 17.75 12 17.75C13.525 17.75 14.9875 17.1442 16.0659 16.0659C17.1442 14.9875 17.75 13.525 17.75 12V11.8438C17.75 11.2915 18.1977 10.8438 18.75 10.8438H19.25C19.8023 10.8438 20.25 11.2915 20.25 11.8437V12C20.25 14.188 19.3808 16.2865 17.8336 17.8336C16.5842 19.0831 14.9753 19.8903 13.25 20.1548V22C13.25 22.5523 12.8023 23 12.25 23H11.75C11.1977 23 10.75 22.5523 10.75 22V20.1548C9.02471 19.8903 7.41579 19.0831 6.16637 17.8336C4.61919 16.2865 3.75 14.188 3.75 12V11.8438C3.75 11.2915 4.19772 10.8438 4.75 10.8438H5.25C5.80228 10.8438 6.25 11.2915 6.25 11.8438Z' />
12
+ </svg>
13
+ )
14
+
15
+ export default VoiceRecordIcon