@red-hat-developer-hub/backstage-plugin-lightspeed 0.5.0 → 0.5.2

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/CHANGELOG.md +27 -0
  2. package/README.md +15 -0
  3. package/config.d.ts +21 -0
  4. package/dist/api/LightspeedApiClient.esm.js +3 -2
  5. package/dist/api/LightspeedApiClient.esm.js.map +1 -1
  6. package/dist/api/api.esm.js.map +1 -1
  7. package/dist/components/Attachment.esm.js +98 -0
  8. package/dist/components/Attachment.esm.js.map +1 -0
  9. package/dist/components/AttachmentContext.esm.js +105 -0
  10. package/dist/components/AttachmentContext.esm.js.map +1 -0
  11. package/dist/components/FilePreview.esm.js +59 -0
  12. package/dist/components/FilePreview.esm.js.map +1 -0
  13. package/dist/components/LightSpeedChat.esm.js +40 -26
  14. package/dist/components/LightSpeedChat.esm.js.map +1 -1
  15. package/dist/components/LightspeedChatBox.esm.js +56 -5
  16. package/dist/components/LightspeedChatBox.esm.js.map +1 -1
  17. package/dist/components/LightspeedPage.esm.js +3 -2
  18. package/dist/components/LightspeedPage.esm.js.map +1 -1
  19. package/dist/const.esm.js +58 -1
  20. package/dist/const.esm.js.map +1 -1
  21. package/dist/hooks/useAutoScroll.esm.js +78 -0
  22. package/dist/hooks/useAutoScroll.esm.js.map +1 -0
  23. package/dist/hooks/useBufferedMessages.esm.js +27 -0
  24. package/dist/hooks/useBufferedMessages.esm.js.map +1 -0
  25. package/dist/hooks/useConversationMessages.esm.js +34 -4
  26. package/dist/hooks/useConversationMessages.esm.js.map +1 -1
  27. package/dist/hooks/useCreateCoversationMessage.esm.js +4 -2
  28. package/dist/hooks/useCreateCoversationMessage.esm.js.map +1 -1
  29. package/dist/hooks/useWelcomePrompts.esm.js +20 -0
  30. package/dist/hooks/useWelcomePrompts.esm.js.map +1 -0
  31. package/dist/types.esm.js +9 -0
  32. package/dist/types.esm.js.map +1 -0
  33. package/dist/utils/attachment-utils.esm.js +23 -0
  34. package/dist/utils/attachment-utils.esm.js.map +1 -0
  35. package/dist/utils/lightspeed-chatbox-utils.esm.js +9 -4
  36. package/dist/utils/lightspeed-chatbox-utils.esm.js.map +1 -1
  37. package/dist/utils/prompt-utils.esm.js +17 -0
  38. package/dist/utils/prompt-utils.esm.js.map +1 -0
  39. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  ## @red-hat-developer-hub/backstage-plugin-lightspeed
2
2
 
3
+ ## 0.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - fe413e3: Functional disclaimer with topic restriction
8
+ - a9e5f32: Updated dependency `@openapitools/openapi-generator-cli` to `2.20.0`.
9
+ Updated dependency `prettier` to `3.5.3`.
10
+ Updated dependency `@redhat-developer/red-hat-developer-hub-theme` to `0.5.1`.
11
+ Updated dependency `@red-hat-developer-hub/backstage-plugin-theme` to `^0.6.0`.
12
+ Updated dependency `@janus-idp/cli` to `3.5.0`.
13
+ - 3a76aaf: Updated dependency `@patternfly/react-core` to `6.3.0-prerelease.4`.
14
+ - 002f7c9: Updated dependency `@testing-library/user-event` to `14.6.1`.
15
+ - ab6a532: Add configurable sample prompts
16
+ - dc4ff7c: Add file attachment support
17
+ Update functional and legal disclaimers
18
+ Add citation links in the bot response
19
+ Update initial user prompts
20
+ - db3257b: Upgrade PF chatbot
21
+ - Updated dependencies [a9e5f32]
22
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@0.3.1
23
+
24
+ ## 0.5.1
25
+
26
+ ### Patch Changes
27
+
28
+ - e6c1643: Pause autoscroll when user scrolls up during streaming chat responses
29
+
3
30
  ## 0.5.0
4
31
 
5
32
  ### Minor Changes
package/README.md CHANGED
@@ -122,6 +122,21 @@ lightspeed:
122
122
  - id: <server_id>
123
123
  url: <server_URL>
124
124
  token: <api_key>
125
+ questionValidation: true # Optional - To disable question (prompt) validation set it to false.
126
+ prompts: # optional
127
+ - title: <prompt_title>
128
+ - message: <prompt_message>
129
+ ```
130
+
131
+ `questionValidation` is default to be enabled with topic restriction on RHDH related topics.
132
+ If you want to disable the validation, set the value to be `false`.
133
+
134
+ Example configuration to disable `questionValidation`:
135
+
136
+ ```yaml
137
+ lightspeed:
138
+ questionValidation: false
139
+ servers: ... ...
125
140
  ```
126
141
 
127
142
  ---
package/config.d.ts CHANGED
@@ -38,5 +38,26 @@ export interface Config {
38
38
  */
39
39
  url: string;
40
40
  }>;
41
+ /**
42
+ * query restriction validation (Enabled by default).
43
+ * @visibility frontend
44
+ */
45
+ questionValidation?: boolean;
46
+ prompts?: Array</**
47
+ * @visibility frontend
48
+ */
49
+ {
50
+ /**
51
+ * The title of the prompt.
52
+ * Displayed as the heading of the prompt.
53
+ * @visibility frontend
54
+ */
55
+ title: string;
56
+ /**
57
+ * The main question or message shown in the prompt.
58
+ * @visibility frontend
59
+ */
60
+ message: string;
61
+ }>;
41
62
  };
42
63
  }
@@ -13,7 +13,7 @@ class LightspeedApiClient {
13
13
  getServerUrl() {
14
14
  return `${this.configApi.getConfigArray("lightspeed.servers")[0].getOptionalString("url")}`;
15
15
  }
16
- async createMessage(prompt, selectedModel, conversation_id) {
16
+ async createMessage(prompt, selectedModel, conversation_id, attachments) {
17
17
  const baseUrl = await this.getBaseUrl();
18
18
  const response = await this.fetchApi.fetch(`${baseUrl}/v1/query`, {
19
19
  method: "POST",
@@ -25,7 +25,8 @@ class LightspeedApiClient {
25
25
  model: selectedModel,
26
26
  provider: this.configApi.getConfigArray("lightspeed.servers")[0].getOptionalString("id"),
27
27
  // Currently supports a single llm server
28
- query: prompt
28
+ query: prompt,
29
+ attachments
29
30
  })
30
31
  });
31
32
  if (!response.body) {
@@ -1 +1 @@
1
- {"version":3,"file":"LightspeedApiClient.esm.js","sources":["../../src/api/LightspeedApiClient.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConfigApi, FetchApi } from '@backstage/core-plugin-api';\n\nimport { TEMP_CONVERSATION_ID } from '../const';\nimport { LightspeedAPI } from './api';\n\nexport type Options = {\n configApi: ConfigApi;\n fetchApi: FetchApi;\n};\n\nexport class LightspeedApiClient implements LightspeedAPI {\n private readonly configApi: ConfigApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: Options) {\n this.configApi = options.configApi;\n this.fetchApi = options.fetchApi;\n }\n\n async getBaseUrl() {\n return `${this.configApi.getString('backend.baseUrl')}/api/lightspeed`;\n }\n\n getServerUrl() {\n // Currently supports a single llm server\n return `${this.configApi\n .getConfigArray('lightspeed.servers')[0]\n .getOptionalString('url')}`;\n }\n\n async createMessage(\n prompt: string,\n selectedModel: string,\n conversation_id: string,\n ) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(`${baseUrl}/v1/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n conversation_id:\n conversation_id === TEMP_CONVERSATION_ID\n ? undefined\n : conversation_id,\n model: selectedModel,\n provider: this.configApi\n .getConfigArray('lightspeed.servers')[0]\n .getOptionalString('id'), // Currently supports a single llm server\n query: prompt,\n }),\n });\n\n if (!response.body) {\n throw new Error('Readable stream is not supported or there is no body.');\n }\n\n if (!response.ok) {\n const body = await response.body.getReader();\n const reader = body.read();\n const decoder = new TextDecoder('utf-8');\n const text = await reader.then(({ done, value }) => {\n if (done) {\n return '';\n }\n return decoder.decode(value);\n });\n const errorMessage = JSON.parse(text);\n if (errorMessage?.error) {\n throw new Error(`failed to create message: ${errorMessage.error}`);\n }\n }\n return response.body.getReader();\n }\n\n private async fetcher(url: string) {\n const response = await this.fetchApi.fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n throw new Error(\n `failed to fetch data, status ${response.status}: ${response.statusText}`,\n );\n }\n return response;\n }\n\n async getAllModels() {\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(`${baseUrl}/v1/models`);\n\n if (!result.ok) {\n throw new Error(\n `failed to get models, status ${result.status}: ${result.statusText}`,\n );\n }\n\n const response = await result.json();\n return response?.data ? response.data : [];\n }\n\n async getConversationMessages(conversation_id: string) {\n if (conversation_id === TEMP_CONVERSATION_ID) {\n return [];\n }\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(\n `${baseUrl}/conversations/${encodeURIComponent(conversation_id)}`,\n );\n if (!result.ok) {\n throw new Error(\n `failed to get conversation messages, status ${result.status}: ${result.statusText}`,\n );\n }\n const response = await result.json();\n return response.chat_history ?? [];\n }\n\n async getConversations() {\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(`${baseUrl}/conversations`);\n\n if (!result.ok) {\n throw new Error(\n `failed to get conversation, status ${result.status}: ${result.statusText}`,\n );\n }\n\n const response = await result.json();\n return response.conversations ?? [];\n }\n\n async deleteConversation(conversation_id: string) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/conversations/${encodeURIComponent(conversation_id)}`,\n {\n method: 'DELETE',\n headers: {},\n },\n );\n\n if (!response.ok) {\n throw new Error(\n `failed to delete conversation, status ${response.status}: ${response.statusText}`,\n );\n }\n return { success: true };\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LightspeedApiClient.esm.js","sources":["../../src/api/LightspeedApiClient.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ConfigApi, FetchApi } from '@backstage/core-plugin-api';\n\nimport { TEMP_CONVERSATION_ID } from '../const';\nimport { Attachment } from '../types';\nimport { LightspeedAPI } from './api';\n\nexport type Options = {\n configApi: ConfigApi;\n fetchApi: FetchApi;\n};\n\nexport class LightspeedApiClient implements LightspeedAPI {\n private readonly configApi: ConfigApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: Options) {\n this.configApi = options.configApi;\n this.fetchApi = options.fetchApi;\n }\n\n async getBaseUrl() {\n return `${this.configApi.getString('backend.baseUrl')}/api/lightspeed`;\n }\n\n getServerUrl() {\n // Currently supports a single llm server\n return `${this.configApi\n .getConfigArray('lightspeed.servers')[0]\n .getOptionalString('url')}`;\n }\n\n async createMessage(\n prompt: string,\n selectedModel: string,\n conversation_id: string,\n attachments: Attachment[],\n ) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(`${baseUrl}/v1/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n conversation_id:\n conversation_id === TEMP_CONVERSATION_ID\n ? undefined\n : conversation_id,\n model: selectedModel,\n provider: this.configApi\n .getConfigArray('lightspeed.servers')[0]\n .getOptionalString('id'), // Currently supports a single llm server\n query: prompt,\n attachments,\n }),\n });\n\n if (!response.body) {\n throw new Error('Readable stream is not supported or there is no body.');\n }\n\n if (!response.ok) {\n const body = await response.body.getReader();\n const reader = body.read();\n const decoder = new TextDecoder('utf-8');\n const text = await reader.then(({ done, value }) => {\n if (done) {\n return '';\n }\n return decoder.decode(value);\n });\n const errorMessage = JSON.parse(text);\n if (errorMessage?.error) {\n throw new Error(`failed to create message: ${errorMessage.error}`);\n }\n }\n return response.body.getReader();\n }\n\n private async fetcher(url: string) {\n const response = await this.fetchApi.fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n throw new Error(\n `failed to fetch data, status ${response.status}: ${response.statusText}`,\n );\n }\n return response;\n }\n\n async getAllModels() {\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(`${baseUrl}/v1/models`);\n\n if (!result.ok) {\n throw new Error(\n `failed to get models, status ${result.status}: ${result.statusText}`,\n );\n }\n\n const response = await result.json();\n return response?.data ? response.data : [];\n }\n\n async getConversationMessages(conversation_id: string) {\n if (conversation_id === TEMP_CONVERSATION_ID) {\n return [];\n }\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(\n `${baseUrl}/conversations/${encodeURIComponent(conversation_id)}`,\n );\n if (!result.ok) {\n throw new Error(\n `failed to get conversation messages, status ${result.status}: ${result.statusText}`,\n );\n }\n const response = await result.json();\n return response.chat_history ?? [];\n }\n\n async getConversations() {\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(`${baseUrl}/conversations`);\n\n if (!result.ok) {\n throw new Error(\n `failed to get conversation, status ${result.status}: ${result.statusText}`,\n );\n }\n\n const response = await result.json();\n return response.conversations ?? [];\n }\n\n async deleteConversation(conversation_id: string) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/conversations/${encodeURIComponent(conversation_id)}`,\n {\n method: 'DELETE',\n headers: {},\n },\n );\n\n if (!response.ok) {\n throw new Error(\n `failed to delete conversation, status ${response.status}: ${response.statusText}`,\n );\n }\n return { success: true };\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.esm.js","sources":["../../src/api/api.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createApiRef } from '@backstage/core-plugin-api';\n\nimport OpenAI from 'openai';\n\nimport { BaseMessage, ConversationList } from '../types';\n\nexport type LightspeedAPI = {\n getAllModels: () => Promise<OpenAI.Models.Model[]>;\n getConversationMessages: (conversation_id: string) => Promise<BaseMessage[]>;\n createMessage: (\n prompt: string,\n selectedModel: string,\n conversation_id: string,\n ) => Promise<ReadableStreamDefaultReader>;\n deleteConversation: (\n conversation_id: string,\n ) => Promise<{ success: boolean }>;\n getConversations: () => Promise<ConversationList>;\n};\n\nexport const lightspeedApiRef = createApiRef<LightspeedAPI>({\n id: 'plugin.lightspeed.service',\n});\n"],"names":[],"mappings":";;AAoCO,MAAM,mBAAmB,YAA4B,CAAA;AAAA,EAC1D,EAAI,EAAA;AACN,CAAC;;;;"}
1
+ {"version":3,"file":"api.esm.js","sources":["../../src/api/api.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createApiRef } from '@backstage/core-plugin-api';\n\nimport OpenAI from 'openai';\n\nimport { Attachment, BaseMessage, ConversationList } from '../types';\n\nexport type LightspeedAPI = {\n getAllModels: () => Promise<OpenAI.Models.Model[]>;\n getConversationMessages: (conversation_id: string) => Promise<BaseMessage[]>;\n createMessage: (\n prompt: string,\n selectedModel: string,\n conversation_id: string,\n attachments: Attachment[],\n ) => Promise<ReadableStreamDefaultReader>;\n deleteConversation: (\n conversation_id: string,\n ) => Promise<{ success: boolean }>;\n getConversations: () => Promise<ConversationList>;\n};\n\nexport const lightspeedApiRef = createApiRef<LightspeedAPI>({\n id: 'plugin.lightspeed.service',\n});\n"],"names":[],"mappings":";;AAqCO,MAAM,mBAAmB,YAA4B,CAAA;AAAA,EAC1D,EAAI,EAAA;AACN,CAAC;;;;"}
@@ -0,0 +1,98 @@
1
+ import React__default from 'react';
2
+ import { makeStyles } from '@material-ui/core';
3
+ import { PreviewAttachment, ChatbotDisplayMode, AttachmentEdit } from '@patternfly/chatbot';
4
+ import { useFileAttachmentContext } from './AttachmentContext.esm.js';
5
+
6
+ const useStyles = makeStyles(() => ({
7
+ modalFooter: {
8
+ "&>button": {
9
+ width: "12% !important"
10
+ }
11
+ }
12
+ }));
13
+ const Attachment = () => {
14
+ const {
15
+ currentFileContent,
16
+ setFileContents,
17
+ modalState,
18
+ setCurrentFileContent
19
+ } = useFileAttachmentContext();
20
+ const classes = useStyles();
21
+ if (!currentFileContent) {
22
+ return null;
23
+ }
24
+ const {
25
+ previewModalKey,
26
+ isPreviewModalOpen,
27
+ isEditModalOpen,
28
+ setPreviewModalKey,
29
+ setIsEditModalOpen,
30
+ setIsPreviewModalOpen
31
+ } = modalState;
32
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
33
+ PreviewAttachment,
34
+ {
35
+ key: previewModalKey,
36
+ code: currentFileContent?.content,
37
+ fileName: currentFileContent?.name,
38
+ isModalOpen: isPreviewModalOpen,
39
+ secondaryActionButtonText: "Close",
40
+ modalFooterClassName: classes.modalFooter,
41
+ onEdit: () => {
42
+ setIsPreviewModalOpen(false);
43
+ setIsEditModalOpen(true);
44
+ },
45
+ onDismiss: () => {
46
+ setCurrentFileContent(undefined);
47
+ setIsPreviewModalOpen(false);
48
+ },
49
+ handleModalToggle: () => setIsPreviewModalOpen(false),
50
+ displayMode: ChatbotDisplayMode.fullscreen
51
+ }
52
+ ), /* @__PURE__ */ React__default.createElement(
53
+ AttachmentEdit,
54
+ {
55
+ key: currentFileContent?.content,
56
+ code: currentFileContent?.content,
57
+ fileName: currentFileContent?.name,
58
+ isModalOpen: isEditModalOpen,
59
+ modalFooterClassName: classes.modalFooter,
60
+ onSave: (_, content) => {
61
+ setCurrentFileContent({
62
+ ...currentFileContent,
63
+ content
64
+ });
65
+ setFileContents((prev) => {
66
+ const existingIndex = prev.findIndex(
67
+ (f) => f.name === currentFileContent?.name
68
+ );
69
+ if (existingIndex !== -1) {
70
+ const updated = [...prev];
71
+ updated[existingIndex] = {
72
+ ...updated[existingIndex],
73
+ content
74
+ };
75
+ return updated;
76
+ }
77
+ return [
78
+ ...prev,
79
+ {
80
+ name: currentFileContent?.name,
81
+ type: currentFileContent?.type,
82
+ content
83
+ }
84
+ ];
85
+ });
86
+ setPreviewModalKey((prev) => prev + 1);
87
+ setIsEditModalOpen(false);
88
+ setIsPreviewModalOpen(true);
89
+ },
90
+ onCancel: () => setCurrentFileContent(undefined),
91
+ handleModalToggle: () => setIsEditModalOpen(false),
92
+ displayMode: ChatbotDisplayMode.fullscreen
93
+ }
94
+ ));
95
+ };
96
+
97
+ export { Attachment as default };
98
+ //# sourceMappingURL=Attachment.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Attachment.esm.js","sources":["../../src/components/Attachment.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\n\nimport { makeStyles } from '@material-ui/core';\nimport {\n AttachmentEdit,\n ChatbotDisplayMode,\n PreviewAttachment,\n} from '@patternfly/chatbot';\n\nimport { useFileAttachmentContext } from './AttachmentContext';\n\nconst useStyles = makeStyles(() => ({\n modalFooter: {\n '&>button': {\n width: '12% !important',\n },\n },\n}));\nconst Attachment = () => {\n const {\n currentFileContent,\n setFileContents,\n modalState,\n setCurrentFileContent,\n } = useFileAttachmentContext();\n const classes = useStyles();\n\n if (!currentFileContent) {\n return null;\n }\n const {\n previewModalKey,\n isPreviewModalOpen,\n isEditModalOpen,\n setPreviewModalKey,\n setIsEditModalOpen,\n setIsPreviewModalOpen,\n } = modalState;\n\n return (\n <>\n <PreviewAttachment\n key={previewModalKey}\n code={currentFileContent?.content}\n fileName={currentFileContent?.name}\n isModalOpen={isPreviewModalOpen}\n secondaryActionButtonText=\"Close\"\n modalFooterClassName={classes.modalFooter}\n onEdit={() => {\n setIsPreviewModalOpen(false);\n setIsEditModalOpen(true);\n }}\n onDismiss={() => {\n setCurrentFileContent(undefined);\n setIsPreviewModalOpen(false);\n }}\n handleModalToggle={() => setIsPreviewModalOpen(false)}\n displayMode={ChatbotDisplayMode.fullscreen}\n />\n <AttachmentEdit\n key={currentFileContent?.content}\n code={currentFileContent?.content}\n fileName={currentFileContent?.name}\n isModalOpen={isEditModalOpen}\n modalFooterClassName={classes.modalFooter}\n onSave={(_, content) => {\n setCurrentFileContent({\n ...currentFileContent,\n content,\n });\n setFileContents(prev => {\n const existingIndex = prev.findIndex(\n f => f.name === currentFileContent?.name,\n );\n\n if (existingIndex !== -1) {\n // Update the existing file's content\n const updated = [...prev];\n updated[existingIndex] = {\n ...updated[existingIndex],\n content: content as string,\n };\n return updated;\n }\n\n // File doesn't exist, add a new one\n return [\n ...prev,\n {\n name: currentFileContent?.name,\n type: currentFileContent?.type,\n content: content as string,\n },\n ];\n });\n\n setPreviewModalKey(prev => prev + 1);\n setIsEditModalOpen(false);\n setIsPreviewModalOpen(true);\n }}\n onCancel={() => setCurrentFileContent(undefined)}\n handleModalToggle={() => setIsEditModalOpen(false)}\n displayMode={ChatbotDisplayMode.fullscreen}\n />\n </>\n );\n};\n\nexport default Attachment;\n"],"names":["React"],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ import React__default from 'react';
2
+ import { isSupportedFileType, readFileAsText } from '../utils/attachment-utils.esm.js';
3
+
4
+ const FileAttachmentContext = React__default.createContext(null);
5
+ const FileAttachmentContextProvider = ({ children }) => {
6
+ const [currentFileContent, setCurrentFileContent] = React__default.useState();
7
+ const [isLoadingFile, setIsLoadingFile] = React__default.useState({});
8
+ const [previewModalKey, setPreviewModalKey] = React__default.useState(0);
9
+ const [showAlert, setShowAlert] = React__default.useState(false);
10
+ const [uploadError, setUploadError] = React__default.useState({
11
+ message: null
12
+ });
13
+ const [fileContents, setFileContents] = React__default.useState([]);
14
+ const [isPreviewModalOpen, setIsPreviewModalOpen] = React__default.useState(false);
15
+ const [isEditModalOpen, setIsEditModalOpen] = React__default.useState(false);
16
+ const handleFileUpload = (fileArr) => {
17
+ const existingFile = fileContents.find(
18
+ (file) => file.name === fileArr[0].name
19
+ );
20
+ if (existingFile) {
21
+ setShowAlert(true);
22
+ setUploadError({ type: "info", message: "File already exists." });
23
+ return;
24
+ }
25
+ setIsLoadingFile({ [fileArr[0].name]: true });
26
+ if (fileArr.length > 1) {
27
+ setShowAlert(true);
28
+ setUploadError({
29
+ message: "Uploaded more than one file."
30
+ });
31
+ return;
32
+ }
33
+ if (!isSupportedFileType(fileArr[0])) {
34
+ setShowAlert(true);
35
+ setUploadError({
36
+ message: "Unsupported file type. Supported types are: .txt, .yaml, .json."
37
+ });
38
+ return;
39
+ }
40
+ if (fileArr[0].size > 25e6) {
41
+ setShowAlert(true);
42
+ setUploadError({
43
+ message: "Your file size is too large. Please ensure that your file is less than 25 MB."
44
+ });
45
+ return;
46
+ }
47
+ readFileAsText(fileArr[0]).then((data) => {
48
+ setFileContents((prev) => [
49
+ ...prev,
50
+ {
51
+ name: fileArr[0].name,
52
+ type: fileArr[0].type,
53
+ content: data
54
+ }
55
+ ]);
56
+ setShowAlert(false);
57
+ setUploadError({ message: null });
58
+ setIsLoadingFile({ [fileArr[0].name]: false });
59
+ }).catch((error) => {
60
+ setUploadError({
61
+ message: `Failed to read file: ${error.message}`
62
+ });
63
+ });
64
+ };
65
+ const modalState = {
66
+ isPreviewModalOpen,
67
+ setIsPreviewModalOpen,
68
+ isEditModalOpen,
69
+ setIsEditModalOpen,
70
+ previewModalKey,
71
+ setPreviewModalKey
72
+ };
73
+ return /* @__PURE__ */ React__default.createElement(
74
+ FileAttachmentContext.Provider,
75
+ {
76
+ value: {
77
+ modalState,
78
+ fileContents,
79
+ setFileContents,
80
+ handleFileUpload,
81
+ isLoadingFile,
82
+ showAlert,
83
+ uploadError,
84
+ setUploadError,
85
+ currentFileContent,
86
+ setCurrentFileContent
87
+ }
88
+ },
89
+ children
90
+ );
91
+ };
92
+ const useFileAttachmentContext = () => {
93
+ const context = React__default.useContext(
94
+ FileAttachmentContext
95
+ );
96
+ if (context === null) {
97
+ throw new Error(
98
+ "useFileAttachmentContext must be within a FileAttachmentContextProvider"
99
+ );
100
+ }
101
+ return context;
102
+ };
103
+
104
+ export { FileAttachmentContext, FileAttachmentContextProvider as default, useFileAttachmentContext };
105
+ //# sourceMappingURL=AttachmentContext.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttachmentContext.esm.js","sources":["../../src/components/AttachmentContext.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\n\nimport { FileContent } from '../types';\nimport { isSupportedFileType, readFileAsText } from '../utils/attachment-utils';\n\ntype UploadError = { type?: 'info' | 'danger'; message: string | null };\ninterface FileAttachmentContextType {\n showAlert: boolean;\n uploadError: UploadError;\n fileContents: FileContent[];\n isLoadingFile: Record<string, boolean>;\n handleFileUpload: (files: File[]) => void;\n setFileContents: React.Dispatch<React.SetStateAction<FileContent[]>>;\n setUploadError: React.Dispatch<React.SetStateAction<UploadError>>;\n currentFileContent?: FileContent;\n setCurrentFileContent: React.Dispatch<\n React.SetStateAction<FileContent | undefined>\n >;\n modalState: {\n previewModalKey: number;\n setPreviewModalKey: React.Dispatch<React.SetStateAction<number>>;\n isPreviewModalOpen: boolean;\n setIsPreviewModalOpen: React.Dispatch<React.SetStateAction<boolean>>;\n isEditModalOpen: boolean;\n setIsEditModalOpen: React.Dispatch<React.SetStateAction<boolean>>;\n };\n}\n\nexport const FileAttachmentContext =\n React.createContext<FileAttachmentContextType | null>(null);\n\nconst FileAttachmentContextProvider: React.FC<{\n children: React.ReactNode;\n}> = ({ children }) => {\n const [currentFileContent, setCurrentFileContent] = React.useState<\n FileContent | undefined\n >();\n const [isLoadingFile, setIsLoadingFile] = React.useState<\n Record<string, boolean>\n >({});\n const [previewModalKey, setPreviewModalKey] = React.useState<number>(0);\n const [showAlert, setShowAlert] = React.useState<boolean>(false);\n const [uploadError, setUploadError] = React.useState<UploadError>({\n message: null,\n });\n const [fileContents, setFileContents] = React.useState<FileContent[]>([]);\n const [isPreviewModalOpen, setIsPreviewModalOpen] =\n React.useState<boolean>(false);\n const [isEditModalOpen, setIsEditModalOpen] = React.useState<boolean>(false);\n const handleFileUpload = (fileArr: File[]) => {\n const existingFile = fileContents.find(\n file => file.name === fileArr[0].name,\n );\n if (existingFile) {\n setShowAlert(true);\n setUploadError({ type: 'info', message: 'File already exists.' });\n return;\n }\n\n setIsLoadingFile({ [fileArr[0].name]: true });\n\n if (fileArr.length > 1) {\n setShowAlert(true);\n setUploadError({\n message: 'Uploaded more than one file.',\n });\n return;\n }\n if (!isSupportedFileType(fileArr[0])) {\n setShowAlert(true);\n setUploadError({\n message:\n 'Unsupported file type. Supported types are: .txt, .yaml, .json.',\n });\n return;\n }\n\n // this is 25MB in bytes; size is in bytes\n if (fileArr[0].size > 25000000) {\n setShowAlert(true);\n setUploadError({\n message:\n 'Your file size is too large. Please ensure that your file is less than 25 MB.',\n });\n return;\n }\n\n readFileAsText(fileArr[0])\n .then(data => {\n setFileContents(prev => [\n ...prev,\n {\n name: fileArr[0].name,\n type: fileArr[0].type,\n content: data as string,\n },\n ]);\n setShowAlert(false);\n setUploadError({ message: null });\n setIsLoadingFile({ [fileArr[0].name]: false });\n })\n .catch((error: DOMException) => {\n setUploadError({\n message: `Failed to read file: ${error.message}`,\n });\n });\n };\n\n const modalState = {\n isPreviewModalOpen,\n setIsPreviewModalOpen,\n isEditModalOpen,\n setIsEditModalOpen,\n previewModalKey,\n setPreviewModalKey,\n };\n return (\n <FileAttachmentContext.Provider\n value={{\n modalState,\n fileContents,\n setFileContents,\n handleFileUpload,\n isLoadingFile,\n showAlert,\n uploadError,\n setUploadError,\n currentFileContent,\n setCurrentFileContent,\n }}\n >\n {children}\n </FileAttachmentContext.Provider>\n );\n};\n\nexport default FileAttachmentContextProvider;\n\nexport const useFileAttachmentContext = (): FileAttachmentContextType => {\n const context = React.useContext<FileAttachmentContextType | null>(\n FileAttachmentContext,\n );\n\n if (context === null) {\n throw new Error(\n 'useFileAttachmentContext must be within a FileAttachmentContextProvider',\n );\n }\n\n return context;\n};\n"],"names":["React"],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import React__default from 'react';
2
+ import { Divider } from '@material-ui/core';
3
+ import { FileDetailsLabel, ChatbotAlert } from '@patternfly/chatbot';
4
+ import { useFileAttachmentContext } from './AttachmentContext.esm.js';
5
+
6
+ const FilePreview = () => {
7
+ const {
8
+ showAlert,
9
+ fileContents,
10
+ isLoadingFile,
11
+ uploadError,
12
+ modalState,
13
+ setFileContents,
14
+ setUploadError,
15
+ setCurrentFileContent
16
+ } = useFileAttachmentContext();
17
+ const { setIsEditModalOpen, setIsPreviewModalOpen, setPreviewModalKey } = modalState;
18
+ const onAttachmentClick = (_, name) => {
19
+ const file = fileContents?.find((f) => f.name === name);
20
+ setCurrentFileContent({
21
+ name,
22
+ type: file?.type ?? "text/plain",
23
+ content: file?.content ?? ""
24
+ });
25
+ setPreviewModalKey((prev) => prev + 1);
26
+ setIsEditModalOpen(false);
27
+ setIsPreviewModalOpen(true);
28
+ };
29
+ const removeFile = (indexToRemove) => {
30
+ setFileContents(
31
+ (prevContents) => prevContents.filter((_, index) => index !== indexToRemove)
32
+ );
33
+ };
34
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, fileContents.length > 0 && /* @__PURE__ */ React__default.createElement(Divider, null), fileContents && /* @__PURE__ */ React__default.createElement("div", { style: { display: "flex", gap: "10px" } }, fileContents.map((file, index) => /* @__PURE__ */ React__default.createElement(
35
+ FileDetailsLabel,
36
+ {
37
+ key: index,
38
+ fileName: file.name,
39
+ isLoading: isLoadingFile[file.name],
40
+ onClick: onAttachmentClick,
41
+ onClose: () => {
42
+ removeFile(index);
43
+ }
44
+ }
45
+ ))), showAlert && uploadError.message && /* @__PURE__ */ React__default.createElement(
46
+ ChatbotAlert,
47
+ {
48
+ component: "h4",
49
+ title: "File upload failed",
50
+ variant: uploadError.type ?? "danger",
51
+ isInline: true,
52
+ onClose: () => setUploadError({ message: null })
53
+ },
54
+ uploadError.message
55
+ ));
56
+ };
57
+
58
+ export { FilePreview as default };
59
+ //# sourceMappingURL=FilePreview.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilePreview.esm.js","sources":["../../src/components/FilePreview.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React from 'react';\n\nimport { Divider } from '@material-ui/core';\nimport { ChatbotAlert, FileDetailsLabel } from '@patternfly/chatbot';\n\nimport { useFileAttachmentContext } from './AttachmentContext';\n\nconst FilePreview = () => {\n const {\n showAlert,\n fileContents,\n isLoadingFile,\n uploadError,\n modalState,\n setFileContents,\n setUploadError,\n setCurrentFileContent,\n } = useFileAttachmentContext();\n\n const { setIsEditModalOpen, setIsPreviewModalOpen, setPreviewModalKey } =\n modalState;\n\n const onAttachmentClick = (_: React.MouseEvent, name: string) => {\n const file = fileContents?.find(f => f.name === name);\n setCurrentFileContent({\n name,\n type: file?.type ?? 'text/plain',\n content: file?.content ?? '',\n });\n setPreviewModalKey(prev => prev + 1); // update key to re-render content\n setIsEditModalOpen(false);\n setIsPreviewModalOpen(true);\n };\n\n const removeFile = (indexToRemove: number) => {\n setFileContents(prevContents =>\n prevContents.filter((_, index) => index !== indexToRemove),\n );\n };\n\n return (\n <>\n {fileContents.length > 0 && <Divider />}\n {fileContents && (\n <div style={{ display: 'flex', gap: '10px' }}>\n {fileContents.map((file, index) => (\n <FileDetailsLabel\n key={index}\n fileName={file.name}\n isLoading={isLoadingFile[file.name]}\n onClick={onAttachmentClick}\n onClose={() => {\n removeFile(index);\n }}\n />\n ))}\n </div>\n )}\n {showAlert && uploadError.message && (\n <ChatbotAlert\n component=\"h4\"\n title=\"File upload failed\"\n variant={uploadError.type ?? 'danger'}\n isInline\n onClose={() => setUploadError({ message: null })}\n >\n {uploadError.message}\n </ChatbotAlert>\n )}\n </>\n );\n};\n\nexport default FilePreview;\n"],"names":["React"],"mappings":";;;;;AAsBA,MAAM,cAAc,MAAM;AACxB,EAAM,MAAA;AAAA,IACJ,SAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,eAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,MACE,wBAAyB,EAAA;AAE7B,EAAA,MAAM,EAAE,kBAAA,EAAoB,qBAAuB,EAAA,kBAAA,EACjD,GAAA,UAAA;AAEF,EAAM,MAAA,iBAAA,GAAoB,CAAC,CAAA,EAAqB,IAAiB,KAAA;AAC/D,IAAA,MAAM,OAAO,YAAc,EAAA,IAAA,CAAK,CAAK,CAAA,KAAA,CAAA,CAAE,SAAS,IAAI,CAAA;AACpD,IAAsB,qBAAA,CAAA;AAAA,MACpB,IAAA;AAAA,MACA,IAAA,EAAM,MAAM,IAAQ,IAAA,YAAA;AAAA,MACpB,OAAA,EAAS,MAAM,OAAW,IAAA;AAAA,KAC3B,CAAA;AACD,IAAmB,kBAAA,CAAA,CAAA,IAAA,KAAQ,OAAO,CAAC,CAAA;AACnC,IAAA,kBAAA,CAAmB,KAAK,CAAA;AACxB,IAAA,qBAAA,CAAsB,IAAI,CAAA;AAAA,GAC5B;AAEA,EAAM,MAAA,UAAA,GAAa,CAAC,aAA0B,KAAA;AAC5C,IAAA,eAAA;AAAA,MAAgB,kBACd,YAAa,CAAA,MAAA,CAAO,CAAC,CAAG,EAAA,KAAA,KAAU,UAAU,aAAa;AAAA,KAC3D;AAAA,GACF;AAEA,EACE,uBAAAA,cAAA,CAAA,aAAA,CAAAA,cAAA,CAAA,QAAA,EAAA,IAAA,EACG,aAAa,MAAS,GAAA,CAAA,iDAAM,OAAQ,EAAA,IAAA,CAAA,EACpC,gCACEA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAI,OAAO,EAAE,OAAA,EAAS,QAAQ,GAAK,EAAA,MAAA,MACjC,YAAa,CAAA,GAAA,CAAI,CAAC,IAAA,EAAM,KACvB,qBAAAA,cAAA,CAAA,aAAA;AAAA,IAAC,gBAAA;AAAA,IAAA;AAAA,MACC,GAAK,EAAA,KAAA;AAAA,MACL,UAAU,IAAK,CAAA,IAAA;AAAA,MACf,SAAA,EAAW,aAAc,CAAA,IAAA,CAAK,IAAI,CAAA;AAAA,MAClC,OAAS,EAAA,iBAAA;AAAA,MACT,SAAS,MAAM;AACb,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB;AAAA,GAEH,CACH,CAED,EAAA,SAAA,IAAa,YAAY,OACxB,oBAAAA,cAAA,CAAA,aAAA;AAAA,IAAC,YAAA;AAAA,IAAA;AAAA,MACC,SAAU,EAAA,IAAA;AAAA,MACV,KAAM,EAAA,oBAAA;AAAA,MACN,OAAA,EAAS,YAAY,IAAQ,IAAA,QAAA;AAAA,MAC7B,QAAQ,EAAA,IAAA;AAAA,MACR,SAAS,MAAM,cAAA,CAAe,EAAE,OAAA,EAAS,MAAM;AAAA,KAAA;AAAA,IAE9C,WAAY,CAAA;AAAA,GAGnB,CAAA;AAEJ;;;;"}
@@ -17,8 +17,13 @@ import { useLastOpenedConversation } from '../hooks/useLastOpenedConversation.es
17
17
  import { useLightspeedDeletePermission } from '../hooks/useLightspeedDeletePermission.esm.js';
18
18
  import '@backstage/plugin-permission-react';
19
19
  import '@red-hat-developer-hub/backstage-plugin-lightspeed-common';
20
+ import { useWelcomePrompts } from '../hooks/useWelcomePrompts.esm.js';
21
+ import { getAttachments } from '../utils/attachment-utils.esm.js';
20
22
  import { getCategorizeMessages, getFootnoteProps } from '../utils/lightspeed-chatbox-utils.esm.js';
23
+ import Attachment from './Attachment.esm.js';
24
+ import { useFileAttachmentContext } from './AttachmentContext.esm.js';
21
25
  import { DeleteModal } from './DeleteModal.esm.js';
26
+ import FilePreview from './FilePreview.esm.js';
22
27
  import { LightspeedChatBox } from './LightspeedChatBox.esm.js';
23
28
  import { LightspeedChatBoxHeader } from './LightspeedChatBoxHeader.esm.js';
24
29
 
@@ -48,6 +53,11 @@ const useStyles = makeStyles((theme) => ({
48
53
  width: "95% !important",
49
54
  maxWidth: "unset !important"
50
55
  }
56
+ },
57
+ footerPopover: {
58
+ "& img": {
59
+ maxWidth: "100%"
60
+ }
51
61
  }
52
62
  }));
53
63
  const LightspeedChat = ({
@@ -71,6 +81,7 @@ const LightspeedChat = ({
71
81
  const [targetConversationId, setTargetConversationId] = React__default.useState("");
72
82
  const [isDeleteModalOpen, setIsDeleteModalOpen] = React__default.useState(false);
73
83
  const { isReady, lastOpenedId, setLastOpenedId, clearLastOpenedId } = useLastOpenedConversation(user);
84
+ const { fileContents, setFileContents, handleFileUpload } = useFileAttachmentContext();
74
85
  React__default.useEffect(() => {
75
86
  if (isReady && lastOpenedId !== null) {
76
87
  setConversationId(lastOpenedId);
@@ -80,6 +91,7 @@ const LightspeedChat = ({
80
91
  const { data: conversations = [] } = useConversations();
81
92
  const { mutateAsync: deleteConversation } = useDeleteConversation();
82
93
  const { allowed: hasDeleteAccess } = useLightspeedDeletePermission();
94
+ const samplePrompts = useWelcomePrompts();
83
95
  React__default.useEffect(() => {
84
96
  if (user && lastOpenedId === null && isReady) {
85
97
  setConversationId(TEMP_CONVERSATION_ID);
@@ -100,6 +112,9 @@ const LightspeedChat = ({
100
112
  queryClient.invalidateQueries({
101
113
  queryKey: ["conversations"]
102
114
  });
115
+ queryClient.invalidateQueries({
116
+ queryKey: ["conversationMessages", conversationId]
117
+ });
103
118
  setNewChatCreated(false);
104
119
  };
105
120
  const { conversationMessages, handleInputPrompt, scrollToBottomRef } = useConversationMessages(
@@ -111,11 +126,6 @@ const LightspeedChat = ({
111
126
  onStart
112
127
  );
113
128
  const [messages, setMessages] = React__default.useState(conversationMessages);
114
- React__default.useEffect(() => {
115
- setTimeout(() => {
116
- scrollToBottomRef.current?.scrollIntoView({ behavior: "auto" });
117
- }, 10);
118
- }, [messages, scrollToBottomRef.current]);
119
129
  const sendMessage = (message) => {
120
130
  if (conversationId !== TEMP_CONVERSATION_ID) {
121
131
  setNewChatCreated(false);
@@ -123,16 +133,19 @@ const LightspeedChat = ({
123
133
  setAnnouncement(
124
134
  `Message from User: ${prompt}. Message from Bot is loading.`
125
135
  );
126
- handleInputPrompt(message.toString());
136
+ handleInputPrompt(message.toString(), getAttachments(fileContents));
127
137
  setIsSendButtonDisabled(true);
138
+ setFileContents([]);
128
139
  };
129
140
  const onNewChat = React__default.useCallback(() => {
130
141
  (async () => {
131
- setMessages([]);
132
- setConversationId(TEMP_CONVERSATION_ID);
133
- setNewChatCreated(true);
142
+ if (conversationId !== TEMP_CONVERSATION_ID) {
143
+ setMessages([]);
144
+ setConversationId(TEMP_CONVERSATION_ID);
145
+ setNewChatCreated(true);
146
+ }
134
147
  })();
135
- }, [setConversationId, setMessages]);
148
+ }, [conversationId, setConversationId, setMessages]);
136
149
  const openDeleteModal = (conversation_id) => {
137
150
  setTargetConversationId(conversation_id);
138
151
  setIsDeleteModalOpen(true);
@@ -208,30 +221,30 @@ const LightspeedChat = ({
208
221
  }
209
222
  return c_id;
210
223
  });
224
+ scrollToBottomRef.current?.scrollToBottom();
211
225
  },
212
- [setConversationId]
226
+ [setConversationId, scrollToBottomRef]
213
227
  );
214
228
  const conversationFound = !!conversations.find(
215
229
  (c) => c.conversation_id === conversationId
216
230
  );
217
- const welcomePrompts = newChatCreated && conversationMessages.length === 0 || !conversationFound && conversationMessages.length === 0 ? [
218
- {
219
- title: "Topic 1",
220
- message: "Helpful prompt for Topic 1",
221
- onClick: () => sendMessage("Helpful prompt for Topic 1")
222
- },
223
- {
224
- title: "Topic 2",
225
- message: "Helpful prompt for Topic 2",
226
- onClick: () => sendMessage("Helpful prompt for Topic 2")
231
+ const welcomePrompts = newChatCreated && conversationMessages.length === 0 || !conversationFound && conversationMessages.length === 0 ? samplePrompts?.map((prompt2) => ({
232
+ title: prompt2.title,
233
+ message: prompt2.message,
234
+ onClick: () => {
235
+ sendMessage(prompt2.message);
227
236
  }
228
- ] : [];
237
+ })) : [];
229
238
  const handleFilter = React__default.useCallback((value) => {
230
239
  setFilterValue(value);
231
240
  }, []);
232
241
  const onDrawerToggle = React__default.useCallback(() => {
233
242
  setIsDrawerOpen((isOpen) => !isOpen);
234
243
  }, []);
244
+ const handleAttach = (data, event) => {
245
+ event.preventDefault();
246
+ handleFileUpload(data);
247
+ };
235
248
  if (error) {
236
249
  return /* @__PURE__ */ React__default.createElement(Box, { padding: 1 }, /* @__PURE__ */ React__default.createElement(ErrorPanel, { error }));
237
250
  }
@@ -287,18 +300,19 @@ const LightspeedChat = ({
287
300
  ref: scrollToBottomRef,
288
301
  welcomePrompts
289
302
  }
290
- )), /* @__PURE__ */ React__default.createElement(ChatbotFooter, { className: classes.footer }, /* @__PURE__ */ React__default.createElement(
303
+ )), /* @__PURE__ */ React__default.createElement(ChatbotFooter, { className: classes.footer }, /* @__PURE__ */ React__default.createElement(FilePreview, null), /* @__PURE__ */ React__default.createElement(
291
304
  MessageBar,
292
305
  {
293
306
  onSendMessage: sendMessage,
294
307
  isSendButtonDisabled,
295
- hasAttachButton: false,
308
+ hasAttachButton: true,
309
+ handleAttach,
296
310
  hasMicrophoneButton: true
297
311
  }
298
- ), /* @__PURE__ */ React__default.createElement(ChatbotFootnote, { ...getFootnoteProps() })))
312
+ ), /* @__PURE__ */ React__default.createElement(ChatbotFootnote, { ...getFootnoteProps(classes.footerPopover) })))
299
313
  }
300
314
  )
301
- ));
315
+ ), /* @__PURE__ */ React__default.createElement(Attachment, null));
302
316
  };
303
317
 
304
318
  export { LightspeedChat };