mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
service
This commit is contained in:
@ -29,10 +29,10 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
const detailParams = { url: '/apps', id: appId }
|
const detailParams = { url: '/apps', id: appId }
|
||||||
const { data: response } = useSWR(detailParams, fetchAppDetail)
|
const { data: response } = useSWR(detailParams, fetchAppDetail)
|
||||||
|
|
||||||
// redirection
|
// redirections
|
||||||
if ((response?.mode === 'workflow' || response?.mode === 'advanced-chat') && (pathname).endsWith('configuration'))
|
if (response && (response?.mode === 'workflow' || response?.mode === 'advanced-chat') && (pathname).endsWith('configuration'))
|
||||||
router.replace(`/app/${appId}/workflow`)
|
router.replace(`/app/${appId}/workflow`)
|
||||||
if ((response?.mode !== 'workflow' && response?.mode !== 'advanced-chat') && (pathname).endsWith('workflow'))
|
if (response && (response?.mode !== 'workflow' && response?.mode !== 'advanced-chat') && (pathname).endsWith('workflow'))
|
||||||
router.replace(`/app/${appId}/configuration`)
|
router.replace(`/app/${appId}/configuration`)
|
||||||
|
|
||||||
const appModeName = (() => {
|
const appModeName = (() => {
|
||||||
@ -89,11 +89,12 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
}, [response])
|
}, [response])
|
||||||
if (!response)
|
if (!response)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(s.app, 'flex', 'overflow-hidden')}>
|
<div className={cn(s.app, 'flex', 'overflow-hidden')}>
|
||||||
<AppSideBar title={response.name} icon={response.icon} icon_background={response.icon_background} desc={appModeName} navigation={navigation} />
|
<AppSideBar title={response.name} icon={response.icon} icon_background={response.icon_background} desc={appModeName} navigation={navigation} />
|
||||||
<div className="bg-white grow overflow-hidden">
|
<div className="bg-white grow overflow-hidden">
|
||||||
{React.cloneElement(children as React.ReactElement<any>, { appMode: response.mode })}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,96 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import Workflow from '@/app/components/workflow'
|
import Workflow from '@/app/components/workflow'
|
||||||
|
|
||||||
const initialNodes = [
|
const Page = () => {
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'custom',
|
|
||||||
position: { x: 180, y: 180 },
|
|
||||||
data: { type: 'start' },
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// id: '2',
|
|
||||||
// type: 'custom',
|
|
||||||
// position: { x: 0, y: 0 },
|
|
||||||
// data: {
|
|
||||||
// type: 'if-else',
|
|
||||||
// branches: [
|
|
||||||
// {
|
|
||||||
// id: 'if-true',
|
|
||||||
// name: 'IS TRUE',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 'if-false',
|
|
||||||
// name: 'IS FALSE',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '3',
|
|
||||||
// type: 'custom',
|
|
||||||
// position: { x: 0, y: 0 },
|
|
||||||
// data: { type: 'question-classifier', sortIndexInBranches: 0 },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '4',
|
|
||||||
// type: 'custom',
|
|
||||||
// position: { x: 0, y: 0 },
|
|
||||||
// data: {
|
|
||||||
// type: 'if-else',
|
|
||||||
// sortIndexInBranches: 1,
|
|
||||||
// branches: [
|
|
||||||
// {
|
|
||||||
// id: 'if-true',
|
|
||||||
// name: 'IS TRUE',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 'if-false',
|
|
||||||
// name: 'IS FALSE',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
]
|
|
||||||
|
|
||||||
const initialEdges = [
|
|
||||||
// {
|
|
||||||
// id: '0',
|
|
||||||
// type: 'custom',
|
|
||||||
// source: '1',
|
|
||||||
// sourceHandle: 'source',
|
|
||||||
// target: '2',
|
|
||||||
// targetHandle: 'target',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '1',
|
|
||||||
// type: 'custom',
|
|
||||||
// source: '2',
|
|
||||||
// sourceHandle: 'if-true',
|
|
||||||
// target: '3',
|
|
||||||
// targetHandle: 'target',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '2',
|
|
||||||
// type: 'custom',
|
|
||||||
// source: '2',
|
|
||||||
// sourceHandle: 'if-false',
|
|
||||||
// target: '4',
|
|
||||||
// targetHandle: 'target',
|
|
||||||
// },
|
|
||||||
]
|
|
||||||
|
|
||||||
const Page: FC = () => {
|
|
||||||
return (
|
return (
|
||||||
<div className='min-w-[720px] w-full h-full overflow-x-auto'>
|
<Workflow
|
||||||
<Workflow
|
nodes={[]}
|
||||||
nodes={initialNodes}
|
edges={[]}
|
||||||
edges={initialEdges}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default memo(Page)
|
export default memo(Page)
|
||||||
|
|||||||
@ -1,96 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import { memo } from 'react'
|
|
||||||
import Workflow from '@/app/components/workflow'
|
|
||||||
|
|
||||||
const initialNodes = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
type: 'custom',
|
|
||||||
position: { x: 180, y: 180 },
|
|
||||||
data: { type: 'start' },
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// id: '2',
|
|
||||||
// type: 'custom',
|
|
||||||
// position: { x: 0, y: 0 },
|
|
||||||
// data: {
|
|
||||||
// type: 'if-else',
|
|
||||||
// branches: [
|
|
||||||
// {
|
|
||||||
// id: 'if-true',
|
|
||||||
// name: 'IS TRUE',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 'if-false',
|
|
||||||
// name: 'IS FALSE',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '3',
|
|
||||||
// type: 'custom',
|
|
||||||
// position: { x: 0, y: 0 },
|
|
||||||
// data: { type: 'question-classifier', sortIndexInBranches: 0 },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '4',
|
|
||||||
// type: 'custom',
|
|
||||||
// position: { x: 0, y: 0 },
|
|
||||||
// data: {
|
|
||||||
// type: 'if-else',
|
|
||||||
// sortIndexInBranches: 1,
|
|
||||||
// branches: [
|
|
||||||
// {
|
|
||||||
// id: 'if-true',
|
|
||||||
// name: 'IS TRUE',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 'if-false',
|
|
||||||
// name: 'IS FALSE',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
]
|
|
||||||
|
|
||||||
const initialEdges = [
|
|
||||||
// {
|
|
||||||
// id: '0',
|
|
||||||
// type: 'custom',
|
|
||||||
// source: '1',
|
|
||||||
// sourceHandle: 'source',
|
|
||||||
// target: '2',
|
|
||||||
// targetHandle: 'target',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '1',
|
|
||||||
// type: 'custom',
|
|
||||||
// source: '2',
|
|
||||||
// sourceHandle: 'if-true',
|
|
||||||
// target: '3',
|
|
||||||
// targetHandle: 'target',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: '2',
|
|
||||||
// type: 'custom',
|
|
||||||
// source: '2',
|
|
||||||
// sourceHandle: 'if-false',
|
|
||||||
// target: '4',
|
|
||||||
// targetHandle: 'target',
|
|
||||||
// },
|
|
||||||
]
|
|
||||||
|
|
||||||
const Page: FC = () => {
|
|
||||||
return (
|
|
||||||
<div className='min-w-[720px] w-full h-full overflow-x-auto'>
|
|
||||||
<Workflow
|
|
||||||
nodes={initialNodes}
|
|
||||||
edges={initialEdges}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default memo(Page)
|
|
||||||
@ -3,6 +3,8 @@ import {
|
|||||||
memo,
|
memo,
|
||||||
// useEffect,
|
// useEffect,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { useParams } from 'next/navigation'
|
||||||
|
import useSWR from 'swr'
|
||||||
import { useKeyPress } from 'ahooks'
|
import { useKeyPress } from 'ahooks'
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
@ -26,6 +28,12 @@ import CustomConnectionLine from './custom-connection-line'
|
|||||||
import Panel from './panel'
|
import Panel from './panel'
|
||||||
import Features from './features'
|
import Features from './features'
|
||||||
import { useStore } from './store'
|
import { useStore } from './store'
|
||||||
|
import { NodeInitialData } from './constants'
|
||||||
|
import {
|
||||||
|
fetchWorkflowDraft,
|
||||||
|
syncWorkflowDraft,
|
||||||
|
} from '@/service/workflow'
|
||||||
|
import Loading from '@/app/components/base/loading'
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
custom: CustomNode,
|
custom: CustomNode,
|
||||||
@ -112,6 +120,36 @@ const WorkflowWrap: FC<WorkflowProps> = ({
|
|||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
}) => {
|
}) => {
|
||||||
|
const appId = useParams().appId
|
||||||
|
const { data, isLoading, error } = useSWR(`/apps/${appId}/workflows/draft`, fetchWorkflowDraft)
|
||||||
|
// const { data: configsData } = useSWR(`/apps/${appId}/workflows/default-workflow-block-configs`, fetchNodesDefaultConfigs)
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
syncWorkflowDraft({
|
||||||
|
url: `/apps/${appId}/workflows/draft`,
|
||||||
|
params: {
|
||||||
|
graph: {
|
||||||
|
nodes: [{
|
||||||
|
id: `${Date.now()}`,
|
||||||
|
data: NodeInitialData.start,
|
||||||
|
position: {
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
edges: [],
|
||||||
|
},
|
||||||
|
features: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Loading />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<Workflow
|
<Workflow
|
||||||
|
|||||||
@ -44,6 +44,7 @@ type IOtherOptions = {
|
|||||||
bodyStringify?: boolean
|
bodyStringify?: boolean
|
||||||
needAllResponseContent?: boolean
|
needAllResponseContent?: boolean
|
||||||
deleteContentType?: boolean
|
deleteContentType?: boolean
|
||||||
|
silent?: boolean
|
||||||
onData?: IOnData // for stream
|
onData?: IOnData // for stream
|
||||||
onThought?: IOnThought
|
onThought?: IOnThought
|
||||||
onFile?: IOnFile
|
onFile?: IOnFile
|
||||||
@ -176,6 +177,7 @@ const baseFetch = <T>(
|
|||||||
needAllResponseContent,
|
needAllResponseContent,
|
||||||
deleteContentType,
|
deleteContentType,
|
||||||
getAbortController,
|
getAbortController,
|
||||||
|
silent,
|
||||||
}: IOtherOptions,
|
}: IOtherOptions,
|
||||||
): Promise<T> => {
|
): Promise<T> => {
|
||||||
const options: typeof baseOptions & FetchOptionType = Object.assign({}, baseOptions, fetchOptions)
|
const options: typeof baseOptions & FetchOptionType = Object.assign({}, baseOptions, fetchOptions)
|
||||||
@ -250,13 +252,14 @@ const baseFetch = <T>(
|
|||||||
case 401: {
|
case 401: {
|
||||||
if (isPublicAPI) {
|
if (isPublicAPI) {
|
||||||
return bodyJson.then((data: ResponseError) => {
|
return bodyJson.then((data: ResponseError) => {
|
||||||
Toast.notify({ type: 'error', message: data.message })
|
if (!silent)
|
||||||
|
Toast.notify({ type: 'error', message: data.message })
|
||||||
return Promise.reject(data)
|
return Promise.reject(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const loginUrl = `${globalThis.location.origin}/signin`
|
const loginUrl = `${globalThis.location.origin}/signin`
|
||||||
bodyJson.then((data: ResponseError) => {
|
bodyJson.then((data: ResponseError) => {
|
||||||
if (data.code === 'init_validate_failed' && IS_CE_EDITION)
|
if (data.code === 'init_validate_failed' && IS_CE_EDITION && !silent)
|
||||||
Toast.notify({ type: 'error', message: data.message, duration: 4000 })
|
Toast.notify({ type: 'error', message: data.message, duration: 4000 })
|
||||||
else if (data.code === 'not_init_validated' && IS_CE_EDITION)
|
else if (data.code === 'not_init_validated' && IS_CE_EDITION)
|
||||||
globalThis.location.href = `${globalThis.location.origin}/init`
|
globalThis.location.href = `${globalThis.location.origin}/init`
|
||||||
@ -264,7 +267,7 @@ const baseFetch = <T>(
|
|||||||
globalThis.location.href = `${globalThis.location.origin}/install`
|
globalThis.location.href = `${globalThis.location.origin}/install`
|
||||||
else if (location.pathname !== '/signin' || !IS_CE_EDITION)
|
else if (location.pathname !== '/signin' || !IS_CE_EDITION)
|
||||||
globalThis.location.href = loginUrl
|
globalThis.location.href = loginUrl
|
||||||
else
|
else if (!silent)
|
||||||
Toast.notify({ type: 'error', message: data.message })
|
Toast.notify({ type: 'error', message: data.message })
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Handle any other errors
|
// Handle any other errors
|
||||||
@ -275,7 +278,8 @@ const baseFetch = <T>(
|
|||||||
}
|
}
|
||||||
case 403:
|
case 403:
|
||||||
bodyJson.then((data: ResponseError) => {
|
bodyJson.then((data: ResponseError) => {
|
||||||
Toast.notify({ type: 'error', message: data.message })
|
if (!silent)
|
||||||
|
Toast.notify({ type: 'error', message: data.message })
|
||||||
if (data.code === 'already_setup')
|
if (data.code === 'already_setup')
|
||||||
globalThis.location.href = `${globalThis.location.origin}/signin`
|
globalThis.location.href = `${globalThis.location.origin}/signin`
|
||||||
})
|
})
|
||||||
@ -283,7 +287,8 @@ const baseFetch = <T>(
|
|||||||
// fall through
|
// fall through
|
||||||
default:
|
default:
|
||||||
bodyJson.then((data: ResponseError) => {
|
bodyJson.then((data: ResponseError) => {
|
||||||
Toast.notify({ type: 'error', message: data.message })
|
if (!silent)
|
||||||
|
Toast.notify({ type: 'error', message: data.message })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return Promise.reject(resClone)
|
return Promise.reject(resClone)
|
||||||
@ -301,7 +306,8 @@ const baseFetch = <T>(
|
|||||||
resolve(needAllResponseContent ? resClone : data)
|
resolve(needAllResponseContent ? resClone : data)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
Toast.notify({ type: 'error', message: err })
|
if (!silent)
|
||||||
|
Toast.notify({ type: 'error', message: err })
|
||||||
reject(err)
|
reject(err)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|||||||
18
web/service/workflow.ts
Normal file
18
web/service/workflow.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { Fetcher } from 'swr'
|
||||||
|
import { get, post } from './base'
|
||||||
|
import type { CommonResponse } from '@/models/common'
|
||||||
|
import type {
|
||||||
|
FetchWorkflowDraftResponse,
|
||||||
|
} from '@/types/workflow'
|
||||||
|
|
||||||
|
export const fetchWorkflowDraft: Fetcher<FetchWorkflowDraftResponse, string> = (url) => {
|
||||||
|
return get<FetchWorkflowDraftResponse>(url, {}, { silent: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const syncWorkflowDraft: Fetcher<CommonResponse, { url: string; params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features'> }> = ({ url, params }) => {
|
||||||
|
return post<CommonResponse>(url, { body: params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchNodesDefaultConfigs: Fetcher<any, string> = (url) => {
|
||||||
|
return get<any>(url)
|
||||||
|
}
|
||||||
13
web/types/workflow.ts
Normal file
13
web/types/workflow.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type {
|
||||||
|
Edge,
|
||||||
|
Node,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
|
||||||
|
export type FetchWorkflowDraftResponse = {
|
||||||
|
id: string
|
||||||
|
graph: {
|
||||||
|
nodes: Node[]
|
||||||
|
edges: Edge[]
|
||||||
|
}
|
||||||
|
features?: any
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user