From 4e41b8ade0434008ed494ae992dc65bbf5fcf8f3 Mon Sep 17 00:00:00 2001 From: "hobo.l" Date: Mon, 16 Sep 2024 13:05:46 +0300 Subject: [PATCH 01/21] feat: support workflow multi version recovery --- api/controllers/console/app/workflow.py | 14 +++++ api/fields/workflow_fields.py | 1 + api/services/workflow_service.py | 34 +++++++++++++ web/app/components/workflow/header/index.tsx | 49 ++++++++++-------- .../workflow/header/version-history-item.tsx | 51 +++++++++++++++++++ .../workflow/header/version-history-modal.tsx | 45 ++++++++++++++++ .../workflow/hooks/use-workflow-run.ts | 37 +++++--------- web/app/components/workflow/store.ts | 6 ++- web/app/components/workflow/types.ts | 5 ++ web/service/workflow.ts | 4 ++ web/types/workflow.ts | 3 ++ 11 files changed, 205 insertions(+), 44 deletions(-) create mode 100644 web/app/components/workflow/header/version-history-item.tsx create mode 100644 web/app/components/workflow/header/version-history-modal.tsx diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index f228c3ec4a0e07..99df1d8d60b1b7 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -429,7 +429,19 @@ def post(self, app_model: App): class WorkflowConfigApi(Resource): """Resource for workflow configuration.""" + @marshal_with(workflow_fields) + def get(self, app_model: App): + """ + Get published workflows + """ + if not current_user.is_editor: + raise Forbidden() + + workflow_service = WorkflowService() + workflows = workflow_service.get_all_published_workflow(app_model=app_model) + return workflows +class PublishedAllWorkflowApi(Resource): @setup_required @login_required @account_initialization_required @@ -438,6 +450,7 @@ def get(self, app_model: App): return { "parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT, } + api.add_resource(DraftWorkflowApi, "/apps//workflows/draft") @@ -454,6 +467,7 @@ def get(self, app_model: App): WorkflowDraftRunIterationNodeApi, "/apps//workflows/draft/iteration/nodes//run" ) api.add_resource(PublishedWorkflowApi, "/apps//workflows/publish") +api.add_resource(PublishedAllWorkflowApi, "/apps//workflows/publish/all") api.add_resource(DefaultBlockConfigsApi, "/apps//workflows/default-workflow-block-configs") api.add_resource( DefaultBlockConfigApi, "/apps//workflows/default-workflow-block-configs/" diff --git a/api/fields/workflow_fields.py b/api/fields/workflow_fields.py index 0d860d6f406502..2b6b32b0e2d37a 100644 --- a/api/fields/workflow_fields.py +++ b/api/fields/workflow_fields.py @@ -45,6 +45,7 @@ def format(self, value): "graph": fields.Raw(attribute="graph_dict"), "features": fields.Raw(attribute="features_dict"), "hash": fields.String(attribute="unique_hash"), + "version": fields.String(attribute="version"), "created_by": fields.Nested(simple_account_fields, attribute="created_by_account"), "created_at": TimestampField, "updated_by": fields.Nested(simple_account_fields, attribute="updated_by_account", allow_null=True), diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index ead552d6c2e83e..ec6775083a4171 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -4,6 +4,8 @@ from datetime import UTC, datetime from typing import Optional, cast +from sqlalchemy import desc + from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.model_runtime.utils.encoders import jsonable_encoder @@ -76,6 +78,38 @@ def get_published_workflow(self, app_model: App) -> Optional[Workflow]: return workflow + def get_all_published_workflow(self, app_model: App) -> list[Workflow]: + """ + Get published workflow + """ + + if not app_model.workflow_id: + return None + + # fetch published workflow by workflow_id + workflows = ( + db.session.query(Workflow) + .filter(Workflow.app_id == app_model.id) + .order_by(desc(Workflow.version)) + .limit(10) + .all() + ) + + if len(workflows) > 1: + workflows[1].version = "current" + + for workflow in workflows: + try: + # Try to parse the version as a datetime object + version_datetime = datetime.strptime(workflow.version, "%Y-%m-%d %H:%M:%S.%f") + # If successful, reformat it to the desired format + workflow.version = version_datetime.strftime("%Y-%m-%d %H:%M:%S") + except ValueError: + # If parsing fails, then the version is not a datetime string, so we skip formatting + pass + + return workflows + def sync_draft_workflow( self, *, diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx index 6e46990df843cd..d15cd92d0661fc 100644 --- a/web/app/components/workflow/header/index.tsx +++ b/web/app/components/workflow/header/index.tsx @@ -20,6 +20,7 @@ import type { StartNodeType } from '../nodes/start/types' import { useChecklistBeforePublish, useIsChatMode, + useNodesInteractions, useNodesReadOnly, useNodesSyncDraft, useWorkflowMode, @@ -35,6 +36,7 @@ import RestoringTitle from './restoring-title' import ViewHistory from './view-history' import ChatVariableButton from './chat-variable-button' import EnvButton from './env-button' +import VersionHistoryModal from './version-history-modal' import Button from '@/app/components/base/button' import { useStore as useAppStore } from '@/app/components/app/store' import { publishWorkflow } from '@/service/workflow' @@ -49,11 +51,13 @@ const Header: FC = () => { const appID = appDetail?.id const isChatMode = useIsChatMode() const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly() + const { handleNodeSelect } = useNodesInteractions() const publishedAt = useStore(s => s.publishedAt) const draftUpdatedAt = useStore(s => s.draftUpdatedAt) const toolPublished = useStore(s => s.toolPublished) const nodes = useNodes() const startNode = nodes.find(node => node.data.type === BlockEnum.Start) + const selectedNode = nodes.find(node => node.data.selected) const startVariables = startNode?.data.variables const fileSettings = useFeatures(s => s.features.file) const variables = useMemo(() => { @@ -76,7 +80,6 @@ const Header: FC = () => { const { handleLoadBackupDraft, handleBackupDraft, - handleRestoreFromPublishedWorkflow, } = useWorkflowRun() const { handleCheckBeforePublish } = useChecklistBeforePublish() const { handleSyncWorkflowDraft } = useNodesSyncDraft() @@ -126,8 +129,10 @@ const Header: FC = () => { const onStartRestoring = useCallback(() => { workflowStore.setState({ isRestoring: true }) handleBackupDraft() - handleRestoreFromPublishedWorkflow() - }, [handleBackupDraft, handleRestoreFromPublishedWorkflow, workflowStore]) + // clear right panel + if (selectedNode) + handleNodeSelect(selectedNode.id, true) + }, [handleBackupDraft, workflowStore, handleNodeSelect, selectedNode]) const onPublisherToggle = useCallback((state: boolean) => { if (state) @@ -209,23 +214,27 @@ const Header: FC = () => { } { restoring && ( -
- - - - +
+
+ +
+ + +
+
) } diff --git a/web/app/components/workflow/header/version-history-item.tsx b/web/app/components/workflow/header/version-history-item.tsx new file mode 100644 index 00000000000000..9886415e498304 --- /dev/null +++ b/web/app/components/workflow/header/version-history-item.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import dayjs from 'dayjs'; +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 +} + +const VersionHistoryItem: React.FC = ({ item, selectedVersion, onClick }) => { + const formatTime = (time: number) => { + return dayjs.unix(time).format('YYYY-MM-DD HH:mm:ss') + } + + const renderVersionLabel = (version: string) => { + switch (version) { + case WorkflowVersion.Draft: + return ( +
+ {version} +
+ ) + case WorkflowVersion.Current: + return ( +
+ {version} +
+ ) + default: + return null + } + } + + return ( +
onClick(item)} + > +
{formatTime(item.version === WorkflowVersion.Draft ? item.updated_at : item.created_at)}
+ {renderVersionLabel(item.version)} +
+ ) +} + +export default React.memo(VersionHistoryItem); diff --git a/web/app/components/workflow/header/version-history-modal.tsx b/web/app/components/workflow/header/version-history-modal.tsx new file mode 100644 index 00000000000000..69207bd9a2c6bc --- /dev/null +++ b/web/app/components/workflow/header/version-history-modal.tsx @@ -0,0 +1,45 @@ +'use client' +import React, { useState } from 'react' +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' + +const VersionHistoryModal = () => { + const [selectedVersion, setSelectedVersion] = useState('draft') + const { handleRestoreFromPublishedWorkflow } = useWorkflowRun() + const appDetail = useAppStore.getState().appDetail + const { + data: versionHistory, + isLoading, + } = useSWR(`/apps/${appDetail?.id}/workflows/publish/all`, fetchPublishedAllWorkflow) + + const handleVersionClick = (item: VersionHistory) => { + if (item.version !== selectedVersion) { + setSelectedVersion(item.version) + handleRestoreFromPublishedWorkflow(item) + } + } + + return ( +
+ {isLoading && ( +
+ +
+ )} + {versionHistory?.map(item => ( + + ))} +
+ ) +} +export default React.memo(VersionHistoryModal) diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index 822aa490dbe4ec..3d9b02a47c9bac 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -18,17 +18,14 @@ import { useWorkflowUpdate } from './use-workflow-interactions' import { useStore as useAppStore } from '@/app/components/app/store' import type { IOtherOptions } from '@/service/base' import { ssePost } from '@/service/base' -import { - fetchPublishedWorkflow, - stopWorkflowRun, -} from '@/service/workflow' +import { stopWorkflowRun } from '@/service/workflow' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' import { getFilesInLogs, } from '@/app/components/base/file-uploader/utils' 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 = () => { const store = useStoreApi() @@ -703,24 +700,18 @@ export const useWorkflowRun = () => { stopWorkflowRun(`/apps/${appId}/workflow-runs/tasks/${taskId}/stop`) }, []) - const handleRestoreFromPublishedWorkflow = useCallback(async () => { - const appDetail = useAppStore.getState().appDetail - const publishedWorkflow = await fetchPublishedWorkflow(`/apps/${appDetail?.id}/workflows/publish`) - - if (publishedWorkflow) { - const nodes = publishedWorkflow.graph.nodes - const edges = publishedWorkflow.graph.edges - const viewport = publishedWorkflow.graph.viewport! - - handleUpdateWorkflowCanvas({ - nodes, - edges, - viewport, - }) - featuresStore?.setState({ features: publishedWorkflow.features }) - workflowStore.getState().setPublishedAt(publishedWorkflow.created_at) - workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || []) - } + const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => { + const nodes = publishedWorkflow.graph.nodes.map(node => ({ ...node, selected: false, data: { ...node.data, selected: false } })) + const edges = publishedWorkflow.graph.edges + const viewport = publishedWorkflow.graph.viewport! + handleUpdateWorkflowCanvas({ + nodes, + edges, + viewport, + }) + featuresStore?.setState({ features: publishedWorkflow.features }) + workflowStore.getState().setPublishedAt(publishedWorkflow.created_at) + workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || []) }, [featuresStore, handleUpdateWorkflowCanvas, workflowStore]) return { diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index 23f4188d85a5c7..8e9cdbfc450bb7 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -21,7 +21,7 @@ import type { WorkflowRunningData, } from './types' import { WorkflowContext } from './context' -import type { NodeTracing } from '@/types/workflow' +import type { NodeTracing, VersionHistory } from '@/types/workflow' // #TODO chatVar# // const MOCK_DATA = [ @@ -171,6 +171,8 @@ type Shape = { setIterTimes: (iterTimes: number) => void iterParallelLogMap: Map> setIterParallelLogMap: (iterParallelLogMap: Map>) => void + versionHistory: VersionHistory[] + setVersionHistory: (versionHistory: VersionHistory[]) => void } export const createWorkflowStore = () => { @@ -291,6 +293,8 @@ export const createWorkflowStore = () => { iterParallelLogMap: new Map>(), setIterParallelLogMap: iterParallelLogMap => set(() => ({ iterParallelLogMap })), + versionHistory: [], + setVersionHistory: versionHistory => set(() => ({ versionHistory })), })) } diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 6d0fabd90ef8c1..970cb5386d7b8f 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -289,6 +289,11 @@ export enum WorkflowRunningStatus { Stopped = 'stopped', } +export enum WorkflowVersion { + Draft = 'draft', + Current = 'current', +} + export enum NodeRunningStatus { NotStart = 'not-start', Waiting = 'waiting', diff --git a/web/service/workflow.ts b/web/service/workflow.ts index f50595e0713350..82aab8c2b4acc2 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -46,6 +46,10 @@ export const fetchPublishedWorkflow: Fetcher return get(url) } +export const fetchPublishedAllWorkflow: Fetcher = (url) => { + return get(url) +} + export const stopWorkflowRun = (url: string) => { return post(url) } diff --git a/web/types/workflow.ts b/web/types/workflow.ts index cd6e9cfa5f02ac..0f234408bbe297 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -79,8 +79,11 @@ export type FetchWorkflowDraftResponse = { tool_published: boolean environment_variables?: EnvironmentVariable[] conversation_variables?: ConversationVariable[] + version: string } +export type VersionHistory = FetchWorkflowDraftResponse + export type NodeTracingListResponse = { data: NodeTracing[] } From b1397e291ddf816983ce861f19fcc052c65c0586 Mon Sep 17 00:00:00 2001 From: "hobo.l" Date: Mon, 16 Sep 2024 13:45:53 +0300 Subject: [PATCH 02/21] feat: support workflow multi version recovery --- .../workflow/header/version-history-item.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/app/components/workflow/header/version-history-item.tsx b/web/app/components/workflow/header/version-history-item.tsx index 9886415e498304..95ee9245118d31 100644 --- a/web/app/components/workflow/header/version-history-item.tsx +++ b/web/app/components/workflow/header/version-history-item.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import dayjs from 'dayjs'; -import { WorkflowVersion } from '../types'; -import cn from '@/utils/classnames'; -import type { VersionHistory } from '@/types/workflow'; +import React from 'react' +import dayjs from 'dayjs' +import { WorkflowVersion } from '../types' +import cn from '@/utils/classnames' +import type { VersionHistory } from '@/types/workflow' type VersionHistoryItemProps = { item: VersionHistory @@ -48,4 +48,4 @@ const VersionHistoryItem: React.FC = ({ item, selectedV ) } -export default React.memo(VersionHistoryItem); +export default React.memo(VersionHistoryItem) From 0c91279fe16646c9862025bec89b0c566385eadd Mon Sep 17 00:00:00 2001 From: crazywoola <427733928@qq.com> Date: Fri, 20 Dec 2024 16:16:08 +0800 Subject: [PATCH 03/21] resolve conflict --- api/controllers/console/app/workflow.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 99df1d8d60b1b7..fda0abcf271cf1 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -429,6 +429,15 @@ def post(self, app_model: App): class WorkflowConfigApi(Resource): """Resource for workflow configuration.""" + def get(self, app_model: App): + return { + "parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT, + } +class PublishedAllWorkflowApi(Resource): + @setup_required + @login_required + @account_initialization_required + @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) @marshal_with(workflow_fields) def get(self, app_model: App): """ @@ -441,16 +450,6 @@ def get(self, app_model: App): workflow_service = WorkflowService() workflows = workflow_service.get_all_published_workflow(app_model=app_model) return workflows -class PublishedAllWorkflowApi(Resource): - @setup_required - @login_required - @account_initialization_required - @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) - def get(self, app_model: App): - return { - "parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT, - } - api.add_resource(DraftWorkflowApi, "/apps//workflows/draft") From 7004656c467bce03837136d8eb5fa968262743b9 Mon Sep 17 00:00:00 2001 From: crazywoola <427733928@qq.com> Date: Fri, 20 Dec 2024 16:16:19 +0800 Subject: [PATCH 04/21] resolve conflict --- api/controllers/console/app/workflow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index fda0abcf271cf1..59625bd765fea1 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -433,6 +433,8 @@ def get(self, app_model: App): return { "parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT, } + + class PublishedAllWorkflowApi(Resource): @setup_required @login_required From 0853f66239e83b86a6824ce989d75f8c1b93fab7 Mon Sep 17 00:00:00 2001 From: crazywoola <427733928@qq.com> Date: Fri, 20 Dec 2024 16:20:58 +0800 Subject: [PATCH 05/21] resolve conflict --- api/controllers/console/app/workflow.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 59625bd765fea1..0b3ad5e78bf33a 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -429,6 +429,10 @@ def post(self, app_model: App): class WorkflowConfigApi(Resource): """Resource for workflow configuration.""" + @setup_required + @login_required + @account_initialization_required + @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) def get(self, app_model: App): return { "parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT, From 03af025b2ff64bd30684ae040880d8dd4e3e44d2 Mon Sep 17 00:00:00 2001 From: crazywoola <427733928@qq.com> Date: Fri, 20 Dec 2024 16:47:33 +0800 Subject: [PATCH 06/21] fix: ui --- api/services/workflow_service.py | 2 +- .../workflow/header/version-history-item.tsx | 38 +++++++------------ .../workflow/header/version-history-modal.tsx | 2 +- web/app/components/workflow/types.ts | 2 +- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index ec6775083a4171..16bad54de86f9a 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -96,7 +96,7 @@ def get_all_published_workflow(self, app_model: App) -> list[Workflow]: ) if len(workflows) > 1: - workflows[1].version = "current" + workflows[1].version = "latest" for workflow in workflows: try: diff --git a/web/app/components/workflow/header/version-history-item.tsx b/web/app/components/workflow/header/version-history-item.tsx index 95ee9245118d31..964cfe9a8aa7e9 100644 --- a/web/app/components/workflow/header/version-history-item.tsx +++ b/web/app/components/workflow/header/version-history-item.tsx @@ -11,36 +11,26 @@ type VersionHistoryItemProps = { } const VersionHistoryItem: React.FC = ({ item, selectedVersion, onClick }) => { - const formatTime = (time: number) => { - return dayjs.unix(time).format('YYYY-MM-DD HH:mm:ss') - } + const formatTime = (time: number) => dayjs.unix(time).format('YYYY-MM-DD HH:mm:ss') - const renderVersionLabel = (version: string) => { - switch (version) { - case WorkflowVersion.Draft: - return ( -
- {version} -
- ) - case WorkflowVersion.Current: - return ( -
- {version} -
- ) - default: - return null - } - } + const renderVersionLabel = (version: string) => ( + (version === WorkflowVersion.Draft || version === WorkflowVersion.Latest) + ? ( +
+ {version} +
+ ) + : null + ) return (
onClick(item)} + onClick={() => item.version !== WorkflowVersion.Draft && onClick(item)} >
{formatTime(item.version === WorkflowVersion.Draft ? item.updated_at : item.created_at)}
{renderVersionLabel(item.version)} diff --git a/web/app/components/workflow/header/version-history-modal.tsx b/web/app/components/workflow/header/version-history-modal.tsx index 69207bd9a2c6bc..ce88ee0238c493 100644 --- a/web/app/components/workflow/header/version-history-modal.tsx +++ b/web/app/components/workflow/header/version-history-modal.tsx @@ -25,7 +25,7 @@ const VersionHistoryModal = () => { } return ( -
+
{isLoading && (
diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 970cb5386d7b8f..7c61ca98fe6be2 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -291,7 +291,7 @@ export enum WorkflowRunningStatus { export enum WorkflowVersion { Draft = 'draft', - Current = 'current', + Latest = 'latest', } export enum NodeRunningStatus { From a20f15d04b87bf426ef8e692e5b76960b7fbb461 Mon Sep 17 00:00:00 2001 From: crazywoola <427733928@qq.com> Date: Fri, 20 Dec 2024 16:50:20 +0800 Subject: [PATCH 07/21] chore: make linter happy --- api/controllers/console/app/workflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 0b3ad5e78bf33a..fd70ee3329cde0 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -429,6 +429,7 @@ def post(self, app_model: App): class WorkflowConfigApi(Resource): """Resource for workflow configuration.""" + @setup_required @login_required @account_initialization_required From 73cb902b92a0c6d3d34829c48594a871031e9a23 Mon Sep 17 00:00:00 2001 From: warren Date: Sun, 22 Dec 2024 17:21:41 +0800 Subject: [PATCH 08/21] modify --- api/controllers/console/app/workflow.py | 24 ++++++- api/fields/workflow_fields.py | 7 +++ api/services/workflow_service.py | 23 ++++--- .../workflow/header/version-history-modal.tsx | 62 ++++++++++++++----- 4 files changed, 87 insertions(+), 29 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index fd70ee3329cde0..b5b45c0ece0bf5 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -441,6 +441,11 @@ def get(self, app_model: App): class PublishedAllWorkflowApi(Resource): + def __init__(self): + self.parser = reqparse.RequestParser() + self.parser.add_argument('page', type=int, default=1, location='args') + self.parser.add_argument('page_size', type=int, default=10, location='args') + @setup_required @login_required @account_initialization_required @@ -450,13 +455,26 @@ def get(self, app_model: App): """ Get published workflows """ - if not current_user.is_editor: raise Forbidden() + args = self.parser.parse_args() + page = args['page'] + limit = args['limit'] + workflow_service = WorkflowService() - workflows = workflow_service.get_all_published_workflow(app_model=app_model) - return workflows + workflows, has_more = workflow_service.get_all_published_workflow( + app_model=app_model, + page=page, + limit=limit + ) + + return { + 'data': workflows, + 'page': page, + 'limit': limit, + 'has_more': has_more + } api.add_resource(DraftWorkflowApi, "/apps//workflows/draft") diff --git a/api/fields/workflow_fields.py b/api/fields/workflow_fields.py index 2b6b32b0e2d37a..7bd9fd5f0639b7 100644 --- a/api/fields/workflow_fields.py +++ b/api/fields/workflow_fields.py @@ -62,3 +62,10 @@ def format(self, value): "updated_by": fields.String, "updated_at": TimestampField, } + +workflow_pagination_fields = { + 'data': fields.List(fields.Nested(workflow_fields), attribute="data"), + 'page': fields.Integer, + "limit": fields.Integer(attribute="limit"), + "has_more": fields.Boolean(attribute="has_more"), +} diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 16bad54de86f9a..3b9a9cec215dda 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -78,37 +78,40 @@ def get_published_workflow(self, app_model: App) -> Optional[Workflow]: return workflow - def get_all_published_workflow(self, app_model: App) -> list[Workflow]: + def get_all_published_workflow(self, app_model: App, page: int, limit: int) -> tuple[list[Workflow], bool]: """ - Get published workflow + Get published workflow with pagination """ - if not app_model.workflow_id: - return None + return [], False - # fetch published workflow by workflow_id + # 多查询一条数据来判断是否还有下一页 workflows = ( db.session.query(Workflow) .filter(Workflow.app_id == app_model.id) .order_by(desc(Workflow.version)) - .limit(10) + .offset((page - 1) * limit) + .limit(limit + 1) # 多查一条 .all() ) + # 判断是否还有更多数据 + has_more = len(workflows) > limit + # 如果多查到了数据,则移除最后一条 + if has_more: + workflows = workflows[:-1] + if len(workflows) > 1: workflows[1].version = "latest" for workflow in workflows: try: - # Try to parse the version as a datetime object version_datetime = datetime.strptime(workflow.version, "%Y-%m-%d %H:%M:%S.%f") - # If successful, reformat it to the desired format workflow.version = version_datetime.strftime("%Y-%m-%d %H:%M:%S") except ValueError: - # If parsing fails, then the version is not a datetime string, so we skip formatting pass - return workflows + return workflows, has_more def sync_draft_workflow( self, diff --git a/web/app/components/workflow/header/version-history-modal.tsx b/web/app/components/workflow/header/version-history-modal.tsx index ce88ee0238c493..be676e2b1a66eb 100644 --- a/web/app/components/workflow/header/version-history-modal.tsx +++ b/web/app/components/workflow/header/version-history-modal.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useState } from 'react' +import React, { useState, useCallback } from 'react' import useSWR from 'swr' import { useWorkflowRun } from '../hooks' import VersionHistoryItem from './version-history-item' @@ -7,15 +7,24 @@ 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 InfiniteScroll from '@/app/components/base/infinite-scroll' + +const limit = 10 const VersionHistoryModal = () => { const [selectedVersion, setSelectedVersion] = useState('draft') + const [page, setPage] = useState(1) const { handleRestoreFromPublishedWorkflow } = useWorkflowRun() const appDetail = useAppStore.getState().appDetail + const { - data: versionHistory, + data, isLoading, - } = useSWR(`/apps/${appDetail?.id}/workflows/publish/all`, fetchPublishedAllWorkflow) + mutate, + } = useSWR( + `/apps/${appDetail?.id}/workflows/publish/all?page=${page}&limit=${limit}`, + fetchPublishedAllWorkflow + ) const handleVersionClick = (item: VersionHistory) => { if (item.version !== selectedVersion) { @@ -24,22 +33,43 @@ const VersionHistoryModal = () => { } } + const loadMore = useCallback(() => { + if (data?.has_more) { + setPage(prev => prev + 1) + } + }, [data?.has_more]) + return (
- {isLoading && ( -
- -
- )} - {versionHistory?.map(item => ( - - ))} + + {isLoading && page === 1 ? ( +
+ +
+ ) : ( + <> + {data?.items.map(item => ( + + ))} + {isLoading && page > 1 && ( +
+ +
+ )} + + )} +
) } + export default React.memo(VersionHistoryModal) From 82de0ee4edaa8af3b624009c52adbcb39c7ca6b4 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 23 Dec 2024 14:00:02 +0800 Subject: [PATCH 09/21] fix --- api/controllers/console/app/workflow.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index b5b45c0ece0bf5..acf64a49024e76 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -25,6 +25,8 @@ from services.errors.app import WorkflowHashNotEqualError from services.workflow_service import WorkflowService +from api.fields.workflow_fields import workflow_pagination_fields + logger = logging.getLogger(__name__) @@ -441,16 +443,11 @@ def get(self, app_model: App): class PublishedAllWorkflowApi(Resource): - def __init__(self): - self.parser = reqparse.RequestParser() - self.parser.add_argument('page', type=int, default=1, location='args') - self.parser.add_argument('page_size', type=int, default=10, location='args') - @setup_required @login_required @account_initialization_required @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) - @marshal_with(workflow_fields) + @marshal_with(workflow_pagination_fields) def get(self, app_model: App): """ Get published workflows @@ -470,7 +467,7 @@ def get(self, app_model: App): ) return { - 'data': workflows, + 'items': workflows, 'page': page, 'limit': limit, 'has_more': has_more From 957fa7f3b6a87dbb069c43f443f34563bd4363d3 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 23 Dec 2024 22:00:27 +0800 Subject: [PATCH 10/21] fix --- api/controllers/console/app/workflow.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index acf64a49024e76..6d7948d3b315a1 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -2,7 +2,7 @@ import logging from flask import abort, request -from flask_restful import Resource, marshal_with, reqparse +from flask_restful import Resource, marshal_with, reqparse, inputs from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services @@ -455,15 +455,16 @@ def get(self, app_model: App): if not current_user.is_editor: raise Forbidden() - args = self.parser.parse_args() - page = args['page'] - limit = args['limit'] - + 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 + args=args ) return { From 02ed3075ff46620d2fd2ca1e53b70516924f721a Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 23 Dec 2024 22:01:24 +0800 Subject: [PATCH 11/21] fix --- api/controllers/console/app/workflow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 6d7948d3b315a1..4e736682f2f2bf 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -464,7 +464,8 @@ def get(self, app_model: App): workflow_service = WorkflowService() workflows, has_more = workflow_service.get_all_published_workflow( app_model=app_model, - args=args + page=page, + limit=limit ) return { From 981643bb9f887958ba72f6bc650390f6a61654f8 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 23 Dec 2024 23:45:50 +0800 Subject: [PATCH 12/21] fix --- api/controllers/console/app/workflow.py | 2 +- .../workflow/header/version-history-modal.tsx | 40 ++++++++++++------- web/service/workflow.ts | 16 ++++---- web/types/workflow.ts | 6 +++ 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 4e736682f2f2bf..f07140e4aac0a5 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -25,7 +25,7 @@ from services.errors.app import WorkflowHashNotEqualError from services.workflow_service import WorkflowService -from api.fields.workflow_fields import workflow_pagination_fields +from fields.workflow_fields import workflow_pagination_fields logger = logging.getLogger(__name__) diff --git a/web/app/components/workflow/header/version-history-modal.tsx b/web/app/components/workflow/header/version-history-modal.tsx index be676e2b1a66eb..2babbeade08f14 100644 --- a/web/app/components/workflow/header/version-history-modal.tsx +++ b/web/app/components/workflow/header/version-history-modal.tsx @@ -7,7 +7,7 @@ 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 InfiniteScroll from '@/app/components/base/infinite-scroll' +import Button from '@/app/components/base/button' const limit = 10 @@ -18,9 +18,8 @@ const VersionHistoryModal = () => { const appDetail = useAppStore.getState().appDetail const { - data, + data: versionHistory, isLoading, - mutate, } = useSWR( `/apps/${appDetail?.id}/workflows/publish/all?page=${page}&limit=${limit}`, fetchPublishedAllWorkflow @@ -33,26 +32,22 @@ const VersionHistoryModal = () => { } } - const loadMore = useCallback(() => { - if (data?.has_more) { - setPage(prev => prev + 1) + const handleNextPage = () => { + if (versionHistory?.has_more) { + setPage(page => page + 1) } - }, [data?.has_more]) + } return ( -
- +
+
{isLoading && page === 1 ? (
) : ( <> - {data?.items.map(item => ( + {versionHistory?.items?.map(item => ( {
)} + {!isLoading && versionHistory?.has_more && ( +
+ +
+ )} + {!isLoading && !versionHistory?.items?.length && ( +
+ 暂无历史版本 +
+ )} )} - +
) } diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 82aab8c2b4acc2..10b5eac7cc5f29 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -1,12 +1,12 @@ import type { Fetcher } from 'swr' import { get, post } from './base' import type { CommonResponse } from '@/models/common' -import type { - ChatRunHistoryResponse, - ConversationVariableResponse, - FetchWorkflowDraftResponse, - NodesDefaultConfigsResponse, - WorkflowRunHistoryResponse, +import { + ChatRunHistoryResponse, + ConversationVariableResponse, FetchWorkflowDraftPageResponse, + FetchWorkflowDraftResponse, + NodesDefaultConfigsResponse, + WorkflowRunHistoryResponse, } from '@/types/workflow' import type { BlockEnum } from '@/app/components/workflow/types' @@ -46,8 +46,8 @@ export const fetchPublishedWorkflow: Fetcher return get(url) } -export const fetchPublishedAllWorkflow: Fetcher = (url) => { - return get(url) +export const fetchPublishedAllWorkflow: Fetcher = (url) => { + return get(url) } export const stopWorkflowRun = (url: string) => { diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 0f234408bbe297..74ba458c825ed6 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -84,6 +84,12 @@ export type FetchWorkflowDraftResponse = { export type VersionHistory = FetchWorkflowDraftResponse +export type FetchWorkflowDraftPageResponse = { + items: VersionHistory[] + has_more: boolean + page: number +} + export type NodeTracingListResponse = { data: NodeTracing[] } From a3b1df5abba47f7847c89af6435f6c22543ae0e3 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 23 Dec 2024 23:51:27 +0800 Subject: [PATCH 13/21] fix lint --- api/controllers/console/app/workflow.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index f07140e4aac0a5..ae679a0ce4ec4c 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -2,7 +2,7 @@ import logging from flask import abort, request -from flask_restful import Resource, marshal_with, reqparse, inputs +from flask_restful import Resource, inputs, marshal_with, reqparse from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services @@ -14,7 +14,7 @@ from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import InvokeFrom 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 libs import helper from libs.helper import TimestampField, uuid_value @@ -25,8 +25,6 @@ from services.errors.app import WorkflowHashNotEqualError from services.workflow_service import WorkflowService -from fields.workflow_fields import workflow_pagination_fields - logger = logging.getLogger(__name__) From 52de3458048e3c5bfb7d50d92a68c53983d8a530 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 23 Dec 2024 23:54:45 +0800 Subject: [PATCH 14/21] reformat api --- api/controllers/console/app/workflow.py | 17 ++++------------- api/fields/workflow_fields.py | 4 ++-- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index ae679a0ce4ec4c..648cf5c02499cb 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -457,21 +457,12 @@ def get(self, app_model: App): 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') + 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 - ) + 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 - } + return {"items": workflows, "page": page, "limit": limit, "has_more": has_more} api.add_resource(DraftWorkflowApi, "/apps//workflows/draft") diff --git a/api/fields/workflow_fields.py b/api/fields/workflow_fields.py index 7bd9fd5f0639b7..8e4de7d813aa7b 100644 --- a/api/fields/workflow_fields.py +++ b/api/fields/workflow_fields.py @@ -64,8 +64,8 @@ def format(self, value): } workflow_pagination_fields = { - 'data': fields.List(fields.Nested(workflow_fields), attribute="data"), - 'page': fields.Integer, + "data": fields.List(fields.Nested(workflow_fields), attribute="data"), + "page": fields.Integer, "limit": fields.Integer(attribute="limit"), "has_more": fields.Boolean(attribute="has_more"), } From 440141a6377c5c67d81a93b2d7605d3f379c66b2 Mon Sep 17 00:00:00 2001 From: warren Date: Wed, 25 Dec 2024 22:36:13 +0800 Subject: [PATCH 15/21] fix --- api/fields/workflow_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fields/workflow_fields.py b/api/fields/workflow_fields.py index d7e06ccbc725df..32f979a5f2aa08 100644 --- a/api/fields/workflow_fields.py +++ b/api/fields/workflow_fields.py @@ -64,7 +64,7 @@ def format(self, value): } workflow_pagination_fields = { - "data": fields.List(fields.Nested(workflow_fields), attribute="data"), + "items": fields.List(fields.Nested(workflow_fields), attribute="items"), "page": fields.Integer, "limit": fields.Integer(attribute="limit"), "has_more": fields.Boolean(attribute="has_more"), From 427fb24a39a13dae260fb986f70952d06a1f697b Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 26 Dec 2024 11:12:35 +0800 Subject: [PATCH 16/21] fix --- api/controllers/console/app/workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index fb36a4df16dab5..d0b039e7930914 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -2,7 +2,7 @@ import logging from flask import abort, request -from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore +from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services From 24186ae488cf9ae6738958de9587495aa5f3f40d Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 26 Dec 2024 11:17:19 +0800 Subject: [PATCH 17/21] add created by --- web/app/components/workflow/header/version-history-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/workflow/header/version-history-item.tsx b/web/app/components/workflow/header/version-history-item.tsx index 964cfe9a8aa7e9..73357d6b8e28b0 100644 --- a/web/app/components/workflow/header/version-history-item.tsx +++ b/web/app/components/workflow/header/version-history-item.tsx @@ -33,7 +33,7 @@ const VersionHistoryItem: React.FC = ({ item, selectedV onClick={() => item.version !== WorkflowVersion.Draft && onClick(item)} >
{formatTime(item.version === WorkflowVersion.Draft ? item.updated_at : item.created_at)}
- {renderVersionLabel(item.version)} + {renderVersionLabel(item.version)} authored by {item.created_by.name}
) } From 2fac78575aa073216620b90cafc35c70c286354f Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 26 Dec 2024 15:08:10 +0800 Subject: [PATCH 18/21] reformat --- web/service/workflow.ts | 41 ++++++++++++++++++++++++----------------- web/types/workflow.ts | 20 +++++++------------- web/utils/var.ts | 17 +++++++++++------ 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 10b5eac7cc5f29..52864fd13f3c8b 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -1,21 +1,25 @@ -import type { Fetcher } from 'swr' -import { get, post } from './base' -import type { CommonResponse } from '@/models/common' +import type {Fetcher} from 'swr' +import {get, post} from './base' +import type {CommonResponse} from '@/models/common' import { - ChatRunHistoryResponse, - ConversationVariableResponse, FetchWorkflowDraftPageResponse, - FetchWorkflowDraftResponse, - NodesDefaultConfigsResponse, - WorkflowRunHistoryResponse, + ChatRunHistoryResponse, + ConversationVariableResponse, + FetchWorkflowDraftPageResponse, + FetchWorkflowDraftResponse, + NodesDefaultConfigsResponse, + WorkflowRunHistoryResponse, } from '@/types/workflow' -import type { BlockEnum } from '@/app/components/workflow/types' +import type {BlockEnum} from '@/app/components/workflow/types' export const fetchWorkflowDraft = (url: string) => { - return get(url, {}, { silent: true }) as Promise + return get(url, {}, {silent: true}) as Promise } -export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick }) => { - return post(url, { body: params }, { silent: true }) +export const syncWorkflowDraft = ({url, params}: { + url: string; + params: Pick +}) => { + return post(url, {body: params}, {silent: true}) } export const fetchNodesDefaultConfigs: Fetcher = (url) => { @@ -31,7 +35,7 @@ export const fetchChatRunHistory: Fetcher = (url } export const singleNodeRun = (appId: string, nodeId: string, params: object) => { - return post(`apps/${appId}/workflows/draft/nodes/${nodeId}/run`, { body: params }) + return post(`apps/${appId}/workflows/draft/nodes/${nodeId}/run`, {body: params}) } export const getIterationSingleNodeRunUrl = (isChatFlow: boolean, appId: string, nodeId: string) => { @@ -56,15 +60,18 @@ export const stopWorkflowRun = (url: string) => { export const fetchNodeDefault = (appId: string, blockType: BlockEnum, query = {}) => { return get(`apps/${appId}/workflows/default-workflow-block-configs/${blockType}`, { - params: { q: JSON.stringify(query) }, + params: {q: JSON.stringify(query)}, }) } // TODO: archived export const updateWorkflowDraftFromDSL = (appId: string, data: string) => { - return post(`apps/${appId}/workflows/draft/import`, { body: { data } }) + return post(`apps/${appId}/workflows/draft/import`, {body: {data}}) } -export const fetchCurrentValueOfConversationVariable: Fetcher = ({ url, params }) => { - return get(url, { params }) +export const fetchCurrentValueOfConversationVariable: Fetcher = ({url, params}) => { + return get(url, {params}) } diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 74ba458c825ed6..e1c47f3cfa8e74 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -1,13 +1,7 @@ -import type { Viewport } from 'reactflow' -import type { - BlockEnum, - ConversationVariable, - Edge, - EnvironmentVariable, - Node, -} from '@/app/components/workflow/types' -import type { TransferMethod } from '@/types/app' -import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import type {Viewport} from 'reactflow' +import type {BlockEnum, ConversationVariable, Edge, EnvironmentVariable, Node,} from '@/app/components/workflow/types' +import type {TransferMethod} from '@/types/app' +import type {ErrorHandleTypeEnum} from '@/app/components/workflow/nodes/_base/components/error-handle/types' export type NodeTracing = { id: string @@ -85,9 +79,9 @@ export type FetchWorkflowDraftResponse = { export type VersionHistory = FetchWorkflowDraftResponse export type FetchWorkflowDraftPageResponse = { - items: VersionHistory[] - has_more: boolean - page: number + items: VersionHistory[] + has_more: boolean + page: number } export type NodeTracingListResponse = { diff --git a/web/utils/var.ts b/web/utils/var.ts index 236c9debac6b31..4f8d9ae7961e88 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -1,11 +1,16 @@ -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 { InputVarType } from '@/app/components/workflow/types' +import {getMaxVarNameLength, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW} from '@/config' +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' const otherAllowedRegex = /^[a-zA-Z0-9_]+$/ export const getNewVar = (key: string, type: string) => { - const { max_length, ...rest } = VAR_ITEM_TEMPLATE + const {max_length, ...rest} = VAR_ITEM_TEMPLATE if (type !== 'string') { return { ...rest, @@ -23,7 +28,7 @@ export const getNewVar = (key: string, type: string) => { } export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput) => { - const { max_length, ...rest } = VAR_ITEM_TEMPLATE_IN_WORKFLOW + const {max_length, ...rest} = VAR_ITEM_TEMPLATE_IN_WORKFLOW if (type !== InputVarType.textInput) { return { ...rest, @@ -74,7 +79,7 @@ export const checkKeys = (keys: string[], canBeEmpty?: boolean) => { errorMessageKey = res } }) - return { isValid, errorKey, errorMessageKey } + return {isValid, errorKey, errorMessageKey} } const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g From edef4cce05ef15720437d239b481a026f31ed7a1 Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 26 Dec 2024 15:15:29 +0800 Subject: [PATCH 19/21] reformat --- web/service/workflow.ts | 8 ++++---- web/types/workflow.ts | 8 ++++---- web/utils/var.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 52864fd13f3c8b..6bdc3c7a22e0e8 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -1,6 +1,6 @@ -import type {Fetcher} from 'swr' -import {get, post} from './base' -import type {CommonResponse} from '@/models/common' +import type { Fetcher } from 'swr' +import { get, post } from './base' +import type { CommonResponse } from '@/models/common' import { ChatRunHistoryResponse, ConversationVariableResponse, @@ -9,7 +9,7 @@ import { NodesDefaultConfigsResponse, WorkflowRunHistoryResponse, } from '@/types/workflow' -import type {BlockEnum} from '@/app/components/workflow/types' +import type { BlockEnum } from '@/app/components/workflow/types' export const fetchWorkflowDraft = (url: string) => { return get(url, {}, {silent: true}) as Promise diff --git a/web/types/workflow.ts b/web/types/workflow.ts index e1c47f3cfa8e74..e292ebcd8a1b16 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -1,7 +1,7 @@ -import type {Viewport} from 'reactflow' -import type {BlockEnum, ConversationVariable, Edge, EnvironmentVariable, Node,} from '@/app/components/workflow/types' -import type {TransferMethod} from '@/types/app' -import type {ErrorHandleTypeEnum} from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import type { Viewport } from 'reactflow' +import type { BlockEnum, ConversationVariable, Edge, EnvironmentVariable, Node, } from '@/app/components/workflow/types' +import type { TransferMethod } from '@/types/app' +import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' export type NodeTracing = { id: string diff --git a/web/utils/var.ts b/web/utils/var.ts index 4f8d9ae7961e88..fda58a042c9248 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -1,11 +1,11 @@ -import {getMaxVarNameLength, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW} from '@/config' +import { getMaxVarNameLength, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW } from '@/config' 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_]+$/ From cf6d313e7af3bfb000518fc5e9350f058dee650a Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 26 Dec 2024 15:41:10 +0800 Subject: [PATCH 20/21] reformat --- api/services/workflow_service.py | 5 +- .../workflow/header/version-history-modal.tsx | 79 ++++++++++--------- web/service/workflow.ts | 22 +++--- web/types/workflow.ts | 2 +- web/utils/var.ts | 10 +-- 5 files changed, 58 insertions(+), 60 deletions(-) diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index d914b2e2bbf652..e8a1d735737384 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -85,19 +85,16 @@ def get_all_published_workflow(self, app_model: App, page: int, limit: int) -> t 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) # 多查一条 + .limit(limit + 1) .all() ) - # 判断是否还有更多数据 has_more = len(workflows) > limit - # 如果多查到了数据,则移除最后一条 if has_more: workflows = workflows[:-1] diff --git a/web/app/components/workflow/header/version-history-modal.tsx b/web/app/components/workflow/header/version-history-modal.tsx index 2babbeade08f14..9c563d63892f41 100644 --- a/web/app/components/workflow/header/version-history-modal.tsx +++ b/web/app/components/workflow/header/version-history-modal.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useState, useCallback } from 'react' +import React, { useState } from 'react' import useSWR from 'swr' import { useWorkflowRun } from '../hooks' import VersionHistoryItem from './version-history-item' @@ -16,13 +16,13 @@ const VersionHistoryModal = () => { const [page, setPage] = useState(1) const { handleRestoreFromPublishedWorkflow } = useWorkflowRun() const appDetail = useAppStore.getState().appDetail - + const { data: versionHistory, isLoading, } = useSWR( `/apps/${appDetail?.id}/workflows/publish/all?page=${page}&limit=${limit}`, - fetchPublishedAllWorkflow + fetchPublishedAllWorkflow, ) const handleVersionClick = (item: VersionHistory) => { @@ -33,50 +33,51 @@ const VersionHistoryModal = () => { } const handleNextPage = () => { - if (versionHistory?.has_more) { + if (versionHistory?.has_more) setPage(page => page + 1) - } } return (
- {isLoading && page === 1 ? ( -
- -
- ) : ( - <> - {versionHistory?.items?.map(item => ( - - ))} - {isLoading && page > 1 && ( -
- -
- )} - {!isLoading && versionHistory?.has_more && ( -
- -
- )} - {!isLoading && !versionHistory?.items?.length && ( -
+ +
+ )} + {!isLoading && !versionHistory?.items?.length && ( +
暂无历史版本 -
- )} - - )} +
+ )} + + )}
) diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 6bdc3c7a22e0e8..b2c8d323b63662 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -1,7 +1,7 @@ import type { Fetcher } from 'swr' import { get, post } from './base' import type { CommonResponse } from '@/models/common' -import { +import type { ChatRunHistoryResponse, ConversationVariableResponse, FetchWorkflowDraftPageResponse, @@ -12,14 +12,14 @@ import { import type { BlockEnum } from '@/app/components/workflow/types' export const fetchWorkflowDraft = (url: string) => { - return get(url, {}, {silent: true}) as Promise + return get(url, {}, { silent: true }) as Promise } -export const syncWorkflowDraft = ({url, params}: { - url: string; +export const syncWorkflowDraft = ({ url, params }: { + url: string params: Pick }) => { - return post(url, {body: params}, {silent: true}) + return post(url, { body: params }, { silent: true }) } export const fetchNodesDefaultConfigs: Fetcher = (url) => { @@ -35,7 +35,7 @@ export const fetchChatRunHistory: Fetcher = (url } export const singleNodeRun = (appId: string, nodeId: string, params: object) => { - return post(`apps/${appId}/workflows/draft/nodes/${nodeId}/run`, {body: params}) + return post(`apps/${appId}/workflows/draft/nodes/${nodeId}/run`, { body: params }) } export const getIterationSingleNodeRunUrl = (isChatFlow: boolean, appId: string, nodeId: string) => { @@ -60,18 +60,18 @@ export const stopWorkflowRun = (url: string) => { export const fetchNodeDefault = (appId: string, blockType: BlockEnum, query = {}) => { return get(`apps/${appId}/workflows/default-workflow-block-configs/${blockType}`, { - params: {q: JSON.stringify(query)}, + params: { q: JSON.stringify(query) }, }) } // TODO: archived export const updateWorkflowDraftFromDSL = (appId: string, data: string) => { - return post(`apps/${appId}/workflows/draft/import`, {body: {data}}) + return post(`apps/${appId}/workflows/draft/import`, { body: { data } }) } export const fetchCurrentValueOfConversationVariable: Fetcher = ({url, params}) => { - return get(url, {params}) +}> = ({ url, params }) => { + return get(url, { params }) } diff --git a/web/types/workflow.ts b/web/types/workflow.ts index e292ebcd8a1b16..ee0c1c64543230 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -1,5 +1,5 @@ import type { Viewport } from 'reactflow' -import type { BlockEnum, ConversationVariable, Edge, EnvironmentVariable, Node, } from '@/app/components/workflow/types' +import type { BlockEnum, ConversationVariable, Edge, EnvironmentVariable, Node } from '@/app/components/workflow/types' import type { TransferMethod } from '@/types/app' import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' diff --git a/web/utils/var.ts b/web/utils/var.ts index fda58a042c9248..70359337585f5b 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -1,16 +1,16 @@ -import { getMaxVarNameLength, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW } 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 + QUERY_PLACEHOLDER_TEXT, } from '@/app/components/base/prompt-editor/constants' import { InputVarType } from '@/app/components/workflow/types' const otherAllowedRegex = /^[a-zA-Z0-9_]+$/ export const getNewVar = (key: string, type: string) => { - const {max_length, ...rest} = VAR_ITEM_TEMPLATE + const { ...rest } = VAR_ITEM_TEMPLATE if (type !== 'string') { return { ...rest, @@ -28,7 +28,7 @@ export const getNewVar = (key: string, type: string) => { } export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput) => { - const {max_length, ...rest} = VAR_ITEM_TEMPLATE_IN_WORKFLOW + const { max_length, ...rest } = VAR_ITEM_TEMPLATE_IN_WORKFLOW if (type !== InputVarType.textInput) { return { ...rest, @@ -79,7 +79,7 @@ export const checkKeys = (keys: string[], canBeEmpty?: boolean) => { errorMessageKey = res } }) - return {isValid, errorKey, errorMessageKey} + return { isValid, errorKey, errorMessageKey } } const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g From 2f12a8e4dd97bedcfe6f32462b7240a1481f72bf Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 26 Dec 2024 15:47:16 +0800 Subject: [PATCH 21/21] redesign restore list --- .../workflow/header/version-history-item.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/web/app/components/workflow/header/version-history-item.tsx b/web/app/components/workflow/header/version-history-item.tsx index 73357d6b8e28b0..2826da3c197c78 100644 --- a/web/app/components/workflow/header/version-history-item.tsx +++ b/web/app/components/workflow/header/version-history-item.tsx @@ -26,14 +26,25 @@ const VersionHistoryItem: React.FC = ({ item, selectedV return (
item.version !== WorkflowVersion.Draft && onClick(item)} > -
{formatTime(item.version === WorkflowVersion.Draft ? item.updated_at : item.created_at)}
- {renderVersionLabel(item.version)} authored by {item.created_by.name} +
+ + {formatTime(item.version === WorkflowVersion.Draft ? item.updated_at : item.created_at)} + +
+ {renderVersionLabel(item.version)} + + published by {item.created_by.name} + +
+
) }