import { useState, useEffect, useMemo } from 'react'; import { Input } from '../../ui/input'; import { Button } from '../../ui/button'; import { useConfig } from '../../ConfigContext'; import { cn } from '../../../utils'; import { Save, RotateCcw, FileText, Settings } from 'lucide-react'; import { toastSuccess, toastError } from '../../../toasts'; import { getUiNames, providerPrefixes } from '../../../utils/configUtils'; import type { ConfigData, ConfigValue } from '../../../types/config'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '../../ui/dialog'; import { errorMessage } from '../../../utils/conversionUtils'; export default function ConfigSettings() { const { config, upsert } = useConfig(); const typedConfig = config as ConfigData; const [configValues, setConfigValues] = useState({}); const [modifiedKeys, setModifiedKeys] = useState>(new Set()); const [saving, setSaving] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [originalKeyOrder, setOriginalKeyOrder] = useState([]); useEffect(() => { setConfigValues(typedConfig); setModifiedKeys(new Set()); // Capture the original key order only on first load or when new keys are added const currentKeys = Object.keys(typedConfig); setOriginalKeyOrder((prevOrder) => { if (prevOrder.length === 0) { // First load - capture the initial order return currentKeys; } else if (currentKeys.length > prevOrder.length) { // New keys have been added - add them to the end while preserving existing order const newKeys = currentKeys.filter((key) => !prevOrder.includes(key)); return [...prevOrder, ...newKeys]; } // Don't reorder when keys are just updated/saved - preserve the original order return prevOrder; }); }, [typedConfig]); const handleChange = (key: string, value: string) => { setConfigValues((prev: ConfigData) => ({ ...prev, [key]: value, })); setModifiedKeys((prev) => { const newSet = new Set(prev); if (value !== String(typedConfig[key] || '')) { newSet.add(key); } else { newSet.delete(key); } return newSet; }); }; const handleSave = async (key: string) => { setSaving(key); try { await upsert(key, configValues[key], false); toastSuccess({ title: 'Configuration Updated', msg: `Successfully saved "${getUiNames(key)}"`, }); // Remove this key from modified keys since it's now saved setModifiedKeys((prev) => { const newSet = new Set(prev); newSet.delete(key); return newSet; }); } catch (error) { console.error('Failed to save config:', error); toastError({ title: 'Save Failed', msg: `Failed to save "${getUiNames(key)}"`, traceback: errorMessage(error), }); } finally { setSaving(null); } }; const handleReset = () => { setConfigValues(typedConfig); setModifiedKeys(new Set()); toastSuccess({ title: 'Configuration Reset', msg: 'All changes have been reverted', }); }; const handleModalClose = (open: boolean) => { if (!open && modifiedKeys.size > 0) { // Reset any unsaved changes when closing the modal setConfigValues(typedConfig); setModifiedKeys(new Set()); } setIsModalOpen(open); }; const currentProvider = typedConfig.GOOSE_PROVIDER || ''; const configEntries: [string, ConfigValue][] = useMemo(() => { const currentProviderPrefixes = providerPrefixes[currentProvider] || []; const allProviderPrefixes = Object.values(providerPrefixes).flat(); return originalKeyOrder .filter((key) => { // skip secrets if (key === 'extensions' || key.includes('_KEY') || key.includes('_TOKEN')) { return false; } // Only show provider-specific entries for the current provider const providerSpecific = allProviderPrefixes.some((prefix: string) => key.startsWith(prefix) ); if (providerSpecific) { return currentProviderPrefixes.some((prefix: string) => key.startsWith(prefix)); } return true; }) .map((key) => [key, configValues[key]]); }, [originalKeyOrder, configValues, currentProvider]); return ( Configuration Edit your goose configuration settings {currentProvider && ` (current settings for ${currentProvider})`} Configuration Editor Edit your goose configuration settings {currentProvider && ` (current settings for ${currentProvider})`}
{configEntries.length === 0 ? (

No configuration settings found.

) : ( configEntries.map(([key, _value]) => (
handleChange(key, e.target.value)} className={cn( 'text-textStandard border-borderSubtle hover:border-borderStandard transition-colors', modifiedKeys.has(key) && 'border-blue-500 focus:ring-blue-500/20' )} placeholder={`Enter ${getUiNames(key)}`} />
)) )}
{modifiedKeys.size > 0 && ( )}
); }