This commit is contained in:
Damien 2025-02-05 10:05:22 -05:00
parent 2a3e60a16c
commit 0244ab851e
14 changed files with 504 additions and 112 deletions

View File

@ -1,3 +1,3 @@
FROM deepseek-r1:8b FROM deepseek-r1:1.5b
SYSTEM You are a helpful AI assistant. SYSTEM You are a helpful AI assistant.
PARAMETER num_gpu 0 PARAMETER num_gpu 0

View File

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

View File

@ -12,6 +12,8 @@ function createWindow() {
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
width: 1200, width: 1200,
height: 800, height: 800,
minWidth: 1200, // Minimum width
minHeight: 800, // Minimum height
frame: false, frame: false,
titleBarStyle: 'hidden', titleBarStyle: 'hidden',
webPreferences: { webPreferences: {
@ -40,7 +42,7 @@ function createWindow() {
const response = await fetch(process.env.VITE_DEV_SERVER_URL); const response = await fetch(process.env.VITE_DEV_SERVER_URL);
if (response.ok) { if (response.ok) {
mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL); mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL);
mainWindow.webContents.openDevTools(); //mainWindow.webContents.openDevTools();
} else { } else {
setTimeout(pollDevServer, 500); setTimeout(pollDevServer, 500);
} }

View File

@ -1,6 +1,7 @@
const { contextBridge, ipcRenderer } = require('electron'); const { contextBridge, ipcRenderer } = require('electron');
// Import types // Import types
import type { IpcRendererEvent } from 'electron';
import type { LLMConfig, DocumentMetadata } from './types'; import type { LLMConfig, DocumentMetadata } from './types';
interface Directory { interface Directory {
@ -73,7 +74,7 @@ contextBridge.exposeInMainWorld('electron', {
checkModel: (modelName: string) => ipcRenderer.invoke('check-model', modelName), checkModel: (modelName: string) => ipcRenderer.invoke('check-model', modelName),
pullModel: (modelName: string, onProgress: (status: string) => void) => { pullModel: (modelName: string, onProgress: (status: string) => void) => {
const channel = `pull-model-progress-${modelName}`; const channel = `pull-model-progress-${modelName}`;
ipcRenderer.on(channel, (_event, status) => onProgress(status)); ipcRenderer.on(channel, (_event: IpcRendererEvent, status: string) => onProgress(status));
return ipcRenderer.invoke('pull-model', modelName).finally(() => { return ipcRenderer.invoke('pull-model', modelName).finally(() => {
ipcRenderer.removeListener(channel, onProgress); ipcRenderer.removeListener(channel, onProgress);
}); });

View File

@ -12,7 +12,7 @@ export class LLMService {
): Promise<{ answer: string, sources: DocumentMetadata[] }> { ): Promise<{ answer: string, sources: DocumentMetadata[] }> {
try { try {
const ollamaResponse = await ollamaService.chat({ const ollamaResponse = await ollamaService.chat({
model: 'damien113/datahound:latest', model: 'damien113/datahound-gpu:8b',
messages: [{ role: 'user', content: question }], messages: [{ role: 'user', content: question }],
temperature: 0.7, temperature: 0.7,
onChunk, onChunk,
@ -36,7 +36,7 @@ export class LLMService {
getConfig() { getConfig() {
return { return {
provider: 'ollama', provider: 'ollama',
model: 'damien113/datahound:latest', model: 'damien113/datahound-gpu:8b',
baseUrl: 'http://localhost:11434', baseUrl: 'http://localhost:11434',
temperature: 0.7 temperature: 0.7
}; };

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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:;" /> <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 Hound</title> <title>Data Identification Manager : Personal Edition</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -41,14 +41,15 @@
"dist-electron/**/*" "dist-electron/**/*"
], ],
"win": { "win": {
"target": "nsis" "target": "portable"
}, },
"mac": { "mac": {
"target": "dmg" "target": "dmg"
}, },
"linux": { "linux": {
"target": "AppImage" "target": "AppImage"
} },
"forceCodeSigning": false
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.16", "@types/node": "^20.11.16",

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Box, CssBaseline, ThemeProvider, createTheme, Tabs, Tab, TextField, IconButton } from '@mui/material'; 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 } from '@mui/icons-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 { useChat } from './hooks/useChat'; import { useChat } from './hooks/useChat';
import ChatPanel from './components/ChatPanel'; import ChatPanel from './components/ChatPanel';
import FileExplorer from './components/FileExplorer'; import FileExplorer from './components/FileExplorer';
@ -97,6 +97,9 @@ function AppContent() {
await sendMessage(message); await sendMessage(message);
}; };
const handleClick = () => {
window.open('https://data443.com', '_blank');
};
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
@ -111,7 +114,7 @@ function AppContent() {
{/* Custom titlebar */} {/* Custom titlebar */}
<Box <Box
sx={{ sx={{
height: '28px', height: '40px',
bgcolor: '#2f2f2f', bgcolor: '#2f2f2f',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -150,7 +153,133 @@ function AppContent() {
}, },
}} }}
> >
<div className="window-controls">
<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">
<Chip
label="Personal Edition (Free)"
variant="outlined"
size="small"
sx={{
bgcolor: 'primary.main', // or 'secondary.main', 'error.main', etc.
color: 'primary.contrastText',
borderRadius: 1,
height: 'auto',
'& .MuiChip-label': {
padding: '4px 8px',
}
}}
/>
<button <button
className="control-button" className="control-button"
onClick={() => window.electron.closeWindow()} onClick={() => window.electron.closeWindow()}
@ -200,8 +329,9 @@ function AppContent() {
<Tab label="Home" /> <Tab label="Home" />
<Tab label="Chat" /> <Tab label="Chat" />
<Tab label="Settings" /> <Tab label="Settings" />
<Tab label="Scanning" /> <Tab disabled label="Scanning" />
<Tab label="Reports" /> <Tab disabled label="Reports" />
<Tab disabled label="Cleanup" />
</Tabs> </Tabs>
</Box> </Box>
<Box sx={{ <Box sx={{
@ -264,13 +394,34 @@ function AppContent() {
> >
<SendIcon /> <SendIcon />
</IconButton> </IconButton>
<Tooltip title="Clear all messages"
arrow
placement="top">
<IconButton <IconButton
onClick={clearMessages} onClick={clearMessages}
color="error" color="error"
disabled={messages.length === 0} 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 /> <ClearIcon /> {/* Added back the icon */}
</IconButton> </IconButton>
</Tooltip>
</Box> </Box>
</Box> </Box>
</ThemeProvider> </ThemeProvider>
@ -283,7 +434,7 @@ function App() {
return ( return (
<ElectronProvider> <ElectronProvider>
{!ollamaInstalled ? ( {!ollamaInstalled ? (
<OllamaCheck onInstalled={() => setOllamaInstalled(true)} /> <OllamaCheck onInstalled={(arg: boolean) => setOllamaInstalled(arg)} />
) : ( ) : (
<AppContent /> <AppContent />
)} )}

View File

@ -1,10 +1,11 @@
import React, { useEffect, useRef, useState, useMemo } from 'react'; import React, { useEffect, useRef, useState, useMemo } from 'react';
import { Box, Typography, Paper, Avatar, IconButton } from '@mui/material'; import { Box, Typography, Paper, Avatar, IconButton, List, ListItem, ListItemIcon, ListItemText, useTheme } from '@mui/material';
import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import type { DocumentMetadata } from '../../../electron/types'; import type { DocumentMetadata } from '../../../electron/types';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight'; import rehypeHighlight from 'rehype-highlight';
import 'highlight.js/styles/github.css'; import 'highlight.js/styles/github.css';
import { Keyboard, Waves, Shield, DataObject, Folder, Lock } from '@mui/icons-material';
interface ChatMessage { interface ChatMessage {
id: string; id: string;
@ -54,7 +55,11 @@ export default function MessageList({ messages }: MessageListProps) {
background: 'transparent', background: 'transparent',
}, },
}}> }}>
{messages.map((message) => (
{messages.length>0 ? (
<>
{messages.map((message) => (
<Box <Box
key={message.id} key={message.id}
sx={{ sx={{
@ -85,14 +90,16 @@ export default function MessageList({ messages }: MessageListProps) {
fontWeight: 500 fontWeight: 500
}} }}
> >
{message.isUser ? 'User' : 'Data Hound'} {message.isUser ? 'User' : 'Data Identification Manager'}
</Typography> </Typography>
</Box> </Box>
<Box <Box
component={Paper} component={Paper}
elevation={1} elevation={1}
sx={{ sx={{
p: 2.5, px: 2.5, // Keep horizontal padding
py: message.text.includes('<think>') ? 0 : 1.5, // Reduce vertical padding
pb: 1.5,
flex: 1, flex: 1,
bgcolor: message.isUser ? 'primary.main' : 'background.paper', bgcolor: message.isUser ? 'primary.main' : 'background.paper',
color: message.isUser ? 'primary.contrastText' : 'text.primary', color: message.isUser ? 'primary.contrastText' : 'text.primary',
@ -106,31 +113,29 @@ export default function MessageList({ messages }: MessageListProps) {
> >
<Box sx={{ <Box sx={{
'& .markdown-body': { '& .markdown-body': {
whiteSpace: 'pre-wrap',
wordBreak: 'break-word', wordBreak: 'break-word',
minHeight: '1.5em', minHeight: '1.5em',
lineHeight: 1.6, lineHeight: 1.6,
'& pre': { '& pre': {
backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#1e1e1e' : '#f6f8fa', backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#1e1e1e' : '#f6f8fa',
padding: 2, padding: 0,
borderRadius: 1, borderRadius: 1,
overflow: 'auto' overflow: 'auto'
}, },
'& code': { '& code': {
backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#1e1e1e' : '#f6f8fa', backgroundColor: (theme) => theme.palette.mode === 'dark' ? '#1e1e1e' : '#f6f8fa',
padding: '0.2em 0.4em',
borderRadius: 1, borderRadius: 1,
fontSize: '85%' fontSize: '85%'
}, },
'& h1, & h2, & h3, & h4, & h5, & h6': { '& h1, & h2, & h3, & h4, & h5, & h6': {
marginTop: '24px', marginTop: '0px', // Reduced from 24px
marginBottom: '16px', marginBottom: '0px', // Added margin bottom for headers
fontWeight: 600, fontWeight: 600,
lineHeight: 1.25 lineHeight: 1.25
}, },
'& p': { '& p': {
marginTop: '0', marginTop: '0',
marginBottom: '16px' marginBottom: '0px' // Added margin bottom for paragraphs
}, },
'& a': { '& a': {
color: (theme) => theme.palette.primary.main, color: (theme) => theme.palette.primary.main,
@ -147,15 +152,28 @@ export default function MessageList({ messages }: MessageListProps) {
padding: '0 1em', padding: '0 1em',
color: (theme) => theme.palette.text.secondary, color: (theme) => theme.palette.text.secondary,
borderLeft: (theme) => `0.25em solid ${theme.palette.divider}`, borderLeft: (theme) => `0.25em solid ${theme.palette.divider}`,
margin: '0 0 16px 0' margin: '0 0 0px 0'
}, },
'& ul, & ol': { '& ul, & ol': {
paddingLeft: '2em', paddingLeft: '2em',
marginBottom: '16px' 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'
} }
} }
}}> }}>
{message.text.includes('<think>') ? ( {message.text.includes('<think>') || message.text.length==0 ? (
message.text.split(/<think>|<\/think>/).map((segment, index) => { message.text.split(/<think>|<\/think>/).map((segment, index) => {
if (index % 2 === 1) { // This is a thinking section if (index % 2 === 1) { // This is a thinking section
// Calculate thinking time - assume 1 character = 0.1s // Calculate thinking time - assume 1 character = 0.1s
@ -181,13 +199,19 @@ export default function MessageList({ messages }: MessageListProps) {
/> />
<span>Reasoning</span> <span>Reasoning</span>
</div> </div>
<span className="timestamp">(thought for {thinkingTime}s)</span> <span className="timestamp">(thought for {thinkingTime}ms)</span>
</div> </div>
<div <div
id={`thinking-content-${message.id}-${index}`} id={`thinking-content-${message.id}-${index}`}
className="thinking-content" className="thinking-content"
>
<ReactMarkdown
key={index}
className="markdown-body"
rehypePlugins={[rehypeHighlight]}
> >
{segment} {segment}
</ReactMarkdown>
</div> </div>
</div> </div>
); );
@ -247,16 +271,178 @@ export default function MessageList({ messages }: MessageListProps) {
))} ))}
</Box> </Box>
)} )}
<Typography
variant="caption"
color={message.isUser ? '#ffffff' : 'text.secondary'}
sx={{ display: 'block', mt: 0.5 }}
>
{new Date(message.timestamp).toLocaleTimeString()}
</Typography>
</Box> </Box>
</Box> </Box>
))} ))}
</>
):(
<>
<WelcomeMessage/>
</>
)}
</Box> </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>
);
};

View File

@ -13,6 +13,9 @@ import {
Alert, Alert,
Box, Box,
Typography, Typography,
InputAdornment,
IconButton,
TextField,
} from '@mui/material'; } from '@mui/material';
import { import {
Folder as FolderIcon, Folder as FolderIcon,
@ -21,6 +24,8 @@ import {
Cloud as DropboxIcon, Cloud as DropboxIcon,
Chat as DiscordIcon, Chat as DiscordIcon,
Computer as LocalIcon, Computer as LocalIcon,
X,
Search,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { useElectron } from '../../hooks/useElectron'; import { useElectron } from '../../hooks/useElectron';
@ -105,16 +110,48 @@ export default function DirectoryPicker({ onSelect }: DirectoryPickerProps) {
mr: 2, mr: 2,
mb: 2, mb: 2,
}}> }}>
<Typography sx={{ pl:2 ,flexGrow: 1 }} variant="h6">Folders</Typography>
<Button <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
variant="contained" variant="contained"
onClick={handleOpen} onClick={handleOpen}
size="small" size="small"
sx={{ sx={{
minWidth: 0, minWidth: 0,
width: '32px', width: '40px',
height: '32px', height: '40px',
borderRadius: '50%',
padding: 0, padding: 0,
overflow: 'hidden', overflow: 'hidden',
backgroundColor: 'primary.main', backgroundColor: 'primary.main',
@ -130,16 +167,15 @@ export default function DirectoryPicker({ onSelect }: DirectoryPickerProps) {
}, },
'&:hover': { '&:hover': {
backgroundColor: 'primary.dark', backgroundColor: 'primary.dark',
width: '180px', width: '400px',
borderRadius: '16px',
'& .buttonText': { '& .buttonText': {
width: '110px', width: '300px',
marginLeft: '8px', marginLeft: '8px',
}, },
}, },
}} }}
> >
<AddIcon sx={{ fontSize: 20 }} /> <AddIcon sx={{ ml:0.25, fontSize: 20 }} />
<span className="buttonText">Add New Folder</span> <span className="buttonText">Add New Folder</span>
</Button> </Button>
</Box> </Box>

View File

@ -15,7 +15,7 @@ const HomePanel = () => {
}}> }}>
<Paper sx={{ p: 3, mb: 3 }}> <Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h5" gutterBottom>Welcome to Data Hound</Typography> <Typography variant="h5" gutterBottom>Welcome to Data Identification Manager</Typography>
<Typography variant="body1" paragraph> <Typography variant="body1" paragraph>
This application helps you search through your files and interact with their contents using AI assistance. This application helps you search through your files and interact with their contents using AI assistance.
</Typography> </Typography>

View File

@ -19,7 +19,7 @@ const theme = createTheme({
}); });
interface OllamaCheckProps { interface OllamaCheckProps {
onInstalled: () => void; onInstalled: (arg:boolean) => void;
} }
interface OllamaStatus { interface OllamaStatus {
@ -37,7 +37,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
const [modelStatus, setModelStatus] = useState<ModelStatus | null>(null); const [modelStatus, setModelStatus] = useState<ModelStatus | null>(null);
const [isChecking, setIsChecking] = useState(true); const [isChecking, setIsChecking] = useState(true);
const [downloadProgress, setDownloadProgress] = useState<number>(0); const [downloadProgress, setDownloadProgress] = useState<number>(0);
const MODEL_NAME = 'damien113/datahound:latest'; const MODEL_NAME = 'damien113/datahound-gpu:8b';
const checkOllama = async () => { const checkOllama = async () => {
try { try {
@ -53,17 +53,21 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
} }
}; };
const checkModel = async () => { const checkModel = async (retries = 3) => {
try { try {
const status = await window.electron.checkModel(MODEL_NAME); const status = await window.electron.checkModel(MODEL_NAME);
setModelStatus(status); setModelStatus(status);
if (status.installed) { if (status.installed) {
onInstalled(); onInstalled(true);
} }
} catch (error) { } catch (error) {
console.error('Error checking model:', error); console.error('Error checking model:', error);
if (retries > 0) {
setTimeout(() => checkModel(retries - 1), 2000);
} else {
setModelStatus({ installed: false, installing: false }); setModelStatus({ installed: false, installing: false });
} }
}
}; };
const [installError, setInstallError] = useState<string | null>(null); const [installError, setInstallError] = useState<string | null>(null);
@ -72,6 +76,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
try { try {
// Set initial installation state once at the start // Set initial installation state once at the start
setModelStatus({ installed: false, installing: true }); setModelStatus({ installed: false, installing: true });
onInstalled(false);
setInstallError(null); setInstallError(null);
setDownloadProgress(0); setDownloadProgress(0);
let downloadComplete = false; let downloadComplete = false;
@ -96,7 +101,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
// Show verification screen for a moment before transitioning // Show verification screen for a moment before transitioning
await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay
setModelStatus({ installed: true, installing: false }); setModelStatus({ installed: true, installing: false });
onInstalled(); onInstalled(true);
return; return;
} }
@ -108,6 +113,8 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
} catch (error) { } catch (error) {
console.error('Error installing model:', error); console.error('Error installing model:', error);
setModelStatus({ installed: false, installing: false }); setModelStatus({ installed: false, installing: false });
onInstalled(false);
setInstallError(error instanceof Error ? error.message : 'Unknown error occurred'); setInstallError(error instanceof Error ? error.message : 'Unknown error occurred');
setDownloadProgress(0); setDownloadProgress(0);
} }
@ -126,7 +133,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
if (!ollamaStatus?.installed || !ollamaStatus?.running) { if (!ollamaStatus?.installed || !ollamaStatus?.running) {
checkOllama(); checkOllama();
} }
}, 5000); }, 2000);
// Cleanup interval on unmount // Cleanup interval on unmount
return () => clearInterval(interval); return () => clearInterval(interval);
@ -236,7 +243,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
color: 'text.primary', color: 'text.primary',
}}> }}>
<Typography variant="h5" sx={{ color: 'text.primary', mb: 1 }}> <Typography variant="h5" sx={{ color: 'text.primary', mb: 1 }}>
{modelStatus?.installing ? 'Installing Data Hound AI Model' : 'AI Model Required'} {modelStatus?.installing ? 'Installing Data Identification Manager AI Model' : 'AI Model Required'}
</Typography> </Typography>
{installError && ( {installError && (
<Typography sx={{ color: 'error.main', mb: 2, textAlign: 'center' }}> <Typography sx={{ color: 'error.main', mb: 2, textAlign: 'center' }}>
@ -284,7 +291,7 @@ export function OllamaCheck({ onInstalled }: OllamaCheckProps) {
fontSize: '1.1rem', fontSize: '1.1rem',
}} }}
> >
Download Data Hound AI Model Download Data Identification Manager AI Model
</Button> </Button>
)} )}
</Box> </Box>

View File

@ -33,7 +33,7 @@ html, body {
.thinking-section { .thinking-section {
border-radius: 8px; border-radius: 8px;
margin: 0 0 8px 0; margin: 0 0 28px 0;
overflow: hidden; overflow: hidden;
background-color: #1976d2; background-color: #1976d2;
color: white; color: white;
@ -42,11 +42,10 @@ html, body {
[data-theme="dark"] .thinking-section { [data-theme="dark"] .thinking-section {
background-color: #1565c0; background-color: #1565c0;
} }
.thinking-header { .thinking-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between; /* Ensures left content stays left, timestamp stays right */
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
@ -68,13 +67,17 @@ html, body {
[data-theme="dark"] .thinking-header:hover { [data-theme="dark"] .thinking-header:hover {
background-color: rgba(0, 0, 0, 0.3); background-color: rgba(0, 0, 0, 0.3);
} }
.markdown-body {
padding-top: ;
}
.thinking-header-left { .thinking-header-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px; /* Controls spacing between chevron and "Reasoning" */
} }
.thinking-header .indicator { .thinking-header .indicator {
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -87,8 +90,7 @@ html, body {
} }
.thinking-header .timestamp { .thinking-header .timestamp {
font-size: 0.85rem; margin-left: auto; /* Pushes it to the right */
color: rgba(255, 255, 255, 0.7);
} }
.thinking-content { .thinking-content {

View File

@ -5,17 +5,23 @@ import path from 'path';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
base: process.env.ELECTRON_VITE_DEV_SERVER_URL ? '/' : './', base: './',
build: { build: {
outDir: 'dist', outDir: 'dist',
emptyOutDir: true, emptyOutDir: true,
target: 'esnext', target: 'esnext',
rollupOptions: { rollupOptions: {
external: ['http', 'https', 'path', 'fs', 'electron'] external: ['electron', 'electron-store', ...Object.keys(require('./package.json').dependencies)],
output: {
format: 'es',
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
assetFileNames: '[name][extname]'
}
}, },
assetsDir: '.', assetsInlineLimit: 0,
minify: true, minify: 'esbuild',
sourcemap: false sourcemap: true
}, },
resolve: { resolve: {
alias: { alias: {