mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
fix(web): form content preview
This commit is contained in:
parent
7316a1be2b
commit
d72794bc67
@ -1,5 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { Note, rehypeNotes, rehypeVariable, Variable } from '../variable-in-markdown'
|
||||
|
||||
describe('variable-in-markdown', () => {
|
||||
@ -119,5 +121,67 @@ describe('variable-in-markdown', () => {
|
||||
|
||||
expect(screen.getByText('Plain value')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render a select preview control for select inputs', () => {
|
||||
render(
|
||||
<Note
|
||||
input={{
|
||||
type: InputVarType.select,
|
||||
output_variable_name: 'approval',
|
||||
option_source: {
|
||||
type: 'constant',
|
||||
selector: [],
|
||||
value: ['Approved', 'Rejected'],
|
||||
},
|
||||
}}
|
||||
nodeName={nodeId => nodeId}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('human-input-note-select-preview')).toBeInTheDocument()
|
||||
expect(screen.getByText('Approved')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should open the select preview and show option items', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
render(
|
||||
<Note
|
||||
input={{
|
||||
type: InputVarType.select,
|
||||
output_variable_name: 'approval',
|
||||
option_source: {
|
||||
type: 'constant',
|
||||
selector: [],
|
||||
value: ['Approved', 'Rejected'],
|
||||
},
|
||||
}}
|
||||
nodeName={nodeId => nodeId}
|
||||
/>,
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('combobox', { name: 'human-input-note-select' }))
|
||||
|
||||
expect(await screen.findByRole('option', { name: 'Rejected' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render upload placeholders for file inputs', () => {
|
||||
render(
|
||||
<Note
|
||||
input={{
|
||||
type: InputVarType.singleFile,
|
||||
output_variable_name: 'attachment',
|
||||
allowed_file_extensions: ['.pdf'],
|
||||
allowed_file_types: [],
|
||||
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
|
||||
}}
|
||||
nodeName={nodeId => nodeId}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('human-input-note-file-preview')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.fileUploader.uploadFromComputer')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.fileUploader.pasteFileLink')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type * as React from 'react'
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
import type { FormInputItem } from '../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { isFileFormInput, isFileListFormInput, isSelectFormInput } from '../types'
|
||||
|
||||
const variableRegex = /\{\{#(.+?)#\}\}/g
|
||||
@ -134,38 +138,83 @@ export const Variable: React.FC<{ path: string }> = ({ path }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const SelectPreview: React.FC<{ label: string, options: string[] }> = ({ label, options }) => {
|
||||
const [value, setValue] = React.useState(options[0] || label)
|
||||
|
||||
return (
|
||||
<div data-testid="human-input-note-select-preview" className="my-3">
|
||||
<Select value={value} onValueChange={nextValue => nextValue && setValue(nextValue)}>
|
||||
<SelectTrigger size="large" className="w-full rounded-[10px]" aria-label="human-input-note-select">
|
||||
{value}
|
||||
</SelectTrigger>
|
||||
<SelectContent listClassName="max-h-[140px] overflow-y-auto">
|
||||
{options.map(option => (
|
||||
<SelectItem key={option} value={option}>
|
||||
<SelectItemText>{option}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FileUploadPreview: React.FC<{ methods: TransferMethod[], t: (key: string, options?: Record<string, unknown>) => string }> = ({ methods, t }) => {
|
||||
const normalizedMethods = methods.length
|
||||
? methods
|
||||
: [TransferMethod.local_file, TransferMethod.remote_url]
|
||||
const actions = [
|
||||
normalizedMethods.includes(TransferMethod.local_file) && {
|
||||
iconClassName: 'i-ri-upload-cloud-2-line',
|
||||
label: t('fileUploader.uploadFromComputer', { ns: 'common' }),
|
||||
},
|
||||
normalizedMethods.includes(TransferMethod.remote_url) && {
|
||||
iconClassName: 'i-ri-link',
|
||||
label: t('fileUploader.pasteFileLink', { ns: 'common' }),
|
||||
},
|
||||
].filter(Boolean) as Array<{ iconClassName: string, label: string }>
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="human-input-note-file-preview"
|
||||
className={cn(
|
||||
'my-3 grid gap-2',
|
||||
actions.length > 1 ? 'grid-cols-2' : 'grid-cols-1',
|
||||
)}
|
||||
>
|
||||
{actions.map(action => (
|
||||
<div
|
||||
key={action.label}
|
||||
className="flex h-10 items-center justify-center rounded-xl bg-components-input-bg-normal px-3"
|
||||
>
|
||||
<span className={cn('mr-2 size-5 shrink-0 text-text-tertiary', action.iconClassName)} />
|
||||
<span className="truncate system-sm-medium text-text-tertiary">
|
||||
{action.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Note: React.FC<{ input: FormInputItem, nodeName: (nodeId: string) => string }> = ({ input, nodeName }) => {
|
||||
const { t } = useTranslation()
|
||||
if (isSelectFormInput(input)) {
|
||||
const isVariable = input.option_source.type === 'variable'
|
||||
const path = `{{#${input.option_source.selector.join('.')}#}}`
|
||||
const newPath = path ? replaceNodeIdsWithNames(path, nodeName) : path
|
||||
return (
|
||||
<div className="my-3 rounded-[10px] bg-components-input-bg-normal px-2.5 py-2">
|
||||
{isVariable ? <Variable path={newPath} /> : <span>{input.option_source.value.join(', ') || input.type}</span>}
|
||||
</div>
|
||||
)
|
||||
const label = isVariable
|
||||
? t('nodes.humanInput.insertInputField.variable', { ns: 'workflow' })
|
||||
: input.option_source.value[0] || t('variableConfig.select', { ns: 'appDebug' })
|
||||
const options = isVariable ? [label] : input.option_source.value
|
||||
return <SelectPreview label={label} options={options} />
|
||||
}
|
||||
|
||||
if (isFileFormInput(input)) {
|
||||
return (
|
||||
<div className="my-3 rounded-[10px] bg-components-input-bg-normal px-2.5 py-2">
|
||||
<span>{input.allowed_file_types.join(', ') || input.type}</span>
|
||||
</div>
|
||||
)
|
||||
return <FileUploadPreview methods={input.allowed_file_upload_methods} t={t} />
|
||||
}
|
||||
|
||||
if (isFileListFormInput(input)) {
|
||||
const summary = [
|
||||
input.allowed_file_types.join(', '),
|
||||
input.max_upload_count ? `${t('nodes.humanInput.insertInputField.maxUploads', { ns: 'workflow' })}: ${input.max_upload_count}` : null,
|
||||
].filter(Boolean).join(' · ')
|
||||
|
||||
return (
|
||||
<div className="my-3 rounded-[10px] bg-components-input-bg-normal px-2.5 py-2">
|
||||
<span>{summary || input.type}</span>
|
||||
</div>
|
||||
)
|
||||
return <FileUploadPreview methods={input.allowed_file_upload_methods} t={t} />
|
||||
}
|
||||
|
||||
const isVariable = input.default.type === 'variable'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user