Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
1c5f63de
Unverified
Commit
1c5f63de
authored
May 23, 2023
by
zxhlyh
Committed by
GitHub
May 23, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: azure-openai key validate (#164)
parent
f3219ff1
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
253 additions
and
385 deletions
+253
-385
index.tsx
...er/account-setting/provider-page/azure-provider/index.tsx
+55
-14
index.tsx
...components/header/account-setting/provider-page/index.tsx
+1
-1
index.tsx
...r/account-setting/provider-page/openai-provider/index.tsx
+71
-199
provider.tsx
...ccount-setting/provider-page/openai-provider/provider.tsx
+0
-52
Validate.tsx
...account-setting/provider-page/provider-input/Validate.tsx
+59
-0
index.tsx
...er/account-setting/provider-page/provider-input/index.tsx
+8
-95
useValidateToken.ts
...-setting/provider-page/provider-input/useValidateToken.ts
+21
-8
index.tsx
...der/account-setting/provider-page/provider-item/index.tsx
+18
-7
common.ts
web/models/common.ts
+20
-9
No files found.
web/app/components/header/account-setting/provider-page/azure-provider/index.tsx
View file @
1c5f63de
import
type
{
Provider
,
ProviderAzureToken
}
from
'@/models/common'
import
type
{
Provider
,
ProviderAzureToken
}
from
'@/models/common'
import
{
ProviderName
}
from
'@/models/common'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
Link
from
'next/link'
import
Link
from
'next/link'
import
{
ArrowTopRightOnSquareIcon
}
from
'@heroicons/react/24/outline'
import
{
ArrowTopRightOnSquareIcon
}
from
'@heroicons/react/24/outline'
import
ProviderInput
,
{
ProviderValidateTokenInput
}
from
'../provider-input'
import
{
useState
,
useEffect
}
from
'react'
import
{
useState
}
from
'react'
import
ProviderInput
from
'../provider-input'
import
{
ValidatedStatus
}
from
'../provider-input/useValidateToken'
import
useValidateToken
,
{
ValidatedStatus
}
from
'../provider-input/useValidateToken'
import
{
ValidatedErrorIcon
,
ValidatedSuccessIcon
,
ValidatingTip
,
ValidatedErrorOnAzureOpenaiTip
}
from
'../provider-input/Validate'
interface
IAzureProviderProps
{
interface
IAzureProviderProps
{
provider
:
Provider
provider
:
Provider
...
@@ -17,19 +24,51 @@ const AzureProvider = ({
...
@@ -17,19 +24,51 @@ const AzureProvider = ({
onValidatedStatus
onValidatedStatus
}:
IAzureProviderProps
)
=>
{
}:
IAzureProviderProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
const
[
token
,
setToken
]
=
useState
(
provider
.
token
as
ProviderAzureToken
||
{})
const
[
token
,
setToken
]
=
useState
<
ProviderAzureToken
>
(
provider
.
provider_name
===
ProviderName
.
AZURE_OPENAI
?
{...
provider
.
token
}:
{})
const
handleFocus
=
()
=>
{
const
[
validating
,
validatedStatus
,
setValidatedStatus
,
validate
]
=
useValidateToken
(
provider
.
provider_name
)
if
(
token
===
provider
.
token
)
{
const
handleFocus
=
(
type
:
keyof
ProviderAzureToken
)
=>
{
token
.
openai_api_key
=
''
if
(
token
[
type
]
===
(
provider
?.
token
as
ProviderAzureToken
)[
type
])
{
token
[
type
]
=
''
setToken
({...
token
})
setToken
({...
token
})
onTokenChange
({...
token
})
onTokenChange
({...
token
})
setValidatedStatus
(
undefined
)
}
}
}
}
const
handleChange
=
(
type
:
keyof
ProviderAzureToken
,
v
:
string
)
=>
{
const
handleChange
=
(
type
:
keyof
ProviderAzureToken
,
v
:
string
,
validate
:
any
)
=>
{
token
[
type
]
=
v
token
[
type
]
=
v
setToken
({...
token
})
setToken
({...
token
})
onTokenChange
({...
token
})
onTokenChange
({...
token
})
validate
({...
token
},
{
beforeValidating
:
()
=>
{
if
(
!
token
.
openai_api_base
||
!
token
.
openai_api_key
)
{
setValidatedStatus
(
undefined
)
return
false
}
return
true
}
})
}
}
const
getValidatedIcon
=
()
=>
{
if
(
validatedStatus
===
ValidatedStatus
.
Error
||
validatedStatus
===
ValidatedStatus
.
Exceed
)
{
return
<
ValidatedErrorIcon
/>
}
if
(
validatedStatus
===
ValidatedStatus
.
Success
)
{
return
<
ValidatedSuccessIcon
/>
}
}
const
getValidatedTip
=
()
=>
{
if
(
validating
)
{
return
<
ValidatingTip
/>
}
if
(
validatedStatus
===
ValidatedStatus
.
Error
)
{
return
<
ValidatedErrorOnAzureOpenaiTip
/>
}
}
useEffect
(()
=>
{
if
(
typeof
onValidatedStatus
===
'function'
)
{
onValidatedStatus
(
validatedStatus
)
}
},
[
validatedStatus
])
return
(
return
(
<
div
className=
'px-4 py-3'
>
<
div
className=
'px-4 py-3'
>
...
@@ -38,17 +77,19 @@ const AzureProvider = ({
...
@@ -38,17 +77,19 @@ const AzureProvider = ({
name=
{
t
(
'common.provider.azure.apiBase'
)
}
name=
{
t
(
'common.provider.azure.apiBase'
)
}
placeholder=
{
t
(
'common.provider.azure.apiBasePlaceholder'
)
}
placeholder=
{
t
(
'common.provider.azure.apiBasePlaceholder'
)
}
value=
{
token
.
openai_api_base
}
value=
{
token
.
openai_api_base
}
onChange=
{
(
v
)
=>
handleChange
(
'openai_api_base'
,
v
)
}
onChange=
{
(
v
)
=>
handleChange
(
'openai_api_base'
,
v
,
validate
)
}
onFocus=
{
()
=>
handleFocus
(
'openai_api_base'
)
}
validatedIcon=
{
getValidatedIcon
()
}
/>
/>
<
Provider
ValidateToken
Input
<
ProviderInput
className=
'mb-4'
className=
'mb-4'
name=
{
t
(
'common.provider.azure.apiKey'
)
}
name=
{
t
(
'common.provider.azure.apiKey'
)
}
placeholder=
{
t
(
'common.provider.azure.apiKeyPlaceholder'
)
}
placeholder=
{
t
(
'common.provider.azure.apiKeyPlaceholder'
)
}
value=
{
token
.
openai_api_key
}
value=
{
token
.
openai_api_key
}
onChange=
{
v
=>
handleChange
(
'openai_api_key'
,
v
)
}
onChange=
{
(
v
)
=>
handleChange
(
'openai_api_key'
,
v
,
validate
)
}
onFocus=
{
handleFocus
}
onFocus=
{
()
=>
handleFocus
(
'openai_api_key'
)
}
onValidatedStatus=
{
onValidatedStatus
}
validatedIcon=
{
getValidatedIcon
()
}
providerName=
{
provider
.
provider_name
}
validatedTip=
{
getValidatedTip
()
}
/>
/>
<
Link
className=
"flex items-center text-xs cursor-pointer text-primary-600"
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
<
Link
className=
"flex items-center text-xs cursor-pointer text-primary-600"
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
{
t
(
'common.provider.azure.helpTip'
)
}
{
t
(
'common.provider.azure.helpTip'
)
}
...
...
web/app/components/header/account-setting/provider-page/index.tsx
View file @
1c5f63de
...
@@ -67,7 +67,7 @@ const ProviderPage = () => {
...
@@ -67,7 +67,7 @@ const ProviderPage = () => {
const
providerHosted
=
data
?.
filter
(
provider
=>
provider
.
provider_name
===
'openai'
&&
provider
.
provider_type
===
'system'
)?.[
0
]
const
providerHosted
=
data
?.
filter
(
provider
=>
provider
.
provider_name
===
'openai'
&&
provider
.
provider_type
===
'system'
)?.[
0
]
return
(
return
(
<
div
>
<
div
className=
'pb-7'
>
{
{
providerHosted
&&
!
IS_CE_EDITION
&&
(
providerHosted
&&
!
IS_CE_EDITION
&&
(
<>
<>
...
...
web/app/components/header/account-setting/provider-page/openai-provider/index.tsx
View file @
1c5f63de
import
{
ChangeEvent
,
useEffect
,
useRef
,
useState
}
from
'react
'
import
type
{
Provider
}
from
'@/models/common
'
import
{
use
Context
}
from
'use-context-selector
'
import
{
use
State
,
useEffect
}
from
'react
'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
debounce
}
from
'lodash-es
'
import
ProviderInput
from
'../provider-input
'
import
Link
from
'next/link'
import
Link
from
'next/link'
import
useSWR
from
'swr'
import
{
ArrowTopRightOnSquareIcon
}
from
'@heroicons/react/24/outline'
import
{
ArrowTopRightOnSquareIcon
,
PencilIcon
}
from
'@heroicons/react/24/outline'
import
useValidateToken
,
{
ValidatedStatus
}
from
'../provider-input/useValidateToken'
import
{
CheckCircleIcon
,
ExclamationCircleIcon
}
from
'@heroicons/react/24/solid'
import
{
import
Button
from
'@/app/components/base/button'
ValidatedErrorIcon
,
import
s
from
'./index.module.css'
ValidatedSuccessIcon
,
import
classNames
from
'classnames'
ValidatingTip
,
import
{
fetchTenantInfo
,
validateProviderKey
,
updateProviderAIKey
}
from
'@/service/common'
ValidatedExceedOnOpenaiTip
,
import
{
ToastContext
}
from
'@/app/components/base/toast'
ValidatedErrorOnOpenaiTip
import
Indicator
from
'../../../indicator'
}
from
'../provider-input/Validate'
import
I18n
from
'@/context/i18n'
type
IStatusType
=
'normal'
|
'verified'
|
'error'
|
'error-api-key-exceed-bill'
interface
IOpenaiProviderProps
{
provider
:
Provider
type
TInputWithStatusProps
=
{
onValidatedStatus
:
(
status
?:
ValidatedStatus
)
=>
void
value
:
string
onTokenChange
:
(
token
:
string
)
=>
void
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
<
HTMLInputElement
>
)
=>
{
const
inputValue
=
e
.
target
.
value
onChange
(
inputValue
)
if
(
!
inputValue
)
{
onVerified
(
'normal'
)
}
validateKey
.
current
(
inputValue
)
}
return
(
<
div
className=
{
classNames
(
'flex items-center h-9 px-3 bg-white border border-gray-300 rounded-lg'
,
s
.
input
)
}
>
<
input
value=
{
value
}
placeholder=
{
t
(
'common.provider.enterYourKey'
)
||
''
}
className=
'w-full h-9 mr-2 appearance-none outline-none bg-transparent text-xs'
onChange=
{
handleChange
}
/>
{
verifiedStatus
===
'error'
&&
<
ExclamationCircleIcon
className=
'w-4 h-4 text-[#D92D20]'
/>
}
{
verifiedStatus
===
'verified'
&&
<
CheckCircleIcon
className=
'w-4 h-4 text-[#039855]'
/>
}
</
div
>
)
}
}
const
OpenaiProvider
=
()
=>
{
const
OpenaiProvider
=
({
provider
,
onValidatedStatus
,
onTokenChange
}:
IOpenaiProviderProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
const
{
locale
}
=
useContext
(
I18n
)
const
[
token
,
setToken
]
=
useState
(
provider
.
token
as
string
||
''
)
const
{
data
:
userInfo
,
mutate
}
=
useSWR
({
url
:
'/info'
},
fetchTenantInfo
)
const
[
validating
,
validatedStatus
,
setValidatedStatus
,
validate
]
=
useValidateToken
(
provider
.
provider_name
)
const
[
inputValue
,
setInputValue
]
=
useState
<
string
>
(
''
)
const
handleFocus
=
()
=>
{
const
[
validating
,
setValidating
]
=
useState
(
false
)
if
(
token
===
provider
.
token
)
{
const
[
editStatus
,
setEditStatus
]
=
useState
<
IStatusType
>
(
'normal'
)
setToken
(
''
)
const
[
loading
,
setLoading
]
=
useState
(
false
)
onTokenChange
(
''
)
const
[
editing
,
setEditing
]
=
useState
(
false
)
setValidatedStatus
(
undefined
)
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
()
=>
{
const
handleChange
=
(
v
:
string
)
=>
{
if
(
editStatus
===
'verified'
)
{
setToken
(
v
)
try
{
onTokenChange
(
v
)
setLoading
(
true
)
validate
(
v
,
{
await
updateProviderAIKey
({
url
:
'/workspaces/current/providers/openai/token'
,
body
:
{
token
:
inputValue
??
''
}
})
beforeValidating
:
()
=>
{
notify
({
type
:
'success'
,
message
:
t
(
'common.actionMsg.modifiedSuccessfully'
)
})
if
(
!
v
)
{
}
catch
(
e
)
{
setValidatedStatus
(
undefined
)
notify
({
type
:
'error'
,
message
:
t
(
'common.provider.saveFailed'
)
})
return
false
}
finally
{
}
setLoading
(
false
)
return
true
handleReset
()
mutate
()
}
}
}
}
)
}
}
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
provider
&&
!
provider
.
token_is_valid
&&
provider
.
token_is_set
)
{
if
(
typeof
onValidatedStatus
===
'function'
)
{
setInvalidStatus
(
true
)
onValidatedStatus
(
validatedStatus
)
}
}
},
[
userInfo
])
},
[
validatedStatus
])
const
showInvalidStatus
=
invalidStatus
&&
!
editing
const
getValidatedIcon
=
()
=>
{
const
renderErrorMessage
=
()
=>
{
if
(
validatedStatus
===
ValidatedStatus
.
Error
||
validatedStatus
===
ValidatedStatus
.
Exceed
)
{
return
<
ValidatedErrorIcon
/>
}
if
(
validatedStatus
===
ValidatedStatus
.
Success
)
{
return
<
ValidatedSuccessIcon
/>
}
}
const
getValidatedTip
=
()
=>
{
if
(
validating
)
{
if
(
validating
)
{
return
(
return
<
ValidatingTip
/>
<
div
className=
{
`mt-2 text-primary-600 text-xs font-normal`
}
>
{
t
(
'common.provider.validating'
)
}
</
div
>
)
}
}
if
(
editStatus
===
'error-api-key-exceed-bill'
)
{
if
(
validatedStatus
===
ValidatedStatus
.
Exceed
)
{
return
(
return
<
ValidatedExceedOnOpenaiTip
/>
<
div
className=
{
`mt-2 text-[#D92D20] text-xs font-normal`
}
>
{
t
(
'common.provider.apiKeyExceedBill'
)
}
<
Link
className=
'underline'
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
{
locale
===
'en'
?
'this link'
:
'这篇文档'
}
</
Link
>
</
div
>
)
}
}
if
(
showInvalidStatus
||
editStatus
===
'error'
)
{
if
(
validatedStatus
===
ValidatedStatus
.
Error
)
{
return
(
return
<
ValidatedErrorOnOpenaiTip
/>
<
div
className=
{
`mt-2 text-[#D92D20] text-xs font-normal`
}
>
{
t
(
'common.provider.invalidKey'
)
}
</
div
>
)
}
}
return
null
}
}
return
(
return
(
<
div
className=
'px-4 pt-3 pb-4'
>
<
div
className=
'px-4 pt-3 pb-4'
>
<
div
className=
'flex items-center mb-2 h-6'
>
<
ProviderInput
<
div
className=
'grow text-[13px] text-gray-800 font-medium'
>
value=
{
token
}
{
t
(
'common.provider.apiKey'
)
}
name=
{
t
(
'common.provider.apiKey'
)
}
</
div
>
placeholder=
{
t
(
'common.provider.enterYourKey'
)
}
{
onChange=
{
handleChange
}
provider
&&
!
editing
&&
(
onFocus=
{
handleFocus
}
<
div
validatedIcon=
{
getValidatedIcon
()
}
className=
'
validatedTip=
{
getValidatedTip
()
}
flex items-center h-6 px-2 rounded-md border border-gray-200
/>
text-xs font-medium text-gray-700 cursor-pointer
<
Link
className=
"inline-flex items-center mt-3 text-xs font-normal cursor-pointer text-primary-600 w-fit"
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
'
{
t
(
'appOverview.welcome.getKeyTip'
)
}
onClick=
{
()
=>
setEditing
(
true
)
}
<
ArrowTopRightOnSquareIcon
className=
'w-3 h-3 ml-1 text-primary-600'
aria
-
hidden=
"true"
/>
>
</
Link
>
<
PencilIcon
className=
'mr-1 w-3 h-3 text-gray-500'
/>
</
div
>
{
t
(
'common.operation.edit'
)
}
</
div
>
)
}
{
(
inputValue
||
editing
)
&&
(
<>
<
Button
className=
{
classNames
(
'mr-1'
,
s
.
button
)
}
loading=
{
loading
}
onClick=
{
handleReset
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
<
Button
type=
'primary'
className=
{
classNames
(
s
.
button
)
}
loading=
{
loading
}
onClick=
{
handleSave
}
>
{
t
(
'common.operation.save'
)
}
</
Button
>
</>
)
}
</
div
>
{
(
!
provider
||
(
provider
&&
editing
))
&&
(
<
InputWithStatus
value=
{
inputValue
}
onChange=
{
v
=>
setInputValue
(
v
)
}
verifiedStatus=
{
editStatus
}
onVerified=
{
v
=>
setEditStatus
(
v
)
}
onValidating=
{
v
=>
setValidating
(
v
)
}
/>
)
}
{
(
provider
&&
!
editing
)
&&
(
<
div
className=
{
classNames
(
'flex justify-between items-center bg-white px-3 h-9 rounded-lg text-gray-800 text-xs font-medium'
,
s
.
input
)
}
>
sk-0C...skuA
<
Indicator
color=
{
(
provider
.
token_is_set
&&
provider
.
token_is_valid
)
?
'green'
:
'orange'
}
/>
</
div
>
)
}
{
renderErrorMessage
()
}
<
Link
className=
"inline-flex items-center mt-3 text-xs font-normal cursor-pointer text-primary-600 w-fit"
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
{
t
(
'appOverview.welcome.getKeyTip'
)
}
<
ArrowTopRightOnSquareIcon
className=
'w-3 h-3 ml-1 text-primary-600'
aria
-
hidden=
"true"
/>
</
Link
>
</
div
>
)
)
}
}
...
...
web/app/components/header/account-setting/provider-page/openai-provider/provider.tsx
deleted
100644 → 0
View file @
f3219ff1
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
(
<
div
className=
'px-4 pt-3 pb-4'
>
<
ProviderValidateTokenInput
value=
{
token
}
name=
{
t
(
'common.provider.apiKey'
)
}
placeholder=
{
t
(
'common.provider.enterYourKey'
)
}
onChange=
{
handleChange
}
onFocus=
{
handleFocus
}
onValidatedStatus=
{
onValidatedStatus
}
providerName=
{
provider
.
provider_name
}
/>
<
Link
className=
"inline-flex items-center mt-3 text-xs font-normal cursor-pointer text-primary-600 w-fit"
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
{
t
(
'appOverview.welcome.getKeyTip'
)
}
<
ArrowTopRightOnSquareIcon
className=
'w-3 h-3 ml-1 text-primary-600'
aria
-
hidden=
"true"
/>
</
Link
>
</
div
>
)
}
export
default
OpenaiProvider
\ No newline at end of file
web/app/components/header/account-setting/provider-page/provider-input/Validate.tsx
0 → 100644
View file @
1c5f63de
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
<
ExclamationCircleIcon
className=
'w-4 h-4 text-[#D92D20]'
/>
}
export
const
ValidatedSuccessIcon
=
()
=>
{
return
<
CheckCircleIcon
className=
'w-4 h-4 text-[#039855]'
/>
}
export
const
ValidatingTip
=
()
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
{
`mt-2 text-primary-600 text-xs font-normal`
}
>
{
t
(
'common.provider.validating'
)
}
</
div
>
)
}
export
const
ValidatedExceedOnOpenaiTip
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
locale
}
=
useContext
(
I18n
)
return
(
<
div
className=
{
`mt-2 text-[#D92D20] text-xs font-normal`
}
>
{
t
(
'common.provider.apiKeyExceedBill'
)
}
<
Link
className=
'underline'
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
{
locale
===
'en'
?
'this link'
:
'这篇文档'
}
</
Link
>
</
div
>
)
}
export
const
ValidatedErrorOnOpenaiTip
=
()
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
{
`mt-2 text-[#D92D20] text-xs font-normal`
}
>
{
t
(
'common.provider.invalidKey'
)
}
</
div
>
)
}
export
const
ValidatedErrorOnAzureOpenaiTip
=
()
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
{
`mt-2 text-[#D92D20] text-xs font-normal`
}
>
{
t
(
'common.provider.invalidApiKey'
)
}
</
div
>
)
}
\ No newline at end of file
web/app/components/header/account-setting/provider-page/provider-input/index.tsx
View file @
1c5f63de
import
{
ChangeEvent
,
useEffect
}
from
'react'
import
{
ChangeEvent
}
from
'react'
import
Link
from
'next/link'
import
{
ReactElement
}
from
'react-markdown/lib/react-markdown'
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'
interface
IProviderInputProps
{
interface
IProviderInputProps
{
value
?:
string
value
?:
string
...
@@ -13,6 +8,8 @@ interface IProviderInputProps {
...
@@ -13,6 +8,8 @@ interface IProviderInputProps {
className
?:
string
className
?:
string
onChange
:
(
v
:
string
)
=>
void
onChange
:
(
v
:
string
)
=>
void
onFocus
?:
()
=>
void
onFocus
?:
()
=>
void
validatedIcon
?:
ReactElement
validatedTip
?:
ReactElement
}
}
const
ProviderInput
=
({
const
ProviderInput
=
({
...
@@ -22,6 +19,8 @@ const ProviderInput = ({
...
@@ -22,6 +19,8 @@ const ProviderInput = ({
className
,
className
,
onChange
,
onChange
,
onFocus
,
onFocus
,
validatedIcon
,
validatedTip
}:
IProviderInputProps
)
=>
{
}:
IProviderInputProps
)
=>
{
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
)
=>
{
...
@@ -47,95 +46,9 @@ const ProviderInput = ({
...
@@ -47,95 +46,9 @@ const ProviderInput = ({
onChange=
{
handleChange
}
onChange=
{
handleChange
}
onFocus=
{
onFocus
}
onFocus=
{
onFocus
}
/>
/>
{
validatedIcon
}
</
div
>
</
div
>
</
div
>
{
validatedTip
}
)
}
type
TproviderInputProps
=
IProviderInputProps
&
{
onValidatedStatus
?:
(
status
?:
ValidatedStatus
)
=>
void
providerName
:
string
}
export
const
ProviderValidateTokenInput
=
({
value
,
name
,
placeholder
,
className
,
onChange
,
onFocus
,
onValidatedStatus
,
providerName
}:
TproviderInputProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
{
locale
}
=
useContext
(
I18n
)
const
[
validating
,
validatedStatus
,
validate
]
=
useValidateToken
(
providerName
)
useEffect
(()
=>
{
if
(
typeof
onValidatedStatus
===
'function'
)
{
onValidatedStatus
(
validatedStatus
)
}
},
[
validatedStatus
])
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
inputValue
=
e
.
target
.
value
onChange
(
inputValue
)
validate
(
inputValue
)
}
return
(
<
div
className=
{
className
}
>
<
div
className=
"mb-2 text-[13px] font-medium text-gray-800"
>
{
name
}
</
div
>
<
div
className=
'
flex items-center px-3 bg-white rounded-lg
shadow-[0_1px_2px_rgba(16,24,40,0.05)]
'
>
<
input
className=
'
w-full py-[9px]
text-xs font-medium text-gray-700 leading-[18px]
appearance-none outline-none bg-transparent
'
value=
{
value
}
placeholder=
{
placeholder
}
onChange=
{
handleChange
}
onFocus=
{
onFocus
}
/>
{
validatedStatus
===
ValidatedStatus
.
Error
&&
<
ExclamationCircleIcon
className=
'w-4 h-4 text-[#D92D20]'
/>
}
{
validatedStatus
===
ValidatedStatus
.
Success
&&
<
CheckCircleIcon
className=
'w-4 h-4 text-[#039855]'
/>
}
</
div
>
{
validating
&&
(
<
div
className=
{
`mt-2 text-primary-600 text-xs font-normal`
}
>
{
t
(
'common.provider.validating'
)
}
</
div
>
)
}
{
validatedStatus
===
ValidatedStatus
.
Exceed
&&
!
validating
&&
(
<
div
className=
{
`mt-2 text-[#D92D20] text-xs font-normal`
}
>
{
t
(
'common.provider.apiKeyExceedBill'
)
}
<
Link
className=
'underline'
href=
"https://platform.openai.com/account/api-keys"
target=
{
'_blank'
}
>
{
locale
===
'en'
?
'this link'
:
'这篇文档'
}
</
Link
>
</
div
>
)
}
{
validatedStatus
===
ValidatedStatus
.
Error
&&
!
validating
&&
(
<
div
className=
{
`mt-2 text-[#D92D20] text-xs font-normal`
}
>
{
t
(
'common.provider.invalidKey'
)
}
</
div
>
)
}
</
div
>
</
div
>
)
)
}
}
...
...
web/app/components/header/account-setting/provider-page/provider-input/useValidateToken.ts
View file @
1c5f63de
import
{
useState
,
useCallback
}
from
'react'
import
{
useState
,
useCallback
,
SetStateAction
,
Dispatch
}
from
'react'
import
debounce
from
'lodash-es/debounce'
import
debounce
from
'lodash-es/debounce'
import
{
DebouncedFunc
}
from
'lodash-es'
import
{
DebouncedFunc
}
from
'lodash-es'
import
{
validateProviderKey
}
from
'@/service/common'
import
{
validateProviderKey
}
from
'@/service/common'
...
@@ -8,14 +8,24 @@ export enum ValidatedStatus {
...
@@ -8,14 +8,24 @@ export enum ValidatedStatus {
Error
=
'error'
,
Error
=
'error'
,
Exceed
=
'exceed'
Exceed
=
'exceed'
}
}
export
type
SetValidatedStatus
=
Dispatch
<
SetStateAction
<
ValidatedStatus
|
undefined
>>
export
type
ValidateFn
=
DebouncedFunc
<
(
token
:
any
,
config
:
ValidateFnConfig
)
=>
void
>
type
ValidateTokenReturn
=
[
boolean
,
ValidatedStatus
|
undefined
,
SetValidatedStatus
,
ValidateFn
]
export
type
ValidateFnConfig
=
{
beforeValidating
:
(
token
:
any
)
=>
boolean
}
const
useValidateToken
=
(
providerName
:
string
):
[
boolean
,
ValidatedStatus
|
undefined
,
DebouncedFunc
<
(
token
:
string
)
=>
Promise
<
void
>>
]
=>
{
const
useValidateToken
=
(
providerName
:
string
):
ValidateTokenReturn
=>
{
const
[
validating
,
setValidating
]
=
useState
(
false
)
const
[
validating
,
setValidating
]
=
useState
(
false
)
const
[
validatedStatus
,
setValidatedStatus
]
=
useState
<
ValidatedStatus
|
undefined
>
()
const
[
validatedStatus
,
setValidatedStatus
]
=
useState
<
ValidatedStatus
|
undefined
>
()
const
validate
=
useCallback
(
debounce
(
async
(
token
:
string
)
=>
{
const
validate
=
useCallback
(
debounce
(
async
(
token
:
string
,
config
:
ValidateFnConfig
)
=>
{
if
(
!
token
)
{
if
(
!
config
.
beforeValidating
(
token
))
{
setValidatedStatus
(
undefined
)
return
false
return
}
}
setValidating
(
true
)
setValidating
(
true
)
try
{
try
{
...
@@ -24,8 +34,10 @@ const useValidateToken = (providerName: string): [boolean, ValidatedStatus | und
...
@@ -24,8 +34,10 @@ const useValidateToken = (providerName: string): [boolean, ValidatedStatus | und
}
catch
(
e
:
any
)
{
}
catch
(
e
:
any
)
{
if
(
e
.
status
===
400
)
{
if
(
e
.
status
===
400
)
{
e
.
json
().
then
(({
code
}:
any
)
=>
{
e
.
json
().
then
(({
code
}:
any
)
=>
{
if
(
code
===
'provider_request_failed'
)
{
if
(
code
===
'provider_request_failed'
&&
providerName
===
'openai'
)
{
setValidatedStatus
(
ValidatedStatus
.
Exceed
)
setValidatedStatus
(
ValidatedStatus
.
Exceed
)
}
else
{
setValidatedStatus
(
ValidatedStatus
.
Error
)
}
}
})
})
}
else
{
}
else
{
...
@@ -39,7 +51,8 @@ const useValidateToken = (providerName: string): [boolean, ValidatedStatus | und
...
@@ -39,7 +51,8 @@ const useValidateToken = (providerName: string): [boolean, ValidatedStatus | und
return
[
return
[
validating
,
validating
,
validatedStatus
,
validatedStatus
,
validate
,
setValidatedStatus
,
validate
]
]
}
}
...
...
web/app/components/header/account-setting/provider-page/provider-item/index.tsx
View file @
1c5f63de
...
@@ -5,7 +5,8 @@ import { useContext } from 'use-context-selector'
...
@@ -5,7 +5,8 @@ import { useContext } from 'use-context-selector'
import
Indicator
from
'../../../indicator'
import
Indicator
from
'../../../indicator'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
Provider
,
ProviderAzureToken
}
from
'@/models/common'
import
type
{
Provider
,
ProviderAzureToken
}
from
'@/models/common'
import
OpenaiProvider
from
'../openai-provider/provider'
import
{
ProviderName
}
from
'@/models/common'
import
OpenaiProvider
from
'../openai-provider'
import
AzureProvider
from
'../azure-provider'
import
AzureProvider
from
'../azure-provider'
import
{
ValidatedStatus
}
from
'../provider-input/useValidateToken'
import
{
ValidatedStatus
}
from
'../provider-input/useValidateToken'
import
{
updateProviderAIKey
}
from
'@/service/common'
import
{
updateProviderAIKey
}
from
'@/service/common'
...
@@ -38,13 +39,23 @@ const ProviderItem = ({
...
@@ -38,13 +39,23 @@ const ProviderItem = ({
)
)
const
id
=
`
${
provider
.
provider_name
}
-
${
provider
.
provider_type
}
`
const
id
=
`
${
provider
.
provider_name
}
-
${
provider
.
provider_type
}
`
const
isOpen
=
id
===
activeId
const
isOpen
=
id
===
activeId
const
providerKey
=
provider
.
provider_name
===
'azure_openai'
?
(
provider
.
token
as
ProviderAzureToken
)?.
openai_api_key
:
provider
.
token
const
comingSoon
=
false
const
comingSoon
=
false
const
isValid
=
provider
.
is_valid
const
isValid
=
provider
.
is_valid
const
providerTokenHasSetted
=
()
=>
{
if
(
provider
.
provider_name
===
ProviderName
.
AZURE_OPENAI
)
{
return
provider
.
token
&&
provider
.
token
.
openai_api_base
&&
provider
.
token
.
openai_api_key
?
{
openai_api_base
:
provider
.
token
.
openai_api_base
,
openai_api_key
:
provider
.
token
.
openai_api_key
}:
undefined
}
if
(
provider
.
provider_name
===
ProviderName
.
OPENAI
)
{
return
provider
.
token
}
}
const
handleUpdateToken
=
async
()
=>
{
const
handleUpdateToken
=
async
()
=>
{
if
(
loading
)
return
if
(
loading
)
return
if
(
validatedStatus
===
ValidatedStatus
.
Success
||
!
token
)
{
if
(
validatedStatus
===
ValidatedStatus
.
Success
)
{
try
{
try
{
setLoading
(
true
)
setLoading
(
true
)
await
updateProviderAIKey
({
url
:
`/workspaces/current/providers/
${
provider
.
provider_name
}
/token`
,
body
:
{
token
}
})
await
updateProviderAIKey
({
url
:
`/workspaces/current/providers/
${
provider
.
provider_name
}
/token`
,
body
:
{
token
}
})
...
@@ -65,7 +76,7 @@ const ProviderItem = ({
...
@@ -65,7 +76,7 @@ const ProviderItem = ({
<
div
className=
{
cn
(
s
[
`icon-${icon}`
],
'mr-3 w-6 h-6 rounded-md'
)
}
/>
<
div
className=
{
cn
(
s
[
`icon-${icon}`
],
'mr-3 w-6 h-6 rounded-md'
)
}
/>
<
div
className=
'grow text-sm font-medium text-gray-800'
>
{
name
}
</
div
>
<
div
className=
'grow text-sm font-medium text-gray-800'
>
{
name
}
</
div
>
{
{
provider
Key
&&
!
comingSoon
&&
!
isOpen
&&
(
provider
TokenHasSetted
()
&&
!
comingSoon
&&
!
isOpen
&&
(
<
div
className=
'flex items-center mr-4'
>
<
div
className=
'flex items-center mr-4'
>
{
!
isValid
&&
<
div
className=
'text-xs text-[#D92D20]'
>
{
t
(
'common.provider.invalidApiKey'
)
}
</
div
>
}
{
!
isValid
&&
<
div
className=
'text-xs text-[#D92D20]'
>
{
t
(
'common.provider.invalidApiKey'
)
}
</
div
>
}
<
Indicator
color=
{
!
isValid
?
'red'
:
'green'
}
className=
'ml-2'
/>
<
Indicator
color=
{
!
isValid
?
'red'
:
'green'
}
className=
'ml-2'
/>
...
@@ -78,7 +89,7 @@ const ProviderItem = ({
...
@@ -78,7 +89,7 @@ const ProviderItem = ({
px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
text-xs font-medium text-gray-700 flex items-center
text-xs font-medium text-gray-700 flex items-center
'
onClick=
{
()
=>
onActive
(
id
)
}
>
'
onClick=
{
()
=>
onActive
(
id
)
}
>
{
provider
Key
?
t
(
'common.provider.editKey'
)
:
t
(
'common.provider.addKey'
)
}
{
provider
TokenHasSetted
()
?
t
(
'common.provider.editKey'
)
:
t
(
'common.provider.addKey'
)
}
</
div
>
</
div
>
)
)
}
}
...
@@ -114,7 +125,7 @@ const ProviderItem = ({
...
@@ -114,7 +125,7 @@ const ProviderItem = ({
}
}
</
div
>
</
div
>
{
{
provider
.
provider_name
===
'openai'
&&
isOpen
&&
(
provider
.
provider_name
===
ProviderName
.
OPENAI
&&
isOpen
&&
(
<
OpenaiProvider
<
OpenaiProvider
provider=
{
provider
}
provider=
{
provider
}
onValidatedStatus=
{
v
=>
setValidatedStatus
(
v
)
}
onValidatedStatus=
{
v
=>
setValidatedStatus
(
v
)
}
...
@@ -123,7 +134,7 @@ const ProviderItem = ({
...
@@ -123,7 +134,7 @@ const ProviderItem = ({
)
)
}
}
{
{
provider
.
provider_name
===
'azure_openai'
&&
isOpen
&&
(
provider
.
provider_name
===
ProviderName
.
AZURE_OPENAI
&&
isOpen
&&
(
<
AzureProvider
<
AzureProvider
provider=
{
provider
}
provider=
{
provider
}
onValidatedStatus=
{
v
=>
setValidatedStatus
(
v
)
}
onValidatedStatus=
{
v
=>
setValidatedStatus
(
v
)
}
...
...
web/models/common.ts
View file @
1c5f63de
...
@@ -54,18 +54,29 @@ export type Member = Pick<UserProfileResponse, 'id' | 'name' | 'email' | 'last_l
...
@@ -54,18 +54,29 @@ export type Member = Pick<UserProfileResponse, 'id' | 'name' | 'email' | 'last_l
role
:
'owner'
|
'admin'
|
'normal'
role
:
'owner'
|
'admin'
|
'normal'
}
}
export
enum
ProviderName
{
OPENAI
=
'openai'
,
AZURE_OPENAI
=
'azure_openai'
}
export
type
ProviderAzureToken
=
{
export
type
ProviderAzureToken
=
{
openai_api_base
:
string
openai_api_base
?
:
string
openai_api_key
:
string
openai_api_key
?
:
string
}
}
export
type
Provider
=
{
export
type
ProviderTokenType
=
{
provider_name
:
string
[
ProviderName
.
OPENAI
]:
string
provider_type
:
string
[
ProviderName
.
AZURE_OPENAI
]:
ProviderAzureToken
is_valid
:
boolean
is_enabled
:
boolean
last_used
:
string
token
?:
string
|
ProviderAzureToken
}
}
export
type
Provider
=
{
[
Name
in
ProviderName
]:
{
provider_name
:
Name
}
&
{
provider_type
:
'custom'
|
'system'
is_valid
:
boolean
is_enabled
:
boolean
last_used
:
string
token
?:
ProviderTokenType
[
Name
]
}
}[
ProviderName
]
export
type
ProviderHosted
=
Provider
&
{
export
type
ProviderHosted
=
Provider
&
{
quota_type
:
string
quota_type
:
string
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment