@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.
Files changed (101) hide show
  1. package/dist/components/shared/Button/button.d.ts +1 -1
  2. package/dist/components/shared/Card/card.d.ts +1 -4
  3. package/dist/components/ui/agent-response/agent-response.d.ts +2 -1
  4. package/dist/index.css +1424 -1
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.esm.js +19194 -38923
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +19198 -38927
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/LanguageContext.d.ts +1 -1
  11. package/dist/lib/custom-hooks/useAblyConnection.d.ts +25 -0
  12. package/dist/lib/custom-hooks/useActionHandler.d.ts +1 -7
  13. package/dist/lib/custom-hooks/useChatSession.d.ts +37 -0
  14. package/dist/lib/custom-hooks/useMessageQueue.d.ts +16 -0
  15. package/dist/lib/custom-hooks/useReview.d.ts +14 -0
  16. package/dist/lib/index.d.ts +1 -2
  17. package/dist/services.d.ts +9 -6
  18. package/dist/services.esm.js +1 -14348
  19. package/dist/services.esm.js.map +1 -1
  20. package/dist/services.js +19 -14344
  21. package/dist/services.js.map +1 -1
  22. package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +1 -1
  23. package/dist/ui/chatbot-popup/chat-window-screen/in-chat-review.d.ts +1 -1
  24. package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +2 -2
  25. package/dist/ui/chatbot-popup/options-list-screen/helpscreen-list.d.ts +1 -1
  26. package/dist/ui/chatbot-popup/options-list-screen/helpscreen-option.d.ts +1 -1
  27. package/dist/ui/chatbot-popup/options-list-screen/index.d.ts +1 -1
  28. package/dist/ui/help-center.d.ts +1 -1
  29. package/dist/ui/help-popup.d.ts +4 -27
  30. package/dist/ui/review-dialog/index.d.ts +1 -1
  31. package/package.json +12 -26
  32. package/postcss.config.js +5 -0
  33. package/rollup.config.mjs +34 -0
  34. package/dist/core/AblyService.d.ts +0 -16
  35. package/dist/core/ApiService.d.ts +0 -16
  36. package/dist/core/api.d.ts +0 -10
  37. package/dist/core/token-service.d.ts +0 -10
  38. package/dist/i18n.d.ts +0 -3
  39. package/dist/lib/config.d.ts +0 -18
  40. package/dist/lib/theme-utils.d.ts +0 -10
  41. package/dist/lib/types.d.ts +0 -145
  42. package/dist/lib/utils.d.ts +0 -2
  43. package/src/assets/animatedLogo.gif +0 -0
  44. package/src/assets/logo.svg +0 -5
  45. package/src/assets/seperator.svg +0 -5
  46. package/src/components/index.ts +0 -1
  47. package/src/components/shared/Button/button.tsx +0 -38
  48. package/src/components/shared/Button/index.ts +0 -1
  49. package/src/components/shared/Card/card.tsx +0 -44
  50. package/src/components/shared/Card/index.ts +0 -1
  51. package/src/components/shared/index.ts +0 -2
  52. package/src/components/ui/agent-response/agent-response.tsx +0 -57
  53. package/src/components/ui/agent-response/doc.md +0 -88
  54. package/src/components/ui/image-attachment.tsx +0 -119
  55. package/src/components/ui/image-preview-dialog.tsx +0 -400
  56. package/src/components/ui/index.ts +0 -3
  57. package/src/core/AblyService.ts +0 -243
  58. package/src/core/ApiService.ts +0 -116
  59. package/src/core/api.ts +0 -278
  60. package/src/core/token-service.ts +0 -35
  61. package/src/globals.css +0 -268
  62. package/src/i18n.ts +0 -21
  63. package/src/index.ts +0 -19
  64. package/src/lib/LanguageContext.tsx +0 -28
  65. package/src/lib/config.ts +0 -52
  66. package/src/lib/custom-hooks/useActionHandler.ts +0 -102
  67. package/src/lib/custom-hooks/useTypewriter.ts +0 -26
  68. package/src/lib/index.ts +0 -4
  69. package/src/lib/theme-utils.ts +0 -56
  70. package/src/lib/types.ts +0 -158
  71. package/src/lib/utils.ts +0 -6
  72. package/src/locales/ar.json +0 -45
  73. package/src/locales/en.json +0 -45
  74. package/src/services.ts +0 -14
  75. package/src/types/icons.d.ts +0 -6
  76. package/src/types/svg.d.ts +0 -5
  77. package/src/types.d.ts +0 -9
  78. package/src/ui/chatbot-popup/active-chat-actions.tsx +0 -39
  79. package/src/ui/chatbot-popup/chat-window-screen/action-button.tsx +0 -37
  80. package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +0 -313
  81. package/src/ui/chatbot-popup/chat-window-screen/header.tsx +0 -53
  82. package/src/ui/chatbot-popup/chat-window-screen/in-chat-review.tsx +0 -116
  83. package/src/ui/chatbot-popup/chat-window-screen/index.tsx +0 -366
  84. package/src/ui/chatbot-popup/chat-window-screen/typing-indicator.tsx +0 -31
  85. package/src/ui/chatbot-popup/error-screen/index.tsx +0 -22
  86. package/src/ui/chatbot-popup/loading-screen/index.tsx +0 -21
  87. package/src/ui/chatbot-popup/options-list-screen/company-card.tsx +0 -39
  88. package/src/ui/chatbot-popup/options-list-screen/header.tsx +0 -23
  89. package/src/ui/chatbot-popup/options-list-screen/helpscreen-intro.tsx +0 -32
  90. package/src/ui/chatbot-popup/options-list-screen/helpscreen-list.tsx +0 -57
  91. package/src/ui/chatbot-popup/options-list-screen/helpscreen-option.tsx +0 -56
  92. package/src/ui/chatbot-popup/options-list-screen/index.tsx +0 -70
  93. package/src/ui/confirmation-modal/index.tsx +0 -62
  94. package/src/ui/floating-message.tsx +0 -29
  95. package/src/ui/help-button.tsx +0 -25
  96. package/src/ui/help-center.tsx +0 -448
  97. package/src/ui/help-popup.tsx +0 -367
  98. package/src/ui/powered-by.tsx +0 -62
  99. package/src/ui/review-dialog/index.tsx +0 -149
  100. package/src/ui/review-dialog/rating.tsx +0 -79
  101. package/src/useLocalTranslation.ts +0 -15
@@ -1,88 +0,0 @@
1
- # AgentResponse Component
2
-
3
- ## Overview
4
-
5
- The `AgentResponse` component renders a chat message in a conversational interface, supporting user and agent messages with optional typewriter animation for agent responses. It uses `react-markdown` to render the message content, allowing Markdown formatting, and applies conditional styling based on the sender type.
6
-
7
- ## Props
8
-
9
- | Prop Name | Type | Required | Description |
10
- |------------------|--------|----------|-----------------------------------------------------------------------------|
11
- | `messageContent` | string | Yes | The content of the message to display, supports Markdown formatting. |
12
- | `senderType` | number | Yes | The type of sender: `1` for user, `2` or `3` for agents (triggers animation). |
13
- | `messageId` | number | Yes | A unique identifier for the message, used to track seen messages. |
14
- | `onType` | () => void | No | A Callback function, used to trigger scroll on typing. |
15
-
16
- ## Features
17
-
18
- - **Conditional Styling**:
19
- - User messages (`senderType === 1`): Rendered with a primary background (`babylai-bg-primary-500`), white text (`!babylai-text-black-white-50`), and aligned to the right (`babylai-self-end`).
20
- - Agent messages (`senderType === 2` or `3`): Rendered with a neutral background (`babylai-bg-black-white-50`).
21
- - **Typewriter Animation**:
22
- - Applied to agent messages (`senderType === 2` or `3`) on first render (when `messageId` is not in `seenMessagesRef`).
23
- - Uses the `useTypewriter` hook with a 20ms delay per character.
24
- - Animation is skipped for subsequent renders of the same `messageId`.
25
- - **Markdown Support**:
26
- - Renders `messageContent` using `react-markdown`.
27
- - Customizes `<p>` tags with classes for consistent styling (`babylai-m-0`, `babylai-leading-6`, `babylai-text-sm`, `babylai-text-right`, `babylai-font-sans`).
28
- - **Seen Message Tracking**:
29
- - Maintains a global `seenMessagesRef` Set to track which `messageId`s have been fully animated.
30
- - Adds `messageId` to `seenMessagesRef` when animation completes (`animatedText === messageContent`).
31
-
32
- ## Usage
33
-
34
- ### Example
35
-
36
- ```tsx
37
- import { AgentResponse } from './agent-response';
38
-
39
- function Chat() {
40
- return (
41
- <div>
42
- {/* User message */}
43
- <AgentResponse
44
- senderType={1}
45
- messageContent="Hello, how can I help?"
46
- messageId={1}
47
- />
48
- {/* Agent message with animation */}
49
- <AgentResponse
50
- senderType={2}
51
- messageContent="Hi! I'm here to assist."
52
- messageId={2}
53
- />
54
- </div>
55
- );
56
- }
57
- ```
58
-
59
- ### Styling
60
-
61
- The component uses the following Tailwind/custom classes:
62
- - Container: `babylai-max-w-[80%] babylai-rounded-2xl babylai-p-4 babylai-text-right`
63
- - User-specific: `babylai-bg-primary-500 !babylai-text-black-white-50 babylai-self-end`
64
- - Agent-specific: `babylai-bg-black-white-50`
65
- - Paragraph: `babylai-m-0 babylai-leading-6 babylai-text-sm babylai-text-right babylai-font-sans`
66
-
67
- Ensure these classes are defined in your CSS (e.g., Tailwind configuration).
68
-
69
- ## Notes
70
-
71
- - **Animation**: The typewriter effect is only applied to agent messages (`senderType` 2 or 3) on their first render. Once the animation completes, the `messageId` is marked as seen, and subsequent renders use the full message.
72
- - **Markdown**: Supports basic Markdown (e.g., bold, italic). Complex Markdown features depend on `react-markdown` capabilities.
73
- - **Performance**: The `seenMessagesRef` is a global `Set`, so ensure `messageId`s are unique to avoid conflicts across instances.
74
-
75
- ## Dependencies
76
-
77
- - `react-markdown`: For rendering Markdown content.
78
- - `@/lib/custom-hooks/useTypewriter`: Custom hook for typewriter animation.
79
-
80
- ## Testing
81
-
82
- The component is tested with Jest and React Testing Library, covering:
83
- - Rendering and styling for user (`senderType=1`) and agent (`senderType=2/3`) messages.
84
- - Animation behavior for unseen and seen messages.
85
- - `seenMessagesRef` updates when animation completes.
86
- - Markdown rendering with custom paragraph styling.
87
-
88
- See `agent-response.test.tsx` for the full test suite.
@@ -1,119 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { presignDownload } from '@/core/api';
3
- import { cn } from '@/lib/utils';
4
- import { useLocalTranslation } from '../../useLocalTranslation';
5
- import { ImagePreviewDialog } from './image-preview-dialog';
6
-
7
- interface ImageAttachmentProps {
8
- fileId?: string; // File ID (for user-sent messages, requires presignDownload)
9
- imageUrl?: string; // Direct URL (for received messages from Ably)
10
- className?: string;
11
- enablePreview?: boolean;
12
- onClick?: () => void;
13
- }
14
-
15
- export const ImageAttachment: React.FC<ImageAttachmentProps> = ({
16
- fileId,
17
- imageUrl: propImageUrl,
18
- className,
19
- enablePreview = true,
20
- onClick,
21
- }) => {
22
- const { i18n } = useLocalTranslation();
23
- const [imageUrl, setImageUrl] = useState<string | null>(propImageUrl || null);
24
- const [loading, setLoading] = useState(!propImageUrl && !!fileId);
25
- const [error, setError] = useState(false);
26
- const [isPreviewOpen, setIsPreviewOpen] = useState(false);
27
-
28
- useEffect(() => {
29
- // If we have a direct URL, use it immediately
30
- if (propImageUrl) {
31
- setImageUrl(propImageUrl);
32
- setLoading(false);
33
- return;
34
- }
35
-
36
- // If we only have a fileId, fetch the URL using presignDownload
37
- if (fileId) {
38
- const fetchImageUrl = async () => {
39
- try {
40
- setLoading(true);
41
- setError(false);
42
- const response = await presignDownload(fileId, i18n.language as 'ar' | 'en');
43
- setImageUrl(response.downloadUrl);
44
- } catch (err) {
45
- setError(true);
46
- } finally {
47
- setLoading(false);
48
- }
49
- };
50
-
51
- fetchImageUrl();
52
- }
53
- }, [fileId, propImageUrl, i18n.language as 'ar' | 'en']);
54
-
55
- const handleImageClick = () => {
56
- if (onClick) {
57
- onClick();
58
- } else if (enablePreview && imageUrl) {
59
- setIsPreviewOpen(true);
60
- }
61
- };
62
-
63
- if (loading) {
64
- return (
65
- <div
66
- className={cn(
67
- 'babylai:flex babylai:items-center babylai:justify-center babylai:bg-black-white-100 babylai:rounded-lg babylai:w-16 babylai:h-16',
68
- className
69
- )}
70
- >
71
- <div className='babylai:animate-pulse babylai:text-xs babylai:text-black-white-500'>...</div>
72
- </div>
73
- );
74
- }
75
-
76
- if (error || !imageUrl) {
77
- return (
78
- <div
79
- className={cn(
80
- 'babylai:flex babylai:items-center babylai:justify-center babylai:bg-black-white-100 babylai:rounded-lg babylai:w-16 babylai:h-16 babylai:border babylai:border-black-white-200',
81
- className
82
- )}
83
- >
84
- <div className='babylai:text-xs babylai:text-black-white-500'>!</div>
85
- </div>
86
- );
87
- }
88
-
89
- return (
90
- <>
91
- <img
92
- src={imageUrl}
93
- alt='Attachment'
94
- className={cn(
95
- 'babylai:w-16 babylai:h-16 babylai:object-cover babylai:rounded-lg babylai:border babylai:border-black-white-200 babylai:max-w-[50px]',
96
- (enablePreview || onClick) && 'babylai:cursor-pointer babylai:hover:opacity-80 babylai:transition-opacity',
97
- className
98
- )}
99
- onError={() => setError(true)}
100
- onClick={handleImageClick}
101
- role={enablePreview || onClick ? 'button' : undefined}
102
- aria-label={enablePreview || onClick ? 'Click to preview image' : undefined}
103
- />
104
- {enablePreview && !onClick && (
105
- <ImagePreviewDialog
106
- imageUrls={[imageUrl]}
107
- initialIndex={0}
108
- isOpen={isPreviewOpen}
109
- onClose={() => setIsPreviewOpen(false)}
110
- alt='Attachment preview'
111
- />
112
- )}
113
- </>
114
- );
115
- };
116
-
117
- ImageAttachment.displayName = 'ImageAttachment';
118
-
119
- export default ImageAttachment;
@@ -1,400 +0,0 @@
1
- import React, { useEffect, useCallback, useRef, useState } from 'react';
2
- import { cn } from '@/lib/utils';
3
- import { Button } from '@/components';
4
- import MaterialSymbolsCloseSmallOutlineRounded from '~icons/material-symbols/close-small-outline-rounded'
5
- import SolarArrowRightBoldDuotone from '~icons/solar/arrow-right-bold-duotone';
6
- import { useLocalTranslation } from '../../useLocalTranslation';
7
-
8
- interface ImagePreviewDialogProps {
9
- imageUrls: string[];
10
- initialIndex: number;
11
- isOpen: boolean;
12
- onClose: () => void;
13
- alt?: string;
14
- }
15
-
16
- export const ImagePreviewDialog: React.FC<ImagePreviewDialogProps> = ({
17
- imageUrls,
18
- initialIndex,
19
- isOpen,
20
- onClose,
21
- alt = 'Preview',
22
- }) => {
23
- const { dir } = useLocalTranslation();
24
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
25
- const [zoomLevel, setZoomLevel] = useState(1);
26
- const [imagePosition, setImagePosition] = useState({ x: 0, y: 0 });
27
- const [isDragging, setIsDragging] = useState(false);
28
- const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
29
- const imageRef = useRef<HTMLImageElement>(null);
30
- const containerRef = useRef<HTMLDivElement>(null);
31
-
32
- const currentImageUrl = imageUrls[currentIndex];
33
- const hasMultipleImages = imageUrls.length > 1;
34
-
35
- // Update current index when initialIndex changes
36
- useEffect(() => {
37
- if (isOpen) {
38
- setCurrentIndex(initialIndex);
39
- setZoomLevel(1);
40
- setImagePosition({ x: 0, y: 0 });
41
- }
42
- }, [initialIndex, isOpen]);
43
-
44
- const handlePrevious = useCallback(() => {
45
- if (currentIndex > 0) {
46
- setCurrentIndex(currentIndex - 1);
47
- setZoomLevel(1);
48
- setImagePosition({ x: 0, y: 0 });
49
- }
50
- }, [currentIndex]);
51
-
52
- const handleNext = useCallback(() => {
53
- if (currentIndex < imageUrls.length - 1) {
54
- setCurrentIndex(currentIndex + 1);
55
- setZoomLevel(1);
56
- setImagePosition({ x: 0, y: 0 });
57
- }
58
- }, [currentIndex, imageUrls.length]);
59
-
60
- const handleZoomIn = useCallback(() => {
61
- setZoomLevel((prev) => Math.min(prev + 0.25, 3));
62
- }, []);
63
-
64
- const handleZoomOut = useCallback(() => {
65
- setZoomLevel((prev) => Math.max(prev - 0.25, 0.5));
66
- }, []);
67
-
68
- const handleResetZoom = useCallback(() => {
69
- setZoomLevel(1);
70
- setImagePosition({ x: 0, y: 0 });
71
- }, []);
72
-
73
- const handleDownload = useCallback(async () => {
74
- if (!currentImageUrl) return;
75
-
76
- try {
77
- // Fetch the image as a blob
78
- const response = await fetch(currentImageUrl);
79
- const blob = await response.blob();
80
-
81
- // Create a temporary URL for the blob
82
- const blobUrl = URL.createObjectURL(blob);
83
-
84
- // Extract filename from URL or use a default
85
- const urlParts = currentImageUrl.split('/');
86
- const filename = urlParts[urlParts.length - 1].split('?')[0] || 'image.png';
87
-
88
- // Create a temporary anchor element and trigger download
89
- const link = document.createElement('a');
90
- link.href = blobUrl;
91
- link.download = filename;
92
- document.body.appendChild(link);
93
- link.click();
94
-
95
- // Cleanup
96
- document.body.removeChild(link);
97
- URL.revokeObjectURL(blobUrl);
98
- } catch (error) {
99
- console.error('Failed to download image:', error);
100
- // Fallback: open in new tab if download fails
101
- window.open(currentImageUrl, '_blank');
102
- }
103
- }, [currentImageUrl]);
104
-
105
- const handleClose = useCallback(() => {
106
- setZoomLevel(1);
107
- setImagePosition({ x: 0, y: 0 });
108
- onClose();
109
- }, [onClose]);
110
-
111
- // Keyboard navigation
112
- useEffect(() => {
113
- if (!isOpen) return;
114
-
115
- const handleKeyDown = (e: KeyboardEvent) => {
116
- if (e.key === 'ArrowLeft') {
117
- e.preventDefault();
118
- if (dir === 'rtl') {
119
- handleNext();
120
- } else {
121
- handlePrevious();
122
- }
123
- } else if (e.key === 'ArrowRight') {
124
- e.preventDefault();
125
- if (dir === 'rtl') {
126
- handlePrevious();
127
- } else {
128
- handleNext();
129
- }
130
- } else if (e.key === 'Escape') {
131
- e.preventDefault();
132
- handleClose();
133
- } else if (e.key === '+' || e.key === '=') {
134
- e.preventDefault();
135
- handleZoomIn();
136
- } else if (e.key === '-') {
137
- e.preventDefault();
138
- handleZoomOut();
139
- } else if (e.key === '0') {
140
- e.preventDefault();
141
- handleResetZoom();
142
- }
143
- };
144
-
145
- window.addEventListener('keydown', handleKeyDown);
146
- return () => window.removeEventListener('keydown', handleKeyDown);
147
- }, [isOpen, currentIndex, imageUrls.length, dir, handlePrevious, handleNext, handleZoomIn, handleZoomOut, handleResetZoom, handleClose]);
148
-
149
- // Mouse wheel zoom
150
- const handleWheel = useCallback(
151
- (e: React.WheelEvent) => {
152
- if (e.ctrlKey || e.metaKey) {
153
- e.preventDefault();
154
- if (e.deltaY < 0) {
155
- handleZoomIn();
156
- } else {
157
- handleZoomOut();
158
- }
159
- }
160
- },
161
- [handleZoomIn, handleZoomOut]
162
- );
163
-
164
- // Drag to pan when zoomed
165
- const handleMouseDown = useCallback(
166
- (e: React.MouseEvent) => {
167
- if (zoomLevel > 1) {
168
- setIsDragging(true);
169
- setDragStart({ x: e.clientX - imagePosition.x, y: e.clientY - imagePosition.y });
170
- }
171
- },
172
- [zoomLevel, imagePosition]
173
- );
174
-
175
- const handleMouseMove = useCallback(
176
- (e: React.MouseEvent) => {
177
- if (isDragging && zoomLevel > 1) {
178
- setImagePosition({
179
- x: e.clientX - dragStart.x,
180
- y: e.clientY - dragStart.y,
181
- });
182
- }
183
- },
184
- [isDragging, zoomLevel, dragStart]
185
- );
186
-
187
- const handleMouseUp = useCallback(() => {
188
- setIsDragging(false);
189
- }, []);
190
-
191
- // Constrain image position when zoomed
192
- useEffect(() => {
193
- if (zoomLevel <= 1) {
194
- setImagePosition({ x: 0, y: 0 });
195
- }
196
- }, [zoomLevel]);
197
-
198
- // Prevent body scroll when dialog is open
199
- useEffect(() => {
200
- if (isOpen) {
201
- document.body.style.overflow = 'hidden';
202
- } else {
203
- document.body.style.overflow = 'unset';
204
- }
205
-
206
- return () => {
207
- document.body.style.overflow = 'unset';
208
- };
209
- }, [isOpen]);
210
-
211
- if (!isOpen || !currentImageUrl) return null;
212
-
213
- return (
214
- <div
215
- className='babylai:fixed babylai:inset-0 babylai:z-9999 babylai:flex babylai:items-center babylai:justify-center'
216
- onClick={handleClose}
217
- role='dialog'
218
- aria-modal='true'
219
- aria-label='Image preview dialog'
220
- >
221
- {/* Backdrop */}
222
- <div className='babylai:absolute babylai:inset-0 babylai:bg-black babylai:bg-opacity-95' />
223
-
224
- {/* Dialog content */}
225
- <div
226
- ref={containerRef}
227
- className='babylai:relative babylai:w-full babylai:h-full babylai:flex babylai:items-center babylai:justify-center babylai:overflow-hidden'
228
- onClick={(e) => e.stopPropagation()}
229
- onWheel={handleWheel}
230
- onMouseDown={handleMouseDown}
231
- onMouseMove={handleMouseMove}
232
- onMouseUp={handleMouseUp}
233
- onMouseLeave={handleMouseUp}
234
- >
235
- {/* Close Button */}
236
- <Button
237
- variant='ghost'
238
- size='icon'
239
- onClick={handleClose}
240
- className={cn(
241
- 'babylai:absolute babylai:top-4 babylai:z-60',
242
- dir === 'rtl' ? 'babylai:left-4' : 'babylai:right-4',
243
- 'babylai:text-white babylai:hover:text-white/80 babylai:hover:bg-white/10',
244
- 'babylai:h-10 babylai:w-10 babylai:rounded-full'
245
- )}
246
- aria-label='Close preview'
247
- type='button'
248
- >
249
- <MaterialSymbolsCloseSmallOutlineRounded className='babylai:w-6 babylai:h-6' />
250
- </Button>
251
-
252
- {/* Navigation Buttons */}
253
- {hasMultipleImages && (
254
- <>
255
- <Button
256
- variant='ghost'
257
- size='icon'
258
- onClick={dir === 'rtl' ? handleNext : handlePrevious}
259
- disabled={dir === 'rtl' ? currentIndex === imageUrls.length - 1 : currentIndex === 0}
260
- className={cn(
261
- 'babylai:absolute babylai:top-1/2 babylai:-translate-y-1/2 babylai:z-60',
262
- dir === 'rtl' ? 'babylai:right-4' : 'babylai:left-4',
263
- 'babylai:text-white babylai:hover:text-white/80 babylai:hover:bg-white/10',
264
- 'babylai:h-12 babylai:w-12 babylai:rounded-full',
265
- 'babylai:disabled:opacity-30'
266
- )}
267
- aria-label='Previous image'
268
- type='button'
269
- >
270
- <SolarArrowRightBoldDuotone className={cn('babylai:w-8 babylai:h-8', dir === 'rtl' ? '' : 'babylai:rotate-180')} />
271
- </Button>
272
- <Button
273
- variant='ghost'
274
- size='icon'
275
- onClick={dir === 'rtl' ? handlePrevious : handleNext}
276
- disabled={dir === 'rtl' ? currentIndex === 0 : currentIndex === imageUrls.length - 1}
277
- className={cn(
278
- 'babylai:absolute babylai:top-1/2 babylai:-translate-y-1/2 babylai:z-60',
279
- dir === 'rtl' ? 'babylai:left-4' : 'babylai:right-4',
280
- 'babylai:text-white babylai:hover:text-white/80 babylai:hover:bg-white/10',
281
- 'babylai:h-12 babylai:w-12 babylai:rounded-full',
282
- 'babylai:disabled:opacity-30'
283
- )}
284
- aria-label='Next image'
285
- type='button'
286
- >
287
- <SolarArrowRightBoldDuotone className={cn('babylai:w-8 babylai:h-8', dir === 'rtl' ? 'babylai:rotate-180' : '')} />
288
- </Button>
289
- </>
290
- )}
291
-
292
- {/* Zoom Controls */}
293
- <div
294
- className={cn(
295
- 'babylai:absolute babylai:bottom-4 babylai:z-60',
296
- 'babylai:flex babylai:items-center babylai:gap-2',
297
- 'babylai:bg-black/50 babylai:backdrop-blur-sm babylai:rounded-lg babylai:p-2',
298
- dir === 'rtl' ? 'babylai:right-1/2 babylai:translate-x-1/2' : 'babylai:left-1/2 babylai:-translate-x-1/2'
299
- )}
300
- >
301
- <Button
302
- variant='ghost'
303
- size='icon'
304
- onClick={handleZoomOut}
305
- disabled={zoomLevel <= 0.5}
306
- className='babylai:text-white babylai:hover:text-white/80 babylai:hover:bg-white/10 babylai:h-9 babylai:w-9 babylai:disabled:opacity-30'
307
- aria-label='Zoom out'
308
- type='button'
309
- >
310
- <svg className='babylai:w-5 babylai:h-5' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
311
- <path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7' />
312
- </svg>
313
- </Button>
314
- <span className='babylai:text-white babylai:text-sm babylai:font-medium babylai:min-w-12 babylai:text-center'>
315
- {Math.round(zoomLevel * 100)}%
316
- </span>
317
- <Button
318
- variant='ghost'
319
- size='icon'
320
- onClick={handleZoomIn}
321
- disabled={zoomLevel >= 3}
322
- className='babylai:text-white babylai:hover:text-white/80 babylai:hover:bg-white/10 babylai:h-9 babylai:w-9 babylai:disabled:opacity-30'
323
- aria-label='Zoom in'
324
- type='button'
325
- >
326
- <svg className='babylai:w-5 babylai:h-5' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
327
- <path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7' />
328
- </svg>
329
- </Button>
330
- {zoomLevel !== 1 && (
331
- <Button
332
- variant='ghost'
333
- size='sm'
334
- onClick={handleResetZoom}
335
- className='babylai:text-white babylai:hover:text-white/80 babylai:hover:bg-white/10 babylai:h-9 babylai:px-3 babylai:ml-2'
336
- aria-label='Reset zoom'
337
- type='button'
338
- >
339
- Reset
340
- </Button>
341
- )}
342
- {/* Download Button */}
343
- <div className='babylai:h-9 babylai:w-px babylai:bg-white/20 babylai:mx-1' />
344
- <Button
345
- variant='ghost'
346
- size='icon'
347
- onClick={handleDownload}
348
- className='babylai:text-white babylai:hover:text-white/80 babylai:hover:bg-white/10 babylai:h-9 babylai:w-9'
349
- aria-label='Download image'
350
- type='button'
351
- >
352
- <svg className='babylai:w-5 babylai:h-5' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
353
- <path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4' />
354
- </svg>
355
- </Button>
356
- </div>
357
-
358
- {/* Image Counter */}
359
- {hasMultipleImages && (
360
- <div
361
- className={cn(
362
- 'babylai:absolute babylai:top-4 babylai:z-60',
363
- 'babylai:bg-black/50 babylai:backdrop-blur-sm babylai:rounded-lg babylai:px-4 babylai:py-2',
364
- dir === 'rtl' ? 'babylai:right-1/2 babylai:translate-x-1/2' : 'babylai:left-1/2 babylai:-translate-x-1/2'
365
- )}
366
- >
367
- <span className='babylai:text-white babylai:text-sm babylai:font-medium'>
368
- {currentIndex + 1} / {imageUrls.length}
369
- </span>
370
- </div>
371
- )}
372
-
373
- {/* Image Container */}
374
- <div
375
- className='babylai:flex babylai:items-center babylai:justify-center'
376
- style={{
377
- transform: `translate(${imagePosition.x}px, ${imagePosition.y}px)`,
378
- cursor: zoomLevel > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default',
379
- }}
380
- >
381
- <img
382
- ref={imageRef}
383
- src={currentImageUrl}
384
- alt={alt}
385
- className='babylai:max-w-[90vw] babylai:max-h-[85vh] babylai:object-contain babylai:select-none'
386
- style={{
387
- transform: `scale(${zoomLevel})`,
388
- transformOrigin: 'center center',
389
- }}
390
- draggable={false}
391
- />
392
- </div>
393
- </div>
394
- </div>
395
- );
396
- };
397
-
398
- ImagePreviewDialog.displayName = 'ImagePreviewDialog';
399
-
400
- export default ImagePreviewDialog;
@@ -1,3 +0,0 @@
1
- export { ImageAttachment } from './image-attachment';
2
- export { ImagePreviewDialog } from './image-preview-dialog';
3
- export { default as AgentResponse } from './agent-response/agent-response';