| return ( | return ( | ||||
| <div className='flex flex-col items-center'> | <div className='flex flex-col items-center'> | ||||
| <div className="shrink-0 w-[163px] h-[149px] bg-cover bg-no-repeat bg-[url('~@/app/components/tools/add-tool-modal/empty.png')]"></div> | <div className="shrink-0 w-[163px] h-[149px] bg-cover bg-no-repeat bg-[url('~@/app/components/tools/add-tool-modal/empty.png')]"></div> | ||||
| <div className='mb-1 text-[13px] font-medium text-gray-700 leading-[18px]'>{t('tools.addToolModal.emptyTitle')}</div> | |||||
| <div className='text-[13px] text-gray-500 leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div> | |||||
| <div className='mb-1 text-[13px] font-medium text-text-primary leading-[18px]'>{t('tools.addToolModal.emptyTitle')}</div> | |||||
| <div className='text-[13px] text-text-tertiary leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div> | |||||
| </div> | </div> | ||||
| ) | ) | ||||
| } | } |
| return mergedTools.filter((toolWithProvider) => { | return mergedTools.filter((toolWithProvider) => { | ||||
| return isMatchingKeywords(toolWithProvider.name, searchText) | return isMatchingKeywords(toolWithProvider.name, searchText) | ||||
| || toolWithProvider.tools.some((tool) => { | |||||
| return Object.values(tool.label).some((label) => { | |||||
| return isMatchingKeywords(label, searchText) | |||||
| || toolWithProvider.tools.some((tool) => { | |||||
| return Object.values(tool.label).some((label) => { | |||||
| return isMatchingKeywords(label, searchText) | |||||
| }) | |||||
| }) | }) | ||||
| }) | |||||
| }) | }) | ||||
| }, [activeTab, buildInTools, customTools, workflowTools, searchText]) | }, [activeTab, buildInTools, customTools, workflowTools, searchText]) | ||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| <div className='flex items-center px-3 h-8 space-x-1 bg-gray-25 border-b-[0.5px] border-black/[0.08] shadow-xs'> | |||||
| <div className='flex items-center px-3 h-8 space-x-1 bg-background-default-hover border-b-[0.5px] border-divider-subtle shadow-xs'> | |||||
| { | { | ||||
| tabs.map(tab => ( | tabs.map(tab => ( | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center px-2 h-6 rounded-md hover:bg-gray-100 cursor-pointer', | |||||
| 'text-xs font-medium text-gray-700', | |||||
| activeTab === tab.key && 'bg-gray-200', | |||||
| 'flex items-center px-2 h-6 rounded-md hover:bg-state-base-hover-alt cursor-pointer', | |||||
| 'system-xs-medium text-text-tertiary', | |||||
| activeTab === tab.key && 'system-xs-semibold bg-state-base-hover-alt text-text-primary', | |||||
| )} | )} | ||||
| key={tab.key} | key={tab.key} | ||||
| onClick={() => setActiveTab(tab.key)} | onClick={() => setActiveTab(tab.key)} |
| > | > | ||||
| { | { | ||||
| classification !== '-' && !!list.length && ( | classification !== '-' && !!list.length && ( | ||||
| <div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'> | |||||
| <div className='flex items-start px-3 h-[22px] text-xs font-medium text-text-tertiary'> | |||||
| {t(`workflow.tabs.${classification}`)} | {t(`workflow.tabs.${classification}`)} | ||||
| </div> | </div> | ||||
| ) | ) | ||||
| <Tooltip | <Tooltip | ||||
| key={block.type} | key={block.type} | ||||
| position='right' | position='right' | ||||
| popupClassName='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' | |||||
| popupClassName='w-[200px]' | |||||
| popupContent={( | popupContent={( | ||||
| <div> | <div> | ||||
| <BlockIcon | <BlockIcon | ||||
| className='mb-2' | className='mb-2' | ||||
| type={block.type} | type={block.type} | ||||
| /> | /> | ||||
| <div className='mb-1 text-sm leading-5 text-gray-900'>{block.title}</div> | |||||
| <div className='text-xs text-gray-700 leading-[18px]'>{nodesExtraData[block.type].about}</div> | |||||
| <div className='mb-1 system-md-medium text-text-primary'>{block.title}</div> | |||||
| <div className='text-text-tertiary system-xs-regular'>{nodesExtraData[block.type].about}</div> | |||||
| </div> | </div> | ||||
| )} | )} | ||||
| > | > | ||||
| <div | <div | ||||
| key={block.type} | key={block.type} | ||||
| className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-gray-50 cursor-pointer' | |||||
| className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer' | |||||
| onClick={() => onSelect(block.type)} | onClick={() => onSelect(block.type)} | ||||
| > | > | ||||
| <BlockIcon | <BlockIcon | ||||
| className='mr-2 shrink-0' | className='mr-2 shrink-0' | ||||
| type={block.type} | type={block.type} | ||||
| /> | /> | ||||
| <div className='text-sm text-gray-900'>{block.title}</div> | |||||
| <div className='text-sm text-text-secondary'>{block.title}</div> | |||||
| </div> | </div> | ||||
| </Tooltip> | </Tooltip> | ||||
| )) | )) | ||||
| <div className='p-1'> | <div className='p-1'> | ||||
| { | { | ||||
| isEmpty && ( | isEmpty && ( | ||||
| <div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div> | |||||
| <div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div> | |||||
| ) | ) | ||||
| } | } | ||||
| { | { |
| element.scrollIntoView({ behavior: 'smooth' }) | element.scrollIntoView({ behavior: 'smooth' }) | ||||
| } | } | ||||
| return ( | return ( | ||||
| <div className="index-bar fixed right-4 top-36 flex flex-col items-center text-xs font-medium text-gray-500"> | |||||
| <div className="index-bar fixed right-4 top-36 flex flex-col items-center text-xs font-medium text-text-quaternary"> | |||||
| {letters.map(letter => ( | {letters.map(letter => ( | ||||
| <div className="hover:text-gray-900 cursor-pointer" key={letter} onClick={() => handleIndexClick(letter)}> | |||||
| <div className="hover:text-text-secondary cursor-pointer" key={letter} onClick={() => handleIndexClick(letter)}> | |||||
| {letter} | {letter} | ||||
| </div> | </div> | ||||
| ))} | ))} |
| import { | import { | ||||
| Plus02, | Plus02, | ||||
| } from '@/app/components/base/icons/src/vender/line/general' | } from '@/app/components/base/icons/src/vender/line/general' | ||||
| import classNames from '@/utils/classnames' | |||||
| type NodeSelectorProps = { | type NodeSelectorProps = { | ||||
| open?: boolean | open?: boolean | ||||
| <div | <div | ||||
| className={` | className={` | ||||
| flex items-center justify-center | flex items-center justify-center | ||||
| w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 | |||||
| w-4 h-4 rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover cursor-pointer z-10 | |||||
| ${triggerClassName?.(open)} | ${triggerClassName?.(open)} | ||||
| `} | `} | ||||
| style={triggerStyle} | style={triggerStyle} | ||||
| > | > | ||||
| <Plus02 className='w-2.5 h-2.5 text-white' /> | |||||
| <Plus02 className='w-2.5 h-2.5' /> | |||||
| </div> | </div> | ||||
| ) | ) | ||||
| } | } | ||||
| </PortalToFollowElemTrigger> | </PortalToFollowElemTrigger> | ||||
| <PortalToFollowElemContent className='z-[1000]'> | <PortalToFollowElemContent className='z-[1000]'> | ||||
| <div className={`rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`}> | |||||
| <div className='px-2 pt-2' onClick={e => e.stopPropagation()}> | |||||
| <div className={ | |||||
| classNames(`rounded-lg border-[0.5px] backdrop-blur-[5px] | |||||
| border-components-panel-border bg-components-panel-bg-blur shadow-lg`, popupClassName)}> | |||||
| <div className='p-2 pb-1' onClick={e => e.stopPropagation()}> | |||||
| <Input | <Input | ||||
| showLeftIcon | showLeftIcon | ||||
| showClearIcon | showClearIcon |
| <div onClick={e => e.stopPropagation()}> | <div onClick={e => e.stopPropagation()}> | ||||
| { | { | ||||
| !noBlocks && ( | !noBlocks && ( | ||||
| <div className='flex items-center px-3 border-b-[0.5px] border-b-black/5'> | |||||
| <div className='flex items-center px-3 border-b-[0.5px] border-divider-subtle'> | |||||
| { | { | ||||
| tabs.map(tab => ( | tabs.map(tab => ( | ||||
| <div | <div | ||||
| key={tab.key} | key={tab.key} | ||||
| className={cn( | className={cn( | ||||
| 'relative mr-4 h-[34px] text-[13px] leading-[34px] font-medium cursor-pointer', | |||||
| 'relative mr-4 pt-1 pb-2 system-sm-medium cursor-pointer', | |||||
| activeTab === tab.key | activeTab === tab.key | ||||
| ? 'text-gray-700 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-primary-600' | |||||
| : 'text-gray-500', | |||||
| ? 'text-text-primary after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-util-colors-blue-brand-blue-brand-600' | |||||
| : 'text-text-tertiary', | |||||
| )} | )} | ||||
| onClick={() => onActiveTabChange(tab.key)} | onClick={() => onActiveTabChange(tab.key)} | ||||
| > | > |
| <Tooltip | <Tooltip | ||||
| key={tool.name} | key={tool.name} | ||||
| position='right' | position='right' | ||||
| popupClassName='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' | |||||
| popupClassName='w-[200px]' | |||||
| popupContent={( | popupContent={( | ||||
| <div> | <div> | ||||
| <BlockIcon | <BlockIcon | ||||
| type={BlockEnum.Tool} | type={BlockEnum.Tool} | ||||
| toolIcon={toolWithProvider.icon} | toolIcon={toolWithProvider.icon} | ||||
| /> | /> | ||||
| <div className='mb-1 text-sm leading-5 text-gray-900'>{tool.label[language]}</div> | |||||
| <div className='text-xs text-gray-700 leading-[18px]'>{tool.description[language]}</div> | |||||
| <div className='mb-1 system-md-medium text-text-primary'>{tool.label[language]}</div> | |||||
| <div className='system-xs-regular text-text-tertiary'>{tool.description[language]}</div> | |||||
| </div> | </div> | ||||
| )} | )} | ||||
| > | > | ||||
| <div | <div | ||||
| className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-gray-50 cursor-pointer' | |||||
| className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer' | |||||
| onClick={() => onSelect(BlockEnum.Tool, { | onClick={() => onSelect(BlockEnum.Tool, { | ||||
| provider_id: toolWithProvider.id, | provider_id: toolWithProvider.id, | ||||
| provider_type: toolWithProvider.type, | provider_type: toolWithProvider.type, | ||||
| type={BlockEnum.Tool} | type={BlockEnum.Tool} | ||||
| toolIcon={toolWithProvider.icon} | toolIcon={toolWithProvider.icon} | ||||
| /> | /> | ||||
| <div className='text-sm text-gray-900 flex-1 min-w-0 truncate'>{tool.label[language]}</div> | |||||
| <div className='text-sm text-text-secondary flex-1 min-w-0 truncate'>{tool.label[language]}</div> | |||||
| </div> | </div> | ||||
| </Tooltip> | </Tooltip> | ||||
| )) | )) | ||||
| <div className='p-1 max-w-[320px] max-h-[464px] overflow-y-auto'> | <div className='p-1 max-w-[320px] max-h-[464px] overflow-y-auto'> | ||||
| { | { | ||||
| !tools.length && !showWorkflowEmpty && ( | !tools.length && !showWorkflowEmpty && ( | ||||
| <div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div> | |||||
| <div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div> | |||||
| ) | ) | ||||
| } | } | ||||
| {!tools.length && showWorkflowEmpty && ( | {!tools.length && showWorkflowEmpty && ( |
| const isSyncingWorkflowDraft = useStore(s => s.isSyncingWorkflowDraft) | const isSyncingWorkflowDraft = useStore(s => s.isSyncingWorkflowDraft) | ||||
| return ( | return ( | ||||
| <div className='flex items-center h-[18px] text-xs text-gray-500'> | |||||
| <div className='flex items-center h-[18px] system-xs-regular text-text-tertiary'> | |||||
| { | { | ||||
| !!draftUpdatedAt && ( | !!draftUpdatedAt && ( | ||||
| <> | <> |
| } from '../hooks' | } from '../hooks' | ||||
| import AppPublisher from '../../app/app-publisher' | import AppPublisher from '../../app/app-publisher' | ||||
| import { ToastContext } from '../../base/toast' | import { ToastContext } from '../../base/toast' | ||||
| import Divider from '../../base/divider' | |||||
| import RunAndHistory from './run-and-history' | import RunAndHistory from './run-and-history' | ||||
| import EditingTitle from './editing-title' | import EditingTitle from './editing-title' | ||||
| import RunningTitle from './running-title' | import RunningTitle from './running-title' | ||||
| return ( | return ( | ||||
| <div | <div | ||||
| className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14' | |||||
| style={{ | |||||
| background: 'linear-gradient(180deg, #F9FAFB 0%, rgba(249, 250, 251, 0.00) 100%)', | |||||
| }} | |||||
| className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14 bg-mask-top2bottom-gray-50-to-transparent' | |||||
| > | > | ||||
| <div> | <div> | ||||
| { | { | ||||
| appSidebarExpand === 'collapse' && ( | appSidebarExpand === 'collapse' && ( | ||||
| <div className='text-xs font-medium text-gray-700'>{appDetail?.name}</div> | |||||
| <div className='system-xs-regular text-text-tertiary'>{appDetail?.name}</div> | |||||
| ) | ) | ||||
| } | } | ||||
| { | { | ||||
| {/* <GlobalVariableButton disabled={nodesReadOnly} /> */} | {/* <GlobalVariableButton disabled={nodesReadOnly} /> */} | ||||
| {isChatMode && <ChatVariableButton disabled={nodesReadOnly} />} | {isChatMode && <ChatVariableButton disabled={nodesReadOnly} />} | ||||
| <EnvButton disabled={nodesReadOnly} /> | <EnvButton disabled={nodesReadOnly} /> | ||||
| <div className='w-[1px] h-3.5 bg-gray-200'></div> | |||||
| <Divider type='vertical' className='h-3.5 mx-auto' /> | |||||
| <RunAndHistory /> | <RunAndHistory /> | ||||
| <Button className='text-components-button-secondary-text' onClick={handleShowFeatures}> | <Button className='text-components-button-secondary-text' onClick={handleShowFeatures}> | ||||
| <RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' /> | <RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' /> | ||||
| } | } | ||||
| { | { | ||||
| viewHistory && ( | viewHistory && ( | ||||
| <div className='flex items-center'> | |||||
| <div className='flex items-center space-x-2'> | |||||
| <ViewHistory withText /> | <ViewHistory withText /> | ||||
| <div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div> | |||||
| <Divider type='vertical' className='h-3.5 mx-auto' /> | |||||
| <Button | <Button | ||||
| variant='primary' | variant='primary' | ||||
| className='mr-2' | |||||
| onClick={handleGoBackToEdit} | onClick={handleGoBackToEdit} | ||||
| > | > | ||||
| <ArrowNarrowLeft className='w-4 h-4 mr-1' /> | <ArrowNarrowLeft className='w-4 h-4 mr-1' /> | ||||
| } | } | ||||
| { | { | ||||
| restoring && ( | restoring && ( | ||||
| <div className='flex items-center'> | |||||
| <div className='flex items-center space-x-2'> | |||||
| <Button className='text-components-button-secondary-text' onClick={handleShowFeatures}> | <Button className='text-components-button-secondary-text' onClick={handleShowFeatures}> | ||||
| <RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' /> | <RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' /> | ||||
| {t('workflow.common.features')} | {t('workflow.common.features')} | ||||
| </Button> | </Button> | ||||
| <div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div> | |||||
| <Divider type='vertical' className='h-3.5 mx-auto' /> | |||||
| <Button | <Button | ||||
| className='mr-2' | |||||
| onClick={handleCancelRestore} | onClick={handleCancelRestore} | ||||
| > | > | ||||
| {t('common.operation.cancel')} | {t('common.operation.cancel')} |
| } from '@remixicon/react' | } from '@remixicon/react' | ||||
| import TipPopup from '../operator/tip-popup' | import TipPopup from '../operator/tip-popup' | ||||
| import { useWorkflowHistoryStore } from '../workflow-history-store' | import { useWorkflowHistoryStore } from '../workflow-history-store' | ||||
| import Divider from '../../base/divider' | |||||
| import { useNodesReadOnly } from '@/app/components/workflow/hooks' | import { useNodesReadOnly } from '@/app/components/workflow/hooks' | ||||
| import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history' | import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history' | ||||
| import classNames from '@/utils/classnames' | |||||
| export type UndoRedoProps = { handleUndo: () => void; handleRedo: () => void } | export type UndoRedoProps = { handleUndo: () => void; handleRedo: () => void } | ||||
| const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => { | const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => { | ||||
| const { nodesReadOnly } = useNodesReadOnly() | const { nodesReadOnly } = useNodesReadOnly() | ||||
| return ( | return ( | ||||
| <div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'> | |||||
| <div className='flex items-center space-x-0.5 p-0.5 backdrop-blur-[5px] rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-lg'> | |||||
| <TipPopup title={t('workflow.common.undo')!} shortcuts={['ctrl', 'z']}> | <TipPopup title={t('workflow.common.undo')!} shortcuts={['ctrl', 'z']}> | ||||
| <div | <div | ||||
| data-tooltip-id='workflow.undo' | data-tooltip-id='workflow.undo' | ||||
| className={` | |||||
| flex items-center px-1.5 w-8 h-8 rounded-md text-[13px] font-medium | |||||
| hover:bg-black/5 hover:text-gray-700 cursor-pointer select-none | |||||
| ${(nodesReadOnly || buttonsDisabled.undo) && 'hover:bg-transparent opacity-50 !cursor-not-allowed'} | |||||
| `} | |||||
| className={ | |||||
| classNames('flex items-center px-1.5 w-8 h-8 rounded-md system-sm-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer select-none', | |||||
| (nodesReadOnly || buttonsDisabled.undo) | |||||
| && 'hover:bg-transparent text-text-disabled hover:text-text-disabled cursor-not-allowed')} | |||||
| onClick={() => !nodesReadOnly && !buttonsDisabled.undo && handleUndo()} | onClick={() => !nodesReadOnly && !buttonsDisabled.undo && handleUndo()} | ||||
| > | > | ||||
| <RiArrowGoBackLine className='h-4 w-4' /> | <RiArrowGoBackLine className='h-4 w-4' /> | ||||
| </div> | </div> | ||||
| </TipPopup> | |||||
| </TipPopup > | |||||
| <TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}> | <TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}> | ||||
| <div | <div | ||||
| data-tooltip-id='workflow.redo' | data-tooltip-id='workflow.redo' | ||||
| className={` | |||||
| flex items-center px-1.5 w-8 h-8 rounded-md text-[13px] font-medium | |||||
| hover:bg-black/5 hover:text-gray-700 cursor-pointer select-none | |||||
| ${(nodesReadOnly || buttonsDisabled.redo) && 'hover:bg-transparent opacity-50 !cursor-not-allowed'} | |||||
| `} | |||||
| className={ | |||||
| classNames('flex items-center px-1.5 w-8 h-8 rounded-md system-sm-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer select-none', | |||||
| (nodesReadOnly || buttonsDisabled.redo) | |||||
| && 'hover:bg-transparent text-text-disabled hover:text-text-disabled cursor-not-allowed', | |||||
| )} | |||||
| onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()} | onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()} | ||||
| > | > | ||||
| <RiArrowGoForwardFill className='h-4 w-4' /> | <RiArrowGoForwardFill className='h-4 w-4' /> | ||||
| </div> | </div> | ||||
| </TipPopup> | </TipPopup> | ||||
| <div className="mx-[3px] w-[1px] h-3.5 bg-gray-200"></div> | |||||
| <Divider type='vertical' className="h-3.5 mx-0.5" /> | |||||
| <ViewWorkflowHistory /> | <ViewWorkflowHistory /> | ||||
| </div> | |||||
| </div > | |||||
| ) | ) | ||||
| } | } | ||||
| } from '../hooks' | } from '../hooks' | ||||
| import TipPopup from '../operator/tip-popup' | import TipPopup from '../operator/tip-popup' | ||||
| import type { WorkflowHistoryState } from '../workflow-history-store' | import type { WorkflowHistoryState } from '../workflow-history-store' | ||||
| import Divider from '../../base/divider' | |||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { | import { | ||||
| PortalToFollowElem, | PortalToFollowElem, | ||||
| PortalToFollowElemTrigger, | PortalToFollowElemTrigger, | ||||
| } from '@/app/components/base/portal-to-follow-elem' | } from '@/app/components/base/portal-to-follow-elem' | ||||
| import { useStore as useAppStore } from '@/app/components/app/store' | import { useStore as useAppStore } from '@/app/components/app/store' | ||||
| import classNames from '@/utils/classnames' | |||||
| type ChangeHistoryEntry = { | type ChangeHistoryEntry = { | ||||
| label: string | label: string | ||||
| setCurrentLogItem: state.setCurrentLogItem, | setCurrentLogItem: state.setCurrentLogItem, | ||||
| setShowMessageLogModal: state.setShowMessageLogModal, | setShowMessageLogModal: state.setShowMessageLogModal, | ||||
| }))) | }))) | ||||
| const reactflowStore = useStoreApi() | |||||
| const reactFlowStore = useStoreApi() | |||||
| const { store, getHistoryLabel } = useWorkflowHistory() | const { store, getHistoryLabel } = useWorkflowHistory() | ||||
| const { pastStates, futureStates, undo, redo, clear } = store.temporal.getState() | const { pastStates, futureStates, undo, redo, clear } = store.temporal.getState() | ||||
| }, [clear]) | }, [clear]) | ||||
| const handleSetState = useCallback(({ index }: ChangeHistoryEntry) => { | const handleSetState = useCallback(({ index }: ChangeHistoryEntry) => { | ||||
| const { setEdges, setNodes } = reactflowStore.getState() | |||||
| const { setEdges, setNodes } = reactFlowStore.getState() | |||||
| const diff = currentHistoryStateIndex + index | const diff = currentHistoryStateIndex + index | ||||
| if (diff === 0) | if (diff === 0) | ||||
| return | return | ||||
| setEdges(edges) | setEdges(edges) | ||||
| setNodes(nodes) | setNodes(nodes) | ||||
| }, [currentHistoryStateIndex, reactflowStore, redo, store, undo]) | |||||
| }, [currentHistoryStateIndex, reactFlowStore, redo, store, undo]) | |||||
| const calculateStepLabel = useCallback((index: number) => { | const calculateStepLabel = useCallback((index: number) => { | ||||
| if (!index) | if (!index) | ||||
| const count = index < 0 ? index * -1 : index | const count = index < 0 ? index * -1 : index | ||||
| return `${index > 0 ? t('workflow.changeHistory.stepForward', { count }) : t('workflow.changeHistory.stepBackward', { count })}` | return `${index > 0 ? t('workflow.changeHistory.stepForward', { count }) : t('workflow.changeHistory.stepBackward', { count })}` | ||||
| } | |||||
| , [t]) | |||||
| }, [t]) | |||||
| const calculateChangeList: ChangeHistoryList = useMemo(() => { | const calculateChangeList: ChangeHistoryList = useMemo(() => { | ||||
| const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => { | const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => { | ||||
| title={t('workflow.changeHistory.title')} | title={t('workflow.changeHistory.title')} | ||||
| > | > | ||||
| <div | <div | ||||
| className={` | |||||
| flex items-center justify-center w-8 h-8 rounded-md hover:bg-black/5 cursor-pointer | |||||
| ${open && 'bg-primary-50'} ${nodesReadOnly && 'bg-primary-50 opacity-50 !cursor-not-allowed'} | |||||
| `} | |||||
| className={ | |||||
| classNames('flex items-center justify-center w-8 h-8 rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer', | |||||
| open && 'bg-state-accent-active text-text-accent', | |||||
| nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled', | |||||
| )} | |||||
| onClick={() => { | onClick={() => { | ||||
| if (nodesReadOnly) | if (nodesReadOnly) | ||||
| return | return | ||||
| setShowMessageLogModal(false) | setShowMessageLogModal(false) | ||||
| }} | }} | ||||
| > | > | ||||
| <RiHistoryLine className={`w-4 h-4 hover:bg-black/5 hover:text-gray-700 ${open ? 'text-primary-600' : 'text-gray-500'}`} /> | |||||
| <RiHistoryLine className='w-4 h-4' /> | |||||
| </div> | </div> | ||||
| </TipPopup> | </TipPopup> | ||||
| </PortalToFollowElemTrigger> | </PortalToFollowElemTrigger> | ||||
| <PortalToFollowElemContent className='z-[12]'> | <PortalToFollowElemContent className='z-[12]'> | ||||
| <div | <div | ||||
| className='flex flex-col ml-2 min-w-[240px] max-w-[360px] bg-white border-[0.5px] border-gray-200 shadow-xl rounded-xl overflow-y-auto' | |||||
| className='flex flex-col ml-2 min-w-[240px] max-w-[360px] bg-components-panel-bg-blur backdrop-blur-[5px] border-[0.5px] border-components-panel-border shadow-xl rounded-xl overflow-y-auto' | |||||
| > | > | ||||
| <div className='sticky top-0 bg-white flex items-center justify-between px-4 pt-3 text-base font-semibold text-gray-900'> | |||||
| <div className='grow'>{t('workflow.changeHistory.title')}</div> | |||||
| <div className='sticky top-0 flex items-center justify-between px-4 pt-3'> | |||||
| <div className='grow text-text-secondary system-mg-regular'>{t('workflow.changeHistory.title')}</div> | |||||
| <div | <div | ||||
| className='shrink-0 flex items-center justify-center w-6 h-6 cursor-pointer' | className='shrink-0 flex items-center justify-center w-6 h-6 cursor-pointer' | ||||
| onClick={() => { | onClick={() => { | ||||
| setOpen(false) | setOpen(false) | ||||
| }} | }} | ||||
| > | > | ||||
| <RiCloseLine className='w-4 h-4 text-gray-500' /> | |||||
| <RiCloseLine className='w-4 h-4 text-text-secondary' /> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| { | { | ||||
| { | { | ||||
| !calculateChangeList.statesCount && ( | !calculateChangeList.statesCount && ( | ||||
| <div className='py-12'> | <div className='py-12'> | ||||
| <RiHistoryLine className='mx-auto mb-2 w-8 h-8 text-gray-300' /> | |||||
| <div className='text-center text-[13px] text-gray-400'> | |||||
| <RiHistoryLine className='mx-auto mb-2 w-8 h-8 text-text-tertiary' /> | |||||
| <div className='text-center text-[13px] text-text-tertiary'> | |||||
| {t('workflow.changeHistory.placeholder')} | {t('workflow.changeHistory.placeholder')} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div | <div | ||||
| key={item?.index} | key={item?.index} | ||||
| className={cn( | className={cn( | ||||
| 'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer', | |||||
| item?.index === currentHistoryStateIndex && 'bg-primary-50', | |||||
| 'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-state-base-hover text-text-secondary cursor-pointer', | |||||
| item?.index === currentHistoryStateIndex && 'bg-state-base-hover', | |||||
| )} | )} | ||||
| onClick={() => { | onClick={() => { | ||||
| handleSetState(item) | handleSetState(item) | ||||
| <div> | <div> | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center text-[13px] font-medium leading-[18px]', | |||||
| item?.index === currentHistoryStateIndex && 'text-primary-600', | |||||
| 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', | |||||
| )} | )} | ||||
| > | > | ||||
| {item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')}) | {item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')}) | ||||
| <div | <div | ||||
| key={item?.index} | key={item?.index} | ||||
| className={cn( | className={cn( | ||||
| 'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer', | |||||
| item?.index === calculateChangeList.statesCount - 1 && 'bg-primary-50', | |||||
| 'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-state-base-hover cursor-pointer', | |||||
| item?.index === calculateChangeList.statesCount - 1 && 'bg-state-base-hover', | |||||
| )} | )} | ||||
| onClick={() => { | onClick={() => { | ||||
| handleSetState(item) | handleSetState(item) | ||||
| <div> | <div> | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center text-[13px] font-medium leading-[18px]', | |||||
| item?.index === calculateChangeList.statesCount - 1 && 'text-primary-600', | |||||
| 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', | |||||
| )} | )} | ||||
| > | > | ||||
| {item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}) | {item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}) | ||||
| } | } | ||||
| { | { | ||||
| !!calculateChangeList.statesCount && ( | !!calculateChangeList.statesCount && ( | ||||
| <> | |||||
| <div className="h-[1px] bg-gray-100" /> | |||||
| <div className='px-0.5'> | |||||
| <Divider className='m-0' /> | |||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex my-0.5 px-2 py-[7px] rounded-lg cursor-pointer', | |||||
| 'hover:bg-red-50 hover:text-red-600', | |||||
| 'flex my-0.5 px-2 py-[7px] rounded-lg text-text-secondary cursor-pointer', | |||||
| 'hover:bg-state-base-hover', | |||||
| )} | )} | ||||
| onClick={() => { | onClick={() => { | ||||
| handleClearHistory() | handleClearHistory() | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </> | |||||
| </div> | |||||
| ) | ) | ||||
| } | } | ||||
| <div className="px-3 w-[240px] py-2 text-xs text-gray-500" > | |||||
| <div className="px-3 w-[240px] py-2 text-xs text-text-tertiary" > | |||||
| <div className="flex items-center mb-1 h-[22px] font-medium uppercase">{t('workflow.changeHistory.hint')}</div> | <div className="flex items-center mb-1 h-[22px] font-medium uppercase">{t('workflow.changeHistory.hint')}</div> | ||||
| <div className="mb-1 text-gray-700 leading-[18px]">{t('workflow.changeHistory.hintText')}</div> | |||||
| <div className="mb-1 text-text-tertiary leading-[18px]">{t('workflow.changeHistory.hintText')}</div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </PortalToFollowElemContent> | </PortalToFollowElemContent> |
| return () => { | return () => { | ||||
| handleSyncWorkflowDraft(true, true) | handleSyncWorkflowDraft(true, true) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const { handleRefreshWorkflowDraft } = useWorkflowUpdate() | const { handleRefreshWorkflowDraft } = useWorkflowUpdate() | ||||
| <div | <div | ||||
| id='workflow-container' | id='workflow-container' | ||||
| className={` | className={` | ||||
| relative w-full min-w-[960px] h-full bg-[#F0F2F7] | |||||
| relative w-full min-w-[960px] h-full | |||||
| ${workflowReadOnly && 'workflow-panel-animation'} | ${workflowReadOnly && 'workflow-panel-animation'} | ||||
| ${nodeAnimation && 'workflow-node-animation'} | ${nodeAnimation && 'workflow-node-animation'} | ||||
| `} | `} | ||||
| <Background | <Background | ||||
| gap={[14, 14]} | gap={[14, 14]} | ||||
| size={2} | size={2} | ||||
| color='#E4E5E7' | |||||
| className="bg-workflow-canvas-workflow-bg" | |||||
| color='var(--color-workflow-canvas-workflow-dot-color)' | |||||
| /> | /> | ||||
| </ReactFlow> | </ReactFlow> | ||||
| </div> | </div> | ||||
| if (!data || isLoading) { | if (!data || isLoading) { | ||||
| return ( | return ( | ||||
| <div className='flex justify-center items-center relative w-full h-full bg-[#F0F2F7]'> | |||||
| <div className='flex justify-center items-center relative w-full h-full'> | |||||
| <Loading /> | <Loading /> | ||||
| </div> | </div> | ||||
| ) | ) |
| title={t('workflow.common.addBlock')} | title={t('workflow.common.addBlock')} | ||||
| > | > | ||||
| <div className={cn( | <div className={cn( | ||||
| 'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer', | |||||
| `${nodesReadOnly && '!cursor-not-allowed opacity-50'}`, | |||||
| open && '!bg-black/5', | |||||
| 'flex items-center justify-center w-8 h-8 rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer', | |||||
| `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, | |||||
| open && 'bg-state-accent-active text-text-accent', | |||||
| )}> | )}> | ||||
| <RiAddCircleFill className='w-4 h-4' /> | <RiAddCircleFill className='w-4 h-4' /> | ||||
| </div> | </div> |
| ControlMode, | ControlMode, | ||||
| } from '../types' | } from '../types' | ||||
| import { useStore } from '../store' | import { useStore } from '../store' | ||||
| import Divider from '../../base/divider' | |||||
| import AddBlock from './add-block' | import AddBlock from './add-block' | ||||
| import TipPopup from './tip-popup' | import TipPopup from './tip-popup' | ||||
| import { useOperator } from './hooks' | import { useOperator } from './hooks' | ||||
| } | } | ||||
| return ( | return ( | ||||
| <div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'> | |||||
| <div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-lg text-text-tertiary'> | |||||
| <AddBlock /> | <AddBlock /> | ||||
| <TipPopup title={t('workflow.nodes.note.addNote')}> | <TipPopup title={t('workflow.nodes.note.addNote')}> | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center justify-center ml-[1px] w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer', | |||||
| `${nodesReadOnly && '!cursor-not-allowed opacity-50'}`, | |||||
| 'flex items-center justify-center ml-[1px] w-8 h-8 rounded-lg hover:bg-state-base-hover hover:text-text-secondary cursor-pointer', | |||||
| `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, | |||||
| )} | )} | ||||
| onClick={addNote} | onClick={addNote} | ||||
| > | > | ||||
| <RiStickyNoteAddLine className='w-4 h-4' /> | <RiStickyNoteAddLine className='w-4 h-4' /> | ||||
| </div> | </div> | ||||
| </TipPopup> | </TipPopup> | ||||
| <div className='mx-[3px] w-[1px] h-3.5 bg-gray-200'></div> | |||||
| <Divider type='vertical' className='h-3.5 mx-0.5' /> | |||||
| <TipPopup title={t('workflow.common.pointerMode')} shortcuts={['v']}> | <TipPopup title={t('workflow.common.pointerMode')} shortcuts={['v']}> | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center justify-center mr-[1px] w-8 h-8 rounded-lg cursor-pointer', | 'flex items-center justify-center mr-[1px] w-8 h-8 rounded-lg cursor-pointer', | ||||
| controlMode === ControlMode.Pointer ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700', | |||||
| `${nodesReadOnly && '!cursor-not-allowed opacity-50'}`, | |||||
| controlMode === ControlMode.Pointer ? 'bg-state-accent-active text-text-accent' : 'hover:bg-state-base-hover hover:text-text-secondary', | |||||
| `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, | |||||
| )} | )} | ||||
| onClick={handleModePointer} | onClick={handleModePointer} | ||||
| > | > | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer', | 'flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer', | ||||
| controlMode === ControlMode.Hand ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700', | |||||
| `${nodesReadOnly && '!cursor-not-allowed opacity-50'}`, | |||||
| controlMode === ControlMode.Hand ? 'bg-state-accent-active text-text-accent' : 'hover:bg-state-base-hover hover:text-text-secondary', | |||||
| `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, | |||||
| )} | )} | ||||
| onClick={handleModeHand} | onClick={handleModeHand} | ||||
| > | > | ||||
| <RiHand className='w-4 h-4' /> | <RiHand className='w-4 h-4' /> | ||||
| </div> | </div> | ||||
| </TipPopup> | </TipPopup> | ||||
| <div className='mx-[3px] w-[1px] h-3.5 bg-gray-200'></div> | |||||
| <Divider type='vertical' className='h-3.5 mx-0.5' /> | |||||
| <TipPopup title={t('workflow.panel.organizeBlocks')} shortcuts={['ctrl', 'o']}> | <TipPopup title={t('workflow.panel.organizeBlocks')} shortcuts={['ctrl', 'o']}> | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer', | |||||
| `${nodesReadOnly && '!cursor-not-allowed opacity-50'}`, | |||||
| 'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-state-base-hover hover:text-text-secondary cursor-pointer', | |||||
| `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, | |||||
| )} | )} | ||||
| onClick={handleLayout} | onClick={handleLayout} | ||||
| > | > |
| width: 102, | width: 102, | ||||
| height: 72, | height: 72, | ||||
| }} | }} | ||||
| className='!absolute !left-4 !bottom-14 z-[9] !m-0 !w-[102px] !h-[72px] !border-[0.5px] !border-black/8 !rounded-lg !shadow-lg' | |||||
| maskColor='var(--color-shadow-shadow-5)' | |||||
| className='!absolute !left-4 !bottom-14 z-[9] !m-0 !w-[102px] !h-[72px] !border-[0.5px] !border-divider-subtle | |||||
| !rounded-lg !shadow-md !shadow-shadow-shadow-5 !bg-workflow-minimap-bg' | |||||
| /> | /> | ||||
| <div className='flex items-center mt-1 gap-2 absolute left-4 bottom-4 z-[9]'> | <div className='flex items-center mt-1 gap-2 absolute left-4 bottom-4 z-[9]'> | ||||
| <ZoomInOut /> | <ZoomInOut /> |
| return ( | return ( | ||||
| <Tooltip | <Tooltip | ||||
| offset={4} | offset={4} | ||||
| popupClassName='!p-0 !bg-gray-25' | |||||
| popupClassName='p-0 bg-transparent' | |||||
| popupContent={ | popupContent={ | ||||
| <div className='flex items-center gap-1 px-2 h-6 text-xs font-medium text-gray-700 rounded-lg border-[0.5px] border-black/5'> | |||||
| {title} | |||||
| <div className='flex items-center gap-1 p-1.5 backdrop-blur-[5px] shadow-lg rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg'> | |||||
| <span className='system-xs-medium text-text-secondary'>{title}</span> | |||||
| { | { | ||||
| shortcuts && <ShortcutsName keys={shortcuts} className='!text-[11px]' /> | |||||
| shortcuts && <ShortcutsName keys={shortcuts} /> | |||||
| } | } | ||||
| </div> | </div> | ||||
| } | } |
| useNodesSyncDraft, | useNodesSyncDraft, | ||||
| useWorkflowReadOnly, | useWorkflowReadOnly, | ||||
| } from '../hooks' | } from '../hooks' | ||||
| import { | |||||
| getKeyboardKeyNameBySystem, | |||||
| } from '../utils' | |||||
| import ShortcutsName from '../shortcuts-name' | import ShortcutsName from '../shortcuts-name' | ||||
| import Divider from '../../base/divider' | |||||
| import TipPopup from './tip-popup' | import TipPopup from './tip-popup' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { | import { | ||||
| > | > | ||||
| <PortalToFollowElemTrigger asChild onClick={handleTrigger}> | <PortalToFollowElemTrigger asChild onClick={handleTrigger}> | ||||
| <div className={` | <div className={` | ||||
| p-0.5 h-9 cursor-pointer text-[13px] text-gray-500 font-medium rounded-lg bg-white shadow-lg border-[0.5px] border-gray-100 | |||||
| p-0.5 h-9 cursor-pointer text-[13px] backdrop-blur-[5px] rounded-lg | |||||
| bg-components-actionbar-bg shadow-lg border-[0.5px] border-components-actionbar-border | |||||
| hover:bg-state-base-hover | |||||
| ${workflowReadOnly && '!cursor-not-allowed opacity-50'} | ${workflowReadOnly && '!cursor-not-allowed opacity-50'} | ||||
| `}> | `}> | ||||
| <div className={cn( | <div className={cn( | ||||
| 'flex items-center justify-between w-[98px] h-8 hover:bg-gray-50 rounded-lg', | |||||
| open && 'bg-gray-50', | |||||
| 'flex items-center justify-between w-[98px] h-8 rounded-lg', | |||||
| )}> | )}> | ||||
| <TipPopup | <TipPopup | ||||
| title={t('workflow.operator.zoomOut')} | title={t('workflow.operator.zoomOut')} | ||||
| shortcuts={['ctrl', '-']} | shortcuts={['ctrl', '-']} | ||||
| > | > | ||||
| <div | <div | ||||
| className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5' | |||||
| className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer' | |||||
| onClick={(e) => { | onClick={(e) => { | ||||
| e.stopPropagation() | e.stopPropagation() | ||||
| zoomOut() | zoomOut() | ||||
| }} | }} | ||||
| > | > | ||||
| <RiZoomOutLine className='w-4 h-4' /> | |||||
| <RiZoomOutLine className='w-4 h-4 text-text-tertiary hover:text-text-secondary' /> | |||||
| </div> | </div> | ||||
| </TipPopup> | </TipPopup> | ||||
| <div className='w-[34px]'>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div> | |||||
| <div className={cn('w-[34px] system-sm-medium text-text-tertiary hover:text-text-secondary')}>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div> | |||||
| <TipPopup | <TipPopup | ||||
| title={t('workflow.operator.zoomIn')} | title={t('workflow.operator.zoomIn')} | ||||
| shortcuts={['ctrl', '+']} | shortcuts={['ctrl', '+']} | ||||
| > | > | ||||
| <div | <div | ||||
| className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5' | |||||
| className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer' | |||||
| onClick={(e) => { | onClick={(e) => { | ||||
| e.stopPropagation() | e.stopPropagation() | ||||
| zoomIn() | zoomIn() | ||||
| }} | }} | ||||
| > | > | ||||
| <RiZoomInLine className='w-4 h-4' /> | |||||
| <RiZoomInLine className='w-4 h-4 text-text-tertiary hover:text-text-secondary' /> | |||||
| </div> | </div> | ||||
| </TipPopup> | </TipPopup> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </PortalToFollowElemTrigger> | </PortalToFollowElemTrigger> | ||||
| <PortalToFollowElemContent className='z-10'> | <PortalToFollowElemContent className='z-10'> | ||||
| <div className='w-[145px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'> | |||||
| <div className='w-[145px] backdrop-blur-[5px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> | |||||
| { | { | ||||
| ZOOM_IN_OUT_OPTIONS.map((options, i) => ( | ZOOM_IN_OUT_OPTIONS.map((options, i) => ( | ||||
| <Fragment key={i}> | <Fragment key={i}> | ||||
| { | { | ||||
| i !== 0 && ( | i !== 0 && ( | ||||
| <div className='h-[1px] bg-gray-100' /> | |||||
| <Divider className='m-0' /> | |||||
| ) | ) | ||||
| } | } | ||||
| <div className='p-1'> | <div className='p-1'> | ||||
| options.map(option => ( | options.map(option => ( | ||||
| <div | <div | ||||
| key={option.key} | key={option.key} | ||||
| className='flex items-center justify-between px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer text-sm text-gray-700' | |||||
| className='flex items-center justify-between space-x-1 py-1.5 pl-3 pr-2 h-8 rounded-lg hover:bg-state-base-hover cursor-pointer system-md-regular text-text-secondary' | |||||
| onClick={() => handleZoom(option.key)} | onClick={() => handleZoom(option.key)} | ||||
| > | > | ||||
| {option.text} | |||||
| { | |||||
| option.key === ZoomType.zoomToFit && ( | |||||
| <ShortcutsName keys={[`${getKeyboardKeyNameBySystem('ctrl')}`, '1']} /> | |||||
| ) | |||||
| } | |||||
| { | |||||
| option.key === ZoomType.zoomTo50 && ( | |||||
| <ShortcutsName keys={['shift', '5']} /> | |||||
| ) | |||||
| } | |||||
| { | |||||
| option.key === ZoomType.zoomTo100 && ( | |||||
| <ShortcutsName keys={['shift', '1']} /> | |||||
| ) | |||||
| } | |||||
| <span>{option.text}</span> | |||||
| <div className='flex items-center space-x-0.5'> | |||||
| { | |||||
| option.key === ZoomType.zoomToFit && ( | |||||
| <ShortcutsName keys={['ctrl', '1']} /> | |||||
| ) | |||||
| } | |||||
| { | |||||
| option.key === ZoomType.zoomTo50 && ( | |||||
| <ShortcutsName keys={['shift', '5']} /> | |||||
| ) | |||||
| } | |||||
| { | |||||
| option.key === ZoomType.zoomTo100 && ( | |||||
| <ShortcutsName keys={['shift', '1']} /> | |||||
| ) | |||||
| } | |||||
| </div> | |||||
| </div> | </div> | ||||
| )) | )) | ||||
| } | } |
| } from 'react' | } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { useClickAway } from 'ahooks' | import { useClickAway } from 'ahooks' | ||||
| import Divider from '../base/divider' | |||||
| import ShortcutsName from './shortcuts-name' | import ShortcutsName from './shortcuts-name' | ||||
| import { useStore } from './store' | import { useStore } from './store' | ||||
| import { | import { | ||||
| const renderTrigger = () => { | const renderTrigger = () => { | ||||
| return ( | return ( | ||||
| <div | <div | ||||
| className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50' | |||||
| className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover' | |||||
| > | > | ||||
| {t('workflow.common.addBlock')} | {t('workflow.common.addBlock')} | ||||
| </div> | </div> | ||||
| return ( | return ( | ||||
| <div | <div | ||||
| className='absolute w-[200px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xl z-[9]' | |||||
| className='absolute w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg z-[9]' | |||||
| style={{ | style={{ | ||||
| left: panelMenu.left, | left: panelMenu.left, | ||||
| top: panelMenu.top, | top: panelMenu.top, | ||||
| }} | }} | ||||
| /> | /> | ||||
| <div | <div | ||||
| className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50' | |||||
| className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover' | |||||
| onClick={(e) => { | onClick={(e) => { | ||||
| e.stopPropagation() | e.stopPropagation() | ||||
| handleAddNote() | handleAddNote() | ||||
| {t('workflow.nodes.note.addNote')} | {t('workflow.nodes.note.addNote')} | ||||
| </div> | </div> | ||||
| <div | <div | ||||
| className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50' | |||||
| className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover' | |||||
| onClick={() => { | onClick={() => { | ||||
| handleStartWorkflowRun() | handleStartWorkflowRun() | ||||
| handlePaneContextmenuCancel() | handlePaneContextmenuCancel() | ||||
| <ShortcutsName keys={['alt', 'r']} /> | <ShortcutsName keys={['alt', 'r']} /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className='h-[1px] bg-gray-100'></div> | |||||
| <Divider className='m-0' /> | |||||
| <div className='p-1'> | <div className='p-1'> | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer', | |||||
| !clipboardElements.length ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50', | |||||
| 'flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer', | |||||
| !clipboardElements.length ? 'opacity-50 cursor-not-allowed' : 'hover:bg-state-base-hover', | |||||
| )} | )} | ||||
| onClick={() => { | onClick={() => { | ||||
| if (clipboardElements.length) { | if (clipboardElements.length) { | ||||
| <ShortcutsName keys={['ctrl', 'v']} /> | <ShortcutsName keys={['ctrl', 'v']} /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className='h-[1px] bg-gray-100'></div> | |||||
| <Divider className='m-0' /> | |||||
| <div className='p-1'> | <div className='p-1'> | ||||
| <div | <div | ||||
| className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50' | |||||
| className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover' | |||||
| onClick={() => exportCheck()} | onClick={() => exportCheck()} | ||||
| > | > | ||||
| {t('app.export')} | {t('app.export')} | ||||
| </div> | </div> | ||||
| <div | <div | ||||
| className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50' | |||||
| className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover' | |||||
| onClick={() => setShowImportDSLModal(true)} | onClick={() => setShowImportDSLModal(true)} | ||||
| > | > | ||||
| {t('workflow.common.importDSL')} | {t('workflow.common.importDSL')} |
| }: ShortcutsNameProps) => { | }: ShortcutsNameProps) => { | ||||
| return ( | return ( | ||||
| <div className={cn( | <div className={cn( | ||||
| 'flex items-center gap-0.5 h-4 text-xs text-gray-400', | |||||
| 'flex items-center gap-0.5', | |||||
| className, | className, | ||||
| )}> | )}> | ||||
| { | { | ||||
| keys.map(key => ( | keys.map(key => ( | ||||
| <div | <div | ||||
| key={key} | key={key} | ||||
| className='capitalize' | |||||
| className='w-4 h-4 flex items-center justify-center bg-components-kbd-bg-gray rounded-[4px] system-kbd capitalize' | |||||
| > | > | ||||
| {getKeyboardKeyNameBySystem(key)} | {getKeyboardKeyNameBySystem(key)} | ||||
| </div> | </div> |
| #workflow-container .react-flow__node-custom-note { | #workflow-container .react-flow__node-custom-note { | ||||
| z-index: -1000 !important; | z-index: -1000 !important; | ||||
| } | |||||
| } | |||||
| #workflow-container .react-flow {} |
| const specialKeysNameMap: Record<string, string | undefined> = { | const specialKeysNameMap: Record<string, string | undefined> = { | ||||
| ctrl: '⌘', | ctrl: '⌘', | ||||
| alt: '⌥', | alt: '⌥', | ||||
| shift: '⇧', | |||||
| } | } | ||||
| export const getKeyboardKeyNameBySystem = (key: string) => { | export const getKeyboardKeyNameBySystem = (key: string) => { |
| <meta name="apple-mobile-web-app-status-bar-style" content="default" /> | <meta name="apple-mobile-web-app-status-bar-style" content="default" /> | ||||
| </head> | </head> | ||||
| <body | <body | ||||
| className="h-full select-auto" | |||||
| className="h-full select-auto color-scheme" | |||||
| data-api-prefix={process.env.NEXT_PUBLIC_API_PREFIX} | data-api-prefix={process.env.NEXT_PUBLIC_API_PREFIX} | ||||
| data-pubic-api-prefix={process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX} | data-pubic-api-prefix={process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX} | ||||
| data-public-edition={process.env.NEXT_PUBLIC_EDITION} | data-public-edition={process.env.NEXT_PUBLIC_EDITION} |
| @import "../../themes/manual-light.css"; | @import "../../themes/manual-light.css"; | ||||
| @import "../../themes/manual-dark.css"; | @import "../../themes/manual-dark.css"; | ||||
| html { | |||||
| color-scheme: light; | |||||
| } | |||||
| html[data-theme='dark'] { | |||||
| color-scheme: dark; | |||||
| } | |||||
| html[data-changing-theme] * { | html[data-changing-theme] * { | ||||
| transition: none !important; | transition: none !important; | ||||
| } | } |
| 'chatbot-bg': 'var(--color-chatbot-bg)', | 'chatbot-bg': 'var(--color-chatbot-bg)', | ||||
| 'chat-bubble-bg': 'var(--color-chat-bubble-bg)', | 'chat-bubble-bg': 'var(--color-chat-bubble-bg)', | ||||
| 'workflow-process-bg': 'var(--color-workflow-process-bg)', | 'workflow-process-bg': 'var(--color-workflow-process-bg)', | ||||
| 'mask-top2bottom-gray-50-to-transparent': 'var(--mask-top2bottom-gray-50-to-transparent)', | |||||
| }, | }, | ||||
| }, | }, | ||||
| }, | }, |
| --color-chatbot-bg: linear-gradient(180deg, rgba(34, 34, 37, 0.90) 0%, rgba(29, 29, 32, 0.90) 90.48%); | --color-chatbot-bg: linear-gradient(180deg, rgba(34, 34, 37, 0.90) 0%, rgba(29, 29, 32, 0.90) 90.48%); | ||||
| --color-chat-bubble-bg: linear-gradient(180deg, rgba(200, 206, 218, 0.08) 0%, rgba(200, 206, 218, 0.02) 100%); | --color-chat-bubble-bg: linear-gradient(180deg, rgba(200, 206, 218, 0.08) 0%, rgba(200, 206, 218, 0.02) 100%); | ||||
| --color-workflow-process-bg: linear-gradient(90deg, rgba(24, 24, 27, 0.25) 0%, rgba(24, 24, 27, 0.04) 100%); | --color-workflow-process-bg: linear-gradient(90deg, rgba(24, 24, 27, 0.25) 0%, rgba(24, 24, 27, 0.04) 100%); | ||||
| --mask-top2bottom-gray-50-to-transparent:linear-gradient(180deg, rgba(24, 24, 27, 0.08) 0%, rgba(0, 0, 0, 0.00) 100%); | |||||
| } | } |
| --color-chatbot-bg: linear-gradient(180deg, rgba(249, 250, 251, 0.90) 0%, rgba(242, 244, 247, 0.90) 90.48%); | --color-chatbot-bg: linear-gradient(180deg, rgba(249, 250, 251, 0.90) 0%, rgba(242, 244, 247, 0.90) 90.48%); | ||||
| --color-chat-bubble-bg: linear-gradient(180deg, #FFF 0%, rgba(255, 255, 255, 0.60) 100%); | --color-chat-bubble-bg: linear-gradient(180deg, #FFF 0%, rgba(255, 255, 255, 0.60) 100%); | ||||
| --color-workflow-process-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%); | --color-workflow-process-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%); | ||||
| --mask-top2bottom-gray-50-to-transparent: linear-gradient(180deg, rgba(200, 206, 218, 0.20) 0%, rgba(255, 255, 255, 0.00) 100%); | |||||
| } | } |