Signed-off-by: -LAN- <laipz8200@outlook.com>tags/1.5.0
| import logging | import logging | ||||
| from typing import Optional, Union | from typing import Optional, Union | ||||
| from sqlalchemy import func, select | |||||
| from sqlalchemy import select | |||||
| from sqlalchemy.engine import Engine | from sqlalchemy.engine import Engine | ||||
| from sqlalchemy.orm import sessionmaker | from sqlalchemy.orm import sessionmaker | ||||
| db_model.workflow_id = domain_model.workflow_id | db_model.workflow_id = domain_model.workflow_id | ||||
| db_model.triggered_from = self._triggered_from | db_model.triggered_from = self._triggered_from | ||||
| # Check if this is a new record | |||||
| with self._session_factory() as session: | |||||
| existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_)) | |||||
| if not existing: | |||||
| # For new records, get the next sequence number | |||||
| stmt = select(func.max(WorkflowRun.sequence_number)).where( | |||||
| WorkflowRun.app_id == self._app_id, | |||||
| WorkflowRun.tenant_id == self._tenant_id, | |||||
| ) | |||||
| max_sequence = session.scalar(stmt) | |||||
| db_model.sequence_number = (max_sequence or 0) + 1 | |||||
| else: | |||||
| # For updates, keep the existing sequence number | |||||
| db_model.sequence_number = existing.sequence_number | |||||
| # No sequence number generation needed anymore | |||||
| db_model.type = domain_model.workflow_type | db_model.type = domain_model.workflow_type | ||||
| db_model.version = domain_model.workflow_version | db_model.version = domain_model.workflow_version |
| workflow_run_for_list_fields = { | workflow_run_for_list_fields = { | ||||
| "id": fields.String, | "id": fields.String, | ||||
| "sequence_number": fields.Integer, | |||||
| "version": fields.String, | "version": fields.String, | ||||
| "status": fields.String, | "status": fields.String, | ||||
| "elapsed_time": fields.Float, | "elapsed_time": fields.Float, | ||||
| "id": fields.String, | "id": fields.String, | ||||
| "conversation_id": fields.String, | "conversation_id": fields.String, | ||||
| "message_id": fields.String, | "message_id": fields.String, | ||||
| "sequence_number": fields.Integer, | |||||
| "version": fields.String, | "version": fields.String, | ||||
| "status": fields.String, | "status": fields.String, | ||||
| "elapsed_time": fields.Float, | "elapsed_time": fields.Float, | ||||
| workflow_run_detail_fields = { | workflow_run_detail_fields = { | ||||
| "id": fields.String, | "id": fields.String, | ||||
| "sequence_number": fields.Integer, | |||||
| "version": fields.String, | "version": fields.String, | ||||
| "graph": fields.Raw(attribute="graph_dict"), | "graph": fields.Raw(attribute="graph_dict"), | ||||
| "inputs": fields.Raw(attribute="inputs_dict"), | "inputs": fields.Raw(attribute="inputs_dict"), |
| """remove sequence_number from workflow_runs | |||||
| Revision ID: 0ab65e1cc7fa | |||||
| Revises: 4474872b0ee6 | |||||
| Create Date: 2025-06-19 16:33:13.377215 | |||||
| """ | |||||
| from alembic import op | |||||
| import models as models | |||||
| import sqlalchemy as sa | |||||
| # revision identifiers, used by Alembic. | |||||
| revision = '0ab65e1cc7fa' | |||||
| down_revision = '4474872b0ee6' | |||||
| branch_labels = None | |||||
| depends_on = None | |||||
| def upgrade(): | |||||
| # ### commands auto generated by Alembic - please adjust! ### | |||||
| with op.batch_alter_table('workflow_runs', schema=None) as batch_op: | |||||
| batch_op.drop_index(batch_op.f('workflow_run_tenant_app_sequence_idx')) | |||||
| batch_op.drop_column('sequence_number') | |||||
| # ### end Alembic commands ### | |||||
| def downgrade(): | |||||
| # ### commands auto generated by Alembic - please adjust! ### | |||||
| # WARNING: This downgrade CANNOT recover the original sequence_number values! | |||||
| # The original sequence numbers are permanently lost after the upgrade. | |||||
| # This downgrade will regenerate sequence numbers based on created_at order, | |||||
| # which may result in different values than the original sequence numbers. | |||||
| # | |||||
| # If you need to preserve original sequence numbers, use the alternative | |||||
| # migration approach that creates a backup table before removal. | |||||
| # Step 1: Add sequence_number column as nullable first | |||||
| with op.batch_alter_table('workflow_runs', schema=None) as batch_op: | |||||
| batch_op.add_column(sa.Column('sequence_number', sa.INTEGER(), autoincrement=False, nullable=True)) | |||||
| # Step 2: Populate sequence_number values based on created_at order within each app | |||||
| # NOTE: This recreates sequence numbering logic but values will be different | |||||
| # from the original sequence numbers that were removed in the upgrade | |||||
| connection = op.get_bind() | |||||
| connection.execute(sa.text(""" | |||||
| UPDATE workflow_runs | |||||
| SET sequence_number = subquery.row_num | |||||
| FROM ( | |||||
| SELECT id, ROW_NUMBER() OVER ( | |||||
| PARTITION BY tenant_id, app_id | |||||
| ORDER BY created_at, id | |||||
| ) as row_num | |||||
| FROM workflow_runs | |||||
| ) subquery | |||||
| WHERE workflow_runs.id = subquery.id | |||||
| """)) | |||||
| # Step 3: Make the column NOT NULL and add the index | |||||
| with op.batch_alter_table('workflow_runs', schema=None) as batch_op: | |||||
| batch_op.alter_column('sequence_number', nullable=False) | |||||
| batch_op.create_index(batch_op.f('workflow_run_tenant_app_sequence_idx'), ['tenant_id', 'app_id', 'sequence_number'], unique=False) | |||||
| # ### end Alembic commands ### |
| - id (uuid) Run ID | - id (uuid) Run ID | ||||
| - tenant_id (uuid) Workspace ID | - tenant_id (uuid) Workspace ID | ||||
| - app_id (uuid) App ID | - app_id (uuid) App ID | ||||
| - sequence_number (int) Auto-increment sequence number, incremented within the App, starting from 1 | |||||
| - workflow_id (uuid) Workflow ID | - workflow_id (uuid) Workflow ID | ||||
| - type (string) Workflow type | - type (string) Workflow type | ||||
| - triggered_from (string) Trigger source | - triggered_from (string) Trigger source | ||||
| __table_args__ = ( | __table_args__ = ( | ||||
| db.PrimaryKeyConstraint("id", name="workflow_run_pkey"), | db.PrimaryKeyConstraint("id", name="workflow_run_pkey"), | ||||
| db.Index("workflow_run_triggerd_from_idx", "tenant_id", "app_id", "triggered_from"), | db.Index("workflow_run_triggerd_from_idx", "tenant_id", "app_id", "triggered_from"), | ||||
| db.Index("workflow_run_tenant_app_sequence_idx", "tenant_id", "app_id", "sequence_number"), | |||||
| ) | ) | ||||
| id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) | id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) | ||||
| tenant_id: Mapped[str] = mapped_column(StringUUID) | tenant_id: Mapped[str] = mapped_column(StringUUID) | ||||
| app_id: Mapped[str] = mapped_column(StringUUID) | app_id: Mapped[str] = mapped_column(StringUUID) | ||||
| sequence_number: Mapped[int] = mapped_column() | |||||
| workflow_id: Mapped[str] = mapped_column(StringUUID) | workflow_id: Mapped[str] = mapped_column(StringUUID) | ||||
| type: Mapped[str] = mapped_column(db.String(255)) | type: Mapped[str] = mapped_column(db.String(255)) | ||||
| triggered_from: Mapped[str] = mapped_column(db.String(255)) | triggered_from: Mapped[str] = mapped_column(db.String(255)) | ||||
| "id": self.id, | "id": self.id, | ||||
| "tenant_id": self.tenant_id, | "tenant_id": self.tenant_id, | ||||
| "app_id": self.app_id, | "app_id": self.app_id, | ||||
| "sequence_number": self.sequence_number, | |||||
| "workflow_id": self.workflow_id, | "workflow_id": self.workflow_id, | ||||
| "type": self.type, | "type": self.type, | ||||
| "triggered_from": self.triggered_from, | "triggered_from": self.triggered_from, | ||||
| id=data.get("id"), | id=data.get("id"), | ||||
| tenant_id=data.get("tenant_id"), | tenant_id=data.get("tenant_id"), | ||||
| app_id=data.get("app_id"), | app_id=data.get("app_id"), | ||||
| sequence_number=data.get("sequence_number"), | |||||
| workflow_id=data.get("workflow_id"), | workflow_id=data.get("workflow_id"), | ||||
| type=data.get("type"), | type=data.get("type"), | ||||
| triggered_from=data.get("triggered_from"), | triggered_from=data.get("triggered_from"), |
| workflow_run.tenant_id = "test-tenant-id" | workflow_run.tenant_id = "test-tenant-id" | ||||
| workflow_run.app_id = "test-app-id" | workflow_run.app_id = "test-app-id" | ||||
| workflow_run.workflow_id = "test-workflow-id" | workflow_run.workflow_id = "test-workflow-id" | ||||
| workflow_run.sequence_number = 1 | |||||
| workflow_run.type = "chat" | workflow_run.type = "chat" | ||||
| workflow_run.triggered_from = "app-run" | workflow_run.triggered_from = "app-run" | ||||
| workflow_run.version = "1.0" | workflow_run.version = "1.0" |
| - `data` (object) detail | - `data` (object) detail | ||||
| - `id` (string) Unique ID of workflow execution | - `id` (string) Unique ID of workflow execution | ||||
| - `workflow_id` (string) ID of related workflow | - `workflow_id` (string) ID of related workflow | ||||
| - `sequence_number` (int) Self-increasing serial number, self-increasing in the App, starting from 1 | |||||
| - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 | - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 | ||||
| - `event: node_started` node execution started | - `event: node_started` node execution started | ||||
| - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API | - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API | ||||
| ### Streaming Mode | ### Streaming Mode | ||||
| <CodeGroup title="Response"> | <CodeGroup title="Response"> | ||||
| ```streaming {{ title: 'Response' }} | ```streaming {{ title: 'Response' }} | ||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}} | |||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}} | |||||
| data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | ||||
| data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | ||||
| data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} | data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} |
| - `data` (object) 詳細 | - `data` (object) 詳細 | ||||
| - `id` (string) ワークフロー実行の一意ID | - `id` (string) ワークフロー実行の一意ID | ||||
| - `workflow_id` (string) 関連ワークフローのID | - `workflow_id` (string) 関連ワークフローのID | ||||
| - `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1から始まります | |||||
| - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 | - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 | ||||
| - `event: node_started` ノード実行が開始 | - `event: node_started` ノード実行が開始 | ||||
| - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 | - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 | ||||
| ### ストリーミングモード | ### ストリーミングモード | ||||
| <CodeGroup title="応答"> | <CodeGroup title="応答"> | ||||
| ```streaming {{ title: '応答' }} | ```streaming {{ title: '応答' }} | ||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}} | |||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}} | |||||
| data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | ||||
| data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | ||||
| data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} | data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} |
| - `data` (object) 详细内容 | - `data` (object) 详细内容 | ||||
| - `id` (string) workflow 执行 ID | - `id` (string) workflow 执行 ID | ||||
| - `workflow_id` (string) 关联 Workflow ID | - `workflow_id` (string) 关联 Workflow ID | ||||
| - `sequence_number` (int) 自增序号,App 内自增,从 1 开始 | |||||
| - `created_at` (timestamp) 开始时间 | - `created_at` (timestamp) 开始时间 | ||||
| - `event: node_started` node 开始执行 | - `event: node_started` node 开始执行 | ||||
| - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 | - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 | ||||
| ### 流式模式 | ### 流式模式 | ||||
| <CodeGroup title="Response"> | <CodeGroup title="Response"> | ||||
| ```streaming {{ title: 'Response' }} | ```streaming {{ title: 'Response' }} | ||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}} | |||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}} | |||||
| data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | ||||
| data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | ||||
| data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} | data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} |
| - `data` (object) detail | - `data` (object) detail | ||||
| - `id` (string) Unique ID of workflow execution | - `id` (string) Unique ID of workflow execution | ||||
| - `workflow_id` (string) ID of related workflow | - `workflow_id` (string) ID of related workflow | ||||
| - `sequence_number` (int) Self-increasing serial number, self-increasing in the App, starting from 1 | |||||
| - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 | - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 | ||||
| - `event: node_started` node execution started | - `event: node_started` node execution started | ||||
| - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API | - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API | ||||
| ### Streaming Mode | ### Streaming Mode | ||||
| <CodeGroup title="Response"> | <CodeGroup title="Response"> | ||||
| ```streaming {{ title: 'Response' }} | ```streaming {{ title: 'Response' }} | ||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}} | |||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}} | |||||
| data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | ||||
| data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | ||||
| data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} | data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} |
| - `data` (object) 詳細 | - `data` (object) 詳細 | ||||
| - `id` (string) ワークフロー実行の一意の ID | - `id` (string) ワークフロー実行の一意の ID | ||||
| - `workflow_id` (string) 関連するワークフローの ID | - `workflow_id` (string) 関連するワークフローの ID | ||||
| - `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1 から始まります | |||||
| - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 | - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 | ||||
| - `event: node_started` ノード実行開始 | - `event: node_started` ノード実行開始 | ||||
| - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 | - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 | ||||
| ### ストリーミングモード | ### ストリーミングモード | ||||
| <CodeGroup title="応答"> | <CodeGroup title="応答"> | ||||
| ```streaming {{ title: '応答' }} | ```streaming {{ title: '応答' }} | ||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}} | |||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}} | |||||
| data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | ||||
| data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | ||||
| data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} | data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} |
| - `data` (object) 详细内容 | - `data` (object) 详细内容 | ||||
| - `id` (string) workflow 执行 ID | - `id` (string) workflow 执行 ID | ||||
| - `workflow_id` (string) 关联 Workflow ID | - `workflow_id` (string) 关联 Workflow ID | ||||
| - `sequence_number` (int) 自增序号,App 内自增,从 1 开始 | |||||
| - `created_at` (timestamp) 开始时间 | - `created_at` (timestamp) 开始时间 | ||||
| - `event: node_started` node 开始执行 | - `event: node_started` node 开始执行 | ||||
| - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 | - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 | ||||
| ### Streaming Mode | ### Streaming Mode | ||||
| <CodeGroup title="Response"> | <CodeGroup title="Response"> | ||||
| ```streaming {{ title: 'Response' }} | ```streaming {{ title: 'Response' }} | ||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "sequence_number": 1, "created_at": 1679586595}} | |||||
| data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}} | |||||
| data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}} | ||||
| data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}} | ||||
| data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} | data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}} |
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { useIsChatMode } from '../hooks' | import { useIsChatMode } from '../hooks' | ||||
| import { useStore } from '../store' | import { useStore } from '../store' | ||||
| import { formatWorkflowRunIdentifier } from '../utils' | |||||
| import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' | import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' | ||||
| const RunningTitle = () => { | const RunningTitle = () => { | ||||
| return ( | return ( | ||||
| <div className='flex h-[18px] items-center text-xs text-gray-500'> | <div className='flex h-[18px] items-center text-xs text-gray-500'> | ||||
| <ClockPlay className='mr-1 h-3 w-3 text-gray-500' /> | <ClockPlay className='mr-1 h-3 w-3 text-gray-500' /> | ||||
| <span>{isChatMode ? `Test Chat#${historyWorkflowData?.sequence_number}` : `Test Run#${historyWorkflowData?.sequence_number}`}</span> | |||||
| <span>{isChatMode ? `Test Chat${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}` : `Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}</span> | |||||
| <span className='mx-1'>·</span> | <span className='mx-1'>·</span> | ||||
| <span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'> | <span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'> | ||||
| {t('workflow.common.viewOnly')} | {t('workflow.common.viewOnly')} |
| useWorkflowRun, | useWorkflowRun, | ||||
| } from '../hooks' | } from '../hooks' | ||||
| import { ControlMode, WorkflowRunningStatus } from '../types' | import { ControlMode, WorkflowRunningStatus } from '../types' | ||||
| import { formatWorkflowRunIdentifier } from '../utils' | |||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { | import { | ||||
| PortalToFollowElem, | PortalToFollowElem, | ||||
| item.id === historyWorkflowData?.id && 'text-text-accent', | item.id === historyWorkflowData?.id && 'text-text-accent', | ||||
| )} | )} | ||||
| > | > | ||||
| {`Test ${isChatMode ? 'Chat' : 'Run'} #${item.sequence_number}`} | |||||
| {`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at)}`} | |||||
| </div> | </div> | ||||
| <div className='flex items-center text-xs leading-[18px] text-text-tertiary'> | <div className='flex items-center text-xs leading-[18px] text-text-tertiary'> | ||||
| {item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)} | {item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)} |
| useWorkflowStore, | useWorkflowStore, | ||||
| } from '../../store' | } from '../../store' | ||||
| import { useWorkflowRun } from '../../hooks' | import { useWorkflowRun } from '../../hooks' | ||||
| import { formatWorkflowRunIdentifier } from '../../utils' | |||||
| import UserInput from './user-input' | import UserInput from './user-input' | ||||
| import Chat from '@/app/components/base/chat/chat' | import Chat from '@/app/components/base/chat/chat' | ||||
| import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types' | import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types' | ||||
| {fetched && ( | {fetched && ( | ||||
| <> | <> | ||||
| <div className='flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> | <div className='flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> | ||||
| {`TEST CHAT#${historyWorkflowData?.sequence_number}`} | |||||
| {`TEST CHAT${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`} | |||||
| <div | <div | ||||
| className='flex h-6 w-6 cursor-pointer items-center justify-center' | className='flex h-6 w-6 cursor-pointer items-center justify-center' | ||||
| onClick={() => { | onClick={() => { |
| import Run from '../run' | import Run from '../run' | ||||
| import { useStore } from '../store' | import { useStore } from '../store' | ||||
| import { useWorkflowUpdate } from '../hooks' | import { useWorkflowUpdate } from '../hooks' | ||||
| import { formatWorkflowRunIdentifier } from '../utils' | |||||
| const Record = () => { | const Record = () => { | ||||
| const historyWorkflowData = useStore(s => s.historyWorkflowData) | const historyWorkflowData = useStore(s => s.historyWorkflowData) | ||||
| return ( | return ( | ||||
| <div className='flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'> | <div className='flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'> | ||||
| <div className='system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary'> | <div className='system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary'> | ||||
| {`Test Run#${historyWorkflowData?.sequence_number}`} | |||||
| {`Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`} | |||||
| </div> | </div> | ||||
| <Run | <Run | ||||
| runID={historyWorkflowData?.id || ''} | runID={historyWorkflowData?.id || ''} |
| import { | import { | ||||
| WorkflowRunningStatus, | WorkflowRunningStatus, | ||||
| } from '../types' | } from '../types' | ||||
| import { formatWorkflowRunIdentifier } from '../utils' | |||||
| import Toast from '../../base/toast' | import Toast from '../../base/toast' | ||||
| import InputsPanel from './inputs-panel' | import InputsPanel from './inputs-panel' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| onMouseDown={startResizing} | onMouseDown={startResizing} | ||||
| /> | /> | ||||
| <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> | <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> | ||||
| {`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`} | |||||
| {`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at)}`} | |||||
| <div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}> | <div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}> | ||||
| <RiCloseLine className='h-4 w-4 text-text-tertiary' /> | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> | ||||
| </div> | </div> |
| message_id?: string | message_id?: string | ||||
| conversation_id?: string | conversation_id?: string | ||||
| result: { | result: { | ||||
| sequence_number?: number | |||||
| workflow_id?: string | workflow_id?: string | ||||
| inputs?: string | inputs?: string | ||||
| process_data?: string | process_data?: string | ||||
| export type HistoryWorkflowData = { | export type HistoryWorkflowData = { | ||||
| id: string | id: string | ||||
| sequence_number: number | |||||
| status: string | status: string | ||||
| conversation_id?: string | conversation_id?: string | ||||
| finished_at?: number | |||||
| } | } | ||||
| export enum ChangeType { | export enum ChangeType { |
| if (target.contentEditable === 'true') | if (target.contentEditable === 'true') | ||||
| return true | return true | ||||
| } | } | ||||
| /** | |||||
| * Format workflow run identifier using finished_at timestamp | |||||
| * @param finishedAt - Unix timestamp in seconds | |||||
| * @param fallbackText - Text to show when finishedAt is not available (default: 'Running') | |||||
| * @returns Formatted string like " (14:30:25)" or " (Running)" | |||||
| */ | |||||
| export const formatWorkflowRunIdentifier = (finishedAt?: number, fallbackText = 'Running'): string => { | |||||
| if (!finishedAt) | |||||
| return ` (${fallbackText})` | |||||
| const date = new Date(finishedAt * 1000) | |||||
| const timeStr = date.toLocaleTimeString([], { | |||||
| hour: '2-digit', | |||||
| minute: '2-digit', | |||||
| second: '2-digit', | |||||
| }) | |||||
| return ` (${timeStr})` | |||||
| } |
| export type WorkflowRunDetailResponse = { | export type WorkflowRunDetailResponse = { | ||||
| id: string | id: string | ||||
| sequence_number: number | |||||
| version: string | version: string | ||||
| graph: { | graph: { | ||||
| nodes: Node[] | nodes: Node[] |
| data: { | data: { | ||||
| id: string | id: string | ||||
| workflow_id: string | workflow_id: string | ||||
| sequence_number: number | |||||
| created_at: number | created_at: number | ||||
| } | } | ||||
| } | } | ||||
| export type WorkflowRunHistory = { | export type WorkflowRunHistory = { | ||||
| id: string | id: string | ||||
| sequence_number: number | |||||
| version: string | version: string | ||||
| conversation_id?: string | conversation_id?: string | ||||
| message_id?: string | message_id?: string |