mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
fix(dify-ui): improve picker type inference (#38428)
This commit is contained in:
parent
acfef79a8f
commit
e742e0ec17
@ -17,7 +17,30 @@ import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export const Autocomplete = BaseAutocomplete.Root
|
||||
export type AutocompleteRootProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
|
||||
export type AutocompleteRootGroupedProps<
|
||||
Items extends readonly { items: readonly unknown[] }[],
|
||||
> = Omit<AutocompleteRootProps<Items[number]['items'][number]>, 'items'> & {
|
||||
items: Items
|
||||
}
|
||||
export type AutocompleteRootFlatProps<ItemValue>
|
||||
= Omit<AutocompleteRootProps<ItemValue>, 'items'>
|
||||
& {
|
||||
items?: readonly ItemValue[]
|
||||
}
|
||||
|
||||
export function Autocomplete<Items extends readonly { items: readonly unknown[] }[]>(
|
||||
props: AutocompleteRootGroupedProps<Items>,
|
||||
): React.JSX.Element
|
||||
export function Autocomplete<ItemValue>(
|
||||
props: AutocompleteRootFlatProps<ItemValue>,
|
||||
): React.JSX.Element
|
||||
export function Autocomplete(
|
||||
props: AutocompleteRootProps<unknown>,
|
||||
): React.JSX.Element {
|
||||
return <BaseAutocomplete.Root {...props} />
|
||||
}
|
||||
|
||||
export const AutocompleteValue = BaseAutocomplete.Value
|
||||
export const AutocompleteGroup = BaseAutocomplete.Group
|
||||
export const AutocompleteCollection = BaseAutocomplete.Collection
|
||||
@ -25,7 +48,6 @@ export const AutocompleteRow = BaseAutocomplete.Row
|
||||
export const useAutocompleteFilter = BaseAutocomplete.useFilter
|
||||
export const useAutocompleteFilteredItems = BaseAutocomplete.useFilteredItems
|
||||
|
||||
export type AutocompleteRootProps<ItemValue> = BaseAutocomplete.Root.Props<ItemValue>
|
||||
export type AutocompleteRootChangeEventDetails = BaseAutocomplete.Root.ChangeEventDetails
|
||||
export type AutocompleteRootHighlightEventDetails = BaseAutocomplete.Root.HighlightEventDetails
|
||||
|
||||
|
||||
@ -17,7 +17,15 @@ import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export const Combobox = BaseCombobox.Root
|
||||
export type ComboboxRootProps<Value, Multiple extends boolean | undefined = false>
|
||||
= BaseCombobox.Root.Props<Value, Multiple>
|
||||
|
||||
export function Combobox<Value, Multiple extends boolean | undefined = false>(
|
||||
props: ComboboxRootProps<Value, Multiple>,
|
||||
): React.JSX.Element {
|
||||
return <BaseCombobox.Root {...props} />
|
||||
}
|
||||
|
||||
export const ComboboxValue = BaseCombobox.Value
|
||||
export const ComboboxGroup = BaseCombobox.Group
|
||||
export const ComboboxCollection = BaseCombobox.Collection
|
||||
@ -25,8 +33,6 @@ export const ComboboxRow = BaseCombobox.Row
|
||||
export const useComboboxFilter = BaseCombobox.useFilter
|
||||
export const useComboboxFilteredItems = BaseCombobox.useFilteredItems
|
||||
|
||||
export type ComboboxRootProps<Value, Multiple extends boolean | undefined = false>
|
||||
= BaseCombobox.Root.Props<Value, Multiple>
|
||||
export type ComboboxRootChangeEventDetails = BaseCombobox.Root.ChangeEventDetails
|
||||
export type ComboboxRootHighlightEventDetails = BaseCombobox.Root.HighlightEventDetails
|
||||
|
||||
|
||||
@ -16,7 +16,17 @@ import { parsePlacement } from '../placement'
|
||||
|
||||
export type { Placement }
|
||||
|
||||
export const Select = BaseSelect.Root
|
||||
export type SelectRootProps<
|
||||
Value,
|
||||
Multiple extends boolean | undefined = false,
|
||||
> = BaseSelect.Root.Props<Value, Multiple>
|
||||
|
||||
export function Select<Value, Multiple extends boolean | undefined = false>(
|
||||
props: SelectRootProps<Value, Multiple>,
|
||||
): React.JSX.Element {
|
||||
return <BaseSelect.Root {...props} />
|
||||
}
|
||||
|
||||
export const SelectValue = BaseSelect.Value
|
||||
export const SelectGroup = BaseSelect.Group
|
||||
|
||||
|
||||
@ -36,9 +36,9 @@ const RangeSelector: FC<Props> = ({
|
||||
name: t(`filter.period.${range.name}`, { ns: 'appLog' }),
|
||||
}))
|
||||
}, [ranges, t])
|
||||
const [value, setValue] = useState('0')
|
||||
const [value, setValue] = useState(0)
|
||||
const selectedItem = useMemo(() => {
|
||||
return items.find(item => String(item.value) === value) ?? null
|
||||
return items.find(item => item.value === value) ?? null
|
||||
}, [items, value])
|
||||
|
||||
const handleSelectRange = useCallback((item: TimePeriodOption) => {
|
||||
@ -50,20 +50,20 @@ const RangeSelector: FC<Props> = ({
|
||||
period = { start: startOfToday, end: endOfToday }
|
||||
}
|
||||
else {
|
||||
period = { start: today.subtract(item.value as number, 'day').startOf('day'), end: today.endOf('day') }
|
||||
period = { start: today.subtract(item.value, 'day').startOf('day'), end: today.endOf('day') }
|
||||
}
|
||||
onSelect({ query: period!, name })
|
||||
}, [onSelect])
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={selectedItem ? String(selectedItem.value) : null}
|
||||
<Select<number>
|
||||
value={selectedItem?.value ?? null}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null)
|
||||
return
|
||||
const nextItem = items.find(item => String(item.value) === nextValue)
|
||||
const nextItem = items.find(item => item.value === nextValue)
|
||||
if (!nextItem)
|
||||
return
|
||||
setValue(nextValue)
|
||||
@ -80,7 +80,7 @@ const RangeSelector: FC<Props> = ({
|
||||
</SelectTrigger>
|
||||
<SelectContent className="translate-x-[-24px]" popupClassName="w-[200px]" listClassName="p-1">
|
||||
{items.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)} className="h-8 py-0 pr-2 pl-7 system-md-regular">
|
||||
<SelectItem key={item.value} value={item.value} className="h-8 py-0 pr-2 pl-7 system-md-regular">
|
||||
<SelectItemText className="px-0">{item.name}</SelectItemText>
|
||||
<SelectItemIndicator className="absolute top-[8px] left-2 ml-0" />
|
||||
</SelectItem>
|
||||
|
||||
@ -33,7 +33,7 @@ import TypeSelector from './type-select'
|
||||
import { CHECKBOX_DEFAULT_FALSE_VALUE, CHECKBOX_DEFAULT_TRUE_VALUE, TEXT_MAX_LENGTH } from './utils'
|
||||
|
||||
type Translate = (key: string, options?: Record<string, unknown>) => string
|
||||
const EMPTY_SELECT_VALUE = '__empty__'
|
||||
const EMPTY_SELECT_VALUE = '__empty__' as const
|
||||
|
||||
type ConfigModalFormFieldsProps = {
|
||||
checkboxDefaultSelectValue: string
|
||||
@ -169,10 +169,14 @@ const ConfigModalFormFields: FC<ConfigModalFormFieldsProps> = ({
|
||||
</Field>
|
||||
{options && options.length > 0 && (
|
||||
<Field title={t('variableConfig.defaultValue', { ns: 'appDebug' })}>
|
||||
<Select
|
||||
<Select<string>
|
||||
key={`default-select-${options.join('-')}`}
|
||||
value={tempPayload.default ? String(tempPayload.default) : EMPTY_SELECT_VALUE}
|
||||
onValueChange={value => onPayloadChange('default')(value === EMPTY_SELECT_VALUE ? undefined : value)}
|
||||
value={typeof tempPayload.default === 'string' ? tempPayload.default : EMPTY_SELECT_VALUE}
|
||||
onValueChange={(value) => {
|
||||
if (value == null)
|
||||
return
|
||||
onPayloadChange('default')(value === EMPTY_SELECT_VALUE ? undefined : value)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="large" className="w-full">
|
||||
<SelectValue placeholder={t('variableConfig.selectDefaultValue', { ns: 'appDebug' })} />
|
||||
|
||||
@ -105,17 +105,17 @@ const ChatUserInput = ({
|
||||
/>
|
||||
)}
|
||||
{type === 'select' && (
|
||||
<Select
|
||||
value={inputs[key] ? String(inputs[key]) : null}
|
||||
<Select<string>
|
||||
value={typeof inputs[key] === 'string' && inputs[key] !== '' ? inputs[key] : null}
|
||||
disabled={debugInputReadonly}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null || nextValue === '')
|
||||
return
|
||||
handleInputValueChange(key, nextValue)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
{String(inputs[key] || t('placeholder.select', { ns: 'common' }))}
|
||||
{typeof inputs[key] === 'string' && inputs[key] !== '' ? inputs[key] : t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(options || []).map(option => (
|
||||
|
||||
@ -165,17 +165,17 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
|
||||
/>
|
||||
)}
|
||||
{type === 'select' && (
|
||||
<Select
|
||||
value={inputs[key] ? String(inputs[key]) : null}
|
||||
<Select<string>
|
||||
value={typeof inputs[key] === 'string' && inputs[key] !== '' ? inputs[key] : null}
|
||||
disabled={debugInputReadonly}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null || nextValue === '')
|
||||
return
|
||||
handleInputValueChange(key, nextValue)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full bg-gray-50">
|
||||
{String(inputs[key] || t('placeholder.select', { ns: 'common' }))}
|
||||
{typeof inputs[key] === 'string' && inputs[key] !== '' ? inputs[key] : t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(options || []).map(option => (
|
||||
|
||||
@ -21,7 +21,7 @@ import { useAppVoices } from '@/service/use-apps'
|
||||
import { TtsAutoPlay } from '@/types/app'
|
||||
|
||||
type SelectOption = {
|
||||
value: string | number
|
||||
value: string
|
||||
name: string
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ const VoiceParamConfig = ({
|
||||
const text2speech = useFeatures(state => state.features.text2speech)
|
||||
const featuresStore = useFeaturesStore()
|
||||
const formatLanguageName = (item: SelectOption) => {
|
||||
return t(`voice.language.${replace(String(item.value), '-', '')}`, item.name, { ns: 'common' as const })
|
||||
return t(`voice.language.${replace(item.value, '-', '')}`, item.name, { ns: 'common' as const })
|
||||
}
|
||||
|
||||
let languageItem = languages.find(item => item.value === text2speech?.language)
|
||||
@ -101,9 +101,9 @@ const VoiceParamConfig = ({
|
||||
</Infotip>
|
||||
</div>
|
||||
<Select
|
||||
value={languageItem ? String(languageItem.value) : null}
|
||||
value={languageItem?.value ?? null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null)
|
||||
return
|
||||
handleChange({
|
||||
language: nextValue,
|
||||
@ -115,7 +115,7 @@ const VoiceParamConfig = ({
|
||||
</SelectTrigger>
|
||||
<SelectContent listClassName="max-h-60">
|
||||
{languages.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>
|
||||
{formatLanguageName(item)}
|
||||
</SelectItemText>
|
||||
@ -131,10 +131,10 @@ const VoiceParamConfig = ({
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Select
|
||||
value={voiceItem ? String(voiceItem.value) : null}
|
||||
value={voiceItem?.value ?? null}
|
||||
disabled={!languageItem}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null || nextValue === '')
|
||||
return
|
||||
handleChange({
|
||||
voice: nextValue,
|
||||
@ -147,7 +147,7 @@ const VoiceParamConfig = ({
|
||||
</SelectTrigger>
|
||||
<SelectContent listClassName="max-h-60">
|
||||
{voiceItems?.map((item: SelectOption) => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>
|
||||
{item.name}
|
||||
</SelectItemText>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@ -12,7 +13,7 @@ vi.mock('../../display-toggle', () => ({
|
||||
}))
|
||||
|
||||
describe('MenuBar', () => {
|
||||
const defaultProps = {
|
||||
const defaultProps: ComponentProps<typeof MenuBar> = {
|
||||
hasSelectableSegments: true,
|
||||
isLoading: false,
|
||||
totalText: '10 Chunks',
|
||||
@ -21,7 +22,7 @@ describe('MenuBar', () => {
|
||||
{ value: 0, name: 'Enabled' },
|
||||
{ value: 1, name: 'Disabled' },
|
||||
],
|
||||
selectDefaultValue: 'all' as const,
|
||||
selectDefaultValue: 'all',
|
||||
onChangeStatus: vi.fn(),
|
||||
inputValue: '',
|
||||
onInputChange: vi.fn(),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import type { SegmentStatusFilterOption, SegmentStatusFilterValue } from '../hooks/use-search-filter'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -7,18 +8,13 @@ import Input from '@/app/components/base/input'
|
||||
import DisplayToggle from '../display-toggle'
|
||||
import s from '../style.module.css'
|
||||
|
||||
type Item = {
|
||||
value: number | string
|
||||
name: string
|
||||
} & Record<string, unknown>
|
||||
|
||||
type MenuBarProps = {
|
||||
hasSelectableSegments: boolean
|
||||
isLoading: boolean
|
||||
totalText: string
|
||||
statusList: Item[]
|
||||
selectDefaultValue: 'all' | 0 | 1
|
||||
onChangeStatus: (item: Item) => void
|
||||
statusList: SegmentStatusFilterOption[]
|
||||
selectDefaultValue: SegmentStatusFilterValue
|
||||
onChangeStatus: (item: SegmentStatusFilterOption) => void
|
||||
inputValue: string
|
||||
onInputChange: (value: string) => void
|
||||
isCollapsed: boolean
|
||||
@ -55,12 +51,12 @@ function MenuBar({
|
||||
<span className="size-4 shrink-0" aria-hidden />
|
||||
)}
|
||||
<div className="flex-1 pl-5 system-sm-semibold-uppercase text-text-secondary">{totalText}</div>
|
||||
<Select
|
||||
value={selectedStatus ? String(selectedStatus.value) : null}
|
||||
<Select<SegmentStatusFilterValue>
|
||||
value={selectedStatus?.value ?? null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null)
|
||||
return
|
||||
const nextItem = statusList.find(item => String(item.value) === nextValue)
|
||||
const nextItem = statusList.find(item => item.value === nextValue)
|
||||
if (nextItem)
|
||||
onChangeStatus(nextItem)
|
||||
}}
|
||||
@ -70,7 +66,7 @@ function MenuBar({
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-[160px]">
|
||||
{statusList.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
|
||||
@ -2,8 +2,10 @@ import { useDebounceFn } from 'ahooks'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type SelectOption = {
|
||||
value: string | number
|
||||
export type SegmentStatusFilterValue = 'all' | 0 | 1
|
||||
|
||||
export type SegmentStatusFilterOption = {
|
||||
value: SegmentStatusFilterValue
|
||||
name: string
|
||||
}
|
||||
|
||||
@ -11,10 +13,10 @@ type UseSearchFilterReturn = {
|
||||
inputValue: string
|
||||
searchValue: string
|
||||
selectedStatus: boolean | 'all'
|
||||
statusList: SelectOption[]
|
||||
selectDefaultValue: 'all' | 0 | 1
|
||||
statusList: SegmentStatusFilterOption[]
|
||||
selectDefaultValue: SegmentStatusFilterValue
|
||||
handleInputChange: (value: string) => void
|
||||
onChangeStatus: (item: SelectOption) => void
|
||||
onChangeStatus: (item: SegmentStatusFilterOption) => void
|
||||
onClearFilter: () => void
|
||||
resetPage: () => void
|
||||
}
|
||||
@ -31,7 +33,7 @@ export const useSearchFilter = (options: UseSearchFilterOptions): UseSearchFilte
|
||||
const [searchValue, setSearchValue] = useState<string>('')
|
||||
const [selectedStatus, setSelectedStatus] = useState<boolean | 'all'>('all')
|
||||
|
||||
const statusList = useRef<SelectOption[]>([
|
||||
const statusList = useRef<SegmentStatusFilterOption[]>([
|
||||
{ value: 'all', name: t('list.index.all', { ns: 'datasetDocuments' }) },
|
||||
{ value: 0, name: t('list.status.disabled', { ns: 'datasetDocuments' }) },
|
||||
{ value: 1, name: t('list.status.enabled', { ns: 'datasetDocuments' }) },
|
||||
@ -47,7 +49,7 @@ export const useSearchFilter = (options: UseSearchFilterOptions): UseSearchFilte
|
||||
handleSearch()
|
||||
}, [handleSearch])
|
||||
|
||||
const onChangeStatus = useCallback(({ value }: SelectOption) => {
|
||||
const onChangeStatus = useCallback(({ value }: SegmentStatusFilterOption) => {
|
||||
setSelectedStatus(value === 'all' ? 'all' : !!value)
|
||||
onPageChange(1)
|
||||
}, [onPageChange])
|
||||
|
||||
@ -19,7 +19,7 @@ type SelectOption = {
|
||||
}
|
||||
|
||||
type TimezoneOption = {
|
||||
value: string | number
|
||||
value: string
|
||||
name: string
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ export default function PreferencePage() {
|
||||
value={selectedLanguage?.value ?? null}
|
||||
disabled={editing}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null)
|
||||
return
|
||||
const nextItem = languageOptions.find(item => item.value === nextValue)
|
||||
if (nextItem)
|
||||
@ -142,12 +142,12 @@ export default function PreferencePage() {
|
||||
<div className="mb-6">
|
||||
<div className={titleClassName}>{t('language.timezone', { ns: 'common' })}</div>
|
||||
<Select
|
||||
value={selectedTimezone ? String(selectedTimezone.value) : null}
|
||||
value={selectedTimezone?.value ?? null}
|
||||
disabled={editing}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
const nextItem = timezones.find(item => String(item.value) === nextValue)
|
||||
const nextItem = timezones.find(item => item.value === nextValue)
|
||||
if (nextItem)
|
||||
handleSelectTimezone(nextItem)
|
||||
}}
|
||||
@ -157,7 +157,7 @@ export default function PreferencePage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{timezones.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
|
||||
@ -24,7 +24,7 @@ import SelectPackage from './steps/selectPackage'
|
||||
const i18nPrefix = 'installFromGitHub'
|
||||
|
||||
type SelectOption = {
|
||||
value: string | number
|
||||
value: string
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { PluginCategoryEnum } from '../../../../types'
|
||||
import SelectPackage from '../selectPackage'
|
||||
|
||||
type SelectOption = {
|
||||
value: string | number
|
||||
value: string
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import { handleUpload } from '../../hooks'
|
||||
const i18nPrefix = 'installFromGitHub'
|
||||
|
||||
type SelectOption = {
|
||||
value: string | number
|
||||
value: string
|
||||
name: string
|
||||
}
|
||||
|
||||
@ -49,8 +49,8 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const isEdit = Boolean(updatePayload)
|
||||
const [isUploading, setIsUploading] = React.useState(false)
|
||||
const selectedVersionOption = versions.find(item => String(item.value) === selectedVersion) ?? null
|
||||
const selectedPackageOption = packages.find(item => String(item.value) === selectedPackage) ?? null
|
||||
const selectedVersionOption = versions.find(item => item.value === selectedVersion) ?? null
|
||||
const selectedPackageOption = packages.find(item => item.value === selectedPackage) ?? null
|
||||
|
||||
const handleUploadPackage = async () => {
|
||||
if (isUploading)
|
||||
@ -80,11 +80,11 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
<>
|
||||
<FieldRoot name="version" className="gap-4 self-stretch">
|
||||
<Select
|
||||
value={selectedVersionOption ? String(selectedVersionOption.value) : null}
|
||||
value={selectedVersionOption?.value ?? null}
|
||||
onValueChange={(value) => {
|
||||
if (!value)
|
||||
if (value == null)
|
||||
return
|
||||
const selectedItem = versions.find(item => String(item.value) === value)
|
||||
const selectedItem = versions.find(item => item.value === value)
|
||||
if (selectedItem)
|
||||
onSelectVersion(selectedItem)
|
||||
}}
|
||||
@ -110,7 +110,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-[512px]">
|
||||
{versions.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
{item.value === updatePayload?.originalPackageInfo.version && (
|
||||
<Badge uppercase={true} className="ml-1 shrink-0">INSTALLED</Badge>
|
||||
@ -123,12 +123,12 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
</FieldRoot>
|
||||
<FieldRoot name="package" className="gap-4 self-stretch">
|
||||
<Select
|
||||
value={selectedPackageOption ? String(selectedPackageOption.value) : null}
|
||||
value={selectedPackageOption?.value ?? null}
|
||||
readOnly={!selectedVersion}
|
||||
onValueChange={(value) => {
|
||||
if (!value)
|
||||
if (value == null)
|
||||
return
|
||||
const selectedItem = packages.find(item => String(item.value) === value)
|
||||
const selectedItem = packages.find(item => item.value === value)
|
||||
if (selectedItem)
|
||||
onSelectPackage(selectedItem)
|
||||
}}
|
||||
@ -141,7 +141,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-[512px]">
|
||||
{packages.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
|
||||
@ -156,8 +156,9 @@ const ReasoningConfigForm: React.FC<Props> = ({
|
||||
language,
|
||||
schema,
|
||||
})
|
||||
const selectValue = typeof varInput?.value === 'string' ? varInput.value : null
|
||||
const selectedOption = isSelect && options
|
||||
? pickerProps.selectItems.find(item => item.value === (varInput?.value as string | number | undefined)) ?? null
|
||||
? pickerProps.selectItems.find(item => item.value === selectValue) ?? null
|
||||
: null
|
||||
|
||||
return (
|
||||
@ -230,13 +231,20 @@ const ReasoningConfigForm: React.FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
{isSelect && options && (
|
||||
<Select value={selectedOption ? String(selectedOption.value) : null} onValueChange={value => value && handleValueChange(variable, type)(value)}>
|
||||
<Select<string>
|
||||
value={selectedOption?.value ?? null}
|
||||
onValueChange={(value) => {
|
||||
if (value == null || value === '')
|
||||
return
|
||||
handleValueChange(variable, type)(value)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-8 grow">
|
||||
{selectedOption?.name ?? placeholder?.[language] ?? placeholder?.en_US}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pickerProps.selectItems.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
|
||||
@ -38,6 +38,7 @@ type IRunOnceProps = {
|
||||
isStopping: boolean
|
||||
} | null
|
||||
}
|
||||
|
||||
const RunOnce: FC<IRunOnceProps> = ({
|
||||
promptConfig,
|
||||
inputs,
|
||||
@ -117,109 +118,115 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
<form onSubmit={onSubmit}>
|
||||
{(inputs === null || inputs === undefined || Object.keys(inputs).length === 0) || !isInitialized
|
||||
? null
|
||||
: promptConfig.prompt_variables.filter(item => item.hide !== true).map(item => (
|
||||
<div className="mt-4 w-full" key={item.key}>
|
||||
{item.type !== 'checkbox' && (
|
||||
<div className="flex h-6 items-center gap-1 system-md-semibold text-text-secondary">
|
||||
<div className="truncate">{item.name}</div>
|
||||
{!item.required && <span className="system-xs-regular text-text-tertiary">{t('panel.optional', { ns: 'workflow' })}</span>}
|
||||
: promptConfig.prompt_variables.filter(item => item.hide !== true).map((item) => {
|
||||
const inputValue = inputs[item.key]
|
||||
const selectValue = typeof inputValue === 'string' && inputValue !== '' ? inputValue : null
|
||||
const defaultSelectValue = typeof item.default === 'string' && item.default !== '' ? item.default : null
|
||||
|
||||
return (
|
||||
<div className="mt-4 w-full" key={item.key}>
|
||||
{item.type !== 'checkbox' && (
|
||||
<div className="flex h-6 items-center gap-1 system-md-semibold text-text-secondary">
|
||||
<div className="truncate">{item.name}</div>
|
||||
{!item.required && <span className="system-xs-regular text-text-tertiary">{t('panel.optional', { ns: 'workflow' })}</span>}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-1">
|
||||
{item.type === 'select' && (
|
||||
<Select<string>
|
||||
value={selectValue}
|
||||
onValueChange={(nextValue) => {
|
||||
if (nextValue == null || nextValue === '')
|
||||
return
|
||||
handleInputsChange({ ...inputsRef.current, [item.key]: nextValue })
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
{selectValue ?? defaultSelectValue ?? t('placeholder.select', { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(item.options || []).map(option => (
|
||||
<SelectItem key={option} value={option}>
|
||||
<SelectItemText>{option}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
{item.type === 'string' && (
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={item.name}
|
||||
value={inputs[item.key] as string}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
|
||||
maxLength={item.max_length}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'paragraph' && (
|
||||
<Textarea
|
||||
aria-label={item.name}
|
||||
className="h-[104px] sm:text-xs"
|
||||
placeholder={item.name}
|
||||
value={inputs[item.key] as string}
|
||||
onValueChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'number' && (
|
||||
<Input
|
||||
type="number"
|
||||
placeholder={item.name}
|
||||
value={inputs[item.key] as number}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'checkbox' && (
|
||||
<BoolInput
|
||||
name={item.name || item.key}
|
||||
value={!!inputs[item.key] as boolean}
|
||||
required={item.required}
|
||||
onChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'file' && (
|
||||
<FileUploaderInAttachmentWrapper
|
||||
value={inputs[item.key] && typeof inputs[item.key] === 'object' && !Array.isArray(inputs[item.key])
|
||||
? [inputs[item.key] as FileEntity]
|
||||
: []}
|
||||
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: files[0] }) }}
|
||||
fileConfig={{
|
||||
...item.config,
|
||||
fileUploadConfig: (visionConfig as any).fileUploadConfig,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'file-list' && (
|
||||
<FileUploaderInAttachmentWrapper
|
||||
value={Array.isArray(inputs[item.key]) ? inputs[item.key] as FileEntity[] : []}
|
||||
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: files }) }}
|
||||
fileConfig={{
|
||||
...item.config,
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
fileUploadConfig: (visionConfig as any).fileUploadConfig,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'json_object' && (
|
||||
<CodeEditor
|
||||
language={CodeLanguage.json}
|
||||
value={inputs[item.key] as string}
|
||||
onChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
|
||||
noWrapper
|
||||
className="bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1"
|
||||
placeholder={
|
||||
<div className="whitespace-pre">{typeof item.json_schema === 'string' ? item.json_schema : JSON.stringify(item.json_schema || '', null, 2)}</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-1">
|
||||
{item.type === 'select' && (
|
||||
<Select
|
||||
value={inputs[item.key] ? String(inputs[item.key]) : null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
handleInputsChange({ ...inputsRef.current, [item.key]: nextValue })
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
{String(inputs[item.key] || item.default || t('placeholder.select', { ns: 'common' }))}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(item.options || []).map(option => (
|
||||
<SelectItem key={option} value={option}>
|
||||
<SelectItemText>{option}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
{item.type === 'string' && (
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={item.name}
|
||||
value={inputs[item.key] as string}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
|
||||
maxLength={item.max_length}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'paragraph' && (
|
||||
<Textarea
|
||||
aria-label={item.name}
|
||||
className="h-[104px] sm:text-xs"
|
||||
placeholder={item.name}
|
||||
value={inputs[item.key] as string}
|
||||
onValueChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'number' && (
|
||||
<Input
|
||||
type="number"
|
||||
placeholder={item.name}
|
||||
value={inputs[item.key] as number}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'checkbox' && (
|
||||
<BoolInput
|
||||
name={item.name || item.key}
|
||||
value={!!inputs[item.key] as boolean}
|
||||
required={item.required}
|
||||
onChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'file' && (
|
||||
<FileUploaderInAttachmentWrapper
|
||||
value={inputs[item.key] && typeof inputs[item.key] === 'object' && !Array.isArray(inputs[item.key])
|
||||
? [inputs[item.key] as FileEntity]
|
||||
: []}
|
||||
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: files[0] }) }}
|
||||
fileConfig={{
|
||||
...item.config,
|
||||
fileUploadConfig: (visionConfig as any).fileUploadConfig,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'file-list' && (
|
||||
<FileUploaderInAttachmentWrapper
|
||||
value={Array.isArray(inputs[item.key]) ? inputs[item.key] as FileEntity[] : []}
|
||||
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: files }) }}
|
||||
fileConfig={{
|
||||
...item.config,
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
fileUploadConfig: (visionConfig as any).fileUploadConfig,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'json_object' && (
|
||||
<CodeEditor
|
||||
language={CodeLanguage.json}
|
||||
value={inputs[item.key] as string}
|
||||
onChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
|
||||
noWrapper
|
||||
className="bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1"
|
||||
placeholder={
|
||||
<div className="whitespace-pre">{typeof item.json_schema === 'string' ? item.json_schema : JSON.stringify(item.json_schema || '', null, 2)}</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
{
|
||||
visionConfig?.enabled && (
|
||||
<div className="mt-4 w-full">
|
||||
|
||||
@ -74,7 +74,7 @@ const createVar = (type: VarType, variable = 'test.variable'): Var => ({
|
||||
|
||||
type SelectOption = {
|
||||
name: string
|
||||
value: string | number
|
||||
value: ErrorHandleMode
|
||||
}
|
||||
|
||||
describe('iteration/use-config', () => {
|
||||
|
||||
@ -124,12 +124,12 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
|
||||
<div className="px-4 py-2">
|
||||
<Field title={errorResponseMethodLabel}>
|
||||
<Select
|
||||
value={selectedResponseMethod ? String(selectedResponseMethod.value) : null}
|
||||
<Select<ErrorHandleMode>
|
||||
value={selectedResponseMethod?.value ?? null}
|
||||
onValueChange={(nextValue) => {
|
||||
if (!nextValue)
|
||||
if (nextValue == null)
|
||||
return
|
||||
const nextItem = responseMethod.find(item => String(item.value) === nextValue)
|
||||
const nextItem = responseMethod.find(item => item.value === nextValue)
|
||||
if (nextItem)
|
||||
changeErrorResponseMode(nextItem)
|
||||
}}
|
||||
@ -140,7 +140,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{responseMethod.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
|
||||
@ -22,7 +22,7 @@ import { toNodeOutputVars } from '../_base/components/variable/utils'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
|
||||
type SelectOption = {
|
||||
value: string | number
|
||||
value: ErrorHandleMode
|
||||
name: string
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
|
||||
const changeErrorResponseMode = useCallback((item: SelectOption) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.error_handle_mode = item.value as ErrorHandleMode
|
||||
draft.error_handle_mode = item.value
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import tz from './timezone.json'
|
||||
|
||||
type Item = {
|
||||
value: number | string
|
||||
value: string
|
||||
name: string
|
||||
}
|
||||
export const timezones: Item[] = tz
|
||||
|
||||
Loading…
Reference in New Issue
Block a user