fix(ui): enable selection of zero-config providers in desktop GUI (#3378)
Signed-off-by: Kyle Santiago <kyle@privkey.io>
This commit is contained in:
@@ -44,7 +44,7 @@ const ModelAndProviderContext = createContext<ModelAndProviderContextType | unde
|
||||
export const ModelAndProviderProvider: React.FC<ModelAndProviderProviderProps> = ({ children }) => {
|
||||
const [currentModel, setCurrentModel] = useState<string | null>(null);
|
||||
const [currentProvider, setCurrentProvider] = useState<string | null>(null);
|
||||
const { read, upsert, getProviders } = useConfig();
|
||||
const { read, upsert, getProviders, config } = useConfig();
|
||||
|
||||
const changeModel = useCallback(
|
||||
async (model: Model) => {
|
||||
@@ -183,6 +183,19 @@ export const ModelAndProviderProvider: React.FC<ModelAndProviderProviderProps> =
|
||||
refreshCurrentModelAndProvider();
|
||||
}, [refreshCurrentModelAndProvider]);
|
||||
|
||||
// Extract config values for dependency array
|
||||
const configObj = config as Record<string, unknown>;
|
||||
const gooseModel = configObj?.GOOSE_MODEL;
|
||||
const gooseProvider = configObj?.GOOSE_PROVIDER;
|
||||
|
||||
// Listen for config changes and refresh when GOOSE_MODEL or GOOSE_PROVIDER changes
|
||||
useEffect(() => {
|
||||
// Only refresh if the config has loaded and model/provider values exist
|
||||
if (config && Object.keys(config).length > 0 && (gooseModel || gooseProvider)) {
|
||||
refreshCurrentModelAndProvider();
|
||||
}
|
||||
}, [config, gooseModel, gooseProvider, refreshCurrentModelAndProvider]);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
currentModel,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '../../../ui/dropdown-menu';
|
||||
import { useCurrentModelInfo } from '../../../BaseChat';
|
||||
import { useConfig } from '../../../ConfigContext';
|
||||
import { getProviderMetadata } from '../modelInterface';
|
||||
import { Alert } from '../../../alerts';
|
||||
import BottomMenuAlertPopover from '../../../bottom_menu/BottomMenuAlertPopover';
|
||||
import { Recipe } from '../../../../recipe';
|
||||
@@ -42,12 +43,13 @@ export default function ModelsBottomBar({
|
||||
getCurrentProviderDisplayName,
|
||||
} = useModelAndProvider();
|
||||
const currentModelInfo = useCurrentModelInfo();
|
||||
const { read } = useConfig();
|
||||
const { read, getProviders } = useConfig();
|
||||
const [displayProvider, setDisplayProvider] = useState<string | null>(null);
|
||||
const [displayModelName, setDisplayModelName] = useState<string>('Select Model');
|
||||
const [isAddModelModalOpen, setIsAddModelModalOpen] = useState(false);
|
||||
const [isLeadWorkerModalOpen, setIsLeadWorkerModalOpen] = useState(false);
|
||||
const [isLeadWorkerActive, setIsLeadWorkerActive] = useState(false);
|
||||
const [providerDefaultModel, setProviderDefaultModel] = useState<string | null>(null);
|
||||
|
||||
// Save recipe dialog state (like in RecipeEditor.tsx)
|
||||
const [showSaveDialog, setShowSaveDialog] = useState(false);
|
||||
@@ -91,10 +93,6 @@ export default function ModelsBottomBar({
|
||||
checkLeadWorker();
|
||||
};
|
||||
|
||||
// Determine which model to display - activeModel takes priority when lead/worker is active
|
||||
const displayModel =
|
||||
isLeadWorkerActive && currentModelInfo?.model ? currentModelInfo.model : displayModelName;
|
||||
|
||||
// Since currentModelInfo.mode is not working, let's determine mode differently
|
||||
// We'll need to get the lead model and compare it with the current model
|
||||
const [leadModelName, setLeadModelName] = useState<string>('');
|
||||
@@ -122,6 +120,12 @@ export default function ModelsBottomBar({
|
||||
: 'worker'
|
||||
: undefined;
|
||||
|
||||
// Determine which model to display - activeModel takes priority when lead/worker is active
|
||||
const displayModel =
|
||||
isLeadWorkerActive && currentModelInfo?.model
|
||||
? currentModelInfo.model
|
||||
: currentModel || providerDefaultModel || displayModelName;
|
||||
|
||||
// Update display provider when current provider changes
|
||||
useEffect(() => {
|
||||
if (currentProvider) {
|
||||
@@ -137,6 +141,24 @@ export default function ModelsBottomBar({
|
||||
}
|
||||
}, [currentProvider, getCurrentProviderDisplayName, getCurrentModelAndProviderForDisplay]);
|
||||
|
||||
// Fetch provider default model when provider changes and no current model
|
||||
useEffect(() => {
|
||||
if (currentProvider && !currentModel) {
|
||||
(async () => {
|
||||
try {
|
||||
const metadata = await getProviderMetadata(currentProvider, getProviders);
|
||||
setProviderDefaultModel(metadata.default_model);
|
||||
} catch (error) {
|
||||
console.error('Failed to get provider default model:', error);
|
||||
setProviderDefaultModel(null);
|
||||
}
|
||||
})();
|
||||
} else if (currentModel) {
|
||||
// Clear provider default when we have a current model
|
||||
setProviderDefaultModel(null);
|
||||
}
|
||||
}, [currentProvider, currentModel, getProviders]);
|
||||
|
||||
// Update display model name when current model changes
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
||||
+18
-8
@@ -116,14 +116,24 @@ export default function ProviderSetupActions({
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle hover:text-textStandard text-textSubtle hover:bg-bgSubtle text-md font-regular"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="ghost"
|
||||
onClick={onSubmit}
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle text-md hover:bg-bgSubtle text-textProminent font-medium"
|
||||
>
|
||||
Enable Provider
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle hover:text-textStandard text-textSubtle hover:bg-bgSubtle text-md font-regular"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
+32
@@ -5,6 +5,7 @@
|
||||
export const DefaultSubmitHandler = async (
|
||||
upsertFn: (key: string, value: unknown, isSecret: boolean) => Promise<void>,
|
||||
provider: {
|
||||
name: string;
|
||||
metadata: {
|
||||
config_keys?: Array<{
|
||||
name: string;
|
||||
@@ -18,6 +19,37 @@ export const DefaultSubmitHandler = async (
|
||||
) => {
|
||||
const parameters = provider.metadata.config_keys || [];
|
||||
|
||||
if (parameters.length === 0) {
|
||||
// For zero-config providers, mark them as configured
|
||||
const configKey = `${provider.name}_configured`;
|
||||
await upsertFn(configKey, true, false);
|
||||
|
||||
await upsertFn('GOOSE_PROVIDER', provider.name, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const requiredParams = parameters.filter((param) => param.required);
|
||||
if (requiredParams.length === 0 && parameters.length > 0) {
|
||||
const allOptionalWithDefaults = parameters.every(
|
||||
(param) => !param.required && param.default !== undefined
|
||||
);
|
||||
if (allOptionalWithDefaults) {
|
||||
const promises: Promise<void>[] = [];
|
||||
const configKey = `${provider.name}_configured`;
|
||||
promises.push(upsertFn(configKey, true, false));
|
||||
|
||||
for (const param of parameters) {
|
||||
if (param.default !== undefined) {
|
||||
const value =
|
||||
configValues[param.name] !== undefined ? configValues[param.name] : param.default;
|
||||
promises.push(upsertFn(param.name, value, param.secret === true));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
}
|
||||
|
||||
const upsertPromises = parameters.map(
|
||||
(parameter: { name: string; required?: boolean; default?: unknown; secret?: boolean }) => {
|
||||
// Skip parameters that don't have a value and aren't required
|
||||
|
||||
Reference in New Issue
Block a user