This commit is contained in:
Damien 2025-02-09 03:10:26 -05:00
parent 7a24d3cb98
commit 29f76c9e9d
13 changed files with 229 additions and 226 deletions

View File

@ -1,4 +1,4 @@
# Data Identification Manager : Personal Edition
# Data Identification Manager
An Electron-based desktop application that uses AI to help you search and understand your files through natural language queries.

View File

@ -277,6 +277,28 @@ export function setupIpcHandlers() {
return { success: false, error: err.message };
}
});
ipcMain.handle('meilisearch-get-documents', async (_: unknown, indexName: string) => {
try {
const documents = await (global as any).meilisearchService.getDocuments(indexName);
return { success: true, data: documents };
} catch (error) {
const err = error as Error;
console.error('Error getting documents from Meilisearch:', err);
return { success: false, error: err.message };
}
});
ipcMain.handle('meilisearch-search', async (_: unknown, indexName: string, query: string) => {
try {
const results = await (global as any).meilisearchService.search(indexName, query);
return { success: true, data: results };
} catch (error) {
const err = error as Error;
console.error('Error searching Meilisearch:', err);
return { success: false, error: err.message };
}
});
}
export default setupIpcHandlers;

View File

@ -14,170 +14,14 @@ const slugify = (str: string) => {
};
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();
import MeilisearchService from './services/meilisearchService';
const meilisearchService = new MeilisearchService();
(global as any).meilisearchService = meilisearchService;
// Initialize IPC handlers immediately
setupIpcHandlers();
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,

View File

@ -84,6 +84,9 @@ contextBridge.exposeInMainWorld('electron', {
ipcRenderer.removeListener(channel, onProgress);
});
},
meilisearchSearch: async (indexName: string, query: string): Promise<{ success: boolean; data: any[] }> =>
ipcRenderer.invoke('meilisearch-search', indexName, query),
// Window Controls
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),

View File

@ -1,13 +1,89 @@
import { MeiliSearch } from 'meilisearch'
import { MeiliSearch } from 'meilisearch';
import slugify from 'slugify';
const { platform } = require('os');
const path = require('path');
const spawn = require('child_process').spawn;
class MeilisearchService {
private client: MeiliSearch;
private binaryPath: string;
private serverProcess: any | null = null;
constructor() {
this.client = new MeiliSearch({
host: 'http://localhost:7700',
});
this.createIndex('files'); // Ensure the index exists
this.binaryPath = this.getBinaryPath();
}
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
@ -68,17 +144,26 @@ class MeilisearchService {
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;
const documents = await index.getDocuments();
return documents.results;
} catch (error) {
console.error(`Failed to get documents from index ${indexName}:`, error);
throw error;
}
}
private slugifyPath(path):string {
return path
.toLowerCase() // Convert to lowercase
.replace(/[\\\/:*?"<>|]/g, '-') // Replace invalid characters with a dash
.replace(/\s+/g, '-') // Replace spaces with a dash
.replace(/-+/g, '-') // Replace multiple dashes with a single dash
.replace(/^-+|-+$/g, '');
}
public async search(indexName: string, query: string): Promise<any[]> {
try {
const index = this.client.index(indexName);
console.log(indexName)
const index = this.client.index(this.slugifyPath(indexName));
const { hits } = await index.search(query);
return hits;
} catch (error) {

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:;" />
<title>Data Identification Manager : Personal Edition</title>
<title>Data Identification Manager</title>
</head>
<body>
<div id="root"></div>

View File

@ -26,7 +26,8 @@
"react-dom": "^18.2.0",
"react-markdown": "^9.0.3",
"react-virtuoso": "^4.12.5",
"rehype-highlight": "^7.0.2"
"rehype-highlight": "^7.0.2",
"slugify": "^1.6.6"
},
"devDependencies": {
"@types/node": "^20.11.16",
@ -8910,6 +8911,15 @@
"node": ">=8"
}
},
"node_modules/slugify": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",

View File

@ -31,7 +31,8 @@
"react-dom": "^18.2.0",
"react-markdown": "^9.0.3",
"react-virtuoso": "^4.12.5",
"rehype-highlight": "^7.0.2"
"rehype-highlight": "^7.0.2",
"slugify": "^1.6.6"
},
"build": {
"appId": "com.electron-file-search",

View File

@ -154,7 +154,7 @@ function AppContent() {
}}
>
<Button
<Button
variant="outlined"
size="small"
startIcon={<UpgradeTwoTone />}
@ -250,7 +250,7 @@ function AppContent() {
}}
>
Enterprise
</Button>
</Button>
<Typography
sx={{
position: 'absolute',
@ -266,20 +266,7 @@ function AppContent() {
Data Identification Manager
</Typography>
<div className="window-controls">
<Chip
label="Personal Edition (Free)"
variant="outlined"
size="small"
sx={{
bgcolor: 'primary.main', // or 'secondary.main', 'error.main', etc.
color: 'primary.contrastText',
borderRadius: 1,
height: 'auto',
'& .MuiChip-label': {
padding: '4px 8px',
}
}}
/>
<button
className="control-button"
onClick={() => window.electron.closeWindow()}

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
Button,
Modal,
TextField,
@ -29,54 +28,67 @@ interface FileData {
}
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: 'size',
headerName: 'Size',
width: 100,
valueFormatter: (value:any) => {
console.log("params.value:", value); // Debugging lin
// Ensure the value is a number
const bytes = typeof value === 'number' ? value : 0;
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = 2;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
},
},
{ 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);
}
});
};
handleSearchChange({ target: { value: '*' } } as any);
}, []);
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())
);
});
const handleSearchChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const query = event.target.value;
setSearchQuery(query);
window.electron.meilisearchSearch(dir, query)
.then((response: { success: boolean; data: any[]; error?: string }) => {
if (response.success && response.data) {
const fileData: FileData[] = response.data.map((doc: any, index: number) => ({
id: doc.name,
filePath: doc.name,
fileName: doc.fileName,
fileExtension: doc.extension,
size: doc.size, // Ensure size is not undefined
createdAt: doc.createdAt,
modifiedAt: doc.modifiedAt,
accessedAt: doc.accessedAt,
}));
setRows(fileData);
} else {
console.error('Error searching Meilisearch:', response.error);
setRows([]);
}
});
};
return (
<Modal
@ -131,19 +143,47 @@ const DirectorySearchModal: React.FC<DirectorySearchModalProps> = ({ dir, open,
</Box>
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={filteredRows}
rows={rows}
columns={columns}
checkboxSelection
rowCount={rows.length}
/>
</div>
<Button
variant="contained"
color="error"
color="primary"
onClick={onClose}
sx={{ mt: 2 }}
sx={{ mt: 2, m:1 }}
>
Close
</Button>
<Button
variant="contained"
color="warning"
onClick={onClose}
disabled
sx={{ mt: 2, m:1 }}
>
Move To
</Button>
<Button
variant="contained"
color="warning"
onClick={onClose}
disabled
sx={{ mt: 2, m:1 }}
>
Copy To
</Button>
<Button
variant="contained"
color="error"
onClick={onClose}
disabled
sx={{ mt: 2, m:1 }}
>
Clean From System
</Button>
</Box>
</Modal>
);

View File

@ -19,7 +19,7 @@ declare global {
sources: DocumentMetadata[];
}>;
getLLMConfig: () => Promise<LLMConfig>;
meilisearchSearch: (indexName: string, query: string) => Promise<{ success: boolean; data: any[] }>;
// Vector Store Operations
getDocuments: () => Promise<{ success: boolean; data?: DocumentMetadata[]; error?: string }>;
addDocument: (content: string, metadata: DocumentMetadata) => Promise<void>;

12
package-lock.json generated
View File

@ -6,7 +6,8 @@
"": {
"dependencies": {
"@mui/x-data-grid": "^7.26.0",
"meilisearch": "^0.48.2"
"meilisearch": "^0.48.2",
"slugify": "^1.6.6"
}
},
"node_modules/@babel/runtime": {
@ -537,6 +538,15 @@
"license": "MIT",
"peer": true
},
"node_modules/slugify": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",

View File

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