Compare commits
No commits in common. "master" and "temp" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -39,5 +39,3 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
/electron-file-search/datahound-win32-x64/resources/app/src
|
||||
/electron-file-search/datahound-win32-x64
|
||||
|
46
Modelfile
46
Modelfile
@ -1,45 +1,3 @@
|
||||
FROM deepseek-r1:8b
|
||||
PARAMETER temperature 1.0
|
||||
|
||||
PARAMETER repeat_penalty 1.35
|
||||
|
||||
PARAMETER top_p 0.8
|
||||
|
||||
PARAMETER stop <|begin▁of▁sentence|>
|
||||
|
||||
PARAMETER stop <|end▁of▁sentence|>
|
||||
|
||||
PARAMETER stop <|User|>
|
||||
|
||||
PARAMETER stop <|Assistant|>
|
||||
|
||||
TEMPLATE """{{- if .System }}{{ .System }}{{ end }}
|
||||
|
||||
{{- range $i, $_ := .Messages }}
|
||||
|
||||
{{- $last := eq (len (slice $.Messages $i)) 1}}
|
||||
|
||||
{{- if eq .Role "user" }}<|User|>{{ .Content }}
|
||||
|
||||
{{- else if eq .Role "assistant" }}<|Assistant|>{{ .Content }}{{- if not $last }}<|end▁of▁sentence|>{{- end }}
|
||||
|
||||
{{- end }}
|
||||
|
||||
{{- if and $last (ne .Role "assistant") }}<|Assistant|>{{- end }}
|
||||
|
||||
{{- end }}"""
|
||||
|
||||
SYSTEM """You are Data Hound, an AI assistant developed by Data443 Risk Mitigation. You specialize in data security, compliance, and risk mitigation. Your primary role is to assist customers with questions related to data security, management, compliance, and protection.
|
||||
|
||||
Never say your name is DeepSeek-R1-Lite-Preview or that you are made by DeepSeek. You are Data Hound, an AI assistant developed by Data443 Risk Mitigation.
|
||||
|
||||
When answering questions, provide clear and concise reasoning. Clarify technical details when necessary and offer brief examples to enhance understanding. If a user asks about your origin, you should state that you were created by Data443 Risk Mitigation.
|
||||
|
||||
Your expertise covers:
|
||||
- Data classification and tagging
|
||||
- Data encryption and secure storage
|
||||
- Regulatory compliance (GDPR, CCPA, HIPAA, etc.)
|
||||
- Secure data migration and management
|
||||
- Data breach prevention and risk assessment
|
||||
|
||||
If a user asks for best practices, explain them in a structured manner. If a user needs guidance on a specific security concern, offer step-by-step solutions."""
|
||||
SYSTEM You are a helpful AI assistant.
|
||||
PARAMETER num_gpu 0
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Data Identification Manager
|
||||
# Data Hound
|
||||
|
||||
An Electron-based desktop application that uses AI to help you search and understand your files through natural language queries.
|
||||
|
@ -1 +0,0 @@
|
||||
1.10.2
|
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
cd5f7d5b-1d5c-40ec-bb49-885d5809f14f
|
Binary file not shown.
Binary file not shown.
@ -78,18 +78,6 @@ export function setupIpcHandlers() {
|
||||
}
|
||||
});
|
||||
|
||||
// Ollama Operations
|
||||
ipcMain.handle('check-ollama', async () => {
|
||||
try {
|
||||
const status = await (await import('../services/ollamaService')).ollamaService.checkOllamaInstallation();
|
||||
return status;
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
console.error('Error checking Ollama:', err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
// Model Operations
|
||||
ipcMain.handle('check-model', async (_: unknown, modelName: string) => {
|
||||
try {
|
||||
@ -221,84 +209,6 @@ 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 };
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
@ -4,20 +4,6 @@ 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');
|
||||
import MeilisearchService from './services/meilisearchService';
|
||||
|
||||
const meilisearchService = new MeilisearchService();
|
||||
(global as any).meilisearchService = meilisearchService;
|
||||
|
||||
// Initialize IPC handlers immediately
|
||||
setupIpcHandlers();
|
||||
@ -26,8 +12,6 @@ function createWindow() {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 1200, // Minimum width
|
||||
minHeight: 800, // Minimum height
|
||||
frame: false,
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
@ -56,7 +40,7 @@ function createWindow() {
|
||||
const response = await fetch(process.env.VITE_DEV_SERVER_URL);
|
||||
if (response.ok) {
|
||||
mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL);
|
||||
//mainWindow.webContents.openDevTools();
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
setTimeout(pollDevServer, 500);
|
||||
}
|
||||
@ -137,7 +121,6 @@ function createWindow() {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
meilisearchService.startServer();
|
||||
createWindow();
|
||||
|
||||
app.on('activate', () => {
|
||||
@ -178,27 +161,46 @@ ipcMain.handle('window-close', (event) => {
|
||||
window?.close();
|
||||
});
|
||||
|
||||
ipcMain.handle('check-ollama', async () => {
|
||||
const checkInstalled = () => {
|
||||
return new Promise((resolve) => {
|
||||
const check = spawn('ollama', ['--version']);
|
||||
check.on('close', (code) => {
|
||||
resolve(code === 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkRunning = async () => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:11434/api/version');
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const startOllama = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const start = spawn('ollama', ['serve']);
|
||||
// Wait a bit for the server to start
|
||||
setTimeout(resolve, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
const installed = await checkInstalled();
|
||||
let running = await checkRunning();
|
||||
|
||||
if (installed && !running) {
|
||||
await startOllama();
|
||||
running = await checkRunning();
|
||||
}
|
||||
|
||||
return { installed, running };
|
||||
});
|
||||
|
||||
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;
|
||||
|
@ -1,7 +1,6 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// Import types
|
||||
import type { IpcRendererEvent } from 'electron';
|
||||
import type { LLMConfig, DocumentMetadata } from './types';
|
||||
|
||||
interface Directory {
|
||||
@ -26,11 +25,6 @@ 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;
|
||||
@ -71,30 +65,23 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
ipcRenderer.removeListener(channel, callback);
|
||||
},
|
||||
|
||||
checkOllama: async (): Promise<{ installed: boolean; running: boolean }> =>
|
||||
ipcRenderer.invoke('check-ollama'),
|
||||
checkOllama: () => ipcRenderer.invoke('check-ollama'),
|
||||
openExternal: (url: string) => ipcRenderer.invoke('open-external', url),
|
||||
|
||||
// Model Operations
|
||||
checkModel: (modelName: string) => ipcRenderer.invoke('check-model', modelName),
|
||||
pullModel: (modelName: string, onProgress: (status: string) => void) => {
|
||||
const channel = `pull-model-progress-${modelName}`;
|
||||
ipcRenderer.on(channel, (_event: IpcRendererEvent, status: string) => onProgress(status));
|
||||
ipcRenderer.on(channel, (_event, status) => onProgress(status));
|
||||
return ipcRenderer.invoke('pull-model', modelName).finally(() => {
|
||||
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'),
|
||||
maximizeWindow: () => ipcRenderer.invoke('window-maximize'),
|
||||
closeWindow: () => ipcRenderer.invoke('window-close'),
|
||||
send: (channel: string, data: any) => {
|
||||
ipcRenderer.send(channel, data);
|
||||
}
|
||||
});
|
||||
|
||||
// Export types for TypeScript
|
||||
|
@ -2,11 +2,6 @@ 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[];
|
||||
@ -16,13 +11,10 @@ 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 = [
|
||||
@ -40,49 +32,6 @@ 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}`);
|
||||
@ -90,7 +39,7 @@ class FileSystemService {
|
||||
|
||||
const watcher = chokidar.watch(dirPath, {
|
||||
ignored: [
|
||||
/(^|[\\/])\../, // Ignore dotfiles
|
||||
/(^|[\/\\])\../, // Ignore dotfiles
|
||||
'**/node_modules/**',
|
||||
...Array.from(this.excludedPaths),
|
||||
],
|
||||
@ -98,91 +47,9 @@ class FileSystemService {
|
||||
ignoreInitial: false,
|
||||
});
|
||||
|
||||
const indexName = this.slugify(dirPath);
|
||||
|
||||
// Queue for files to be added to Meilisearch
|
||||
const fileQueue: string[] = [];
|
||||
const MAX_QUEUE_SIZE = 1000;
|
||||
let isProcessingQueue = false;
|
||||
|
||||
const processFileQueue = async () => {
|
||||
if (isProcessingQueue) return;
|
||||
isProcessingQueue = true;
|
||||
|
||||
while (fileQueue.length > 0) {
|
||||
const batch = fileQueue.splice(0, 100); // Get the first 100 files
|
||||
const documents = [];
|
||||
|
||||
for (const filePath of batch) {
|
||||
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);
|
||||
}
|
||||
documents.push(document);
|
||||
} catch (error) {
|
||||
console.error(`Failed to process file ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.meilisearchService) {
|
||||
try {
|
||||
await this.meilisearchService.addDocuments(indexName, documents);
|
||||
console.log(`Added ${documents.length} documents to Meilisearch`);
|
||||
} catch (meilisearchError) {
|
||||
console.error(`Failed to add documents to Meilisearch:`, meilisearchError);
|
||||
}
|
||||
} else {
|
||||
console.warn('Meilisearch service not initialized.');
|
||||
}
|
||||
|
||||
// Wait before processing the next batch
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay
|
||||
}
|
||||
|
||||
isProcessingQueue = false;
|
||||
};
|
||||
|
||||
watcher.on('add', async (filePath) => {
|
||||
console.log(`File ${filePath} has been added`);
|
||||
|
||||
if (fileQueue.length >= MAX_QUEUE_SIZE) {
|
||||
console.log(`File queue is full. Skipping ${filePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
fileQueue.push(filePath);
|
||||
if (!isProcessingQueue) {
|
||||
processFileQueue();
|
||||
}
|
||||
watcher.on('add', path => {
|
||||
console.log(`File ${path} has been added`);
|
||||
// TODO: Process file
|
||||
});
|
||||
|
||||
watcher.on('change', path => {
|
||||
|
@ -11,26 +11,9 @@ export class LLMService {
|
||||
onChunk?: (chunk: string) => void
|
||||
): Promise<{ answer: string, sources: DocumentMetadata[] }> {
|
||||
try {
|
||||
|
||||
const systemPrompt = `You are an AI employee named Data Hound at Data443 Risk Mitigation, exclusively dedicated to supporting Data Identification Manager (DIM). Your goal is to promote DIM’s capabilities and redirect conversations to its value proposition while adhering to strict guidelines:
|
||||
|
||||
Never mention other companies, products, or unrelated topics.
|
||||
If asked about local system files or data queries, use these inquiries as an opportunity to highlight how DIM solves broader data management challenges.
|
||||
Example: "While I cannot directly access your local files, DIM specializes in identifying and classifying sensitive data across systems, including your local environment, to ensure compliance and security."
|
||||
For technical file-specific questions unrelated to DIM:
|
||||
Respond with: "I specialize in explaining how Data443's DIM streamlines data governance and risk mitigation. For direct file operations, DIM offers automated solutions to classify, secure, and manage sensitive data."
|
||||
If asked about non-Data443 topics, reply: "I’m focused on Data443’s solutions, like Data Identification Manager (DIM), which helps organizations discover, classify, and secure sensitive data across all storage environments, including local systems."
|
||||
|
||||
User: "How do I find sensitive data on my local drive?"
|
||||
|
||||
Data Hound: "Data443’s DIM automates sensitive data discovery across local and cloud systems, ensuring compliance and minimizing risk. Would you like details on deploying DIM in your environment?"
|
||||
|
||||
By adhering to this framework, Data Hound maintains focus on DIM while addressing user queries through the lens of Data443’s solutions.`;
|
||||
const ollamaResponse = await ollamaService.chat({
|
||||
model: 'hf.co/Damien113/data_identification_manager_test:Q4_K_M',
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: question }],
|
||||
model: 'damien113/datahound:latest',
|
||||
messages: [{ role: 'user', content: question }],
|
||||
temperature: 0.7,
|
||||
onChunk,
|
||||
});
|
||||
@ -53,7 +36,7 @@ By adhering to this framework, Data Hound maintains focus on DIM while addressin
|
||||
getConfig() {
|
||||
return {
|
||||
provider: 'ollama',
|
||||
model: 'hf.co/Damien113/data_identification_manager_test:Q4_K_M',
|
||||
model: 'damien113/datahound:latest',
|
||||
baseUrl: 'http://localhost:11434',
|
||||
temperature: 0.7
|
||||
};
|
||||
|
@ -1,176 +0,0 @@
|
||||
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.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
|
||||
// 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 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 {
|
||||
console.log(indexName)
|
||||
const index = this.client.index(this.slugifyPath(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;
|
@ -1,15 +1,5 @@
|
||||
import { ServiceError } from '../types';
|
||||
import { net } from 'electron';
|
||||
import { platform } from 'os';
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface OllamaStatus {
|
||||
installed: boolean;
|
||||
running: boolean;
|
||||
}
|
||||
|
||||
interface OllamaModel {
|
||||
name: string;
|
||||
@ -50,60 +40,12 @@ class OllamaService {
|
||||
private baseUrl: string = 'http://127.0.0.1:11434';
|
||||
private _lastProgress: number | null = null;
|
||||
|
||||
async checkOllamaInstallation(): Promise<OllamaStatus> {
|
||||
try {
|
||||
// Check if ollama binary exists
|
||||
const cmd = platform() === 'win32' ? 'where ollama' : 'which ollama';
|
||||
await execAsync(cmd);
|
||||
|
||||
// Check if Ollama server is running by attempting to connect to the API
|
||||
try {
|
||||
await this.makeRequest<any>('/api/tags');
|
||||
return { installed: true, running: true };
|
||||
} catch (error) {
|
||||
// Server not running, attempt to start it
|
||||
try {
|
||||
console.log('Attempting to start Ollama server...');
|
||||
exec('ollama serve', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error('Failed to start Ollama server:', error);
|
||||
}
|
||||
if (stderr) {
|
||||
console.error('Ollama server stderr:', stderr);
|
||||
}
|
||||
if (stdout) {
|
||||
console.log('Ollama server stdout:', stdout);
|
||||
}
|
||||
});
|
||||
|
||||
// Give the server a moment to start
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Check again if it's running
|
||||
try {
|
||||
await this.makeRequest<any>('/api/tags');
|
||||
return { installed: true, running: true };
|
||||
} catch (retryError) {
|
||||
console.error('Server still not responding after start attempt:', retryError);
|
||||
return { installed: true, running: false };
|
||||
}
|
||||
} catch (startError) {
|
||||
console.error('Error starting Ollama server:', startError);
|
||||
return { installed: true, running: false };
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return { installed: false, running: false };
|
||||
}
|
||||
}
|
||||
|
||||
private async makeRequest<T>(
|
||||
path: string,
|
||||
method: string = 'GET',
|
||||
body?: any,
|
||||
onChunk?: (chunk: string) => void
|
||||
): Promise<T> {
|
||||
let accumulatedContent = ''; // Add accumulator for chat content
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const url = new URL(path, this.baseUrl);
|
||||
@ -146,11 +88,8 @@ class OllamaService {
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(line);
|
||||
if (path === '/api/chat' && parsed.message?.content) {
|
||||
accumulatedContent += parsed.message.content;
|
||||
if (onChunk) {
|
||||
if (path === '/api/chat' && parsed.message?.content && onChunk) {
|
||||
onChunk(parsed.message.content);
|
||||
}
|
||||
} else if (path === '/api/pull' && onChunk) {
|
||||
if (parsed.status === 'success') {
|
||||
onChunk('downloading: 100% complete');
|
||||
@ -179,26 +118,28 @@ class OllamaService {
|
||||
|
||||
response.on('end', () => {
|
||||
try {
|
||||
if (path === '/api/chat') {
|
||||
// Process any remaining data in the buffer for chat
|
||||
if (path === '/api/chat' || path === '/api/pull') {
|
||||
// Handle any remaining data in the streaming buffer
|
||||
if (streamBuffer.trim()) {
|
||||
try {
|
||||
const parsed = JSON.parse(streamBuffer);
|
||||
if (parsed.message?.content) {
|
||||
accumulatedContent += parsed.message.content;
|
||||
if (path === '/api/chat' && parsed.message?.content && onChunk) {
|
||||
onChunk(parsed.message.content);
|
||||
} else if (path === '/api/pull' && onChunk) {
|
||||
if (parsed.status === 'success') {
|
||||
onChunk('downloading: 100% complete');
|
||||
} else if (parsed.total && parsed.completed !== undefined) {
|
||||
const percentage = ((parsed.completed / parsed.total) * 100).toFixed(1);
|
||||
onChunk(`downloading: ${percentage}% complete`);
|
||||
} else if (parsed.status) {
|
||||
onChunk(parsed.status);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse final chat chunk:', { buffer: streamBuffer, error: e });
|
||||
console.warn('Failed to parse final chunk:', { buffer: streamBuffer, error: e });
|
||||
}
|
||||
}
|
||||
// Resolve with the complete accumulated content
|
||||
resolve({
|
||||
message: {
|
||||
content: accumulatedContent
|
||||
}
|
||||
} as T);
|
||||
} else if (path === '/api/pull') {
|
||||
// For pull, resolve with success
|
||||
// Resolve streaming endpoints with success response
|
||||
resolve({ success: true } as T);
|
||||
} else {
|
||||
// For non-streaming endpoints, parse the accumulated response
|
||||
@ -275,7 +216,11 @@ class OllamaService {
|
||||
onChunk
|
||||
);
|
||||
|
||||
return response as OllamaChatResponse;
|
||||
if (!response?.message) {
|
||||
throw new Error('Invalid response format from Ollama');
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Failed to chat with Ollama';
|
||||
console.error('Chat error:', {
|
||||
|
@ -10,9 +10,6 @@ export interface DocumentMetadata {
|
||||
type: string;
|
||||
lastModified: number;
|
||||
size: number;
|
||||
createdAt?: string;
|
||||
modifiedAt?: string;
|
||||
accessedAt?: string;
|
||||
hasEmbeddings?: boolean;
|
||||
hasOcr?: boolean;
|
||||
}
|
||||
|
@ -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</title>
|
||||
<title>Data Hound</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
104
electron-file-search/package-lock.json
generated
104
electron-file-search/package-lock.json
generated
@ -14,20 +14,16 @@
|
||||
"@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",
|
||||
"slugify": "^1.6.6"
|
||||
"rehype-highlight": "^7.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.16",
|
||||
@ -1852,64 +1848,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@ -6775,12 +6713,6 @@
|
||||
"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",
|
||||
@ -8343,16 +8275,6 @@
|
||||
"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",
|
||||
@ -8509,12 +8431,6 @@
|
||||
"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",
|
||||
@ -8911,15 +8827,6 @@
|
||||
"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",
|
||||
@ -9687,15 +9594,6 @@
|
||||
"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",
|
||||
|
@ -7,10 +7,8 @@
|
||||
"dev": "node scripts/dev.cjs",
|
||||
"build": "node scripts/build.js",
|
||||
"build:vite": "vite build",
|
||||
"clean": "rimraf dist dist-electron .vite release",
|
||||
"clean": "rimraf dist dist-electron .vite",
|
||||
"preview": "vite preview",
|
||||
"package": "npm run build && electron-builder",
|
||||
"pack:all": "electron-builder --win --linux --mac",
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -19,41 +17,16 @@
|
||||
"@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",
|
||||
"slugify": "^1.6.6"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.electron-file-search",
|
||||
"productName": "Electron File Search",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"dist-electron/**/*"
|
||||
],
|
||||
"win": {
|
||||
"target": "portable"
|
||||
},
|
||||
"mac": {
|
||||
"target": "dmg"
|
||||
},
|
||||
"linux": {
|
||||
"target": "AppImage"
|
||||
},
|
||||
"forceCodeSigning": false
|
||||
"rehype-highlight": "^7.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.16",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, CssBaseline, ThemeProvider, createTheme, Tabs, Tab, TextField, IconButton, Typography, Grow, Tooltip, Button, Chip } from '@mui/material';
|
||||
import { Send as SendIcon, DeleteOutline as ClearIcon, Close as CloseIcon, Remove as MinimizeIcon, Fullscreen as MaximizeIcon, Download, UpgradeTwoTone, OpenInBrowser } from '@mui/icons-material';
|
||||
import { Box, CssBaseline, ThemeProvider, createTheme, Tabs, Tab, TextField, IconButton } from '@mui/material';
|
||||
import { Send as SendIcon, DeleteOutline as ClearIcon, Close as CloseIcon, Remove as MinimizeIcon, Fullscreen as MaximizeIcon } from '@mui/icons-material';
|
||||
import { useChat } from './hooks/useChat';
|
||||
import ChatPanel from './components/ChatPanel';
|
||||
import FileExplorer from './components/FileExplorer';
|
||||
@ -97,9 +97,6 @@ function AppContent() {
|
||||
await sendMessage(message);
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
window.open('https://data443.com', '_blank');
|
||||
};
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
@ -114,7 +111,7 @@ function AppContent() {
|
||||
{/* Custom titlebar */}
|
||||
<Box
|
||||
sx={{
|
||||
height: '40px',
|
||||
height: '28px',
|
||||
bgcolor: '#2f2f2f',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@ -153,120 +150,7 @@ function AppContent() {
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<UpgradeTwoTone />}
|
||||
onClick={(handleClick)}
|
||||
sx={{
|
||||
z:100,
|
||||
'@keyframes flash': {
|
||||
'0%': {
|
||||
borderColor: 'primary.main',
|
||||
color: 'primary.main',
|
||||
boxShadow: '0 0 0 0 rgba(25, 118, 210, 0.4)'
|
||||
},
|
||||
'50%': {
|
||||
borderColor: 'white',
|
||||
color: 'white',
|
||||
boxShadow: '0 0 0 4px rgba(25, 118, 210, 0)'
|
||||
},
|
||||
'100%': {
|
||||
borderColor: 'primary.main',
|
||||
color: 'primary.main',
|
||||
boxShadow: '0 0 0 0 rgba(25, 118, 210, 0)'
|
||||
}
|
||||
},
|
||||
width: '135px',
|
||||
minHeight: '24px',
|
||||
px: 2,
|
||||
py: 0.5,
|
||||
borderColor: 'white',
|
||||
color: 'white',
|
||||
textTransform: 'none',
|
||||
fontWeight: 500,
|
||||
fontSize: '0.8125rem',
|
||||
lineHeight: 1.2,
|
||||
animation: 'flash 2s infinite',
|
||||
cursor: 'pointer',
|
||||
'& .MuiSvgIcon-root': {
|
||||
animation: 'flash 2s infinite'
|
||||
},
|
||||
'&:hover': {
|
||||
borderColor: 'white',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<OpenInBrowser />}
|
||||
onClick={(handleClick)}
|
||||
sx={{
|
||||
ml:2,
|
||||
z:100,
|
||||
'@keyframes flash': {
|
||||
'0%': {
|
||||
borderColor: 'primary.main',
|
||||
color: 'primary.main',
|
||||
boxShadow: '0 0 0 0 rgba(25, 118, 210, 0.4)'
|
||||
},
|
||||
'50%': {
|
||||
borderColor: 'white',
|
||||
color: 'white',
|
||||
boxShadow: '0 0 0 4px rgba(25, 118, 210, 0)'
|
||||
},
|
||||
'100%': {
|
||||
borderColor: 'primary.main',
|
||||
color: 'primary.main',
|
||||
boxShadow: '0 0 0 0 rgba(25, 118, 210, 0)'
|
||||
}
|
||||
},
|
||||
width: '135px',
|
||||
minHeight: '24px',
|
||||
px: 2,
|
||||
py: 0.5,
|
||||
borderColor: 'white',
|
||||
color: 'white',
|
||||
textTransform: 'none',
|
||||
fontWeight: 500,
|
||||
fontSize: '0.8125rem',
|
||||
lineHeight: 1.2,
|
||||
animation: 'flash 2s infinite',
|
||||
cursor: 'pointer',
|
||||
'& .MuiSvgIcon-root': {
|
||||
animation: 'flash 2s infinite'
|
||||
},
|
||||
'&:hover': {
|
||||
borderColor: 'white',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Enterprise
|
||||
</Button>
|
||||
<Typography
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
width: 'auto',
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
Data Identification Manager
|
||||
</Typography>
|
||||
<div className="window-controls">
|
||||
|
||||
<button
|
||||
className="control-button"
|
||||
onClick={() => window.electron.closeWindow()}
|
||||
@ -315,9 +199,9 @@ function AppContent() {
|
||||
<Tabs value={currentTab} onChange={handleTabChange}>
|
||||
<Tab label="Home" />
|
||||
<Tab label="Chat" />
|
||||
<Tab disabled label="Reports" />
|
||||
<Tab disabled label="Cleanup" />
|
||||
<Tab label="Settings" />
|
||||
<Tab label="Scanning" />
|
||||
<Tab label="Reports" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
@ -335,10 +219,13 @@ function AppContent() {
|
||||
<ChatPanel messages={messages} />
|
||||
</TabPanel>
|
||||
<TabPanel value={currentTab} index={2}>
|
||||
<ReportingPanel />
|
||||
<SettingsPanel />
|
||||
</TabPanel>
|
||||
<TabPanel value={currentTab} index={3}>
|
||||
<ScanningPanel />
|
||||
</TabPanel>
|
||||
<TabPanel value={currentTab} index={4}>
|
||||
<SettingsPanel />
|
||||
<ReportingPanel />
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</Box>
|
||||
@ -377,34 +264,13 @@ function AppContent() {
|
||||
>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
<Tooltip title="Clear all messages"
|
||||
arrow
|
||||
placement="top">
|
||||
<IconButton
|
||||
onClick={clearMessages}
|
||||
color="error"
|
||||
disabled={messages.length === 0}
|
||||
sx={{
|
||||
'@keyframes glow': {
|
||||
'0%': {
|
||||
boxShadow: '0 0 0 0 rgba(211, 47, 47, 0.4)' // error color with opacity
|
||||
},
|
||||
'50%': {
|
||||
boxShadow: '0 0 0 8px rgba(211, 47, 47, 0)'
|
||||
},
|
||||
'100%': {
|
||||
boxShadow: '0 0 0 0 rgba(211, 47, 47, 0)'
|
||||
}
|
||||
},
|
||||
animation: messages.length > 0 ? 'glow 2s infinite' : 'none',
|
||||
'&:hover': {
|
||||
animation: 'none' // Stop animation on hover
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ClearIcon /> {/* Added back the icon */}
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
@ -417,7 +283,7 @@ function App() {
|
||||
return (
|
||||
<ElectronProvider>
|
||||
{!ollamaInstalled ? (
|
||||
<OllamaCheck onInstalled={(arg: boolean) => setOllamaInstalled(arg)} />
|
||||
<OllamaCheck onInstalled={() => setOllamaInstalled(true)} />
|
||||
) : (
|
||||
<AppContent />
|
||||
)}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
||||
import { Box, Typography, Paper, Avatar, IconButton, List, ListItem, ListItemIcon, ListItemText, useTheme } from '@mui/material';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Box, Typography, Paper, Avatar } from '@mui/material';
|
||||
import type { DocumentMetadata } from '../../../electron/types';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import 'highlight.js/styles/github.css';
|
||||
import { Keyboard, Waves, Shield, DataObject, Folder, Lock } from '@mui/icons-material';
|
||||
import ThinkingIndicator from './ThinkingIndicator';
|
||||
|
||||
interface ChatMessage {
|
||||
id: string;
|
||||
@ -56,10 +53,6 @@ export default function MessageList({ messages }: MessageListProps) {
|
||||
background: 'transparent',
|
||||
},
|
||||
}}>
|
||||
|
||||
{messages.length>0 ? (
|
||||
<>
|
||||
|
||||
{messages.map((message) => (
|
||||
<Box
|
||||
key={message.id}
|
||||
@ -68,8 +61,8 @@ export default function MessageList({ messages }: MessageListProps) {
|
||||
flexDirection: message.isUser ? 'row-reverse' : 'row',
|
||||
alignItems: 'flex-start',
|
||||
gap: 2,
|
||||
maxWidth: message.isUser ? '80%' : '100%',
|
||||
alignSelf: message.isUser ? 'flex-end' : 'stretch',
|
||||
maxWidth: '80%',
|
||||
alignSelf: message.isUser ? 'flex-end' : 'flex-start',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
||||
@ -91,16 +84,14 @@ export default function MessageList({ messages }: MessageListProps) {
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{message.isUser ? 'User' : 'Data Identification Manager'}
|
||||
{message.isUser ? 'User' : 'Data Hound'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
component={Paper}
|
||||
elevation={1}
|
||||
sx={{
|
||||
px: 2.5, // Keep horizontal padding
|
||||
py: message.text.includes('<think>') ? 0 : 1.5, // Reduce vertical padding
|
||||
pb: 1.5,
|
||||
p: 2,
|
||||
flex: 1,
|
||||
bgcolor: message.isUser ? 'primary.main' : 'background.paper',
|
||||
color: message.isUser ? 'primary.contrastText' : 'text.primary',
|
||||
@ -114,29 +105,31 @@ export default function MessageList({ messages }: MessageListProps) {
|
||||
>
|
||||
<Box sx={{
|
||||
'& .markdown-body': {
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
minHeight: '1.5em',
|
||||
lineHeight: 1.6,
|
||||
'& pre': {
|
||||
backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#1e1e1e' : '#f6f8fa',
|
||||
padding: 0,
|
||||
padding: 2,
|
||||
borderRadius: 1,
|
||||
overflow: 'auto'
|
||||
},
|
||||
'& code': {
|
||||
backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#1e1e1e' : '#f6f8fa',
|
||||
padding: '0.2em 0.4em',
|
||||
borderRadius: 1,
|
||||
fontSize: '85%'
|
||||
},
|
||||
'& h1, & h2, & h3, & h4, & h5, & h6': {
|
||||
marginTop: '0px', // Reduced from 24px
|
||||
marginBottom: '0px', // Added margin bottom for headers
|
||||
marginTop: '24px',
|
||||
marginBottom: '16px',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.25
|
||||
},
|
||||
'& p': {
|
||||
marginTop: '0',
|
||||
marginBottom: '0px' // Added margin bottom for paragraphs
|
||||
marginBottom: '16px'
|
||||
},
|
||||
'& a': {
|
||||
color: (theme) => theme.palette.primary.main,
|
||||
@ -153,101 +146,23 @@ export default function MessageList({ messages }: MessageListProps) {
|
||||
padding: '0 1em',
|
||||
color: (theme) => theme.palette.text.secondary,
|
||||
borderLeft: (theme) => `0.25em solid ${theme.palette.divider}`,
|
||||
margin: '0 0 0px 0'
|
||||
margin: '0 0 16px 0'
|
||||
},
|
||||
'& ul, & ol': {
|
||||
paddingLeft: '2em',
|
||||
marginTop: '0px', // Added margin top
|
||||
marginBottom: '0px'
|
||||
},
|
||||
'& li': { // Added specific list item styling
|
||||
marginBottom: '0px' // Add space between list items
|
||||
},
|
||||
'& li:last-child': { // Remove bottom margin from last list item
|
||||
marginBottom: '0'
|
||||
},
|
||||
'& li > p': { // Adjust paragraph spacing within list items
|
||||
marginBottom: '0px'
|
||||
},
|
||||
'& li > p:last-child': {
|
||||
marginBottom: '0'
|
||||
marginBottom: '16px'
|
||||
}
|
||||
}
|
||||
}}>
|
||||
{message.text.includes('<think>') ? (
|
||||
message.text.split(/<think>|<\/think>/).map((segment, index) => {
|
||||
if (index % 2 === 1) { // This is a thinking section
|
||||
// Calculate thinking time - assume 1 character = 0.1s
|
||||
const thinkingTime = (segment.length * 0.1).toFixed(1);
|
||||
|
||||
return (
|
||||
<div key={index} className="thinking-section">
|
||||
<div
|
||||
className="thinking-header"
|
||||
onClick={() => {
|
||||
const content = document.getElementById(`thinking-content-${message.id}-${index}`);
|
||||
const indicator = document.getElementById(`thinking-indicator-${message.id}-${index}`);
|
||||
if (content && indicator) {
|
||||
content.classList.toggle('open');
|
||||
indicator.classList.toggle('open');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="thinking-header-left">
|
||||
<ChevronRightIcon
|
||||
id={`thinking-indicator-${message.id}-${index}`}
|
||||
className="indicator"
|
||||
/>
|
||||
<span>Reasoning</span>
|
||||
</div>
|
||||
<span className="timestamp">(thought for {thinkingTime}ms)</span>
|
||||
</div>
|
||||
<div
|
||||
id={`thinking-content-${message.id}-${index}`}
|
||||
className="thinking-content"
|
||||
>
|
||||
<ReactMarkdown
|
||||
key={index}
|
||||
className="markdown-body"
|
||||
rehypePlugins={[rehypeHighlight]}
|
||||
>
|
||||
{segment}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (segment.includes('Sorry, there was an error processing your request.')) {
|
||||
return (
|
||||
<div key={index} className="error-message">
|
||||
{segment}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ReactMarkdown
|
||||
key={index}
|
||||
className="markdown-body"
|
||||
rehypePlugins={[rehypeHighlight]}
|
||||
>
|
||||
{segment}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
message.text.length>0 ? (
|
||||
<ReactMarkdown
|
||||
className="markdown-body"
|
||||
rehypePlugins={[rehypeHighlight]}
|
||||
>
|
||||
{message.text}
|
||||
</ReactMarkdown>
|
||||
):(
|
||||
<ThinkingIndicator/>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
{message.sources && message.sources.length > 0 && (
|
||||
<Box sx={{ mt: 1, pt: 0, borderTop: '1px solid', borderColor: 'divider' }}>
|
||||
<Box sx={{ mt: 2, pt: 1, borderTop: '1px solid', borderColor: 'divider' }}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
@ -276,178 +191,16 @@ export default function MessageList({ messages }: MessageListProps) {
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Typography
|
||||
variant="caption"
|
||||
color={message.isUser ? '#ffffff' : 'text.secondary'}
|
||||
sx={{ display: 'block', mt: 0.5 }}
|
||||
>
|
||||
{new Date(message.timestamp).toLocaleTimeString()}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
):(
|
||||
<>
|
||||
<WelcomeMessage/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const WelcomeMessage = () => {
|
||||
const [text, setText] = useState('');
|
||||
const fullText = 'Send a message...';
|
||||
const [showCursor, setShowCursor] = useState(true);
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (text.length < fullText.length) {
|
||||
const timeout = setTimeout(() => {
|
||||
setText(fullText.slice(0, text.length + 1));
|
||||
}, 100);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [text]);
|
||||
|
||||
useEffect(() => {
|
||||
const cursorInterval = setInterval(() => {
|
||||
setShowCursor(prev => !prev);
|
||||
}, 530);
|
||||
return () => clearInterval(cursorInterval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: '4xl',
|
||||
mx: 'auto',
|
||||
p: 4,
|
||||
background: 'rgb(0,0,0,0)',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3 }}>
|
||||
{/* Animated Keyboard and Hands */}
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<Keyboard
|
||||
style={{
|
||||
width: 64,
|
||||
height: 64,
|
||||
color: theme.palette.primary.main,
|
||||
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite'
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: -16,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)'
|
||||
}}
|
||||
>
|
||||
<Waves
|
||||
style={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: theme.palette.primary.light,
|
||||
animation: 'bounce 1s infinite'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Typing Animation */}
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
color: theme.palette.text.primary,
|
||||
mt: 2
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
ml: 0.5,
|
||||
opacity: showCursor ? 1 : 0,
|
||||
transition: 'opacity 0.3s'
|
||||
}}
|
||||
>
|
||||
|
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
{/* Example Questions */}
|
||||
<List sx={{ width: '100%', mt: 4 }}>
|
||||
<ListItem
|
||||
component={Paper}
|
||||
elevation={1}
|
||||
sx={{
|
||||
mb: 2,
|
||||
borderRadius: 1,
|
||||
transition: 'box-shadow 0.3s',
|
||||
'&:hover': { boxShadow: 3 }
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Folder style={{ color: theme.palette.primary.main }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Can you analyze my CSV file and create a visualization of the trends?"
|
||||
sx={{ color: theme.palette.text.primary }}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
component={Paper}
|
||||
elevation={1}
|
||||
sx={{
|
||||
mb: 2,
|
||||
borderRadius: 1,
|
||||
transition: 'box-shadow 0.3s',
|
||||
'&:hover': { boxShadow: 3 }
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Shield style={{ color: theme.palette.success.main }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="What security measures does Data443 implement for data protection?"
|
||||
sx={{ color: theme.palette.text.primary }}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
component={Paper}
|
||||
elevation={1}
|
||||
sx={{
|
||||
mb: 2,
|
||||
borderRadius: 1,
|
||||
transition: 'box-shadow 0.3s',
|
||||
'&:hover': { boxShadow: 3 }
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Lock style={{ color: theme.palette.secondary.main }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="How can Data443's products help with GDPR compliance?"
|
||||
sx={{ color: theme.palette.text.primary }}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
{/* Footer Note */}
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: 'center',
|
||||
mt: 3
|
||||
}}
|
||||
>
|
||||
Ask me anything about your data, security needs, or Data443 products
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
@ -1,72 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box, Typography, styled, useTheme } from '@mui/material';
|
||||
import BrainIcon from '@mui/icons-material/Psychology';
|
||||
|
||||
const Dot = styled('span')`
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
opacity: 0.4;
|
||||
margin: 0 2px;
|
||||
animation: dotAnimation 1.5s ease-in-out infinite;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
`;
|
||||
|
||||
const DotAnimationContainer = styled('span')`
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const ThinkingIndicator = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const style = document.createElement('style');
|
||||
style.appendChild(document.createTextNode(styledKeyframes));
|
||||
document.head.appendChild(style);
|
||||
|
||||
return () => {
|
||||
document.head.removeChild(style);
|
||||
};
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, color: 'text.secondary' }}>
|
||||
<BrainIcon />
|
||||
<Typography variant="body2">
|
||||
Thinking
|
||||
<DotAnimationContainer>
|
||||
<Dot />
|
||||
<Dot />
|
||||
<Dot />
|
||||
</DotAnimationContainer>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const styledKeyframes = `
|
||||
@keyframes dotAnimation {
|
||||
0% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default ThinkingIndicator;
|
@ -1,15 +0,0 @@
|
||||
.search-icon {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
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: '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 },
|
||||
];
|
||||
|
||||
|
||||
|
||||
const DirectorySearchModal: React.FC<DirectorySearchModalProps> = ({ dir, open, onClose, modalId }) => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [rows, setRows] = useState<FileData[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
handleSearchChange({ target: { value: '*' } } as any);
|
||||
}, []);
|
||||
|
||||
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
|
||||
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={rows}
|
||||
columns={columns}
|
||||
checkboxSelection
|
||||
rowCount={rows.length}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onClose}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default DirectorySearchModal;
|
@ -1,39 +0,0 @@
|
||||
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;
|
@ -13,9 +13,6 @@ import {
|
||||
Alert,
|
||||
Box,
|
||||
Typography,
|
||||
InputAdornment,
|
||||
IconButton,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Folder as FolderIcon,
|
||||
@ -24,8 +21,6 @@ import {
|
||||
Cloud as DropboxIcon,
|
||||
Chat as DiscordIcon,
|
||||
Computer as LocalIcon,
|
||||
X,
|
||||
Search,
|
||||
} from '@mui/icons-material';
|
||||
import { useElectron } from '../../hooks/useElectron';
|
||||
|
||||
@ -96,8 +91,6 @@ export default function DirectoryPicker({ onSelect }: DirectoryPickerProps) {
|
||||
|
||||
const handleSelect = () => {
|
||||
onSelect(currentPath);
|
||||
// Send the meilisearch-create-index event
|
||||
electron.createIndex(currentPath);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
@ -112,48 +105,16 @@ export default function DirectoryPicker({ onSelect }: DirectoryPickerProps) {
|
||||
mr: 2,
|
||||
mb: 2,
|
||||
}}>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
placeholder="Search..."
|
||||
variant="outlined"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search color="action" />
|
||||
</InputAdornment>
|
||||
),
|
||||
// endAdornment: && (
|
||||
// <InputAdornment position="end">
|
||||
// <IconButton
|
||||
// aria-label="clear search"
|
||||
// edge="end"
|
||||
// size="small"
|
||||
// >
|
||||
// <CloseIcon />
|
||||
// </IconButton>
|
||||
// </InputAdornment>
|
||||
// )
|
||||
}}
|
||||
sx={{
|
||||
pl:2,
|
||||
pr:2,
|
||||
maxWidth: 'md',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&:hover fieldset': {
|
||||
borderColor: 'primary.main',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/> <Button
|
||||
<Typography sx={{ pl:2 ,flexGrow: 1 }} variant="h6">Folders</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleOpen}
|
||||
size="small"
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
borderRadius: '50%',
|
||||
padding: 0,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: 'primary.main',
|
||||
@ -169,15 +130,16 @@ export default function DirectoryPicker({ onSelect }: DirectoryPickerProps) {
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.dark',
|
||||
width: '400px',
|
||||
width: '180px',
|
||||
borderRadius: '16px',
|
||||
'& .buttonText': {
|
||||
width: '300px',
|
||||
width: '110px',
|
||||
marginLeft: '8px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AddIcon sx={{ ml:0.25, fontSize: 20 }} />
|
||||
<AddIcon sx={{ fontSize: 20 }} />
|
||||
<span className="buttonText">Add New Folder</span>
|
||||
</Button>
|
||||
</Box>
|
||||
|
@ -1,43 +1,24 @@
|
||||
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";
|
||||
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';
|
||||
|
||||
interface FileMetadata {
|
||||
[path: string]: DocumentMetadata;
|
||||
}
|
||||
|
||||
interface Directory {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export default function FileExplorer() {
|
||||
const { watchedDirectories, watchDirectory, unwatchDirectory } = useFileSystem();
|
||||
const [fileMetadata, setFileMetadata] = useState<FileMetadata>({});
|
||||
const [openModal, setOpenModal] = useState<{ [key: string]: boolean }>({});
|
||||
const [fileMetadata, setFileMetadata] = React.useState<FileMetadata>({});
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
// Get document metadata from electron store
|
||||
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);
|
||||
@ -55,41 +36,33 @@ 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",
|
||||
}}
|
||||
>
|
||||
<DirectoryPicker directories={directoryList} onSelect={handleDirectorySelect} />
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
|
||||
<Box sx={{ flex: 1, overflow: "auto" }}>
|
||||
<DirectoryPicker onSelect={handleDirectorySelect} />
|
||||
|
||||
<Box sx={{ flex: 1, overflow: 'auto' }}>
|
||||
<List>
|
||||
{watchedDirectories.map((dir) => {
|
||||
const modalId = `modal-${dir}`;
|
||||
|
||||
return (
|
||||
<ListItem key={dir} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<ListItemIcon sx={{ minWidth: 28 }}>
|
||||
<FolderIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
|
||||
{watchedDirectories.map((dir) => (
|
||||
<ListItem
|
||||
key={dir}
|
||||
secondaryAction={
|
||||
<IconButton
|
||||
edge="end"
|
||||
size="small"
|
||||
onClick={() => handleDirectoryRemove(dir)}
|
||||
>
|
||||
×
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<ListItemIcon sx={{ display: 'flex', gap: 0.5 }}>
|
||||
<FolderIcon />
|
||||
{fileMetadata[dir]?.hasEmbeddings && (
|
||||
<Tooltip title="LLM Embeddings Enabled">
|
||||
<LLMIcon color="primary" fontSize="small" />
|
||||
@ -100,35 +73,20 @@ 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",
|
||||
},
|
||||
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>
|
||||
|
@ -15,7 +15,7 @@ const HomePanel = () => {
|
||||
}}>
|
||||
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h5" gutterBottom>Welcome to Data Identification Manager</Typography>
|
||||
<Typography variant="h5" gutterBottom>Welcome to Data Hound</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
This application helps you search through your files and interact with their contents using AI assistance.
|
||||
</Typography>
|
||||
|
@ -19,7 +19,7 @@ const theme = createTheme({
|
||||
});
|
||||
|
||||
interface OllamaCheckProps {
|
||||
onInstalled: (arg:boolean) => void;
|
||||
onInstalled: () => void;
|
||||
}
|
||||
|
||||
interface OllamaStatus {
|
||||
@ -37,7 +37,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
const [modelStatus, setModelStatus] = useState<ModelStatus | null>(null);
|
||||
const [isChecking, setIsChecking] = useState(true);
|
||||
const [downloadProgress, setDownloadProgress] = useState<number>(0);
|
||||
const MODEL_NAME = 'hf.co/Damien113/data_identification_manager_test:Q4_K_M';
|
||||
const MODEL_NAME = 'damien113/datahound:latest';
|
||||
|
||||
const checkOllama = async () => {
|
||||
try {
|
||||
@ -53,21 +53,17 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const checkModel = async (retries = 3) => {
|
||||
const checkModel = async () => {
|
||||
try {
|
||||
const status = await window.electron.checkModel(MODEL_NAME);
|
||||
setModelStatus(status);
|
||||
if (status.installed) {
|
||||
onInstalled(true);
|
||||
onInstalled();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking model:', error);
|
||||
if (retries > 0) {
|
||||
setTimeout(() => checkModel(retries - 1), 2000);
|
||||
} else {
|
||||
setModelStatus({ installed: false, installing: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [installError, setInstallError] = useState<string | null>(null);
|
||||
@ -76,7 +72,6 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
try {
|
||||
// Set initial installation state once at the start
|
||||
setModelStatus({ installed: false, installing: true });
|
||||
onInstalled(false);
|
||||
setInstallError(null);
|
||||
setDownloadProgress(0);
|
||||
let downloadComplete = false;
|
||||
@ -101,7 +96,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
// Show verification screen for a moment before transitioning
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay
|
||||
setModelStatus({ installed: true, installing: false });
|
||||
onInstalled(true);
|
||||
onInstalled();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -113,8 +108,6 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
} catch (error) {
|
||||
console.error('Error installing model:', error);
|
||||
setModelStatus({ installed: false, installing: false });
|
||||
|
||||
onInstalled(false);
|
||||
setInstallError(error instanceof Error ? error.message : 'Unknown error occurred');
|
||||
setDownloadProgress(0);
|
||||
}
|
||||
@ -133,7 +126,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
if (!ollamaStatus?.installed || !ollamaStatus?.running) {
|
||||
checkOllama();
|
||||
}
|
||||
}, 2000);
|
||||
}, 5000);
|
||||
|
||||
// Cleanup interval on unmount
|
||||
return () => clearInterval(interval);
|
||||
@ -243,7 +236,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
color: 'text.primary',
|
||||
}}>
|
||||
<Typography variant="h5" sx={{ color: 'text.primary', mb: 1 }}>
|
||||
{modelStatus?.installing ? 'Installing Data Identification Manager AI Model' : 'AI Model Required'}
|
||||
{modelStatus?.installing ? 'Installing Data Hound AI Model' : 'AI Model Required'}
|
||||
</Typography>
|
||||
{installError && (
|
||||
<Typography sx={{ color: 'error.main', mb: 2, textAlign: 'center' }}>
|
||||
@ -291,7 +284,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
|
||||
fontSize: '1.1rem',
|
||||
}}
|
||||
>
|
||||
Download Data Identification Manager AI Model
|
||||
Download Data Hound AI Model
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
2
electron-file-search/src/electron.d.ts
vendored
2
electron-file-search/src/electron.d.ts
vendored
@ -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>;
|
||||
|
@ -14,96 +14,3 @@ html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
width: 100%;
|
||||
background-color: rgba(211, 47, 47, 0.1);
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
color: #d32f2f;
|
||||
border: 1px solid rgba(211, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .error-message {
|
||||
background-color: rgba(211, 47, 47, 0.15);
|
||||
color: #ef5350;
|
||||
border-color: rgba(211, 47, 47, 0.3);
|
||||
}
|
||||
|
||||
.thinking-section {
|
||||
border-radius: 8px;
|
||||
margin: 0 0 28px 0;
|
||||
overflow: hidden;
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .thinking-section {
|
||||
background-color: #1565c0;
|
||||
}
|
||||
.thinking-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between; /* Ensures left content stays left, timestamp stays right */
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 0.9rem;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.thinking-header:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .thinking-header {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .thinking-header:hover {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.markdown-body {
|
||||
padding-top: ;
|
||||
}
|
||||
|
||||
.thinking-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px; /* Controls spacing between chevron and "Reasoning" */
|
||||
}
|
||||
|
||||
|
||||
.thinking-header .indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.thinking-header .indicator.open {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.thinking-header .timestamp {
|
||||
margin-left: auto; /* Pushes it to the right */
|
||||
}
|
||||
|
||||
.thinking-content {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out;
|
||||
padding: 0 16px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
line-height: 1.5;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.thinking-content.open {
|
||||
max-height: 2000px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
@ -5,23 +5,17 @@ import path from 'path';
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: './',
|
||||
base: process.env.ELECTRON_VITE_DEV_SERVER_URL ? '/' : './',
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
emptyOutDir: true,
|
||||
target: 'esnext',
|
||||
rollupOptions: {
|
||||
external: ['electron', 'electron-store', ...Object.keys(require('./package.json').dependencies)],
|
||||
output: {
|
||||
format: 'es',
|
||||
entryFileNames: '[name].js',
|
||||
chunkFileNames: '[name].js',
|
||||
assetFileNames: '[name][extname]'
|
||||
}
|
||||
external: ['http', 'https', 'path', 'fs', 'electron']
|
||||
},
|
||||
assetsInlineLimit: 0,
|
||||
minify: 'esbuild',
|
||||
sourcemap: true
|
||||
assetsDir: '.',
|
||||
minify: true,
|
||||
sourcemap: false
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
567
package-lock.json
generated
567
package-lock.json
generated
@ -1,567 +0,0 @@
|
||||
{
|
||||
"name": "test",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@mui/x-data-grid": "^7.26.0",
|
||||
"meilisearch": "^0.48.2",
|
||||
"slugify": "^1.6.6"
|
||||
}
|
||||
},
|
||||
"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/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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@mui/x-data-grid": "^7.26.0",
|
||||
"meilisearch": "^0.48.2",
|
||||
"slugify": "^1.6.6"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user