Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 87 additions & 6 deletions src/main/ipc/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ipcMain, BrowserWindow, webContents, dialog, shell } from 'electron';
import path from 'path';
import { databaseService, HistoryEntry, Bookmark, Tab, Download } from '../services/database';
import fs from 'fs';
import { databaseService, HistoryEntry, Bookmark, Tab } from '../services/database';
import {
validateUrl,
validatePositiveInteger,
Expand All @@ -11,7 +12,33 @@ import { ollamaService } from '../services/ollama';
import { captureService } from '../services/capture';
import { downloadService } from '../services/download';
import { createDownloadManagerWindow } from '../index';
import type { GenerateOptions, ChatOptions } from '../../shared/types';
import type {
GenerateOptions,
ChatOptions,
PersonalitiesConfig,
Personality,
} from '../../shared/types';

// Load personalities configuration
let personalitiesConfig: PersonalitiesConfig | null = null;
try {
const personalitiesPath = path.join(__dirname, '../../shared/personalities/personalities.json');
const personalitiesData = fs.readFileSync(personalitiesPath, 'utf-8');
personalitiesConfig = JSON.parse(personalitiesData);
} catch (error) {
console.error('Failed to load personalities config:', error);
}

// Helper function to get personality by id
function getPersonalityById(personalityId: string): Personality | null {
if (!personalitiesConfig) return null;

for (const category of Object.values(personalitiesConfig.categories)) {
const personality = category.personalities.find((p) => p.id === personalityId);
if (personality) return personality;
}
return null;
}

export function registerIpcHandlers() {
console.log('registerIpcHandlers called');
Expand Down Expand Up @@ -590,6 +617,11 @@ When Planning Mode is enabled, you have access to these tools:
const userInfo = databaseService.getSetting('user-info') || '';
const customInstructions = databaseService.getSetting('custom-instructions') || '';

// Get selected personality
const selectedPersonalityId =
databaseService.getSetting('selected-personality') || 'best-friend';
const selectedPersonality = getPersonalityById(selectedPersonalityId);

// Get current date and time
const now = new Date();
const dateTimeInfo = `Current date and time: ${now.toLocaleString('en-US', {
Expand All @@ -606,6 +638,11 @@ When Planning Mode is enabled, you have access to these tools:
// Build full system message - start with base prompt
let fullSystemMessage = `${defaultSystemPrompt}\n\n${dateTimeInfo}`;

// Add personality prompt if available
if (selectedPersonality) {
fullSystemMessage += `\n\n## AI Personality\nYou have been given the "${selectedPersonality.name}" personality. Follow these personality guidelines:\n\n${selectedPersonality.systemPrompt}`;
}

// Add user customizations at the bottom
if (userCustomPrompt && userCustomPrompt.trim()) {
fullSystemMessage += `\n\n## Additional Instructions\n${userCustomPrompt}`;
Expand Down Expand Up @@ -694,14 +731,16 @@ When Planning Mode is enabled, you have access to these tools:
}
});

ipcMain.handle('tool:analyze_page_content', async (event) => {
ipcMain.handle('tool:analyze_page_content', async (_event) => {
try {
// Find the active webview (browser tab) instead of the main window
const allWebContents = webContents.getAllWebContents();
const webviewContents = allWebContents.find((wc) => wc.getType() === 'webview');

if (!webviewContents) {
throw new Error('No browser tab is currently open. Please open a webpage first, then try again.');
throw new Error(
'No browser tab is currently open. Please open a webpage first, then try again.'
);
}

const capture = await captureService.capturePage(webviewContents, {
Expand All @@ -721,14 +760,16 @@ When Planning Mode is enabled, you have access to these tools:
}
});

ipcMain.handle('tool:capture_screenshot', async (event) => {
ipcMain.handle('tool:capture_screenshot', async (_event) => {
try {
// Find the active webview (browser tab) instead of the main window
const allWebContents = webContents.getAllWebContents();
const webviewContents = allWebContents.find((wc) => wc.getType() === 'webview');

if (!webviewContents) {
throw new Error('No browser tab is currently open. Please open a webpage first, then try again.');
throw new Error(
'No browser tab is currently open. Please open a webpage first, then try again.'
);
}

const screenshot = await captureService.captureScreenshot(webviewContents);
Expand Down Expand Up @@ -814,6 +855,46 @@ When Planning Mode is enabled, you have access to these tools:
}
});

// Personality handlers
ipcMain.handle('personalities:getAll', async () => {
try {
return personalitiesConfig;
} catch (error: any) {
console.error('personalities:getAll error:', error.message);
throw error;
}
});

ipcMain.handle('personalities:getCurrent', async () => {
try {
const selectedPersonalityId =
databaseService.getSetting('selected-personality') || 'best-friend';
const personality = getPersonalityById(selectedPersonalityId);
return personality || null;
} catch (error: any) {
console.error('personalities:getCurrent error:', error.message);
throw error;
}
});

ipcMain.handle('personalities:select', async (_event, personalityId: string) => {
try {
validateString(personalityId, 'Personality ID', 128);

// Verify the personality exists
const personality = getPersonalityById(personalityId);
if (!personality) {
throw new Error(`Personality not found: ${personalityId}`);
}

databaseService.setSetting('selected-personality', personalityId);
return personality;
} catch (error: any) {
console.error('personalities:select error:', error.message);
throw error;
}
});

// Models folder handlers
ipcMain.handle('models:getFolder', async () => {
try {
Expand Down
3 changes: 3 additions & 0 deletions src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ const ALLOWED_INVOKE_CHANNELS = [
'tool:web_search',
'settings:get',
'settings:set',
'personalities:getAll',
'personalities:getCurrent',
'personalities:select',
'models:getFolder',
'models:list',
'models:pull-progress',
Expand Down
6 changes: 6 additions & 0 deletions src/main/services/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ class DatabaseService {
this.setSetting('custom-instructions', '');
}

// Initialize default personality if not exists
const selectedPersonality = this.getSetting('selected-personality');
if (!selectedPersonality) {
this.setSetting('selected-personality', 'best-friend');
}

// Initialize default download settings if not exists
const defaultDownloadFolder = this.getSetting('default-download-folder');
if (!defaultDownloadFolder) {
Expand Down
33 changes: 28 additions & 5 deletions src/renderer/components/Browser/MultiWebViewContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
import React, { useEffect, useRef, useImperativeHandle, forwardRef, useState } from 'react';
import { useBrowserStore } from '../../store/browser';
import { useTabsStore } from '../../store/tabs';
import { browserDataService } from '../../services/browserData';
import { PersonalitySelector } from '../Settings/PersonalitySelector';

export interface WebViewHandle {
goBack: () => void;
Expand Down Expand Up @@ -30,6 +31,7 @@ export const MultiWebViewContainer = forwardRef<WebViewHandle>((props, ref) => {
} = useBrowserStore();
const { tabs, activeTabId, updateTab } = useTabsStore();
const webviewRefs = useRef<Record<string, any>>({});
const [isPersonalitySelectorOpen, setIsPersonalitySelectorOpen] = useState(false);

// Get active webview
const getActiveWebview = () => {
Expand Down Expand Up @@ -334,8 +336,9 @@ export const MultiWebViewContainer = forwardRef<WebViewHandle>((props, ref) => {
}, [tabs]);

return (
<div className="flex-1 relative bg-background">
{tabs.map((tab) => {
<>
<div className="flex-1 relative bg-background">
{tabs.map((tab) => {
const isVisible = tab.id === activeTabId;
const shouldRenderWebview = !tab.isSuspended;

Expand Down Expand Up @@ -385,7 +388,7 @@ export const MultiWebViewContainer = forwardRef<WebViewHandle>((props, ref) => {
{/* Welcome Screen Overlay - shown when no URL */}
{!tab.url && !tab.isSuspended && isVisible && (
<div className="absolute inset-0 flex flex-col items-center justify-center text-center p-8 z-10 bg-background">
<div className="space-y-4 max-w-md">
<div className="space-y-6 max-w-md">
<svg
className="w-16 h-16 mx-auto text-muted-foreground"
fill="none"
Expand All @@ -406,6 +409,19 @@ export const MultiWebViewContainer = forwardRef<WebViewHandle>((props, ref) => {
<p className="text-sm text-muted-foreground">
Click the AI button to chat with local models about any page.
</p>

{/* Personality Selection Button */}
<div className="pt-4">
<button
onClick={() => setIsPersonalitySelectorOpen(true)}
className="px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium shadow-lg"
>
Choose AI Personality
</button>
<p className="text-xs text-muted-foreground mt-2">
Customize how your AI assistant talks to you
</p>
</div>
</div>
</div>
)}
Expand Down Expand Up @@ -444,7 +460,14 @@ export const MultiWebViewContainer = forwardRef<WebViewHandle>((props, ref) => {
</div>
);
})}
</div>
</div>

{/* Personality Selector Modal */}
<PersonalitySelector
isOpen={isPersonalitySelectorOpen}
onClose={() => setIsPersonalitySelectorOpen(false)}
/>
</>
);
});

Expand Down
Loading