+
{
providerHosted && !IS_CE_EDITION && (
<>
diff --git a/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx b/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx
index adff6bdf30..d33042fcfd 100644
--- a/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx
+++ b/web/app/components/header/account-setting/provider-page/openai-provider/index.tsx
@@ -1,222 +1,91 @@
-import { ChangeEvent, useEffect, useRef, useState } from 'react'
-import { useContext } from 'use-context-selector'
+import type { Provider } from '@/models/common'
+import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
-import { debounce } from 'lodash-es'
+import ProviderInput from '../provider-input'
import Link from 'next/link'
-import useSWR from 'swr'
-import { ArrowTopRightOnSquareIcon, PencilIcon } from '@heroicons/react/24/outline'
-import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/24/solid'
-import Button from '@/app/components/base/button'
-import s from './index.module.css'
-import classNames from 'classnames'
-import { fetchTenantInfo, validateProviderKey, updateProviderAIKey } from '@/service/common'
-import { ToastContext } from '@/app/components/base/toast'
-import Indicator from '../../../indicator'
-import I18n from '@/context/i18n'
+import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
+import useValidateToken, { ValidatedStatus, ValidatedStatusState } from '../provider-input/useValidateToken'
+import {
+ ValidatedErrorIcon,
+ ValidatedSuccessIcon,
+ ValidatingTip,
+ ValidatedExceedOnOpenaiTip,
+ ValidatedErrorOnOpenaiTip
+} from '../provider-input/Validate'
-type IStatusType = 'normal' | 'verified' | 'error' | 'error-api-key-exceed-bill'
-
-type TInputWithStatusProps = {
- value: string
- onChange: (v: string) => void
- onValidating: (validating: boolean) => void
- verifiedStatus: IStatusType
- onVerified: (verified: IStatusType) => void
-}
-const InputWithStatus = ({
- value,
- onChange,
- onValidating,
- verifiedStatus,
- onVerified
-}: TInputWithStatusProps) => {
- const { t } = useTranslation()
- const validateKey = useRef(debounce(async (token: string) => {
- if (!token) return
- onValidating(true)
- try {
- const res = await validateProviderKey({ url: '/workspaces/current/providers/openai/token-validate', body: { token } })
- onVerified(res.result === 'success' ? 'verified' : 'error')
- } catch (e: any) {
- if (e.status === 400) {
- e.json().then(({ code }: any) => {
- if (code === 'provider_request_failed') {
- onVerified('error-api-key-exceed-bill')
- }
- })
- } else {
- onVerified('error')
- }
- } finally {
- onValidating(false)
- }
- }, 500))
-
- const handleChange = (e: ChangeEvent
) => {
- const inputValue = e.target.value
- onChange(inputValue)
- if (!inputValue) {
- onVerified('normal')
- }
- validateKey.current(inputValue)
- }
- return (
-
-
- {
- verifiedStatus === 'error' &&
- }
- {
- verifiedStatus === 'verified' &&
- }
-
- )
+interface IOpenaiProviderProps {
+ provider: Provider
+ onValidatedStatus: (status?: ValidatedStatusState) => void
+ onTokenChange: (token: string) => void
}
-const OpenaiProvider = () => {
+const OpenaiProvider = ({
+ provider,
+ onValidatedStatus,
+ onTokenChange
+}: IOpenaiProviderProps) => {
const { t } = useTranslation()
- const { locale } = useContext(I18n)
- const { data: userInfo, mutate } = useSWR({ url: '/info' }, fetchTenantInfo)
- const [inputValue, setInputValue] = useState('')
- const [validating, setValidating] = useState(false)
- const [editStatus, setEditStatus] = useState('normal')
- const [loading, setLoading] = useState(false)
- const [editing, setEditing] = useState(false)
- const [invalidStatus, setInvalidStatus] = useState(false)
- const { notify } = useContext(ToastContext)
- const provider = userInfo?.providers?.find(({ provider }) => provider === 'openai')
-
- const handleReset = () => {
- setInputValue('')
- setValidating(false)
- setEditStatus('normal')
- setLoading(false)
- setEditing(false)
- }
- const handleSave = async () => {
- if (editStatus === 'verified') {
- try {
- setLoading(true)
- await updateProviderAIKey({ url: '/workspaces/current/providers/openai/token', body: { token: inputValue ?? '' } })
- notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
- } catch (e) {
- notify({ type: 'error', message: t('common.provider.saveFailed') })
- } finally {
- setLoading(false)
- handleReset()
- mutate()
- }
+ const [token, setToken] = useState(provider.token as string || '')
+ const [ validating, validatedStatus, setValidatedStatus, validate ] = useValidateToken(provider.provider_name)
+ const handleFocus = () => {
+ if (token === provider.token) {
+ setToken('')
+ onTokenChange('')
+ setValidatedStatus({})
}
}
+ const handleChange = (v: string) => {
+ setToken(v)
+ onTokenChange(v)
+ validate(v, {
+ beforeValidating: () => {
+ if (!v) {
+ setValidatedStatus({})
+ return false
+ }
+ return true
+ }
+ })
+ }
useEffect(() => {
- if (provider && !provider.token_is_valid && provider.token_is_set) {
- setInvalidStatus(true)
+ if (typeof onValidatedStatus === 'function') {
+ onValidatedStatus(validatedStatus)
}
- }, [userInfo])
+ }, [validatedStatus])
- const showInvalidStatus = invalidStatus && !editing
- const renderErrorMessage = () => {
+ const getValidatedIcon = () => {
+ if (validatedStatus?.status === ValidatedStatus.Error || validatedStatus.status === ValidatedStatus.Exceed) {
+ return
+ }
+ if (validatedStatus.status === ValidatedStatus.Success) {
+ return
+ }
+ }
+ const getValidatedTip = () => {
if (validating) {
- return (
-
- {t('common.provider.validating')}
-
- )
+ return
}
- if (editStatus === 'error-api-key-exceed-bill') {
- return (
-
- {t('common.provider.apiKeyExceedBill')}
-
- {locale === 'en' ? 'this link' : '这篇文档'}
-
-
- )
+ if (validatedStatus?.status === ValidatedStatus.Error) {
+ return
}
- if (showInvalidStatus || editStatus === 'error') {
- return (
-
- {t('common.provider.invalidKey')}
-
- )
- }
- return null
}
return (
-
-
- {t('common.provider.apiKey')}
-
- {
- provider && !editing && (
-
setEditing(true)}
- >
-
- {t('common.operation.edit')}
-
- )
- }
- {
- (inputValue || editing) && (
- <>
-
-
- >
- )
- }
-
- {
- (!provider || (provider && editing)) && (
-
setInputValue(v)}
- verifiedStatus={editStatus}
- onVerified={v => setEditStatus(v)}
- onValidating={v => setValidating(v)}
- />
- )
- }
- {
- (provider && !editing) && (
-
- sk-0C...skuA
-
-
- )
- }
- {renderErrorMessage()}
-
- {t('appOverview.welcome.getKeyTip')}
-
-
-
+
+
+ {t('appOverview.welcome.getKeyTip')}
+
+
+
)
}
diff --git a/web/app/components/header/account-setting/provider-page/openai-provider/provider.tsx b/web/app/components/header/account-setting/provider-page/openai-provider/provider.tsx
deleted file mode 100644
index 45747cb3a8..0000000000
--- a/web/app/components/header/account-setting/provider-page/openai-provider/provider.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Provider } from '@/models/common'
-import { useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { ProviderValidateTokenInput } from '../provider-input'
-import Link from 'next/link'
-import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'
-import { ValidatedStatus } from '../provider-input/useValidateToken'
-
-interface IOpenaiProviderProps {
- provider: Provider
- onValidatedStatus: (status?: ValidatedStatus) => void
- onTokenChange: (token: string) => void
-}
-
-const OpenaiProvider = ({
- provider,
- onValidatedStatus,
- onTokenChange
-}: IOpenaiProviderProps) => {
- const { t } = useTranslation()
- const [token, setToken] = useState(provider.token as string || '')
- const handleFocus = () => {
- if (token === provider.token) {
- setToken('')
- onTokenChange('')
- }
- }
- const handleChange = (v: string) => {
- setToken(v)
- onTokenChange(v)
- }
-
- return (
-
-
-
- {t('appOverview.welcome.getKeyTip')}
-
-
-
- )
-}
-
-export default OpenaiProvider
\ No newline at end of file
diff --git a/web/app/components/header/account-setting/provider-page/provider-input/Validate.tsx b/web/app/components/header/account-setting/provider-page/provider-input/Validate.tsx
new file mode 100644
index 0000000000..721e266a4c
--- /dev/null
+++ b/web/app/components/header/account-setting/provider-page/provider-input/Validate.tsx
@@ -0,0 +1,59 @@
+import Link from 'next/link'
+import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/24/solid'
+import { useTranslation } from 'react-i18next'
+import { useContext } from 'use-context-selector'
+import I18n from '@/context/i18n'
+
+export const ValidatedErrorIcon = () => {
+ return
+}
+
+export const ValidatedSuccessIcon = () => {
+ return
+}
+
+export const ValidatingTip = () => {
+ const { t } = useTranslation()
+ return (
+
+ {t('common.provider.validating')}
+
+ )
+}
+
+export const ValidatedExceedOnOpenaiTip = () => {
+ const { t } = useTranslation()
+ const { locale } = useContext(I18n)
+
+ return (
+
+ {t('common.provider.apiKeyExceedBill')}
+
+ {locale === 'en' ? 'this link' : '这篇文档'}
+
+
+ )
+}
+
+export const ValidatedErrorOnOpenaiTip = ({ errorMessage }: { errorMessage: string }) => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t('common.provider.validatedError')}{errorMessage}
+
+ )
+}
+
+export const ValidatedErrorOnAzureOpenaiTip = ({ errorMessage }: { errorMessage: string }) => {
+ const { t } = useTranslation()
+
+ return (
+
+ {t('common.provider.validatedError')}{errorMessage}
+
+ )
+}
\ No newline at end of file
diff --git a/web/app/components/header/account-setting/provider-page/provider-input/index.tsx b/web/app/components/header/account-setting/provider-page/provider-input/index.tsx
index 5a489d17f2..84ab9901c1 100644
--- a/web/app/components/header/account-setting/provider-page/provider-input/index.tsx
+++ b/web/app/components/header/account-setting/provider-page/provider-input/index.tsx
@@ -1,10 +1,5 @@
-import { ChangeEvent, useEffect } from 'react'
-import Link from 'next/link'
-import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/24/solid'
-import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
-import I18n from '@/context/i18n'
-import useValidateToken, { ValidatedStatus } from './useValidateToken'
+import { ChangeEvent } from 'react'
+import { ReactElement } from 'react-markdown/lib/react-markdown'
interface IProviderInputProps {
value?: string
@@ -13,6 +8,8 @@ interface IProviderInputProps {
className?: string
onChange: (v: string) => void
onFocus?: () => void
+ validatedIcon?: ReactElement
+ validatedTip?: ReactElement
}
const ProviderInput = ({
@@ -22,6 +19,8 @@ const ProviderInput = ({
className,
onChange,
onFocus,
+ validatedIcon,
+ validatedTip
}: IProviderInputProps) => {
const handleChange = (e: ChangeEvent
) => {
@@ -47,95 +46,9 @@ const ProviderInput = ({
onChange={handleChange}
onFocus={onFocus}
/>
+ {validatedIcon}
-