Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: settings v2 scaffolding #1374

Merged
merged 1 commit into from
Feb 25, 2025
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
31 changes: 21 additions & 10 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import WelcomeView from './components/WelcomeView';
import ChatView from './components/ChatView';
import SettingsView, { type SettingsViewOptions } from './components/settings/SettingsView';
import SettingsViewV2 from './components/settings_v2/SettingsView';
import MoreModelsView from './components/settings/models/MoreModelsView';
import ConfigureProvidersView from './components/settings/providers/ConfigureProvidersView';
import ProviderSettings from './components/settings/providers/providers/NewProviderSettingsPage';
Expand All @@ -28,11 +29,12 @@
| 'moreModels'
| 'configureProviders'
| 'configPage'
| 'alphaConfigureProviders';
| 'alphaConfigureProviders'
| 'settingsV2';

export type ViewConfig = {
view: View;
viewOptions?: SettingsViewOptions | Record<any, any>;

Check warning on line 37 in ui/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

Unexpected any. Specify a different type

Check warning on line 37 in ui/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

Unexpected any. Specify a different type
};

export default function App() {
Expand All @@ -48,7 +50,7 @@

const { switchModel } = useModel();
const { addRecentModel } = useRecentModels();
const setView = (view: View, viewOptions: Record<any, any> = {}) => {

Check warning on line 53 in ui/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

Unexpected any. Specify a different type

Check warning on line 53 in ui/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

Unexpected any. Specify a different type
setInternalView({ view, viewOptions });
};

Expand All @@ -61,7 +63,7 @@
}

useEffect(() => {
const handleAddExtension = (_: any, link: string) => {

Check warning on line 66 in ui/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

Unexpected any. Specify a different type
const command = extractCommand(link);
const extName = extractExtensionName(link);
window.electron.logInfo(`Adding extension from deep link ${link}`);
Expand Down Expand Up @@ -135,10 +137,10 @@
};

setupStoredProvider();
}, []);

Check warning on line 140 in ui/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

React Hook useEffect has missing dependencies: 'addRecentModel' and 'switchModel'. Either include them or remove the dependency array

useEffect(() => {
const handleFatalError = (_: any, errorMessage: string) => {

Check warning on line 143 in ui/desktop/src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint Electron Desktop App

Unexpected any. Specify a different type
setFatalError(errorMessage);
};

Expand Down Expand Up @@ -202,15 +204,24 @@
}}
/>
)}
{view === 'settings' && (
<SettingsView
onClose={() => {
setView('chat');
}}
setView={setView}
viewOptions={viewOptions as SettingsViewOptions}
/>
)}
{view === 'settings' &&
(process.env.ALPHA ? (
<SettingsViewV2
onClose={() => {
setView('chat');
}}
setView={setView}
viewOptions={viewOptions as SettingsViewOptions}
/>
) : (
<SettingsView
onClose={() => {
setView('chat');
}}
setView={setView}
viewOptions={viewOptions as SettingsViewOptions}
/>
))}
{view === 'moreModels' && (
<MoreModelsView
onClose={() => {
Expand Down
206 changes: 206 additions & 0 deletions ui/desktop/src/components/settings_v2/SettingsView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import React from 'react';
import { ScrollArea } from '../ui/scroll-area';
import BackButton from '../ui/BackButton';
import type { View } from '../../App';
import { Button } from '../ui/button';
import { Switch } from '../ui/switch';
import { Plus } from 'lucide-react';
import { Gear } from '../icons/Gear';
import { GPSIcon } from '../ui/icons';

interface ModelOption {
id: string;
name: string;
description: string;
selected: boolean;
}

interface ExtensionItem {
id: string;
title: string;
subtitle: string;
enabled: boolean;
canConfigure?: boolean;
}

// Mock data - replace with actual data source
const defaultModelOptions: ModelOption[] = [
{
id: 'gpt-4',
name: 'GPT-4',
description: 'Most capable model, best for complex tasks',
selected: true,
},
{
id: 'gpt-3.5',
name: 'GPT-3.5',
description: 'Fast and efficient for most tasks',
selected: false,
},
];

const extensionItems: ExtensionItem[] = [
{
id: 'dev',
title: 'Developer Tools',
subtitle: 'Code editing and shell access',
enabled: true,
canConfigure: true,
},
{
id: 'browser',
title: 'Web Browser',
subtitle: 'Internet access and web automation',
enabled: false,
canConfigure: true,
},
];

export type SettingsViewOptions = {
extensionId?: string;
showEnvVars?: boolean;
};

export default function SettingsView({
onClose,
setView,
viewOptions,
}: {
onClose: () => void;
setView: (view: View) => void;
viewOptions: SettingsViewOptions;
}) {
const [modelOptions, setModelOptions] = React.useState<ModelOption[]>(defaultModelOptions);
const [extensions, setExtensions] = React.useState<ExtensionItem[]>(extensionItems);

const handleModelSelect = (selectedId: string) => {
setModelOptions(
modelOptions.map((model) => ({
...model,
selected: model.id === selectedId,
}))
);
};

const handleExtensionToggle = (id: string) => {
setExtensions(
extensions.map((extension) => ({
...extension,
enabled: extension.id === id ? !extension.enabled : extension.enabled,
}))
);
};

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="flex flex-col pb-24">
<div className="px-8 pt-6 pb-4">
<BackButton onClick={() => onClose()} />
</div>

{/* Content Area */}
<div className="flex-1 pt-[20px]">
<div className="space-y-8">
{/* Models Section */}
<section id="models">
<div className="flex justify-between items-center mb-6 px-8">
<h1 className="text-3xl font-medium text-textStandard">Models</h1>
</div>
<div className="px-8">
<div className="space-y-2">
{modelOptions.map((model, index) => (
<React.Fragment key={model.id}>
<div className="flex items-center justify-between py-3">
<div className="space-y-1">
<h3 className="font-medium text-textStandard">{model.name}</h3>
<p className="text-sm text-textSubtle">{model.description}</p>
</div>
<input
type="radio"
name="model"
checked={model.selected}
onChange={() => handleModelSelect(model.id)}
className="h-4 w-4 text-white accent-[#393838] bg-[#393838] border-[#393838] checked:bg-[#393838] focus:ring-0 focus:ring-offset-0"
/>
</div>
{index < modelOptions.length - 1 && (
<div className="h-px bg-borderSubtle" />
)}
</React.Fragment>
))}
</div>
<div className="flex gap-4 pt-4 w-full">
<Button className="flex items-center gap-2 flex-1 justify-center bg-[#393838] hover:bg-subtle">
<Plus className="h-4 w-4" />
Add Model
</Button>
<Button className="flex items-center gap-2 flex-1 justify-center text-textSubtle border-standard bg-grey-60 hover:bg-subtle">
<Gear className="h-4 w-4" />
Configure Providers
</Button>
</div>
</div>
</section>

{/* Extensions Section */}
<section id="extensions">
<div className="flex justify-between items-center mb-6 px-8">
<h1 className="text-3xl font-medium text-textStandard">Extensions</h1>
</div>
<div className="px-8">
<p className="text-sm text-textStandard mb-6">
These extensions use the Model Context Protocol (MCP). They can expand Goose's
capabilities using three main components: Prompts, Resources, and Tools.
</p>
<div className="space-y-2">
{extensions.map((extension, index) => (
<React.Fragment key={extension.id}>
<div className="flex items-center justify-between py-3">
<div className="space-y-1">
<h3 className="font-medium text-textStandard">{extension.title}</h3>
<p className="text-sm text-textSubtle">{extension.subtitle}</p>
</div>
<div className="flex items-center gap-4">
{extension.canConfigure && (
<button className="text-textSubtle hover:text-textStandard">
<Gear className="h-5 w-5" />
</button>
)}
<Switch
checked={extension.enabled}
onCheckedChange={() => handleExtensionToggle(extension.id)}
className="bg-[#393838] [&_span[data-state]]:bg-white"
/>
</div>
</div>
{index < extensions.length - 1 && <div className="h-px bg-borderSubtle" />}
</React.Fragment>
))}
</div>
<div className="flex gap-4 pt-4 w-full">
<Button className="flex items-center gap-2 flex-1 justify-center bg-[#393838] hover:bg-subtle">
<Plus className="h-4 w-4" />
Manually Add
</Button>
<Button
className="flex items-center gap-2 flex-1 justify-center text-textSubtle border-standard bg-grey-60 hover:bg-subtle"
onClick={() =>
window.open('https://block.github.io/goose/v1/extensions/', '_blank')
}
>
<GPSIcon size={18} />
Visit Extensions
</Button>
</div>
</div>
</section>
</div>
</div>
</div>
</ScrollArea>
</div>
);
}
2 changes: 1 addition & 1 deletion ui/desktop/src/components/ui/BackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const BackButton: React.FC<BackButtonProps> = ({ onClick, className = '' }) => {
className={`flex items-center text-sm text-textSubtle group hover:text-textStandard ${className}`}
>
<Back className="w-3 h-3 group-hover:-translate-x-1 transition-all mr-1" />
<span>Back</span>
<span>Exit</span>
</button>
);
};
Expand Down
Loading