Skip to content

Commit 3c9c556

Browse files
JslYoonlokanandaprabhucursoragentrhdh-botgithub-actions[bot]
authored
[Notebooks] UI fixes (#3062)
* UI fixes Signed-off-by: Lucas <lyoon@redhat.com> * filter out notebook conversations Signed-off-by: Lucas <lyoon@redhat.com> * adding changeset Signed-off-by: Lucas <lyoon@redhat.com> * clean up changesets Signed-off-by: Lucas <lyoon@redhat.com> * more cleanup Signed-off-by: Lucas <lyoon@redhat.com> * deleting unused variable Signed-off-by: Lucas <lyoon@redhat.com> * fix tests Signed-off-by: Lucas <lyoon@redhat.com> * fix break when querydefualt is not defined Signed-off-by: Lucas <lyoon@redhat.com> * remove tab when notebook not enabled Signed-off-by: Lucas <lyoon@redhat.com> * fix UI test Signed-off-by: Lucas <lyoon@redhat.com> * fix ui Signed-off-by: Lucas <lyoon@redhat.com> * fix ci Signed-off-by: Lucas <lyoon@redhat.com> * fix(orchestrator-form-widgets): evaluate templates in fetch response selectors (#3058) Evaluate placeholder templates in fetch:response:value, fetch:response:autocomplete, fetch:response:label, and dropdown fetch:response:value before JSONata, consistent with other fetch fields. Treat undefined array selector results as empty options for ActiveDropdown and ActiveTextInput autocomplete (same as ActiveMultiSelect). Add changeset for patch release. Co-authored-by: Cursor <cursoragent@cursor.com> * Version Packages (#3073) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix(lightspeed): fix tool call response (#3049) * fix(lightspeed): fix tool call response * fix sonarqube issues * fix tool-call codeblock in dark theme * fix(extensions): preserve pagination state on search and add i18n support (#2979) * fix(extensions): preserve pagination state on search and add i18n support Signed-off-by: its-mitesh-kumar <itsmiteshkumar98@gmail.com> * updating api report Signed-off-by: its-mitesh-kumar <itsmiteshkumar98@gmail.com> * retaining state even after opening drwaer Signed-off-by: its-mitesh-kumar <itsmiteshkumar98@gmail.com> * fixing author filter Signed-off-by: its-mitesh-kumar <itsmiteshkumar98@gmail.com> * adding changeset Signed-off-by: its-mitesh-kumar <itsmiteshkumar98@gmail.com> --------- Signed-off-by: its-mitesh-kumar <itsmiteshkumar98@gmail.com> * Version Packages (#3075) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix(lightspeed): add return statement to address unhandled error in /v1/feedback (#3070) * fix(lightspeed): add return statement after error catch to prevent unhandled error Signed-off-by: Jordan Dubrick <jdubrick@redhat.com> * add changeset Signed-off-by: Jordan Dubrick <jdubrick@redhat.com> * use fetchResponse.status instead of hardcoded 500 Signed-off-by: Jordan Dubrick <jdubrick@redhat.com> --------- Signed-off-by: Jordan Dubrick <jdubrick@redhat.com> * Version Packages (#3076) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Version Packages (#3077) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * temp * address git comments Signed-off-by: Lucas <lyoon@redhat.com> * fix CI Signed-off-by: Lucas <lyoon@redhat.com> --------- Signed-off-by: Lucas <lyoon@redhat.com> Signed-off-by: its-mitesh-kumar <itsmiteshkumar98@gmail.com> Signed-off-by: Jordan Dubrick <jdubrick@redhat.com> Co-authored-by: Lokananda Prabhu <102503482+lokanandaprabhu@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: rhdh-bot service account <rhdh-bot@redhat.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Debsmita Santra <debsmita.santra@gmail.com> Co-authored-by: Mitesh Kumar <itsmiteshkumar98@gmail.com> Co-authored-by: Jordan Dubrick <jdubrick@redhat.com>
1 parent ee4732e commit 3c9c556

16 files changed

Lines changed: 232 additions & 49 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': minor
3+
'@red-hat-developer-hub/backstage-plugin-lightspeed': minor
4+
---
5+
6+
- Hide notebooks tab when `lightspeed.notebooks.enabled: false` in config
7+
- Fix notebook queries to display correct model from config instead of chat's selected model
8+
- Add `/notebook-conversation-ids` endpoint to filter notebook conversations from chat list even when notebooks disabled

workspaces/lightspeed/app-config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ app:
1616
organization:
1717
name: Red Hat
1818

19+
# Disable AI Notebooks feature by default
1920
lightspeed:
2021
notebooks:
21-
enabled: false
22+
enabled: ${NOTEBOOKS_ENABLED:-false}
2223
queryDefaults:
2324
model: ${NOTEBOOKS_QUERY_MODEL}
2425
provider_id: ${NOTEBOOKS_QUERY_PROVIDER_ID}
@@ -118,3 +119,6 @@ catalog:
118119
pullRequestBranchName: backstage-integration
119120
rules:
120121
- allow: [Component, System, API, Resource, Location]
122+
locations:
123+
- type: file
124+
target: ./catalog-info.yaml

workspaces/lightspeed/playwright.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export default defineConfig({
3838
port: 3000,
3939
reuseExistingServer: true,
4040
cwd: __dirname,
41+
env: {
42+
NOTEBOOKS_ENABLED: 'true',
43+
NOTEBOOKS_QUERY_MODEL: 'gpt-4',
44+
NOTEBOOKS_QUERY_PROVIDER_ID: 'openai',
45+
},
4146
},
4247

4348
retries: process.env.CI ? 2 : 0,

workspaces/lightspeed/plugins/lightspeed-backend/src/plugin.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,38 @@ export const lightspeedPlugin = createBackendPlugin({
6464

6565
const aiNotebooksEnabled =
6666
config.getOptionalBoolean('lightspeed.notebooks.enabled') ?? false;
67+
6768
if (aiNotebooksEnabled) {
68-
http.use(
69-
await createNotebooksRouter({
70-
config: config,
71-
logger: logger,
72-
httpAuth: httpAuth,
73-
userInfo: userInfo,
74-
permissions,
75-
}),
69+
const queryModel = config.getOptionalString(
70+
'lightspeed.notebooks.queryDefaults.model',
71+
);
72+
const queryProvider = config.getOptionalString(
73+
'lightspeed.notebooks.queryDefaults.provider_id',
7674
);
77-
logger.info('AI Notebooks enabled');
7875

79-
http.addAuthPolicy({
80-
path: '/notebooks/health',
81-
allow: 'unauthenticated',
82-
});
76+
if (!queryModel || !queryProvider) {
77+
logger.warn(
78+
'AI Notebooks feature is enabled but required configuration is missing. ' +
79+
'Please configure lightspeed.notebooks.queryDefaults.model and lightspeed.notebooks.queryDefaults.provider_id. ' +
80+
'Notebooks will not be available until these are set.',
81+
);
82+
} else {
83+
http.use(
84+
await createNotebooksRouter({
85+
config: config,
86+
logger: logger,
87+
httpAuth: httpAuth,
88+
userInfo: userInfo,
89+
permissions,
90+
}),
91+
);
92+
logger.info('AI Notebooks enabled');
93+
94+
http.addAuthPolicy({
95+
path: '/notebooks/health',
96+
allow: 'unauthenticated',
97+
});
98+
}
8399
}
84100

85101
// Configure authentication policies

workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,14 @@ export async function createNotebooksRouter(
6969
config.getOptionalNumber('lightspeed.servicePort') ??
7070
DEFAULT_LIGHTSPEED_SERVICE_PORT;
7171
const lightspeedBaseUrl = `http://${DEFAULT_LIGHTSPEED_SERVICE_HOST}:${lightSpeedPort}`;
72-
const queryModel = config.getOptionalString(
72+
const queryModel = config.getString(
7373
'lightspeed.notebooks.queryDefaults.model',
7474
);
75-
const queryProvider = config.getOptionalString(
75+
const queryProvider = config.getString(
7676
'lightspeed.notebooks.queryDefaults.provider_id',
7777
);
7878
const systemPrompt = NOTEBOOKS_SYSTEM_PROMPT;
7979

80-
if (!queryModel || !queryProvider) {
81-
throw new Error(
82-
'Query model and provider are required. Please configure lightspeed.notebooks.queryDefaults.model and lightspeed.notebooks.queryDefaults.provider_id',
83-
);
84-
}
85-
8680
logger.info(
8781
`AI Notebooks connecting to Lightspeed-Core at ${lightspeedBaseUrl}`,
8882
);
@@ -490,9 +484,9 @@ export async function createNotebooksRouter(
490484
tools: [{ type: 'file_search', vector_store_ids: [sessionId] }],
491485
model: `${queryProvider}/${queryModel}`,
492486
stream: true,
493-
temperature: 0.05,
487+
temperature: 0.35,
494488
shield_ids: [],
495-
max_tool_calls: 10,
489+
max_tool_calls: 15,
496490
...(conversationId && { conversation: conversationId }),
497491
};
498492

@@ -552,6 +546,7 @@ export async function createNotebooksRouter(
552546
.pipe(createResponsesApiTransform(session, sessionId, userId))
553547
.pipe(res);
554548
}
549+
console.log('response1234', response.body);
555550
break;
556551
}
557552
}),

workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,43 @@ export async function createRouter(
415415
}
416416
});
417417

418+
// Returns conversation IDs associated with notebook sessions for filtering
419+
router.get('/notebook-conversation-ids', async (req, res) => {
420+
try {
421+
const credentials = await httpAuth.credentials(req);
422+
const user = await userInfo.getUserInfo(credentials);
423+
const userId = user.userEntityRef;
424+
425+
const vectorStoresPage = await vectorStoresOperator.vectorStores.list();
426+
const vectorStores = vectorStoresPage.data || [];
427+
428+
const conversationIds: string[] = [];
429+
430+
for (const store of vectorStores) {
431+
const sessionUserId = store.metadata?.user_id as string;
432+
const conversationId = store.metadata?.conversation_id as string | null;
433+
434+
// Only include this user's sessions with a conversation_id
435+
if (sessionUserId === userId && conversationId) {
436+
conversationIds.push(conversationId);
437+
}
438+
}
439+
440+
res.json({
441+
conversation_ids: conversationIds,
442+
});
443+
} catch (error) {
444+
const errormsg = `Error fetching notebook conversation IDs: ${error}`;
445+
logger.error(errormsg);
446+
447+
if (error instanceof NotAllowedError) {
448+
res.status(403).json({ error: error.message });
449+
} else {
450+
res.status(500).json({ error: errormsg });
451+
}
452+
}
453+
});
454+
418455
// ─── Proxy Middleware (existing) ────────────────────────────────────
419456

420457
router.use('/', async (req, res, next) => {

workspaces/lightspeed/plugins/lightspeed/config.d.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,35 @@ export interface Config {
3636
*/
3737
message: string;
3838
}>;
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+
};
3969
};
4070
}

workspaces/lightspeed/plugins/lightspeed/src/api/LightspeedApiClient.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ export class LightspeedApiClient implements LightspeedAPI {
183183
return response.conversations ?? [];
184184
}
185185

186+
async getNotebookConversationIds() {
187+
const baseUrl = await this.getBaseUrl();
188+
const result = await this.fetcher(`${baseUrl}/notebook-conversation-ids`);
189+
190+
if (!result.ok) {
191+
throw new Error(
192+
`failed to get notebook conversation IDs, status ${result.status}: ${result.statusText}`,
193+
);
194+
}
195+
196+
const response = await result.json();
197+
return response.conversation_ids ?? [];
198+
}
199+
186200
async stopMessage(requestId: string): Promise<{ success: boolean }> {
187201
const baseUrl = await this.getBaseUrl();
188202
const response = await this.fetchApi.fetch(

workspaces/lightspeed/plugins/lightspeed/src/api/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type LightspeedAPI = {
4747
newName: string,
4848
) => Promise<{ success: boolean }>;
4949
getConversations: () => Promise<ConversationList>;
50+
getNotebookConversationIds: () => Promise<string[]>;
5051
getFeedbackStatus: () => Promise<boolean>;
5152
captureFeedback: (payload: CaptureFeedback) => Promise<{ response: string }>;
5253
isTopicRestrictionEnabled: () => Promise<boolean>;

workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
} from 'react-dropzone';
3232
import { useLocation, useMatch, useNavigate } from 'react-router-dom';
3333

34+
import { configApiRef, useApi } from '@backstage/core-plugin-api';
35+
3436
import { Button, makeStyles } from '@material-ui/core';
3537
import {
3638
Chatbot,
@@ -87,6 +89,7 @@ import {
8789
useLastOpenedConversation,
8890
useLightspeedDeletePermission,
8991
useLightspeedNotebooksPermission,
92+
useNotebookConversationIds,
9093
useNotebookSession,
9194
useNotebookSessions,
9295
usePinnedChatsSettings,
@@ -614,9 +617,16 @@ export const LightspeedChat = ({
614617
const classes = useStyles();
615618
const { t } = useTranslation();
616619
const navigate = useNavigate();
620+
const configApi = useApi(configApiRef);
621+
const notebooksEnabled =
622+
configApi.getOptionalBoolean('lightspeed.notebooks.enabled') ?? false;
617623
const notebooksRouteMatch = useMatch('/lightspeed/notebooks');
618624
const notebookViewRouteMatch = useMatch('/lightspeed/notebooks/:notebookId');
619625
const routeNotebookId = notebookViewRouteMatch?.params?.notebookId;
626+
const isOnNotebookRoute = Boolean(
627+
notebooksRouteMatch || notebookViewRouteMatch,
628+
);
629+
const shouldShowTabs = notebooksEnabled || isOnNotebookRoute;
620630
const {
621631
displayMode,
622632
setDisplayMode,
@@ -656,6 +666,9 @@ export const LightspeedChat = ({
656666
useLightspeedNotebooksPermission();
657667
const notebooksPermissionResolved =
658668
!notebooksPermissionLoading && hasNotebooksAccess;
669+
670+
const { data: notebookConversationIdsArray = [] } =
671+
useNotebookConversationIds();
659672
const { data: notebooks = [], refetch: refetchNotebooks } =
660673
useNotebookSessions(notebooksPermissionResolved);
661674
const hasNotebooks = notebooks.length > 0;
@@ -715,9 +728,9 @@ export const LightspeedChat = ({
715728
const wasStoppedByUserRef = useRef(false);
716729
const { isReady, lastOpenedId, setLastOpenedId, clearLastOpenedId } =
717730
useLastOpenedConversation(user);
718-
// Chat vs Notebooks tabs are fullscreen-only; overlay and docked always show Chat.
719731
const showChatPanel = !isFullscreenMode || activeTab === 0;
720-
const showNotebooksPanel = isFullscreenMode && activeTab !== 0;
732+
const showNotebooksPanel =
733+
(notebooksEnabled || isOnNotebookRoute) && activeTab !== 0;
721734
const [isChatHistoryDrawerOpen, setIsChatHistoryDrawerOpen] =
722735
useState<boolean>(!isMobile && isFullscreenMode);
723736

@@ -1193,13 +1206,8 @@ export const LightspeedChat = ({
11931206
);
11941207

11951208
const notebookConversationIds = useMemo(
1196-
() =>
1197-
new Set(
1198-
notebooks
1199-
.map(n => n.metadata?.conversation_id)
1200-
.filter((id): id is string => !!id),
1201-
),
1202-
[notebooks],
1209+
() => new Set(notebookConversationIdsArray),
1210+
[notebookConversationIdsArray],
12031211
);
12041212

12051213
const chatOnlyConversations = useMemo(
@@ -1928,8 +1936,10 @@ export const LightspeedChat = ({
19281936
onMcpSettingsClick={() => setIsMcpSettingsOpen(true)}
19291937
/>
19301938
</ChatbotHeader>
1931-
{isFullscreenMode && <div className={classes.headerDivider} />}
1932-
{isFullscreenMode && (
1939+
{isFullscreenMode && shouldShowTabs && (
1940+
<div className={classes.headerDivider} />
1941+
)}
1942+
{isFullscreenMode && shouldShowTabs && (
19331943
<>
19341944
<Tabs
19351945
activeKey={activeTab}
@@ -2051,7 +2061,6 @@ export const LightspeedChat = ({
20512061
avatar={avatar}
20522062
profileLoading={profileLoading}
20532063
topicRestrictionEnabled={topicRestrictionEnabled}
2054-
selectedModel={selectedModel}
20552064
onClose={handleCloseNotebook}
20562065
/>
20572066
)}

0 commit comments

Comments
 (0)