[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:
Warren Chen 2024-12-27 21:05:06 +08:00 committed by GitHub
parent adfbfc1255
commit 901028f1e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 301 additions and 58 deletions

View File

@ -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>"

View File

@ -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"),
}

View File

@ -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,
*, *,

View File

@ -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>
) )
} }

View 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)

View 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)

View File

@ -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>

View File

@ -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 {

View File

@ -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 })),
})) }))
} }

View File

@ -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',

View File

@ -104,6 +104,8 @@ const translation = {
onFailure: '异常时', onFailure: '异常时',
addFailureBranch: '添加异常分支', addFailureBranch: '添加异常分支',
openInExplore: '在“探索”中打开', openInExplore: '在“探索”中打开',
loadMore: '加载更多',
noHistory: '没有历史版本',
}, },
env: { env: {
envPanelTitle: '环境变量', envPanelTitle: '环境变量',

View File

@ -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 })
} }

View File

@ -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 = {

View File

@ -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,