@red-hat-developer-hub/backstage-plugin-lightspeed 2.4.0 → 2.5.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 (61) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -1
  3. package/dist/alpha.d.ts +6 -0
  4. package/dist/api/NotebooksApiClient.esm.js +36 -0
  5. package/dist/api/NotebooksApiClient.esm.js.map +1 -1
  6. package/dist/api/notebooksApi.esm.js.map +1 -1
  7. package/dist/components/LightSpeedChat.esm.js +143 -26
  8. package/dist/components/LightSpeedChat.esm.js.map +1 -1
  9. package/dist/components/LightspeedChatBoxHeader.esm.js +2 -1
  10. package/dist/components/LightspeedChatBoxHeader.esm.js.map +1 -1
  11. package/dist/components/LightspeedDrawerContext.esm.js.map +1 -1
  12. package/dist/components/Router.esm.js +2 -1
  13. package/dist/components/Router.esm.js.map +1 -1
  14. package/dist/components/notebooks/AddDocumentModal.esm.js +122 -23
  15. package/dist/components/notebooks/AddDocumentModal.esm.js.map +1 -1
  16. package/dist/components/notebooks/DocumentSidebar.esm.js +85 -5
  17. package/dist/components/notebooks/DocumentSidebar.esm.js.map +1 -1
  18. package/dist/components/notebooks/FileListItem.esm.js +94 -0
  19. package/dist/components/notebooks/FileListItem.esm.js.map +1 -0
  20. package/dist/components/notebooks/NotebookView.esm.js +249 -45
  21. package/dist/components/notebooks/NotebookView.esm.js.map +1 -1
  22. package/dist/components/notebooks/OverwriteConfirmModal.esm.js +2 -1
  23. package/dist/components/notebooks/OverwriteConfirmModal.esm.js.map +1 -1
  24. package/dist/components/notebooks/SidebarCollapseIcon.esm.js +5 -2
  25. package/dist/components/notebooks/SidebarCollapseIcon.esm.js.map +1 -1
  26. package/dist/hooks/notebooks/useCreateNotebookMessage.esm.js +24 -0
  27. package/dist/hooks/notebooks/useCreateNotebookMessage.esm.js.map +1 -0
  28. package/dist/hooks/notebooks/useNotebookDocuments.esm.js +1 -1
  29. package/dist/hooks/notebooks/useNotebookDocuments.esm.js.map +1 -1
  30. package/dist/hooks/notebooks/useNotebookSession.esm.js +18 -0
  31. package/dist/hooks/notebooks/useNotebookSession.esm.js.map +1 -0
  32. package/dist/hooks/toolCallsCacheStore.esm.js +61 -0
  33. package/dist/hooks/toolCallsCacheStore.esm.js.map +1 -0
  34. package/dist/hooks/useConversationMessages.esm.js +21 -16
  35. package/dist/hooks/useConversationMessages.esm.js.map +1 -1
  36. package/dist/hooks/useCreateCoversationMessage.esm.js.map +1 -1
  37. package/dist/hooks/useDeleteConversation.esm.js +2 -0
  38. package/dist/hooks/useDeleteConversation.esm.js.map +1 -1
  39. package/dist/hooks/useLightspeedProviderState.esm.js +35 -8
  40. package/dist/hooks/useLightspeedProviderState.esm.js.map +1 -1
  41. package/dist/index.d.ts +1 -0
  42. package/dist/plugin.esm.js +3 -2
  43. package/dist/plugin.esm.js.map +1 -1
  44. package/dist/routes.esm.js +6 -1
  45. package/dist/routes.esm.js.map +1 -1
  46. package/dist/translations/de.esm.js +6 -0
  47. package/dist/translations/de.esm.js.map +1 -1
  48. package/dist/translations/es.esm.js +6 -0
  49. package/dist/translations/es.esm.js.map +1 -1
  50. package/dist/translations/fr.esm.js +6 -0
  51. package/dist/translations/fr.esm.js.map +1 -1
  52. package/dist/translations/it.esm.js +6 -0
  53. package/dist/translations/it.esm.js.map +1 -1
  54. package/dist/translations/ja.esm.js +6 -0
  55. package/dist/translations/ja.esm.js.map +1 -1
  56. package/dist/translations/ref.esm.js +6 -0
  57. package/dist/translations/ref.esm.js.map +1 -1
  58. package/dist/types.esm.js.map +1 -1
  59. package/dist/utils/lightspeed-chatbox-utils.esm.js +1 -1
  60. package/dist/utils/lightspeed-chatbox-utils.esm.js.map +1 -1
  61. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  ## @red-hat-developer-hub/backstage-plugin-lightspeed
2
2
 
3
+ ## 2.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d621f93: Improved notebook upload modal and MessageBar UX.
8
+ - d621f93: Fixed overwrite flow to add duplicate files to the Add Document modal instead of uploading immediately. Reduced notebook delete toast timeout to 2 seconds.
9
+ - 5d17950: Retain Lightspeed chat and tool-call state when the chat UI remounts (for example when switching display mode between embedded and overlay), so the active thread and tool metadata are not dropped while messages are still streaming or before history refetches.
10
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@2.5.1
11
+
12
+ ## 2.5.0
13
+
14
+ ### Minor Changes
15
+
16
+ - d9df5b8: Add notebook chat with streaming support, document management, and UI improvements.
17
+ - Backend: add SSE transform to normalize Responses API format to legacy streaming format so notebook chat streams token-by-token like the chat tab.
18
+ - Frontend: add notebook chat view with conversation messages, document sidebar with per-document delete, and topic summary display.
19
+ - Fix stale document list when re-opening a notebook by setting query staleTime to 0.
20
+ - Hide model selector on the Notebooks tab while keeping the settings ellipsis menu visible.
21
+
22
+ - 8b0cb12: Add dedicated route for individual notebook view (/lightspeed/notebooks/:notebookId).
23
+
24
+ ### Patch Changes
25
+
26
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@2.5.0
27
+
3
28
  ## 2.4.0
4
29
 
5
30
  ### Patch Changes
package/README.md CHANGED
@@ -85,7 +85,11 @@ Lightspeed is a front-end plugin that enables you to interact with any LLM serve
85
85
  #### Procedure
86
86
 
87
87
  1. Open your Backstage application and select a Lightspeed nav item from the **Navigation**.
88
- 2. Ask you questions to the Lightspeed chatbot.
88
+ 2. Ask your questions to the Lightspeed chatbot.
89
+
90
+ ### Display modes and chat continuity
91
+
92
+ Lightspeed supports multiple **display modes** from Settings (for example overlay, docked, embedded, and fullscreen). Switching modes can remount the chat surface; your **current conversation** and **tool-call metadata** for that thread stay with the session so the active chat is not reset. Live streaming text may not update continuously across a mode switch until the assistant response finishes loading.
89
93
 
90
94
  ### MCP servers settings
91
95
 
package/dist/alpha.d.ts CHANGED
@@ -66,10 +66,12 @@ declare const lightspeedTranslationRef: _backstage_frontend_plugin_api.Translati
66
66
  readonly "notebook.view.upload.heading": string;
67
67
  readonly "notebook.view.upload.action": string;
68
68
  readonly "notebook.view.input.placeholder": string;
69
+ readonly "notebook.view.input.disabledTooltip": string;
69
70
  readonly "notebook.view.sidebar.collapse": string;
70
71
  readonly "notebook.view.sidebar.expand": string;
71
72
  readonly "notebook.view.sidebar.resize": string;
72
73
  readonly "notebook.view.documents.uploading": string;
74
+ readonly "notebook.view.documents.maxReached": string;
73
75
  readonly "notebook.upload.success": string;
74
76
  readonly "notebook.upload.failed": string;
75
77
  readonly "notebook.upload.modal.title": string;
@@ -77,12 +79,16 @@ declare const lightspeedTranslationRef: _backstage_frontend_plugin_api.Translati
77
79
  readonly "notebook.upload.modal.browseButton": string;
78
80
  readonly "notebook.upload.modal.separator": string;
79
81
  readonly "notebook.upload.modal.infoText": string;
82
+ readonly "notebook.upload.modal.selectedFiles": string;
83
+ readonly "notebook.upload.modal.addButton": string;
84
+ readonly "notebook.upload.modal.removeFile": string;
80
85
  readonly "notebook.upload.error.unsupportedType": string;
81
86
  readonly "notebook.upload.error.fileTooLarge": string;
82
87
  readonly "notebook.upload.error.tooManyFiles": string;
83
88
  readonly "notebook.overwrite.modal.title": string;
84
89
  readonly "notebook.overwrite.modal.description": string;
85
90
  readonly "notebook.overwrite.modal.action": string;
91
+ readonly "notebook.document.delete": string;
86
92
  readonly "conversation.delete.confirm.title": string;
87
93
  readonly "conversation.delete.confirm.message": string;
88
94
  readonly "conversation.delete.confirm.action": string;
@@ -78,6 +78,16 @@ class NotebooksApiClient {
78
78
  );
79
79
  return response?.sessions ?? [];
80
80
  }
81
+ async getSession(sessionId) {
82
+ const baseUrl = await this.getBaseUrl();
83
+ const response = await this.fetchJson(
84
+ `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}`
85
+ );
86
+ if (!response.session) {
87
+ throw new Error(response.error ?? "Session not found");
88
+ }
89
+ return response.session;
90
+ }
81
91
  async renameSession(sessionId, name) {
82
92
  const baseUrl = await this.getBaseUrl();
83
93
  await this.fetchJson(
@@ -137,6 +147,32 @@ class NotebooksApiClient {
137
147
  `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents/${encodeURIComponent(documentId)}/status`
138
148
  );
139
149
  }
150
+ async querySession(sessionId, query) {
151
+ const baseUrl = await this.getBaseUrl();
152
+ const response = await this.fetchApi.fetch(
153
+ `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/query`,
154
+ {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({ query })
158
+ }
159
+ );
160
+ if (!response.body) {
161
+ throw new Error("Readable stream is not supported or there is no body.");
162
+ }
163
+ if (!response.ok) {
164
+ const reader = response.body.getReader();
165
+ const { done, value } = await reader.read();
166
+ const text = done ? "" : new TextDecoder("utf-8").decode(value);
167
+ const errorMessage = JSON.parse(text);
168
+ if (errorMessage?.error) {
169
+ throw new Error(
170
+ `failed to query notebook session: ${errorMessage.error}`
171
+ );
172
+ }
173
+ }
174
+ return response.body.getReader();
175
+ }
140
176
  }
141
177
 
142
178
  export { NotebooksApiClient };
@@ -1 +1 @@
1
- {"version":3,"file":"NotebooksApiClient.esm.js","sources":["../../src/api/NotebooksApiClient.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 {\n DocumentListResponse,\n DocumentStatus,\n NotebookSession,\n SessionResponse,\n UploadDocumentResponse,\n} from '../types';\nimport { NotebooksAPI } from './notebooksApi';\n\n/**\n * @public\n * AI Notebooks API client options\n */\nexport type NotebooksOptions = {\n configApi: ConfigApi;\n fetchApi: FetchApi;\n};\n\n/**\n * @public\n * AI Notebooks API client implementation\n */\nexport class NotebooksApiClient implements NotebooksAPI {\n private readonly configApi: ConfigApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: NotebooksOptions) {\n this.configApi = options.configApi;\n this.fetchApi = options.fetchApi;\n }\n\n async getBaseUrl() {\n return `${this.configApi.getString('backend.baseUrl')}/api/lightspeed/notebooks`;\n }\n\n private async handleResponseError(response: Response): Promise<string> {\n let errorMessage = `failed to fetch data, status ${response.status}: ${response.statusText}`;\n try {\n const errorText = await response.text();\n if (errorText) {\n try {\n const errorBody = JSON.parse(errorText);\n if (errorBody?.error) {\n errorMessage = errorBody.error;\n }\n } catch {\n errorMessage = errorText;\n }\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(e);\n }\n return errorMessage;\n }\n\n private async fetchJson<T>(url: string, init?: RequestInit): Promise<T> {\n const response = await this.fetchApi.fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n },\n ...init,\n });\n\n if (!response.ok) {\n throw new Error(await this.handleResponseError(response));\n }\n\n const text = await response.text();\n if (!text) {\n return {} as T;\n }\n return JSON.parse(text) as T;\n }\n\n private async fetchFormData<T>(\n url: string,\n formData: FormData,\n method: string = 'PUT',\n ): Promise<T> {\n const response = await this.fetchApi.fetch(url, {\n method,\n body: formData,\n });\n\n if (!response.ok && response.status !== 202) {\n throw new Error(await this.handleResponseError(response));\n }\n\n const text = await response.text();\n if (!text) {\n return {} as T;\n }\n return JSON.parse(text) as T;\n }\n\n async createSession(name: string, description?: string) {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchJson<SessionResponse>(\n `${baseUrl}/v1/sessions`,\n {\n method: 'POST',\n body: JSON.stringify({ name, description }),\n },\n );\n if (!response.session) {\n throw new Error(response.error ?? 'Failed to create session');\n }\n return response.session;\n }\n\n async listSessions() {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchJson<{ sessions?: NotebookSession[] }>(\n `${baseUrl}/v1/sessions`,\n );\n return response?.sessions ?? [];\n }\n\n async renameSession(sessionId: string, name: string) {\n const baseUrl = await this.getBaseUrl();\n await this.fetchJson(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,\n {\n method: 'PUT',\n body: JSON.stringify({ name }),\n },\n );\n }\n\n async deleteSession(sessionId: string) {\n const baseUrl = await this.getBaseUrl();\n await this.fetchJson(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,\n {\n method: 'DELETE',\n },\n );\n }\n\n async uploadDocument(\n sessionId: string,\n file: File,\n fileType: string,\n title: string,\n newTitle?: string,\n ) {\n const baseUrl = await this.getBaseUrl();\n const formData = new FormData();\n formData.append('file', file);\n formData.append('fileType', fileType);\n formData.append('title', title);\n if (newTitle) {\n formData.append('newTitle', newTitle);\n }\n return this.fetchFormData<UploadDocumentResponse>(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents`,\n formData,\n );\n }\n\n async listDocuments(sessionId: string) {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchJson<DocumentListResponse>(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents`,\n );\n const docs = response?.documents ?? [];\n return docs.map(doc => ({\n ...doc,\n title: (doc as any).title ?? doc.document_id,\n }));\n }\n\n async deleteDocument(sessionId: string, documentId: string) {\n const baseUrl = await this.getBaseUrl();\n await this.fetchJson(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents/${encodeURIComponent(documentId)}`,\n {\n method: 'DELETE',\n },\n );\n }\n\n async getDocumentStatus(sessionId: string, documentId: string) {\n const baseUrl = await this.getBaseUrl();\n return this.fetchJson<DocumentStatus>(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents/${encodeURIComponent(documentId)}/status`,\n );\n }\n}\n"],"names":[],"mappings":"AAwCO,MAAM,kBAA2C,CAAA;AAAA,EACrC,SAAA;AAAA,EACA,QAAA;AAAA,EAEjB,YAAY,OAA2B,EAAA;AACrC,IAAA,IAAA,CAAK,YAAY,OAAQ,CAAA,SAAA;AACzB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AAAA;AAC1B,EAEA,MAAM,UAAa,GAAA;AACjB,IAAA,OAAO,CAAG,EAAA,IAAA,CAAK,SAAU,CAAA,SAAA,CAAU,iBAAiB,CAAC,CAAA,yBAAA,CAAA;AAAA;AACvD,EAEA,MAAc,oBAAoB,QAAqC,EAAA;AACrE,IAAA,IAAI,eAAe,CAAgC,6BAAA,EAAA,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA,CAAA;AAC1F,IAAI,IAAA;AACF,MAAM,MAAA,SAAA,GAAY,MAAM,QAAA,CAAS,IAAK,EAAA;AACtC,MAAA,IAAI,SAAW,EAAA;AACb,QAAI,IAAA;AACF,UAAM,MAAA,SAAA,GAAY,IAAK,CAAA,KAAA,CAAM,SAAS,CAAA;AACtC,UAAA,IAAI,WAAW,KAAO,EAAA;AACpB,YAAA,YAAA,GAAe,SAAU,CAAA,KAAA;AAAA;AAC3B,SACM,CAAA,MAAA;AACN,UAAe,YAAA,GAAA,SAAA;AAAA;AACjB;AACF,aACO,CAAG,EAAA;AAEV,MAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA;AAEhB,IAAO,OAAA,YAAA;AAAA;AACT,EAEA,MAAc,SAAa,CAAA,GAAA,EAAa,IAAgC,EAAA;AACtE,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,GAAK,EAAA;AAAA,MAC9C,OAAS,EAAA;AAAA,QACP,cAAgB,EAAA;AAAA,OAClB;AAAA,MACA,GAAG;AAAA,KACJ,CAAA;AAED,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,IAAI,KAAM,CAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,QAAQ,CAAC,CAAA;AAAA;AAG1D,IAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AACjC,IAAA,IAAI,CAAC,IAAM,EAAA;AACT,MAAA,OAAO,EAAC;AAAA;AAEV,IAAO,OAAA,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA;AACxB,EAEA,MAAc,aAAA,CACZ,GACA,EAAA,QAAA,EACA,SAAiB,KACL,EAAA;AACZ,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,GAAK,EAAA;AAAA,MAC9C,MAAA;AAAA,MACA,IAAM,EAAA;AAAA,KACP,CAAA;AAED,IAAA,IAAI,CAAC,QAAA,CAAS,EAAM,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AAC3C,MAAA,MAAM,IAAI,KAAM,CAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,QAAQ,CAAC,CAAA;AAAA;AAG1D,IAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AACjC,IAAA,IAAI,CAAC,IAAM,EAAA;AACT,MAAA,OAAO,EAAC;AAAA;AAEV,IAAO,OAAA,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA;AACxB,EAEA,MAAM,aAAc,CAAA,IAAA,EAAc,WAAsB,EAAA;AACtD,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,SAAA;AAAA,MAC1B,GAAG,OAAO,CAAA,YAAA,CAAA;AAAA,MACV;AAAA,QACE,MAAQ,EAAA,MAAA;AAAA,QACR,MAAM,IAAK,CAAA,SAAA,CAAU,EAAE,IAAA,EAAM,aAAa;AAAA;AAC5C,KACF;AACA,IAAI,IAAA,CAAC,SAAS,OAAS,EAAA;AACrB,MAAA,MAAM,IAAI,KAAA,CAAM,QAAS,CAAA,KAAA,IAAS,0BAA0B,CAAA;AAAA;AAE9D,IAAA,OAAO,QAAS,CAAA,OAAA;AAAA;AAClB,EAEA,MAAM,YAAe,GAAA;AACnB,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,SAAA;AAAA,MAC1B,GAAG,OAAO,CAAA,YAAA;AAAA,KACZ;AACA,IAAO,OAAA,QAAA,EAAU,YAAY,EAAC;AAAA;AAChC,EAEA,MAAM,aAAc,CAAA,SAAA,EAAmB,IAAc,EAAA;AACnD,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,MAAM,IAAK,CAAA,SAAA;AAAA,MACT,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,CAAA;AAAA,MACvD;AAAA,QACE,MAAQ,EAAA,KAAA;AAAA,QACR,IAAM,EAAA,IAAA,CAAK,SAAU,CAAA,EAAE,MAAM;AAAA;AAC/B,KACF;AAAA;AACF,EAEA,MAAM,cAAc,SAAmB,EAAA;AACrC,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,MAAM,IAAK,CAAA,SAAA;AAAA,MACT,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,CAAA;AAAA,MACvD;AAAA,QACE,MAAQ,EAAA;AAAA;AACV,KACF;AAAA;AACF,EAEA,MAAM,cACJ,CAAA,SAAA,EACA,IACA,EAAA,QAAA,EACA,OACA,QACA,EAAA;AACA,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,IAAI,QAAS,EAAA;AAC9B,IAAS,QAAA,CAAA,MAAA,CAAO,QAAQ,IAAI,CAAA;AAC5B,IAAS,QAAA,CAAA,MAAA,CAAO,YAAY,QAAQ,CAAA;AACpC,IAAS,QAAA,CAAA,MAAA,CAAO,SAAS,KAAK,CAAA;AAC9B,IAAA,IAAI,QAAU,EAAA;AACZ,MAAS,QAAA,CAAA,MAAA,CAAO,YAAY,QAAQ,CAAA;AAAA;AAEtC,IAAA,OAAO,IAAK,CAAA,aAAA;AAAA,MACV,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,UAAA,CAAA;AAAA,MACvD;AAAA,KACF;AAAA;AACF,EAEA,MAAM,cAAc,SAAmB,EAAA;AACrC,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,SAAA;AAAA,MAC1B,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,UAAA;AAAA,KACzD;AACA,IAAM,MAAA,IAAA,GAAO,QAAU,EAAA,SAAA,IAAa,EAAC;AACrC,IAAO,OAAA,IAAA,CAAK,IAAI,CAAQ,GAAA,MAAA;AAAA,MACtB,GAAG,GAAA;AAAA,MACH,KAAA,EAAQ,GAAY,CAAA,KAAA,IAAS,GAAI,CAAA;AAAA,KACjC,CAAA,CAAA;AAAA;AACJ,EAEA,MAAM,cAAe,CAAA,SAAA,EAAmB,UAAoB,EAAA;AAC1D,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,MAAM,IAAK,CAAA,SAAA;AAAA,MACT,CAAA,EAAG,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,WAAA,EAAc,kBAAmB,CAAA,UAAU,CAAC,CAAA,CAAA;AAAA,MACnG;AAAA,QACE,MAAQ,EAAA;AAAA;AACV,KACF;AAAA;AACF,EAEA,MAAM,iBAAkB,CAAA,SAAA,EAAmB,UAAoB,EAAA;AAC7D,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,OAAO,IAAK,CAAA,SAAA;AAAA,MACV,CAAA,EAAG,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,WAAA,EAAc,kBAAmB,CAAA,UAAU,CAAC,CAAA,OAAA;AAAA,KACrG;AAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"NotebooksApiClient.esm.js","sources":["../../src/api/NotebooksApiClient.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 {\n DocumentListResponse,\n DocumentStatus,\n NotebookSession,\n SessionResponse,\n UploadDocumentResponse,\n} from '../types';\nimport { NotebooksAPI } from './notebooksApi';\n\n/**\n * @public\n * AI Notebooks API client options\n */\nexport type NotebooksOptions = {\n configApi: ConfigApi;\n fetchApi: FetchApi;\n};\n\n/**\n * @public\n * AI Notebooks API client implementation\n */\nexport class NotebooksApiClient implements NotebooksAPI {\n private readonly configApi: ConfigApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: NotebooksOptions) {\n this.configApi = options.configApi;\n this.fetchApi = options.fetchApi;\n }\n\n async getBaseUrl() {\n return `${this.configApi.getString('backend.baseUrl')}/api/lightspeed/notebooks`;\n }\n\n private async handleResponseError(response: Response): Promise<string> {\n let errorMessage = `failed to fetch data, status ${response.status}: ${response.statusText}`;\n try {\n const errorText = await response.text();\n if (errorText) {\n try {\n const errorBody = JSON.parse(errorText);\n if (errorBody?.error) {\n errorMessage = errorBody.error;\n }\n } catch {\n errorMessage = errorText;\n }\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(e);\n }\n return errorMessage;\n }\n\n private async fetchJson<T>(url: string, init?: RequestInit): Promise<T> {\n const response = await this.fetchApi.fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n },\n ...init,\n });\n\n if (!response.ok) {\n throw new Error(await this.handleResponseError(response));\n }\n\n const text = await response.text();\n if (!text) {\n return {} as T;\n }\n return JSON.parse(text) as T;\n }\n\n private async fetchFormData<T>(\n url: string,\n formData: FormData,\n method: string = 'PUT',\n ): Promise<T> {\n const response = await this.fetchApi.fetch(url, {\n method,\n body: formData,\n });\n\n if (!response.ok && response.status !== 202) {\n throw new Error(await this.handleResponseError(response));\n }\n\n const text = await response.text();\n if (!text) {\n return {} as T;\n }\n return JSON.parse(text) as T;\n }\n\n async createSession(name: string, description?: string) {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchJson<SessionResponse>(\n `${baseUrl}/v1/sessions`,\n {\n method: 'POST',\n body: JSON.stringify({ name, description }),\n },\n );\n if (!response.session) {\n throw new Error(response.error ?? 'Failed to create session');\n }\n return response.session;\n }\n\n async listSessions() {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchJson<{ sessions?: NotebookSession[] }>(\n `${baseUrl}/v1/sessions`,\n );\n return response?.sessions ?? [];\n }\n\n async getSession(sessionId: string) {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchJson<SessionResponse>(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,\n );\n if (!response.session) {\n throw new Error(response.error ?? 'Session not found');\n }\n return response.session;\n }\n\n async renameSession(sessionId: string, name: string) {\n const baseUrl = await this.getBaseUrl();\n await this.fetchJson(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,\n {\n method: 'PUT',\n body: JSON.stringify({ name }),\n },\n );\n }\n\n async deleteSession(sessionId: string) {\n const baseUrl = await this.getBaseUrl();\n await this.fetchJson(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,\n {\n method: 'DELETE',\n },\n );\n }\n\n async uploadDocument(\n sessionId: string,\n file: File,\n fileType: string,\n title: string,\n newTitle?: string,\n ) {\n const baseUrl = await this.getBaseUrl();\n const formData = new FormData();\n formData.append('file', file);\n formData.append('fileType', fileType);\n formData.append('title', title);\n if (newTitle) {\n formData.append('newTitle', newTitle);\n }\n return this.fetchFormData<UploadDocumentResponse>(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents`,\n formData,\n );\n }\n\n async listDocuments(sessionId: string) {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchJson<DocumentListResponse>(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents`,\n );\n const docs = response?.documents ?? [];\n return docs.map(doc => ({\n ...doc,\n title: (doc as any).title ?? doc.document_id,\n }));\n }\n\n async deleteDocument(sessionId: string, documentId: string) {\n const baseUrl = await this.getBaseUrl();\n await this.fetchJson(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents/${encodeURIComponent(documentId)}`,\n {\n method: 'DELETE',\n },\n );\n }\n\n async getDocumentStatus(sessionId: string, documentId: string) {\n const baseUrl = await this.getBaseUrl();\n return this.fetchJson<DocumentStatus>(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/documents/${encodeURIComponent(documentId)}/status`,\n );\n }\n\n async querySession(\n sessionId: string,\n query: string,\n ): Promise<ReadableStreamDefaultReader<Uint8Array>> {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v1/sessions/${encodeURIComponent(sessionId)}/query`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ query }),\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 reader = response.body.getReader();\n const { done, value } = await reader.read();\n const text = done ? '' : new TextDecoder('utf-8').decode(value);\n const errorMessage = JSON.parse(text);\n if (errorMessage?.error) {\n throw new Error(\n `failed to query notebook session: ${errorMessage.error}`,\n );\n }\n }\n\n return response.body.getReader();\n }\n}\n"],"names":[],"mappings":"AAwCO,MAAM,kBAA2C,CAAA;AAAA,EACrC,SAAA;AAAA,EACA,QAAA;AAAA,EAEjB,YAAY,OAA2B,EAAA;AACrC,IAAA,IAAA,CAAK,YAAY,OAAQ,CAAA,SAAA;AACzB,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA;AAAA;AAC1B,EAEA,MAAM,UAAa,GAAA;AACjB,IAAA,OAAO,CAAG,EAAA,IAAA,CAAK,SAAU,CAAA,SAAA,CAAU,iBAAiB,CAAC,CAAA,yBAAA,CAAA;AAAA;AACvD,EAEA,MAAc,oBAAoB,QAAqC,EAAA;AACrE,IAAA,IAAI,eAAe,CAAgC,6BAAA,EAAA,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA,CAAA;AAC1F,IAAI,IAAA;AACF,MAAM,MAAA,SAAA,GAAY,MAAM,QAAA,CAAS,IAAK,EAAA;AACtC,MAAA,IAAI,SAAW,EAAA;AACb,QAAI,IAAA;AACF,UAAM,MAAA,SAAA,GAAY,IAAK,CAAA,KAAA,CAAM,SAAS,CAAA;AACtC,UAAA,IAAI,WAAW,KAAO,EAAA;AACpB,YAAA,YAAA,GAAe,SAAU,CAAA,KAAA;AAAA;AAC3B,SACM,CAAA,MAAA;AACN,UAAe,YAAA,GAAA,SAAA;AAAA;AACjB;AACF,aACO,CAAG,EAAA;AAEV,MAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA;AAEhB,IAAO,OAAA,YAAA;AAAA;AACT,EAEA,MAAc,SAAa,CAAA,GAAA,EAAa,IAAgC,EAAA;AACtE,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,GAAK,EAAA;AAAA,MAC9C,OAAS,EAAA;AAAA,QACP,cAAgB,EAAA;AAAA,OAClB;AAAA,MACA,GAAG;AAAA,KACJ,CAAA;AAED,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,IAAI,KAAM,CAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,QAAQ,CAAC,CAAA;AAAA;AAG1D,IAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AACjC,IAAA,IAAI,CAAC,IAAM,EAAA;AACT,MAAA,OAAO,EAAC;AAAA;AAEV,IAAO,OAAA,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA;AACxB,EAEA,MAAc,aAAA,CACZ,GACA,EAAA,QAAA,EACA,SAAiB,KACL,EAAA;AACZ,IAAA,MAAM,QAAW,GAAA,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,GAAK,EAAA;AAAA,MAC9C,MAAA;AAAA,MACA,IAAM,EAAA;AAAA,KACP,CAAA;AAED,IAAA,IAAI,CAAC,QAAA,CAAS,EAAM,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AAC3C,MAAA,MAAM,IAAI,KAAM,CAAA,MAAM,IAAK,CAAA,mBAAA,CAAoB,QAAQ,CAAC,CAAA;AAAA;AAG1D,IAAM,MAAA,IAAA,GAAO,MAAM,QAAA,CAAS,IAAK,EAAA;AACjC,IAAA,IAAI,CAAC,IAAM,EAAA;AACT,MAAA,OAAO,EAAC;AAAA;AAEV,IAAO,OAAA,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA;AACxB,EAEA,MAAM,aAAc,CAAA,IAAA,EAAc,WAAsB,EAAA;AACtD,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,SAAA;AAAA,MAC1B,GAAG,OAAO,CAAA,YAAA,CAAA;AAAA,MACV;AAAA,QACE,MAAQ,EAAA,MAAA;AAAA,QACR,MAAM,IAAK,CAAA,SAAA,CAAU,EAAE,IAAA,EAAM,aAAa;AAAA;AAC5C,KACF;AACA,IAAI,IAAA,CAAC,SAAS,OAAS,EAAA;AACrB,MAAA,MAAM,IAAI,KAAA,CAAM,QAAS,CAAA,KAAA,IAAS,0BAA0B,CAAA;AAAA;AAE9D,IAAA,OAAO,QAAS,CAAA,OAAA;AAAA;AAClB,EAEA,MAAM,YAAe,GAAA;AACnB,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,SAAA;AAAA,MAC1B,GAAG,OAAO,CAAA,YAAA;AAAA,KACZ;AACA,IAAO,OAAA,QAAA,EAAU,YAAY,EAAC;AAAA;AAChC,EAEA,MAAM,WAAW,SAAmB,EAAA;AAClC,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,SAAA;AAAA,MAC1B,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA;AAAA,KACzD;AACA,IAAI,IAAA,CAAC,SAAS,OAAS,EAAA;AACrB,MAAA,MAAM,IAAI,KAAA,CAAM,QAAS,CAAA,KAAA,IAAS,mBAAmB,CAAA;AAAA;AAEvD,IAAA,OAAO,QAAS,CAAA,OAAA;AAAA;AAClB,EAEA,MAAM,aAAc,CAAA,SAAA,EAAmB,IAAc,EAAA;AACnD,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,MAAM,IAAK,CAAA,SAAA;AAAA,MACT,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,CAAA;AAAA,MACvD;AAAA,QACE,MAAQ,EAAA,KAAA;AAAA,QACR,IAAM,EAAA,IAAA,CAAK,SAAU,CAAA,EAAE,MAAM;AAAA;AAC/B,KACF;AAAA;AACF,EAEA,MAAM,cAAc,SAAmB,EAAA;AACrC,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,MAAM,IAAK,CAAA,SAAA;AAAA,MACT,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,CAAA;AAAA,MACvD;AAAA,QACE,MAAQ,EAAA;AAAA;AACV,KACF;AAAA;AACF,EAEA,MAAM,cACJ,CAAA,SAAA,EACA,IACA,EAAA,QAAA,EACA,OACA,QACA,EAAA;AACA,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,IAAI,QAAS,EAAA;AAC9B,IAAS,QAAA,CAAA,MAAA,CAAO,QAAQ,IAAI,CAAA;AAC5B,IAAS,QAAA,CAAA,MAAA,CAAO,YAAY,QAAQ,CAAA;AACpC,IAAS,QAAA,CAAA,MAAA,CAAO,SAAS,KAAK,CAAA;AAC9B,IAAA,IAAI,QAAU,EAAA;AACZ,MAAS,QAAA,CAAA,MAAA,CAAO,YAAY,QAAQ,CAAA;AAAA;AAEtC,IAAA,OAAO,IAAK,CAAA,aAAA;AAAA,MACV,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,UAAA,CAAA;AAAA,MACvD;AAAA,KACF;AAAA;AACF,EAEA,MAAM,cAAc,SAAmB,EAAA;AACrC,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,SAAA;AAAA,MAC1B,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,UAAA;AAAA,KACzD;AACA,IAAM,MAAA,IAAA,GAAO,QAAU,EAAA,SAAA,IAAa,EAAC;AACrC,IAAO,OAAA,IAAA,CAAK,IAAI,CAAQ,GAAA,MAAA;AAAA,MACtB,GAAG,GAAA;AAAA,MACH,KAAA,EAAQ,GAAY,CAAA,KAAA,IAAS,GAAI,CAAA;AAAA,KACjC,CAAA,CAAA;AAAA;AACJ,EAEA,MAAM,cAAe,CAAA,SAAA,EAAmB,UAAoB,EAAA;AAC1D,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,MAAM,IAAK,CAAA,SAAA;AAAA,MACT,CAAA,EAAG,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,WAAA,EAAc,kBAAmB,CAAA,UAAU,CAAC,CAAA,CAAA;AAAA,MACnG;AAAA,QACE,MAAQ,EAAA;AAAA;AACV,KACF;AAAA;AACF,EAEA,MAAM,iBAAkB,CAAA,SAAA,EAAmB,UAAoB,EAAA;AAC7D,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAA,OAAO,IAAK,CAAA,SAAA;AAAA,MACV,CAAA,EAAG,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,WAAA,EAAc,kBAAmB,CAAA,UAAU,CAAC,CAAA,OAAA;AAAA,KACrG;AAAA;AACF,EAEA,MAAM,YACJ,CAAA,SAAA,EACA,KACkD,EAAA;AAClD,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,UAAW,EAAA;AACtC,IAAM,MAAA,QAAA,GAAW,MAAM,IAAA,CAAK,QAAS,CAAA,KAAA;AAAA,MACnC,CAAG,EAAA,OAAO,CAAgB,aAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,MAAA,CAAA;AAAA,MACvD;AAAA,QACE,MAAQ,EAAA,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAmB,EAAA;AAAA,QAC9C,IAAM,EAAA,IAAA,CAAK,SAAU,CAAA,EAAE,OAAO;AAAA;AAChC,KACF;AAEA,IAAI,IAAA,CAAC,SAAS,IAAM,EAAA;AAClB,MAAM,MAAA,IAAI,MAAM,uDAAuD,CAAA;AAAA;AAGzE,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,MAAA,GAAS,QAAS,CAAA,IAAA,CAAK,SAAU,EAAA;AACvC,MAAA,MAAM,EAAE,IAAM,EAAA,KAAA,EAAU,GAAA,MAAM,OAAO,IAAK,EAAA;AAC1C,MAAM,MAAA,IAAA,GAAO,OAAO,EAAK,GAAA,IAAI,YAAY,OAAO,CAAA,CAAE,OAAO,KAAK,CAAA;AAC9D,MAAM,MAAA,YAAA,GAAe,IAAK,CAAA,KAAA,CAAM,IAAI,CAAA;AACpC,MAAA,IAAI,cAAc,KAAO,EAAA;AACvB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,kCAAA,EAAqC,aAAa,KAAK,CAAA;AAAA,SACzD;AAAA;AACF;AAGF,IAAO,OAAA,QAAA,CAAS,KAAK,SAAU,EAAA;AAAA;AAEnC;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"notebooksApi.esm.js","sources":["../../src/api/notebooksApi.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, type ApiRef } from '@backstage/core-plugin-api';\n\nimport {\n DocumentStatus,\n NotebookSession,\n SessionDocument,\n UploadDocumentResponse,\n} from '../types';\n\n/**\n * @public\n * AI Notebooks API\n */\nexport type NotebooksAPI = {\n createSession: (\n name: string,\n description?: string,\n ) => Promise<NotebookSession>;\n listSessions: () => Promise<NotebookSession[]>;\n renameSession: (sessionId: string, name: string) => Promise<void>;\n deleteSession: (sessionId: string) => Promise<void>;\n uploadDocument: (\n sessionId: string,\n file: File,\n fileType: string,\n title: string,\n newTitle?: string,\n ) => Promise<UploadDocumentResponse>;\n listDocuments: (sessionId: string) => Promise<SessionDocument[]>;\n deleteDocument: (sessionId: string, documentId: string) => Promise<void>;\n getDocumentStatus: (\n sessionId: string,\n documentId: string,\n ) => Promise<DocumentStatus>;\n};\n\n/**\n * @public\n * AI Notebooks API interface\n */\nexport const notebooksApiRef: ApiRef<NotebooksAPI> = createApiRef<NotebooksAPI>(\n {\n id: 'plugin.lightspeed.notebooks.service',\n },\n);\n"],"names":[],"mappings":";;AAwDO,MAAM,eAAwC,GAAA,YAAA;AAAA,EACnD;AAAA,IACE,EAAI,EAAA;AAAA;AAER;;;;"}
1
+ {"version":3,"file":"notebooksApi.esm.js","sources":["../../src/api/notebooksApi.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, type ApiRef } from '@backstage/core-plugin-api';\n\nimport {\n DocumentStatus,\n NotebookSession,\n SessionDocument,\n UploadDocumentResponse,\n} from '../types';\n\n/**\n * @public\n * AI Notebooks API\n */\nexport type NotebooksAPI = {\n createSession: (\n name: string,\n description?: string,\n ) => Promise<NotebookSession>;\n listSessions: () => Promise<NotebookSession[]>;\n getSession: (sessionId: string) => Promise<NotebookSession>;\n renameSession: (sessionId: string, name: string) => Promise<void>;\n deleteSession: (sessionId: string) => Promise<void>;\n uploadDocument: (\n sessionId: string,\n file: File,\n fileType: string,\n title: string,\n newTitle?: string,\n ) => Promise<UploadDocumentResponse>;\n listDocuments: (sessionId: string) => Promise<SessionDocument[]>;\n deleteDocument: (sessionId: string, documentId: string) => Promise<void>;\n getDocumentStatus: (\n sessionId: string,\n documentId: string,\n ) => Promise<DocumentStatus>;\n querySession: (\n sessionId: string,\n query: string,\n ) => Promise<ReadableStreamDefaultReader<Uint8Array>>;\n};\n\n/**\n * @public\n * AI Notebooks API interface\n */\nexport const notebooksApiRef: ApiRef<NotebooksAPI> = createApiRef<NotebooksAPI>(\n {\n id: 'plugin.lightspeed.notebooks.service',\n },\n);\n"],"names":[],"mappings":";;AA6DO,MAAM,eAAwC,GAAA,YAAA;AAAA,EACnD;AAAA,IACE,EAAI,EAAA;AAAA;AAER;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import { useState, useRef, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
2
+ import { useState, useEffect, useRef, useCallback, useLayoutEffect, useMemo } from 'react';
3
3
  import { useNavigate, useMatch } from 'react-router-dom';
4
4
  import { makeStyles, Button } from '@material-ui/core';
5
5
  import { ChatbotDisplayMode, ChatbotContent, ChatbotFooter, MessageBar, ChatbotFootnote, Settings, Chatbot, ChatbotHeader, ChatbotHeaderMain, ChatbotHeaderMenu, ChatbotHeaderTitle, FileDropZone, ChatbotAlert } from '@patternfly/chatbot';
@@ -20,6 +20,7 @@ import { useLightspeedDeletePermission } from '../hooks/useLightspeedDeletePermi
20
20
  import { useLightspeedNotebooksPermission } from '../hooks/notebooks/useLightspeedNotebooksPermission.esm.js';
21
21
  import '@backstage/plugin-permission-react';
22
22
  import '@red-hat-developer-hub/backstage-plugin-lightspeed-common';
23
+ import { useNotebookSession } from '../hooks/notebooks/useNotebookSession.esm.js';
23
24
  import { useNotebookSessions } from '../hooks/notebooks/useNotebookSessions.esm.js';
24
25
  import { usePinnedChatsSettings } from '../hooks/usePinnedChatsSettings.esm.js';
25
26
  import { useSortSettings } from '../hooks/useSortSettings.esm.js';
@@ -381,15 +382,17 @@ const LightspeedChat = ({
381
382
  const { t } = useTranslation();
382
383
  const navigate = useNavigate();
383
384
  const notebooksRouteMatch = useMatch("/lightspeed/notebooks");
385
+ const notebookViewRouteMatch = useMatch("/lightspeed/notebooks/:notebookId");
386
+ const routeNotebookId = notebookViewRouteMatch?.params?.notebookId;
384
387
  const user = useBackstageUserIdentity();
385
388
  const [filterValue, setFilterValue] = useState("");
386
389
  const [announcement, setAnnouncement] = useState("");
387
390
  const [activeTab, setActiveTab] = useState(
388
- notebooksRouteMatch ? 1 : 0
391
+ notebooksRouteMatch || notebookViewRouteMatch ? 1 : 0
389
392
  );
390
393
  const { allowed: hasNotebooksAccess, loading: notebooksPermissionLoading } = useLightspeedNotebooksPermission();
391
394
  const notebooksPermissionResolved = !notebooksPermissionLoading && hasNotebooksAccess;
392
- const { data: notebooks = [], refetch: refetchNotebooks } = useNotebookSessions(activeTab === 1 && notebooksPermissionResolved);
395
+ const { data: notebooks = [], refetch: refetchNotebooks } = useNotebookSessions(notebooksPermissionResolved);
393
396
  const hasNotebooks = notebooks.length > 0;
394
397
  const [openNotebookMenuId, setOpenNotebookMenuId] = useState(
395
398
  null
@@ -399,6 +402,27 @@ const LightspeedChat = ({
399
402
  const [activeNotebook, setActiveNotebook] = useState(
400
403
  null
401
404
  );
405
+ const {
406
+ data: routeNotebook,
407
+ isLoading: routeNotebookLoading,
408
+ isError: routeNotebookError
409
+ } = useNotebookSession(routeNotebookId);
410
+ useEffect(() => {
411
+ if (routeNotebookId && routeNotebook && !routeNotebookLoading) {
412
+ setActiveNotebook(routeNotebook);
413
+ } else if (routeNotebookId && routeNotebookError) {
414
+ navigate("/lightspeed/notebooks", { replace: true });
415
+ } else if (!routeNotebookId && notebooksRouteMatch) {
416
+ setActiveNotebook(null);
417
+ }
418
+ }, [
419
+ routeNotebookId,
420
+ routeNotebook,
421
+ routeNotebookLoading,
422
+ routeNotebookError,
423
+ notebooksRouteMatch,
424
+ navigate
425
+ ]);
402
426
  const [notebookAlerts, setNotebookAlerts] = useState(
403
427
  []
404
428
  );
@@ -428,7 +452,8 @@ const LightspeedChat = ({
428
452
  currentConversationId: routeConversationId,
429
453
  setCurrentConversationId,
430
454
  draftMessage,
431
- setDraftMessage
455
+ setDraftMessage,
456
+ consumePendingOverlayThreadHandoff
432
457
  } = useLightspeedDrawerContext();
433
458
  const isFullscreenMode = displayMode === ChatbotDisplayMode.embedded;
434
459
  const showChatPanel = !isFullscreenMode || activeTab === 0;
@@ -453,15 +478,15 @@ const LightspeedChat = ({
453
478
  { name: UNTITLED_NOTEBOOK_NAME },
454
479
  {
455
480
  onSuccess: (session) => {
456
- setActiveNotebook(session);
481
+ navigate(`/lightspeed/notebooks/${session.session_id}`);
457
482
  }
458
483
  }
459
484
  );
460
- }, [createNotebookMutation]);
485
+ }, [createNotebookMutation, navigate]);
461
486
  const handleCloseNotebook = useCallback(() => {
462
- setActiveNotebook(null);
487
+ navigate("/lightspeed/notebooks");
463
488
  refetchNotebooks();
464
- }, [refetchNotebooks]);
489
+ }, [navigate, refetchNotebooks]);
465
490
  const handleRemoveNotebookAlert = (key) => {
466
491
  setNotebookAlerts(
467
492
  (prevAlerts) => prevAlerts.filter((alert) => alert.key !== key)
@@ -505,11 +530,44 @@ const LightspeedChat = ({
505
530
  handleFileUpload,
506
531
  setUploadError
507
532
  } = useFileAttachmentContext();
533
+ useLayoutEffect(() => {
534
+ if (!isReady) return;
535
+ if (isFullscreenMode) {
536
+ return;
537
+ }
538
+ const handoff = consumePendingOverlayThreadHandoff?.() ?? false;
539
+ if (handoff) {
540
+ if (routeConversationId) {
541
+ setConversationId(routeConversationId);
542
+ setNewChatCreated(false);
543
+ } else {
544
+ setConversationId(TEMP_CONVERSATION_ID);
545
+ setNewChatCreated(true);
546
+ }
547
+ return;
548
+ }
549
+ if (routeConversationId && routeConversationId !== conversationId) {
550
+ setConversationId(routeConversationId);
551
+ setNewChatCreated(false);
552
+ } else if (!routeConversationId && conversationId === "" && lastOpenedId !== null) {
553
+ setConversationId(lastOpenedId);
554
+ }
555
+ }, [
556
+ isReady,
557
+ isFullscreenMode,
558
+ routeConversationId,
559
+ conversationId,
560
+ lastOpenedId,
561
+ consumePendingOverlayThreadHandoff
562
+ ]);
508
563
  useEffect(() => {
509
- if (isReady && lastOpenedId !== null) {
564
+ if (!isReady || !isFullscreenMode) {
565
+ return;
566
+ }
567
+ if (lastOpenedId !== null) {
510
568
  setConversationId(lastOpenedId);
511
569
  }
512
- }, [lastOpenedId, isReady]);
570
+ }, [lastOpenedId, isReady, isFullscreenMode]);
513
571
  const queryClient = useQueryClient();
514
572
  const {
515
573
  data: conversations = [],
@@ -521,27 +579,53 @@ const LightspeedChat = ({
521
579
  const samplePrompts = useWelcomePrompts();
522
580
  useEffect(() => {
523
581
  if (!user || !isReady) return;
582
+ const onOverlayLikeSurface = isFullscreenMode || !routeConversationId;
583
+ const stillOnProvisionalThread = !conversationId || conversationId === TEMP_CONVERSATION_ID;
524
584
  if (lastOpenedId === null) {
525
- setConversationId(TEMP_CONVERSATION_ID);
585
+ if (onOverlayLikeSurface && stillOnProvisionalThread) {
586
+ setConversationId(TEMP_CONVERSATION_ID);
587
+ }
526
588
  }
527
589
  if (lastOpenedId === TEMP_CONVERSATION_ID || lastOpenedId === null) {
528
- setNewChatCreated(true);
590
+ if (onOverlayLikeSurface && stillOnProvisionalThread) {
591
+ setNewChatCreated(true);
592
+ }
529
593
  }
530
- }, [user, isReady, lastOpenedId, setConversationId]);
594
+ }, [
595
+ user,
596
+ isReady,
597
+ lastOpenedId,
598
+ setConversationId,
599
+ isFullscreenMode,
600
+ routeConversationId,
601
+ conversationId
602
+ ]);
531
603
  useEffect(() => {
532
604
  if (!isLoading && !isRefetching && conversations.length === 0 && lastOpenedId) {
533
605
  clearLastOpenedId();
606
+ setConversationId(TEMP_CONVERSATION_ID);
607
+ setNewChatCreated(true);
608
+ setCurrentConversationId(undefined);
534
609
  }
535
- }, [isLoading, isRefetching, conversations, lastOpenedId, clearLastOpenedId]);
610
+ }, [
611
+ isLoading,
612
+ isRefetching,
613
+ conversations,
614
+ lastOpenedId,
615
+ clearLastOpenedId,
616
+ setCurrentConversationId
617
+ ]);
536
618
  useEffect(() => {
537
619
  if (!isLoading && !isRefetching && routeConversationId && isFullscreenMode) {
538
620
  const conversationExists = conversations.some(
539
621
  (c) => c.conversation_id === routeConversationId
540
622
  );
541
623
  if (!conversationExists) {
542
- setConversationId(TEMP_CONVERSATION_ID);
543
- setCurrentConversationId(undefined);
544
- setNewChatCreated(true);
624
+ if (routeConversationId !== conversationId) {
625
+ setConversationId(TEMP_CONVERSATION_ID);
626
+ setCurrentConversationId(undefined);
627
+ setNewChatCreated(true);
628
+ }
545
629
  } else if (conversationId !== routeConversationId) {
546
630
  setConversationId(routeConversationId);
547
631
  }
@@ -561,6 +645,10 @@ const LightspeedChat = ({
561
645
  setLastOpenedId(conversationId);
562
646
  }
563
647
  }, [conversationId, setLastOpenedId]);
648
+ const viewConversationId = useMemo(
649
+ () => !isFullscreenMode && routeConversationId ? routeConversationId : conversationId,
650
+ [isFullscreenMode, routeConversationId, conversationId]
651
+ );
564
652
  const onStart = (conv_id) => {
565
653
  setConversationId(conv_id);
566
654
  setCurrentConversationId(conv_id);
@@ -578,25 +666,26 @@ const LightspeedChat = ({
578
666
  queryKey: ["conversations"]
579
667
  });
580
668
  queryClient.invalidateQueries({
581
- queryKey: ["conversationMessages", conversationId]
669
+ queryKey: ["conversationMessages", viewConversationId]
582
670
  });
583
671
  setNewChatCreated(false);
584
672
  };
585
673
  const { conversationMessages, handleInputPrompt, scrollToBottomRef } = useConversationMessages(
586
- conversationId,
674
+ viewConversationId,
587
675
  userName,
588
676
  selectedModel,
589
677
  selectedProvider,
590
678
  avatar,
591
679
  onComplete,
592
680
  onStart,
681
+ undefined,
593
682
  onRequestIdReady
594
683
  );
595
684
  const [messages, setMessages] = useState(conversationMessages);
596
685
  const sendMessage = (message) => {
597
686
  if (!message.toString().trim()) return;
598
687
  wasStoppedByUserRef.current = false;
599
- if (conversationId !== TEMP_CONVERSATION_ID) {
688
+ if (viewConversationId !== TEMP_CONVERSATION_ID) {
600
689
  setNewChatCreated(false);
601
690
  }
602
691
  setAnnouncement(
@@ -700,15 +789,33 @@ const LightspeedChat = ({
700
789
  unpinChat
701
790
  ]
702
791
  );
792
+ const notebookConversationIds = useMemo(
793
+ () => new Set(
794
+ notebooks.map((n) => n.metadata?.conversation_id).filter((id) => !!id)
795
+ ),
796
+ [notebooks]
797
+ );
798
+ const chatOnlyConversations = useMemo(
799
+ () => conversations.filter(
800
+ (c) => !notebookConversationIds.has(c.conversation_id)
801
+ ),
802
+ [conversations, notebookConversationIds]
803
+ );
703
804
  const categorizedMessages = useMemo(
704
805
  () => getCategorizeMessages(
705
- conversations,
806
+ chatOnlyConversations,
706
807
  pinnedChats,
707
808
  additionalMessageProps,
708
809
  t,
709
810
  selectedSort
710
811
  ),
711
- [additionalMessageProps, conversations, pinnedChats, t, selectedSort]
812
+ [
813
+ additionalMessageProps,
814
+ chatOnlyConversations,
815
+ pinnedChats,
816
+ t,
817
+ selectedSort
818
+ ]
712
819
  );
713
820
  const filterConversations = useCallback(
714
821
  (targetValue) => {
@@ -796,7 +903,7 @@ const LightspeedChat = ({
796
903
  ]
797
904
  );
798
905
  const conversationFound = !!conversations.find(
799
- (c) => c.conversation_id === conversationId
906
+ (c) => c.conversation_id === viewConversationId
800
907
  );
801
908
  const getMaxPrompts = () => {
802
909
  if (isFullscreenMode) {
@@ -1174,7 +1281,7 @@ const LightspeedChat = ({
1174
1281
  variant: AlertVariant[variant ?? "success"],
1175
1282
  title,
1176
1283
  className: classes.toastAlert,
1177
- timeout: 8e3,
1284
+ timeout: 2e3,
1178
1285
  onTimeout: () => handleRemoveNotebookAlert(key),
1179
1286
  actionClose: /* @__PURE__ */ jsx(
1180
1287
  AlertActionCloseButton,
@@ -1257,6 +1364,7 @@ const LightspeedChat = ({
1257
1364
  models,
1258
1365
  isPinningChatsEnabled,
1259
1366
  isModelSelectorDisabled: isSendButtonDisabled,
1367
+ hideModelSelector: showNotebooksPanel,
1260
1368
  setDisplayMode,
1261
1369
  displayMode,
1262
1370
  onPinnedChatsToggle: handlePinningChatsToggle,
@@ -1298,7 +1406,7 @@ const LightspeedChat = ({
1298
1406
  "aria-label": t("aria.closeDrawerPanel")
1299
1407
  },
1300
1408
  setIsDrawerOpen: setIsChatHistoryDrawerOpen,
1301
- activeItemId: conversationId,
1409
+ activeItemId: viewConversationId,
1302
1410
  onSelectActiveItem,
1303
1411
  conversations: filterConversations(filterValue),
1304
1412
  onNewChat: newChatCreated ? undefined : onNewChat,
@@ -1354,6 +1462,15 @@ const LightspeedChat = ({
1354
1462
  sessionId: activeNotebook.session_id,
1355
1463
  notebookName: activeNotebook.name,
1356
1464
  documents: notebookDocuments,
1465
+ metadata: activeNotebook.metadata,
1466
+ topicSummary: conversations.find(
1467
+ (c) => c.conversation_id === activeNotebook.metadata?.conversation_id
1468
+ )?.topic_summary ?? undefined,
1469
+ userName,
1470
+ avatar,
1471
+ profileLoading,
1472
+ topicRestrictionEnabled,
1473
+ selectedModel,
1357
1474
  onClose: handleCloseNotebook
1358
1475
  }
1359
1476
  ),
@@ -1365,7 +1482,7 @@ const LightspeedChat = ({
1365
1482
  classes,
1366
1483
  openNotebookMenuId,
1367
1484
  setOpenNotebookMenuId,
1368
- onSelectNotebook: setActiveNotebook,
1485
+ onSelectNotebook: (notebook) => navigate(`/lightspeed/notebooks/${notebook.session_id}`),
1369
1486
  onRename: setRenameNotebookId,
1370
1487
  onDelete: setDeleteNotebookId,
1371
1488
  onCreateNotebook: handleCreateNotebook,