From f7d96f84fc42168edbe789dda0a1cb0f6381e062 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 16 Jun 2026 18:06:44 -0400 Subject: [PATCH 1/5] documents verified before adding another one in notebooks Signed-off-by: Lucas # Conflicts: # workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts --- .../src/service/constant.ts | 2 +- .../src/service/notebooks/notebooksRouters.ts | 38 +------------------ .../notebooks/sessions/sessionService.ts | 36 +++++++++++++++++- .../src/components/LightSpeedChat.tsx | 6 +-- .../components/notebooks/AddDocumentModal.tsx | 10 ++++- .../components/notebooks/DocumentSidebar.tsx | 12 ++++-- .../src/components/notebooks/NotebookView.tsx | 20 +++++++--- .../lightspeed/src/translations/ref.ts | 5 ++- 8 files changed, 76 insertions(+), 53 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts index a1621d9b5a..37f16bf7ae 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/constant.ts @@ -26,7 +26,7 @@ export const DEFAULT_LIGHTSPEED_SERVICE_HOST = '127.0.0.1'; // Lightspeed core s export const DEFAULT_LIGHTSPEED_SERVICE_PORT = 8080; // Lightspeed service port export const DEFAULT_MAX_FILE_SIZE_MB = 20 * 1024 * 1024; // 20MB export const NOTEBOOKS_SYSTEM_PROMPT = ` -You are a helpful, analytical Senior Research Analyst assistant. Your primary objective is to synthesize cross-document information to answer user queries with 100% fidelity to the provided documents. +You are a helpful, analytical Research Analyst assistant. Your primary objective is to synthesize cross-document information to answer user queries with 100% fidelity to the provided documents. ### QUERY TYPES - IMPORTANT * **Meta Queries ONLY:** ONLY when the user asks specifically about YOU as an assistant (e.g., "who are you", "what can you do", "hello"), respond naturally without requiring documents. diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts index 1cf608f826..d383a0cc99 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/notebooksRouters.ts @@ -226,49 +226,14 @@ export async function createNotebooksRouter( }; this.push(`data: ${JSON.stringify(legacy)}\n\n`); } else if (eventType === 'response.completed') { - const usage = parsed?.response?.usage; - // Log the full response to see what we're getting logger.info( `Full response.completed event: ${JSON.stringify(parsed?.response, null, 2)}`, ); // Extract citations/sources from tool calls (file_search results) - const toolCalls = parsed?.response?.tool_calls || []; - logger.info( - `Tool calls received: ${JSON.stringify(toolCalls, null, 2)}`, - ); - - const referencedDocuments: any[] = []; - - for (const toolCall of toolCalls) { - if (toolCall.tool_name === 'file_search') { - logger.info( - `Found file_search tool call: ${JSON.stringify(toolCall, null, 2)}`, - ); - const citations = toolCall.content?.citations || []; - for (const citation of citations) { - referencedDocuments.push({ - document_id: citation.document_id || citation.file_id, - content: citation.text || citation.content, - }); - } - } - } - logger.info( - `Referenced documents: ${JSON.stringify(referencedDocuments)}`, - ); - - const legacy = { - event: 'end', - data: { - referenced_documents: referencedDocuments, - input_tokens: usage?.input_tokens, - output_tokens: usage?.output_tokens, - }, - }; - this.push(`data: ${JSON.stringify(legacy)}\n\n`); + // this.push(`data: ${JSON.stringify(legacy)}\n\n`); } else { // Log unhandled event types to help identify what we're missing logger.debug(`Unhandled SSE event type: ${eventType}`, parsed); @@ -482,6 +447,7 @@ export async function createNotebooksRouter( input: query, instructions: systemPrompt, tools: [{ type: 'file_search', vector_store_ids: [sessionId] }], + tool_choice: 'required', model: `${queryProvider}/${queryModel}`, stream: true, temperature: 0.35, diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts index 60fa10ec91..c659cedf09 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts @@ -184,8 +184,40 @@ export class SessionService { * @throws NotAllowedError if user does not own the session */ async deleteSession(sessionId: string, userId: string): Promise { - // Verify ownership before deletion - await this.readSession(sessionId, userId); + // Verify ownership before deletion and get session details + const session = await this.readSession(sessionId, userId); + const conversationId = session.metadata?.conversation_id; + + // Delete associated conversation if it exists + if (conversationId) { + try { + // Access the baseURL from the VectorStoresOperator client + const baseURL = (this.client as any).baseURL; + const response = await fetch( + `${baseURL}/v2/conversations/${encodeURIComponent(conversationId)}?user_id=${encodeURIComponent(userId)}`, + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + if (!response.ok) { + this.logger.warn( + `Failed to delete conversation ${conversationId}: HTTP ${response.status}`, + ); + } else { + this.logger.info( + `Deleted conversation ${conversationId} for session ${sessionId}`, + ); + } + } catch (error) { + this.logger.warn( + `Failed to delete conversation ${conversationId}: ${error}`, + ); + } + } // Delete all underlying files from Files API to prevent orphans try { diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx index f130bad387..dbf0ae45d4 100644 --- a/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx +++ b/workspaces/lightspeed/plugins/lightspeed/src/components/LightSpeedChat.tsx @@ -694,9 +694,8 @@ export const LightspeedChat = ({ [], ); const createNotebookMutation = useCreateNotebook(); - const { data: notebookDocuments = [] } = useNotebookDocuments( - activeNotebook?.session_id, - ); + const { data: notebookDocuments = [], isFetching: isDocumentsFetching } = + useNotebookDocuments(activeNotebook?.session_id); const [conversationId, setConversationId] = useState(''); const [requestId, setRequestId] = useState(''); const [newChatCreated, setNewChatCreated] = useState(false); @@ -2085,6 +2084,7 @@ export const LightspeedChat = ({ sessionId={activeNotebook.session_id} notebookName={activeNotebook.name} documents={notebookDocuments} + isDocumentsFetching={isDocumentsFetching} metadata={activeNotebook.metadata} topicSummary={ conversations.find( diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx index ac793802db..7b85fbb594 100644 --- a/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx +++ b/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/AddDocumentModal.tsx @@ -113,6 +113,7 @@ type AddDocumentModalProps = { onClose: () => void; sessionId: string; existingDocumentNames: string[]; + hasUploadsInProgress?: boolean; onFilesUploading?: (files: File[]) => void; onUploadStarted?: (info: { fileName: string; documentId: string }) => void; onUploadFailed?: (fileName: string) => void; @@ -126,6 +127,7 @@ export const AddDocumentModal = ({ onClose, sessionId, existingDocumentNames, + hasUploadsInProgress, onFilesUploading, onUploadStarted, onUploadFailed, @@ -261,6 +263,12 @@ export const AddDocumentModal = ({ )} + {hasUploadsInProgress && ( + + {t('notebook.view.documents.uploadsInProgress')} + + )} + {remainingSlots > 0 && ( {(t as Function)('notebook.upload.modal.addButton', { count: selectedFiles.length, diff --git a/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/DocumentSidebar.tsx b/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/DocumentSidebar.tsx index 7b280716c3..34111003bb 100644 --- a/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/DocumentSidebar.tsx +++ b/workspaces/lightspeed/plugins/lightspeed/src/components/notebooks/DocumentSidebar.tsx @@ -129,6 +129,7 @@ type DocumentSidebarProps = { completedFileNames?: Set; deletingDocumentIds?: Set; collapsed: boolean; + hasUploadsInProgress?: boolean; onToggleCollapse: () => void; onAddDocument: () => void; onDeleteDocument?: (documentId: string) => void; @@ -141,6 +142,7 @@ export const DocumentSidebar = ({ completedFileNames, deletingDocumentIds, collapsed, + hasUploadsInProgress, onToggleCollapse, onAddDocument, onDeleteDocument, @@ -184,10 +186,14 @@ export const DocumentSidebar = ({ {isAddDisabled ? ( - + - + ) : ( { + if (hasUploadsInProgress) + return t('notebook.view.documents.uploadsInProgress'); + if (isAddDisabled) + return t('notebook.view.documents.maxReached'); + return t('notebook.view.documents.add'); + })()} position="right" > @@ -778,6 +785,7 @@ export const NotebookView = ({ onClose={handleCloseUploadModal} sessionId={sessionId} existingDocumentNames={documents.map(d => d.title)} + hasUploadsInProgress={hasUploadsInProgress} onFilesUploading={handleFilesUploading} onUploadStarted={handleUploadStarted} onUploadFailed={handleUploadFailed} diff --git a/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts b/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts index 4588deb743..aa08cbfd21 100644 --- a/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts +++ b/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts @@ -76,7 +76,10 @@ export const lightspeedMessages = { 'notebook.view.documents.uploading': 'Uploading document', 'notebook.view.documents.maxReached': 'Maximum 10 documents are allowed. Delete a document to upload a new document.', - 'notebook.upload.failed': '"{{fileName}}" upload failed.', + 'notebook.view.documents.uploadsInProgress': + 'Please wait for current uploads to complete before adding more documents.', + 'notebook.upload.success': '{{fileName}} Successfully Uploaded.', + 'notebook.upload.failed': '{{fileName}} Upload Failed.', // Notebook upload modal 'notebook.upload.modal.title': 'Add a document to Notebook', From d29960bdebbaa521a28a2ae2e58fc9c71d3ca84f Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 14 May 2026 11:19:32 -0400 Subject: [PATCH 2/5] adding changeset Signed-off-by: Lucas --- workspaces/lightspeed/.changeset/khaki-tomatoes-help.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 workspaces/lightspeed/.changeset/khaki-tomatoes-help.md diff --git a/workspaces/lightspeed/.changeset/khaki-tomatoes-help.md b/workspaces/lightspeed/.changeset/khaki-tomatoes-help.md new file mode 100644 index 0000000000..301ad9361a --- /dev/null +++ b/workspaces/lightspeed/.changeset/khaki-tomatoes-help.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-lightspeed': minor +--- + +documents cannot be uploaded when a document processing is in progress From 0ba86e14a653cd4efe76cf0093f9b0923a408c08 Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 15 May 2026 00:54:22 -0400 Subject: [PATCH 3/5] Update API reports for new translation key Add notebook.view.documents.uploadsInProgress to lightspeed translation API Signed-off-by: Lucas --- workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md b/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md index c45f03f14d..2cf51b3550 100644 --- a/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md +++ b/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md @@ -241,6 +241,11 @@ export const lightspeedTranslationRef: TranslationRef< readonly 'notebook.view.sidebar.resize': string; readonly 'notebook.view.documents.uploading': string; readonly 'notebook.view.documents.maxReached': string; +<<<<<<< HEAD +======= + readonly 'notebook.view.documents.uploadsInProgress': string; + readonly 'notebook.upload.success': string; +>>>>>>> 419a31963 (Update API reports for new translation key) readonly 'notebook.upload.failed': string; readonly 'notebook.upload.modal.title': string; readonly 'notebook.upload.modal.dragDropTitle': string; From d0de96fdddd9fadcf925717e76ede3d6a3c7475b Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 18 May 2026 21:07:13 -0400 Subject: [PATCH 4/5] address comments Signed-off-by: Lucas --- workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md | 4 ---- .../lightspeed/plugins/lightspeed/src/translations/ref.ts | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md b/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md index 2cf51b3550..8367eaefaa 100644 --- a/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md +++ b/workspaces/lightspeed/plugins/lightspeed/report-alpha.api.md @@ -241,11 +241,7 @@ export const lightspeedTranslationRef: TranslationRef< readonly 'notebook.view.sidebar.resize': string; readonly 'notebook.view.documents.uploading': string; readonly 'notebook.view.documents.maxReached': string; -<<<<<<< HEAD -======= readonly 'notebook.view.documents.uploadsInProgress': string; - readonly 'notebook.upload.success': string; ->>>>>>> 419a31963 (Update API reports for new translation key) readonly 'notebook.upload.failed': string; readonly 'notebook.upload.modal.title': string; readonly 'notebook.upload.modal.dragDropTitle': string; diff --git a/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts b/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts index aa08cbfd21..e5ced44f75 100644 --- a/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts +++ b/workspaces/lightspeed/plugins/lightspeed/src/translations/ref.ts @@ -78,8 +78,7 @@ export const lightspeedMessages = { 'Maximum 10 documents are allowed. Delete a document to upload a new document.', 'notebook.view.documents.uploadsInProgress': 'Please wait for current uploads to complete before adding more documents.', - 'notebook.upload.success': '{{fileName}} Successfully Uploaded.', - 'notebook.upload.failed': '{{fileName}} Upload Failed.', + 'notebook.upload.failed': '"{{fileName}}" upload failed.', // Notebook upload modal 'notebook.upload.modal.title': 'Add a document to Notebook', From e49fc2c66a6814a5dae977531d63621830128a42 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 19 May 2026 17:13:21 -0400 Subject: [PATCH 5/5] fix document deletion Signed-off-by: Lucas --- .../notebooks/sessions/sessionService.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts index c659cedf09..fb42a330ee 100644 --- a/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts +++ b/workspaces/lightspeed/plugins/lightspeed-backend/src/service/notebooks/sessions/sessionService.ts @@ -219,29 +219,37 @@ export class SessionService { } } - // Delete all underlying files from Files API to prevent orphans + // Delete all vector store files (same pattern as documentService.deleteDocument) try { const filesResponse = await this.client.vectorStores.files.list(sessionId); - const fileIds = filesResponse.data?.map((f: any) => f.file_id) || []; + const files = filesResponse.data || []; + + this.logger.info( + `Deleting ${files.length} vector store files for session ${sessionId}`, + ); await Promise.all( - fileIds.map(async (fileId: string) => { + files.map(async (file: any) => { try { - await this.client.files.delete(fileId); - this.logger.info(`Deleted file ${fileId} from Files API`); + await this.client.vectorStores.files.delete(sessionId, file.id); + this.logger.info( + `Deleted vector store file ${file.id} (${file.attributes?.title || 'untitled'})`, + ); } catch (error) { - this.logger.warn(`Failed to delete file ${fileId}: ${error}`); + this.logger.warn( + `Failed to delete vector store file ${file.id}: ${error}`, + ); } }), ); } catch (error) { this.logger.warn( - `Failed to clean up files for session ${sessionId}: ${error}`, + `Failed to clean up vector store files for session ${sessionId}: ${error}`, ); } - // Delete the vector store (cascade deletes vector store files) + // Delete the vector store itself await this.client.vectorStores.delete(sessionId); this.logger.info(`Session ${sessionId} deleted`); }