use new env variables update api

This commit is contained in:
hjlarry 2025-09-10 09:07:36 +08:00
parent 3867fece4a
commit ab438b42da
4 changed files with 144 additions and 29 deletions

View File

@ -17,8 +17,8 @@ from core.variables.segment_group import SegmentGroup
from core.variables.segments import ArrayFileSegment, FileSegment, Segment
from core.variables.types import SegmentType
from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID
from factories import variable_factory
from factories.file_factory import build_from_mapping, build_from_mappings
from factories.variable_factory import build_segment_with_type
from libs.login import current_user, login_required
from models import App, AppMode, db
from models.workflow import WorkflowDraftVariable
@ -295,7 +295,7 @@ class VariableApi(Resource):
if len(raw_value) > 0 and not isinstance(raw_value[0], dict):
raise InvalidArgumentError(description=f"expected dict for files[0], got {type(raw_value)}")
raw_value = build_from_mappings(mappings=raw_value, tenant_id=app_model.tenant_id)
new_value = build_segment_with_type(variable.value_type, raw_value)
new_value = variable_factory.build_segment_with_type(variable.value_type, raw_value)
draft_var_srv.update_variable(variable, name=new_name, value=new_value)
db.session.commit()
return variable
@ -411,6 +411,34 @@ class EnvironmentVariableCollectionApi(Resource):
)
return {"items": env_vars_list}
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
def post(self, app_model: App):
# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
raise Forbidden()
parser = reqparse.RequestParser()
parser.add_argument("environment_variables", type=list, required=True, location="json")
args = parser.parse_args()
workflow_service = WorkflowService()
environment_variables_list = args.get("environment_variables") or []
environment_variables = [
variable_factory.build_environment_variable_from_mapping(obj) for obj in environment_variables_list
]
workflow_service.update_draft_workflow_environment_variables(
app_model=app_model,
account=current_user,
environment_variables=environment_variables,
)
return { "result": "success" }
api.add_resource(

View File

@ -244,6 +244,28 @@ class WorkflowService:
# return draft workflow
return workflow
def update_draft_workflow_environment_variables(
self, *,
app_model: App,
environment_variables: Sequence[Variable],
account: Account,
):
"""
Update draft workflow environment variables
"""
# fetch draft workflow by app_model
workflow = self.get_draft_workflow(app_model=app_model)
if not workflow:
raise ValueError("No draft workflow found.")
workflow.environment_variables = environment_variables
workflow.updated_by = account.id
workflow.updated_at = datetime.now(UTC).replace(tzinfo=None)
# commit db session changes
db.session.commit()
def publish_workflow(
self,

View File

@ -17,9 +17,9 @@ 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'
const EnvPanel = () => {
const { t } = useTranslation()
@ -29,7 +29,6 @@ const EnvPanel = () => {
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)
const [showVariableModal, setShowVariableModal] = useState(false)
@ -70,18 +69,31 @@ const EnvPanel = () => {
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)
await doSyncWorkflowDraft()
// Emit update event to other connected clients
const socket = webSocketClient.getSocket(appId)
if (socket?.connected) {
socket.emit('collaboration_event', {
type: 'varsAndFeaturesUpdate',
timestamp: Date.now(),
// 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: 'varsAndFeaturesUpdate',
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') {
@ -89,7 +101,7 @@ const EnvPanel = () => {
delete newMap[env.id]
setEnvSecrets(newMap)
}
}, [doSyncWorkflowDraft, envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList, appId])
}, [envList, envSecrets, removeUsedVarInNodes, setEnvSecrets, updateEnvList, appId])
const deleteCheck = useCallback((env: EnvironmentVariable) => {
const effectedNodes = getEffectedNodes(env)
@ -105,26 +117,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()
const socket = webSocketClient.getSocket(appId)
if (socket) {
socket.emit('collaboration_event', {
type: 'varsAndFeaturesUpdate',
// Use new dedicated environment variables API
try {
await updateEnvironmentVariables({
appId,
environmentVariables: newList,
})
const socket = webSocketClient.getSocket(appId)
if (socket) {
socket.emit('collaboration_event', {
type: 'varsAndFeaturesUpdate',
})
}
// 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)
}
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
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
@ -147,8 +179,10 @@ 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()
@ -161,15 +195,30 @@ const EnvPanel = () => {
})
setNodes(newNodes)
}
await doSyncWorkflowDraft()
const socket = webSocketClient.getSocket(appId)
if (socket) {
socket.emit('collaboration_event', {
type: 'varsAndFeaturesUpdate',
// Use new dedicated environment variables API
try {
await updateEnvironmentVariables({
appId,
environmentVariables: newList,
})
const socket = webSocketClient.getSocket(appId)
if (socket) {
socket.emit('collaboration_event', {
type: 'varsAndFeaturesUpdate',
})
}
// Hide secret values in UI
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
}
updateEnvList(newList.map(e => (e.id === env.id && env.value_type === 'secret') ? { ...e, value: '[__HIDDEN__]' } : e))
}, [currentVar, doSyncWorkflowDraft, envList, envSecrets, getEffectedNodes, setEnvSecrets, store, updateEnvList, appId])
catch (error) {
console.error('Failed to update environment variables:', error)
// Revert local state on error
updateEnvList(envList)
}
}, [currentVar, envList, envSecrets, getEffectedNodes, setEnvSecrets, store, updateEnvList, appId])
return (
<div

View File

@ -10,6 +10,7 @@ import type {
} from '@/types/workflow'
import type { BlockEnum } from '@/app/components/workflow/types'
import type { VarInInspect } from '@/types/workflow'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
export const fetchWorkflowDraft = (url: string) => {
return get(url, {}, { silent: true }) as Promise<FetchWorkflowDraftResponse>
@ -99,3 +100,18 @@ export const fetchNodeInspectVars = async (appId: string, nodeId: string): Promi
const { items } = (await get(`apps/${appId}/workflows/draft/nodes/${nodeId}/variables`)) as { items: VarInInspect[] }
return items
}
// Environment Variables API
export const fetchEnvironmentVariables = async (appId: string): Promise<EnvironmentVariable[]> => {
const { items } = (await get(`apps/${appId}/workflows/draft/environment-variables`)) as { items: EnvironmentVariable[] }
return items
}
export const updateEnvironmentVariables = ({ appId, environmentVariables }: {
appId: string
environmentVariables: EnvironmentVariable[]
}) => {
return post<CommonResponse>(`apps/${appId}/workflows/draft/environment-variables`, {
body: { environment_variables: environmentVariables },
})
}