@aslaluroba/help-center-react 3.2.17 → 3.2.18
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/dist/components/shared/Button/button.d.ts +1 -1
- package/dist/components/shared/Card/card.d.ts +1 -4
- package/dist/components/ui/agent-response/agent-response.d.ts +2 -1
- package/dist/index.css +1424 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.esm.js +19194 -38923
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +19198 -38927
- package/dist/index.js.map +1 -1
- package/dist/lib/LanguageContext.d.ts +1 -1
- package/dist/lib/custom-hooks/useAblyConnection.d.ts +25 -0
- package/dist/lib/custom-hooks/useActionHandler.d.ts +1 -7
- package/dist/lib/custom-hooks/useChatSession.d.ts +37 -0
- package/dist/lib/custom-hooks/useMessageQueue.d.ts +16 -0
- package/dist/lib/custom-hooks/useReview.d.ts +14 -0
- package/dist/lib/index.d.ts +1 -2
- package/dist/services.d.ts +9 -6
- package/dist/services.esm.js +1 -14348
- package/dist/services.esm.js.map +1 -1
- package/dist/services.js +19 -14344
- package/dist/services.js.map +1 -1
- package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +1 -1
- package/dist/ui/chatbot-popup/chat-window-screen/in-chat-review.d.ts +1 -1
- package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +2 -2
- package/dist/ui/chatbot-popup/options-list-screen/helpscreen-list.d.ts +1 -1
- package/dist/ui/chatbot-popup/options-list-screen/helpscreen-option.d.ts +1 -1
- package/dist/ui/chatbot-popup/options-list-screen/index.d.ts +1 -1
- package/dist/ui/help-center.d.ts +1 -1
- package/dist/ui/help-popup.d.ts +4 -27
- package/dist/ui/review-dialog/index.d.ts +1 -1
- package/package.json +12 -26
- package/postcss.config.js +5 -0
- package/rollup.config.mjs +34 -0
- package/dist/core/AblyService.d.ts +0 -16
- package/dist/core/ApiService.d.ts +0 -16
- package/dist/core/api.d.ts +0 -10
- package/dist/core/token-service.d.ts +0 -10
- package/dist/i18n.d.ts +0 -3
- package/dist/lib/config.d.ts +0 -18
- package/dist/lib/theme-utils.d.ts +0 -10
- package/dist/lib/types.d.ts +0 -145
- package/dist/lib/utils.d.ts +0 -2
- package/src/assets/animatedLogo.gif +0 -0
- package/src/assets/logo.svg +0 -5
- package/src/assets/seperator.svg +0 -5
- package/src/components/index.ts +0 -1
- package/src/components/shared/Button/button.tsx +0 -38
- package/src/components/shared/Button/index.ts +0 -1
- package/src/components/shared/Card/card.tsx +0 -44
- package/src/components/shared/Card/index.ts +0 -1
- package/src/components/shared/index.ts +0 -2
- package/src/components/ui/agent-response/agent-response.tsx +0 -57
- package/src/components/ui/agent-response/doc.md +0 -88
- package/src/components/ui/image-attachment.tsx +0 -119
- package/src/components/ui/image-preview-dialog.tsx +0 -400
- package/src/components/ui/index.ts +0 -3
- package/src/core/AblyService.ts +0 -243
- package/src/core/ApiService.ts +0 -116
- package/src/core/api.ts +0 -278
- package/src/core/token-service.ts +0 -35
- package/src/globals.css +0 -268
- package/src/i18n.ts +0 -21
- package/src/index.ts +0 -19
- package/src/lib/LanguageContext.tsx +0 -28
- package/src/lib/config.ts +0 -52
- package/src/lib/custom-hooks/useActionHandler.ts +0 -102
- package/src/lib/custom-hooks/useTypewriter.ts +0 -26
- package/src/lib/index.ts +0 -4
- package/src/lib/theme-utils.ts +0 -56
- package/src/lib/types.ts +0 -158
- package/src/lib/utils.ts +0 -6
- package/src/locales/ar.json +0 -45
- package/src/locales/en.json +0 -45
- package/src/services.ts +0 -14
- package/src/types/icons.d.ts +0 -6
- package/src/types/svg.d.ts +0 -5
- package/src/types.d.ts +0 -9
- package/src/ui/chatbot-popup/active-chat-actions.tsx +0 -39
- package/src/ui/chatbot-popup/chat-window-screen/action-button.tsx +0 -37
- package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +0 -313
- package/src/ui/chatbot-popup/chat-window-screen/header.tsx +0 -53
- package/src/ui/chatbot-popup/chat-window-screen/in-chat-review.tsx +0 -116
- package/src/ui/chatbot-popup/chat-window-screen/index.tsx +0 -366
- package/src/ui/chatbot-popup/chat-window-screen/typing-indicator.tsx +0 -31
- package/src/ui/chatbot-popup/error-screen/index.tsx +0 -22
- package/src/ui/chatbot-popup/loading-screen/index.tsx +0 -21
- package/src/ui/chatbot-popup/options-list-screen/company-card.tsx +0 -39
- package/src/ui/chatbot-popup/options-list-screen/header.tsx +0 -23
- package/src/ui/chatbot-popup/options-list-screen/helpscreen-intro.tsx +0 -32
- package/src/ui/chatbot-popup/options-list-screen/helpscreen-list.tsx +0 -57
- package/src/ui/chatbot-popup/options-list-screen/helpscreen-option.tsx +0 -56
- package/src/ui/chatbot-popup/options-list-screen/index.tsx +0 -70
- package/src/ui/confirmation-modal/index.tsx +0 -62
- package/src/ui/floating-message.tsx +0 -29
- package/src/ui/help-button.tsx +0 -25
- package/src/ui/help-center.tsx +0 -448
- package/src/ui/help-popup.tsx +0 -367
- package/src/ui/powered-by.tsx +0 -62
- package/src/ui/review-dialog/index.tsx +0 -149
- package/src/ui/review-dialog/rating.tsx +0 -79
- package/src/useLocalTranslation.ts +0 -15
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { useLocalTranslation } from "@/useLocalTranslation";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
import ActionButton from "./action-button";
|
|
5
|
-
import SolarAltArrowLeftLinear from '~icons/solar/alt-arrow-left-linear'
|
|
6
|
-
import IcRoundMinus from '~icons/ic/round-minus'
|
|
7
|
-
import MaterialSymbolsCloseSmallOutlineRounded from '~icons/material-symbols/close-small-outline-rounded'
|
|
8
|
-
|
|
9
|
-
interface ChatWindowHeaderProps {
|
|
10
|
-
handleBack: () => void;
|
|
11
|
-
handleEndChat: () => void;
|
|
12
|
-
handleMinimize: () => void;
|
|
13
|
-
optionTitle: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const ChatWindowHeader: React.FC<ChatWindowHeaderProps> = ({
|
|
17
|
-
handleBack,
|
|
18
|
-
handleEndChat,
|
|
19
|
-
handleMinimize,
|
|
20
|
-
optionTitle,
|
|
21
|
-
}) => {
|
|
22
|
-
const { i18n } = useLocalTranslation();
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<header className="bg-header babylai:flex babylai:items-center babylai:justify-between babylai:p-6 babylai:border-b babylai:border-black-white-200">
|
|
26
|
-
<div className="babylai:flex babylai:items-center babylai:gap-2">
|
|
27
|
-
<ActionButton
|
|
28
|
-
onClick={handleBack}
|
|
29
|
-
icon={<SolarAltArrowLeftLinear />}
|
|
30
|
-
ariaLabel="Back"
|
|
31
|
-
className={cn(i18n.language === 'ar' && 'babylai:rotate-180')}
|
|
32
|
-
/>
|
|
33
|
-
|
|
34
|
-
<ActionButton
|
|
35
|
-
onClick={handleEndChat}
|
|
36
|
-
icon={<MaterialSymbolsCloseSmallOutlineRounded />}
|
|
37
|
-
ariaLabel="End Chat"
|
|
38
|
-
/>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<h2 className="babylai:text-lg! babylai:font-semibold! babylai:text-card-foreground">
|
|
42
|
-
{optionTitle}
|
|
43
|
-
</h2>
|
|
44
|
-
|
|
45
|
-
<ActionButton
|
|
46
|
-
onClick={handleMinimize}
|
|
47
|
-
icon={<IcRoundMinus />}
|
|
48
|
-
ariaLabel="Minimize"
|
|
49
|
-
/>
|
|
50
|
-
</header>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
53
|
-
export default ChatWindowHeader;
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react';
|
|
2
|
-
import { ReviewProps } from '@/lib/types';
|
|
3
|
-
import { cn } from '@/lib/utils';
|
|
4
|
-
import { Rating } from '@/ui/review-dialog/rating';
|
|
5
|
-
import { useLocalTranslation } from '@/useLocalTranslation';
|
|
6
|
-
import LogoIcon from '@/assets/logo.svg';
|
|
7
|
-
import { Button } from '@/components';
|
|
8
|
-
|
|
9
|
-
const COMMENT_MAX_LENGTH = 500;
|
|
10
|
-
|
|
11
|
-
interface InChatReviewProps {
|
|
12
|
-
onSubmit: (payload: ReviewProps) => void | Promise<void>;
|
|
13
|
-
onDone?: () => void;
|
|
14
|
-
isSubmitting?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const InChatReview: React.FC<InChatReviewProps> = ({ onSubmit, isSubmitting = false }) => {
|
|
18
|
-
const { t } = useLocalTranslation();
|
|
19
|
-
const [rating, setRating] = useState<number>(0);
|
|
20
|
-
const [comment, setComment] = useState<string>('');
|
|
21
|
-
const [commentError, setCommentError] = useState<string | null>(null);
|
|
22
|
-
|
|
23
|
-
const hasRating = rating >= 1 && rating <= 5;
|
|
24
|
-
|
|
25
|
-
const handleRatingChange = useCallback((val: number) => {
|
|
26
|
-
setRating(val);
|
|
27
|
-
}, []);
|
|
28
|
-
|
|
29
|
-
const handleCommentChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
30
|
-
const value = e.target.value;
|
|
31
|
-
setComment(value);
|
|
32
|
-
if (commentError && value.length <= COMMENT_MAX_LENGTH) {
|
|
33
|
-
setCommentError(null);
|
|
34
|
-
}
|
|
35
|
-
}, [commentError]);
|
|
36
|
-
|
|
37
|
-
const handleSubmit = useCallback(() => {
|
|
38
|
-
const trimmed = comment.trim();
|
|
39
|
-
if (trimmed.length > COMMENT_MAX_LENGTH) {
|
|
40
|
-
setCommentError(t('homeSdk.ReviewDialog.comment_error') || 'The field Comment must be a string with a maximum length of 500.');
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
setCommentError(null);
|
|
44
|
-
void onSubmit({ rating, comment: trimmed });
|
|
45
|
-
}, [rating, comment, onSubmit, t]);
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<section className="babylai:mb-4 babylai:flex babylai:justify-start">
|
|
49
|
-
<div className="babylai:shrink-0 babylai:me-3 babylai:w-8 babylai:h-8 babylai:rounded-full babylai:flex babylai:items-center babylai:justify-center babylai:bg-primary">
|
|
50
|
-
<LogoIcon className="babylai:w-4 babylai:h-4 babylai:text-primary" />
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
<div className="babylai:bg-card babylai:max-w-[80%] babylai:p-5 babylai:rounded-3xl babylai:text-sm babylai:flex babylai:flex-col babylai:gap-2.5 babylai:text-start">
|
|
54
|
-
<h2 className='babylai:text-card-foreground'>
|
|
55
|
-
{t('homeSdk.InChatReview.title')}
|
|
56
|
-
</h2>
|
|
57
|
-
<p className="babylai:font-normal babylai:text-muted-foreground babylai:leading-snug">
|
|
58
|
-
{t('homeSdk.InChatReview.description')}
|
|
59
|
-
</p>
|
|
60
|
-
|
|
61
|
-
<section className='babylai:bg-muted babylai:rounded-3xl babylai:p-3'>
|
|
62
|
-
<Rating value={rating} onChange={handleRatingChange} size="md" />
|
|
63
|
-
</section>
|
|
64
|
-
|
|
65
|
-
{hasRating && (
|
|
66
|
-
<>
|
|
67
|
-
<p className="babylai:text-card-foreground babylai:leading-snug">{t('homeSdk.InChatReview.follow_up')}</p>
|
|
68
|
-
|
|
69
|
-
<textarea
|
|
70
|
-
className={cn(
|
|
71
|
-
'babylai:resize-none babylai:w-full babylai:bg-secondary babylai:border babylai:rounded-xl babylai:text-card-foreground babylai:text-sm babylai:p-3 babylai:resize-vertical babylai:min-h-20 babylai:disabled:opacity-50 babylai:disabled:cursor-not-allowed babylai:disabled:bg-secondary',
|
|
72
|
-
commentError ? 'babylai:border-destructive' : 'babylai:border-black-white-200'
|
|
73
|
-
)}
|
|
74
|
-
rows={4}
|
|
75
|
-
maxLength={COMMENT_MAX_LENGTH}
|
|
76
|
-
placeholder={t('homeSdk.InChatReview.note_placeholder')}
|
|
77
|
-
value={comment}
|
|
78
|
-
onChange={handleCommentChange}
|
|
79
|
-
aria-label={t('homeSdk.InChatReview.note_placeholder')}
|
|
80
|
-
aria-invalid={!!commentError}
|
|
81
|
-
aria-describedby={commentError ? 'in-chat-review-comment-error' : undefined}
|
|
82
|
-
/>
|
|
83
|
-
{commentError && (
|
|
84
|
-
<p id="in-chat-review-comment-error" className="babylai:text-sm babylai:text-destructive" role="alert">
|
|
85
|
-
{commentError}
|
|
86
|
-
</p>
|
|
87
|
-
)}
|
|
88
|
-
<p className="babylai:text-xs babylai:text-muted-foreground">
|
|
89
|
-
{comment.length}/{COMMENT_MAX_LENGTH}
|
|
90
|
-
</p>
|
|
91
|
-
|
|
92
|
-
<Button
|
|
93
|
-
variant='default'
|
|
94
|
-
onClick={handleSubmit}
|
|
95
|
-
disabled={isSubmitting}
|
|
96
|
-
>
|
|
97
|
-
{isSubmitting ? (
|
|
98
|
-
<>
|
|
99
|
-
<span
|
|
100
|
-
className="babylai:inline-block babylai:animate-spin babylai:rounded-full babylai:h-4 babylai:w-4 babylai:border-2 babylai:border-white babylai:border-t-transparent babylai:box-border"
|
|
101
|
-
aria-hidden
|
|
102
|
-
/>
|
|
103
|
-
{t('homeSdk.InChatReview.submit_button')}
|
|
104
|
-
</>
|
|
105
|
-
) : (
|
|
106
|
-
t('homeSdk.InChatReview.submit_button')
|
|
107
|
-
)}
|
|
108
|
-
</Button>
|
|
109
|
-
</>
|
|
110
|
-
)}
|
|
111
|
-
</div>
|
|
112
|
-
</section>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
InChatReview.displayName = 'InChatReview';
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import AgentResponse from '@/components/ui/agent-response/agent-response';
|
|
2
|
-
import { ImageAttachment, ImagePreviewDialog } from '@/components/ui';
|
|
3
|
-
import { Message, ReviewProps } from '@/lib/types';
|
|
4
|
-
import ChatWindowFooter from '@/ui/chatbot-popup/chat-window-screen/footer';
|
|
5
|
-
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
|
6
|
-
import Seperator from './../../../assets/seperator.svg';
|
|
7
|
-
import LogoIcon from './../../../assets/logo.svg';
|
|
8
|
-
import { presignDownload } from '@/core/api';
|
|
9
|
-
import { useLocalTranslation } from '../../../useLocalTranslation';
|
|
10
|
-
import PoweredBy from '@/ui/powered-by';
|
|
11
|
-
import { TypingIndicator } from './typing-indicator';
|
|
12
|
-
import { InChatReview } from './in-chat-review';
|
|
13
|
-
import SolarUserBold from '~icons/solar/user-bold'
|
|
14
|
-
|
|
15
|
-
interface ChatWindowProps {
|
|
16
|
-
onSendMessage: (message: string, attachmentIds: string[]) => void;
|
|
17
|
-
onEnsureSession: () => Promise<string>;
|
|
18
|
-
messages: Message[];
|
|
19
|
-
assistantStatus: string;
|
|
20
|
-
needsAgent: boolean;
|
|
21
|
-
sessionId?: string | null;
|
|
22
|
-
isChatClosed?: boolean;
|
|
23
|
-
inChatReviewSessionId?: string | null;
|
|
24
|
-
isSubmittingReview?: boolean;
|
|
25
|
-
onInChatReviewSubmit?: (payload: ReviewProps) => void | Promise<void>;
|
|
26
|
-
onInChatReviewDone?: () => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Memoize individual message component to prevent unnecessary re-renders
|
|
30
|
-
const MessageComponent = React.memo(
|
|
31
|
-
({
|
|
32
|
-
message,
|
|
33
|
-
index,
|
|
34
|
-
messages,
|
|
35
|
-
firstHumanAgentIndex,
|
|
36
|
-
onType,
|
|
37
|
-
onImageClick,
|
|
38
|
-
}: {
|
|
39
|
-
message: Message;
|
|
40
|
-
index: number;
|
|
41
|
-
messages: Message[];
|
|
42
|
-
firstHumanAgentIndex: number;
|
|
43
|
-
onType: () => void;
|
|
44
|
-
onImageClick: (attachmentIdsOrUrls: string[], clickedIndex: number) => void;
|
|
45
|
-
}) => {
|
|
46
|
-
const isFirstInSequence = index === 0 || messages[index - 1].senderType !== message.senderType;
|
|
47
|
-
const isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
|
|
48
|
-
const textDirection = message.senderType === 1 ? 'babylai:justify-end' : 'babylai:justify-start';
|
|
49
|
-
|
|
50
|
-
const handleImageClick = useCallback(
|
|
51
|
-
(clickedIndex: number) => {
|
|
52
|
-
// Use attachmentUrls if available (from Ably), otherwise use attachmentIds (user-sent)
|
|
53
|
-
const attachments =
|
|
54
|
-
message.attachmentUrls?.length ? message.attachmentUrls : (message.attachmentIds || []);
|
|
55
|
-
if (attachments.length > 0) {
|
|
56
|
-
onImageClick(attachments, clickedIndex);
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
[message.attachmentIds, message.attachmentUrls, onImageClick]
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div key={message.id}>
|
|
64
|
-
{isFirstHumanAgentMessage && (
|
|
65
|
-
<div className='babylai:flex babylai:justify-center babylai:items-center babylai:my-4'>
|
|
66
|
-
<Seperator className='babylai:w-full babylai:text-primary-500' />
|
|
67
|
-
</div>
|
|
68
|
-
)}
|
|
69
|
-
<div className={`babylai:mb-4 babylai:flex ${textDirection}`}>
|
|
70
|
-
{isFirstInSequence && (message.senderType === 3 || message.senderType === 2) && (
|
|
71
|
-
<div className='babylai:shrink-0 babylai:me-3'>
|
|
72
|
-
<div
|
|
73
|
-
className={`babylai:w-8 babylai:h-8 babylai:rounded-full babylai:flex babylai:items-center babylai:justify-center ${message?.senderType === 3 ? 'babylai:bg-primary' : 'babylai:bg-black-white-50'
|
|
74
|
-
}`}
|
|
75
|
-
>
|
|
76
|
-
{message?.senderType === 3 ? (
|
|
77
|
-
<LogoIcon className='babylai:w-4 babylai:h-4 babylai:text-primary' />
|
|
78
|
-
) : (
|
|
79
|
-
<SolarUserBold className='babylai:w-4 babylai:h-4 babylai:text-primary' />
|
|
80
|
-
)}
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
)}
|
|
84
|
-
{!isFirstInSequence && <div className='babylai:shrink-0 babylai:me-3 babylai:w-8'></div>}
|
|
85
|
-
|
|
86
|
-
<div className='babylai:flex babylai:flex-col babylai:gap-2'>
|
|
87
|
-
{/* Display attachment URLs (from Ably) */}
|
|
88
|
-
{message.attachmentUrls && message.attachmentUrls.length > 0 && (
|
|
89
|
-
<div className='babylai:flex babylai:flex-row babylai:flex-wrap babylai:gap-2 babylai:max-w-full'>
|
|
90
|
-
{message.attachmentUrls.map((attachmentUrl, imgIndex) => (
|
|
91
|
-
<ImageAttachment
|
|
92
|
-
key={attachmentUrl}
|
|
93
|
-
imageUrl={attachmentUrl}
|
|
94
|
-
enablePreview={false}
|
|
95
|
-
onClick={() => handleImageClick(imgIndex)}
|
|
96
|
-
/>
|
|
97
|
-
))}
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
{/* Display attachment IDs (user-sent messages) */}
|
|
101
|
-
{message.attachmentIds && message.attachmentIds.length > 0 && (
|
|
102
|
-
<div className='babylai:flex babylai:flex-row babylai:flex-wrap babylai:gap-2 babylai:max-w-full'>
|
|
103
|
-
{message.attachmentIds.map((attachmentId, imgIndex) => (
|
|
104
|
-
<ImageAttachment
|
|
105
|
-
key={attachmentId}
|
|
106
|
-
fileId={attachmentId}
|
|
107
|
-
enablePreview={false}
|
|
108
|
-
onClick={() => handleImageClick(imgIndex)}
|
|
109
|
-
/>
|
|
110
|
-
))}
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
{/* Only show chat bubble if there's message content */}
|
|
114
|
-
{message.messageContent && message.messageContent.trim() !== '' && (
|
|
115
|
-
<AgentResponse
|
|
116
|
-
messageContent={message.messageContent}
|
|
117
|
-
senderType={message.senderType}
|
|
118
|
-
messageId={message.id}
|
|
119
|
-
onType={onType}
|
|
120
|
-
/>
|
|
121
|
-
)}
|
|
122
|
-
</div>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
MessageComponent.displayName = 'MessageComponent';
|
|
130
|
-
|
|
131
|
-
export const ChatWindow = React.memo(
|
|
132
|
-
({
|
|
133
|
-
onSendMessage,
|
|
134
|
-
onEnsureSession,
|
|
135
|
-
messages,
|
|
136
|
-
assistantStatus = 'loading',
|
|
137
|
-
needsAgent,
|
|
138
|
-
sessionId,
|
|
139
|
-
isChatClosed = false,
|
|
140
|
-
inChatReviewSessionId,
|
|
141
|
-
isSubmittingReview = false,
|
|
142
|
-
onInChatReviewSubmit,
|
|
143
|
-
onInChatReviewDone,
|
|
144
|
-
}: ChatWindowProps) => {
|
|
145
|
-
const { i18n } = useLocalTranslation();
|
|
146
|
-
const [inputMessage, setInputMessage] = useState('');
|
|
147
|
-
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
148
|
-
const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
149
|
-
const lastMessageCountRef = useRef(messages.length);
|
|
150
|
-
const [galleryState, setGalleryState] = useState<{
|
|
151
|
-
isOpen: boolean;
|
|
152
|
-
imageUrls: string[];
|
|
153
|
-
initialIndex: number;
|
|
154
|
-
}>({
|
|
155
|
-
isOpen: false,
|
|
156
|
-
imageUrls: [],
|
|
157
|
-
initialIndex: 0,
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// Debounced scroll to bottom function
|
|
161
|
-
const scrollToBottom = useCallback(() => {
|
|
162
|
-
if (scrollTimeoutRef.current) {
|
|
163
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
scrollTimeoutRef.current = setTimeout(() => {
|
|
167
|
-
messagesEndRef.current?.scrollIntoView({
|
|
168
|
-
behavior: 'smooth',
|
|
169
|
-
block: 'end',
|
|
170
|
-
});
|
|
171
|
-
}, 100);
|
|
172
|
-
}, []);
|
|
173
|
-
|
|
174
|
-
// Only scroll when new messages are added or status changes
|
|
175
|
-
useEffect(() => {
|
|
176
|
-
if (messages.length !== lastMessageCountRef.current || assistantStatus === 'typing') {
|
|
177
|
-
lastMessageCountRef.current = messages.length;
|
|
178
|
-
scrollToBottom();
|
|
179
|
-
}
|
|
180
|
-
}, [messages.length, assistantStatus, scrollToBottom]);
|
|
181
|
-
|
|
182
|
-
// Cleanup timeout on unmount
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
return () => {
|
|
185
|
-
if (scrollTimeoutRef.current) {
|
|
186
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
}, []);
|
|
190
|
-
|
|
191
|
-
const handleSendMessage = useCallback(
|
|
192
|
-
(attachmentIds: string[]) => {
|
|
193
|
-
// Allow sending if there's text OR attachments
|
|
194
|
-
if (inputMessage.trim() || attachmentIds.length > 0) {
|
|
195
|
-
onSendMessage(inputMessage, attachmentIds);
|
|
196
|
-
setInputMessage('');
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
[inputMessage, onSendMessage]
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
// Memoize the first human agent index calculation
|
|
203
|
-
const firstHumanAgentIndex = useMemo(() => {
|
|
204
|
-
return messages.findIndex((message) => message.senderType === 2);
|
|
205
|
-
}, [messages]);
|
|
206
|
-
|
|
207
|
-
// Handle image gallery opening
|
|
208
|
-
const handleImageClick = useCallback(
|
|
209
|
-
async (attachmentIdsOrUrls: string[], clickedIndex: number) => {
|
|
210
|
-
if (!attachmentIdsOrUrls || attachmentIdsOrUrls.length === 0) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
// Check if the first item is a URL (starts with http:// or https://)
|
|
216
|
-
// If so, they're all URLs from Ably and can be used directly
|
|
217
|
-
const isUrl = attachmentIdsOrUrls[0]?.startsWith('http://') || attachmentIdsOrUrls[0]?.startsWith('https://');
|
|
218
|
-
|
|
219
|
-
let imageUrls: string[];
|
|
220
|
-
|
|
221
|
-
if (isUrl) {
|
|
222
|
-
// These are already URLs from Ably, use them directly (no async needed)
|
|
223
|
-
imageUrls = attachmentIdsOrUrls.filter((url): url is string => url !== null && url.length > 0);
|
|
224
|
-
|
|
225
|
-
// Open gallery immediately with URLs
|
|
226
|
-
if (imageUrls.length > 0) {
|
|
227
|
-
const adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
|
|
228
|
-
setGalleryState({
|
|
229
|
-
isOpen: true,
|
|
230
|
-
imageUrls,
|
|
231
|
-
initialIndex: adjustedIndex,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
return; // Exit early since we don't need to fetch anything
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// These are file IDs, need to fetch URLs using presignDownload
|
|
238
|
-
const imageUrlPromises = attachmentIdsOrUrls.map((fileId) => {
|
|
239
|
-
if (!fileId || typeof fileId !== 'string') {
|
|
240
|
-
return Promise.resolve(null);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return presignDownload(fileId, i18n.language as 'ar' | 'en')
|
|
244
|
-
.then((response) => {
|
|
245
|
-
if (!response || !response.downloadUrl) {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
return response.downloadUrl;
|
|
249
|
-
})
|
|
250
|
-
.catch(() => {
|
|
251
|
-
// Return null for failed downloads so we can filter them out
|
|
252
|
-
return null;
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
imageUrls = (await Promise.all(imageUrlPromises)).filter(
|
|
257
|
-
(url): url is string => url !== null && url.length > 0
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
if (imageUrls.length === 0) {
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Adjust the initial index if some images failed to load
|
|
265
|
-
const adjustedIndex = Math.max(0, Math.min(clickedIndex, imageUrls.length - 1));
|
|
266
|
-
|
|
267
|
-
setGalleryState({
|
|
268
|
-
isOpen: true,
|
|
269
|
-
imageUrls,
|
|
270
|
-
initialIndex: adjustedIndex,
|
|
271
|
-
});
|
|
272
|
-
} catch (error) {
|
|
273
|
-
// Handle unexpected errors silently
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
[i18n.language]
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
const handleCloseGallery = useCallback(() => {
|
|
280
|
-
setGalleryState({
|
|
281
|
-
isOpen: false,
|
|
282
|
-
imageUrls: [],
|
|
283
|
-
initialIndex: 0,
|
|
284
|
-
});
|
|
285
|
-
}, []);
|
|
286
|
-
|
|
287
|
-
// Memoize loading state check
|
|
288
|
-
// When a human agent has joined, don't disable based on assistantStatus
|
|
289
|
-
const hasHumanAgent = useMemo(() => {
|
|
290
|
-
return needsAgent || messages.some((msg) => msg.senderType === 2);
|
|
291
|
-
}, [needsAgent, messages]);
|
|
292
|
-
|
|
293
|
-
const isLoading = useMemo(() => {
|
|
294
|
-
// If human agent has joined, don't disable file uploads based on assistantStatus
|
|
295
|
-
if (hasHumanAgent) {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
return assistantStatus === 'typing' || assistantStatus === 'loading' || assistantStatus === 'error';
|
|
299
|
-
}, [assistantStatus, hasHumanAgent]);
|
|
300
|
-
|
|
301
|
-
// Memoize the message list to prevent unnecessary re-renders
|
|
302
|
-
const messagesListWithGallery = useMemo(() => {
|
|
303
|
-
return messages.map((message, index) => (
|
|
304
|
-
<MessageComponent
|
|
305
|
-
key={`${message.id}-${index}`}
|
|
306
|
-
message={message}
|
|
307
|
-
index={index}
|
|
308
|
-
messages={messages}
|
|
309
|
-
firstHumanAgentIndex={firstHumanAgentIndex}
|
|
310
|
-
onType={scrollToBottom}
|
|
311
|
-
onImageClick={handleImageClick}
|
|
312
|
-
/>
|
|
313
|
-
));
|
|
314
|
-
}, [messages, firstHumanAgentIndex, scrollToBottom, handleImageClick]);
|
|
315
|
-
|
|
316
|
-
const showInChatReview = !!inChatReviewSessionId && !!onInChatReviewSubmit;
|
|
317
|
-
|
|
318
|
-
return (
|
|
319
|
-
<>
|
|
320
|
-
<div className='babylai:flex-1 babylai:overflow-y-auto babylai:p-4 babylai:h-full'>
|
|
321
|
-
{messagesListWithGallery}
|
|
322
|
-
|
|
323
|
-
{assistantStatus === 'typing' && <TypingIndicator firstHumanAgentIndex={firstHumanAgentIndex} />}
|
|
324
|
-
|
|
325
|
-
{showInChatReview && (
|
|
326
|
-
<>
|
|
327
|
-
<Seperator className="babylai:my-6 babylai:text-black-white-300" />
|
|
328
|
-
<InChatReview
|
|
329
|
-
onSubmit={onInChatReviewSubmit}
|
|
330
|
-
onDone={onInChatReviewDone}
|
|
331
|
-
isSubmitting={isSubmittingReview}
|
|
332
|
-
/>
|
|
333
|
-
</>
|
|
334
|
-
)}
|
|
335
|
-
|
|
336
|
-
<div ref={messagesEndRef} />
|
|
337
|
-
</div>
|
|
338
|
-
|
|
339
|
-
<ChatWindowFooter
|
|
340
|
-
inputMessage={inputMessage}
|
|
341
|
-
handleSendMessage={handleSendMessage}
|
|
342
|
-
setInputMessage={setInputMessage}
|
|
343
|
-
isLoading={isLoading}
|
|
344
|
-
isChatClosed={isChatClosed}
|
|
345
|
-
onEnsureSession={onEnsureSession}
|
|
346
|
-
sessionId={sessionId}
|
|
347
|
-
/>
|
|
348
|
-
|
|
349
|
-
<PoweredBy />
|
|
350
|
-
|
|
351
|
-
{/* Gallery Preview Dialog */}
|
|
352
|
-
{galleryState.isOpen && galleryState.imageUrls.length > 0 && (
|
|
353
|
-
<ImagePreviewDialog
|
|
354
|
-
imageUrls={galleryState.imageUrls}
|
|
355
|
-
initialIndex={galleryState.initialIndex}
|
|
356
|
-
isOpen={galleryState.isOpen}
|
|
357
|
-
onClose={handleCloseGallery}
|
|
358
|
-
alt='Image gallery preview'
|
|
359
|
-
/>
|
|
360
|
-
)}
|
|
361
|
-
</>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
ChatWindow.displayName = 'ChatWindow';
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import LogoIcon from '../../../assets/logo.svg';
|
|
3
|
-
|
|
4
|
-
interface TypingIndicatorProps {
|
|
5
|
-
firstHumanAgentIndex: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const TypingIndicator = React.memo(({ firstHumanAgentIndex }: TypingIndicatorProps) => {
|
|
9
|
-
if (firstHumanAgentIndex !== -1) return null;
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<div className='babylai:mb-4 babylai:flex'>
|
|
13
|
-
<div className='babylai:shrink-0 babylai:me-3'>
|
|
14
|
-
<div className='babylai:w-8 babylai:h-8 babylai:rounded-full babylai:flex babylai:items-center babylai:justify-center babylai:bg-primary'>
|
|
15
|
-
<LogoIcon className='babylai:w-4 babylai:h-4 babylai:text-primary' />
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
<div className='babylai:max-w-[80%] babylai:rounded-2xl babylai:p-4 babylai:bg-card'>
|
|
19
|
-
<p className='babylai:text-sm babylai:text-muted-foreground babylai:m-0 babylai:flex babylai:gap-0.5 babylai:items-center' aria-hidden="true">
|
|
20
|
-
<span className="babylai-typing-dot babylai:inline-block babylai:w-1.5 babylai:h-1.5 babylai:rounded-full babylai:bg-current" />
|
|
21
|
-
<span className="babylai-typing-dot babylai:inline-block babylai:w-1.5 babylai:h-1.5 babylai:rounded-full babylai:bg-current" />
|
|
22
|
-
<span className="babylai-typing-dot babylai:inline-block babylai:w-1.5 babylai:h-1.5 babylai:rounded-full babylai:bg-current" />
|
|
23
|
-
</p>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
TypingIndicator.displayName = 'TypingIndicator';
|
|
30
|
-
|
|
31
|
-
export default TypingIndicator;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import Header from '../options-list-screen/header'
|
|
3
|
-
|
|
4
|
-
interface ChatBotErrorScreenProps {
|
|
5
|
-
onClose: () => void
|
|
6
|
-
error: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const ChatBotErrorScreen: React.FC<ChatBotErrorScreenProps> = (props) => {
|
|
10
|
-
return (
|
|
11
|
-
<div className="babylai:w-full babylai:h-full babylai:bg-secondary babylai:rounded-3xl babylai:shadow-lg babylai:z-50 babylai:flex babylai:flex-col">
|
|
12
|
-
<div className="babylai:h-full babylai:rounded-3xl babylai:flex babylai:flex-col babylai:gap-8">
|
|
13
|
-
<Header onMinimize={props.onClose} />
|
|
14
|
-
<div className="babylai:flex babylai:flex-col babylai:items-center babylai:justify-center babylai:w-full babylai:h-full babylai:px-6 babylai:py-28">
|
|
15
|
-
<div className="babylai:text-secondary-foreground babylai:text-lg">Error: {props.error}</div>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export default ChatBotErrorScreen
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import animatedLogo from '../../../assets/animatedLogo.gif'
|
|
2
|
-
import Header from '../options-list-screen/header'
|
|
3
|
-
|
|
4
|
-
interface ChatBotLoadingScreenProps {
|
|
5
|
-
onClose: () => void
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const ChatBotLoadingScreen: React.FC<ChatBotLoadingScreenProps> = (props) => {
|
|
9
|
-
return (
|
|
10
|
-
<div className="babylai:w-full babylai:h-full babylai:bg-secondary babylai:rounded-3xl babylai:shadow-lg babylai:flex babylai:flex-col">
|
|
11
|
-
<div className="babylai:rounded-3xl babylai:h-full babylai:flex babylai:flex-col babylai:gap-4">
|
|
12
|
-
<Header onMinimize={props.onClose} />
|
|
13
|
-
<div className="babylai:flex babylai:flex-col babylai:items-center babylai:justify-center babylai:w-full babylai:h-full babylai:py-28">
|
|
14
|
-
<img src={animatedLogo} alt="Animated Logo" className="babylai:w-20 babylai:h-20" />
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default ChatBotLoadingScreen
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
interface CompanyCardProps {
|
|
4
|
-
logoUrl?: string;
|
|
5
|
-
name?: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
title?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const CompanyCard: React.FC<CompanyCardProps> = ({
|
|
11
|
-
logoUrl,
|
|
12
|
-
name,
|
|
13
|
-
description,
|
|
14
|
-
title,
|
|
15
|
-
}) => {
|
|
16
|
-
return (
|
|
17
|
-
<section className='babylai:border-b babylai:border-black-white-200'>
|
|
18
|
-
<div className="babylai:flex babylai:items-center babylai:gap-3 babylai:p-6 babylai:mb-6 babylai:rounded-3xl babylai:border babylai:border-black-white-200 babylai:bg-card">
|
|
19
|
-
{logoUrl && (
|
|
20
|
-
<div className="babylai:shrink-0 babylai:w-12 babylai:h-12 babylai:rounded-md babylai:overflow-hidden">
|
|
21
|
-
<img
|
|
22
|
-
src={logoUrl}
|
|
23
|
-
alt={title ? `${title} logo` : 'Company logo'}
|
|
24
|
-
className="babylai:w-full babylai:h-full babylai:object-contain"
|
|
25
|
-
/>
|
|
26
|
-
</div>
|
|
27
|
-
)}
|
|
28
|
-
<div className="babylai:flex-1 babylai:min-w-0 babylai:text-start">
|
|
29
|
-
<h3 className="babylai:text-lg babylai:font-semibold babylai:mb-1 babylai:text-card-foreground" dir="auto">{name}</h3>
|
|
30
|
-
<p className="babylai:text-sm babylai:text-muted-foreground" dir="auto">{description}</p>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
</section>
|
|
34
|
-
);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
CompanyCard.displayName = 'CompanyCard';
|
|
38
|
-
|
|
39
|
-
export default CompanyCard;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import ActionButton from '../chat-window-screen/action-button';
|
|
2
|
-
import IcRoundMinus from '~icons/ic/round-minus'
|
|
3
|
-
|
|
4
|
-
interface OptionsListHeaderProps {
|
|
5
|
-
onMinimize: () => void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const OptionsListHeader: React.FC<OptionsListHeaderProps> = ({
|
|
9
|
-
onMinimize,
|
|
10
|
-
}) => {
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<header className="bg-header babylai:flex babylai:items-center babylai:justify-end babylai:p-6 babylai:border-b babylai:border-black-white-200">
|
|
14
|
-
<ActionButton
|
|
15
|
-
onClick={onMinimize}
|
|
16
|
-
icon={<IcRoundMinus />}
|
|
17
|
-
ariaLabel="Minimize"
|
|
18
|
-
/>
|
|
19
|
-
</header >
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default OptionsListHeader;
|