diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index e35bb5ec..a9bb1cd9 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -13,12 +13,13 @@ import { extractExtensionName } from './components/settings/extensions/utils'; import WelcomeView from './components/WelcomeView'; import ChatView from './components/ChatView'; -import SettingsView from './components/settings/SettingsView'; +import SettingsView, { type SettingsViewOptions } from './components/settings/SettingsView'; import MoreModelsView from './components/settings/models/MoreModelsView'; import ConfigureProvidersView from './components/settings/providers/ConfigureProvidersView'; import 'react-toastify/dist/ReactToastify.css'; +// Views and their options export type View = | 'welcome' | 'chat' @@ -27,15 +28,27 @@ export type View = | 'configureProviders' | 'configPage'; +export type ViewConfig = { + view: View; + viewOptions?: SettingsViewOptions | Record; +}; + export default function App() { const [fatalError, setFatalError] = useState(null); const [modalVisible, setModalVisible] = useState(false); const [pendingLink, setPendingLink] = useState(null); const [modalMessage, setModalMessage] = useState(''); const [isInstalling, setIsInstalling] = useState(false); - const [view, setView] = useState('welcome'); + const [{ view, viewOptions }, setInternalView] = useState({ + view: 'welcome', + viewOptions: {}, + }); + const { switchModel } = useModel(); const { addRecentModel } = useRecentModels(); + const setView = (view: View, viewOptions: Record = {}) => { + setInternalView({ view, viewOptions }); + }; // Utility function to extract the command from the link function extractCommand(link: string): string { @@ -193,6 +206,7 @@ export default function App() { setView('chat'); }} setView={setView} + viewOptions={viewOptions as SettingsViewOptions} /> )} {view === 'moreModels' && ( diff --git a/ui/desktop/src/components/settings/SettingsView.tsx b/ui/desktop/src/components/settings/SettingsView.tsx index 8f16fe2e..52f10b72 100644 --- a/ui/desktop/src/components/settings/SettingsView.tsx +++ b/ui/desktop/src/components/settings/SettingsView.tsx @@ -46,15 +46,20 @@ const DEFAULT_SETTINGS: SettingsType = { extensions: BUILT_IN_EXTENSIONS, }; +export type SettingsViewOptions = { + extensionId: string; + showEnvVars: boolean; +}; + export default function SettingsView({ onClose, setView, + viewOptions, }: { onClose: () => void; setView: (view: View) => void; + viewOptions: SettingsViewOptions; }) { - const [searchParams] = useState(() => new URLSearchParams(window.location.search)); - const [settings, setSettings] = React.useState(() => { const saved = localStorage.getItem('user_settings'); window.electron.logInfo('Settings: ' + saved); @@ -101,10 +106,10 @@ export default function SettingsView({ // Handle URL parameters for auto-opening extension configuration useEffect(() => { - const extensionId = searchParams.get('extensionId'); - const showEnvVars = searchParams.get('showEnvVars'); + const extensionId = viewOptions.extensionId; + const showEnvVars = viewOptions.showEnvVars; - if (extensionId && showEnvVars === 'true') { + if (extensionId && showEnvVars === true) { // Find the extension in settings const extension = settings.extensions.find((ext) => ext.id === extensionId); if (extension) { diff --git a/ui/desktop/src/extensions.ts b/ui/desktop/src/extensions.ts index 0ef4134f..f1524463 100644 --- a/ui/desktop/src/extensions.ts +++ b/ui/desktop/src/extensions.ts @@ -1,5 +1,6 @@ import { getApiUrl, getSecretKey } from './config'; import { type View } from './App'; +import { type SettingsViewOptions } from './components/settings/SettingsView'; import { toast } from 'react-toastify'; // ExtensionConfig type matching the Rust version @@ -194,7 +195,7 @@ function storeExtensionConfig(config: FullExtensionConfig) { localStorage.setItem('user_settings', JSON.stringify(userSettings)); console.log('Extension config stored successfully in user_settings'); // Notify settings update through electron IPC - window.electron.send('settings-updated'); + window.electron.emit('settings-updated'); } else { console.log('Extension config already exists in user_settings'); } @@ -258,7 +259,10 @@ function handleError(message: string, shouldThrow = false): void { } } -export async function addExtensionFromDeepLink(url: string, setView: (view: View) => void) { +export async function addExtensionFromDeepLink( + url: string, + setView: (view: View, options: SettingsViewOptions) => void +) { if (!url.startsWith('goose://extension')) { handleError( 'Failed to install extension: Invalid URL: URL must use the goose://extension scheme' @@ -344,9 +348,7 @@ export async function addExtensionFromDeepLink(url: string, setView: (view: View // Check if extension requires env vars and go to settings if so if (envVarsRequired(config)) { console.log('Environment variables required, redirecting to settings'); - setView('settings'); - // TODO - add code which can auto-open the modal on the settings view - // navigate(`/settings?extensionId=${config.id}&showEnvVars=true`); + setView('settings', { extensionId: config.id, showEnvVars: true }); return; } diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 9e97579f..b4c9255d 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -14,7 +14,6 @@ import { } from 'electron'; import started from 'electron-squirrel-startup'; import path from 'node:path'; -import { handleSquirrelEvent } from './setup-events'; import { startGoosed } from './goosed'; import { getBinaryPath } from './utils/binaryPath'; import { loadShellEnv } from './utils/loadEnv'; @@ -34,82 +33,13 @@ import { promisify } from 'util'; const exec = promisify(execCallback); -// Handle Squirrel events for Windows installer -if (process.platform === 'win32') { - console.log('Windows detected, command line args:', process.argv); - - if (handleSquirrelEvent()) { - // squirrel event handled and app will exit in 1000ms, so don't do anything else - process.exit(0); - } - - // Handle the protocol on Windows during first launch - if (process.argv.length >= 2) { - const url = process.argv[1]; - console.log('Checking URL from command line:', url); - if (url.startsWith('goose://')) { - console.log('Found goose:// URL in command line args'); - app.emit('open-url', { preventDefault: () => {} }, url); - } - } -} - -// Ensure single instance lock -const gotTheLock = app.requestSingleInstanceLock(); - -if (!gotTheLock) { - app.quit(); -} else { - app.on('second-instance', (event, commandLine, _workingDirectory) => { - // Someone tried to run a second instance - console.log('Second instance detected with args:', commandLine); - - // Get existing window or create new one - const existingWindows = BrowserWindow.getAllWindows(); - if (existingWindows.length > 0) { - const window = existingWindows[0]; - if (window.isMinimized()) window.restore(); - window.focus(); - - if (process.platform === 'win32') { - // Protocol handling for Windows - const url = commandLine[commandLine.length - 1]; - console.log('Checking last arg for protocol:', url); - if (url.startsWith('goose://')) { - console.log('Found goose:// URL in second instance'); - // Send the URL to the window - if (!window.webContents.isLoading()) { - window.webContents.send('add-extension', url); - } else { - window.webContents.once('did-finish-load', () => { - window.webContents.send('add-extension', url); - }); - } - } - } - } - }); -} - -// Handle creating/removing shortcuts on Windows when installing/uninstalling. if (started) app.quit(); -// Register protocol handler -if (process.platform === 'win32') { - const success = app.setAsDefaultProtocolClient('goose', process.execPath, ['--']); - console.log('Registering protocol handler for Windows:', success ? 'success' : 'failed'); -} else { - const success = app.setAsDefaultProtocolClient('goose'); - console.log('Registering protocol handler:', success ? 'success' : 'failed'); -} - -// Log if we're the default protocol handler -console.log('Is default protocol handler:', app.isDefaultProtocolClient('goose')); +app.setAsDefaultProtocolClient('goose'); // Triggered when the user opens "goose://..." links app.on('open-url', async (event, url) => { event.preventDefault(); - console.log('open-url:', url); // Get existing window or create new one let targetWindow: BrowserWindow; diff --git a/ui/desktop/src/preload.ts b/ui/desktop/src/preload.ts index 7a2c0281..943e27e6 100644 --- a/ui/desktop/src/preload.ts +++ b/ui/desktop/src/preload.ts @@ -26,6 +26,7 @@ type ElectronAPI = { channel: string, callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void ) => void; + emit: (channel: string, ...args: any[]) => void; }; type AppConfigAPI = { @@ -55,6 +56,9 @@ const electronAPI: ElectronAPI = { off: (channel: string, callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => { ipcRenderer.off(channel, callback); }, + emit: (channel: string, ...args: any[]) => { + ipcRenderer.emit(channel, ...args); + }, }; const appConfigAPI: AppConfigAPI = {