mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
add app-info-panel
This commit is contained in:
58
web/app/components/workflow/app-info-panel.tsx
Normal file
58
web/app/components/workflow/app-info-panel.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import BlockIcon from './block-icon'
|
||||
import { BlockEnum } from './types'
|
||||
import { useWorkflowContext } from './context'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import { FileCheck02 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
|
||||
const AppInfoPanel: FC = () => {
|
||||
const { selectedNode } = useWorkflowContext()
|
||||
|
||||
if (selectedNode)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='absolute top-14 right-2 bottom-2 w-[420px] bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl z-10 overflow-y-auto'>
|
||||
<div className='sticky top-0 bg-white border-b-[0.5px] border-black/5'>
|
||||
<div className='flex pt-4 px-4 pb-1'>
|
||||
<div className='mr-3 w-10 h-10'></div>
|
||||
<div className='mt-2 text-base font-semibold text-gray-900'>
|
||||
Fitness and Nutrition Expert
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4 py-[13px] text-xs leading-[18px] text-gray-500'>
|
||||
A Fitness and Nutrition Expert specializes in guiding individuals towards healthier lifestyles through exercise and diet.
|
||||
</div>
|
||||
<div className='flex items-center px-4 h-[42px] text-[13px] font-semibold text-gray-700'>
|
||||
<FileCheck02 className='mr-1 w-4 h-4' />
|
||||
Checklist(2)
|
||||
</div>
|
||||
</div>
|
||||
<div className='py-2'>
|
||||
<div className='px-4 py-2 text-xs text-gray-400'>
|
||||
Make sure all issues are resolved before publishing
|
||||
</div>
|
||||
<div className='px-4 py-2'>
|
||||
<div className='border-[0.5px] border-gray-200 bg-white shadow-xs rounded-lg'>
|
||||
<div className='flex items-center p-2 h-9 text-xs font-medium text-gray-700'>
|
||||
<BlockIcon
|
||||
type={BlockEnum.Start}
|
||||
className='mr-1.5'
|
||||
/>
|
||||
Start
|
||||
</div>
|
||||
<div className='px-3 py-2 border-t-[0.5px] border-t-black/[0.02] bg-gray-25 rounded-b-lg'>
|
||||
<div className='flex text-xs leading-[18px] text-gray-500'>
|
||||
<AlertTriangle className='mt-[3px] mr-2 w-3 h-3 text-[#F79009]' />
|
||||
This step is not connected to anything
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AppInfoPanel)
|
||||
@ -37,6 +37,7 @@ const getIcon = (type: BlockEnum, className: string) => {
|
||||
[BlockEnum.QuestionClassifier]: <QuestionClassifier className={className} />,
|
||||
[BlockEnum.TemplateTransform]: <TemplatingTransform className={className} />,
|
||||
[BlockEnum.VariableAssigner]: <VariableX className={className} />,
|
||||
[BlockEnum.Tool]: <VariableX className={className} />,
|
||||
}[type]
|
||||
}
|
||||
const ICON_CONTAINER_BG_COLOR_MAP: Record<string, string> = {
|
||||
|
||||
@ -17,6 +17,8 @@ import Header from './header'
|
||||
import CustomNode, {
|
||||
Panel,
|
||||
} from './nodes'
|
||||
import AppInfoPanel from './app-info-panel'
|
||||
import ZoomInOut from './zoom-in-out'
|
||||
import CustomEdge from './custom-edge'
|
||||
import type { Node } from './types'
|
||||
|
||||
@ -36,7 +38,9 @@ const Workflow = () => {
|
||||
return (
|
||||
<div className='relative w-full h-full'>
|
||||
<Header />
|
||||
<AppInfoPanel />
|
||||
<Panel />
|
||||
<ZoomInOut />
|
||||
<ReactFlow
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
|
||||
@ -22,7 +22,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
} = useWorkflowContext()
|
||||
|
||||
return (
|
||||
<div className='absolute top-2 right-2 bottom-2 w-[420px] bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl z-20 overflow-y-auto'>
|
||||
<div className='absolute top-14 right-2 bottom-2 w-[420px] bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl z-10 overflow-y-auto'>
|
||||
<div className='sticky top-0 bg-white border-b-[0.5px] border-black/5'>
|
||||
<div className='flex items-center px-4 pt-3'>
|
||||
<BlockIcon
|
||||
|
||||
@ -34,11 +34,12 @@ const CustomNode = ({
|
||||
|
||||
export const Panel = () => {
|
||||
const { selectedNode } = useWorkflowContext()
|
||||
const PanelComponent = PanelMap[selectedNode?.data.type || '']
|
||||
|
||||
if (!PanelComponent)
|
||||
if (!selectedNode)
|
||||
return null
|
||||
|
||||
const PanelComponent = PanelMap[selectedNode.data.type]
|
||||
|
||||
return (
|
||||
<PanelComponent />
|
||||
)
|
||||
|
||||
96
web/app/components/workflow/zoom-in-out.tsx
Normal file
96
web/app/components/workflow/zoom-in-out.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
Fragment,
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { SearchLg } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
const ZOOM_IN_OUT_OPTIONS = [
|
||||
[
|
||||
{
|
||||
key: 'in',
|
||||
text: 'Zoom In',
|
||||
},
|
||||
{
|
||||
key: 'out',
|
||||
text: 'Zoom Out',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
key: 'to50',
|
||||
text: 'Zoom to 50%',
|
||||
},
|
||||
{
|
||||
key: 'to100',
|
||||
text: 'Zoom to 100%',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
key: 'fit',
|
||||
text: 'Zoom to Fit',
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
const ZoomInOut: FC = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
placement='top-start'
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
offset={4}
|
||||
>
|
||||
<PortalToFollowElemTrigger asChild onClick={() => setOpen(v => !v)}>
|
||||
<div className={`
|
||||
absolute left-6 bottom-6
|
||||
flex items-center px-2.5 h-9 cursor-pointer rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg
|
||||
text-[13px] text-gray-500 z-10
|
||||
`}>
|
||||
<SearchLg className='mr-1 w-4 h-4' />
|
||||
100%
|
||||
<ChevronDown className='ml-1 w-4 h-4' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent>
|
||||
<div className='w-[168px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'>
|
||||
{
|
||||
ZOOM_IN_OUT_OPTIONS.map((options, i) => (
|
||||
<Fragment key={i}>
|
||||
{
|
||||
i !== 0 && (
|
||||
<div className='h-[1px] bg-gray-100' />
|
||||
)
|
||||
}
|
||||
<div className='p-1'>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.key}
|
||||
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer text-sm text-gray-700'
|
||||
>
|
||||
{option.text}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ZoomInOut)
|
||||
Reference in New Issue
Block a user