@red-hat-developer-hub/backstage-plugin-intelligent-assistant-backend 3.0.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 +607 -0
- package/README.md +400 -0
- package/app-config.yaml +25 -0
- package/config.d.ts +142 -0
- package/dist/database/migration.cjs.js +19 -0
- package/dist/database/migration.cjs.js.map +1 -0
- package/dist/index.cjs.js +12 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/plugin.cjs.js +95 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/service/constant.cjs.js +107 -0
- package/dist/service/constant.cjs.js.map +1 -0
- package/dist/service/mcp-server-store.cjs.js +107 -0
- package/dist/service/mcp-server-store.cjs.js.map +1 -0
- package/dist/service/mcp-server-validator.cjs.js +215 -0
- package/dist/service/mcp-server-validator.cjs.js.map +1 -0
- package/dist/service/middleware/createPermissionMiddleware.cjs.js +24 -0
- package/dist/service/middleware/createPermissionMiddleware.cjs.js.map +1 -0
- package/dist/service/middleware/createRateLimitMiddleware.cjs.js +52 -0
- package/dist/service/middleware/createRateLimitMiddleware.cjs.js.map +1 -0
- package/dist/service/middleware/getIdentity.cjs.js +39 -0
- package/dist/service/middleware/getIdentity.cjs.js.map +1 -0
- package/dist/service/notebooks/VectorStoresOperator.cjs.js +347 -0
- package/dist/service/notebooks/VectorStoresOperator.cjs.js.map +1 -0
- package/dist/service/notebooks/documents/documentHelpers.cjs.js +36 -0
- package/dist/service/notebooks/documents/documentHelpers.cjs.js.map +1 -0
- package/dist/service/notebooks/documents/documentService.cjs.js +189 -0
- package/dist/service/notebooks/documents/documentService.cjs.js.map +1 -0
- package/dist/service/notebooks/documents/fileParser.cjs.js +139 -0
- package/dist/service/notebooks/documents/fileParser.cjs.js.map +1 -0
- package/dist/service/notebooks/notebooksRouters.cjs.js +477 -0
- package/dist/service/notebooks/notebooksRouters.cjs.js.map +1 -0
- package/dist/service/notebooks/sessions/sessionService.cjs.js +226 -0
- package/dist/service/notebooks/sessions/sessionService.cjs.js.map +1 -0
- package/dist/service/notebooks/types/notebooksTypes.cjs.js +40 -0
- package/dist/service/notebooks/types/notebooksTypes.cjs.js.map +1 -0
- package/dist/service/notebooks/utils.cjs.js +70 -0
- package/dist/service/notebooks/utils.cjs.js.map +1 -0
- package/dist/service/permission.cjs.js +26 -0
- package/dist/service/permission.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +638 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/service/token-encryption.cjs.js +101 -0
- package/dist/service/token-encryption.cjs.js.map +1 -0
- package/dist/service/utils.cjs.js +40 -0
- package/dist/service/utils.cjs.js.map +1 -0
- package/dist/service/validation.cjs.js +45 -0
- package/dist/service/validation.cjs.js.map +1 -0
- package/migrations/20260302120000_add_mcp_servers.js +41 -0
- package/package.json +109 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var constant = require('../../constant.cjs.js');
|
|
5
|
+
var fileParser = require('./fileParser.cjs.js');
|
|
6
|
+
|
|
7
|
+
class DocumentService {
|
|
8
|
+
logger;
|
|
9
|
+
client;
|
|
10
|
+
chunkingStrategy;
|
|
11
|
+
constructor(client, logger, config) {
|
|
12
|
+
this.client = client;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
const chunkingType = config?.getOptionalString(
|
|
15
|
+
"intelligent-assistant.notebooks.chunkingStrategy.type"
|
|
16
|
+
) || constant.DEFAULT_CHUNKING_STRATEGY_TYPE;
|
|
17
|
+
if (chunkingType === "static") {
|
|
18
|
+
this.chunkingStrategy = {
|
|
19
|
+
type: "static",
|
|
20
|
+
static: {
|
|
21
|
+
max_chunk_size_tokens: config?.getOptionalNumber(
|
|
22
|
+
"intelligent-assistant.notebooks.chunkingStrategy.maxChunkSizeTokens"
|
|
23
|
+
) || constant.DEFAULT_MAX_CHUNK_SIZE_TOKENS,
|
|
24
|
+
chunk_overlap_tokens: config?.getOptionalNumber(
|
|
25
|
+
"intelligent-assistant.notebooks.chunkingStrategy.chunkOverlapTokens"
|
|
26
|
+
) || constant.DEFAULT_CHUNK_OVERLAP_TOKENS
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
} else {
|
|
30
|
+
this.chunkingStrategy = { type: "auto" };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Find a file by title in vector store
|
|
35
|
+
* @param sessionId - Vector store ID
|
|
36
|
+
* @param documentTitle - Document title to search for
|
|
37
|
+
* @returns File object if found, null otherwise
|
|
38
|
+
*/
|
|
39
|
+
async findFileByTitle(sessionId, documentTitle) {
|
|
40
|
+
const filesResponse = await this.client.vectorStores.files.list(sessionId);
|
|
41
|
+
return filesResponse.data.find(
|
|
42
|
+
(f) => f.attributes?.title === documentTitle
|
|
43
|
+
) || null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Upload a file to the Files API
|
|
47
|
+
* @param content - File content as string
|
|
48
|
+
* @param title - File title/name
|
|
49
|
+
* @returns File ID from the Files API
|
|
50
|
+
* @throws Error if upload fails
|
|
51
|
+
*/
|
|
52
|
+
async uploadFile(content, title) {
|
|
53
|
+
try {
|
|
54
|
+
const mimeType = "text/plain";
|
|
55
|
+
const txtFilename = `${title.replace(/\.[^.]+$/, "")}.txt`;
|
|
56
|
+
const file = await this.client.files.create({
|
|
57
|
+
file: await fileParser.toFile(Buffer.from(content, "utf-8"), txtFilename, {
|
|
58
|
+
type: mimeType
|
|
59
|
+
}),
|
|
60
|
+
purpose: "assistants"
|
|
61
|
+
});
|
|
62
|
+
this.logger.info(
|
|
63
|
+
`File created - id: ${file.id}, filename: ${file.filename}`
|
|
64
|
+
);
|
|
65
|
+
return file.id;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error instanceof Error) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Failed to upload file: ${String(error)}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Upsert a document - create if it doesn't exist, update if it does
|
|
75
|
+
* @param sessionId - Vector store ID
|
|
76
|
+
* @param title - Original document title
|
|
77
|
+
* @param fileType - Document source type (text, pdf, url, etc.)
|
|
78
|
+
* @param fileId - File ID from Files API
|
|
79
|
+
* @param newTitle - New title for rename operation (optional)
|
|
80
|
+
* @returns Upsert result with document ID and status
|
|
81
|
+
* @throws ConflictError if newTitle conflicts with existing document
|
|
82
|
+
*/
|
|
83
|
+
async upsertDocument(sessionId, title, fileType, fileId, newTitle) {
|
|
84
|
+
const existingFile = await this.findFileByTitle(sessionId, title);
|
|
85
|
+
const createdAt = existingFile?.attributes?.created_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
86
|
+
if (newTitle && title !== newTitle) {
|
|
87
|
+
const conflictingFile = await this.findFileByTitle(sessionId, newTitle);
|
|
88
|
+
if (conflictingFile) {
|
|
89
|
+
throw new errors.ConflictError(
|
|
90
|
+
`A document with the title "${newTitle || title}" already exists in this session`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (existingFile) {
|
|
95
|
+
await this.deleteDocument(sessionId, title);
|
|
96
|
+
}
|
|
97
|
+
const vectorStoreFile = await this.client.vectorStores.files.create(
|
|
98
|
+
sessionId,
|
|
99
|
+
{
|
|
100
|
+
file_id: fileId,
|
|
101
|
+
chunking_strategy: this.chunkingStrategy,
|
|
102
|
+
attributes: {
|
|
103
|
+
title: newTitle || title,
|
|
104
|
+
source_type: fileType,
|
|
105
|
+
created_at: createdAt,
|
|
106
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
this.logger.info(
|
|
111
|
+
`Document "${newTitle || title}" (ID: ${title}) upload started with file ${fileId}`
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
document_id: newTitle || title,
|
|
115
|
+
file_id: fileId,
|
|
116
|
+
replaced: false,
|
|
117
|
+
status: vectorStoreFile.status
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get file processing status
|
|
122
|
+
* @param sessionId - Vector store ID
|
|
123
|
+
* @param documentTitle - Document title
|
|
124
|
+
* @returns File status including processing state, chunk count, and error if any
|
|
125
|
+
* @throws NotFoundError if document not found
|
|
126
|
+
*/
|
|
127
|
+
async getFileStatus(sessionId, documentTitle) {
|
|
128
|
+
const file = await this.findFileByTitle(sessionId, documentTitle);
|
|
129
|
+
if (!file) {
|
|
130
|
+
throw new errors.NotFoundError(`Document not found: ${documentTitle}`);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
status: file.status,
|
|
134
|
+
chunks_count: file.chunks_count,
|
|
135
|
+
error: file.last_error?.message
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* List all documents in a session
|
|
140
|
+
* @param sessionId - Vector store ID
|
|
141
|
+
* @param fileTypeFilter - Optional filter by source type (text, pdf, url, etc.)
|
|
142
|
+
* @returns Array of session documents
|
|
143
|
+
*/
|
|
144
|
+
async listDocuments(sessionId, fileTypeFilter) {
|
|
145
|
+
this.logger.info(`Listing documents for session ${sessionId}`);
|
|
146
|
+
const filesResponse = await this.client.vectorStores.files.list(sessionId);
|
|
147
|
+
if (!filesResponse.data || filesResponse.data.length === 0) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const documents = filesResponse.data.filter((file) => {
|
|
151
|
+
if (fileTypeFilter && file.attributes?.source_type !== fileTypeFilter) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}).map((file) => {
|
|
156
|
+
const attrs = file.attributes || {};
|
|
157
|
+
return {
|
|
158
|
+
document_id: attrs.title,
|
|
159
|
+
source_type: attrs.source_type || "text",
|
|
160
|
+
created_at: attrs.created_at,
|
|
161
|
+
updated_at: attrs.updated_at
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
this.logger.info(
|
|
165
|
+
`Found ${documents.length} documents in session ${sessionId}`
|
|
166
|
+
);
|
|
167
|
+
return documents;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Delete a document from the vector store and Files API
|
|
171
|
+
* @param sessionId - Vector store ID
|
|
172
|
+
* @param documentTitle - Document title to delete
|
|
173
|
+
* @throws NotFoundError if document not found
|
|
174
|
+
*/
|
|
175
|
+
async deleteDocument(sessionId, documentTitle) {
|
|
176
|
+
this.logger.info(`Deleting document ${documentTitle} from ${sessionId}`);
|
|
177
|
+
const file = await this.findFileByTitle(sessionId, documentTitle);
|
|
178
|
+
if (!file) {
|
|
179
|
+
throw new errors.NotFoundError(`Document not found: ${documentTitle}`);
|
|
180
|
+
}
|
|
181
|
+
await this.client.vectorStores.files.delete(sessionId, file.id);
|
|
182
|
+
this.logger.info(
|
|
183
|
+
`Deleted document ${documentTitle} (file ${file.id}) from session ${sessionId}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
exports.DocumentService = DocumentService;
|
|
189
|
+
//# sourceMappingURL=documentService.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documentService.cjs.js","sources":["../../../../src/service/notebooks/documents/documentService.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\n\nimport {\n DEFAULT_CHUNK_OVERLAP_TOKENS,\n DEFAULT_CHUNKING_STRATEGY_TYPE,\n DEFAULT_MAX_CHUNK_SIZE_TOKENS,\n} from '../../constant';\nimport { SessionDocument, UpsertResult } from '../types/notebooksTypes';\nimport { VectorStoresOperator } from '../VectorStoresOperator';\nimport { toFile } from './fileParser';\n\n/**\n * Service for managing documents within notebook sessions using File-Based API\n * Each session has its own dedicated vector store\n * Uses VectorStoresOperator to proxy through lightspeed-core\n */\nexport class DocumentService {\n private logger: LoggerService;\n private client: VectorStoresOperator;\n private chunkingStrategy: {\n type: string;\n static?: { max_chunk_size_tokens: number; chunk_overlap_tokens: number };\n };\n\n constructor(\n client: VectorStoresOperator,\n logger: LoggerService,\n config?: Config,\n ) {\n this.client = client;\n this.logger = logger;\n\n // Chunking strategy configuration\n const chunkingType =\n config?.getOptionalString(\n 'intelligent-assistant.notebooks.chunkingStrategy.type',\n ) || DEFAULT_CHUNKING_STRATEGY_TYPE;\n\n if (chunkingType === 'static') {\n this.chunkingStrategy = {\n type: 'static',\n static: {\n max_chunk_size_tokens:\n config?.getOptionalNumber(\n 'intelligent-assistant.notebooks.chunkingStrategy.maxChunkSizeTokens',\n ) || DEFAULT_MAX_CHUNK_SIZE_TOKENS,\n chunk_overlap_tokens:\n config?.getOptionalNumber(\n 'intelligent-assistant.notebooks.chunkingStrategy.chunkOverlapTokens',\n ) || DEFAULT_CHUNK_OVERLAP_TOKENS,\n },\n };\n } else {\n this.chunkingStrategy = { type: 'auto' };\n }\n }\n\n /**\n * Find a file by title in vector store\n * @param sessionId - Vector store ID\n * @param documentTitle - Document title to search for\n * @returns File object if found, null otherwise\n */\n async findFileByTitle(\n sessionId: string,\n documentTitle: string,\n ): Promise<any | null> {\n const filesResponse = await this.client.vectorStores.files.list(sessionId);\n return (\n filesResponse.data.find(\n (f: any) => f.attributes?.title === documentTitle,\n ) || null\n );\n }\n\n /**\n * Upload a file to the Files API\n * @param content - File content as string\n * @param title - File title/name\n * @returns File ID from the Files API\n * @throws Error if upload fails\n */\n async uploadFile(content: string, title: string): Promise<string> {\n try {\n // Determine MIME type from file type or default to text/plain\n const mimeType = 'text/plain';\n const txtFilename = `${title.replace(/\\.[^.]+$/, '')}.txt`;\n const file = await this.client.files.create({\n file: await toFile(Buffer.from(content, 'utf-8'), txtFilename, {\n type: mimeType,\n }),\n purpose: 'assistants',\n });\n\n this.logger.info(\n `File created - id: ${file.id}, filename: ${file.filename}`,\n );\n return file.id;\n } catch (error) {\n // Preserve the original error type and message\n if (error instanceof Error) {\n throw error;\n }\n // For non-Error objects, wrap with context\n throw new Error(`Failed to upload file: ${String(error)}`);\n }\n }\n\n /**\n * Upsert a document - create if it doesn't exist, update if it does\n * @param sessionId - Vector store ID\n * @param title - Original document title\n * @param fileType - Document source type (text, pdf, url, etc.)\n * @param fileId - File ID from Files API\n * @param newTitle - New title for rename operation (optional)\n * @returns Upsert result with document ID and status\n * @throws ConflictError if newTitle conflicts with existing document\n */\n async upsertDocument(\n sessionId: string,\n title: string,\n fileType: string,\n fileId: string,\n newTitle?: string,\n ): Promise<UpsertResult> {\n // Find existing file by document_id\n const existingFile = await this.findFileByTitle(sessionId, title);\n const createdAt =\n (existingFile?.attributes?.created_at as string) ||\n new Date().toISOString();\n\n if (newTitle && title !== newTitle) {\n // Check for title conflicts when renaming\n const conflictingFile = await this.findFileByTitle(sessionId, newTitle);\n\n if (conflictingFile) {\n throw new ConflictError(\n `A document with the title \"${newTitle || title}\" already exists in this session`,\n );\n }\n }\n if (existingFile) {\n await this.deleteDocument(sessionId, title);\n }\n\n const vectorStoreFile = await this.client.vectorStores.files.create(\n sessionId,\n {\n file_id: fileId,\n chunking_strategy: this.chunkingStrategy,\n attributes: {\n title: newTitle || title,\n source_type: fileType,\n created_at: createdAt,\n updated_at: new Date().toISOString(),\n },\n },\n );\n\n this.logger.info(\n `Document \"${newTitle || title}\" (ID: ${title}) upload started with file ${fileId}`,\n );\n\n return {\n document_id: newTitle || title,\n file_id: fileId,\n replaced: false,\n status: vectorStoreFile.status,\n };\n }\n\n /**\n * Get file processing status\n * @param sessionId - Vector store ID\n * @param documentTitle - Document title\n * @returns File status including processing state, chunk count, and error if any\n * @throws NotFoundError if document not found\n */\n async getFileStatus(\n sessionId: string,\n documentTitle: string,\n ): Promise<{\n status: 'in_progress' | 'completed' | 'failed' | 'cancelled';\n chunks_count: number;\n error?: string;\n }> {\n const file = await this.findFileByTitle(sessionId, documentTitle);\n\n if (!file) {\n throw new NotFoundError(`Document not found: ${documentTitle}`);\n }\n return {\n status: file.status,\n chunks_count: file.chunks_count,\n error: file.last_error?.message,\n };\n }\n\n /**\n * List all documents in a session\n * @param sessionId - Vector store ID\n * @param fileTypeFilter - Optional filter by source type (text, pdf, url, etc.)\n * @returns Array of session documents\n */\n async listDocuments(\n sessionId: string,\n fileTypeFilter?: string,\n ): Promise<SessionDocument[]> {\n this.logger.info(`Listing documents for session ${sessionId}`);\n\n // List all files in vector store\n const filesResponse = await this.client.vectorStores.files.list(sessionId);\n\n if (!filesResponse.data || filesResponse.data.length === 0) {\n return [];\n }\n\n // Map files to SessionDocument format\n const documents = filesResponse.data\n .filter((file: any) => {\n // Apply file type filter if provided\n if (fileTypeFilter && file.attributes?.source_type !== fileTypeFilter) {\n return false;\n }\n return true;\n })\n .map((file: any) => {\n const attrs = file.attributes || {};\n return {\n document_id: attrs.title,\n source_type:\n (attrs.source_type as SessionDocument['source_type']) || 'text',\n created_at: attrs.created_at,\n updated_at: attrs.updated_at,\n };\n });\n\n this.logger.info(\n `Found ${documents.length} documents in session ${sessionId}`,\n );\n return documents;\n }\n\n /**\n * Delete a document from the vector store and Files API\n * @param sessionId - Vector store ID\n * @param documentTitle - Document title to delete\n * @throws NotFoundError if document not found\n */\n async deleteDocument(\n sessionId: string,\n documentTitle: string,\n ): Promise<void> {\n this.logger.info(`Deleting document ${documentTitle} from ${sessionId}`);\n\n const file = await this.findFileByTitle(sessionId, documentTitle);\n\n if (!file) {\n throw new NotFoundError(`Document not found: ${documentTitle}`);\n }\n\n // Delete from vector store first\n await this.client.vectorStores.files.delete(sessionId, file.id);\n\n this.logger.info(\n `Deleted document ${documentTitle} (file ${file.id}) from session ${sessionId}`,\n );\n }\n}\n"],"names":["DEFAULT_CHUNKING_STRATEGY_TYPE","DEFAULT_MAX_CHUNK_SIZE_TOKENS","DEFAULT_CHUNK_OVERLAP_TOKENS","toFile","ConflictError","NotFoundError"],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var yaml = require('js-yaml');
|
|
5
|
+
var pdfjsLib = require('pdfjs-dist');
|
|
6
|
+
var stream = require('stream');
|
|
7
|
+
var constant = require('../../constant.cjs.js');
|
|
8
|
+
|
|
9
|
+
function _interopNamespaceCompat(e) {
|
|
10
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var yaml__namespace = /*#__PURE__*/_interopNamespaceCompat(yaml);
|
|
28
|
+
var pdfjsLib__namespace = /*#__PURE__*/_interopNamespaceCompat(pdfjsLib);
|
|
29
|
+
|
|
30
|
+
function parseTextFile(buffer, fileName, fileType) {
|
|
31
|
+
const content = buffer.toString("utf-8");
|
|
32
|
+
return {
|
|
33
|
+
content,
|
|
34
|
+
metadata: {
|
|
35
|
+
fileName,
|
|
36
|
+
fileType,
|
|
37
|
+
parseTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function parseJSONFile(buffer, fileName, fileType) {
|
|
42
|
+
try {
|
|
43
|
+
const content = buffer.toString("utf-8");
|
|
44
|
+
JSON.parse(content);
|
|
45
|
+
return {
|
|
46
|
+
content,
|
|
47
|
+
metadata: {
|
|
48
|
+
fileName,
|
|
49
|
+
fileType,
|
|
50
|
+
parseTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new errors.InputError(`Invalid JSON file: ${error}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function parseYAMLFile(buffer, fileName, fileType) {
|
|
58
|
+
try {
|
|
59
|
+
const content = buffer.toString("utf-8");
|
|
60
|
+
yaml__namespace.load(content);
|
|
61
|
+
return {
|
|
62
|
+
content,
|
|
63
|
+
metadata: {
|
|
64
|
+
fileName,
|
|
65
|
+
fileType,
|
|
66
|
+
parseTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new errors.InputError(`Invalid YAML file: ${error}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function parsePDFFile(buffer, fileName, fileType) {
|
|
74
|
+
try {
|
|
75
|
+
const loadingTask = pdfjsLib__namespace.getDocument({
|
|
76
|
+
data: new Uint8Array(buffer),
|
|
77
|
+
useSystemFonts: true
|
|
78
|
+
});
|
|
79
|
+
const pdf = await loadingTask.promise;
|
|
80
|
+
const textParts = [];
|
|
81
|
+
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
|
82
|
+
const page = await pdf.getPage(pageNum);
|
|
83
|
+
const textContent = await page.getTextContent();
|
|
84
|
+
const pageText = textContent.items.map((item) => {
|
|
85
|
+
if ("str" in item) {
|
|
86
|
+
return item.str;
|
|
87
|
+
}
|
|
88
|
+
return "";
|
|
89
|
+
}).filter((str) => str.trim().length > 0).join(" ");
|
|
90
|
+
if (pageText.trim().length > 0) {
|
|
91
|
+
textParts.push(`--- Page ${pageNum} ---
|
|
92
|
+
${pageText}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const content = textParts.join("\n\n");
|
|
96
|
+
return {
|
|
97
|
+
content,
|
|
98
|
+
metadata: {
|
|
99
|
+
fileName,
|
|
100
|
+
fileType,
|
|
101
|
+
pageCount: pdf.numPages,
|
|
102
|
+
parseTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new errors.InputError(`Error parsing PDF: ${error}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function parseFile(buffer, fileName, fileType) {
|
|
110
|
+
const normalizedType = fileType.toLowerCase().replace(/^\./, "");
|
|
111
|
+
switch (normalizedType) {
|
|
112
|
+
case constant.SupportedFileType.MARKDOWN:
|
|
113
|
+
case constant.SupportedFileType.TEXT:
|
|
114
|
+
case constant.SupportedFileType.LOG:
|
|
115
|
+
return parseTextFile(buffer, fileName, fileType);
|
|
116
|
+
case constant.SupportedFileType.JSON:
|
|
117
|
+
return parseJSONFile(buffer, fileName, fileType);
|
|
118
|
+
case constant.SupportedFileType.YAML:
|
|
119
|
+
case constant.SupportedFileType.YML:
|
|
120
|
+
return parseYAMLFile(buffer, fileName, fileType);
|
|
121
|
+
case constant.SupportedFileType.PDF:
|
|
122
|
+
return parsePDFFile(buffer, fileName, fileType);
|
|
123
|
+
default:
|
|
124
|
+
throw new errors.InputError(`Unsupported file type: ${fileType}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function toFile(buffer, filename, options) {
|
|
128
|
+
const stream$1 = stream.Readable.from(buffer);
|
|
129
|
+
return {
|
|
130
|
+
name: filename,
|
|
131
|
+
stream: stream$1,
|
|
132
|
+
buffer,
|
|
133
|
+
type: options?.type || "application/octet-stream"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
exports.parseFile = parseFile;
|
|
138
|
+
exports.toFile = toFile;
|
|
139
|
+
//# sourceMappingURL=fileParser.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileParser.cjs.js","sources":["../../../../src/service/notebooks/documents/fileParser.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 { InputError } from '@backstage/errors';\n\nimport * as yaml from 'js-yaml';\nimport * as pdfjsLib from 'pdfjs-dist';\n\nimport { Readable } from 'stream';\n\nimport { SupportedFileType } from '../../constant';\n\nexport interface ParsedDocument {\n content: string;\n metadata: {\n fileName: string;\n fileType: string;\n pageCount?: number;\n parseTimestamp: string;\n };\n}\n\n/**\n * Parse text-based files (md, txt, log)\n */\nfunction parseTextFile(\n buffer: Buffer,\n fileName: string,\n fileType: string,\n): ParsedDocument {\n const content = buffer.toString('utf-8');\n\n return {\n content,\n metadata: {\n fileName,\n fileType,\n parseTimestamp: new Date().toISOString(),\n },\n };\n}\n\n/**\n * Parse JSON files\n */\nfunction parseJSONFile(\n buffer: Buffer,\n fileName: string,\n fileType: string,\n): ParsedDocument {\n try {\n const content = buffer.toString('utf-8');\n // Validate JSON but keep original formatting\n JSON.parse(content);\n\n return {\n content,\n metadata: {\n fileName,\n fileType,\n parseTimestamp: new Date().toISOString(),\n },\n };\n } catch (error) {\n throw new InputError(`Invalid JSON file: ${error}`);\n }\n}\n\n/**\n * Parse YAML files\n */\nfunction parseYAMLFile(\n buffer: Buffer,\n fileName: string,\n fileType: string,\n): ParsedDocument {\n try {\n const content = buffer.toString('utf-8');\n // Validate YAML but keep original formatting and comments\n yaml.load(content);\n\n return {\n content,\n metadata: {\n fileName,\n fileType,\n parseTimestamp: new Date().toISOString(),\n },\n };\n } catch (error) {\n throw new InputError(`Invalid YAML file: ${error}`);\n }\n}\n\n/**\n * Parse PDF files\n * Extracts text content from PDF using PDF.js\n */\nasync function parsePDFFile(\n buffer: Buffer,\n fileName: string,\n fileType: string,\n): Promise<ParsedDocument> {\n try {\n const loadingTask = pdfjsLib.getDocument({\n data: new Uint8Array(buffer),\n useSystemFonts: true,\n });\n\n const pdf = await loadingTask.promise;\n const textParts: string[] = [];\n\n // Extract text from each page\n for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\n const page = await pdf.getPage(pageNum);\n const textContent = await page.getTextContent();\n\n const pageText = textContent.items\n .map((item: any) => {\n if ('str' in item) {\n return item.str;\n }\n return '';\n })\n .filter((str: string) => str.trim().length > 0)\n .join(' ');\n\n if (pageText.trim().length > 0) {\n textParts.push(`--- Page ${pageNum} ---\\n${pageText}`);\n }\n }\n\n const content = textParts.join('\\n\\n');\n\n return {\n content,\n metadata: {\n fileName,\n fileType,\n pageCount: pdf.numPages,\n parseTimestamp: new Date().toISOString(),\n },\n };\n } catch (error) {\n throw new InputError(`Error parsing PDF: ${error}`);\n }\n}\n\n/**\n * Parse file based on its type\n * @param buffer - File buffer\n * @param fileName - File name\n * @param fileType - File type (md, txt, pdf, json, yaml, yml, log)\n * @returns Parsed document with content and metadata\n */\nexport async function parseFile(\n buffer: Buffer,\n fileName: string,\n fileType: string,\n): Promise<ParsedDocument> {\n // Normalize file type: lowercase and remove leading dot\n const normalizedType = fileType.toLowerCase().replace(/^\\./, '');\n\n switch (normalizedType) {\n case SupportedFileType.MARKDOWN:\n case SupportedFileType.TEXT:\n case SupportedFileType.LOG:\n return parseTextFile(buffer, fileName, fileType);\n\n case SupportedFileType.JSON:\n return parseJSONFile(buffer, fileName, fileType);\n\n case SupportedFileType.YAML:\n case SupportedFileType.YML:\n return parseYAMLFile(buffer, fileName, fileType);\n\n case SupportedFileType.PDF:\n return parsePDFFile(buffer, fileName, fileType);\n\n default:\n throw new InputError(`Unsupported file type: ${fileType}`);\n }\n}\n\n/**\n * File-like object interface matching what toFile() returns\n */\nexport interface FileObject {\n name: string;\n stream: Readable;\n buffer: Buffer;\n type: string;\n}\n\n/**\n * Convert Buffer to File-like object for upload\n */\nexport async function toFile(\n buffer: Buffer,\n filename: string,\n options?: { type?: string },\n): Promise<FileObject> {\n const stream = Readable.from(buffer);\n\n return {\n name: filename,\n stream,\n buffer,\n type: options?.type || 'application/octet-stream',\n };\n}\n"],"names":["InputError","yaml","pdfjsLib","SupportedFileType","stream","Readable"],"mappings":"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"}
|