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}
+
+
+
+ {#if queryMode}
+
+
+
+
+
+
+ {#if queryError}
+
{queryError}
+ {/if}
+
+
+ {#if queryColumns.length > 0}
+
+
+
+
+ | # |
+ {#each queryColumns as col}
+ {col} |
+ {/each}
+
+
+
+ {#each queryRows as row, i}
+
+ | {i + 1} |
+ {#each row as cell}
+ {cell} |
+ {/each}
+
+ {/each}
+
+
+
+ {/if}
+ {:else}
+
+
+ {#if columns.length > 0}
+
+
+
+ | # |
+ {#each columns as col}
+ {col} |
+ {/each}
+
+
+
+ {#each rows as row, i}
+
+ | {page * pageSize + i + 1} |
+ {#each row as cell}
+ {cell} |
+ {/each}
+
+ {/each}
+
+
+ {: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