@katechat/ui 1.0.2 → 1.0.3

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 (58) hide show
  1. package/dist/cjs/index.css +491 -0
  2. package/dist/cjs/index.css.map +7 -0
  3. package/dist/cjs/index.js +75305 -0
  4. package/dist/cjs/index.js.map +7 -0
  5. package/dist/esm/index.css +491 -0
  6. package/dist/esm/index.css.map +7 -0
  7. package/dist/esm/index.js +75304 -0
  8. package/dist/esm/index.js.map +7 -0
  9. package/dist/index.css +1 -0
  10. package/dist/index.js +539 -0
  11. package/dist/types/tsconfig.tsbuildinfo +1 -0
  12. package/package.json +27 -4
  13. package/.prettierrc +0 -9
  14. package/esbuild.js +0 -56
  15. package/jest.config.js +0 -24
  16. package/postcss.config.cjs +0 -14
  17. package/src/__mocks__/fileMock.js +0 -1
  18. package/src/__mocks__/styleMock.js +0 -1
  19. package/src/components/chat/ChatMessagesContainer.module.scss +0 -77
  20. package/src/components/chat/ChatMessagesContainer.tsx +0 -151
  21. package/src/components/chat/ChatMessagesList.tsx +0 -216
  22. package/src/components/chat/index.ts +0 -4
  23. package/src/components/chat/input/ChatInput.module.scss +0 -113
  24. package/src/components/chat/input/ChatInput.tsx +0 -259
  25. package/src/components/chat/input/index.ts +0 -1
  26. package/src/components/chat/message/ChatMessage.Carousel.module.scss +0 -7
  27. package/src/components/chat/message/ChatMessage.module.scss +0 -378
  28. package/src/components/chat/message/ChatMessage.tsx +0 -271
  29. package/src/components/chat/message/ChatMessagePreview.tsx +0 -22
  30. package/src/components/chat/message/LinkedChatMessage.tsx +0 -64
  31. package/src/components/chat/message/MessageStatus.tsx +0 -38
  32. package/src/components/chat/message/controls/CopyMessageButton.tsx +0 -32
  33. package/src/components/chat/message/index.ts +0 -4
  34. package/src/components/icons/ProviderIcon.tsx +0 -49
  35. package/src/components/icons/index.ts +0 -1
  36. package/src/components/index.ts +0 -3
  37. package/src/components/modal/ImagePopup.tsx +0 -97
  38. package/src/components/modal/index.ts +0 -1
  39. package/src/controls/FileDropzone/FileDropzone.module.scss +0 -15
  40. package/src/controls/FileDropzone/FileDropzone.tsx +0 -120
  41. package/src/controls/index.ts +0 -1
  42. package/src/core/ai.ts +0 -1
  43. package/src/core/index.ts +0 -4
  44. package/src/core/message.ts +0 -59
  45. package/src/core/model.ts +0 -23
  46. package/src/core/user.ts +0 -8
  47. package/src/hooks/index.ts +0 -2
  48. package/src/hooks/useIntersectionObserver.ts +0 -24
  49. package/src/hooks/useTheme.tsx +0 -66
  50. package/src/index.ts +0 -5
  51. package/src/lib/__tests__/markdown.parser.test.ts +0 -289
  52. package/src/lib/__tests__/markdown.parser.testUtils.ts +0 -31
  53. package/src/lib/__tests__/markdown.parser_sanitizeUrl.test.ts +0 -130
  54. package/src/lib/assert.ts +0 -14
  55. package/src/lib/markdown.parser.ts +0 -189
  56. package/src/setupTests.ts +0 -1
  57. package/src/types/scss.d.ts +0 -4
  58. package/tsconfig.json +0 -26
@@ -1,113 +0,0 @@
1
- [data-mantine-color-scheme="light"] {
2
- .chatControlsContainer {
3
- .chatControls {
4
- border: 1px solid var(--app-shell-border-color);
5
- }
6
-
7
- .headerRow {
8
- background-color: var(--mantine-color-gray-4);
9
- }
10
-
11
- .chatInputGroup .chatInput :global(textarea) {
12
- border: 1px solid var(--app-shell-border-color);
13
- }
14
- }
15
- }
16
-
17
- .chatControlsContainer {
18
- display: flex;
19
- flex-direction: column;
20
- align-items: stretch;
21
- justify-content: center;
22
-
23
- &.hidden {
24
- display: none;
25
- }
26
-
27
- &.fullHeight {
28
- flex-grow: 1;
29
- padding-bottom: 15rem;
30
- }
31
-
32
- .chatControls {
33
- border: 1px solid var(--app-shell-border-color);
34
- border-radius: var(--mantine-radius-default);
35
- background-color: var(--app-shell-border-color);
36
- }
37
-
38
- .headerRow {
39
- padding: 0.33rem;
40
- border-top-left-radius: var(--mantine-radius-default);
41
- border-top-right-radius: var(--mantine-radius-default);
42
- }
43
- }
44
-
45
- .chatInputContainer {
46
- display: flex;
47
- flex-direction: row;
48
- align-items: flex-start;
49
- padding: 0.5rem;
50
- gap: 0.5rem;
51
-
52
- background-color: var(--mantine-color-body);
53
- border-radius: var(--mantine-radius-default);
54
-
55
- .documentsInput {
56
- display: flex;
57
- flex-direction: row;
58
- gap: 0.25rem;
59
- align-items: stretch;
60
- }
61
-
62
- &.columned {
63
- flex-direction: column;
64
- align-items: flex-start;
65
- justify-items: stretch;
66
-
67
- .chatInputGroup {
68
- align-self: stretch;
69
- }
70
- }
71
-
72
- .filesList {
73
- display: flex;
74
- flex-direction: row;
75
- gap: 0.5rem;
76
-
77
- .previewImage {
78
- position: relative;
79
- background-color: var(--input-bg);
80
- height: 64px;
81
- border-radius: var(--mantine-radius-default);
82
-
83
- :global img {
84
- max-width: 64px;
85
- max-height: 64px;
86
- object-fit: cover;
87
- border-radius: var(--mantine-radius-default);
88
- }
89
-
90
- .removeButton {
91
- position: absolute;
92
- top: 0.1rem;
93
- right: 0.1rem;
94
- border-radius: 50%;
95
- }
96
- }
97
- }
98
-
99
- .chatInputGroup {
100
- flex-grow: 1;
101
- display: flex;
102
- flex-direction: row;
103
- gap: 0.5rem;
104
-
105
- .chatInput {
106
- flex-grow: 1;
107
-
108
- :global(textarea) {
109
- border: 1px solid var(--app-shell-border-color);
110
- }
111
- }
112
- }
113
- }
@@ -1,259 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { Text, Textarea, Button, Group, ActionIcon, Stack } from "@mantine/core";
3
- import { IconSend, IconX } from "@tabler/icons-react";
4
- import { notifications } from "@mantine/notifications";
5
- import { ImageInput } from "@/core";
6
- import { FileDropzone } from "@/controls";
7
-
8
- import classes from "./ChatInput.module.scss";
9
-
10
- interface IProps {
11
- chatId?: string;
12
- loadCompleted?: boolean;
13
- disabled?: boolean;
14
- fullHeight?: boolean;
15
- uploadAllowed?: boolean;
16
- streaming: boolean;
17
- setSending: (value: boolean) => void;
18
- previousMessages?: string[];
19
- header?: React.ReactNode;
20
- inputPlugins?: React.ReactNode;
21
-
22
- uploadFormats?: string[];
23
- maxUploadFileSize?: number;
24
- maxImagesCount?: number;
25
-
26
- onSendMessage: (message: string, images?: ImageInput[]) => Promise<void>;
27
- onDocumentsUpload?: (documents: File[]) => void;
28
- }
29
-
30
- export const ChatInput = ({
31
- loadCompleted = false,
32
- disabled = false,
33
- fullHeight = false,
34
- uploadAllowed = true,
35
- streaming,
36
- setSending,
37
- previousMessages = [],
38
- header,
39
- inputPlugins,
40
- uploadFormats,
41
- maxUploadFileSize = 64 * 1024 * 1024,
42
- maxImagesCount = 5,
43
- onSendMessage,
44
- onDocumentsUpload,
45
- }: IProps) => {
46
- const [userMessage, setUserMessage] = useState("");
47
- const [selectedImages, setSelectedImages] = useState<ImageInput[]>([]);
48
- const [prevImageNdx, setPrevImageNdx] = useState<number>(0);
49
- const [isImagesSeek, setIsImagesSeek] = useState<boolean>(false);
50
-
51
- const inputRef = useRef<HTMLTextAreaElement>(null);
52
-
53
- useEffect(() => {
54
- inputRef.current?.focus();
55
- setPrevImageNdx(previousMessages?.length ? previousMessages.length : 0);
56
- }, [loadCompleted, previousMessages]);
57
-
58
- const handleSendMessage = async () => {
59
- if (!userMessage?.trim() && !selectedImages.length) return;
60
- setSending(true);
61
-
62
- try {
63
- setUserMessage("");
64
- setSelectedImages([]);
65
- await onSendMessage(userMessage, selectedImages);
66
- } catch (error) {
67
- notifications.show({
68
- title: "Error",
69
- message: error instanceof Error ? error.message : "Failed to send message",
70
- color: "red",
71
- });
72
- } finally {
73
- setSending(false);
74
- }
75
- };
76
-
77
- const handleInputKeyDown = useCallback(
78
- (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
79
- const isEmpty = !userMessage?.trim();
80
- if (event.key === "Enter" && !event.shiftKey && !event.altKey) {
81
- event.preventDefault();
82
- handleSendMessage();
83
- setIsImagesSeek(false);
84
- } else if (event.key === "ArrowUp" && (isEmpty || isImagesSeek)) {
85
- const ndx = Math.max(0, prevImageNdx - 1);
86
- setUserMessage(previousMessages[ndx]);
87
- setPrevImageNdx(ndx);
88
- setIsImagesSeek(true);
89
- } else if (event.key === "ArrowDown" && (isEmpty || isImagesSeek)) {
90
- const ndx = Math.min(previousMessages.length - 1, prevImageNdx + 1);
91
- setUserMessage(previousMessages[ndx]);
92
- setPrevImageNdx(ndx);
93
- setIsImagesSeek(true);
94
- } else {
95
- setIsImagesSeek(false);
96
- }
97
- },
98
- [handleSendMessage, userMessage, previousMessages, prevImageNdx]
99
- );
100
-
101
- const handleInputChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
102
- setUserMessage(event.currentTarget.value);
103
- }, []);
104
-
105
- const sendMessageNotAllowed = useMemo(() => {
106
- return disabled || streaming || (!userMessage?.trim() && !selectedImages.length);
107
- }, [userMessage, selectedImages, streaming, disabled]);
108
-
109
- const handleAddFiles = useCallback(
110
- (files: File[]) => {
111
- const filesToAdd = files.filter(f => f.size < maxUploadFileSize);
112
- if (filesToAdd.length < files.length) {
113
- notifications.show({
114
- title: "Warning",
115
- message: `Some files are too large and were not added (max size: ${maxUploadFileSize / 1024 / 1024} MB)`,
116
- color: "yellow",
117
- });
118
- }
119
-
120
- let imageFiles = filesToAdd.filter(f => f.type?.startsWith("image/"));
121
- const documents = filesToAdd.filter(f => !f.type?.startsWith("image/"));
122
-
123
- // Limit images
124
- if (imageFiles.length + selectedImages.length > maxImagesCount) {
125
- notifications.show({
126
- title: "Warning",
127
- message: `You can only add up to ${maxImagesCount} images at a time`,
128
- color: "yellow",
129
- });
130
-
131
- imageFiles = imageFiles.slice(0, maxImagesCount - selectedImages.length);
132
- }
133
-
134
- if (imageFiles.length) {
135
- Promise.all(
136
- imageFiles.map(file => {
137
- return new Promise<ImageInput>((resolve, reject) => {
138
- const reader = new FileReader();
139
- reader.onload = e => {
140
- if (e.target?.result) {
141
- const bytesBase64 = e.target.result as string;
142
- resolve({
143
- fileName: file.name,
144
- mimeType: file.type,
145
- bytesBase64,
146
- });
147
- } else {
148
- reject(new Error(`Failed to read file: ${file.name}`));
149
- }
150
- };
151
- reader.onerror = err => {
152
- reject(new Error(`Failed to read file: ${file.name}, error: ${err}`));
153
- };
154
- reader.readAsDataURL(file);
155
- });
156
- })
157
- )
158
- .then(images => {
159
- setSelectedImages(prev => [...prev, ...images]);
160
- })
161
- .catch(error => {
162
- notifications.show({
163
- title: "Error",
164
- message: error.message || "Failed to read image files",
165
- color: "red",
166
- });
167
- });
168
- }
169
-
170
- if (documents.length && onDocumentsUpload) {
171
- onDocumentsUpload(documents);
172
- } else if (documents.length) {
173
- notifications.show({
174
- title: "Warning",
175
- message: "Document upload is not available in this chat.",
176
- color: "orange",
177
- });
178
- }
179
- },
180
- [selectedImages, onDocumentsUpload]
181
- );
182
-
183
- const handleRemoveImage = (fileName: string): React.MouseEventHandler<HTMLButtonElement> => {
184
- return event => {
185
- event.stopPropagation();
186
- setSelectedImages(prev => prev.filter(f => f.fileName !== fileName));
187
- };
188
- };
189
-
190
- return (
191
- <div
192
- className={[
193
- classes.chatControlsContainer,
194
- fullHeight ? classes.fullHeight : "",
195
- loadCompleted ? "" : classes.hidden,
196
- ].join(" ")}
197
- >
198
- {fullHeight ? (
199
- <Stack align="center" justify="center" gap="md" mb="lg">
200
- <Text c="dimmed" size="lg" ta="center">
201
- Start the conversation by sending a message
202
- </Text>
203
- </Stack>
204
- ) : null}
205
-
206
- <div className={classes.chatControls}>
207
- {header && (
208
- <Group align="center" gap="xs" className={classes.headerRow}>
209
- {header}
210
- </Group>
211
- )}
212
-
213
- <div className={[classes.chatInputContainer, selectedImages.length ? classes.columned : ""].join(" ")}>
214
- {uploadAllowed && (
215
- <div className={classes.documentsInput}>
216
- <FileDropzone onFilesAdd={handleAddFiles} disabled={!uploadAllowed} uploadFormats={uploadFormats} />
217
-
218
- {inputPlugins}
219
-
220
- <div className={classes.filesList}>
221
- {selectedImages.map(file => (
222
- <div key={file.fileName} className={classes.previewImage}>
223
- <img src={file.bytesBase64} alt={file.fileName} />
224
- <ActionIcon
225
- className={classes.removeButton}
226
- color="red"
227
- size="xs"
228
- onClick={handleRemoveImage(file.fileName)}
229
- >
230
- <IconX size={16} />
231
- </ActionIcon>
232
- </div>
233
- ))}
234
- </div>
235
- </div>
236
- )}
237
-
238
- <div className={classes.chatInputGroup}>
239
- <Textarea
240
- ref={inputRef}
241
- className={classes.chatInput}
242
- placeholder="Type your message..."
243
- value={userMessage || ""}
244
- autosize
245
- minRows={1}
246
- maxRows={7}
247
- onChange={handleInputChange}
248
- onKeyDown={handleInputKeyDown}
249
- disabled={disabled}
250
- />
251
- <Button onClick={handleSendMessage} disabled={sendMessageNotAllowed}>
252
- <IconSend size={16} /> Send
253
- </Button>
254
- </div>
255
- </div>
256
- </div>
257
- </div>
258
- );
259
- };
@@ -1 +0,0 @@
1
- export * from "./ChatInput";
@@ -1,7 +0,0 @@
1
- .controls:where([data-orientation="horizontal"]) {
2
- bottom: auto !important;
3
- top: 0.5rem !important;
4
- left: calc(50% - 5rem) !important;
5
- width: 10rem !important;
6
- padding: 0 !important;
7
- }