@red-hat-developer-hub/backstage-plugin-lightspeed 2.7.0 → 2.8.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  ## @red-hat-developer-hub/backstage-plugin-lightspeed
2
2
 
3
+ ## 2.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3c9c556: - Hide notebooks tab when `lightspeed.notebooks.enabled: false` in config
8
+ - Fix notebook queries to display correct model from config instead of chat's selected model
9
+ - Add `/notebook-conversation-ids` endpoint to filter notebook conversations from chat list even when notebooks disabled
10
+
11
+ ### Patch Changes
12
+
13
+ - a866b21: fixed dark theme issues
14
+ - 17e0527: Updated dependency `@patternfly/react-core` to `6.4.3`.
15
+ Updated dependency `@red-hat-developer-hub/backstage-plugin-app-react` to `^0.0.5`.
16
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@2.8.0
17
+
18
+ ## 2.7.1
19
+
20
+ ### Patch Changes
21
+
22
+ - 75181f0: preserve indentation for lists in chat window
23
+ - ca793d5: Disable automatic model refetch on window focus to prevent unnecessary network requests
24
+ - @red-hat-developer-hub/backstage-plugin-lightspeed-common@2.7.1
25
+
3
26
  ## 2.7.0
4
27
 
5
28
  ### Minor Changes
package/config.d.ts CHANGED
@@ -36,5 +36,35 @@ export interface Config {
36
36
  */
37
37
  message: string;
38
38
  }>;
39
+ /**
40
+ * Configuration for AI Notebooks
41
+ * @visibility frontend
42
+ */
43
+ notebooks?: {
44
+ /**
45
+ * Enable/disable AI Notebooks feature
46
+ * When enabled, exposes AI Notebooks REST API endpoints for document-based conversations with RAG.
47
+ * Requires Lightspeed service to be running (default: http://0.0.0.0:8080).
48
+ * @default false
49
+ * @visibility frontend
50
+ */
51
+ enabled: boolean;
52
+ /**
53
+ * Query configuration for notebooks
54
+ * @visibility frontend
55
+ */
56
+ queryDefaults?: {
57
+ /**
58
+ * Model to use for answering queries
59
+ * @visibility frontend
60
+ */
61
+ model: string;
62
+ /**
63
+ * AI provider for the query model
64
+ * @visibility frontend
65
+ */
66
+ provider_id: string;
67
+ };
68
+ };
39
69
  };
40
70
  }
@@ -115,6 +115,17 @@ class LightspeedApiClient {
115
115
  const response = await result.json();
116
116
  return response.conversations ?? [];
117
117
  }
118
+ async getNotebookConversationIds() {
119
+ const baseUrl = await this.getBaseUrl();
120
+ const result = await this.fetcher(`${baseUrl}/notebook-conversation-ids`);
121
+ if (!result.ok) {
122
+ throw new Error(
123
+ `failed to get notebook conversation IDs, status ${result.status}: ${result.statusText}`
124
+ );
125
+ }
126
+ const response = await result.json();
127
+ return response.conversation_ids ?? [];
128
+ }
118
129
  async stopMessage(requestId) {
119
130
  const baseUrl = await this.getBaseUrl();
120
131
  const response = await this.fetchApi.fetch(
@@ -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 {\n TEMP_CONVERSATION_ID,\n VALID_TOPIC_RESTRICTION_PROVIDER_IDS,\n} from '../const';\nimport { Attachment, CaptureFeedback } from '../types';\nimport { LightspeedAPI } from './api';\n\n/**\n * @public\n * Lightspeed API client options\n */\n\nexport type Options = {\n configApi: ConfigApi;\n fetchApi: FetchApi;\n};\n\n/**\n * @public\n * Lightspeed API client implementation\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 async createMessage(\n prompt: string,\n selectedModel: string,\n selectedProvider: 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: selectedProvider,\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 isTopicRestrictionEnabled() {\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(`${baseUrl}/v1/shields`);\n\n if (!result.ok) {\n throw new Error(\n `failed to get shields, status ${result.status}: ${result.statusText}`,\n );\n }\n\n const response = await result.json();\n if (!response?.shields || !Array.isArray(response.shields)) {\n return false;\n }\n\n return response.shields.some((shield: any) => {\n const providerId = shield.provider_resource_id;\n\n return VALID_TOPIC_RESTRICTION_PROVIDER_IDS.some(\n validId => providerId.toLowerCase() === validId.toLowerCase(),\n );\n });\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\n return response?.models ? response?.models : [];\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}/v2/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}/v2/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 stopMessage(requestId: string): Promise<{ success: boolean }> {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v1/query/interrupt`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ request_id: requestId }),\n },\n );\n if (!response.ok) {\n throw new Error(\n `failed to stop message, status ${response.status}: ${response.statusText}`,\n );\n }\n return await response.json();\n }\n async deleteConversation(conversation_id: string) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v2/conversations/${encodeURIComponent(conversation_id)}`,\n {\n method: 'DELETE',\n headers: {},\n },\n );\n\n if (!response.ok) {\n let errorMessage = `failed to delete conversation, status ${response.status}: ${response.statusText}`;\n\n try {\n const errorBody = await response.json();\n if (errorBody?.error) {\n errorMessage = errorBody.error;\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(e);\n }\n\n throw new Error(errorMessage);\n }\n return { success: true };\n }\n\n async renameConversation(conversation_id: string, newName: string) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v2/conversations/${encodeURIComponent(conversation_id)}`,\n {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ topic_summary: newName }),\n },\n );\n\n if (!response.ok) {\n let errorMessage = `failed to rename conversation, status ${response.status}: ${response.statusText}`;\n\n try {\n const errorBody = await response.json();\n if (errorBody?.error) {\n errorMessage = errorBody.error;\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(e);\n }\n\n throw new Error(errorMessage);\n }\n return { success: true };\n }\n\n getFeedbackStatus = async () => {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v1/feedback/status`,\n {\n method: 'GET',\n headers: {},\n },\n );\n\n if (!response.ok) {\n throw new Error(\n `failed to GET feedback status, status ${response.status}: ${response.statusText}`,\n );\n }\n const result = await response.json();\n return result?.status?.enabled ?? false;\n };\n\n captureFeedback = async (payload: CaptureFeedback) => {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(`${baseUrl}/v1/feedback`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n accept: 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(\n `failed to capture feedback, status ${response.status}: ${response.statusText}`,\n );\n }\n return await response.json();\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 {\n TEMP_CONVERSATION_ID,\n VALID_TOPIC_RESTRICTION_PROVIDER_IDS,\n} from '../const';\nimport { Attachment, CaptureFeedback } from '../types';\nimport { LightspeedAPI } from './api';\n\n/**\n * @public\n * Lightspeed API client options\n */\n\nexport type Options = {\n configApi: ConfigApi;\n fetchApi: FetchApi;\n};\n\n/**\n * @public\n * Lightspeed API client implementation\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 async createMessage(\n prompt: string,\n selectedModel: string,\n selectedProvider: 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: selectedProvider,\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 isTopicRestrictionEnabled() {\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(`${baseUrl}/v1/shields`);\n\n if (!result.ok) {\n throw new Error(\n `failed to get shields, status ${result.status}: ${result.statusText}`,\n );\n }\n\n const response = await result.json();\n if (!response?.shields || !Array.isArray(response.shields)) {\n return false;\n }\n\n return response.shields.some((shield: any) => {\n const providerId = shield.provider_resource_id;\n\n return VALID_TOPIC_RESTRICTION_PROVIDER_IDS.some(\n validId => providerId.toLowerCase() === validId.toLowerCase(),\n );\n });\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\n return response?.models ? response?.models : [];\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}/v2/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}/v2/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 getNotebookConversationIds() {\n const baseUrl = await this.getBaseUrl();\n const result = await this.fetcher(`${baseUrl}/notebook-conversation-ids`);\n\n if (!result.ok) {\n throw new Error(\n `failed to get notebook conversation IDs, status ${result.status}: ${result.statusText}`,\n );\n }\n\n const response = await result.json();\n return response.conversation_ids ?? [];\n }\n\n async stopMessage(requestId: string): Promise<{ success: boolean }> {\n const baseUrl = await this.getBaseUrl();\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v1/query/interrupt`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ request_id: requestId }),\n },\n );\n if (!response.ok) {\n throw new Error(\n `failed to stop message, status ${response.status}: ${response.statusText}`,\n );\n }\n return await response.json();\n }\n async deleteConversation(conversation_id: string) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v2/conversations/${encodeURIComponent(conversation_id)}`,\n {\n method: 'DELETE',\n headers: {},\n },\n );\n\n if (!response.ok) {\n let errorMessage = `failed to delete conversation, status ${response.status}: ${response.statusText}`;\n\n try {\n const errorBody = await response.json();\n if (errorBody?.error) {\n errorMessage = errorBody.error;\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(e);\n }\n\n throw new Error(errorMessage);\n }\n return { success: true };\n }\n\n async renameConversation(conversation_id: string, newName: string) {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v2/conversations/${encodeURIComponent(conversation_id)}`,\n {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ topic_summary: newName }),\n },\n );\n\n if (!response.ok) {\n let errorMessage = `failed to rename conversation, status ${response.status}: ${response.statusText}`;\n\n try {\n const errorBody = await response.json();\n if (errorBody?.error) {\n errorMessage = errorBody.error;\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(e);\n }\n\n throw new Error(errorMessage);\n }\n return { success: true };\n }\n\n getFeedbackStatus = async () => {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/v1/feedback/status`,\n {\n method: 'GET',\n headers: {},\n },\n );\n\n if (!response.ok) {\n throw new Error(\n `failed to GET feedback status, status ${response.status}: ${response.statusText}`,\n );\n }\n const result = await response.json();\n return result?.status?.enabled ?? false;\n };\n\n captureFeedback = async (payload: CaptureFeedback) => {\n const baseUrl = await this.getBaseUrl();\n\n const response = await this.fetchApi.fetch(`${baseUrl}/v1/feedback`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n accept: 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(\n `failed to capture feedback, status ${response.status}: ${response.statusText}`,\n );\n }\n return await response.json();\n };\n}\n"],"names":[],"mappings":";;AAwCO,MAAM,mBAAA,CAA6C;AAAA,EACvC,SAAA;AAAA,EACA,QAAA;AAAA,EAEjB,YAAY,OAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,YAAY,OAAA,CAAQ,SAAA;AACzB,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AAAA,EAC1B;AAAA,EAEA,MAAM,UAAA,GAAa;AACjB,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,SAAA,CAAU,SAAA,CAAU,iBAAiB,CAAC,CAAA,eAAA,CAAA;AAAA,EACvD;AAAA,EAEA,MAAM,aAAA,CACJ,MAAA,EACA,aAAA,EACA,gBAAA,EACA,iBACA,WAAA,EACA;AACA,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AAEtC,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,SAAA,CAAA,EAAa;AAAA,MAChE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB;AAAA,OAClB;AAAA,MACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACnB,eAAA,EACE,eAAA,KAAoB,oBAAA,GAChB,MAAA,GACA,eAAA;AAAA,QACN,KAAA,EAAO,aAAA;AAAA,QACP,QAAA,EAAU,gBAAA;AAAA,QACV,KAAA,EAAO,MAAA;AAAA,QACP;AAAA,OACD;AAAA,KACF,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,IAAA,EAAM;AAClB,MAAA,MAAM,IAAI,MAAM,uDAAuD,CAAA;AAAA,IACzE;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AAC3C,MAAA,MAAM,MAAA,GAAS,KAAK,IAAA,EAAK;AACzB,MAAA,MAAM,OAAA,GAAU,IAAI,WAAA,CAAY,OAAO,CAAA;AACvC,MAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,IAAA,CAAK,CAAC,EAAE,IAAA,EAAM,OAAM,KAAM;AAClD,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,OAAO,EAAA;AAAA,QACT;AACA,QAAA,OAAO,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,MAC7B,CAAC,CAAA;AACD,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AACpC,MAAA,IAAI,cAAc,KAAA,EAAO;AACvB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,YAAA,CAAa,KAAK,CAAA,CAAE,CAAA;AAAA,MACnE;AAAA,IACF;AACA,IAAA,OAAO,QAAA,CAAS,KAAK,SAAA,EAAU;AAAA,EACjC;AAAA,EAEA,MAAc,QAAQ,GAAA,EAAa;AACjC,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,MAAM,GAAA,EAAK;AAAA,MAC9C,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB;AAAA;AAClB,KACD,CAAA;AACD,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,6BAAA,EAAgC,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA;AAAA,OACzE;AAAA,IACF;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA,EAEA,MAAM,yBAAA,GAA4B;AAChC,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AACtC,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,OAAA,CAAQ,CAAA,EAAG,OAAO,CAAA,WAAA,CAAa,CAAA;AAEzD,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,8BAAA,EAAiC,MAAA,CAAO,MAAM,CAAA,EAAA,EAAK,OAAO,UAAU,CAAA;AAAA,OACtE;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAA,EAAK;AACnC,IAAA,IAAI,CAAC,UAAU,OAAA,IAAW,CAAC,MAAM,OAAA,CAAQ,QAAA,CAAS,OAAO,CAAA,EAAG;AAC1D,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,CAAC,MAAA,KAAgB;AAC5C,MAAA,MAAM,aAAa,MAAA,CAAO,oBAAA;AAE1B,MAAA,OAAO,oCAAA,CAAqC,IAAA;AAAA,QAC1C,CAAA,OAAA,KAAW,UAAA,CAAW,WAAA,EAAY,KAAM,QAAQ,WAAA;AAAY,OAC9D;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,YAAA,GAAe;AACnB,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AACtC,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,OAAA,CAAQ,CAAA,EAAG,OAAO,CAAA,UAAA,CAAY,CAAA;AAExD,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,6BAAA,EAAgC,MAAA,CAAO,MAAM,CAAA,EAAA,EAAK,OAAO,UAAU,CAAA;AAAA,OACrE;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAA,EAAK;AAEnC,IAAA,OAAO,QAAA,EAAU,MAAA,GAAS,QAAA,EAAU,MAAA,GAAS,EAAC;AAAA,EAChD;AAAA,EAEA,MAAM,wBAAwB,eAAA,EAAyB;AACrD,IAAA,IAAI,oBAAoB,oBAAA,EAAsB;AAC5C,MAAA,OAAO,EAAC;AAAA,IACV;AACA,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AACtC,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,OAAA;AAAA,MACxB,CAAA,EAAG,OAAO,CAAA,kBAAA,EAAqB,kBAAA,CAAmB,eAAe,CAAC,CAAA;AAAA,KACpE;AACA,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,4CAAA,EAA+C,MAAA,CAAO,MAAM,CAAA,EAAA,EAAK,OAAO,UAAU,CAAA;AAAA,OACpF;AAAA,IACF;AACA,IAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAA,EAAK;AACnC,IAAA,OAAO,QAAA,CAAS,gBAAgB,EAAC;AAAA,EACnC;AAAA,EAEA,MAAM,gBAAA,GAAmB;AACvB,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AACtC,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,OAAA,CAAQ,CAAA,EAAG,OAAO,CAAA,iBAAA,CAAmB,CAAA;AAE/D,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,mCAAA,EAAsC,MAAA,CAAO,MAAM,CAAA,EAAA,EAAK,OAAO,UAAU,CAAA;AAAA,OAC3E;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAA,EAAK;AACnC,IAAA,OAAO,QAAA,CAAS,iBAAiB,EAAC;AAAA,EACpC;AAAA,EAEA,MAAM,0BAAA,GAA6B;AACjC,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AACtC,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,OAAA,CAAQ,CAAA,EAAG,OAAO,CAAA,0BAAA,CAA4B,CAAA;AAExE,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gDAAA,EAAmD,MAAA,CAAO,MAAM,CAAA,EAAA,EAAK,OAAO,UAAU,CAAA;AAAA,OACxF;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAA,EAAK;AACnC,IAAA,OAAO,QAAA,CAAS,oBAAoB,EAAC;AAAA,EACvC;AAAA,EAEA,MAAM,YAAY,SAAA,EAAkD;AAClE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AACtC,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA;AAAA,MACnC,GAAG,OAAO,CAAA,mBAAA,CAAA;AAAA,MACV;AAAA,QACE,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA,SAClB;AAAA,QACA,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,UAAA,EAAY,WAAW;AAAA;AAChD,KACF;AACA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,+BAAA,EAAkC,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA;AAAA,OAC3E;AAAA,IACF;AACA,IAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,EAC7B;AAAA,EACA,MAAM,mBAAmB,eAAA,EAAyB;AAChD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AAEtC,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA;AAAA,MACnC,CAAA,EAAG,OAAO,CAAA,kBAAA,EAAqB,kBAAA,CAAmB,eAAe,CAAC,CAAA,CAAA;AAAA,MAClE;AAAA,QACE,MAAA,EAAQ,QAAA;AAAA,QACR,SAAS;AAAC;AACZ,KACF;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,IAAI,eAAe,CAAA,sCAAA,EAAyC,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA,CAAA;AAEnG,MAAA,IAAI;AACF,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,IAAI,WAAW,KAAA,EAAO;AACpB,UAAA,YAAA,GAAe,SAAA,CAAU,KAAA;AAAA,QAC3B;AAAA,MACF,SAAS,CAAA,EAAG;AAEV,QAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,MAChB;AAEA,MAAA,MAAM,IAAI,MAAM,YAAY,CAAA;AAAA,IAC9B;AACA,IAAA,OAAO,EAAE,SAAS,IAAA,EAAK;AAAA,EACzB;AAAA,EAEA,MAAM,kBAAA,CAAmB,eAAA,EAAyB,OAAA,EAAiB;AACjE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AAEtC,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA;AAAA,MACnC,CAAA,EAAG,OAAO,CAAA,kBAAA,EAAqB,kBAAA,CAAmB,eAAe,CAAC,CAAA,CAAA;AAAA,MAClE;AAAA,QACE,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA,SAClB;AAAA,QACA,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,aAAA,EAAe,SAAS;AAAA;AACjD,KACF;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,IAAI,eAAe,CAAA,sCAAA,EAAyC,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA,CAAA;AAEnG,MAAA,IAAI;AACF,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,IAAI,WAAW,KAAA,EAAO;AACpB,UAAA,YAAA,GAAe,SAAA,CAAU,KAAA;AAAA,QAC3B;AAAA,MACF,SAAS,CAAA,EAAG;AAEV,QAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,MAChB;AAEA,MAAA,MAAM,IAAI,MAAM,YAAY,CAAA;AAAA,IAC9B;AACA,IAAA,OAAO,EAAE,SAAS,IAAA,EAAK;AAAA,EACzB;AAAA,EAEA,oBAAoB,YAAY;AAC9B,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AAEtC,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA;AAAA,MACnC,GAAG,OAAO,CAAA,mBAAA,CAAA;AAAA,MACV;AAAA,QACE,MAAA,EAAQ,KAAA;AAAA,QACR,SAAS;AAAC;AACZ,KACF;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,sCAAA,EAAyC,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA;AAAA,OAClF;AAAA,IACF;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,IAAA,EAAK;AACnC,IAAA,OAAO,MAAA,EAAQ,QAAQ,OAAA,IAAW,KAAA;AAAA,EACpC,CAAA;AAAA,EAEA,eAAA,GAAkB,OAAO,OAAA,KAA6B;AACpD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,UAAA,EAAW;AAEtC,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,YAAA,CAAA,EAAgB;AAAA,MACnE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC7B,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,mCAAA,EAAsC,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA;AAAA,OAC/E;AAAA,IACF;AACA,IAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,EAC7B,CAAA;AACF;;;;"}
@@ -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, type ApiRef } from '@backstage/core-plugin-api';\n\nimport {\n Attachment,\n BaseMessage,\n CaptureFeedback,\n ConversationList,\n LCSModel,\n} from '../types';\n\n/**\n * @public\n * Lightspeed API\n */\n\nexport type LightspeedAPI = {\n getAllModels: () => Promise<LCSModel[]>;\n getConversationMessages: (conversation_id: string) => Promise<BaseMessage[]>;\n createMessage: (\n prompt: string,\n selectedModel: string,\n selectedProvider: string,\n conversation_id: string,\n attachments: Attachment[],\n ) => Promise<ReadableStreamDefaultReader>;\n deleteConversation: (\n conversation_id: string,\n ) => Promise<{ success: boolean }>;\n renameConversation: (\n conversation_id: string,\n newName: string,\n ) => Promise<{ success: boolean }>;\n getConversations: () => Promise<ConversationList>;\n getFeedbackStatus: () => Promise<boolean>;\n captureFeedback: (payload: CaptureFeedback) => Promise<{ response: string }>;\n isTopicRestrictionEnabled: () => Promise<boolean>;\n stopMessage: (requestId: string) => Promise<{ success: boolean }>;\n};\n\n/**\n * @public\n * Lightspeed API interface\n */\n\nexport const lightspeedApiRef: ApiRef<LightspeedAPI> =\n createApiRef<LightspeedAPI>({\n id: 'plugin.lightspeed.service',\n });\n"],"names":[],"mappings":";;AA4DO,MAAM,mBACX,YAAA,CAA4B;AAAA,EAC1B,EAAA,EAAI;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, type ApiRef } from '@backstage/core-plugin-api';\n\nimport {\n Attachment,\n BaseMessage,\n CaptureFeedback,\n ConversationList,\n LCSModel,\n} from '../types';\n\n/**\n * @public\n * Lightspeed API\n */\n\nexport type LightspeedAPI = {\n getAllModels: () => Promise<LCSModel[]>;\n getConversationMessages: (conversation_id: string) => Promise<BaseMessage[]>;\n createMessage: (\n prompt: string,\n selectedModel: string,\n selectedProvider: string,\n conversation_id: string,\n attachments: Attachment[],\n ) => Promise<ReadableStreamDefaultReader>;\n deleteConversation: (\n conversation_id: string,\n ) => Promise<{ success: boolean }>;\n renameConversation: (\n conversation_id: string,\n newName: string,\n ) => Promise<{ success: boolean }>;\n getConversations: () => Promise<ConversationList>;\n getNotebookConversationIds: () => Promise<string[]>;\n getFeedbackStatus: () => Promise<boolean>;\n captureFeedback: (payload: CaptureFeedback) => Promise<{ response: string }>;\n isTopicRestrictionEnabled: () => Promise<boolean>;\n stopMessage: (requestId: string) => Promise<{ success: boolean }>;\n};\n\n/**\n * @public\n * Lightspeed API interface\n */\n\nexport const lightspeedApiRef: ApiRef<LightspeedAPI> =\n createApiRef<LightspeedAPI>({\n id: 'plugin.lightspeed.service',\n });\n"],"names":[],"mappings":";;AA6DO,MAAM,mBACX,YAAA,CAA4B;AAAA,EAC1B,EAAA,EAAI;AACN,CAAC;;;;"}
@@ -1,14 +1,16 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { useState, useEffect, useRef, useLayoutEffect, useCallback, useMemo } from 'react';
3
3
  import { useNavigate, useMatch, useLocation } from 'react-router-dom';
4
+ import { useApi, configApiRef } from '@backstage/core-plugin-api';
4
5
  import { Button, makeStyles } from '@material-ui/core';
6
+ import Tab from '@mui/material/Tab';
7
+ import Tabs from '@mui/material/Tabs';
5
8
  import { ChatbotDisplayMode, ChatbotContent, ChatbotFooter, MessageBar, ChatbotFootnote, Settings, Chatbot, ChatbotHeader, ChatbotHeaderMain, ChatbotHeaderMenu, ChatbotHeaderTitle, FileDropZone, ChatbotAlert } from '@patternfly/chatbot';
6
9
  import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
7
- import { DropdownItem, Tooltip, MenuToggle, Select, SelectList, SelectOption, AlertGroup, Alert, AlertVariant, AlertActionCloseButton, Title, Tabs, Tab } from '@patternfly/react-core';
10
+ import { DropdownItem, Tooltip, MenuToggle, Select, SelectList, SelectOption, AlertGroup, Alert, AlertVariant, AlertActionCloseButton, Title } from '@patternfly/react-core';
8
11
  import { SortAmountDownAltIcon, SortAmountDownIcon, PlusIcon, SearchIcon } from '@patternfly/react-icons';
9
12
  import { useQueryClient } from '@tanstack/react-query';
10
13
  import { UNTITLED_NOTEBOOK_NAME, TEMP_CONVERSATION_ID, supportedFileTypes } from '../const.esm.js';
11
- import '@backstage/core-plugin-api';
12
14
  import '../api/api.esm.js';
13
15
  import { useBackstageUserIdentity } from '../hooks/useBackstageUserIdentity.esm.js';
14
16
  import { useConversationMessages } from '../hooks/useConversationMessages.esm.js';
@@ -20,6 +22,7 @@ import { useLightspeedDeletePermission } from '../hooks/useLightspeedDeletePermi
20
22
  import { useLightspeedNotebooksPermission } from '../hooks/notebooks/useLightspeedNotebooksPermission.esm.js';
21
23
  import '@backstage/plugin-permission-react';
22
24
  import '@red-hat-developer-hub/backstage-plugin-lightspeed-common';
25
+ import { useNotebookConversationIds } from '../hooks/useNotebookConversationIds.esm.js';
23
26
  import { useNotebookSession } from '../hooks/notebooks/useNotebookSession.esm.js';
24
27
  import { useNotebookSessions } from '../hooks/notebooks/useNotebookSessions.esm.js';
25
28
  import { usePinnedChatsSettings } from '../hooks/usePinnedChatsSettings.esm.js';
@@ -59,9 +62,13 @@ const ConditionalWrapper = ({
59
62
  const useStyles = makeStyles((theme) => ({
60
63
  body: {
61
64
  // remove default margin and padding from common elements
62
- "& h1, & h2, & h3, & h4, & h5, & h6, & p, & ul, & ol, & li": {
65
+ // lists excluded for proper formatting
66
+ "& h1, & h2, & h3, & h4, & h5, & h6, & p, & li": {
63
67
  margin: 0,
64
68
  padding: 0
69
+ },
70
+ "& .pf-chatbot__content": {
71
+ backgroundColor: "var(--pf-t--global--background--color--floating--default)"
65
72
  }
66
73
  },
67
74
  header: {
@@ -104,29 +111,6 @@ const useStyles = makeStyles((theme) => ({
104
111
  fontFamily: '"Red Hat Display", sans-serif !important'
105
112
  }
106
113
  },
107
- tabs: {
108
- padding: `0 ${theme.spacing(2)}px`,
109
- backgroundColor: "var(--pf-t--global--background--color--floating--default)",
110
- "& .pf-v6-c-tabs__item, & .pf-v5-c-tabs__item": {
111
- backgroundColor: "transparent"
112
- },
113
- "& .pf-v6-c-tabs__item:not(:last-child), & .pf-v5-c-tabs__item:not(:last-child)": {
114
- marginRight: theme.spacing(5)
115
- },
116
- "& .pf-v6-c-tabs__link, & .pf-v5-c-tabs__link": {
117
- backgroundColor: "transparent",
118
- paddingTop: theme.spacing(2),
119
- paddingBottom: theme.spacing(2),
120
- fontWeight: 700,
121
- cursor: "pointer"
122
- },
123
- "& .pf-v6-c-tabs__item.pf-m-current .pf-v6-c-tabs__link, & .pf-v5-c-tabs__item.pf-m-current .pf-v5-c-tabs__link": {
124
- color: "var(--pf-t--global--text--color--brand--default)"
125
- }
126
- },
127
- tabsDivider: {
128
- borderTop: "1px solid var(--pf-t--global--border--color--default)"
129
- },
130
114
  headerDivider: {
131
115
  paddingTop: 8,
132
116
  borderBottom: "1px solid var(--pf-t--global--border--color--default)",
@@ -275,7 +259,7 @@ const useStyles = makeStyles((theme) => ({
275
259
  maxWidth: "unset !important"
276
260
  },
277
261
  "& .pf-chatbot__message-bar": {
278
- backgroundColor: theme.palette.grey[300]
262
+ backgroundColor: theme.palette.type === "light" ? theme.palette.grey[100] : "var(--pf-t--global--background--color--secondary--default)"
279
263
  }
280
264
  },
281
265
  fullscreenFooter: {
@@ -287,7 +271,6 @@ const useStyles = makeStyles((theme) => ({
287
271
  }
288
272
  },
289
273
  fullscreenMessageBar: {
290
- backgroundColor: "var(--pf-t--global--background--color--secondary--default)",
291
274
  border: "1px solid var(--pf-t--global--border--color--default)",
292
275
  borderRadius: 24,
293
276
  padding: theme.spacing(0.5),
@@ -502,9 +485,15 @@ const LightspeedChat = ({
502
485
  const classes = useStyles();
503
486
  const { t } = useTranslation();
504
487
  const navigate = useNavigate();
488
+ const configApi = useApi(configApiRef);
489
+ const notebooksEnabled = configApi.getOptionalBoolean("lightspeed.notebooks.enabled") ?? false;
505
490
  const notebooksRouteMatch = useMatch("/lightspeed/notebooks");
506
491
  const notebookViewRouteMatch = useMatch("/lightspeed/notebooks/:notebookId");
507
492
  const routeNotebookId = notebookViewRouteMatch?.params?.notebookId;
493
+ const isOnNotebookRoute = Boolean(
494
+ notebooksRouteMatch || notebookViewRouteMatch
495
+ );
496
+ const shouldShowTabs = notebooksEnabled || isOnNotebookRoute;
508
497
  const {
509
498
  displayMode,
510
499
  setDisplayMode,
@@ -540,6 +529,7 @@ const LightspeedChat = ({
540
529
  });
541
530
  const { allowed: hasNotebooksAccess, loading: notebooksPermissionLoading } = useLightspeedNotebooksPermission();
542
531
  const notebooksPermissionResolved = !notebooksPermissionLoading && hasNotebooksAccess;
532
+ const { data: notebookConversationIdsArray = [] } = useNotebookConversationIds();
543
533
  const { data: notebooks = [], refetch: refetchNotebooks } = useNotebookSessions(notebooksPermissionResolved);
544
534
  const hasNotebooks = notebooks.length > 0;
545
535
  const [openNotebookMenuId, setOpenNotebookMenuId] = useState(
@@ -595,7 +585,7 @@ const LightspeedChat = ({
595
585
  const wasStoppedByUserRef = useRef(false);
596
586
  const { isReady, lastOpenedId, setLastOpenedId, clearLastOpenedId } = useLastOpenedConversation(user);
597
587
  const showChatPanel = !isFullscreenMode || activeTab === 0;
598
- const showNotebooksPanel = isFullscreenMode && activeTab !== 0;
588
+ const showNotebooksPanel = (notebooksEnabled || isOnNotebookRoute) && activeTab !== 0;
599
589
  const [isChatHistoryDrawerOpen, setIsChatHistoryDrawerOpen] = useState(!isMobile && isFullscreenMode);
600
590
  useLayoutEffect(() => {
601
591
  if (!isFullscreenMode) {
@@ -621,8 +611,7 @@ const LightspeedChat = ({
621
611
  navigate,
622
612
  setShellViewTab
623
613
  ]);
624
- const handleNotebookTabSelect = (_event, tabIndex) => {
625
- const nextTab = Number(tabIndex);
614
+ const handleNotebookTabSelect = (_event, nextTab) => {
626
615
  setActiveTab(nextTab);
627
616
  setShellViewTab(nextTab);
628
617
  if (nextTab === 1) {
@@ -978,10 +967,8 @@ const LightspeedChat = ({
978
967
  ]
979
968
  );
980
969
  const notebookConversationIds = useMemo(
981
- () => new Set(
982
- notebooks.map((n) => n.metadata?.conversation_id).filter((id) => !!id)
983
- ),
984
- [notebooks]
970
+ () => new Set(notebookConversationIdsArray),
971
+ [notebookConversationIdsArray]
985
972
  );
986
973
  const chatOnlyConversations = useMemo(
987
974
  () => conversations.filter(
@@ -1605,22 +1592,63 @@ const LightspeedChat = ({
1605
1592
  )
1606
1593
  ] }),
1607
1594
  isFullscreenMode && /* @__PURE__ */ jsx("div", { className: classes.headerDivider }),
1608
- isFullscreenMode && /* @__PURE__ */ jsxs(Fragment, { children: [
1609
- /* @__PURE__ */ jsxs(
1610
- Tabs,
1611
- {
1612
- activeKey: activeTab,
1613
- onSelect: handleNotebookTabSelect,
1614
- "aria-label": t("tabs.ariaLabel"),
1615
- className: classes.tabs,
1616
- children: [
1617
- /* @__PURE__ */ jsx(Tab, { eventKey: 0, title: t("tabs.chat") }),
1618
- /* @__PURE__ */ jsx(Tab, { eventKey: 1, title: t("tabs.notebooks") })
1619
- ]
1620
- }
1621
- ),
1622
- /* @__PURE__ */ jsx("div", { className: classes.tabsDivider })
1623
- ] }),
1595
+ isFullscreenMode && shouldShowTabs && /* @__PURE__ */ jsxs(
1596
+ Tabs,
1597
+ {
1598
+ value: activeTab,
1599
+ onChange: handleNotebookTabSelect,
1600
+ "aria-label": t("tabs.ariaLabel"),
1601
+ variant: "standard",
1602
+ textColor: "primary",
1603
+ sx: (theme) => ({
1604
+ backgroundColor: "var(--pf-t--global--background--color--floating--default)",
1605
+ borderBottom: "1px solid var(--pf-t--global--border--color--default)",
1606
+ minHeight: theme.spacing(6),
1607
+ paddingLeft: theme.spacing(2),
1608
+ paddingRight: theme.spacing(2),
1609
+ '& [role="tab"]': {
1610
+ fontWeight: 700,
1611
+ textTransform: "none",
1612
+ opacity: 1,
1613
+ '&:not([aria-selected="true"])': {
1614
+ color: `${theme.palette.text.primary} !important`
1615
+ },
1616
+ "&:hover": {
1617
+ background: "none !important",
1618
+ backgroundColor: "transparent !important",
1619
+ backgroundImage: "none !important"
1620
+ },
1621
+ '&:not([aria-selected="true"]):hover': {
1622
+ color: "var(--pf-t--global--text--color--brand--default)"
1623
+ },
1624
+ '&[aria-selected="true"]:hover': {
1625
+ color: `${theme.palette.primary.main} !important`
1626
+ }
1627
+ },
1628
+ '& [class*="Tabs-indicator"]': {
1629
+ height: 3
1630
+ }
1631
+ }),
1632
+ children: [
1633
+ /* @__PURE__ */ jsx(
1634
+ Tab,
1635
+ {
1636
+ disableRipple: true,
1637
+ label: t("tabs.chat"),
1638
+ "aria-label": t("tabs.chat")
1639
+ }
1640
+ ),
1641
+ /* @__PURE__ */ jsx(
1642
+ Tab,
1643
+ {
1644
+ disableRipple: true,
1645
+ label: t("tabs.notebooks"),
1646
+ "aria-label": t("tabs.notebooks")
1647
+ }
1648
+ )
1649
+ ]
1650
+ }
1651
+ ),
1624
1652
  showChatPanel && /* @__PURE__ */ jsx(
1625
1653
  ConditionalWrapper,
1626
1654
  {
@@ -1720,7 +1748,6 @@ const LightspeedChat = ({
1720
1748
  avatar,
1721
1749
  profileLoading,
1722
1750
  topicRestrictionEnabled,
1723
- selectedModel,
1724
1751
  onClose: handleCloseNotebook
1725
1752
  }
1726
1753
  ),