@smarter.sh/ui-chat 0.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.
@@ -0,0 +1,419 @@
1
+ //---------------------------------------------------------------------------------
2
+ // written by: Lawrence McDaniel
3
+ // https://lawrencemcdaniel.com
4
+ //
5
+ // date: Mar-2024
6
+ //---------------------------------------------------------------------------------
7
+
8
+ // React stuff
9
+ import React, { useRef, useState, useEffect } from "react";
10
+
11
+ // see: https://www.npmjs.com/package/styled-components
12
+ import styled from "styled-components";
13
+
14
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
15
+ import { faCheckCircle, faTimesCircle, faRocket } from "@fortawesome/free-solid-svg-icons";
16
+
17
+ // Chat UI stuff
18
+ import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
19
+ import {
20
+ MainContainer,
21
+ ChatContainer,
22
+ MessageList,
23
+ Message,
24
+ MessageInput,
25
+ TypingIndicator,
26
+ ConversationHeader,
27
+ InfoButton,
28
+ AddUserButton,
29
+ } from "@chatscope/chat-ui-kit-react";
30
+
31
+ // this repo
32
+ import { ErrorModal } from "../ErrorModal/ErrorModal.jsx";
33
+
34
+ // This component
35
+ import "./styles.css";
36
+ import { MessageDirectionEnum, SenderRoleEnum } from "./enums.js";
37
+ import { setCookie, fetchConfig, fetchPrompt } from "./api.js";
38
+ import { cookieMetaFactory, messageFactory, chatMessages2RequestMessages, chatInit } from "./utils.jsx";
39
+ import { ErrorBoundary } from "./ErrorBoundary.jsx";
40
+
41
+ export const ContainerLayout = styled.div`
42
+ height: 100%;
43
+ display: flex;
44
+ flex-direction: row;
45
+ `;
46
+
47
+ export const ContentLayout = styled.div`
48
+ flex: 1;
49
+ display: flex;
50
+ flex-direction: row;
51
+ margin: 0;
52
+ padding: 0;
53
+ height: 100%;
54
+ `;
55
+
56
+ export const ComponentLayout = styled.div`
57
+ flex-basis: 100%;
58
+ margin: 0;
59
+ padding: 5px;
60
+ height: 100%;
61
+ @media (max-width: 992px) {
62
+ flex-basis: 100%;
63
+ }
64
+ `;
65
+
66
+ const DEBUG_MODE = false;
67
+
68
+ // The main chat component. This is the top-level component that
69
+ // is exported and used in the index.js file. It is responsible for
70
+ // managing the chat message thread, sending messages to the backend
71
+ // Api, and rendering the chat UI.
72
+ function SmarterChat({
73
+ apiUrl,
74
+ apiKey,
75
+ toggleMetadata,
76
+ csrfCookieName,
77
+ debugCookieName,
78
+ debugCookieExpiration,
79
+ sessionCookieName,
80
+ sessionCookieExpiration,
81
+ }) {
82
+ const csrfCookie = cookieMetaFactory(csrfCookieName, null); // we read this but never set it.
83
+ const sessionCookie = cookieMetaFactory(sessionCookieName, sessionCookieExpiration);
84
+ const debugCookie = cookieMetaFactory(debugCookieName, debugCookieExpiration);
85
+ const cookies = {
86
+ csrfCookie: csrfCookie,
87
+ sessionCookie: sessionCookie,
88
+ debugCookie: debugCookie,
89
+ };
90
+
91
+ const [configApiUrl, setConfigApiUrl] = useState(apiUrl);
92
+ const [showMetadata, setShowMetadata] = useState(toggleMetadata);
93
+
94
+ const [config, setConfig] = useState({});
95
+ const [placeholderText, setPlaceholderText] = useState("");
96
+ const [assistantName, setAssistantName] = useState("");
97
+ const [infoUrl, setInfoUrl] = useState("");
98
+ const [fileAttachButton, setFileAttachButton] = useState(false);
99
+ const [isValid, setIsValid] = useState(false);
100
+ const [isDeployed, setIsDeployed] = useState(false);
101
+ const [debugMode, setDebugMode] = useState(DEBUG_MODE);
102
+ const [messages, setMessages] = useState([]);
103
+ const [title, setTitle] = useState("");
104
+ const [info, setInfo] = useState("");
105
+
106
+ // future use
107
+ // const [backgroundImageUrl, setBackgroundImageUrl] = useState('');
108
+ // const [sandboxMode, setSandboxMode] = useState(false);
109
+
110
+ // component internal state
111
+ const [isTyping, setIsTyping] = useState(false);
112
+ const fileInputRef = useRef(null);
113
+
114
+ const refetchConfig = async () => {
115
+ const newConfig = await fetchConfig(configApiUrl, cookies);
116
+
117
+ if (newConfig?.debug_mode) {
118
+ console.log("fetchAndSetConfig()...");
119
+ console.log("fetchAndSetConfig() config:", newConfig);
120
+ }
121
+
122
+ setConfig(newConfig);
123
+ return newConfig;
124
+ };
125
+
126
+ const fetchAndSetConfig = async () => {
127
+ try {
128
+ const newConfig = await refetchConfig();
129
+
130
+ console.log("fetchAndSetConfig() config:", newConfig);
131
+
132
+ setPlaceholderText(newConfig.chatbot.app_placeholder);
133
+ setConfigApiUrl(newConfig.chatbot.url_chatbot);
134
+ setAssistantName(newConfig.chatbot.app_assistant);
135
+ setInfoUrl(newConfig.chatbot.app_info_url);
136
+ setFileAttachButton(newConfig.chatbot.app_file_attachment);
137
+ setIsValid(newConfig.meta_data.is_valid);
138
+ setIsDeployed(newConfig.meta_data.is_deployed);
139
+ setDebugMode(newConfig.debug_mode);
140
+
141
+ // wrap up the rest of the initialization
142
+ const newHistory = newConfig.history?.chat_history || [];
143
+ const newThread = chatInit(
144
+ newConfig.chatbot.app_welcome_message,
145
+ newConfig.chatbot.default_system_role,
146
+ newConfig.chatbot.app_example_prompts,
147
+ newConfig.session_key,
148
+ newHistory,
149
+ "BACKEND_CHAT_MOST_RECENT_RESPONSE",
150
+ );
151
+ setMessages(newThread);
152
+
153
+ const newTitle = `${newConfig.chatbot.app_name} v${newConfig.chatbot.version || "1.0.0"}`;
154
+ setTitle(newTitle);
155
+ let newInfo = `${newConfig.chatbot.provider} ${newConfig.chatbot.default_model}`;
156
+ if (newConfig.plugins.meta_data.total_plugins > 0) {
157
+ newInfo += ` with ${newConfig.plugins.meta_data.total_plugins} additional plugins`;
158
+ }
159
+ setInfo(newInfo);
160
+
161
+ if (newConfig?.debug_mode) {
162
+ console.log("fetchAndSetConfig() done!");
163
+ }
164
+ } catch (error) {
165
+ console.error("Failed to fetch config:", error);
166
+ }
167
+ };
168
+
169
+ // Lifecycle hooks
170
+ useEffect(() => {
171
+ if (debugMode) {
172
+ console.log("ChatApp() component mounted");
173
+ }
174
+
175
+ fetchAndSetConfig();
176
+
177
+ return () => {
178
+ if (debugMode) {
179
+ console.log("ChatApp() component unmounted");
180
+ }
181
+ };
182
+ }, []);
183
+
184
+ // Error modal state management
185
+ function openErrorModal(title, msg) {
186
+ setIsModalOpen(true);
187
+ setmodalTitle(title);
188
+ setmodalMessage(msg);
189
+ }
190
+
191
+ function closeChatModal() {
192
+ setIsModalOpen(false);
193
+ }
194
+ const [isModalOpen, setIsModalOpen] = useState(false);
195
+ const [modalMessage, setmodalMessage] = useState("");
196
+ const [modalTitle, setmodalTitle] = useState("");
197
+
198
+ const handleInfoButtonClick = () => {
199
+ const newValue = !showMetadata;
200
+ setShowMetadata(newValue);
201
+ if (debugMode) {
202
+ console.log("showMetadata:", newValue);
203
+ }
204
+ const newMessages = messages.map((message) => {
205
+ if (message.message === null) {
206
+ return { ...message, display: false };
207
+ }
208
+ if (["smarter", "system", "tool"].includes(message.sender)) {
209
+ // toggle backend messages
210
+ if (debugMode) {
211
+ //console.log("toggle message:", message);
212
+ }
213
+ return { ...message, display: newValue };
214
+ } else {
215
+ // always show user and assistant messages
216
+ return { ...message, display: true };
217
+ }
218
+ });
219
+ setMessages(newMessages);
220
+ };
221
+
222
+ const handleAddUserButtonClick = () => {
223
+ setCookie(cookies.sessionCookie, "");
224
+ fetchAndSetConfig();
225
+ };
226
+
227
+ async function handleApiRequest(input_text, base64_encode = false) {
228
+ // Api request handler. This function is indirectly called by UI event handlers
229
+ // inside this module. It asynchronously sends the user's input to the
230
+ // backend Api using the fetch() function. The response from the Api is
231
+ // then used to update the chat message thread and the UI via React state.
232
+ const newMessage = messageFactory({}, input_text, MessageDirectionEnum.OUTGOING, SenderRoleEnum.USER);
233
+ if (base64_encode) {
234
+ console.error("base64 encoding not implemented yet.");
235
+ }
236
+
237
+ setMessages((prevMessages) => {
238
+ const updatedMessages = [...prevMessages, newMessage];
239
+ setIsTyping(true);
240
+
241
+ (async () => {
242
+ try {
243
+ if (debugMode) {
244
+ console.log("handleApiRequest() messages:", updatedMessages);
245
+ }
246
+ const msgs = chatMessages2RequestMessages(updatedMessages);
247
+ const response = await fetchPrompt(config, msgs, cookies);
248
+
249
+ if (response) {
250
+ const responseMessages = response.smarter.messages
251
+ .filter((message) => message.content !== null)
252
+ .map((message) => {
253
+ return messageFactory(message, message.content, MessageDirectionEnum.INCOMING, message.role);
254
+ });
255
+ setMessages((prevMessages) => [...prevMessages, ...responseMessages]);
256
+ setIsTyping(false);
257
+ refetchConfig();
258
+ }
259
+ } catch (error) {
260
+ setIsTyping(false);
261
+ console.error("Api error: ", error);
262
+ openErrorModal("Api error", error.message);
263
+ }
264
+ })();
265
+
266
+ return updatedMessages;
267
+ });
268
+ }
269
+
270
+ // file upload event handlers
271
+ const handleAttachClick = async () => {
272
+ fileInputRef.current.click();
273
+ };
274
+ function handleFileChange(event) {
275
+ const file = event.target.files[0];
276
+ const reader = new FileReader();
277
+
278
+ reader.onload = (event) => {
279
+ const fileContent = event.target.result;
280
+ handleApiRequest(fileContent, true);
281
+ };
282
+ reader.readAsText(file);
283
+ }
284
+
285
+ // send button event handler
286
+ const handleSend = (input_text) => {
287
+ // remove any HTML tags from the input_text. Pasting text into the
288
+ // input box (from any source) tends to result in HTML span tags being included
289
+ // in the input_text. This is a problem because the Api doesn't know how to
290
+ // handle HTML tags. So we remove them here.
291
+ const sanitized_input_text = input_text.replace(/<[^>]+>/g, "");
292
+
293
+ // check if the sanitized input text is empty or only contains whitespace
294
+ if (!sanitized_input_text.trim()) {
295
+ return;
296
+ }
297
+ handleApiRequest(sanitized_input_text, false);
298
+ };
299
+
300
+ // Creates a fancier title for the chat app which includes
301
+ // fontawesome icons for validation and deployment status.
302
+ function AppTitle({ title, isValid, isDeployed }) {
303
+ return (
304
+ <div>
305
+ {title}&nbsp;
306
+ {isValid ? (
307
+ <FontAwesomeIcon icon={faCheckCircle} style={{ color: "green" }} />
308
+ ) : (
309
+ <FontAwesomeIcon icon={faTimesCircle} style={{ color: "red" }} />
310
+ )}
311
+ {isDeployed ? (
312
+ <>
313
+ &nbsp;
314
+ <FontAwesomeIcon icon={faRocket} style={{ color: "orange" }} />
315
+ </>
316
+ ) : null}
317
+ </div>
318
+ );
319
+ }
320
+
321
+ function SmarterMessage({ i, message }) {
322
+ let messageClassNames = "";
323
+ if (message.sender === "smarter") {
324
+ messageClassNames = "smarter-message";
325
+ } else if (["tool", "system"].includes(message.sender)) {
326
+ messageClassNames = "system-message";
327
+ }
328
+ return <Message key={i} model={message} className={messageClassNames} />;
329
+ }
330
+
331
+ // UI widget styles
332
+ // note that most styling is intended to be created in Component.css
333
+ // these are outlying cases where inline styles are required in order to override the default styles
334
+ const fullWidthStyle = {
335
+ width: "100%",
336
+ };
337
+ const transparentBackgroundStyle = {
338
+ backgroundColor: "rgba(0,0,0,0.10)",
339
+ color: "lightgray",
340
+ };
341
+ const mainContainerStyle = {
342
+ // backgroundImage:
343
+ // "linear-gradient(rgba(255, 255, 255, 0.95), rgba(255, 255, 255, .75)), apiUrl('" +
344
+ // background_image_url +
345
+ // "')",
346
+ // backgroundSize: "cover",
347
+ // backgroundPosition: "center",
348
+ width: "100%",
349
+ height: "100%",
350
+ };
351
+ const chatContainerStyle = {
352
+ ...fullWidthStyle,
353
+ ...transparentBackgroundStyle,
354
+ };
355
+
356
+ // render the chat app
357
+ return (
358
+ <div id="smarter_chat_component_container" className="SmarterChat">
359
+ <ContainerLayout>
360
+ <ContentLayout>
361
+ <ComponentLayout>
362
+ <div className="chat-app">
363
+ <MainContainer style={mainContainerStyle}>
364
+ <ErrorBoundary>
365
+ <ErrorModal
366
+ isModalOpen={isModalOpen}
367
+ title={modalTitle}
368
+ message={modalMessage}
369
+ onCloseClick={closeChatModal}
370
+ />
371
+ </ErrorBoundary>
372
+ <ChatContainer style={chatContainerStyle}>
373
+ <ConversationHeader>
374
+ <ConversationHeader.Content
375
+ userName={<AppTitle title={title} isValid={isValid} isDeployed={isDeployed} />}
376
+ info={info}
377
+ />
378
+ <ConversationHeader.Actions>
379
+ <AddUserButton onClick={handleAddUserButtonClick} title="Start a new chat" />
380
+ {toggleMetadata && <InfoButton onClick={handleInfoButtonClick} title="Toggle system meta data" />}
381
+ </ConversationHeader.Actions>
382
+ </ConversationHeader>
383
+ <MessageList
384
+ style={transparentBackgroundStyle}
385
+ scrollBehavior="auto"
386
+ typingIndicator={isTyping ? <TypingIndicator content={assistantName + " is typing"} /> : null}
387
+ >
388
+ {messages
389
+ .filter((message) => message.display)
390
+ .map((message, i) => {
391
+ return <SmarterMessage i={i} message={message} />;
392
+ })}
393
+ </MessageList>
394
+ <MessageInput
395
+ placeholder={placeholderText}
396
+ onSend={handleSend}
397
+ onAttachClick={handleAttachClick}
398
+ attachButton={fileAttachButton}
399
+ fancyScroll={false}
400
+ />
401
+ </ChatContainer>
402
+ <input
403
+ type="file"
404
+ accept=".py"
405
+ title="Select a Python file"
406
+ ref={fileInputRef}
407
+ style={{ display: "none" }}
408
+ onChange={handleFileChange}
409
+ />
410
+ </MainContainer>
411
+ </div>
412
+ </ComponentLayout>
413
+ </ContentLayout>
414
+ </ContainerLayout>
415
+ </div>
416
+ );
417
+ }
418
+
419
+ export default SmarterChat;
@@ -0,0 +1,233 @@
1
+ /*-----------------------------------------------------------------------------
2
+ Description: This file contains the function that makes the API request to
3
+ the backend. It is called exclusively from chatApp/Component.jsx
4
+
5
+ Notes:
6
+ - The backend API is an AWS API Gateway endpoint that is configured to
7
+ call an AWS Lambda function. The Lambda function is written in Python
8
+ and calls the OpenAI API.
9
+
10
+ - The backend API is configured to require an API key. The API key is
11
+ passed in the header of the request. In real terms, the api key is
12
+ pointless because it is exposed in the client code. However, it is
13
+ required by the API Gateway configuration.
14
+
15
+ - The backend API is configured to allow CORS requests from the client.
16
+ This is necessary because the client and backend are served from
17
+ different domains.
18
+
19
+ Returns: the backend API is configured to return a JSON object that substantially
20
+ conforms to the following structure for all 200 responses:
21
+ v0.1.0 - v0.4.0: ./test/events/openai.response.v0.4.0.json
22
+ v0.5.0: ./test/events/langchain.response.v0.5.0.json
23
+ -----------------------------------------------------------------------------*/
24
+
25
+ // Set to true to enable local development mode,
26
+ // which will simulate the server-side API calls.
27
+ const developerMode = false;
28
+ const userAgent = "SmarterChat/1.0";
29
+ const applicationJson = "application/json";
30
+
31
+ function getCookie(cookie, defaultValue = null) {
32
+ let cookieValue = null;
33
+ if (document.cookie && document.cookie !== "") {
34
+ const cookies = document.cookie.split(";");
35
+ for (let i = 0; i < cookies.length; i++) {
36
+ const thisCookie = cookies[i].trim();
37
+ if (thisCookie.substring(0, cookie.name.length + 1) === cookie.name + "=") {
38
+ cookieValue = decodeURIComponent(thisCookie.substring(cookie.name.length + 1));
39
+ if (developerMode) {
40
+ console.log("getCookie(): found ", cookieValue, "for cookie", cookie.name);
41
+ }
42
+ break;
43
+ }
44
+ }
45
+ }
46
+ if (developerMode && !cookieValue) {
47
+ console.warn("getCookie(): no value found for", cookie.name);
48
+ }
49
+ return cookieValue || defaultValue;
50
+ }
51
+
52
+ export function setCookie(cookie, value) {
53
+ const currentPath = window.location.pathname;
54
+ if (value) {
55
+ const expirationDate = new Date();
56
+ expirationDate.setTime(expirationDate.getTime() + cookie.expiration);
57
+ const expires = expirationDate.toUTCString();
58
+ const cookieData = `${cookie.name}=${value}; path=${currentPath}; SameSite=Lax; expires=${expires}`;
59
+ document.cookie = cookieData;
60
+ if (developerMode) {
61
+ console.log(
62
+ "setCookie(): ",
63
+ cookieData,
64
+ "now: ",
65
+ new Date().toUTCString(),
66
+ "expiration: ",
67
+ expirationDate.toUTCString(),
68
+ );
69
+ }
70
+ } else {
71
+ // Unset the cookie by setting its expiration date to the past
72
+ const expirationDate = new Date(0);
73
+ const expires = expirationDate.toUTCString();
74
+ const cookieData = `${cookie.name}=; path=${currentPath}; SameSite=Lax; expires=${expires}`;
75
+ document.cookie = cookieData;
76
+ if (developerMode) {
77
+ console.log("setCookie(): Unsetting cookie", cookieData);
78
+ }
79
+ }
80
+ }
81
+
82
+ function promptRequestBodyFactory(messages, config) {
83
+ const body = {
84
+ session_key: config.session_key,
85
+ messages: messages,
86
+ };
87
+ return JSON.stringify(body);
88
+ }
89
+
90
+ function requestHeadersFactory(cookies) {
91
+ console.log("requestHeadersFactory(): cookies", cookies);
92
+ function getRequestCookies(cookies) {
93
+ // Ensure that csrftoken is not included in the Cookie header.
94
+ const cookiesArray = document.cookie.split(";").filter((cookie) => {
95
+ const trimmedCookie = cookie.trim();
96
+ return !trimmedCookie.startsWith(`${cookies.csrfCookie.name}=`);
97
+ });
98
+ const selectedCookies = cookiesArray.join("; ");
99
+ return selectedCookies;
100
+ }
101
+
102
+ const requestCookies = getRequestCookies(cookies);
103
+ const csrftoken = getCookie(cookies.csrfCookie, "");
104
+ const authToken = null; // FIX NOTE: add me.
105
+
106
+ return {
107
+ Accept: applicationJson,
108
+ "Content-Type": applicationJson,
109
+ "X-CSRFToken": csrftoken,
110
+ Origin: window.location.origin,
111
+ Cookie: requestCookies,
112
+ Authorization: `Bearer ${authToken}`,
113
+ "User-Agent": userAgent,
114
+ };
115
+ }
116
+
117
+ function requestInitFactory(headers, body) {
118
+ return {
119
+ method: "POST",
120
+ credentials: "include",
121
+ mode: "cors",
122
+ headers: headers,
123
+ body: body,
124
+ };
125
+ }
126
+
127
+ function urlFactory(apiUrl, endpoint, sessionKey) {
128
+ if (!apiUrl.endsWith("/")) {
129
+ apiUrl += "/";
130
+ }
131
+ endpoint = endpoint || "";
132
+ let apiConfigUrl = new URL(endpoint, apiUrl);
133
+ if (sessionKey) {
134
+ apiConfigUrl.searchParams.append("session_key", sessionKey);
135
+ }
136
+ const url = apiConfigUrl.toString();
137
+ return url;
138
+ }
139
+
140
+ async function getJsonResponse(url, init, cookies) {
141
+ const debugMode = getCookie(cookies.debugCookie) === "true";
142
+ try {
143
+ if (debugMode || developerMode) {
144
+ console.log("getJsonResponse(): url: ", url, ", init: ", init, ", cookies: ", cookies);
145
+ }
146
+ const response = await fetch(url, init);
147
+ const contentType = response.headers.get("content-type");
148
+ if (contentType && contentType.includes(applicationJson)) {
149
+ const status = await response.status;
150
+ if (response.ok) {
151
+ const responseJson = await response.json(); // Convert the ReadableStream to a JSON object
152
+ const responseJsonData = await responseJson.data; // ditto
153
+ if (debugMode || developerMode) {
154
+ console.log("getJsonResponse(): response: ", responseJson);
155
+ }
156
+ return responseJsonData;
157
+ } else {
158
+ /*
159
+ note:
160
+ - the responseBody object is not available when the status is 504, because
161
+ these responses are generated exclusively by API Gateway.
162
+ - the responseBody object is potentially not available when the status is 500
163
+ depending on whether the 500 response was generated by the Lambda or the API Gateway
164
+ - the responseBody object is intended to always be available when the status is 400.
165
+ However, there potentially COULD be a case where the response itself contains message text.
166
+ */
167
+ console.error("getJsonResponse(): error: ", status, response.statusText);
168
+ return response;
169
+ }
170
+ } else {
171
+ const errorText = await response.text();
172
+ throw new Error(`getJsonResponse() Unexpected response format: ${errorText}`);
173
+ }
174
+ } catch (error) {
175
+ return error;
176
+ }
177
+ }
178
+
179
+ export async function fetchPrompt(config, messages, cookies) {
180
+ console.log("fetchPrompt(): config", config);
181
+ const apiUrl = config.meta_data.url;
182
+ const sessionKey = getCookie(cookies.sessionCookie, "");
183
+ const url = urlFactory(apiUrl, null, sessionKey);
184
+ const headers = requestHeadersFactory(cookies);
185
+ const body = promptRequestBodyFactory(messages, config);
186
+ const init = requestInitFactory(headers, body);
187
+ const responseJson = await getJsonResponse(url, init, cookies);
188
+ if (responseJson && responseJson.body) {
189
+ const responseBody = await JSON.parse(responseJson.body);
190
+ return responseBody;
191
+ }
192
+ return null;
193
+ }
194
+
195
+ async function fetchLocalConfig(configFile) {
196
+ const response = await fetch("../data/" + configFile);
197
+ const sampleConfig = await response.json();
198
+ return sampleConfig.data;
199
+ }
200
+
201
+ export async function fetchConfig(apiUrl, cookies) {
202
+ /*
203
+ Fetch the chat configuration from the backend server. This is a POST request with the
204
+ session key as the payload. The server will return the configuration
205
+ as a JSON object.
206
+
207
+ See class ChatConfigView(View, AccountMixin) in
208
+ https://github.com/smarter-sh/smarter/blob/main/smarter/smarter/apps/chatapp/views.py
209
+ Things to note:
210
+ - The session key is used to identify the user, the chatbot,
211
+ and the chat history.
212
+ - The session key is stored in a cookie that is specific to the path. Thus,
213
+ each chatbot has its own session key.
214
+ - The CSRF token is stored in a cookie and is managed by Django.
215
+ - debugMode is a boolean that is also stored in a cookie, managed by Django
216
+ based on a Waffle switch 'reactapp_debug_mode'
217
+ */
218
+ if (developerMode) {
219
+ return fetchLocalConfig("sample-config.json");
220
+ }
221
+ const sessionKey = getCookie(cookies.sessionCookie, "");
222
+ const headers = requestHeadersFactory(cookies);
223
+ const body = JSON.stringify({ session_key: sessionKey });
224
+ const init = requestInitFactory(headers, body);
225
+ const url = urlFactory(apiUrl, "config/", sessionKey);
226
+ const newConfig = await getJsonResponse(url, init, cookies);
227
+ if (newConfig) {
228
+ setCookie(cookies.sessionCookie, newConfig.session_key);
229
+ setCookie(cookies.debugCookie, newConfig.debug_mode);
230
+ return newConfig;
231
+ }
232
+ return null;
233
+ }
@@ -0,0 +1,17 @@
1
+ export const MessageDirectionEnum = {
2
+ INCOMING: "incoming",
3
+ OUTGOING: "outgoing",
4
+ };
5
+ export const SenderRoleEnum = {
6
+ SYSTEM: "system",
7
+ ASSISTANT: "assistant",
8
+ USER: "user",
9
+ TOOL: "tool",
10
+ SMARTER: "smarter",
11
+ };
12
+ export const ValidMessageRolesEnum = [
13
+ SenderRoleEnum.SYSTEM,
14
+ SenderRoleEnum.ASSISTANT,
15
+ SenderRoleEnum.USER,
16
+ SenderRoleEnum.TOOL,
17
+ ];
@@ -0,0 +1,3 @@
1
+ import SmarterChat from "./SmarterChat";
2
+ export * from "./SmarterChat";
3
+ export default SmarterChat;