mirror of https://github.com/langgenius/dify.git
record panel
This commit is contained in:
parent
6d5618447e
commit
1840d05a37
|
|
@ -57,8 +57,8 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||
}, [onOpenChange])
|
||||
const handleTrigger = useCallback<MouseEventHandler<HTMLDivElement>>((e) => {
|
||||
e.stopPropagation()
|
||||
handleOpenChange(!open)
|
||||
}, [open, handleOpenChange])
|
||||
setLocalOpen(v => !v)
|
||||
}, [])
|
||||
const handleSelect = useCallback((type: BlockEnum) => {
|
||||
handleOpenChange(false)
|
||||
onSelect(type)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arr
|
|||
const Header: FC = () => {
|
||||
const mode = useStore(state => state.mode)
|
||||
const setShowFeatures = useStore(state => state.setShowFeatures)
|
||||
const runStaus = useStore(state => state.runStaus)
|
||||
const setRunStaus = useStore(state => state.setRunStaus)
|
||||
|
||||
const handleShowFeatures = useCallback(() => {
|
||||
setShowFeatures(true)
|
||||
|
|
@ -36,13 +38,20 @@ const Header: FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Button className={`
|
||||
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-primary-600
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
`}>
|
||||
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
|
||||
Go back to editor
|
||||
</Button>
|
||||
{
|
||||
runStaus && (
|
||||
<Button
|
||||
className={`
|
||||
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-primary-600
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
`}
|
||||
onClick={() => setRunStaus('')}
|
||||
>
|
||||
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
|
||||
Go back to editor
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<RunAndHistory />
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,19 +4,39 @@ import { useStore } from '../store'
|
|||
import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
const RunAndHistory: FC = () => {
|
||||
const showRunHistory = useStore(state => state.showRunHistory)
|
||||
const setShowRunHistory = useStore(state => state.setShowRunHistory)
|
||||
const runStaus = useStore(state => state.runStaus)
|
||||
const setRunStaus = useStore(state => state.setRunStaus)
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-0.5 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
|
||||
<div className={`
|
||||
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600
|
||||
hover:bg-primary-50 cursor-pointer
|
||||
`}>
|
||||
<Play className='mr-1 w-4 h-4' />
|
||||
Run
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600
|
||||
hover:bg-primary-50 cursor-pointer
|
||||
${runStaus === 'running' && 'bg-primary-50 !cursor-not-allowed'}
|
||||
`}
|
||||
onClick={() => runStaus !== 'running' && setRunStaus('running')}
|
||||
>
|
||||
{
|
||||
runStaus === 'running'
|
||||
? (
|
||||
<>
|
||||
<Loading02 className='mr-1 w-4 h-4 animate-spin' />
|
||||
Running
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Play className='mr-1 w-4 h-4' />
|
||||
Run
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='mx-0.5 w-[0.5px] h-8 bg-gray-200'></div>
|
||||
<TooltipPlus
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import type {
|
|||
SelectedNode,
|
||||
} from './types'
|
||||
import { NodeInitialData } from './constants'
|
||||
import { initialNodesPosition } from './utils'
|
||||
|
||||
export const useWorkflow = () => {
|
||||
const store = useStoreApi()
|
||||
|
|
@ -78,14 +77,11 @@ export const useWorkflow = () => {
|
|||
} = store.getState()
|
||||
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
const selectedNode = draft.find(node => node.id === nodeId)
|
||||
draft.forEach(node => node.selected = false)
|
||||
const selectedNode = draft.find(node => node.id === nodeId)!
|
||||
|
||||
if (selectedNode) {
|
||||
if (cancelSelection)
|
||||
selectedNode.selected = false
|
||||
else
|
||||
selectedNode.selected = true
|
||||
}
|
||||
if (!cancelSelection)
|
||||
selectedNode.selected = true
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [store])
|
||||
|
|
@ -124,9 +120,9 @@ export const useWorkflow = () => {
|
|||
setEdges,
|
||||
} = store.getState()
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const currentEdge = draft.find(e => e.id === edge.id)
|
||||
if (currentEdge)
|
||||
currentEdge.data = { ...currentEdge.data, hovering: true }
|
||||
const currentEdge = draft.find(e => e.id === edge.id)!
|
||||
|
||||
currentEdge.data = { ...currentEdge.data, hovering: true }
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
|
@ -137,9 +133,9 @@ export const useWorkflow = () => {
|
|||
setEdges,
|
||||
} = store.getState()
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const currentEdge = draft.find(e => e.id === edge.id)
|
||||
if (currentEdge)
|
||||
currentEdge.data = { ...currentEdge.data, hovering: false }
|
||||
const currentEdge = draft.find(e => e.id === edge.id)!
|
||||
|
||||
currentEdge.data = { ...currentEdge.data, hovering: false }
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
|
@ -281,59 +277,6 @@ export const useWorkflow = () => {
|
|||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
||||
const handleInitialLayoutNodes = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
|
||||
setNodes(initialNodesPosition(getNodes(), edges))
|
||||
setEdges(produce(edges, (draft) => {
|
||||
draft.forEach((edge) => {
|
||||
edge.hidden = false
|
||||
})
|
||||
}))
|
||||
}, [store])
|
||||
|
||||
const handleUpdateNodesPosition = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
|
||||
const nodes = getNodes()
|
||||
const groups = nodes.reduce((acc, cur) => {
|
||||
const x = cur.data.position.x
|
||||
|
||||
if (!acc[x])
|
||||
acc[x] = [cur]
|
||||
else
|
||||
acc[x].push(cur)
|
||||
|
||||
return acc
|
||||
}, {} as Record<string, Node[]>)
|
||||
const heightMap: Record<string, number> = {}
|
||||
|
||||
Object.keys(groups).forEach((key) => {
|
||||
let baseHeight = 0
|
||||
groups[key].sort((a, b) => a.data.position!.y - b.data.position!.y).forEach((node) => {
|
||||
heightMap[node.id] = baseHeight
|
||||
baseHeight = node.height! + 39
|
||||
})
|
||||
})
|
||||
setNodes(produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.position = {
|
||||
...node.position,
|
||||
x: node.data.position.x * (220 + 64),
|
||||
y: heightMap[node.id],
|
||||
}
|
||||
})
|
||||
}))
|
||||
}, [store])
|
||||
|
||||
return {
|
||||
handleEnterNode,
|
||||
handleLeaveNode,
|
||||
|
|
@ -346,7 +289,5 @@ export const useWorkflow = () => {
|
|||
handleAddNextNode,
|
||||
handleChangeCurrentNode,
|
||||
handleDeleteNode,
|
||||
handleInitialLayoutNodes,
|
||||
handleUpdateNodesPosition,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { MouseEvent } from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
|
|
@ -35,10 +36,11 @@ export const NodeTargetHandle = ({
|
|||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
setOpen(v)
|
||||
}, [])
|
||||
const handleHandleClick = () => {
|
||||
const handleHandleClick = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (!connected)
|
||||
handleOpenChange(!open)
|
||||
}
|
||||
setOpen(v => !v)
|
||||
}, [connected])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -93,10 +95,11 @@ export const NodeSourceHandle = ({
|
|||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
setOpen(v)
|
||||
}, [])
|
||||
const handleHandleClick = () => {
|
||||
const handleHandleClick = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (!connected)
|
||||
handleOpenChange(!open)
|
||||
}
|
||||
setOpen(v => !v)
|
||||
}, [connected])
|
||||
const handleSelect = useCallback((type: BlockEnum) => {
|
||||
handleAddNextNode(id, type, handleId)
|
||||
}, [handleAddNextNode, id, handleId])
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import { useStore } from '../store'
|
|||
import WorkflowInfo from './workflow-info'
|
||||
import DebugAndPreview from './debug-and-preview'
|
||||
import RunHistory from './run-history'
|
||||
import Record from './record'
|
||||
|
||||
const Panel: FC = () => {
|
||||
const mode = useStore(state => state.mode)
|
||||
const runStaus = useStore(state => state.runStaus)
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const selectedNode = nodes.find(node => node.selected)
|
||||
const showRunHistory = useStore(state => state.showRunHistory)
|
||||
|
|
@ -30,6 +32,11 @@ const Panel: FC = () => {
|
|||
|
||||
return (
|
||||
<div className='absolute top-14 right-0 bottom-2 flex z-10'>
|
||||
{
|
||||
runStaus && (
|
||||
<Record />
|
||||
)
|
||||
}
|
||||
{
|
||||
showNodePanel && (
|
||||
<NodePanel {...selectedNode!} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
const Record = () => {
|
||||
return (
|
||||
<div className='w-[400px] h-full rounded-2xl border-[0.5px] border-gray-200 shadow-xl bg-white'>
|
||||
<div className='p-4 pb-1 text-base font-semibold text-gray-900'>
|
||||
Test Run#5
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Record
|
||||
|
|
@ -8,6 +8,7 @@ import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsA
|
|||
const RunHistory = () => {
|
||||
const mode = useStore(state => state.mode)
|
||||
const setShowRunHistory = useStore(state => state.setShowRunHistory)
|
||||
const setRunStaus = useStore(state => state.setRunStaus)
|
||||
|
||||
return (
|
||||
<div className='w-[200px] h-full bg-white border-[0.5px] border-gray-200 shadow-xl rounded-l-2xl'>
|
||||
|
|
@ -34,7 +35,10 @@ const RunHistory = () => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
<div className='flex px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer'>
|
||||
<div
|
||||
className='flex px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer'
|
||||
onClick={() => setRunStaus('finished')}
|
||||
>
|
||||
<AlertCircle className='mt-0.5 mr-1.5 w-3.5 h-3.5 text-[#F79009]' />
|
||||
<div>
|
||||
<div className='flex items-center text-[13px] font-medium text-primary-600 leading-[18px]'>Test Run#6</div>
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ type State = {
|
|||
mode: string
|
||||
showRunHistory: boolean
|
||||
showFeatures: boolean
|
||||
runStaus: string
|
||||
}
|
||||
|
||||
type Action = {
|
||||
setShowRunHistory: (showRunHistory: boolean) => void
|
||||
setShowFeatures: (showFeatures: boolean) => void
|
||||
setRunStaus: (runStaus: string) => void
|
||||
}
|
||||
|
||||
export const useStore = create<State & Action>(set => ({
|
||||
|
|
@ -17,4 +19,6 @@ export const useStore = create<State & Action>(set => ({
|
|||
setShowRunHistory: showRunHistory => set(() => ({ showRunHistory })),
|
||||
showFeatures: false,
|
||||
setShowFeatures: showFeatures => set(() => ({ showFeatures })),
|
||||
runStaus: 'finished',
|
||||
setRunStaus: runStaus => set(() => ({ runStaus })),
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -24,13 +24,12 @@ export type Branch = {
|
|||
}
|
||||
|
||||
export type CommonNodeType = {
|
||||
position?: {
|
||||
index?: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
sortIndexInBranches?: number
|
||||
hovering?: boolean
|
||||
branches?: Branch[]
|
||||
targetBranches?: Branch[]
|
||||
title: string
|
||||
desc: string
|
||||
type: BlockEnum
|
||||
|
|
|
|||
|
|
@ -1,54 +1,83 @@
|
|||
import produce from 'immer'
|
||||
import {
|
||||
getConnectedEdges,
|
||||
getOutgoers,
|
||||
} from 'reactflow'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
} from './types'
|
||||
import { BlockEnum } from './types'
|
||||
|
||||
export const initialNodesPosition = (oldNodes: Node[], edges: Edge[]) => {
|
||||
const nodes = cloneDeep(oldNodes)
|
||||
const start = nodes.find(node => node.data.type === BlockEnum.Start)!
|
||||
export const nodesLevelOrderTraverse = (
|
||||
firstNode: Node,
|
||||
nodes: Node[],
|
||||
edges: Edge[],
|
||||
callback: (n: Node) => void,
|
||||
) => {
|
||||
const queue = [{
|
||||
node: firstNode,
|
||||
}]
|
||||
|
||||
start.position.x = 0
|
||||
start.position.y = 0
|
||||
start.data.position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
const queue = [start]
|
||||
|
||||
let depth = 0
|
||||
let breadth = 0
|
||||
let baseHeight = 0
|
||||
while (queue.length) {
|
||||
const node = queue.shift()!
|
||||
const { node } = queue.shift()!
|
||||
callback(node)
|
||||
|
||||
if (node.data.position?.x !== depth) {
|
||||
breadth = 0
|
||||
baseHeight = 0
|
||||
const targetBranches = node.data.targetBranches
|
||||
if (targetBranches?.length) {
|
||||
const targetEdges = getConnectedEdges([node], edges)
|
||||
|
||||
if (targetEdges.length) {
|
||||
const sortedTargetEdges = targetEdges
|
||||
.filter(edge => edge.source === node.id)
|
||||
.sort((a, b) => {
|
||||
const aIndex = targetBranches.findIndex(branch => branch.id === a.sourceHandle)
|
||||
const bIndex = targetBranches.findIndex(branch => branch.id === b.sourceHandle)
|
||||
|
||||
return aIndex - bIndex
|
||||
})
|
||||
|
||||
const outgoers = getOutgoers(node, nodes, sortedTargetEdges)
|
||||
queue.push(...outgoers.map((outgoer) => {
|
||||
return {
|
||||
node: outgoer,
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
else {
|
||||
const outgoers = getOutgoers(node, nodes, edges)
|
||||
|
||||
depth = node.data.position?.x || 0
|
||||
|
||||
const outgoers = getOutgoers(node, nodes, edges).sort((a, b) => (a.data.sortIndexInBranches || 0) - (b.data.sortIndexInBranches || 0))
|
||||
|
||||
if (outgoers.length) {
|
||||
queue.push(...outgoers.map((outgoer) => {
|
||||
outgoer.data.position = {
|
||||
x: depth + 1,
|
||||
y: breadth,
|
||||
}
|
||||
outgoer.position.x = (depth + 1) * (220 + 64)
|
||||
outgoer.position.y = baseHeight
|
||||
baseHeight += ((outgoer.height || 0) + 39)
|
||||
breadth += 1
|
||||
return outgoer
|
||||
}))
|
||||
if (outgoers.length === 1) {
|
||||
queue.push({
|
||||
node: outgoers[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
export const initialNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.type = 'custom'
|
||||
})
|
||||
})
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.forEach((edge) => {
|
||||
edge.type = 'custom'
|
||||
})
|
||||
})
|
||||
|
||||
return [newNodes, newEdges]
|
||||
}
|
||||
|
||||
export type PositionMap = {
|
||||
position: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
index: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue