mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-09 02:46:52 +08:00
Feat/environment variables in workflow (#6515)
Co-authored-by: JzoNg <jzongcode@gmail.com>
This commit is contained in:
@@ -28,6 +28,9 @@ import type { CreateAppModalProps } from '@/app/components/explore/create-app-mo
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { getRedirection } from '@/utils/app-redirection'
|
||||
import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
|
||||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
|
||||
export type IAppInfoProps = {
|
||||
expand: boolean
|
||||
@@ -47,6 +50,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
const [showSwitchTip, setShowSwitchTip] = useState<string>('')
|
||||
const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false)
|
||||
const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false)
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
|
||||
const mutateApps = useContextSelector(
|
||||
AppsContext,
|
||||
@@ -108,11 +112,14 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
const onExport = async () => {
|
||||
const onExport = async (include = false) => {
|
||||
if (!appDetail)
|
||||
return
|
||||
try {
|
||||
const { data } = await exportAppConfig(appDetail.id)
|
||||
const { data } = await exportAppConfig({
|
||||
appID: appDetail.id,
|
||||
include,
|
||||
})
|
||||
const a = document.createElement('a')
|
||||
const file = new Blob([data], { type: 'application/yaml' })
|
||||
a.href = URL.createObjectURL(file)
|
||||
@@ -124,6 +131,27 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
const exportCheck = async () => {
|
||||
if (!appDetail)
|
||||
return
|
||||
if (appDetail.mode !== 'workflow' && appDetail.mode !== 'advanced-chat') {
|
||||
onExport()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`)
|
||||
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
|
||||
if (list.length === 0) {
|
||||
onExport()
|
||||
return
|
||||
}
|
||||
setSecretEnvList(list)
|
||||
}
|
||||
catch (e) {
|
||||
notify({ type: 'error', message: t('app.exportFailed') })
|
||||
}
|
||||
}
|
||||
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
if (!appDetail)
|
||||
return
|
||||
@@ -314,7 +342,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
</>
|
||||
)}
|
||||
<Divider className="!my-1" />
|
||||
<div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={onExport}>
|
||||
<div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={exportCheck}>
|
||||
<span className='text-gray-700 text-sm leading-5'>{t('app.export')}</span>
|
||||
</div>
|
||||
{
|
||||
@@ -403,14 +431,19 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
onCancel={() => setShowConfirmDelete(false)}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
showImportDSLModal && (
|
||||
<UpdateDSLModal
|
||||
onCancel={() => setShowImportDSLModal(false)}
|
||||
onBackup={onExport}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{showImportDSLModal && (
|
||||
<UpdateDSLModal
|
||||
onCancel={() => setShowImportDSLModal(false)}
|
||||
onBackup={onExport}
|
||||
/>
|
||||
)}
|
||||
{secretEnvList.length > 0 && (
|
||||
<DSLExportConfirmModal
|
||||
envList={secretEnvList}
|
||||
onConfirm={onExport}
|
||||
onClose={() => setSecretEnvList([])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
|
||||
@@ -119,11 +119,11 @@ const AppPublisher = ({
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='pl-3 pr-1'
|
||||
className='pl-3 pr-2'
|
||||
disabled={disabled}
|
||||
>
|
||||
{t('workflow.common.publish')}
|
||||
<RiArrowDownSLine className='ml-0.5' />
|
||||
<RiArrowDownSLine className='w-4 h-4 ml-0.5' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Left Icon">
|
||||
<path id="Vector" d="M7.83333 2.66683H5.7C4.5799 2.66683 4.01984 2.66683 3.59202 2.88482C3.21569 3.07656 2.90973 3.38252 2.71799 3.75885C2.5 4.18667 2.5 4.74672 2.5 5.86683V9.3335C2.5 9.95348 2.5 10.2635 2.56815 10.5178C2.75308 11.208 3.29218 11.7471 3.98236 11.932C4.2367 12.0002 4.54669 12.0002 5.16667 12.0002V13.5572C5.16667 13.9124 5.16667 14.09 5.23949 14.1812C5.30282 14.2606 5.39885 14.3067 5.50036 14.3066C5.61708 14.3065 5.75578 14.1955 6.03317 13.9736L7.62348 12.7014C7.94834 12.4415 8.11078 12.3115 8.29166 12.2191C8.45213 12.1371 8.62295 12.0772 8.79948 12.041C8.99845 12.0002 9.20646 12.0002 9.6225 12.0002H10.6333C11.7534 12.0002 12.3135 12.0002 12.7413 11.7822C13.1176 11.5904 13.4236 11.2845 13.6153 10.9081C13.8333 10.4803 13.8333 9.92027 13.8333 8.80016V8.66683M11.6551 6.472L14.8021 4.44889C15.0344 4.29958 15.1505 4.22493 15.1906 4.13C15.2257 4.04706 15.2257 3.95347 15.1906 3.87052C15.1505 3.7756 15.0344 3.70094 14.8021 3.55163L11.6551 1.52852C11.3874 1.35646 11.2536 1.27043 11.1429 1.27833C11.0465 1.28522 10.9578 1.33365 10.8998 1.41105C10.8333 1.49987 10.8333 1.65896 10.8333 1.97715V6.02337C10.8333 6.34156 10.8333 6.50066 10.8998 6.58948C10.9578 6.66688 11.0465 6.71531 11.1429 6.72219C11.2536 6.7301 11.3874 6.64407 11.6551 6.472Z" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="env">
|
||||
<g id="Vector">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.33325 3.33325C1.33325 2.22868 2.22868 1.33325 3.33325 1.33325H12.6666C13.7712 1.33325 14.6666 2.22869 14.6666 3.33325V3.66659C14.6666 4.03478 14.3681 4.33325 13.9999 4.33325C13.6317 4.33325 13.3333 4.03478 13.3333 3.66659V3.33325C13.3333 2.96506 13.0348 2.66659 12.6666 2.66659H3.33325C2.96506 2.66659 2.66659 2.96506 2.66659 3.33325V3.66659C2.66659 4.03478 2.36811 4.33325 1.99992 4.33325C1.63173 4.33325 1.33325 4.03478 1.33325 3.66659V3.33325Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6666 12.6666C14.6666 13.7712 13.7712 14.6666 12.6666 14.6666L3.33325 14.6666C2.22866 14.6666 1.33325 13.7711 1.33325 12.6666L1.33325 12.3333C1.33325 11.9651 1.63173 11.6666 1.99992 11.6666C2.36811 11.6666 2.66659 11.9651 2.66659 12.3333V12.6666C2.66659 13.0348 2.96505 13.3333 3.33325 13.3333L12.6666 13.3333C13.0348 13.3333 13.3333 13.0348 13.3333 12.6666V12.3333C13.3333 11.9651 13.6317 11.6666 13.9999 11.6666C14.3681 11.6666 14.6666 11.9651 14.6666 12.3333V12.6666Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.33325 5.99992C1.33325 5.63173 1.63173 5.33325 1.99992 5.33325H4.33325C4.70144 5.33325 4.99992 5.63173 4.99992 5.99992C4.99992 6.36811 4.70144 6.66658 4.33325 6.66658H2.66659V7.33325H3.99992C4.36811 7.33325 4.66659 7.63173 4.66659 7.99992C4.66659 8.36811 4.36811 8.66658 3.99992 8.66658H2.66659V9.33325H4.33325C4.70144 9.33325 4.99992 9.63173 4.99992 9.99992C4.99992 10.3681 4.70144 10.6666 4.33325 10.6666H1.99992C1.63173 10.6666 1.33325 10.3681 1.33325 9.99992V5.99992Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.4734 5.36186C6.75457 5.27673 7.05833 5.38568 7.22129 5.63012L8.66659 7.79807V5.99992C8.66659 5.63173 8.96506 5.33325 9.33325 5.33325C9.70144 5.33325 9.99992 5.63173 9.99992 5.99992V9.99992C9.99992 10.2937 9.80761 10.5528 9.52644 10.638C9.24527 10.7231 8.94151 10.6142 8.77855 10.3697L7.33325 8.20177V9.99992C7.33325 10.3681 7.03478 10.6666 6.66659 10.6666C6.2984 10.6666 5.99992 10.3681 5.99992 9.99992V5.99992C5.99992 5.70614 6.19222 5.44699 6.4734 5.36186Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0768 5.38453C11.4167 5.24292 11.807 5.40364 11.9486 5.74351L12.9999 8.26658L14.0512 5.74351C14.1928 5.40364 14.5831 5.24292 14.923 5.38453C15.2629 5.52614 15.4236 5.91646 15.282 6.25633L13.6153 10.2563C13.5118 10.5048 13.2691 10.6666 12.9999 10.6666C12.7308 10.6666 12.488 10.5048 12.3845 10.2563L10.7179 6.25633C10.5763 5.91646 10.737 5.52614 11.0768 5.38453Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "17",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 17 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Left Icon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"d": "M7.83333 2.66683H5.7C4.5799 2.66683 4.01984 2.66683 3.59202 2.88482C3.21569 3.07656 2.90973 3.38252 2.71799 3.75885C2.5 4.18667 2.5 4.74672 2.5 5.86683V9.3335C2.5 9.95348 2.5 10.2635 2.56815 10.5178C2.75308 11.208 3.29218 11.7471 3.98236 11.932C4.2367 12.0002 4.54669 12.0002 5.16667 12.0002V13.5572C5.16667 13.9124 5.16667 14.09 5.23949 14.1812C5.30282 14.2606 5.39885 14.3067 5.50036 14.3066C5.61708 14.3065 5.75578 14.1955 6.03317 13.9736L7.62348 12.7014C7.94834 12.4415 8.11078 12.3115 8.29166 12.2191C8.45213 12.1371 8.62295 12.0772 8.79948 12.041C8.99845 12.0002 9.20646 12.0002 9.6225 12.0002H10.6333C11.7534 12.0002 12.3135 12.0002 12.7413 11.7822C13.1176 11.5904 13.4236 11.2845 13.6153 10.9081C13.8333 10.4803 13.8333 9.92027 13.8333 8.80016V8.66683M11.6551 6.472L14.8021 4.44889C15.0344 4.29958 15.1505 4.22493 15.1906 4.13C15.2257 4.04706 15.2257 3.95347 15.1906 3.87052C15.1505 3.7756 15.0344 3.70094 14.8021 3.55163L11.6551 1.52852C11.3874 1.35646 11.2536 1.27043 11.1429 1.27833C11.0465 1.28522 10.9578 1.33365 10.8998 1.41105C10.8333 1.49987 10.8333 1.65896 10.8333 1.97715V6.02337C10.8333 6.34156 10.8333 6.50066 10.8998 6.58948C10.9578 6.66688 11.0465 6.71531 11.1429 6.72219C11.2536 6.7301 11.3874 6.64407 11.6551 6.472Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "1.5",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "MessagePlay"
|
||||
}
|
||||
@@ -4,4 +4,3 @@ export { default as ChatBot } from './ChatBot'
|
||||
export { default as CuteRobot } from './CuteRobot'
|
||||
export { default as MessageCheckRemove } from './MessageCheckRemove'
|
||||
export { default as MessageFastPlus } from './MessageFastPlus'
|
||||
export { default as MessagePlay } from './MessagePlay'
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"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": "env"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Vector"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M1.33325 3.33325C1.33325 2.22868 2.22868 1.33325 3.33325 1.33325H12.6666C13.7712 1.33325 14.6666 2.22869 14.6666 3.33325V3.66659C14.6666 4.03478 14.3681 4.33325 13.9999 4.33325C13.6317 4.33325 13.3333 4.03478 13.3333 3.66659V3.33325C13.3333 2.96506 13.0348 2.66659 12.6666 2.66659H3.33325C2.96506 2.66659 2.66659 2.96506 2.66659 3.33325V3.66659C2.66659 4.03478 2.36811 4.33325 1.99992 4.33325C1.63173 4.33325 1.33325 4.03478 1.33325 3.66659V3.33325Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M14.6666 12.6666C14.6666 13.7712 13.7712 14.6666 12.6666 14.6666L3.33325 14.6666C2.22866 14.6666 1.33325 13.7711 1.33325 12.6666L1.33325 12.3333C1.33325 11.9651 1.63173 11.6666 1.99992 11.6666C2.36811 11.6666 2.66659 11.9651 2.66659 12.3333V12.6666C2.66659 13.0348 2.96505 13.3333 3.33325 13.3333L12.6666 13.3333C13.0348 13.3333 13.3333 13.0348 13.3333 12.6666V12.3333C13.3333 11.9651 13.6317 11.6666 13.9999 11.6666C14.3681 11.6666 14.6666 11.9651 14.6666 12.3333V12.6666Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M1.33325 5.99992C1.33325 5.63173 1.63173 5.33325 1.99992 5.33325H4.33325C4.70144 5.33325 4.99992 5.63173 4.99992 5.99992C4.99992 6.36811 4.70144 6.66658 4.33325 6.66658H2.66659V7.33325H3.99992C4.36811 7.33325 4.66659 7.63173 4.66659 7.99992C4.66659 8.36811 4.36811 8.66658 3.99992 8.66658H2.66659V9.33325H4.33325C4.70144 9.33325 4.99992 9.63173 4.99992 9.99992C4.99992 10.3681 4.70144 10.6666 4.33325 10.6666H1.99992C1.63173 10.6666 1.33325 10.3681 1.33325 9.99992V5.99992Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M6.4734 5.36186C6.75457 5.27673 7.05833 5.38568 7.22129 5.63012L8.66659 7.79807V5.99992C8.66659 5.63173 8.96506 5.33325 9.33325 5.33325C9.70144 5.33325 9.99992 5.63173 9.99992 5.99992V9.99992C9.99992 10.2937 9.80761 10.5528 9.52644 10.638C9.24527 10.7231 8.94151 10.6142 8.77855 10.3697L7.33325 8.20177V9.99992C7.33325 10.3681 7.03478 10.6666 6.66659 10.6666C6.2984 10.6666 5.99992 10.3681 5.99992 9.99992V5.99992C5.99992 5.70614 6.19222 5.44699 6.4734 5.36186Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M11.0768 5.38453C11.4167 5.24292 11.807 5.40364 11.9486 5.74351L12.9999 8.26658L14.0512 5.74351C14.1928 5.40364 14.5831 5.24292 14.923 5.38453C15.2629 5.52614 15.4236 5.91646 15.282 6.25633L13.6153 10.2563C13.5118 10.5048 13.2691 10.6666 12.9999 10.6666C12.7308 10.6666 12.488 10.5048 12.3845 10.2563L10.7179 6.25633C10.5763 5.91646 10.737 5.52614 11.0768 5.38453Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Env"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './MessagePlay.json'
|
||||
import data from './Env.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
@@ -11,6 +11,6 @@ const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseP
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'MessagePlay'
|
||||
Icon.displayName = 'Env'
|
||||
|
||||
export default Icon
|
||||
@@ -1,6 +1,7 @@
|
||||
export { default as Apps02 } from './Apps02'
|
||||
export { default as Colors } from './Colors'
|
||||
export { default as DragHandle } from './DragHandle'
|
||||
export { default as Env } from './Env'
|
||||
export { default as Exchange02 } from './Exchange02'
|
||||
export { default as FileCode } from './FileCode'
|
||||
export { default as Icon3Dots } from './Icon3Dots'
|
||||
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
} from './index'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
|
||||
type WorkflowVariableBlockComponentProps = {
|
||||
@@ -50,6 +51,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
)()
|
||||
const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState<WorkflowNodesMap>(workflowNodesMap)
|
||||
const node = localWorkflowNodesMap![variables[0]]
|
||||
const isEnv = isENV(variables)
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([WorkflowVariableBlockNode]))
|
||||
@@ -73,30 +75,33 @@ const WorkflowVariableBlockComponent = ({
|
||||
className={cn(
|
||||
'mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none',
|
||||
isSelected ? ' border-[#84ADFF] bg-[#F5F8FF]' : ' border-black/5 bg-white',
|
||||
!node && '!border-[#F04438] !bg-[#FEF3F2]',
|
||||
!node && !isEnv && '!border-[#F04438] !bg-[#FEF3F2]',
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
node?.type && (
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-500'
|
||||
type={node?.type}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='shrink-0 mx-0.5 max-w-[60px] text-xs font-medium text-gray-500 truncate' title={node?.title} style={{
|
||||
}}>{node?.title}</div>
|
||||
<Line3 className='mr-0.5 text-gray-300'></Line3>
|
||||
</div>
|
||||
{!isEnv && (
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
node?.type && (
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-500'
|
||||
type={node?.type}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='shrink-0 mx-0.5 max-w-[60px] text-xs font-medium text-gray-500 truncate' title={node?.title} style={{
|
||||
}}>{node?.title}</div>
|
||||
<Line3 className='mr-0.5 text-gray-300'></Line3>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<Variable02 className='w-3.5 h-3.5' />
|
||||
<div className='shrink-0 ml-0.5 text-xs font-medium truncate' title={varName}>{varName}</div>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('shrink-0 ml-0.5 text-xs font-medium truncate', isEnv && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
{
|
||||
!node && (
|
||||
!node && !isEnv && (
|
||||
<RiErrorWarningFill className='ml-0.5 w-3 h-3 text-[#D92D20]' />
|
||||
)
|
||||
}
|
||||
@@ -104,7 +109,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!node) {
|
||||
if (!node && !isEnv) {
|
||||
return (
|
||||
<TooltipPlus popupContent={t('workflow.errorMsg.invalidVariable')}>
|
||||
{Item}
|
||||
|
||||
@@ -396,3 +396,4 @@ export const PARAMETER_EXTRACTOR_COMMON_STRUCT: Var[] = [
|
||||
|
||||
export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'
|
||||
export const CUSTOM_NODE = 'custom'
|
||||
export const DSL_EXPORT_CHECK = 'DSL_EXPORT_CHECK'
|
||||
|
||||
85
web/app/components/workflow/dsl-export-confirm-modal.tsx
Normal file
85
web/app/components/workflow/dsl-export-confirm-modal.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine, RiLock2Line } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
|
||||
export type DSLExportConfirmModalProps = {
|
||||
envList: EnvironmentVariable[]
|
||||
onConfirm: (state: boolean) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const DSLExportConfirmModal = ({
|
||||
envList = [],
|
||||
onConfirm,
|
||||
onClose,
|
||||
}: DSLExportConfirmModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [exportSecrets, setExportSecrets] = useState<boolean>(false)
|
||||
|
||||
const submit = () => {
|
||||
onConfirm(exportSecrets)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={() => { }}
|
||||
className={cn('max-w-[480px] w-[480px]')}
|
||||
>
|
||||
<div className='relative pb-6 title-2xl-semi-bold text-text-primary'>{t('workflow.env.export.title')}</div>
|
||||
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onClose}>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='relative'>
|
||||
<table className='w-full border-separate border-spacing-0 border border-divider-regular radius-md shadow-xs'>
|
||||
<thead className='system-xs-medium-uppercase text-text-tertiary'>
|
||||
<tr>
|
||||
<td width={220} className='h-7 pl-3 border-r border-b border-divider-regular'>NAME</td>
|
||||
<td className='h-7 pl-3 border-b border-divider-regular'>VALUE</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{envList.map((env, index) => (
|
||||
<tr key={env.name}>
|
||||
<td className={cn('h-7 pl-3 border-r system-xs-medium', index + 1 !== envList.length && 'border-b')}>
|
||||
<div className='flex gap-1 items-center w-[200px]'>
|
||||
<Env className='shrink-0 w-4 h-4 text-util-colors-violet-violet-600' />
|
||||
<div className='text-text-primary truncate'>{env.name}</div>
|
||||
<div className='shrink-0 text-text-tertiary'>Secret</div>
|
||||
<RiLock2Line className='shrink-0 w-3 h-3 text-text-tertiary' />
|
||||
</div>
|
||||
</td>
|
||||
<td className={cn('h-7 pl-3', index + 1 !== envList.length && 'border-b')}>
|
||||
<div className='system-xs-regular text-text-secondary truncate'>{env.value}</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='mt-4 flex gap-2'>
|
||||
<Checkbox
|
||||
className='shrink-0'
|
||||
checked={exportSecrets}
|
||||
onCheck={() => setExportSecrets(!exportSecrets)}
|
||||
/>
|
||||
<div className='text-text-primary system-sm-medium cursor-pointer' onClick={() => setExportSecrets(!exportSecrets)}>{t('workflow.env.export.checkbox')}</div>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse pt-6'>
|
||||
<Button className='ml-2' variant='primary' onClick={submit}>{exportSecrets ? t('workflow.env.export.export') : t('workflow.env.export.ignore')}</Button>
|
||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default DSLExportConfirmModal
|
||||
@@ -57,22 +57,15 @@ const WorkflowChecklist = ({
|
||||
<PortalToFollowElemTrigger onClick={() => !disabled && setOpen(v => !v)}>
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex items-center justify-center p-0.5 w-8 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs',
|
||||
'relative ml-0.5 flex items-center justify-center w-7 h-7 rounded-md',
|
||||
disabled && 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
group flex items-center justify-center w-full h-full rounded-md cursor-pointer
|
||||
hover:bg-primary-50
|
||||
${open && 'bg-primary-50'}
|
||||
`}
|
||||
className={cn('group flex items-center justify-center w-full h-full rounded-md cursor-pointer hover:bg-state-accent-hover', open && 'bg-state-accent-hover')}
|
||||
>
|
||||
<RiListCheck3
|
||||
className={`
|
||||
w-4 h-4 group-hover:text-primary-600
|
||||
${open ? 'text-primary-600' : 'text-gray-500'}`
|
||||
}
|
||||
className={cn('w-4 h-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
|
||||
22
web/app/components/workflow/header/env-button.tsx
Normal file
22
web/app/components/workflow/header/env-button.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { memo } from 'react'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const EnvButton = () => {
|
||||
const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
|
||||
const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel)
|
||||
|
||||
const handleClick = () => {
|
||||
setShowEnvPanel(true)
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('relative flex items-center justify-center p-0.5 w-8 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs cursor-pointer hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover')} onClick={handleClick}>
|
||||
<Env className='w-4 h-4 text-components-button-secondary-text' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(EnvButton)
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { RiApps2AddLine } from '@remixicon/react'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
@@ -30,8 +31,7 @@ import EditingTitle from './editing-title'
|
||||
import RunningTitle from './running-title'
|
||||
import RestoringTitle from './restoring-title'
|
||||
import ViewHistory from './view-history'
|
||||
import Checklist from './checklist'
|
||||
import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
|
||||
import EnvButton from './env-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { publishWorkflow } from '@/service/workflow'
|
||||
@@ -44,10 +44,7 @@ const Header: FC = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
|
||||
const appID = appDetail?.id
|
||||
const {
|
||||
nodesReadOnly,
|
||||
getNodesReadOnly,
|
||||
} = useNodesReadOnly()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const publishedAt = useStore(s => s.publishedAt)
|
||||
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
||||
const toolPublished = useStore(s => s.toolPublished)
|
||||
@@ -167,14 +164,12 @@ const Header: FC = () => {
|
||||
</div>
|
||||
{
|
||||
normal && (
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<EnvButton />
|
||||
<div className='w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<RunAndHistory />
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Button
|
||||
className='mr-2'
|
||||
onClick={handleShowFeatures}
|
||||
>
|
||||
<Grid01 className='w-4 h-4 mr-1 text-gray-500' />
|
||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<AppPublisher
|
||||
@@ -188,11 +183,9 @@ const Header: FC = () => {
|
||||
onPublish,
|
||||
onRestore: onStartRestoring,
|
||||
onToggle: onPublisherToggle,
|
||||
crossAxisOffset: 53,
|
||||
crossAxisOffset: 4,
|
||||
}}
|
||||
/>
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Checklist disabled={nodesReadOnly} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -215,10 +208,8 @@ const Header: FC = () => {
|
||||
{
|
||||
restoring && (
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
onClick={handleShowFeatures}
|
||||
>
|
||||
<Grid01 className='w-4 h-4 mr-1 text-gray-500' />
|
||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
|
||||
@@ -3,21 +3,22 @@ import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiLoader2Line,
|
||||
RiPlayLargeFill,
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import { useStore } from '../store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useWorkflowRun,
|
||||
useWorkflowStartRun,
|
||||
} from '../hooks'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
import ViewHistory from './view-history'
|
||||
import Checklist from './checklist'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
StopCircle,
|
||||
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import { MessagePlay } from '@/app/components/base/icons/src/vender/line/communication'
|
||||
|
||||
const RunMode = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
@@ -30,9 +31,9 @@ const RunMode = memo(() => {
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600',
|
||||
'hover:bg-primary-50 cursor-pointer',
|
||||
isRunning && 'bg-primary-50 !cursor-not-allowed',
|
||||
'flex items-center px-2.5 h-7 rounded-md text-[13px] font-medium text-components-button-secondary-accent-text',
|
||||
'hover:bg-state-accent-hover cursor-pointer',
|
||||
isRunning && 'bg-state-accent-hover !cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => handleWorkflowStartRunInWorkflow()}
|
||||
>
|
||||
@@ -46,7 +47,7 @@ const RunMode = memo(() => {
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<RiPlayLargeFill className='mr-1 w-4 h-4' />
|
||||
<RiPlayLargeLine className='mr-1 w-4 h-4' />
|
||||
{t('workflow.common.run')}
|
||||
</>
|
||||
)
|
||||
@@ -58,7 +59,7 @@ const RunMode = memo(() => {
|
||||
className='flex items-center justify-center ml-0.5 w-7 h-7 cursor-pointer hover:bg-black/5 rounded-md'
|
||||
onClick={() => handleStopRun(workflowRunningData?.task_id || '')}
|
||||
>
|
||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||
<StopCircle className='w-4 h-4 text-components-button-ghost-text' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -74,12 +75,12 @@ const PreviewMode = memo(() => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600',
|
||||
'hover:bg-primary-50 cursor-pointer',
|
||||
'flex items-center px-2.5 h-7 rounded-md text-[13px] font-medium text-components-button-secondary-accent-text',
|
||||
'hover:bg-state-accent-hover cursor-pointer',
|
||||
)}
|
||||
onClick={() => handleWorkflowStartRunInChatflow()}
|
||||
>
|
||||
<MessagePlay className='mr-1 w-4 h-4' />
|
||||
<RiPlayLargeLine className='mr-1 w-4 h-4' />
|
||||
{t('workflow.common.debugAndPreview')}
|
||||
</div>
|
||||
)
|
||||
@@ -88,17 +89,19 @@ PreviewMode.displayName = 'PreviewMode'
|
||||
|
||||
const RunAndHistory: FC = () => {
|
||||
const isChatMode = useIsChatMode()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-0.5 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
|
||||
<div className='flex items-center px-0.5 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs'>
|
||||
{
|
||||
!isChatMode && <RunMode />
|
||||
}
|
||||
{
|
||||
isChatMode && <PreviewMode />
|
||||
}
|
||||
<div className='mx-0.5 w-[0.5px] h-8 bg-gray-200'></div>
|
||||
<div className='mx-0.5 w-[1px] h-3.5 bg-divider-regular'></div>
|
||||
<ViewHistory />
|
||||
<Checklist disabled={nodesReadOnly} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -103,16 +103,13 @@ const ViewHistory = ({
|
||||
popupContent={t('workflow.common.viewRunHistory')}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center w-7 h-7 rounded-md hover:bg-black/5 cursor-pointer
|
||||
${open && 'bg-primary-50'}
|
||||
`}
|
||||
className={cn('group flex items-center justify-center w-7 h-7 rounded-md hover:bg-state-accent-hover cursor-pointer', open && 'bg-state-accent-hover')}
|
||||
onClick={() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
}}
|
||||
>
|
||||
<ClockPlay className={`w-4 h-4 ${open ? 'text-primary-600' : 'text-gray-500'}`} />
|
||||
<ClockPlay className={cn('w-4 h-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')} />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
)
|
||||
@@ -170,6 +167,7 @@ const ViewHistory = ({
|
||||
workflowStore.setState({
|
||||
historyWorkflowData: item,
|
||||
showInputsPanel: false,
|
||||
showEnvPanel: false,
|
||||
})
|
||||
handleBackupDraft()
|
||||
setOpen(false)
|
||||
|
||||
@@ -14,3 +14,4 @@ export * from './use-panel-interactions'
|
||||
export * from './use-workflow-start-run'
|
||||
export * from './use-nodes-layout'
|
||||
export * from './use-workflow-history'
|
||||
export * from './use-workflow-variables'
|
||||
|
||||
@@ -31,6 +31,7 @@ export const useNodesSyncDraft = () => {
|
||||
const [x, y, zoom] = transform
|
||||
const {
|
||||
appId,
|
||||
environmentVariables,
|
||||
syncWorkflowDraftHash,
|
||||
} = workflowStore.getState()
|
||||
|
||||
@@ -80,6 +81,7 @@ export const useNodesSyncDraft = () => {
|
||||
sensitive_word_avoidance: features.moderation,
|
||||
file_upload: features.file,
|
||||
},
|
||||
environment_variables: environmentVariables,
|
||||
hash: syncWorkflowDraftHash,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import { WORKFLOW_DATA_UPDATE } from '../constants'
|
||||
import { DSL_EXPORT_CHECK, WORKFLOW_DATA_UPDATE } from '../constants'
|
||||
import type { WorkflowDataUpdator } from '../types'
|
||||
import {
|
||||
initialEdges,
|
||||
@@ -66,11 +66,18 @@ export const useWorkflowUpdate = () => {
|
||||
appId,
|
||||
setSyncWorkflowDraftHash,
|
||||
setIsSyncingWorkflowDraft,
|
||||
setEnvironmentVariables,
|
||||
setEnvSecrets,
|
||||
} = workflowStore.getState()
|
||||
setIsSyncingWorkflowDraft(true)
|
||||
fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => {
|
||||
handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdator)
|
||||
setSyncWorkflowDraftHash(response.hash)
|
||||
setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
|
||||
acc[env.id] = env.value
|
||||
return acc
|
||||
}, {} as Record<string, string>))
|
||||
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
|
||||
}).finally(() => setIsSyncingWorkflowDraft(false))
|
||||
}, [handleUpdateWorkflowCanvas, workflowStore])
|
||||
|
||||
@@ -83,12 +90,13 @@ export const useWorkflowUpdate = () => {
|
||||
export const useDSL = () => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [exporting, setExporting] = useState(false)
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
|
||||
const handleExportDSL = useCallback(async () => {
|
||||
const handleExportDSL = useCallback(async (include = false) => {
|
||||
if (!appDetail)
|
||||
return
|
||||
|
||||
@@ -98,7 +106,10 @@ export const useDSL = () => {
|
||||
try {
|
||||
setExporting(true)
|
||||
await doSyncWorkflowDraft()
|
||||
const { data } = await exportAppConfig(appDetail.id)
|
||||
const { data } = await exportAppConfig({
|
||||
appID: appDetail.id,
|
||||
include,
|
||||
})
|
||||
const a = document.createElement('a')
|
||||
const file = new Blob([data], { type: 'application/yaml' })
|
||||
a.href = URL.createObjectURL(file)
|
||||
@@ -113,7 +124,30 @@ export const useDSL = () => {
|
||||
}
|
||||
}, [appDetail, notify, t, doSyncWorkflowDraft, exporting])
|
||||
|
||||
const exportCheck = useCallback(async () => {
|
||||
if (!appDetail)
|
||||
return
|
||||
try {
|
||||
const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail?.id}/workflows/draft`)
|
||||
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
|
||||
if (list.length === 0) {
|
||||
handleExportDSL()
|
||||
return
|
||||
}
|
||||
eventEmitter?.emit({
|
||||
type: DSL_EXPORT_CHECK,
|
||||
payload: {
|
||||
data: list,
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
catch (e) {
|
||||
notify({ type: 'error', message: t('app.exportFailed') })
|
||||
}
|
||||
}, [appDetail, eventEmitter, handleExportDSL, notify, t])
|
||||
|
||||
return {
|
||||
exportCheck,
|
||||
handleExportDSL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
backupDraft,
|
||||
setBackupDraft,
|
||||
environmentVariables,
|
||||
} = workflowStore.getState()
|
||||
const { features } = featuresStore!.getState()
|
||||
|
||||
@@ -50,6 +51,7 @@ export const useWorkflowRun = () => {
|
||||
edges,
|
||||
viewport: getViewport(),
|
||||
features,
|
||||
environmentVariables,
|
||||
})
|
||||
doSyncWorkflowDraft()
|
||||
}
|
||||
@@ -59,6 +61,7 @@ export const useWorkflowRun = () => {
|
||||
const {
|
||||
backupDraft,
|
||||
setBackupDraft,
|
||||
setEnvironmentVariables,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (backupDraft) {
|
||||
@@ -67,12 +70,14 @@ export const useWorkflowRun = () => {
|
||||
edges,
|
||||
viewport,
|
||||
features,
|
||||
environmentVariables,
|
||||
} = backupDraft
|
||||
handleUpdateWorkflowCanvas({
|
||||
nodes,
|
||||
edges,
|
||||
viewport,
|
||||
})
|
||||
setEnvironmentVariables(environmentVariables)
|
||||
featuresStore!.setState({ features })
|
||||
setBackupDraft(undefined)
|
||||
}
|
||||
@@ -522,6 +527,7 @@ export const useWorkflowRun = () => {
|
||||
})
|
||||
featuresStore?.setState({ features: publishedWorkflow.features })
|
||||
workflowStore.getState().setPublishedAt(publishedWorkflow.created_at)
|
||||
workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
|
||||
}
|
||||
}, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
|
||||
|
||||
|
||||
@@ -39,8 +39,11 @@ export const useWorkflowStartRun = () => {
|
||||
showDebugAndPreviewPanel,
|
||||
setShowDebugAndPreviewPanel,
|
||||
setShowInputsPanel,
|
||||
setShowEnvPanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setShowEnvPanel(false)
|
||||
|
||||
if (showDebugAndPreviewPanel) {
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
return
|
||||
@@ -63,8 +66,11 @@ export const useWorkflowStartRun = () => {
|
||||
showDebugAndPreviewPanel,
|
||||
setShowDebugAndPreviewPanel,
|
||||
setHistoryWorkflowData,
|
||||
setShowEnvPanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setShowEnvPanel(false)
|
||||
|
||||
if (showDebugAndPreviewPanel)
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
else
|
||||
|
||||
69
web/app/components/workflow/hooks/use-workflow-variables.ts
Normal file
69
web/app/components/workflow/hooks/use-workflow-variables.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '../store'
|
||||
import { getVarType, toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import type {
|
||||
Node,
|
||||
NodeOutPutVar,
|
||||
ValueSelector,
|
||||
Var,
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
export const useWorkflowVariables = () => {
|
||||
const { t } = useTranslation()
|
||||
const environmentVariables = useStore(s => s.environmentVariables)
|
||||
|
||||
const getNodeAvailableVars = useCallback(({
|
||||
parentNode,
|
||||
beforeNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
hideEnv,
|
||||
}: {
|
||||
parentNode?: Node | null
|
||||
beforeNodes: Node[]
|
||||
isChatMode: boolean
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
hideEnv?: boolean
|
||||
}): NodeOutPutVar[] => {
|
||||
return toNodeAvailableVars({
|
||||
parentNode,
|
||||
t,
|
||||
beforeNodes,
|
||||
isChatMode,
|
||||
environmentVariables: hideEnv ? [] : environmentVariables,
|
||||
filterVar,
|
||||
})
|
||||
}, [environmentVariables, t])
|
||||
|
||||
const getCurrentVariableType = useCallback(({
|
||||
parentNode,
|
||||
valueSelector,
|
||||
isIterationItem,
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant,
|
||||
}: {
|
||||
valueSelector: ValueSelector
|
||||
parentNode?: Node | null
|
||||
isIterationItem?: boolean
|
||||
availableNodes: any[]
|
||||
isChatMode: boolean
|
||||
isConstant?: boolean
|
||||
}) => {
|
||||
return getVarType({
|
||||
parentNode,
|
||||
valueSelector,
|
||||
isIterationItem,
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant,
|
||||
environmentVariables,
|
||||
})
|
||||
}, [environmentVariables])
|
||||
|
||||
return {
|
||||
getNodeAvailableVars,
|
||||
getCurrentVariableType,
|
||||
}
|
||||
}
|
||||
@@ -471,8 +471,14 @@ export const useWorkflowInit = () => {
|
||||
const handleGetInitialWorkflowData = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`)
|
||||
|
||||
setData(res)
|
||||
workflowStore.setState({
|
||||
envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
|
||||
acc[env.id] = env.value
|
||||
return acc
|
||||
}, {} as Record<string, string>),
|
||||
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
|
||||
})
|
||||
setSyncWorkflowDraftHash(res.hash)
|
||||
setIsLoading(false)
|
||||
}
|
||||
@@ -491,6 +497,7 @@ export const useWorkflowInit = () => {
|
||||
features: {
|
||||
retriever_resource: { enabled: true },
|
||||
},
|
||||
environment_variables: [],
|
||||
},
|
||||
}).then((res) => {
|
||||
workflowStore.getState().setDraftUpdatedAt(res.updated_at)
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { setAutoFreeze } from 'immer'
|
||||
import {
|
||||
@@ -30,6 +31,7 @@ import 'reactflow/dist/style.css'
|
||||
import './style.css'
|
||||
import type {
|
||||
Edge,
|
||||
EnvironmentVariable,
|
||||
Node,
|
||||
} from './types'
|
||||
import { WorkflowContextProvider } from './context'
|
||||
@@ -62,6 +64,7 @@ import PanelContextmenu from './panel-contextmenu'
|
||||
import NodeContextmenu from './node-contextmenu'
|
||||
import SyncingDataModal from './syncing-data-modal'
|
||||
import UpdateDSLModal from './update-dsl-modal'
|
||||
import DSLExportConfirmModal from './dsl-export-confirm-modal'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
@@ -74,6 +77,7 @@ import {
|
||||
} from './utils'
|
||||
import {
|
||||
CUSTOM_NODE,
|
||||
DSL_EXPORT_CHECK,
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
WORKFLOW_DATA_UPDATE,
|
||||
} from './constants'
|
||||
@@ -114,6 +118,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
const nodeAnimation = useStore(s => s.nodeAnimation)
|
||||
const showConfirm = useStore(s => s.showConfirm)
|
||||
const showImportDSLModal = useStore(s => s.showImportDSLModal)
|
||||
|
||||
const {
|
||||
setShowConfirm,
|
||||
setControlPromptEditorRerenderKey,
|
||||
@@ -127,6 +132,8 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
const { workflowReadOnly } = useWorkflowReadOnly()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
@@ -148,6 +155,8 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
|
||||
setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
|
||||
}
|
||||
if (v.type === DSL_EXPORT_CHECK)
|
||||
setSecretEnvList(v.payload.data as EnvironmentVariable[])
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -330,6 +339,15 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
secretEnvList.length > 0 && (
|
||||
<DSLExportConfirmModal
|
||||
envList={secretEnvList}
|
||||
onConfirm={handleExportDSL}
|
||||
onClose={() => setSecretEnvList([])}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<ReactFlow
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '../../../store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '../../../hooks'
|
||||
import type {
|
||||
ValueSelector,
|
||||
@@ -20,7 +20,6 @@ import type {
|
||||
import { useVariableAssigner } from '../../variable-assigner/hooks'
|
||||
import { filterVar } from '../../variable-assigner/utils'
|
||||
import AddVariablePopup from './add-variable-popup'
|
||||
import { toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
type AddVariablePopupWithPositionProps = {
|
||||
nodeId: string
|
||||
@@ -30,7 +29,6 @@ const AddVariablePopupWithPosition = ({
|
||||
nodeId,
|
||||
nodeData,
|
||||
}: AddVariablePopupWithPositionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const ref = useRef(null)
|
||||
const showAssignVariablePopup = useStore(s => s.showAssignVariablePopup)
|
||||
const setShowAssignVariablePopup = useStore(s => s.setShowAssignVariablePopup)
|
||||
@@ -38,6 +36,7 @@ const AddVariablePopupWithPosition = ({
|
||||
const { handleAddVariableInAddVariablePopupWithPosition } = useVariableAssigner()
|
||||
const isChatMode = useIsChatMode()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const { getNodeAvailableVars } = useWorkflowVariables()
|
||||
|
||||
const outputType = useMemo(() => {
|
||||
if (!showAssignVariablePopup)
|
||||
@@ -55,9 +54,8 @@ const AddVariablePopupWithPosition = ({
|
||||
if (!showAssignVariablePopup)
|
||||
return []
|
||||
|
||||
return toNodeAvailableVars({
|
||||
return getNodeAvailableVars({
|
||||
parentNode: showAssignVariablePopup.parentNode,
|
||||
t,
|
||||
beforeNodes: [
|
||||
...getBeforeNodesInSameBranch(showAssignVariablePopup.nodeId),
|
||||
{
|
||||
@@ -65,10 +63,16 @@ const AddVariablePopupWithPosition = ({
|
||||
data: showAssignVariablePopup.nodeData,
|
||||
} as any,
|
||||
],
|
||||
hideEnv: true,
|
||||
isChatMode,
|
||||
filterVar: filterVar(outputType as VarType),
|
||||
})
|
||||
}, [getBeforeNodesInSameBranch, isChatMode, showAssignVariablePopup, t, outputType])
|
||||
.map(node => ({
|
||||
...node,
|
||||
vars: node.isStartNode ? node.vars.filter(v => !v.variable.startsWith('sys.')) : node.vars,
|
||||
}))
|
||||
.filter(item => item.vars.length > 0)
|
||||
}, [showAssignVariablePopup, getNodeAvailableVars, getBeforeNodesInSameBranch, isChatMode, outputType])
|
||||
|
||||
useClickAway(() => {
|
||||
if (nodeData._holdAddVariablePopup) {
|
||||
|
||||
@@ -5,9 +5,10 @@ import cn from 'classnames'
|
||||
import { useWorkflow } from '../../../hooks'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import { VarBlockIcon } from '../../../block-icon'
|
||||
import { getNodeInfoById, isSystemVar } from './variable/utils'
|
||||
import { getNodeInfoById, isENV, isSystemVar } from './variable/utils'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
type Props = {
|
||||
nodeId: string
|
||||
value: string
|
||||
@@ -40,25 +41,29 @@ const ReadonlyInputWithSelectVar: FC<Props> = ({
|
||||
|
||||
const value = vars[index].split('.')
|
||||
const isSystem = isSystemVar(value)
|
||||
const isEnv = isENV(value)
|
||||
const node = (isSystem ? startNode : getNodeInfoById(availableNodes, value[0]))?.data
|
||||
const varName = `${isSystem ? 'sys.' : ''}${value[value.length - 1]}`
|
||||
|
||||
return (<span key={index}>
|
||||
<span className='relative top-[-3px] leading-[16px]'>{str}</span>
|
||||
<div className=' inline-flex h-[16px] items-center px-1.5 rounded-[5px] bg-white'>
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={node?.type || BlockEnum.Start}
|
||||
/>
|
||||
{!isEnv && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={node?.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-w-[60px] mx-0.5 text-xs font-medium text-gray-700 truncate' title={node?.title}>{node?.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
<div className='max-w-[60px] mx-0.5 text-xs font-medium text-gray-700 truncate' title={node?.title}>{node?.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<Variable02 className='w-3.5 h-3.5' />
|
||||
<div className='max-w-[50px] ml-0.5 text-xs font-medium truncate' title={varName}>{varName}</div>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('max-w-[50px] ml-0.5 text-xs font-medium truncate', isEnv && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>)
|
||||
|
||||
@@ -10,7 +10,9 @@ import type {
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type VariableTagProps = {
|
||||
valueSelector: ValueSelector
|
||||
@@ -27,36 +29,40 @@ const VariableTag = ({
|
||||
|
||||
return nodes.find(node => node.id === valueSelector[0])
|
||||
}, [nodes, valueSelector])
|
||||
const isEnv = isENV(valueSelector)
|
||||
|
||||
const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.')
|
||||
|
||||
return (
|
||||
<div className='inline-flex items-center px-1.5 max-w-full h-6 text-xs rounded-md border-[0.5px] border-[rgba(16, 2440,0.08)] bg-white shadow-xs'>
|
||||
{
|
||||
node && (
|
||||
<VarBlockIcon
|
||||
className='shrink-0 mr-0.5 text-[#354052]'
|
||||
type={node!.data.type}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{!isEnv && (
|
||||
<>
|
||||
{node && (
|
||||
<VarBlockIcon
|
||||
className='shrink-0 mr-0.5 text-text-secondary'
|
||||
type={node!.data.type}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className='max-w-[60px] truncate text-text-secondary font-medium'
|
||||
title={node?.data.title}
|
||||
>
|
||||
{node?.data.title}
|
||||
</div>
|
||||
<Line3 className='shrink-0 mx-0.5' />
|
||||
<Variable02 className='shrink-0 mr-0.5 w-3.5 h-3.5 text-text-accent' />
|
||||
</>
|
||||
)}
|
||||
{isEnv && <Env className='shrink-0 mr-0.5 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div
|
||||
className='max-w-[60px] truncate text-[#354052] font-medium'
|
||||
title={node?.data.title}
|
||||
>
|
||||
{node?.data.title}
|
||||
</div>
|
||||
<Line3 className='shrink-0 mx-0.5' />
|
||||
<Variable02 className='shrink-0 mr-0.5 w-3.5 h-3.5 text-[#155AEF]' />
|
||||
<div
|
||||
className='truncate text-[#155AEF] font-medium'
|
||||
className={cn('truncate text-text-accent font-medium', isEnv && 'text-text-secondary')}
|
||||
title={variableName}
|
||||
>
|
||||
{variableName}
|
||||
</div>
|
||||
{
|
||||
varType && (
|
||||
<div className='shrink-0 ml-0.5 text-[#676F83]'>{capitalize(varType)}</div>
|
||||
<div className='shrink-0 ml-0.5 text-text-tertiary'>{capitalize(varType)}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { ParameterExtractorNodeType } from '../../../parameter-extractor/ty
|
||||
import type { IterationNodeType } from '../../../iteration/types'
|
||||
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { EnvironmentVariable, Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
|
||||
import {
|
||||
HTTP_REQUEST_OUTPUT_STRUCT,
|
||||
@@ -34,6 +34,10 @@ export const isSystemVar = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'sys' || valueSelector[1] === 'sys'
|
||||
}
|
||||
|
||||
export const isENV = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'env'
|
||||
}
|
||||
|
||||
const inputVarTypeToVarType = (type: InputVarType): VarType => {
|
||||
if (type === InputVarType.number)
|
||||
return VarType.number
|
||||
@@ -59,7 +63,11 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val
|
||||
return res
|
||||
}
|
||||
|
||||
const formatItem = (item: any, isChatMode: boolean, filterVar: (payload: Var, selector: ValueSelector) => boolean): NodeOutPutVar => {
|
||||
const formatItem = (
|
||||
item: any,
|
||||
isChatMode: boolean,
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean,
|
||||
): NodeOutPutVar => {
|
||||
const { id, data } = item
|
||||
|
||||
const res: NodeOutPutVar = {
|
||||
@@ -226,6 +234,16 @@ const formatItem = (item: any, isChatMode: boolean, filterVar: (payload: Var, se
|
||||
]
|
||||
break
|
||||
}
|
||||
|
||||
case 'env': {
|
||||
res.vars = data.envList.map((env: EnvironmentVariable) => {
|
||||
return {
|
||||
variable: `env.${env.name}`,
|
||||
type: env.value_type,
|
||||
}
|
||||
}) as Var[]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const selector = [id]
|
||||
@@ -246,16 +264,30 @@ const formatItem = (item: any, isChatMode: boolean, filterVar: (payload: Var, se
|
||||
|
||||
return res
|
||||
}
|
||||
export const toNodeOutputVars = (nodes: any[], isChatMode: boolean, filterVar = (_payload: Var, _selector: ValueSelector) => true): NodeOutPutVar[] => {
|
||||
const res = nodes
|
||||
.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node.data.type))
|
||||
.map((node) => {
|
||||
return {
|
||||
...formatItem(node, isChatMode, filterVar),
|
||||
isStartNode: node.data.type === BlockEnum.Start,
|
||||
}
|
||||
})
|
||||
.filter(item => item.vars.length > 0)
|
||||
export const toNodeOutputVars = (
|
||||
nodes: any[],
|
||||
isChatMode: boolean,
|
||||
filterVar = (_payload: Var, _selector: ValueSelector) => true,
|
||||
environmentVariables: EnvironmentVariable[] = [],
|
||||
): NodeOutPutVar[] => {
|
||||
// ENV_NODE data format
|
||||
const ENV_NODE = {
|
||||
id: 'env',
|
||||
data: {
|
||||
title: 'ENVIRONMENT',
|
||||
type: 'env',
|
||||
envList: environmentVariables,
|
||||
},
|
||||
}
|
||||
const res = [
|
||||
...nodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node.data.type)),
|
||||
...(environmentVariables.length > 0 ? [ENV_NODE] : []),
|
||||
].map((node) => {
|
||||
return {
|
||||
...formatItem(node, isChatMode, filterVar),
|
||||
isStartNode: node.data.type === BlockEnum.Start,
|
||||
}
|
||||
}).filter(item => item.vars.length > 0)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -313,6 +345,7 @@ export const getVarType = ({
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant,
|
||||
environmentVariables = [],
|
||||
}:
|
||||
{
|
||||
valueSelector: ValueSelector
|
||||
@@ -321,11 +354,17 @@ export const getVarType = ({
|
||||
availableNodes: any[]
|
||||
isChatMode: boolean
|
||||
isConstant?: boolean
|
||||
environmentVariables?: EnvironmentVariable[]
|
||||
}): VarType => {
|
||||
if (isConstant)
|
||||
return VarType.string
|
||||
|
||||
const beforeNodesOutputVars = toNodeOutputVars(availableNodes, isChatMode)
|
||||
const beforeNodesOutputVars = toNodeOutputVars(
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
undefined,
|
||||
environmentVariables,
|
||||
)
|
||||
|
||||
const isIterationInnerVar = parentNode?.data.type === BlockEnum.Iteration
|
||||
if (isIterationItem) {
|
||||
@@ -346,6 +385,7 @@ export const getVarType = ({
|
||||
return VarType.number
|
||||
}
|
||||
const isSystem = isSystemVar(valueSelector)
|
||||
const isEnv = isENV(valueSelector)
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
@@ -358,7 +398,7 @@ export const getVarType = ({
|
||||
|
||||
let type: VarType = VarType.string
|
||||
let curr: any = targetVar.vars
|
||||
if (isSystem) {
|
||||
if (isSystem || isEnv) {
|
||||
return curr.find((v: any) => v.variable === (valueSelector as ValueSelector).join('.'))?.type
|
||||
}
|
||||
else {
|
||||
@@ -383,6 +423,7 @@ export const toNodeAvailableVars = ({
|
||||
t,
|
||||
beforeNodes,
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
filterVar,
|
||||
}: {
|
||||
parentNode?: Node | null
|
||||
@@ -390,9 +431,16 @@ export const toNodeAvailableVars = ({
|
||||
// to get those nodes output vars
|
||||
beforeNodes: Node[]
|
||||
isChatMode: boolean
|
||||
// env
|
||||
environmentVariables?: EnvironmentVariable[]
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
}): NodeOutPutVar[] => {
|
||||
const beforeNodesOutputVars = toNodeOutputVars(beforeNodes, isChatMode, filterVar)
|
||||
const beforeNodesOutputVars = toNodeOutputVars(
|
||||
beforeNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
environmentVariables,
|
||||
)
|
||||
const isInIteration = parentNode?.data.type === BlockEnum.Iteration
|
||||
if (isInIteration) {
|
||||
const iterationNode: any = parentNode
|
||||
@@ -402,6 +450,7 @@ export const toNodeAvailableVars = ({
|
||||
valueSelector: iterationNode?.data.iterator_selector || [],
|
||||
availableNodes: beforeNodes,
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
})
|
||||
const iterationVar = {
|
||||
nodeId: iterationNode?.id,
|
||||
@@ -493,7 +542,7 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
|
||||
case BlockEnum.IfElse: {
|
||||
res = (data as IfElseNodeType).conditions?.map((c) => {
|
||||
return c.variable_selector
|
||||
})
|
||||
}) || []
|
||||
break
|
||||
}
|
||||
case BlockEnum.Code: {
|
||||
|
||||
@@ -9,12 +9,13 @@ import {
|
||||
import produce from 'immer'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import VarReferencePopup from './var-reference-popup'
|
||||
import { getNodeInfoById, getVarType, isSystemVar, toNodeAvailableVars } from './utils'
|
||||
import { getNodeInfoById, isENV, isSystemVar } from './utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
@@ -71,6 +73,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const { getCurrentVariableType, getNodeAvailableVars } = useWorkflowVariables()
|
||||
const availableNodes = useMemo(() => {
|
||||
return passedInAvailableNodes || (onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranch(nodeId))
|
||||
}, [getBeforeNodesInSameBranch, getTreeLeafNodes, nodeId, onlyLeafNodeVar, passedInAvailableNodes])
|
||||
@@ -97,16 +100,15 @@ const VarReferencePicker: FC<Props> = ({
|
||||
if (availableVars)
|
||||
return availableVars
|
||||
|
||||
const vars = toNodeAvailableVars({
|
||||
const vars = getNodeAvailableVars({
|
||||
parentNode: iterationNode,
|
||||
t,
|
||||
beforeNodes: availableNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
})
|
||||
|
||||
return vars
|
||||
}, [iterationNode, availableNodes, isChatMode, filterVar, availableVars, t])
|
||||
}, [iterationNode, availableNodes, isChatMode, filterVar, availableVars, getNodeAvailableVars])
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
useEffect(() => {
|
||||
@@ -201,7 +203,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
onChange([], varKindType)
|
||||
}, [onChange, varKindType])
|
||||
|
||||
const type = getVarType({
|
||||
const type = getCurrentVariableType({
|
||||
parentNode: iterationNode,
|
||||
valueSelector: value as ValueSelector,
|
||||
availableNodes,
|
||||
@@ -209,6 +211,8 @@ const VarReferencePicker: FC<Props> = ({
|
||||
isConstant: !!isConstant,
|
||||
})
|
||||
|
||||
const isEnv = isENV(value as ValueSelector)
|
||||
|
||||
// 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 17 buff
|
||||
const availableWidth = triggerWidth - 56
|
||||
const [maxNodeNameWidth, maxVarNameWidth, maxTypeWidth] = (() => {
|
||||
@@ -276,7 +280,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
{hasValue
|
||||
? (
|
||||
<>
|
||||
{isShowNodeName && (
|
||||
{isShowNodeName && !isEnv && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
@@ -292,7 +296,8 @@ const VarReferencePicker: FC<Props> = ({
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||
<div className='ml-0.5 text-xs font-medium truncate' title={varName} style={{
|
||||
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('ml-0.5 text-xs font-medium truncate', isEnv && '!text-gray-900')} title={varName} style={{
|
||||
maxWidth: maxVarNameWidth,
|
||||
}}>{varName}</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { checkKeys } from '@/utils/var'
|
||||
|
||||
type ObjectChildrenProps = {
|
||||
@@ -48,6 +49,8 @@ const Item: FC<ItemProps> = ({
|
||||
itemWidth,
|
||||
}) => {
|
||||
const isObj = itemData.type === VarType.object && itemData.children && itemData.children.length > 0
|
||||
const isSys = itemData.variable.startsWith('sys.')
|
||||
const isEnv = itemData.variable.startsWith('env.')
|
||||
const itemRef = useRef(null)
|
||||
const [isItemHovering, setIsItemHovering] = useState(false)
|
||||
const _ = useHover(itemRef, {
|
||||
@@ -76,7 +79,7 @@ const Item: FC<ItemProps> = ({
|
||||
}, [isHovering])
|
||||
const handleChosen = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (itemData.variable.startsWith('sys.')) { // system variable
|
||||
if (isSys || isEnv) { // system variable or environment variable
|
||||
onChange([...objPath, ...itemData.variable.split('.')], itemData)
|
||||
}
|
||||
else {
|
||||
@@ -101,8 +104,9 @@ const Item: FC<ItemProps> = ({
|
||||
onClick={handleChosen}
|
||||
>
|
||||
<div className='flex items-center w-0 grow'>
|
||||
<Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />
|
||||
<div title={itemData.variable} className='ml-1 w-0 grow truncate text-[13px] font-normal text-gray-900'>{itemData.variable}</div>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div title={itemData.variable} className='ml-1 w-0 grow truncate text-[13px] font-normal text-gray-900'>{!isEnv ? itemData.variable : itemData.variable.replace('env.', '')}</div>
|
||||
</div>
|
||||
<div className='ml-1 shrink-0 text-xs font-normal text-gray-500 capitalize'>{itemData.type}</div>
|
||||
{isObj && (
|
||||
@@ -205,8 +209,9 @@ const VarReferenceVars: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [searchText, setSearchText] = useState('')
|
||||
|
||||
const filteredVars = vars.filter((v) => {
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.'))
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.'))
|
||||
return children.length > 0
|
||||
}).filter((node) => {
|
||||
if (!searchText)
|
||||
@@ -217,7 +222,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
})
|
||||
return children.length > 0
|
||||
}).map((node) => {
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.'))
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.'))
|
||||
if (searchText) {
|
||||
const searchTextLower = searchText.toLowerCase()
|
||||
if (!node.title.toLowerCase().includes(searchTextLower))
|
||||
@@ -229,6 +234,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
vars,
|
||||
}
|
||||
})
|
||||
|
||||
const [isFocus, {
|
||||
setFalse: setBlur,
|
||||
setTrue: setFocus,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useNodeInfo from './use-node-info'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
type Params = {
|
||||
onlyLeafNodeVar?: boolean
|
||||
@@ -18,9 +17,8 @@ const useAvailableVarList = (nodeId: string, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const { getNodeAvailableVars } = useWorkflowVariables()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const availableNodes = onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranch(nodeId)
|
||||
@@ -29,9 +27,8 @@ const useAvailableVarList = (nodeId: string, {
|
||||
parentNode: iterationNode,
|
||||
} = useNodeInfo(nodeId)
|
||||
|
||||
const availableVars = toNodeAvailableVars({
|
||||
const availableVars = getNodeAvailableVars({
|
||||
parentNode: iterationNode,
|
||||
t,
|
||||
beforeNodes: availableNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { getNodeInfoById, isSystemVar, toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { getNodeInfoById, isENV, isSystemVar, toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
||||
@@ -329,7 +329,7 @@ const useOneStepRun = <T>({
|
||||
if (!variables)
|
||||
return []
|
||||
|
||||
const varInputs = variables.map((item) => {
|
||||
const varInputs = variables.filter(item => !isENV(item.value_selector)).map((item) => {
|
||||
const originalVar = getVar(item.value_selector)
|
||||
if (!originalVar) {
|
||||
return {
|
||||
|
||||
@@ -161,7 +161,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||
})
|
||||
|
||||
const filterVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.string, VarType.number, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(varPayload.type)
|
||||
return [VarType.string, VarType.number, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
// single run
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import type { EndNodeType } from './types'
|
||||
import type { NodeProps, Variable } from '@/app/components/workflow/types'
|
||||
import { getVarType, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
@@ -18,6 +21,7 @@ const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
}) => {
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const availableNodes = getBeforeNodesInSameBranch(id)
|
||||
const { getCurrentVariableType } = useWorkflowVariables()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
@@ -39,8 +43,9 @@ const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
{filteredOutputs.map(({ value_selector }, index) => {
|
||||
const node = getNode(value_selector[0])
|
||||
const isSystem = isSystemVar(value_selector)
|
||||
const isEnv = isENV(value_selector)
|
||||
const varName = isSystem ? `sys.${value_selector[value_selector.length - 1]}` : value_selector[value_selector.length - 1]
|
||||
const varType = getVarType({
|
||||
const varType = getCurrentVariableType({
|
||||
valueSelector: value_selector,
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
@@ -48,17 +53,22 @@ const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
return (
|
||||
<div key={index} className='flex items-center h-6 justify-between bg-gray-100 rounded-md px-1 space-x-1 text-xs font-normal text-gray-700'>
|
||||
<div className='flex items-center text-xs font-medium text-gray-500'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={node?.data.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-w-[75px] truncate'>{node?.data.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
{!isEnv && (
|
||||
<>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={node?.data.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-w-[75px] truncate'>{node?.data.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<Variable02 className='w-3.5 h-3.5' />
|
||||
<div className='max-w-[50px] ml-0.5 text-xs font-medium truncate'>{varName}</div>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('max-w-[50px] ml-0.5 text-xs font-medium truncate', isEnv && '!max-w-[70px] text-gray-900')}>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-xs font-normal text-gray-700'>
|
||||
|
||||
@@ -42,7 +42,7 @@ const ApiInput: FC<Props> = ({
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number].includes(varPayload.type)
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import React, { useCallback } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import type { Authorization as AuthorizationPayloadType } from '../../types'
|
||||
import { APIType, AuthorizationType } from '../../types'
|
||||
import RadioGroup from './radio-group'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http.authorization'
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
payload: AuthorizationPayloadType
|
||||
onChange: (payload: AuthorizationPayloadType) => void
|
||||
isShow: boolean
|
||||
@@ -31,6 +37,7 @@ const Field = ({ title, isRequired, children }: { title: string; isRequired?: bo
|
||||
}
|
||||
|
||||
const Authorization: FC<Props> = ({
|
||||
nodeId,
|
||||
payload,
|
||||
onChange,
|
||||
isShow,
|
||||
@@ -38,6 +45,14 @@ const Authorization: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [isFocus, setIsFocus] = useState(false)
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
const [tempPayload, setTempPayload] = React.useState<AuthorizationPayloadType>(payload)
|
||||
const handleAuthTypeChange = useCallback((type: string) => {
|
||||
const newPayload = produce(tempPayload, (draft: AuthorizationPayloadType) => {
|
||||
@@ -80,6 +95,19 @@ const Authorization: FC<Props> = ({
|
||||
}
|
||||
}, [tempPayload, setTempPayload])
|
||||
|
||||
const handleAPIKeyChange = useCallback((str: string) => {
|
||||
const newPayload = produce(tempPayload, (draft: AuthorizationPayloadType) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {
|
||||
type: APIType.basic,
|
||||
api_key: '',
|
||||
}
|
||||
}
|
||||
draft.config.api_key = str
|
||||
})
|
||||
setTempPayload(newPayload)
|
||||
}, [tempPayload, setTempPayload])
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
onChange(tempPayload)
|
||||
onHide()
|
||||
@@ -128,12 +156,19 @@ const Authorization: FC<Props> = ({
|
||||
)}
|
||||
|
||||
<Field title={t(`${i18nPrefix}.api-key-title`)} isRequired>
|
||||
<input
|
||||
type='text'
|
||||
className='w-full h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
||||
value={tempPayload.config?.api_key || ''}
|
||||
onChange={handleAPIKeyOrHeaderChange('api_key')}
|
||||
/>
|
||||
<div className='flex'>
|
||||
<Input
|
||||
instanceId='http-api-key'
|
||||
className={cn(isFocus ? 'shadow-xs bg-gray-50 border-gray-300' : 'bg-gray-100 border-gray-100', 'w-0 grow rounded-lg px-3 py-[6px] border')}
|
||||
value={tempPayload.config?.api_key || ''}
|
||||
onChange={handleAPIKeyChange}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
onFocusChange={setIsFocus}
|
||||
placeholder={' '}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
/>
|
||||
</div>
|
||||
</Field>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -44,7 +44,7 @@ const EditBody: FC<Props> = ({
|
||||
const { availableVars, availableNodes } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number].includes(varPayload.type)
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ const InputItem: FC<Props> = ({
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number].includes(varPayload.type)
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
</div>
|
||||
{(isShowAuthorization && !readOnly) && (
|
||||
<AuthorizationModal
|
||||
nodeId={id}
|
||||
isShow
|
||||
onHide={hideAuthorization}
|
||||
payload={inputs.authorization}
|
||||
|
||||
@@ -103,7 +103,7 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const filterVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.string, VarType.number].includes(varPayload.type)
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
// single run
|
||||
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
isComparisonOperatorNeedTranslate,
|
||||
} from '../utils'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
type ConditionValueProps = {
|
||||
variableSelector: string[]
|
||||
@@ -32,7 +33,7 @@ const ConditionValue = ({
|
||||
return ''
|
||||
|
||||
return value.replace(/{{#([^#]*)#}}/g, (a, b) => {
|
||||
const arr = b.split('.')
|
||||
const arr: string[] = b.split('.')
|
||||
if (isSystemVar(arr))
|
||||
return `{{${b}}}`
|
||||
|
||||
@@ -42,7 +43,8 @@ const ConditionValue = ({
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-1 h-6 rounded-md bg-workflow-block-parma-bg'>
|
||||
<Variable02 className='shrink-0 mr-1 w-3.5 h-3.5 text-text-accent' />
|
||||
{!isENV(variableSelector) && <Variable02 className='shrink-0 mr-1 w-3.5 h-3.5 text-text-accent' />}
|
||||
{isENV(variableSelector) && <Env className='shrink-0 mr-1 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div
|
||||
className={cn(
|
||||
'shrink-0 truncate text-xs font-medium text-text-accent',
|
||||
|
||||
@@ -328,11 +328,11 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const filterInputVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.number, VarType.string].includes(varPayload.type)
|
||||
return [VarType.number, VarType.string, VarType.secret].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const filterVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.arrayObject, VarType.array, VarType.string].includes(varPayload.type)
|
||||
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const {
|
||||
|
||||
@@ -40,7 +40,7 @@ const InputVarList: FC<Props> = ({
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number].includes(varPayload.type)
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
const paramType = (type: string) => {
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
import { filterVar } from '../utils'
|
||||
import AddVariable from './add-variable'
|
||||
import NodeVariableItem from './node-variable-item'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
type GroupItem = {
|
||||
@@ -55,7 +55,7 @@ const NodeGroupItem = ({
|
||||
const group = item.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === item.targetHandleId)
|
||||
return group?.output_type || ''
|
||||
}, [item.variableAssignerNodeData, item.targetHandleId, groupEnabled])
|
||||
const availableVars = getAvailableVars(item.variableAssignerNodeId, item.targetHandleId, filterVar(outputType as VarType))
|
||||
const availableVars = getAvailableVars(item.variableAssignerNodeId, item.targetHandleId, filterVar(outputType as VarType), true)
|
||||
const showSelectionBorder = useMemo(() => {
|
||||
if (groupEnabled && enteringNodePayload?.nodeId === item.variableAssignerNodeId) {
|
||||
if (hoveringAssignVariableGroupId)
|
||||
@@ -123,12 +123,14 @@ const NodeGroupItem = ({
|
||||
{
|
||||
!!item.variables.length && item.variables.map((variable = [], index) => {
|
||||
const isSystem = isSystemVar(variable)
|
||||
const isEnv = isENV(variable)
|
||||
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
||||
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
|
||||
|
||||
return (
|
||||
<NodeVariableItem
|
||||
key={index}
|
||||
isEnv={isEnv}
|
||||
node={node as Node}
|
||||
varName={varName}
|
||||
showBorder={showSelectedBorder || showSelectionBorder}
|
||||
|
||||
@@ -3,15 +3,18 @@ import cn from '@/utils/classnames'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
type NodeVariableItemProps = {
|
||||
isEnv: boolean
|
||||
node: Node
|
||||
varName: string
|
||||
showBorder?: boolean
|
||||
}
|
||||
const NodeVariableItem = ({
|
||||
isEnv,
|
||||
node,
|
||||
varName,
|
||||
showBorder,
|
||||
@@ -21,19 +24,22 @@ const NodeVariableItem = ({
|
||||
'relative flex items-center mt-0.5 h-6 bg-gray-100 rounded-md px-1 text-xs font-normal text-gray-700',
|
||||
showBorder && '!bg-black/[0.02]',
|
||||
)}>
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={node?.data.type || BlockEnum.Start}
|
||||
/>
|
||||
{!isEnv && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={node?.data.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-w-[85px] truncate mx-0.5 text-xs font-medium text-gray-700' title={node?.data.title}>{node?.data.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
<div className='max-w-[85px] truncate mx-0.5 text-xs font-medium text-gray-700' title={node?.data.title}>{node?.data.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<Variable02 className='w-3.5 h-3.5' />
|
||||
<div className='max-w-[75px] truncate ml-0.5 text-xs font-medium' title={varName}>{varName}</div>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('max-w-[75px] truncate ml-0.5 text-xs font-medium', isEnv && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
useNodes,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import produce from 'immer'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '../../hooks'
|
||||
import type {
|
||||
Node,
|
||||
@@ -21,7 +21,6 @@ import type {
|
||||
VarGroupItem,
|
||||
VariableAssignerNodeType,
|
||||
} from './types'
|
||||
import { toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
export const useVariableAssigner = () => {
|
||||
const store = useStoreApi()
|
||||
@@ -123,11 +122,11 @@ export const useVariableAssigner = () => {
|
||||
}
|
||||
|
||||
export const useGetAvailableVars = () => {
|
||||
const { t } = useTranslation()
|
||||
const nodes: Node[] = useNodes()
|
||||
const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
|
||||
const { getNodeAvailableVars } = useWorkflowVariables()
|
||||
const isChatMode = useIsChatMode()
|
||||
const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean) => {
|
||||
const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean, hideEnv = false) => {
|
||||
const availableNodes: Node[] = []
|
||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||
|
||||
@@ -138,14 +137,28 @@ export const useGetAvailableVars = () => {
|
||||
availableNodes.push(...beforeNodes)
|
||||
const parentNode = nodes.find(node => node.id === currentNode.parentId)
|
||||
|
||||
return toNodeAvailableVars({
|
||||
if (hideEnv) {
|
||||
return getNodeAvailableVars({
|
||||
parentNode,
|
||||
beforeNodes: uniqBy(availableNodes, 'id').filter(node => node.id !== nodeId),
|
||||
isChatMode,
|
||||
hideEnv,
|
||||
filterVar,
|
||||
})
|
||||
.map(node => ({
|
||||
...node,
|
||||
vars: node.isStartNode ? node.vars.filter(v => !v.variable.startsWith('sys.')) : node.vars,
|
||||
}))
|
||||
.filter(item => item.vars.length > 0)
|
||||
}
|
||||
|
||||
return getNodeAvailableVars({
|
||||
parentNode,
|
||||
t,
|
||||
beforeNodes: uniqBy(availableNodes, 'id').filter(node => node.id !== nodeId),
|
||||
isChatMode,
|
||||
filterVar,
|
||||
})
|
||||
}, [nodes, t, isChatMode, getBeforeNodesInSameBranchIncludeParent])
|
||||
}, [nodes, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode])
|
||||
|
||||
return getAvailableVars
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
|
||||
}}
|
||||
onChange={handleListOrTypeChange}
|
||||
groupEnabled={false}
|
||||
availableVars={getAvailableVars(id, 'target', filterVar(inputs.output_type))}
|
||||
availableVars={getAvailableVars(id, 'target', filterVar(inputs.output_type), true)}
|
||||
/>
|
||||
)
|
||||
: (<div>
|
||||
@@ -67,7 +67,7 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
|
||||
canRemove={!readOnly && inputs.advanced_settings?.groups.length > 1}
|
||||
onRemove={handleGroupRemoved(item.groupId)}
|
||||
onGroupNameChange={handleVarGroupNameChange(item.groupId)}
|
||||
availableVars={getAvailableVars(id, item.groupId, filterVar(item.output_type))}
|
||||
availableVars={getAvailableVars(id, item.groupId, filterVar(item.output_type), true)}
|
||||
/>
|
||||
{index !== inputs.advanced_settings?.groups.length - 1 && <Split className='my-4' />}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@ const PanelContextmenu = () => {
|
||||
const { handlePaneContextmenuCancel } = usePanelInteractions()
|
||||
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
||||
const { handleAddNote } = useOperator()
|
||||
const { handleExportDSL } = useDSL()
|
||||
const { exportCheck } = useDSL()
|
||||
|
||||
useClickAway(() => {
|
||||
handlePaneContextmenuCancel()
|
||||
@@ -105,7 +105,7 @@ const PanelContextmenu = () => {
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
onClick={() => handleExportDSL()}
|
||||
onClick={() => exportCheck()}
|
||||
>
|
||||
{t('app.export')}
|
||||
</div>
|
||||
|
||||
210
web/app/components/workflow/panel/env-panel/index.tsx
Normal file
210
web/app/components/workflow/panel/env-panel/index.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import {
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { RiCloseLine, RiDeleteBinLine, RiEditLine, RiLock2Line } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import VariableTrigger from '@/app/components/workflow/panel/env-panel/variable-trigger'
|
||||
import type {
|
||||
EnvironmentVariable,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||
|
||||
const EnvPanel = () => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
|
||||
const envList = useStore(s => s.environmentVariables) as EnvironmentVariable[]
|
||||
const envSecrets = useStore(s => s.envSecrets)
|
||||
const updateEnvList = useStore(s => s.setEnvironmentVariables)
|
||||
const setEnvSecrets = useStore(s => s.setEnvSecrets)
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const [showVariableModal, setShowVariableModal] = useState(false)
|
||||
const [currentVar, setCurrentVar] = useState<EnvironmentVariable>()
|
||||
|
||||
const [showRemoveVarConfirm, setShowRemoveConfirm] = useState(false)
|
||||
const [cacheForDelete, setCacheForDelete] = useState<EnvironmentVariable>()
|
||||
|
||||
const formatSecret = (s: string) => {
|
||||
return s.length > 8 ? `${s.slice(0, 6)}************${s.slice(-2)}` : '********************'
|
||||
}
|
||||
|
||||
const getEffectedNodes = useCallback((env: EnvironmentVariable) => {
|
||||
const { getNodes } = store.getState()
|
||||
const allNodes = getNodes()
|
||||
return findUsedVarNodes(
|
||||
['env', env.name],
|
||||
allNodes,
|
||||
)
|
||||
}, [store])
|
||||
|
||||
const removeUsedVarInNodes = useCallback((env: EnvironmentVariable) => {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const effectedNodes = getEffectedNodes(env)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['env', env.name], [])
|
||||
|
||||
return node
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [getEffectedNodes, store])
|
||||
|
||||
const handleDelete = useCallback((env: EnvironmentVariable) => {
|
||||
removeUsedVarInNodes(env)
|
||||
updateEnvList(envList.filter(e => e.id !== env.id))
|
||||
setCacheForDelete(undefined)
|
||||
setShowRemoveConfirm(false)
|
||||
doSyncWorkflowDraft()
|
||||
if (env.value_type === 'secret') {
|
||||
const newMap = { ...envSecrets }
|
||||
delete newMap[env.id]
|
||||
setEnvSecrets(newMap)
|
||||
}
|
||||
}, [doSyncWorkflowDraft, envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList])
|
||||
|
||||
const deleteCheck = useCallback((env: EnvironmentVariable) => {
|
||||
const effectedNodes = getEffectedNodes(env)
|
||||
if (effectedNodes.length > 0) {
|
||||
setCacheForDelete(env)
|
||||
setShowRemoveConfirm(true)
|
||||
}
|
||||
else {
|
||||
handleDelete(env)
|
||||
}
|
||||
}, [getEffectedNodes, handleDelete])
|
||||
|
||||
const handleSave = useCallback(async (env: EnvironmentVariable) => {
|
||||
// add env
|
||||
let newEnv = env
|
||||
if (!currentVar) {
|
||||
if (env.value_type === 'secret') {
|
||||
setEnvSecrets({
|
||||
...envSecrets,
|
||||
[env.id]: formatSecret(env.value),
|
||||
})
|
||||
}
|
||||
const newList = [env, ...envList]
|
||||
updateEnvList(newList)
|
||||
await doSyncWorkflowDraft()
|
||||
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
||||
return
|
||||
}
|
||||
else if (currentVar.value_type === 'secret') {
|
||||
if (env.value_type === 'secret') {
|
||||
if (envSecrets[currentVar.id] !== env.value) {
|
||||
newEnv = env
|
||||
setEnvSecrets({
|
||||
...envSecrets,
|
||||
[env.id]: formatSecret(env.value),
|
||||
})
|
||||
}
|
||||
else {
|
||||
newEnv = { ...env, value: '[__HIDDEN__]' }
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (env.value_type === 'secret') {
|
||||
newEnv = env
|
||||
setEnvSecrets({
|
||||
...envSecrets,
|
||||
[env.id]: formatSecret(env.value),
|
||||
})
|
||||
}
|
||||
}
|
||||
const newList = envList.map(e => e.id === currentVar.id ? newEnv : e)
|
||||
updateEnvList(newList)
|
||||
// side effects of rename env
|
||||
if (currentVar.name !== env.name) {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const effectedNodes = getEffectedNodes(currentVar)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['env', currentVar.name], ['env', env.name])
|
||||
|
||||
return node
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}
|
||||
await doSyncWorkflowDraft()
|
||||
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
||||
}, [currentVar, doSyncWorkflowDraft, envList, envSecrets, getEffectedNodes, setEnvSecrets, store, updateEnvList])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex flex-col w-[400px] bg-components-panel-bg-alt rounded-l-2xl h-full border border-components-panel-border',
|
||||
)}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between p-4 pb-0 text-text-primary system-xl-semibold'>
|
||||
{t('workflow.env.envPanelTitle')}
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
className='flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={() => setShowEnvPanel(false)}
|
||||
>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='shrink-0 py-1 px-4 system-sm-regular text-text-tertiary'>{t('workflow.env.envDescription')}</div>
|
||||
<div className='shrink-0 px-4 pt-2 pb-3'>
|
||||
<VariableTrigger
|
||||
open={showVariableModal}
|
||||
setOpen={setShowVariableModal}
|
||||
env={currentVar}
|
||||
onSave={handleSave}
|
||||
onClose={() => setCurrentVar(undefined)}
|
||||
/>
|
||||
</div>
|
||||
<div className='grow px-4 rounded-b-2xl overflow-y-auto'>
|
||||
{envList.map(env => (
|
||||
<div
|
||||
key={env.name}
|
||||
className='mb-1 px-2.5 py-2 bg-components-panel-on-panel-item-bg radius-md border-[0.5px] border-components-panel-border-subtle shadow-xs'
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='grow flex gap-1 items-center'>
|
||||
<Env className='w-4 h-4 text-util-colors-violet-violet-600' />
|
||||
<div className='text-text-primary system-sm-medium'>{env.name}</div>
|
||||
<div className='text-text-tertiary system-xs-medium'>{capitalize(env.value_type)}</div>
|
||||
{env.value_type === 'secret' && <RiLock2Line className='w-3 h-3 text-text-tertiary' />}
|
||||
</div>
|
||||
<div className='shrink-0 flex gap-1 items-center text-text-tertiary'>
|
||||
<div className='p-1 radius-md cursor-pointer hover:bg-state-base-hover hover:text-text-secondary'>
|
||||
<RiEditLine className='w-4 h-4' onClick={() => {
|
||||
setCurrentVar(env)
|
||||
setShowVariableModal(true)
|
||||
}}/>
|
||||
</div>
|
||||
<div className='p-1 radius-md cursor-pointer hover:bg-state-destructive-hover hover:text-text-destructive'>
|
||||
<RiDeleteBinLine className='w-4 h-4' onClick={() => deleteCheck(env)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular truncate'>{env.value_type === 'secret' ? envSecrets[env.id] : env.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<RemoveEffectVarConfirm
|
||||
isShow={showRemoveVarConfirm}
|
||||
onCancel={() => setShowRemoveConfirm(false)}
|
||||
onConfirm={() => cacheForDelete && handleDelete(cacheForDelete)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(EnvPanel)
|
||||
151
web/app/components/workflow/panel/env-panel/variable-modal.tsx
Normal file
151
web/app/components/workflow/panel/env-panel/variable-modal.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import { RiCloseLine, RiQuestionLine } from '@remixicon/react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import Button from '@/app/components/base/button'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type ModalPropsType = {
|
||||
env?: EnvironmentVariable
|
||||
onClose: () => void
|
||||
onSave: (env: EnvironmentVariable) => void
|
||||
}
|
||||
const VariableModal = ({
|
||||
env,
|
||||
onClose,
|
||||
onSave,
|
||||
}: ModalPropsType) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const envList = useStore(s => s.environmentVariables)
|
||||
const envSecrets = useStore(s => s.envSecrets)
|
||||
const [type, setType] = React.useState<'string' | 'number' | 'secret'>('string')
|
||||
const [name, setName] = React.useState('')
|
||||
const [value, setValue] = React.useState<any>()
|
||||
|
||||
const handleNameChange = (v: string) => {
|
||||
if (!v)
|
||||
return setName('')
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(v))
|
||||
return notify({ type: 'error', message: 'name is can only contain letters, numbers and underscores' })
|
||||
if (/^[0-9]/.test(v))
|
||||
return notify({ type: 'error', message: 'name can not start with a number' })
|
||||
setName(v)
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
if (!name)
|
||||
return notify({ type: 'error', message: 'name can not be empty' })
|
||||
if (!value)
|
||||
return notify({ type: 'error', message: 'value can not be empty' })
|
||||
if (!env && envList.some(env => env.name === name))
|
||||
return notify({ type: 'error', message: 'name is existed' })
|
||||
onSave({
|
||||
id: env ? env.id : uuid4(),
|
||||
value_type: type,
|
||||
name,
|
||||
value: type === 'number' ? Number(value) : value,
|
||||
})
|
||||
onClose()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (env) {
|
||||
setType(env.value_type)
|
||||
setName(env.name)
|
||||
setValue(env.value_type === 'secret' ? envSecrets[env.id] : env.value)
|
||||
}
|
||||
}, [env, envSecrets])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('flex flex-col w-[360px] bg-components-panel-bg rounded-2xl h-full border-[0.5px] border-components-panel-border shadow-2xl')}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between mb-3 p-4 pb-0 text-text-primary system-xl-semibold'>
|
||||
{!env ? t('workflow.env.modal.title') : t('workflow.env.modal.editTitle')}
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
className='flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={onClose}
|
||||
>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4 py-2'>
|
||||
{/* type */}
|
||||
<div className='mb-4'>
|
||||
<div className='mb-1 text-text-secondary system-sm-semibold'>{t('workflow.env.modal.type')}</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className={cn(
|
||||
'w-[106px] flex items-center justify-center p-2 radius-md bg-components-option-card-option-bg border border-components-option-card-option-border text-text-secondary system-sm-regular cursor-pointer hover:shadow-xs hover:bg-components-option-card-option-bg-hover hover:border-components-option-card-option-border-hover',
|
||||
type === 'string' && 'text-text-primary system-sm-medium border-[1.5px] shadow-xs bg-components-option-card-option-selected-bg border-components-option-card-option-selected-border hover:border-components-option-card-option-selected-border',
|
||||
)} onClick={() => setType('string')}>String</div>
|
||||
<div className={cn(
|
||||
'w-[106px] flex items-center justify-center p-2 radius-md bg-components-option-card-option-bg border border-components-option-card-option-border text-text-secondary system-sm-regular cursor-pointer hover:shadow-xs hover:bg-components-option-card-option-bg-hover hover:border-components-option-card-option-border-hover',
|
||||
type === 'number' && 'text-text-primary font-medium border-[1.5px] shadow-xs bg-components-option-card-option-selected-bg border-components-option-card-option-selected-border hover:border-components-option-card-option-selected-border',
|
||||
)} onClick={() => {
|
||||
setType('number')
|
||||
if (!(/^[0-9]$/).test(value))
|
||||
setValue('')
|
||||
}}>Number</div>
|
||||
<div className={cn(
|
||||
'w-[106px] flex items-center justify-center p-2 radius-md bg-components-option-card-option-bg border border-components-option-card-option-border text-text-secondary system-sm-regular cursor-pointer hover:shadow-xs hover:bg-components-option-card-option-bg-hover hover:border-components-option-card-option-border-hover',
|
||||
type === 'secret' && 'text-text-primary font-medium border-[1.5px] shadow-xs bg-components-option-card-option-selected-bg border-components-option-card-option-selected-border hover:border-components-option-card-option-selected-border',
|
||||
)} onClick={() => setType('secret')}>
|
||||
<span>Secret</span>
|
||||
<TooltipPlus popupContent={
|
||||
<div className='w-[240px]'>
|
||||
{t('workflow.env.modal.secretTip')}
|
||||
</div>
|
||||
}>
|
||||
<RiQuestionLine className='ml-0.5 w-[14px] h-[14px] text-text-quaternary' />
|
||||
</TooltipPlus>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* name */}
|
||||
<div className='mb-4'>
|
||||
<div className='mb-1 text-text-secondary system-sm-semibold'>{t('workflow.env.modal.name')}</div>
|
||||
<div className='flex'>
|
||||
<input
|
||||
tabIndex={0}
|
||||
className='block px-3 w-full h-9 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.env.modal.namePlaceholder') || ''}
|
||||
value={name}
|
||||
onChange={e => handleNameChange(e.target.value)}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* value */}
|
||||
<div className=''>
|
||||
<div className='mb-1 text-text-secondary system-sm-semibold'>{t('workflow.env.modal.value')}</div>
|
||||
<div className='flex'>
|
||||
<input
|
||||
tabIndex={0}
|
||||
className='block px-3 w-full h-9 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.env.modal.valuePlaceholder') || ''}
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
type={type !== 'number' ? 'text' : 'number'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-4 pt-2 flex flex-row-reverse rounded-b-2xl'>
|
||||
<div className='flex gap-2'>
|
||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariableModal
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import VariableModal from '@/app/components/workflow/panel/env-panel/variable-modal'
|
||||
// import cn from '@/utils/classnames'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
setOpen: (value: React.SetStateAction<boolean>) => void
|
||||
env?: EnvironmentVariable
|
||||
onClose: () => void
|
||||
onSave: (env: EnvironmentVariable) => void
|
||||
}
|
||||
|
||||
const VariableTrigger = ({
|
||||
open,
|
||||
setOpen,
|
||||
env,
|
||||
onClose,
|
||||
onSave,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={() => {
|
||||
setOpen(v => !v)
|
||||
open && onClose()
|
||||
}}
|
||||
placement='left-start'
|
||||
offset={{
|
||||
mainAxis: 8,
|
||||
alignmentAxis: -104,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => {
|
||||
setOpen(v => !v)
|
||||
open && onClose()
|
||||
}}>
|
||||
<Button variant='primary'>
|
||||
<RiAddLine className='mr-1 w-4 h-4' />
|
||||
<span className='system-sm-medium'>{t('workflow.env.envPanelButton')}</span>
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<VariableModal
|
||||
env={env}
|
||||
onSave={onSave}
|
||||
onClose={() => {
|
||||
onClose()
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariableTrigger
|
||||
@@ -13,6 +13,7 @@ import DebugAndPreview from './debug-and-preview'
|
||||
import Record from './record'
|
||||
import WorkflowPreview from './workflow-preview'
|
||||
import ChatRecord from './chat-record'
|
||||
import EnvPanel from './env-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import MessageLogModal from '@/app/components/base/message-log-modal'
|
||||
@@ -23,6 +24,7 @@ const Panel: FC = () => {
|
||||
const selectedNode = nodes.find(node => node.data.selected)
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
const showEnvPanel = useStore(s => s.showEnvPanel)
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const {
|
||||
enableShortcuts,
|
||||
@@ -39,9 +41,7 @@ const Panel: FC = () => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
className={cn(
|
||||
'absolute top-14 right-0 bottom-2 flex z-10 outline-none',
|
||||
)}
|
||||
className={cn('absolute top-14 right-0 bottom-2 flex z-10 outline-none')}
|
||||
onFocus={disableShortcuts}
|
||||
onBlur={enableShortcuts}
|
||||
key={`${isRestoring}`}
|
||||
@@ -85,6 +85,11 @@ const Panel: FC = () => {
|
||||
<WorkflowPreview />
|
||||
)
|
||||
}
|
||||
{
|
||||
showEnvPanel && (
|
||||
<EnvPanel />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,11 +30,7 @@ import cn from '@/utils/classnames'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
const WorkflowPreview = ({
|
||||
onShowIterationDetail,
|
||||
}: {
|
||||
onShowIterationDetail: (detail: NodeTracing[][]) => void
|
||||
}) => {
|
||||
const WorkflowPreview = () => {
|
||||
const { t } = useTranslation()
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
import type { VariableAssignerNodeType } from './nodes/variable-assigner/types'
|
||||
import type {
|
||||
Edge,
|
||||
EnvironmentVariable,
|
||||
HistoryWorkflowData,
|
||||
Node,
|
||||
RunFile,
|
||||
@@ -59,6 +60,7 @@ type Shape = {
|
||||
edges: Edge[]
|
||||
viewport: Viewport
|
||||
features: Record<string, any>
|
||||
environmentVariables: EnvironmentVariable[]
|
||||
}
|
||||
setBackupDraft: (backupDraft?: Shape['backupDraft']) => void
|
||||
notInitialWorkflow: boolean
|
||||
@@ -82,6 +84,12 @@ type Shape = {
|
||||
setShortcutsDisabled: (shortcutsDisabled: boolean) => void
|
||||
showDebugAndPreviewPanel: boolean
|
||||
setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void
|
||||
showEnvPanel: boolean
|
||||
setShowEnvPanel: (showEnvPanel: boolean) => void
|
||||
environmentVariables: EnvironmentVariable[]
|
||||
setEnvironmentVariables: (environmentVariables: EnvironmentVariable[]) => void
|
||||
envSecrets: Record<string, string>
|
||||
setEnvSecrets: (envSecrets: Record<string, string>) => void
|
||||
selection: null | { x1: number; y1: number; x2: number; y2: number }
|
||||
setSelection: (selection: Shape['selection']) => void
|
||||
bundleNodeSize: { width: number; height: number } | null
|
||||
@@ -190,6 +198,12 @@ export const createWorkflowStore = () => {
|
||||
setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })),
|
||||
showDebugAndPreviewPanel: false,
|
||||
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
|
||||
showEnvPanel: false,
|
||||
setShowEnvPanel: showEnvPanel => set(() => ({ showEnvPanel })),
|
||||
environmentVariables: [],
|
||||
setEnvironmentVariables: environmentVariables => set(() => ({ environmentVariables })),
|
||||
envSecrets: {},
|
||||
setEnvSecrets: envSecrets => set(() => ({ envSecrets })),
|
||||
selection: null,
|
||||
setSelection: selection => set(() => ({ selection })),
|
||||
bundleNodeSize: null,
|
||||
|
||||
@@ -102,6 +102,13 @@ export type Variable = {
|
||||
isParagraph?: boolean
|
||||
}
|
||||
|
||||
export type EnvironmentVariable = {
|
||||
id: string
|
||||
name: string
|
||||
value: any
|
||||
value_type: 'string' | 'number' | 'secret'
|
||||
}
|
||||
|
||||
export type VariableWithValue = {
|
||||
key: string
|
||||
value: string
|
||||
@@ -183,6 +190,7 @@ export type Memory = {
|
||||
export enum VarType {
|
||||
string = 'string',
|
||||
number = 'number',
|
||||
secret = 'secret',
|
||||
boolean = 'boolean',
|
||||
object = 'object',
|
||||
array = 'array',
|
||||
|
||||
Reference in New Issue
Block a user