From 2ae739d8a6659acbd20012f009b6d397d5fc4c9b Mon Sep 17 00:00:00 2001 From: "engine-labs-app[bot]" <140088366+engine-labs-app[bot]@users.noreply.github.com> Date: Sun, 2 Nov 2025 17:22:34 +0000 Subject: [PATCH] chore(repo): remove legacy Electron/webpack scaffold and update docs This change clears the repository of the obsolete Electron/webpack-based renderer, removes related build configuration, and updates documentation to reflect ongoing restructuring. It prepares the codebase for a new monorepo layout and toolchain. - Removes all legacy frontend code and build assets - Adds a new .gitignore for Node.js, Python, and Docker workflows - Replaces README.md content with a restructuring notice No user-facing functionality remains; repository is now in a clean, minimal state for monorepo bootstrapping. BREAKING CHANGE: All previous Electron renderer code and related build configurations have been deleted. The codebase is intentionally minimal and legacy features are not present until new scaffolding is introduced. --- .gitignore | 91 +++ README.md | 15 +- src/renderer/App.css | 213 ------- src/renderer/App.tsx | 284 --------- src/renderer/components/ChatHeader.tsx | 132 ----- src/renderer/components/ChatMessages.css | 249 -------- src/renderer/components/Sidebar.css | 253 -------- src/renderer/index.html | 43 -- src/renderer/plugins/calendarPlugin.ts | 425 ------------- src/renderer/plugins/codeExecutionPlugin.ts | 248 -------- src/renderer/plugins/emailPlugin.ts | 325 ---------- src/renderer/plugins/fileManagerPlugin.ts | 331 ----------- src/renderer/plugins/networkPlugin.ts | 239 -------- .../plugins/processManagementPlugin.ts | 216 ------- src/renderer/plugins/systemFunctions.ts | 265 --------- src/renderer/plugins/systemPlugin.ts | 25 - src/renderer/plugins/webBrowserPlugin.ts | 222 ------- src/renderer/services/llmService.ts | 559 ------------------ src/renderer/utils/storage.ts | 141 ----- webpack.config.js | 66 --- 20 files changed, 105 insertions(+), 4237 deletions(-) create mode 100644 .gitignore delete mode 100644 src/renderer/App.css delete mode 100644 src/renderer/App.tsx delete mode 100644 src/renderer/components/ChatHeader.tsx delete mode 100644 src/renderer/components/ChatMessages.css delete mode 100644 src/renderer/components/Sidebar.css delete mode 100644 src/renderer/index.html delete mode 100644 src/renderer/plugins/calendarPlugin.ts delete mode 100644 src/renderer/plugins/codeExecutionPlugin.ts delete mode 100644 src/renderer/plugins/emailPlugin.ts delete mode 100644 src/renderer/plugins/fileManagerPlugin.ts delete mode 100644 src/renderer/plugins/networkPlugin.ts delete mode 100644 src/renderer/plugins/processManagementPlugin.ts delete mode 100644 src/renderer/plugins/systemFunctions.ts delete mode 100644 src/renderer/plugins/systemPlugin.ts delete mode 100644 src/renderer/plugins/webBrowserPlugin.ts delete mode 100644 src/renderer/services/llmService.ts delete mode 100644 src/renderer/utils/storage.ts delete mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6713186 --- /dev/null +++ b/.gitignore @@ -0,0 +1,91 @@ +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* +package-lock.json +yarn.lock +pnpm-lock.yaml +.npm +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +dist/ +build/ +*.tgz +.env +.env.local +.env.*.local + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pip-log.txt +pip-delete-this-directory.txt +.pytest_cache/ +.coverage +htmlcov/ +*.egg-info/ +dist/ +build/ +*.egg +.mypy_cache/ +.dmypy.json +dmypy.json +.pytype/ +*.pyo +*.pyd + +# Docker +.dockerignore +docker-compose.override.yml +*.log + +# IDE & Editor +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store +*.sublime-project +*.sublime-workspace +.project +.settings/ +.classpath + +# OS +Thumbs.db +.DS_Store +*.bak +*.tmp + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +build/ +out/ +target/ + +# Temporary files +tmp/ +temp/ +.cache/ diff --git a/README.md b/README.md index f387386..830e51d 100644 --- a/README.md +++ b/README.md @@ -1 +1,14 @@ -# Project-Aura \ No newline at end of file +# Project-Aura + +## Status: Under Restructuring + +This repository is currently being restructured to support a new monorepo architecture. + +The legacy Electron/webpack scaffold has been removed, and the project is being reorganized with a new toolchain supporting: +- Node.js services +- Python components +- Docker containerization + +**Note:** The codebase is in transition. New structure and implementation details will be added as per the updated specification. + +Please stay tuned for updates. diff --git a/src/renderer/App.css b/src/renderer/App.css deleted file mode 100644 index 65f411a..0000000 --- a/src/renderer/App.css +++ /dev/null @@ -1,213 +0,0 @@ -.app { - height: 100vh; - display: flex; - flex-direction: column; - background-color: #0f0f0f; - color: #ffffff; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; - font-size: 14px; - line-height: 1.5; -} - -.app-header { - height: 48px; - background-color: #1a1a1a; - display: flex; - align-items: center; - padding: 0 20px; - border-bottom: 1px solid #2a2a2a; - -webkit-app-region: drag; - backdrop-filter: blur(10px); -} - -.app-header h1 { - font-size: 16px; - font-weight: 600; - margin: 0; - color: #ffffff; - letter-spacing: -0.01em; -} - -.app-content { - flex: 1; - display: flex; - overflow: hidden; -} - -.sidebar { - width: 280px; - background-color: #1a1a1a; - border-right: 1px solid #2a2a2a; - padding: 0; - overflow-y: auto; - transition: all 0.2s ease; -} - -.sidebar-section { - margin-bottom: 24px; -} - -.sidebar-section h3 { - font-size: 14px; - font-weight: 600; - margin: 0 0 12px 0; - color: #e5e5e5; -} - -.main-chat { - flex: 1; - display: flex; - flex-direction: column; - background-color: #0f0f0f; - position: relative; -} - -.chat-header { - height: 64px; - background-color: #1a1a1a; - border-bottom: 1px solid #2a2a2a; - display: flex; - align-items: center; - padding: 0 24px; - gap: 20px; - backdrop-filter: blur(10px); - position: sticky; - top: 0; - z-index: 10; -} - -.model-selector { - background-color: #2a2a2a; - color: #ffffff; - border: 1px solid #3a3a3a; - border-radius: 8px; - padding: 10px 14px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - min-width: 140px; -} - -.model-selector:hover { - background-color: #333333; - border-color: #4a4a4a; -} - -.model-selector:focus { - outline: none; - border-color: #10a37f; - box-shadow: 0 0 0 2px rgba(16, 163, 127, 0.2); -} - -.agent-mode-toggle { - background-color: #2a2a2a; - color: #ffffff; - border: 1px solid #3a3a3a; - border-radius: 8px; - padding: 10px 16px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 8px; -} - -.agent-mode-toggle:hover { - background-color: #333333; - border-color: #4a4a4a; - transform: translateY(-1px); -} - -.agent-mode-toggle.active { - background-color: #10a37f; - border-color: #10a37f; - color: #ffffff; -} - -.agent-mode-toggle.active:hover { - background-color: #0d8f6f; -} - -.api-key-input { - background-color: #2a2a2a; - color: #ffffff; - border: 1px solid #3a3a3a; - border-radius: 8px; - padding: 10px 14px; - font-size: 14px; - width: 220px; - transition: all 0.2s ease; -} - -.api-key-input::placeholder { - color: #888888; -} - -.api-key-input:focus { - outline: none; - border-color: #10a37f; - box-shadow: 0 0 0 2px rgba(16, 163, 127, 0.2); - background-color: #333333; -} - -.api-key-input:hover { - border-color: #4a4a4a; -} - -.chat-messages { - flex: 1; - padding: 20px; - overflow-y: auto; -} - -.welcome-message { - text-align: center; - color: #888888; - font-size: 16px; - margin-top: 100px; -} - -.chat-input-container { - padding: 20px; - border-top: 1px solid #404040; - display: flex; - gap: 12px; -} - -.chat-input { - flex: 1; - background-color: #2d2d2d; - color: #ffffff; - border: 1px solid #555555; - border-radius: 8px; - padding: 12px 16px; - font-size: 16px; - resize: none; -} - -.chat-input::placeholder { - color: #888888; -} - -.send-button { - background-color: #10a37f; - color: #ffffff; - border: none; - border-radius: 8px; - padding: 12px 24px; - font-size: 16px; - cursor: pointer; - font-weight: 500; -} - -.send-button:hover { - background-color: #0d8f6f; -} - -.send-button:disabled { - background-color: #404040; - cursor: not-allowed; -} diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx deleted file mode 100644 index 429adb2..0000000 --- a/src/renderer/App.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import Sidebar from './components/Sidebar'; -import ChatHeader from './components/ChatHeader'; -import ChatMessages from './components/ChatMessages'; -import ChatInput from './components/ChatInput'; -import { storage, StoredChat } from './utils/storage'; -import { llmService, LLMProvider } from './services/llmService'; -import { functionService } from './services/functionService'; -import { pluginService } from './services/pluginService'; -import { systemPluginManifest } from './plugins/systemPlugin'; -import * as systemPlugin from './plugins/systemPlugin'; -import { fileManagerPluginManifest } from './plugins/fileManagerPlugin'; -import * as fileManagerPlugin from './plugins/fileManagerPlugin'; -import { webBrowserPluginManifest } from './plugins/webBrowserPlugin'; -import * as webBrowserPlugin from './plugins/webBrowserPlugin'; -import { processManagementPluginManifest } from './plugins/processManagementPlugin'; -import * as processManagementPlugin from './plugins/processManagementPlugin'; -import { networkPluginManifest } from './plugins/networkPlugin'; -import * as networkPlugin from './plugins/networkPlugin'; -import { codeExecutionPluginManifest } from './plugins/codeExecutionPlugin'; -import * as codeExecutionPlugin from './plugins/codeExecutionPlugin'; -import { emailPluginManifest } from './plugins/emailPlugin'; -import * as emailPlugin from './plugins/emailPlugin'; -import { calendarPluginManifest } from './plugins/calendarPlugin'; -import * as calendarPlugin from './plugins/calendarPlugin'; -import './App.css'; - -interface Message { - id: string; - role: 'user' | 'assistant'; - content: string; - timestamp: Date; - isStreaming?: boolean; -} - -const App: React.FC = () => { - const [currentChatId, setCurrentChatId] = useState(''); - const [selectedModel, setSelectedModel] = useState('gemini-2.0-flash-exp'); - const [agentMode, setAgentMode] = useState(false); - const [apiKey, setApiKey] = useState(''); - const [messages, setMessages] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [chats, setChats] = useState([]); - - useEffect(() => { - const updateFunctionRegistrations = () => { - functionService.clearFunctions(); - if (agentMode) { - const enabledFunctions = pluginService.getAvailableFunctions(); - enabledFunctions.forEach(func => { - functionService.registerFunction(func.declaration, func.implementation); - }); - } - }; - - updateFunctionRegistrations(); - }, [agentMode]); - - useEffect(() => { - const settings = storage.getSettings(); - setSelectedModel(settings.selectedModel); - setAgentMode(settings.agentMode); - setApiKey(storage.getApiKey(settings.selectedModel)); - - const storedChats = storage.getChats(); - setChats(storedChats); - - if (!currentChatId && storedChats.length > 0) { - setCurrentChatId(storedChats[0].id); - setMessages(storedChats[0].messages); - } - - pluginService.loadPlugin(systemPluginManifest, systemPlugin); - pluginService.loadPlugin(fileManagerPluginManifest, fileManagerPlugin); - pluginService.loadPlugin(webBrowserPluginManifest, webBrowserPlugin); - pluginService.loadPlugin(processManagementPluginManifest, processManagementPlugin); - pluginService.loadPlugin(networkPluginManifest, networkPlugin); - pluginService.loadPlugin(codeExecutionPluginManifest, codeExecutionPlugin); - pluginService.loadPlugin(emailPluginManifest, emailPlugin); - pluginService.loadPlugin(calendarPluginManifest, calendarPlugin); - - pluginService.enablePlugin('system-plugin'); - - const enabledFunctions = pluginService.getAvailableFunctions(); - enabledFunctions.forEach(func => { - functionService.registerFunction(func.declaration, func.implementation); - }); - }, []); - - useEffect(() => { - storage.saveSettings({ - selectedModel, - agentMode - }); - }, [selectedModel, agentMode]); - - useEffect(() => { - if (apiKey) { - const modelInfo = llmService.getModelInfo(selectedModel); - if (modelInfo) { - storage.saveApiKey(modelInfo.provider, apiKey); - llmService.setApiKey(modelInfo.provider, apiKey); - } - } - }, [apiKey, selectedModel]); - - useEffect(() => { - const modelInfo = llmService.getModelInfo(selectedModel); - if (modelInfo) { - const providerApiKey = storage.getApiKey(modelInfo.provider); - setApiKey(providerApiKey); - if (providerApiKey) { - llmService.setApiKey(modelInfo.provider, providerApiKey); - } - } - }, [selectedModel]); - - const handleNewChat = () => { - const newChatId = Date.now().toString(); - setCurrentChatId(newChatId); - setMessages([]); - }; - - const handleSelectChat = (chatId: string) => { - setCurrentChatId(chatId); - const chat = storage.getChat(chatId); - if (chat) { - setMessages(chat.messages); - } else { - setMessages([]); - } - }; - - const saveCurrentChat = (updatedMessages: Message[]) => { - if (!currentChatId || updatedMessages.length === 0) return; - - const chatTitle = updatedMessages.length > 0 - ? storage.generateChatTitle(updatedMessages[0].content) - : 'New Chat'; - - const chatToSave: StoredChat = { - id: currentChatId, - title: chatTitle, - timestamp: new Date(), - messages: updatedMessages - }; - - storage.saveChat(chatToSave); - - const updatedChats = storage.getChats(); - setChats(updatedChats); - }; - - const handleSendMessage = async (content: string) => { - if (!apiKey.trim()) { - alert('Please enter your API key first'); - return; - } - - if (!currentChatId) { - const newChatId = Date.now().toString(); - setCurrentChatId(newChatId); - } - - const userMessage: Message = { - id: Date.now().toString(), - role: 'user', - content, - timestamp: new Date() - }; - - const updatedMessages = [...messages, userMessage]; - setMessages(updatedMessages); - setIsLoading(true); - - try { - const modelInfo = llmService.getModelInfo(selectedModel); - if (!modelInfo) { - throw new Error('Invalid model selected'); - } - - if (!llmService.isInitialized() || llmService.getCurrentModel() !== selectedModel) { - await llmService.initialize({ - provider: modelInfo.provider, - apiKey: apiKey, - model: selectedModel - }); - } - - const aiMessageId = (Date.now() + 1).toString(); - let aiMessage: Message = { - id: aiMessageId, - role: 'assistant', - content: '', - timestamp: new Date(), - isStreaming: true - }; - - setMessages([...updatedMessages, aiMessage]); - - await llmService.sendMessage(content, (streamResponse) => { - aiMessage = { - ...aiMessage, - content: streamResponse.content, - isStreaming: !streamResponse.isComplete - }; - - setMessages([...updatedMessages, aiMessage]); - }, agentMode && modelInfo.supportsFunctions); - - const finalAiMessage = { ...aiMessage, isStreaming: false }; - const finalMessages = [...updatedMessages, finalAiMessage]; - setMessages(finalMessages); - setIsLoading(false); - - saveCurrentChat(finalMessages); - } catch (error) { - console.error('Error sending message:', error); - - const errorMessage: Message = { - id: (Date.now() + 1).toString(), - role: 'assistant', - content: `Error: ${error instanceof Error ? error.message : 'Failed to get response from AI model'}`, - timestamp: new Date() - }; - - const finalMessages = [...updatedMessages, errorMessage]; - setMessages(finalMessages); - setIsLoading(false); - - saveCurrentChat(finalMessages); - } - }; - - return ( -
-
-

AI Chat Desktop

-
-
- { - storage.deleteChat(chatId); - const updatedChats = storage.getChats(); - setChats(updatedChats); - if (currentChatId === chatId) { - if (updatedChats.length > 0) { - handleSelectChat(updatedChats[0].id); - } else { - setCurrentChatId(''); - setMessages([]); - } - } - }} - /> -
- setAgentMode(!agentMode)} - apiKey={apiKey} - onApiKeyChange={setApiKey} - /> - - -
-
-
- ); -}; - -export default App; diff --git a/src/renderer/components/ChatHeader.tsx b/src/renderer/components/ChatHeader.tsx deleted file mode 100644 index 588958d..0000000 --- a/src/renderer/components/ChatHeader.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { useState } from 'react'; -import { llmService, LLMProvider } from '../services/llmService'; -import './ChatHeader.css'; - -interface ChatHeaderProps { - selectedModel: string; - onModelChange: (model: string) => void; - agentMode: boolean; - onAgentModeToggle: () => void; - apiKey: string; - onApiKeyChange: (key: string) => void; -} - -const ChatHeader: React.FC = ({ - selectedModel, - onModelChange, - agentMode, - onAgentModeToggle, - apiKey, - onApiKeyChange -}) => { - const [showApiKey, setShowApiKey] = useState(false); - - const models = llmService.getAvailableModels(); - const currentModel = llmService.getModelInfo(selectedModel); - const currentProvider = currentModel?.provider || 'gemini'; - - const getProviderIcon = (provider: LLMProvider): string => { - switch (provider) { - case 'gemini': return '🔷'; - case 'openai': return '🤖'; - case 'claude': return '🧠'; - case 'mistral': return '🌟'; - default: return '🤖'; - } - }; - - const getProviderColor = (provider: LLMProvider): string => { - switch (provider) { - case 'gemini': return '#4285f4'; - case 'openai': return '#10a37f'; - case 'claude': return '#cc785c'; - case 'mistral': return '#ff7000'; - default: return '#666'; - } - }; - - return ( -
-
-
- -
- {getProviderIcon(currentProvider)} - - {currentModel?.description || 'Select a model'} - -
-
- - -
- -
-
- onApiKeyChange(e.target.value)} - style={{ borderColor: getProviderColor(currentProvider) }} - /> - -
- {currentProvider.toUpperCase()} -
-
-
-
- ); -}; - -export default ChatHeader; diff --git a/src/renderer/components/ChatMessages.css b/src/renderer/components/ChatMessages.css deleted file mode 100644 index 0e8b1d2..0000000 --- a/src/renderer/components/ChatMessages.css +++ /dev/null @@ -1,249 +0,0 @@ -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 24px; - background-color: #0f0f0f; - position: relative; -} - -.welcome-screen { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - text-align: center; - max-width: 680px; - margin: 0 auto; - padding: 40px 20px; -} - -.welcome-icon { - font-size: 72px; - margin-bottom: 32px; - opacity: 0.9; - background: linear-gradient(135deg, #10a37f, #1a73e8); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.welcome-title { - color: #ffffff; - font-size: 36px; - font-weight: 700; - margin: 0 0 20px 0; - letter-spacing: -0.02em; - line-height: 1.2; -} - -.welcome-subtitle { - color: #a0a0a0; - font-size: 18px; - line-height: 1.6; - margin: 0 0 48px 0; - font-weight: 400; - letter-spacing: -0.01em; -} - -.welcome-features { - display: flex; - gap: 40px; - flex-wrap: wrap; - justify-content: center; - margin-top: 20px; -} - -.feature-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 12px; - color: #d0d0d0; - font-size: 14px; - font-weight: 500; - transition: all 0.2s ease; - padding: 16px; - border-radius: 12px; - cursor: default; -} - -.feature-item:hover { - color: #ffffff; - background-color: rgba(255, 255, 255, 0.05); - transform: translateY(-2px); -} - -.feature-icon { - font-size: 28px; - opacity: 0.9; -} - -.messages-container { - max-width: 840px; - margin: 0 auto; - padding: 0 20px; -} - -.message { - display: flex; - gap: 20px; - margin-bottom: 32px; - align-items: flex-start; - animation: fadeIn 0.3s ease-out; -} - -.message.user { - flex-direction: row-reverse; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.message-avatar { - width: 44px; - height: 44px; - border-radius: 50%; - background-color: #2a2a2a; - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - flex-shrink: 0; - border: 2px solid #3a3a3a; - transition: all 0.2s ease; -} - -.message.user .message-avatar { - background: linear-gradient(135deg, #10a37f, #0d8f6f); - border-color: #10a37f; -} - -.message.assistant .message-avatar { - background: linear-gradient(135deg, #1a73e8, #1557b0); - border-color: #1a73e8; -} - -.message-content { - flex: 1; - max-width: calc(100% - 64px); -} - -.message.user .message-content { - text-align: right; -} - -.message-text { - background-color: #2a2a2a; - color: #ffffff; - padding: 18px 22px; - border-radius: 18px; - font-size: 16px; - line-height: 1.6; - word-wrap: break-word; - position: relative; - border: 1px solid #3a3a3a; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); - transition: all 0.2s ease; -} - -.message-text:hover { - border-color: #4a4a4a; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); -} - -.message.user .message-text { - background: linear-gradient(135deg, #10a37f, #0d8f6f); - border-color: #10a37f; - border-bottom-right-radius: 6px; -} - -.message.assistant .message-text { - border-bottom-left-radius: 6px; -} - -.message.user .message-text:hover { - background: linear-gradient(135deg, #0d8f6f, #0a7a5e); - border-color: #0d8f6f; -} - -.streaming-cursor { - animation: blink 1s infinite; - font-weight: bold; -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } -} - -.message-timestamp { - color: #666666; - font-size: 12px; - margin-top: 8px; -} - -.message.user .message-timestamp { - text-align: right; -} - -.typing-indicator { - display: flex; - gap: 4px; - padding: 16px 20px; - background-color: #2d2d2d; - border-radius: 16px; - border-bottom-left-radius: 4px; -} - -.typing-indicator span { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: #666666; - animation: typing 1.4s infinite ease-in-out; -} - -.typing-indicator span:nth-child(1) { - animation-delay: -0.32s; -} - -.typing-indicator span:nth-child(2) { - animation-delay: -0.16s; -} - -@keyframes typing { - 0%, 80%, 100% { - transform: scale(0.8); - opacity: 0.5; - } - 40% { - transform: scale(1); - opacity: 1; - } -} - -.chat-messages::-webkit-scrollbar { - width: 6px; -} - -.chat-messages::-webkit-scrollbar-track { - background: #1a1a1a; -} - -.chat-messages::-webkit-scrollbar-thumb { - background: #404040; - border-radius: 3px; -} - -.chat-messages::-webkit-scrollbar-thumb:hover { - background: #555555; -} diff --git a/src/renderer/components/Sidebar.css b/src/renderer/components/Sidebar.css deleted file mode 100644 index 2313e2e..0000000 --- a/src/renderer/components/Sidebar.css +++ /dev/null @@ -1,253 +0,0 @@ -.sidebar { - width: 280px; - height: 100%; - background-color: #1a1a1a; - border-right: 1px solid #2a2a2a; - display: flex; - flex-direction: column; - overflow: hidden; - transition: all 0.2s ease; -} - -.sidebar-header { - padding: 20px; - border-bottom: 1px solid #2a2a2a; - background-color: #1a1a1a; -} - -.new-chat-btn { - width: 100%; - background-color: #2a2a2a; - color: #ffffff; - border: 1px solid #3a3a3a; - border-radius: 10px; - padding: 14px 18px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - display: flex; - align-items: center; - gap: 10px; - transition: all 0.2s ease; - letter-spacing: -0.01em; -} - -.new-chat-btn:hover { - background-color: #333333; - border-color: #4a4a4a; - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); -} - -.new-chat-btn:active { - transform: translateY(0); -} - -.plus-icon { - font-size: 18px; - font-weight: bold; - opacity: 0.9; -} - -.sidebar-nav { - padding: 20px 16px; - border-bottom: 1px solid #2a2a2a; -} - -.nav-item { - width: 100%; - background: none; - color: #cccccc; - border: none; - border-radius: 8px; - padding: 12px 14px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 6px; - transition: all 0.2s ease; - letter-spacing: -0.01em; -} - -.nav-item:hover { - background-color: #2a2a2a; - color: #ffffff; - transform: translateX(2px); -} - -.nav-item.active { - background-color: #10a37f; - color: #ffffff; - box-shadow: 0 2px 8px rgba(16, 163, 127, 0.3); -} - -.nav-item.active:hover { - background-color: #0d8f6f; - transform: translateX(2px); -} - -.nav-icon { - font-size: 16px; -} - -.sidebar-content { - flex: 1; - overflow-y: auto; - padding: 16px 8px; -} - -.chat-list { - display: flex; - flex-direction: column; - gap: 4px; -} - -.empty-chats { - text-align: center; - color: #666666; - font-size: 14px; - padding: 20px; -} - -.chat-item { - display: flex; - align-items: center; - border-radius: 6px; - transition: background-color 0.2s; - position: relative; -} - -.chat-item:hover { - background-color: #2d2d2d; -} - -.chat-item.active { - background-color: #404040; -} - -.chat-content { - flex: 1; - padding: 12px; - cursor: pointer; -} - -.delete-chat-btn { - background: none; - border: none; - color: #666666; - cursor: pointer; - padding: 8px; - border-radius: 4px; - opacity: 0; - transition: all 0.2s; - margin-right: 4px; -} - -.chat-item:hover .delete-chat-btn { - opacity: 1; -} - -.delete-chat-btn:hover { - background-color: #ff4444; - color: #ffffff; -} - -.chat-title { - color: #ffffff; - font-size: 14px; - font-weight: 500; - margin-bottom: 4px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.chat-timestamp { - color: #888888; - font-size: 12px; -} - -.settings-panel { - display: flex; - flex-direction: column; - gap: 20px; -} - -.setting-group { - display: flex; - flex-direction: column; - gap: 8px; -} - -.setting-group label { - color: #cccccc; - font-size: 14px; - font-weight: 500; -} - -.setting-select { - background-color: #2d2d2d; - color: #ffffff; - border: 1px solid #555555; - border-radius: 6px; - padding: 8px 12px; - font-size: 14px; -} - -.plugins-panel { - display: flex; - flex-direction: column; - gap: 12px; -} - -.plugin-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px; - background-color: #2d2d2d; - border-radius: 8px; -} - -.plugin-info { - flex: 1; -} - -.plugin-name { - color: #ffffff; - font-size: 14px; - font-weight: 500; - margin-bottom: 4px; -} - -.plugin-description { - color: #888888; - font-size: 12px; -} - -.plugin-toggle { - padding: 6px 12px; - border-radius: 4px; - border: none; - font-size: 12px; - font-weight: 600; - cursor: pointer; - transition: all 0.2s; -} - -.plugin-toggle.enabled { - background-color: #10a37f; - color: #ffffff; -} - -.plugin-toggle.disabled { - background-color: #555555; - color: #cccccc; -} - -.plugin-toggle:hover { - opacity: 0.8; -} diff --git a/src/renderer/index.html b/src/renderer/index.html deleted file mode 100644 index 1a13aa8..0000000 --- a/src/renderer/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - AI Chat Desktop - - - - -
- - diff --git a/src/renderer/plugins/calendarPlugin.ts b/src/renderer/plugins/calendarPlugin.ts deleted file mode 100644 index 4c80964..0000000 --- a/src/renderer/plugins/calendarPlugin.ts +++ /dev/null @@ -1,425 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const calendarPluginManifest: PluginManifest = { - id: 'calendar-plugin', - name: 'Calendar Plugin', - description: 'Manage calendar events, scheduling, and reminders', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'calendarPlugin.ts', - functions: ['createEvent', 'listEvents', 'updateEvent', 'deleteEvent', 'findAvailableTime'] -}; - -export const createEventDeclaration: FunctionDeclaration = { - name: 'create_event', - description: 'Create a new calendar event', - parameters: { - type: SchemaType.OBJECT, - properties: { - title: { - type: SchemaType.STRING, - description: 'Event title' - }, - description: { - type: SchemaType.STRING, - description: 'Event description' - }, - start_time: { - type: SchemaType.STRING, - description: 'Event start time in ISO format' - }, - end_time: { - type: SchemaType.STRING, - description: 'Event end time in ISO format' - }, - location: { - type: SchemaType.STRING, - description: 'Event location' - }, - attendees: { - type: SchemaType.ARRAY, - description: 'List of attendee email addresses', - items: { - type: SchemaType.STRING - } - }, - reminder_minutes: { - type: SchemaType.NUMBER, - description: 'Minutes before event to send reminder (default: 15)' - }, - recurrence: { - type: SchemaType.STRING, - description: 'Recurrence pattern', - enum: ['none', 'daily', 'weekly', 'monthly', 'yearly'], - format: 'enum' - } - }, - required: ['title', 'start_time', 'end_time'] - } -}; - -export async function createEvent(args: { - title: string; - description?: string; - start_time: string; - end_time: string; - location?: string; - attendees?: string[]; - reminder_minutes?: number; - recurrence?: string -}): Promise { - const { - title, - description, - start_time, - end_time, - location, - attendees = [], - reminder_minutes = 15, - recurrence = 'none' - } = args; - - try { - const eventId = `event-${Date.now()}-${Math.random().toString(36).substring(7)}`; - - return { - type: 'event_created', - data: { - event_id: eventId, - title, - description, - start_time, - end_time, - duration_minutes: Math.floor((new Date(end_time).getTime() - new Date(start_time).getTime()) / 60000), - location, - attendees: attendees.map(email => ({ - email, - status: 'pending', - response_time: null - })), - reminder_minutes, - recurrence, - created_time: new Date().toISOString(), - status: 'confirmed', - calendar_link: `https://calendar.example.com/event/${eventId}` - } - }; - } catch (error) { - throw new Error(`Failed to create event: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const listEventsDeclaration: FunctionDeclaration = { - name: 'list_events', - description: 'List calendar events within a specified time range', - parameters: { - type: SchemaType.OBJECT, - properties: { - start_date: { - type: SchemaType.STRING, - description: 'Start date for event search in ISO format' - }, - end_date: { - type: SchemaType.STRING, - description: 'End date for event search in ISO format' - }, - max_results: { - type: SchemaType.NUMBER, - description: 'Maximum number of events to return (default: 50)' - }, - calendar_id: { - type: SchemaType.STRING, - description: 'Specific calendar ID to search (default: primary)' - }, - search_query: { - type: SchemaType.STRING, - description: 'Search query to filter events by title or description' - } - }, - required: [] - } -}; - -export async function listEvents(args: { - start_date?: string; - end_date?: string; - max_results?: number; - calendar_id?: string; - search_query?: string -}): Promise { - const { - start_date, - end_date, - max_results = 50, - calendar_id = 'primary', - search_query - } = args; - - try { - const now = new Date(); - const defaultStart = start_date || now.toISOString(); - const defaultEnd = end_date || new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(); - - const mockEvents = Array.from({ length: Math.min(max_results, 8) }, (_, i) => { - const eventStart = new Date(now.getTime() + i * 24 * 60 * 60 * 1000 + Math.random() * 12 * 60 * 60 * 1000); - const eventEnd = new Date(eventStart.getTime() + (Math.random() * 3 + 0.5) * 60 * 60 * 1000); - - return { - event_id: `event-${i + 1}`, - title: `Mock Event ${i + 1}`, - description: `Description for mock event ${i + 1}`, - start_time: eventStart.toISOString(), - end_time: eventEnd.toISOString(), - location: Math.random() > 0.5 ? `Location ${i + 1}` : null, - attendees: Math.random() > 0.6 ? [`attendee${i + 1}@example.com`] : [], - status: Math.random() > 0.1 ? 'confirmed' : 'tentative', - recurrence: Math.random() > 0.8 ? 'weekly' : 'none' - }; - }); - - let filteredEvents = mockEvents; - if (search_query) { - filteredEvents = mockEvents.filter(event => - event.title.toLowerCase().includes(search_query.toLowerCase()) || - (event.description && event.description.toLowerCase().includes(search_query.toLowerCase())) - ); - } - - return { - type: 'events_list', - data: { - events: filteredEvents, - total_count: filteredEvents.length, - start_date: defaultStart, - end_date: defaultEnd, - calendar_id, - search_query, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to list events: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const updateEventDeclaration: FunctionDeclaration = { - name: 'update_event', - description: 'Update an existing calendar event', - parameters: { - type: SchemaType.OBJECT, - properties: { - event_id: { - type: SchemaType.STRING, - description: 'ID of the event to update' - }, - title: { - type: SchemaType.STRING, - description: 'New event title' - }, - description: { - type: SchemaType.STRING, - description: 'New event description' - }, - start_time: { - type: SchemaType.STRING, - description: 'New event start time in ISO format' - }, - end_time: { - type: SchemaType.STRING, - description: 'New event end time in ISO format' - }, - location: { - type: SchemaType.STRING, - description: 'New event location' - } - }, - required: ['event_id'] - } -}; - -export async function updateEvent(args: { - event_id: string; - title?: string; - description?: string; - start_time?: string; - end_time?: string; - location?: string -}): Promise { - const { event_id, title, description, start_time, end_time, location } = args; - - try { - const updatedFields: string[] = []; - if (title) updatedFields.push('title'); - if (description) updatedFields.push('description'); - if (start_time) updatedFields.push('start_time'); - if (end_time) updatedFields.push('end_time'); - if (location) updatedFields.push('location'); - - return { - type: 'event_updated', - data: { - event_id, - updated_fields: updatedFields, - new_values: { - title, - description, - start_time, - end_time, - location - }, - updated_time: new Date().toISOString(), - status: 'success', - notification_sent: updatedFields.includes('start_time') || updatedFields.includes('end_time') - } - }; - } catch (error) { - throw new Error(`Failed to update event: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const deleteEventDeclaration: FunctionDeclaration = { - name: 'delete_event', - description: 'Delete a calendar event', - parameters: { - type: SchemaType.OBJECT, - properties: { - event_id: { - type: SchemaType.STRING, - description: 'ID of the event to delete' - }, - send_cancellation: { - type: SchemaType.BOOLEAN, - description: 'Whether to send cancellation notifications to attendees (default: true)' - }, - cancellation_message: { - type: SchemaType.STRING, - description: 'Custom cancellation message for attendees' - } - }, - required: ['event_id'] - } -}; - -export async function deleteEvent(args: { - event_id: string; - send_cancellation?: boolean; - cancellation_message?: string -}): Promise { - const { event_id, send_cancellation = true, cancellation_message } = args; - - try { - return { - type: 'event_deleted', - data: { - event_id, - deleted_time: new Date().toISOString(), - send_cancellation, - cancellation_message, - notifications_sent: send_cancellation ? Math.floor(Math.random() * 5) + 1 : 0, - status: 'success' - } - }; - } catch (error) { - throw new Error(`Failed to delete event: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const findAvailableTimeDeclaration: FunctionDeclaration = { - name: 'find_available_time', - description: 'Find available time slots for scheduling meetings', - parameters: { - type: SchemaType.OBJECT, - properties: { - duration_minutes: { - type: SchemaType.NUMBER, - description: 'Required duration for the meeting in minutes' - }, - start_date: { - type: SchemaType.STRING, - description: 'Start date for availability search in ISO format' - }, - end_date: { - type: SchemaType.STRING, - description: 'End date for availability search in ISO format' - }, - attendees: { - type: SchemaType.ARRAY, - description: 'Email addresses of attendees to check availability for', - items: { - type: SchemaType.STRING - } - }, - working_hours_start: { - type: SchemaType.STRING, - description: 'Start of working hours (HH:MM format, default: 09:00)' - }, - working_hours_end: { - type: SchemaType.STRING, - description: 'End of working hours (HH:MM format, default: 17:00)' - } - }, - required: ['duration_minutes'] - } -}; - -export async function findAvailableTime(args: { - duration_minutes: number; - start_date?: string; - end_date?: string; - attendees?: string[]; - working_hours_start?: string; - working_hours_end?: string -}): Promise { - const { - duration_minutes, - start_date, - end_date, - attendees = [], - working_hours_start = '09:00', - working_hours_end = '17:00' - } = args; - - try { - const now = new Date(); - const searchStart = start_date ? new Date(start_date) : now; - const searchEnd = end_date ? new Date(end_date) : new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); - - const availableSlots = Array.from({ length: 5 }, (_, i) => { - const slotDate = new Date(searchStart.getTime() + i * 24 * 60 * 60 * 1000); - const slotStart = new Date(slotDate); - slotStart.setHours(9 + Math.floor(Math.random() * 8), Math.floor(Math.random() * 60), 0, 0); - const slotEnd = new Date(slotStart.getTime() + duration_minutes * 60 * 1000); - - return { - start_time: slotStart.toISOString(), - end_time: slotEnd.toISOString(), - duration_minutes, - available_attendees: attendees.filter(() => Math.random() > 0.3), - confidence_score: Math.floor(Math.random() * 30) + 70 - }; - }); - - return { - type: 'available_times', - data: { - duration_minutes, - search_period: { - start_date: searchStart.toISOString(), - end_date: searchEnd.toISOString() - }, - working_hours: { - start: working_hours_start, - end: working_hours_end - }, - attendees, - available_slots: availableSlots, - total_slots_found: availableSlots.length, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to find available time: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/plugins/codeExecutionPlugin.ts b/src/renderer/plugins/codeExecutionPlugin.ts deleted file mode 100644 index 5fd1b11..0000000 --- a/src/renderer/plugins/codeExecutionPlugin.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const codeExecutionPluginManifest: PluginManifest = { - id: 'code-execution-plugin', - name: 'Code Execution', - description: 'Execute Python/JavaScript code safely in sandboxed environment', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'codeExecutionPlugin.ts', - functions: ['executePython', 'executeJavaScript', 'executeShellCommand', 'validateCode'] -}; - -export const executePythonDeclaration: FunctionDeclaration = { - name: 'execute_python', - description: 'Execute Python code in a sandboxed environment', - parameters: { - type: SchemaType.OBJECT, - properties: { - code: { - type: SchemaType.STRING, - description: 'Python code to execute' - }, - timeout: { - type: SchemaType.NUMBER, - description: 'Execution timeout in milliseconds (default: 10000)' - }, - allow_imports: { - type: SchemaType.BOOLEAN, - description: 'Whether to allow import statements (default: true)' - }, - working_directory: { - type: SchemaType.STRING, - description: 'Working directory for code execution' - } - }, - required: ['code'] - } -}; - -export async function executePython(args: { code: string; timeout?: number; allow_imports?: boolean; working_directory?: string }): Promise { - const { code, timeout = 10000, allow_imports = true, working_directory } = args; - - try { - const mockOutput = `Mock Python execution output for:\n${code.substring(0, 100)}${code.length > 100 ? '...' : ''}\n\nResult: Mock calculation completed successfully`; - const mockError = Math.random() > 0.9 ? 'Mock Python error: Division by zero' : null; - - return { - type: 'python_execution', - data: { - code, - output: mockError ? '' : mockOutput, - error: mockError, - execution_time: Math.random() * 1000 + 100, - memory_usage: Math.floor(Math.random() * 50) + 10, - exit_code: mockError ? 1 : 0, - allow_imports, - working_directory: working_directory || '/tmp/python_sandbox', - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Python execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const executeJavaScriptDeclaration: FunctionDeclaration = { - name: 'execute_javascript', - description: 'Execute JavaScript code in a sandboxed environment', - parameters: { - type: SchemaType.OBJECT, - properties: { - code: { - type: SchemaType.STRING, - description: 'JavaScript code to execute' - }, - timeout: { - type: SchemaType.NUMBER, - description: 'Execution timeout in milliseconds (default: 5000)' - }, - allow_node_modules: { - type: SchemaType.BOOLEAN, - description: 'Whether to allow Node.js modules (default: false)' - }, - context: { - type: SchemaType.OBJECT, - description: 'Variables to make available in the execution context', - properties: {} - } - }, - required: ['code'] - } -}; - -export async function executeJavaScript(args: { code: string; timeout?: number; allow_node_modules?: boolean; context?: Record }): Promise { - const { code, timeout = 5000, allow_node_modules = false, context = {} } = args; - - try { - const mockResult = `Mock JavaScript execution result for:\n${code.substring(0, 100)}${code.length > 100 ? '...' : ''}`; - const mockError = Math.random() > 0.95 ? 'Mock JavaScript error: ReferenceError: variable is not defined' : null; - - return { - type: 'javascript_execution', - data: { - code, - result: mockError ? null : mockResult, - error: mockError, - console_output: ['Mock console.log output', 'Another console message'], - execution_time: Math.random() * 500 + 50, - memory_usage: Math.floor(Math.random() * 30) + 5, - allow_node_modules, - context_variables: Object.keys(context), - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`JavaScript execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const executeShellCommandDeclaration: FunctionDeclaration = { - name: 'execute_shell_command', - description: 'Execute a shell command in a controlled environment', - parameters: { - type: SchemaType.OBJECT, - properties: { - command: { - type: SchemaType.STRING, - description: 'Shell command to execute' - }, - working_directory: { - type: SchemaType.STRING, - description: 'Working directory for command execution' - }, - timeout: { - type: SchemaType.NUMBER, - description: 'Command timeout in milliseconds (default: 30000)' - }, - environment_vars: { - type: SchemaType.OBJECT, - description: 'Environment variables to set for the command', - properties: {} - } - }, - required: ['command'] - } -}; - -export async function executeShellCommand(args: { command: string; working_directory?: string; timeout?: number; environment_vars?: Record }): Promise { - const { command, working_directory, timeout = 30000, environment_vars = {} } = args; - - try { - const mockOutput = `Mock shell output for command: ${command}\nDirectory listing:\nfile1.txt\nfile2.txt\ndirectory1/\nCommand completed successfully`; - const mockError = Math.random() > 0.9 ? 'Mock shell error: Command not found' : null; - - return { - type: 'shell_execution', - data: { - command, - stdout: mockError ? '' : mockOutput, - stderr: mockError || '', - exit_code: mockError ? 127 : 0, - execution_time: Math.random() * 2000 + 200, - working_directory: working_directory || process.cwd(), - environment_vars, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Shell command execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const validateCodeDeclaration: FunctionDeclaration = { - name: 'validate_code', - description: 'Validate code syntax and check for potential security issues', - parameters: { - type: SchemaType.OBJECT, - properties: { - code: { - type: SchemaType.STRING, - description: 'Code to validate' - }, - language: { - type: SchemaType.STRING, - description: 'Programming language of the code', - enum: ['python', 'javascript', 'bash', 'typescript'], - format: 'enum' - }, - check_security: { - type: SchemaType.BOOLEAN, - description: 'Whether to perform security checks (default: true)' - } - }, - required: ['code', 'language'] - } -}; - -export async function validateCode(args: { code: string; language: string; check_security?: boolean }): Promise { - const { code, language, check_security = true } = args; - - try { - const mockIssues: Array<{ - type: string; - severity: string; - line: number; - message: string; - }> = []; - - if (Math.random() > 0.7) { - mockIssues.push({ - type: 'syntax', - severity: 'error', - line: Math.floor(Math.random() * 10) + 1, - message: 'Mock syntax error: Missing closing parenthesis' - }); - } - - if (check_security && Math.random() > 0.8) { - mockIssues.push({ - type: 'security', - severity: 'warning', - line: Math.floor(Math.random() * 10) + 1, - message: 'Mock security warning: Potential code injection vulnerability' - }); - } - - return { - type: 'code_validation', - data: { - code: code.substring(0, 200) + (code.length > 200 ? '...' : ''), - language, - valid: mockIssues.filter(i => i.severity === 'error').length === 0, - issues: mockIssues, - metrics: { - lines_of_code: code.split('\n').length, - complexity_score: Math.floor(Math.random() * 10) + 1, - maintainability_index: Math.floor(Math.random() * 100) + 1 - }, - check_security, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Code validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/plugins/emailPlugin.ts b/src/renderer/plugins/emailPlugin.ts deleted file mode 100644 index d70847d..0000000 --- a/src/renderer/plugins/emailPlugin.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const emailPluginManifest: PluginManifest = { - id: 'email-plugin', - name: 'Email Plugin', - description: 'Send emails, manage email communications, and handle attachments', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'emailPlugin.ts', - functions: ['sendEmail', 'checkInbox', 'replyToEmail', 'manageAttachments'] -}; - -export const sendEmailDeclaration: FunctionDeclaration = { - name: 'send_email', - description: 'Send an email to specified recipients', - parameters: { - type: SchemaType.OBJECT, - properties: { - to: { - type: SchemaType.ARRAY, - description: 'Email addresses of recipients', - items: { - type: SchemaType.STRING - } - }, - subject: { - type: SchemaType.STRING, - description: 'Email subject line' - }, - body: { - type: SchemaType.STRING, - description: 'Email body content' - }, - cc: { - type: SchemaType.ARRAY, - description: 'Email addresses for CC recipients', - items: { - type: SchemaType.STRING - } - }, - bcc: { - type: SchemaType.ARRAY, - description: 'Email addresses for BCC recipients', - items: { - type: SchemaType.STRING - } - }, - attachments: { - type: SchemaType.ARRAY, - description: 'File paths of attachments to include', - items: { - type: SchemaType.STRING - } - }, - priority: { - type: SchemaType.STRING, - description: 'Email priority level', - enum: ['low', 'normal', 'high'], - format: 'enum' - } - }, - required: ['to', 'subject', 'body'] - } -}; - -export async function sendEmail(args: { - to: string[]; - subject: string; - body: string; - cc?: string[]; - bcc?: string[]; - attachments?: string[]; - priority?: string -}): Promise { - const { to, subject, body, cc = [], bcc = [], attachments = [], priority = 'normal' } = args; - - try { - const mockMessageId = `mock-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`; - - return { - type: 'email_sent', - data: { - message_id: mockMessageId, - to, - cc, - bcc, - subject, - body: body.substring(0, 200) + (body.length > 200 ? '...' : ''), - attachments: attachments.map(path => ({ - filename: path.split('/').pop(), - path, - size: Math.floor(Math.random() * 1000000) + 1000 - })), - priority, - sent_time: new Date().toISOString(), - status: 'delivered', - delivery_time: Math.random() * 3000 + 500 - } - }; - } catch (error) { - throw new Error(`Failed to send email: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const checkInboxDeclaration: FunctionDeclaration = { - name: 'check_inbox', - description: 'Check inbox for new emails and return summary', - parameters: { - type: SchemaType.OBJECT, - properties: { - limit: { - type: SchemaType.NUMBER, - description: 'Maximum number of emails to retrieve (default: 10)' - }, - unread_only: { - type: SchemaType.BOOLEAN, - description: 'Whether to show only unread emails (default: false)' - }, - folder: { - type: SchemaType.STRING, - description: 'Email folder to check', - enum: ['inbox', 'sent', 'drafts', 'spam', 'trash'], - format: 'enum' - } - }, - required: [] - } -}; - -export async function checkInbox(args: { limit?: number; unread_only?: boolean; folder?: string }): Promise { - const { limit = 10, unread_only = false, folder = 'inbox' } = args; - - try { - const mockEmails = Array.from({ length: Math.min(limit, 5) }, (_, i) => ({ - id: `email-${i + 1}`, - from: `sender${i + 1}@example.com`, - subject: `Mock Email Subject ${i + 1}`, - preview: `This is a preview of mock email ${i + 1} content...`, - received_time: new Date(Date.now() - Math.random() * 86400000 * 7).toISOString(), - read: unread_only ? false : Math.random() > 0.5, - has_attachments: Math.random() > 0.7, - priority: Math.random() > 0.8 ? 'high' : 'normal', - size: Math.floor(Math.random() * 50000) + 1000 - })); - - const filteredEmails = unread_only ? mockEmails.filter(email => !email.read) : mockEmails; - - return { - type: 'inbox_check', - data: { - folder, - emails: filteredEmails, - total_count: filteredEmails.length, - unread_count: filteredEmails.filter(email => !email.read).length, - unread_only, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to check inbox: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const replyToEmailDeclaration: FunctionDeclaration = { - name: 'reply_to_email', - description: 'Reply to an existing email', - parameters: { - type: SchemaType.OBJECT, - properties: { - original_email_id: { - type: SchemaType.STRING, - description: 'ID of the original email to reply to' - }, - reply_body: { - type: SchemaType.STRING, - description: 'Content of the reply' - }, - reply_all: { - type: SchemaType.BOOLEAN, - description: 'Whether to reply to all recipients (default: false)' - }, - include_original: { - type: SchemaType.BOOLEAN, - description: 'Whether to include original message in reply (default: true)' - } - }, - required: ['original_email_id', 'reply_body'] - } -}; - -export async function replyToEmail(args: { - original_email_id: string; - reply_body: string; - reply_all?: boolean; - include_original?: boolean -}): Promise { - const { original_email_id, reply_body, reply_all = false, include_original = true } = args; - - try { - const mockReplyId = `reply-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`; - - return { - type: 'email_reply', - data: { - reply_id: mockReplyId, - original_email_id, - reply_body: reply_body.substring(0, 200) + (reply_body.length > 200 ? '...' : ''), - reply_all, - include_original, - recipients: reply_all ? ['sender@example.com', 'cc1@example.com', 'cc2@example.com'] : ['sender@example.com'], - subject: `Re: Mock Original Subject`, - sent_time: new Date().toISOString(), - status: 'sent' - } - }; - } catch (error) { - throw new Error(`Failed to reply to email: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const manageAttachmentsDeclaration: FunctionDeclaration = { - name: 'manage_attachments', - description: 'Download, save, or manage email attachments', - parameters: { - type: SchemaType.OBJECT, - properties: { - email_id: { - type: SchemaType.STRING, - description: 'ID of the email containing attachments' - }, - action: { - type: SchemaType.STRING, - description: 'Action to perform on attachments', - enum: ['list', 'download', 'save_all'], - format: 'enum' - }, - attachment_name: { - type: SchemaType.STRING, - description: 'Specific attachment name to download (for download action)' - }, - save_path: { - type: SchemaType.STRING, - description: 'Local path to save attachments' - } - }, - required: ['email_id', 'action'] - } -}; - -export async function manageAttachments(args: { - email_id: string; - action: string; - attachment_name?: string; - save_path?: string -}): Promise { - const { email_id, action, attachment_name, save_path } = args; - - try { - const mockAttachments = [ - { name: 'document.pdf', size: 245760, type: 'application/pdf' }, - { name: 'image.jpg', size: 102400, type: 'image/jpeg' }, - { name: 'spreadsheet.xlsx', size: 51200, type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } - ]; - - switch (action) { - case 'list': - return { - type: 'attachment_list', - data: { - email_id, - attachments: mockAttachments, - total_count: mockAttachments.length, - total_size: mockAttachments.reduce((sum, att) => sum + att.size, 0), - timestamp: new Date().toISOString() - } - }; - - case 'download': - if (!attachment_name) { - throw new Error('attachment_name is required for download action'); - } - const attachment = mockAttachments.find(att => att.name === attachment_name); - if (!attachment) { - throw new Error(`Attachment ${attachment_name} not found`); - } - return { - type: 'attachment_download', - data: { - email_id, - attachment_name, - local_path: save_path || `/tmp/${attachment_name}`, - size: attachment.size, - type: attachment.type, - download_time: Math.random() * 2000 + 500, - status: 'completed', - timestamp: new Date().toISOString() - } - }; - - case 'save_all': - return { - type: 'attachments_saved', - data: { - email_id, - saved_attachments: mockAttachments.map(att => ({ - name: att.name, - local_path: `${save_path || '/tmp'}/${att.name}`, - size: att.size - })), - save_path: save_path || '/tmp', - total_saved: mockAttachments.length, - total_size: mockAttachments.reduce((sum, att) => sum + att.size, 0), - timestamp: new Date().toISOString() - } - }; - - default: - throw new Error(`Unknown action: ${action}`); - } - } catch (error) { - throw new Error(`Failed to manage attachments: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/plugins/fileManagerPlugin.ts b/src/renderer/plugins/fileManagerPlugin.ts deleted file mode 100644 index cc55852..0000000 --- a/src/renderer/plugins/fileManagerPlugin.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const fileManagerPluginManifest: PluginManifest = { - id: 'file-manager-plugin', - name: 'File Manager', - description: 'Manage files and directories on the local system', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'fileManagerPlugin.ts', - functions: ['listFiles', 'readFile', 'writeFile'] -}; - -export const listFilesDeclaration: FunctionDeclaration = { - name: 'list_files', - description: 'List files and directories in a specified path', - parameters: { - type: SchemaType.OBJECT, - properties: { - path: { - type: SchemaType.STRING, - description: 'Directory path to list files from (default: current directory)' - }, - show_hidden: { - type: SchemaType.BOOLEAN, - description: 'Whether to show hidden files (default: false)' - } - }, - required: [] - } -}; - -export async function listFiles(args: { path?: string; show_hidden?: boolean }): Promise { - const { path = '.', show_hidden = false } = args; - - try { - const commonFiles = [ - { name: 'Documents', type: 'directory', size: null, modified: new Date(Date.now() - 86400000).toISOString() }, - { name: 'Downloads', type: 'directory', size: null, modified: new Date(Date.now() - 172800000).toISOString() }, - { name: 'Desktop', type: 'directory', size: null, modified: new Date(Date.now() - 259200000).toISOString() }, - { name: 'package.json', type: 'file', size: 1247, modified: new Date(Date.now() - 345600000).toISOString() }, - { name: 'README.md', type: 'file', size: 2048, modified: new Date(Date.now() - 432000000).toISOString() }, - { name: 'tsconfig.json', type: 'file', size: 512, modified: new Date(Date.now() - 518400000).toISOString() }, - { name: 'src', type: 'directory', size: null, modified: new Date(Date.now() - 604800000).toISOString() } - ]; - - const hiddenFiles = [ - { name: '.gitignore', type: 'file', size: 256, modified: new Date(Date.now() - 691200000).toISOString() }, - { name: '.env', type: 'file', size: 128, modified: new Date(Date.now() - 777600000).toISOString() }, - { name: '.vscode', type: 'directory', size: null, modified: new Date(Date.now() - 864000000).toISOString() } - ]; - - let files = [...commonFiles]; - if (show_hidden) { - files = [...files, ...hiddenFiles]; - } - - if (path.includes('src')) { - files = [ - { name: 'main', type: 'directory', size: null, modified: new Date(Date.now() - 86400000).toISOString() }, - { name: 'renderer', type: 'directory', size: null, modified: new Date(Date.now() - 172800000).toISOString() }, - { name: 'types', type: 'directory', size: null, modified: new Date(Date.now() - 259200000).toISOString() } - ]; - } - - return { - type: 'file_list', - data: { - path: path === '.' ? process.cwd?.() || '/current/directory' : path, - files, - total_count: files.length, - hidden_count: show_hidden ? hiddenFiles.length : 0, - permissions: { - readable: true, - writable: true, - executable: path.includes('bin') || path.includes('scripts') - }, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to list files in ${path}: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const readFileDeclaration: FunctionDeclaration = { - name: 'read_file', - description: 'Read the contents of a text file', - parameters: { - type: SchemaType.OBJECT, - properties: { - file_path: { - type: SchemaType.STRING, - description: 'Path to the file to read' - }, - encoding: { - type: SchemaType.STRING, - description: 'File encoding (default: utf-8)', - enum: ['utf-8', 'ascii', 'base64'], - format: 'enum' - } - }, - required: ['file_path'] - } -}; - -export async function readFile(args: { file_path: string; encoding?: string }): Promise { - const { file_path, encoding = 'utf-8' } = args; - - try { - let content = ''; - const extension = file_path.split('.').pop()?.toLowerCase(); - - switch (extension) { - case 'json': - content = JSON.stringify({ - name: "AI Chat Desktop", - version: "1.0.0", - description: "Modern AI chat application with plugin system", - main: "dist/main.js", - scripts: { - dev: "webpack serve --mode development", - build: "webpack --mode production" - }, - dependencies: { - "@google/generative-ai": "^0.1.0", - "electron": "^28.0.0" - } - }, null, 2); - break; - - case 'md': - content = `# ${file_path.split('/').pop()?.replace('.md', '') || 'Document'} - -This is a markdown document that would contain actual file content in a real implementation. - -## Features -- Modern AI chat interface -- Plugin system for extensibility -- Multi-LLM support -- Agent mode for computer control - -## Installation -\`\`\`bash -npm install -npm run dev -\`\`\` - -Last modified: ${new Date().toLocaleDateString()}`; - break; - - case 'ts': - case 'js': - content = `// ${file_path} - -export interface FileContent { - path: string; - content: string; - encoding: string; - lastModified: Date; -} - -export class FileManager { - constructor() { - console.log('FileManager initialized'); - } - - async readFile(path: string): Promise { - return { - path, - content: 'File content', - encoding: 'utf-8', - lastModified: new Date() - }; - } -}`; - break; - - case 'txt': - default: - content = `Content of ${file_path} - -This file would contain the actual text content in a real implementation. -The file system operations would be handled through Electron's main process -using Node.js fs module for security reasons. - -File encoding: ${encoding} -Read timestamp: ${new Date().toISOString()} - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim -veniam, quis nostrud exercitation ullamco laboris.`; - break; - } - - const stats = { - size: content.length, - created: new Date(Date.now() - Math.random() * 86400000 * 30).toISOString(), - modified: new Date(Date.now() - Math.random() * 86400000 * 7).toISOString(), - accessed: new Date().toISOString(), - isDirectory: false, - isFile: true, - permissions: { - readable: true, - writable: !file_path.includes('node_modules'), - executable: extension === 'sh' || extension === 'exe' - } - }; - - return { - type: 'file_content', - data: { - file_path, - content, - encoding, - size: content.length, - stats, - lines: content.split('\n').length, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to read file ${file_path}: ${error instanceof Error ? error.message : 'File not found or permission denied'}`); - } -} - -export const writeFileDeclaration: FunctionDeclaration = { - name: 'write_file', - description: 'Write content to a text file', - parameters: { - type: SchemaType.OBJECT, - properties: { - file_path: { - type: SchemaType.STRING, - description: 'Path to the file to write' - }, - content: { - type: SchemaType.STRING, - description: 'Content to write to the file' - }, - encoding: { - type: SchemaType.STRING, - description: 'File encoding (default: utf-8)', - enum: ['utf-8', 'ascii'], - format: 'enum' - }, - append: { - type: SchemaType.BOOLEAN, - description: 'Whether to append to the file instead of overwriting (default: false)' - } - }, - required: ['file_path', 'content'] - } -}; - -export async function writeFile(args: { file_path: string; content: string; encoding?: string; append?: boolean }): Promise { - const { file_path, content, encoding = 'utf-8', append = false } = args; - - try { - - if (!file_path || file_path.trim() === '') { - throw new Error('File path cannot be empty'); - } - - const restrictedPaths = ['/system', '/windows', '/etc', 'node_modules']; - const isRestricted = restrictedPaths.some(restricted => - file_path.toLowerCase().includes(restricted.toLowerCase()) - ); - - if (isRestricted) { - throw new Error(`Access denied: Cannot write to restricted path ${file_path}`); - } - - const maxFileSize = 10 * 1024 * 1024; // 10MB - if (content.length > maxFileSize) { - throw new Error(`File too large: ${content.length} bytes exceeds maximum of ${maxFileSize} bytes`); - } - - const backupInfo = !append ? { - backup_created: Math.random() > 0.5, - backup_path: `${file_path}.backup.${Date.now()}`, - original_size: Math.floor(Math.random() * 10000) - } : null; - - const writeResult: any = { - file_path, - bytes_written: content.length, - encoding, - append, - operation: append ? 'append' : 'write', - backup_info: backupInfo, - file_stats: { - size_before: append ? Math.floor(Math.random() * 5000) : 0, - size_after: append ? Math.floor(Math.random() * 5000) + content.length : content.length, - lines_written: content.split('\n').length, - created: !append, - modified: true - }, - permissions: { - readable: true, - writable: true, - executable: file_path.endsWith('.sh') || file_path.endsWith('.exe') - }, - timestamp: new Date().toISOString(), - status: 'success' - }; - - const warnings = []; - if (file_path.includes(' ')) { - warnings.push('File path contains spaces which may cause issues on some systems'); - } - if (content.includes('\r\n') && encoding === 'utf-8') { - warnings.push('File contains Windows line endings (CRLF)'); - } - if (content.length === 0) { - warnings.push('Writing empty content to file'); - } - - if (warnings.length > 0) { - writeResult.warnings = warnings; - } - - return { - type: 'file_write_result', - data: writeResult - }; - } catch (error) { - throw new Error(`Failed to write file ${file_path}: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/plugins/networkPlugin.ts b/src/renderer/plugins/networkPlugin.ts deleted file mode 100644 index d6a3722..0000000 --- a/src/renderer/plugins/networkPlugin.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const networkPluginManifest: PluginManifest = { - id: 'network-plugin', - name: 'Network Plugin', - description: 'Make HTTP requests, check connectivity, and download files', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'networkPlugin.ts', - functions: ['makeHttpRequest', 'checkConnectivity', 'downloadFile', 'pingHost'] -}; - -export const makeHttpRequestDeclaration: FunctionDeclaration = { - name: 'make_http_request', - description: 'Make an HTTP request to a specified URL', - parameters: { - type: SchemaType.OBJECT, - properties: { - url: { - type: SchemaType.STRING, - description: 'The URL to make the request to' - }, - method: { - type: SchemaType.STRING, - description: 'HTTP method to use', - enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], - format: 'enum' - }, - headers: { - type: SchemaType.OBJECT, - description: 'HTTP headers to include in the request', - properties: {} - }, - body: { - type: SchemaType.STRING, - description: 'Request body (for POST, PUT, PATCH requests)' - }, - timeout: { - type: SchemaType.NUMBER, - description: 'Request timeout in milliseconds (default: 5000)' - } - }, - required: ['url'] - } -}; - -export async function makeHttpRequest(args: { url: string; method?: string; headers?: Record; body?: string; timeout?: number }): Promise { - const { url, method = 'GET', headers = {}, body, timeout = 5000 } = args; - - try { - const mockResponse = { - status: 200, - statusText: 'OK', - headers: { - 'content-type': 'application/json', - 'content-length': '1234', - 'server': 'mock-server/1.0' - }, - data: method === 'GET' ? - { message: `Mock GET response from ${url}`, timestamp: new Date().toISOString() } : - { message: `Mock ${method} response from ${url}`, received_body: body, timestamp: new Date().toISOString() } - }; - - return { - type: 'http_response', - data: { - url, - method, - request_headers: headers, - request_body: body, - response: mockResponse, - response_time: Math.random() * 1000 + 100, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`HTTP request failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const checkConnectivityDeclaration: FunctionDeclaration = { - name: 'check_connectivity', - description: 'Check internet connectivity and network status', - parameters: { - type: SchemaType.OBJECT, - properties: { - test_urls: { - type: SchemaType.ARRAY, - description: 'URLs to test connectivity against', - items: { - type: SchemaType.STRING - } - }, - timeout: { - type: SchemaType.NUMBER, - description: 'Timeout for each connectivity test in milliseconds (default: 3000)' - } - }, - required: [] - } -}; - -export async function checkConnectivity(args: { test_urls?: string[]; timeout?: number }): Promise { - const { test_urls = ['https://google.com', 'https://cloudflare.com'], timeout = 3000 } = args; - - try { - const results = test_urls.map(url => ({ - url, - reachable: Math.random() > 0.1, - response_time: Math.random() * 500 + 50, - status_code: Math.random() > 0.1 ? 200 : 0 - })); - - const connected = results.some(r => r.reachable); - - return { - type: 'connectivity_check', - data: { - connected, - test_results: results, - network_info: { - ip_address: '192.168.1.100', - dns_servers: ['8.8.8.8', '1.1.1.1'], - gateway: '192.168.1.1' - }, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Connectivity check failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const downloadFileDeclaration: FunctionDeclaration = { - name: 'download_file', - description: 'Download a file from a URL to local storage', - parameters: { - type: SchemaType.OBJECT, - properties: { - url: { - type: SchemaType.STRING, - description: 'URL of the file to download' - }, - destination: { - type: SchemaType.STRING, - description: 'Local path where the file should be saved' - }, - overwrite: { - type: SchemaType.BOOLEAN, - description: 'Whether to overwrite existing files (default: false)' - } - }, - required: ['url', 'destination'] - } -}; - -export async function downloadFile(args: { url: string; destination: string; overwrite?: boolean }): Promise { - const { url, destination, overwrite = false } = args; - - try { - const mockFileSize = Math.floor(Math.random() * 10000000) + 1000; - const downloadTime = Math.random() * 5000 + 1000; - - return { - type: 'file_download', - data: { - url, - destination, - file_size: mockFileSize, - download_time: downloadTime, - download_speed: Math.floor(mockFileSize / (downloadTime / 1000)), - overwrite, - status: 'completed', - checksum: 'mock-md5-checksum-' + Math.random().toString(36).substring(7), - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`File download failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const pingHostDeclaration: FunctionDeclaration = { - name: 'ping_host', - description: 'Ping a host to check connectivity and measure latency', - parameters: { - type: SchemaType.OBJECT, - properties: { - host: { - type: SchemaType.STRING, - description: 'Hostname or IP address to ping' - }, - count: { - type: SchemaType.NUMBER, - description: 'Number of ping packets to send (default: 4)' - }, - timeout: { - type: SchemaType.NUMBER, - description: 'Timeout for each ping in milliseconds (default: 1000)' - } - }, - required: ['host'] - } -}; - -export async function pingHost(args: { host: string; count?: number; timeout?: number }): Promise { - const { host, count = 4, timeout = 1000 } = args; - - try { - const pings = Array.from({ length: count }, (_, i) => ({ - sequence: i + 1, - time: Math.random() * 100 + 10, - success: Math.random() > 0.05 - })); - - const successfulPings = pings.filter(p => p.success); - const avgTime = successfulPings.length > 0 ? - successfulPings.reduce((sum, p) => sum + p.time, 0) / successfulPings.length : 0; - - return { - type: 'ping_result', - data: { - host, - packets_sent: count, - packets_received: successfulPings.length, - packet_loss: ((count - successfulPings.length) / count) * 100, - min_time: Math.min(...successfulPings.map(p => p.time)), - max_time: Math.max(...successfulPings.map(p => p.time)), - avg_time: avgTime, - pings, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Ping failed: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/plugins/processManagementPlugin.ts b/src/renderer/plugins/processManagementPlugin.ts deleted file mode 100644 index 07f8a3e..0000000 --- a/src/renderer/plugins/processManagementPlugin.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const processManagementPluginManifest: PluginManifest = { - id: 'process-management-plugin', - name: 'Process Management', - description: 'Start/stop applications and manage system processes', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'processManagementPlugin.ts', - functions: ['startApplication', 'stopProcess', 'listProcesses', 'getProcessInfo'] -}; - -export const startApplicationDeclaration: FunctionDeclaration = { - name: 'start_application', - description: 'Start an application or program', - parameters: { - type: SchemaType.OBJECT, - properties: { - application_name: { - type: SchemaType.STRING, - description: 'Name or path of the application to start' - }, - arguments: { - type: SchemaType.ARRAY, - description: 'Command line arguments for the application', - items: { - type: SchemaType.STRING - } - }, - working_directory: { - type: SchemaType.STRING, - description: 'Working directory for the application' - } - }, - required: ['application_name'] - } -}; - -export async function startApplication(args: { application_name: string; arguments?: string[]; working_directory?: string }): Promise { - const { application_name, arguments: appArgs = [], working_directory } = args; - - try { - const mockProcessId = Math.floor(Math.random() * 10000) + 1000; - - return { - type: 'process_started', - data: { - application_name, - process_id: mockProcessId, - arguments: appArgs, - working_directory: working_directory || process.cwd(), - status: 'running', - start_time: new Date().toISOString(), - memory_usage: Math.floor(Math.random() * 100) + 10 - } - }; - } catch (error) { - throw new Error(`Failed to start application: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const stopProcessDeclaration: FunctionDeclaration = { - name: 'stop_process', - description: 'Stop a running process by ID or name', - parameters: { - type: SchemaType.OBJECT, - properties: { - process_id: { - type: SchemaType.NUMBER, - description: 'Process ID to stop' - }, - process_name: { - type: SchemaType.STRING, - description: 'Process name to stop' - }, - force: { - type: SchemaType.BOOLEAN, - description: 'Whether to force kill the process (default: false)' - } - }, - required: [] - } -}; - -export async function stopProcess(args: { process_id?: number; process_name?: string; force?: boolean }): Promise { - const { process_id, process_name, force = false } = args; - - try { - if (!process_id && !process_name) { - throw new Error('Either process_id or process_name must be provided'); - } - - return { - type: 'process_stopped', - data: { - process_id: process_id || Math.floor(Math.random() * 10000) + 1000, - process_name: process_name || 'mock-process', - force, - status: 'terminated', - stop_time: new Date().toISOString(), - exit_code: force ? -9 : 0 - } - }; - } catch (error) { - throw new Error(`Failed to stop process: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const listProcessesDeclaration: FunctionDeclaration = { - name: 'list_processes', - description: 'List running processes on the system', - parameters: { - type: SchemaType.OBJECT, - properties: { - filter_name: { - type: SchemaType.STRING, - description: 'Filter processes by name (partial match)' - }, - include_system: { - type: SchemaType.BOOLEAN, - description: 'Whether to include system processes (default: false)' - }, - max_results: { - type: SchemaType.NUMBER, - description: 'Maximum number of processes to return (default: 50)' - } - }, - required: [] - } -}; - -export async function listProcesses(args: { filter_name?: string; include_system?: boolean; max_results?: number }): Promise { - const { filter_name, include_system = false, max_results = 50 } = args; - - try { - const mockProcesses = [ - { pid: 1234, name: 'chrome', cpu: 15.2, memory: 256.8, status: 'running' }, - { pid: 5678, name: 'code', cpu: 8.5, memory: 128.4, status: 'running' }, - { pid: 9012, name: 'node', cpu: 12.1, memory: 89.6, status: 'running' }, - { pid: 3456, name: 'firefox', cpu: 6.3, memory: 198.2, status: 'running' }, - { pid: 7890, name: 'terminal', cpu: 2.1, memory: 45.7, status: 'running' } - ]; - - if (include_system) { - mockProcesses.push( - { pid: 1, name: 'systemd', cpu: 0.1, memory: 12.3, status: 'running' }, - { pid: 2, name: 'kthreadd', cpu: 0.0, memory: 0.0, status: 'running' } - ); - } - - let filteredProcesses = mockProcesses; - if (filter_name) { - filteredProcesses = mockProcesses.filter(p => - p.name.toLowerCase().includes(filter_name.toLowerCase()) - ); - } - - return { - type: 'process_list', - data: { - processes: filteredProcesses.slice(0, max_results), - total_count: filteredProcesses.length, - filter_name, - include_system, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to list processes: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const getProcessInfoDeclaration: FunctionDeclaration = { - name: 'get_process_info', - description: 'Get detailed information about a specific process', - parameters: { - type: SchemaType.OBJECT, - properties: { - process_id: { - type: SchemaType.NUMBER, - description: 'Process ID to get information for' - } - }, - required: ['process_id'] - } -}; - -export async function getProcessInfo(args: { process_id: number }): Promise { - const { process_id } = args; - - try { - return { - type: 'process_info', - data: { - pid: process_id, - name: 'mock-process', - command: '/usr/bin/mock-process --arg1 --arg2', - status: 'running', - cpu_percent: Math.random() * 20, - memory_percent: Math.random() * 10, - memory_rss: Math.floor(Math.random() * 500) + 50, - memory_vms: Math.floor(Math.random() * 1000) + 200, - start_time: new Date(Date.now() - Math.random() * 86400000).toISOString(), - user: 'user', - threads: Math.floor(Math.random() * 10) + 1, - open_files: Math.floor(Math.random() * 50) + 5, - connections: Math.floor(Math.random() * 20), - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to get process info: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/plugins/systemFunctions.ts b/src/renderer/plugins/systemFunctions.ts deleted file mode 100644 index d95337f..0000000 --- a/src/renderer/plugins/systemFunctions.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const getSystemInfoFunction: FunctionDeclaration = { - name: 'get_system_info', - description: 'Get basic system information like OS, platform, and current time', - parameters: { - type: SchemaType.OBJECT, - properties: { - info_type: { - type: SchemaType.STRING, - description: 'Type of system information to retrieve', - enum: ['os', 'platform', 'time', 'all'], - format: 'enum' - } - }, - required: ['info_type'] - } -}; - -export async function getSystemInfo(args: { info_type: string }): Promise { - const { info_type } = args; - - const getMemoryInfo = () => { - const memory = (navigator as any).deviceMemory || 'unknown'; - const connection = (navigator as any).connection; - return { - device_memory_gb: memory, - hardware_concurrency: navigator.hardwareConcurrency || 'unknown', - max_touch_points: navigator.maxTouchPoints || 0, - connection_type: connection?.effectiveType || 'unknown', - connection_downlink: connection?.downlink || 'unknown' - }; - }; - - const getScreenInfo = () => ({ - screen_width: screen.width, - screen_height: screen.height, - available_width: screen.availWidth, - available_height: screen.availHeight, - color_depth: screen.colorDepth, - pixel_depth: screen.pixelDepth, - device_pixel_ratio: window.devicePixelRatio || 1 - }); - - const getBrowserInfo = () => ({ - user_agent: navigator.userAgent, - vendor: navigator.vendor, - app_name: navigator.appName, - app_version: navigator.appVersion, - app_code_name: navigator.appCodeName, - product: navigator.product, - online: navigator.onLine, - cookie_enabled: navigator.cookieEnabled, - do_not_track: navigator.doNotTrack, - languages: navigator.languages || [navigator.language], - pdf_viewer_enabled: navigator.pdfViewerEnabled || false - }); - - const getLocationInfo = () => ({ - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - locale: Intl.DateTimeFormat().resolvedOptions().locale, - calendar: Intl.DateTimeFormat().resolvedOptions().calendar || 'gregory', - numbering_system: Intl.DateTimeFormat().resolvedOptions().numberingSystem || 'latn', - time_zone_name: Intl.DateTimeFormat().resolvedOptions().timeZoneName || 'short' - }); - - const getPerformanceInfo = () => { - const timing = performance.timing; - const navigation = performance.navigation; - return { - navigation_type: navigation?.type || 'unknown', - redirect_count: navigation?.redirectCount || 0, - page_load_time: timing ? timing.loadEventEnd - timing.navigationStart : 'unknown', - dom_ready_time: timing ? timing.domContentLoadedEventEnd - timing.navigationStart : 'unknown', - memory_used_js_heap: (performance as any).memory?.usedJSHeapSize || 'unknown', - memory_total_js_heap: (performance as any).memory?.totalJSHeapSize || 'unknown', - memory_js_heap_limit: (performance as any).memory?.jsHeapSizeLimit || 'unknown' - }; - }; - - switch (info_type) { - case 'os': - return { - type: 'os_info', - data: { - platform: navigator.platform, - user_agent: navigator.userAgent, - app_version: navigator.appVersion, - vendor: navigator.vendor, - ...getMemoryInfo(), - timestamp: new Date().toISOString() - } - }; - - case 'platform': - return { - type: 'platform_info', - data: { - platform: navigator.platform, - language: navigator.language, - languages: navigator.languages || [navigator.language], - cookie_enabled: navigator.cookieEnabled, - online: navigator.onLine, - ...getScreenInfo(), - timestamp: new Date().toISOString() - } - }; - - case 'time': - return { - type: 'time_info', - data: { - current_time: new Date().toISOString(), - local_time: new Date().toLocaleString(), - utc_time: new Date().toUTCString(), - unix_timestamp: Date.now(), - ...getLocationInfo(), - timestamp: new Date().toISOString() - } - }; - - case 'performance': - return { - type: 'performance_info', - data: { - ...getPerformanceInfo(), - timestamp: new Date().toISOString() - } - }; - - case 'browser': - return { - type: 'browser_info', - data: { - ...getBrowserInfo(), - timestamp: new Date().toISOString() - } - }; - - case 'all': - return { - type: 'full_system_info', - data: { - basic: { - platform: navigator.platform, - user_agent: navigator.userAgent, - language: navigator.language, - current_time: new Date().toISOString(), - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - cookie_enabled: navigator.cookieEnabled - }, - memory: getMemoryInfo(), - screen: getScreenInfo(), - browser: getBrowserInfo(), - location: getLocationInfo(), - performance: getPerformanceInfo(), - timestamp: new Date().toISOString() - } - }; - - default: - throw new Error(`Unknown info_type: ${info_type}. Available types: os, platform, time, performance, browser, all`); - } -} - -export const calculateFunction: FunctionDeclaration = { - name: 'calculate', - description: 'Perform basic mathematical calculations', - parameters: { - type: SchemaType.OBJECT, - properties: { - expression: { - type: SchemaType.STRING, - description: 'Mathematical expression to evaluate (e.g., "2 + 3 * 4")' - } - }, - required: ['expression'] - } -}; - -export async function calculate(args: { expression: string }): Promise { - const { expression } = args; - - try { - const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, ''); - if (sanitized !== expression) { - throw new Error('Invalid characters in expression'); - } - - const result = Function(`"use strict"; return (${sanitized})`)(); - - return { - type: 'calculation_result', - data: { - expression, - result, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Calculation error: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const showNotificationFunction: FunctionDeclaration = { - name: 'show_notification', - description: 'Display a desktop notification to the user', - parameters: { - type: SchemaType.OBJECT, - properties: { - title: { - type: SchemaType.STRING, - description: 'Title of the notification' - }, - message: { - type: SchemaType.STRING, - description: 'Message content of the notification' - }, - type: { - type: SchemaType.STRING, - description: 'Type of notification', - enum: ['info', 'success', 'warning', 'error'], - format: 'enum' - } - }, - required: ['title', 'message'] - } -}; - -export async function showNotification(args: { title: string; message: string; type?: string }): Promise { - const { title, message, type = 'info' } = args; - - try { - if ('Notification' in window) { - if (Notification.permission === 'granted') { - new Notification(title, { - body: message, - icon: type === 'error' ? '❌' : type === 'warning' ? 'âš ī¸' : type === 'success' ? '✅' : 'â„šī¸' - }); - } else if (Notification.permission !== 'denied') { - const permission = await Notification.requestPermission(); - if (permission === 'granted') { - new Notification(title, { - body: message, - icon: type === 'error' ? '❌' : type === 'warning' ? 'âš ī¸' : type === 'success' ? '✅' : 'â„šī¸' - }); - } - } - } - - return { - type: 'notification_result', - data: { - title, - message, - type, - timestamp: new Date().toISOString(), - status: 'sent' - } - }; - } catch (error) { - throw new Error(`Notification error: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/plugins/systemPlugin.ts b/src/renderer/plugins/systemPlugin.ts deleted file mode 100644 index a508c75..0000000 --- a/src/renderer/plugins/systemPlugin.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { - getSystemInfoFunction, - getSystemInfo, - calculateFunction, - calculate, - showNotificationFunction, - showNotification -} from './systemFunctions'; - -export const systemPluginManifest: PluginManifest = { - id: 'system-plugin', - name: 'System Plugin', - description: 'Basic system functions for getting system info, calculations, and notifications', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'systemPlugin.ts', - functions: ['getSystemInfo', 'calculate', 'showNotification'] -}; - -export const getSystemInfoDeclaration = getSystemInfoFunction; -export const calculateDeclaration = calculateFunction; -export const showNotificationDeclaration = showNotificationFunction; - -export { getSystemInfo, calculate, showNotification }; diff --git a/src/renderer/plugins/webBrowserPlugin.ts b/src/renderer/plugins/webBrowserPlugin.ts deleted file mode 100644 index 7f002cb..0000000 --- a/src/renderer/plugins/webBrowserPlugin.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { PluginManifest } from '../services/pluginService'; -import { FunctionDeclaration } from '../services/functionService'; -import { SchemaType } from '@google/generative-ai'; - -export const webBrowserPluginManifest: PluginManifest = { - id: 'web-browser-plugin', - name: 'Web Browser', - description: 'Navigate websites, extract content, and take screenshots', - version: '1.0.0', - author: 'AI Chat Desktop', - main: 'webBrowserPlugin.ts', - functions: ['navigateToUrl', 'extractPageContent', 'takeScreenshot', 'searchWeb'] -}; - -export const navigateToUrlDeclaration: FunctionDeclaration = { - name: 'navigate_to_url', - description: 'Navigate to a specific URL and return page information', - parameters: { - type: SchemaType.OBJECT, - properties: { - url: { - type: SchemaType.STRING, - description: 'The URL to navigate to' - }, - wait_for_load: { - type: SchemaType.BOOLEAN, - description: 'Whether to wait for the page to fully load (default: true)' - } - }, - required: ['url'] - } -}; - -export async function navigateToUrl(args: { url: string; wait_for_load?: boolean }): Promise { - const { url, wait_for_load = true } = args; - - try { - return { - type: 'navigation_result', - data: { - url, - title: `Mock Page Title for ${url}`, - status: 'success', - load_time: Math.random() * 2000 + 500, - timestamp: new Date().toISOString(), - page_info: { - has_forms: Math.random() > 0.5, - has_images: Math.random() > 0.3, - has_videos: Math.random() > 0.7 - } - } - }; - } catch (error) { - throw new Error(`Failed to navigate to URL: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const extractPageContentDeclaration: FunctionDeclaration = { - name: 'extract_page_content', - description: 'Extract text content, links, and metadata from the current page', - parameters: { - type: SchemaType.OBJECT, - properties: { - content_type: { - type: SchemaType.STRING, - description: 'Type of content to extract', - enum: ['text', 'links', 'images', 'all'], - format: 'enum' - }, - max_length: { - type: SchemaType.NUMBER, - description: 'Maximum length of extracted text (default: 5000)' - } - }, - required: [] - } -}; - -export async function extractPageContent(args: { content_type?: string; max_length?: number }): Promise { - const { content_type = 'all', max_length = 5000 } = args; - - try { - const mockContent = { - text: 'This is mock extracted text content from the webpage. It contains various information about the page topic.', - links: [ - { url: 'https://example.com/page1', text: 'Example Link 1' }, - { url: 'https://example.com/page2', text: 'Example Link 2' } - ], - images: [ - { src: 'https://example.com/image1.jpg', alt: 'Example Image 1' }, - { src: 'https://example.com/image2.png', alt: 'Example Image 2' } - ], - metadata: { - title: 'Mock Page Title', - description: 'Mock page description', - keywords: ['mock', 'example', 'content'] - } - }; - - let result; - switch (content_type) { - case 'text': - result = { text: mockContent.text.substring(0, max_length) }; - break; - case 'links': - result = { links: mockContent.links }; - break; - case 'images': - result = { images: mockContent.images }; - break; - default: - result = mockContent; - } - - return { - type: 'page_content', - data: { - content_type, - extracted_content: result, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to extract page content: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const takeScreenshotDeclaration: FunctionDeclaration = { - name: 'take_screenshot', - description: 'Take a screenshot of the current webpage', - parameters: { - type: SchemaType.OBJECT, - properties: { - full_page: { - type: SchemaType.BOOLEAN, - description: 'Whether to capture the full page or just the viewport (default: false)' - }, - format: { - type: SchemaType.STRING, - description: 'Screenshot format', - enum: ['png', 'jpeg'], - format: 'enum' - } - }, - required: [] - } -}; - -export async function takeScreenshot(args: { full_page?: boolean; format?: string }): Promise { - const { full_page = false, format = 'png' } = args; - - try { - return { - type: 'screenshot_result', - data: { - screenshot_path: `/tmp/screenshot_${Date.now()}.${format}`, - format, - full_page, - dimensions: { - width: full_page ? 1920 : 1280, - height: full_page ? 3000 : 720 - }, - file_size: Math.floor(Math.random() * 500000) + 100000, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to take screenshot: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} - -export const searchWebDeclaration: FunctionDeclaration = { - name: 'search_web', - description: 'Perform a web search and return results', - parameters: { - type: SchemaType.OBJECT, - properties: { - query: { - type: SchemaType.STRING, - description: 'Search query' - }, - max_results: { - type: SchemaType.NUMBER, - description: 'Maximum number of results to return (default: 10)' - }, - search_engine: { - type: SchemaType.STRING, - description: 'Search engine to use', - enum: ['google', 'bing', 'duckduckgo'], - format: 'enum' - } - }, - required: ['query'] - } -}; - -export async function searchWeb(args: { query: string; max_results?: number; search_engine?: string }): Promise { - const { query, max_results = 10, search_engine = 'google' } = args; - - try { - const mockResults = Array.from({ length: Math.min(max_results, 5) }, (_, i) => ({ - title: `Mock Search Result ${i + 1} for "${query}"`, - url: `https://example${i + 1}.com/result`, - snippet: `This is a mock search result snippet for the query "${query}". It contains relevant information about the topic.`, - rank: i + 1 - })); - - return { - type: 'search_results', - data: { - query, - search_engine, - results: mockResults, - total_results: mockResults.length, - search_time: Math.random() * 500 + 100, - timestamp: new Date().toISOString() - } - }; - } catch (error) { - throw new Error(`Failed to search web: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} diff --git a/src/renderer/services/llmService.ts b/src/renderer/services/llmService.ts deleted file mode 100644 index 12ee18b..0000000 --- a/src/renderer/services/llmService.ts +++ /dev/null @@ -1,559 +0,0 @@ -import { GoogleGenerativeAI } from '@google/generative-ai'; -import { FunctionDeclaration, FunctionCall, functionService } from './functionService'; - -export type LLMProvider = 'gemini' | 'openai' | 'claude' | 'mistral'; - -export interface LLMConfig { - provider: LLMProvider; - apiKey: string; - model: string; -} - -export interface StreamingResponse { - content: string; - isComplete: boolean; -} - -export interface LLMModel { - id: string; - name: string; - provider: LLMProvider; - description: string; - maxTokens: number; - supportsFunctions: boolean; -} - -export const AVAILABLE_MODELS: LLMModel[] = [ - { - id: 'gemini-2.0-flash-exp', - name: 'Gemini 2.0 Flash (Experimental)', - provider: 'gemini', - description: 'Latest experimental Gemini model with enhanced capabilities', - maxTokens: 1000000, - supportsFunctions: true - }, - { - id: 'gemini-1.5-pro', - name: 'Gemini 1.5 Pro', - provider: 'gemini', - description: 'Advanced reasoning and multimodal capabilities', - maxTokens: 2000000, - supportsFunctions: true - }, - { - id: 'gemini-1.5-flash', - name: 'Gemini 1.5 Flash', - provider: 'gemini', - description: 'Fast and efficient for everyday tasks', - maxTokens: 1000000, - supportsFunctions: true - }, - { - id: 'gpt-4o', - name: 'GPT-4o', - provider: 'openai', - description: 'Most advanced GPT-4 model with multimodal capabilities', - maxTokens: 128000, - supportsFunctions: true - }, - { - id: 'gpt-4o-mini', - name: 'GPT-4o Mini', - provider: 'openai', - description: 'Smaller, faster version of GPT-4o', - maxTokens: 128000, - supportsFunctions: true - }, - { - id: 'gpt-4-turbo', - name: 'GPT-4 Turbo', - provider: 'openai', - description: 'Enhanced GPT-4 with improved performance', - maxTokens: 128000, - supportsFunctions: true - }, - { - id: 'gpt-3.5-turbo', - name: 'GPT-3.5 Turbo', - provider: 'openai', - description: 'Fast and cost-effective for most tasks', - maxTokens: 16385, - supportsFunctions: true - }, - { - id: 'claude-3-5-sonnet-20241022', - name: 'Claude 3.5 Sonnet', - provider: 'claude', - description: 'Most intelligent Claude model with enhanced capabilities', - maxTokens: 200000, - supportsFunctions: true - }, - { - id: 'claude-3-5-haiku-20241022', - name: 'Claude 3.5 Haiku', - provider: 'claude', - description: 'Fast and lightweight Claude model', - maxTokens: 200000, - supportsFunctions: true - }, - { - id: 'claude-3-opus-20240229', - name: 'Claude 3 Opus', - provider: 'claude', - description: 'Most powerful Claude model for complex tasks', - maxTokens: 200000, - supportsFunctions: true - }, - { - id: 'mistral-large-latest', - name: 'Mistral Large', - provider: 'mistral', - description: 'Most capable Mistral model for complex reasoning', - maxTokens: 128000, - supportsFunctions: true - }, - { - id: 'mistral-medium-latest', - name: 'Mistral Medium', - provider: 'mistral', - description: 'Balanced performance and efficiency', - maxTokens: 32000, - supportsFunctions: false - }, - { - id: 'mistral-small-latest', - name: 'Mistral Small', - provider: 'mistral', - description: 'Fast and cost-effective for simple tasks', - maxTokens: 32000, - supportsFunctions: false - } -]; - -export class LLMService { - private genAI: GoogleGenerativeAI | null = null; - private currentModel: any = null; - private currentProvider: LLMProvider | null = null; - private currentModelId: string | null = null; - private apiKeys: Partial> = {}; - - async initialize(config: LLMConfig): Promise { - try { - this.currentProvider = config.provider; - this.currentModelId = config.model; - this.apiKeys[config.provider] = config.apiKey; - - switch (config.provider) { - case 'gemini': - await this.initializeGemini(config.apiKey, config.model); - break; - case 'openai': - await this.initializeOpenAI(config.apiKey, config.model); - break; - case 'claude': - await this.initializeClaude(config.apiKey, config.model); - break; - case 'mistral': - await this.initializeMistral(config.apiKey, config.model); - break; - default: - throw new Error(`Unsupported LLM provider: ${config.provider}`); - } - } catch (error) { - console.error(`Failed to initialize ${config.provider}:`, error); - throw new Error(`Failed to initialize ${config.provider} API`); - } - } - - async initializeGemini(apiKey: string, model: string = "gemini-2.0-flash-exp"): Promise { - try { - this.genAI = new GoogleGenerativeAI(apiKey); - this.currentModel = this.genAI.getGenerativeModel({ model }); - this.currentProvider = 'gemini'; - this.currentModelId = model; - } catch (error) { - console.error('Failed to initialize Gemini:', error); - throw new Error('Failed to initialize Gemini API'); - } - } - - async initializeOpenAI(apiKey: string, model: string = "gpt-4o"): Promise { - try { - this.currentModel = { - provider: 'openai', - model, - apiKey, - baseURL: 'https://api.openai.com/v1' - }; - this.currentProvider = 'openai'; - this.currentModelId = model; - - if (!apiKey.startsWith('sk-')) { - throw new Error('Invalid OpenAI API key format'); - } - } catch (error) { - console.error('Failed to initialize OpenAI:', error); - throw new Error('Failed to initialize OpenAI API'); - } - } - - async initializeClaude(apiKey: string, model: string = "claude-3-5-sonnet-20241022"): Promise { - try { - this.currentModel = { - provider: 'claude', - model, - apiKey, - baseURL: 'https://api.anthropic.com/v1' - }; - this.currentProvider = 'claude'; - this.currentModelId = model; - - if (!apiKey.startsWith('sk-ant-')) { - throw new Error('Invalid Claude API key format'); - } - } catch (error) { - console.error('Failed to initialize Claude:', error); - throw new Error('Failed to initialize Claude API'); - } - } - - async initializeMistral(apiKey: string, model: string = "mistral-large-latest"): Promise { - try { - this.currentModel = { - provider: 'mistral', - model, - apiKey, - baseURL: 'https://api.mistral.ai/v1' - }; - this.currentProvider = 'mistral'; - this.currentModelId = model; - } catch (error) { - console.error('Failed to initialize Mistral:', error); - throw new Error('Failed to initialize Mistral API'); - } - } - - async sendMessage( - message: string, - onStream?: (response: StreamingResponse) => void, - enableFunctions: boolean = false - ): Promise { - if (!this.currentModel || !this.currentProvider) { - throw new Error('LLM not initialized. Please check your API key.'); - } - - try { - switch (this.currentProvider) { - case 'gemini': - return await this.sendMessageGemini(message, onStream, enableFunctions); - case 'openai': - return await this.sendMessageOpenAI(message, onStream, enableFunctions); - case 'claude': - return await this.sendMessageClaude(message, onStream, enableFunctions); - case 'mistral': - return await this.sendMessageMistral(message, onStream, enableFunctions); - default: - throw new Error(`Unsupported provider: ${this.currentProvider}`); - } - } catch (error) { - console.error(`Error sending message to ${this.currentProvider}:`, error); - - if (error instanceof Error) { - if (error.message.includes('API_KEY_INVALID') || error.message.includes('401')) { - throw new Error(`Invalid API key for ${this.currentProvider}. Please check your API key.`); - } else if (error.message.includes('QUOTA_EXCEEDED') || error.message.includes('429')) { - throw new Error(`API quota exceeded for ${this.currentProvider}. Please check your usage limits.`); - } else if (error.message.includes('SAFETY') || error.message.includes('content_filter')) { - throw new Error('Content was blocked by safety filters.'); - } - } - - throw new Error(`Failed to get response from ${this.currentProvider}. Please try again.`); - } - } - - private async sendMessageGemini( - message: string, - onStream?: (response: StreamingResponse) => void, - enableFunctions: boolean = false - ): Promise { - let model = this.currentModel; - - if (enableFunctions) { - const functions = functionService.getFunctionDeclarations(); - if (functions.length > 0) { - model = this.genAI!.getGenerativeModel({ - model: this.currentModelId!, - tools: [{ functionDeclarations: functions }] - }); - } - } - - if (onStream) { - const result = await model.generateContentStream(message); - let fullResponse = ''; - let functionCalls: FunctionCall[] = []; - - for await (const chunk of result.stream) { - const chunkText = chunk.text(); - fullResponse += chunkText; - - if (chunk.functionCalls) { - const calls = chunk.functionCalls(); - if (calls) { - functionCalls.push(...calls); - } - } - - onStream({ - content: fullResponse, - isComplete: false - }); - } - - if (functionCalls.length > 0) { - const functionResults = await functionService.executeFunctions(functionCalls); - const resultsText = functionResults.map(result => - result.error - ? `Function ${result.name} failed: ${result.error}` - : `Function ${result.name} result: ${JSON.stringify(result.result, null, 2)}` - ).join('\n\n'); - - fullResponse += '\n\n' + resultsText; - } - - onStream({ - content: fullResponse, - isComplete: true - }); - - return fullResponse; - } else { - const result = await model.generateContent(message); - const response = await result.response; - let responseText = response.text(); - - if (response.functionCalls) { - const calls = response.functionCalls(); - if (calls && calls.length > 0) { - const functionResults = await functionService.executeFunctions(calls); - const resultsText = functionResults.map(result => - result.error - ? `Function ${result.name} failed: ${result.error}` - : `Function ${result.name} result: ${JSON.stringify(result.result, null, 2)}` - ).join('\n\n'); - - responseText += '\n\n' + resultsText; - } - } - - return responseText; - } - } - - private async sendMessageOpenAI( - message: string, - onStream?: (response: StreamingResponse) => void, - enableFunctions: boolean = false - ): Promise { - const simulatedResponse = `OpenAI ${this.currentModelId} Response:\n\n${message}\n\nThis is a simulated response from OpenAI's ${this.currentModelId} model. In a real implementation, this would make an actual API call to OpenAI's servers using the provided API key.`; - - if (onStream) { - const words = simulatedResponse.split(' '); - let currentResponse = ''; - - for (let i = 0; i < words.length; i++) { - currentResponse += (i > 0 ? ' ' : '') + words[i]; - onStream({ - content: currentResponse, - isComplete: false - }); - - await new Promise(resolve => setTimeout(resolve, 50)); - } - - onStream({ - content: simulatedResponse, - isComplete: true - }); - } - - return simulatedResponse; - } - - private async sendMessageClaude( - message: string, - onStream?: (response: StreamingResponse) => void, - enableFunctions: boolean = false - ): Promise { - const simulatedResponse = `Claude ${this.currentModelId} Response:\n\n${message}\n\nThis is a simulated response from Anthropic's ${this.currentModelId} model. In a real implementation, this would make an actual API call to Anthropic's servers using the provided API key.`; - - if (onStream) { - const words = simulatedResponse.split(' '); - let currentResponse = ''; - - for (let i = 0; i < words.length; i++) { - currentResponse += (i > 0 ? ' ' : '') + words[i]; - onStream({ - content: currentResponse, - isComplete: false - }); - - await new Promise(resolve => setTimeout(resolve, 60)); - } - - onStream({ - content: simulatedResponse, - isComplete: true - }); - } - - return simulatedResponse; - } - - private async sendMessageMistral( - message: string, - onStream?: (response: StreamingResponse) => void, - enableFunctions: boolean = false - ): Promise { - const simulatedResponse = `Mistral ${this.currentModelId} Response:\n\n${message}\n\nThis is a simulated response from Mistral's ${this.currentModelId} model. In a real implementation, this would make an actual API call to Mistral's servers using the provided API key.`; - - if (onStream) { - const words = simulatedResponse.split(' '); - let currentResponse = ''; - - for (let i = 0; i < words.length; i++) { - currentResponse += (i > 0 ? ' ' : '') + words[i]; - onStream({ - content: currentResponse, - isComplete: false - }); - - await new Promise(resolve => setTimeout(resolve, 40)); - } - - onStream({ - content: simulatedResponse, - isComplete: true - }); - } - - return simulatedResponse; - } - - async sendMessageWithFunctions( - message: string, - functions: any[], - onStream?: (response: StreamingResponse) => void - ): Promise<{ content: string; functionCalls?: any[] }> { - if (!this.currentModel) { - throw new Error('LLM not initialized. Please check your API key.'); - } - - try { - const model = this.genAI!.getGenerativeModel({ - model: "gemini-2.0-flash-exp", - tools: functions.length > 0 ? [{ functionDeclarations: functions }] : undefined - }); - - if (onStream) { - const result = await model.generateContentStream(message); - let fullResponse = ''; - let functionCalls: any[] = []; - - for await (const chunk of result.stream) { - const chunkText = chunk.text(); - fullResponse += chunkText; - - if (chunk.functionCalls) { - const calls = chunk.functionCalls(); - if (calls) { - functionCalls.push(...calls); - } - } - - onStream({ - content: fullResponse, - isComplete: false - }); - } - - onStream({ - content: fullResponse, - isComplete: true - }); - - return { - content: fullResponse, - functionCalls: functionCalls.length > 0 ? functionCalls : undefined - }; - } else { - const result = await model.generateContent(message); - const response = await result.response; - - return { - content: response.text(), - functionCalls: response.functionCalls ? response.functionCalls() : undefined - }; - } - } catch (error) { - console.error('Error sending message with functions:', error); - throw new Error('Failed to get response from AI model with functions.'); - } - } - - isInitialized(): boolean { - return this.currentModel !== null && this.currentProvider !== null; - } - - getCurrentProvider(): LLMProvider | null { - return this.currentProvider; - } - - getCurrentModel(): string | null { - return this.currentModelId; - } - - getAvailableModels(): LLMModel[] { - return AVAILABLE_MODELS; - } - - getModelsByProvider(provider: LLMProvider): LLMModel[] { - return AVAILABLE_MODELS.filter(model => model.provider === provider); - } - - getModelInfo(modelId: string): LLMModel | undefined { - return AVAILABLE_MODELS.find(model => model.id === modelId); - } - - hasApiKey(provider: LLMProvider): boolean { - return !!this.apiKeys[provider]; - } - - setApiKey(provider: LLMProvider, apiKey: string): void { - this.apiKeys[provider] = apiKey; - } - - getApiKey(provider: LLMProvider): string | undefined { - return this.apiKeys[provider]; - } - - async switchModel(config: LLMConfig): Promise { - await this.initialize(config); - } - - supportsStreaming(provider?: LLMProvider): boolean { - const targetProvider = provider || this.currentProvider; - return targetProvider !== null; // All providers support streaming in this implementation - } - - supportsFunctions(modelId?: string): boolean { - const targetModelId = modelId || this.currentModelId; - if (!targetModelId) return false; - - const modelInfo = this.getModelInfo(targetModelId); - return modelInfo?.supportsFunctions || false; - } -} - -export const llmService = new LLMService(); diff --git a/src/renderer/utils/storage.ts b/src/renderer/utils/storage.ts deleted file mode 100644 index b2da7d1..0000000 --- a/src/renderer/utils/storage.ts +++ /dev/null @@ -1,141 +0,0 @@ -export interface StoredChat { - id: string; - title: string; - timestamp: Date; - messages: Array<{ - id: string; - role: 'user' | 'assistant'; - content: string; - timestamp: Date; - }>; -} - -export interface StoredSettings { - apiKeys: { - [providerId: string]: string; - }; - selectedModel: string; - agentMode: boolean; - theme: 'dark' | 'light'; - language: 'en' | 'de'; -} - -class StorageManager { - private getStorageKey(key: string): string { - return `ai-chat-desktop-${key}`; - } - - saveApiKey(providerId: string, apiKey: string): void { - const settings = this.getSettings(); - settings.apiKeys[providerId] = apiKey; - this.saveSettings(settings); - } - - getApiKey(providerId: string): string { - const settings = this.getSettings(); - return settings.apiKeys[providerId] || ''; - } - - saveSettings(settings: Partial): void { - const currentSettings = this.getSettings(); - const updatedSettings = { ...currentSettings, ...settings }; - localStorage.setItem( - this.getStorageKey('settings'), - JSON.stringify(updatedSettings) - ); - } - - getSettings(): StoredSettings { - const defaultSettings: StoredSettings = { - apiKeys: {}, - selectedModel: 'gemini-2.0-flash-exp', - agentMode: false, - theme: 'dark', - language: 'en' - }; - - try { - const stored = localStorage.getItem(this.getStorageKey('settings')); - if (stored) { - return { ...defaultSettings, ...JSON.parse(stored) }; - } - } catch (error) { - console.error('Error loading settings:', error); - } - - return defaultSettings; - } - - saveChat(chat: StoredChat): void { - const chats = this.getChats(); - const existingIndex = chats.findIndex(c => c.id === chat.id); - - if (existingIndex >= 0) { - chats[existingIndex] = chat; - } else { - chats.unshift(chat); - } - - if (chats.length > 100) { - chats.splice(100); - } - - localStorage.setItem( - this.getStorageKey('chats'), - JSON.stringify(chats) - ); - } - - getChats(): StoredChat[] { - try { - const stored = localStorage.getItem(this.getStorageKey('chats')); - if (stored) { - const chats = JSON.parse(stored); - return chats.map((chat: any) => ({ - ...chat, - timestamp: new Date(chat.timestamp), - messages: chat.messages.map((msg: any) => ({ - ...msg, - timestamp: new Date(msg.timestamp) - })) - })); - } - } catch (error) { - console.error('Error loading chats:', error); - } - - return []; - } - - getChat(chatId: string): StoredChat | null { - const chats = this.getChats(); - return chats.find(chat => chat.id === chatId) || null; - } - - deleteChat(chatId: string): void { - const chats = this.getChats(); - const filteredChats = chats.filter(chat => chat.id !== chatId); - localStorage.setItem( - this.getStorageKey('chats'), - JSON.stringify(filteredChats) - ); - } - - generateChatTitle(firstMessage: string): string { - const maxLength = 50; - const cleaned = firstMessage.trim().replace(/\n/g, ' '); - - if (cleaned.length <= maxLength) { - return cleaned; - } - - return cleaned.substring(0, maxLength - 3) + '...'; - } - - clearAllData(): void { - localStorage.removeItem(this.getStorageKey('settings')); - localStorage.removeItem(this.getStorageKey('chats')); - } -} - -export const storage = new StorageManager(); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index ff0c959..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,66 +0,0 @@ -const path = require('path'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const webpack = require('webpack'); - -module.exports = { - mode: process.env.NODE_ENV || 'development', - entry: './src/renderer/index.tsx', - target: 'electron-renderer', - devtool: 'source-map', - module: { - rules: [ - { - test: /\.ts(x?)$/, - include: /src/, - use: [{ - loader: 'ts-loader', - options: { - configFile: 'tsconfig.webpack.json' - } - }] - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - } - ] - }, - output: { - path: path.resolve(__dirname, 'dist'), - filename: 'renderer.js' - }, - plugins: [ - new HtmlWebpackPlugin({ - template: './src/renderer/index.html' - }), - new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - global: ['global', 'global'], - }) - ], - resolve: { - extensions: ['.tsx', '.ts', '.js'], - fallback: { - "buffer": require.resolve("buffer"), - "events": require.resolve("events"), - "util": false, - "path": false, - "fs": false, - "os": false, - "crypto": false, - "stream": false, - "assert": false, - "http": false, - "https": false, - "url": false, - "zlib": false - } - }, - devServer: { - port: 3000, - hot: true, - static: { - directory: path.join(__dirname, 'dist') - } - } -};