You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

workflow.py 28KB

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