@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
package/src/lib/types.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
export interface HelpCenterProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
2
|
-
helpScreenId: string;
|
|
3
|
-
primaryColor?: string;
|
|
4
|
-
language?: 'ar' | 'en';
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface Theme {
|
|
8
|
-
primary: string;
|
|
9
|
-
secondary: string;
|
|
10
|
-
text: string;
|
|
11
|
-
background: string;
|
|
12
|
-
error: string;
|
|
13
|
-
success: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface UserData {
|
|
17
|
-
id: string;
|
|
18
|
-
name: string;
|
|
19
|
-
email: string;
|
|
20
|
-
avatar?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface Message {
|
|
24
|
-
id: number;
|
|
25
|
-
senderType: number; // 1: Customer, 2: Agent, 3: AI
|
|
26
|
-
messageContent: string;
|
|
27
|
-
sentAt: Date;
|
|
28
|
-
isSeen: boolean;
|
|
29
|
-
attachmentIds?: string[]; // For user-sent messages (file IDs)
|
|
30
|
-
attachmentUrls?: string[]; // For received messages from Ably (download URLs)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ChatSession {
|
|
34
|
-
id: string;
|
|
35
|
-
messages: Message[];
|
|
36
|
-
participants: UserData[];
|
|
37
|
-
status: 'active' | 'closed';
|
|
38
|
-
createdAt: Date;
|
|
39
|
-
updatedAt: Date;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface ChatSessionData {
|
|
43
|
-
helpScreenId: string;
|
|
44
|
-
user: UserData;
|
|
45
|
-
initialMessage?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface HelpCenterConfig {
|
|
49
|
-
baseUrl: string;
|
|
50
|
-
getToken: () => Promise<{ token: string; expiresIn: number }>;
|
|
51
|
-
helpScreenId: string;
|
|
52
|
-
user?: UserData;
|
|
53
|
-
theme?: Partial<Theme>;
|
|
54
|
-
onMessageReceived?: (message: Message) => void;
|
|
55
|
-
onSessionClosed?: () => void;
|
|
56
|
-
onError?: (error: Error) => void;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface HelpScreenOption {
|
|
60
|
-
id: string;
|
|
61
|
-
title: string;
|
|
62
|
-
paragraphs?: string[];
|
|
63
|
-
nestedOptions?: HelpScreenOption[];
|
|
64
|
-
chatWithUs?: boolean;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface HelpScreen {
|
|
68
|
-
id: string;
|
|
69
|
-
title: string;
|
|
70
|
-
options: HelpScreenOption[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface ApiResponse<T> {
|
|
74
|
-
data: T;
|
|
75
|
-
success: boolean;
|
|
76
|
-
error?: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface TokenResponse {
|
|
80
|
-
token: string;
|
|
81
|
-
expiresIn: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface HelpScreenData {
|
|
85
|
-
id: string;
|
|
86
|
-
tenantId: string;
|
|
87
|
-
tenant: {
|
|
88
|
-
id: string;
|
|
89
|
-
name: string;
|
|
90
|
-
key: string;
|
|
91
|
-
logoUrl: string;
|
|
92
|
-
settings: {
|
|
93
|
-
description: string;
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
title: string;
|
|
97
|
-
description: string;
|
|
98
|
-
options: Option[];
|
|
99
|
-
chatWithUs: boolean;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface Option {
|
|
103
|
-
id: string;
|
|
104
|
-
helpScreenId: string;
|
|
105
|
-
parentOptionId: string | null;
|
|
106
|
-
parentOption: Option | null;
|
|
107
|
-
files: any[];
|
|
108
|
-
nestedOptions: Option[];
|
|
109
|
-
title: string;
|
|
110
|
-
paragraphs: string[];
|
|
111
|
-
chatWithUs: boolean;
|
|
112
|
-
assistantId?: string;
|
|
113
|
-
assistant?: {
|
|
114
|
-
id: string;
|
|
115
|
-
tenantId: string;
|
|
116
|
-
tenant: {
|
|
117
|
-
id: string;
|
|
118
|
-
name: string;
|
|
119
|
-
key: string;
|
|
120
|
-
};
|
|
121
|
-
name: string;
|
|
122
|
-
openAIAssistantId: string;
|
|
123
|
-
greeting: string;
|
|
124
|
-
closing: string;
|
|
125
|
-
};
|
|
126
|
-
hasNestedOptions: boolean;
|
|
127
|
-
order: number;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export interface ReviewProps {
|
|
131
|
-
comment?: string;
|
|
132
|
-
rating: number;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface PresignUploadRequestDto {
|
|
136
|
-
name: string;
|
|
137
|
-
contentType: string;
|
|
138
|
-
sizeBytes: number;
|
|
139
|
-
pathData: {
|
|
140
|
-
type: number;
|
|
141
|
-
chatSessionId: string;
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export interface PresignUploadResponse {
|
|
146
|
-
id: string;
|
|
147
|
-
uploadUrl: string;
|
|
148
|
-
path: string;
|
|
149
|
-
expiresAt: string;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export interface PresignDownloadResponse {
|
|
153
|
-
id: string;
|
|
154
|
-
name: string;
|
|
155
|
-
downloadUrl: string;
|
|
156
|
-
contentType: string;
|
|
157
|
-
expiresAt: string;
|
|
158
|
-
}
|
package/src/lib/utils.ts
DELETED
package/src/locales/ar.json
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"homeSdk": {
|
|
3
|
-
"chatTitle": "تحدث مع",
|
|
4
|
-
"tryBabylAi": "جرّب Babyl AI مجانًا 🎉",
|
|
5
|
-
"contactSdk": "تواصل معنا، دعنا نتحدث!",
|
|
6
|
-
"welcomeMessage": "مرحبًا! 👋 أنا Babyl AI، هنا لمساعدتك.",
|
|
7
|
-
"chatNow": "ابدأ المحادثة",
|
|
8
|
-
"getstarted": "للبدء",
|
|
9
|
-
"pickTopic": "اختر موضوعًا",
|
|
10
|
-
"endChat": "إنهاء المحادثة",
|
|
11
|
-
"needAssistance": "هل تحتاج مساعدة؟ لاتردد في اخباري اضغط هنا",
|
|
12
|
-
"placeholder": "اكتب سؤالك هنا",
|
|
13
|
-
"poweredBy": "مدعوم بواسطة",
|
|
14
|
-
|
|
15
|
-
"ReviewDialog": {
|
|
16
|
-
"title": "نحن نقدر ملاحظاتك!",
|
|
17
|
-
"simple_title": "نود معرفة رأيك!",
|
|
18
|
-
"simple_description": "رأيك يساعدنا على التحسين وتقديم دعم أفضل. يُرجى تقييم تجربتك مع خدمتنا وإخبارنا كيف كان أداؤنا.",
|
|
19
|
-
"description": "رأيك يساعدنا على التحسين وتقديم دعم أفضل. يُرجى تقييم تجربتك مع خدمتنا وإخبارنا كيف كان أداؤنا.",
|
|
20
|
-
"rating_label": "التقييم:",
|
|
21
|
-
"comment_label": "التعليق:",
|
|
22
|
-
"comment_placeholder": "اكتب تعليقك هنا...",
|
|
23
|
-
"submit_button": "إرسال التقييم",
|
|
24
|
-
"rate_button": "تقييم",
|
|
25
|
-
"comment_error": "يجب أن يكون الحقل التعليق نصًا بحد أقصى 500 حرف.",
|
|
26
|
-
"rating_error": "يجب أن يكون التقييم بين 1 و5.",
|
|
27
|
-
"skip_button": "تخطي"
|
|
28
|
-
},
|
|
29
|
-
"InChatReview": {
|
|
30
|
-
"title": "كيف كانت محادثتك معنا اليوم؟",
|
|
31
|
-
"description": "نحب أن نعرف كيف كان أداؤنا - ملاحظاتك تساعدنا على جعل كل محادثة أفضل.",
|
|
32
|
-
"follow_up": "رائع! نحن سعداء أنك استمتعت بالدردشة معنا. هل يمكنك تقييم تجربتك الإجمالية لمساعدتنا على الحفاظ على ذلك؟",
|
|
33
|
-
"note_placeholder": "اكتب ملاحظتك",
|
|
34
|
-
"submit_button": "تقديم"
|
|
35
|
-
},
|
|
36
|
-
"ConfirmationModal": {
|
|
37
|
-
"title": "تريد المغادرة؟ 👋",
|
|
38
|
-
"message": "لا تقلق، يمكنك العودة في أي وقت. نحن دائما هنا إذا كنت تحتاج مساعدة أو لديك أسئلة.",
|
|
39
|
-
"confirmation_button": "إغلاق المحادثة",
|
|
40
|
-
"cancel_button": "استمرار",
|
|
41
|
-
"endAndStartNewChatTitle": "إنهاء وبدء محادثة جديدة",
|
|
42
|
-
"endAndStartNewChatMessage": "هل أنت متأكد أنك تريد إنهاء هذه المحادثة وبدء محادثة جديدة؟"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
package/src/locales/en.json
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"homeSdk": {
|
|
3
|
-
"chatTitle": "Chat with",
|
|
4
|
-
"tryBabylAi": "Try Babyl AI for Free 🎉",
|
|
5
|
-
"contactSdk": "Contact us, Let's Talk!",
|
|
6
|
-
"welcomeMessage": "Hey there! 👋 I'm Babyl AI, here to assist you.",
|
|
7
|
-
"chatNow": "Chat Now",
|
|
8
|
-
"getstarted": "Get Started",
|
|
9
|
-
"pickTopic": "Pick a Topic to",
|
|
10
|
-
"endChat": "End Chat",
|
|
11
|
-
"needAssistance": "Need assistance Or You want to try the Product? Click here",
|
|
12
|
-
"placeholder": "write your message here...",
|
|
13
|
-
"poweredBy": "Powered by",
|
|
14
|
-
|
|
15
|
-
"ReviewDialog": {
|
|
16
|
-
"title": "We’d Love Your Feedback!",
|
|
17
|
-
"simple_title": "We'd Love Your Feedback!",
|
|
18
|
-
"simple_description": "Your opinion helps us improve and deliver better support. Please rate your experience with our service and let us know how we did.",
|
|
19
|
-
"description": "Your opinion helps us improve and deliver better support. Please rate your experience with our service and let us know how we did.",
|
|
20
|
-
"rating_label": "Rating:",
|
|
21
|
-
"comment_label": "Comment:",
|
|
22
|
-
"comment_placeholder": "Write your comment here...",
|
|
23
|
-
"submit_button": "Submit Review",
|
|
24
|
-
"rate_button": "Rate",
|
|
25
|
-
"comment_error": "The field Comment must be a string with a maximum length of 500.",
|
|
26
|
-
"rating_error": "Rating must be between 1 and 5.",
|
|
27
|
-
"skip_button": "Skip"
|
|
28
|
-
},
|
|
29
|
-
"InChatReview": {
|
|
30
|
-
"title": "How was your conversation with us today?",
|
|
31
|
-
"description": "We'd love to know how we did - your feedback helps us make every chat even better.",
|
|
32
|
-
"follow_up": "Awesome! We're glad you enjoyed chatting with us. Could you rate your overall experience to help us keep it that way?",
|
|
33
|
-
"note_placeholder": "Write Your Note",
|
|
34
|
-
"submit_button": "Submit Review"
|
|
35
|
-
},
|
|
36
|
-
"ConfirmationModal": {
|
|
37
|
-
"title": "Leaving so soon? 👋",
|
|
38
|
-
"message": "Don't worry, you can come back anytime. We're always here if you need help or have questions.",
|
|
39
|
-
"confirmation_button": "Close Chat",
|
|
40
|
-
"cancel_button": "Continue",
|
|
41
|
-
"endAndStartNewChatTitle": "End and Start New Chat",
|
|
42
|
-
"endAndStartNewChatMessage": "Are you sure you want to end the current conversation and start a new one?"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
package/src/services.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Services export file - separate from main component exports
|
|
2
|
-
// This prevents Fast Refresh issues with non-React components
|
|
3
|
-
|
|
4
|
-
// Re-export services for backward compatibility
|
|
5
|
-
export { ApiService } from './core/ApiService';
|
|
6
|
-
export { ClientAblyService as AblyService } from './core/AblyService';
|
|
7
|
-
export { TokenService } from './core/token-service';
|
|
8
|
-
|
|
9
|
-
// Re-export API functions
|
|
10
|
-
export { initializeAPI, getValidToken, apiRequest } from './core/api';
|
|
11
|
-
|
|
12
|
-
// Re-export types that might be needed with services
|
|
13
|
-
export type { TokenResponse } from './lib/types';
|
|
14
|
-
export * from './lib/types';
|
package/src/types/icons.d.ts
DELETED
package/src/types/svg.d.ts
DELETED
package/src/types.d.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Button } from '@/components';
|
|
2
|
-
import { useLocalTranslation } from '@/useLocalTranslation';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import SolarPlain2BoldDuotone from '~icons/solar/plain-2-bold-duotone';
|
|
5
|
-
|
|
6
|
-
interface ActiveChatActionsProps {
|
|
7
|
-
onConfirm: () => void;
|
|
8
|
-
onCancel: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const ActiveChatActions: React.FC<ActiveChatActionsProps> = ({
|
|
12
|
-
onConfirm,
|
|
13
|
-
onCancel,
|
|
14
|
-
}) => {
|
|
15
|
-
const { t } = useLocalTranslation();
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<section className="babylai:flex babylai:justify-between babylai:gap-3 babylai:px-4 babylai:py-6 babylai:absolute babylai:left-0 babylai:right-0 babylai:bottom-11 babylai:z-20 babylai:bg-linear-to-t babylai:from-card babylai:to-transparent babylai:from-[28.32%] babylai:to-[112.59%]">
|
|
19
|
-
<Button
|
|
20
|
-
onClick={onConfirm}
|
|
21
|
-
variant='secondary'
|
|
22
|
-
>
|
|
23
|
-
{t('homeSdk.ConfirmationModal.confirmation_button')}
|
|
24
|
-
|
|
25
|
-
</Button>
|
|
26
|
-
<Button
|
|
27
|
-
onClick={onCancel}
|
|
28
|
-
variant='default'
|
|
29
|
-
>
|
|
30
|
-
{t('homeSdk.ConfirmationModal.cancel_button')}
|
|
31
|
-
<SolarPlain2BoldDuotone className="babylai:w-6 babylai:h-6" />
|
|
32
|
-
</Button>
|
|
33
|
-
</section>
|
|
34
|
-
);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
ActiveChatActions.displayName = 'ActiveChatActions';
|
|
38
|
-
|
|
39
|
-
export default ActiveChatActions;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { cn } from '@/lib/utils';
|
|
3
|
-
|
|
4
|
-
interface ActionButtonProps {
|
|
5
|
-
onClick: () => void;
|
|
6
|
-
icon?: React.ReactNode;
|
|
7
|
-
children?: React.ReactNode;
|
|
8
|
-
ariaLabel?: string;
|
|
9
|
-
className?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const ActionButton: React.FC<ActionButtonProps> = ({
|
|
13
|
-
onClick,
|
|
14
|
-
icon,
|
|
15
|
-
children,
|
|
16
|
-
ariaLabel,
|
|
17
|
-
className,
|
|
18
|
-
}) => {
|
|
19
|
-
return (
|
|
20
|
-
<button
|
|
21
|
-
onClick={onClick}
|
|
22
|
-
className={cn(
|
|
23
|
-
'babylai:bg-card babylai:text-card-foreground babylai:h-6 babylai:w-8 babylai:rounded-full babylai:flex babylai:items-center babylai:justify-center babylai:font-bold babylai:text-base babylai:cursor-pointer babylai:border-0',
|
|
24
|
-
className
|
|
25
|
-
)}
|
|
26
|
-
aria-label={ariaLabel}
|
|
27
|
-
type="button"
|
|
28
|
-
>
|
|
29
|
-
{icon}
|
|
30
|
-
{children}
|
|
31
|
-
</button>
|
|
32
|
-
);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
ActionButton.displayName = 'ActionButton';
|
|
36
|
-
|
|
37
|
-
export default ActionButton;
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useState, useCallback } from 'react';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import { ImagePreviewDialog } from '@/components/ui';
|
|
4
|
-
import MaterialSymbolsCloseSmallOutlineRounded from '~icons/material-symbols/close-small-outline-rounded'
|
|
5
|
-
import { useLocalTranslation } from '../../../useLocalTranslation';
|
|
6
|
-
import { presignUpload } from '@/core/api';
|
|
7
|
-
import SolarPlain2BoldDuotone from '~icons/solar/plain-2-bold-duotone'
|
|
8
|
-
import SolarPaperclipBoldDuotone from '~icons/solar/paperclip-bold-duotone'
|
|
9
|
-
|
|
10
|
-
interface SelectedFileDto {
|
|
11
|
-
file: File;
|
|
12
|
-
previewUrl: string;
|
|
13
|
-
uploading: boolean;
|
|
14
|
-
uploadedId: string | null;
|
|
15
|
-
error: string | null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ChatWindowFooterProps {
|
|
19
|
-
inputMessage: string;
|
|
20
|
-
setInputMessage: (e: string) => void;
|
|
21
|
-
handleSendMessage: (attachmentIds: string[]) => void;
|
|
22
|
-
isLoading: boolean;
|
|
23
|
-
isChatClosed?: boolean;
|
|
24
|
-
onEnsureSession: () => Promise<string>;
|
|
25
|
-
sessionId?: string | null; // Pass existing sessionId to avoid creating new sessions
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const ChatWindowFooter: React.FC<ChatWindowFooterProps> = (props) => {
|
|
29
|
-
const { t, i18n } = useLocalTranslation();
|
|
30
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
31
|
-
const [selectedFiles, setSelectedFiles] = useState<SelectedFileDto[]>([]);
|
|
32
|
-
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
|
33
|
-
const [isSending, setIsSending] = useState(false);
|
|
34
|
-
|
|
35
|
-
const handleAttachClick = useCallback(() => {
|
|
36
|
-
fileInputRef.current?.click();
|
|
37
|
-
}, []);
|
|
38
|
-
|
|
39
|
-
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
40
|
-
const files = Array.from(e.target.files || []);
|
|
41
|
-
|
|
42
|
-
// Validate that all files are images
|
|
43
|
-
const imageFiles = files.filter((file) => file.type.startsWith('image/'));
|
|
44
|
-
|
|
45
|
-
// Create preview URLs and add to selected files (don't upload yet)
|
|
46
|
-
const newFiles: SelectedFileDto[] = imageFiles.map((file) => ({
|
|
47
|
-
file,
|
|
48
|
-
previewUrl: URL.createObjectURL(file),
|
|
49
|
-
uploading: false,
|
|
50
|
-
uploadedId: null,
|
|
51
|
-
error: null,
|
|
52
|
-
}));
|
|
53
|
-
|
|
54
|
-
setSelectedFiles((prev) => [...prev, ...newFiles]);
|
|
55
|
-
|
|
56
|
-
// Clear the input
|
|
57
|
-
if (fileInputRef.current) {
|
|
58
|
-
fileInputRef.current.value = '';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Don't upload files immediately - wait for send button click
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
// Removed handleUploadFiles - files are now uploaded in handleSendMessageWithAttachments
|
|
65
|
-
|
|
66
|
-
const handleRemoveFile = useCallback((previewUrl: string) => {
|
|
67
|
-
setSelectedFiles((prev) => {
|
|
68
|
-
const fileToRemove = prev.find((f) => f.previewUrl === previewUrl);
|
|
69
|
-
if (fileToRemove) {
|
|
70
|
-
URL.revokeObjectURL(fileToRemove.previewUrl);
|
|
71
|
-
}
|
|
72
|
-
return prev.filter((f) => f.previewUrl !== previewUrl);
|
|
73
|
-
});
|
|
74
|
-
}, []);
|
|
75
|
-
|
|
76
|
-
const handleSendMessageWithAttachments = useCallback(async () => {
|
|
77
|
-
// Prevent sending if already loading or chat was closed from chat screen
|
|
78
|
-
if (props.isLoading || props.isChatClosed || isSending) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
setIsSending(true);
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
// Get files that need to be uploaded (those without uploadedId)
|
|
86
|
-
const filesToUpload = selectedFiles.filter((f) => f.uploadedId === null && !f.error);
|
|
87
|
-
const alreadyUploadedIds = selectedFiles.filter((f) => f.uploadedId !== null).map((f) => f.uploadedId as string);
|
|
88
|
-
|
|
89
|
-
// Declare uploadedIds outside the if block so it's accessible later
|
|
90
|
-
let uploadedIds: string[] = [];
|
|
91
|
-
|
|
92
|
-
// If there are files to upload, upload them first
|
|
93
|
-
if (filesToUpload.length > 0) {
|
|
94
|
-
// Get session ID - ensure session exists if needed (for image-only messages)
|
|
95
|
-
let sessionId: string | null = null;
|
|
96
|
-
try {
|
|
97
|
-
// Use existing sessionId if available, otherwise ensure session is created
|
|
98
|
-
if (props.sessionId) {
|
|
99
|
-
sessionId = props.sessionId;
|
|
100
|
-
} else {
|
|
101
|
-
// Ensure session exists before uploading files (allows starting chat with image only)
|
|
102
|
-
sessionId = await props.onEnsureSession();
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error('[ChatWindowFooter] Failed to get sessionId for file upload:', error);
|
|
106
|
-
// Mark all files as error
|
|
107
|
-
setSelectedFiles((prev) =>
|
|
108
|
-
prev.map((f) =>
|
|
109
|
-
filesToUpload.some((ftl) => ftl.previewUrl === f.previewUrl)
|
|
110
|
-
? { ...f, error: 'Failed to initialize session', uploading: false }
|
|
111
|
-
: f
|
|
112
|
-
)
|
|
113
|
-
);
|
|
114
|
-
setIsSending(false);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Upload each file and collect uploaded IDs
|
|
119
|
-
uploadedIds = [];
|
|
120
|
-
let hasUploadErrors = false;
|
|
121
|
-
|
|
122
|
-
for (const fileDto of filesToUpload) {
|
|
123
|
-
try {
|
|
124
|
-
// Mark as uploading
|
|
125
|
-
setSelectedFiles((prev) =>
|
|
126
|
-
prev.map((f) => (f.previewUrl === fileDto.previewUrl ? { ...f, uploading: true, error: null } : f))
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
// Get presigned URL
|
|
130
|
-
const presignResponse = await presignUpload(sessionId, fileDto.file, i18n.language as 'ar' | 'en');
|
|
131
|
-
|
|
132
|
-
// Upload file to presigned URL using axios
|
|
133
|
-
const uploadResponse = await axios.put(presignResponse.uploadUrl, fileDto.file, {
|
|
134
|
-
headers: {
|
|
135
|
-
'Content-Type': fileDto.file.type,
|
|
136
|
-
},
|
|
137
|
-
onUploadProgress: () => {
|
|
138
|
-
// Upload progress tracking (silent)
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
|
|
143
|
-
throw new Error(`Upload failed with status ${uploadResponse.status}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Collect uploaded ID
|
|
147
|
-
uploadedIds.push(presignResponse.id);
|
|
148
|
-
|
|
149
|
-
// Update with uploaded ID
|
|
150
|
-
setSelectedFiles((prev) =>
|
|
151
|
-
prev.map((f) =>
|
|
152
|
-
f.previewUrl === fileDto.previewUrl
|
|
153
|
-
? { ...f, uploading: false, uploadedId: presignResponse.id, error: null }
|
|
154
|
-
: f
|
|
155
|
-
)
|
|
156
|
-
);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error('[ChatWindowFooter] File upload failed:', error);
|
|
159
|
-
hasUploadErrors = true;
|
|
160
|
-
setSelectedFiles((prev) =>
|
|
161
|
-
prev.map((f) =>
|
|
162
|
-
f.previewUrl === fileDto.previewUrl
|
|
163
|
-
? { ...f, uploading: false, error: 'Upload failed', uploadedId: null }
|
|
164
|
-
: f
|
|
165
|
-
)
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// If any uploads failed, don't send the message
|
|
171
|
-
if (hasUploadErrors) {
|
|
172
|
-
console.error('[ChatWindowFooter] Some files failed to upload, not sending message');
|
|
173
|
-
setIsSending(false);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Get all successfully uploaded file IDs (already uploaded + newly uploaded)
|
|
179
|
-
// Use uploadedIds from the upload loop instead of reading from state
|
|
180
|
-
const allAttachmentIds = [...alreadyUploadedIds, ...uploadedIds];
|
|
181
|
-
|
|
182
|
-
// Call the original send message with attachment IDs
|
|
183
|
-
props.handleSendMessage(allAttachmentIds);
|
|
184
|
-
|
|
185
|
-
// Clear selected files and revoke URLs
|
|
186
|
-
selectedFiles.forEach((f) => URL.revokeObjectURL(f.previewUrl));
|
|
187
|
-
setSelectedFiles([]);
|
|
188
|
-
setIsSending(false);
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.error('[ChatWindowFooter] Error sending message:', error);
|
|
191
|
-
setIsSending(false);
|
|
192
|
-
}
|
|
193
|
-
}, [selectedFiles, props, i18n.language as 'ar' | 'en', isSending]);
|
|
194
|
-
|
|
195
|
-
// Check if any files are currently uploading
|
|
196
|
-
const hasUploadingFiles = selectedFiles.some((f) => f.uploading);
|
|
197
|
-
|
|
198
|
-
// Check if there are files with errors
|
|
199
|
-
const hasFileErrors = selectedFiles.some((f) => f.error !== null);
|
|
200
|
-
|
|
201
|
-
// Allow sending if there's text OR files selected (files will be uploaded on send)
|
|
202
|
-
const hasContentToSend = props.inputMessage.trim() !== '' || selectedFiles.length > 0;
|
|
203
|
-
const isSendDisabled =
|
|
204
|
-
props.isLoading || props.isChatClosed || isSending || !hasContentToSend || hasUploadingFiles || hasFileErrors;
|
|
205
|
-
const showLoading = props.isLoading || isSending || hasUploadingFiles;
|
|
206
|
-
|
|
207
|
-
const handleKeyDown = useCallback(
|
|
208
|
-
(e: React.KeyboardEvent) => {
|
|
209
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
210
|
-
e.preventDefault();
|
|
211
|
-
if (!isSendDisabled) {
|
|
212
|
-
handleSendMessageWithAttachments();
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
[isSendDisabled, handleSendMessageWithAttachments]
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
return (
|
|
220
|
-
<footer className='babylai:flex babylai:flex-col babylai:gap-2 babylai:mx-4 babylai:mb-2'>
|
|
221
|
-
{selectedFiles.length > 0 && (
|
|
222
|
-
<div className='babylai:flex babylai:gap-2 babylai:flex-wrap babylai:p-2 babylai:bg-card babylai:rounded-lg'>
|
|
223
|
-
{selectedFiles.map((file) => (
|
|
224
|
-
<div key={file.previewUrl} className='babylai:relative babylai:group'>
|
|
225
|
-
<img
|
|
226
|
-
src={file.previewUrl}
|
|
227
|
-
alt='Preview'
|
|
228
|
-
className='babylai:w-16 babylai:h-16 babylai:object-cover babylai:rounded-lg babylai:border babylai:border-black-white-200 babylai:cursor-pointer babylai:hover:opacity-80 babylai:transition-opacity'
|
|
229
|
-
onClick={() => setPreviewImage(file.previewUrl)}
|
|
230
|
-
role='button'
|
|
231
|
-
aria-label='Click to preview image'
|
|
232
|
-
/>
|
|
233
|
-
{file.uploading && (
|
|
234
|
-
<div className='babylai:absolute babylai:inset-0 babylai:flex babylai:items-center babylai:justify-center babylai:bg-black babylai:bg-opacity-50 babylai:rounded-lg'>
|
|
235
|
-
<div className='babylai:animate-spin babylai:rounded-full babylai:h-6 babylai:w-6 babylai:border-2 babylai:border-white babylai:border-t-transparent'></div>
|
|
236
|
-
</div>
|
|
237
|
-
)}
|
|
238
|
-
{file.error && (
|
|
239
|
-
<div className='babylai:absolute babylai:inset-0 babylai:flex babylai:items-center babylai:justify-center babylai:bg-red-500 babylai:bg-opacity-70 babylai:rounded-lg'>
|
|
240
|
-
<span className='babylai:text-white babylai:text-xs'>Error</span>
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
243
|
-
<button
|
|
244
|
-
onClick={() => handleRemoveFile(file.previewUrl)}
|
|
245
|
-
className='babylai:border-0 babylai:p-0 babylai:absolute babylai:-top-2 babylai:-right-2 babylai:bg-destructive babylai:text-white babylai:rounded-full babylai:w-5 babylai:h-5 babylai:flex babylai:items-center babylai:justify-center babylai:cursor-pointer'
|
|
246
|
-
type='button'
|
|
247
|
-
aria-label='Remove image'
|
|
248
|
-
>
|
|
249
|
-
<MaterialSymbolsCloseSmallOutlineRounded className='babylai:w-3 babylai:h-3' />
|
|
250
|
-
</button>
|
|
251
|
-
</div>
|
|
252
|
-
))}
|
|
253
|
-
</div>
|
|
254
|
-
)}
|
|
255
|
-
|
|
256
|
-
<div className='babylai:flex babylai:items-center babylai:gap-2 babylai:relative babylai:rounded-full babylai:bg-card babylai:py-3 babylai:px-4'>
|
|
257
|
-
<input
|
|
258
|
-
type='file'
|
|
259
|
-
ref={fileInputRef}
|
|
260
|
-
onChange={handleFileSelect}
|
|
261
|
-
accept='image/*'
|
|
262
|
-
multiple
|
|
263
|
-
className='babylai:hidden'
|
|
264
|
-
/>
|
|
265
|
-
<div className='babylai:border-e babylai:border-border babylai:pe-2'>
|
|
266
|
-
<button
|
|
267
|
-
onClick={handleAttachClick}
|
|
268
|
-
disabled={props.isLoading || props.isChatClosed}
|
|
269
|
-
className='babylai:flex babylai:items-center babylai:justify-center babylai:border-0 babylai:rounded-full babylai:w-8 babylai:h-8 babylai:cursor-pointer babylai:bg-secondary babylai:text-muted-foreground babylai:hover:text-primary-500 babylai:transition-colors babylai:disabled:opacity-50 babylai:disabled:cursor-not-allowed'
|
|
270
|
-
type='button'
|
|
271
|
-
aria-label='Attach image'
|
|
272
|
-
>
|
|
273
|
-
<SolarPaperclipBoldDuotone className='babylai:w-4 babylai:h-4' />
|
|
274
|
-
</button>
|
|
275
|
-
</div>
|
|
276
|
-
<input
|
|
277
|
-
type='text'
|
|
278
|
-
value={props.inputMessage}
|
|
279
|
-
onChange={(e) => props.setInputMessage(e.target.value)}
|
|
280
|
-
onKeyDown={handleKeyDown}
|
|
281
|
-
placeholder={t('homeSdk.placeholder')}
|
|
282
|
-
disabled={props.isChatClosed}
|
|
283
|
-
className='babylai:flex-1 babylai:py-2 babylai:px-2 babylai:bg-transparent babylai:outline-none babylai:text-sm babylai:border-none babylai:text-card-foreground babylai:disabled:opacity-50 babylai:disabled:cursor-not-allowed'
|
|
284
|
-
/>
|
|
285
|
-
<button
|
|
286
|
-
onClick={handleSendMessageWithAttachments}
|
|
287
|
-
disabled={isSendDisabled || props.isChatClosed}
|
|
288
|
-
className='babylai:border-0 babylai:rounded-full babylai:bg-primary-500 babylai:hover:bg-primary-600 babylai:w-8 babylai:h-8 babylai:p-0 babylai:flex babylai:items-center babylai:justify-center babylai:disabled:opacity-50 babylai:text-white babylai:cursor-pointer babylai:disabled:cursor-not-allowed'
|
|
289
|
-
type='button'
|
|
290
|
-
>
|
|
291
|
-
{showLoading ? (
|
|
292
|
-
<div className='babylai:inline-block babylai:animate-spin babylai:rounded-full babylai:h-4 babylai:w-4 babylai:aspect-square babylai:border-2 babylai:border-white babylai:border-t-transparent babylai:box-border'></div>
|
|
293
|
-
) : (
|
|
294
|
-
<SolarPlain2BoldDuotone className="babylai:w-4 babylai:h-4" />
|
|
295
|
-
)}
|
|
296
|
-
</button>
|
|
297
|
-
</div>
|
|
298
|
-
|
|
299
|
-
{/* Image Preview Dialog */}
|
|
300
|
-
{previewImage && (
|
|
301
|
-
<ImagePreviewDialog
|
|
302
|
-
imageUrls={[previewImage]}
|
|
303
|
-
initialIndex={0}
|
|
304
|
-
isOpen={!!previewImage}
|
|
305
|
-
onClose={() => setPreviewImage(null)}
|
|
306
|
-
alt='Image preview'
|
|
307
|
-
/>
|
|
308
|
-
)}
|
|
309
|
-
</footer>
|
|
310
|
-
);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
export default ChatWindowFooter;
|