diff --git a/Dockerfile b/Dockerfile index 3e77d9d..54e10bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM node:22.16.0-alpine3.22 AS base -# Install bash & curl for entrypoint script compatibility -RUN apk add --no-cache bash curl +# Install bash & curl for entrypoint script compatibility, graphicsmagick for pdf2pic, and vips-dev & build-base for sharp +RUN apk add --no-cache bash curl graphicsmagick vips-dev build-base # All deps stage FROM base AS deps diff --git a/admin/app/controllers/rag_controller.ts b/admin/app/controllers/rag_controller.ts new file mode 100644 index 0000000..c11a45c --- /dev/null +++ b/admin/app/controllers/rag_controller.ts @@ -0,0 +1,18 @@ +import { cuid } from '@adonisjs/core/helpers' +import type { HttpContext } from '@adonisjs/core/http' +import app from '@adonisjs/core/services/app' + +export default class RagsController { + public async upload({ request, response }: HttpContext) { + const uploadedFile = request.file('file') + if (!uploadedFile) { + return response.status(400).json({ error: 'No file uploaded' }) + } + + const fileName = `${cuid()}.${uploadedFile.extname}` + + await uploadedFile.move(app.makePath('storage/uploads'), { + name: fileName, + }) + } +} diff --git a/admin/app/models/kv_store.ts b/admin/app/models/kv_store.ts new file mode 100644 index 0000000..3c5165f --- /dev/null +++ b/admin/app/models/kv_store.ts @@ -0,0 +1,46 @@ +import { DateTime } from 'luxon' +import { BaseModel, column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm' +import type { KVStoreKey, KVStoreValue } from '../../types/kv_store.js' + +/** + * Generic key-value store model for storing various settings + * that don't necessitate their own dedicated models. + */ +export default class KVStore extends BaseModel { + static namingStrategy = new SnakeCaseNamingStrategy() + + @column({ isPrimary: true }) + declare id: number + + @column() + declare key: KVStoreKey + + @column() + declare value: KVStoreValue + + @column.dateTime({ autoCreate: true }) + declare created_at: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updated_at: DateTime + + /** + * Get a setting value by key + */ + static async getValue(key: KVStoreKey): Promise { + const setting = await this.findBy('key', key) + return setting?.value ?? null + } + + /** + * Set a setting value by key (creates if not exists) + */ + static async setValue(key: KVStoreKey, value: KVStoreValue): Promise { + const setting = await this.firstOrCreate({ key }, { key, value }) + if (setting.value !== value) { + setting.value = value + await setting.save() + } + return setting + } +} diff --git a/admin/app/services/benchmark_service.ts b/admin/app/services/benchmark_service.ts index d62ad5d..b7ac41c 100644 --- a/admin/app/services/benchmark_service.ts +++ b/admin/app/services/benchmark_service.ts @@ -426,21 +426,10 @@ export class BenchmarkService { } // Check if the benchmark model is available, pull if not - const modelsResponse = await axios.get(`${ollamaAPIURL}/api/tags`) - const models = modelsResponse.data.models || [] - const hasModel = models.some((m: any) => m.name === AI_BENCHMARK_MODEL || m.name.startsWith(AI_BENCHMARK_MODEL.split(':')[0])) - - if (!hasModel) { - this._updateStatus('downloading_ai_model', `Downloading AI benchmark model (${AI_BENCHMARK_MODEL})... This may take a few minutes on first run.`) - logger.info(`[BenchmarkService] Model ${AI_BENCHMARK_MODEL} not found, downloading...`) - - try { - // Model pull can take several minutes, use longer timeout - await axios.post(`${ollamaAPIURL}/api/pull`, { name: AI_BENCHMARK_MODEL }, { timeout: 600000 }) - logger.info(`[BenchmarkService] Model ${AI_BENCHMARK_MODEL} downloaded successfully`) - } catch (pullError) { - throw new Error(`Failed to download AI benchmark model (${AI_BENCHMARK_MODEL}): ${pullError.message}`) - } + const openWebUIService = new (await import('./openwebui_service.js')).OpenWebUIService(this.dockerService) + const modelResponse = await openWebUIService.downloadModelSync(AI_BENCHMARK_MODEL) + if (!modelResponse.success) { + throw new Error(`Model does not exist and failed to download: ${modelResponse.message}`) } // Run inference benchmark diff --git a/admin/app/services/docker_service.ts b/admin/app/services/docker_service.ts index ade5e15..d2e34ec 100644 --- a/admin/app/services/docker_service.ts +++ b/admin/app/services/docker_service.ts @@ -13,6 +13,7 @@ export class DockerService { private activeInstallations: Set = new Set() public static KIWIX_SERVICE_NAME = 'nomad_kiwix_serve' public static OLLAMA_SERVICE_NAME = 'nomad_ollama' + public static QDRANT_SERVICE_NAME = 'nomad_qdrant' public static OPEN_WEBUI_SERVICE_NAME = 'nomad_open_webui' public static CYBERCHEF_SERVICE_NAME = 'nomad_cyberchef' public static FLATNOTES_SERVICE_NAME = 'nomad_flatnotes' diff --git a/admin/app/services/openwebui_service.ts b/admin/app/services/openwebui_service.ts index 3ae0e37..3acf299 100644 --- a/admin/app/services/openwebui_service.ts +++ b/admin/app/services/openwebui_service.ts @@ -8,6 +8,9 @@ import path from 'node:path' import { PassThrough } from 'node:stream' import { DownloadModelJob } from '#jobs/download_model_job' import { FALLBACK_RECOMMENDED_OLLAMA_MODELS } from '../../constants/ollama.js' +import { chromium } from 'playwright' +import KVStore from '#models/kv_store' +import { getFile } from '../utils/fs.js' const NOMAD_MODELS_API_BASE_URL = 'https://api.projectnomad.us/api/v1/ollama/models' const MODELS_CACHE_FILE = path.join(process.cwd(), 'storage', 'ollama-models-cache.json') @@ -15,6 +18,10 @@ const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000 // 24 hours @inject() export class OpenWebUIService { + public static NOMAD_KNOWLEDGE_BASE_NAME = 'nomad-knowledge-base' + public static NOMAD_KNOWLEDGE_BASE_DESCRIP = + 'Knowledge base managed by Project NOMAD, used to enhance LLM responses with up-to-date information. Do not delete.' + constructor(private dockerService: DockerService) {} /** We need to call this in the DownloadModelJob, so it can't be private, @@ -200,6 +207,45 @@ export class OpenWebUIService { }) } + /** + * Synchronous version of model download (waits for completion). Should only be used for + * small models or in contexts where a background job is incompatible. + * @param model Model name to download + * @returns Success status and message + */ + async downloadModelSync(model: string): Promise<{ success: boolean; message: string }> { + try { + // See if model is already installed + const installedModels = await this.getInstalledModels() + if (installedModels && installedModels.some((m) => m.name === model)) { + logger.info(`[OpenWebUIService] Model "${model}" is already installed.`) + return { success: true, message: 'Model is already installed.' } + } + + const ollamAPIURL = await this.dockerService.getServiceURL(DockerService.OLLAMA_SERVICE_NAME) + if (!ollamAPIURL) { + logger.warn('[OpenWebUIService] Ollama service is not running. Cannot download model.') + return { + success: false, + message: 'Ollama is not running. Please start Ollama and try again.', + } + } + + // 10 minutes timeout for large model downloads + await axios.post(`${ollamAPIURL}/api/pull`, { name: model }, { timeout: 600000 }) + + logger.info(`[OpenWebUIService] Model "${model}" downloaded via API.`) + return { success: true, message: 'Model downloaded successfully.' } + } catch (error) { + logger.error( + `[OpenWebUIService] Failed to download model "${model}": ${ + error instanceof Error ? error.message : error + }` + ) + return { success: false, message: 'Failed to download model.' } + } + } + async deleteModel(model: string): Promise<{ success: boolean; message: string }> { return new Promise((resolve) => { try { @@ -529,6 +575,163 @@ export class OpenWebUIService { }) } + async getOrCreateKnowledgeBase(): Promise { + try { + // See if we already have the knowledge base ID stored + const existing = await KVStore.getValue('open_webui_knowledge_id') + if (existing) { + return existing as string + } + + // Create a new knowledge base via Open WebUI API + const tokenData = await this.getOpenWebUIToken() + if (!tokenData) { + logger.warn( + '[OpenWebUIService] Cannot get or create knowledge base because Open WebUI token is unavailable.' + ) + return null + } + + const response = await axios.post( + `${tokenData.url}/api/v1/knowledge/create`, + { + name: OpenWebUIService.NOMAD_KNOWLEDGE_BASE_NAME, + description: OpenWebUIService.NOMAD_KNOWLEDGE_BASE_DESCRIP, + }, + { + headers: { + Authorization: `Bearer ${tokenData.token}`, + }, + } + ) + + if (response.data && response.data.id) { + await KVStore.setValue('open_webui_knowledge_id', response.data.id) + return response.data.id + } + + logger.error( + `[OpenWebUIService] Invalid response when creating knowledge base: ${JSON.stringify( + response.data + )}` + ) + return null + } catch (error) { + logger.error( + `[OpenWebUIService] Failed to get or create knowledge base: ${ + error instanceof Error ? error.message : error + }` + ) + return null + } + } + + async uploadFileToKnowledgeBase(filepath: string): Promise { + try { + const knowledgeBaseId = await this.getOrCreateKnowledgeBase() + if (!knowledgeBaseId) { + logger.warn( + '[OpenWebUIService] Cannot upload file because knowledge base ID is unavailable and could not be created.' + ) + return false + } + + const tokenData = await this.getOpenWebUIToken() + if (!tokenData) { + logger.warn( + '[OpenWebUIService] Cannot upload file because Open WebUI token is unavailable.' + ) + return false + } + + const fileStream = await getFile(filepath, 'stream') + if (!fileStream) { + logger.warn( + `[OpenWebUIService] Cannot upload file because it could not be read: ${filepath}` + ) + return false + } + + const formData = new FormData() + formData.append('file', fileStream) + + const uploadRes = await axios.post( + `${tokenData.url}/api/v1/files/`, // Trailing slash seems to be required by OWUI + formData, + { + headers: { + 'Authorization': `Bearer ${tokenData.token}`, + 'Content-Type': 'multipart/form-data', + 'Accept': 'application/json', + }, + } + ) + + if (!uploadRes.data || !uploadRes.data.id) { + logger.error( + `[OpenWebUIService] Invalid response when uploading file: ${JSON.stringify( + uploadRes.data + )}` + ) + return false + } + + const fileId = uploadRes.data.id + + // Now associate the uploaded file with the knowledge base + const associateRes = await axios.post( + `${tokenData.url}/api/v1/knowledge/${knowledgeBaseId}/file/add`, + { + file_id: fileId, + }, + { + headers: { + Authorization: `Bearer ${tokenData.token}`, + }, + } + ) + + } catch (error) { + logger.error( + `[OpenWebUIService] Failed to upload file to knowledge base: ${ + error instanceof Error ? error.message : error + }` + ) + return false + } + } + + private async getOpenWebUIToken(): Promise<{ token: string; url: string } | null> { + try { + const openWebUIURL = await this.dockerService.getServiceURL( + DockerService.OPEN_WEBUI_SERVICE_NAME + ) + if (!openWebUIURL) { + logger.warn('[OpenWebUIService] Open WebUI service is not running. Cannot retrieve token.') + return null + } + const browser = await chromium.launch({ headless: true }) + const context = await browser.newContext() + const page = await context.newPage() + + await page.goto(openWebUIURL) + await page.waitForLoadState('networkidle') + + const cookies = await context.cookies() + const tokenCookie = cookies.find((cookie) => cookie.name === 'token') + await browser.close() + + return tokenCookie ? { token: tokenCookie.value, url: openWebUIURL } : null + } catch (error) { + logger.error( + `[OpenWebUIService] Failed to retrieve Open WebUI token: ${ + error instanceof Error ? error.message : error + }` + ) + return null + } + } + private async retrieveAndRefreshModels( sort?: 'pulls' | 'name' ): Promise { diff --git a/admin/app/services/rag_service.ts b/admin/app/services/rag_service.ts new file mode 100644 index 0000000..31fbb12 --- /dev/null +++ b/admin/app/services/rag_service.ts @@ -0,0 +1,262 @@ +import { Ollama } from 'ollama' +import { QdrantClient } from '@qdrant/js-client-rest' +import { DockerService } from './docker_service.js' +import { inject } from '@adonisjs/core' +import logger from '@adonisjs/core/services/logger' +import { chunk } from 'llm-chunk' +import { OpenWebUIService } from './openwebui_service.js' +import sharp from 'sharp' +import { determineFileType, getFile } from '../utils/fs.js' +import { PDFParse } from 'pdf-parse' +import { createWorker } from 'tesseract.js' +import { fromBuffer } from 'pdf2pic' + +@inject() +export class RagService { + private qdrant: QdrantClient | null = null + private ollama: Ollama | null = null + private qdrantInitPromise: Promise | null = null + private ollamaInitPromise: Promise | null = null + public static CONTENT_COLLECTION_NAME = 'open-webui_knowledge' // This is the collection name OWUI uses for uploaded knowledge + public static EMBEDDING_MODEL = 'nomic-embed-text:v1.5' + public static EMBEDDING_DIMENSION = 768 // Nomic Embed Text v1.5 dimension is 768 + + constructor( + private dockerService: DockerService, + private openWebUIService: OpenWebUIService + ) {} + + private async _initializeQdrantClient() { + if (!this.qdrantInitPromise) { + this.qdrantInitPromise = (async () => { + const qdrantUrl = await this.dockerService.getServiceURL(DockerService.QDRANT_SERVICE_NAME) + if (!qdrantUrl) { + throw new Error('Qdrant service is not installed or running.') + } + this.qdrant = new QdrantClient({ url: `http://${qdrantUrl}` }) + })() + } + return this.qdrantInitPromise + } + + private async _initializeOllamaClient() { + if (!this.ollamaInitPromise) { + this.ollamaInitPromise = (async () => { + const ollamaUrl = await this.dockerService.getServiceURL(DockerService.OLLAMA_SERVICE_NAME) + if (!ollamaUrl) { + throw new Error('Ollama service is not installed or running.') + } + this.ollama = new Ollama({ host: `http://${ollamaUrl}` }) + })() + } + return this.ollamaInitPromise + } + + private async _ensureDependencies() { + if (!this.qdrant) { + await this._initializeQdrantClient() + } + if (!this.ollama) { + await this._initializeOllamaClient() + } + } + + private async _ensureCollection( + collectionName: string, + dimensions: number = RagService.EMBEDDING_DIMENSION + ) { + try { + await this._ensureDependencies() + const collections = await this.qdrant!.getCollections() + const collectionExists = collections.collections.some((col) => col.name === collectionName) + + if (!collectionExists) { + await this.qdrant!.createCollection(collectionName, { + vectors: { + size: dimensions, + distance: 'Cosine', + }, + }) + } + } catch (error) { + logger.error('Error ensuring Qdrant collection:', error) + throw error + } + } + + public async embedAndStoreText( + text: string, + metadata: Record = {} + ): Promise<{ chunks: number } | null> { + try { + await this._ensureCollection( + RagService.CONTENT_COLLECTION_NAME, + RagService.EMBEDDING_DIMENSION + ) + const initModelResponse = await this.openWebUIService.downloadModelSync( + RagService.EMBEDDING_MODEL + ) + if (!initModelResponse.success) { + throw new Error( + `${RagService.EMBEDDING_MODEL} does not exist and could not be downloaded: ${initModelResponse.message}` + ) + } + + const chunks = chunk(text, { + // These settings should provide a good balance between context and precision + minLength: 512, + maxLength: 1024, + overlap: 200, + }) + + if (!chunks || chunks.length === 0) { + throw new Error('No text chunks generated for embedding.') + } + + const embeddings: number[][] = [] + for (const chunkText of chunks) { + const response = await this.ollama!.embeddings({ + model: RagService.EMBEDDING_MODEL, + prompt: chunkText, + }) + + embeddings.push(response.embedding) + } + + const points = chunks.map((chunkText, index) => ({ + id: `${Date.now()}_${index}`, + vector: embeddings[index], + payload: { + ...metadata, + text: chunkText, + chunk_index: index, + }, + })) + + await this.qdrant!.upsert(RagService.CONTENT_COLLECTION_NAME, { points }) + + return { chunks: chunks.length } + } catch (error) { + logger.error('Error embedding text:', error) + return null + } + } + + /** + * Preprocess an image to enhance text extraction quality. + * Normalizes, grayscales, sharpens, and resizes the image to a manageable size. + * @param filebuffer Buffer of the image file + * @returns - Processed image buffer + */ + private async preprocessImage(filebuffer: Buffer): Promise { + return await sharp(filebuffer) + .grayscale() + .normalize() + .sharpen() + .resize({ width: 2000, fit: 'inside' }) + .toBuffer() + } + + /** + * If the original PDF has little to no extractable text, + * we can use this method to convert each page to an image for OCR processing. + * @param filebuffer - Buffer of the PDF file + * @returns - Array of image buffers, one per page + */ + private async convertPDFtoImages(filebuffer: Buffer): Promise { + const converted = await fromBuffer(filebuffer, { + quality: 50, + density: 200, + format: 'png', + }).bulk(-1, { + responseType: 'buffer', + }) + return converted.filter((res) => res.buffer).map((res) => res.buffer!) + } + + /** + * Extract text from a PDF file using pdf-parse. + * @param filebuffer - Buffer of the PDF file + * @returns - Extracted text + */ + private async extractPDFText(filebuffer: Buffer): Promise { + const parser = new PDFParse({ data: filebuffer }) + const data = await parser.getText() + await parser.destroy() + return data.text + } + + /** + * Extract text from a plain text file. + * @param filebuffer - Buffer of the text file + * @returns - Extracted text + */ + private async extractTXTText(filebuffer: Buffer): Promise { + return filebuffer.toString('utf-8') + } + + /** + * Extract text from an image file using Tesseract.js OCR. + * @param filebuffer - Buffer of the image file + * @returns - Extracted text + */ + private async extractImageText(filebuffer: Buffer): Promise { + const worker = await createWorker('eng') + const result = await worker.recognize(filebuffer) + await worker.terminate() + return result.data.text + } + + /** + * Main pipeline to process and embed an uploaded file into the RAG knowledge base. + * This includes text extraction, chunking, embedding, and storing in Qdrant. + */ + public async processAndEmbedFile( + filepath: string + ): Promise<{ success: boolean; message: string }> { + try { + const fileType = determineFileType(filepath) + if (fileType === 'unknown') { + return { success: false, message: 'Unsupported file type.' } + } + + const origFileBuffer = await getFile(filepath, 'buffer') + if (!origFileBuffer) { + return { success: false, message: 'Failed to read the uploaded file.' } + } + + let extractedText = '' + + if (fileType === 'image') { + const preprocessedBuffer = await this.preprocessImage(origFileBuffer) + extractedText = await this.extractImageText(preprocessedBuffer) + } else if (fileType === 'pdf') { + extractedText = await this.extractPDFText(origFileBuffer) + // Check if there was no extracted text or it was very minimal + if (!extractedText || extractedText.trim().length < 100) { + // Convert PDF pages to images for OCR + const imageBuffers = await this.convertPDFtoImages(origFileBuffer) + for (const imgBuffer of imageBuffers) { + const preprocessedImg = await this.preprocessImage(imgBuffer) + const pageText = await this.extractImageText(preprocessedImg) + extractedText += pageText + '\n' + } + } + } else { + extractedText = await this.extractTXTText(origFileBuffer) + } + + if (!extractedText || extractedText.trim().length === 0) { + return { success: false, message: 'No text could be extracted from the file.' } + } + + const embedResult = await this.embedAndStoreText(extractedText, {}) + + + return { success: true, message: 'File processed and embedded successfully.' } + } catch (error) { + logger.error('Error processing and embedding file:', error) + return { success: false, message: 'Error processing and embedding file.' } + } + } +} diff --git a/admin/app/utils/fs.ts b/admin/app/utils/fs.ts index c18ab39..4b0e295 100644 --- a/admin/app/utils/fs.ts +++ b/admin/app/utils/fs.ts @@ -1,5 +1,5 @@ import { mkdir, readdir, readFile, stat, unlink } from 'fs/promises' -import { join } from 'path' +import path, { join } from 'path' import { FileEntry } from '../../types/files.js' import { createReadStream } from 'fs' import { LSBlockDevice, NomadDiskInfoRaw } from '../../types/system.js' @@ -151,3 +151,16 @@ export function matchesDevice(fsPath: string, deviceName: string): boolean { return false } + +export function determineFileType(filename: string): 'image' | 'pdf' | 'text' | 'unknown' { + const ext = path.extname(filename).toLowerCase() + if (['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'].includes(ext)) { + return 'image' + } else if (ext === '.pdf') { + return 'pdf' + } else if (['.txt', '.md', '.docx', '.rtf'].includes(ext)) { + return 'text' + } else { + return 'unknown' + } +} diff --git a/admin/database/migrations/1769400000002_create_kv_store_table.ts b/admin/database/migrations/1769400000002_create_kv_store_table.ts new file mode 100644 index 0000000..8a5e0c0 --- /dev/null +++ b/admin/database/migrations/1769400000002_create_kv_store_table.ts @@ -0,0 +1,19 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'kv_store' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + table.string('key').unique().notNullable() + table.text('value').nullable() + table.timestamp('created_at') + table.timestamp('updated_at') + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} \ No newline at end of file diff --git a/admin/database/seeders/service_seeder.ts b/admin/database/seeders/service_seeder.ts index ddf8b01..e93c53f 100644 --- a/admin/database/seeders/service_seeder.ts +++ b/admin/database/seeders/service_seeder.ts @@ -3,17 +3,25 @@ import { DockerService } from '#services/docker_service' import { BaseSeeder } from '@adonisjs/lucid/seeders' import { ModelAttributes } from '@adonisjs/lucid/types/model' import env from '#start/env' +import { RagService } from '#services/rag_service' export default class ServiceSeeder extends BaseSeeder { // Use environment variable with fallback to production default - private static NOMAD_STORAGE_ABS_PATH = env.get('NOMAD_STORAGE_PATH', '/opt/project-nomad/storage') - private static DEFAULT_SERVICES: Omit, 'created_at' | 'updated_at' | 'metadata' | 'id'>[] = [ + private static NOMAD_STORAGE_ABS_PATH = env.get( + 'NOMAD_STORAGE_PATH', + '/opt/project-nomad/storage' + ) + private static DEFAULT_SERVICES: Omit< + ModelAttributes, + 'created_at' | 'updated_at' | 'metadata' | 'id' + >[] = [ { service_name: DockerService.KIWIX_SERVICE_NAME, friendly_name: 'Information Library', powered_by: 'Kiwix', display_order: 1, - description: 'Offline access to Wikipedia, medical references, how-to guides, and encyclopedias', + description: + 'Offline access to Wikipedia, medical references, how-to guides, and encyclopedias', icon: 'IconBooks', container_image: 'ghcr.io/kiwix/kiwix-serve:3.8.1', container_command: '*.zim --address=all', @@ -21,9 +29,9 @@ export default class ServiceSeeder extends BaseSeeder { HostConfig: { RestartPolicy: { Name: 'unless-stopped' }, Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/zim:/data`], - PortBindings: { '8080/tcp': [{ HostPort: '8090' }] } + PortBindings: { '8080/tcp': [{ HostPort: '8090' }] }, }, - ExposedPorts: { '8080/tcp': {} } + ExposedPorts: { '8080/tcp': {} }, }), ui_location: '8090', installed: false, @@ -31,6 +39,29 @@ export default class ServiceSeeder extends BaseSeeder { is_dependency_service: false, depends_on: null, }, + { + service_name: DockerService.QDRANT_SERVICE_NAME, + friendly_name: 'Qdrant Vector Database', + powered_by: null, + display_order: 100, // Dependency service, not shown directly + description: 'Vector database for storing and searching embeddings', + icon: 'IconRobot', + container_image: 'qdrant/qdrant:latest', + container_command: null, + container_config: JSON.stringify({ + HostConfig: { + RestartPolicy: { Name: 'unless-stopped' }, + Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/qdrant:/qdrant/storage`], + PortBindings: { '6333/tcp': [{ HostPort: '6333' }], '6334/tcp': [{ HostPort: '6334' }] }, + }, + ExposedPorts: { '6333/tcp': {}, '6334/tcp': {} }, + }), + ui_location: '6333', + installed: false, + installation_status: 'idle', + is_dependency_service: true, + depends_on: null, + }, { service_name: DockerService.OLLAMA_SERVICE_NAME, friendly_name: 'Ollama', @@ -44,15 +75,15 @@ export default class ServiceSeeder extends BaseSeeder { HostConfig: { RestartPolicy: { Name: 'unless-stopped' }, Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/ollama:/root/.ollama`], - PortBindings: { '11434/tcp': [{ HostPort: '11434' }] } + PortBindings: { '11434/tcp': [{ HostPort: '11434' }] }, }, - ExposedPorts: { '11434/tcp': {} } + ExposedPorts: { '11434/tcp': {} }, }), ui_location: null, installed: false, installation_status: 'idle', is_dependency_service: true, - depends_on: null, + depends_on: DockerService.QDRANT_SERVICE_NAME, }, { service_name: DockerService.OPEN_WEBUI_SERVICE_NAME, @@ -68,9 +99,17 @@ export default class ServiceSeeder extends BaseSeeder { RestartPolicy: { Name: 'unless-stopped' }, NetworkMode: 'host', Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/open-webui:/app/backend/data`], - PortBindings: { '8080/tcp': [{ HostPort: '3000' }] } + PortBindings: { '8080/tcp': [{ HostPort: '3000' }] }, }, - Env: ['WEBUI_AUTH=False', 'PORT=3000', 'OLLAMA_BASE_URL=http://127.0.0.1:11434'] + Env: [ + 'WEBUI_AUTH=False', + 'PORT=3000', + 'OLLAMA_BASE_URL=http://127.0.0.1:11434', + 'VECTOR_DB=qdrant', + 'QDRANT_URI=http://127.0.0.1:6333', + 'RAG_EMBEDDING_ENGINE=ollama', + `RAG_EMBEDDING_MODEL=${RagService.EMBEDDING_MODEL}`, + ], }), ui_location: '3000', installed: false, @@ -90,9 +129,9 @@ export default class ServiceSeeder extends BaseSeeder { container_config: JSON.stringify({ HostConfig: { RestartPolicy: { Name: 'unless-stopped' }, - PortBindings: { '80/tcp': [{ HostPort: '8100' }] } + PortBindings: { '80/tcp': [{ HostPort: '8100' }] }, }, - ExposedPorts: { '80/tcp': {} } + ExposedPorts: { '80/tcp': {} }, }), ui_location: '8100', installed: false, @@ -113,10 +152,10 @@ export default class ServiceSeeder extends BaseSeeder { HostConfig: { RestartPolicy: { Name: 'unless-stopped' }, PortBindings: { '8080/tcp': [{ HostPort: '8200' }] }, - Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/flatnotes:/data`] + Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/flatnotes:/data`], }, ExposedPorts: { '8080/tcp': {} }, - Env: ['FLATNOTES_AUTH_TYPE=none'] + Env: ['FLATNOTES_AUTH_TYPE=none'], }), ui_location: '8200', installed: false, @@ -137,7 +176,7 @@ export default class ServiceSeeder extends BaseSeeder { HostConfig: { RestartPolicy: { Name: 'unless-stopped' }, PortBindings: { '8080/tcp': [{ HostPort: '8300' }] }, - Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/kolibri:/root/.kolibri`] + Binds: [`${ServiceSeeder.NOMAD_STORAGE_ABS_PATH}/kolibri:/root/.kolibri`], }, ExposedPorts: { '8080/tcp': {} }, }), @@ -146,15 +185,17 @@ export default class ServiceSeeder extends BaseSeeder { installation_status: 'idle', is_dependency_service: false, depends_on: null, - } + }, ] async run() { const existingServices = await Service.query().select('service_name') - const existingServiceNames = new Set(existingServices.map(service => service.service_name)) + const existingServiceNames = new Set(existingServices.map((service) => service.service_name)) - const newServices = ServiceSeeder.DEFAULT_SERVICES.filter(service => !existingServiceNames.has(service.service_name)) + const newServices = ServiceSeeder.DEFAULT_SERVICES.filter( + (service) => !existingServiceNames.has(service.service_name) + ) await Service.createMany([...newServices]) } -} \ No newline at end of file +} diff --git a/admin/inertia/components/file-uploader/index.css b/admin/inertia/components/file-uploader/index.css new file mode 100644 index 0000000..66b1dea --- /dev/null +++ b/admin/inertia/components/file-uploader/index.css @@ -0,0 +1,3 @@ +.uppy-size--md .uppy-Dashboard-AddFiles-title { + font-size: 1.15rem !important; +} \ No newline at end of file diff --git a/admin/inertia/components/file-uploader/index.tsx b/admin/inertia/components/file-uploader/index.tsx new file mode 100644 index 0000000..68ff261 --- /dev/null +++ b/admin/inertia/components/file-uploader/index.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react' +import Uppy from '@uppy/core' +import '@uppy/core/css/style.min.css' +import '@uppy/dashboard/css/style.min.css' +import { useUppyEvent } from '@uppy/react' +import Dashboard from '@uppy/react/dashboard' +import classNames from 'classnames' +import './index.css' // Custom styles for the uploader + +interface FileUploaderProps { + minFiles?: number // minimum number of files required + maxFiles?: number + maxFileSize?: number // in bytes, e.g., 10485760 for 10MB + fileTypes?: string[] // e.g., ['image/*', 'application/pdf'] + disabled?: boolean + onUpload: (files: FileList) => void + className?: string +} + +/** + * A drag-and-drop (or click) file upload area with customizations for + * multiple and maximum numbers of files. + */ +const FileUploader: React.FC = ({ + minFiles = 0, + maxFiles = 1, + maxFileSize = 10485760, // default to 10MB + fileTypes, + disabled = false, + onUpload, + className, +}) => { + const [uppy] = useState(() => { + const uppy = new Uppy({ + debug: true, + restrictions: { + maxFileSize: maxFileSize, + minNumberOfFiles: minFiles, + maxNumberOfFiles: maxFiles, + allowedFileTypes: fileTypes || undefined, + }, + }) + return uppy + }) + + useUppyEvent(uppy, 'state-update', (_, newState) => { + const stateFiles = Object.values(newState.files) + + const dataTransfer = new DataTransfer() + stateFiles.forEach((file) => { + if (file.data) { + if (file.data instanceof File) { + dataTransfer.items.add(file.data) + } else if (file.data instanceof Blob) { + const newFile = new File( + [file.data], + file.name || `${crypto.randomUUID()}.${file.extension}`, + { + type: file.type, + lastModified: new Date().getTime(), + } + ) + dataTransfer.items.add(newFile) + } + } + }) + + const fileList = dataTransfer.files + onUpload(fileList) // Always send new file list even if empty + }) + + return ( + + ) +} + +export default FileUploader diff --git a/admin/inertia/pages/home.tsx b/admin/inertia/pages/home.tsx index b8c53be..1a8bfa6 100644 --- a/admin/inertia/pages/home.tsx +++ b/admin/inertia/pages/home.tsx @@ -1,5 +1,6 @@ import { IconBolt, + IconBrain, IconHelp, IconMapRoute, IconPlus, @@ -80,6 +81,17 @@ interface DashboardItem { poweredBy: string | null } +const KNOWLEDGE_BASE_ITEM: DashboardItem = { + label: 'Knowledge Base', + to: '/knowledge-base', + target: '', + description: 'Upload documents to your personal knowledge base for AI access', + icon: , + installed: true, + displayOrder: 5, + poweredBy: null, +} + export default function Home(props: { system: { services: ServiceSlim[] @@ -114,6 +126,9 @@ export default function Home(props: { // Add system items items.push(...SYSTEM_ITEMS) + if (props.system.services.find((s) => s.service_name === 'nomad_open_webui' && s.installed)) { + items.push(KNOWLEDGE_BASE_ITEM) + } // Sort all items by display order items.sort((a, b) => a.displayOrder - b.displayOrder) @@ -130,9 +145,7 @@ export default function Home(props: { >
{item.icon}

{item.label}

- {item.poweredBy && ( -

Powered by {item.poweredBy}

- )} + {item.poweredBy &&

Powered by {item.poweredBy}

}

{item.description}

diff --git a/admin/inertia/pages/knowledge-base.tsx b/admin/inertia/pages/knowledge-base.tsx new file mode 100644 index 0000000..e77fdb8 --- /dev/null +++ b/admin/inertia/pages/knowledge-base.tsx @@ -0,0 +1,97 @@ +import { Head } from '@inertiajs/react' +import { useState } from 'react' +import FileUploader from '~/components/file-uploader' +import StyledButton from '~/components/StyledButton' +import AppLayout from '~/layouts/AppLayout' + +export default function KnowledgeBase() { + const [loading, setLoading] = useState(false) + const [files, setFiles] = useState([]) + + return ( + + +
+
+
+ { + setLoading(true) + setFiles(Array.from(files)) + setLoading(false) + }} + /> +
+ {}} + disabled={files.length === 0 || loading} + loading={loading} + > + Upload + +
+
+
+

+ Why upload documents to your Knowledge Base? +

+
+
+
+ 1 +
+
+

+ AI Assistant Knowledge Base Integration +

+

+ When you upload documents to your Knowledge Base, NOMAD processes and embeds the + content, making it directly accessible to the AI Assistant. This allows the AI + Assistant to reference your specific documents during conversations, providing + more accurate and personalized responses based on your uploaded data. +

+
+
+
+
+ 2 +
+
+

+ Enhanced Document Processing with OCR +

+

+ NOMAD includes built-in Optical Character Recognition (OCR) capabilities, + allowing it to extract text from image-based documents such as scanned PDFs or + photos. This means that even if your documents are not in a standard text + format, NOMAD can still process and embed their content for AI access. +

+
+
+
+
+ 3 +
+
+

+ Information Library Integration +

+

+ NOMAD will automatically discover and extract any content you save to your + Information Library (if installed), making it instantly available to the AI + Assistant without any extra steps. +

+
+
+
+
+
+
+
+ ) +} diff --git a/admin/package-lock.json b/admin/package-lock.json index b8b6e23..4b2a296 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -25,11 +25,15 @@ "@inertiajs/react": "^2.0.13", "@markdoc/markdoc": "^0.5.2", "@protomaps/basemaps": "^5.7.0", + "@qdrant/js-client-rest": "^1.16.2", "@tabler/icons-react": "^3.34.0", "@tailwindcss/vite": "^4.1.10", "@tanstack/react-query": "^5.81.5", "@tanstack/react-query-devtools": "^5.83.0", "@tanstack/react-virtual": "^3.13.12", + "@uppy/core": "^5.2.0", + "@uppy/dashboard": "^5.1.0", + "@uppy/react": "^5.1.1", "@vinejs/vine": "^3.0.1", "@vitejs/plugin-react": "^4.6.0", "autoprefixer": "^10.4.21", @@ -39,10 +43,15 @@ "dockerode": "^4.0.7", "edge.js": "^6.2.1", "fast-xml-parser": "^5.2.5", + "llm-chunk": "^0.0.1", "luxon": "^3.6.1", "maplibre-gl": "^4.7.1", "mysql2": "^3.14.1", + "ollama": "^0.6.3", + "pdf-parse": "^2.4.5", + "pdf2pic": "^3.2.0", "pino-pretty": "^13.0.0", + "playwright": "^1.58.0", "pmtiles": "^4.3.0", "postcss": "^8.5.6", "react": "^19.1.0", @@ -50,9 +59,11 @@ "react-dom": "^19.1.0", "react-map-gl": "^8.1.0", "reflect-metadata": "^0.2.2", + "sharp": "^0.34.5", "systeminformation": "^5.27.14", "tailwindcss": "^4.1.10", "tar": "^7.5.6", + "tesseract.js": "^7.0.0", "url-join": "^5.0.0", "usehooks-ts": "^3.1.1", "yaml": "^2.8.0" @@ -1277,6 +1288,16 @@ "node": ">=12" } }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -2074,6 +2095,471 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@inertiajs/core": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.0.13.tgz", @@ -2527,6 +3013,190 @@ "win32" ] }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "license": "MIT", + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2873,6 +3543,33 @@ "generate_style": "src/cli.ts" } }, + "node_modules/@qdrant/js-client-rest": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.16.2.tgz", + "integrity": "sha512-Zm4wEZURrZ24a+Hmm4l1QQYjiz975Ep3vF0yzWR7ICGcxittNz47YK2iBOk8kb8qseCu8pg7WmO1HOIsO8alvw==", + "license": "Apache-2.0", + "dependencies": { + "@qdrant/openapi-typescript-fetch": "1.2.6", + "undici": "^6.0.0" + }, + "engines": { + "node": ">=18.17.0", + "pnpm": ">=8" + }, + "peerDependencies": { + "typescript": ">=4.7" + } + }, + "node_modules/@qdrant/openapi-typescript-fetch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@qdrant/openapi-typescript-fetch/-/openapi-typescript-fetch-1.2.6.tgz", + "integrity": "sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8" + } + }, "node_modules/@react-aria/focus": { "version": "3.20.5", "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.5.tgz", @@ -3997,6 +4694,12 @@ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, + "node_modules/@transloadit/prettier-bytes": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.3.5.tgz", + "integrity": "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA==", + "license": "MIT" + }, "node_modules/@ts-morph/common": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.24.0.tgz", @@ -4541,6 +5244,205 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@uppy/components": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@uppy/components/-/components-1.1.0.tgz", + "integrity": "sha512-omiNBzJn49FQznkSwOIGn3TKz+3r4T+y8sxWIBDMO6De2genzywRk1drAWO9GbSAF3htlVuvamNojQ2pSLeh3w==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "dequal": "^2.0.3", + "preact": "^10.5.13", + "pretty-bytes": "^6.1.1" + }, + "peerDependencies": { + "@uppy/core": "^5.1.1", + "@uppy/image-editor": "^4.0.2", + "@uppy/screen-capture": "^5.0.1", + "@uppy/webcam": "^5.0.2" + }, + "peerDependenciesMeta": { + "@uppy/image-editor": { + "optional": true + }, + "@uppy/screen-capture": { + "optional": true + }, + "@uppy/webcam": { + "optional": true + } + } + }, + "node_modules/@uppy/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@uppy/core/-/core-5.2.0.tgz", + "integrity": "sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^0.3.4", + "@uppy/store-default": "^5.0.0", + "@uppy/utils": "^7.1.4", + "lodash": "^4.17.21", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^5.0.9", + "preact": "^10.5.13" + } + }, + "node_modules/@uppy/core/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@uppy/dashboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-5.1.0.tgz", + "integrity": "sha512-TCuDbsAWokPO2LzubDoXyBskY/DHFB9tyDeHJe1FGBHrm0sY2zd+yEW7c4HNMDiIYCs/AdFJWAjQosCT07rD6Q==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^0.3.4", + "@uppy/provider-views": "^5.2.0", + "@uppy/thumbnail-generator": "^5.1.0", + "@uppy/utils": "^7.1.4", + "classnames": "^2.2.6", + "lodash": "^4.17.21", + "nanoid": "^5.0.9", + "preact": "^10.5.13", + "shallow-equal": "^3.0.0" + }, + "peerDependencies": { + "@uppy/core": "^5.2.0" + } + }, + "node_modules/@uppy/dashboard/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@uppy/provider-views": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@uppy/provider-views/-/provider-views-5.2.2.tgz", + "integrity": "sha512-NAazIJ5sjrAc6++CeJ/u9dB5gDaaAOLHrYeEmWs/HqLlftlIinRZOybnyzJRXwI8jWI/FK5moluzt2HXu6dPQQ==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^7.1.5", + "classnames": "^2.2.6", + "lodash": "^4.17.21", + "nanoid": "^5.0.9", + "p-queue": "^8.0.0", + "preact": "^10.5.13" + }, + "peerDependencies": { + "@uppy/core": "^5.2.0" + } + }, + "node_modules/@uppy/provider-views/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@uppy/react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@uppy/react/-/react-5.1.1.tgz", + "integrity": "sha512-VP9c5YMpAhDFAI5Z+mvvwqXXCg3ximcYSurG/38pbRLnQpvNr8ldtQ7g+2t5awRhtY8uWaI2QT6qdhMGKWFeDQ==", + "license": "MIT", + "dependencies": { + "@uppy/components": "^1.1.0", + "preact": "^10.5.13", + "use-sync-external-store": "^1.3.0" + }, + "peerDependencies": { + "@uppy/core": "^5.1.1", + "@uppy/dashboard": "^5.0.3", + "@uppy/screen-capture": "^5.0.1", + "@uppy/status-bar": "^5.0.2", + "@uppy/webcam": "^5.0.2", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@uppy/dashboard": { + "optional": true + }, + "@uppy/screen-capture": { + "optional": true + }, + "@uppy/status-bar": { + "optional": true + }, + "@uppy/webcam": { + "optional": true + } + } + }, + "node_modules/@uppy/store-default": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-5.0.0.tgz", + "integrity": "sha512-hQtCSQ1yGiaval/wVYUWquYGDJ+bpQ7e4FhUUAsRQz1x1K+o7NBtjfp63O9I4Ks1WRoKunpkarZ+as09l02cPw==", + "license": "MIT" + }, + "node_modules/@uppy/thumbnail-generator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@uppy/thumbnail-generator/-/thumbnail-generator-5.1.0.tgz", + "integrity": "sha512-QAKJHHkMrD/30GOyUb5U9HyJ7Ie3jiMLp4pVdw27PoA4pNV7fDQz0tyDeRPj2H+BWPEB1NsTSSfHI2pjHNI+OQ==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^7.1.4", + "exifr": "^7.0.0" + }, + "peerDependencies": { + "@uppy/core": "^5.2.0" + } + }, + "node_modules/@uppy/utils": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-7.1.5.tgz", + "integrity": "sha512-Vz4WGTjef6WebECGur4clWjpkET4o3bdvPMj1m2sD5cL+dTt69m+FIE5h5JD3HBMLEPTXPVkrXGMIFcbOYC12Q==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "preact": "^10.5.13" + } + }, "node_modules/@vavite/multibuild": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@vavite/multibuild/-/multibuild-5.1.0.tgz", @@ -4841,6 +5743,18 @@ "node": ">=0.10.0" } }, + "node_modules/array-parallel": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz", + "integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==", + "license": "MIT" + }, + "node_modules/array-series": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz", + "integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==", + "license": "MIT" + }, "node_modules/as-table": { "version": "1.0.55", "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", @@ -5057,6 +5971,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -5822,7 +6742,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5975,6 +6894,15 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5986,9 +6914,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -6723,6 +7651,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/execa": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", @@ -6750,6 +7684,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exifr": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/exifr/-/exifr-7.1.3.tgz", + "integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==", + "license": "MIT" + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -7506,6 +8446,31 @@ "node": ">= 4" } }, + "node_modules/gm": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/gm/-/gm-1.25.1.tgz", + "integrity": "sha512-jgcs2vKir9hFogGhXIfs0ODhJTfIrbECCehg38tqFgHm8zqXx7kAJyCYAFK4jTjx71AxrkFtkJBawbAxYUPX9A==", + "deprecated": "The gm module has been sunset. Please migrate to an alternative. https://github.com/aheckmann/gm?tab=readme-ov-file#2025-02-24-this-project-is-not-maintained", + "license": "MIT", + "dependencies": { + "array-parallel": "~0.1.3", + "array-series": "~0.1.5", + "cross-spawn": "^7.0.5", + "debug": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gm/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7685,6 +8650,12 @@ "node": ">=0.10.0" } }, + "node_modules/idb-keyval": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "license": "Apache-2.0" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7979,11 +8950,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, "license": "ISC" }, "node_modules/isobject": { @@ -8509,6 +9485,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/llm-chunk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/llm-chunk/-/llm-chunk-0.0.1.tgz", + "integrity": "sha512-n9fHgsSiJb7vXZiC5c4XV6rme+tC7WX/cWH6EJvPPmMOMwOZ9xdg/U9LY5Qhmixd3K1PdRB0FVOdzoJF2HUZbg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", @@ -8872,6 +9854,15 @@ "node": ">= 0.6" } }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "license": "ISC", + "dependencies": { + "wildcard": "^1.1.0" + } + }, "node_modules/mime-types": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", @@ -9079,6 +10070,12 @@ "node": ">=12" } }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", + "license": "MIT" + }, "node_modules/nan": { "version": "2.22.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", @@ -9144,6 +10141,26 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp-build-optional-packages": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", @@ -9261,6 +10278,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ollama": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.6.3.tgz", + "integrity": "sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==", + "license": "MIT", + "dependencies": { + "whatwg-fetch": "^3.6.20" + } + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -9306,6 +10332,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9399,11 +10434,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-timeout": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=14.16" @@ -9509,7 +10559,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -9557,6 +10606,54 @@ "pbf": "bin/pbf" } }, + "node_modules/pdf-parse": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", + "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/canvas": "0.1.80", + "pdfjs-dist": "5.4.296" + }, + "bin": { + "pdf-parse": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.16.0 <21 || >=22.3.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/mehmet-kozan" + } + }, + "node_modules/pdf2pic": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pdf2pic/-/pdf2pic-3.2.0.tgz", + "integrity": "sha512-p0bp+Mp4iJy2hqSCLvJ521rDaZkzBvDFT9O9Y0BUID3I04/eDaebAFM5t8hoWeo2BCf42cDijLCGJWTOtkJVpA==", + "license": "MIT", + "dependencies": { + "gm": "^1.25.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "paypal", + "url": "https://www.paypal.me/yakovmeister" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, "node_modules/pg-connection-string": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", @@ -9691,6 +10788,50 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/playwright": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.0.tgz", + "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0.tgz", + "integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -9749,6 +10890,16 @@ "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", "license": "ISC" }, + "node_modules/preact": { + "version": "10.28.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", + "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -9827,6 +10978,18 @@ "uglify-js": "^3.19.2" } }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -10398,6 +11561,12 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -10652,9 +11821,9 @@ "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10768,11 +11937,60 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -10785,7 +12003,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11525,6 +12742,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tesseract.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-7.0.0.tgz", + "integrity": "sha512-exPBkd+z+wM1BuMkx/Bjv43OeLBxhL5kKWsz/9JY+DXcXdiBjiAch0V49QR3oAJqCaL5qURE0vx9Eo+G5YE7mA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^7.0.0", + "wasm-feature-detect": "^1.8.0", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-7.0.0.tgz", + "integrity": "sha512-WnNH518NzmbSq9zgTPeoF8c+xmilS8rFIl1YKbk/ptuuc7p6cLNELNuPAzcmsYw450ca6bLa8j3t0VAtq435Vw==", + "license": "Apache-2.0" + }, "node_modules/thread-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", @@ -11636,6 +12877,12 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/truncatise": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/truncatise/-/truncatise-0.0.8.tgz", @@ -11787,7 +13034,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11872,6 +13118,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -12130,11 +13385,38 @@ "pbf": "^3.2.1" } }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -12146,6 +13428,12 @@ "node": ">= 8" } }, + "node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", + "license": "MIT" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12415,6 +13703,15 @@ "engines": { "node": ">= 0.6" } + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } } } } diff --git a/admin/package.json b/admin/package.json index 3f29eaf..580605a 100644 --- a/admin/package.json +++ b/admin/package.json @@ -76,11 +76,15 @@ "@inertiajs/react": "^2.0.13", "@markdoc/markdoc": "^0.5.2", "@protomaps/basemaps": "^5.7.0", + "@qdrant/js-client-rest": "^1.16.2", "@tabler/icons-react": "^3.34.0", "@tailwindcss/vite": "^4.1.10", "@tanstack/react-query": "^5.81.5", "@tanstack/react-query-devtools": "^5.83.0", "@tanstack/react-virtual": "^3.13.12", + "@uppy/core": "^5.2.0", + "@uppy/dashboard": "^5.1.0", + "@uppy/react": "^5.1.1", "@vinejs/vine": "^3.0.1", "@vitejs/plugin-react": "^4.6.0", "autoprefixer": "^10.4.21", @@ -90,10 +94,15 @@ "dockerode": "^4.0.7", "edge.js": "^6.2.1", "fast-xml-parser": "^5.2.5", + "llm-chunk": "^0.0.1", "luxon": "^3.6.1", "maplibre-gl": "^4.7.1", "mysql2": "^3.14.1", + "ollama": "^0.6.3", + "pdf-parse": "^2.4.5", + "pdf2pic": "^3.2.0", "pino-pretty": "^13.0.0", + "playwright": "^1.58.0", "pmtiles": "^4.3.0", "postcss": "^8.5.6", "react": "^19.1.0", @@ -101,9 +110,11 @@ "react-dom": "^19.1.0", "react-map-gl": "^8.1.0", "reflect-metadata": "^0.2.2", + "sharp": "^0.34.5", "systeminformation": "^5.27.14", "tailwindcss": "^4.1.10", "tar": "^7.5.6", + "tesseract.js": "^7.0.0", "url-join": "^5.0.0", "usehooks-ts": "^3.1.1", "yaml": "^2.8.0" diff --git a/admin/start/routes.ts b/admin/start/routes.ts index a43daa3..a5a08b8 100644 --- a/admin/start/routes.ts +++ b/admin/start/routes.ts @@ -24,6 +24,7 @@ transmit.registerRoutes() router.get('/', [HomeController, 'index']) router.get('/home', [HomeController, 'home']) router.on('/about').renderInertia('about') +router.on('/knowledge-base').renderInertia('knowledge-base') router.get('/maps', [MapsController, 'index']) router.get('/easy-setup', [EasySetupController, 'index']) diff --git a/admin/types/kv_store.ts b/admin/types/kv_store.ts new file mode 100644 index 0000000..cf8addf --- /dev/null +++ b/admin/types/kv_store.ts @@ -0,0 +1,3 @@ + +export type KVStoreKey = 'open_webui_knowledge_id' +export type KVStoreValue = string | null \ No newline at end of file diff --git a/admin/types/ollama.ts b/admin/types/ollama.ts index f7235fb..ed9f864 100644 --- a/admin/types/ollama.ts +++ b/admin/types/ollama.ts @@ -27,3 +27,13 @@ export type OllamaModelListing = { size: string modified: string } + + +export type OpenWebUIKnowledgeFileMetadata = { + source: string + name: string + created_by: string + file_id: string + start_index: number + hash: string +} \ No newline at end of file