[feat] Support Multi-Version Workflows (#11990)
Co-authored-by: hobo.l <hobo.l@binance.com> Co-authored-by: crazywoola <427733928@qq.com>
This commit is contained in:
parent
adfbfc1255
commit
901028f1e8
@ -2,7 +2,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore
|
||||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -14,7 +14,7 @@ from controllers.console.wraps import account_initialization_required, setup_req
|
|||||||
from core.app.apps.base_app_queue_manager import AppQueueManager
|
from core.app.apps.base_app_queue_manager import AppQueueManager
|
||||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
from factories import variable_factory
|
from factories import variable_factory
|
||||||
from fields.workflow_fields import workflow_fields
|
from fields.workflow_fields import workflow_fields, workflow_pagination_fields
|
||||||
from fields.workflow_run_fields import workflow_run_node_execution_fields
|
from fields.workflow_run_fields import workflow_run_node_execution_fields
|
||||||
from libs import helper
|
from libs import helper
|
||||||
from libs.helper import TimestampField, uuid_value
|
from libs.helper import TimestampField, uuid_value
|
||||||
@ -440,6 +440,31 @@ class WorkflowConfigApi(Resource):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PublishedAllWorkflowApi(Resource):
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
||||||
|
@marshal_with(workflow_pagination_fields)
|
||||||
|
def get(self, app_model: App):
|
||||||
|
"""
|
||||||
|
Get published workflows
|
||||||
|
"""
|
||||||
|
if not current_user.is_editor:
|
||||||
|
raise Forbidden()
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("page", type=inputs.int_range(1, 99999), required=False, default=1, location="args")
|
||||||
|
parser.add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args")
|
||||||
|
args = parser.parse_args()
|
||||||
|
page = args.get("page")
|
||||||
|
limit = args.get("limit")
|
||||||
|
workflow_service = WorkflowService()
|
||||||
|
workflows, has_more = workflow_service.get_all_published_workflow(app_model=app_model, page=page, limit=limit)
|
||||||
|
|
||||||
|
return {"items": workflows, "page": page, "limit": limit, "has_more": has_more}
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(DraftWorkflowApi, "/apps/<uuid:app_id>/workflows/draft")
|
api.add_resource(DraftWorkflowApi, "/apps/<uuid:app_id>/workflows/draft")
|
||||||
api.add_resource(WorkflowConfigApi, "/apps/<uuid:app_id>/workflows/draft/config")
|
api.add_resource(WorkflowConfigApi, "/apps/<uuid:app_id>/workflows/draft/config")
|
||||||
api.add_resource(AdvancedChatDraftWorkflowRunApi, "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run")
|
api.add_resource(AdvancedChatDraftWorkflowRunApi, "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run")
|
||||||
@ -454,6 +479,7 @@ api.add_resource(
|
|||||||
WorkflowDraftRunIterationNodeApi, "/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run"
|
WorkflowDraftRunIterationNodeApi, "/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run"
|
||||||
)
|
)
|
||||||
api.add_resource(PublishedWorkflowApi, "/apps/<uuid:app_id>/workflows/publish")
|
api.add_resource(PublishedWorkflowApi, "/apps/<uuid:app_id>/workflows/publish")
|
||||||
|
api.add_resource(PublishedAllWorkflowApi, "/apps/<uuid:app_id>/workflows")
|
||||||
api.add_resource(DefaultBlockConfigsApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs")
|
api.add_resource(DefaultBlockConfigsApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs")
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
DefaultBlockConfigApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs/<string:block_type>"
|
DefaultBlockConfigApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs/<string:block_type>"
|
||||||
|
@ -45,6 +45,7 @@ workflow_fields = {
|
|||||||
"graph": fields.Raw(attribute="graph_dict"),
|
"graph": fields.Raw(attribute="graph_dict"),
|
||||||
"features": fields.Raw(attribute="features_dict"),
|
"features": fields.Raw(attribute="features_dict"),
|
||||||
"hash": fields.String(attribute="unique_hash"),
|
"hash": fields.String(attribute="unique_hash"),
|
||||||
|
"version": fields.String(attribute="version"),
|
||||||
"created_by": fields.Nested(simple_account_fields, attribute="created_by_account"),
|
"created_by": fields.Nested(simple_account_fields, attribute="created_by_account"),
|
||||||
"created_at": TimestampField,
|
"created_at": TimestampField,
|
||||||
"updated_by": fields.Nested(simple_account_fields, attribute="updated_by_account", allow_null=True),
|
"updated_by": fields.Nested(simple_account_fields, attribute="updated_by_account", allow_null=True),
|
||||||
@ -61,3 +62,10 @@ workflow_partial_fields = {
|
|||||||
"updated_by": fields.String,
|
"updated_by": fields.String,
|
||||||
"updated_at": TimestampField,
|
"updated_at": TimestampField,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workflow_pagination_fields = {
|
||||||
|
"items": fields.List(fields.Nested(workflow_fields), attribute="items"),
|
||||||
|
"page": fields.Integer,
|
||||||
|
"limit": fields.Integer(attribute="limit"),
|
||||||
|
"has_more": fields.Boolean(attribute="has_more"),
|
||||||
|
}
|
||||||
|
@ -5,6 +5,8 @@ from datetime import UTC, datetime
|
|||||||
from typing import Any, Optional, cast
|
from typing import Any, Optional, cast
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sqlalchemy import desc
|
||||||
|
|
||||||
from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager
|
from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager
|
||||||
from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager
|
from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager
|
||||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||||
@ -76,6 +78,28 @@ class WorkflowService:
|
|||||||
|
|
||||||
return workflow
|
return workflow
|
||||||
|
|
||||||
|
def get_all_published_workflow(self, app_model: App, page: int, limit: int) -> tuple[list[Workflow], bool]:
|
||||||
|
"""
|
||||||
|
Get published workflow with pagination
|
||||||
|
"""
|
||||||
|
if not app_model.workflow_id:
|
||||||
|
return [], False
|
||||||
|
|
||||||
|
workflows = (
|
||||||
|
db.session.query(Workflow)
|
||||||
|
.filter(Workflow.app_id == app_model.id)
|
||||||
|
.order_by(desc(Workflow.version))
|
||||||
|
.offset((page - 1) * limit)
|
||||||
|
.limit(limit + 1)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
has_more = len(workflows) > limit
|
||||||
|
if has_more:
|
||||||
|
workflows = workflows[:-1]
|
||||||
|
|
||||||
|
return workflows, has_more
|
||||||
|
|
||||||
def sync_draft_workflow(
|
def sync_draft_workflow(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
@ -20,6 +20,7 @@ import type { StartNodeType } from '../nodes/start/types'
|
|||||||
import {
|
import {
|
||||||
useChecklistBeforePublish,
|
useChecklistBeforePublish,
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
|
useNodesInteractions,
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useNodesSyncDraft,
|
useNodesSyncDraft,
|
||||||
useWorkflowMode,
|
useWorkflowMode,
|
||||||
@ -35,6 +36,7 @@ import RestoringTitle from './restoring-title'
|
|||||||
import ViewHistory from './view-history'
|
import ViewHistory from './view-history'
|
||||||
import ChatVariableButton from './chat-variable-button'
|
import ChatVariableButton from './chat-variable-button'
|
||||||
import EnvButton from './env-button'
|
import EnvButton from './env-button'
|
||||||
|
import VersionHistoryModal from './version-history-modal'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { publishWorkflow } from '@/service/workflow'
|
import { publishWorkflow } from '@/service/workflow'
|
||||||
@ -49,11 +51,13 @@ const Header: FC = () => {
|
|||||||
const appID = appDetail?.id
|
const appID = appDetail?.id
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly()
|
const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly()
|
||||||
|
const { handleNodeSelect } = useNodesInteractions()
|
||||||
const publishedAt = useStore(s => s.publishedAt)
|
const publishedAt = useStore(s => s.publishedAt)
|
||||||
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
||||||
const toolPublished = useStore(s => s.toolPublished)
|
const toolPublished = useStore(s => s.toolPublished)
|
||||||
const nodes = useNodes<StartNodeType>()
|
const nodes = useNodes<StartNodeType>()
|
||||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||||
|
const selectedNode = nodes.find(node => node.data.selected)
|
||||||
const startVariables = startNode?.data.variables
|
const startVariables = startNode?.data.variables
|
||||||
const fileSettings = useFeatures(s => s.features.file)
|
const fileSettings = useFeatures(s => s.features.file)
|
||||||
const variables = useMemo(() => {
|
const variables = useMemo(() => {
|
||||||
@ -76,7 +80,6 @@ const Header: FC = () => {
|
|||||||
const {
|
const {
|
||||||
handleLoadBackupDraft,
|
handleLoadBackupDraft,
|
||||||
handleBackupDraft,
|
handleBackupDraft,
|
||||||
handleRestoreFromPublishedWorkflow,
|
|
||||||
} = useWorkflowRun()
|
} = useWorkflowRun()
|
||||||
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
@ -126,8 +129,10 @@ const Header: FC = () => {
|
|||||||
const onStartRestoring = useCallback(() => {
|
const onStartRestoring = useCallback(() => {
|
||||||
workflowStore.setState({ isRestoring: true })
|
workflowStore.setState({ isRestoring: true })
|
||||||
handleBackupDraft()
|
handleBackupDraft()
|
||||||
handleRestoreFromPublishedWorkflow()
|
// clear right panel
|
||||||
}, [handleBackupDraft, handleRestoreFromPublishedWorkflow, workflowStore])
|
if (selectedNode)
|
||||||
|
handleNodeSelect(selectedNode.id, true)
|
||||||
|
}, [handleBackupDraft, workflowStore, handleNodeSelect, selectedNode])
|
||||||
|
|
||||||
const onPublisherToggle = useCallback((state: boolean) => {
|
const onPublisherToggle = useCallback((state: boolean) => {
|
||||||
if (state)
|
if (state)
|
||||||
@ -209,23 +214,27 @@ const Header: FC = () => {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
restoring && (
|
restoring && (
|
||||||
<div className='flex items-center space-x-2'>
|
<div className='flex flex-col mt-auto'>
|
||||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
<div className='flex items-center justify-end my-4'>
|
||||||
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||||
{t('workflow.common.features')}
|
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
||||||
</Button>
|
{t('workflow.common.features')}
|
||||||
<Divider type='vertical' className='h-3.5 mx-auto' />
|
</Button>
|
||||||
<Button
|
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||||
onClick={handleCancelRestore}
|
<Button
|
||||||
>
|
className='mr-2'
|
||||||
{t('common.operation.cancel')}
|
onClick={handleCancelRestore}
|
||||||
</Button>
|
>
|
||||||
<Button
|
{t('common.operation.cancel')}
|
||||||
onClick={handleRestore}
|
</Button>
|
||||||
variant='primary'
|
<Button
|
||||||
>
|
onClick={handleRestore}
|
||||||
{t('workflow.common.restore')}
|
variant='primary'
|
||||||
</Button>
|
>
|
||||||
|
{t('workflow.common.restore')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<VersionHistoryModal />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
66
web/app/components/workflow/header/version-history-item.tsx
Normal file
66
web/app/components/workflow/header/version-history-item.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { WorkflowVersion } from '../types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import type { VersionHistory } from '@/types/workflow'
|
||||||
|
|
||||||
|
type VersionHistoryItemProps = {
|
||||||
|
item: VersionHistory
|
||||||
|
selectedVersion: string
|
||||||
|
onClick: (item: VersionHistory) => void
|
||||||
|
curIdx: number
|
||||||
|
page: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatVersion = (version: string, curIdx: number, page: number): string => {
|
||||||
|
if (curIdx === 0 && page === 1)
|
||||||
|
return WorkflowVersion.Draft
|
||||||
|
if (curIdx === 1 && page === 1)
|
||||||
|
return WorkflowVersion.Latest
|
||||||
|
try {
|
||||||
|
const date = new Date(version)
|
||||||
|
if (isNaN(date.getTime()))
|
||||||
|
return version
|
||||||
|
|
||||||
|
// format as YYYY-MM-DD HH:mm:ss
|
||||||
|
return date.toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ item, selectedVersion, onClick, curIdx, page }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const formatTime = (time: number) => dayjs.unix(time).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
const formattedVersion = formatVersion(item.version, curIdx, page)
|
||||||
|
const renderVersionLabel = (version: string) => (
|
||||||
|
(version === WorkflowVersion.Draft || version === WorkflowVersion.Latest)
|
||||||
|
? (
|
||||||
|
<div className="shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate">
|
||||||
|
{version}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex items-center p-2 h-12 text-xs font-medium text-gray-700 justify-between',
|
||||||
|
formattedVersion === selectedVersion ? '' : 'hover:bg-gray-100',
|
||||||
|
formattedVersion === WorkflowVersion.Draft ? 'cursor-not-allowed' : 'cursor-pointer',
|
||||||
|
)}
|
||||||
|
onClick={() => item.version !== WorkflowVersion.Draft && onClick(item)}
|
||||||
|
>
|
||||||
|
<div className='flex flex-col gap-1 py-2'>
|
||||||
|
<span className="text-left">{formatTime(formattedVersion === WorkflowVersion.Draft ? item.updated_at : item.created_at)}</span>
|
||||||
|
<span className="text-left">{t('workflow.panel.createdBy')} {item.created_by.name}</span>
|
||||||
|
</div>
|
||||||
|
{renderVersionLabel(formattedVersion)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(VersionHistoryItem)
|
89
web/app/components/workflow/header/version-history-modal.tsx
Normal file
89
web/app/components/workflow/header/version-history-modal.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
'use client'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import { useWorkflowRun } from '../hooks'
|
||||||
|
import VersionHistoryItem from './version-history-item'
|
||||||
|
import type { VersionHistory } from '@/types/workflow'
|
||||||
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
|
import { fetchPublishedAllWorkflow } from '@/service/workflow'
|
||||||
|
import Loading from '@/app/components/base/loading'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
|
||||||
|
const limit = 10
|
||||||
|
|
||||||
|
const VersionHistoryModal = () => {
|
||||||
|
const [selectedVersion, setSelectedVersion] = useState('draft')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const { handleRestoreFromPublishedWorkflow } = useWorkflowRun()
|
||||||
|
const appDetail = useAppStore.getState().appDetail
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const {
|
||||||
|
data: versionHistory,
|
||||||
|
isLoading,
|
||||||
|
} = useSWR(
|
||||||
|
`/apps/${appDetail?.id}/workflows?page=${page}&limit=${limit}`,
|
||||||
|
fetchPublishedAllWorkflow,
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleVersionClick = (item: VersionHistory) => {
|
||||||
|
if (item.version !== selectedVersion) {
|
||||||
|
setSelectedVersion(item.version)
|
||||||
|
handleRestoreFromPublishedWorkflow(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextPage = () => {
|
||||||
|
if (versionHistory?.has_more)
|
||||||
|
setPage(page => page + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-[336px] bg-white rounded-2xl border-[0.5px] border-gray-200 shadow-xl p-2'>
|
||||||
|
<div className="max-h-[400px] overflow-auto">
|
||||||
|
{(isLoading && page) === 1
|
||||||
|
? (
|
||||||
|
<div className='flex items-center justify-center h-10'>
|
||||||
|
<Loading/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
{versionHistory?.items?.map((item, idx) => (
|
||||||
|
<VersionHistoryItem
|
||||||
|
key={item.version}
|
||||||
|
item={item}
|
||||||
|
selectedVersion={selectedVersion}
|
||||||
|
onClick={handleVersionClick}
|
||||||
|
curIdx={idx}
|
||||||
|
page={page}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{isLoading && page > 1 && (
|
||||||
|
<div className='flex items-center justify-center h-10'>
|
||||||
|
<Loading/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isLoading && versionHistory?.has_more && (
|
||||||
|
<div className='flex items-center justify-center h-10 mt-2'>
|
||||||
|
<Button
|
||||||
|
className='text-sm'
|
||||||
|
onClick={handleNextPage}
|
||||||
|
>
|
||||||
|
{t('workflow.common.loadMore')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isLoading && !versionHistory?.items?.length && (
|
||||||
|
<div className='flex items-center justify-center h-10 text-gray-500'>
|
||||||
|
{t('workflow.common.noHistory')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(VersionHistoryModal)
|
@ -202,7 +202,7 @@ const ViewHistory = ({
|
|||||||
{`Test ${isChatMode ? 'Chat' : 'Run'}#${item.sequence_number}`}
|
{`Test ${isChatMode ? 'Chat' : 'Run'}#${item.sequence_number}`}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center text-xs text-gray-500 leading-[18px]'>
|
<div className='flex items-center text-xs text-gray-500 leading-[18px]'>
|
||||||
{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)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,17 +18,14 @@ import { useWorkflowUpdate } from './use-workflow-interactions'
|
|||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import type { IOtherOptions } from '@/service/base'
|
import type { IOtherOptions } from '@/service/base'
|
||||||
import { ssePost } from '@/service/base'
|
import { ssePost } from '@/service/base'
|
||||||
import {
|
import { stopWorkflowRun } from '@/service/workflow'
|
||||||
fetchPublishedWorkflow,
|
|
||||||
stopWorkflowRun,
|
|
||||||
} from '@/service/workflow'
|
|
||||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||||
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
|
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
|
||||||
import {
|
import {
|
||||||
getFilesInLogs,
|
getFilesInLogs,
|
||||||
} from '@/app/components/base/file-uploader/utils'
|
} from '@/app/components/base/file-uploader/utils'
|
||||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
import type { NodeTracing } from '@/types/workflow'
|
import type { NodeTracing, VersionHistory } from '@/types/workflow'
|
||||||
|
|
||||||
export const useWorkflowRun = () => {
|
export const useWorkflowRun = () => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
@ -754,24 +751,18 @@ export const useWorkflowRun = () => {
|
|||||||
stopWorkflowRun(`/apps/${appId}/workflow-runs/tasks/${taskId}/stop`)
|
stopWorkflowRun(`/apps/${appId}/workflow-runs/tasks/${taskId}/stop`)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleRestoreFromPublishedWorkflow = useCallback(async () => {
|
const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => {
|
||||||
const appDetail = useAppStore.getState().appDetail
|
const nodes = publishedWorkflow.graph.nodes.map(node => ({ ...node, selected: false, data: { ...node.data, selected: false } }))
|
||||||
const publishedWorkflow = await fetchPublishedWorkflow(`/apps/${appDetail?.id}/workflows/publish`)
|
const edges = publishedWorkflow.graph.edges
|
||||||
|
const viewport = publishedWorkflow.graph.viewport!
|
||||||
if (publishedWorkflow) {
|
handleUpdateWorkflowCanvas({
|
||||||
const nodes = publishedWorkflow.graph.nodes
|
nodes,
|
||||||
const edges = publishedWorkflow.graph.edges
|
edges,
|
||||||
const viewport = publishedWorkflow.graph.viewport!
|
viewport,
|
||||||
|
})
|
||||||
handleUpdateWorkflowCanvas({
|
featuresStore?.setState({ features: publishedWorkflow.features })
|
||||||
nodes,
|
workflowStore.getState().setPublishedAt(publishedWorkflow.created_at)
|
||||||
edges,
|
workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
|
||||||
viewport,
|
|
||||||
})
|
|
||||||
featuresStore?.setState({ features: publishedWorkflow.features })
|
|
||||||
workflowStore.getState().setPublishedAt(publishedWorkflow.created_at)
|
|
||||||
workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
|
|
||||||
}
|
|
||||||
}, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
|
}, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -21,7 +21,7 @@ import type {
|
|||||||
WorkflowRunningData,
|
WorkflowRunningData,
|
||||||
} from './types'
|
} from './types'
|
||||||
import { WorkflowContext } from './context'
|
import { WorkflowContext } from './context'
|
||||||
import type { NodeTracing } from '@/types/workflow'
|
import type { NodeTracing, VersionHistory } from '@/types/workflow'
|
||||||
|
|
||||||
// #TODO chatVar#
|
// #TODO chatVar#
|
||||||
// const MOCK_DATA = [
|
// const MOCK_DATA = [
|
||||||
@ -171,6 +171,8 @@ type Shape = {
|
|||||||
setIterTimes: (iterTimes: number) => void
|
setIterTimes: (iterTimes: number) => void
|
||||||
iterParallelLogMap: Map<string, Map<string, NodeTracing[]>>
|
iterParallelLogMap: Map<string, Map<string, NodeTracing[]>>
|
||||||
setIterParallelLogMap: (iterParallelLogMap: Map<string, Map<string, NodeTracing[]>>) => void
|
setIterParallelLogMap: (iterParallelLogMap: Map<string, Map<string, NodeTracing[]>>) => void
|
||||||
|
versionHistory: VersionHistory[]
|
||||||
|
setVersionHistory: (versionHistory: VersionHistory[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createWorkflowStore = () => {
|
export const createWorkflowStore = () => {
|
||||||
@ -291,6 +293,8 @@ export const createWorkflowStore = () => {
|
|||||||
iterParallelLogMap: new Map<string, Map<string, NodeTracing[]>>(),
|
iterParallelLogMap: new Map<string, Map<string, NodeTracing[]>>(),
|
||||||
setIterParallelLogMap: iterParallelLogMap => set(() => ({ iterParallelLogMap })),
|
setIterParallelLogMap: iterParallelLogMap => set(() => ({ iterParallelLogMap })),
|
||||||
|
|
||||||
|
versionHistory: [],
|
||||||
|
setVersionHistory: versionHistory => set(() => ({ versionHistory })),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +289,11 @@ export enum WorkflowRunningStatus {
|
|||||||
Stopped = 'stopped',
|
Stopped = 'stopped',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WorkflowVersion {
|
||||||
|
Draft = 'draft',
|
||||||
|
Latest = 'latest',
|
||||||
|
}
|
||||||
|
|
||||||
export enum NodeRunningStatus {
|
export enum NodeRunningStatus {
|
||||||
NotStart = 'not-start',
|
NotStart = 'not-start',
|
||||||
Waiting = 'waiting',
|
Waiting = 'waiting',
|
||||||
|
@ -104,6 +104,8 @@ const translation = {
|
|||||||
onFailure: '异常时',
|
onFailure: '异常时',
|
||||||
addFailureBranch: '添加异常分支',
|
addFailureBranch: '添加异常分支',
|
||||||
openInExplore: '在“探索”中打开',
|
openInExplore: '在“探索”中打开',
|
||||||
|
loadMore: '加载更多',
|
||||||
|
noHistory: '没有历史版本',
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
envPanelTitle: '环境变量',
|
envPanelTitle: '环境变量',
|
||||||
|
@ -4,6 +4,7 @@ import type { CommonResponse } from '@/models/common'
|
|||||||
import type {
|
import type {
|
||||||
ChatRunHistoryResponse,
|
ChatRunHistoryResponse,
|
||||||
ConversationVariableResponse,
|
ConversationVariableResponse,
|
||||||
|
FetchWorkflowDraftPageResponse,
|
||||||
FetchWorkflowDraftResponse,
|
FetchWorkflowDraftResponse,
|
||||||
NodesDefaultConfigsResponse,
|
NodesDefaultConfigsResponse,
|
||||||
WorkflowRunHistoryResponse,
|
WorkflowRunHistoryResponse,
|
||||||
@ -14,7 +15,10 @@ export const fetchWorkflowDraft = (url: string) => {
|
|||||||
return get(url, {}, { silent: true }) as Promise<FetchWorkflowDraftResponse>
|
return get(url, {}, { silent: true }) as Promise<FetchWorkflowDraftResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features' | 'environment_variables' | 'conversation_variables'> }) => {
|
export const syncWorkflowDraft = ({ url, params }: {
|
||||||
|
url: string
|
||||||
|
params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features' | 'environment_variables' | 'conversation_variables'>
|
||||||
|
}) => {
|
||||||
return post<CommonResponse & { updated_at: number; hash: string }>(url, { body: params }, { silent: true })
|
return post<CommonResponse & { updated_at: number; hash: string }>(url, { body: params }, { silent: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +50,10 @@ export const fetchPublishedWorkflow: Fetcher<FetchWorkflowDraftResponse, string>
|
|||||||
return get<FetchWorkflowDraftResponse>(url)
|
return get<FetchWorkflowDraftResponse>(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchPublishedAllWorkflow: Fetcher<FetchWorkflowDraftPageResponse, string> = (url) => {
|
||||||
|
return get<FetchWorkflowDraftPageResponse>(url)
|
||||||
|
}
|
||||||
|
|
||||||
export const stopWorkflowRun = (url: string) => {
|
export const stopWorkflowRun = (url: string) => {
|
||||||
return post<CommonResponse>(url)
|
return post<CommonResponse>(url)
|
||||||
}
|
}
|
||||||
@ -61,6 +69,9 @@ export const updateWorkflowDraftFromDSL = (appId: string, data: string) => {
|
|||||||
return post<FetchWorkflowDraftResponse>(`apps/${appId}/workflows/draft/import`, { body: { data } })
|
return post<FetchWorkflowDraftResponse>(`apps/${appId}/workflows/draft/import`, { body: { data } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchCurrentValueOfConversationVariable: Fetcher<ConversationVariableResponse, { url: string; params: { conversation_id: string } }> = ({ url, params }) => {
|
export const fetchCurrentValueOfConversationVariable: Fetcher<ConversationVariableResponse, {
|
||||||
|
url: string
|
||||||
|
params: { conversation_id: string }
|
||||||
|
}> = ({ url, params }) => {
|
||||||
return get<ConversationVariableResponse>(url, { params })
|
return get<ConversationVariableResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import type { Viewport } from 'reactflow'
|
import type { Viewport } from 'reactflow'
|
||||||
import type {
|
import type { BlockEnum, ConversationVariable, Edge, EnvironmentVariable, Node } from '@/app/components/workflow/types'
|
||||||
BlockEnum,
|
|
||||||
ConversationVariable,
|
|
||||||
Edge,
|
|
||||||
EnvironmentVariable,
|
|
||||||
Node,
|
|
||||||
} from '@/app/components/workflow/types'
|
|
||||||
import type { TransferMethod } from '@/types/app'
|
import type { TransferMethod } from '@/types/app'
|
||||||
import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
|
||||||
@ -79,6 +73,15 @@ export type FetchWorkflowDraftResponse = {
|
|||||||
tool_published: boolean
|
tool_published: boolean
|
||||||
environment_variables?: EnvironmentVariable[]
|
environment_variables?: EnvironmentVariable[]
|
||||||
conversation_variables?: ConversationVariable[]
|
conversation_variables?: ConversationVariable[]
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VersionHistory = FetchWorkflowDraftResponse
|
||||||
|
|
||||||
|
export type FetchWorkflowDraftPageResponse = {
|
||||||
|
items: VersionHistory[]
|
||||||
|
has_more: boolean
|
||||||
|
page: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeTracingListResponse = {
|
export type NodeTracingListResponse = {
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW, getMaxVarNameLength } from '@/config'
|
import { MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW, getMaxVarNameLength } from '@/config'
|
||||||
import { CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT } from '@/app/components/base/prompt-editor/constants'
|
import {
|
||||||
|
CONTEXT_PLACEHOLDER_TEXT,
|
||||||
|
HISTORY_PLACEHOLDER_TEXT,
|
||||||
|
PRE_PROMPT_PLACEHOLDER_TEXT,
|
||||||
|
QUERY_PLACEHOLDER_TEXT,
|
||||||
|
} from '@/app/components/base/prompt-editor/constants'
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
const otherAllowedRegex = /^[a-zA-Z0-9_]+$/
|
const otherAllowedRegex = /^[a-zA-Z0-9_]+$/
|
||||||
|
|
||||||
export const getNewVar = (key: string, type: string) => {
|
export const getNewVar = (key: string, type: string) => {
|
||||||
const { max_length, ...rest } = VAR_ITEM_TEMPLATE
|
const { ...rest } = VAR_ITEM_TEMPLATE
|
||||||
if (type !== 'string') {
|
if (type !== 'string') {
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
|
Loading…
Reference in New Issue
Block a user