+18


![dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)

![autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)




FFXN
GitHub
yyh
盐粒 Yanli
autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Tianle
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Yunlu Wen
zyssyz123
Claude Opus 4.7
chariri
Asuka Minato
Copilot Autofix powered by AI
Nian
非法操作
Carmen Fernández Ruiz
wangxiaolei
QuantumGhost
L1nSn0w
Evan
Escape0707
Jingyi
Amr Sherif
ZHOU ZHICHEN
unknown
JzoNg
Xiyuan Chen
-LAN-
107bba0116
Signed-off-by: dependabot[bot] <[email protected]> Signed-off-by: EvanYao826 <[email protected]> Co-authored-by: yyh <[email protected]> Co-authored-by: 盐粒 Yanli <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Tianle <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yunlu Wen <[email protected]> Co-authored-by: zyssyz123 <[email protected]> Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]> Co-authored-by: chariri <[email protected]> Co-authored-by: Asuka Minato <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> Co-authored-by: Nian <[email protected]> Co-authored-by: 非法操作 <[email protected]> Co-authored-by: Carmen Fernández Ruiz <[email protected]> Co-authored-by: wangxiaolei <[email protected]> Co-authored-by: QuantumGhost <[email protected]> Co-authored-by: L1nSn0w <[email protected]> Co-authored-by: Evan <[email protected]> Co-authored-by: Escape0707 <[email protected]> Co-authored-by: Jingyi <[email protected]> Co-authored-by: Amr Sherif <[email protected]> Co-authored-by: ZHOU ZHICHEN <[email protected]> Co-authored-by: unknown <[email protected]> Co-authored-by: JzoNg <[email protected]> Co-authored-by: Xiyuan Chen <[email protected]> Co-authored-by: -LAN- <[email protected]>
637 lines
26 KiB
Python
637 lines
26 KiB
Python
import logging
|
|
from collections.abc import Callable
|
|
from functools import wraps
|
|
from typing import Any, TypedDict
|
|
from uuid import UUID
|
|
|
|
from flask import Response, request
|
|
from flask_restx import Resource, fields, marshal, marshal_with
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from controllers.common.schema import register_schema_models
|
|
from controllers.console import console_ns
|
|
from controllers.console.app.error import (
|
|
DraftWorkflowNotExist,
|
|
)
|
|
from controllers.console.app.wraps import get_app_model
|
|
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
|
|
from controllers.web.error import InvalidArgumentError, NotFoundError
|
|
from core.app.file_access import DatabaseFileAccessController
|
|
from core.workflow.variable_prefixes import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID
|
|
from extensions.ext_database import db
|
|
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 graphon.file import helpers as file_helpers
|
|
from graphon.variables.segment_group import SegmentGroup
|
|
from graphon.variables.segments import ArrayFileSegment, FileSegment, Segment
|
|
from graphon.variables.types import SegmentType
|
|
from libs.login import current_user, login_required
|
|
from models import App, AppMode
|
|
from models.workflow import WorkflowDraftVariable
|
|
from services.workflow_draft_variable_service import WorkflowDraftVariableList, WorkflowDraftVariableService
|
|
from services.workflow_service import WorkflowService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
_file_access_controller = DatabaseFileAccessController()
|
|
|
|
|
|
class WorkflowDraftVariableListQuery(BaseModel):
|
|
page: int = Field(default=1, ge=1, le=100_000, description="Page number")
|
|
limit: int = Field(default=20, ge=1, le=100, description="Items per page")
|
|
|
|
|
|
class WorkflowDraftVariableUpdatePayload(BaseModel):
|
|
name: str | None = Field(default=None, description="Variable name")
|
|
value: Any | None = Field(default=None, description="Variable value")
|
|
|
|
|
|
class ConversationVariableUpdatePayload(BaseModel):
|
|
conversation_variables: list[dict[str, Any]] = Field(
|
|
..., description="Conversation variables for the draft workflow"
|
|
)
|
|
|
|
|
|
class EnvironmentVariableUpdatePayload(BaseModel):
|
|
environment_variables: list[dict[str, Any]] = Field(..., description="Environment variables for the draft workflow")
|
|
|
|
|
|
register_schema_models(
|
|
console_ns,
|
|
WorkflowDraftVariableListQuery,
|
|
WorkflowDraftVariableUpdatePayload,
|
|
ConversationVariableUpdatePayload,
|
|
EnvironmentVariableUpdatePayload,
|
|
)
|
|
|
|
|
|
def _convert_values_to_json_serializable_object(value: Segment):
|
|
match value:
|
|
case FileSegment():
|
|
return value.value.model_dump()
|
|
case ArrayFileSegment():
|
|
return [i.model_dump() for i in value.value]
|
|
case SegmentGroup():
|
|
return [_convert_values_to_json_serializable_object(i) for i in value.value]
|
|
case _:
|
|
return value.value
|
|
|
|
|
|
def _serialize_var_value(variable: WorkflowDraftVariable):
|
|
value = variable.get_value()
|
|
# create a copy of the value to avoid affecting the model cache.
|
|
value = value.model_copy(deep=True)
|
|
# Refresh the url signature before returning it to client.
|
|
match value:
|
|
case FileSegment():
|
|
file = value.value
|
|
file.remote_url = file.generate_url()
|
|
case ArrayFileSegment():
|
|
files = value.value
|
|
for file in files:
|
|
file.remote_url = file.generate_url()
|
|
return _convert_values_to_json_serializable_object(value)
|
|
|
|
|
|
def _serialize_variable_type(workflow_draft_var: WorkflowDraftVariable) -> str:
|
|
value_type = workflow_draft_var.value_type
|
|
return str(value_type.exposed_type())
|
|
|
|
|
|
class FullContentDict(TypedDict):
|
|
size_bytes: int | None
|
|
value_type: str
|
|
length: int | None
|
|
download_url: str
|
|
|
|
|
|
def _serialize_full_content(variable: WorkflowDraftVariable) -> FullContentDict | None:
|
|
"""Serialize full_content information for large variables."""
|
|
if not variable.is_truncated():
|
|
return None
|
|
|
|
variable_file = variable.variable_file
|
|
assert variable_file is not None
|
|
|
|
result: FullContentDict = {
|
|
"size_bytes": variable_file.size,
|
|
"value_type": str(variable_file.value_type.exposed_type()),
|
|
"length": variable_file.length,
|
|
"download_url": file_helpers.get_signed_file_url(variable_file.upload_file_id, as_attachment=True),
|
|
}
|
|
return result
|
|
|
|
|
|
def _ensure_variable_access(
|
|
variable: WorkflowDraftVariable | None,
|
|
app_id: str,
|
|
variable_id: str,
|
|
) -> WorkflowDraftVariable:
|
|
if variable is None:
|
|
raise NotFoundError(description=f"variable not found, id={variable_id}")
|
|
if variable.app_id != app_id or variable.user_id != current_user.id:
|
|
raise NotFoundError(description=f"variable not found, id={variable_id}")
|
|
return variable
|
|
|
|
|
|
_WORKFLOW_DRAFT_VARIABLE_WITHOUT_VALUE_FIELDS = {
|
|
"id": fields.String,
|
|
"type": fields.String(attribute=lambda model: model.get_variable_type()),
|
|
"name": fields.String,
|
|
"description": fields.String,
|
|
"selector": fields.List(fields.String, attribute=lambda model: model.get_selector()),
|
|
"value_type": fields.String(attribute=_serialize_variable_type),
|
|
"edited": fields.Boolean(attribute=lambda model: model.edited),
|
|
"visible": fields.Boolean,
|
|
"is_truncated": fields.Boolean(attribute=lambda model: model.file_id is not None),
|
|
}
|
|
|
|
_WORKFLOW_DRAFT_VARIABLE_FIELDS = {
|
|
**_WORKFLOW_DRAFT_VARIABLE_WITHOUT_VALUE_FIELDS,
|
|
"value": fields.Raw(attribute=_serialize_var_value),
|
|
"full_content": fields.Raw(attribute=_serialize_full_content),
|
|
}
|
|
|
|
_WORKFLOW_DRAFT_ENV_VARIABLE_FIELDS = {
|
|
"id": fields.String,
|
|
"type": fields.String(attribute=lambda _: "env"),
|
|
"name": fields.String,
|
|
"description": fields.String,
|
|
"selector": fields.List(fields.String, attribute=lambda model: model.get_selector()),
|
|
"value_type": fields.String(attribute=_serialize_variable_type),
|
|
"edited": fields.Boolean(attribute=lambda model: model.edited),
|
|
"visible": fields.Boolean,
|
|
}
|
|
|
|
_WORKFLOW_DRAFT_ENV_VARIABLE_LIST_FIELDS = {
|
|
"items": fields.List(fields.Nested(_WORKFLOW_DRAFT_ENV_VARIABLE_FIELDS)),
|
|
}
|
|
|
|
|
|
def _get_items(var_list: WorkflowDraftVariableList) -> list[WorkflowDraftVariable]:
|
|
return var_list.variables
|
|
|
|
|
|
_WORKFLOW_DRAFT_VARIABLE_LIST_WITHOUT_VALUE_FIELDS = {
|
|
"items": fields.List(fields.Nested(_WORKFLOW_DRAFT_VARIABLE_WITHOUT_VALUE_FIELDS), attribute=_get_items),
|
|
"total": fields.Raw(),
|
|
}
|
|
|
|
_WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS = {
|
|
"items": fields.List(fields.Nested(_WORKFLOW_DRAFT_VARIABLE_FIELDS), attribute=_get_items),
|
|
}
|
|
|
|
# Register models for flask_restx to avoid dict type issues in Swagger
|
|
workflow_draft_variable_without_value_model = console_ns.model(
|
|
"WorkflowDraftVariableWithoutValue", _WORKFLOW_DRAFT_VARIABLE_WITHOUT_VALUE_FIELDS
|
|
)
|
|
|
|
workflow_draft_variable_model = console_ns.model("WorkflowDraftVariable", _WORKFLOW_DRAFT_VARIABLE_FIELDS)
|
|
|
|
workflow_draft_env_variable_model = console_ns.model("WorkflowDraftEnvVariable", _WORKFLOW_DRAFT_ENV_VARIABLE_FIELDS)
|
|
|
|
workflow_draft_env_variable_list_fields_copy = _WORKFLOW_DRAFT_ENV_VARIABLE_LIST_FIELDS.copy()
|
|
workflow_draft_env_variable_list_fields_copy["items"] = fields.List(fields.Nested(workflow_draft_env_variable_model))
|
|
workflow_draft_env_variable_list_model = console_ns.model(
|
|
"WorkflowDraftEnvVariableList", workflow_draft_env_variable_list_fields_copy
|
|
)
|
|
|
|
workflow_draft_variable_list_without_value_fields_copy = _WORKFLOW_DRAFT_VARIABLE_LIST_WITHOUT_VALUE_FIELDS.copy()
|
|
workflow_draft_variable_list_without_value_fields_copy["items"] = fields.List(
|
|
fields.Nested(workflow_draft_variable_without_value_model), attribute=_get_items
|
|
)
|
|
workflow_draft_variable_list_without_value_model = console_ns.model(
|
|
"WorkflowDraftVariableListWithoutValue", workflow_draft_variable_list_without_value_fields_copy
|
|
)
|
|
|
|
workflow_draft_variable_list_fields_copy = _WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS.copy()
|
|
workflow_draft_variable_list_fields_copy["items"] = fields.List(
|
|
fields.Nested(workflow_draft_variable_model), attribute=_get_items
|
|
)
|
|
workflow_draft_variable_list_model = console_ns.model(
|
|
"WorkflowDraftVariableList", workflow_draft_variable_list_fields_copy
|
|
)
|
|
|
|
|
|
def _api_prerequisite[**P, R](f: Callable[P, R]) -> Callable[P, R | Response]:
|
|
"""Common prerequisites for all draft workflow variable APIs.
|
|
|
|
It ensures the following conditions are satisfied:
|
|
|
|
- Dify has been property setup.
|
|
- The request user has logged in and initialized.
|
|
- The requested app is a workflow or a chat flow.
|
|
- The request user has the edit permission for the app.
|
|
"""
|
|
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@edit_permission_required
|
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
|
@wraps(f)
|
|
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | Response:
|
|
return f(*args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/variables")
|
|
class WorkflowVariableCollectionApi(Resource):
|
|
@console_ns.expect(console_ns.models[WorkflowDraftVariableListQuery.__name__])
|
|
@console_ns.doc("get_workflow_variables")
|
|
@console_ns.doc(description="Get draft workflow variables")
|
|
@console_ns.doc(params={"app_id": "Application ID"})
|
|
@console_ns.doc(params={"page": "Page number (1-100000)", "limit": "Number of items per page (1-100)"})
|
|
@console_ns.response(
|
|
200, "Workflow variables retrieved successfully", workflow_draft_variable_list_without_value_model
|
|
)
|
|
@_api_prerequisite
|
|
@marshal_with(workflow_draft_variable_list_without_value_model)
|
|
def get(self, app_model: App):
|
|
"""
|
|
Get draft workflow
|
|
"""
|
|
args = WorkflowDraftVariableListQuery.model_validate(request.args.to_dict(flat=True))
|
|
|
|
# fetch draft workflow by app_model
|
|
workflow_service = WorkflowService()
|
|
workflow_exist = workflow_service.is_workflow_exist(app_model=app_model)
|
|
if not workflow_exist:
|
|
raise DraftWorkflowNotExist()
|
|
|
|
# fetch draft workflow by app_model
|
|
with sessionmaker(bind=db.engine, expire_on_commit=False).begin() as session:
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=session,
|
|
)
|
|
workflow_vars = draft_var_srv.list_variables_without_values(
|
|
app_id=app_model.id,
|
|
page=args.page,
|
|
limit=args.limit,
|
|
user_id=current_user.id,
|
|
)
|
|
|
|
return workflow_vars
|
|
|
|
@console_ns.doc("delete_workflow_variables")
|
|
@console_ns.doc(description="Delete all draft workflow variables")
|
|
@console_ns.response(204, "Workflow variables deleted successfully")
|
|
@_api_prerequisite
|
|
def delete(self, app_model: App):
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=db.session(),
|
|
)
|
|
draft_var_srv.delete_user_workflow_variables(app_model.id, user_id=current_user.id)
|
|
db.session.commit()
|
|
return Response("", 204)
|
|
|
|
|
|
def validate_node_id(node_id: str) -> None:
|
|
if node_id in [
|
|
CONVERSATION_VARIABLE_NODE_ID,
|
|
SYSTEM_VARIABLE_NODE_ID,
|
|
]:
|
|
# NOTE(QuantumGhost): While we store the system and conversation variables as node variables
|
|
# with specific `node_id` in database, we still want to make the API separated. By disallowing
|
|
# accessing system and conversation variables in `WorkflowDraftNodeVariableListApi`,
|
|
# we mitigate the risk that user of the API depending on the implementation detail of the API.
|
|
#
|
|
# ref: [Hyrum's Law](https://www.hyrumslaw.com/)
|
|
|
|
raise InvalidArgumentError(
|
|
f"invalid node_id, please use correspond api for conversation and system variables, node_id={node_id}",
|
|
)
|
|
|
|
|
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/variables")
|
|
class NodeVariableCollectionApi(Resource):
|
|
@console_ns.doc("get_node_variables")
|
|
@console_ns.doc(description="Get variables for a specific node")
|
|
@console_ns.doc(params={"app_id": "Application ID", "node_id": "Node ID"})
|
|
@console_ns.response(200, "Node variables retrieved successfully", workflow_draft_variable_list_model)
|
|
@_api_prerequisite
|
|
@marshal_with(workflow_draft_variable_list_model)
|
|
def get(self, app_model: App, node_id: str):
|
|
validate_node_id(node_id)
|
|
with sessionmaker(bind=db.engine, expire_on_commit=False).begin() as session:
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=session,
|
|
)
|
|
node_vars = draft_var_srv.list_node_variables(app_model.id, node_id, user_id=current_user.id)
|
|
|
|
return node_vars
|
|
|
|
@console_ns.doc("delete_node_variables")
|
|
@console_ns.doc(description="Delete all variables for a specific node")
|
|
@console_ns.response(204, "Node variables deleted successfully")
|
|
@_api_prerequisite
|
|
def delete(self, app_model: App, node_id: str):
|
|
validate_node_id(node_id)
|
|
srv = WorkflowDraftVariableService(db.session())
|
|
srv.delete_node_variables(app_model.id, node_id, user_id=current_user.id)
|
|
db.session.commit()
|
|
return Response("", 204)
|
|
|
|
|
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/variables/<uuid:variable_id>")
|
|
class VariableApi(Resource):
|
|
_PATCH_NAME_FIELD = "name"
|
|
_PATCH_VALUE_FIELD = "value"
|
|
|
|
@console_ns.doc("get_variable")
|
|
@console_ns.doc(description="Get a specific workflow variable")
|
|
@console_ns.doc(params={"app_id": "Application ID", "variable_id": "Variable ID"})
|
|
@console_ns.response(200, "Variable retrieved successfully", workflow_draft_variable_model)
|
|
@console_ns.response(404, "Variable not found")
|
|
@_api_prerequisite
|
|
@marshal_with(workflow_draft_variable_model)
|
|
def get(self, app_model: App, variable_id: UUID):
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=db.session(),
|
|
)
|
|
variable_id_str = str(variable_id)
|
|
variable = _ensure_variable_access(
|
|
variable=draft_var_srv.get_variable(variable_id=variable_id_str),
|
|
app_id=app_model.id,
|
|
variable_id=variable_id_str,
|
|
)
|
|
return variable
|
|
|
|
@console_ns.doc("update_variable")
|
|
@console_ns.doc(description="Update a workflow variable")
|
|
@console_ns.expect(console_ns.models[WorkflowDraftVariableUpdatePayload.__name__])
|
|
@console_ns.response(200, "Variable updated successfully", workflow_draft_variable_model)
|
|
@console_ns.response(404, "Variable not found")
|
|
@_api_prerequisite
|
|
@marshal_with(workflow_draft_variable_model)
|
|
def patch(self, app_model: App, variable_id: UUID):
|
|
# Request payload for file types:
|
|
#
|
|
# Local File:
|
|
#
|
|
# {
|
|
# "type": "image",
|
|
# "transfer_method": "local_file",
|
|
# "url": "",
|
|
# "upload_file_id": "daded54f-72c7-4f8e-9d18-9b0abdd9f190"
|
|
# }
|
|
#
|
|
# Remote File:
|
|
#
|
|
#
|
|
# {
|
|
# "type": "image",
|
|
# "transfer_method": "remote_url",
|
|
# "url": "http://127.0.0.1:5001/files/1602650a-4fe4-423c-85a2-af76c083e3c4/file-preview?timestamp=1750041099&nonce=...&sign=...=",
|
|
# "upload_file_id": "1602650a-4fe4-423c-85a2-af76c083e3c4"
|
|
# }
|
|
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=db.session(),
|
|
)
|
|
args_model = WorkflowDraftVariableUpdatePayload.model_validate(console_ns.payload or {})
|
|
|
|
variable_id_str = str(variable_id)
|
|
variable = _ensure_variable_access(
|
|
variable=draft_var_srv.get_variable(variable_id=variable_id_str),
|
|
app_id=app_model.id,
|
|
variable_id=variable_id_str,
|
|
)
|
|
|
|
new_name = args_model.name
|
|
raw_value = args_model.value
|
|
if new_name is None and raw_value is None:
|
|
return variable
|
|
|
|
new_value = None
|
|
if raw_value is not None:
|
|
match variable.value_type:
|
|
case SegmentType.FILE:
|
|
if not isinstance(raw_value, dict):
|
|
raise InvalidArgumentError(description=f"expected dict for file, got {type(raw_value)}")
|
|
raw_value = build_from_mapping(
|
|
mapping=raw_value,
|
|
tenant_id=app_model.tenant_id,
|
|
access_controller=_file_access_controller,
|
|
)
|
|
case SegmentType.ARRAY_FILE:
|
|
if not isinstance(raw_value, list):
|
|
raise InvalidArgumentError(description=f"expected list for files, got {type(raw_value)}")
|
|
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,
|
|
access_controller=_file_access_controller,
|
|
)
|
|
case _:
|
|
pass
|
|
new_value = 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
|
|
|
|
@console_ns.doc("delete_variable")
|
|
@console_ns.doc(description="Delete a workflow variable")
|
|
@console_ns.response(204, "Variable deleted successfully")
|
|
@console_ns.response(404, "Variable not found")
|
|
@_api_prerequisite
|
|
def delete(self, app_model: App, variable_id: UUID):
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=db.session(),
|
|
)
|
|
variable_id_str = str(variable_id)
|
|
variable = _ensure_variable_access(
|
|
variable=draft_var_srv.get_variable(variable_id=variable_id_str),
|
|
app_id=app_model.id,
|
|
variable_id=variable_id_str,
|
|
)
|
|
draft_var_srv.delete_variable(variable)
|
|
db.session.commit()
|
|
return Response("", 204)
|
|
|
|
|
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/variables/<uuid:variable_id>/reset")
|
|
class VariableResetApi(Resource):
|
|
@console_ns.doc("reset_variable")
|
|
@console_ns.doc(description="Reset a workflow variable to its default value")
|
|
@console_ns.doc(params={"app_id": "Application ID", "variable_id": "Variable ID"})
|
|
@console_ns.response(200, "Variable reset successfully", workflow_draft_variable_model)
|
|
@console_ns.response(204, "Variable reset (no content)")
|
|
@console_ns.response(404, "Variable not found")
|
|
@_api_prerequisite
|
|
def put(self, app_model: App, variable_id: UUID):
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=db.session(),
|
|
)
|
|
|
|
workflow_srv = WorkflowService()
|
|
draft_workflow = workflow_srv.get_draft_workflow(app_model)
|
|
if draft_workflow is None:
|
|
raise NotFoundError(
|
|
f"Draft workflow not found, app_id={app_model.id}",
|
|
)
|
|
variable_id_str = str(variable_id)
|
|
variable = _ensure_variable_access(
|
|
variable=draft_var_srv.get_variable(variable_id=variable_id_str),
|
|
app_id=app_model.id,
|
|
variable_id=variable_id_str,
|
|
)
|
|
|
|
resetted = draft_var_srv.reset_variable(draft_workflow, variable)
|
|
db.session.commit()
|
|
if resetted is None:
|
|
return Response("", 204)
|
|
else:
|
|
return marshal(resetted, workflow_draft_variable_model)
|
|
|
|
|
|
def _get_variable_list(app_model: App, node_id) -> WorkflowDraftVariableList:
|
|
with sessionmaker(bind=db.engine, expire_on_commit=False).begin() as session:
|
|
draft_var_srv = WorkflowDraftVariableService(
|
|
session=session,
|
|
)
|
|
if node_id == CONVERSATION_VARIABLE_NODE_ID:
|
|
draft_vars = draft_var_srv.list_conversation_variables(app_model.id, user_id=current_user.id)
|
|
elif node_id == SYSTEM_VARIABLE_NODE_ID:
|
|
draft_vars = draft_var_srv.list_system_variables(app_model.id, user_id=current_user.id)
|
|
else:
|
|
draft_vars = draft_var_srv.list_node_variables(
|
|
app_id=app_model.id,
|
|
node_id=node_id,
|
|
user_id=current_user.id,
|
|
)
|
|
return draft_vars
|
|
|
|
|
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/conversation-variables")
|
|
class ConversationVariableCollectionApi(Resource):
|
|
@console_ns.doc("get_conversation_variables")
|
|
@console_ns.doc(description="Get conversation variables for workflow")
|
|
@console_ns.doc(params={"app_id": "Application ID"})
|
|
@console_ns.response(200, "Conversation variables retrieved successfully", workflow_draft_variable_list_model)
|
|
@console_ns.response(404, "Draft workflow not found")
|
|
@_api_prerequisite
|
|
@marshal_with(workflow_draft_variable_list_model)
|
|
def get(self, app_model: App):
|
|
# NOTE(QuantumGhost): Prefill conversation variables into the draft variables table
|
|
# so their IDs can be returned to the caller.
|
|
workflow_srv = WorkflowService()
|
|
draft_workflow = workflow_srv.get_draft_workflow(app_model)
|
|
if draft_workflow is None:
|
|
raise NotFoundError(description=f"draft workflow not found, id={app_model.id}")
|
|
draft_var_srv = WorkflowDraftVariableService(db.session())
|
|
draft_var_srv.prefill_conversation_variable_default_values(draft_workflow, user_id=current_user.id)
|
|
db.session.commit()
|
|
return _get_variable_list(app_model, CONVERSATION_VARIABLE_NODE_ID)
|
|
|
|
@console_ns.expect(console_ns.models[ConversationVariableUpdatePayload.__name__])
|
|
@console_ns.doc("update_conversation_variables")
|
|
@console_ns.doc(description="Update conversation variables for workflow draft")
|
|
@console_ns.doc(params={"app_id": "Application ID"})
|
|
@console_ns.response(200, "Conversation variables updated successfully")
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@edit_permission_required
|
|
@get_app_model(mode=AppMode.ADVANCED_CHAT)
|
|
def post(self, app_model: App):
|
|
payload = ConversationVariableUpdatePayload.model_validate(console_ns.payload or {})
|
|
|
|
workflow_service = WorkflowService()
|
|
|
|
conversation_variables_list = payload.conversation_variables
|
|
conversation_variables = [
|
|
variable_factory.build_conversation_variable_from_mapping(obj) for obj in conversation_variables_list
|
|
]
|
|
|
|
workflow_service.update_draft_workflow_conversation_variables(
|
|
app_model=app_model,
|
|
account=current_user,
|
|
conversation_variables=conversation_variables,
|
|
)
|
|
|
|
return {"result": "success"}
|
|
|
|
|
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/system-variables")
|
|
class SystemVariableCollectionApi(Resource):
|
|
@console_ns.doc("get_system_variables")
|
|
@console_ns.doc(description="Get system variables for workflow")
|
|
@console_ns.doc(params={"app_id": "Application ID"})
|
|
@console_ns.response(200, "System variables retrieved successfully", workflow_draft_variable_list_model)
|
|
@_api_prerequisite
|
|
@marshal_with(workflow_draft_variable_list_model)
|
|
def get(self, app_model: App):
|
|
return _get_variable_list(app_model, SYSTEM_VARIABLE_NODE_ID)
|
|
|
|
|
|
@console_ns.route("/apps/<uuid:app_id>/workflows/draft/environment-variables")
|
|
class EnvironmentVariableCollectionApi(Resource):
|
|
@console_ns.doc("get_environment_variables")
|
|
@console_ns.doc(description="Get environment variables for workflow")
|
|
@console_ns.doc(params={"app_id": "Application ID"})
|
|
@console_ns.response(200, "Environment variables retrieved successfully")
|
|
@console_ns.response(404, "Draft workflow not found")
|
|
@_api_prerequisite
|
|
def get(self, app_model: App):
|
|
"""
|
|
Get draft workflow
|
|
"""
|
|
# fetch draft workflow by app_model
|
|
workflow_service = WorkflowService()
|
|
workflow = workflow_service.get_draft_workflow(app_model=app_model)
|
|
if workflow is None:
|
|
raise DraftWorkflowNotExist()
|
|
|
|
env_vars = workflow.environment_variables
|
|
env_vars_list = []
|
|
for v in env_vars:
|
|
env_vars_list.append(
|
|
{
|
|
"id": v.id,
|
|
"type": "env",
|
|
"name": v.name,
|
|
"description": v.description,
|
|
"selector": v.selector,
|
|
"value_type": str(v.value_type.exposed_type()),
|
|
"value": v.value,
|
|
# Do not track edited for env vars.
|
|
"edited": False,
|
|
"visible": True,
|
|
"editable": True,
|
|
}
|
|
)
|
|
|
|
return {"items": env_vars_list}
|
|
|
|
@console_ns.expect(console_ns.models[EnvironmentVariableUpdatePayload.__name__])
|
|
@console_ns.doc("update_environment_variables")
|
|
@console_ns.doc(description="Update environment variables for workflow draft")
|
|
@console_ns.doc(params={"app_id": "Application ID"})
|
|
@console_ns.response(200, "Environment variables updated successfully")
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@edit_permission_required
|
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
|
def post(self, app_model: App):
|
|
payload = EnvironmentVariableUpdatePayload.model_validate(console_ns.payload or {})
|
|
|
|
workflow_service = WorkflowService()
|
|
|
|
environment_variables_list = payload.environment_variables
|
|
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"}
|