diff --git a/web/src/pages/user-setting/setting-model/modal/bedrock-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/bedrock-modal/index.tsx index 6a610d34a..664eb9a2f 100644 --- a/web/src/pages/user-setting/setting-model/modal/bedrock-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/bedrock-modal/index.tsx @@ -1,16 +1,17 @@ -import { useTranslate } from '@/hooks/common-hooks'; +import { SelectWithSearch } from '@/components/originui/select-with-search'; +import { RAGFlowFormItem } from '@/components/ragflow-form'; +import { ButtonLoading } from '@/components/ui/button'; +import { Form } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Modal } from '@/components/ui/modal/modal'; +import { Segmented } from '@/components/ui/segmented'; +import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; -import { - Form, - Input, - InputNumber, - Modal, - Segmented, - Select, - Typography, -} from 'antd'; -import { useMemo, useState } from 'react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useMemo } from 'react'; +import { useForm, useWatch } from 'react-hook-form'; +import { z } from 'zod'; import { LLMHeader } from '../../components/llm-header'; import { BedrockRegionList } from '../../constant'; @@ -22,30 +23,84 @@ type FieldType = IAddLlmRequestBody & { aws_role_arn?: string; }; -const { Option } = Select; -const { Text } = Typography; - const BedrockModal = ({ - visible, + visible = false, hideModal, onOk, loading, llmFactory, }: IModalProps & { llmFactory: string }) => { - const [form] = Form.useForm(); - const [authMode, setAuthMode] = - useState('access_key_secret'); - const { t } = useTranslate('setting'); + const { t: ct } = useCommonTranslation(); + + const FormSchema = z + .object({ + model_type: z.enum(['chat', 'embedding'], { + required_error: t('modelTypeMessage'), + }), + llm_name: z.string().min(1, { message: t('bedrockModelNameMessage') }), + bedrock_region: z.string().min(1, { message: t('bedrockRegionMessage') }), + max_tokens: z + .number({ + required_error: t('maxTokensMessage'), + invalid_type_error: t('maxTokensInvalidMessage'), + }) + .nonnegative({ message: t('maxTokensMinMessage') }), + auth_mode: z + .enum(['access_key_secret', 'iam_role', 'assume_role']) + .default('access_key_secret'), + bedrock_ak: z.string().optional(), + bedrock_sk: z.string().optional(), + aws_role_arn: z.string().optional(), + }) + .superRefine((data, ctx) => { + if (data.auth_mode === 'access_key_secret') { + if (!data.bedrock_ak || data.bedrock_ak.trim() === '') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: t('bedrockAKMessage'), + path: ['bedrock_ak'], + }); + } + if (!data.bedrock_sk || data.bedrock_sk.trim() === '') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: t('bedrockSKMessage'), + path: ['bedrock_sk'], + }); + } + } + + if (data.auth_mode === 'iam_role') { + if (!data.aws_role_arn || data.aws_role_arn.trim() === '') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: t('awsRoleArnMessage'), + path: ['aws_role_arn'], + }); + } + } + }); + + const form = useForm({ + resolver: zodResolver(FormSchema), + defaultValues: { + model_type: 'chat', + auth_mode: 'access_key_secret', + }, + }); + + const authMode = useWatch({ + control: form.control, + name: 'auth_mode', + }); + const options = useMemo( () => BedrockRegionList.map((x) => ({ value: x, label: t(x) })), [t], ); - const handleOk = async () => { - const values = await form.validateFields(); - - // Only submit fields related to the active auth mode. + const handleOk = async (values: FieldType) => { const cleanedValues: Record = { ...values }; const fieldsByMode: Record = { @@ -75,145 +130,140 @@ const BedrockModal = ({ return ( - + title={} + open={visible} + onOpenChange={(open) => !open && hideModal?.()} + maskClosable={false} + footer={ +
+ + + {ct('ok')} +
} - open={visible} - onOk={handleOk} - onCancel={hideModal} - okButtonProps={{ loading }} > -
- - label={t('modelType')} - name="model_type" - initialValue={'chat'} - rules={[{ required: true, message: t('modelTypeMessage') }]} + + - - - - label={t('modelName')} - name="llm_name" - rules={[{ required: true, message: t('bedrockModelNameMessage') }]} - > - - + + {(field) => ( + + )} + - {/* AWS Credential Mode Switch (AK/SK section only) */} - - { - const next = v as FieldType['auth_mode']; - setAuthMode(next); - // Clear non-active fields so they won't be validated/submitted by accident. - if (next !== 'access_key_secret') { - form.setFieldsValue({ bedrock_ak: '', bedrock_sk: '' } as any); - } - if (next !== 'iam_role') { - form.setFieldsValue({ aws_role_arn: '' } as any); - } - if (next !== 'assume_role') { - form.setFieldsValue({ role_arn: '' } as any); - } - }} - options={[ - { - label: t('awsAuthModeAccessKeySecret'), - value: 'access_key_secret', - }, - { label: t('awsAuthModeIamRole'), value: 'iam_role' }, - { label: t('awsAuthModeAssumeRole'), value: 'assume_role' }, - ]} - /> - + + + - {authMode === 'access_key_secret' && ( - <> - - label={t('awsAccessKeyId')} - name="bedrock_ak" - rules={[{ required: true, message: t('bedrockAKMessage') }]} +
+ + {(field) => ( + { + // Clear non-active fields so they won't be validated/submitted by accident. + if (value !== 'access_key_secret') { + form.setValue('bedrock_ak', ''); + form.setValue('bedrock_sk', ''); + } + if (value !== 'iam_role') { + form.setValue('aws_role_arn', ''); + } + field.onChange(value); + }} + options={[ + { + label: t('awsAuthModeAccessKeySecret'), + value: 'access_key_secret', + }, + { label: t('awsAuthModeIamRole'), value: 'iam_role' }, + { label: t('awsAuthModeAssumeRole'), value: 'assume_role' }, + ]} + /> + )} + +
+ + {authMode === 'access_key_secret' && ( + <> + + + + + + + + )} + + {authMode === 'iam_role' && ( + - - - - label={t('awsSecretAccessKey')} - name="bedrock_sk" - rules={[{ required: true, message: t('bedrockSKMessage') }]} - > - - - - )} + + + )} - {authMode === 'iam_role' && ( - - label={t('awsRoleArn')} - name="aws_role_arn" - rules={[{ required: true, message: t('awsRoleArnMessage') }]} + {authMode === 'assume_role' && ( +
+ {t('awsAssumeRoleTip')} +
+ )} + + - - - )} + {(field) => ( + + )} + - {authMode === 'assume_role' && ( - - {t('awsAssumeRoleTip')} - - )} - - - label={t('bedrockRegion')} - name="bedrock_region" - rules={[{ required: true, message: t('bedrockRegionMessage') }]} - > - - - - label={t('maxTokens')} - name="max_tokens" - rules={[ - { required: true, message: t('maxTokensMessage') }, - { - type: 'number', - message: t('maxTokensInvalidMessage'), - }, - ({}) => ({ - validator(_, value) { - if (value < 0) { - return Promise.reject(new Error(t('maxTokensMinMessage'))); - } - return Promise.resolve(); - }, - }), - ]} - > - - + + {(field) => ( + field.onChange(Number(e.target.value))} + /> + )} + +
); diff --git a/web/src/pages/user-setting/setting-model/modal/fish-audio-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/fish-audio-modal/index.tsx index 30511fab1..3ce52cef9 100644 --- a/web/src/pages/user-setting/setting-model/modal/fish-audio-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/fish-audio-modal/index.tsx @@ -1,17 +1,15 @@ -import { useTranslate } from '@/hooks/common-hooks'; +import { + DynamicForm, + FormFieldConfig, + FormFieldType, +} from '@/components/dynamic-form'; +import { Modal } from '@/components/ui/modal/modal'; +import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; -import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd'; -import omit from 'lodash/omit'; +import { FieldValues } from 'react-hook-form'; import { LLMHeader } from '../../components/llm-header'; -type FieldType = IAddLlmRequestBody & { - fish_audio_ak: string; - fish_audio_refid: string; -}; - -const { Option } = Select; - const FishAudioModal = ({ visible, hideModal, @@ -19,107 +17,106 @@ const FishAudioModal = ({ loading, llmFactory, }: IModalProps & { llmFactory: string }) => { - const [form] = Form.useForm(); - const { t } = useTranslate('setting'); + const { t: tc } = useCommonTranslation(); - const handleOk = async () => { - const values = await form.validateFields(); - const modelType = values.model_type; + const fields: FormFieldConfig[] = [ + { + name: 'model_type', + label: t('modelType'), + type: FormFieldType.Select, + required: true, + options: [{ label: 'tts', value: 'tts' }], + defaultValue: 'tts', + validation: { message: t('modelTypeMessage') }, + }, + { + name: 'llm_name', + label: t('modelName'), + type: FormFieldType.Text, + required: true, + placeholder: t('FishAudioModelNameMessage'), + validation: { message: t('FishAudioModelNameMessage') }, + }, + { + name: 'fish_audio_ak', + label: t('addFishAudioAK'), + type: FormFieldType.Text, + required: true, + placeholder: t('FishAudioAKMessage'), + validation: { message: t('FishAudioAKMessage') }, + }, + { + name: 'fish_audio_refid', + label: t('addFishAudioRefID'), + type: FormFieldType.Text, + required: true, + placeholder: t('FishAudioRefIDMessage'), + validation: { message: t('FishAudioRefIDMessage') }, + }, + { + name: 'max_tokens', + label: t('maxTokens'), + type: FormFieldType.Number, + required: true, + placeholder: t('maxTokensTip'), + validation: { + min: 0, + message: t('maxTokensInvalidMessage'), + }, + }, + ]; - const data = { - ...omit(values), - model_type: modelType, + const handleOk = async (values?: FieldValues) => { + if (!values) return; + + const data: Record = { llm_factory: llmFactory, - max_tokens: values.max_tokens, + llm_name: values.llm_name as string, + model_type: values.model_type, + fish_audio_ak: values.fish_audio_ak, + fish_audio_refid: values.fish_audio_refid, + max_tokens: values.max_tokens as number, }; - console.info(data); - onOk?.(data); + console.info(data); + await onOk?.(data as IAddLlmRequestBody); }; return ( } - open={visible} - onOk={handleOk} - onCancel={hideModal} - okButtonProps={{ loading }} - footer={(originNode: React.ReactNode) => { - return ( - - - {t('FishAudioLink')} - - {originNode} - - ); - }} - confirmLoading={loading} + open={visible || false} + onOpenChange={(open) => !open && hideModal?.()} + maskClosable={false} + footerClassName="py-1" + footer={
} > -
console.log(data)} + defaultValues={{ model_type: 'tts' }} + labelClassName="font-normal" > - - label={t('modelType')} - name="model_type" - initialValue={'tts'} - rules={[{ required: true, message: t('modelTypeMessage') }]} - > - - - - label={t('modelName')} - name="llm_name" - rules={[{ required: true, message: t('FishAudioModelNameMessage') }]} - > - - - - label={t('addFishAudioAK')} - name="fish_audio_ak" - rules={[{ required: true, message: t('FishAudioAKMessage') }]} - > - - - - label={t('addFishAudioRefID')} - name="fish_audio_refid" - rules={[{ required: true, message: t('FishAudioRefIDMessage') }]} - > - - - - label={t('maxTokens')} - name="max_tokens" - rules={[ - { required: true, message: t('maxTokensMessage') }, - { - type: 'number', - message: t('maxTokensInvalidMessage'), - }, - ({}) => ({ - validator(_, value) { - if (value < 0) { - return Promise.reject(new Error(t('maxTokensMinMessage'))); - } - return Promise.resolve(); - }, - }), - ]} - > - - - +
+ + {t('FishAudioLink')} + +
+ hideModal?.()} /> + handleOk(values)} + /> +
+
+
); };