This commit is contained in:
Damien 2025-02-09 02:09:16 -05:00
parent b724e61d7f
commit 7a24d3cb98
22 changed files with 1413 additions and 59 deletions

View File

@ -0,0 +1 @@
1.10.2

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
cd5f7d5b-1d5c-40ec-bb49-885d5809f14f

Binary file not shown.

Binary file not shown.

View File

@ -221,6 +221,62 @@ export function setupIpcHandlers() {
return { success: false, error: err.message };
}
});
// Meilisearch Handlers
ipcMain.handle('meilisearch-add-documents', async (_: unknown, indexName: string, documents: any[]) => {
try {
await (global as any).meilisearchService.addDocuments(indexName, documents);
return { success: true };
} catch (error) {
const err = error as Error;
console.error('Error adding documents to Meilisearch:', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('meilisearch-remove-documents', async (_: unknown, indexName: string, documentIds: string[]) => {
try {
await (global as any).meilisearchService.removeDocuments(indexName, documentIds);
return { success: true };
} catch (error) {
const err = error as Error;
console.error('Error removing documents from Meilisearch:', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('meilisearch-update-documents', async (_: unknown, indexName: string, documents: any[]) => {
try {
await (global as any).meilisearchService.updateDocuments(indexName, documents);
return { success: true };
} catch (error) {
const err = error as Error;
console.error('Error updating documents in Meilisearch:', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('meilisearch-create-index', async (_: unknown, indexName: string) => {
try {
await (global as any).meilisearchService.createIndex(indexName);
return { success: true };
} catch (error) {
const err = error as Error;
console.error('Error creating index in Meilisearch:', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('meilisearch-delete-index', async (_: unknown, indexName: string) => {
try {
await (global as any).meilisearchService.deleteIndex(indexName);
return { success: true };
} catch (error) {
const err = error as Error;
console.error('Error deleting index in Meilisearch:', err);
return { success: false, error: err.message };
}
});
}
export default setupIpcHandlers;

View File

@ -4,10 +4,180 @@ const os = require('os');
const spawn = require('child_process').spawn;
const { store: electronStore } = require('./store');
const { setupIpcHandlers } = require('./ipc/handlers');
const { MeiliSearch } = require('meilisearch');
const slugify = (str: string) => {
return str
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
};
const { platform } = require('os');
class MeilisearchService {
private binaryPath: string;
private serverProcess: any | null = null;
private client: any | null = null;
constructor() {
this.binaryPath = this.getBinaryPath();
this.client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: process.env.MEILISEARCH_MASTER_KEY || 'Damie',
});
}
private getBinaryPath(): string {
const arch = process.arch;
let binaryName: string;
switch (platform()) {
case 'darwin':
binaryName = arch === 'arm64' ? 'meilisearch-macos-arm' : 'meilisearch-macos-x64';
break;
case 'win32':
binaryName = 'meilisearch-windows.exe';
break;
case 'linux':
binaryName = arch === 'arm' ? 'meilisearch-linux-arm' : 'meilisearch-linux-x64';
break;
default:
throw new Error(`Unsupported platform: ${platform()}`);
}
return path.join(__dirname, '..', 'meilisearch_binaries', binaryName);
}
public async startServer(): Promise<void> {
if (this.serverProcess) {
console.log('Meilisearch server is already running.');
return;
}
try {
const apiKey = 'Damie'; // Use the system's name as the API key
process.env.MEILISEARCH_MASTER_KEY = apiKey;
this.serverProcess = spawn(this.binaryPath, ['--http-addr', '127.0.0.1:7700'], {
env: process.env, // Pass the environment variables to the child process
});
this.serverProcess.stdout?.on('data', (data) => {
console.log(`Meilisearch: ${data}`);
});
this.serverProcess.stderr?.on('data', (data) => {
console.error(`Meilisearch: ${data}`);
});
this.serverProcess.on('close', (code) => {
console.log(`Meilisearch server stopped with code ${code}`);
this.serverProcess = null;
});
this.serverProcess.on('error', (err) => {
console.error('Failed to start Meilisearch server:', err);
this.serverProcess = null;
});
console.log('Meilisearch server started.');
} catch (error) {
console.error('Failed to start Meilisearch server:', error);
}
}
public async stopServer(): Promise<void> {
if (!this.serverProcess) {
console.log('Meilisearch server is not running.');
return;
}
this.serverProcess.kill();
this.serverProcess = null;
console.log('Meilisearch server stopped.');
}
// Implement methods for adding, removing, and updating documents and collections
// using the Meilisearch API.
public async addDocuments(indexName: string, documents: any[]): Promise<void> {
// TODO: Implement this method
console.log(`Adding documents to index ${indexName}`);
}
public async removeDocuments(indexName: string, documentIds: string[]): Promise<void> {
// TODO: Implement this method
console.log(`Removing documents from index ${indexName}`);
}
public async updateDocuments(indexName: string, documents: any[]): Promise<void> {
// TODO: Implement this method
console.log(`Updating documents in index ${indexName}`);
}
public async createIndex(indexName: string): Promise<void> {
const sluggedIndexName = slugify(indexName);
// TODO: Implement this method
console.log(`Creating index ${sluggedIndexName}`);
try {
await this.client.createIndex(sluggedIndexName);
console.log(`Index ${sluggedIndexName} created successfully`);
} catch (error) {
console.error(`Failed to create index ${sluggedIndexName}:`, error);
throw error;
}
}
public async deleteIndex(indexName: string): Promise<void> {
try {
await this.client.deleteIndex(indexName);
console.log(`Index ${indexName} deleted successfully`);
} catch (error) {
console.error(`Failed to delete index ${indexName}:`, error);
throw error;
}
}
public async addDocuments(indexName: string, documents: any[]): Promise<void> {
try {
const index = await this.client.index(indexName);
await index.addDocuments(documents);
console.log(`Documents added to index ${indexName} successfully`);
} catch (error) {
console.error(`Failed to add documents to index ${indexName}:`, error);
throw error;
}
}
public async removeDocuments(indexName: string, documentIds: string[]): Promise<void> {
try {
const index = await this.client.index(indexName);
await index.deleteDocuments(documentIds);
console.log(`Documents removed from index ${indexName} successfully`);
} catch (error) {
console.error(`Failed to remove documents from index ${indexName}:`, error);
throw error;
}
}
public async updateDocuments(indexName: string, documents: any[]): Promise<void> {
try {
const index = await this.client.index(indexName);
await index.updateDocuments(documents);
console.log(`Documents updated in index ${indexName} successfully`);
} catch (error) {
console.error(`Failed to update documents in index ${indexName}:`, error);
throw error;
}
}
}
// Initialize IPC handlers immediately
setupIpcHandlers();
const meilisearchService = new MeilisearchService();
(global as any).meilisearchService = meilisearchService;
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
@ -123,6 +293,7 @@ function createWindow() {
}
app.whenReady().then(() => {
meilisearchService.startServer();
createWindow();
app.on('activate', () => {
@ -167,4 +338,23 @@ ipcMain.handle('open-external', (_, url) => {
return require('electron').shell.openExternal(url);
});
// Launch the server
if (process.env.NODE_ENV !== 'development') {
const serverProcess = spawn('node', [path.join(__dirname, '../scripts/dev.cjs')], {
stdio: 'inherit',
});
serverProcess.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
});
serverProcess.on('error', (err) => {
console.error('Failed to start server process:', err);
});
}
app.on('will-quit', () => {
meilisearchService.stopServer();
});
module.exports = app;

View File

@ -26,6 +26,11 @@ contextBridge.exposeInMainWorld('electron', {
addExcludedPath: async (path: string): Promise<void> =>
ipcRenderer.invoke('add-excluded-path', path),
createIndex: async (folderPath: string): Promise<{
answer: string;
sources: DocumentMetadata[];
}> => ipcRenderer.invoke('meilisearch-create-index', folderPath),
// LLM Operations
queryLLM: async (question: string): Promise<{
answer: string;
@ -84,6 +89,9 @@ contextBridge.exposeInMainWorld('electron', {
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
maximizeWindow: () => ipcRenderer.invoke('window-maximize'),
closeWindow: () => ipcRenderer.invoke('window-close'),
send: (channel: string, data: any) => {
ipcRenderer.send(channel, data);
}
});
// Export types for TypeScript

View File

@ -2,6 +2,11 @@ import { FSWatcher } from 'chokidar';
import * as chokidar from 'chokidar';
import Store from 'electron-store';
import { ServiceError } from '../types';
import * as path from 'path';
const fs = require('fs');
const fsPromises = fs.promises;
import * as crypto from 'crypto';
import MeilisearchService from './meilisearchService';
const store = new Store<{
watchedPaths: string[];
@ -11,10 +16,13 @@ const store = new Store<{
class FileSystemService {
private watchers: Map<string, FSWatcher>;
private excludedPaths: Set<string>;
private meilisearchService: MeilisearchService;
private readonly indexName = 'files';
constructor() {
this.watchers = new Map();
this.excludedPaths = new Set(store.get('excludedPaths', []));
this.meilisearchService = new MeilisearchService();
// Add example paths
const examplePaths = [
@ -32,6 +40,49 @@ class FileSystemService {
});
}
private slugify(filePath: string): string {
return filePath
.replace(/[\\/]/g, '-') // Replace path separators with dashes
.replace(/[^a-zA-Z0-9_-]+/g, '') // Remove non-alphanumeric characters
.toLowerCase();
}
private async calculateFileHash(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(filePath);
if (!stream) {
reject(new Error(`Failed to create read stream for ${filePath}`));
return;
}
stream.on('data', (data: any) => {
try {
hash.update(data);
} catch (dataError) {
reject(new Error(`Failed to update hash with data for ${filePath}: ${dataError}`));
}
});
stream.on('end', () => {
try {
resolve(hash.digest('hex'));
} catch (digestError) {
reject(new Error(`Failed to digest hash for ${filePath}: ${digestError}`));
}
});
stream.on('error', (streamError: any) => {
reject(new Error(`Read stream error for ${filePath}: ${streamError}`));
});
} catch (creationError: any) {
reject(new Error(`Failed to create read stream or hash for ${filePath}: ${creationError}`));
}
});
}
public async startWatching(dirPath: string): Promise<void> {
if (this.watchers.has(dirPath)) {
throw new ServiceError(`Already watching directory: ${dirPath}`);
@ -39,7 +90,7 @@ class FileSystemService {
const watcher = chokidar.watch(dirPath, {
ignored: [
/(^|[\/\\])\../, // Ignore dotfiles
/(^|[\\/])\../, // Ignore dotfiles
'**/node_modules/**',
...Array.from(this.excludedPaths),
],
@ -47,9 +98,58 @@ class FileSystemService {
ignoreInitial: false,
});
watcher.on('add', path => {
console.log(`File ${path} has been added`);
// TODO: Process file
const indexName = this.slugify(dirPath);
watcher.on('add', async (filePath) => {
console.log(`File ${filePath} has been added`);
try {
const stats = await fs.promises.stat(filePath);
const slug = this.slugify(filePath);
const fileContent = await fs.promises.readFile(filePath, 'utf-8');
const fileExtension = path.extname(filePath);
const fileName = path.basename(filePath);
const permissions = {
read: !!(stats.mode & fs.constants.S_IRUSR),
write: !!(stats.mode & fs.constants.S_IWUSR),
execute: !!(stats.mode & fs.constants.S_IXUSR),
};
const document = {
id: slug,
name: filePath,
fileName: fileName,
content: fileContent,
extension: fileExtension,
createdAt: stats.birthtime,
modifiedAt: stats.mtime,
accessedAt: stats.atime,
size: stats.size,
permissions: permissions,
};
let fileHash: string | undefined;
try {
fileHash = await this.calculateFileHash(filePath);
document['hash'] = fileHash;
} catch (hashError) {
console.error(`Failed to calculate file hash for ${filePath}:`, hashError);
}
if (this.meilisearchService) {
try {
await this.meilisearchService.addDocuments(indexName, [document]);
console.log(`Added document to Meilisearch: ${slug}`);
} catch (meilisearchError) {
console.error(`Failed to add document to Meilisearch:`, meilisearchError);
}
} else {
console.warn('Meilisearch service not initialized.');
}
} catch (error) {
console.error(`Failed to add document to Meilisearch:`, error);
}
});
watcher.on('change', path => {

View File

@ -0,0 +1,91 @@
import { MeiliSearch } from 'meilisearch'
class MeilisearchService {
private client: MeiliSearch;
constructor() {
this.client = new MeiliSearch({
host: 'http://localhost:7700',
});
this.createIndex('files'); // Ensure the index exists
}
// Implement methods for adding, removing, and updating documents and collections
// using the Meilisearch API.
public async addDocuments(indexName: string, documents: any[]): Promise<void> {
try {
const index = this.client.index(indexName)
await index.addDocuments(documents)
console.log(`Added ${documents.length} documents to index ${indexName}`);
} catch (error) {
console.error(`Failed to add documents to index ${indexName}:`, error);
throw error;
}
}
public async removeDocuments(indexName: string, documentIds: string[]): Promise<void> {
try {
const index = this.client.index(indexName)
await index.deleteDocuments(documentIds)
console.log(`Removed documents with IDs ${documentIds.join(',')} from index ${indexName}`);
} catch (error) {
console.error(`Failed to remove documents from index ${indexName}:`, error);
throw error;
}
}
public async updateDocuments(indexName: string, documents: any[]): Promise<void> {
try {
const index = this.client.index(indexName)
await index.updateDocuments(documents)
console.log(`Updated ${documents.length} documents in index ${indexName}`);
} catch (error) {
console.error(`Failed to update documents in index ${indexName}:`, error);
throw error;
}
}
public async deleteIndex(indexName: string): Promise<void> {
try {
await this.client.deleteIndex(indexName);
console.log(`Deleted index ${indexName}`);
} catch (error) {
console.error(`Failed to delete index ${indexName}:`, error);
throw error;
}
}
public async createIndex(indexName: string): Promise<void> {
try {
await this.client.createIndex(indexName);
console.log(`Created index ${indexName}`);
} catch (error) {
console.error(`Failed to create index ${indexName}:`, error);
throw error;
}
}
public async getDocuments(indexName: string): Promise<any[]> {
try {
const index = this.client.index(indexName);
const { hits } = await index.search(''); // Empty search to get all documents
return hits;
} catch (error) {
console.error(`Failed to get documents from index ${indexName}:`, error);
throw error;
}
}
public async search(indexName: string, query: string): Promise<any[]> {
try {
const index = this.client.index(indexName);
const { hits } = await index.search(query);
return hits;
} catch (error) {
console.error(`Failed to search index ${indexName} with query ${query}:`, error);
throw error;
}
}
}
export default MeilisearchService;

View File

@ -10,6 +10,9 @@ export interface DocumentMetadata {
type: string;
lastModified: number;
size: number;
createdAt?: string;
modifiedAt?: string;
accessedAt?: string;
hasEmbeddings?: boolean;
hasOcr?: boolean;
}

View File

@ -14,15 +14,18 @@
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5.15.7",
"@mui/material": "^5.15.7",
"@mui/x-data-grid": "^7.26.0",
"chokidar": "^3.5.3",
"electron-store": "^8.1.0",
"highlight.js": "^11.11.1",
"meilisearch": "^0.48.2",
"ollama": "^0.5.12",
"openai": "^4.82.0",
"openrouter-client": "^1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.3",
"react-virtuoso": "^4.12.5",
"rehype-highlight": "^7.0.2"
},
"devDependencies": {
@ -1848,6 +1851,64 @@
}
}
},
"node_modules/@mui/x-data-grid": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.26.0.tgz",
"integrity": "sha512-9RNQeT2OL6jBOCE0MSUH11ol3fV5Zs9MkGxUIAGXcy/Fui0rZRNFO1yLmWDZU5yvskiNmUZJHWV/qXh++ZFarA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.7",
"@mui/utils": "^5.16.6 || ^6.0.0",
"@mui/x-internals": "7.26.0",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"reselect": "^5.1.1",
"use-sync-external-store": "^1.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14 || ^6.0.0",
"@mui/system": "^5.15.14 || ^6.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/x-internals": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.26.0.tgz",
"integrity": "sha512-VxTCYQcZ02d3190pdvys2TDg9pgbvewAVakEopiOgReKAUhLdRlgGJHcOA/eAuGLyK1YIo26A6Ow6ZKlSRLwMg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.7",
"@mui/utils": "^5.16.6 || ^6.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -6713,6 +6774,12 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/meilisearch": {
"version": "0.48.2",
"resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.48.2.tgz",
"integrity": "sha512-auDB6grs3f/+dVzzPiAOOFnrVN/L87/+SWZfy56TpimDinWjU4u6Jc2o6lgeLluotpbMOFY1WpTTbqtUdMduQw==",
"license": "MIT"
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -8275,6 +8342,16 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/react-virtuoso": {
"version": "4.12.5",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.12.5.tgz",
"integrity": "sha512-YeCbRRsC9CLf0buD0Rct7WsDbzf+yBU1wGbo05/XjbcN2nJuhgh040m3y3+6HVogTZxEqVm45ac9Fpae4/MxRQ==",
"license": "MIT",
"peerDependencies": {
"react": ">=16 || >=17 || >= 18 || >= 19",
"react-dom": ">=16 || >=17 || >= 18 || >=19"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -8431,6 +8508,12 @@
"node": ">=0.10.0"
}
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@ -9594,6 +9677,15 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",

View File

@ -19,15 +19,18 @@
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5.15.7",
"@mui/material": "^5.15.7",
"@mui/x-data-grid": "^7.26.0",
"chokidar": "^3.5.3",
"electron-store": "^8.1.0",
"highlight.js": "^11.11.1",
"meilisearch": "^0.48.2",
"ollama": "^0.5.12",
"openai": "^4.82.0",
"openrouter-client": "^1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.3",
"react-virtuoso": "^4.12.5",
"rehype-highlight": "^7.0.2"
},
"build": {

View File

@ -328,10 +328,9 @@ function AppContent() {
<Tabs value={currentTab} onChange={handleTabChange}>
<Tab label="Home" />
<Tab label="Chat" />
<Tab label="Settings" />
<Tab disabled label="Scanning" />
<Tab disabled label="Reports" />
<Tab disabled label="Cleanup" />
<Tab label="Settings" />
</Tabs>
</Box>
<Box sx={{
@ -349,13 +348,10 @@ function AppContent() {
<ChatPanel messages={messages} />
</TabPanel>
<TabPanel value={currentTab} index={2}>
<SettingsPanel />
</TabPanel>
<TabPanel value={currentTab} index={3}>
<ScanningPanel />
<ReportingPanel />
</TabPanel>
<TabPanel value={currentTab} index={4}>
<ReportingPanel />
<SettingsPanel />
</TabPanel>
</Box>
</Box>

View File

@ -0,0 +1,15 @@
.search-icon {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}

View File

@ -0,0 +1,152 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
Button,
Modal,
TextField,
} from "@mui/material";
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { Search } from '@mui/icons-material';
import './DirectorySearchModal.css';
interface DirectorySearchModalProps {
dir: string;
open: boolean;
onClose: () => void;
modalId: string;
}
interface FileData {
id: number;
filePath: string;
fileName: string;
fileExtension: string;
createdAt: string;
modifiedAt: string;
accessedAt: string;
size: number;
}
const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID', width: 50 },
{ field: 'filePath', headerName: 'File Path', width: 250 },
{ field: 'fileName', headerName: 'File Name', width: 150 },
{ field: 'fileExtension', headerName: 'File Extension', width: 100 },
{ field: 'createdAt', headerName: 'Created At', width: 150 },
{ field: 'modifiedAt', headerName: 'Modified At', width: 150 },
{ field: 'accessedAt', headerName: 'Accessed At', width: 150 },
{ field: 'size', headerName: 'Size', width: 70 },
];
const DirectorySearchModal: React.FC<DirectorySearchModalProps> = ({ dir, open, onClose, modalId }) => {
const [searchQuery, setSearchQuery] = useState('');
const [rows, setRows] = useState<FileData[]>([]);
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value);
};
useEffect(() => {
const fetchFileData = async () => {
window.electron.getDocuments().then((response) => {
if (response.success && response.data) {
const filesInDirectory = response.data.filter(doc => doc.path.startsWith(dir));
const fileData: FileData[] = filesInDirectory.map((doc, index) => ({
id: index + 1,
filePath: doc.path,
fileName: doc.path.split(/[/\\]/).pop() || '',
fileExtension: doc.path.split('.').pop() || '',
createdAt: doc.createdAt || '',
modifiedAt: doc.modifiedAt || '',
accessedAt: doc.accessedAt || '',
size: doc.size || 0,
}));
setRows(fileData);
}
});
};
fetchFileData();
}, [dir]);
const filteredRows = rows.filter((row) => {
return (
row.filePath.toLowerCase().includes(searchQuery.toLowerCase()) ||
row.fileName.toLowerCase().includes(searchQuery.toLowerCase()) ||
row.fileExtension.toLowerCase().includes(searchQuery.toLowerCase())
);
});
return (
<Modal
id={modalId}
open={open}
onClose={onClose}
aria-labelledby={`modal-title-${dir}`}
aria-describedby={`modal-description-${dir}`}
>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: '80%',
maxWidth: 1200,
bgcolor: "background.paper",
boxShadow: 24,
p: 4,
borderRadius: 2,
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
border: '1px solid white',
borderRadius: '4px',
paddingLeft: '8px',
marginBottom: '8px',
}}
>
<Search className="search-icon" sx={{ mr: 1, color: 'white' }} />
<TextField
placeholder={dir}
variant="standard"
fullWidth
onChange={handleSearchChange}
InputProps={{
disableUnderline: true,
style: {
color: 'white',
},
}}
sx={{
'& .MuiInputBase-input': {
color: 'white',
},
}}
/>
</Box>
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={filteredRows}
columns={columns}
checkboxSelection
/>
</div>
<Button
variant="contained"
color="error"
onClick={onClose}
sx={{ mt: 2 }}
>
Close
</Button>
</Box>
</Modal>
);
};
export default DirectorySearchModal;

View File

@ -0,0 +1,39 @@
import React, { useState } from 'react';
import { TextField, Button, Box } from '@mui/material';
interface DirectorySearchPanelProps {
folderNameSlugged: string;
}
const DirectorySearchPanel = ({ folderNameSlugged }: DirectorySearchPanelProps) => {
const [searchQuery, setSearchQuery] = useState('');
const handleSearchQueryChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value);
};
const handleSearch = () => {
// Implement Meilisearch search here
console.log(`Searching ${folderNameSlugged} for ${searchQuery}`);
};
return (
<Box sx={{ padding: '16px', display: 'flex', flexDirection: 'column', height: '100%' }}>
<Box sx={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
<TextField
label="Search"
variant="outlined"
value={searchQuery}
onChange={handleSearchQueryChange}
sx={{ flex: 1 }}
/>
<Button variant="contained" color="primary" onClick={handleSearch}>
Search
</Button>
</Box>
{/* Add search results component here */}
</Box>
);
};
export default DirectorySearchPanel;

View File

@ -96,6 +96,8 @@ export default function DirectoryPicker({ onSelect }: DirectoryPickerProps) {
const handleSelect = () => {
onSelect(currentPath);
// Send the meilisearch-create-index event
electron.createIndex(currentPath);
handleClose();
};

View File

@ -1,24 +1,43 @@
import React from 'react';
import { Box, Typography, List, ListItem, ListItemText, ListItemIcon, IconButton, Tooltip } from '@mui/material';
import { Folder as FolderIcon, Psychology as LLMIcon, ImageSearch as OCRIcon } from '@mui/icons-material';
import { DocumentMetadata } from '../../../electron/types';
import DirectoryPicker from './DirectoryPicker';
import { useFileSystem } from '../../hooks/useFileSystem';
import React, { useState, useEffect } from "react";
import {
Box,
List,
ListItem,
ListItemText,
ListItemIcon,
IconButton,
Tooltip,
} from "@mui/material";
import {
Folder as FolderIcon,
Psychology as LLMIcon,
ImageSearch as OCRIcon,
Search as SearchIcon,
} from "@mui/icons-material";
import { DocumentMetadata } from "../../../electron/types";
import DirectoryPicker from "./DirectoryPicker";
import { useFileSystem } from "../../hooks/useFileSystem";
import DirectorySearchModal from "../DirectorySearchModal";
interface FileMetadata {
[path: string]: DocumentMetadata;
}
interface Directory {
name: string;
path: string;
}
export default function FileExplorer() {
const { watchedDirectories, watchDirectory, unwatchDirectory } = useFileSystem();
const [fileMetadata, setFileMetadata] = React.useState<FileMetadata>({});
const [fileMetadata, setFileMetadata] = useState<FileMetadata>({});
const [openModal, setOpenModal] = useState<{ [key: string]: boolean }>({});
React.useEffect(() => {
// Get document metadata from electron store
useEffect(() => {
window.electron.getDocuments().then((response) => {
if (response.success && response.data) {
const metadata: FileMetadata = {};
response.data.forEach(doc => {
response.data.forEach((doc) => {
metadata[doc.path] = doc;
});
setFileMetadata(metadata);
@ -36,33 +55,41 @@ export default function FileExplorer() {
await unwatchDirectory(dirPath);
};
const handleOpenModal = (dir: string) => {
setOpenModal((prev) => ({ ...prev, [dir]: true }));
};
const handleCloseModal = (dir: string) => {
setOpenModal((prev) => ({ ...prev, [dir]: false }));
};
const directoryList: Directory[] = watchedDirectories.map((dir) => ({
name: dir.split(/[/\\]/).pop() || dir,
path: dir,
}));
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
height: '100%',
overflow: 'hidden'
}}>
<Box
sx={{
display: "flex",
flexDirection: "column",
height: "100%",
overflow: "hidden",
}}
>
<DirectoryPicker directories={directoryList} onSelect={handleDirectorySelect} />
<DirectoryPicker onSelect={handleDirectorySelect} />
<Box sx={{ flex: 1, overflow: 'auto' }}>
<Box sx={{ flex: 1, overflow: "auto" }}>
<List>
{watchedDirectories.map((dir) => (
<ListItem
key={dir}
secondaryAction={
<IconButton
edge="end"
size="small"
onClick={() => handleDirectoryRemove(dir)}
>
×
</IconButton>
}
>
<ListItemIcon sx={{ display: 'flex', gap: 0.5 }}>
<FolderIcon />
{watchedDirectories.map((dir) => {
const modalId = `modal-${dir}`;
return (
<ListItem key={dir} sx={{ display: "flex", alignItems: "center" }}>
<ListItemIcon sx={{ minWidth: 28 }}>
<FolderIcon fontSize="small" />
</ListItemIcon>
{fileMetadata[dir]?.hasEmbeddings && (
<Tooltip title="LLM Embeddings Enabled">
<LLMIcon color="primary" fontSize="small" />
@ -73,20 +100,35 @@ export default function FileExplorer() {
<OCRIcon color="secondary" fontSize="small" />
</Tooltip>
)}
</ListItemIcon>
<ListItemText
primary={dir.split(/[/\\]/).pop()}
secondary={dir}
secondaryTypographyProps={{
sx: {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}}
/>
</ListItem>
))}
<ListItemText
primary={dir.split(/[/\\]/).pop()}
secondary={dir}
secondaryTypographyProps={{
sx: {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
}}
/>
{/* Search Icon to Open Modal */}
<Tooltip title="Search">
<IconButton color="primary" size="small" onClick={() => handleOpenModal(dir)}>
<SearchIcon fontSize="small" />
</IconButton>
</Tooltip>
<DirectorySearchModal
dir={dir}
open={!!openModal[dir]}
onClose={() => handleCloseModal(dir)}
modalId={modalId}
/>
</ListItem>
);
})}
</List>
</Box>
</Box>

557
package-lock.json generated Normal file
View File

@ -0,0 +1,557 @@
{
"name": "test",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@mui/x-data-grid": "^7.26.0",
"meilisearch": "^0.48.2"
}
},
"node_modules/@babel/runtime": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
"integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emotion/cache": {
"version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@emotion/memoize": "^0.9.0",
"@emotion/sheet": "^1.4.0",
"@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.4.0",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/hash": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
"license": "MIT",
"peer": true
},
"node_modules/@emotion/memoize": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
"license": "MIT",
"peer": true
},
"node_modules/@emotion/serialize": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/unitless": "^0.10.0",
"@emotion/utils": "^1.4.2",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/sheet": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
"license": "MIT",
"peer": true
},
"node_modules/@emotion/unitless": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
"license": "MIT",
"peer": true
},
"node_modules/@emotion/utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
"license": "MIT",
"peer": true
},
"node_modules/@emotion/weak-memoize": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT",
"peer": true
},
"node_modules/@mui/core-downloads-tracker": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.3.tgz",
"integrity": "sha512-hlyOzo2ObarllAOeT1ZSAusADE5NZNencUeIvXrdQ1Na+FL1lcznhbxfV5He1KqGiuR8Az3xtCUcYKwMVGFdzg==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/material": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.3.tgz",
"integrity": "sha512-ubtQjplbWneIEU8Y+4b2VA0CDBlyH5I3AmVFGmsLyDe/bf0ubxav5t11c8Afem6rkSFWPlZA2DilxmGka1xiKQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/core-downloads-tracker": "^6.4.3",
"@mui/system": "^6.4.3",
"@mui/types": "^7.2.21",
"@mui/utils": "^6.4.3",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
"react-is": "^19.0.0",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^6.4.3",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@mui/material-pigment-css": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/private-theming": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz",
"integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/utils": "^6.4.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/styled-engine": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz",
"integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
"@emotion/cache": "^11.13.5",
"@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/system": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz",
"integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/private-theming": "^6.4.3",
"@mui/styled-engine": "^6.4.3",
"@mui/types": "^7.2.21",
"@mui/utils": "^6.4.3",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/types": {
"version": "7.2.21",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz",
"integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==",
"license": "MIT",
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/utils": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz",
"integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/types": "^7.2.21",
"@types/prop-types": "^15.7.14",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-is": "^19.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/x-data-grid": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.26.0.tgz",
"integrity": "sha512-9RNQeT2OL6jBOCE0MSUH11ol3fV5Zs9MkGxUIAGXcy/Fui0rZRNFO1yLmWDZU5yvskiNmUZJHWV/qXh++ZFarA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.7",
"@mui/utils": "^5.16.6 || ^6.0.0",
"@mui/x-internals": "7.26.0",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"reselect": "^5.1.1",
"use-sync-external-store": "^1.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14 || ^6.0.0",
"@mui/system": "^5.15.14 || ^6.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/x-internals": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.26.0.tgz",
"integrity": "sha512-VxTCYQcZ02d3190pdvys2TDg9pgbvewAVakEopiOgReKAUhLdRlgGJHcOA/eAuGLyK1YIo26A6Ow6ZKlSRLwMg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.7",
"@mui/utils": "^5.16.6 || ^6.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.0.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
"integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "*"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT",
"peer": true
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/meilisearch": {
"version": "0.48.2",
"resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.48.2.tgz",
"integrity": "sha512-auDB6grs3f/+dVzzPiAOOFnrVN/L87/+SWZfy56TpimDinWjU4u6Jc2o6lgeLluotpbMOFY1WpTTbqtUdMduQw==",
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.25.0"
},
"peerDependencies": {
"react": "^19.0.0"
}
},
"node_modules/react-is": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
"integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==",
"license": "MIT"
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT",
"peer": true
},
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
"license": "MIT",
"peer": true
},
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
}
}
}

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"dependencies": {
"@mui/x-data-grid": "^7.26.0",
"meilisearch": "^0.48.2"
}
}