From 89fdb1d49824ac92e6014426a1f5faadace8ae8a Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Thu, 5 Feb 2026 15:53:20 +0800 Subject: [PATCH] Feat: Add model verify (#13005) ### What problem does this PR solve? Feat: Add model verify ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: Liu An --- web/src/app.tsx | 9 +- web/src/components/confirm-delete-dialog.tsx | 24 +- web/src/hooks/use-llm-request.tsx | 17 +- web/src/locales/en.ts | 4 + web/src/locales/zh.ts | 4 + .../setting-model/components/modal-card.tsx | 2 +- .../user-setting/setting-model/hooks.tsx | 477 ++++++++++++++---- .../user-setting/setting-model/index.tsx | 83 +++ .../modal/api-key-modal/index.tsx | 8 + .../modal/azure-openai-modal/index.tsx | 68 ++- .../modal/bedrock-modal/index.tsx | 49 +- .../modal/fish-audio-modal/index.tsx | 24 +- .../modal/google-modal/index.tsx | 28 +- .../modal/mineru-modal/index.tsx | 25 +- .../modal/next-tencent-modal/index.tsx | 27 +- .../modal/ollama-modal/index.tsx | 37 +- .../modal/paddleocr-modal/index.tsx | 49 +- .../setting-model/modal/spark-modal/index.tsx | 39 +- .../modal/verify-button/index.tsx | 123 +++++ .../modal/volcengine-modal/index.tsx | 41 +- .../setting-model/modal/yiyan-modal/index.tsx | 160 ++++-- web/tailwind.config.js | 5 + 22 files changed, 1083 insertions(+), 220 deletions(-) create mode 100644 web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx diff --git a/web/src/app.tsx b/web/src/app.tsx index c6c65fdd4..6db41b863 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -77,7 +77,14 @@ if (process.env.NODE_ENV === 'development') { }, ); } -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 2, + }, + }, +}); type Locale = ConfigProviderProps['locale']; diff --git a/web/src/components/confirm-delete-dialog.tsx b/web/src/components/confirm-delete-dialog.tsx index 99859ccc7..668ba8c6e 100644 --- a/web/src/components/confirm-delete-dialog.tsx +++ b/web/src/components/confirm-delete-dialog.tsx @@ -119,17 +119,19 @@ export const ConfirmDeleteDialogNode = ({ }) => { return (
-
- {avatar && ( - - )} - {name &&
{name}
} -
+ {(avatar || name) && ( +
+ {avatar && ( + + )} + {name &&
{name}
} +
+ )} {warnText &&
{warnText}
} {children}
diff --git a/web/src/hooks/use-llm-request.tsx b/web/src/hooks/use-llm-request.tsx index cdd46c222..99bb7c6e7 100644 --- a/web/src/hooks/use-llm-request.tsx +++ b/web/src/hooks/use-llm-request.tsx @@ -271,11 +271,12 @@ export interface IApiKeySavingParams { llm_name?: string; model_type?: string; base_url?: string; + verify?: boolean; } export const useSaveApiKey = () => { const queryClient = useQueryClient(); - const { t } = useTranslation(); + // const { t } = useTranslation(); const { data, isPending: loading, @@ -285,14 +286,14 @@ export const useSaveApiKey = () => { mutationFn: async (params: IApiKeySavingParams) => { const { data } = await userService.set_api_key(params); if (data.code === 0) { - message.success(t('message.modified')); + // message.success(t('message.modified')); queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] }); queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmListDetailed], }); queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] }); } - return data.code; + return data; }, }); @@ -330,25 +331,25 @@ export const useSaveTenantInfo = () => { export const useAddLlm = () => { const queryClient = useQueryClient(); - const { t } = useTranslation(); + // const { t } = useTranslation(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [LLMApiAction.AddLlm], - mutationFn: async (params: IAddLlmRequestBody) => { + mutationFn: async (params: IAddLlmRequestBody & { verify?: boolean }) => { const { data } = await userService.add_llm(params); - if (data.code === 0) { + if (data.code === 0 && !params.verify) { queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] }); queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmListDetailed], }); queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] }); queryClient.invalidateQueries({ queryKey: [LLMApiAction.LlmList] }); - message.success(t('message.modified')); + // message.success(t('message.modified')); } - return data.code; + return data; }, }); diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 2948914a4..0986add12 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -61,6 +61,7 @@ export default { tokenPlaceholder: 'e.g. eyJhbGciOiJIUzI1Ni...', }, selected: 'Selected', + seeAll: 'See all', }, login: { loginTitle: 'Sign in to your account', @@ -889,6 +890,9 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s deleteSelectedConfirm: 'Delete the selected {count} session(s)?', }, setting: { + Verify: 'Verify', + keyValid: 'Your API key is valid.', + keyInvalid: 'Your API key is invalid.', deleteModel: 'Delete model', bedrockCredentialsHint: 'Tip: Leave Access Key / Secret Key blank to use AWS IAM authentication.', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index bbfef63e4..78ed72694 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -56,6 +56,7 @@ export default { zendeskDescription: '连接 Zendesk,同步工单、文章及其他内容。', promptPlaceholder: '请输入或使用 / 快速插入变量。', selected: '已选择', + seeAll: '查看全部', }, login: { loginTitle: '登录账户', @@ -835,6 +836,9 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 deleteSelectedConfirm: '删除选中的 {count} 个会话?', }, setting: { + Verify: '验证', + keyValid: '你的 API 密钥有效。', + keyInvalid: '你的 API 密钥无效。', deleteModel: '删除模型', modelEmptyTip: '暂无可用模型,
请先在右侧面板添加模型。', sourceEmptyTip: '暂未添加任何数据源,请从下方选择一个进行连接。', diff --git a/web/src/pages/user-setting/setting-model/components/modal-card.tsx b/web/src/pages/user-setting/setting-model/components/modal-card.tsx index b1edaa7a8..5273d893a 100644 --- a/web/src/pages/user-setting/setting-model/components/modal-card.tsx +++ b/web/src/pages/user-setting/setting-model/components/modal-card.tsx @@ -115,7 +115,7 @@ export const ModelProviderCard: FC = ({ content={{ node: ( -
+
{item.name}
diff --git a/web/src/pages/user-setting/setting-model/hooks.tsx b/web/src/pages/user-setting/setting-model/hooks.tsx index bb46c6bc8..3a5ba677b 100644 --- a/web/src/pages/user-setting/setting-model/hooks.tsx +++ b/web/src/pages/user-setting/setting-model/hooks.tsx @@ -20,30 +20,56 @@ import { ApiKeyPostBody } from '../interface'; import { MinerUFormValues } from './modal/mineru-modal'; type SavingParamsState = Omit; - +export type VerifyResult = { + isValid: boolean | null; + logs: string; +}; export const useSubmitApiKey = () => { const [savingParams, setSavingParams] = useState( {} as SavingParamsState, ); const [editMode, setEditMode] = useState(false); - const { saveApiKey, loading } = useSaveApiKey(); + const { saveApiKey } = useSaveApiKey(); + const [saveLoading, setSaveLoading] = useState(false); const { visible: apiKeyVisible, hideModal: hideApiKeyModal, showModal: showApiKeyModal, } = useSetModalState(); const queryClient = useQueryClient(); + const onApiKeySavingOk = useCallback( - async (postBody: ApiKeyPostBody) => { + async (postBody: ApiKeyPostBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } const ret = await saveApiKey({ ...savingParams, ...postBody, + verify: isVerify, }); - - if (ret === 0) { - queryClient.invalidateQueries({ queryKey: ['llmList'] }); - hideApiKeyModal(); - setEditMode(false); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + queryClient.invalidateQueries({ queryKey: ['llmList'] }); + hideApiKeyModal(); + setEditMode(false); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data.message, + }; + } + return res; } }, [hideApiKeyModal, saveApiKey, savingParams, queryClient], @@ -59,7 +85,7 @@ export const useSubmitApiKey = () => { ); return { - saveApiKeyLoading: loading, + saveApiKeyLoading: saveLoading, initialApiKey: '', llmFactory: savingParams.llm_factory, editMode, @@ -119,7 +145,8 @@ export const useSubmitOllama = () => { const [initialValues, setInitialValues] = useState< Partial & { provider_order?: string } >(); - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: llmAddingVisible, hideModal: hideLlmAddingModal, @@ -127,20 +154,41 @@ export const useSubmitOllama = () => { } = useSetModalState(); const onLlmAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } const cleanedPayload = { ...payload }; if (!cleanedPayload.api_key || cleanedPayload.api_key.trim() === '') { delete cleanedPayload.api_key; } - const ret = await addLlm(cleanedPayload); - if (ret === 0) { - hideLlmAddingModal(); - setEditMode(false); - setInitialValues(undefined); + const ret = await addLlm({ ...cleanedPayload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideLlmAddingModal(); + setEditMode(false); + setInitialValues(undefined); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideLlmAddingModal, addLlm], + [hideLlmAddingModal, addLlm, setSaveLoading], ); const handleShowLlmAddingModal = ( @@ -168,7 +216,7 @@ export const useSubmitOllama = () => { }; return { - llmAddingLoading: loading, + llmAddingLoading: saveLoading, editMode, initialValues, onLlmAddingOk, @@ -180,7 +228,8 @@ export const useSubmitOllama = () => { }; export const useSubmitVolcEngine = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: volcAddingVisible, hideModal: hideVolcAddingModal, @@ -188,17 +237,38 @@ export const useSubmitVolcEngine = () => { } = useSetModalState(); const onVolcAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideVolcAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideVolcAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideVolcAddingModal, addLlm], + [hideVolcAddingModal, addLlm, setSaveLoading], ); return { - volcAddingLoading: loading, + volcAddingLoading: saveLoading, onVolcAddingOk, volcAddingVisible, hideVolcAddingModal, @@ -207,7 +277,8 @@ export const useSubmitVolcEngine = () => { }; export const useSubmitTencentCloud = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: TencentCloudAddingVisible, hideModal: hideTencentCloudAddingModal, @@ -215,17 +286,38 @@ export const useSubmitTencentCloud = () => { } = useSetModalState(); const onTencentCloudAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideTencentCloudAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideTencentCloudAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideTencentCloudAddingModal, addLlm], + [hideTencentCloudAddingModal, addLlm, setSaveLoading], ); return { - TencentCloudAddingLoading: loading, + TencentCloudAddingLoading: saveLoading, onTencentCloudAddingOk, TencentCloudAddingVisible, hideTencentCloudAddingModal, @@ -234,7 +326,8 @@ export const useSubmitTencentCloud = () => { }; export const useSubmitSpark = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: SparkAddingVisible, hideModal: hideSparkAddingModal, @@ -242,17 +335,38 @@ export const useSubmitSpark = () => { } = useSetModalState(); const onSparkAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideSparkAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideSparkAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideSparkAddingModal, addLlm], + [hideSparkAddingModal, addLlm, setSaveLoading], ); return { - SparkAddingLoading: loading, + SparkAddingLoading: saveLoading, onSparkAddingOk, SparkAddingVisible, hideSparkAddingModal, @@ -261,7 +375,8 @@ export const useSubmitSpark = () => { }; export const useSubmityiyan = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: yiyanAddingVisible, hideModal: hideyiyanAddingModal, @@ -269,17 +384,38 @@ export const useSubmityiyan = () => { } = useSetModalState(); const onyiyanAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideyiyanAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideyiyanAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideyiyanAddingModal, addLlm], + [hideyiyanAddingModal, addLlm, setSaveLoading], ); return { - yiyanAddingLoading: loading, + yiyanAddingLoading: saveLoading, onyiyanAddingOk, yiyanAddingVisible, hideyiyanAddingModal, @@ -288,7 +424,8 @@ export const useSubmityiyan = () => { }; export const useSubmitFishAudio = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: FishAudioAddingVisible, hideModal: hideFishAudioAddingModal, @@ -296,17 +433,38 @@ export const useSubmitFishAudio = () => { } = useSetModalState(); const onFishAudioAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideFishAudioAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideFishAudioAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideFishAudioAddingModal, addLlm], + [hideFishAudioAddingModal, addLlm, setSaveLoading], ); return { - FishAudioAddingLoading: loading, + FishAudioAddingLoading: saveLoading, onFishAudioAddingOk, FishAudioAddingVisible, hideFishAudioAddingModal, @@ -315,7 +473,8 @@ export const useSubmitFishAudio = () => { }; export const useSubmitGoogle = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: GoogleAddingVisible, hideModal: hideGoogleAddingModal, @@ -323,17 +482,38 @@ export const useSubmitGoogle = () => { } = useSetModalState(); const onGoogleAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideGoogleAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideGoogleAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideGoogleAddingModal, addLlm], + [hideGoogleAddingModal, addLlm, setSaveLoading], ); return { - GoogleAddingLoading: loading, + GoogleAddingLoading: saveLoading, onGoogleAddingOk, GoogleAddingVisible, hideGoogleAddingModal, @@ -342,7 +522,8 @@ export const useSubmitGoogle = () => { }; export const useSubmitBedrock = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: bedrockAddingVisible, hideModal: hideBedrockAddingModal, @@ -350,17 +531,38 @@ export const useSubmitBedrock = () => { } = useSetModalState(); const onBedrockAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideBedrockAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideBedrockAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideBedrockAddingModal, addLlm], + [hideBedrockAddingModal, addLlm, setSaveLoading], ); return { - bedrockAddingLoading: loading, + bedrockAddingLoading: saveLoading, onBedrockAddingOk, bedrockAddingVisible, hideBedrockAddingModal, @@ -369,7 +571,8 @@ export const useSubmitBedrock = () => { }; export const useSubmitAzure = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: AzureAddingVisible, hideModal: hideAzureAddingModal, @@ -377,17 +580,38 @@ export const useSubmitAzure = () => { } = useSetModalState(); const onAzureAddingOk = useCallback( - async (payload: IAddLlmRequestBody) => { - const ret = await addLlm(payload); - if (ret === 0) { - hideAzureAddingModal(); + async (payload: IAddLlmRequestBody, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } + const ret = await addLlm({ ...payload, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideAzureAddingModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [hideAzureAddingModal, addLlm], + [hideAzureAddingModal, addLlm, setSaveLoading], ); return { - AzureAddingLoading: loading, + AzureAddingLoading: saveLoading, onAzureAddingOk, AzureAddingVisible, hideAzureAddingModal, @@ -436,7 +660,8 @@ export const useHandleDeleteFactory = (llmFactory: string) => { }; export const useSubmitMinerU = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: mineruVisible, hideModal: hideMineruModal, @@ -444,7 +669,10 @@ export const useSubmitMinerU = () => { } = useSetModalState(); const onMineruOk = useCallback( - async (payload: MinerUFormValues) => { + async (payload: MinerUFormValues, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } const cfg: any = { ...payload, mineru_delete_output: @@ -461,12 +689,30 @@ export const useSubmitMinerU = () => { api_base: '', max_tokens: 0, }; - const ret = await addLlm(req); - if (ret === 0) { - hideMineruModal(); + const ret = await addLlm({ ...req, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hideMineruModal(); + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } }, - [addLlm, hideMineruModal], + [addLlm, hideMineruModal, setSaveLoading], ); return { @@ -474,12 +720,13 @@ export const useSubmitMinerU = () => { hideMineruModal, showMineruModal, onMineruOk, - mineruLoading: loading, + mineruLoading: saveLoading, }; }; export const useSubmitPaddleOCR = () => { - const { addLlm, loading } = useAddLlm(); + const [saveLoading, setSaveLoading] = useState(false); + const { addLlm } = useAddLlm(); const { visible: paddleocrVisible, hideModal: hidePaddleOCRModal, @@ -487,7 +734,10 @@ export const useSubmitPaddleOCR = () => { } = useSetModalState(); const onPaddleOCROk = useCallback( - async (payload: any) => { + async (payload: any, isVerify = false) => { + if (!isVerify) { + setSaveLoading(true); + } const cfg: any = { ...payload, }; @@ -499,14 +749,32 @@ export const useSubmitPaddleOCR = () => { api_base: '', max_tokens: 0, }; - const ret = await addLlm(req); - if (ret === 0) { - hidePaddleOCRModal(); - return true; + const ret = await addLlm({ ...req, verify: isVerify }); + if (!isVerify) { + setSaveLoading(false); + if (ret.code === 0) { + hidePaddleOCRModal(); + return true; + } + } + if (isVerify) { + let res = {} as VerifyResult; + if (ret.data?.success) { + res = { + isValid: true, + logs: ret.data?.message, + }; + } else { + res = { + isValid: false, + logs: ret.data?.message, + }; + } + return res; } return false; }, - [addLlm, hidePaddleOCRModal], + [addLlm, hidePaddleOCRModal, setSaveLoading], ); return { @@ -514,6 +782,37 @@ export const useSubmitPaddleOCR = () => { hidePaddleOCRModal, showPaddleOCRModal, onPaddleOCROk, - paddleocrLoading: loading, + paddleocrLoading: saveLoading, + }; +}; + +export const useVerifySettings = ({ + onVerify, +}: { + onVerify: + | (( + postBody: ApiKeyPostBody, + isVerify?: boolean, + ) => Promise) + | (( + payload: IAddLlmRequestBody, + isVerify?: boolean, + ) => Promise) + | (( + payload: MinerUFormValues, + isVerify?: boolean, + ) => Promise) + | ((payload: any, isVerify?: boolean) => Promise) + | (() => void); +}) => { + const onApiKeyVerifying = useCallback( + async (postBody: any) => { + const res = await onVerify(postBody, true); + return res; + }, + [onVerify], + ); + return { + onApiKeyVerifying, }; }; diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index 46e77f738..0ca84b142 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -20,6 +20,7 @@ import { useSubmitTencentCloud, useSubmitVolcEngine, useSubmityiyan, + useVerifySettings, } from './hooks'; import ApiKeyModal from './modal/api-key-modal'; import AzureOpenAIModal from './modal/azure-openai-modal'; @@ -204,6 +205,76 @@ const ModelProviders = () => { }, [showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList], ); + + const handleOk = useMemo(() => { + if (apiKeyVisible) { + return onApiKeySavingOk; + } + if (llmAddingVisible) { + return onLlmAddingOk; + } + if (volcAddingVisible) { + return onVolcAddingOk; + } + if (TencentCloudAddingVisible) { + return onTencentCloudAddingOk; + } + if (SparkAddingVisible) { + return onSparkAddingOk; + } + if (yiyanAddingVisible) { + return onyiyanAddingOk; + } + if (FishAudioAddingVisible) { + return onFishAudioAddingOk; + } + if (bedrockAddingVisible) { + return onBedrockAddingOk; + } + if (AzureAddingVisible) { + return onAzureAddingOk; + } + if (mineruVisible) { + return onMineruOk; + } + if (paddleocrVisible) { + return onPaddleOCROk; + } + if (GoogleAddingVisible) { + return onGoogleAddingOk; + } + return () => {}; + }, [ + GoogleAddingVisible, + onGoogleAddingOk, + apiKeyVisible, + onApiKeySavingOk, + llmAddingVisible, + onLlmAddingOk, + volcAddingVisible, + onVolcAddingOk, + TencentCloudAddingVisible, + onTencentCloudAddingOk, + SparkAddingVisible, + onSparkAddingOk, + yiyanAddingVisible, + onyiyanAddingOk, + FishAudioAddingVisible, + onFishAudioAddingOk, + bedrockAddingVisible, + onBedrockAddingOk, + AzureAddingVisible, + onAzureAddingOk, + mineruVisible, + onMineruOk, + paddleocrVisible, + onPaddleOCROk, + ]); + + const { onApiKeyVerifying } = useVerifySettings({ + onVerify: handleOk, + }); + return (
@@ -227,6 +298,7 @@ const ModelProviders = () => { initialValue={initialApiKey} editMode={editMode} onOk={onApiKeySavingOk} + onVerify={onApiKeyVerifying} llmFactory={llmFactory} > {llmAddingVisible && ( @@ -238,6 +310,7 @@ const ModelProviders = () => { editMode={llmEditMode} initialValues={llmInitialValues} llmFactory={selectedLlmFactory} + onVerify={onApiKeyVerifying} > )} { onOk={onVolcAddingOk} loading={volcAddingLoading} llmFactory={LLMFactory.VolcEngine} + onVerify={onApiKeyVerifying} > { onOk={onGoogleAddingOk} loading={GoogleAddingLoading} llmFactory={LLMFactory.GoogleCloud} + onVerify={onApiKeyVerifying} > { onOk={onTencentCloudAddingOk} loading={TencentCloudAddingLoading} llmFactory={LLMFactory.TencentCloud} + onVerify={onApiKeyVerifying} > { onOk={onSparkAddingOk} loading={SparkAddingLoading} llmFactory={LLMFactory.XunFeiSpark} + onVerify={onApiKeyVerifying} > { onOk={onyiyanAddingOk} loading={yiyanAddingLoading} llmFactory={LLMFactory.BaiduYiYan} + onVerify={onApiKeyVerifying} > { onOk={onFishAudioAddingOk} loading={FishAudioAddingLoading} llmFactory={LLMFactory.FishAudio} + onVerify={onApiKeyVerifying} > { onOk={onBedrockAddingOk} loading={bedrockAddingLoading} llmFactory={LLMFactory.Bedrock} + onVerify={onApiKeyVerifying} > { onOk={onAzureAddingOk} loading={AzureAddingLoading} llmFactory={LLMFactory.AzureOpenAI} + onVerify={onApiKeyVerifying} >
); diff --git a/web/src/pages/user-setting/setting-model/modal/api-key-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/api-key-modal/index.tsx index 66fab6229..7a84c0e83 100644 --- a/web/src/pages/user-setting/setting-model/modal/api-key-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/api-key-modal/index.tsx @@ -15,6 +15,8 @@ import { KeyboardEventHandler, useCallback, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { ApiKeyPostBody } from '../../../interface'; import { LLMHeader } from '../../components/llm-header'; +import { VerifyResult } from '../../hooks'; +import VerifyButton from '../verify-button'; interface IProps extends Omit { loading: boolean; @@ -22,6 +24,9 @@ interface IProps extends Omit { llmFactory: string; editMode?: boolean; onOk: (postBody: ApiKeyPostBody) => void; + onVerify: ( + postBody: any, + ) => Promise; showModal?(): void; } @@ -46,6 +51,7 @@ const ApiKeyModal = ({ initialValue, editMode = false, onOk, + onVerify, }: IProps) => { const form = useForm(); const { t } = useTranslate('setting'); @@ -181,6 +187,8 @@ const ApiKeyModal = ({ )} /> )} + +
diff --git a/web/src/pages/user-setting/setting-model/modal/azure-openai-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/azure-openai-modal/index.tsx index fca1bd976..b8442d29a 100644 --- a/web/src/pages/user-setting/setting-model/modal/azure-openai-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/azure-openai-modal/index.tsx @@ -1,5 +1,6 @@ import { DynamicForm, + DynamicFormRef, FormFieldConfig, FormFieldType, } from '@/components/dynamic-form'; @@ -8,19 +9,29 @@ import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; +import { memo, useCallback, useRef } from 'react'; import { FieldValues } from 'react-hook-form'; import { LLMHeader } from '../../components/llm-header'; +import VerifyButton from '../../modal/verify-button'; const AzureOpenAIModal = ({ visible, hideModal, onOk, + onVerify, loading, llmFactory, -}: IModalProps & { llmFactory: string }) => { +}: IModalProps & { + llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslate('setting'); const { t: tg } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); + const formRef = useRef(null); const fields: FormFieldConfig[] = [ { @@ -114,6 +125,27 @@ const AzureOpenAIModal = ({ await onOk?.(data); }; + const verifyParamsFunc = useCallback(() => { + const values = formRef.current?.getValues(); + const modelType = + values.model_type === 'chat' && values.vision + ? 'image2text' + : values.model_type; + return { + llm_factory: llmFactory, + model_type: modelType, + }; + }, [llmFactory]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); + return ( } @@ -127,6 +159,7 @@ const AzureOpenAIModal = ({ onSubmit={(data) => { console.log(data); }} + ref={formRef} defaultValues={ { model_type: 'embedding', @@ -137,23 +170,26 @@ const AzureOpenAIModal = ({ } labelClassName="font-normal" > -
- { - hideModal?.(); - }} - /> - { - handleOk(values); - }} - /> -
+ <> + {onVerify && } +
+ { + hideModal?.(); + }} + /> + { + handleOk(values); + }} + /> +
+
); }; -export default AzureOpenAIModal; +export default memo(AzureOpenAIModal); 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 c8ccaa3a2..f9d4f2b3f 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 @@ -9,12 +9,14 @@ import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { z } from 'zod'; import { LLMHeader } from '../../components/llm-header'; import { BedrockRegionList } from '../../constant'; +import VerifyButton from '../../modal/verify-button'; type FieldType = IAddLlmRequestBody & { auth_mode?: 'access_key_secret' | 'iam_role' | 'assume_role'; @@ -28,9 +30,15 @@ const BedrockModal = ({ visible = false, hideModal, onOk, + onVerify, loading, llmFactory, -}: IModalProps & { llmFactory: string }) => { +}: IModalProps & { + llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslate('setting'); const { t: ct } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); @@ -130,6 +138,40 @@ const BedrockModal = ({ onOk?.(data as unknown as IAddLlmRequestBody); }; + const verifyParamsFunc = useCallback(() => { + const values = form.getValues(); + const cleanedValues: Record = { ...values }; + const fieldsByMode: Record = { + access_key_secret: ['bedrock_ak', 'bedrock_sk'], + iam_role: ['aws_role_arn'], + assume_role: [], + }; + + cleanedValues.auth_mode = authMode; + + Object.keys(fieldsByMode).forEach((mode) => { + if (mode !== authMode) { + fieldsByMode[mode].forEach((field) => { + delete cleanedValues[field]; + }); + } + }); + return { + ...cleanedValues, + llm_factory: llmFactory, + max_tokens: values.max_tokens, + }; + }, [llmFactory, authMode, form]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); + return ( } @@ -262,10 +304,11 @@ const BedrockModal = ({ /> )} + {onVerify && } ); }; -export default BedrockModal; +export default memo(BedrockModal); 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 6962eeb7a..6f9807429 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 @@ -8,16 +8,25 @@ import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; +import { memo, useCallback } from 'react'; import { FieldValues } from 'react-hook-form'; import { LLMHeader } from '../../components/llm-header'; +import VerifyButton from '../../modal/verify-button'; const FishAudioModal = ({ visible, hideModal, onOk, + onVerify, loading, llmFactory, -}: IModalProps & { llmFactory: string }) => { +}: IModalProps & { + llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslate('setting'); const { t: tc } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); @@ -85,6 +94,14 @@ const FishAudioModal = ({ await onOk?.(data as IAddLlmRequestBody); }; + const handleVerify = useCallback( + async (params: any) => { + const res = await onVerify?.({ ...params, llm_factory: llmFactory }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [llmFactory, onVerify], + ); + return ( } @@ -100,6 +117,9 @@ const FishAudioModal = ({ defaultValues={{ model_type: 'tts' }} labelClassName="font-normal" > + {onVerify && ( + + )}
& { llmFactory: string }) => { +}: IModalProps & { + llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslate('setting'); const { t: tc } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); @@ -112,6 +121,20 @@ const GoogleModal = ({ await onOk?.(data); }; + const verifyParamsFunc = useCallback(() => { + return { + llm_factory: llmFactory, + }; + }, [llmFactory]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); return ( } @@ -132,6 +155,7 @@ const GoogleModal = ({ } labelClassName="font-normal" > + {onVerify && }
{ @@ -151,4 +175,4 @@ const GoogleModal = ({ ); }; -export default GoogleModal; +export default memo(GoogleModal); diff --git a/web/src/pages/user-setting/setting-model/modal/mineru-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/mineru-modal/index.tsx index 0a9c1924b..83b4ed712 100644 --- a/web/src/pages/user-setting/setting-model/modal/mineru-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/mineru-modal/index.tsx @@ -13,13 +13,16 @@ import { RAGFlowSelect } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { LLMFactory } from '@/constants/llm'; import { IModalProps } from '@/interfaces/common'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; import { buildOptions } from '@/utils/form'; import { zodResolver } from '@hookform/resolvers/zod'; import { t } from 'i18next'; +import { memo } from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; import { LLMHeader } from '../../components/llm-header'; +import VerifyButton from '../verify-button'; const FormSchema = z.object({ llm_name: z.string().min(1, { @@ -46,8 +49,13 @@ const MinerUModal = ({ visible, hideModal, onOk, + onVerify, loading, -}: IModalProps) => { +}: IModalProps & { + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslation(); const backendOptions = buildOptions([ @@ -152,16 +160,23 @@ const MinerUModal = ({ /> )} + {onVerify && ( + Promise} + /> + )} - - {t('common.save', 'Save')} - +
+ + {t('common.save', 'Save')} + +
); }; -export default MinerUModal; +export default memo(MinerUModal); diff --git a/web/src/pages/user-setting/setting-model/modal/next-tencent-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/next-tencent-modal/index.tsx index 2bc80a1a0..a912998d0 100644 --- a/web/src/pages/user-setting/setting-model/modal/next-tencent-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/next-tencent-modal/index.tsx @@ -8,17 +8,24 @@ import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; +import { memo, useCallback } from 'react'; import { FieldValues } from 'react-hook-form'; import { LLMHeader } from '../../components/llm-header'; +import VerifyButton from '../../modal/verify-button'; const TencentCloudModal = ({ visible, hideModal, onOk, + onVerify, loading, llmFactory, }: IModalProps> & { llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; }) => { const { t } = useTranslate('setting'); const { t: tc } = useCommonTranslation(); @@ -108,6 +115,21 @@ const TencentCloudModal = ({ await onOk?.(data); }; + const verifyParamsFunc = useCallback(() => { + return { + llm_factory: llmFactory, + }; + }, [llmFactory]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); + return ( } @@ -127,6 +149,9 @@ const TencentCloudModal = ({ } labelClassName="font-normal" > + {onVerify && ( + + )}
> = { [LLMFactory.Ollama]: @@ -38,6 +41,7 @@ const OllamaModal = ({ visible, hideModal, onOk, + onVerify, loading, llmFactory, editMode = false, @@ -45,10 +49,14 @@ const OllamaModal = ({ }: IModalProps & { provider_order?: string }> & { llmFactory: string; editMode?: boolean; + onVerify?: ( + postBody: any, + ) => Promise; }) => { const { t } = useTranslate('setting'); const { t: tc } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); + const formRef = useRef(null); const optionsMap: Partial< Record @@ -233,6 +241,27 @@ const OllamaModal = ({ await onOk?.(data); }; + const verifyParamsFunc = useCallback(() => { + const values = formRef.current?.getValues(); + const modelType = + values.model_type === 'chat' && values.vision + ? 'image2text' + : values.model_type; + return { + llm_factory: llmFactory, + model_type: modelType, + }; + }, [llmFactory]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); + return ( } @@ -245,10 +274,14 @@ const OllamaModal = ({ {}} defaultValues={defaultValues} labelClassName="font-normal" > + {onVerify && ( + + )}
{t('ollamaLink', { name: llmFactory })} @@ -273,4 +306,4 @@ const OllamaModal = ({ ); }; -export default OllamaModal; +export default memo(OllamaModal); diff --git a/web/src/pages/user-setting/setting-model/modal/paddleocr-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/paddleocr-modal/index.tsx index 2aab764af..b127f8ef9 100644 --- a/web/src/pages/user-setting/setting-model/modal/paddleocr-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/paddleocr-modal/index.tsx @@ -2,6 +2,7 @@ import { RAGFlowFormItem } from '@/components/ragflow-form'; import { Dialog, DialogContent, + DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; @@ -9,12 +10,15 @@ import { Form } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select'; import { LLMFactory } from '@/constants/llm'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; import { zodResolver } from '@hookform/resolvers/zod'; import { t } from 'i18next'; +import { memo } from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; import { LLMHeader } from '../../components/llm-header'; +import VerifyButton from '../verify-button'; const FormSchema = z.object({ llm_name: z.string().min(1, { @@ -33,6 +37,9 @@ export interface IModalProps { visible: boolean; hideModal: () => void; onOk?: (data: T) => Promise; + onVerify?: ( + postBody: any, + ) => Promise; loading?: boolean; } @@ -44,6 +51,7 @@ const PaddleOCRModal = ({ visible, hideModal, onOk, + onVerify, loading, }: IModalProps) => { const { t } = useTranslation(); @@ -113,22 +121,29 @@ const PaddleOCRModal = ({ /> )} -
- - -
+ {onVerify && ( + Promise} + /> + )} + +
+ + +
+
@@ -136,4 +151,4 @@ const PaddleOCRModal = ({ ); }; -export default PaddleOCRModal; +export default memo(PaddleOCRModal); diff --git a/web/src/pages/user-setting/setting-model/modal/spark-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/spark-modal/index.tsx index 9f119d2de..a3bcd5856 100644 --- a/web/src/pages/user-setting/setting-model/modal/spark-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/spark-modal/index.tsx @@ -1,5 +1,6 @@ import { DynamicForm, + DynamicFormRef, FormFieldConfig, FormFieldType, } from '@/components/dynamic-form'; @@ -8,21 +9,30 @@ import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; import omit from 'lodash/omit'; +import { memo, useCallback, useRef } from 'react'; import { FieldValues } from 'react-hook-form'; import { LLMHeader } from '../../components/llm-header'; +import VerifyButton from '../../modal/verify-button'; const SparkModal = ({ visible, hideModal, onOk, + onVerify, loading, llmFactory, -}: IModalProps & { llmFactory: string }) => { +}: IModalProps & { + llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslate('setting'); const { t: tc } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); - + const formRef = useRef(null); const fields: FormFieldConfig[] = [ { name: 'model_type', @@ -128,6 +138,27 @@ const SparkModal = ({ await onOk?.(data as IAddLlmRequestBody); }; + const verifyParamsFunc = useCallback(() => { + const values = formRef.current?.getValues(); + const modelType = + values.model_type === 'chat' && values.vision + ? 'image2text' + : values.model_type; + return { + llm_factory: llmFactory, + model_type: modelType, + }; + }, [llmFactory]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); + return ( } @@ -141,6 +172,7 @@ const SparkModal = ({ onSubmit={(data) => { console.log(data); }} + ref={formRef} defaultValues={ { model_type: 'chat', @@ -149,6 +181,7 @@ const SparkModal = ({ } labelClassName="font-normal" > + {onVerify && }
{ @@ -168,4 +201,4 @@ const SparkModal = ({ ); }; -export default SparkModal; +export default memo(SparkModal); diff --git a/web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx b/web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx new file mode 100644 index 000000000..b64930cef --- /dev/null +++ b/web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx @@ -0,0 +1,123 @@ +import { Button } from '@/components/ui/button'; +import { useTranslate } from '@/hooks/common-hooks'; +import { cn } from '@/lib/utils'; +import { replaceText } from '@/pages/dataset/process-log-modal'; +import { ApiKeyPostBody } from '@/pages/user-setting/interface'; +import { RefreshCcw } from 'lucide-react'; +import { memo, useCallback, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { VerifyResult } from '../../hooks'; + +interface IVerifyButton { + onVerify: (params: any) => Promise; + isAbsolute?: boolean; + params?: any; +} + +const VerifyButton: React.FC = ({ + onVerify, + isAbsolute = true, + params, +}) => { + const { t } = useTranslate('setting'); + const [isVerifying, setIsVerifying] = useState(false); + const [verifyResult, setVerifyResult] = useState(null); + const form = useFormContext(); + + const onHandleVerify = useCallback(async () => { + const formValid = await form?.trigger(); + if (!formValid) { + return; + } + // setVerifyLoading(true); + try { + const values = form.getValues(); + const result = await onVerify({ + ...values, + verify: true, + ...params, + } as ApiKeyPostBody & { verify: boolean }); + setVerifyResult(result); + } catch (error: any) { + let logs = ''; + + if (error?.message) { + logs = error.message; + } else if (typeof error === 'string') { + logs = error; + } + + setVerifyResult({ + isValid: false, + logs: logs, + }); + } finally { + // setVerifyLoading(false); + } + }, [form, onVerify, params]); + const handleVerify = async () => { + setVerifyResult({ + isValid: null, + logs: '', + }); + setIsVerifying(true); + try { + await onHandleVerify(); + } catch (error) { + setVerifyResult({ + isValid: false, + logs: (error as Error).message || 'Unknown error', + }); + } finally { + setIsVerifying(false); + } + }; + + return ( +
+
+ + + {verifyResult && verifyResult.isValid !== null && ( +
+ + {verifyResult.isValid ? t('keyValid') : t('keyInvalid')} + +
+ )} +
+ {verifyResult && verifyResult.isValid !== null && ( +
+ {verifyResult.logs && ( +
+ {replaceText(verifyResult.logs)} +
+ )} +
+ )} +
+ ); +}; + +export default memo(VerifyButton); diff --git a/web/src/pages/user-setting/setting-model/modal/volcengine-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/volcengine-modal/index.tsx index cd8646d32..0332be298 100644 --- a/web/src/pages/user-setting/setting-model/modal/volcengine-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/modal/volcengine-modal/index.tsx @@ -1,5 +1,6 @@ import { DynamicForm, + DynamicFormRef, FormFieldConfig, FormFieldType, } from '@/components/dynamic-form'; @@ -8,8 +9,11 @@ import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks'; import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options'; import { IModalProps } from '@/interfaces/common'; import { IAddLlmRequestBody } from '@/interfaces/request/llm'; +import { VerifyResult } from '@/pages/user-setting/setting-model/hooks'; +import { memo, useCallback, useRef } from 'react'; import { FieldValues } from 'react-hook-form'; import { LLMHeader } from '../../components/llm-header'; +import VerifyButton from '../../modal/verify-button'; type VolcEngineLlmRequest = IAddLlmRequestBody & { endpoint_id: string; @@ -20,13 +24,19 @@ const VolcEngineModal = ({ visible, hideModal, onOk, + onVerify, loading, llmFactory, -}: IModalProps & { llmFactory: string }) => { +}: IModalProps & { + llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslate('setting'); const { t: tc } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); - + const formRef = useRef(null); const fields: FormFieldConfig[] = [ { name: 'model_type', @@ -91,6 +101,27 @@ const VolcEngineModal = ({ await onOk?.(data); }; + const verifyParamsFunc = useCallback(() => { + const values = formRef.current?.getValues(); + const modelType = + values.model_type === 'chat' && values.vision + ? 'image2text' + : values.model_type; + return { + llm_factory: llmFactory, + model_type: modelType, + }; + }, [llmFactory]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); + return ( } @@ -104,6 +135,7 @@ const VolcEngineModal = ({ onSubmit={(data) => { console.log(data); }} + ref={formRef} defaultValues={ { model_type: 'chat', @@ -112,6 +144,9 @@ const VolcEngineModal = ({ } labelClassName="font-normal" > + {onVerify && ( + + )}
& { llmFactory: string }) => { +}: IModalProps & { + llmFactory: string; + onVerify?: ( + postBody: any, + ) => Promise; +}) => { const { t } = useTranslate('setting'); const { t: tc } = useCommonTranslation(); const { buildModelTypeOptions } = useBuildModelTypeOptions(); + const formRef = useRef(null); - const fields: FormFieldConfig[] = [ - { - name: 'model_type', - label: t('modelType'), - type: FormFieldType.Select, - required: true, - options: buildModelTypeOptions(['chat', 'embedding', 'rerank']), - defaultValue: 'chat', - }, - { - name: 'llm_name', - label: t('modelName'), - type: FormFieldType.Text, - required: true, - placeholder: t('yiyanModelNameMessage'), - }, - { - name: 'yiyan_ak', - label: t('addyiyanAK'), - type: FormFieldType.Text, - required: true, - placeholder: t('yiyanAKMessage'), - }, - { - name: 'yiyan_sk', - label: t('addyiyanSK'), - type: FormFieldType.Text, - required: true, - placeholder: t('yiyanSKMessage'), - }, - { - name: 'max_tokens', - label: t('maxTokens'), - type: FormFieldType.Number, - required: true, - placeholder: t('maxTokensTip'), - validation: { - min: 0, + const fields = useMemo( + () => [ + { + name: 'model_type', + label: t('modelType'), + type: FormFieldType.Select, + required: true, + options: buildModelTypeOptions(['chat', 'embedding', 'rerank']), + defaultValue: 'chat', }, - }, - ]; + { + name: 'llm_name', + label: t('modelName'), + type: FormFieldType.Text, + required: true, + placeholder: t('yiyanModelNameMessage'), + }, + { + name: 'yiyan_ak', + label: t('addyiyanAK'), + type: FormFieldType.Text, + required: true, + placeholder: t('yiyanAKMessage'), + }, + { + name: 'yiyan_sk', + label: t('addyiyanSK'), + type: FormFieldType.Text, + required: true, + placeholder: t('yiyanSKMessage'), + }, + { + name: 'max_tokens', + label: t('maxTokens'), + type: FormFieldType.Number, + required: true, + placeholder: t('maxTokensTip'), + validation: { + min: 0, + }, + }, + ], + [t, buildModelTypeOptions], + ); const handleOk = async (values?: FieldValues) => { if (!values) return; @@ -88,16 +102,47 @@ const YiyanModal = ({ await onOk?.(data); }; + const verifyParamsFunc = useCallback(() => { + const values = formRef.current?.getValues(); + const modelType = + values.model_type === 'chat' && values.vision + ? 'image2text' + : values.model_type; + return { + llm_factory: llmFactory, + llm_name: values.llm_name as string, + model_type: modelType, + api_key: { + yiyan_ak: values.yiyan_ak, + yiyan_sk: values.yiyan_sk, + }, + max_tokens: values.max_tokens as number, + }; + }, [llmFactory]); + + const handleVerify = useCallback( + async (params: any) => { + const verifyParams = verifyParamsFunc(); + const res = await onVerify?.({ ...params, ...verifyParams }); + return (res || { isValid: null, logs: '' }) as VerifyResult; + }, + [verifyParamsFunc, onVerify], + ); + return ( } open={visible || false} onOpenChange={(open) => !open && hideModal?.()} maskClosable={false} - footer={
} + // footer={
} + footer={<>} + footerClassName="pb-10" > { console.log(data); }} @@ -109,23 +154,26 @@ const YiyanModal = ({ } labelClassName="font-normal" > -
- { - hideModal?.(); - }} - /> - { - handleOk(values); - }} - /> +
+ {onVerify && } +
+ { + hideModal?.(); + }} + /> + { + handleOk(values); + }} + /> +
); }; -export default YiyanModal; +export default memo(YiyanModal); diff --git a/web/tailwind.config.js b/web/tailwind.config.js index e66345c79..efef4fff2 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -211,11 +211,16 @@ module.exports = { '0%,70%,100%': { opacity: '1' }, '20%,50%': { opacity: '0' }, }, + 'spin-reverse': { + from: { transform: 'rotate(0deg)' }, + to: { transform: 'rotate(-360deg)' }, + }, }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out', 'caret-blink': 'caret-blink 1.25s ease-out infinite', + 'spin-reverse': 'spin-reverse 1s linear infinite', }, }, },