workflow.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. import json
  2. import logging
  3. from typing import cast
  4. from flask import abort, request
  5. from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore
  6. from sqlalchemy.orm import Session
  7. from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
  8. import services
  9. from configs import dify_config
  10. from controllers.console import api
  11. from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
  12. from controllers.console.app.wraps import get_app_model
  13. from controllers.console.wraps import account_initialization_required, setup_required
  14. from core.app.apps.base_app_queue_manager import AppQueueManager
  15. from core.app.entities.app_invoke_entities import InvokeFrom
  16. from extensions.ext_database import db
  17. from factories import variable_factory
  18. from fields.workflow_fields import workflow_fields, workflow_pagination_fields
  19. from fields.workflow_run_fields import workflow_run_node_execution_fields
  20. from libs import helper
  21. from libs.helper import TimestampField, uuid_value
  22. from libs.login import current_user, login_required
  23. from models import App
  24. from models.account import Account
  25. from models.model import AppMode
  26. from services.app_generate_service import AppGenerateService
  27. from services.errors.app import WorkflowHashNotEqualError
  28. from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService
  29. logger = logging.getLogger(__name__)
  30. class DraftWorkflowApi(Resource):
  31. @setup_required
  32. @login_required
  33. @account_initialization_required
  34. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  35. @marshal_with(workflow_fields)
  36. def get(self, app_model: App):
  37. """
  38. Get draft workflow
  39. """
  40. # The role of the current user in the ta table must be admin, owner, or editor
  41. if not current_user.is_editor:
  42. raise Forbidden()
  43. # fetch draft workflow by app_model
  44. workflow_service = WorkflowService()
  45. workflow = workflow_service.get_draft_workflow(app_model=app_model)
  46. if not workflow:
  47. raise DraftWorkflowNotExist()
  48. # return workflow, if not found, return None (initiate graph by frontend)
  49. return workflow
  50. @setup_required
  51. @login_required
  52. @account_initialization_required
  53. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  54. def post(self, app_model: App):
  55. """
  56. Sync draft workflow
  57. """
  58. # The role of the current user in the ta table must be admin, owner, or editor
  59. if not current_user.is_editor:
  60. raise Forbidden()
  61. content_type = request.headers.get("Content-Type", "")
  62. if "application/json" in content_type:
  63. parser = reqparse.RequestParser()
  64. parser.add_argument("graph", type=dict, required=True, nullable=False, location="json")
  65. parser.add_argument("features", type=dict, required=True, nullable=False, location="json")
  66. parser.add_argument("hash", type=str, required=False, location="json")
  67. # TODO: set this to required=True after frontend is updated
  68. parser.add_argument("environment_variables", type=list, required=False, location="json")
  69. parser.add_argument("conversation_variables", type=list, required=False, location="json")
  70. args = parser.parse_args()
  71. elif "text/plain" in content_type:
  72. try:
  73. data = json.loads(request.data.decode("utf-8"))
  74. if "graph" not in data or "features" not in data:
  75. raise ValueError("graph or features not found in data")
  76. if not isinstance(data.get("graph"), dict) or not isinstance(data.get("features"), dict):
  77. raise ValueError("graph or features is not a dict")
  78. args = {
  79. "graph": data.get("graph"),
  80. "features": data.get("features"),
  81. "hash": data.get("hash"),
  82. "environment_variables": data.get("environment_variables"),
  83. "conversation_variables": data.get("conversation_variables"),
  84. }
  85. except json.JSONDecodeError:
  86. return {"message": "Invalid JSON data"}, 400
  87. else:
  88. abort(415)
  89. if not isinstance(current_user, Account):
  90. raise Forbidden()
  91. workflow_service = WorkflowService()
  92. try:
  93. environment_variables_list = args.get("environment_variables") or []
  94. environment_variables = [
  95. variable_factory.build_environment_variable_from_mapping(obj) for obj in environment_variables_list
  96. ]
  97. conversation_variables_list = args.get("conversation_variables") or []
  98. conversation_variables = [
  99. variable_factory.build_conversation_variable_from_mapping(obj) for obj in conversation_variables_list
  100. ]
  101. workflow = workflow_service.sync_draft_workflow(
  102. app_model=app_model,
  103. graph=args["graph"],
  104. features=args["features"],
  105. unique_hash=args.get("hash"),
  106. account=current_user,
  107. environment_variables=environment_variables,
  108. conversation_variables=conversation_variables,
  109. )
  110. except WorkflowHashNotEqualError:
  111. raise DraftWorkflowNotSync()
  112. return {
  113. "result": "success",
  114. "hash": workflow.unique_hash,
  115. "updated_at": TimestampField().format(workflow.updated_at or workflow.created_at),
  116. }
  117. class AdvancedChatDraftWorkflowRunApi(Resource):
  118. @setup_required
  119. @login_required
  120. @account_initialization_required
  121. @get_app_model(mode=[AppMode.ADVANCED_CHAT])
  122. def post(self, app_model: App):
  123. """
  124. Run draft workflow
  125. """
  126. # The role of the current user in the ta table must be admin, owner, or editor
  127. if not current_user.is_editor:
  128. raise Forbidden()
  129. if not isinstance(current_user, Account):
  130. raise Forbidden()
  131. parser = reqparse.RequestParser()
  132. parser.add_argument("inputs", type=dict, location="json")
  133. parser.add_argument("query", type=str, required=True, location="json", default="")
  134. parser.add_argument("files", type=list, location="json")
  135. parser.add_argument("conversation_id", type=uuid_value, location="json")
  136. parser.add_argument("parent_message_id", type=uuid_value, required=False, location="json")
  137. args = parser.parse_args()
  138. try:
  139. response = AppGenerateService.generate(
  140. app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=True
  141. )
  142. return helper.compact_generate_response(response)
  143. except services.errors.conversation.ConversationNotExistsError:
  144. raise NotFound("Conversation Not Exists.")
  145. except services.errors.conversation.ConversationCompletedError:
  146. raise ConversationCompletedError()
  147. except ValueError as e:
  148. raise e
  149. except Exception:
  150. logging.exception("internal server error.")
  151. raise InternalServerError()
  152. class AdvancedChatDraftRunIterationNodeApi(Resource):
  153. @setup_required
  154. @login_required
  155. @account_initialization_required
  156. @get_app_model(mode=[AppMode.ADVANCED_CHAT])
  157. def post(self, app_model: App, node_id: str):
  158. """
  159. Run draft workflow iteration node
  160. """
  161. # The role of the current user in the ta table must be admin, owner, or editor
  162. if not current_user.is_editor:
  163. raise Forbidden()
  164. if not isinstance(current_user, Account):
  165. raise Forbidden()
  166. parser = reqparse.RequestParser()
  167. parser.add_argument("inputs", type=dict, location="json")
  168. args = parser.parse_args()
  169. try:
  170. response = AppGenerateService.generate_single_iteration(
  171. app_model=app_model, user=current_user, node_id=node_id, args=args, streaming=True
  172. )
  173. return helper.compact_generate_response(response)
  174. except services.errors.conversation.ConversationNotExistsError:
  175. raise NotFound("Conversation Not Exists.")
  176. except services.errors.conversation.ConversationCompletedError:
  177. raise ConversationCompletedError()
  178. except ValueError as e:
  179. raise e
  180. except Exception:
  181. logging.exception("internal server error.")
  182. raise InternalServerError()
  183. class WorkflowDraftRunIterationNodeApi(Resource):
  184. @setup_required
  185. @login_required
  186. @account_initialization_required
  187. @get_app_model(mode=[AppMode.WORKFLOW])
  188. def post(self, app_model: App, node_id: str):
  189. """
  190. Run draft workflow iteration node
  191. """
  192. # The role of the current user in the ta table must be admin, owner, or editor
  193. if not current_user.is_editor:
  194. raise Forbidden()
  195. if not isinstance(current_user, Account):
  196. raise Forbidden()
  197. parser = reqparse.RequestParser()
  198. parser.add_argument("inputs", type=dict, location="json")
  199. args = parser.parse_args()
  200. try:
  201. response = AppGenerateService.generate_single_iteration(
  202. app_model=app_model, user=current_user, node_id=node_id, args=args, streaming=True
  203. )
  204. return helper.compact_generate_response(response)
  205. except services.errors.conversation.ConversationNotExistsError:
  206. raise NotFound("Conversation Not Exists.")
  207. except services.errors.conversation.ConversationCompletedError:
  208. raise ConversationCompletedError()
  209. except ValueError as e:
  210. raise e
  211. except Exception:
  212. logging.exception("internal server error.")
  213. raise InternalServerError()
  214. class AdvancedChatDraftRunLoopNodeApi(Resource):
  215. @setup_required
  216. @login_required
  217. @account_initialization_required
  218. @get_app_model(mode=[AppMode.ADVANCED_CHAT])
  219. def post(self, app_model: App, node_id: str):
  220. """
  221. Run draft workflow loop node
  222. """
  223. # The role of the current user in the ta table must be admin, owner, or editor
  224. if not current_user.is_editor:
  225. raise Forbidden()
  226. if not isinstance(current_user, Account):
  227. raise Forbidden()
  228. parser = reqparse.RequestParser()
  229. parser.add_argument("inputs", type=dict, location="json")
  230. args = parser.parse_args()
  231. try:
  232. response = AppGenerateService.generate_single_loop(
  233. app_model=app_model, user=current_user, node_id=node_id, args=args, streaming=True
  234. )
  235. return helper.compact_generate_response(response)
  236. except services.errors.conversation.ConversationNotExistsError:
  237. raise NotFound("Conversation Not Exists.")
  238. except services.errors.conversation.ConversationCompletedError:
  239. raise ConversationCompletedError()
  240. except ValueError as e:
  241. raise e
  242. except Exception:
  243. logging.exception("internal server error.")
  244. raise InternalServerError()
  245. class WorkflowDraftRunLoopNodeApi(Resource):
  246. @setup_required
  247. @login_required
  248. @account_initialization_required
  249. @get_app_model(mode=[AppMode.WORKFLOW])
  250. def post(self, app_model: App, node_id: str):
  251. """
  252. Run draft workflow loop node
  253. """
  254. # The role of the current user in the ta table must be admin, owner, or editor
  255. if not current_user.is_editor:
  256. raise Forbidden()
  257. if not isinstance(current_user, Account):
  258. raise Forbidden()
  259. parser = reqparse.RequestParser()
  260. parser.add_argument("inputs", type=dict, location="json")
  261. args = parser.parse_args()
  262. try:
  263. response = AppGenerateService.generate_single_loop(
  264. app_model=app_model, user=current_user, node_id=node_id, args=args, streaming=True
  265. )
  266. return helper.compact_generate_response(response)
  267. except services.errors.conversation.ConversationNotExistsError:
  268. raise NotFound("Conversation Not Exists.")
  269. except services.errors.conversation.ConversationCompletedError:
  270. raise ConversationCompletedError()
  271. except ValueError as e:
  272. raise e
  273. except Exception:
  274. logging.exception("internal server error.")
  275. raise InternalServerError()
  276. class DraftWorkflowRunApi(Resource):
  277. @setup_required
  278. @login_required
  279. @account_initialization_required
  280. @get_app_model(mode=[AppMode.WORKFLOW])
  281. def post(self, app_model: App):
  282. """
  283. Run draft workflow
  284. """
  285. # The role of the current user in the ta table must be admin, owner, or editor
  286. if not current_user.is_editor:
  287. raise Forbidden()
  288. if not isinstance(current_user, Account):
  289. raise Forbidden()
  290. parser = reqparse.RequestParser()
  291. parser.add_argument("inputs", type=dict, required=True, nullable=False, location="json")
  292. parser.add_argument("files", type=list, required=False, location="json")
  293. args = parser.parse_args()
  294. response = AppGenerateService.generate(
  295. app_model=app_model,
  296. user=current_user,
  297. args=args,
  298. invoke_from=InvokeFrom.DEBUGGER,
  299. streaming=True,
  300. )
  301. return helper.compact_generate_response(response)
  302. class WorkflowTaskStopApi(Resource):
  303. @setup_required
  304. @login_required
  305. @account_initialization_required
  306. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  307. def post(self, app_model: App, task_id: str):
  308. """
  309. Stop workflow task
  310. """
  311. # The role of the current user in the ta table must be admin, owner, or editor
  312. if not current_user.is_editor:
  313. raise Forbidden()
  314. AppQueueManager.set_stop_flag(task_id, InvokeFrom.DEBUGGER, current_user.id)
  315. return {"result": "success"}
  316. class DraftWorkflowNodeRunApi(Resource):
  317. @setup_required
  318. @login_required
  319. @account_initialization_required
  320. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  321. @marshal_with(workflow_run_node_execution_fields)
  322. def post(self, app_model: App, node_id: str):
  323. """
  324. Run draft workflow node
  325. """
  326. # The role of the current user in the ta table must be admin, owner, or editor
  327. if not current_user.is_editor:
  328. raise Forbidden()
  329. if not isinstance(current_user, Account):
  330. raise Forbidden()
  331. parser = reqparse.RequestParser()
  332. parser.add_argument("inputs", type=dict, required=True, nullable=False, location="json")
  333. args = parser.parse_args()
  334. inputs = args.get("inputs")
  335. if inputs == None:
  336. raise ValueError("missing inputs")
  337. workflow_service = WorkflowService()
  338. workflow_node_execution = workflow_service.run_draft_workflow_node(
  339. app_model=app_model, node_id=node_id, user_inputs=inputs, account=current_user
  340. )
  341. return workflow_node_execution
  342. class PublishedWorkflowApi(Resource):
  343. @setup_required
  344. @login_required
  345. @account_initialization_required
  346. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  347. @marshal_with(workflow_fields)
  348. def get(self, app_model: App):
  349. """
  350. Get published workflow
  351. """
  352. # The role of the current user in the ta table must be admin, owner, or editor
  353. if not current_user.is_editor:
  354. raise Forbidden()
  355. # fetch published workflow by app_model
  356. workflow_service = WorkflowService()
  357. workflow = workflow_service.get_published_workflow(app_model=app_model)
  358. # return workflow, if not found, return None
  359. return workflow
  360. @setup_required
  361. @login_required
  362. @account_initialization_required
  363. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  364. def post(self, app_model: App):
  365. """
  366. Publish workflow
  367. """
  368. # The role of the current user in the ta table must be admin, owner, or editor
  369. if not current_user.is_editor:
  370. raise Forbidden()
  371. if not isinstance(current_user, Account):
  372. raise Forbidden()
  373. parser = reqparse.RequestParser()
  374. parser.add_argument("marked_name", type=str, required=False, default="", location="json")
  375. parser.add_argument("marked_comment", type=str, required=False, default="", location="json")
  376. args = parser.parse_args()
  377. # Validate name and comment length
  378. if args.marked_name and len(args.marked_name) > 20:
  379. raise ValueError("Marked name cannot exceed 20 characters")
  380. if args.marked_comment and len(args.marked_comment) > 100:
  381. raise ValueError("Marked comment cannot exceed 100 characters")
  382. workflow_service = WorkflowService()
  383. with Session(db.engine) as session:
  384. workflow = workflow_service.publish_workflow(
  385. session=session,
  386. app_model=app_model,
  387. account=current_user,
  388. marked_name=args.marked_name or "",
  389. marked_comment=args.marked_comment or "",
  390. )
  391. app_model.workflow_id = workflow.id
  392. db.session.commit()
  393. workflow_created_at = TimestampField().format(workflow.created_at)
  394. session.commit()
  395. return {
  396. "result": "success",
  397. "created_at": workflow_created_at,
  398. }
  399. class DefaultBlockConfigsApi(Resource):
  400. @setup_required
  401. @login_required
  402. @account_initialization_required
  403. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  404. def get(self, app_model: App):
  405. """
  406. Get default block config
  407. """
  408. # The role of the current user in the ta table must be admin, owner, or editor
  409. if not current_user.is_editor:
  410. raise Forbidden()
  411. # Get default block configs
  412. workflow_service = WorkflowService()
  413. return workflow_service.get_default_block_configs()
  414. class DefaultBlockConfigApi(Resource):
  415. @setup_required
  416. @login_required
  417. @account_initialization_required
  418. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  419. def get(self, app_model: App, block_type: str):
  420. """
  421. Get default block config
  422. """
  423. # The role of the current user in the ta table must be admin, owner, or editor
  424. if not current_user.is_editor:
  425. raise Forbidden()
  426. if not isinstance(current_user, Account):
  427. raise Forbidden()
  428. parser = reqparse.RequestParser()
  429. parser.add_argument("q", type=str, location="args")
  430. args = parser.parse_args()
  431. q = args.get("q")
  432. filters = None
  433. if q:
  434. try:
  435. filters = json.loads(args.get("q", ""))
  436. except json.JSONDecodeError:
  437. raise ValueError("Invalid filters")
  438. # Get default block configs
  439. workflow_service = WorkflowService()
  440. return workflow_service.get_default_block_config(node_type=block_type, filters=filters)
  441. class ConvertToWorkflowApi(Resource):
  442. @setup_required
  443. @login_required
  444. @account_initialization_required
  445. @get_app_model(mode=[AppMode.CHAT, AppMode.COMPLETION])
  446. def post(self, app_model: App):
  447. """
  448. Convert basic mode of chatbot app to workflow mode
  449. Convert expert mode of chatbot app to workflow mode
  450. Convert Completion App to Workflow App
  451. """
  452. # The role of the current user in the ta table must be admin, owner, or editor
  453. if not current_user.is_editor:
  454. raise Forbidden()
  455. if not isinstance(current_user, Account):
  456. raise Forbidden()
  457. if request.data:
  458. parser = reqparse.RequestParser()
  459. parser.add_argument("name", type=str, required=False, nullable=True, location="json")
  460. parser.add_argument("icon_type", type=str, required=False, nullable=True, location="json")
  461. parser.add_argument("icon", type=str, required=False, nullable=True, location="json")
  462. parser.add_argument("icon_background", type=str, required=False, nullable=True, location="json")
  463. args = parser.parse_args()
  464. else:
  465. args = {}
  466. # convert to workflow mode
  467. workflow_service = WorkflowService()
  468. new_app_model = workflow_service.convert_to_workflow(app_model=app_model, account=current_user, args=args)
  469. # return app id
  470. return {
  471. "new_app_id": new_app_model.id,
  472. }
  473. class WorkflowConfigApi(Resource):
  474. """Resource for workflow configuration."""
  475. @setup_required
  476. @login_required
  477. @account_initialization_required
  478. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  479. def get(self, app_model: App):
  480. return {
  481. "parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT,
  482. }
  483. class PublishedAllWorkflowApi(Resource):
  484. @setup_required
  485. @login_required
  486. @account_initialization_required
  487. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  488. @marshal_with(workflow_pagination_fields)
  489. def get(self, app_model: App):
  490. """
  491. Get published workflows
  492. """
  493. if not current_user.is_editor:
  494. raise Forbidden()
  495. parser = reqparse.RequestParser()
  496. parser.add_argument("page", type=inputs.int_range(1, 99999), required=False, default=1, location="args")
  497. parser.add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args")
  498. parser.add_argument("user_id", type=str, required=False, location="args")
  499. parser.add_argument("named_only", type=inputs.boolean, required=False, default=False, location="args")
  500. args = parser.parse_args()
  501. page = int(args.get("page", 1))
  502. limit = int(args.get("limit", 10))
  503. user_id = args.get("user_id")
  504. named_only = args.get("named_only", False)
  505. if user_id:
  506. if user_id != current_user.id:
  507. raise Forbidden()
  508. user_id = cast(str, user_id)
  509. workflow_service = WorkflowService()
  510. with Session(db.engine) as session:
  511. workflows, has_more = workflow_service.get_all_published_workflow(
  512. session=session,
  513. app_model=app_model,
  514. page=page,
  515. limit=limit,
  516. user_id=user_id,
  517. named_only=named_only,
  518. )
  519. return {
  520. "items": workflows,
  521. "page": page,
  522. "limit": limit,
  523. "has_more": has_more,
  524. }
  525. class WorkflowByIdApi(Resource):
  526. @setup_required
  527. @login_required
  528. @account_initialization_required
  529. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  530. @marshal_with(workflow_fields)
  531. def patch(self, app_model: App, workflow_id: str):
  532. """
  533. Update workflow attributes
  534. """
  535. # Check permission
  536. if not current_user.is_editor:
  537. raise Forbidden()
  538. if not isinstance(current_user, Account):
  539. raise Forbidden()
  540. parser = reqparse.RequestParser()
  541. parser.add_argument("marked_name", type=str, required=False, location="json")
  542. parser.add_argument("marked_comment", type=str, required=False, location="json")
  543. args = parser.parse_args()
  544. # Validate name and comment length
  545. if args.marked_name and len(args.marked_name) > 20:
  546. raise ValueError("Marked name cannot exceed 20 characters")
  547. if args.marked_comment and len(args.marked_comment) > 100:
  548. raise ValueError("Marked comment cannot exceed 100 characters")
  549. args = parser.parse_args()
  550. # Prepare update data
  551. update_data = {}
  552. if args.get("marked_name") is not None:
  553. update_data["marked_name"] = args["marked_name"]
  554. if args.get("marked_comment") is not None:
  555. update_data["marked_comment"] = args["marked_comment"]
  556. if not update_data:
  557. return {"message": "No valid fields to update"}, 400
  558. workflow_service = WorkflowService()
  559. # Create a session and manage the transaction
  560. with Session(db.engine, expire_on_commit=False) as session:
  561. workflow = workflow_service.update_workflow(
  562. session=session,
  563. workflow_id=workflow_id,
  564. tenant_id=app_model.tenant_id,
  565. account_id=current_user.id,
  566. data=update_data,
  567. )
  568. if not workflow:
  569. raise NotFound("Workflow not found")
  570. # Commit the transaction in the controller
  571. session.commit()
  572. return workflow
  573. @setup_required
  574. @login_required
  575. @account_initialization_required
  576. @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
  577. def delete(self, app_model: App, workflow_id: str):
  578. """
  579. Delete workflow
  580. """
  581. # Check permission
  582. if not current_user.is_editor:
  583. raise Forbidden()
  584. if not isinstance(current_user, Account):
  585. raise Forbidden()
  586. workflow_service = WorkflowService()
  587. # Create a session and manage the transaction
  588. with Session(db.engine) as session:
  589. try:
  590. workflow_service.delete_workflow(
  591. session=session, workflow_id=workflow_id, tenant_id=app_model.tenant_id
  592. )
  593. # Commit the transaction in the controller
  594. session.commit()
  595. except WorkflowInUseError as e:
  596. abort(400, description=str(e))
  597. except DraftWorkflowDeletionError as e:
  598. abort(400, description=str(e))
  599. except ValueError as e:
  600. raise NotFound(str(e))
  601. return None, 204
  602. api.add_resource(
  603. DraftWorkflowApi,
  604. "/apps/<uuid:app_id>/workflows/draft",
  605. )
  606. api.add_resource(
  607. WorkflowConfigApi,
  608. "/apps/<uuid:app_id>/workflows/draft/config",
  609. )
  610. api.add_resource(
  611. AdvancedChatDraftWorkflowRunApi,
  612. "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run",
  613. )
  614. api.add_resource(
  615. DraftWorkflowRunApi,
  616. "/apps/<uuid:app_id>/workflows/draft/run",
  617. )
  618. api.add_resource(
  619. WorkflowTaskStopApi,
  620. "/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop",
  621. )
  622. api.add_resource(
  623. DraftWorkflowNodeRunApi,
  624. "/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/run",
  625. )
  626. api.add_resource(
  627. AdvancedChatDraftRunIterationNodeApi,
  628. "/apps/<uuid:app_id>/advanced-chat/workflows/draft/iteration/nodes/<string:node_id>/run",
  629. )
  630. api.add_resource(
  631. WorkflowDraftRunIterationNodeApi,
  632. "/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run",
  633. )
  634. api.add_resource(
  635. AdvancedChatDraftRunLoopNodeApi,
  636. "/apps/<uuid:app_id>/advanced-chat/workflows/draft/loop/nodes/<string:node_id>/run",
  637. )
  638. api.add_resource(
  639. WorkflowDraftRunLoopNodeApi,
  640. "/apps/<uuid:app_id>/workflows/draft/loop/nodes/<string:node_id>/run",
  641. )
  642. api.add_resource(
  643. PublishedWorkflowApi,
  644. "/apps/<uuid:app_id>/workflows/publish",
  645. )
  646. api.add_resource(
  647. PublishedAllWorkflowApi,
  648. "/apps/<uuid:app_id>/workflows",
  649. )
  650. api.add_resource(
  651. DefaultBlockConfigsApi,
  652. "/apps/<uuid:app_id>/workflows/default-workflow-block-configs",
  653. )
  654. api.add_resource(
  655. DefaultBlockConfigApi,
  656. "/apps/<uuid:app_id>/workflows/default-workflow-block-configs/<string:block_type>",
  657. )
  658. api.add_resource(
  659. ConvertToWorkflowApi,
  660. "/apps/<uuid:app_id>/convert-to-workflow",
  661. )
  662. api.add_resource(
  663. WorkflowByIdApi,
  664. "/apps/<uuid:app_id>/workflows/<string:workflow_id>",
  665. )