From 9f9cb4d17e315b201a864724c38876d9d78558ba Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 20 May 2026 20:05:31 +0800 Subject: [PATCH] feat(ui): migrate radio to Base UI and update web callsites (#36451) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 20 - packages/dify-ui/package.json | 8 + .../src/radio-group/__tests__/index.spec.tsx | 82 +++ .../dify-ui/src/radio-group/index.stories.tsx | 217 ++++++++ packages/dify-ui/src/radio-group/index.tsx | 23 + .../src/radio/__tests__/index.spec.tsx | 178 ++++++ packages/dify-ui/src/radio/index.stories.tsx | 147 +++++ packages/dify-ui/src/radio/index.tsx | 105 ++++ web/README.md | 2 +- .../config/assistant-type-picker/index.tsx | 86 +-- .../follow-up-setting-modal.tsx | 59 +- .../base/__tests__/base-field.spec.tsx | 12 +- .../base/form/components/base/base-field.tsx | 115 ++-- .../page-selector/page-row.tsx | 6 +- .../page-selector/virtual-page-list.tsx | 72 ++- .../base/radio-card/__tests__/index.spec.tsx | 175 +++--- .../base/radio-card/index.stories.tsx | 515 ++---------------- web/app/components/base/radio-card/index.tsx | 162 ++++-- .../simple/__tests__/index.spec.tsx | 137 ----- .../base/radio-card/simple/index.tsx | 45 -- .../base/radio-card/simple/style.module.css | 27 - .../base/radio/__tests__/index.spec.tsx | 44 -- .../base/radio/__tests__/ui.spec.tsx | 88 --- .../component/group/__tests__/index.spec.tsx | 108 ---- .../base/radio/component/group/index.tsx | 24 - .../component/radio/__tests__/index.spec.tsx | 95 ---- .../base/radio/component/radio/index.tsx | 67 --- .../radio/context/__tests__/index.spec.tsx | 59 -- .../components/base/radio/context/index.ts | 6 - .../components/base/radio/index.stories.tsx | 442 --------------- web/app/components/base/radio/index.tsx | 15 - .../components/base/radio/style.module.css | 13 - web/app/components/base/radio/ui.tsx | 41 -- .../__tests__/index.spec.tsx | 23 +- .../__tests__/index.spec.tsx | 42 +- .../common/retrieval-param-config/index.tsx | 13 +- .../components/parent-child-options.tsx | 64 ++- .../file-list/list/__tests__/index.spec.tsx | 19 +- .../file-list/list/__tests__/item.spec.tsx | 16 +- .../online-drive/file-list/list/index.tsx | 47 +- .../online-drive/file-list/list/item.tsx | 21 +- .../__tests__/crawled-result-item.spec.tsx | 16 +- .../base/__tests__/index.spec.tsx | 27 +- .../base/crawled-result-item.tsx | 14 +- .../website-crawl/base/crawled-result.tsx | 55 +- .../__tests__/doc-type-selector.spec.tsx | 7 +- .../metadata/components/doc-type-selector.tsx | 64 ++- .../model-provider-page/model-modal/Form.tsx | 106 ++-- .../model-parameter-modal/parameter-item.tsx | 41 +- .../config-credentials.tsx | 109 ++-- web/docs/test.md | 2 +- web/scripts/analyze-component.js | 6 +- 52 files changed, 1605 insertions(+), 2282 deletions(-) create mode 100644 packages/dify-ui/src/radio-group/__tests__/index.spec.tsx create mode 100644 packages/dify-ui/src/radio-group/index.stories.tsx create mode 100644 packages/dify-ui/src/radio-group/index.tsx create mode 100644 packages/dify-ui/src/radio/__tests__/index.spec.tsx create mode 100644 packages/dify-ui/src/radio/index.stories.tsx create mode 100644 packages/dify-ui/src/radio/index.tsx delete mode 100644 web/app/components/base/radio-card/simple/__tests__/index.spec.tsx delete mode 100644 web/app/components/base/radio-card/simple/index.tsx delete mode 100644 web/app/components/base/radio-card/simple/style.module.css delete mode 100644 web/app/components/base/radio/__tests__/index.spec.tsx delete mode 100644 web/app/components/base/radio/__tests__/ui.spec.tsx delete mode 100644 web/app/components/base/radio/component/group/__tests__/index.spec.tsx delete mode 100644 web/app/components/base/radio/component/group/index.tsx delete mode 100644 web/app/components/base/radio/component/radio/__tests__/index.spec.tsx delete mode 100644 web/app/components/base/radio/component/radio/index.tsx delete mode 100644 web/app/components/base/radio/context/__tests__/index.spec.tsx delete mode 100644 web/app/components/base/radio/context/index.ts delete mode 100644 web/app/components/base/radio/index.stories.tsx delete mode 100644 web/app/components/base/radio/index.tsx delete mode 100644 web/app/components/base/radio/style.module.css delete mode 100644 web/app/components/base/radio/ui.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 01720b8e9f..0dd4cdb0b8 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1574,26 +1574,6 @@ "count": 1 } }, - "web/app/components/base/radio-card/index.stories.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, - "web/app/components/base/radio/component/group/index.tsx": { - "ts/no-explicit-any": { - "count": 2 - } - }, - "web/app/components/base/radio/context/index.ts": { - "ts/no-explicit-any": { - "count": 1 - } - }, - "web/app/components/base/radio/index.stories.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/search-input/index.stories.tsx": { "no-console": { "count": 3 diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json index d58fb954e0..a7dbac0f1c 100644 --- a/packages/dify-ui/package.json +++ b/packages/dify-ui/package.json @@ -73,6 +73,14 @@ "types": "./src/number-field/index.tsx", "import": "./src/number-field/index.tsx" }, + "./radio": { + "types": "./src/radio/index.tsx", + "import": "./src/radio/index.tsx" + }, + "./radio-group": { + "types": "./src/radio-group/index.tsx", + "import": "./src/radio-group/index.tsx" + }, "./popover": { "types": "./src/popover/index.tsx", "import": "./src/popover/index.tsx" diff --git a/packages/dify-ui/src/radio-group/__tests__/index.spec.tsx b/packages/dify-ui/src/radio-group/__tests__/index.spec.tsx new file mode 100644 index 0000000000..423ce95749 --- /dev/null +++ b/packages/dify-ui/src/radio-group/__tests__/index.spec.tsx @@ -0,0 +1,82 @@ +import { useState } from 'react' +import { render } from 'vitest-browser-react' +import { FieldItem, FieldLabel, FieldRoot } from '../../field' +import { FieldsetLegend, FieldsetRoot } from '../../fieldset' +import { Radio } from '../../radio' +import { RadioGroup } from '../index' + +const clickElement = (element: HTMLElement | SVGElement) => { + element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })) +} + +describe('RadioGroup', () => { + it('should manage a controlled single selection', async () => { + function StorageDemo() { + const [value, setValue] = useState('ssd') + + return ( + + }> + Storage type + + + + SSD + + + + + + HDD + + + + + ) + } + + const screen = await render() + + await expect.element(screen.getByRole('radio', { name: 'SSD' })).toHaveAttribute('aria-checked', 'true') + + clickElement(screen.getByRole('radio', { name: 'HDD' }).element()) + + await vi.waitFor(async () => { + await expect.element(screen.getByRole('radio', { name: 'SSD' })).toHaveAttribute('aria-checked', 'false') + await expect.element(screen.getByRole('radio', { name: 'HDD' })).toHaveAttribute('aria-checked', 'true') + }) + }) + + it('should compose with Dify UI Field and Fieldset without losing labels', async () => { + const onValueChange = vi.fn() + const screen = await render( + + }> + Storage type + + + + SSD + + + + + + HDD + + + + , + ) + + await expect.element(screen.getByRole('radiogroup', { name: 'Storage type' })).toBeInTheDocument() + + const hdd = screen.getByRole('radio', { name: 'HDD' }) + await expect.element(hdd).toHaveAttribute('aria-checked', 'false') + + clickElement(hdd.element()) + + expect(onValueChange).toHaveBeenCalledTimes(1) + expect(onValueChange.mock.calls[0]?.[0]).toBe('hdd') + }) +}) diff --git a/packages/dify-ui/src/radio-group/index.stories.tsx b/packages/dify-ui/src/radio-group/index.stories.tsx new file mode 100644 index 0000000000..c2c2451806 --- /dev/null +++ b/packages/dify-ui/src/radio-group/index.stories.tsx @@ -0,0 +1,217 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { useState } from 'react' +import { RadioGroup } from '.' +import { + FieldDescription, + FieldItem, + FieldLabel, + FieldRoot, +} from '../field' +import { FieldsetLegend, FieldsetRoot } from '../fieldset' +import { Radio, RadioControl, RadioRoot } from '../radio' + +const meta = { + title: 'Base/Form/RadioGroup', + component: RadioGroup, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'RadioGroup primitive built on Base UI. For normal form rows, compose FieldRoot, FieldsetRoot, FieldLabel, RadioGroup, and Radio. For option cards, make the card itself a RadioRoot with variant="unstyled" and render RadioControl inside it.', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +function StandardFormRowsDemo() { + const [value, setValue] = useState('vector') + + return ( + + + )} + > + Retrieval index + {[ + { value: 'vector', label: 'Vector storage' }, + { value: 'keyword', label: 'Keyword index' }, + { value: 'hybrid', label: 'Hybrid retrieval' }, + ].map(option => ( + + + + {option.label} + + + ))} + + + ) +} + +export const StandardFormRows: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Default form composition. Most product code should use this shape: RadioGroup owns value, FieldsetLegend names the group, and FieldLabel makes each row clickable.', + }, + }, + }, +} + +function BooleanInlineDemo() { + const [value, setValue] = useState(true) + + return ( + + value={value} onValueChange={setValue} className="gap-3" /> + )} + > + Streaming output +
+ + + + True + + + + + + False + + +
+
+
+ ) +} + +export const BooleanInline: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Compact boolean radio fields. This is the pattern used by model parameters and dynamic boolean schema fields.', + }, + }, + }, +} + +function OptionCardsDemo() { + const [value, setValue] = useState('default') + + return ( + + + )} + > + Prompt mode + {[ + { + value: 'default', + title: 'Default prompt', + description: 'Use the built-in prompt for consistent output.', + }, + { + value: 'custom', + title: 'Custom prompt', + description: 'Write a prompt for this app and keep full control.', + }, + ].map(option => ( + } + className="w-full rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg p-4 text-left transition-colors hover:bg-state-base-hover data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg" + > +
+
+
+ {option.title} +
+
+ {option.description} +
+
+
+
+ ))} +
+
+ ) +} + +export const OptionCards: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Use RadioRoot with variant="unstyled" when the entire option card is the radio. RadioControl renders the visual dot inside the card.', + }, + }, + }, +} + +function DynamicFormFieldDemo() { + const options = [ + { value: 'automatic', label: 'Automatic' }, + { value: 'high_quality', label: 'High quality' }, + { value: 'economy', label: 'Economy' }, + ] + const [selected, setSelected] = useState('automatic') + + return ( + + + This mirrors Dify dynamic form fields where radio options are controlled by schema and persisted as a single value. + + + )} + > + + Generation mode + + {options.map(option => ( + + + + {option.label} + + + ))} + + + ) +} + +export const DynamicFormField: Story = { + render: () => , + parameters: { + docs: { + description: { + story: 'Matches Dify form composition: Field and Fieldset provide group labeling while RadioGroup owns controlled single-selection state.', + }, + }, + }, +} diff --git a/packages/dify-ui/src/radio-group/index.tsx b/packages/dify-ui/src/radio-group/index.tsx new file mode 100644 index 0000000000..2e1dea0e0a --- /dev/null +++ b/packages/dify-ui/src/radio-group/index.tsx @@ -0,0 +1,23 @@ +'use client' + +import type { RadioGroup as BaseRadioGroupNS } from '@base-ui/react/radio-group' +import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group' +import { cn } from '../cn' + +export type RadioGroupProps + = Omit, 'className'> + & { + className?: string + } + +export function RadioGroup({ + className, + ...props +}: RadioGroupProps) { + return ( + + ) +} diff --git a/packages/dify-ui/src/radio/__tests__/index.spec.tsx b/packages/dify-ui/src/radio/__tests__/index.spec.tsx new file mode 100644 index 0000000000..ba02f8a0c4 --- /dev/null +++ b/packages/dify-ui/src/radio/__tests__/index.spec.tsx @@ -0,0 +1,178 @@ +import type { ComponentProps, ReactNode } from 'react' +import { render } from 'vitest-browser-react' +import { FieldItem, FieldLabel, FieldRoot } from '../../field' +import { FieldsetLegend, FieldsetRoot } from '../../fieldset' +import { RadioGroup } from '../../radio-group' +import { + Radio, + RadioControl, + RadioIndicator, + RadioRoot, + RadioSkeleton, +} from '../index' + +const clickElement = (element: HTMLElement | SVGElement) => { + element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })) +} + +type TestRadioGroupProps = ComponentProps & { + children: ReactNode + label: string + name?: string +} + +function TestRadioGroup({ + children, + label, + name = 'radioField', + ...props +}: TestRadioGroupProps) { + return ( + + }> + {label} + {children} + + + ) +} + +type TestRadioOptionProps = ComponentProps & { + children: ReactNode +} + +function TestRadioOption({ + children, + ...props +}: TestRadioOptionProps) { + return ( + + + + {children} + + + ) +} + +describe('Radio', () => { + it('should render unchecked and checked radios with Base UI semantics', async () => { + const screen = await render( + + SSD + HDD + , + ) + + const ssd = screen.getByRole('radio', { name: 'SSD' }) + const hdd = screen.getByRole('radio', { name: 'HDD' }) + + await expect.element(ssd).toHaveAttribute('aria-checked', 'true') + await expect.element(ssd).toHaveAttribute('data-checked', '') + await expect.element(ssd).toHaveClass('data-checked:border-components-radio-border-checked') + await expect.element(hdd).toHaveAttribute('aria-checked', 'false') + await expect.element(hdd).toHaveAttribute('data-unchecked', '') + }) + + it('should call onValueChange and update uncontrolled state when selected', async () => { + const onValueChange = vi.fn() + const screen = await render( + + SSD + HDD + , + ) + + clickElement(screen.getByRole('radio', { name: 'HDD' }).element()) + + expect(onValueChange).toHaveBeenCalledTimes(1) + expect(onValueChange.mock.calls[0]?.[0]).toBe('hdd') + await expect.element(screen.getByRole('radio', { name: 'HDD' })).toHaveAttribute('aria-checked', 'true') + }) + + it('should ignore interaction when disabled', async () => { + const onValueChange = vi.fn() + const screen = await render( + + SSD + HDD + , + ) + + const hdd = screen.getByRole('radio', { name: 'HDD' }) + await expect.element(hdd).toHaveAttribute('data-disabled', '') + await expect.element(hdd).toHaveClass('data-disabled:cursor-not-allowed') + + clickElement(hdd.element()) + + expect(onValueChange).not.toHaveBeenCalled() + await expect.element(hdd).toHaveAttribute('aria-checked', 'false') + }) + + it('should submit the selected group value through the hidden input', async () => { + const screen = await render( +
+ + SSD + HDD + +
, + ) + const form = screen.container.querySelector('form') + expect(form).not.toBeNull() + if (!form) + return + + const data = new FormData(form) + + expect(data.get('storageType')).toBe('ssd') + }) + + it('should support custom compound composition with RadioRoot and RadioIndicator', async () => { + const screen = await render( + + + + + + + Custom + + + , + ) + + await expect.element(screen.getByRole('radio', { name: 'Custom' })).toHaveClass('custom-root') + expect(screen.container.querySelector('.custom-indicator')).toBeInTheDocument() + }) + + it('should support unstyled roots with a visual RadioControl for option cards', async () => { + const screen = await render( + + } + > + Card option + + + , + ) + + await expect.element(screen.getByRole('radio', { name: 'Card option' })).toHaveClass('custom-card') + expect(screen.container.querySelector('.custom-control')).toBeInTheDocument() + await expect.element(screen.getByRole('radio', { name: 'Card option' })).toHaveAttribute('data-checked', '') + }) +}) + +describe('RadioSkeleton', () => { + it('should render a visual placeholder without radio semantics', async () => { + const screen = await render() + const skeleton = screen.container.querySelector('.rounded-full') + + expect(screen.container.querySelector('[role="radio"]')).not.toBeInTheDocument() + await expect.element(skeleton).toHaveClass('rounded-full', 'opacity-20') + }) +}) diff --git a/packages/dify-ui/src/radio/index.stories.tsx b/packages/dify-ui/src/radio/index.stories.tsx new file mode 100644 index 0000000000..58af1bbbc1 --- /dev/null +++ b/packages/dify-ui/src/radio/index.stories.tsx @@ -0,0 +1,147 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import type { ComponentProps } from 'react' +import { useState } from 'react' +import { + Radio, + RadioSkeleton, +} from '.' +import { FieldItem, FieldLabel, FieldRoot } from '../field' +import { FieldsetLegend, FieldsetRoot } from '../fieldset' +import { RadioGroup } from '../radio-group' + +const meta = { + title: 'Base/Form/Radio', + component: Radio, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'Radio primitive built on Base UI. It preserves RadioGroup selection, hidden input, disabled, and form semantics while applying the Dify 16px radio design from Figma. Import from `@langgenius/dify-ui/radio` and place radios inside `RadioGroup` from `@langgenius/dify-ui/radio-group`.', + }, + }, + }, + tags: ['autodocs'], + args: { + disabled: false, + value: 'ssd', + }, + argTypes: { + disabled: { + control: 'boolean', + description: 'Disables user interaction and exposes Base UI disabled state attributes.', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +function RadioDemo(args: Partial>) { + const [value, setValue] = useState('ssd') + + return ( + + + )} + > + Storage type + + + + SSD + + + + + + HDD + + + + + ) +} + +export const Default: Story = { + render: args => , + args: { + disabled: false, + }, +} + +export const Disabled: Story = { + args: { + value: 'checked', + }, + render: () => ( + + }> + Disabled states + + + + Disabled unchecked + + + + + + Disabled checked + + + + + ), +} + +export const StateMatrix: Story = { + args: { + value: 'checked', + }, + render: () => ( +
+ + }> + Radio states + + + + Unchecked + + + + + + Checked + + + + + + Disabled unchecked + + + + + + Disabled checked + + + + +
+
+
+ ), + parameters: { + docs: { + description: { + story: 'The full visual matrix for Dify radio states. State styling comes from Base UI data attributes such as data-checked and data-disabled.', + }, + }, + }, +} diff --git a/packages/dify-ui/src/radio/index.tsx b/packages/dify-ui/src/radio/index.tsx new file mode 100644 index 0000000000..01d8ed5a16 --- /dev/null +++ b/packages/dify-ui/src/radio/index.tsx @@ -0,0 +1,105 @@ +'use client' + +import type { Radio as BaseRadioNS } from '@base-ui/react/radio' +import type { HTMLAttributes } from 'react' +import { Radio as BaseRadio } from '@base-ui/react/radio' +import { cn } from '../cn' + +const radioRootClassName = cn( + 'inline-flex size-4 shrink-0 touch-manipulation items-center justify-center rounded-full p-0 transition-colors motion-reduce:transition-none', + 'border border-components-radio-border bg-components-radio-bg shadow-xs shadow-shadow-shadow-3', + 'hover:border-components-radio-border-hover hover:bg-components-radio-bg-hover', + 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-components-input-border-hover focus-visible:ring-offset-0', + 'data-checked:border-[5px] data-checked:border-components-radio-border-checked data-checked:hover:border-components-radio-border-checked-hover', + 'data-disabled:cursor-not-allowed data-disabled:border-components-radio-border-disabled data-disabled:bg-components-radio-bg-disabled', + 'data-disabled:hover:border-components-radio-border-disabled data-disabled:hover:bg-components-radio-bg-disabled', + 'data-disabled:data-checked:border-[5px] data-disabled:data-checked:border-components-radio-border-checked-disabled', + 'data-disabled:data-checked:hover:border-components-radio-border-checked-disabled', +) + +const radioIndicatorClassName = 'flex items-center justify-center data-unchecked:hidden before:size-1.5 before:rounded-full before:bg-current' + +const radioControlClassName = radioRootClassName + +const radioSkeletonClassName = 'size-4 shrink-0 rounded-full bg-text-quaternary opacity-20' + +export type RadioRootProps + = Omit, 'className'> + & { + className?: string + variant?: 'control' | 'unstyled' + } + +export function RadioRoot({ + className, + variant = 'control', + ...props +}: RadioRootProps) { + return ( + + ) +} + +export type RadioIndicatorProps + = Omit + & { + className?: string + } + +export function RadioIndicator({ + className, + ...props +}: RadioIndicatorProps) { + return ( + + ) +} + +export type RadioControlProps + = Omit + +export function RadioControl({ + className, + ...props +}: RadioControlProps) { + return ( + + ) +} + +export type RadioProps + = Omit, 'children'> + +export function Radio({ + ...props +}: RadioProps) { + return +} + +export type RadioSkeletonProps + = Omit, 'className'> + & { + className?: string + } + +export function RadioSkeleton({ + className, + ...props +}: RadioSkeletonProps) { + return ( +
+ ) +} diff --git a/web/README.md b/web/README.md index 1748ed6947..1a0e526a26 100644 --- a/web/README.md +++ b/web/README.md @@ -167,7 +167,7 @@ The Dify community can be found on [Discord community], where you can ask questi [Storybook]: https://storybook.js.org [Vite+]: https://viteplus.dev [Vitest]: https://vitest.dev -[index.spec.tsx]: ./app/components/base/radio/__tests__/index.spec.tsx +[index.spec.tsx]: ./app/components/base/action-button/__tests__/index.spec.tsx [pnpm]: https://pnpm.io [vinext]: https://github.com/cloudflare/vinext [web/docs/test.md]: ./docs/test.md diff --git a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx index 36fc74a6fd..89f02efe6c 100644 --- a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx +++ b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx @@ -2,11 +2,15 @@ import type { FC } from 'react' import type { AgentConfig } from '@/models/debug' import { cn } from '@langgenius/dify-ui/cn' +import { FieldItem, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field' +import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset' import { Popover, PopoverContent, PopoverTrigger, } from '@langgenius/dify-ui/popover' +import { Radio } from '@langgenius/dify-ui/radio' +import { RadioGroup } from '@langgenius/dify-ui/radio-group' import { RiArrowDownSLine } from '@remixicon/react' import * as React from 'react' import { useState } from 'react' @@ -15,7 +19,6 @@ import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' import { CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' import { BubbleText } from '@/app/components/base/icons/src/vender/solid/education' -import Radio from '@/app/components/base/radio/ui' import AgentSetting from '../agent/agent-setting' type Props = { @@ -35,26 +38,26 @@ type ItemProps = { isChecked: boolean description: string Icon: any - onClick: (value: string) => void } -const SelectItem: FC = ({ text, value, Icon, isChecked, description, onClick, disabled }) => { +const SelectItem: FC = ({ text, value, Icon, isChecked, description, disabled }) => { return ( -
!disabled && onClick(value)} - > -
-
-
- + + +
+
+
+ +
+
{text}
-
{text}
+
- -
-
{description}
-
+
{description}
+ + ) } @@ -127,25 +130,38 @@ const AssistantTypePicker: FC = ({ alignOffset={-2} popupClassName="relative left-0.5 w-[480px] rounded-xl border border-black/8 bg-white p-6 shadow-lg" > -
{t('assistantType.name', { ns: 'appDebug' })}
- - + + + )} + > + + {t('assistantType.name', { ns: 'appDebug' })} + + + + + {!disabled && agentConfigUI} diff --git a/web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx b/web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx index 06865dde70..c99f20f842 100644 --- a/web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx +++ b/web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx @@ -8,10 +8,13 @@ import type { import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' +import { FieldRoot } from '@langgenius/dify-ui/field' +import { FieldsetLegend, FieldsetRoot } from '@langgenius/dify-ui/fieldset' +import { RadioControl, RadioRoot } from '@langgenius/dify-ui/radio' +import { RadioGroup } from '@langgenius/dify-ui/radio-group' import { produce } from 'immer' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import Radio from '@/app/components/base/radio/ui' import Textarea from '@/app/components/base/textarea' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -145,22 +148,30 @@ const FollowUpSettingModal = ({ hideDebugWithMultipleModel />
-
-
- {t('feature.suggestedQuestionsAfterAnswer.modal.promptLabel', { ns: 'appDebug' })} -
-
-
{promptMode === PROMPT_MODE.default && (
@@ -182,18 +191,18 @@ const FollowUpSettingModal = ({
)} - -
{promptMode === PROMPT_MODE.custom && (