@openk9ui/openk9-chatbot 3.0.1

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 (39) hide show
  1. package/README.md +50 -0
  2. package/dist/SingleMessage-Dyc7W2JZ.js +13738 -0
  3. package/dist/assets/Chatbot.css +107 -0
  4. package/dist/components/Chatbot.js +399 -0
  5. package/dist/components/LanguageContext.js +53 -0
  6. package/dist/components/Search.js +91 -0
  7. package/dist/components/SingleMessage.js +9 -0
  8. package/dist/components/Translate.js +69 -0
  9. package/dist/components/client.js +119 -0
  10. package/dist/components/styled.js +21 -0
  11. package/dist/components/useFocusTrap.js +95 -0
  12. package/dist/components/useGenerateResponse.js +271 -0
  13. package/dist/components/useLanguage.js +12 -0
  14. package/dist/lib/components/Chatbot.js +168 -0
  15. package/dist/lib/components/LanguageContext.js +63 -0
  16. package/dist/lib/components/Search.js +56 -0
  17. package/dist/lib/components/SingleMessage.js +385 -0
  18. package/dist/lib/components/Translate.js +71 -0
  19. package/dist/lib/components/client.js +170 -0
  20. package/dist/lib/components/styled.js +14 -0
  21. package/dist/lib/components/useFocusTrap.js +85 -0
  22. package/dist/lib/components/useGenerateResponse.js +310 -0
  23. package/dist/lib/components/useLanguage.js +16 -0
  24. package/dist/lib/main.js +8 -0
  25. package/dist/main.js +4 -0
  26. package/dist/src/theme.js +52 -0
  27. package/dist/types/lib/components/Chatbot.d.ts +26 -0
  28. package/dist/types/lib/components/LanguageContext.d.ts +12 -0
  29. package/dist/types/lib/components/Search.d.ts +9 -0
  30. package/dist/types/lib/components/SingleMessage.d.ts +19 -0
  31. package/dist/types/lib/components/Translate.d.ts +5 -0
  32. package/dist/types/lib/components/client.d.ts +91 -0
  33. package/dist/types/lib/components/styled.d.ts +13 -0
  34. package/dist/types/lib/components/useFocusTrap.d.ts +5 -0
  35. package/dist/types/lib/components/useGenerateResponse.d.ts +33 -0
  36. package/dist/types/lib/components/useLanguage.d.ts +4 -0
  37. package/dist/types/lib/main.d.ts +2 -0
  38. package/dist/types/src/theme.d.ts +1 -0
  39. package/package.json +63 -0
@@ -0,0 +1,119 @@
1
+ import React__default from "react";
2
+ const OpenK9ClientContext = React__default.createContext(
3
+ null
4
+ /* must break app if not provided */
5
+ );
6
+ function Client() {
7
+ return null;
8
+ }
9
+ function OpenK9Client({
10
+ callbackAuthorization
11
+ }) {
12
+ async function authFetch(route, init = {}) {
13
+ const authorization = callbackAuthorization && callbackAuthorization();
14
+ const headers = {
15
+ ...init.headers,
16
+ ...authorization ? { authorization } : {}
17
+ };
18
+ return fetch(route, {
19
+ ...init,
20
+ headers
21
+ });
22
+ }
23
+ return {
24
+ // authInit: keycloakInit,
25
+ // async authenticate() {
26
+ // await keycloak.login();
27
+ // },
28
+ // async deauthenticate() {
29
+ // await keycloak.logout();
30
+ // },
31
+ // async getUserProfile(): Promise<{ name?: string } | undefined | null> {
32
+ // if (!keycloak.authenticated) {
33
+ // throw new Error("User is not authenticated");
34
+ // }
35
+ // try {
36
+ // const userInfo = await keycloak.loadUserInfo();
37
+ // return userInfo as { name: string };
38
+ // } catch (error) {
39
+ // throw error;
40
+ // }
41
+ // },
42
+ async getInitialMessages(chatId) {
43
+ const response = await authFetch(`/api/rag/chat/${chatId}`);
44
+ if (!response.ok) throw new Error("Network response was not ok");
45
+ const data = await response.json();
46
+ return data || [];
47
+ },
48
+ // async getUserInfo(): Promise<getUserInfo> {
49
+ // const response = await fetch(`/api/datasource/buckets/current`);
50
+ // if (!response.ok) throw new Error("Network response was not ok");
51
+ // const data = await response.json();
52
+ // return data;
53
+ // },
54
+ async getHistoryChat(searchQuery) {
55
+ console.log("Calling getHistoryChat with URL:", "/api/rag/user-chats");
56
+ const response = await authFetch(`/api/rag/user-chats`, {
57
+ method: "POST",
58
+ headers: {
59
+ accept: "application/json",
60
+ "Content-Type": "application/json"
61
+ },
62
+ body: JSON.stringify(searchQuery)
63
+ });
64
+ if (!response.ok) {
65
+ console.error("Response not ok:", response.status, response.statusText);
66
+ throw new Error("Network response was not ok");
67
+ }
68
+ const result = await response.json();
69
+ return result;
70
+ },
71
+ async GenerateResponse({
72
+ url,
73
+ searchQuery,
74
+ controller
75
+ }) {
76
+ const response = await authFetch(url, {
77
+ method: "POST",
78
+ headers: {
79
+ accept: "application/json",
80
+ "Content-Type": "application/json"
81
+ },
82
+ body: JSON.stringify(searchQuery),
83
+ signal: controller.signal
84
+ });
85
+ return response;
86
+ }
87
+ // async deleteChat(chatId: string) {
88
+ // const response = await authFetch(`/api/rag/chat/${chatId}`, {
89
+ // method: "DELETE",
90
+ // headers: {
91
+ // accept: "application/json"
92
+ // }
93
+ // });
94
+ // if (!response.ok) {
95
+ // throw new Error("Errore durante l'eliminazione della chat");
96
+ // }
97
+ // return response;
98
+ // },
99
+ // async renameChat(chatId: string, newTitle: string) {
100
+ // const response = await authFetch(`/api/rag/chat/${chatId}`, {
101
+ // method: "PATCH",
102
+ // headers: {
103
+ // accept: "application/json",
104
+ // "Content-Type": "application/json",
105
+ // },
106
+ // body: JSON.stringify({ newTitle })
107
+ // });
108
+ // if (!response.ok) {
109
+ // throw new Error("Errore durante la rinomina della chat");
110
+ // }
111
+ // return response;
112
+ // }
113
+ };
114
+ }
115
+ export {
116
+ OpenK9Client,
117
+ OpenK9ClientContext,
118
+ Client as default
119
+ };
@@ -0,0 +1,21 @@
1
+ import styled from "@emotion/styled";
2
+ const ParagraphTime = styled.p`
3
+ color: ${(props) => props.$color};
4
+ margin: 0px;
5
+ align-self: end;
6
+ font-size: 10px;
7
+ font-weight: 500;
8
+ line-height: 11.72px;
9
+ `;
10
+ const ParagraphMessage = styled.p`
11
+ color:${(props) => props.$color};
12
+ margin: 0px;
13
+ fontSize: "12px",
14
+ fontWeight: "400",
15
+ lineHeight: "18.25px",
16
+ textAlign: "left",
17
+ `;
18
+ export {
19
+ ParagraphMessage,
20
+ ParagraphTime
21
+ };
@@ -0,0 +1,95 @@
1
+ import { useRef, useCallback, useEffect } from "react";
2
+ function convertToIntOrFallback(stringToConvert) {
3
+ const parsed = parseInt(stringToConvert ?? "");
4
+ return isNaN(parsed) ? 0 : parsed;
5
+ }
6
+ function getTabIndexOfNode(targetNode) {
7
+ return convertToIntOrFallback(targetNode.getAttribute("tabindex"));
8
+ }
9
+ function sanitizeTabIndexInput(tabIndex, highestPositiveTabIndex) {
10
+ if (tabIndex < 0) {
11
+ throw new Error(
12
+ `Unable to sort given input. A negative value is not part of the tab order: ${tabIndex}`
13
+ );
14
+ }
15
+ return tabIndex === 0 ? highestPositiveTabIndex + 1 : tabIndex;
16
+ }
17
+ function sortByTabIndex(firstNode, secondNode) {
18
+ const tabIndexes = [firstNode, secondNode].map(
19
+ (node) => getTabIndexOfNode(node)
20
+ );
21
+ return tabIndexes.map(
22
+ (tabIndexValue) => sanitizeTabIndexInput(tabIndexValue, Math.max(...tabIndexes))
23
+ ).reduce((previousValue, currentValue) => previousValue - currentValue);
24
+ }
25
+ const focusableElementsSelector = "a[href], area[href], input:not([disabled]):not([type=hidden]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";
26
+ const TAB_KEY = 9;
27
+ function useFocusTrap(isActive) {
28
+ const trapRef = useRef(null);
29
+ const selectNextFocusableElem = useCallback(
30
+ (sortedFocusableElems, currentIndex, shiftKeyPressed = false, skipCount = 0) => {
31
+ if (skipCount > sortedFocusableElems.length) {
32
+ return;
33
+ }
34
+ const backwards = !!shiftKeyPressed;
35
+ const maxIndex = sortedFocusableElems.length - 1;
36
+ if (currentIndex === void 0) {
37
+ currentIndex = sortedFocusableElems.indexOf(document.activeElement) ?? 0;
38
+ }
39
+ let nextIndex = backwards ? currentIndex - 1 : currentIndex + 1;
40
+ if (nextIndex > maxIndex) {
41
+ nextIndex = 0;
42
+ }
43
+ if (nextIndex < 0) {
44
+ nextIndex = maxIndex;
45
+ }
46
+ const newFocusElem = sortedFocusableElems[nextIndex];
47
+ newFocusElem.focus();
48
+ if (document.activeElement !== newFocusElem) {
49
+ selectNextFocusableElem(
50
+ sortedFocusableElems,
51
+ nextIndex,
52
+ shiftKeyPressed,
53
+ skipCount + 1
54
+ );
55
+ }
56
+ },
57
+ []
58
+ );
59
+ const trapper = useCallback(
60
+ (evt) => {
61
+ const trapRefElem = trapRef.current;
62
+ if (trapRefElem !== null) {
63
+ if (evt.which === TAB_KEY || evt.key === "Tab") {
64
+ evt.preventDefault();
65
+ const shiftKeyPressed = !!evt.shiftKey;
66
+ let focusableElems = Array.from(
67
+ trapRefElem.querySelectorAll(focusableElementsSelector)
68
+ ).filter(
69
+ (focusableElement) => getTabIndexOfNode(focusableElement) >= 0
70
+ );
71
+ focusableElems = focusableElems.sort(sortByTabIndex);
72
+ selectNextFocusableElem(focusableElems, void 0, shiftKeyPressed);
73
+ }
74
+ }
75
+ },
76
+ [selectNextFocusableElem]
77
+ );
78
+ useEffect(() => {
79
+ if (isActive) {
80
+ window.addEventListener("keydown", trapper);
81
+ }
82
+ return () => {
83
+ if (isActive) {
84
+ window.removeEventListener("keydown", trapper);
85
+ }
86
+ };
87
+ }, [isActive, trapper]);
88
+ return [trapRef];
89
+ }
90
+ export {
91
+ convertToIntOrFallback,
92
+ getTabIndexOfNode,
93
+ sortByTabIndex,
94
+ useFocusTrap
95
+ };
@@ -0,0 +1,271 @@
1
+ import React__default, { useState, useCallback } from "react";
2
+ import { useLanguage } from "./useLanguage.js";
3
+ import { OpenK9Client } from "./client.js";
4
+ const byteToHex = [];
5
+ for (let i = 0; i < 256; ++i) {
6
+ byteToHex.push((i + 256).toString(16).slice(1));
7
+ }
8
+ function unsafeStringify(arr, offset = 0) {
9
+ return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
10
+ }
11
+ let getRandomValues;
12
+ const rnds8 = new Uint8Array(16);
13
+ function rng() {
14
+ if (!getRandomValues) {
15
+ if (typeof crypto === "undefined" || !crypto.getRandomValues) {
16
+ throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
17
+ }
18
+ getRandomValues = crypto.getRandomValues.bind(crypto);
19
+ }
20
+ return getRandomValues(rnds8);
21
+ }
22
+ const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
23
+ const native = { randomUUID };
24
+ function v4(options, buf, offset) {
25
+ var _a;
26
+ if (native.randomUUID && true && !options) {
27
+ return native.randomUUID();
28
+ }
29
+ options = options || {};
30
+ const rnds = options.random ?? ((_a = options.rng) == null ? void 0 : _a.call(options)) ?? rng();
31
+ if (rnds.length < 16) {
32
+ throw new Error("Random bytes length must be >= 16");
33
+ }
34
+ rnds[6] = rnds[6] & 15 | 64;
35
+ rnds[8] = rnds[8] & 63 | 128;
36
+ return unsafeStringify(rnds);
37
+ }
38
+ const useGenerateResponse = ({
39
+ initialMessages,
40
+ tenant,
41
+ callbackAuthorization
42
+ }) => {
43
+ const { language } = useLanguage();
44
+ const [messages, setMessages] = useState(initialMessages);
45
+ const [abortControllers, setAbortControllers] = useState(/* @__PURE__ */ new Map());
46
+ const [isChatting, setIsChatting] = useState(false);
47
+ const [isLoading, setIsLoading] = useState(null);
48
+ const client = React__default.useMemo(
49
+ () => OpenK9Client({ callbackAuthorization }),
50
+ [callbackAuthorization]
51
+ );
52
+ const generateResponse = useCallback(
53
+ async (query) => {
54
+ var _a, _b;
55
+ const id = v4();
56
+ setIsLoading({ id, isLoading: true });
57
+ const timestamp = "" + Date.now();
58
+ const nonLoggedUserId = `anonymous_${v4()}_${timestamp}`;
59
+ const chatHistory = messages.map((msg) => ({
60
+ question: msg.question,
61
+ answer: msg.answer,
62
+ title: "",
63
+ sources: msg.sources || [],
64
+ chat_id: nonLoggedUserId,
65
+ //keycloak.authenticated ? chatId : nonLoggedUserId,
66
+ timestamp: msg.timestamp || "",
67
+ chat_sequence_number: msg.chat_sequence_number
68
+ }));
69
+ setMessages((prevMessages) => {
70
+ var _a2;
71
+ const chat_sequence_number = (((_a2 = prevMessages[prevMessages.length - 1]) == null ? void 0 : _a2.chat_sequence_number) || 0) + 1;
72
+ const newMessage = {
73
+ id,
74
+ question: query,
75
+ answer: "",
76
+ sendTime: (/* @__PURE__ */ new Date()).toISOString(),
77
+ status: "CHUNK",
78
+ sources: [],
79
+ chat_sequence_number,
80
+ timestamp
81
+ };
82
+ return [...prevMessages, newMessage];
83
+ });
84
+ setIsChatting(true);
85
+ const controller = new AbortController();
86
+ setAbortControllers((prev) => new Map(prev).set(id, controller));
87
+ const url = `${tenant}/api/rag/chat-tool`;
88
+ const searchQuery = (
89
+ // keycloak.authenticated ? {
90
+ // searchText: query,
91
+ // chatId,
92
+ // chatSequenceNumber: messages[messages.length - 1]?.chat_sequence_number + 1 || 1,
93
+ // timestamp,
94
+ // // language,
95
+ // } :
96
+ {
97
+ searchText: query,
98
+ chatSequenceNumber: ((_a = messages[messages.length - 1]) == null ? void 0 : _a.chat_sequence_number) + 1 || 1,
99
+ timestamp,
100
+ chatHistory
101
+ // language,
102
+ }
103
+ );
104
+ try {
105
+ const response = await client.GenerateResponse({
106
+ controller,
107
+ searchQuery,
108
+ url
109
+ });
110
+ if (response.ok) {
111
+ const reader = (_b = response.body) == null ? void 0 : _b.getReader();
112
+ const decoder = new TextDecoder("utf-8");
113
+ let done = false;
114
+ let buffer = "";
115
+ while (!done && reader) {
116
+ const { value, done: readerDone } = await reader.read();
117
+ done = readerDone;
118
+ buffer += decoder.decode(value, { stream: true });
119
+ let boundaryIndex;
120
+ while ((boundaryIndex = buffer.indexOf("\n")) !== -1) {
121
+ const chunkStr = buffer.slice(0, boundaryIndex);
122
+ buffer = buffer.slice(boundaryIndex + 1);
123
+ if (chunkStr.trim().startsWith("data: ")) {
124
+ const dataStr = chunkStr.trim().slice(6);
125
+ try {
126
+ const data = JSON.parse(dataStr);
127
+ switch (data.type) {
128
+ case "CHUNK":
129
+ setMessages(
130
+ (prev) => prev.map(
131
+ (msg) => msg.id === id ? {
132
+ ...msg,
133
+ answer: msg.answer + data.chunk
134
+ } : msg
135
+ )
136
+ );
137
+ break;
138
+ case "DOCUMENT":
139
+ setMessages(
140
+ (prev) => prev.map(
141
+ (msg) => msg.id === id ? {
142
+ ...msg,
143
+ sources: [...(msg == null ? void 0 : msg.sources) ?? [], data.chunk]
144
+ } : msg
145
+ )
146
+ );
147
+ break;
148
+ case "START":
149
+ setIsLoading(null);
150
+ break;
151
+ case "ERROR":
152
+ setMessages(
153
+ (prev) => prev.map(
154
+ (msg) => msg.id === id ? {
155
+ ...msg,
156
+ answer: data.chunk,
157
+ status: "ERROR"
158
+ } : msg
159
+ )
160
+ );
161
+ setIsChatting(false);
162
+ break;
163
+ case "END":
164
+ setMessages(
165
+ (prev) => prev.map(
166
+ (msg) => msg.id === id ? {
167
+ ...msg,
168
+ status: "END"
169
+ } : msg
170
+ )
171
+ );
172
+ setIsChatting(false);
173
+ break;
174
+ default:
175
+ console.warn(
176
+ "Tipo di chunk non riconosciuto:",
177
+ data.type
178
+ );
179
+ break;
180
+ }
181
+ } catch (e) {
182
+ console.error("Errore nel parsing del JSON", e);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ } else {
188
+ console.error("Errore nel generare la risposta");
189
+ setMessages(
190
+ (prev) => prev.map(
191
+ (msg) => msg.id === id ? {
192
+ ...msg,
193
+ status: "ERROR",
194
+ answer: (response == null ? void 0 : response.statusText) || "Error"
195
+ } : msg
196
+ )
197
+ );
198
+ setIsChatting(false);
199
+ }
200
+ setIsChatting(false);
201
+ setIsLoading(null);
202
+ } catch (error) {
203
+ console.error("Errore durante la richiesta", error);
204
+ setIsChatting(false);
205
+ }
206
+ setAbortControllers((prev) => {
207
+ const updated = new Map(prev);
208
+ updated.delete(id);
209
+ return updated;
210
+ });
211
+ },
212
+ [messages, client, tenant, language]
213
+ //loading, userInfo,
214
+ );
215
+ const cancelResponse = (id) => {
216
+ const controller = abortControllers.get(id);
217
+ if (controller) {
218
+ controller.abort();
219
+ setIsLoading(null);
220
+ setMessages(
221
+ (prev) => prev.map(
222
+ (msg) => msg.id === id ? {
223
+ ...msg,
224
+ status: "END",
225
+ answer: "La risposta è stata annullata"
226
+ } : msg
227
+ )
228
+ );
229
+ setAbortControllers((prev) => {
230
+ const updated = new Map(prev);
231
+ updated.delete(id);
232
+ return updated;
233
+ });
234
+ setIsChatting(abortControllers.size > 0);
235
+ } else {
236
+ console.warn(`No AbortController found for id: ${id}`);
237
+ }
238
+ };
239
+ const cancelAllResponses = () => {
240
+ abortControllers.forEach((controller) => {
241
+ controller.abort();
242
+ });
243
+ setMessages(
244
+ (prev) => prev.map(
245
+ (msg) => msg.status === "CHUNK" ? {
246
+ ...msg,
247
+ status: "END",
248
+ answer: msg.answer + "... La risposta è stata annullata"
249
+ } : msg
250
+ )
251
+ );
252
+ setAbortControllers(/* @__PURE__ */ new Map());
253
+ setIsChatting(false);
254
+ setIsLoading(null);
255
+ };
256
+ const resetMessage = () => {
257
+ setMessages([]);
258
+ };
259
+ return {
260
+ messages,
261
+ generateResponse,
262
+ cancelResponse,
263
+ cancelAllResponses,
264
+ isChatting,
265
+ isLoading,
266
+ resetMessage
267
+ };
268
+ };
269
+ export {
270
+ useGenerateResponse as default
271
+ };
@@ -0,0 +1,12 @@
1
+ import { useContext } from "react";
2
+ import LanguageContext from "./LanguageContext.js";
3
+ const useLanguage = () => {
4
+ const context = useContext(LanguageContext);
5
+ if (!context) {
6
+ throw new Error("useLanguage must used to LanguageProvider");
7
+ }
8
+ return context;
9
+ };
10
+ export {
11
+ useLanguage
12
+ };