Skip to content

Commit 2204143

Browse files
feat(lightspeed): frontend integration with LCS apis (#1580)
* align lightspeed ui with lcs * upgrade pf chatbot * add unit tests * fix e2e tests * use lightspeed_question_validity shiels as valid provider * add changeset, refactor and cleanup * disable file attachment test * address review comments Signed-off-by: Karthik <karthik.jk11@gmail.com> * fix e2e tests * update app-config example --------- Signed-off-by: Karthik <karthik.jk11@gmail.com>
1 parent 52f82ec commit 2204143

33 files changed

+725
-438
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-lightspeed': major
3+
'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': patch
4+
---
5+
6+
Align lightspeed UI with LCS

workspaces/lightspeed/app-config.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,3 @@ catalog:
7676
pullRequestBranchName: backstage-integration
7777
rules:
7878
- allow: [Component, System, API, Resource, Location]
79-
80-
lightspeed:
81-
servers:
82-
- id: ollama
83-
url: http://localhost:11434/v1
84-
token: dummy-token

workspaces/lightspeed/packages/app/e2e-tests/fixtures/responses.ts

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,22 @@ export const createdAt = Date.now();
1919

2020
export const models = [
2121
{
22-
id: 'mock-model-1',
23-
object: 'model',
24-
created: Math.floor(createdAt / 1000),
22+
identifier: 'mock-provider-1/mock-model-1',
23+
api_model_type: 'llm',
24+
model_type: 'llm',
25+
provider_resource_id: 'mock-model-1',
26+
type: 'model',
2527
owned_by: 'library',
28+
provider_id: 'mock-provider-1',
2629
},
2730
{
28-
id: 'mock-model-2',
29-
object: 'model',
30-
created: Math.floor(createdAt / 1000),
31+
identifier: 'mock-provider-1/mock-model-2',
32+
api_model_type: 'llm',
33+
model_type: 'llm',
34+
provider_resource_id: 'mock-model-2',
35+
type: 'model',
3136
owned_by: 'library',
37+
provider_id: 'mock-provider-1',
3238
},
3339
];
3440

@@ -64,57 +70,52 @@ export const moreConversations = [
6470

6571
export const contents = [
6672
{
67-
lc: 1,
68-
type: 'constructor',
69-
id: ['langchain_core', 'messages', 'HumanMessage'],
70-
content: 'New conversation',
71-
response_metadata: {
72-
created_at: createdAt,
73-
},
74-
additional_kwargs: {},
73+
provider: models[1].provider_id,
74+
model: models[1].provider_resource_id,
75+
messages: [
76+
{
77+
content: 'New conversation',
78+
type: 'user',
79+
},
80+
{
81+
content: "Still a placeholder message'",
82+
type: 'assistant',
83+
},
84+
],
85+
started_at: '2025-10-06T15:39:38Z',
86+
completed_at: '2025-10-06T15:39:42Z',
7587
},
88+
];
89+
90+
export const mockShields = [
7691
{
77-
lc: 1,
78-
type: 'constructor',
79-
id: ['langchain_core', 'messages', 'AIMessage'],
80-
content: 'Still a placeholder message',
81-
response_metadata: {
82-
created_at: createdAt,
83-
model: models[1].id,
84-
tool_calls: [],
85-
invalid_tool_calls: [],
86-
additional_kwargs: {},
87-
},
92+
identifier: 'test-shield-id',
93+
provider_id: 'test-shield-id',
94+
type: 'shield',
95+
params: {},
96+
provider_resource_id: 'test-shield-id',
8897
},
8998
];
90-
9199
const repeatedSentence =
92100
'OpenShift deployment is a way to manage applications on the OpenShift platform.';
93101
const openshiftLongParagraph = `${repeatedSentence} `.repeat(30);
94102

95103
export const demoChatContent = [
96104
{
97-
lc: 1,
98-
type: 'constructor',
99-
id: ['langchain_core', 'messages', 'HumanMessage'],
100-
content: 'let me know about openshift deployment in detail',
101-
response_metadata: {
102-
created_at: createdAt,
103-
},
104-
additional_kwargs: {},
105-
},
106-
{
107-
lc: 1,
108-
type: 'constructor',
109-
id: ['langchain_core', 'messages', 'AIMessage'],
110-
content: openshiftLongParagraph,
111-
response_metadata: {
112-
created_at: createdAt,
113-
model: models[1].id,
114-
tool_calls: [],
115-
invalid_tool_calls: [],
116-
additional_kwargs: {},
117-
},
105+
provider: models[1].provider_id,
106+
model: models[1].provider_resource_id,
107+
messages: [
108+
{
109+
content: 'let me know about openshift deployment in detail',
110+
type: 'user',
111+
},
112+
{
113+
content: openshiftLongParagraph,
114+
type: 'assistant',
115+
},
116+
],
117+
started_at: createdAt,
118+
completed_at: createdAt,
118119
},
119120
];
120121

@@ -137,7 +138,7 @@ export const generateQueryResponse = (conversationId: string) => {
137138
tokens.forEach((token, index) => {
138139
events.push({
139140
event: 'token',
140-
data: { id: index, token },
141+
data: { id: index, token, role: 'inference' },
141142
});
142143
});
143144

workspaces/lightspeed/packages/app/e2e-tests/lightspeed.test.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
mockFeedbackStatus,
5252
mockModels,
5353
mockQuery,
54+
mockShields,
5455
} from './utils/devMode';
5556

5657
const botQuery = 'Please respond';
@@ -74,6 +75,7 @@ test.beforeEach(async ({ page }) => {
7475
await mockConversations(page);
7576
await mockChatHistory(page);
7677
await mockQuery(page, botQuery, conversations);
78+
await mockShields(page, mockShields);
7779
}
7880

7981
await page.goto('/');
@@ -95,9 +97,9 @@ test('Lightspeed is available', async ({ page }) => {
9597
});
9698

9799
test('Models are available', async ({ page }) => {
98-
const model = models[1].id;
100+
const model = models[1].provider_resource_id;
99101
const dropdown = page.locator('button[aria-label="Chatbot selector"]');
100-
await expect(dropdown).toHaveText(models[0].id);
102+
await expect(dropdown).toHaveText(models[0].provider_resource_id);
101103

102104
await dropdown.click();
103105
await page.getByText(model).click();
@@ -137,7 +139,6 @@ test('verify default prompts are visible', async ({ page }) => {
137139

138140
expect(nonEmptyTexts.length).toBe(3);
139141
});
140-
141142
test.describe('File Attachment Validation', () => {
142143
const testFiles = [
143144
{ path: '../../package.json', name: 'package.json' },
@@ -227,7 +228,7 @@ describeFn('Conversation', () => {
227228
await mockChatHistory(page, demoChatContent);
228229
}
229230

230-
let message = demoChatContent[0].content;
231+
let message = demoChatContent[0].messages[0].content;
231232
if (!devMode) {
232233
message =
233234
(await page
@@ -236,8 +237,10 @@ describeFn('Conversation', () => {
236237
}
237238
await sendMessage(message, page, false);
238239

239-
const jumpTopButton = page.getByRole('button', { name: 'Jump top' });
240-
const jumpBottomButton = page.getByRole('button', { name: 'Jump bottom' });
240+
const jumpTopButton = page.getByRole('button', { name: 'Back to top' });
241+
const jumpBottomButton = page.getByRole('button', {
242+
name: 'Back to bottom',
243+
});
241244

242245
await expect(jumpTopButton).toBeVisible();
243246
await jumpTopButton.click();
@@ -262,7 +265,7 @@ describeFn('Conversation', () => {
262265
await mockConversations(page, moreConversations);
263266
}
264267
await sendMessage('test', page);
265-
const sidePanel = page.locator('.pf-v6-c-drawer__panel');
268+
const sidePanel = page.locator('.pf-v6-c-drawer__panel-main');
266269

267270
const currentChat = sidePanel.locator('li.pf-chatbot__menu-item--active');
268271
await expect(currentChat).toHaveText(
@@ -293,10 +296,10 @@ describeFn('Conversation', () => {
293296
const botMessage = page.locator('.pf-chatbot__message--bot');
294297

295298
await expect(userMessage).toContainText(
296-
devMode ? contents[0].content : 'tell me about Backstage',
299+
devMode ? contents[0].messages[0].content : 'tell me about Backstage',
297300
);
298301
await expect(botMessage).toContainText(
299-
devMode ? contents[1].content : 'Backstage',
302+
devMode ? contents[0].messages[1].content : 'Backstage',
300303
);
301304
});
302305
});

workspaces/lightspeed/packages/app/e2e-tests/utils/devMode.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { generateQueryResponse, modelBaseUrl } from '../fixtures/responses';
1818

1919
export async function mockModels(page: Page, models: any[]) {
2020
await page.route(`${modelBaseUrl}/v1/models`, async route => {
21-
const json = { object: 'list', data: models };
21+
const json = { models };
2222
await route.fulfill({ json });
2323
});
2424
}
@@ -28,7 +28,7 @@ export async function mockConversations(
2828
conversations?: any[],
2929
allowPost = false,
3030
) {
31-
await page.route(`${modelBaseUrl}/conversations`, async route => {
31+
await page.route(`${modelBaseUrl}/v2/conversations`, async route => {
3232
if (route.request().method() === 'GET') {
3333
const json = conversations ? { conversations: conversations } : [];
3434
await route.fulfill({ json });
@@ -38,8 +38,15 @@ export async function mockConversations(
3838
});
3939
}
4040

41+
export async function mockShields(page: Page, shields: any[] = []) {
42+
await page.route(`${modelBaseUrl}/v1/shields`, async route => {
43+
const json = { shields };
44+
await route.fulfill({ json });
45+
});
46+
}
47+
4148
export async function mockChatHistory(page: Page, contents?: any[]) {
42-
await page.route(`${modelBaseUrl}/conversations/user*`, async route => {
49+
await page.route(`${modelBaseUrl}/v2/conversations/user*`, async route => {
4350
const json = contents ? { chat_history: contents } : [];
4451
await route.fulfill({ json });
4552
});

workspaces/lightspeed/packages/app/e2e-tests/utils/sidebar.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ export async function assertChatDialogInitialState(page: Page) {
1919
await expect(page.getByLabel('Chatbot', { exact: true })).toContainText(
2020
'Developer Lightspeed',
2121
);
22-
await expect(page.getByRole('button', { name: 'Toggle menu' })).toBeVisible();
22+
await expect(
23+
page.getByRole('button', { name: 'Chat history menu' }),
24+
).toBeVisible();
2325
await assertDrawerState(page, 'open');
2426
}
2527

@@ -29,7 +31,7 @@ export async function closeChatDrawer(page: Page) {
2931
}
3032

3133
export async function openChatDrawer(page: Page) {
32-
const toggleButton = page.getByRole('button', { name: 'Toggle menu' });
34+
const toggleButton = page.getByRole('button', { name: 'Chat history menu' });
3335
await toggleButton.click();
3436
}
3537

@@ -41,7 +43,7 @@ export async function assertDrawerState(page: Page, state: 'open' | 'closed') {
4143

4244
const checks = [
4345
page.getByRole('button', { name: 'Close drawer panel' }),
44-
page.getByRole('textbox', { name: 'Filter menu items' }),
46+
page.getByRole('textbox', { name: 'Search previous conversations' }),
4547
page.getByRole('separator', { name: 'Resize' }),
4648
];
4749

@@ -51,7 +53,7 @@ export async function assertDrawerState(page: Page, state: 'open' | 'closed') {
5153
}
5254

5355
export async function verifySidePanelConversation(page: Page) {
54-
const sidePanel = page.locator('.pf-v6-c-drawer__panel');
56+
const sidePanel = page.locator('.pf-v6-c-drawer__panel-main');
5557
await expect(sidePanel).toBeVisible();
5658

5759
const newButton = sidePanel.getByRole('button', { name: 'new chat' });

workspaces/lightspeed/plugins/lightspeed-backend/README.md

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,36 +31,10 @@ Add the following lightspeed configurations into your `app-config.yaml` file:
3131

3232
```yaml
3333
lightspeed:
34-
servers:
35-
- id: <server id>
36-
url: <serverURL>
37-
token: <api key>
38-
questionValidation: true # Optional - To disable question (prompt) validation set it to false.
3934
servicePort: <portNumber> # Optional - Change the LS service port nubmer. Defaults to 8080.
4035
systemPrompt: <system prompt> # Optional - Override the default system prompt.
4136
```
4237
43-
Example local development configuration:
44-
45-
```yaml
46-
lightspeed:
47-
servers:
48-
- id: 'my-llm-server'
49-
url: 'https://localhost:443/v1'
50-
token: 'js92n-ssj28dbdk902' # dummy token
51-
```
52-
53-
`questionValidation` is default to be enabled with topic restriction on RHDH related topics.
54-
If you want to disable the validation, set the value to be `false`.
55-
56-
Example configuration to disable `questionValidation`:
57-
58-
```yaml
59-
lightspeed:
60-
questionValidation: false
61-
servers: ... ...
62-
```
63-
6438
#### Permission Framework Support
6539
6640
The Lightspeed Backend plugin has support for the permission framework.
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
lightspeed:
2-
servers:
3-
- id: ${SERVER_ID}
4-
url: ${SERVER_URL}
5-
token: ${SERVER_TOKEN}
1+
# OPTIONAL: Backend-only configurations
2+
#lightspeed:
3+
# servicePort: 8080 # OPTIONAL: Port for lightspeed service (default: 8080)
4+
# systemPrompt: <custom_system_prompt> # OPTIONAL: Override default RHDH system prompt

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

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,7 @@ export interface Config {
1919
* Configuration required for using lightspeed
2020
* @visibility frontend
2121
*/
22-
lightspeed: {
23-
servers: Array<{
24-
/**
25-
* The id of the server.
26-
* @visibility frontend
27-
*/
28-
id: string;
29-
/**
30-
* The url of the server.
31-
* @visibility frontend
32-
*/
33-
url: string;
34-
/**
35-
* The access token for authenticating server.
36-
* @visibility secret
37-
*/
38-
token?: string;
39-
}>;
22+
lightspeed?: {
4023
/**
4124
* configure the port number for the lightspeed service.
4225
* @visibility backend

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import express, { Router } from 'express';
2222
import { createProxyMiddleware } from 'http-proxy-middleware';
2323
import fetch from 'node-fetch';
2424

25-
// const fetch = (await import('node-fetch')).default;
26-
2725
import {
2826
lightspeedChatCreatePermission,
2927
lightspeedChatDeletePermission,

0 commit comments

Comments
 (0)