@aslaluroba/help-center-react 2.0.4 → 2.0.6

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 (38) hide show
  1. package/dist/core/api.d.ts +4 -1
  2. package/dist/index.d.ts +3 -2
  3. package/dist/index.esm.js +994 -25294
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +995 -25295
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/config.d.ts +1 -1
  8. package/dist/lib/types.d.ts +4 -0
  9. package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +1 -0
  10. package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +1 -1
  11. package/dist/ui/help-center.d.ts +1 -1
  12. package/dist/ui/help-popup.d.ts +9 -3
  13. package/dist/ui/review-dialog/index.d.ts +8 -0
  14. package/dist/ui/review-dialog/rating.d.ts +12 -0
  15. package/package.json +26 -5
  16. package/src/assets/icons/arrowRight.svg +1 -1
  17. package/src/assets/icons/closeCircle.svg +1 -1
  18. package/src/components/ui/agent-response/agent-response.tsx +36 -34
  19. package/src/components/ui/header.tsx +2 -3
  20. package/src/core/SignalRService.ts +25 -25
  21. package/src/core/api.ts +180 -44
  22. package/src/globals.css +0 -9
  23. package/src/index.ts +3 -2
  24. package/src/lib/config.ts +25 -25
  25. package/src/lib/types.ts +5 -0
  26. package/src/locales/ar.json +18 -1
  27. package/src/locales/en.json +26 -8
  28. package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +31 -33
  29. package/src/ui/chatbot-popup/chat-window-screen/header.tsx +47 -53
  30. package/src/ui/chatbot-popup/chat-window-screen/index.tsx +182 -88
  31. package/src/ui/chatbot-popup/options-list-screen/header.tsx +24 -20
  32. package/src/ui/chatbot-popup/options-list-screen/index.tsx +24 -24
  33. package/src/ui/chatbot-popup/options-list-screen/option-card.tsx +9 -4
  34. package/src/ui/help-center.tsx +189 -159
  35. package/src/ui/help-popup.tsx +241 -165
  36. package/src/ui/review-dialog/index.tsx +106 -0
  37. package/src/ui/review-dialog/rating.tsx +78 -0
  38. package/tsconfig.json +48 -0
@@ -1,103 +1,197 @@
1
- import AgentResponse from '@/components/ui/agent-response/agent-response'
2
- import { Message } from '@/lib/types'
3
- import ChatWindowFooter from '@/ui/chatbot-popup/chat-window-screen/footer'
4
- import React, { useEffect, useRef, useState } from 'react'
5
- import LoadingGif from './../../../assets/animatedLogo.gif'
6
- import Seperator from './../../../assets/icons/seperator.svg'
7
- import LogoIcon from './../../../assets/logo.svg'
1
+ import AgentResponse from '@/components/ui/agent-response/agent-response';
2
+ import { Message } from '@/lib/types';
3
+ import ChatWindowFooter from '@/ui/chatbot-popup/chat-window-screen/footer';
4
+ import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
5
+ import LoadingGif from './../../../assets/animatedLogo.gif';
6
+ import Seperator from './../../../assets/icons/seperator.svg';
7
+ import LogoIcon from './../../../assets/logo.svg';
8
8
 
9
9
  interface ChatWindowProps {
10
- onSendMessage: (message: string) => void
11
- messages: Message[]
12
- assistantStatus: string
13
- needsAgent: boolean
10
+ onSendMessage: (message: string) => void;
11
+ messages: Message[];
12
+ assistantStatus: string;
13
+ needsAgent: boolean;
14
+ isSignalRConnected: boolean;
14
15
  }
15
16
 
16
- export function ChatWindow({ onSendMessage, messages, assistantStatus = 'loading' }: ChatWindowProps) {
17
- const [inputMessage, setInputMessage] = useState('')
18
- const messagesEndRef = useRef<HTMLDivElement>(null)
17
+ // Memoize individual message component to prevent unnecessary re-renders
18
+ const MessageComponent = React.memo(
19
+ ({
20
+ message,
21
+ index,
22
+ messages,
23
+ firstHumanAgentIndex,
24
+ onType,
25
+ }: {
26
+ message: Message;
27
+ index: number;
28
+ messages: Message[];
29
+ firstHumanAgentIndex: number;
30
+ onType: () => void;
31
+ }) => {
32
+ const isFirstInSequence = index === 0 || messages[index - 1].senderType !== message.senderType;
33
+ const isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
34
+ const textDirection = message.senderType === 1 ? 'justify-end' : 'justify-start';
19
35
 
20
- const scrollToBottom = () => {
21
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
22
- }
36
+ return (
37
+ <div key={message.id}>
38
+ {isFirstHumanAgentMessage && (
39
+ <div className='babylai-flex babylai-justify-center babylai-items-center babylai-my-4'>
40
+ <Seperator className='babylai-w-full babylai-text-purple-500' />
41
+ </div>
42
+ )}
43
+ <div className={`babylai-mb-4 babylai-flex ${textDirection}`}>
44
+ {isFirstInSequence && message.senderType === 3 && (
45
+ <div className='babylai-flex-shrink-0 babylai-mr-3'>
46
+ <div className='babylai-w-8 babylai-h-8 babylai-rounded-full babylai-bg-purple-500 babylai-flex babylai-items-center babylai-justify-center'>
47
+ <LogoIcon className='babylai-w-4 babylai-h-4 babylai-text-white' />
48
+ </div>
49
+ </div>
50
+ )}
51
+ {!isFirstInSequence && <div className='babylai-flex-shrink-0 babylai-mr-3 babylai-w-8'></div>}
52
+
53
+ <AgentResponse
54
+ messageContent={message.messageContent}
55
+ senderType={message.senderType}
56
+ messageId={message.id}
57
+ onType={onType}
58
+ />
59
+ </div>
60
+ </div>
61
+ );
62
+ }
63
+ );
64
+
65
+ MessageComponent.displayName = 'MessageComponent';
66
+
67
+ // Memoize typing indicator component
68
+ const TypingIndicator = React.memo(({ firstHumanAgentIndex }: { firstHumanAgentIndex: number }) => {
69
+ if (firstHumanAgentIndex !== -1) return null;
70
+
71
+ return (
72
+ <div className='babylai-mb-4 babylai-flex'>
73
+ <div className='babylai-flex-shrink-0 babylai-mr-3'>
74
+ <div className='babylai-w-8 babylai-h-8 babylai-rounded-full babylai-flex babylai-items-center babylai-justify-center'>
75
+ <img src={LoadingGif} alt='Loading' className='babylai-w-8 babylai-h-8' />
76
+ </div>
77
+ </div>
78
+ <div className='babylai-max-w-[80%] babylai-rounded-2xl babylai-p-4 babylai-bg-white'>
79
+ <p className='babylai-text-sm babylai-opacity-70'>...</p>
80
+ </div>
81
+ </div>
82
+ );
83
+ });
84
+
85
+ TypingIndicator.displayName = 'TypingIndicator';
86
+
87
+ export const ChatWindow = React.memo(
88
+ ({ onSendMessage, messages, assistantStatus = 'loading', isSignalRConnected }: ChatWindowProps) => {
89
+ const [inputMessage, setInputMessage] = useState('');
90
+ const messagesEndRef = useRef<HTMLDivElement>(null);
91
+ const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
92
+ const lastMessageCountRef = useRef(messages.length);
93
+
94
+ // Debounced scroll to bottom function
95
+ const scrollToBottom = useCallback(() => {
96
+ if (scrollTimeoutRef.current) {
97
+ clearTimeout(scrollTimeoutRef.current);
98
+ }
99
+
100
+ scrollTimeoutRef.current = setTimeout(() => {
101
+ messagesEndRef.current?.scrollIntoView({
102
+ behavior: 'smooth',
103
+ block: 'end',
104
+ });
105
+ }, 100);
106
+ }, []);
23
107
 
108
+ // Only scroll when new messages are added or status changes
24
109
  useEffect(() => {
25
- scrollToBottom()
26
- }, [messages, assistantStatus])
110
+ if (messages.length !== lastMessageCountRef.current || assistantStatus === 'typing') {
111
+ lastMessageCountRef.current = messages.length;
112
+ scrollToBottom();
113
+ }
114
+ }, [messages.length, assistantStatus, scrollToBottom]);
27
115
 
28
- const handleSendMessage = () => {
29
- if (inputMessage.trim()) {
30
- onSendMessage(inputMessage)
31
- setInputMessage('')
116
+ // Cleanup timeout on unmount
117
+ useEffect(() => {
118
+ return () => {
119
+ if (scrollTimeoutRef.current) {
120
+ clearTimeout(scrollTimeoutRef.current);
32
121
  }
33
- }
122
+ };
123
+ }, []);
124
+
125
+ const handleSendMessage = useCallback(() => {
126
+ if (inputMessage.trim()) {
127
+ onSendMessage(inputMessage);
128
+ setInputMessage('');
129
+ }
130
+ }, [inputMessage, onSendMessage]);
34
131
 
35
- const handleKeyDown = (e: React.KeyboardEvent) => {
36
- if (e.key === 'Enter' && !e.shiftKey) {
37
- e.preventDefault()
38
- onSendMessage(inputMessage)
39
- setInputMessage('')
132
+ const handleKeyDown = useCallback(
133
+ (e: React.KeyboardEvent) => {
134
+ if (e.key === 'Enter' && !e.shiftKey && isSignalRConnected) {
135
+ e.preventDefault();
136
+ if (inputMessage.trim() && assistantStatus !== 'typing') {
137
+ onSendMessage(inputMessage);
138
+ setInputMessage('');
139
+ }
40
140
  }
41
- }
141
+ },
142
+ [inputMessage, onSendMessage, assistantStatus, isSignalRConnected]
143
+ );
144
+
145
+ // Memoize the first human agent index calculation
146
+ const firstHumanAgentIndex = useMemo(() => {
147
+ return messages.findIndex((message) => message.senderType === 2);
148
+ }, [messages]);
42
149
 
43
- // Find the first human agent message
44
- const firstHumanAgentIndex = messages.findIndex((message) => message.senderType === 2)
150
+ // Memoize the message list to prevent unnecessary re-renders
151
+ const messagesList = useMemo(() => {
152
+ return messages.map((message, index) => (
153
+ <MessageComponent
154
+ key={`${message.id}-${index}`}
155
+ message={message}
156
+ index={index}
157
+ messages={messages}
158
+ firstHumanAgentIndex={firstHumanAgentIndex}
159
+ onType={scrollToBottom}
160
+ />
161
+ ));
162
+ }, [messages, firstHumanAgentIndex, scrollToBottom]);
163
+
164
+ // Memoize loading state check
165
+ const isLoading = useMemo(() => {
166
+ return (
167
+ assistantStatus === 'typing' ||
168
+ assistantStatus === 'loading' ||
169
+ assistantStatus === 'error' ||
170
+ inputMessage.trim() === ''
171
+ );
172
+ }, [assistantStatus, inputMessage]);
45
173
 
46
174
  return (
47
- <>
48
- <div className="babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full">
49
- {messages.map((message, index) => {
50
- const isFirstInSequence = index === 0 || messages[index - 1].senderType !== message.senderType
51
- const isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2
52
- const textDirection = message.senderType === 1 ? 'justify-end' : 'justify-start'
53
-
54
- return (
55
- <div key={message.id}>
56
- {isFirstHumanAgentMessage && (
57
- <div className="babylai-flex babylai-justify-center babylai-items-center babylai-my-4">
58
- <Seperator className="babylai-w-full babylai-text-purple-500" />
59
- </div>
60
- )}
61
- <div className={`babylai-mb-4 babylai-flex ${textDirection}`}>
62
- {isFirstInSequence && message.senderType === 3 && (
63
- <div className="babylai-flex-shrink-0 babylai-mr-3">
64
- <div className="babylai-w-8 babylai-h-8 babylai-rounded-full babylai-bg-purple-500 babylai-flex babylai-items-center babylai-justify-center">
65
- <LogoIcon className="babylai-w-4 babylai-h-4 babylai-text-white" />
66
- </div>
67
- </div>
68
- )}
69
- {!isFirstInSequence && <div className="babylai-flex-shrink-0 babylai-mr-3 babylai-w-8"></div>}
70
-
71
- <AgentResponse
72
- messageContent={message.messageContent}
73
- senderType={message.senderType}
74
- messageId={message.id}
75
- onType={scrollToBottom} />
76
- </div>
77
- </div>
78
- )
79
- })}
80
- {assistantStatus === 'typing' && firstHumanAgentIndex === -1 && (
81
- <div className="babylai-mb-4 babylai-flex">
82
- <div className="babylai-flex-shrink-0 babylai-mr-3">
83
- <div className="babylai-w-8 babylai-h-8 babylai-rounded-full babylai-flex babylai-items-center babylai-justify-center">
84
- <img src={LoadingGif} alt="Loading" className="babylai-w-8 babylai-h-8" />
85
- </div>
86
- </div>
87
- <div className="babylai-max-w-[80%] babylai-rounded-2xl babylai-p-4 babylai-bg-white">
88
- <p className="babylai-text-sm babylai-opacity-70">...</p>
89
- </div>
90
- </div>
91
- )}
92
- <div ref={messagesEndRef} />
93
- </div>
175
+ <>
176
+ <div className='babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full'>
177
+ {messagesList}
94
178
 
95
- <ChatWindowFooter
96
- inputMessage={inputMessage}
97
- handleKeyDown={(e) => handleKeyDown(e)}
98
- handleSendMessage={handleSendMessage}
99
- setInputMessage={setInputMessage}
100
- />
101
- </>
102
- )
103
- }
179
+ {assistantStatus === 'typing' && <TypingIndicator firstHumanAgentIndex={firstHumanAgentIndex} />}
180
+
181
+ <div ref={messagesEndRef} />
182
+ </div>
183
+
184
+ <ChatWindowFooter
185
+ inputMessage={inputMessage}
186
+ handleKeyDown={handleKeyDown}
187
+ handleSendMessage={handleSendMessage}
188
+ setInputMessage={setInputMessage}
189
+ isLoading={isLoading}
190
+ isSignalRConnected={isSignalRConnected}
191
+ />
192
+ </>
193
+ );
194
+ }
195
+ );
196
+
197
+ ChatWindow.displayName = 'ChatWindow';
@@ -1,38 +1,42 @@
1
- import { Button } from '@/components'
2
- import React from 'react'
3
- import ArrowRight from './../../../assets/icons/arrowRight.svg'
4
- import ThinkingLogo from './../../../assets/thinking-logo.svg'
5
- import { useLocalTranslation } from '../../../useLocalTranslation'
6
- import CloseCircle from '../../../assets/icons/closeCirclePrimary.svg'
1
+ import { Button } from '@/components';
2
+ import React from 'react';
3
+ import ArrowRight from './../../../assets/icons/arrowRight.svg';
4
+ import ThinkingLogo from './../../../assets/thinking-logo.svg';
5
+ import { useLocalTranslation } from '../../../useLocalTranslation';
6
+ import CloseCircle from '../../../assets/icons/closeCirclePrimary.svg';
7
7
 
8
8
  interface OptionsListHeaderProps {
9
- handleBack: () => void
10
- showHelpScreen: boolean
9
+ handleBack: () => void;
10
+ showHelpScreen: boolean;
11
11
  }
12
12
 
13
13
  const OptionsListHeader: React.FC<OptionsListHeaderProps> = ({ handleBack, showHelpScreen }) => {
14
- const { i18n } = useLocalTranslation()
15
- const isRTL = i18n.dir() === 'rtl'
14
+ const { i18n } = useLocalTranslation();
15
+ const isRTL = i18n.dir() === 'rtl';
16
16
 
17
17
  return (
18
18
  <header
19
19
  dir={i18n.dir()}
20
- className={`babylai-flex babylai-w-full babylai-items-center mb-2 ${!showHelpScreen ? 'babylai-justify-end' : 'babylai-justify-between'}`}
20
+ className={`babylai-flex babylai-w-full babylai-items-center mb-2 ${
21
+ !showHelpScreen ? 'babylai-justify-end' : 'babylai-justify-between'
22
+ }`}
21
23
  >
22
24
  <Button
23
- variant="rounded-icon"
24
- size="icon"
25
- className="!babylai-w-12 !babylai-h-12 babylai-bg-black-white-50 babylai-relative babylai-z-10"
25
+ variant='rounded-icon'
26
+ size='icon'
27
+ className='!babylai-w-12 !babylai-h-12 babylai-bg-black-white-50 babylai-relative babylai-z-10'
26
28
  onClick={handleBack}
27
29
  >
28
30
  {showHelpScreen ? (
29
- <ArrowRight className={`${isRTL ? '' : 'babylai-rotate-180'} babylai-text-primary-500`} />
31
+ <ArrowRight
32
+ className={`${isRTL ? '' : 'babylai-rotate-180'} babylai-text-primary-500 babylai-w-full babylai-h-full`}
33
+ />
30
34
  ) : (
31
- <CloseCircle className="!babylai-w-full !babylai-h-full babylai-cursor-pointer" />
35
+ <CloseCircle className='!babylai-w-full !babylai-h-full babylai-cursor-pointer' />
32
36
  )}
33
37
  </Button>
34
- <ThinkingLogo className="babylai-w-40 babylai-h-40 babylai-text-primary-500 babylai-absolute babylai-top-0 babylai-end-1 babylai-overflow-hidden" />
38
+ <ThinkingLogo className='babylai-w-40 babylai-h-40 babylai-text-primary-500 babylai-absolute babylai-top-0 babylai-end-1 babylai-overflow-hidden' />
35
39
  </header>
36
- )
37
- }
38
- export default OptionsListHeader
40
+ );
41
+ };
42
+ export default OptionsListHeader;
@@ -1,17 +1,17 @@
1
- import { Card } from '@/components'
2
- import { HelpScreenData, Option } from '@/lib/types'
3
- import ExpandedOption from '@/ui/chatbot-popup/options-list-screen/expanded-option'
4
- import OptionsListHeader from '@/ui/chatbot-popup/options-list-screen/header'
5
- import OptionCard from '@/ui/chatbot-popup/options-list-screen/option-card'
6
- import React from 'react'
1
+ import { Card } from '@/components';
2
+ import { HelpScreenData, Option } from '@/lib/types';
3
+ import ExpandedOption from '@/ui/chatbot-popup/options-list-screen/expanded-option';
4
+ import OptionsListHeader from '@/ui/chatbot-popup/options-list-screen/header';
5
+ import OptionCard from '@/ui/chatbot-popup/options-list-screen/option-card';
6
+ import React from 'react';
7
7
 
8
8
  interface OptionsListScreenProps {
9
- helpScreen: HelpScreenData | null
10
- expandedOption: Option | null
11
- setExpandedOption: (option: Option | null) => void
12
- handleStartChat: (option: Option) => void
13
- handleBack: () => void
14
- showHelpScreen: boolean
9
+ helpScreen: HelpScreenData | null;
10
+ expandedOption: Option | null;
11
+ setExpandedOption: (option: Option | null) => void;
12
+ handleStartChat: (option: Option) => void;
13
+ handleBack: () => void;
14
+ showHelpScreen: boolean;
15
15
  }
16
16
 
17
17
  const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
@@ -20,29 +20,29 @@ const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
20
20
  setExpandedOption,
21
21
  handleStartChat,
22
22
  handleBack,
23
- showHelpScreen
23
+ showHelpScreen,
24
24
  }) => {
25
25
  const handleToggleExpandOption = (option: Option) => {
26
26
  if (expandedOption?.id === option.id) {
27
- setExpandedOption(null)
27
+ setExpandedOption(null);
28
28
  } else {
29
- setExpandedOption(option)
29
+ setExpandedOption(option);
30
30
  }
31
- }
31
+ };
32
32
 
33
33
  return (
34
- <div className="babylai-px-8 babylai-pb-12 babylai-pt-8 babylai-overflow-y-auto babylai-h-full">
34
+ <div className='babylai-px-8 babylai-pb-12 babylai-pt-8 babylai-overflow-y-auto babylai-h-full'>
35
35
  <OptionsListHeader handleBack={handleBack} showHelpScreen={showHelpScreen} />
36
36
 
37
- <h1 className="babylai-text-4xl babylai-font-bold babylai-text-black-white-800 mb-4">{helpScreen?.title}</h1>
37
+ <h1 className='babylai-text-4xl babylai-font-bold babylai-text-black-white-800 mb-4'>{helpScreen?.title}</h1>
38
38
 
39
39
  {helpScreen && (
40
- <div className="babylai-flex babylai-flex-col babylai-gap-3 babylai-mt-3 babylai-mb-3">
40
+ <div className='babylai-flex babylai-flex-col babylai-gap-3 babylai-mt-3 babylai-mb-3'>
41
41
  {helpScreen?.options?.map((option) => (
42
42
  <Card
43
43
  key={option.id}
44
- variant="rounded"
45
- className="babylai-cursor-pointer babylai-transition-all babylai-hover:babylai-shadow-md !babylai-px-2 !babylai-py-1"
44
+ variant='rounded'
45
+ className='babylai-cursor-pointer babylai-transition-all babylai-hover:babylai-shadow-md !babylai-px-2 !babylai-py-1'
46
46
  onClick={() => handleToggleExpandOption(option)}
47
47
  >
48
48
  <OptionCard title={option.title} />
@@ -53,7 +53,7 @@ const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
53
53
  </div>
54
54
  )}
55
55
  </div>
56
- )
57
- }
56
+ );
57
+ };
58
58
 
59
- export default OptionsListScreen
59
+ export default OptionsListScreen;
@@ -1,17 +1,22 @@
1
1
  import { Button } from '@/components';
2
2
  import React from 'react';
3
3
  import ArrowRight from '../../../assets/icons/arrowRight.svg';
4
+ import { useTranslation } from 'react-i18next'
4
5
 
5
6
  interface OptionCardProps {
6
7
  title: string;
7
8
  }
8
9
 
9
10
  const OptionCard: React.FC<OptionCardProps> = (props) => {
11
+ const { i18n} = useTranslation();
12
+ const isLTR = i18n.dir() === 'ltr';
10
13
  return (
11
- <div className="babylai-flex babylai-items-center babylai-justify-between babylai-p-4">
12
- <p className="babylai-text-base babylai-font-semibold babylai-text-black-white-800">{props.title}</p>
13
- <Button variant="rounded-icon" size="icon" className="!babylai-w-12 !babylai-h-12 babylai-text-primary-500 hover:babylai-bg-primary-100">
14
- <ArrowRight className={` babylai-text-primary-500 babylai-rotate-90 `}/>
14
+ <div className="babylai-flex babylai-items-center babylai-justify-between babylai-p-2">
15
+ <p className="babylai-text-sm babylai-font-medium babylai-text-black-white-800">{props.title}</p>
16
+ <Button variant="rounded-icon" size="icon" className="babylai-text-primary-500 hover:babylai-bg-primary-100">
17
+ <ArrowRight className={`babylai-w-[12px] babylai-h-[12px] ${
18
+ isLTR ? '' : 'babylai-rotate-180'
19
+ } babylai-text-primary-500`}/>
15
20
  </Button>
16
21
  </div>
17
22
  )