feat: goose windows (#880)

Co-authored-by: Ryan Versaw <ryan@versaw.com>
This commit is contained in:
Max Novich
2025-02-10 15:05:13 -08:00
committed by GitHub
parent 98aecbef23
commit cfd3ee8fd9
43 changed files with 1327 additions and 456 deletions
+12 -4
View File
@@ -3,14 +3,21 @@ import { useModel } from './settings/models/ModelContext';
import { useRecentModels } from './settings/models/RecentModels'; // Hook for recent models
import { Sliders } from 'lucide-react';
import { ModelRadioList } from './settings/models/ModelRadioList';
import { useNavigate } from 'react-router-dom';
// Remove react-router-dom usage
// import { useNavigate } from 'react-router-dom';
import { Document, ChevronUp, ChevronDown } from './icons';
import type { View } from '../ChatWindow';
export default function BottomMenu({ hasMessages }) {
export default function BottomMenu({
hasMessages,
setView,
}: {
hasMessages: boolean;
setView?: (view: View) => void;
}) {
const [isModelMenuOpen, setIsModelMenuOpen] = useState(false);
const { currentModel } = useModel();
const { recentModels } = useRecentModels(); // Get recent models
const navigate = useNavigate();
const dropdownRef = useRef<HTMLDivElement>(null);
// Add effect to handle clicks outside
@@ -126,7 +133,8 @@ export default function BottomMenu({ hasMessages }) {
border-t border-borderSubtle mt-2"
onClick={() => {
setIsModelMenuOpen(false);
navigate('/settings');
// Instead of navigate('/settings'), call setView('settings').
setView?.('settings');
}}
>
<span className="text-sm">Tools and Settings</span>
+12 -5
View File
@@ -2,18 +2,20 @@ import { Popover, PopoverContent, PopoverTrigger, PopoverPortal } from '@radix-u
import React, { useEffect, useState } from 'react';
import { FaMoon, FaSun } from 'react-icons/fa';
import VertDots from './ui/VertDots';
import { useNavigate } from 'react-router-dom';
// Removed react-router-dom import
// import { useNavigate } from 'react-router-dom';
import { More } from './icons';
import { Settings, Grid, MessageSquare } from 'lucide-react';
import { Button } from './ui/button';
import type { View } from '../../ChatWindow';
interface VersionInfo {
current_version: string;
available_versions: string[];
}
export default function MoreMenu() {
const navigate = useNavigate();
// Accept setView as a prop from the parent (e.g. ChatContent)
export default function MoreMenu({ setView }: { setView?: (view: View) => void }) {
const [open, setOpen] = useState(false);
const [versions, setVersions] = useState<VersionInfo | null>(null);
const [showVersions, setShowVersions] = useState(false);
@@ -229,7 +231,8 @@ export default function MoreMenu() {
<button
onClick={() => {
setOpen(false);
navigate('/settings');
// Instead of navigate('/settings'), call setView to switch.
setView?.('settings');
}}
className="w-full text-left p-2 text-sm hover:bg-bgSubtle transition-colors"
>
@@ -264,12 +267,16 @@ export default function MoreMenu() {
>
Reset Provider
</button>
{/* Provider keys settings */}
{process.env.NODE_ENV === 'development' && (
<button
onClick={() => {
setOpen(false);
navigate('/keys');
// Instead of navigate('/keys'), we might do setView('someKeysView') or open new window.
// For now, just do nothing or set to some placeholder.
// setView?.('keys');
window.electron.createChatWindow();
}}
className="w-full text-left p-2 text-sm hover:bg-bgSubtle transition-colors"
>
@@ -1,38 +0,0 @@
import React from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { ChatContent } from '../../ChatWindow';
import Settings from '../settings/Settings';
import MoreModelsSettings from '../settings/models/MoreModels';
import ConfigureProviders from '../settings/providers/ConfigureProviders';
import { WelcomeScreen } from '../welcome_screen/WelcomeScreen';
export const ChatRoutes = ({
chats,
setChats,
selectedChatId,
setSelectedChatId,
setProgressMessage,
setWorking,
}) => (
<Routes>
<Route
path="/chat/:id"
element={
<ChatContent
chats={chats}
setChats={setChats}
selectedChatId={selectedChatId}
setSelectedChatId={setSelectedChatId}
initialQuery={null}
setProgressMessage={setProgressMessage}
setWorking={setWorking}
/>
}
/>
<Route path="/settings" element={<Settings />} />
<Route path="/settings/more-models" element={<MoreModelsSettings />} />
<Route path="/settings/configure-providers" element={<ConfigureProviders />} />
<Route path="/welcome" element={<WelcomeScreen />} />
<Route path="*" element={<Navigate to="/chat/1" replace />} />
</Routes>
);
+25 -32
View File
@@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react';
import { ScrollArea } from '../ui/scroll-area';
import { useNavigate, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Settings as SettingsType } from './types';
import {
@@ -15,6 +14,7 @@ import { ConfigureBuiltInExtensionModal } from './extensions/ConfigureBuiltInExt
import BackButton from '../ui/BackButton';
import { RecentModelsRadio } from './models/RecentModels';
import { ExtensionItem } from './extensions/ExtensionItem';
import type { View } from '../../ChatWindow';
const EXTENSIONS_DESCRIPTION =
'The Model Context Protocol (MCP) is a system that allows AI models to securely connect with local or remote resources using standard server setups. It works like a client-server setup and expands AI capabilities using three main components: Prompts, Resources, and Tools.';
@@ -46,9 +46,18 @@ const DEFAULT_SETTINGS: SettingsType = {
extensions: BUILT_IN_EXTENSIONS,
};
export default function Settings() {
const navigate = useNavigate();
const location = useLocation();
// We'll accept two props:
// onClose: to go back to chat
// setView: to switch to moreModels, configureProviders, etc.
export default function Settings({
onClose,
setView,
}: {
onClose: () => void;
setView: (view: View) => void;
}) {
// We'll read query params from window.location instead of react-router's useLocation
const [searchParams] = useState(() => new URLSearchParams(window.location.search));
const [settings, setSettings] = React.useState<SettingsType>(() => {
const saved = localStorage.getItem('user_settings');
@@ -96,9 +105,8 @@ export default function Settings() {
// Handle URL parameters for auto-opening extension configuration
useEffect(() => {
const params = new URLSearchParams(location.search);
const extensionId = params.get('extensionId');
const showEnvVars = params.get('showEnvVars');
const extensionId = searchParams.get('extensionId');
const showEnvVars = searchParams.get('showEnvVars');
if (extensionId && showEnvVars === 'true') {
// Find the extension in settings
@@ -113,7 +121,9 @@ export default function Settings() {
}
}
}
}, [location.search, settings.extensions]);
// We only run this once on load
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [settings.extensions]);
const handleExtensionToggle = async (extensionId: string) => {
// Find the extension to get its current state
@@ -160,28 +170,11 @@ export default function Settings() {
extensions: prev.extensions.filter((ext) => ext.id !== extensionBeingConfigured.id),
}));
setExtensionBeingConfigured(null);
navigate('/settings', { replace: true });
}
};
const handleNavClick = (section: string, e: React.MouseEvent) => {
e.preventDefault();
const scrollArea = document.querySelector('[data-radix-scroll-area-viewport]');
const element = document.getElementById(section.toLowerCase());
if (scrollArea && element) {
const topPos = element.offsetTop;
scrollArea.scrollTo({
top: topPos,
behavior: 'smooth',
});
}
};
const handleExtensionConfigSubmit = () => {
setExtensionBeingConfigured(null);
// Clear the URL parameters after configuration
navigate('/settings', { replace: true });
};
const isBuiltIn = (extensionId: string) => {
@@ -197,7 +190,8 @@ export default function Settings() {
<div className="px-8 pt-6 pb-4">
<BackButton
onClick={() => {
navigate('/chat/1', { replace: true });
// Instead of navigate('/chat/1', { replace: true });
onClose();
}}
/>
<h1 className="text-3xl font-medium text-textStandard mt-1">Settings</h1>
@@ -210,7 +204,10 @@ export default function Settings() {
<div className="flex justify-between items-center mb-6 border-b border-borderSubtle px-8">
<h2 className="text-xl font-medium text-textStandard">Models</h2>
<button
onClick={() => navigate('/settings/more-models')}
onClick={() => {
// Instead of navigate('/settings/more-models'):
setView('moreModels');
}}
className="text-indigo-500 hover:text-indigo-600 text-sm"
>
Browse
@@ -230,7 +227,6 @@ export default function Settings() {
className="text-indigo-500 hover:text-indigo-600 text-sm"
title="Add Manually"
>
{/* <Plus className="h-4 w-4" /> */}
Add
</button>
@@ -255,7 +251,7 @@ export default function Settings() {
<ExtensionItem
key={ext.id}
{...ext}
canConfigure={true} // Ensure gear icon always appears
canConfigure={true}
onToggle={handleExtensionToggle}
onConfigure={(extension) => setExtensionBeingConfigured(extension)}
/>
@@ -273,7 +269,6 @@ export default function Settings() {
isOpen={!!extensionBeingConfigured && isBuiltIn(extensionBeingConfigured.id)}
onClose={() => {
setExtensionBeingConfigured(null);
navigate('/settings', { replace: true });
}}
extension={extensionBeingConfigured}
onSubmit={handleExtensionConfigSubmit}
@@ -283,8 +278,6 @@ export default function Settings() {
isOpen={!!extensionBeingConfigured}
onClose={() => {
setExtensionBeingConfigured(null);
// Clear URL parameters when closing manually
navigate('/settings', { replace: true });
}}
extension={extensionBeingConfigured}
onSubmit={handleExtensionConfigSubmit}
@@ -6,25 +6,27 @@ import BackButton from '../../ui/BackButton';
import { SearchBar } from './Search';
import { useModel } from './ModelContext';
import { AddModelInline } from './AddModelInline';
import { useNavigate } from 'react-router-dom';
// Removed react-router-dom usage
// import { useNavigate } from 'react-router-dom';
import { ScrollArea } from '../../ui/scroll-area';
import type { View } from '../../../ChatWindow';
export default function MoreModelsPage() {
export default function MoreModelsPage({
onClose,
setView,
}: {
onClose: () => void;
setView: (view: View) => void;
}) {
const { currentModel } = useModel();
const navigate = useNavigate();
return (
<div className="h-screen w-full">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div>
<ScrollArea className="h-full w-full">
{/*
Instead of forcing one row, allow the layout
to stack vertically on small screens:
*/}
<div className="px-8 pt-6 pb-4">
<BackButton />
<BackButton onClick={onClose} />
<h1 className="text-3xl font-medium text-textStandard mt-1">Browse models</h1>
</div>
@@ -34,7 +36,7 @@ export default function MoreModelsPage() {
<div className="flex justify-between items-center mb-6 border-b border-borderSubtle px-8">
<h2 className="text-xl font-medium text-textStandard">Models</h2>
<button
onClick={() => navigate('/settings/configure-providers')}
onClick={() => setView('configureProviders')}
className="text-indigo-500 hover:text-indigo-600 text-sm"
>
Configure
@@ -2,15 +2,22 @@ import React from 'react';
import { ScrollArea } from '../../ui/scroll-area';
import BackButton from '../../ui/BackButton';
import { ConfigureProvidersGrid } from './ConfigureProvidersGrid';
import type { View } from '../../../ChatWindow';
export default function ConfigureProviders() {
export default function ConfigureProviders({
onClose,
setView,
}: {
onClose: () => void;
setView?: (view: View) => void;
}) {
return (
<div className="h-screen w-full">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div>
<ScrollArea className="h-full w-full">
<div className="px-8 pt-6 pb-4">
<BackButton />
<BackButton onClick={onClose} />
<h1 className="text-3xl font-medium text-textStandard mt-1">Configure</h1>
</div>