diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index be9d3367..d1c3e47e 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -29,15 +29,36 @@ interface ModelsDialogProps { onOpenChange: (open: boolean) => void; } +type ExtraArgValue = string | number | boolean | Record; + function convertExtraArgsToObject( args: ExtraArg[], -): Record { - const obj: Record = {}; +): Record { + const obj: Record = {}; args.forEach((arg) => { - if (arg.key.trim()) { - if (arg.type === 'number') obj[arg.key] = Number(arg.value); - else if (arg.type === 'boolean') obj[arg.key] = arg.value === 'true'; - else obj[arg.key] = arg.value; + if (!arg.key.trim()) return; + if (arg.type === 'number') { + obj[arg.key] = Number(arg.value); + } else if (arg.type === 'boolean') { + obj[arg.key] = arg.value === 'true'; + } else if (arg.type === 'object') { + const raw = arg.value.trim() || '{}'; + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + throw new Error(`Invalid JSON for extra parameter "${arg.key}"`); + } + if ( + parsed === null || + typeof parsed !== 'object' || + Array.isArray(parsed) + ) { + throw new Error(`Extra parameter "${arg.key}" must be a JSON object`); + } + obj[arg.key] = parsed as Record; + } else { + obj[arg.key] = arg.value; } }); return obj; diff --git a/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx index b4e3913c..c9dab2cd 100644 --- a/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx +++ b/web/src/app/home/components/models-dialog/components/AddModelPopover.tsx @@ -258,11 +258,16 @@ export default function AddModelPopover({ e.stopPropagation()} + onTouchMove={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} > setTab(v as ModelType)}> @@ -437,7 +442,7 @@ export default function AddModelPopover({
e.stopPropagation()} >
diff --git a/web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx b/web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx index 72a10c1f..86dc8e46 100644 --- a/web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx +++ b/web/src/app/home/components/models-dialog/components/ExtraArgsEditor.tsx @@ -1,6 +1,7 @@ import { Plus, X, HelpCircle } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Select, @@ -47,9 +48,30 @@ export default function ExtraArgsEditor({ ) => { const newArgs = [...args]; newArgs[index] = { ...newArgs[index], [field]: value }; + // When switching to object type, seed an empty JSON object so the textarea + // doesn't start with an unparseable empty string. + if ( + field === 'type' && + value === 'object' && + !newArgs[index].value.trim() + ) { + newArgs[index].value = '{}'; + } onChange(newArgs); }; + const isInvalidJson = (raw: string) => { + if (!raw.trim()) return false; + try { + const parsed = JSON.parse(raw); + return ( + parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) + ); + } catch { + return true; + } + }; + return (
@@ -90,49 +112,79 @@ export default function ExtraArgsEditor({ {args.length === 0 ? (

{t('common.none')}

) : ( - args.map((arg, index) => ( -
- handleUpdate(index, 'key', e.target.value)} - /> - - handleUpdate(index, 'value', e.target.value)} - /> - {!disabled && ( - - )} -
- )) + args.map((arg, index) => { + const isObject = arg.type === 'object'; + const jsonError = isObject && isInvalidJson(arg.value); + return ( +
+
+ handleUpdate(index, 'key', e.target.value)} + /> + + {!isObject && ( + + handleUpdate(index, 'value', e.target.value) + } + /> + )} + {!disabled && ( + + )} +
+ {isObject && ( +