mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
Merge branch 'feat/collaboration' into feat/collaboration2
This commit is contained in:
@ -3,9 +3,6 @@ import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { RiBookOpenLine, RiCloseLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
@ -19,30 +16,24 @@ import type {
|
||||
ConversationVariable,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import cn from '@/utils/classnames'
|
||||
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||
import { updateConversationVariables } from '@/service/workflow'
|
||||
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
|
||||
|
||||
const ChatVariablePanel = () => {
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
const store = useStoreApi()
|
||||
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
|
||||
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
|
||||
const updateChatVarList = useStore(s => s.setConversationVariables)
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const appId = useStore(s => s.appId) as string
|
||||
const {
|
||||
invalidateConversationVarValues,
|
||||
} = useInspectVarsCrud()
|
||||
const handleVarChanged = useCallback(() => {
|
||||
doSyncWorkflowDraft(false, {
|
||||
onSuccess() {
|
||||
invalidateConversationVarValues()
|
||||
},
|
||||
})
|
||||
}, [doSyncWorkflowDraft, invalidateConversationVarValues])
|
||||
|
||||
const [showTip, setShowTip] = useState(true)
|
||||
const [showVariableModal, setShowVariableModal] = useState(false)
|
||||
@ -50,40 +41,63 @@ const ChatVariablePanel = () => {
|
||||
|
||||
const [showRemoveVarConfirm, setShowRemoveConfirm] = useState(false)
|
||||
const [cacheForDelete, setCacheForDelete] = useState<ConversationVariable>()
|
||||
const collaborativeWorkflow = useCollaborativeWorkflow()
|
||||
|
||||
const getEffectedNodes = useCallback((chatVar: ConversationVariable) => {
|
||||
const { getNodes } = store.getState()
|
||||
const allNodes = getNodes()
|
||||
const { nodes: allNodes } = collaborativeWorkflow.getState()
|
||||
return findUsedVarNodes(
|
||||
['conversation', chatVar.name],
|
||||
allNodes,
|
||||
)
|
||||
}, [store])
|
||||
}, [collaborativeWorkflow])
|
||||
|
||||
const removeUsedVarInNodes = useCallback((chatVar: ConversationVariable) => {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||
const effectedNodes = getEffectedNodes(chatVar)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
const newNodes = nodes.map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['conversation', chatVar.name], [])
|
||||
|
||||
return node
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [getEffectedNodes, store])
|
||||
}, [getEffectedNodes, collaborativeWorkflow])
|
||||
|
||||
const handleEdit = (chatVar: ConversationVariable) => {
|
||||
setCurrentVar(chatVar)
|
||||
setShowVariableModal(true)
|
||||
}
|
||||
|
||||
const handleDelete = useCallback((chatVar: ConversationVariable) => {
|
||||
const handleDelete = useCallback(async (chatVar: ConversationVariable) => {
|
||||
removeUsedVarInNodes(chatVar)
|
||||
updateChatVarList(varList.filter(v => v.id !== chatVar.id))
|
||||
const newVarList = varList.filter(v => v.id !== chatVar.id)
|
||||
updateChatVarList(newVarList)
|
||||
setCacheForDelete(undefined)
|
||||
setShowRemoveConfirm(false)
|
||||
handleVarChanged()
|
||||
}, [handleVarChanged, removeUsedVarInNodes, updateChatVarList, varList])
|
||||
|
||||
// Use new dedicated conversation variables API instead of workflow draft sync
|
||||
try {
|
||||
await updateConversationVariables({
|
||||
appId,
|
||||
conversationVariables: newVarList,
|
||||
})
|
||||
|
||||
// Emit update event to other connected clients
|
||||
const socket = webSocketClient.getSocket(appId)
|
||||
if (socket) {
|
||||
socket.emit('collaboration_event', {
|
||||
type: 'vars_and_features_update',
|
||||
})
|
||||
}
|
||||
|
||||
invalidateConversationVarValues()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to update conversation variables:', error)
|
||||
// Revert local state on error
|
||||
updateChatVarList(varList)
|
||||
}
|
||||
}, [removeUsedVarInNodes, updateChatVarList, varList, appId, invalidateConversationVarValues])
|
||||
|
||||
const deleteCheck = useCallback((chatVar: ConversationVariable) => {
|
||||
const effectedNodes = getEffectedNodes(chatVar)
|
||||
@ -97,21 +111,46 @@ const ChatVariablePanel = () => {
|
||||
}, [getEffectedNodes, handleDelete])
|
||||
|
||||
const handleSave = useCallback(async (chatVar: ConversationVariable) => {
|
||||
// add chatVar
|
||||
let newList: ConversationVariable[]
|
||||
|
||||
if (!currentVar) {
|
||||
const newList = [chatVar, ...varList]
|
||||
// Adding new conversation variable
|
||||
newList = [chatVar, ...varList]
|
||||
updateChatVarList(newList)
|
||||
handleVarChanged()
|
||||
|
||||
// Use new dedicated conversation variables API
|
||||
try {
|
||||
await updateConversationVariables({
|
||||
appId,
|
||||
conversationVariables: newList,
|
||||
})
|
||||
|
||||
const socket = webSocketClient.getSocket(appId)
|
||||
if (socket) {
|
||||
socket.emit('collaboration_event', {
|
||||
type: 'vars_and_features_update',
|
||||
})
|
||||
}
|
||||
|
||||
invalidateConversationVarValues()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to update conversation variables:', error)
|
||||
// Revert local state on error
|
||||
updateChatVarList(varList)
|
||||
}
|
||||
return
|
||||
}
|
||||
// edit chatVar
|
||||
const newList = varList.map(v => v.id === currentVar.id ? chatVar : v)
|
||||
|
||||
// Updating existing conversation variable
|
||||
newList = varList.map(v => v.id === currentVar.id ? chatVar : v)
|
||||
updateChatVarList(newList)
|
||||
// side effects of rename env
|
||||
|
||||
// side effects of rename conversation variable
|
||||
if (currentVar.name !== chatVar.name) {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||
const effectedNodes = getEffectedNodes(currentVar)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
const newNodes = nodes.map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['conversation', currentVar.name], ['conversation', chatVar.name])
|
||||
|
||||
@ -119,8 +158,29 @@ const ChatVariablePanel = () => {
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}
|
||||
handleVarChanged()
|
||||
}, [currentVar, getEffectedNodes, handleVarChanged, store, updateChatVarList, varList])
|
||||
|
||||
// Use new dedicated conversation variables API
|
||||
try {
|
||||
await updateConversationVariables({
|
||||
appId,
|
||||
conversationVariables: newList,
|
||||
})
|
||||
|
||||
const socket = webSocketClient.getSocket(appId)
|
||||
if (socket) {
|
||||
socket.emit('collaboration_event', {
|
||||
type: 'vars_and_features_update',
|
||||
})
|
||||
}
|
||||
|
||||
invalidateConversationVarValues()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to update conversation variables:', error)
|
||||
// Revert local state on error
|
||||
updateChatVarList(varList)
|
||||
}
|
||||
}, [currentVar, getEffectedNodes, collaborativeWorkflow, updateChatVarList, varList, appId, invalidateConversationVarValues])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
197
web/app/components/workflow/panel/comments-panel/index.tsx
Normal file
197
web/app/components/workflow/panel/comments-panel/index.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { RiCheckLine, RiCheckboxCircleFill, RiCheckboxCircleLine, RiCloseLine, RiFilter3Line } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import type { WorkflowCommentList } from '@/service/workflow-comment'
|
||||
import { useWorkflowComment } from '@/app/components/workflow/hooks/use-workflow-comment'
|
||||
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
|
||||
import cn from '@/utils/classnames'
|
||||
import { ControlMode } from '@/app/components/workflow/types'
|
||||
import { resolveWorkflowComment } from '@/service/workflow-comment'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
|
||||
const CommentsPanel = () => {
|
||||
const { t } = useTranslation()
|
||||
const activeCommentId = useStore(s => s.activeCommentId)
|
||||
const setActiveCommentId = useStore(s => s.setActiveCommentId)
|
||||
const setControlMode = useStore(s => s.setControlMode)
|
||||
const { comments, loading, loadComments, handleCommentIconClick } = useWorkflowComment()
|
||||
const params = useParams()
|
||||
const appId = params.appId as string
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
|
||||
const [showOnlyMine, setShowOnlyMine] = useState(false)
|
||||
const [showOnlyUnresolved, setShowOnlyUnresolved] = useState(false)
|
||||
const [showFilter, setShowFilter] = useState(false)
|
||||
|
||||
const handleSelect = useCallback((comment: WorkflowCommentList) => {
|
||||
handleCommentIconClick(comment)
|
||||
}, [handleCommentIconClick])
|
||||
|
||||
const { userProfile } = useAppContext()
|
||||
|
||||
const filteredSorted = useMemo(() => {
|
||||
let data = comments
|
||||
if (showOnlyUnresolved)
|
||||
data = data.filter(c => !c.resolved)
|
||||
if (showOnlyMine)
|
||||
data = data.filter(c => c.created_by === userProfile?.id)
|
||||
return data
|
||||
}, [comments, showOnlyMine, showOnlyUnresolved, userProfile?.id])
|
||||
|
||||
const handleResolve = useCallback(async (comment: WorkflowCommentList) => {
|
||||
if (comment.resolved) return
|
||||
if (!appId) return
|
||||
try {
|
||||
await resolveWorkflowComment(appId, comment.id)
|
||||
|
||||
collaborationManager.emitCommentsUpdate(appId)
|
||||
|
||||
await loadComments()
|
||||
setActiveCommentId(comment.id)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Resolve comment failed', e)
|
||||
}
|
||||
}, [appId, loadComments, setActiveCommentId])
|
||||
|
||||
const hasActiveFilter = showOnlyMine || !showOnlyUnresolved
|
||||
|
||||
return (
|
||||
<div className={cn('relative flex h-full w-[420px] flex-col rounded-l-2xl border border-components-panel-border bg-components-panel-bg')}>
|
||||
<div className='flex items-center justify-between p-4 pb-2'>
|
||||
<div className='system-xl-semibold font-semibold leading-6 text-text-primary'>{t('workflow.comments.panelTitle')}</div>
|
||||
<div className='relative flex items-center gap-2'>
|
||||
<button
|
||||
className={cn(
|
||||
'group flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-accent-active',
|
||||
hasActiveFilter && 'bg-state-accent-active',
|
||||
)}
|
||||
aria-label='Filter comments'
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
>
|
||||
<RiFilter3Line className={cn(
|
||||
'h-4 w-4 text-text-secondary group-hover:text-text-accent',
|
||||
hasActiveFilter && 'text-text-accent',
|
||||
)} />
|
||||
</button>
|
||||
{showFilter && (
|
||||
<div className='absolute right-10 top-9 z-50 min-w-[184px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[10px]'>
|
||||
<button
|
||||
className={cn('flex w-full items-center justify-between rounded-md px-2 py-2 text-left text-sm hover:bg-state-base-hover', !showOnlyMine && 'bg-components-panel-on-panel-item-bg')}
|
||||
onClick={() => {
|
||||
setShowOnlyMine(false)
|
||||
setShowFilter(false)
|
||||
}}
|
||||
>
|
||||
<span className='text-text-secondary'>All</span>
|
||||
{!showOnlyMine && <RiCheckLine className='h-4 w-4 text-primary-600' />}
|
||||
</button>
|
||||
<button
|
||||
className={cn('mt-1 flex w-full items-center justify-between rounded-md px-2 py-2 text-left text-sm hover:bg-state-base-hover', showOnlyMine && 'bg-components-panel-on-panel-item-bg')}
|
||||
onClick={() => {
|
||||
setShowOnlyMine(true)
|
||||
setShowFilter(false)
|
||||
}}
|
||||
>
|
||||
<span className='text-text-secondary'>Only your threads</span>
|
||||
{showOnlyMine && <RiCheckLine className='h-4 w-4 text-primary-600' />}
|
||||
</button>
|
||||
<Divider type='horizontal' className='my-1' />
|
||||
<div
|
||||
className='flex w-full items-center justify-between rounded-md px-2 py-2'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<span className='text-sm text-text-secondary'>Show resolved</span>
|
||||
<Switch
|
||||
size='md'
|
||||
defaultValue={!showOnlyUnresolved}
|
||||
onChange={(checked) => {
|
||||
setShowOnlyUnresolved(!checked)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Divider type='vertical' className='h-3.5' />
|
||||
<div
|
||||
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
||||
onClick={() => {
|
||||
setControlMode(ControlMode.Pointer)
|
||||
setActiveCommentId(null)
|
||||
}}
|
||||
>
|
||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow overflow-y-auto px-1'>
|
||||
{filteredSorted.map((c) => {
|
||||
const isActive = activeCommentId === c.id
|
||||
return (
|
||||
<div
|
||||
key={c.id}
|
||||
className={cn('group mb-2 cursor-pointer rounded-xl bg-components-panel-bg p-3 transition-colors hover:bg-components-panel-on-panel-item-bg-hover', isActive && 'bg-components-panel-on-panel-item-bg-hover')}
|
||||
onClick={() => handleSelect(c)}
|
||||
>
|
||||
<div className='min-w-0'>
|
||||
<div className='mb-1 flex items-center justify-between'>
|
||||
<UserAvatarList
|
||||
users={c.participants}
|
||||
maxVisible={3}
|
||||
size={24}
|
||||
/>
|
||||
<div className='ml-2 flex items-center'>
|
||||
{c.resolved ? (
|
||||
<RiCheckboxCircleFill className='h-4 w-4 text-text-secondary'/>
|
||||
) : (
|
||||
<RiCheckboxCircleLine
|
||||
className='h-4 w-4 cursor-pointer text-text-tertiary hover:text-text-secondary'
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleResolve(c)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Header row: creator + time */}
|
||||
<div className='flex items-start'>
|
||||
<div className='flex min-w-0 items-center gap-2'>
|
||||
<div className='system-sm-medium truncate text-text-primary'>{c.created_by_account.name}</div>
|
||||
<div className='system-2xs-regular shrink-0 text-text-tertiary'>
|
||||
{formatTimeFromNow(c.updated_at * 1000)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className='system-sm-regular mt-1 line-clamp-3 break-words text-text-secondary'>{c.content}</div>
|
||||
{/* Footer */}
|
||||
{c.reply_count > 0 && (
|
||||
<div className='mt-2 flex items-center justify-between'>
|
||||
<div className='system-2xs-regular text-text-tertiary'>
|
||||
{c.reply_count} {t('workflow.comments.reply')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{!loading && filteredSorted.length === 0 && (
|
||||
<div className='system-sm-regular mt-6 text-center text-text-tertiary'>{t('workflow.comments.noComments')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(CommentsPanel)
|
||||
@ -3,9 +3,6 @@ import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
@ -17,17 +14,20 @@ import type {
|
||||
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'
|
||||
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||
import { useStore as useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { updateEnvironmentVariables } from '@/service/workflow'
|
||||
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
|
||||
|
||||
const EnvPanel = () => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
const collaborativeWorkflow = useCollaborativeWorkflow()
|
||||
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 appId = useWorkflowStore(s => s.appId) as string
|
||||
|
||||
const [showVariableModal, setShowVariableModal] = useState(false)
|
||||
const [currentVar, setCurrentVar] = useState<EnvironmentVariable>()
|
||||
@ -40,43 +40,65 @@ const EnvPanel = () => {
|
||||
}
|
||||
|
||||
const getEffectedNodes = useCallback((env: EnvironmentVariable) => {
|
||||
const { getNodes } = store.getState()
|
||||
const allNodes = getNodes()
|
||||
const { nodes: allNodes } = collaborativeWorkflow.getState()
|
||||
return findUsedVarNodes(
|
||||
['env', env.name],
|
||||
allNodes,
|
||||
)
|
||||
}, [store])
|
||||
}, [collaborativeWorkflow])
|
||||
|
||||
const removeUsedVarInNodes = useCallback((env: EnvironmentVariable) => {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||
const effectedNodes = getEffectedNodes(env)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
const newNodes = nodes.map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['env', env.name], [])
|
||||
|
||||
return node
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [getEffectedNodes, store])
|
||||
}, [getEffectedNodes, collaborativeWorkflow])
|
||||
|
||||
const handleEdit = (env: EnvironmentVariable) => {
|
||||
setCurrentVar(env)
|
||||
setShowVariableModal(true)
|
||||
}
|
||||
|
||||
const handleDelete = useCallback((env: EnvironmentVariable) => {
|
||||
const handleDelete = useCallback(async (env: EnvironmentVariable) => {
|
||||
removeUsedVarInNodes(env)
|
||||
updateEnvList(envList.filter(e => e.id !== env.id))
|
||||
const newEnvList = envList.filter(e => e.id !== env.id)
|
||||
updateEnvList(newEnvList)
|
||||
setCacheForDelete(undefined)
|
||||
setShowRemoveConfirm(false)
|
||||
doSyncWorkflowDraft()
|
||||
|
||||
// Use new dedicated environment variables API instead of workflow draft sync
|
||||
try {
|
||||
await updateEnvironmentVariables({
|
||||
appId,
|
||||
environmentVariables: newEnvList,
|
||||
})
|
||||
|
||||
// Emit update event to other connected clients
|
||||
const socket = webSocketClient.getSocket(appId)
|
||||
if (socket?.connected) {
|
||||
socket.emit('collaboration_event', {
|
||||
type: 'vars_and_features_update',
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to update environment variables:', error)
|
||||
// Revert local state on error
|
||||
updateEnvList(envList)
|
||||
}
|
||||
|
||||
if (env.value_type === 'secret') {
|
||||
const newMap = { ...envSecrets }
|
||||
delete newMap[env.id]
|
||||
setEnvSecrets(newMap)
|
||||
}
|
||||
}, [doSyncWorkflowDraft, envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList])
|
||||
}, [envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList, appId])
|
||||
|
||||
const deleteCheck = useCallback((env: EnvironmentVariable) => {
|
||||
const effectedNodes = getEffectedNodes(env)
|
||||
@ -92,20 +114,46 @@ const EnvPanel = () => {
|
||||
const handleSave = useCallback(async (env: EnvironmentVariable) => {
|
||||
// add env
|
||||
let newEnv = env
|
||||
let newList: EnvironmentVariable[]
|
||||
|
||||
if (!currentVar) {
|
||||
// Adding new environment variable
|
||||
if (env.value_type === 'secret') {
|
||||
setEnvSecrets({
|
||||
...envSecrets,
|
||||
[env.id]: formatSecret(env.value),
|
||||
})
|
||||
}
|
||||
const newList = [env, ...envList]
|
||||
newList = [env, ...envList]
|
||||
updateEnvList(newList)
|
||||
await doSyncWorkflowDraft()
|
||||
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
||||
|
||||
// Use new dedicated environment variables API
|
||||
try {
|
||||
await updateEnvironmentVariables({
|
||||
appId,
|
||||
environmentVariables: newList,
|
||||
})
|
||||
|
||||
const socket = webSocketClient.getSocket(appId)
|
||||
if (socket) {
|
||||
socket.emit('collaboration_event', {
|
||||
type: 'vars_and_features_update',
|
||||
})
|
||||
}
|
||||
|
||||
// Hide secret values in UI
|
||||
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to update environment variables:', error)
|
||||
// Revert local state on error
|
||||
updateEnvList(envList)
|
||||
}
|
||||
return
|
||||
}
|
||||
else if (currentVar.value_type === 'secret') {
|
||||
|
||||
// Updating existing environment variable
|
||||
if (currentVar.value_type === 'secret') {
|
||||
if (env.value_type === 'secret') {
|
||||
if (envSecrets[currentVar.id] !== env.value) {
|
||||
newEnv = env
|
||||
@ -128,13 +176,15 @@ const EnvPanel = () => {
|
||||
})
|
||||
}
|
||||
}
|
||||
const newList = envList.map(e => e.id === currentVar.id ? newEnv : e)
|
||||
|
||||
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 { nodes, setNodes } = collaborativeWorkflow.getState()
|
||||
const effectedNodes = getEffectedNodes(currentVar)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
const newNodes = nodes.map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['env', currentVar.name], ['env', env.name])
|
||||
|
||||
@ -142,9 +192,30 @@ const EnvPanel = () => {
|
||||
})
|
||||
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])
|
||||
|
||||
// Use new dedicated environment variables API
|
||||
try {
|
||||
await updateEnvironmentVariables({
|
||||
appId,
|
||||
environmentVariables: newList,
|
||||
})
|
||||
|
||||
const socket = webSocketClient.getSocket(appId)
|
||||
if (socket) {
|
||||
socket.emit('collaboration_event', {
|
||||
type: 'vars_and_features_update',
|
||||
})
|
||||
}
|
||||
|
||||
// Hide secret values in UI
|
||||
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to update environment variables:', error)
|
||||
// Revert local state on error
|
||||
updateEnvList(envList)
|
||||
}
|
||||
}, [currentVar, envList, envSecrets, getEffectedNodes, setEnvSecrets, collaborativeWorkflow, updateEnvList, appId])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user