diff --git a/package-lock.json b/package-lock.json index 7998f20cd7..45f1f53721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,7 @@ "shiki": "^4.0.1", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.6", + "sql.js": "^1.14.1", "svelte-sonner": "^0.3.19", "tippy.js": "^6.3.7", "turndown": "^7.2.0", @@ -12363,6 +12364,12 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, + "node_modules/sql.js": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz", + "integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==", + "license": "MIT" + }, "node_modules/ssf": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", diff --git a/package.json b/package.json index 5265d21f49..74a2e04992 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "shiki": "^4.0.1", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.6", + "sql.js": "^1.14.1", "svelte-sonner": "^0.3.19", "tippy.js": "^6.3.7", "turndown": "^7.2.0", diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte index fae0ceb220..3897a3c40c 100644 --- a/src/lib/components/chat/FileNav.svelte +++ b/src/lib/components/chat/FileNav.svelte @@ -94,6 +94,7 @@ let fileVideoUrl: string | null = null; let fileAudioUrl: string | null = null; let filePdfData: ArrayBuffer | null = null; + let fileSqliteData: ArrayBuffer | null = null; let fileLoading = false; let filePreviewRef: FilePreview; @@ -188,9 +189,11 @@ const IMAGE_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico', 'avif']); const VIDEO_EXTS = new Set(['mp4', 'webm', 'mov', 'ogv', 'avi', 'mkv']); const AUDIO_EXTS = new Set(['mp3', 'wav', 'ogg', 'oga', 'flac', 'm4a', 'aac', 'wma', 'opus']); + const SQLITE_EXTS = new Set(['db', 'sqlite', 'sqlite3', 'db3']); const isImage = (path: string) => IMAGE_EXTS.has(path.split('.').pop()?.toLowerCase() ?? ''); const isVideo = (path: string) => VIDEO_EXTS.has(path.split('.').pop()?.toLowerCase() ?? ''); const isAudio = (path: string) => AUDIO_EXTS.has(path.split('.').pop()?.toLowerCase() ?? ''); + const isSqlite = (path: string) => SQLITE_EXTS.has(path.split('.').pop()?.toLowerCase() ?? ''); const isPdf = (path: string) => path.split('.').pop()?.toLowerCase() === 'pdf'; const isOffice = (path: string) => OFFICE_EXTS.has(path.split('.').pop()?.toLowerCase() ?? ''); @@ -224,6 +227,7 @@ fileAudioUrl = null; } filePdfData = null; + fileSqliteData = null; fileOfficeHtml = null; fileOfficeSlides = null; currentSlide = 0; @@ -288,6 +292,9 @@ } else if (isPdf(filePath)) { const result = await downloadFileBlob(terminal.url, terminal.key, filePath); if (result) filePdfData = await result.blob.arrayBuffer(); + } else if (isSqlite(filePath)) { + const result = await downloadFileBlob(terminal.url, terminal.key, filePath); + if (result) fileSqliteData = await result.blob.arrayBuffer(); } else if (isOffice(filePath)) { const result = await downloadFileBlob(terminal.url, terminal.key, filePath); if (result) { @@ -794,6 +801,7 @@ {fileVideoUrl} {fileAudioUrl} {filePdfData} + {fileSqliteData} {fileContent} {fileOfficeHtml} {fileOfficeSlides} diff --git a/src/lib/components/chat/FileNav/FilePreview.svelte b/src/lib/components/chat/FileNav/FilePreview.svelte index fefaf83b5c..a6bb7f639d 100644 --- a/src/lib/components/chat/FileNav/FilePreview.svelte +++ b/src/lib/components/chat/FileNav/FilePreview.svelte @@ -9,6 +9,7 @@ import PDFViewer from '../../common/PDFViewer.svelte'; import JsonTreeView from './JsonTreeView.svelte'; import NotebookView from './NotebookView.svelte'; + import SqliteView from './SqliteView.svelte'; let pdfViewerRef: PDFViewer; @@ -20,6 +21,7 @@ export let fileVideoUrl: string | null = null; export let fileAudioUrl: string | null = null; export let filePdfData: ArrayBuffer | null = null; + export let fileSqliteData: ArrayBuffer | null = null; export let fileContent: string | null = null; // Office preview props @@ -268,6 +270,8 @@ {:else if filePdfData !== null} + {:else if fileSqliteData !== null} + {:else if fileOfficeHtml !== null}
diff --git a/src/lib/components/chat/FileNav/SqliteView.svelte b/src/lib/components/chat/FileNav/SqliteView.svelte new file mode 100644 index 0000000000..34695c7733 --- /dev/null +++ b/src/lib/components/chat/FileNav/SqliteView.svelte @@ -0,0 +1,330 @@ + + +
+ {#if loading} +
+ {:else if error} +
{error}
+ {:else} + +
+ {#each tables as table} + + {/each} +
+ +
+ + {#if queryMode} + +
+
+ + +
+ {#if queryError} +
{queryError}
+ {/if} +
+ + {#if queryColumns.length > 0} +
+ + + + + {#each queryColumns as col} + + {/each} + + + + {#each queryRows as row, i} + + + {#each row as cell} + + {/each} + + {/each} + +
#{col}
{i + 1}{cell}
+
+ {/if} + {:else} + +
+ {#if columns.length > 0} + + + + + {#each columns as col} + + {/each} + + + + {#each rows as row, i} + + + {#each row as cell} + + {/each} + + {/each} + +
#{col}
{page * pageSize + i + 1}{cell}
+ {:else} +
{$i18n.t('No data')}
+ {/if} +
+ + + {#if totalPages > 1} +
+ + {page + 1} / {totalPages} ({totalRows.toLocaleString()} rows) + +
+ {/if} + {/if} + {/if} +
+ + diff --git a/static/sql.js/sql-wasm.wasm b/static/sql.js/sql-wasm.wasm new file mode 100755 index 0000000000..b32b66473d Binary files /dev/null and b/static/sql.js/sql-wasm.wasm differ