feat(dynamic-form): enhance select handling with empty option support and UUID filtering

This commit is contained in:
fdc310
2026-07-06 13:36:58 +08:00
parent 0cce418956
commit 1ed107c9d5
@@ -67,6 +67,18 @@ import SettingsDialog, {
import ToolResourceSelectors from '@/app/home/components/dynamic-form/ToolResourceSelectors'; import ToolResourceSelectors from '@/app/home/components/dynamic-form/ToolResourceSelectors';
import { LANGBOT_MODELS_PROVIDER_REQUESTER } from '@/app/home/components/models-dialog/types'; import { LANGBOT_MODELS_PROVIDER_REQUESTER } from '@/app/home/components/models-dialog/types';
const EMPTY_SELECT_ITEM_VALUE = '__langbot_empty_select_item_value__';
function toSelectValue(value: unknown): string {
return typeof value === 'string' ? value : '';
}
function hasNonEmptyUuid<T extends { uuid?: string | null }>(
item: T,
): item is T & { uuid: string } {
return typeof item.uuid === 'string' && item.uuid.length > 0;
}
export default function DynamicFormItemComponent({ export default function DynamicFormItemComponent({
config, config,
field, field,
@@ -380,19 +392,33 @@ export default function DynamicFormItemComponent({
</div> </div>
); );
case DynamicFormItemType.SELECT: case DynamicFormItemType.SELECT: {
const hasEmptyOption =
config.options?.some((option) => option.name === '') ?? false;
const selectValue =
hasEmptyOption && field.value === ''
? EMPTY_SELECT_ITEM_VALUE
: toSelectValue(field.value);
return ( return (
<Select value={field.value} onValueChange={field.onChange}> <Select
value={selectValue}
onValueChange={(value) =>
field.onChange(value === EMPTY_SELECT_ITEM_VALUE ? '' : value)
}
>
<SelectTrigger className="w-full max-w-md bg-[#ffffff] dark:bg-[#2a2a2e]"> <SelectTrigger className="w-full max-w-md bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('common.select')} /> <SelectValue placeholder={t('common.select')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{config.options?.map((option) => ( {config.options?.map((option, index) => (
<SelectItem <SelectItem
key={option.name} key={`${option.name}-${index}`}
value={option.name} value={
description={option.name} option.name === '' ? EMPTY_SELECT_ITEM_VALUE : option.name
}
description={option.name || undefined}
> >
{extractI18nObject(option.label)} {extractI18nObject(option.label)}
</SelectItem> </SelectItem>
@@ -401,6 +427,7 @@ export default function DynamicFormItemComponent({
</SelectContent> </SelectContent>
</Select> </Select>
); );
}
case DynamicFormItemType.LLM_MODEL_SELECTOR: case DynamicFormItemType.LLM_MODEL_SELECTOR:
// Separate space models from regular models // Separate space models from regular models
@@ -455,7 +482,7 @@ export default function DynamicFormItemComponent({
{Object.entries(groupedModels).map(([providerName, models]) => ( {Object.entries(groupedModels).map(([providerName, models]) => (
<SelectGroup key={providerName}> <SelectGroup key={providerName}>
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.filter(hasNonEmptyUuid).map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
{model.name} {model.name}
@@ -559,7 +586,7 @@ export default function DynamicFormItemComponent({
{providerName} {providerName}
</span> </span>
</SelectLabel> </SelectLabel>
{models.map((model) => ( {models.filter(hasNonEmptyUuid).map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
{model.name} {model.name}
@@ -656,7 +683,7 @@ export default function DynamicFormItemComponent({
([providerName, models]) => ( ([providerName, models]) => (
<SelectGroup key={providerName}> <SelectGroup key={providerName}>
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.filter(hasNonEmptyUuid).map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
{model.name} {model.name}
</SelectItem> </SelectItem>
@@ -748,7 +775,7 @@ export default function DynamicFormItemComponent({
{providerName} {providerName}
</span> </span>
</SelectLabel> </SelectLabel>
{models.map((model) => ( {models.filter(hasNonEmptyUuid).map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
{model.name} {model.name}
</SelectItem> </SelectItem>
@@ -813,7 +840,7 @@ export default function DynamicFormItemComponent({
([providerName, models]) => ( ([providerName, models]) => (
<SelectGroup key={providerName}> <SelectGroup key={providerName}>
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.filter(hasNonEmptyUuid).map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
{model.name} {model.name}
</SelectItem> </SelectItem>
@@ -908,7 +935,7 @@ export default function DynamicFormItemComponent({
([providerName, models]) => ( ([providerName, models]) => (
<SelectGroup key={providerName}> <SelectGroup key={providerName}>
<SelectLabel>{providerName}</SelectLabel> <SelectLabel>{providerName}</SelectLabel>
{models.map((model) => ( {models.filter(hasNonEmptyUuid).map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
{model.name} {model.name}
@@ -1013,7 +1040,7 @@ export default function DynamicFormItemComponent({
{providerName} {providerName}
</span> </span>
</SelectLabel> </SelectLabel>
{models.map((model) => ( {models.filter(hasNonEmptyUuid).map((model) => (
<SelectItem key={model.uuid} value={model.uuid}> <SelectItem key={model.uuid} value={model.uuid}>
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
{model.name} {model.name}
@@ -1223,8 +1250,8 @@ export default function DynamicFormItemComponent({
{Object.entries(kbsByEngine).map(([engineName, kbs]) => ( {Object.entries(kbsByEngine).map(([engineName, kbs]) => (
<SelectGroup key={engineName}> <SelectGroup key={engineName}>
<SelectLabel>{engineName}</SelectLabel> <SelectLabel>{engineName}</SelectLabel>
{kbs.map((base) => ( {kbs.filter(hasNonEmptyUuid).map((base) => (
<SelectItem key={base.uuid} value={base.uuid ?? ''}> <SelectItem key={base.uuid} value={base.uuid}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{base.emoji && ( {base.emoji && (
<span className="text-sm shrink-0">{base.emoji}</span> <span className="text-sm shrink-0">{base.emoji}</span>
@@ -1417,8 +1444,8 @@ export default function DynamicFormItemComponent({
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{bots.map((bot) => ( {bots.filter(hasNonEmptyUuid).map((bot) => (
<SelectItem key={bot.uuid} value={bot.uuid ?? ''}> <SelectItem key={bot.uuid} value={bot.uuid}>
{bot.name} {bot.name}
</SelectItem> </SelectItem>
))} ))}