mirror of
https://github.com/langgenius/dify.git
synced 2026-02-19 07:01:42 -05:00
feat: Human Input Node (#32060)
The frontend and backend implementation for the human input node. Co-authored-by: twwu <twwu@dify.ai> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do>
This commit is contained in:
@@ -34,6 +34,8 @@ from .enums import (
|
||||
WorkflowRunTriggeredFrom,
|
||||
WorkflowTriggerStatus,
|
||||
)
|
||||
from .execution_extra_content import ExecutionExtraContent, HumanInputContent
|
||||
from .human_input import HumanInputForm
|
||||
from .model import (
|
||||
AccountTrialAppRecord,
|
||||
ApiRequest,
|
||||
@@ -155,9 +157,12 @@ __all__ = [
|
||||
"DocumentSegment",
|
||||
"Embedding",
|
||||
"EndUser",
|
||||
"ExecutionExtraContent",
|
||||
"ExporleBanner",
|
||||
"ExternalKnowledgeApis",
|
||||
"ExternalKnowledgeBindings",
|
||||
"HumanInputContent",
|
||||
"HumanInputForm",
|
||||
"IconType",
|
||||
"InstalledApp",
|
||||
"InvitationCode",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import DateTime, func
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
|
||||
@@ -41,7 +42,7 @@ class DefaultFieldsMixin:
|
||||
)
|
||||
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
__name_pos=DateTime,
|
||||
DateTime,
|
||||
nullable=False,
|
||||
default=naive_utc_now,
|
||||
server_default=func.current_timestamp(),
|
||||
@@ -50,3 +51,16 @@ class DefaultFieldsMixin:
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__}(id={self.id})>"
|
||||
|
||||
|
||||
def gen_uuidv4_string() -> str:
|
||||
"""gen_uuidv4_string generate a UUIDv4 string.
|
||||
|
||||
NOTE: This function exists only for historical reasons. New models should use uuidv7 for primary key generation.
|
||||
"""
|
||||
return str(uuid4())
|
||||
|
||||
|
||||
def gen_uuidv7_string() -> str:
|
||||
"""gen_uuidv4_string generate a UUIDv4 string."""
|
||||
return str(uuidv7())
|
||||
|
||||
@@ -36,6 +36,7 @@ class MessageStatus(StrEnum):
|
||||
"""
|
||||
|
||||
NORMAL = "normal"
|
||||
PAUSED = "paused"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
|
||||
78
api/models/execution_extra_content.py
Normal file
78
api/models/execution_extra_content.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from enum import StrEnum, auto
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from .base import Base, DefaultFieldsMixin
|
||||
from .types import EnumText, StringUUID
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .human_input import HumanInputForm
|
||||
|
||||
|
||||
class ExecutionContentType(StrEnum):
|
||||
HUMAN_INPUT = auto()
|
||||
|
||||
|
||||
class ExecutionExtraContent(DefaultFieldsMixin, Base):
|
||||
"""ExecutionExtraContent stores extra contents produced during workflow / chatflow execution."""
|
||||
|
||||
# The `ExecutionExtraContent` uses single table inheritance to model different
|
||||
# kinds of contents produced during message generation.
|
||||
#
|
||||
# See: https://docs.sqlalchemy.org/en/20/orm/inheritance.html#single-table-inheritance
|
||||
|
||||
__tablename__ = "execution_extra_contents"
|
||||
__mapper_args__ = {
|
||||
"polymorphic_abstract": True,
|
||||
"polymorphic_on": "type",
|
||||
"with_polymorphic": "*",
|
||||
}
|
||||
# type records the type of the content. It serves as the `discriminator` for the
|
||||
# single table inheritance.
|
||||
type: Mapped[ExecutionContentType] = mapped_column(
|
||||
EnumText(ExecutionContentType, length=30),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# `workflow_run_id` records the workflow execution which generates this content, correspond to
|
||||
# `WorkflowRun.id`.
|
||||
workflow_run_id: Mapped[str] = mapped_column(StringUUID, nullable=False, index=True)
|
||||
|
||||
# `message_id` records the messages generated by the execution associated with this `ExecutionExtraContent`.
|
||||
# It references to `Message.id`.
|
||||
#
|
||||
# For workflow execution, this field is `None`.
|
||||
#
|
||||
# For chatflow execution, `message_id`` is not None, and the following condition holds:
|
||||
#
|
||||
# The message referenced by `message_id` has `message.workflow_run_id == execution_extra_content.workflow_run_id`
|
||||
#
|
||||
message_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, index=True)
|
||||
|
||||
|
||||
class HumanInputContent(ExecutionExtraContent):
|
||||
"""HumanInputContent is a concrete class that represents human input content.
|
||||
It should only be initialized with the `new` class method."""
|
||||
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": ExecutionContentType.HUMAN_INPUT,
|
||||
}
|
||||
|
||||
# A relation to HumanInputForm table.
|
||||
#
|
||||
# While the form_id column is nullable in database (due to the nature of single table inheritance),
|
||||
# the form_id field should not be null for a given `HumanInputContent` instance.
|
||||
form_id: Mapped[str] = mapped_column(StringUUID, nullable=True)
|
||||
|
||||
@classmethod
|
||||
def new(cls, form_id: str, message_id: str | None) -> "HumanInputContent":
|
||||
return cls(form_id=form_id, message_id=message_id)
|
||||
|
||||
form: Mapped["HumanInputForm"] = relationship(
|
||||
"HumanInputForm",
|
||||
foreign_keys=[form_id],
|
||||
uselist=False,
|
||||
lazy="raise",
|
||||
primaryjoin="foreign(HumanInputContent.form_id) == HumanInputForm.id",
|
||||
)
|
||||
237
api/models/human_input.py
Normal file
237
api/models/human_input.py
Normal file
@@ -0,0 +1,237 @@
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Annotated, Literal, Self, final
|
||||
|
||||
import sqlalchemy as sa
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from core.workflow.nodes.human_input.enums import (
|
||||
DeliveryMethodType,
|
||||
HumanInputFormKind,
|
||||
HumanInputFormStatus,
|
||||
)
|
||||
from libs.helper import generate_string
|
||||
|
||||
from .base import Base, DefaultFieldsMixin
|
||||
from .types import EnumText, StringUUID
|
||||
|
||||
_token_length = 22
|
||||
# A 32-character string can store a base64-encoded value with 192 bits of entropy
|
||||
# or a base62-encoded value with over 180 bits of entropy, providing sufficient
|
||||
# uniqueness for most use cases.
|
||||
_token_field_length = 32
|
||||
_email_field_length = 330
|
||||
|
||||
|
||||
def _generate_token() -> str:
|
||||
return generate_string(_token_length)
|
||||
|
||||
|
||||
class HumanInputForm(DefaultFieldsMixin, Base):
|
||||
__tablename__ = "human_input_forms"
|
||||
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
workflow_run_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
form_kind: Mapped[HumanInputFormKind] = mapped_column(
|
||||
EnumText(HumanInputFormKind),
|
||||
nullable=False,
|
||||
default=HumanInputFormKind.RUNTIME,
|
||||
)
|
||||
|
||||
# The human input node the current form corresponds to.
|
||||
node_id: Mapped[str] = mapped_column(sa.String(60), nullable=False)
|
||||
form_definition: Mapped[str] = mapped_column(sa.Text, nullable=False)
|
||||
rendered_content: Mapped[str] = mapped_column(sa.Text, nullable=False)
|
||||
status: Mapped[HumanInputFormStatus] = mapped_column(
|
||||
EnumText(HumanInputFormStatus),
|
||||
nullable=False,
|
||||
default=HumanInputFormStatus.WAITING,
|
||||
)
|
||||
|
||||
expiration_time: Mapped[datetime] = mapped_column(
|
||||
sa.DateTime,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Submission-related fields (nullable until a submission happens).
|
||||
selected_action_id: Mapped[str | None] = mapped_column(sa.String(200), nullable=True)
|
||||
submitted_data: Mapped[str | None] = mapped_column(sa.Text, nullable=True)
|
||||
submitted_at: Mapped[datetime | None] = mapped_column(sa.DateTime, nullable=True)
|
||||
submission_user_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
submission_end_user_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
|
||||
completed_by_recipient_id: Mapped[str | None] = mapped_column(
|
||||
StringUUID,
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
deliveries: Mapped[list["HumanInputDelivery"]] = relationship(
|
||||
"HumanInputDelivery",
|
||||
primaryjoin="HumanInputForm.id == foreign(HumanInputDelivery.form_id)",
|
||||
uselist=True,
|
||||
back_populates="form",
|
||||
lazy="raise",
|
||||
)
|
||||
completed_by_recipient: Mapped["HumanInputFormRecipient | None"] = relationship(
|
||||
"HumanInputFormRecipient",
|
||||
primaryjoin="HumanInputForm.completed_by_recipient_id == foreign(HumanInputFormRecipient.id)",
|
||||
lazy="raise",
|
||||
viewonly=True,
|
||||
)
|
||||
|
||||
|
||||
class HumanInputDelivery(DefaultFieldsMixin, Base):
|
||||
__tablename__ = "human_input_form_deliveries"
|
||||
|
||||
form_id: Mapped[str] = mapped_column(
|
||||
StringUUID,
|
||||
nullable=False,
|
||||
)
|
||||
delivery_method_type: Mapped[DeliveryMethodType] = mapped_column(
|
||||
EnumText(DeliveryMethodType),
|
||||
nullable=False,
|
||||
)
|
||||
delivery_config_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
channel_payload: Mapped[str] = mapped_column(sa.Text, nullable=False)
|
||||
|
||||
form: Mapped[HumanInputForm] = relationship(
|
||||
"HumanInputForm",
|
||||
uselist=False,
|
||||
foreign_keys=[form_id],
|
||||
primaryjoin="HumanInputDelivery.form_id == HumanInputForm.id",
|
||||
back_populates="deliveries",
|
||||
lazy="raise",
|
||||
)
|
||||
|
||||
recipients: Mapped[list["HumanInputFormRecipient"]] = relationship(
|
||||
"HumanInputFormRecipient",
|
||||
primaryjoin="HumanInputDelivery.id == foreign(HumanInputFormRecipient.delivery_id)",
|
||||
uselist=True,
|
||||
back_populates="delivery",
|
||||
# Require explicit preloading
|
||||
lazy="raise",
|
||||
)
|
||||
|
||||
|
||||
class RecipientType(StrEnum):
|
||||
# EMAIL_MEMBER member means that the
|
||||
EMAIL_MEMBER = "email_member"
|
||||
EMAIL_EXTERNAL = "email_external"
|
||||
# STANDALONE_WEB_APP is used by the standalone web app.
|
||||
#
|
||||
# It's not used while running workflows / chatflows containing HumanInput
|
||||
# node inside console.
|
||||
STANDALONE_WEB_APP = "standalone_web_app"
|
||||
# CONSOLE is used while running workflows / chatflows containing HumanInput
|
||||
# node inside console. (E.G. running installed apps or debugging workflows / chatflows)
|
||||
CONSOLE = "console"
|
||||
# BACKSTAGE is used for backstage input inside console.
|
||||
BACKSTAGE = "backstage"
|
||||
|
||||
|
||||
@final
|
||||
class EmailMemberRecipientPayload(BaseModel):
|
||||
TYPE: Literal[RecipientType.EMAIL_MEMBER] = RecipientType.EMAIL_MEMBER
|
||||
user_id: str
|
||||
|
||||
# The `email` field here is only used for mail sending.
|
||||
email: str
|
||||
|
||||
|
||||
@final
|
||||
class EmailExternalRecipientPayload(BaseModel):
|
||||
TYPE: Literal[RecipientType.EMAIL_EXTERNAL] = RecipientType.EMAIL_EXTERNAL
|
||||
email: str
|
||||
|
||||
|
||||
@final
|
||||
class StandaloneWebAppRecipientPayload(BaseModel):
|
||||
TYPE: Literal[RecipientType.STANDALONE_WEB_APP] = RecipientType.STANDALONE_WEB_APP
|
||||
|
||||
|
||||
@final
|
||||
class ConsoleRecipientPayload(BaseModel):
|
||||
TYPE: Literal[RecipientType.CONSOLE] = RecipientType.CONSOLE
|
||||
account_id: str | None = None
|
||||
|
||||
|
||||
@final
|
||||
class BackstageRecipientPayload(BaseModel):
|
||||
TYPE: Literal[RecipientType.BACKSTAGE] = RecipientType.BACKSTAGE
|
||||
account_id: str | None = None
|
||||
|
||||
|
||||
@final
|
||||
class ConsoleDeliveryPayload(BaseModel):
|
||||
type: Literal["console"] = "console"
|
||||
internal: bool = True
|
||||
|
||||
|
||||
RecipientPayload = Annotated[
|
||||
EmailMemberRecipientPayload
|
||||
| EmailExternalRecipientPayload
|
||||
| StandaloneWebAppRecipientPayload
|
||||
| ConsoleRecipientPayload
|
||||
| BackstageRecipientPayload,
|
||||
Field(discriminator="TYPE"),
|
||||
]
|
||||
|
||||
|
||||
class HumanInputFormRecipient(DefaultFieldsMixin, Base):
|
||||
__tablename__ = "human_input_form_recipients"
|
||||
|
||||
form_id: Mapped[str] = mapped_column(
|
||||
StringUUID,
|
||||
nullable=False,
|
||||
)
|
||||
delivery_id: Mapped[str] = mapped_column(
|
||||
StringUUID,
|
||||
nullable=False,
|
||||
)
|
||||
recipient_type: Mapped["RecipientType"] = mapped_column(EnumText(RecipientType), nullable=False)
|
||||
recipient_payload: Mapped[str] = mapped_column(sa.Text, nullable=False)
|
||||
|
||||
# Token primarily used for authenticated resume links (email, etc.).
|
||||
access_token: Mapped[str | None] = mapped_column(
|
||||
sa.VARCHAR(_token_field_length),
|
||||
nullable=False,
|
||||
default=_generate_token,
|
||||
unique=True,
|
||||
)
|
||||
|
||||
delivery: Mapped[HumanInputDelivery] = relationship(
|
||||
"HumanInputDelivery",
|
||||
uselist=False,
|
||||
foreign_keys=[delivery_id],
|
||||
back_populates="recipients",
|
||||
primaryjoin="HumanInputFormRecipient.delivery_id == HumanInputDelivery.id",
|
||||
# Require explicit preloading
|
||||
lazy="raise",
|
||||
)
|
||||
|
||||
form: Mapped[HumanInputForm] = relationship(
|
||||
"HumanInputForm",
|
||||
uselist=False,
|
||||
foreign_keys=[form_id],
|
||||
primaryjoin="HumanInputFormRecipient.form_id == HumanInputForm.id",
|
||||
# Require explicit preloading
|
||||
lazy="raise",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def new(
|
||||
cls,
|
||||
form_id: str,
|
||||
delivery_id: str,
|
||||
payload: RecipientPayload,
|
||||
) -> Self:
|
||||
recipient_model = cls(
|
||||
form_id=form_id,
|
||||
delivery_id=delivery_id,
|
||||
recipient_type=payload.TYPE,
|
||||
recipient_payload=payload.model_dump_json(),
|
||||
access_token=_generate_token(),
|
||||
)
|
||||
return recipient_model
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Mapping, Sequence
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from enum import StrEnum, auto
|
||||
@@ -26,7 +26,7 @@ from libs.helper import generate_string # type: ignore[import-not-found]
|
||||
from libs.uuid_utils import uuidv7
|
||||
|
||||
from .account import Account, Tenant
|
||||
from .base import Base, TypeBase
|
||||
from .base import Base, TypeBase, gen_uuidv4_string
|
||||
from .engine import db
|
||||
from .enums import CreatorUserRole
|
||||
from .provider_ids import GenericProviderID
|
||||
@@ -620,7 +620,7 @@ class TrialApp(Base):
|
||||
sa.UniqueConstraint("app_id", name="unique_trail_app_id"),
|
||||
)
|
||||
|
||||
id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
|
||||
id = mapped_column(StringUUID, default=gen_uuidv4_string)
|
||||
app_id = mapped_column(StringUUID, nullable=False)
|
||||
tenant_id = mapped_column(StringUUID, nullable=False)
|
||||
created_at = mapped_column(sa.DateTime, nullable=False, server_default=func.current_timestamp())
|
||||
@@ -640,7 +640,7 @@ class AccountTrialAppRecord(Base):
|
||||
sa.Index("account_trial_app_record_app_id_idx", "app_id"),
|
||||
sa.UniqueConstraint("account_id", "app_id", name="unique_account_trial_app_record"),
|
||||
)
|
||||
id = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"))
|
||||
id = mapped_column(StringUUID, default=gen_uuidv4_string)
|
||||
account_id = mapped_column(StringUUID, nullable=False)
|
||||
app_id = mapped_column(StringUUID, nullable=False)
|
||||
count = mapped_column(sa.Integer, nullable=False, default=0)
|
||||
@@ -660,7 +660,7 @@ class AccountTrialAppRecord(Base):
|
||||
class ExporleBanner(TypeBase):
|
||||
__tablename__ = "exporle_banners"
|
||||
__table_args__ = (sa.PrimaryKeyConstraint("id", name="exporler_banner_pkey"),)
|
||||
id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"), init=False)
|
||||
id: Mapped[str] = mapped_column(StringUUID, default=gen_uuidv4_string, init=False)
|
||||
content: Mapped[dict[str, Any]] = mapped_column(sa.JSON, nullable=False)
|
||||
link: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
sort: Mapped[int] = mapped_column(sa.Integer, nullable=False)
|
||||
@@ -943,6 +943,7 @@ class Conversation(Base):
|
||||
WorkflowExecutionStatus.FAILED: 0,
|
||||
WorkflowExecutionStatus.STOPPED: 0,
|
||||
WorkflowExecutionStatus.PARTIAL_SUCCEEDED: 0,
|
||||
WorkflowExecutionStatus.PAUSED: 0,
|
||||
}
|
||||
|
||||
for message in messages:
|
||||
@@ -963,6 +964,7 @@ class Conversation(Base):
|
||||
"success": status_counts[WorkflowExecutionStatus.SUCCEEDED],
|
||||
"failed": status_counts[WorkflowExecutionStatus.FAILED],
|
||||
"partial_success": status_counts[WorkflowExecutionStatus.PARTIAL_SUCCEEDED],
|
||||
"paused": status_counts[WorkflowExecutionStatus.PAUSED],
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -1345,6 +1347,14 @@ class Message(Base):
|
||||
db.session.commit()
|
||||
return result
|
||||
|
||||
# TODO(QuantumGhost): dirty hacks, fix this later.
|
||||
def set_extra_contents(self, contents: Sequence[dict[str, Any]]) -> None:
|
||||
self._extra_contents = list(contents)
|
||||
|
||||
@property
|
||||
def extra_contents(self) -> list[dict[str, Any]]:
|
||||
return getattr(self, "_extra_contents", [])
|
||||
|
||||
@property
|
||||
def workflow_run(self):
|
||||
if self.workflow_run_id:
|
||||
|
||||
@@ -20,6 +20,7 @@ from sqlalchemy import (
|
||||
select,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, declared_attr, mapped_column
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from core.file.constants import maybe_file_object
|
||||
from core.file.models import File
|
||||
@@ -31,7 +32,7 @@ from core.workflow.constants import (
|
||||
)
|
||||
from core.workflow.entities.graph_config import NodeConfigDict, NodeConfigDictAdapter
|
||||
from core.workflow.entities.pause_reason import HumanInputRequired, PauseReason, PauseReasonType, SchedulingPause
|
||||
from core.workflow.enums import NodeType
|
||||
from core.workflow.enums import NodeType, WorkflowExecutionStatus
|
||||
from extensions.ext_storage import Storage
|
||||
from factories.variable_factory import TypeMismatchError, build_segment_with_type
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
@@ -405,6 +406,11 @@ class Workflow(Base): # bug
|
||||
return helper.generate_text_hash(json.dumps(entity, sort_keys=True))
|
||||
|
||||
@property
|
||||
@deprecated(
|
||||
"This property is not accurate for determining if a workflow is published as a tool."
|
||||
"It only checks if there's a WorkflowToolProvider for the app, "
|
||||
"not if this specific workflow version is the one being used by the tool."
|
||||
)
|
||||
def tool_published(self) -> bool:
|
||||
"""
|
||||
DEPRECATED: This property is not accurate for determining if a workflow is published as a tool.
|
||||
@@ -607,13 +613,16 @@ class WorkflowRun(Base):
|
||||
version: Mapped[str] = mapped_column(String(255))
|
||||
graph: Mapped[str | None] = mapped_column(LongText)
|
||||
inputs: Mapped[str | None] = mapped_column(LongText)
|
||||
status: Mapped[str] = mapped_column(String(255)) # running, succeeded, failed, stopped, partial-succeeded
|
||||
status: Mapped[WorkflowExecutionStatus] = mapped_column(
|
||||
EnumText(WorkflowExecutionStatus, length=255),
|
||||
nullable=False,
|
||||
)
|
||||
outputs: Mapped[str | None] = mapped_column(LongText, default="{}")
|
||||
error: Mapped[str | None] = mapped_column(LongText)
|
||||
elapsed_time: Mapped[float] = mapped_column(sa.Float, nullable=False, server_default=sa.text("0"))
|
||||
total_tokens: Mapped[int] = mapped_column(sa.BigInteger, server_default=sa.text("0"))
|
||||
total_steps: Mapped[int] = mapped_column(sa.Integer, server_default=sa.text("0"), nullable=True)
|
||||
created_by_role: Mapped[str] = mapped_column(String(255)) # account, end_user
|
||||
created_by_role: Mapped[CreatorUserRole] = mapped_column(EnumText(CreatorUserRole, length=255)) # account, end_user
|
||||
created_by: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=func.current_timestamp())
|
||||
finished_at: Mapped[datetime | None] = mapped_column(DateTime)
|
||||
@@ -629,11 +638,13 @@ class WorkflowRun(Base):
|
||||
)
|
||||
|
||||
@property
|
||||
@deprecated("This method is retained for historical reasons; avoid using it if possible.")
|
||||
def created_by_account(self):
|
||||
created_by_role = CreatorUserRole(self.created_by_role)
|
||||
return db.session.get(Account, self.created_by) if created_by_role == CreatorUserRole.ACCOUNT else None
|
||||
|
||||
@property
|
||||
@deprecated("This method is retained for historical reasons; avoid using it if possible.")
|
||||
def created_by_end_user(self):
|
||||
from .model import EndUser
|
||||
|
||||
@@ -653,6 +664,7 @@ class WorkflowRun(Base):
|
||||
return json.loads(self.outputs) if self.outputs else {}
|
||||
|
||||
@property
|
||||
@deprecated("This method is retained for historical reasons; avoid using it if possible.")
|
||||
def message(self):
|
||||
from .model import Message
|
||||
|
||||
@@ -661,6 +673,7 @@ class WorkflowRun(Base):
|
||||
)
|
||||
|
||||
@property
|
||||
@deprecated("This method is retained for historical reasons; avoid using it if possible.")
|
||||
def workflow(self):
|
||||
return db.session.query(Workflow).where(Workflow.id == self.workflow_id).first()
|
||||
|
||||
@@ -1861,7 +1874,12 @@ class WorkflowPauseReason(DefaultFieldsMixin, Base):
|
||||
|
||||
def to_entity(self) -> PauseReason:
|
||||
if self.type_ == PauseReasonType.HUMAN_INPUT_REQUIRED:
|
||||
return HumanInputRequired(form_id=self.form_id, node_id=self.node_id)
|
||||
return HumanInputRequired(
|
||||
form_id=self.form_id,
|
||||
form_content="",
|
||||
node_id=self.node_id,
|
||||
node_title="",
|
||||
)
|
||||
elif self.type_ == PauseReasonType.SCHEDULED_PAUSE:
|
||||
return SchedulingPause(message=self.message)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user