mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
feat: Manual flow for adding an extension (#692)
This commit is contained in:
Generated
+35
-6
@@ -16,6 +16,7 @@
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-icons": "^1.3.1",
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.5",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-tabs": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.5",
|
||||
@@ -3103,9 +3104,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-select": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.4.tgz",
|
||||
"integrity": "sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==",
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.5.tgz",
|
||||
"integrity": "sha512-eVV7N8jBXAXnyrc+PsOF89O9AfVgGnbLxUtBb0clJ8y8ENMWLARGMI/1/SBRLz7u4HqxLgN71BJ17eono3wcjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.0",
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
@@ -3113,7 +3115,7 @@
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.3",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.4",
|
||||
"@radix-ui/react-focus-guards": "1.1.1",
|
||||
"@radix-ui/react-focus-scope": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
@@ -3126,8 +3128,35 @@
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0",
|
||||
"@radix-ui/react-use-previous": "1.1.0",
|
||||
"@radix-ui/react-visually-hidden": "1.1.1",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "^2.6.1"
|
||||
"aria-hidden": "^1.2.4",
|
||||
"react-remove-scroll": "^2.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz",
|
||||
"integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-icons": "^1.3.1",
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.5",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-tabs": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.5",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ScrollArea } from '../ui/scroll-area';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Settings as SettingsType } from './types';
|
||||
import { FullExtensionConfig, replaceWithShims } from '../../extensions';
|
||||
import { FullExtensionConfig, replaceWithShims, extendGoosed } from '../../extensions';
|
||||
import { ConfigureExtensionModal } from './extensions/ConfigureExtensionModal';
|
||||
import { ManualExtensionModal } from './extensions/ManualExtensionModal';
|
||||
import { showToast } from '../ui/toast';
|
||||
import BackButton from '../ui/BackButton';
|
||||
import { RecentModelsRadio } from './models/RecentModels';
|
||||
@@ -94,6 +96,7 @@ export default function Settings() {
|
||||
|
||||
const [extensionBeingConfigured, setExtensionBeingConfigured] =
|
||||
useState<FullExtensionConfig | null>(null);
|
||||
const [isManualModalOpen, setIsManualModalOpen] = useState(false);
|
||||
|
||||
// Persist settings changes
|
||||
React.useEffect(() => {
|
||||
@@ -249,16 +252,26 @@ export default function Settings() {
|
||||
<section id="extensions">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold text-textStandard">Extensions</h2>
|
||||
<button
|
||||
onClick={() =>
|
||||
window.electron.openInChrome(
|
||||
'https://silver-disco-nvm6v4e.pages.github.io/'
|
||||
)
|
||||
}
|
||||
className="text-indigo-500 hover:text-indigo-600 font-medium"
|
||||
>
|
||||
Add Extensions
|
||||
</button>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={() => setIsManualModalOpen(true)}
|
||||
className="text-indigo-500 hover:text-indigo-600 font-medium"
|
||||
title="Add Manually"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</button>{' '}
|
||||
|
|
||||
<button
|
||||
onClick={() =>
|
||||
window.electron.openInChrome(
|
||||
'https://silver-disco-nvm6v4e.pages.github.io/'
|
||||
)
|
||||
}
|
||||
className="text-indigo-500 hover:text-indigo-600 font-medium"
|
||||
>
|
||||
Browse Extensions
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-textStandard mb-4">{EXTENSIONS_DESCRIPTION}</p>
|
||||
{settings.extensions.length === 0 ? (
|
||||
@@ -292,6 +305,45 @@ export default function Settings() {
|
||||
extension={extensionBeingConfigured}
|
||||
onSubmit={handleExtensionConfigSubmit}
|
||||
/>
|
||||
|
||||
<ManualExtensionModal
|
||||
isOpen={isManualModalOpen}
|
||||
onClose={() => setIsManualModalOpen(false)}
|
||||
onSubmit={async (extension) => {
|
||||
// Create config for extendGoosed
|
||||
const config = {
|
||||
type: extension.type,
|
||||
...(extension.type === 'stdio' && {
|
||||
cmd: await replaceWithShims(extension.cmd),
|
||||
args: extension.args || [],
|
||||
}),
|
||||
...(extension.type === 'sse' && {
|
||||
uri: extension.uri,
|
||||
}),
|
||||
...(extension.type === 'builtin' && {
|
||||
name: extension.name,
|
||||
}),
|
||||
env_keys: extension.env_keys,
|
||||
};
|
||||
|
||||
try {
|
||||
const success = await extendGoosed(config);
|
||||
if (success) {
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
extensions: [...prev.extensions, extension],
|
||||
}));
|
||||
setIsManualModalOpen(false);
|
||||
showToast('Extension added successfully', 'success');
|
||||
} else {
|
||||
throw new Error('Failed to add extension');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding extension:', error);
|
||||
showToast('Error adding extension', 'error');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Card } from '../../ui/card';
|
||||
import { Button } from '../../ui/button';
|
||||
import { Input } from '../../ui/input';
|
||||
import { FullExtensionConfig } from '../../../extensions';
|
||||
import { showToast } from '../../ui/toast';
|
||||
import Select from 'react-select';
|
||||
import { createDarkSelectStyles, darkSelectTheme } from '../../ui/select-styles';
|
||||
|
||||
interface ManualExtensionModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (extension: FullExtensionConfig) => void;
|
||||
}
|
||||
|
||||
export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtensionModalProps) {
|
||||
const [formData, setFormData] = useState<
|
||||
Partial<FullExtensionConfig> & { commandInput?: string }
|
||||
>({
|
||||
type: 'stdio',
|
||||
enabled: true,
|
||||
args: [],
|
||||
commandInput: '',
|
||||
});
|
||||
const [envKey, setEnvKey] = useState('');
|
||||
const [envKeys, setEnvKeys] = useState<string[]>([]);
|
||||
|
||||
const typeOptions = [
|
||||
{ value: 'stdio', label: 'Standard IO' },
|
||||
{ value: 'sse', label: 'Server-Sent Events' },
|
||||
{ value: 'builtin', label: 'Built-in' },
|
||||
];
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!formData.id || !formData.name || !formData.description) {
|
||||
showToast('Please fill in all required fields', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.type === 'stdio' && !formData.commandInput) {
|
||||
showToast('Command is required for stdio type', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.type === 'sse' && !formData.uri) {
|
||||
showToast('URI is required for SSE type', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.type === 'builtin' && !formData.name) {
|
||||
showToast('Name is required for builtin type', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse command input into cmd and args
|
||||
let cmd = '';
|
||||
let args: string[] = [];
|
||||
if (formData.type === 'stdio' && formData.commandInput) {
|
||||
const parts = formData.commandInput.trim().split(/\s+/);
|
||||
[cmd, ...args] = parts;
|
||||
}
|
||||
|
||||
const extension: FullExtensionConfig = {
|
||||
...formData,
|
||||
type: formData.type!,
|
||||
enabled: true,
|
||||
env_keys: envKeys,
|
||||
...(formData.type === 'stdio' && { cmd, args }),
|
||||
} as FullExtensionConfig;
|
||||
|
||||
onSubmit(extension);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
type: 'stdio',
|
||||
enabled: true,
|
||||
args: [],
|
||||
commandInput: '',
|
||||
});
|
||||
setEnvKeys([]);
|
||||
setEnvKey('');
|
||||
};
|
||||
|
||||
const handleAddEnvKey = () => {
|
||||
if (envKey && !envKeys.includes(envKey)) {
|
||||
setEnvKeys([...envKeys, envKey]);
|
||||
setEnvKey('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveEnvKey = (key: string) => {
|
||||
setEnvKeys(envKeys.filter((k) => k !== key));
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm">
|
||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] bg-white dark:bg-gray-800 rounded-xl shadow-xl overflow-hidden p-[16px] pt-[24px] pb-0">
|
||||
<div className="px-8 pb-0 space-y-8">
|
||||
<div className="flex">
|
||||
<h2 className="text-2xl font-regular dark:text-white text-gray-900">
|
||||
Add Extension Manually
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Type
|
||||
</label>
|
||||
<Select
|
||||
options={typeOptions}
|
||||
value={typeOptions.find((option) => option.value === formData.type)}
|
||||
onChange={(option) =>
|
||||
setFormData({ ...formData, type: option?.value as FullExtensionConfig['type'] })
|
||||
}
|
||||
styles={createDarkSelectStyles()}
|
||||
theme={darkSelectTheme}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
ID *
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={formData.id || ''}
|
||||
onChange={(e) => setFormData({ ...formData, id: e.target.value })}
|
||||
className="w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Name *
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={formData.name || ''}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Description *
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={formData.description || ''}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
className="w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formData.type === 'stdio' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Command * (command and arguments separated by spaces)
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={formData.commandInput || ''}
|
||||
onChange={(e) => setFormData({ ...formData, commandInput: e.target.value })}
|
||||
placeholder="e.g. goosed mcp example"
|
||||
className="w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.type === 'sse' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
URI *
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={formData.uri || ''}
|
||||
onChange={(e) => setFormData({ ...formData, uri: e.target.value })}
|
||||
className="w-full"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Environment Variables
|
||||
</label>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Input
|
||||
type="text"
|
||||
value={envKey}
|
||||
onChange={(e) => setEnvKey(e.target.value)}
|
||||
placeholder="Add environment variable key"
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button type="button" onClick={handleAddEnvKey}>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
{envKeys.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{envKeys.map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="flex items-center justify-between bg-gray-100 dark:bg-gray-700 p-2 rounded"
|
||||
>
|
||||
<span className="text-sm">{key}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveEnvKey(key)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-[8px] ml-[-24px] mr-[-24px] pt-[16px]">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="ghost"
|
||||
className="w-full h-[60px] rounded-none border-t dark:border-gray-600 text-lg hover:bg-gray-50 hover:dark:text-black dark:text-white dark:border-gray-600 font-regular"
|
||||
>
|
||||
Add Extension
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
onClose();
|
||||
}}
|
||||
className="w-full h-[60px] rounded-none border-t dark:border-gray-600 text-gray-400 hover:bg-gray-50 dark:border-gray-600 text-lg font-regular"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { Plus } from 'lucide-react';
|
||||
import { createSelectedModel, useHandleModelSelection } from './utils';
|
||||
import { useActiveKeys } from '../api_keys/ActiveKeysContext';
|
||||
import { goose_models } from './hardcoded_stuff';
|
||||
import { createDarkSelectStyles, darkSelectTheme } from '../../ui/select-styles';
|
||||
|
||||
export function AddModelInline() {
|
||||
const { activeKeys } = useActiveKeys(); // Access active keys from context
|
||||
@@ -82,91 +83,8 @@ export function AddModelInline() {
|
||||
}}
|
||||
placeholder="Select provider"
|
||||
isClearable
|
||||
styles={{
|
||||
control: (base) => ({
|
||||
...base,
|
||||
minWidth: '200px',
|
||||
backgroundColor: '#1a1b1e', // Dark background
|
||||
borderColor: '#2a2b2e',
|
||||
color: '#ffffff',
|
||||
}),
|
||||
menu: (base) => ({
|
||||
...base,
|
||||
backgroundColor: '#1a1b1e', // Dark solid background
|
||||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
border: '1px solid #2a2b2e',
|
||||
// Force solid background
|
||||
background: '#1a1b1e',
|
||||
}),
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
backgroundColor: '#1a1b1e', // Dark solid background
|
||||
background: '#1a1b1e', // Force solid background
|
||||
padding: '4px',
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
backgroundColor: state.isFocused
|
||||
? '#2a2b2e' // Slightly lighter when focused
|
||||
: '#1a1b1e', // Dark background
|
||||
color: '#ffffff', // White text
|
||||
cursor: 'pointer',
|
||||
// Force solid background
|
||||
background: state.isFocused ? '#2a2b2e' : '#1a1b1e',
|
||||
':hover': {
|
||||
backgroundColor: '#2a2b2e', // Same as focused
|
||||
color: '#ffffff',
|
||||
background: '#2a2b2e', // Force solid background
|
||||
},
|
||||
padding: '8px',
|
||||
margin: '2px 0',
|
||||
borderRadius: '4px',
|
||||
}),
|
||||
singleValue: (base) => ({
|
||||
...base,
|
||||
color: '#ffffff', // White text
|
||||
}),
|
||||
input: (base) => ({
|
||||
...base,
|
||||
color: '#ffffff', // White text
|
||||
}),
|
||||
placeholder: (base) => ({
|
||||
...base,
|
||||
color: '#9ca3af', // Gray text
|
||||
}),
|
||||
dropdownIndicator: (base) => ({
|
||||
...base,
|
||||
color: '#9ca3af', // Gray color for the dropdown arrow
|
||||
':hover': {
|
||||
color: '#ffffff', // White on hover
|
||||
},
|
||||
}),
|
||||
indicatorSeparator: (base) => ({
|
||||
...base,
|
||||
backgroundColor: '#2a2b2e', // Dark separator
|
||||
}),
|
||||
}}
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
colors: {
|
||||
...theme.colors,
|
||||
primary: '#2a2b2e',
|
||||
primary75: '#2a2b2e',
|
||||
primary50: '#2a2b2e',
|
||||
primary25: '#2a2b2e',
|
||||
neutral0: '#1a1b1e',
|
||||
neutral5: '#1a1b1e',
|
||||
neutral10: '#2a2b2e',
|
||||
neutral20: '#2a2b2e',
|
||||
neutral30: '#3a3b3e',
|
||||
neutral40: '#ffffff',
|
||||
neutral50: '#ffffff',
|
||||
neutral60: '#ffffff',
|
||||
neutral70: '#ffffff',
|
||||
neutral80: '#ffffff',
|
||||
neutral90: '#ffffff',
|
||||
},
|
||||
})}
|
||||
styles={createDarkSelectStyles('200px')}
|
||||
theme={darkSelectTheme}
|
||||
/>
|
||||
<div className="relative" style={{ minWidth: '150px', maxWidth: '250px' }}>
|
||||
<Input
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { StylesConfig, ThemeConfig } from 'react-select';
|
||||
|
||||
export const createDarkSelectStyles = (minWidth?: string): StylesConfig => ({
|
||||
control: (base) => ({
|
||||
...base,
|
||||
...(minWidth ? { minWidth } : {}),
|
||||
backgroundColor: '#1a1b1e',
|
||||
borderColor: '#2a2b2e',
|
||||
color: '#ffffff',
|
||||
}),
|
||||
menu: (base) => ({
|
||||
...base,
|
||||
backgroundColor: '#1a1b1e',
|
||||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
border: '1px solid #2a2b2e',
|
||||
background: '#1a1b1e',
|
||||
}),
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
backgroundColor: '#1a1b1e',
|
||||
background: '#1a1b1e',
|
||||
padding: '4px',
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
backgroundColor: state.isFocused ? '#2a2b2e' : '#1a1b1e',
|
||||
color: '#ffffff',
|
||||
cursor: 'pointer',
|
||||
background: state.isFocused ? '#2a2b2e' : '#1a1b1e',
|
||||
':hover': {
|
||||
backgroundColor: '#2a2b2e',
|
||||
color: '#ffffff',
|
||||
background: '#2a2b2e',
|
||||
},
|
||||
padding: '8px',
|
||||
margin: '2px 0',
|
||||
borderRadius: '4px',
|
||||
}),
|
||||
singleValue: (base) => ({
|
||||
...base,
|
||||
color: '#ffffff',
|
||||
}),
|
||||
input: (base) => ({
|
||||
...base,
|
||||
color: '#ffffff',
|
||||
}),
|
||||
placeholder: (base) => ({
|
||||
...base,
|
||||
color: '#9ca3af',
|
||||
}),
|
||||
dropdownIndicator: (base) => ({
|
||||
...base,
|
||||
color: '#9ca3af',
|
||||
':hover': {
|
||||
color: '#ffffff',
|
||||
},
|
||||
}),
|
||||
indicatorSeparator: (base) => ({
|
||||
...base,
|
||||
backgroundColor: '#2a2b2e',
|
||||
}),
|
||||
});
|
||||
|
||||
export const darkSelectTheme: ThemeConfig = (theme) => ({
|
||||
...theme,
|
||||
colors: {
|
||||
...theme.colors,
|
||||
primary: '#2a2b2e',
|
||||
primary75: '#2a2b2e',
|
||||
primary50: '#2a2b2e',
|
||||
primary25: '#2a2b2e',
|
||||
neutral0: '#1a1b1e',
|
||||
neutral5: '#1a1b1e',
|
||||
neutral10: '#2a2b2e',
|
||||
neutral20: '#2a2b2e',
|
||||
neutral30: '#3a3b3e',
|
||||
neutral40: '#ffffff',
|
||||
neutral50: '#ffffff',
|
||||
neutral60: '#ffffff',
|
||||
neutral70: '#ffffff',
|
||||
neutral80: '#ffffff',
|
||||
neutral90: '#ffffff',
|
||||
},
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
export const Select = ({ children }: { children: ReactNode }) => {
|
||||
return <div className="relative">{children}</div>;
|
||||
};
|
||||
|
||||
export const SelectTrigger = ({
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<button onClick={onClick} className="p-2 border rounded-md w-full text-left bg-white">
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectValue = ({
|
||||
value,
|
||||
placeholder,
|
||||
}: {
|
||||
value?: string | null;
|
||||
placeholder: string;
|
||||
}) => {
|
||||
return <span>{value || placeholder}</span>;
|
||||
};
|
||||
|
||||
export const SelectContent = ({ isOpen, children }: { isOpen: boolean; children: ReactNode }) => {
|
||||
if (!isOpen) return null;
|
||||
return <ul className="absolute bg-white border rounded-md mt-2 w-full shadow-lg">{children}</ul>;
|
||||
};
|
||||
|
||||
export const SelectItem = ({
|
||||
value,
|
||||
onSelect,
|
||||
children,
|
||||
}: {
|
||||
value: string;
|
||||
onSelect: (value: string) => void;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<li className="p-2 cursor-pointer hover:bg-gray-100" onClick={() => onSelect(value)}>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -111,11 +111,6 @@ export const extendGoosed = async (config: ExtensionConfig) => {
|
||||
console.log('extendGoosed', config);
|
||||
// allowlist the CMD for stdio type
|
||||
if (config.type === 'stdio') {
|
||||
const allowedCMDs = ['goosed', 'npx', 'uvx'];
|
||||
if (!allowedCMDs.includes(config.cmd)) {
|
||||
console.error(`System ${config.cmd} is not supported right now`);
|
||||
return;
|
||||
}
|
||||
config.cmd = await replaceWithShims(config.cmd);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user