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
6c1fa275
Commit
6c1fa275
authored
Jul 14, 2023
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add plugin setting
parent
59ee4215
Changes
29
Hide whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
787 additions
and
6 deletions
+787
-6
link-external-02.svg
...ase/icons/assets/vender/line/general/link-external-02.svg
+5
-0
alert-circle.svg
...ns/assets/vender/solid/alertsAndFeedback/alert-circle.svg
+5
-0
check-circle.svg
...s/base/icons/assets/vender/solid/general/check-circle.svg
+5
-0
LinkExternal02.json
...ts/base/icons/src/vender/line/general/LinkExternal02.json
+38
-0
LinkExternal02.tsx
...nts/base/icons/src/vender/line/general/LinkExternal02.tsx
+14
-0
index.ts
...pp/components/base/icons/src/vender/line/general/index.ts
+1
-0
AlertCircle.json
...icons/src/vender/solid/alertsAndFeedback/AlertCircle.json
+38
-0
AlertCircle.tsx
.../icons/src/vender/solid/alertsAndFeedback/AlertCircle.tsx
+14
-0
index.ts
...ts/base/icons/src/vender/solid/alertsAndFeedback/index.ts
+1
-0
CheckCircle.json
...ents/base/icons/src/vender/solid/general/CheckCircle.json
+38
-0
CheckCircle.tsx
...nents/base/icons/src/vender/solid/general/CheckCircle.tsx
+14
-0
index.ts
...p/components/base/icons/src/vender/solid/general/index.ts
+1
-0
index.tsx
web/app/components/header/account-dropdown/index.tsx
+7
-2
index.tsx
web/app/components/header/account-setting/index.tsx
+13
-3
KeyInput.tsx
...ponents/header/account-setting/key-validator/KeyInput.tsx
+77
-0
Operate.tsx
...mponents/header/account-setting/key-validator/Operate.tsx
+85
-0
ValidateStatus.tsx
...s/header/account-setting/key-validator/ValidateStatus.tsx
+30
-0
declarations.ts
...ents/header/account-setting/key-validator/declarations.ts
+43
-0
hooks.ts
.../components/header/account-setting/key-validator/hooks.ts
+32
-0
index.tsx
...components/header/account-setting/key-validator/index.tsx
+118
-0
SerpapiPlugin.tsx
...ents/header/account-setting/plugin-page/SerpapiPlugin.tsx
+74
-0
index.tsx
...p/components/header/account-setting/plugin-page/index.tsx
+38
-0
utils.ts
...pp/components/header/account-setting/plugin-page/utils.ts
+34
-0
serpapi.png
web/app/components/header/assets/serpapi.png
+0
-0
key-validator.tsx
web/context/key-validator.tsx
+26
-0
common.en.ts
web/i18n/lang/common.en.ts
+8
-0
common.zh.ts
web/i18n/lang/common.zh.ts
+8
-0
common.ts
web/models/common.ts
+8
-0
common.ts
web/service/common.ts
+12
-1
No files found.
web/app/components/base/icons/assets/vender/line/general/link-external-02.svg
0 → 100644
View file @
6c1fa275
<svg
width=
"12"
height=
"12"
viewBox=
"0 0 12 12"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"link-external-02"
>
<path
id=
"Icon"
d=
"M10.5 4.5L10.5 1.5M10.5 1.5H7.49999M10.5 1.5L6 6M5 1.5H3.9C3.05992 1.5 2.63988 1.5 2.31901 1.66349C2.03677 1.8073 1.8073 2.03677 1.66349 2.31901C1.5 2.63988 1.5 3.05992 1.5 3.9V8.1C1.5 8.94008 1.5 9.36012 1.66349 9.68099C1.8073 9.96323 2.03677 10.1927 2.31901 10.3365C2.63988 10.5 3.05992 10.5 3.9 10.5H8.1C8.94008 10.5 9.36012 10.5 9.68099 10.3365C9.96323 10.1927 10.1927 9.96323 10.3365 9.68099C10.5 9.36012 10.5 8.94008 10.5 8.1V7"
stroke=
"#155EEF"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/alertsAndFeedback/alert-circle.svg
0 → 100644
View file @
6c1fa275
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"alert-circle"
>
<path
id=
"Solid"
fill-rule=
"evenodd"
clip-rule=
"evenodd"
d=
"M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM8.66667 5.33329C8.66667 4.9651 8.36819 4.66663 8 4.66663C7.63181 4.66663 7.33334 4.9651 7.33334 5.33329V7.99996C7.33334 8.36815 7.63181 8.66663 8 8.66663C8.36819 8.66663 8.66667 8.36815 8.66667 7.99996V5.33329ZM8 9.99996C7.63181 9.99996 7.33334 10.2984 7.33334 10.6666C7.33334 11.0348 7.63181 11.3333 8 11.3333H8.00667C8.37486 11.3333 8.67334 11.0348 8.67334 10.6666C8.67334 10.2984 8.37486 9.99996 8.00667 9.99996H8Z"
fill=
"#D92D20"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/general/check-circle.svg
0 → 100644
View file @
6c1fa275
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"check-circle"
>
<path
id=
"Solid"
fill-rule=
"evenodd"
clip-rule=
"evenodd"
d=
"M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM11.4714 6.47136C11.7318 6.21101 11.7318 5.7889 11.4714 5.52855C11.2111 5.26821 10.7889 5.26821 10.5286 5.52855L7 9.05715L5.47141 7.52855C5.21106 7.2682 4.78895 7.2682 4.5286 7.52855C4.26825 7.7889 4.26825 8.21101 4.5286 8.47136L6.5286 10.4714C6.78895 10.7317 7.21106 10.7317 7.47141 10.4714L11.4714 6.47136Z"
fill=
"#039855"
/>
</g>
</svg>
web/app/components/base/icons/src/vender/line/general/LinkExternal02.json
0 → 100644
View file @
6c1fa275
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"12"
,
"height"
:
"12"
,
"viewBox"
:
"0 0 12 12"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"link-external-02"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Icon"
,
"d"
:
"M10.5 4.5L10.5 1.5M10.5 1.5H7.49999M10.5 1.5L6 6M5 1.5H3.9C3.05992 1.5 2.63988 1.5 2.31901 1.66349C2.03677 1.8073 1.8073 2.03677 1.66349 2.31901C1.5 2.63988 1.5 3.05992 1.5 3.9V8.1C1.5 8.94008 1.5 9.36012 1.66349 9.68099C1.8073 9.96323 2.03677 10.1927 2.31901 10.3365C2.63988 10.5 3.05992 10.5 3.9 10.5H8.1C8.94008 10.5 9.36012 10.5 9.68099 10.3365C9.96323 10.1927 10.1927 9.96323 10.3365 9.68099C10.5 9.36012 10.5 8.94008 10.5 8.1V7"
,
"stroke"
:
"currentColor"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"LinkExternal02"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/general/LinkExternal02.tsx
0 → 100644
View file @
6c1fa275
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./LinkExternal02.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
export
default
Icon
web/app/components/base/icons/src/vender/line/general/index.ts
View file @
6c1fa275
export
{
default
as
LinkExternal02
}
from
'./LinkExternal02'
export
{
default
as
Loading02
}
from
'./Loading02'
export
{
default
as
Trash03
}
from
'./Trash03'
export
{
default
as
XClose
}
from
'./XClose'
...
...
web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.json
0 → 100644
View file @
6c1fa275
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"16"
,
"height"
:
"16"
,
"viewBox"
:
"0 0 16 16"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"alert-circle"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Solid"
,
"fill-rule"
:
"evenodd"
,
"clip-rule"
:
"evenodd"
,
"d"
:
"M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM8.66667 5.33329C8.66667 4.9651 8.36819 4.66663 8 4.66663C7.63181 4.66663 7.33334 4.9651 7.33334 5.33329V7.99996C7.33334 8.36815 7.63181 8.66663 8 8.66663C8.36819 8.66663 8.66667 8.36815 8.66667 7.99996V5.33329ZM8 9.99996C7.63181 9.99996 7.33334 10.2984 7.33334 10.6666C7.33334 11.0348 7.63181 11.3333 8 11.3333H8.00667C8.37486 11.3333 8.67334 11.0348 8.67334 10.6666C8.67334 10.2984 8.37486 9.99996 8.00667 9.99996H8Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"AlertCircle"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.tsx
0 → 100644
View file @
6c1fa275
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./AlertCircle.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
export
default
Icon
web/app/components/base/icons/src/vender/solid/alertsAndFeedback/index.ts
View file @
6c1fa275
export
{
default
as
AlertCircle
}
from
'./AlertCircle'
export
{
default
as
AlertTriangle
}
from
'./AlertTriangle'
web/app/components/base/icons/src/vender/solid/general/CheckCircle.json
0 → 100644
View file @
6c1fa275
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"16"
,
"height"
:
"16"
,
"viewBox"
:
"0 0 16 16"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"check-circle"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Solid"
,
"fill-rule"
:
"evenodd"
,
"clip-rule"
:
"evenodd"
,
"d"
:
"M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM11.4714 6.47136C11.7318 6.21101 11.7318 5.7889 11.4714 5.52855C11.2111 5.26821 10.7889 5.26821 10.5286 5.52855L7 9.05715L5.47141 7.52855C5.21106 7.2682 4.78895 7.2682 4.5286 7.52855C4.26825 7.7889 4.26825 8.21101 4.5286 8.47136L6.5286 10.4714C6.78895 10.7317 7.21106 10.7317 7.47141 10.4714L11.4714 6.47136Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"CheckCircle"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/general/CheckCircle.tsx
0 → 100644
View file @
6c1fa275
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./CheckCircle.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
export
default
Icon
web/app/components/base/icons/src/vender/solid/general/index.ts
View file @
6c1fa275
export
{
default
as
CheckCircle
}
from
'./CheckCircle'
export
{
default
as
Download02
}
from
'./Download02'
export
{
default
as
XCircle
}
from
'./XCircle'
web/app/components/header/account-dropdown/index.tsx
View file @
6c1fa275
...
...
@@ -12,6 +12,7 @@ import AccountSetting from '../account-setting'
import
AccountAbout
from
'../account-about'
import
WorkplaceSelector
from
'./workplace-selector'
import
type
{
LangGeniusVersionResponse
,
UserProfileResponse
}
from
'@/models/common'
import
{
KeyValidatorContextProvider
}
from
'@/context/key-validator'
import
I18n
from
'@/context/i18n'
import
Avatar
from
'@/app/components/base/avatar'
import
{
logout
}
from
'@/service/common'
...
...
@@ -27,7 +28,7 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS
rounded-lg font-normal hover:bg-gray-100 cursor-pointer
`
const
router
=
useRouter
()
const
[
settingVisible
,
setSettingVisible
]
=
useState
(
fals
e
)
const
[
settingVisible
,
setSettingVisible
]
=
useState
(
tru
e
)
const
[
aboutVisible
,
setAboutVisible
]
=
useState
(
false
)
const
{
locale
}
=
useContext
(
I18n
)
...
...
@@ -130,7 +131,11 @@ export default function AppSelector({ userProfile, langeniusVersionInfo }: IAppS
</
Transition
>
</
Menu
>
{
settingVisible
&&
<
AccountSetting
onCancel=
{
()
=>
setSettingVisible
(
false
)
}
/>
settingVisible
&&
(
<
KeyValidatorContextProvider
>
<
AccountSetting
onCancel=
{
()
=>
setSettingVisible
(
false
)
}
/>
</
KeyValidatorContextProvider
>
)
}
{
aboutVisible
&&
<
AccountAbout
onCancel=
{
()
=>
setAboutVisible
(
false
)
}
langeniusVersionInfo=
{
langeniusVersionInfo
}
/>
...
...
web/app/components/header/account-setting/index.tsx
View file @
6c1fa275
...
...
@@ -8,11 +8,12 @@ import MembersPage from './members-page'
import
IntegrationsPage
from
'./Integrations-page'
import
LanguagePage
from
'./language-page'
import
ProviderPage
from
'./provider-page'
import
PluginPage
from
'./plugin-page'
import
DataSourcePage
from
'./data-source-page'
import
s
from
'./index.module.css'
import
Modal
from
'@/app/components/base/modal'
import
{
Database03
}
from
'@/app/components/base/icons/src/vender/line/development'
import
{
Database03
as
Database03Solid
}
from
'@/app/components/base/icons/src/vender/solid/development'
import
{
Database03
,
PuzzlePiece01
}
from
'@/app/components/base/icons/src/vender/line/development'
import
{
Database03
as
Database03Solid
,
PuzzlePiece01
as
PuzzlePiece01Solid
}
from
'@/app/components/base/icons/src/vender/solid/development'
const
iconClassName
=
`
w-4 h-4 ml-3 mr-2
...
...
@@ -24,7 +25,7 @@ type IAccountSettingProps = {
}
export
default
function
AccountSetting
({
onCancel
,
activeTab
=
'
account
'
,
activeTab
=
'
plugin
'
,
}:
IAccountSettingProps
)
{
const
[
activeMenu
,
setActiveMenu
]
=
useState
(
activeTab
)
const
{
t
}
=
useTranslation
()
...
...
@@ -75,6 +76,12 @@ export default function AccountSetting({
icon
:
<
Database03
className=
{
iconClassName
}
/>,
activeIcon
:
<
Database03Solid
className=
{
iconClassName
}
/>,
},
{
key
:
'plugin'
,
name
:
t
(
'common.settings.plugin'
),
icon
:
<
PuzzlePiece01
className=
{
iconClassName
}
/>,
activeIcon
:
<
PuzzlePiece01Solid
className=
{
iconClassName
}
/>,
},
],
},
]
...
...
@@ -138,6 +145,9 @@ export default function AccountSetting({
{
activeMenu
===
'data-source'
&&
<
DataSourcePage
/>
}
{
activeMenu
===
'plugin'
&&
<
PluginPage
/>
}
</
div
>
</
div
>
</
Modal
>
...
...
web/app/components/header/account-setting/key-validator/KeyInput.tsx
0 → 100644
View file @
6c1fa275
import
type
{
ChangeEvent
}
from
'react'
import
{
ValidatedErrorIcon
,
ValidatedErrorMessage
,
ValidatedSuccessIcon
,
ValidatingTip
,
}
from
'./ValidateStatus'
import
{
ValidatedStatus
}
from
'./declarations'
import
type
{
ValidatedStatusState
}
from
'./declarations'
type
KeyInputProps
=
{
value
?:
string
name
:
string
placeholder
:
string
className
?:
string
onChange
:
(
v
:
string
)
=>
void
onFocus
?:
()
=>
void
validating
:
boolean
validatedStatusState
:
ValidatedStatusState
}
const
KeyInput
=
({
value
,
name
,
placeholder
,
className
,
onChange
,
onFocus
,
validating
,
validatedStatusState
,
}:
KeyInputProps
)
=>
{
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
inputValue
=
e
.
target
.
value
onChange
(
inputValue
)
}
const
getValidatedIcon
=
()
=>
{
if
(
validatedStatusState
.
status
===
ValidatedStatus
.
Error
||
validatedStatusState
.
status
===
ValidatedStatus
.
Exceed
)
return
<
ValidatedErrorIcon
/>
if
(
validatedStatusState
.
status
===
ValidatedStatus
.
Success
)
return
<
ValidatedSuccessIcon
/>
}
const
getValidatedTip
=
()
=>
{
if
(
validating
)
return
<
ValidatingTip
/>
if
(
validatedStatusState
.
status
===
ValidatedStatus
.
Error
)
return
<
ValidatedErrorMessage
errorMessage=
{
validatedStatusState
.
message
??
''
}
/>
}
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
}
/>
{
getValidatedIcon
()
}
</
div
>
{
getValidatedTip
()
}
</
div
>
)
}
export
default
KeyInput
web/app/components/header/account-setting/key-validator/Operate.tsx
0 → 100644
View file @
6c1fa275
import
{
useTranslation
}
from
'react-i18next'
import
Indicator
from
'../../indicator'
import
type
{
Status
}
from
'./declarations'
type
OperateProps
=
{
isOpen
:
boolean
status
:
Status
onCancel
:
()
=>
void
onSave
:
()
=>
void
onAdd
:
()
=>
void
onEdit
:
()
=>
void
}
const
Operate
=
({
isOpen
,
status
,
onCancel
,
onSave
,
onAdd
,
onEdit
,
}:
OperateProps
)
=>
{
const
{
t
}
=
useTranslation
()
if
(
isOpen
)
{
return
(
<
div
className=
'flex items-center'
>
<
div
className=
'
flex items-center
mr-[5px] px-3 h-7 rounded-md cursor-pointer
text-xs font-medium text-gray-700
'
onClick=
{
onCancel
}
>
{
t
(
'common.operation.cancel'
)
}
</
div
>
<
div
className=
'
flex items-center
px-3 h-7 rounded-md cursor-pointer bg-primary-700
text-xs font-medium text-white
'
onClick=
{
onSave
}
>
{
t
(
'common.operation.save'
)
}
</
div
>
</
div
>
)
}
if
(
status
===
'add'
)
{
return
(
<
div
className=
'
px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
text-xs font-medium text-gray-700 flex items-center
'
onClick=
{
onAdd
}
>
{
t
(
'common.provider.addKey'
)
}
</
div
>
)
}
if
(
status
===
'fail'
||
status
===
'success'
)
{
return
(
<
div
className=
'flex items-center'
>
{
status
===
'fail'
&&
(
<
div
className=
'flex items-center mr-4'
>
<
div
className=
'text-xs text-[#D92D20]'
>
{
t
(
'common.provider.invalidApiKey'
)
}
</
div
>
<
Indicator
color=
'red'
className=
'ml-2'
/>
</
div
>
)
}
{
status
===
'success'
&&
(
<
Indicator
color=
'green'
className=
'mr-4'
/>
)
}
<
div
className=
'
px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
text-xs font-medium text-gray-700 flex items-center
'
onClick=
{
onEdit
}
>
{
t
(
'common.provider.editKey'
)
}
</
div
>
</
div
>
)
}
return
null
}
export
default
Operate
web/app/components/header/account-setting/key-validator/ValidateStatus.tsx
0 → 100644
View file @
6c1fa275
import
{
useTranslation
}
from
'react-i18next'
import
{
AlertCircle
}
from
'@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import
{
CheckCircle
}
from
'@/app/components/base/icons/src/vender/solid/general'
export
const
ValidatedErrorIcon
=
()
=>
{
return
<
AlertCircle
className=
'w-4 h-4 text-[#D92D20]'
/>
}
export
const
ValidatedSuccessIcon
=
()
=>
{
return
<
CheckCircle
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
ValidatedErrorMessage
=
({
errorMessage
}:
{
errorMessage
:
string
})
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
{
'mt-2 text-[#D92D20] text-xs font-normal'
}
>
{
t
(
'common.provider.validatedError'
)
}{
errorMessage
}
</
div
>
)
}
web/app/components/header/account-setting/key-validator/declarations.ts
0 → 100644
View file @
6c1fa275
import
type
{
Dispatch
,
SetStateAction
}
from
'react'
export
enum
ValidatedStatus
{
Success
=
'success'
,
Error
=
'error'
,
Exceed
=
'exceed'
,
}
export
type
ValidatedStatusState
=
{
status
?:
ValidatedStatus
message
?:
string
}
export
type
Status
=
'add'
|
'fail'
|
'success'
export
type
ValidateValue
=
Record
<
string
,
string
|
undefined
>
export
type
ValidateCallback
=
{
before
:
(
v
?:
ValidateValue
)
=>
boolean
|
undefined
run
?:
(
v
?:
ValidateValue
)
=>
Promise
<
ValidatedStatusState
>
}
export
type
Form
=
{
key
:
string
title
:
string
placeholder
:
string
value
?:
string
validate
?:
ValidateCallback
handleFocus
?:
(
v
:
ValidateValue
,
dispatch
:
Dispatch
<
SetStateAction
<
ValidateValue
>>
)
=>
void
}
export
type
KeyFrom
=
{
text
:
string
link
:
string
}
export
type
KeyValidatorProps
=
{
type
:
string
title
:
React
.
ReactNode
status
:
Status
forms
:
Form
[]
keyFrom
:
KeyFrom
}
web/app/components/header/account-setting/key-validator/hooks.ts
0 → 100644
View file @
6c1fa275
import
{
useState
}
from
'react'
import
{
useDebounceFn
}
from
'ahooks'
import
type
{
DebouncedFunc
}
from
'lodash-es'
import
{
ValidatedStatus
}
from
'./declarations'
import
type
{
ValidateCallback
,
ValidateValue
,
ValidatedStatusState
}
from
'./declarations'
export
const
useValidate
:
(
value
:
ValidateValue
)
=>
[
DebouncedFunc
<
(
validateCallback
:
ValidateCallback
)
=>
Promise
<
void
>>
,
boolean
,
ValidatedStatusState
]
=
(
value
)
=>
{
const
[
validating
,
setValidating
]
=
useState
(
false
)
const
[
validatedStatus
,
setValidatedStatus
]
=
useState
<
ValidatedStatusState
>
({})
const
{
run
}
=
useDebounceFn
(
async
(
validateCallback
:
ValidateCallback
)
=>
{
if
(
!
validateCallback
.
before
(
value
))
{
setValidating
(
false
)
setValidatedStatus
({})
return
}
setValidating
(
true
)
if
(
validateCallback
.
run
)
{
const
res
=
await
validateCallback
?.
run
(
value
)
setValidatedStatus
(
res
.
status
===
'success'
?
{
status
:
ValidatedStatus
.
Success
}
:
{
status
:
ValidatedStatus
.
Error
,
message
:
res
.
message
})
setValidating
(
false
)
}
},
{
wait
:
500
})
return
[
run
,
validating
,
validatedStatus
]
}
web/app/components/header/account-setting/key-validator/index.tsx
0 → 100644
View file @
6c1fa275
import
{
useState
}
from
'react'
import
Operate
from
'./Operate'
import
KeyInput
from
'./KeyInput'
import
{
useValidate
}
from
'./hooks'
import
type
{
Form
,
KeyFrom
,
Status
,
ValidateValue
}
from
'./declarations'
import
{
useKeyValidatorContext
}
from
'@/context/key-validator'
import
{
LinkExternal02
}
from
'@/app/components/base/icons/src/vender/line/general'
export
type
KeyValidatorProps
=
{
type
:
string
title
:
React
.
ReactNode
status
:
Status
forms
:
Form
[]
keyFrom
:
KeyFrom
onSave
:
(
v
:
ValidateValue
)
=>
Promise
<
boolean
|
undefined
>
}
const
KeyValidator
=
({
type
,
title
,
status
,
forms
,
keyFrom
,
onSave
,
}:
KeyValidatorProps
)
=>
{
const
{
trigger
}
=
useKeyValidatorContext
()
const
[
isOpen
,
setIsOpen
]
=
useState
(
false
)
const
prevValue
=
forms
.
reduce
((
prev
:
ValidateValue
,
next
:
Form
)
=>
{
prev
[
next
.
key
]
=
next
.
value
return
prev
},
{})
const
[
value
,
setValue
]
=
useState
(
prevValue
)
const
[
validate
,
validating
,
validatedStatusState
]
=
useValidate
(
value
)
trigger
?.
useSubscription
((
v
)
=>
{
if
(
v
!==
type
)
{
setIsOpen
(
false
)
setValue
(
prevValue
)
validate
({
before
:
()
=>
false
})
}
})
const
handleCancel
=
()
=>
{
trigger
?.
emit
(
''
)
}
const
handleSave
=
async
()
=>
{
if
(
await
onSave
(
value
))
trigger
?.
emit
(
''
)
}
const
handleAdd
=
()
=>
{
setIsOpen
(
true
)
trigger
?.
emit
(
type
)
}
const
handleEdit
=
()
=>
{
setIsOpen
(
true
)
trigger
?.
emit
(
type
)
}
const
handleChange
=
(
form
:
Form
,
val
:
string
)
=>
{
setValue
({
...
value
,
[
form
.
key
]:
val
})
if
(
form
.
validate
)
validate
(
form
.
validate
)
}
const
handleFocus
=
(
form
:
Form
)
=>
{
if
(
form
.
handleFocus
)
form
.
handleFocus
(
value
,
setValue
)
}
return
(
<
div
className=
'mb-2 border-[0.5px] border-gray-200 bg-gray-50 rounded-md'
>
<
div
className=
{
`flex items-center justify-between px-4 h-[52px] cursor-pointer ${isOpen && 'border-b-[0.5px] border-b-gray-200'}`
}
>
{
title
}
<
Operate
isOpen=
{
isOpen
}
status=
{
status
}
onCancel=
{
handleCancel
}
onSave=
{
handleSave
}
onAdd=
{
handleAdd
}
onEdit=
{
handleEdit
}
/>
</
div
>
{
isOpen
&&
(
<
div
className=
'px-4 py-3'
>
{
forms
.
map
(
form
=>
(
<
KeyInput
key=
{
form
.
key
}
className=
'mb-4'
name=
{
form
.
title
}
placeholder=
{
form
.
placeholder
}
value=
{
value
[
form
.
key
]
||
''
}
onChange=
{
v
=>
handleChange
(
form
,
v
)
}
onFocus=
{
()
=>
handleFocus
(
form
)
}
validating=
{
validating
}
validatedStatusState=
{
validatedStatusState
}
/>
))
}
<
a
className=
"flex items-center text-xs cursor-pointer text-primary-600"
href=
{
keyFrom
.
link
}
target=
{
'_blank'
}
>
{
keyFrom
.
text
}
<
LinkExternal02
className=
'w-3 h-3 ml-1 text-primary-600'
/>
</
a
>
</
div
>
)
}
</
div
>
)
}
export
default
KeyValidator
web/app/components/header/account-setting/plugin-page/SerpapiPlugin.tsx
0 → 100644
View file @
6c1fa275
import
{
useTranslation
}
from
'react-i18next'
import
Image
from
'next/image'
import
SerpapiLogo
from
'../../assets/serpapi.png'
import
KeyValidator
from
'../key-validator'
import
type
{
Form
,
ValidateValue
}
from
'../key-validator/declarations'
import
{
updatePluginKey
,
validatePluginKey
}
from
'./utils'
import
type
{
PluginProvider
}
from
'@/models/common'
type
SerpapiPluginProps
=
{
plugin
:
PluginProvider
onUpdate
:
()
=>
void
}
const
SerpapiPlugin
=
({
plugin
,
onUpdate
,
}:
SerpapiPluginProps
)
=>
{
const
{
t
}
=
useTranslation
()
const
forms
:
Form
[]
=
[{
key
:
'api_key'
,
title
:
t
(
'common.plugin.serpapi.apiKey'
),
placeholder
:
t
(
'common.plugin.serpapi.apiKeyPlaceholder'
),
value
:
plugin
.
credentials
?.
api_key
,
validate
:
{
before
:
(
v
)
=>
{
if
(
v
?.
api_key
)
return
true
},
run
:
async
(
v
)
=>
{
return
validatePluginKey
(
'serpapi'
,
{
credentials
:
{
api_key
:
v
?.
api_key
,
},
})
},
},
handleFocus
:
(
v
,
dispatch
)
=>
{
if
(
v
.
api_key
===
plugin
.
credentials
?.
api_key
)
dispatch
({
...
v
,
api_key
:
''
})
},
}]
const
handleSave
=
async
(
v
:
ValidateValue
)
=>
{
if
(
!
v
?.
api_key
||
v
?.
api_key
===
plugin
.
credentials
?.
api_key
)
return
const
res
=
await
updatePluginKey
(
'serpapi'
,
{
credentials
:
{
api_key
:
v
?.
api_key
,
},
})
if
(
res
.
status
===
'success'
)
{
onUpdate
()
return
true
}
}
return
(
<
KeyValidator
type=
'serpapi'
title=
{
<
Image
alt=
'serpapi logo'
src=
{
SerpapiLogo
}
width=
{
64
}
/>
}
status=
{
plugin
.
credentials
?.
api_key
?
'success'
:
'add'
}
forms=
{
forms
}
keyFrom=
{
{
text
:
t
(
'common.plugin.serpapi.keyFrom'
),
link
:
'https://serpapi.com/manage-api-key'
,
}
}
onSave=
{
handleSave
}
/>
)
}
export
default
SerpapiPlugin
web/app/components/header/account-setting/plugin-page/index.tsx
0 → 100644
View file @
6c1fa275
import
useSWR
from
'swr'
import
{
LockClosedIcon
}
from
'@heroicons/react/24/solid'
import
{
useTranslation
}
from
'react-i18next'
import
Link
from
'next/link'
import
SerpapiPlugin
from
'./SerpapiPlugin'
import
{
fetchPluginProviders
}
from
'@/service/common'
import
type
{
PluginProvider
}
from
'@/models/common'
const
PluginPage
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
data
:
plugins
,
mutate
}
=
useSWR
(
'/workspaces/current/tool-providers'
,
fetchPluginProviders
)
const
Plugin_MAP
:
Record
<
string
,
any
>
=
{
serpapi
:
(
plugin
:
PluginProvider
)
=>
<
SerpapiPlugin
plugin=
{
plugin
}
onUpdate=
{
()
=>
mutate
()
}
/>,
}
return
(
<
div
className=
'pb-7'
>
<
div
>
{
plugins
?.
map
(
plugin
=>
Plugin_MAP
[
plugin
.
tool_name
](
plugin
))
}
</
div
>
<
div
className=
'absolute bottom-0 w-full h-[42px] flex items-center bg-white text-xs text-gray-500'
>
<
LockClosedIcon
className=
'w-3 h-3 mr-1'
/>
{
t
(
'common.provider.encrypted.front'
)
}
<
Link
className=
'text-primary-600 mx-1'
target=
{
'_blank'
}
href=
'https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
>
PKCS1_OAEP
</
Link
>
{
t
(
'common.provider.encrypted.back'
)
}
</
div
>
</
div
>
)
}
export
default
PluginPage
web/app/components/header/account-setting/plugin-page/utils.ts
0 → 100644
View file @
6c1fa275
import
{
ValidatedStatus
}
from
'../key-validator/declarations'
import
{
updatePluginProviderAIKey
,
validatePluginProviderKey
}
from
'@/service/common'
export
const
validatePluginKey
=
async
(
pluginType
:
string
,
body
:
any
)
=>
{
try
{
const
res
=
await
validatePluginProviderKey
({
url
:
`/workspaces/current/tool-providers/
${
pluginType
}
/credentials-validate`
,
body
,
})
if
(
res
.
result
===
'success'
)
return
Promise
.
resolve
({
status
:
ValidatedStatus
.
Success
})
else
return
Promise
.
resolve
({
status
:
ValidatedStatus
.
Error
,
message
:
res
.
error
})
}
catch
(
e
:
any
)
{
return
Promise
.
resolve
({
status
:
ValidatedStatus
.
Error
,
message
:
e
.
message
})
}
}
export
const
updatePluginKey
=
async
(
pluginType
:
string
,
body
:
any
)
=>
{
try
{
const
res
=
await
updatePluginProviderAIKey
({
url
:
`/workspaces/current/tool-providers/
${
pluginType
}
/credentials`
,
body
,
})
if
(
res
.
result
===
'success'
)
return
Promise
.
resolve
({
status
:
ValidatedStatus
.
Success
})
else
return
Promise
.
resolve
({
status
:
ValidatedStatus
.
Error
,
message
:
res
.
error
})
}
catch
(
e
:
any
)
{
return
Promise
.
resolve
({
status
:
ValidatedStatus
.
Error
,
message
:
e
.
message
})
}
}
web/app/components/header/assets/serpapi.png
0 → 100644
View file @
6c1fa275
2.97 KB
web/context/key-validator.tsx
0 → 100644
View file @
6c1fa275
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
{
useEventEmitter
}
from
'ahooks'
import
type
{
EventEmitter
}
from
'ahooks/lib/useEventEmitter'
const
KeyValidatorContext
=
createContext
<
{
trigger
:
EventEmitter
<
string
>
|
null
}
>
({
trigger
:
null
,
})
export
const
useKeyValidatorContext
=
()
=>
useContext
(
KeyValidatorContext
)
type
KeyValidatorContextProviderProps
=
{
children
:
React
.
ReactNode
}
export
const
KeyValidatorContextProvider
=
({
children
,
}:
KeyValidatorContextProviderProps
)
=>
{
const
trigger
=
useEventEmitter
<
string
>
()
return
(
<
KeyValidatorContext
.
Provider
value=
{
{
trigger
}
}
>
{
children
}
</
KeyValidatorContext
.
Provider
>
)
}
export
default
KeyValidatorContext
web/i18n/lang/common.en.ts
View file @
6c1fa275
...
...
@@ -90,6 +90,7 @@ const translation = {
language
:
'Language'
,
provider
:
'Model Provider'
,
dataSource
:
'Data Source'
,
plugin
:
'Plugins'
,
},
account
:
{
avatar
:
'Avatar'
,
...
...
@@ -198,6 +199,13 @@ const translation = {
},
},
},
plugin
:
{
serpapi
:
{
apiKey
:
'API Key'
,
apiKeyPlaceholder
:
'Enter your API key'
,
keyFrom
:
'Get your SerpAPI key from SerpAPI Account Page'
,
},
},
about
:
{
changeLog
:
'Changlog'
,
updateNow
:
'Update now'
,
...
...
web/i18n/lang/common.zh.ts
View file @
6c1fa275
...
...
@@ -90,6 +90,7 @@ const translation = {
language
:
'语言'
,
provider
:
'模型供应商'
,
dataSource
:
'数据来源'
,
plugin
:
'插件'
,
},
account
:
{
avatar
:
'头像'
,
...
...
@@ -199,6 +200,13 @@ const translation = {
},
},
},
plugin
:
{
serpapi
:
{
apiKey
:
'API Key'
,
apiKeyPlaceholder
:
'输入你的 API 密钥'
,
keyFrom
:
'从 SerpAPI 帐户页面获取您的 SerpAPI 密钥'
,
},
},
about
:
{
changeLog
:
'更新日志'
,
updateNow
:
'现在更新'
,
...
...
web/models/common.ts
View file @
6c1fa275
...
...
@@ -135,3 +135,11 @@ export type DataSourceNotion = {
export
type
GithubRepo
=
{
stargazers_count
:
number
}
export
type
PluginProvider
=
{
tool_name
:
string
is_enabled
:
boolean
credentials
:
{
api_key
:
string
}
|
null
}
web/service/common.ts
View file @
6c1fa275
...
...
@@ -3,7 +3,7 @@ import { del, get, patch, post, put } from './base'
import
type
{
AccountIntegrate
,
CommonResponse
,
DataSourceNotion
,
IWorkspace
,
LangGeniusVersionResponse
,
Member
,
OauthResponse
,
Provider
,
ProviderAzureToken
,
TenantInfoResponse
,
OauthResponse
,
P
luginProvider
,
P
rovider
,
ProviderAzureToken
,
TenantInfoResponse
,
UserProfileOriginResponse
,
}
from
'@/models/common'
import
type
{
...
...
@@ -101,3 +101,14 @@ export const syncDataSourceNotion: Fetcher<CommonResponse, { url: string }> = ({
export
const
updateDataSourceNotionAction
:
Fetcher
<
CommonResponse
,
{
url
:
string
}
>
=
({
url
})
=>
{
return
patch
(
url
)
as
Promise
<
CommonResponse
>
}
export
const
fetchPluginProviders
:
Fetcher
<
PluginProvider
[]
|
null
,
string
>
=
(
url
)
=>
{
return
get
(
url
)
as
Promise
<
PluginProvider
[]
|
null
>
}
export
const
validatePluginProviderKey
:
Fetcher
<
ValidateOpenAIKeyResponse
,
{
url
:
string
;
body
:
{
credentials
:
any
}
}
>
=
({
url
,
body
})
=>
{
return
post
(
url
,
{
body
})
as
Promise
<
ValidateOpenAIKeyResponse
>
}
export
const
updatePluginProviderAIKey
:
Fetcher
<
UpdateOpenAIKeyResponse
,
{
url
:
string
;
body
:
{
credentials
:
any
}
}
>
=
({
url
,
body
})
=>
{
return
post
(
url
,
{
body
})
as
Promise
<
UpdateOpenAIKeyResponse
>
}
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