Merge branch 'main' into feat/hitl-frontend

This commit is contained in:
twwu
2025-12-25 13:43:27 +08:00
3693 changed files with 107816 additions and 91946 deletions

View File

@ -1,17 +1,19 @@
import React, { useCallback } from 'react'
import type { MockedFunction } from 'vitest'
import type { EntryNodeStatus } from '../store/trigger-status'
import type { BlockEnum } from '../types'
import { act, render } from '@testing-library/react'
import * as React from 'react'
import { useCallback } from 'react'
import { useTriggerStatusStore } from '../store/trigger-status'
import { isTriggerNode } from '../types'
import type { BlockEnum } from '../types'
import type { EntryNodeStatus } from '../store/trigger-status'
// Mock the isTriggerNode function while preserving BlockEnum
jest.mock('../types', () => ({
...jest.requireActual('../types'),
isTriggerNode: jest.fn(),
vi.mock('../types', async importOriginal => ({
...await importOriginal<typeof import('../types')>(),
isTriggerNode: vi.fn(),
}))
const mockIsTriggerNode = isTriggerNode as jest.MockedFunction<typeof isTriggerNode>
const mockIsTriggerNode = isTriggerNode as MockedFunction<typeof isTriggerNode>
// Test component that mimics BaseNode's usage pattern
const TestTriggerNode: React.FC<{
@ -24,7 +26,9 @@ const TestTriggerNode: React.FC<{
return (
<div data-testid={`node-${nodeId}`} data-status={triggerStatus}>
Status: {triggerStatus}
Status:
{' '}
{triggerStatus}
</div>
)
}
@ -79,7 +83,7 @@ describe('Trigger Status Synchronization Integration', () => {
})
// Reset mocks
jest.clearAllMocks()
vi.clearAllMocks()
})
describe('Real-time Status Synchronization', () => {
@ -273,14 +277,14 @@ describe('Trigger Status Synchronization Integration', () => {
nodeType: string
}> = ({ nodeId, nodeType }) => {
const triggerStatusSelector = useCallback((state: any) =>
mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses[nodeId] || 'disabled') : 'enabled',
[nodeId, nodeType],
)
mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses[nodeId] || 'disabled') : 'enabled', [nodeId, nodeType])
const triggerStatus = useTriggerStatusStore(triggerStatusSelector)
return (
<div data-testid={`optimized-node-${nodeId}`} data-status={triggerStatus}>
Status: {triggerStatus}
Status:
{' '}
{triggerStatus}
</div>
)
}
@ -314,9 +318,10 @@ describe('Trigger Status Synchronization Integration', () => {
mockIsTriggerNode.mockImplementation(nodeType => nodeType === 'trigger-webhook')
const TestComponent: React.FC<{ nodeType: string }> = ({ nodeType }) => {
const triggerStatusSelector = useCallback((state: any) =>
mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses['test-node'] || 'disabled') : 'enabled',
['test-node', nodeType], // Dependencies should match implementation
const triggerStatusSelector = useCallback(
(state: any) =>
mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses['test-node'] || 'disabled') : 'enabled',
['test-node', nodeType], // Dependencies should match implementation
)
const status = useTriggerStatusStore(triggerStatusSelector)
return <div data-testid="test-component" data-status={status} />

View File

@ -1,6 +1,6 @@
import type { FC } from 'react'
import { memo } from 'react'
import { BlockEnum } from './types'
import AppIcon from '@/app/components/base/app-icon'
import {
Agent,
Answer,
@ -27,14 +27,14 @@ import {
VariableX,
WebhookLine,
} from '@/app/components/base/icons/src/vender/workflow'
import AppIcon from '@/app/components/base/app-icon'
import { cn } from '@/utils/classnames'
import { BlockEnum } from './types'
type BlockIconProps = {
type: BlockEnum
size?: string
className?: string
toolIcon?: string | { content: string; background: string }
toolIcon?: string | { content: string, background: string }
}
const ICON_CONTAINER_CLASSNAME_SIZE_MAP: Record<string, string> = {
xs: 'w-4 h-4 rounded-[5px] shadow-xs',
@ -128,15 +128,14 @@ const BlockIcon: FC<BlockIconProps> = ({
showDefaultIcon && ICON_CONTAINER_BG_COLOR_MAP[type],
toolIcon && '!shadow-none',
className,
)}
)
}
>
{
showDefaultIcon && (
getIcon(type,
(type === BlockEnum.TriggerSchedule || type === BlockEnum.TriggerWebhook)
? (size === 'xs' ? 'w-4 h-4' : 'w-4.5 h-4.5')
: (size === 'xs' ? 'w-3 h-3' : 'w-3.5 h-3.5'),
)
getIcon(type, (type === BlockEnum.TriggerSchedule || type === BlockEnum.TriggerWebhook)
? (size === 'xs' ? 'w-4 h-4' : 'w-4.5 h-4.5')
: (size === 'xs' ? 'w-3 h-3' : 'w-3.5 h-3.5'))
)
}
{
@ -145,21 +144,22 @@ const BlockIcon: FC<BlockIconProps> = ({
{
typeof toolIcon === 'string'
? (
<div
className='h-full w-full shrink-0 rounded-md bg-cover bg-center'
style={{
backgroundImage: `url(${toolIcon})`,
}}
></div>
)
<div
className="h-full w-full shrink-0 rounded-md bg-cover bg-center"
style={{
backgroundImage: `url(${toolIcon})`,
}}
>
</div>
)
: (
<AppIcon
className='!h-full !w-full shrink-0'
size='tiny'
icon={toolIcon?.content}
background={toolIcon?.background}
/>
)
<AppIcon
className="!h-full !w-full shrink-0"
size="tiny"
icon={toolIcon?.content}
background={toolIcon?.background}
/>
)
}
</>
)

View File

@ -1,4 +1,12 @@
'use client'
import type {
RefObject,
} from 'react'
import type { BlockEnum, OnSelectBlock } from '../types'
import type { ListRef } from './market-place-plugin/list'
import type { TriggerDefaultValue, TriggerWithProvider } from './types'
import { RiArrowRightUpLine } from '@remixicon/react'
import Link from 'next/link'
import {
useCallback,
useEffect,
@ -6,30 +14,23 @@ import {
useRef,
useState,
} from 'react'
import type {
RefObject,
} from 'react'
import { useTranslation } from 'react-i18next'
import type { BlockEnum, OnSelectBlock } from '../types'
import type { TriggerDefaultValue, TriggerWithProvider } from './types'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useFeaturedTriggersRecommendations } from '@/service/use-plugins'
import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers'
import { cn } from '@/utils/classnames'
import { getMarketplaceUrl } from '@/utils/var'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import { PluginCategoryEnum } from '../../plugins/types'
import { BlockEnum as BlockEnumValue } from '../types'
import { ENTRY_NODE_TYPES } from './constants'
import FeaturedTriggers from './featured-triggers'
import PluginList from './market-place-plugin/list'
import StartBlocks from './start-blocks'
import TriggerPluginList from './trigger-plugin/list'
import { ENTRY_NODE_TYPES } from './constants'
import { cn } from '@/utils/classnames'
import Link from 'next/link'
import { RiArrowRightUpLine } from '@remixicon/react'
import { getMarketplaceUrl } from '@/utils/var'
import Button from '@/app/components/base/button'
import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
import { BlockEnum as BlockEnumValue } from '../types'
import FeaturedTriggers from './featured-triggers'
import Divider from '@/app/components/base/divider'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers'
import { useFeaturedTriggersRecommendations } from '@/service/use-plugins'
import { PluginCategoryEnum } from '../../plugins/types'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import PluginList, { type ListRef } from './market-place-plugin/list'
const marketplaceFooterClassName = 'system-sm-medium z-10 flex h-8 flex-none cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg'
@ -114,7 +115,8 @@ const AllStartBlocks = ({
}, [enableTriggerPlugin, hasPluginContent])
useEffect(() => {
if (!enableTriggerPlugin || !enable_marketplace) return
if (!enableTriggerPlugin || !enable_marketplace)
return
if (hasFilter) {
fetchPlugins({
query: searchText,
@ -126,10 +128,10 @@ const AllStartBlocks = ({
return (
<div className={cn('min-w-[400px] max-w-[500px]', className)}>
<div className='flex max-h-[640px] flex-col'>
<div className="flex max-h-[640px] flex-col">
<div
ref={wrapElemRef}
className='flex-1 overflow-y-auto'
className="flex-1 overflow-y-auto"
onScroll={() => pluginRef.current?.handleScroll()}
>
<div className={cn(shouldShowEmptyState && 'hidden')}>
@ -144,14 +146,14 @@ const AllStartBlocks = ({
invalidateTriggers()
}}
/>
<div className='px-3'>
<Divider className='!h-px' />
<div className="px-3">
<Divider className="!h-px" />
</div>
</>
)}
{shouldShowTriggerListTitle && (
<div className='px-3 pb-1 pt-2'>
<span className='system-xs-medium text-text-primary'>{t('workflow.tabs.allTriggers')}</span>
<div className="px-3 pb-1 pt-2">
<span className="system-xs-medium text-text-primary">{t('workflow.tabs.allTriggers')}</span>
</div>
)}
<StartBlocks
@ -184,19 +186,19 @@ const AllStartBlocks = ({
</div>
{shouldShowEmptyState && (
<div className='flex h-full flex-col items-center justify-center gap-3 py-12 text-center'>
<SearchMenu className='h-8 w-8 text-text-quaternary' />
<div className='text-sm font-medium text-text-secondary'>
<div className="flex h-full flex-col items-center justify-center gap-3 py-12 text-center">
<SearchMenu className="h-8 w-8 text-text-quaternary" />
<div className="text-sm font-medium text-text-secondary">
{t('workflow.tabs.noPluginsFound')}
</div>
<Link
href='https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml'
target='_blank'
href="https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml"
target="_blank"
>
<Button
size='small'
variant='secondary-accent'
className='h-6 cursor-pointer px-3 text-xs'
size="small"
variant="secondary-accent"
className="h-6 cursor-pointer px-3 text-xs"
>
{t('workflow.tabs.requestToCommunity')}
</Button>
@ -210,10 +212,10 @@ const AllStartBlocks = ({
<Link
className={marketplaceFooterClassName}
href={getMarketplaceUrl('', { category: PluginCategoryEnum.trigger })}
target='_blank'
target="_blank"
>
<span>{t('plugin.findMoreInMarketplace')}</span>
<RiArrowRightUpLine className='ml-0.5 h-3 w-3' />
<RiArrowRightUpLine className="ml-0.5 h-3 w-3" />
</Link>
)}
</div>

View File

@ -3,34 +3,34 @@ import type {
RefObject,
SetStateAction,
} from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { Plugin } from '../../plugins/types'
import type {
BlockEnum,
ToolWithProvider,
} from '../types'
import type { ToolDefaultValue, ToolValue } from './types'
import { ToolTypeEnum } from './types'
import Tools from './tools'
import { useToolTabs } from './hooks'
import ViewTypeSelect, { ViewType } from './view-type-select'
import { cn } from '@/utils/classnames'
import Button from '@/app/components/base/button'
import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import PluginList, { type ListProps } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import type { Plugin } from '../../plugins/types'
import { PluginCategoryEnum } from '../../plugins/types'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import { useGlobalPublicStore } from '@/context/global-public-context'
import RAGToolRecommendations from './rag-tool-recommendations'
import FeaturedTools from './featured-tools'
import Link from 'next/link'
import Divider from '@/app/components/base/divider'
import { RiArrowRightUpLine } from '@remixicon/react'
import { getMarketplaceUrl } from '@/utils/var'
import { useGetLanguage } from '@/context/i18n'
import type { ListProps, ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import type { OnSelectBlock } from '@/app/components/workflow/types'
import { RiArrowRightUpLine } from '@remixicon/react'
import Link from 'next/link'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useGetLanguage } from '@/context/i18n'
import { cn } from '@/utils/classnames'
import { getMarketplaceUrl } from '@/utils/var'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import { PluginCategoryEnum } from '../../plugins/types'
import FeaturedTools from './featured-tools'
import { useToolTabs } from './hooks'
import RAGToolRecommendations from './rag-tool-recommendations'
import Tools from './tools'
import { ToolTypeEnum } from './types'
import ViewTypeSelect, { ViewType } from './view-type-select'
const marketplaceFooterClassName = 'system-sm-medium z-10 flex h-8 flex-none cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg'
@ -172,7 +172,8 @@ const AllTools = ({
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
useEffect(() => {
if (!enable_marketplace) return
if (!enable_marketplace)
return
if (hasFilter) {
fetchPlugins({
query: searchText,
@ -205,8 +206,8 @@ const AllTools = ({
return (
<div className={cn('max-w-[500px]', className)}>
<div className='flex items-center justify-between border-b border-divider-subtle px-3'>
<div className='flex h-8 items-center space-x-1'>
<div className="flex items-center justify-between border-b border-divider-subtle px-3">
<div className="flex h-8 items-center space-x-1">
{
tabs.map(tab => (
<div
@ -227,10 +228,10 @@ const AllTools = ({
<ViewTypeSelect viewType={activeView} onChange={setActiveView} />
)}
</div>
<div className='flex max-h-[464px] flex-col'>
<div className="flex max-h-[464px] flex-col">
<div
ref={wrapElemRef}
className='flex-1 overflow-y-auto'
className="flex-1 overflow-y-auto"
onScroll={() => pluginRef.current?.handleScroll()}
>
<div className={cn(shouldShowEmptyState && 'hidden')}>
@ -254,15 +255,15 @@ const AllTools = ({
await onFeaturedInstallSuccess?.()
}}
/>
<div className='px-3'>
<Divider className='!h-px' />
<div className="px-3">
<Divider className="!h-px" />
</div>
</>
)}
{hasToolsListContent && (
<>
<div className='px-3 pb-1 pt-2'>
<span className='system-xs-medium text-text-primary'>{t('tools.allTools')}</span>
<div className="px-3 pb-1 pt-2">
<span className="system-xs-medium text-text-primary">{t('tools.allTools')}</span>
</div>
<Tools
className={toolContentClassName}
@ -293,19 +294,19 @@ const AllTools = ({
</div>
{shouldShowEmptyState && (
<div className='flex h-full flex-col items-center justify-center gap-3 py-12 text-center'>
<SearchMenu className='h-8 w-8 text-text-quaternary' />
<div className='text-sm font-medium text-text-secondary'>
<div className="flex h-full flex-col items-center justify-center gap-3 py-12 text-center">
<SearchMenu className="h-8 w-8 text-text-quaternary" />
<div className="text-sm font-medium text-text-secondary">
{t('workflow.tabs.noPluginsFound')}
</div>
<Link
href='https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml'
target='_blank'
href="https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml"
target="_blank"
>
<Button
size='small'
variant='secondary-accent'
className='h-6 cursor-pointer px-3 text-xs'
size="small"
variant="secondary-accent"
className="h-6 cursor-pointer px-3 text-xs"
>
{t('workflow.tabs.requestToCommunity')}
</Button>
@ -317,10 +318,10 @@ const AllTools = ({
<Link
className={marketplaceFooterClassName}
href={getMarketplaceUrl('', { category: PluginCategoryEnum.tool })}
target='_blank'
target="_blank"
>
<span>{t('plugin.findMoreInMarketplace')}</span>
<RiArrowRightUpLine className='ml-0.5 h-3 w-3' />
<RiArrowRightUpLine className="ml-0.5 h-3 w-3" />
</Link>
)}
</div>

View File

@ -1,18 +1,18 @@
import type { NodeDefault } from '../types'
import { groupBy } from 'es-toolkit/compat'
import {
memo,
useCallback,
useMemo,
} from 'react'
import { useStoreApi } from 'reactflow'
import { useTranslation } from 'react-i18next'
import { groupBy } from 'lodash-es'
import { useStoreApi } from 'reactflow'
import Badge from '@/app/components/base/badge'
import Tooltip from '@/app/components/base/tooltip'
import BlockIcon from '../block-icon'
import { BlockEnum } from '../types'
import type { NodeDefault } from '../types'
import { BLOCK_CLASSIFICATIONS } from './constants'
import { useBlocks } from './hooks'
import Tooltip from '@/app/components/base/tooltip'
import Badge from '@/app/components/base/badge'
type BlocksProps = {
searchText: string
@ -50,9 +50,10 @@ const Blocks = ({
const list = (grouped[classification] || []).filter((block) => {
// Filter out trigger types from Blocks tab
if (block.metaData.type === BlockEnum.TriggerWebhook
|| block.metaData.type === BlockEnum.TriggerSchedule
|| block.metaData.type === BlockEnum.TriggerPlugin)
|| block.metaData.type === BlockEnum.TriggerSchedule
|| block.metaData.type === BlockEnum.TriggerPlugin) {
return false
}
return block.metaData.title.toLowerCase().includes(searchText.toLowerCase()) && availableBlocksTypes.includes(block.metaData.type)
})
@ -79,12 +80,12 @@ const Blocks = ({
return (
<div
key={classification}
className='mb-1 last-of-type:mb-0'
className="mb-1 last-of-type:mb-0"
>
{
classification !== '-' && !!filteredList.length && (
<div className='flex h-[22px] items-start px-3 text-xs font-medium text-text-tertiary'>
{t(`workflow.tabs.${classification}`)}
<div className="flex h-[22px] items-start px-3 text-xs font-medium text-text-tertiary">
{t(`workflow.tabs.${classification}` as any) as string}
</div>
)
}
@ -92,36 +93,36 @@ const Blocks = ({
filteredList.map(block => (
<Tooltip
key={block.metaData.type}
position='right'
popupClassName='w-[200px] rounded-xl'
position="right"
popupClassName="w-[200px] rounded-xl"
needsDelay={false}
popupContent={(
<div>
<BlockIcon
size='md'
className='mb-2'
size="md"
className="mb-2"
type={block.metaData.type}
/>
<div className='system-md-medium mb-1 text-text-primary'>{block.metaData.title}</div>
<div className='system-xs-regular text-text-tertiary'>{block.metaData.description}</div>
<div className="system-md-medium mb-1 text-text-primary">{block.metaData.title}</div>
<div className="system-xs-regular text-text-tertiary">{block.metaData.description}</div>
</div>
)}
>
<div
key={block.metaData.type}
className='flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover'
className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover"
onClick={() => onSelect(block.metaData.type)}
>
<BlockIcon
className='mr-2 shrink-0'
className="mr-2 shrink-0"
type={block.metaData.type}
/>
<div className='grow text-sm text-text-secondary'>{block.metaData.title}</div>
<div className="grow text-sm text-text-secondary">{block.metaData.title}</div>
{
block.metaData.type === BlockEnum.LoopEnd && (
<Badge
text={t('workflow.nodes.loop.loopNode')}
className='ml-2 shrink-0'
className="ml-2 shrink-0"
/>
)
}
@ -134,10 +135,10 @@ const Blocks = ({
}, [groups, onSelect, t, store])
return (
<div className='max-h-[480px] max-w-[500px] overflow-y-auto p-1'>
<div className="max-h-[480px] max-w-[500px] overflow-y-auto p-1">
{
isEmpty && (
<div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div>
<div className="flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary">{t('workflow.tabs.noResult')}</div>
)
}
{

View File

@ -1,24 +1,25 @@
import type {
OnSelectBlock,
ToolWithProvider,
} from '../types'
import type { DataSourceDefaultValue, ToolDefaultValue } from './types'
import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import {
useCallback,
useEffect,
useMemo,
useRef,
} from 'react'
import { BlockEnum } from '../types'
import type {
OnSelectBlock,
ToolWithProvider,
} from '../types'
import type { DataSourceDefaultValue, ToolDefaultValue } from './types'
import Tools from './tools'
import { ViewType } from './view-type-select'
import { cn } from '@/utils/classnames'
import PluginList, { type ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { DEFAULT_FILE_EXTENSIONS_IN_LOCAL_FILE_DATA_SOURCE } from './constants'
import { useGetLanguage } from '@/context/i18n'
import { cn } from '@/utils/classnames'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import { PluginCategoryEnum } from '../../plugins/types'
import { useGetLanguage } from '@/context/i18n'
import { BlockEnum } from '../types'
import { DEFAULT_FILE_EXTENSIONS_IN_LOCAL_FILE_DATA_SOURCE } from './constants'
import Tools from './tools'
import { ViewType } from './view-type-select'
type AllToolsProps = {
className?: string
@ -83,7 +84,8 @@ const DataSources = ({
} = useMarketplacePlugins()
useEffect(() => {
if (!enable_marketplace) return
if (!enable_marketplace)
return
if (searchText) {
fetchPlugins({
query: searchText,
@ -96,7 +98,7 @@ const DataSources = ({
<div className={cn('w-[400px] min-w-0 max-w-full', className)}>
<div
ref={wrapElemRef}
className='max-h-[464px] overflow-y-auto overflow-x-hidden'
className="max-h-[464px] overflow-y-auto overflow-x-hidden"
onScroll={pluginRef.current?.handleScroll}
>
<Tools

View File

@ -1,23 +1,25 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BlockEnum, type ToolWithProvider } from '../types'
import type { ToolWithProvider } from '../types'
import type { ToolDefaultValue, ToolValue } from './types'
import type { Plugin } from '@/app/components/plugins/types'
import { useGetLanguage } from '@/context/i18n'
import BlockIcon from '../block-icon'
import Tooltip from '@/app/components/base/tooltip'
import type { Locale } from '@/i18n-config'
import { RiMoreLine } from '@remixicon/react'
import Loading from '@/app/components/base/loading'
import Link from 'next/link'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { useGetLanguage } from '@/context/i18n'
import { formatNumber } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import BlockIcon from '../block-icon'
import { BlockEnum } from '../types'
import Tools from './tools'
import { ToolTypeEnum } from './types'
import { ViewType } from './view-type-select'
import Tools from './tools'
import { formatNumber } from '@/utils/format'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
const MAX_RECOMMENDED_COUNT = 15
const INITIAL_VISIBLE_COUNT = 5
@ -125,27 +127,27 @@ const FeaturedTools = ({
const showEmptyState = !isLoading && totalVisible === 0
return (
<div className='px-3 pb-3 pt-2'>
<div className="px-3 pb-3 pt-2">
<button
type='button'
className='flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary'
type="button"
className="flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary"
onClick={() => setIsCollapsed(prev => !prev)}
>
<span className='system-xs-medium text-text-primary'>{t('workflow.tabs.featuredTools')}</span>
<span className="system-xs-medium text-text-primary">{t('workflow.tabs.featuredTools')}</span>
<ArrowDownRoundFill className={`ml-0.5 h-4 w-4 text-text-tertiary transition-transform ${isCollapsed ? '-rotate-90' : 'rotate-0'}`} />
</button>
{!isCollapsed && (
<>
{isLoading && (
<div className='py-3'>
<Loading type='app' />
<div className="py-3">
<Loading type="app" />
</div>
)}
{showEmptyState && (
<p className='system-xs-regular py-2 text-text-tertiary'>
<Link className='text-text-accent' href={getMarketplaceUrl('', { category: 'tool' })} target='_blank' rel='noopener noreferrer'>
<p className="system-xs-regular py-2 text-text-tertiary">
<Link className="text-text-accent" href={getMarketplaceUrl('', { category: 'tool' })} target="_blank" rel="noopener noreferrer">
{t('workflow.tabs.noFeaturedPlugins')}
</Link>
</p>
@ -155,7 +157,7 @@ const FeaturedTools = ({
<>
{visibleInstalledProviders.length > 0 && (
<Tools
className='p-0'
className="p-0"
tools={visibleInstalledProviders}
onSelect={onSelect}
canNotSelectMultiple
@ -168,7 +170,7 @@ const FeaturedTools = ({
)}
{visibleUninstalledPlugins.length > 0 && (
<div className='mt-1 flex flex-col gap-1'>
<div className="mt-1 flex flex-col gap-1">
{visibleUninstalledPlugins.map(plugin => (
<FeaturedToolUninstalledItem
key={plugin.plugin_id}
@ -177,7 +179,7 @@ const FeaturedTools = ({
onInstallSuccess={async () => {
await onInstallSuccess?.()
}}
t={t}
t={t as any}
/>
))}
</div>
@ -187,7 +189,7 @@ const FeaturedTools = ({
{!isLoading && totalVisible > 0 && canToggleVisibility && (
<div
className='group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary'
className="group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary"
onClick={() => {
setVisibleCount((count) => {
if (count >= maxAvailable)
@ -197,15 +199,17 @@ const FeaturedTools = ({
})
}}
>
<div className='flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary'>
<RiMoreLine className='size-4 group-hover:hidden' />
{isExpanded ? (
<ArrowUpDoubleLine className='hidden size-4 group-hover:block' />
) : (
<ArrowDownDoubleLine className='hidden size-4 group-hover:block' />
)}
<div className="flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary">
<RiMoreLine className="size-4 group-hover:hidden" />
{isExpanded
? (
<ArrowUpDoubleLine className="hidden size-4 group-hover:block" />
)
: (
<ArrowDownDoubleLine className="hidden size-4 group-hover:block" />
)}
</div>
<div className='system-xs-regular'>
<div className="system-xs-regular">
{t(isExpanded ? 'workflow.tabs.showLessFeatured' : 'workflow.tabs.showMoreFeatured')}
</div>
</div>
@ -218,7 +222,7 @@ const FeaturedTools = ({
type FeaturedToolUninstalledItemProps = {
plugin: Plugin
language: string
language: Locale
onInstallSuccess?: () => Promise<void> | void
t: (key: string, options?: Record<string, any>) => string
}
@ -255,28 +259,28 @@ function FeaturedToolUninstalledItem({
return (
<>
<Tooltip
position='right'
position="right"
needsDelay={false}
popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg'
popupClassName="!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg"
popupContent={(
<div>
<BlockIcon size='md' className='mb-2' type={BlockEnum.Tool} toolIcon={plugin.icon} />
<div className='mb-1 text-sm leading-5 text-text-primary'>{label}</div>
<div className='text-xs leading-[18px] text-text-secondary'>{description}</div>
<BlockIcon size="md" className="mb-2" type={BlockEnum.Tool} toolIcon={plugin.icon} />
<div className="mb-1 text-sm leading-5 text-text-primary">{label}</div>
<div className="text-xs leading-[18px] text-text-secondary">{description}</div>
</div>
)}
disabled={!description || isActionHovered || actionOpen || isInstallModalOpen}
>
<div
className='group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover'
className="group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover"
>
<div className='flex h-full min-w-0 items-center'>
<div className="flex h-full min-w-0 items-center">
<BlockIcon type={BlockEnum.Tool} toolIcon={plugin.icon} />
<div className='ml-2 min-w-0'>
<div className='system-sm-medium truncate text-text-secondary'>{label}</div>
<div className="ml-2 min-w-0">
<div className="system-sm-medium truncate text-text-secondary">{label}</div>
</div>
</div>
<div className='ml-auto flex h-full items-center gap-1 pl-1'>
<div className="ml-auto flex h-full items-center gap-1 pl-1">
<span className={`system-xs-regular text-text-tertiary ${actionOpen ? 'hidden' : 'group-hover:hidden'}`}>{installCountLabel}</span>
<div
className={`system-xs-medium flex h-full items-center gap-1 text-components-button-secondary-accent-text [&_.action-btn]:h-6 [&_.action-btn]:min-h-0 [&_.action-btn]:w-6 [&_.action-btn]:rounded-lg [&_.action-btn]:p-0 ${actionOpen ? 'flex' : 'hidden group-hover:flex'}`}
@ -287,8 +291,8 @@ function FeaturedToolUninstalledItem({
}}
>
<button
type='button'
className='cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover'
type="button"
className="cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover"
onClick={() => {
setActionOpen(false)
setIsInstallModalOpen(true)

View File

@ -1,21 +1,22 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BlockEnum } from '../types'
import type { TriggerDefaultValue, TriggerWithProvider } from './types'
import type { Plugin } from '@/app/components/plugins/types'
import { useGetLanguage } from '@/context/i18n'
import BlockIcon from '../block-icon'
import Tooltip from '@/app/components/base/tooltip'
import type { Locale } from '@/i18n-config'
import { RiMoreLine } from '@remixicon/react'
import Loading from '@/app/components/base/loading'
import Link from 'next/link'
import { getMarketplaceUrl } from '@/utils/var'
import TriggerPluginItem from './trigger-plugin/item'
import { formatNumber } from '@/utils/format'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { useGetLanguage } from '@/context/i18n'
import { formatNumber } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import BlockIcon from '../block-icon'
import { BlockEnum } from '../types'
import TriggerPluginItem from './trigger-plugin/item'
const MAX_RECOMMENDED_COUNT = 15
const INITIAL_VISIBLE_COUNT = 5
@ -119,27 +120,27 @@ const FeaturedTriggers = ({
const showEmptyState = !isLoading && totalVisible === 0
return (
<div className='px-3 pb-3 pt-2'>
<div className="px-3 pb-3 pt-2">
<button
type='button'
className='flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary'
type="button"
className="flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary"
onClick={() => setIsCollapsed(prev => !prev)}
>
<span className='system-xs-medium text-text-primary'>{t('workflow.tabs.featuredTools')}</span>
<span className="system-xs-medium text-text-primary">{t('workflow.tabs.featuredTools')}</span>
<ArrowDownRoundFill className={`ml-0.5 h-4 w-4 text-text-tertiary transition-transform ${isCollapsed ? '-rotate-90' : 'rotate-0'}`} />
</button>
{!isCollapsed && (
<>
{isLoading && (
<div className='py-3'>
<Loading type='app' />
<div className="py-3">
<Loading type="app" />
</div>
)}
{showEmptyState && (
<p className='system-xs-regular py-2 text-text-tertiary'>
<Link className='text-text-accent' href={getMarketplaceUrl('', { category: 'trigger' })} target='_blank' rel='noopener noreferrer'>
<p className="system-xs-regular py-2 text-text-tertiary">
<Link className="text-text-accent" href={getMarketplaceUrl('', { category: 'trigger' })} target="_blank" rel="noopener noreferrer">
{t('workflow.tabs.noFeaturedTriggers')}
</Link>
</p>
@ -148,7 +149,7 @@ const FeaturedTriggers = ({
{!showEmptyState && !isLoading && (
<>
{visibleInstalledProviders.length > 0 && (
<div className='mt-1'>
<div className="mt-1">
{visibleInstalledProviders.map(provider => (
<TriggerPluginItem
key={provider.id}
@ -161,7 +162,7 @@ const FeaturedTriggers = ({
)}
{visibleUninstalledPlugins.length > 0 && (
<div className='mt-1 flex flex-col gap-1'>
<div className="mt-1 flex flex-col gap-1">
{visibleUninstalledPlugins.map(plugin => (
<FeaturedTriggerUninstalledItem
key={plugin.plugin_id}
@ -170,7 +171,7 @@ const FeaturedTriggers = ({
onInstallSuccess={async () => {
await onInstallSuccess?.()
}}
t={t}
t={t as any}
/>
))}
</div>
@ -180,7 +181,7 @@ const FeaturedTriggers = ({
{!isLoading && totalVisible > 0 && canToggleVisibility && (
<div
className='group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary'
className="group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary"
onClick={() => {
setVisibleCount((count) => {
if (count >= maxAvailable)
@ -190,15 +191,17 @@ const FeaturedTriggers = ({
})
}}
>
<div className='flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary'>
<RiMoreLine className='size-4 group-hover:hidden' />
{isExpanded ? (
<ArrowUpDoubleLine className='hidden size-4 group-hover:block' />
) : (
<ArrowDownDoubleLine className='hidden size-4 group-hover:block' />
)}
<div className="flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary">
<RiMoreLine className="size-4 group-hover:hidden" />
{isExpanded
? (
<ArrowUpDoubleLine className="hidden size-4 group-hover:block" />
)
: (
<ArrowDownDoubleLine className="hidden size-4 group-hover:block" />
)}
</div>
<div className='system-xs-regular'>
<div className="system-xs-regular">
{t(isExpanded ? 'workflow.tabs.showLessFeatured' : 'workflow.tabs.showMoreFeatured')}
</div>
</div>
@ -211,7 +214,7 @@ const FeaturedTriggers = ({
type FeaturedTriggerUninstalledItemProps = {
plugin: Plugin
language: string
language: Locale
onInstallSuccess?: () => Promise<void> | void
t: (key: string, options?: Record<string, any>) => string
}
@ -248,28 +251,28 @@ function FeaturedTriggerUninstalledItem({
return (
<>
<Tooltip
position='right'
position="right"
needsDelay={false}
popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg'
popupClassName="!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg"
popupContent={(
<div>
<BlockIcon size='md' className='mb-2' type={BlockEnum.TriggerPlugin} toolIcon={plugin.icon} />
<div className='mb-1 text-sm leading-5 text-text-primary'>{label}</div>
<div className='text-xs leading-[18px] text-text-secondary'>{description}</div>
<BlockIcon size="md" className="mb-2" type={BlockEnum.TriggerPlugin} toolIcon={plugin.icon} />
<div className="mb-1 text-sm leading-5 text-text-primary">{label}</div>
<div className="text-xs leading-[18px] text-text-secondary">{description}</div>
</div>
)}
disabled={!description || isActionHovered || actionOpen || isInstallModalOpen}
>
<div
className='group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover'
className="group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover"
>
<div className='flex h-full min-w-0 items-center'>
<div className="flex h-full min-w-0 items-center">
<BlockIcon type={BlockEnum.TriggerPlugin} toolIcon={plugin.icon} />
<div className='ml-2 min-w-0'>
<div className='system-sm-medium truncate text-text-secondary'>{label}</div>
<div className="ml-2 min-w-0">
<div className="system-sm-medium truncate text-text-secondary">{label}</div>
</div>
</div>
<div className='ml-auto flex h-full items-center gap-1 pl-1'>
<div className="ml-auto flex h-full items-center gap-1 pl-1">
<span className={`system-xs-regular text-text-tertiary ${actionOpen ? 'hidden' : 'group-hover:hidden'}`}>{installCountLabel}</span>
<div
className={`system-xs-medium flex h-full items-center gap-1 text-components-button-secondary-accent-text [&_.action-btn]:h-6 [&_.action-btn]:min-h-0 [&_.action-btn]:w-6 [&_.action-btn]:rounded-lg [&_.action-btn]:p-0 ${actionOpen ? 'flex' : 'hidden group-hover:flex'}`}
@ -280,8 +283,8 @@ function FeaturedTriggerUninstalledItem({
}}
>
<button
type='button'
className='cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover'
type="button"
className="cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover"
onClick={() => {
setActionOpen(false)
setIsInstallModalOpen(true)

View File

@ -17,7 +17,7 @@ export const useBlocks = () => {
return BLOCKS.map((block) => {
return {
...block,
title: t(`workflow.blocks.${block.type}`),
title: t(`workflow.blocks.${block.type}` as any) as string,
}
})
}
@ -28,7 +28,7 @@ export const useStartBlocks = () => {
return START_BLOCKS.map((block) => {
return {
...block,
title: t(`workflow.blocks.${block.type}`),
title: t(`workflow.blocks.${block.type}` as any) as string,
}
})
}
@ -66,8 +66,7 @@ export const useTabs = ({
key: TabsEnum.Tools,
name: t('workflow.tabs.tools'),
show: !noTools,
},
{
}, {
key: TabsEnum.Start,
name: t('workflow.tabs.start'),
show: shouldShowStartTab,

View File

@ -1,8 +1,8 @@
import { pinyin } from 'pinyin-pro'
import type { FC, RefObject } from 'react'
import type { ToolWithProvider } from '../types'
import { CollectionType } from '../../tools/types'
import { pinyin } from 'pinyin-pro'
import { cn } from '@/utils/classnames'
import { CollectionType } from '../../tools/types'
export const CUSTOM_GROUP_NAME = '@@@custom@@@'
export const WORKFLOW_GROUP_NAME = '@@@workflow@@@'

View File

@ -1,11 +1,11 @@
import type { NodeSelectorProps } from './main'
import {
useMemo,
} from 'react'
import type { NodeSelectorProps } from './main'
import NodeSelector from './main'
import { useHooksStore } from '@/app/components/workflow/hooks-store/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { useStore } from '../store'
import NodeSelector from './main'
const NodeSelectorWrapper = (props: NodeSelectorProps) => {
const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData)

View File

@ -1,7 +1,17 @@
import type {
OffsetOptions,
Placement,
} from '@floating-ui/react'
import type {
FC,
MouseEventHandler,
} from 'react'
import type {
CommonNodeType,
NodeDefault,
OnSelectBlock,
ToolWithProvider,
} from '../types'
import {
memo,
useCallback,
@ -9,31 +19,21 @@ import {
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import type {
OffsetOptions,
Placement,
} from '@floating-ui/react'
import type {
CommonNodeType,
NodeDefault,
OnSelectBlock,
ToolWithProvider,
} from '../types'
import { BlockEnum, isTriggerNode } from '../types'
import Tabs from './tabs'
import { TabsEnum } from './types'
import { useTabs } from './hooks'
import {
Plus02,
} from '@/app/components/base/icons/src/vender/line/general'
import Input from '@/app/components/base/input'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Input from '@/app/components/base/input'
import {
Plus02,
} from '@/app/components/base/icons/src/vender/line/general'
import SearchBox from '@/app/components/plugins/marketplace/search-box'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { BlockEnum, isTriggerNode } from '../types'
import { useTabs } from './hooks'
import Tabs from './tabs'
import { TabsEnum } from './types'
export type NodeSelectorProps = {
open?: boolean
@ -190,20 +190,20 @@ const NodeSelector: FC<NodeSelectorProps> = ({
trigger
? trigger(open)
: (
<div
className={`
<div
className={`
z-10 flex h-4
w-4 cursor-pointer items-center justify-center rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover
${triggerClassName?.(open)}
`}
style={triggerStyle}
>
<Plus02 className='h-2.5 w-2.5' />
</div>
)
style={triggerStyle}
>
<Plus02 className="h-2.5 w-2.5" />
</div>
)
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<PortalToFollowElemContent className="z-[1000]">
<div className={`rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg ${popupClassName}`}>
<Tabs
tabs={tabs}
@ -211,8 +211,8 @@ const NodeSelector: FC<NodeSelectorProps> = ({
blocks={blocks}
allowStartNodeSelection={canSelectUserInput}
onActiveTabChange={handleActiveTabChange}
filterElem={
<div className='relative m-2' onClick={e => e.stopPropagation()}>
filterElem={(
<div className="relative m-2" onClick={e => e.stopPropagation()}>
{activeTab === TabsEnum.Start && (
<SearchBox
autoFocus
@ -221,7 +221,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
tags={tags}
onTagsChange={setTags}
placeholder={searchPlaceholder}
inputClassName='grow'
inputClassName="grow"
/>
)}
{activeTab === TabsEnum.Blocks && (
@ -254,11 +254,11 @@ const NodeSelector: FC<NodeSelectorProps> = ({
tags={tags}
onTagsChange={setTags}
placeholder={t('plugin.searchTools')!}
inputClassName='grow'
inputClassName="grow"
/>
)}
</div>
}
)}
onSelect={handleSelect}
searchText={searchText}
tags={tags}

View File

@ -1,9 +1,11 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTheme } from 'next-themes'
import { useTranslation } from 'react-i18next'
import { RiMoreFill } from '@remixicon/react'
import { useQueryClient } from '@tanstack/react-query'
import { useTheme } from 'next-themes'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
// import Button from '@/app/components/base/button'
import {
@ -11,11 +13,10 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { cn } from '@/utils/classnames'
import { useDownloadPlugin } from '@/service/use-plugins'
import { cn } from '@/utils/classnames'
import { downloadFile } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import { useQueryClient } from '@tanstack/react-query'
type Props = {
open: boolean
@ -53,7 +54,8 @@ const OperationDropdown: FC<Props> = ({
}), [author, name, version])
const { data: blob, isLoading } = useDownloadPlugin(downloadInfo, needDownload)
const handleDownload = useCallback(() => {
if (isLoading) return
if (isLoading)
return
queryClient.removeQueries({
queryKey: ['plugins', 'downloadPlugin', downloadInfo],
exact: true,
@ -76,7 +78,7 @@ const OperationDropdown: FC<Props> = ({
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
placement="bottom-end"
offset={{
mainAxis: 0,
crossAxis: 0,
@ -84,13 +86,13 @@ const OperationDropdown: FC<Props> = ({
>
<PortalToFollowElemTrigger onClick={handleTrigger}>
<ActionButton className={cn(open && 'bg-state-base-hover')}>
<RiMoreFill className='h-4 w-4 text-components-button-secondary-accent-text' />
<RiMoreFill className="h-4 w-4 text-components-button-secondary-accent-text" />
</ActionButton>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[9999]'>
<div className='min-w-[176px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<div onClick={handleDownload} className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.download')}</div>
<a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
<PortalToFollowElemContent className="z-[9999]">
<div className="min-w-[176px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg">
<div onClick={handleDownload} className="system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover">{t('common.operation.download')}</div>
<a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target="_blank" className="system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover">{t('common.operation.viewDetails')}</a>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>

View File

@ -1,16 +1,16 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import Action from './action'
import type { Plugin } from '@/app/components/plugins/types.ts'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import I18n from '@/context/i18n'
import { cn } from '@/utils/classnames'
import { formatNumber } from '@/utils/format'
import { useBoolean } from 'ahooks'
import Action from './action'
enum ActionType {
install = 'install',
@ -36,16 +36,16 @@ const Item: FC<Props> = ({
}] = useBoolean(false)
return (
<div className='group/plugin flex rounded-lg py-1 pl-3 pr-1 hover:bg-state-base-hover'>
<div className="group/plugin flex rounded-lg py-1 pl-3 pr-1 hover:bg-state-base-hover">
<div
className='relative h-6 w-6 shrink-0 rounded-md border-[0.5px] border-components-panel-border-subtle bg-contain bg-center bg-no-repeat'
className="relative h-6 w-6 shrink-0 rounded-md border-[0.5px] border-components-panel-border-subtle bg-contain bg-center bg-no-repeat"
style={{ backgroundImage: `url(${payload.icon})` }}
/>
<div className='ml-2 flex w-0 grow'>
<div className='w-0 grow'>
<div className='system-sm-medium h-4 truncate leading-4 text-text-primary '>{getLocalizedText(payload.label)}</div>
<div className='system-xs-regular h-5 truncate leading-5 text-text-tertiary'>{getLocalizedText(payload.brief)}</div>
<div className='system-xs-regular flex space-x-1 text-text-tertiary'>
<div className="ml-2 flex w-0 grow">
<div className="w-0 grow">
<div className="system-sm-medium h-4 truncate leading-4 text-text-primary ">{getLocalizedText(payload.label)}</div>
<div className="system-xs-regular h-5 truncate leading-5 text-text-tertiary">{getLocalizedText(payload.brief)}</div>
<div className="system-xs-regular flex space-x-1 text-text-tertiary">
<div>{payload.org}</div>
<div>·</div>
<div>{t('plugin.install', { num: formatNumber(payload.install_count || 0) })}</div>
@ -54,7 +54,7 @@ const Item: FC<Props> = ({
{/* Action */}
<div className={cn(!open ? 'hidden' : 'flex', 'system-xs-medium h-4 items-center space-x-1 text-components-button-secondary-accent-text group-hover/plugin:flex')}>
<div
className='cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover'
className="cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover"
onClick={showInstallModal}
>
{t('plugin.installAction')}

View File

@ -1,15 +1,15 @@
'use client'
import { useEffect, useImperativeHandle, useMemo, useRef } from 'react'
import type { RefObject } from 'react'
import type { Plugin, PluginCategoryEnum } from '@/app/components/plugins/types'
import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react'
import { noop } from 'es-toolkit/compat'
import Link from 'next/link'
import { useEffect, useImperativeHandle, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
import { getMarketplaceUrl } from '@/utils/var'
import useStickyScroll, { ScrollPosition } from '../use-sticky-scroll'
import Item from './item'
import type { Plugin, PluginCategoryEnum } from '@/app/components/plugins/types'
import { cn } from '@/utils/classnames'
import Link from 'next/link'
import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react'
import { noop } from 'lodash-es'
import { getMarketplaceUrl } from '@/utils/var'
export type ListProps = {
wrapElemRef: React.RefObject<HTMLElement | null>
@ -79,12 +79,12 @@ const List = ({
return (
<Link
className='system-sm-medium sticky bottom-0 z-10 flex h-8 cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg'
className="system-sm-medium sticky bottom-0 z-10 flex h-8 cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg"
href={getMarketplaceUrl('', { category })}
target='_blank'
target="_blank"
>
<span>{t('plugin.findMoreInMarketplace')}</span>
<RiArrowRightUpLine className='ml-0.5 h-3 w-3' />
<RiArrowRightUpLine className="ml-0.5 h-3 w-3" />
</Link>
)
}
@ -101,12 +101,12 @@ const List = ({
<span>{t('plugin.fromMarketplace')}</span>
<Link
href={urlWithSearchText}
target='_blank'
className='flex items-center text-text-accent-light-mode-only'
target="_blank"
className="flex items-center text-text-accent-light-mode-only"
onClick={e => e.stopPropagation()}
>
<span>{t('plugin.searchInMarketplace')}</span>
<RiArrowRightUpLine className='ml-0.5 h-3 w-3' />
<RiArrowRightUpLine className="ml-0.5 h-3 w-3" />
</Link>
</div>
)}
@ -119,14 +119,14 @@ const List = ({
/>
))}
{hasRes && (
<div className='mb-3 mt-2 flex items-center justify-center space-x-2'>
<div className="mb-3 mt-2 flex items-center justify-center space-x-2">
<div className="h-[2px] w-[90px] bg-gradient-to-l from-[rgba(16,24,40,0.08)] to-[rgba(255,255,255,0.01)]"></div>
<Link
href={urlWithSearchText}
target='_blank'
className='system-sm-medium flex h-4 shrink-0 items-center text-text-accent-light-mode-only'
target="_blank"
className="system-sm-medium flex h-4 shrink-0 items-center text-text-accent-light-mode-only"
>
<RiSearchLine className='mr-0.5 h-3 w-3' />
<RiSearchLine className="mr-0.5 h-3 w-3" />
<span>{t('plugin.searchInMarketplace')}</span>
</Link>
<div className="h-[2px] w-[90px] bg-gradient-to-l from-[rgba(255,255,255,0.01)] to-[rgba(16,24,40,0.08)]"></div>

View File

@ -1,17 +1,18 @@
'use client'
import type { Dispatch, SetStateAction } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import type { OnSelectBlock } from '@/app/components/workflow/types'
import type { ViewType } from '@/app/components/workflow/block-selector/view-type-select'
import type { OnSelectBlock } from '@/app/components/workflow/types'
import { RiMoreLine } from '@remixicon/react'
import Loading from '@/app/components/base/loading'
import Link from 'next/link'
import { getMarketplaceUrl } from '@/utils/var'
import { useRAGRecommendedPlugins } from '@/service/use-tools'
import List from './list'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/arrows'
import Loading from '@/app/components/base/loading'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
import { useRAGRecommendedPlugins } from '@/service/use-tools'
import { getMarketplaceUrl } from '@/utils/var'
import List from './list'
type RAGToolRecommendationsProps = {
viewType: ViewType
@ -75,33 +76,33 @@ const RAGToolRecommendations = ({
}, [onTagsChange])
return (
<div className='flex flex-col p-1'>
<div className="flex flex-col p-1">
<button
type='button'
className='flex w-full items-center rounded-md px-3 pb-0.5 pt-1 text-left text-text-tertiary'
type="button"
className="flex w-full items-center rounded-md px-3 pb-0.5 pt-1 text-left text-text-tertiary"
onClick={() => setIsCollapsed(prev => !prev)}
>
<span className='system-xs-medium text-text-tertiary'>{t('pipeline.ragToolSuggestions.title')}</span>
<span className="system-xs-medium text-text-tertiary">{t('pipeline.ragToolSuggestions.title')}</span>
<ArrowDownRoundFill className={`ml-1 h-4 w-4 text-text-tertiary transition-transform ${isCollapsed ? '-rotate-90' : 'rotate-0'}`} />
</button>
{!isCollapsed && (
<>
{/* For first time loading, show loading */}
{isLoadingRAGRecommendedPlugins && (
<div className='py-2'>
<Loading type='app' />
<div className="py-2">
<Loading type="app" />
</div>
)}
{!isFetchingRAGRecommendedPlugins && recommendedPlugins.length === 0 && unInstalledPlugins.length === 0 && (
<p className='system-xs-regular px-3 py-1 text-text-tertiary'>
<p className="system-xs-regular px-3 py-1 text-text-tertiary">
<Trans
i18nKey='pipeline.ragToolSuggestions.noRecommendationPlugins'
i18nKey="pipeline.ragToolSuggestions.noRecommendationPlugins"
components={{
CustomLink: (
<Link
className='text-text-accent'
target='_blank'
rel='noopener noreferrer'
className="text-text-accent"
target="_blank"
rel="noopener noreferrer"
href={getMarketplaceUrl('', { tags: 'rag' })}
/>
),
@ -118,13 +119,13 @@ const RAGToolRecommendations = ({
viewType={viewType}
/>
<div
className='flex cursor-pointer items-center gap-x-2 py-1 pl-3 pr-2'
className="flex cursor-pointer items-center gap-x-2 py-1 pl-3 pr-2"
onClick={loadMore}
>
<div className='px-1'>
<RiMoreLine className='size-4 text-text-tertiary' />
<div className="px-1">
<RiMoreLine className="size-4 text-text-tertiary" />
</div>
<div className='system-xs-regular text-text-tertiary'>
<div className="system-xs-regular text-text-tertiary">
{t('common.operation.more')}
</div>
</div>

View File

@ -1,15 +1,15 @@
import { useCallback, useMemo, useRef } from 'react'
import type { BlockEnum, ToolWithProvider } from '../../types'
import type { ToolDefaultValue } from '../types'
import { ViewType } from '../view-type-select'
import { useGetLanguage } from '@/context/i18n'
import { groupItems } from '../index-bar'
import { cn } from '@/utils/classnames'
import ToolListTreeView from '../tool/tool-list-tree-view/list'
import ToolListFlatView from '../tool/tool-list-flat-view/list'
import UninstalledItem from './uninstalled-item'
import type { Plugin } from '@/app/components/plugins/types'
import type { OnSelectBlock } from '@/app/components/workflow/types'
import { useCallback, useMemo, useRef } from 'react'
import { useGetLanguage } from '@/context/i18n'
import { cn } from '@/utils/classnames'
import { groupItems } from '../index-bar'
import ToolListFlatView from '../tool/tool-list-flat-view/list'
import ToolListTreeView from '../tool/tool-list-tree-view/list'
import { ViewType } from '../view-type-select'
import UninstalledItem from './uninstalled-item'
type ListProps = {
onSelect: OnSelectBlock
@ -67,25 +67,27 @@ const List = ({
return (
<div className={cn('max-w-[100%] p-1', className)}>
{!!tools.length && (
isFlatView ? (
<ToolListFlatView
toolRefs={toolRefs}
letters={letters}
payload={listViewToolData}
isShowLetterIndex={false}
hasSearchText={false}
onSelect={handleSelect}
canNotSelectMultiple
indexBar={null}
/>
) : (
<ToolListTreeView
payload={treeViewToolsData}
hasSearchText={false}
onSelect={handleSelect}
canNotSelectMultiple
/>
)
isFlatView
? (
<ToolListFlatView
toolRefs={toolRefs}
letters={letters}
payload={listViewToolData}
isShowLetterIndex={false}
hasSearchText={false}
onSelect={handleSelect}
canNotSelectMultiple
indexBar={null}
/>
)
: (
<ToolListTreeView
payload={treeViewToolsData}
hasSearchText={false}
onSelect={handleSelect}
canNotSelectMultiple
/>
)
)}
{
unInstalledPlugins.map((item) => {

View File

@ -1,13 +1,13 @@
'use client'
import React from 'react'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import type { Plugin } from '@/app/components/plugins/types'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import I18n from '@/context/i18n'
import { useBoolean } from 'ahooks'
import { BlockEnum } from '../../types'
import BlockIcon from '../../block-icon'
import { BlockEnum } from '../../types'
type UninstalledItemProps = {
payload: Plugin
@ -27,23 +27,23 @@ const UninstalledItem = ({
}] = useBoolean(false)
return (
<div className='flex h-8 items-center rounded-lg pl-3 pr-2 hover:bg-state-base-hover'>
<div className="flex h-8 items-center rounded-lg pl-3 pr-2 hover:bg-state-base-hover">
<BlockIcon
className='shrink-0'
className="shrink-0"
type={BlockEnum.Tool}
toolIcon={payload.icon}
/>
<div className='ml-2 flex w-0 grow items-center'>
<div className='flex w-0 grow items-center gap-x-2'>
<span className='system-sm-regular truncate text-text-primary'>
<div className="ml-2 flex w-0 grow items-center">
<div className="flex w-0 grow items-center gap-x-2">
<span className="system-sm-regular truncate text-text-primary">
{getLocalizedText(payload.label)}
</span>
<span className='system-xs-regular text-text-quaternary'>
<span className="system-xs-regular text-text-quaternary">
{payload.org}
</span>
</div>
<div
className='system-xs-medium cursor-pointer pl-1.5 text-components-button-secondary-accent-text'
className="system-xs-medium cursor-pointer pl-1.5 text-components-button-secondary-accent-text"
onClick={showInstallModal}
>
{t('plugin.installAction')}

View File

@ -1,19 +1,19 @@
import type { BlockEnum, CommonNodeType } from '../types'
import type { TriggerDefaultValue } from './types'
import {
memo,
useCallback,
useEffect,
useMemo,
} from 'react'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { useAvailableNodesMetaData } from '../../workflow-app/hooks'
import BlockIcon from '../block-icon'
import type { BlockEnum, CommonNodeType } from '../types'
import { BlockEnum as BlockEnumValues } from '../types'
// import { useNodeMetaData } from '../hooks'
import { START_BLOCKS } from './constants'
import type { TriggerDefaultValue } from './types'
import Tooltip from '@/app/components/base/tooltip'
import { useAvailableNodesMetaData } from '../../workflow-app/hooks'
type StartBlocksProps = {
searchText: string
@ -43,7 +43,7 @@ const StartBlocks = ({
if (blockType === BlockEnumValues.TriggerWebhook)
return t('workflow.customWebhook')
return t(`workflow.blocks.${blockType}`)
return t(`workflow.blocks.${blockType}` as any) as string
}
return START_BLOCKS.filter((block) => {
@ -67,48 +67,49 @@ const StartBlocks = ({
onContentStateChange?.(!isEmpty)
}, [isEmpty, onContentStateChange])
const renderBlock = useCallback((block: { type: BlockEnum; title: string; description?: string }) => (
const renderBlock = useCallback((block: { type: BlockEnum, title: string, description?: string }) => (
<Tooltip
key={block.type}
position='right'
popupClassName='w-[224px] rounded-xl'
position="right"
popupClassName="w-[224px] rounded-xl"
needsDelay={false}
popupContent={(
<div>
<BlockIcon
size='md'
className='mb-2'
size="md"
className="mb-2"
type={block.type}
/>
<div className='system-md-medium mb-1 text-text-primary'>
<div className="system-md-medium mb-1 text-text-primary">
{block.type === BlockEnumValues.TriggerWebhook
? t('workflow.customWebhook')
: t(`workflow.blocks.${block.type}`)
}
: t(`workflow.blocks.${block.type}` as any) as string}
</div>
<div className='system-xs-regular text-text-secondary'>
{t(`workflow.blocksAbout.${block.type}`)}
<div className="system-xs-regular text-text-secondary">
{t(`workflow.blocksAbout.${block.type}` as any) as string}
</div>
{(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && (
<div className='system-xs-regular mb-1 mt-1 text-text-tertiary'>
{t('tools.author')} {t('workflow.difyTeam')}
<div className="system-xs-regular mb-1 mt-1 text-text-tertiary">
{t('tools.author')}
{' '}
{t('workflow.difyTeam')}
</div>
)}
</div>
)}
>
<div
className='flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover'
className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover"
onClick={() => onSelect(block.type)}
>
<BlockIcon
className='mr-2 shrink-0'
className="mr-2 shrink-0"
type={block.type}
/>
<div className='flex w-0 grow items-center justify-between text-sm text-text-secondary'>
<span className='truncate'>{t(`workflow.blocks.${block.type}`)}</span>
<div className="flex w-0 grow items-center justify-between text-sm text-text-secondary">
<span className="truncate">{t(`workflow.blocks.${block.type}` as any) as string}</span>
{block.type === BlockEnumValues.Start && (
<span className='system-xs-regular ml-2 shrink-0 text-text-quaternary'>{t('workflow.blocks.originalStartNode')}</span>
<span className="system-xs-regular ml-2 shrink-0 text-text-quaternary">{t('workflow.blocks.originalStartNode')}</span>
)}
</div>
</div>
@ -119,14 +120,14 @@ const StartBlocks = ({
return null
return (
<div className='p-1'>
<div className='mb-1'>
<div className="p-1">
<div className="mb-1">
{filteredBlocks.map((block, index) => (
<div key={block.type}>
{renderBlock(block)}
{block.type === BlockEnumValues.Start && index < filteredBlocks.length - 1 && (
<div className='my-1 px-3'>
<div className='border-t border-divider-subtle' />
<div className="my-1 px-3">
<div className="border-t border-divider-subtle" />
</div>
)}
</div>

View File

@ -1,24 +1,24 @@
import type { Dispatch, FC, SetStateAction } from 'react'
import { memo, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools'
import type {
BlockEnum,
NodeDefault,
OnSelectBlock,
ToolWithProvider,
} from '../types'
import { TabsEnum } from './types'
import Blocks from './blocks'
import { memo, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useFeaturedToolsRecommendations } from '@/service/use-plugins'
import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools'
import { cn } from '@/utils/classnames'
import { basePath } from '@/utils/var'
import { useWorkflowStore } from '../store'
import AllStartBlocks from './all-start-blocks'
import AllTools from './all-tools'
import Blocks from './blocks'
import DataSources from './data-sources'
import { cn } from '@/utils/classnames'
import { useFeaturedToolsRecommendations } from '@/service/use-plugins'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useWorkflowStore } from '../store'
import { basePath } from '@/utils/var'
import Tooltip from '@/app/components/base/tooltip'
import { TabsEnum } from './types'
export type TabsProps = {
activeTab: TabsEnum
@ -129,7 +129,7 @@ const Tabs: FC<TabsProps> = ({
<div onClick={e => e.stopPropagation()}>
{
!noBlocks && (
<div className='relative flex bg-background-section-burn pl-1 pt-1'>
<div className="relative flex bg-background-section-burn pl-1 pt-1">
{
tabs.map((tab) => {
const commonProps = {
@ -152,8 +152,8 @@ const Tabs: FC<TabsProps> = ({
return (
<Tooltip
key={tab.key}
position='top'
popupClassName='max-w-[200px]'
position="top"
popupClassName="max-w-[200px]"
popupContent={t('workflow.tabs.startDisabledTip')}
>
<div {...commonProps}>
@ -178,7 +178,7 @@ const Tabs: FC<TabsProps> = ({
{filterElem}
{
activeTab === TabsEnum.Start && (!noBlocks || forceShowStartContent) && (
<div className='border-t border-divider-subtle'>
<div className="border-t border-divider-subtle">
<AllStartBlocks
allowUserInputSelection={allowStartNodeSelection}
searchText={searchText}
@ -191,7 +191,7 @@ const Tabs: FC<TabsProps> = ({
}
{
activeTab === TabsEnum.Blocks && !noBlocks && (
<div className='border-t border-divider-subtle'>
<div className="border-t border-divider-subtle">
<Blocks
searchText={searchText}
onSelect={onSelect}
@ -203,7 +203,7 @@ const Tabs: FC<TabsProps> = ({
}
{
activeTab === TabsEnum.Sources && !!dataSources.length && (
<div className='border-t border-divider-subtle'>
<div className="border-t border-divider-subtle">
<DataSources
searchText={searchText}
onSelect={onSelect}

View File

@ -1,28 +1,30 @@
'use client'
import type {
OffsetOptions,
Placement,
} from '@floating-ui/react'
import type { FC } from 'react'
import React from 'react'
import type { ToolDefaultValue, ToolValue } from './types'
import type { CustomCollectionBackend } from '@/app/components/tools/types'
import type { BlockEnum, OnSelectBlock } from '@/app/components/workflow/types'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type {
OffsetOptions,
Placement,
} from '@floating-ui/react'
import AllTools from '@/app/components/workflow/block-selector/all-tools'
import type { ToolDefaultValue, ToolValue } from './types'
import type { BlockEnum, OnSelectBlock } from '@/app/components/workflow/types'
import Toast from '@/app/components/base/toast'
import SearchBox from '@/app/components/plugins/marketplace/search-box'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
import AllTools from '@/app/components/workflow/block-selector/all-tools'
import { useGlobalPublicStore } from '@/context/global-public-context'
import {
createCustomCollection,
} from '@/service/tools'
import type { CustomCollectionBackend } from '@/app/components/tools/types'
import Toast from '@/app/components/base/toast'
import { useFeaturedToolsRecommendations } from '@/service/use-plugins'
import {
useAllBuiltInTools,
useAllCustomTools,
@ -33,8 +35,6 @@ import {
useInvalidateAllMCPTools,
useInvalidateAllWorkflowTools,
} from '@/service/use-tools'
import { useFeaturedToolsRecommendations } from '@/service/use-plugins'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { cn } from '@/utils/classnames'
type Props = {
@ -119,7 +119,8 @@ const ToolPicker: FC<Props> = ({
const handleAddedCustomTool = invalidateCustomTools
const handleTriggerClick = () => {
if (disabled) return
if (disabled)
return
onShowChange(true)
}
@ -149,7 +150,7 @@ const ToolPicker: FC<Props> = ({
if (isShowEditCollectionToolModal) {
return (
<EditCustomToolModal
dialogClassName='bg-background-overlay'
dialogClassName="bg-background-overlay"
payload={null}
onHide={hideEditCustomCollectionModal}
onAdd={doCreateCustomToolCollection}
@ -170,9 +171,9 @@ const ToolPicker: FC<Props> = ({
{trigger}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'>
<PortalToFollowElemContent className="z-[1000]">
<div className={cn('relative min-h-20 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm', panelClassName)}>
<div className='p-2 pb-1'>
<div className="p-2 pb-1">
<SearchBox
search={searchText}
onSearchChange={setSearchText}
@ -182,12 +183,12 @@ const ToolPicker: FC<Props> = ({
supportAddCustomTool={supportAddCustomTool}
onAddedCustomTool={handleAddedCustomTool}
onShowAddCustomCollectionModal={showEditCustomCollectionModal}
inputClassName='grow'
inputClassName="grow"
/>
</div>
<AllTools
className='mt-1'
toolContentClassName='max-w-[100%]'
className="mt-1"
toolContentClassName="max-w-[100%]"
tags={tags}
searchText={searchText}
onSelect={handleSelect as OnSelectBlock}

View File

@ -1,19 +1,20 @@
'use client'
import type { FC } from 'react'
import React, { useMemo } from 'react'
import type { ToolWithProvider } from '../../types'
import { BlockEnum } from '../../types'
import type { ToolDefaultValue } from '../types'
import Tooltip from '@/app/components/base/tooltip'
import type { Tool } from '@/app/components/tools/types'
import { useGetLanguage } from '@/context/i18n'
import BlockIcon from '../../block-icon'
import { cn } from '@/utils/classnames'
import * as React from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { trackEvent } from '@/app/components/base/amplitude'
import Tooltip from '@/app/components/base/tooltip'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
import { Theme } from '@/types/app'
import { cn } from '@/utils/classnames'
import { basePath } from '@/utils/var'
import { trackEvent } from '@/app/components/base/amplitude'
import BlockIcon from '../../block-icon'
import { BlockEnum } from '../../types'
const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
if (!icon)
@ -59,27 +60,28 @@ const ToolItem: FC<Props> = ({
return (
<Tooltip
key={payload.name}
position='right'
position="right"
needsDelay={false}
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="!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"
popupContent={(
<div>
<BlockIcon
size='md'
className='mb-2'
size="md"
className="mb-2"
type={BlockEnum.Tool}
toolIcon={providerIcon}
/>
<div className='mb-1 text-sm leading-5 text-text-primary'>{payload.label[language]}</div>
<div className='text-xs leading-[18px] text-text-secondary'>{payload.description[language]}</div>
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.label[language]}</div>
<div className="text-xs leading-[18px] text-text-secondary">{payload.description[language]}</div>
</div>
)}
>
<div
key={payload.name}
className='flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover'
className="flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover"
onClick={() => {
if (disabled) return
if (disabled)
return
const params: Record<string, string> = {}
if (payload.parameters) {
payload.parameters.forEach((item) => {
@ -113,10 +115,10 @@ const ToolItem: FC<Props> = ({
<span className={cn(disabled && 'opacity-30')}>{payload.label[language]}</span>
</div>
{isAdded && (
<div className='system-xs-regular mr-4 text-text-tertiary'>{t('tools.addToolModal.added')}</div>
<div className="system-xs-regular mr-4 text-text-tertiary">{t('tools.addToolModal.added')}</div>
)}
</div>
</Tooltip >
</Tooltip>
)
}
export default React.memo(ToolItem)

View File

@ -1,12 +1,11 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { ToolWithProvider } from '../../../types'
import type { BlockEnum } from '../../../types'
import type { BlockEnum, ToolWithProvider } from '../../../types'
import type { ToolDefaultValue, ToolValue } from '../../types'
import Tool from '../tool'
import { ViewType } from '../../view-type-select'
import * as React from 'react'
import { useMemo } from 'react'
import { ViewType } from '../../view-type-select'
import Tool from '../tool'
type Props = {
payload: ToolWithProvider[]
@ -45,8 +44,8 @@ const ToolViewFlatView: FC<Props> = ({
return res
}, [payload, letters])
return (
<div className='flex w-full'>
<div className='mr-1 grow'>
<div className="flex w-full">
<div className="mr-1 grow">
{payload.map(tool => (
<div
key={tool.id}

View File

@ -1,11 +1,10 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { ToolWithProvider } from '../../../types'
import Tool from '../tool'
import type { BlockEnum } from '../../../types'
import { ViewType } from '../../view-type-select'
import type { BlockEnum, ToolWithProvider } from '../../../types'
import type { ToolDefaultValue, ToolValue } from '../../types'
import * as React from 'react'
import { ViewType } from '../../view-type-select'
import Tool from '../tool'
type Props = {
groupName: string
@ -30,7 +29,7 @@ const Item: FC<Props> = ({
}) => {
return (
<div>
<div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'>
<div className="flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary">
{groupName}
</div>
<div>

View File

@ -1,12 +1,12 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import type { ToolWithProvider } from '../../../types'
import type { BlockEnum } from '../../../types'
import type { BlockEnum, ToolWithProvider } from '../../../types'
import type { ToolDefaultValue, ToolValue } from '../../types'
import Item from './item'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { AGENT_GROUP_NAME, CUSTOM_GROUP_NAME, WORKFLOW_GROUP_NAME } from '../../index-bar'
import Item from './item'
type Props = {
payload: Record<string, ToolWithProvider[]>
@ -41,7 +41,8 @@ const ToolListTreeView: FC<Props> = ({
return name
}, [t])
if (!payload) return null
if (!payload)
return null
return (
<div>

View File

@ -1,24 +1,25 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { cn } from '@/utils/classnames'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import { useGetLanguage } from '@/context/i18n'
import type { Tool as ToolType } from '../../../tools/types'
import { CollectionType } from '../../../tools/types'
import type { ToolWithProvider } from '../../types'
import { BlockEnum } from '../../types'
import type { ToolDefaultValue, ToolValue } from '../types'
import { ViewType } from '../view-type-select'
import ActionItem from './action-item'
import BlockIcon from '../../block-icon'
import { useTranslation } from 'react-i18next'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import { useHover } from 'ahooks'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Mcp } from '@/app/components/base/icons/src/vender/other'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
import { Theme } from '@/types/app'
import McpToolNotSupportTooltip from '../../nodes/_base/components/mcp-tool-not-support-tooltip'
import { Mcp } from '@/app/components/base/icons/src/vender/other'
import { cn } from '@/utils/classnames'
import { basePath } from '@/utils/var'
import { CollectionType } from '../../../tools/types'
import BlockIcon from '../../block-icon'
import McpToolNotSupportTooltip from '../../nodes/_base/components/mcp-tool-not-support-tooltip'
import { BlockEnum } from '../../types'
import { ViewType } from '../view-type-select'
import ActionItem from './action-item'
const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
if (!icon)
@ -78,7 +79,8 @@ const Tool: FC<Props> = ({
return normalizedIcon
}, [theme, normalizedIcon, normalizedIconDark])
const getIsDisabled = useCallback((tool: ToolType) => {
if (!selectedTools || !selectedTools.length) return false
if (!selectedTools || !selectedTools.length)
return false
return selectedTools.some(selectedTool => (selectedTool.provider_name === payload.name || selectedTool.provider_name === payload.id) && selectedTool.tool_name === tool.name)
}, [payload.id, payload.name, selectedTools])
@ -89,7 +91,7 @@ const Tool: FC<Props> = ({
const notShowProviderSelectInfo = useMemo(() => {
if (isAllSelected) {
return (
<span className='system-xs-regular text-text-tertiary'>
<span className="system-xs-regular text-text-tertiary">
{t('tools.addToolModal.added')}
</span>
)
@ -98,7 +100,8 @@ const Tool: FC<Props> = ({
const selectedInfo = useMemo(() => {
if (isHovering && !isAllSelected) {
return (
<span className='system-xs-regular text-components-button-secondary-accent-text'
<span
className="system-xs-regular text-components-button-secondary-accent-text"
onClick={() => {
onSelectMultiple?.(BlockEnum.Tool, actions.filter(action => !getIsDisabled(action)).map((tool) => {
const params: Record<string, string> = {}
@ -135,11 +138,10 @@ const Tool: FC<Props> = ({
return <></>
return (
<span className='system-xs-regular text-text-tertiary'>
<span className="system-xs-regular text-text-tertiary">
{isAllSelected
? t('workflow.tabs.allAdded')
: `${selectedToolsNum} / ${totalToolsNum}`
}
: `${selectedToolsNum} / ${totalToolsNum}`}
</span>
)
}, [actions, getIsDisabled, isAllSelected, isHovering, language, onSelectMultiple, payload.id, payload.is_team_authorization, payload.name, payload.type, selectedToolsNum, t, totalToolsNum])
@ -176,7 +178,7 @@ const Tool: FC<Props> = ({
>
<div className={cn(className)}>
<div
className='group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover'
className="group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover"
onClick={() => {
if (hasAction) {
setFold(!isFold)
@ -210,20 +212,20 @@ const Tool: FC<Props> = ({
>
<div className={cn('flex h-8 grow items-center', isShowCanNotChooseMCPTip && 'opacity-30')}>
<BlockIcon
className='shrink-0'
className="shrink-0"
type={BlockEnum.Tool}
toolIcon={providerIcon}
/>
<div className='ml-2 flex w-0 grow items-center text-sm text-text-primary'>
<span className='max-w-[250px] truncate'>{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span>
<div className="ml-2 flex w-0 grow items-center text-sm text-text-primary">
<span className="max-w-[250px] truncate">{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span>
{isFlatView && groupName && (
<span className='system-xs-regular ml-2 shrink-0 text-text-quaternary'>{groupName}</span>
<span className="system-xs-regular ml-2 shrink-0 text-text-quaternary">{groupName}</span>
)}
{isMCPTool && <Mcp className='ml-2 size-3.5 shrink-0 text-text-quaternary' />}
{isMCPTool && <Mcp className="ml-2 size-3.5 shrink-0 text-text-quaternary" />}
</div>
</div>
<div className='ml-2 flex items-center'>
<div className="ml-2 flex items-center">
{!isShowCanNotChooseMCPTip && !canNotSelectMultiple && (notShowProvider ? notShowProviderSelectInfo : selectedInfo)}
{isShowCanNotChooseMCPTip && <McpToolNotSupportTooltip />}
{hasAction && (

View File

@ -1,14 +1,13 @@
import { memo, useMemo, useRef } from 'react'
import type { BlockEnum, ToolWithProvider } from '../types'
import IndexBar, { groupItems } from './index-bar'
import type { ToolDefaultValue, ToolValue } from './types'
import type { ToolTypeEnum } from './types'
import { ViewType } from './view-type-select'
import type { ToolDefaultValue, ToolTypeEnum, ToolValue } from './types'
import { memo, useMemo, useRef } from 'react'
import Empty from '@/app/components/tools/provider/empty'
import { useGetLanguage } from '@/context/i18n'
import ToolListTreeView from './tool/tool-list-tree-view/list'
import ToolListFlatView from './tool/tool-list-flat-view/list'
import { cn } from '@/utils/classnames'
import IndexBar, { groupItems } from './index-bar'
import ToolListFlatView from './tool/tool-list-flat-view/list'
import ToolListTreeView from './tool/tool-list-tree-view/list'
import { ViewType } from './view-type-select'
type ToolsProps = {
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
@ -93,36 +92,38 @@ const Tools = ({
return (
<div className={cn('max-w-[100%] p-1', className)}>
{!tools.length && !hasSearchText && (
<div className='py-10'>
<div className="py-10">
<Empty type={toolType!} isAgent={isAgent} />
</div>
)}
{!!tools.length && (
isFlatView ? (
<ToolListFlatView
toolRefs={toolRefs}
letters={letters}
payload={listViewToolData}
isShowLetterIndex={isShowLetterIndex}
hasSearchText={hasSearchText}
onSelect={onSelect}
canNotSelectMultiple={canNotSelectMultiple}
onSelectMultiple={onSelectMultiple}
selectedTools={selectedTools}
canChooseMCPTool={canChooseMCPTool}
indexBar={<IndexBar letters={letters} itemRefs={toolRefs} className={indexBarClassName} />}
/>
) : (
<ToolListTreeView
payload={treeViewToolsData}
hasSearchText={hasSearchText}
onSelect={onSelect}
canNotSelectMultiple={canNotSelectMultiple}
onSelectMultiple={onSelectMultiple}
selectedTools={selectedTools}
canChooseMCPTool={canChooseMCPTool}
/>
)
isFlatView
? (
<ToolListFlatView
toolRefs={toolRefs}
letters={letters}
payload={listViewToolData}
isShowLetterIndex={isShowLetterIndex}
hasSearchText={hasSearchText}
onSelect={onSelect}
canNotSelectMultiple={canNotSelectMultiple}
onSelectMultiple={onSelectMultiple}
selectedTools={selectedTools}
canChooseMCPTool={canChooseMCPTool}
indexBar={<IndexBar letters={letters} itemRefs={toolRefs} className={indexBarClassName} />}
/>
)
: (
<ToolListTreeView
payload={treeViewToolsData}
hasSearchText={hasSearchText}
onSelect={onSelect}
canNotSelectMultiple={canNotSelectMultiple}
onSelectMultiple={onSelectMultiple}
selectedTools={selectedTools}
canChooseMCPTool={canChooseMCPTool}
/>
)
)}
</div>
)

View File

@ -1,15 +1,14 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { TriggerWithProvider } from '../types'
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
import type { Event } from '@/app/components/tools/types'
import { BlockEnum } from '../../types'
import type { TriggerDefaultValue } from '../types'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { useGetLanguage } from '@/context/i18n'
import BlockIcon from '../../block-icon'
import { cn } from '@/utils/classnames'
import { useTranslation } from 'react-i18next'
import BlockIcon from '../../block-icon'
import { BlockEnum } from '../../types'
type Props = {
provider: TriggerWithProvider
@ -32,27 +31,28 @@ const TriggerPluginActionItem: FC<Props> = ({
return (
<Tooltip
key={payload.name}
position='right'
position="right"
needsDelay={false}
popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg'
popupClassName="!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg"
popupContent={(
<div>
<BlockIcon
size='md'
className='mb-2'
size="md"
className="mb-2"
type={BlockEnum.TriggerPlugin}
toolIcon={provider.icon}
/>
<div className='mb-1 text-sm leading-5 text-text-primary'>{payload.label[language]}</div>
<div className='text-xs leading-[18px] text-text-secondary'>{payload.description[language]}</div>
<div className="mb-1 text-sm leading-5 text-text-primary">{payload.label[language]}</div>
<div className="text-xs leading-[18px] text-text-secondary">{payload.description[language]}</div>
</div>
)}
>
<div
key={payload.name}
className='flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover'
className="flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover"
onClick={() => {
if (disabled) return
if (disabled)
return
const params: Record<string, string> = {}
if (payload.parameters) {
payload.parameters.forEach((item: any) => {
@ -81,10 +81,10 @@ const TriggerPluginActionItem: FC<Props> = ({
<span className={cn(disabled && 'opacity-30')}>{payload.label[language]}</span>
</div>
{isAdded && (
<div className='system-xs-regular mr-4 text-text-tertiary'>{t('tools.addToolModal.added')}</div>
<div className="system-xs-regular mr-4 text-text-tertiary">{t('tools.addToolModal.added')}</div>
)}
</div>
</Tooltip >
</Tooltip>
)
}
export default React.memo(TriggerPluginActionItem)

View File

@ -1,18 +1,19 @@
'use client'
import { useGetLanguage } from '@/context/i18n'
import { cn } from '@/utils/classnames'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import type { FC } from 'react'
import React, { useEffect, useMemo, useRef } from 'react'
import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import * as React from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { CollectionType } from '@/app/components/tools/types'
import BlockIcon from '@/app/components/workflow/block-icon'
import { BlockEnum } from '@/app/components/workflow/types'
import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import TriggerPluginActionItem from './action-item'
import { Theme } from '@/types/app'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
import { Theme } from '@/types/app'
import { cn } from '@/utils/classnames'
import { basePath } from '@/utils/var'
import TriggerPluginActionItem from './action-item'
const normalizeProviderIcon = (icon?: TriggerWithProvider['icon']) => {
if (!icon)
@ -93,7 +94,7 @@ const TriggerPluginItem: FC<Props> = ({
>
<div className={cn(className)}>
<div
className='group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover'
className="group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover"
onClick={() => {
if (hasAction) {
setFold(!isFold)
@ -124,19 +125,19 @@ const TriggerPluginItem: FC<Props> = ({
})
}}
>
<div className='flex h-8 grow items-center'>
<div className="flex h-8 grow items-center">
<BlockIcon
className='shrink-0'
className="shrink-0"
type={BlockEnum.TriggerPlugin}
toolIcon={providerIcon}
/>
<div className='ml-2 flex min-w-0 flex-1 items-center text-sm text-text-primary'>
<span className='max-w-[200px] truncate'>{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span>
<span className='system-xs-regular ml-2 truncate text-text-quaternary'>{groupName}</span>
<div className="ml-2 flex min-w-0 flex-1 items-center text-sm text-text-primary">
<span className="max-w-[200px] truncate">{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span>
<span className="system-xs-regular ml-2 truncate text-text-quaternary">{groupName}</span>
</div>
</div>
<div className='ml-2 flex items-center'>
<div className="ml-2 flex items-center">
{hasAction && (
<FoldIcon className={cn('h-4 w-4 shrink-0 text-text-tertiary group-hover/item:text-text-tertiary', isFold && 'text-text-quaternary')} />
)}

View File

@ -1,10 +1,10 @@
'use client'
import { memo, useEffect, useMemo } from 'react'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import TriggerPluginItem from './item'
import type { BlockEnum } from '../../types'
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
import { memo, useEffect, useMemo } from 'react'
import { useGetLanguage } from '@/context/i18n'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import TriggerPluginItem from './item'
type TriggerPluginListProps = {
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void

View File

@ -1,6 +1,6 @@
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { ParametersSchema, PluginMeta, PluginTriggerSubscriptionConstructor, SupportedCreationMethods, TriggerEvent } from '../../plugins/types'
import type { Collection, Event } from '../../tools/types'
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
export enum TabsEnum {
Start = 'start',
@ -39,9 +39,9 @@ export type TriggerDefaultValue = PluginCommonDefaultValue & {
title: string
plugin_unique_identifier: string
is_team_authorization: boolean
params: Record<string, any>
paramSchemas: Record<string, any>[]
output_schema: Record<string, any>
params: Record<string, unknown>
paramSchemas: Record<string, unknown>[]
output_schema: Record<string, unknown>
subscription_id?: string
meta?: PluginMeta
}
@ -52,9 +52,9 @@ export type ToolDefaultValue = PluginCommonDefaultValue & {
tool_description: string
title: string
is_team_authorization: boolean
params: Record<string, any>
paramSchemas: Record<string, any>[]
output_schema?: Record<string, any>
params: Record<string, unknown>
paramSchemas: Record<string, unknown>[]
output_schema?: Record<string, unknown>
credential_id?: string
meta?: PluginMeta
plugin_id?: string
@ -82,10 +82,10 @@ export type ToolValue = {
tool_name: string
tool_label: string
tool_description?: string
settings?: Record<string, any>
parameters?: Record<string, any>
settings?: Record<string, unknown>
parameters?: Record<string, unknown>
enabled?: boolean
extra?: Record<string, any>
extra?: { description?: string } & Record<string, unknown>
credential_id?: string
}
@ -94,12 +94,12 @@ export type DataSourceItem = {
plugin_unique_identifier: string
provider: string
declaration: {
credentials_schema: any[]
credentials_schema: unknown[]
provider_type: string
identity: {
author: string
description: TypeWithI18N
icon: string | { background: string; content: string }
icon: string | { background: string, content: string }
label: TypeWithI18N
name: string
tags: string[]
@ -108,15 +108,15 @@ export type DataSourceItem = {
description: TypeWithI18N
identity: {
author: string
icon?: string | { background: string; content: string }
icon?: string | { background: string, content: string }
label: TypeWithI18N
name: string
provider: string
}
parameters: any[]
parameters: unknown[]
output_schema?: {
type: string
properties: Record<string, any>
properties: Record<string, unknown>
}
}[]
}
@ -130,18 +130,18 @@ export type TriggerParameter = {
label: TypeWithI18N
description?: TypeWithI18N
type: 'string' | 'number' | 'boolean' | 'select' | 'file' | 'files'
| 'model-selector' | 'app-selector' | 'object' | 'array' | 'dynamic-select'
| 'model-selector' | 'app-selector' | 'object' | 'array' | 'dynamic-select'
auto_generate?: {
type: string
value?: any
value?: unknown
} | null
template?: {
type: string
value?: any
value?: unknown
} | null
scope?: string | null
required?: boolean
default?: any
default?: unknown
min?: number | null
max?: number | null
precision?: number | null
@ -154,11 +154,11 @@ export type TriggerParameter = {
export type TriggerCredentialField = {
type: 'secret-input' | 'text-input' | 'select' | 'boolean'
| 'app-selector' | 'model-selector' | 'tools-selector'
| 'app-selector' | 'model-selector' | 'tools-selector'
name: string
scope?: string | null
required: boolean
default?: string | number | boolean | Array<any> | null
default?: string | number | boolean | Array<unknown> | null
options?: Array<{
value: string
label: TypeWithI18N
@ -191,7 +191,7 @@ export type TriggerApiEntity = {
identity: TriggerIdentity
description: TypeWithI18N
parameters: TriggerParameter[]
output_schema?: Record<string, any>
output_schema?: Record<string, unknown>
}
export type TriggerProviderApiEntity = {
@ -237,32 +237,15 @@ type TriggerSubscriptionStructure = {
name: string
provider: string
credential_type: TriggerCredentialTypeEnum
credentials: TriggerSubCredentials
credentials: Record<string, unknown>
endpoint: string
parameters: TriggerSubParameters
properties: TriggerSubProperties
parameters: Record<string, unknown>
properties: Record<string, unknown>
workflows_in_use: number
}
export type TriggerSubscription = TriggerSubscriptionStructure
export type TriggerSubCredentials = {
access_tokens: string
}
export type TriggerSubParameters = {
repository: string
webhook_secret?: string
}
export type TriggerSubProperties = {
active: boolean
events: string[]
external_id: string
repository: string
webhook_secret?: string
}
export type TriggerSubscriptionBuilder = TriggerSubscriptionStructure
// OAuth configuration types
@ -275,7 +258,7 @@ export type TriggerOAuthConfig = {
params: {
client_id: string
client_secret: string
[key: string]: any
[key: string]: string
}
system_configured: boolean
}

View File

@ -5,7 +5,8 @@ const useCheckVerticalScrollbar = (ref: React.RefObject<HTMLElement>) => {
useEffect(() => {
const elem = ref.current
if (!elem) return
if (!elem)
return
const checkScrollbar = () => {
setHasVerticalScrollbar(elem.scrollHeight > elem.clientHeight)

View File

@ -1,5 +1,5 @@
import React from 'react'
import { useThrottleFn } from 'ahooks'
import * as React from 'react'
export enum ScrollPosition {
belowTheWrap = 'belowTheWrap',

View File

@ -1,5 +1,5 @@
import type { Tool } from '@/app/components/tools/types'
import type { DataSourceItem } from './types'
import type { Tool } from '@/app/components/tools/types'
export const transformDataSourceToTool = (dataSourceItem: DataSourceItem) => {
return {

View File

@ -1,7 +1,8 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { RiNodeTree, RiSortAlphabetAsc } from '@remixicon/react'
import * as React from 'react'
import { useCallback } from 'react'
import { cn } from '@/utils/classnames'
export enum ViewType {
@ -27,30 +28,26 @@ const ViewTypeSelect: FC<Props> = ({
}, [viewType, onChange])
return (
<div className='flex items-center rounded-lg bg-components-segmented-control-bg-normal p-px'>
<div className="flex items-center rounded-lg bg-components-segmented-control-bg-normal p-px">
<div
className={
cn('rounded-lg p-[3px]',
viewType === ViewType.flat
? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs'
: 'cursor-pointer text-text-tertiary',
)
cn('rounded-lg p-[3px]', viewType === ViewType.flat
? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs'
: 'cursor-pointer text-text-tertiary')
}
onClick={handleChange(ViewType.flat)}
>
<RiSortAlphabetAsc className='h-4 w-4' />
<RiSortAlphabetAsc className="h-4 w-4" />
</div>
<div
className={
cn('rounded-lg p-[3px]',
viewType === ViewType.tree
? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs'
: 'cursor-pointer text-text-tertiary',
)
cn('rounded-lg p-[3px]', viewType === ViewType.tree
? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs'
: 'cursor-pointer text-text-tertiary')
}
onClick={handleChange(ViewType.tree)}
>
<RiNodeTree className='h-4 w-4 ' />
<RiNodeTree className="h-4 w-4 " />
</div>
</div>
)

View File

@ -4,27 +4,27 @@ import type {
import type {
Node,
} from '@/app/components/workflow/types'
import { useEventListener } from 'ahooks'
import { produce } from 'immer'
import {
memo,
} from 'react'
import { produce } from 'immer'
import {
useReactFlow,
useStoreApi,
useViewport,
} from 'reactflow'
import { useEventListener } from 'ahooks'
import { CUSTOM_NODE } from './constants'
import { useAutoGenerateWebhookUrl, useNodesInteractions, useNodesSyncDraft, useWorkflowHistory, WorkflowHistoryEvent } from './hooks'
import CustomNode from './nodes'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
import {
useStore,
useWorkflowStore,
} from './store'
import { WorkflowHistoryEvent, useAutoGenerateWebhookUrl, useNodesInteractions, useNodesSyncDraft, useWorkflowHistory } from './hooks'
import { CUSTOM_NODE } from './constants'
import { getIterationStartNode, getLoopStartNode } from './utils'
import CustomNode from './nodes'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
import { BlockEnum } from './types'
import { getIterationStartNode, getLoopStartNode } from './utils'
type Props = {
candidateNode: Node
@ -94,7 +94,7 @@ const CandidateNodeMain: FC<Props> = ({
return (
<div
className='absolute z-10'
className="absolute z-10"
style={{
left: mousePosition.elementX,
top: mousePosition.elementY,

View File

@ -2,10 +2,10 @@ import {
memo,
} from 'react'
import CandidateNodeMain from './candidate-node-main'
import {
useStore,
} from './store'
import CandidateNodeMain from './candidate-node-main'
const CandidateNode = () => {
const candidateNode = useStore(s => s.candidateNode)

View File

@ -1,5 +1,6 @@
import type { Var } from './types'
import { BlockEnum, VarType } from './types'
export const MAX_ITERATION_PARALLEL_NUM = 10
export const MIN_ITERATION_PARALLEL_NUM = 1
export const DEFAULT_ITER_TIMES = 1
@ -42,16 +43,18 @@ export const isInWorkflowPage = () => {
export const getGlobalVars = (isChatMode: boolean): Var[] => {
const isInWorkflow = isInWorkflowPage()
const vars: Var[] = [
...(isChatMode ? [
{
variable: 'sys.dialogue_count',
type: VarType.number,
},
{
variable: 'sys.conversation_id',
type: VarType.string,
},
] : []),
...(isChatMode
? [
{
variable: 'sys.dialogue_count',
type: VarType.number,
},
{
variable: 'sys.conversation_id',
type: VarType.string,
},
]
: []),
{
variable: 'sys.user_id',
type: VarType.string,
@ -68,12 +71,14 @@ export const getGlobalVars = (isChatMode: boolean): Var[] => {
variable: 'sys.workflow_run_id',
type: VarType.string,
},
...((isInWorkflow && !isChatMode) ? [
{
variable: 'sys.timestamp',
type: VarType.number,
},
] : []),
...((isInWorkflow && !isChatMode)
? [
{
variable: 'sys.timestamp',
type: VarType.number,
},
]
: []),
]
return vars
}
@ -104,11 +109,25 @@ export const RETRIEVAL_OUTPUT_STRUCT = `{
}`
export const SUPPORT_OUTPUT_VARS_NODE = [
BlockEnum.Start, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, BlockEnum.LLM, BlockEnum.KnowledgeRetrieval, BlockEnum.Code, BlockEnum.TemplateTransform,
BlockEnum.HttpRequest, BlockEnum.Tool, BlockEnum.VariableAssigner, BlockEnum.VariableAggregator, BlockEnum.QuestionClassifier,
BlockEnum.ParameterExtractor, BlockEnum.Iteration, BlockEnum.Loop,
BlockEnum.DocExtractor, BlockEnum.ListFilter,
BlockEnum.Agent, BlockEnum.DataSource,
BlockEnum.Start,
BlockEnum.TriggerWebhook,
BlockEnum.TriggerPlugin,
BlockEnum.LLM,
BlockEnum.KnowledgeRetrieval,
BlockEnum.Code,
BlockEnum.TemplateTransform,
BlockEnum.HttpRequest,
BlockEnum.Tool,
BlockEnum.VariableAssigner,
BlockEnum.VariableAggregator,
BlockEnum.QuestionClassifier,
BlockEnum.ParameterExtractor,
BlockEnum.Iteration,
BlockEnum.Loop,
BlockEnum.DocExtractor,
BlockEnum.ListFilter,
BlockEnum.Agent,
BlockEnum.DataSource,
]
export const AGENT_OUTPUT_STRUCT: Var[] = [

View File

@ -1,26 +1,26 @@
import llmDefault from '@/app/components/workflow/nodes/llm/default'
import knowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
import agentDefault from '@/app/components/workflow/nodes/agent/default'
import questionClassifierDefault from '@/app/components/workflow/nodes/question-classifier/default'
import ifElseDefault from '@/app/components/workflow/nodes/if-else/default'
import iterationDefault from '@/app/components/workflow/nodes/iteration/default'
import iterationStartDefault from '@/app/components/workflow/nodes/iteration-start/default'
import loopDefault from '@/app/components/workflow/nodes/loop/default'
import loopStartDefault from '@/app/components/workflow/nodes/loop-start/default'
import loopEndDefault from '@/app/components/workflow/nodes/loop-end/default'
import codeDefault from '@/app/components/workflow/nodes/code/default'
import templateTransformDefault from '@/app/components/workflow/nodes/template-transform/default'
import variableAggregatorDefault from '@/app/components/workflow/nodes/variable-assigner/default'
import documentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default'
import assignerDefault from '@/app/components/workflow/nodes/assigner/default'
import codeDefault from '@/app/components/workflow/nodes/code/default'
import documentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default'
import httpRequestDefault from '@/app/components/workflow/nodes/http/default'
import parameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default'
import listOperatorDefault from '@/app/components/workflow/nodes/list-operator/default'
import toolDefault from '@/app/components/workflow/nodes/tool/default'
import humanInputDefault from '@/app/components/workflow/nodes/human-input/default'
import ifElseDefault from '@/app/components/workflow/nodes/if-else/default'
import iterationStartDefault from '@/app/components/workflow/nodes/iteration-start/default'
import iterationDefault from '@/app/components/workflow/nodes/iteration/default'
import knowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
import listOperatorDefault from '@/app/components/workflow/nodes/list-operator/default'
import llmDefault from '@/app/components/workflow/nodes/llm/default'
import loopEndDefault from '@/app/components/workflow/nodes/loop-end/default'
import loopStartDefault from '@/app/components/workflow/nodes/loop-start/default'
import loopDefault from '@/app/components/workflow/nodes/loop/default'
import parameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default'
import questionClassifierDefault from '@/app/components/workflow/nodes/question-classifier/default'
import templateTransformDefault from '@/app/components/workflow/nodes/template-transform/default'
import toolDefault from '@/app/components/workflow/nodes/tool/default'
import variableAggregatorDefault from '@/app/components/workflow/nodes/variable-assigner/default'
export const WORKFLOW_COMMON_NODES = [
llmDefault,

View File

@ -1,12 +1,12 @@
import type { StateCreator } from 'zustand'
import type { SliceFromInjection } from './store'
import {
createContext,
useRef,
} from 'react'
import type { SliceFromInjection } from './store'
import {
createWorkflowStore,
} from './store'
import type { StateCreator } from 'zustand'
type WorkflowStore = ReturnType<typeof createWorkflowStore>
export const WorkflowContext = createContext<WorkflowStore | null>(null)

View File

@ -1,8 +1,8 @@
import { memo } from 'react'
import type { ConnectionLineComponentProps } from 'reactflow'
import { memo } from 'react'
import {
Position,
getBezierPath,
Position,
} from 'reactflow'
const CustomConnectionLine = ({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) => {
@ -22,7 +22,7 @@ const CustomConnectionLine = ({ fromX, fromY, toX, toY }: ConnectionLineComponen
<g>
<path
fill="none"
stroke='#D0D5DD'
stroke="#D0D5DD"
strokeWidth={2}
d={edgePath}
/>
@ -31,7 +31,7 @@ const CustomConnectionLine = ({ fromX, fromY, toX, toY }: ConnectionLineComponen
y={toY - 4}
width={2}
height={8}
fill='#2970FF'
fill="#2970FF"
/>
</g>
)

View File

@ -25,21 +25,21 @@ const CustomEdgeLinearGradientRender = ({
<defs>
<linearGradient
id={id}
gradientUnits='userSpaceOnUse'
gradientUnits="userSpaceOnUse"
x1={x1}
y1={y1}
x2={x2}
y2={y2}
>
<stop
offset='0%'
offset="0%"
style={{
stopColor: startColor,
stopOpacity: 1,
}}
/>
<stop
offset='100%'
offset="100%"
style={{
stopColor,
stopOpacity: 1,

View File

@ -1,32 +1,32 @@
import type { EdgeProps } from 'reactflow'
import type {
Edge,
OnSelectBlock,
} from './types'
import { intersection } from 'es-toolkit/compat'
import {
memo,
useCallback,
useMemo,
useState,
} from 'react'
import { intersection } from 'lodash-es'
import type { EdgeProps } from 'reactflow'
import {
BaseEdge,
EdgeLabelRenderer,
Position,
getBezierPath,
Position,
} from 'reactflow'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import { cn } from '@/utils/classnames'
import BlockSelector from './block-selector'
import { ITERATION_CHILDREN_Z_INDEX, LOOP_CHILDREN_Z_INDEX } from './constants'
import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render'
import {
useAvailableBlocks,
useNodesInteractions,
} from './hooks'
import BlockSelector from './block-selector'
import type {
Edge,
OnSelectBlock,
} from './types'
import { NodeRunningStatus } from './types'
import { getEdgeColor } from './utils'
import { ITERATION_CHILDREN_Z_INDEX, LOOP_CHILDREN_Z_INDEX } from './constants'
import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render'
import { cn } from '@/utils/classnames'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
const CustomEdge = ({
id,
@ -75,8 +75,9 @@ const CustomEdge = ({
|| _targetRunningStatus === NodeRunningStatus.Exception
|| _targetRunningStatus === NodeRunningStatus.Running
)
)
) {
return id
}
}, [_sourceRunningStatus, _targetRunningStatus, id])
const handleOpenChange = useCallback((v: boolean) => {

View File

@ -1,10 +1,10 @@
import type { FC } from 'react'
import { createContext, useCallback, useEffect, useRef } from 'react'
import { createDatasetsDetailStore } from './store'
import type { CommonNodeType, Node } from '../types'
import { BlockEnum } from '../types'
import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types'
import type { CommonNodeType, Node } from '../types'
import { createContext, useCallback, useEffect, useRef } from 'react'
import { fetchDatasets } from '@/service/datasets'
import { BlockEnum } from '../types'
import { createDatasetsDetailStore } from './store'
type DatasetsDetailStoreApi = ReturnType<typeof createDatasetsDetailStore>
@ -33,12 +33,14 @@ const DatasetsDetailProvider: FC<DatasetsDetailProviderProps> = ({
}, [])
useEffect(() => {
if (!storeRef.current) return
if (!storeRef.current)
return
const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval)
const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => {
return Array.from(new Set([...acc, ...(node.data as CommonNodeType<KnowledgeRetrievalNodeType>).dataset_ids]))
}, [])
if (allDatasetIds.length === 0) return
if (allDatasetIds.length === 0)
return
updateDatasetsDetail(allDatasetIds)
}, [])

View File

@ -1,8 +1,8 @@
import type { DataSet } from '@/models/datasets'
import { produce } from 'immer'
import { useContext } from 'react'
import { createStore, useStore } from 'zustand'
import type { DataSet } from '@/models/datasets'
import { DatasetsDetailContext } from './provider'
import { produce } from 'immer'
type DatasetsDetailStore = {
datasetsDetail: Record<string, DataSet>

View File

@ -1,14 +1,15 @@
'use client'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
import { RiCloseLine, RiLock2Line } from '@remixicon/react'
import { cn } from '@/utils/classnames'
import { noop } from 'es-toolkit/compat'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Checkbox from '@/app/components/base/checkbox'
import { Env } from '@/app/components/base/icons/src/vender/line/others'
import Modal from '@/app/components/base/modal'
import Checkbox from '@/app/components/base/checkbox'
import Button from '@/app/components/base/button'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
import { noop } from 'lodash-es'
import { cn } from '@/utils/classnames'
export type DSLExportConfirmModalProps = {
envList: EnvironmentVariable[]
@ -36,47 +37,47 @@ const DSLExportConfirmModal = ({
onClose={noop}
className={cn('w-[480px] max-w-[480px]')}
>
<div className='title-2xl-semi-bold relative pb-6 text-text-primary'>{t('workflow.env.export.title')}</div>
<div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onClose}>
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
<div className="title-2xl-semi-bold relative pb-6 text-text-primary">{t('workflow.env.export.title')}</div>
<div className="absolute right-4 top-4 cursor-pointer p-2" onClick={onClose}>
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
</div>
<div className='relative'>
<table className='radius-md w-full border-separate border-spacing-0 border border-divider-regular shadow-xs'>
<thead className='system-xs-medium-uppercase text-text-tertiary'>
<div className="relative">
<table className="radius-md w-full border-separate border-spacing-0 border border-divider-regular shadow-xs">
<thead className="system-xs-medium-uppercase text-text-tertiary">
<tr>
<td width={220} className='h-7 border-b border-r border-divider-regular pl-3'>NAME</td>
<td className='h-7 border-b border-divider-regular pl-3'>VALUE</td>
<td width={220} className="h-7 border-b border-r border-divider-regular pl-3">NAME</td>
<td className="h-7 border-b border-divider-regular pl-3">VALUE</td>
</tr>
</thead>
<tbody>
{envList.map((env, index) => (
<tr key={env.name}>
<td className={cn('system-xs-medium h-7 border-r pl-3', index + 1 !== envList.length && 'border-b')}>
<div className='flex w-[200px] items-center gap-1'>
<Env className='h-4 w-4 shrink-0 text-util-colors-violet-violet-600' />
<div className='truncate text-text-primary'>{env.name}</div>
<div className='shrink-0 text-text-tertiary'>Secret</div>
<RiLock2Line className='h-3 w-3 shrink-0 text-text-tertiary' />
<div className="flex w-[200px] items-center gap-1">
<Env className="h-4 w-4 shrink-0 text-util-colors-violet-violet-600" />
<div className="truncate text-text-primary">{env.name}</div>
<div className="shrink-0 text-text-tertiary">Secret</div>
<RiLock2Line className="h-3 w-3 shrink-0 text-text-tertiary" />
</div>
</td>
<td className={cn('h-7 pl-3', index + 1 !== envList.length && 'border-b')}>
<div className='system-xs-regular truncate text-text-secondary'>{env.value}</div>
<div className="system-xs-regular truncate text-text-secondary">{env.value}</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className='mt-4 flex gap-2'>
<div className="mt-4 flex gap-2">
<Checkbox
className='shrink-0'
className="shrink-0"
checked={exportSecrets}
onCheck={() => setExportSecrets(!exportSecrets)}
/>
<div className='system-sm-medium cursor-pointer text-text-primary' onClick={() => setExportSecrets(!exportSecrets)}>{t('workflow.env.export.checkbox')}</div>
<div className="system-sm-medium cursor-pointer text-text-primary" onClick={() => setExportSecrets(!exportSecrets)}>{t('workflow.env.export.checkbox')}</div>
</div>
<div className='flex flex-row-reverse pt-6'>
<Button className='ml-2' variant='primary' onClick={submit}>{exportSecrets ? t('workflow.env.export.export') : t('workflow.env.export.ignore')}</Button>
<div className="flex flex-row-reverse pt-6">
<Button className="ml-2" variant="primary" onClick={submit}>{exportSecrets ? t('workflow.env.export.export') : t('workflow.env.export.ignore')}</Button>
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
</div>
</Modal>

View File

@ -1,19 +1,20 @@
import type { StartNodeType } from './nodes/start/types'
import type { CommonNodeType, InputVar, Node } from './types'
import type { PromptVariable } from '@/models/debug'
import {
memo,
useCallback,
} from 'react'
import { useNodes } from 'reactflow'
import { useStore } from './store'
import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
import {
useIsChatMode,
useNodesReadOnly,
useNodesSyncDraft,
} from './hooks'
import { type CommonNodeType, type InputVar, InputVarType, type Node } from './types'
import useConfig from './nodes/start/use-config'
import type { StartNodeType } from './nodes/start/types'
import type { PromptVariable } from '@/models/debug'
import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
import { useStore } from './store'
import { InputVarType } from './types'
const Features = () => {
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)

View File

@ -28,9 +28,9 @@ const ChatVariableButton = ({ disabled }: { disabled: boolean }) => {
)}
disabled={disabled}
onClick={handleClick}
variant='ghost'
variant="ghost"
>
<BubbleX className='h-4 w-4 text-components-button-secondary-text' />
<BubbleX className="h-4 w-4 text-components-button-secondary-text" />
</Button>
)
}

View File

@ -1,3 +1,12 @@
import type { ChecklistItem } from '../hooks/use-checklist'
import type {
BlockEnum,
CommonEdgeType,
} from '../types'
import {
RiCloseLine,
RiListCheck3,
} from '@remixicon/react'
import {
memo,
useState,
@ -6,34 +15,23 @@ import { useTranslation } from 'react-i18next'
import {
useEdges,
} from 'reactflow'
import { Warning } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import { IconR } from '@/app/components/base/icons/src/vender/line/arrows'
import {
RiCloseLine,
RiListCheck3,
} from '@remixicon/react'
import BlockIcon from '../block-icon'
import {
useChecklist,
useNodesInteractions,
} from '../hooks'
import type { ChecklistItem } from '../hooks/use-checklist'
import type {
CommonEdgeType,
} from '../types'
import { cn } from '@/utils/classnames'
ChecklistSquare,
} from '@/app/components/base/icons/src/vender/line/general'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import {
ChecklistSquare,
} from '@/app/components/base/icons/src/vender/line/general'
import { Warning } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import { IconR } from '@/app/components/base/icons/src/vender/line/arrows'
import type {
BlockEnum,
} from '../types'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { cn } from '@/utils/classnames'
import BlockIcon from '../block-icon'
import {
useChecklist,
useNodesInteractions,
} from '../hooks'
type WorkflowChecklistProps = {
disabled: boolean
@ -65,7 +63,7 @@ const WorkflowChecklist = ({
return (
<PortalToFollowElem
placement='bottom-end'
placement="bottom-end"
offset={{
mainAxis: 12,
crossAxis: 4,
@ -89,35 +87,38 @@ const WorkflowChecklist = ({
</div>
{
!!needWarningNodes.length && (
<div className='absolute -right-1.5 -top-1.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full border border-gray-100 bg-[#F79009] text-[11px] font-semibold text-white'>
<div className="absolute -right-1.5 -top-1.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full border border-gray-100 bg-[#F79009] text-[11px] font-semibold text-white">
{needWarningNodes.length}
</div>
)
}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[12]'>
<PortalToFollowElemContent className="z-[12]">
<div
className='w-[420px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg'
className="w-[420px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg"
style={{
maxHeight: 'calc(2 / 3 * 100vh)',
}}
>
<div className='text-md sticky top-0 z-[1] flex h-[44px] items-center bg-components-panel-bg pl-4 pr-3 pt-3 font-semibold text-text-primary'>
<div className='grow'>{t('workflow.panel.checklist')}{needWarningNodes.length ? `(${needWarningNodes.length})` : ''}</div>
<div className="text-md sticky top-0 z-[1] flex h-[44px] items-center bg-components-panel-bg pl-4 pr-3 pt-3 font-semibold text-text-primary">
<div className="grow">
{t('workflow.panel.checklist')}
{needWarningNodes.length ? `(${needWarningNodes.length})` : ''}
</div>
<div
className='flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center'
className="flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center"
onClick={() => setOpen(false)}
>
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
</div>
</div>
<div className='pb-2'>
<div className="pb-2">
{
!!needWarningNodes.length && (
<>
<div className='px-4 pt-1 text-xs text-text-tertiary'>{t('workflow.panel.checklistTip')}</div>
<div className='px-4 py-2'>
<div className="px-4 pt-1 text-xs text-text-tertiary">{t('workflow.panel.checklistTip')}</div>
<div className="px-4 py-2">
{
needWarningNodes.map(node => (
<div
@ -128,22 +129,22 @@ const WorkflowChecklist = ({
)}
onClick={() => handleChecklistItemClick(node)}
>
<div className='flex h-9 items-center p-2 text-xs font-medium text-text-secondary'>
<div className="flex h-9 items-center p-2 text-xs font-medium text-text-secondary">
<BlockIcon
type={node.type as BlockEnum}
className='mr-1.5'
className="mr-1.5"
toolIcon={node.toolIcon}
/>
<span className='grow truncate'>
<span className="grow truncate">
{node.title}
</span>
{
(showGoTo && node.canNavigate && !node.disableGoTo) && (
<div className='flex h-4 w-[60px] shrink-0 items-center justify-center gap-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100'>
<span className='whitespace-nowrap text-xs font-medium leading-4 text-primary-600'>
<div className="flex h-4 w-[60px] shrink-0 items-center justify-center gap-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<span className="whitespace-nowrap text-xs font-medium leading-4 text-primary-600">
{t('workflow.panel.goTo')}
</span>
<IconR className='h-3.5 w-3.5 text-primary-600' />
<IconR className="h-3.5 w-3.5 text-primary-600" />
</div>
)
}
@ -156,9 +157,9 @@ const WorkflowChecklist = ({
>
{
node.unConnected && (
<div className='px-3 py-1 first:pt-1.5 last:pb-1.5'>
<div className='flex text-xs leading-4 text-text-tertiary'>
<Warning className='mr-2 mt-[2px] h-3 w-3 text-[#F79009]' />
<div className="px-3 py-1 first:pt-1.5 last:pb-1.5">
<div className="flex text-xs leading-4 text-text-tertiary">
<Warning className="mr-2 mt-[2px] h-3 w-3 text-[#F79009]" />
{t('workflow.common.needConnectTip')}
</div>
</div>
@ -166,9 +167,9 @@ const WorkflowChecklist = ({
}
{
node.errorMessage && (
<div className='px-3 py-1 first:pt-1.5 last:pb-1.5'>
<div className='flex text-xs leading-4 text-text-tertiary'>
<Warning className='mr-2 mt-[2px] h-3 w-3 text-[#F79009]' />
<div className="px-3 py-1 first:pt-1.5 last:pb-1.5">
<div className="flex text-xs leading-4 text-text-tertiary">
<Warning className="mr-2 mt-[2px] h-3 w-3 text-[#F79009]" />
{node.errorMessage}
</div>
</div>
@ -184,8 +185,8 @@ const WorkflowChecklist = ({
}
{
!needWarningNodes.length && (
<div className='mx-4 mb-3 rounded-lg bg-components-panel-bg py-4 text-center text-xs text-text-tertiary'>
<ChecklistSquare className='mx-auto mb-[5px] h-8 w-8 text-text-quaternary' />
<div className="mx-4 mb-3 rounded-lg bg-components-panel-bg py-4 text-center text-xs text-text-tertiary">
<ChecklistSquare className="mx-auto mb-[5px] h-8 w-8 text-text-quaternary" />
{t('workflow.panel.checklistResolved')}
</div>
)

View File

@ -1,7 +1,7 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { useStore } from '@/app/components/workflow/store'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import useTimestamp from '@/hooks/use-timestamp'
const EditingTitle = () => {
@ -18,11 +18,13 @@ const EditingTitle = () => {
{
!!draftUpdatedAt && (
<>
{t('workflow.common.autoSaved')} {formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')}
{t('workflow.common.autoSaved')}
{' '}
{formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')}
</>
)
}
<span className='mx-1 flex items-center'>·</span>
<span className="mx-1 flex items-center">·</span>
{
publishedAt
? `${t('workflow.common.published')} ${formatTimeFromNow(publishedAt)}`
@ -31,7 +33,7 @@ const EditingTitle = () => {
{
isSyncingWorkflowDraft && (
<>
<span className='mx-1 flex items-center'>·</span>
<span className="mx-1 flex items-center">·</span>
{t('workflow.common.syncingData')}
</>
)

View File

@ -1,10 +1,10 @@
import { memo } from 'react'
import Button from '@/app/components/base/button'
import { Env } from '@/app/components/base/icons/src/vender/line/others'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
import { useStore } from '@/app/components/workflow/store'
import useTheme from '@/hooks/use-theme'
import { cn } from '@/utils/classnames'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
const EnvButton = ({ disabled }: { disabled: boolean }) => {
const { theme } = useTheme()
@ -29,11 +29,11 @@ const EnvButton = ({ disabled }: { disabled: boolean }) => {
'p-2',
theme === 'dark' && showEnvPanel && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm',
)}
variant='ghost'
variant="ghost"
disabled={disabled}
onClick={handleClick}
>
<Env className='h-4 w-4 text-components-button-secondary-text' />
<Env className="h-4 w-4 text-components-button-secondary-text" />
</Button>
)
}

View File

@ -1,10 +1,10 @@
import { memo } from 'react'
import Button from '@/app/components/base/button'
import { GlobalVariable } from '@/app/components/base/icons/src/vender/line/others'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
import { useStore } from '@/app/components/workflow/store'
import useTheme from '@/hooks/use-theme'
import { cn } from '@/utils/classnames'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
const GlobalVariableButton = ({ disabled }: { disabled: boolean }) => {
const { theme } = useTheme()
@ -31,9 +31,9 @@ const GlobalVariableButton = ({ disabled }: { disabled: boolean }) => {
)}
disabled={disabled}
onClick={handleClick}
variant='ghost'
variant="ghost"
>
<GlobalVariable className='h-4 w-4 text-components-button-secondary-text' />
<GlobalVariable className="h-4 w-4 text-components-button-secondary-text" />
</Button>
)
}

View File

@ -1,26 +1,26 @@
import type { StartNodeType } from '../nodes/start/types'
import type { RunAndHistoryProps } from './run-and-history'
import {
useCallback,
} from 'react'
import { useNodes } from 'reactflow'
import {
useStore,
useWorkflowStore,
} from '../store'
import type { StartNodeType } from '../nodes/start/types'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
import Divider from '../../base/divider'
import {
useNodesInteractions,
useNodesReadOnly,
useWorkflowRun,
} from '../hooks'
import Divider from '../../base/divider'
import type { RunAndHistoryProps } from './run-and-history'
import RunAndHistory from './run-and-history'
import {
useStore,
useWorkflowStore,
} from '../store'
import EditingTitle from './editing-title'
import EnvButton from './env-button'
import VersionHistoryButton from './version-history-button'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
import ScrollToSelectedNodeButton from './scroll-to-selected-node-button'
import GlobalVariableButton from './global-variable-button'
import RunAndHistory from './run-and-history'
import ScrollToSelectedNodeButton from './scroll-to-selected-node-button'
import VersionHistoryButton from './version-history-button'
export type HeaderInNormalProps = {
components?: {
@ -64,18 +64,18 @@ const HeaderInNormal = ({
}, [workflowStore, handleBackupDraft, selectedNode, handleNodeSelect, setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel, setShowChatVariablePanel, setShowGlobalVariablePanel])
return (
<div className='flex w-full items-center justify-between'>
<div className="flex w-full items-center justify-between">
<div>
<EditingTitle />
</div>
<div>
<ScrollToSelectedNodeButton />
</div>
<div className='flex items-center gap-2'>
<div className="flex items-center gap-2">
{components?.left}
<Divider type='vertical' className='mx-auto h-3.5' />
<Divider type="vertical" className="mx-auto h-3.5" />
<RunAndHistory {...runAndHistoryProps} />
<div className='shrink-0 cursor-pointer rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs backdrop-blur-[10px]'>
<div className="shrink-0 cursor-pointer rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs backdrop-blur-[10px]">
{components?.chatVariableTrigger}
<EnvButton disabled={nodesReadOnly} />
<GlobalVariableButton disabled={nodesReadOnly} />

View File

@ -1,8 +1,18 @@
import { RiHistoryLine } from '@remixicon/react'
import {
useCallback,
} from 'react'
import { RiHistoryLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import useTheme from '@/hooks/use-theme'
import { useInvalidAllLastRun } from '@/service/use-workflow'
import { cn } from '@/utils/classnames'
import Toast from '../../base/toast'
import {
useNodesSyncDraft,
useWorkflowRun,
} from '../hooks'
import { useHooksStore } from '../hooks-store'
import {
useStore,
useWorkflowStore,
@ -10,17 +20,7 @@ import {
import {
WorkflowVersion,
} from '../types'
import {
useNodesSyncDraft,
useWorkflowRun,
} from '../hooks'
import Toast from '../../base/toast'
import RestoringTitle from './restoring-title'
import Button from '@/app/components/base/button'
import { useInvalidAllLastRun } from '@/service/use-workflow'
import { useHooksStore } from '../hooks-store'
import useTheme from '@/hooks/use-theme'
import { cn } from '@/utils/classnames'
export type HeaderInRestoringProps = {
onRestoreSettled?: () => void
@ -80,11 +80,11 @@ const HeaderInRestoring = ({
<div>
<RestoringTitle />
</div>
<div className=' flex items-center justify-end gap-x-2'>
<div className=" flex items-center justify-end gap-x-2">
<Button
onClick={handleRestore}
disabled={!currentVersion || currentVersion.version === WorkflowVersion.Draft}
variant='primary'
variant="primary"
className={cn(
theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm',
)}
@ -98,9 +98,9 @@ const HeaderInRestoring = ({
theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm',
)}
>
<div className='flex items-center gap-x-0.5'>
<RiHistoryLine className='h-4 w-4' />
<span className='px-0.5'>{t('workflow.common.exitVersions')}</span>
<div className="flex items-center gap-x-0.5">
<RiHistoryLine className="h-4 w-4" />
<span className="px-0.5">{t('workflow.common.exitVersions')}</span>
</div>
</Button>
</div>

View File

@ -1,19 +1,19 @@
import type { ViewHistoryProps } from './view-history'
import {
useCallback,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
useWorkflowStore,
} from '../store'
import Button from '@/app/components/base/button'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
import Divider from '../../base/divider'
import {
useWorkflowRun,
} from '../hooks'
import Divider from '../../base/divider'
import {
useWorkflowStore,
} from '../store'
import RunningTitle from './running-title'
import type { ViewHistoryProps } from './view-history'
import ViewHistory from './view-history'
import Button from '@/app/components/base/button'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
export type HeaderInHistoryProps = {
viewHistoryProps?: ViewHistoryProps
@ -38,14 +38,14 @@ const HeaderInHistory = ({
<div>
<RunningTitle />
</div>
<div className='flex items-center space-x-2'>
<div className="flex items-center space-x-2">
<ViewHistory {...viewHistoryProps} withText />
<Divider type='vertical' className='mx-auto h-3.5' />
<Divider type="vertical" className="mx-auto h-3.5" />
<Button
variant='primary'
variant="primary"
onClick={handleGoBackToEdit}
>
<ArrowNarrowLeft className='mr-1 h-4 w-4' />
<ArrowNarrowLeft className="mr-1 h-4 w-4" />
{t('workflow.common.goBackToEdit')}
</Button>
</div>

View File

@ -1,13 +1,13 @@
import type { HeaderInNormalProps } from './header-in-normal'
import type { HeaderInRestoringProps } from './header-in-restoring'
import type { HeaderInHistoryProps } from './header-in-view-history'
import dynamic from 'next/dynamic'
import { usePathname } from 'next/navigation'
import {
useWorkflowMode,
} from '../hooks'
import type { HeaderInNormalProps } from './header-in-normal'
import HeaderInNormal from './header-in-normal'
import type { HeaderInHistoryProps } from './header-in-view-history'
import type { HeaderInRestoringProps } from './header-in-restoring'
import { useStore } from '../store'
import dynamic from 'next/dynamic'
import HeaderInNormal from './header-in-normal'
const HeaderInHistory = dynamic(() => import('./header-in-view-history'), {
ssr: false,
@ -38,9 +38,9 @@ const Header = ({
return (
<div
className='absolute left-0 top-7 z-10 flex h-0 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3'
className="absolute left-0 top-7 z-10 flex h-0 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3"
>
{(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas && <div className='h-14 w-[52px]' />}
{(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas && <div className="h-14 w-[52px]" />}
{
normal && (
<HeaderInNormal

View File

@ -1,9 +1,9 @@
import { memo, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import useTimestamp from '@/hooks/use-timestamp'
import { useStore } from '../store'
import { WorkflowVersion } from '../types'
import useTimestamp from '@/hooks/use-timestamp'
const RestoringTitle = () => {
const { t } = useTranslation()
@ -20,16 +20,16 @@ const RestoringTitle = () => {
}, [currentVersion, t, isDraft])
return (
<div className='flex flex-col gap-y-0.5'>
<div className='flex items-center gap-x-1'>
<span className='system-sm-semibold text-text-primary'>
<div className="flex flex-col gap-y-0.5">
<div className="flex items-center gap-x-1">
<span className="system-sm-semibold text-text-primary">
{versionName}
</span>
<span className='system-2xs-medium-uppercase rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-1 py-0.5 text-text-accent-secondary'>
<span className="system-2xs-medium-uppercase rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-1 py-0.5 text-text-accent-secondary">
{t('workflow.common.viewOnly')}
</span>
</div>
<div className='system-xs-regular flex h-4 items-center gap-x-1 text-text-tertiary'>
<div className="system-xs-regular flex h-4 items-center gap-x-1 text-text-tertiary">
{
currentVersion && (
<>

View File

@ -1,17 +1,17 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import type { ViewHistoryProps } from './view-history'
import {
RiPlayLargeLine,
} from '@remixicon/react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
import {
useNodesReadOnly,
useWorkflowStartRun,
} from '../hooks'
import type { ViewHistoryProps } from './view-history'
import ViewHistory from './view-history'
import Checklist from './checklist'
import { cn } from '@/utils/classnames'
import RunMode from './run-mode'
import ViewHistory from './view-history'
const PreviewMode = memo(() => {
const { t } = useTranslation()
@ -25,7 +25,7 @@ const PreviewMode = memo(() => {
)}
onClick={() => handleWorkflowStartRunInChatflow()}
>
<RiPlayLargeLine className='mr-1 h-4 w-4' />
<RiPlayLargeLine className="mr-1 h-4 w-4" />
{t('workflow.common.debugAndPreview')}
</div>
)
@ -56,7 +56,7 @@ const RunAndHistory = ({
const { RunMode: CustomRunMode } = components || {}
return (
<div className='flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs'>
<div className="flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs">
{
showRunButton && (
CustomRunMode ? <CustomRunMode text={runButtonText} /> : <RunMode text={runButtonText} />
@ -65,7 +65,7 @@ const RunAndHistory = ({
{
showPreviewButton && <PreviewMode />
}
<div className='mx-0.5 h-3.5 w-[1px] bg-divider-regular'></div>
<div className="mx-0.5 h-3.5 w-[1px] bg-divider-regular"></div>
<ViewHistory {...viewHistoryProps} />
<Checklist disabled={nodesReadOnly} />
</div>

View File

@ -1,18 +1,20 @@
import React, { useCallback, useEffect, useRef } from 'react'
import type { TestRunMenuRef, TriggerOption } from './test-run-menu'
import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
import * as React from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { trackEvent } from '@/app/components/base/amplitude'
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { useToastContext } from '@/app/components/base/toast'
import { useWorkflowRun, useWorkflowRunValidation, useWorkflowStartRun } from '@/app/components/workflow/hooks'
import { useStore } from '@/app/components/workflow/store'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { cn } from '@/utils/classnames'
import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
import TestRunMenu, { type TestRunMenuRef, type TriggerOption, TriggerType } from './test-run-menu'
import { useToastContext } from '@/app/components/base/toast'
import { trackEvent } from '@/app/components/base/amplitude'
import TestRunMenu, { TriggerType } from './test-run-menu'
type RunModeProps = {
text?: string
@ -112,57 +114,57 @@ const RunMode = ({
})
return (
<div className='flex items-center gap-x-px'>
<div className="flex items-center gap-x-px">
{
isRunning
? (
<button
type='button'
className={cn(
'system-xs-medium flex h-7 cursor-not-allowed items-center gap-x-1 rounded-l-md bg-state-accent-hover px-1.5 text-text-accent',
)}
disabled={true}
>
<RiLoader2Line className='mr-1 size-4 animate-spin' />
{isListening ? t('workflow.common.listening') : t('workflow.common.running')}
</button>
)
: (
<TestRunMenu
ref={testRunMenuRef}
options={dynamicOptions}
onSelect={handleTriggerSelect}
>
<div
<button
type="button"
className={cn(
'system-xs-medium flex h-7 cursor-pointer items-center gap-x-1 rounded-md px-1.5 text-text-accent hover:bg-state-accent-hover',
'system-xs-medium flex h-7 cursor-not-allowed items-center gap-x-1 rounded-l-md bg-state-accent-hover px-1.5 text-text-accent',
)}
style={{ userSelect: 'none' }}
disabled={true}
>
<RiPlayLargeLine className='mr-1 size-4' />
{text ?? t('workflow.common.run')}
<div className='system-kbd flex items-center gap-x-0.5 text-text-tertiary'>
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
{getKeyboardKeyNameBySystem('alt')}
</div>
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
R
<RiLoader2Line className="mr-1 size-4 animate-spin" />
{isListening ? t('workflow.common.listening') : t('workflow.common.running')}
</button>
)
: (
<TestRunMenu
ref={testRunMenuRef}
options={dynamicOptions}
onSelect={handleTriggerSelect}
>
<div
className={cn(
'system-xs-medium flex h-7 cursor-pointer items-center gap-x-1 rounded-md px-1.5 text-text-accent hover:bg-state-accent-hover',
)}
style={{ userSelect: 'none' }}
>
<RiPlayLargeLine className="mr-1 size-4" />
{text ?? t('workflow.common.run')}
<div className="system-kbd flex items-center gap-x-0.5 text-text-tertiary">
<div className="flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray">
{getKeyboardKeyNameBySystem('alt')}
</div>
<div className="flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray">
R
</div>
</div>
</div>
</div>
</TestRunMenu>
)
</TestRunMenu>
)
}
{
isRunning && (
<button
type='button'
type="button"
className={cn(
'flex size-7 items-center justify-center rounded-r-md bg-state-accent-active',
)}
onClick={handleStop}
>
<StopCircle className='size-4 text-text-accent' />
<StopCircle className="size-4 text-text-accent" />
</button>
)
}

View File

@ -1,9 +1,9 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
import { useIsChatMode } from '../hooks'
import { useStore } from '../store'
import { formatWorkflowRunIdentifier } from '../utils'
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
const RunningTitle = () => {
const { t } = useTranslation()
@ -11,11 +11,11 @@ const RunningTitle = () => {
const historyWorkflowData = useStore(s => s.historyWorkflowData)
return (
<div className='flex h-[18px] items-center text-xs text-gray-500'>
<ClockPlay className='mr-1 h-3 w-3 text-gray-500' />
<div className="flex h-[18px] items-center text-xs text-gray-500">
<ClockPlay className="mr-1 h-3 w-3 text-gray-500" />
<span>{isChatMode ? `Test Chat${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}` : `Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}</span>
<span className='mx-1'>·</span>
<span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'>
<span className="mx-1">·</span>
<span className="ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600">
{t('workflow.common.viewOnly')}
</span>
</div>

View File

@ -1,10 +1,10 @@
import type { FC } from 'react'
import { useCallback } from 'react'
import { useNodes } from 'reactflow'
import { useTranslation } from 'react-i18next'
import type { CommonNodeType } from '../types'
import { scrollToWorkflowNode } from '../utils/node-navigation'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import { cn } from '@/utils/classnames'
import { scrollToWorkflowNode } from '../utils/node-navigation'
const ScrollToSelectedNodeButton: FC = () => {
const { t } = useTranslation()
@ -12,7 +12,8 @@ const ScrollToSelectedNodeButton: FC = () => {
const selectedNode = nodes.find(node => node.data.selected)
const handleScrollToSelectedNode = useCallback(() => {
if (!selectedNode) return
if (!selectedNode)
return
scrollToWorkflowNode(selectedNode.id)
}, [selectedNode])

View File

@ -1,10 +1,9 @@
import type { MouseEvent, MouseEventHandler, ReactElement } from 'react'
import {
type MouseEvent,
type MouseEventHandler,
type ReactElement,
cloneElement,
forwardRef,
isValidElement,
useCallback,
useEffect,
useImperativeHandle,
@ -158,14 +157,14 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({
return (
<div
key={option.id}
className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
className="system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover"
onClick={() => handleSelect(option)}
>
<div className='flex min-w-0 flex-1 items-center'>
<div className='flex h-6 w-6 shrink-0 items-center justify-center'>
<div className="flex min-w-0 flex-1 items-center">
<div className="flex h-6 w-6 shrink-0 items-center justify-center">
{option.icon}
</div>
<span className='ml-2 truncate'>{option.name}</span>
<span className="ml-2 truncate">{option.name}</span>
</div>
{shortcutKey && (
<ShortcutsName keys={[shortcutKey]} className="ml-2" textColor="secondary" />
@ -214,7 +213,7 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
placement="bottom-start"
offset={{ mainAxis: 8, crossAxis: -4 }}
>
<PortalToFollowElemTrigger asChild onClick={() => setOpen(!open)}>
@ -222,16 +221,16 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({
{children}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[12]'>
<div className='w-[284px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg'>
<div className='mb-2 px-3 pt-2 text-sm font-medium text-text-primary'>
<PortalToFollowElemContent className="z-[12]">
<div className="w-[284px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg">
<div className="mb-2 px-3 pt-2 text-sm font-medium text-text-primary">
{t('workflow.common.chooseStartNodeToRun')}
</div>
<div>
{hasUserInput && renderOption(options.userInput!)}
{(hasTriggers || hasRunAll) && hasUserInput && (
<div className='mx-3 my-1 h-px bg-divider-subtle' />
<div className="mx-3 my-1 h-px bg-divider-subtle" />
)}
{hasRunAll && renderOption(options.runAll!)}

View File

@ -1,18 +1,18 @@
import type { FC } from 'react'
import { memo, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowGoBackLine,
RiArrowGoForwardFill,
} from '@remixicon/react'
import { memo, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history'
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
import { cn } from '@/utils/classnames'
import Divider from '../../base/divider'
import TipPopup from '../operator/tip-popup'
import { useWorkflowHistoryStore } from '../workflow-history-store'
import Divider from '../../base/divider'
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history'
import { cn } from '@/utils/classnames'
export type UndoRedoProps = { handleUndo: () => void; handleRedo: () => void }
export type UndoRedoProps = { handleUndo: () => void, handleRedo: () => void }
const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => {
const { t } = useTranslation()
const { store } = useWorkflowHistoryStore()
@ -31,34 +31,34 @@ const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => {
const { nodesReadOnly } = useNodesReadOnly()
return (
<div className='flex items-center space-x-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg backdrop-blur-[5px]'>
<div className="flex items-center space-x-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg backdrop-blur-[5px]">
<TipPopup title={t('workflow.common.undo')!} shortcuts={['ctrl', 'z']}>
<div
data-tooltip-id='workflow.undo'
data-tooltip-id="workflow.undo"
className={
cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
(nodesReadOnly || buttonsDisabled.undo)
&& 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')}
cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', (nodesReadOnly || buttonsDisabled.undo)
&& 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')
}
onClick={() => !nodesReadOnly && !buttonsDisabled.undo && handleUndo()}
>
<RiArrowGoBackLine className='h-4 w-4' />
</div>
</TipPopup >
<TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}>
<div
data-tooltip-id='workflow.redo'
className={
cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
(nodesReadOnly || buttonsDisabled.redo)
&& 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')}
onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()}
>
<RiArrowGoForwardFill className='h-4 w-4' />
<RiArrowGoBackLine className="h-4 w-4" />
</div>
</TipPopup>
<Divider type='vertical' className="mx-0.5 h-3.5" />
<TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}>
<div
data-tooltip-id="workflow.redo"
className={
cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', (nodesReadOnly || buttonsDisabled.redo)
&& 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')
}
onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()}
>
<RiArrowGoForwardFill className="h-4 w-4" />
</div>
</TipPopup>
<Divider type="vertical" className="mx-0.5 h-3.5" />
<ViewWorkflowHistory />
</div >
</div>
)
}

View File

@ -1,12 +1,14 @@
import React, { type FC, useCallback } from 'react'
import type { FC } from 'react'
import { RiHistoryLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useKeyPress } from 'ahooks'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import useTheme from '@/hooks/use-theme'
import { cn } from '@/utils/classnames'
import Button from '../../base/button'
import Tooltip from '../../base/tooltip'
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../utils'
import useTheme from '@/hooks/use-theme'
import { cn } from '@/utils/classnames'
type VersionHistoryButtonProps = {
onClick: () => Promise<unknown> | unknown
@ -17,15 +19,15 @@ const VERSION_HISTORY_SHORTCUT = ['ctrl', '⇧', 'H']
const PopupContent = React.memo(() => {
const { t } = useTranslation()
return (
<div className='flex items-center gap-x-1'>
<div className='system-xs-medium px-0.5 text-text-secondary'>
<div className="flex items-center gap-x-1">
<div className="system-xs-medium px-0.5 text-text-secondary">
{t('workflow.common.versionHistory')}
</div>
<div className='flex items-center gap-x-0.5'>
<div className="flex items-center gap-x-0.5">
{VERSION_HISTORY_SHORTCUT.map(key => (
<span
key={key}
className='system-kbd rounded-[4px] bg-components-kbd-bg-white px-[1px] text-text-tertiary'
className="system-kbd rounded-[4px] bg-components-kbd-bg-white px-[1px] text-text-tertiary"
>
{getKeyboardKeyNameBySystem(key)}
</span>
@ -50,22 +52,24 @@ const VersionHistoryButton: FC<VersionHistoryButtonProps> = ({
handleViewVersionHistory()
}, { exactMatch: true, useCapture: true })
return <Tooltip
popupContent={<PopupContent />}
noDecoration
popupClassName='rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg
shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px] p-1.5'
>
<Button
className={cn(
'p-2',
theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm',
)}
onClick={handleViewVersionHistory}
return (
<Tooltip
popupContent={<PopupContent />}
noDecoration
popupClassName="rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg
shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px] p-1.5"
>
<RiHistoryLine className='h-4 w-4 text-components-button-secondary-text' />
</Button>
</Tooltip>
<Button
className={cn(
'p-2',
theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm',
)}
onClick={handleViewVersionHistory}
>
<RiHistoryLine className="h-4 w-4 text-components-button-secondary-text" />
</Button>
</Tooltip>
)
}
export default VersionHistoryButton

View File

@ -1,56 +1,51 @@
import {
memo,
useState,
} from 'react'
import type { Fetcher } from 'swr'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import { noop } from 'lodash-es'
import {
RiCheckboxCircleLine,
RiCloseLine,
RiErrorWarningLine,
} from '@remixicon/react'
import {
useIsChatMode,
useNodesInteractions,
useWorkflowInteractions,
useWorkflowRun,
} from '../hooks'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { ControlMode, WorkflowRunningStatus } from '../types'
import { formatWorkflowRunIdentifier } from '../utils'
import { cn } from '@/utils/classnames'
memo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import {
ClockPlay,
ClockPlaySlim,
} from '@/app/components/base/icons/src/vender/line/time'
import Loading from '@/app/components/base/loading'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Tooltip from '@/app/components/base/tooltip'
import {
ClockPlay,
ClockPlaySlim,
} from '@/app/components/base/icons/src/vender/line/time'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import Loading from '@/app/components/base/loading'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
import {
useStore,
useWorkflowStore,
} from '@/app/components/workflow/store'
import type { WorkflowRunHistoryResponse } from '@/types/workflow'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { useWorkflowRunHistory } from '@/service/use-workflow'
import { cn } from '@/utils/classnames'
import {
useIsChatMode,
useNodesInteractions,
useWorkflowInteractions,
useWorkflowRun,
} from '../hooks'
import { ControlMode, WorkflowRunningStatus } from '../types'
import { formatWorkflowRunIdentifier } from '../utils'
export type ViewHistoryProps = {
withText?: boolean
onClearLogAndMessageModal?: () => void
historyUrl?: string
historyFetcher?: Fetcher<WorkflowRunHistoryResponse, string>
}
const ViewHistory = ({
withText,
onClearLogAndMessageModal,
historyUrl,
historyFetcher,
}: ViewHistoryProps) => {
const { t } = useTranslation()
const isChatMode = useIsChatMode()
@ -68,11 +63,11 @@ const ViewHistory = ({
const { handleBackupDraft } = useWorkflowRun()
const { closeAllInputFieldPanels } = useInputFieldPanel()
const fetcher = historyFetcher ?? (noop as Fetcher<WorkflowRunHistoryResponse, string>)
const shouldFetchHistory = open && !!historyUrl
const {
data,
isLoading,
} = useSWR((open && historyUrl && historyFetcher) ? historyUrl : null, fetcher)
} = useWorkflowRunHistory(historyUrl, shouldFetchHistory)
return (
(
@ -92,9 +87,10 @@ const ViewHistory = ({
'flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3 shadow-xs',
'cursor-pointer text-[13px] font-medium text-components-button-secondary-text hover:bg-components-button-secondary-bg-hover',
open && 'bg-components-button-secondary-bg-hover',
)}>
)}
>
<ClockPlay
className={'mr-1 h-4 w-4'}
className="mr-1 h-4 w-4"
/>
{t('workflow.common.showRunHistory')}
</div>
@ -117,40 +113,40 @@ const ViewHistory = ({
)
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[12]'>
<PortalToFollowElemContent className="z-[12]">
<div
className='ml-2 flex w-[240px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
className="ml-2 flex w-[240px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl"
style={{
maxHeight: 'calc(2 / 3 * 100vh)',
}}
>
<div className='sticky top-0 flex items-center justify-between bg-components-panel-bg px-4 pt-3 text-base font-semibold text-text-primary'>
<div className='grow'>{t('workflow.common.runHistory')}</div>
<div className="sticky top-0 flex items-center justify-between bg-components-panel-bg px-4 pt-3 text-base font-semibold text-text-primary">
<div className="grow">{t('workflow.common.runHistory')}</div>
<div
className='flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center'
className="flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center"
onClick={() => {
onClearLogAndMessageModal?.()
setOpen(false)
}}
>
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
</div>
</div>
{
isLoading && (
<div className='flex h-10 items-center justify-center'>
<div className="flex h-10 items-center justify-center">
<Loading />
</div>
)
}
{
!isLoading && (
<div className='p-2'>
<div className="p-2">
{
!data?.data.length && (
<div className='py-12'>
<ClockPlaySlim className='mx-auto mb-2 h-8 w-8 text-text-quaternary' />
<div className='text-center text-[13px] text-text-quaternary'>
<div className="py-12">
<ClockPlaySlim className="mx-auto mb-2 h-8 w-8 text-text-quaternary" />
<div className="text-center text-[13px] text-text-quaternary">
{t('workflow.common.notRunning')}
</div>
</div>
@ -180,17 +176,17 @@ const ViewHistory = ({
>
{
!isChatMode && item.status === WorkflowRunningStatus.Stopped && (
<AlertTriangle className='mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F79009]' />
<AlertTriangle className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F79009]" />
)
}
{
!isChatMode && item.status === WorkflowRunningStatus.Failed && (
<RiErrorWarningLine className='mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F04438]' />
<RiErrorWarningLine className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F04438]" />
)
}
{
!isChatMode && item.status === WorkflowRunningStatus.Succeeded && (
<RiCheckboxCircleLine className='mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#12B76A]' />
<RiCheckboxCircleLine className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#12B76A]" />
)
}
<div>
@ -202,8 +198,11 @@ const ViewHistory = ({
>
{`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at)}`}
</div>
<div className='flex items-center text-xs leading-[18px] text-text-tertiary'>
{item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
<div className="flex items-center text-xs leading-[18px] text-text-tertiary">
{item.created_by_account?.name}
{' '}
·
{formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
</div>
</div>
</div>

View File

@ -1,30 +1,30 @@
import type { WorkflowHistoryState } from '../workflow-history-store'
import {
RiCloseLine,
RiHistoryLine,
} from '@remixicon/react'
import {
memo,
useCallback,
useMemo,
useState,
} from 'react'
import {
RiCloseLine,
RiHistoryLine,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow'
import { useStoreApi } from 'reactflow'
import {
useNodesReadOnly,
useWorkflowHistory,
} from '../hooks'
import TipPopup from '../operator/tip-popup'
import type { WorkflowHistoryState } from '../workflow-history-store'
import Divider from '../../base/divider'
import { useShallow } from 'zustand/react/shallow'
import { useStore as useAppStore } from '@/app/components/app/store'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { useStore as useAppStore } from '@/app/components/app/store'
import { cn } from '@/utils/classnames'
import Divider from '../../base/divider'
import {
useNodesReadOnly,
useWorkflowHistory,
} from '../hooks'
import TipPopup from '../operator/tip-popup'
type ChangeHistoryEntry = {
label: string
@ -96,10 +96,12 @@ const ViewWorkflowHistory = () => {
index: reverse ? list.length - 1 - index - startIndex : index - startIndex,
state: {
...state,
workflowHistoryEventMeta: state.workflowHistoryEventMeta ? {
...state.workflowHistoryEventMeta,
nodeTitle: state.workflowHistoryEventMeta.nodeTitle || targetTitle,
} : undefined,
workflowHistoryEventMeta: state.workflowHistoryEventMeta
? {
...state.workflowHistoryEventMeta,
nodeTitle: state.workflowHistoryEventMeta.nodeTitle || targetTitle,
}
: undefined,
},
}
}).filter(Boolean)
@ -127,7 +129,7 @@ const ViewWorkflowHistory = () => {
return (
(
<PortalToFollowElem
placement='bottom-end'
placement="bottom-end"
offset={{
mainAxis: 4,
crossAxis: 131,
@ -141,9 +143,8 @@ const ViewWorkflowHistory = () => {
>
<div
className={
cn('flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
open && 'bg-state-accent-active text-text-accent',
nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')}
cn('flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', open && 'bg-state-accent-active text-text-accent', nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')
}
onClick={() => {
if (nodesReadOnly)
return
@ -151,110 +152,115 @@ const ViewWorkflowHistory = () => {
setShowMessageLogModal(false)
}}
>
<RiHistoryLine className='h-4 w-4' />
<RiHistoryLine className="h-4 w-4" />
</div>
</TipPopup>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[12]'>
<PortalToFollowElemContent className="z-[12]">
<div
className='ml-2 flex min-w-[240px] max-w-[360px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl backdrop-blur-[5px]'
className="ml-2 flex min-w-[240px] max-w-[360px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl backdrop-blur-[5px]"
>
<div className='sticky top-0 flex items-center justify-between px-4 pt-3'>
<div className='system-mg-regular grow text-text-secondary'>{t('workflow.changeHistory.title')}</div>
<div className="sticky top-0 flex items-center justify-between px-4 pt-3">
<div className="system-mg-regular grow text-text-secondary">{t('workflow.changeHistory.title')}</div>
<div
className='flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center'
className="flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center"
onClick={() => {
setCurrentLogItem()
setShowMessageLogModal(false)
setOpen(false)
}}
>
<RiCloseLine className='h-4 w-4 text-text-secondary' />
<RiCloseLine className="h-4 w-4 text-text-secondary" />
</div>
</div>
<div
className="overflow-y-auto p-2"
style={{
maxHeight: 'calc(1 / 2 * 100vh)',
}}
>
{
!calculateChangeList.statesCount && (
<div className="py-12">
<RiHistoryLine className="mx-auto mb-2 h-8 w-8 text-text-tertiary" />
<div className="text-center text-[13px] text-text-tertiary">
{t('workflow.changeHistory.placeholder')}
</div>
</div>
)
}
<div className="flex flex-col">
{
calculateChangeList.futureStates.map((item: ChangeHistoryEntry) => (
<div
key={item?.index}
className={cn(
'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] text-text-secondary hover:bg-state-base-hover',
item?.index === currentHistoryStateIndex && 'bg-state-base-hover',
)}
onClick={() => {
handleSetState(item)
setOpen(false)
}}
>
<div>
<div
className={cn(
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
)}
>
{composeHistoryItemLabel(
item?.state?.workflowHistoryEventMeta?.nodeTitle,
item?.label || t('workflow.changeHistory.sessionStart'),
)}
{' '}
(
{calculateStepLabel(item?.index)}
{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')}
)
</div>
</div>
</div>
))
}
{
calculateChangeList.pastStates.map((item: ChangeHistoryEntry) => (
<div
key={item?.index}
className={cn(
'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] hover:bg-state-base-hover',
item?.index === calculateChangeList.statesCount - 1 && 'bg-state-base-hover',
)}
onClick={() => {
handleSetState(item)
setOpen(false)
}}
>
<div>
<div
className={cn(
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
)}
>
{composeHistoryItemLabel(
item?.state?.workflowHistoryEventMeta?.nodeTitle,
item?.label || t('workflow.changeHistory.sessionStart'),
)}
{' '}
(
{calculateStepLabel(item?.index)}
)
</div>
</div>
</div>
))
}
</div>
</div>
{
(
<div
className='overflow-y-auto p-2'
style={{
maxHeight: 'calc(1 / 2 * 100vh)',
}}
>
{
!calculateChangeList.statesCount && (
<div className='py-12'>
<RiHistoryLine className='mx-auto mb-2 h-8 w-8 text-text-tertiary' />
<div className='text-center text-[13px] text-text-tertiary'>
{t('workflow.changeHistory.placeholder')}
</div>
</div>
)
}
<div className='flex flex-col'>
{
calculateChangeList.futureStates.map((item: ChangeHistoryEntry) => (
<div
key={item?.index}
className={cn(
'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] text-text-secondary hover:bg-state-base-hover',
item?.index === currentHistoryStateIndex && 'bg-state-base-hover',
)}
onClick={() => {
handleSetState(item)
setOpen(false)
}}
>
<div>
<div
className={cn(
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
)}
>
{composeHistoryItemLabel(
item?.state?.workflowHistoryEventMeta?.nodeTitle,
item?.label || t('workflow.changeHistory.sessionStart'),
)} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
</div>
</div>
</div>
))
}
{
calculateChangeList.pastStates.map((item: ChangeHistoryEntry) => (
<div
key={item?.index}
className={cn(
'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] hover:bg-state-base-hover',
item?.index === calculateChangeList.statesCount - 1 && 'bg-state-base-hover',
)}
onClick={() => {
handleSetState(item)
setOpen(false)
}}
>
<div>
<div
className={cn(
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
)}
>
{composeHistoryItemLabel(
item?.state?.workflowHistoryEventMeta?.nodeTitle,
item?.label || t('workflow.changeHistory.sessionStart'),
)} ({calculateStepLabel(item?.index)})
</div>
</div>
</div>
))
}
</div>
</div>
)
}
{
!!calculateChangeList.statesCount && (
<div className='px-0.5'>
<Divider className='m-0' />
<div className="px-0.5">
<Divider className="m-0" />
<div
className={cn(
'my-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] text-text-secondary',
@ -278,7 +284,7 @@ const ViewWorkflowHistory = () => {
</div>
)
}
<div className="w-[240px] px-3 py-2 text-xs text-text-tertiary" >
<div className="w-[240px] px-3 py-2 text-xs text-text-tertiary">
<div className="mb-1 flex h-[22px] items-center font-medium uppercase">{t('workflow.changeHistory.hint')}</div>
<div className="mb-1 leading-[18px] text-text-tertiary">{t('workflow.changeHistory.hintText')}</div>
</div>

View File

@ -1,10 +1,10 @@
import { memo } from 'react'
import { useViewport } from 'reactflow'
import { useStore } from '../store'
import type {
HelpLineHorizontalPosition,
HelpLineVerticalPosition,
} from './types'
import { memo } from 'react'
import { useViewport } from 'reactflow'
import { useStore } from '../store'
const HelpLineHorizontal = memo(({
top,
@ -15,7 +15,7 @@ const HelpLineHorizontal = memo(({
return (
<div
className='absolute z-[9] h-px bg-primary-300'
className="absolute z-[9] h-px bg-primary-300"
style={{
top: top * zoom + y,
left: left * zoom + x,
@ -35,7 +35,7 @@ const HelpLineVertical = memo(({
return (
<div
className='absolute z-[9] w-[1px] bg-primary-300'
className="absolute z-[9] w-[1px] bg-primary-300"
style={{
top: top * zoom + y,
left: left * zoom + x,

View File

@ -1,3 +1,4 @@
import type { Shape } from './store'
import {
createContext,
useEffect,
@ -7,7 +8,6 @@ import { useStore } from 'reactflow'
import {
createHooksStore,
} from './store'
import type { Shape } from './store'
type HooksStore = ReturnType<typeof createHooksStore>
export const HooksStoreContext = createContext<HooksStore | null | undefined>(null)

View File

@ -1,26 +1,24 @@
import { useContext } from 'react'
import type { FileUpload } from '../../base/features/types'
import type {
BlockEnum,
Node,
NodeDefault,
ToolWithProvider,
ValueSelector,
} from '@/app/components/workflow/types'
import type { IOtherOptions } from '@/service/base'
import type { SchemaTypeDefinition } from '@/service/use-common'
import type { FlowType } from '@/types/common'
import type { VarInInspect } from '@/types/workflow'
import {
noop,
} from 'lodash-es'
} from 'es-toolkit/compat'
import { useContext } from 'react'
import {
useStore as useZustandStore,
} from 'zustand'
import { createStore } from 'zustand/vanilla'
import { HooksStoreContext } from './provider'
import type {
BlockEnum,
NodeDefault,
ToolWithProvider,
} from '@/app/components/workflow/types'
import type { IOtherOptions } from '@/service/base'
import type { VarInInspect } from '@/types/workflow'
import type {
Node,
ValueSelector,
} from '@/app/components/workflow/types'
import type { FlowType } from '@/types/common'
import type { FileUpload } from '../../base/features/types'
import type { SchemaTypeDefinition } from '@/service/use-common'
export type AvailableNodesMetaData = {
nodes: NodeDefault[]
@ -32,7 +30,7 @@ export type CommonHooksFnMap = {
callback?: {
onSuccess?: () => void
onError?: () => void
onSettled?: () => void,
onSettled?: () => void
},
) => Promise<void>
syncWorkflowDraftWhenPageClose: () => void
@ -50,7 +48,7 @@ export type CommonHooksFnMap = {
handleWorkflowTriggerPluginRunInWorkflow: (nodeId?: string) => void
handleWorkflowRunAllTriggersInWorkflow: (nodeIds: string[]) => void
availableNodesMetaData?: AvailableNodesMetaData
getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string; traceUrl: string }
getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string, traceUrl: string }
exportCheck?: () => Promise<void>
handleExportDSL?: (include?: boolean, flowId?: string) => Promise<void>
fetchInspectVars: (params: { passInVars?: boolean, vars?: VarInInspect[], passedInAllPluginInfoList?: Record<string, ToolWithProvider[]>, passedInSchemaTypeDefinitions?: SchemaTypeDefinition[] }) => Promise<void>

View File

@ -1,26 +1,26 @@
export * from './use-auto-generate-webhook-url'
export * from './use-available-blocks'
export * from './use-checklist'
export * from './use-DSL'
export * from './use-edges-interactions'
export * from './use-inspect-vars-crud'
export * from './use-node-data-update'
export * from './use-nodes-interactions'
export * from './use-nodes-sync-draft'
export * from './use-workflow'
export * from './use-workflow-run'
export * from './use-checklist'
export * from './use-selection-interactions'
export * from './use-panel-interactions'
export * from './use-workflow-start-run'
export * from './use-nodes-layout'
export * from './use-workflow-history'
export * from './use-workflow-variables'
export * from './use-nodes-meta-data'
export * from './use-nodes-sync-draft'
export * from './use-panel-interactions'
export * from './use-selection-interactions'
export * from './use-serial-async-callback'
export * from './use-set-workflow-vars-with-value'
export * from './use-shortcuts'
export * from './use-tool-icon'
export * from './use-workflow'
export * from './use-workflow-history'
export * from './use-workflow-interactions'
export * from './use-workflow-mode'
export * from './use-nodes-meta-data'
export * from './use-available-blocks'
export * from './use-workflow-refresh-draft'
export * from './use-tool-icon'
export * from './use-DSL'
export * from './use-inspect-vars-crud'
export * from './use-set-workflow-vars-with-value'
export * from './use-workflow-run'
export * from './use-workflow-search'
export * from './use-auto-generate-webhook-url'
export * from './use-serial-async-callback'
export * from './use-workflow-start-run'
export * from './use-workflow-variables'

View File

@ -1,5 +1,5 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { BlockEnum } from '@/app/components/workflow/types'

View File

@ -23,8 +23,9 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean)
const availablePrevBlocks = useMemo(() => {
if (!nodeType || nodeType === BlockEnum.Start || nodeType === BlockEnum.DataSource
|| nodeType === BlockEnum.TriggerPlugin || nodeType === BlockEnum.TriggerWebhook
|| nodeType === BlockEnum.TriggerSchedule)
|| nodeType === BlockEnum.TriggerSchedule) {
return []
}
return availableNodesType
}, [availableNodesType, nodeType])

View File

@ -1,10 +1,9 @@
import {
useCallback,
useMemo,
useRef,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useEdges, useStoreApi } from 'reactflow'
import type { AgentNodeType } from '../nodes/agent/types'
import type { DataSourceNodeType } from '../nodes/data-source/types'
import type { KnowledgeBaseNodeType } from '../nodes/knowledge-base/types'
import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types'
import type { ToolNodeType } from '../nodes/tool/types'
import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types'
import type {
CommonEdgeType,
CommonNodeType,
@ -12,51 +11,52 @@ import type {
Node,
ValueSelector,
} from '../types'
import { BlockEnum } from '../types'
import type { Emoji } from '@/app/components/tools/types'
import type { DataSet } from '@/models/datasets'
import {
useCallback,
useMemo,
useRef,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useEdges, useStoreApi } from 'reactflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useToastContext } from '@/app/components/base/toast'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { MAX_TREE_DEPTH } from '@/config'
import { useGetLanguage } from '@/context/i18n'
import { fetchDatasets } from '@/service/datasets'
import { useStrategyProviders } from '@/service/use-strategy'
import {
useAllBuiltInTools,
useAllCustomTools,
useAllWorkflowTools,
} from '@/service/use-tools'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import { AppModeEnum } from '@/types/app'
import {
CUSTOM_NODE,
} from '../constants'
import { useDatasetsDetailStore } from '../datasets-detail-store/store'
import {
useGetToolIcon,
useNodesMetaData,
} from '../hooks'
import { getNodeUsedVars, isSpecialVar } from '../nodes/_base/components/variable/utils'
import {
useStore,
useWorkflowStore,
} from '../store'
import { BlockEnum } from '../types'
import {
getDataSourceCheckParams,
getToolCheckParams,
getValidTreeNodes,
} from '../utils'
import { getTriggerCheckParams } from '../utils/trigger'
import {
CUSTOM_NODE,
} from '../constants'
import {
useGetToolIcon,
useNodesMetaData,
} from '../hooks'
import type { ToolNodeType } from '../nodes/tool/types'
import type { DataSourceNodeType } from '../nodes/data-source/types'
import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types'
import { useToastContext } from '@/app/components/base/toast'
import { useGetLanguage } from '@/context/i18n'
import type { AgentNodeType } from '../nodes/agent/types'
import { useStrategyProviders } from '@/service/use-strategy'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import { useDatasetsDetailStore } from '../datasets-detail-store/store'
import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types'
import type { DataSet } from '@/models/datasets'
import { fetchDatasets } from '@/service/datasets'
import { MAX_TREE_DEPTH } from '@/config'
import useNodesAvailableVarList, { useGetNodesAvailableVarList } from './use-nodes-available-var-list'
import { getNodeUsedVars, isSpecialVar } from '../nodes/_base/components/variable/utils'
import type { Emoji } from '@/app/components/tools/types'
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { KnowledgeBaseNodeType } from '../nodes/knowledge-base/types'
import {
useAllBuiltInTools,
useAllCustomTools,
useAllWorkflowTools,
} from '@/service/use-tools'
import { useStore as useAppStore } from '@/app/components/app/store'
import { AppModeEnum } from '@/types/app'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
export type ChecklistItem = {
id: string
@ -237,8 +237,8 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
list.push({
id: `${type}-need-added`,
type,
title: t(`workflow.blocks.${type}`),
errorMessage: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}`) }),
title: t(`workflow.blocks.${type}` as any) as string,
errorMessage: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}` as any) as string }),
canNavigate: false,
})
}
@ -409,7 +409,7 @@ export const useChecklistBeforePublish = () => {
const type = isRequiredNodesType[i]
if (!filteredNodes.find(node => node.data.type === type)) {
notify({ type: 'error', message: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}`) }) })
notify({ type: 'error', message: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}` as any) as string }) })
return false
}
}

View File

@ -1,12 +1,12 @@
import type { ModelConfig, VisionSetting } from '@/app/components/workflow/types'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useIsChatMode } from './use-workflow'
import type { ModelConfig, VisionSetting } from '@/app/components/workflow/types'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import {
ModelFeatureEnum,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { Resolution } from '@/types/app'
import { useIsChatMode } from './use-workflow'
type Payload = {
enabled: boolean

View File

@ -1,13 +1,15 @@
import type { TestRunOptions, TriggerOption } from '../header/test-run-menu'
import type { CommonNodeType } from '../types'
import { useMemo } from 'react'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { useTranslation } from 'react-i18next'
import { BlockEnum, type CommonNodeType } from '../types'
import { getWorkflowEntryNode } from '../utils/workflow-entry'
import { type TestRunOptions, type TriggerOption, TriggerType } from '../header/test-run-menu'
import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow'
import BlockIcon from '../block-icon'
import { useStore } from '../store'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import BlockIcon from '../block-icon'
import { TriggerType } from '../header/test-run-menu'
import { useStore } from '../store'
import { BlockEnum } from '../types'
import { getWorkflowEntryNode } from '../utils/workflow-entry'
export const useDynamicTestRunOptions = (): TestRunOptions => {
const { t } = useTranslation()
@ -25,7 +27,8 @@ export const useDynamicTestRunOptions = (): TestRunOptions => {
for (const node of nodes) {
const nodeData = node.data as CommonNodeType
if (!nodeData?.type) continue
if (!nodeData?.type)
continue
if (nodeData.type === BlockEnum.Start) {
userInput = {
@ -35,7 +38,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => {
icon: (
<BlockIcon
type={BlockEnum.Start}
size='md'
size="md"
/>
),
nodeId: node.id,
@ -50,7 +53,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => {
icon: (
<BlockIcon
type={BlockEnum.TriggerSchedule}
size='md'
size="md"
/>
),
nodeId: node.id,
@ -65,7 +68,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => {
icon: (
<BlockIcon
type={BlockEnum.TriggerWebhook}
size='md'
size="md"
/>
),
nodeId: node.id,
@ -83,7 +86,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => {
const icon = (
<BlockIcon
type={BlockEnum.TriggerPlugin}
size='md'
size="md"
toolIcon={triggerIcon}
/>
)
@ -109,7 +112,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => {
icon: (
<BlockIcon
type={BlockEnum.Start}
size='md'
size="md"
/>
),
nodeId: startNode.id,
@ -122,18 +125,20 @@ export const useDynamicTestRunOptions = (): TestRunOptions => {
.map(trigger => trigger.nodeId)
.filter((nodeId): nodeId is string => Boolean(nodeId))
const runAll: TriggerOption | undefined = triggerNodeIds.length > 1 ? {
id: 'run-all',
type: TriggerType.All,
name: t('workflow.common.runAllTriggers'),
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-lg border-[0.5px] border-white/2 bg-util-colors-purple-purple-500 text-white shadow-md">
<TriggerAll className="h-4.5 w-4.5" />
</div>
),
relatedNodeIds: triggerNodeIds,
enabled: true,
} : undefined
const runAll: TriggerOption | undefined = triggerNodeIds.length > 1
? {
id: 'run-all',
type: TriggerType.All,
name: t('workflow.common.runAllTriggers'),
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-lg border-[0.5px] border-white/2 bg-util-colors-purple-purple-500 text-white shadow-md">
<TriggerAll className="h-4.5 w-4.5" />
</div>
),
relatedNodeIds: triggerNodeIds,
enabled: true,
}
: undefined
return {
userInput,

View File

@ -1,5 +1,5 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
export const useEdgesInteractionsWithoutSync = () => {

View File

@ -1,19 +1,19 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import type {
EdgeMouseHandler,
OnEdgesChange,
} from 'reactflow'
import {
useStoreApi,
} from 'reactflow'
import type {
Node,
} from '../types'
import { produce } from 'immer'
import { useCallback } from 'react'
import {
useStoreApi,
} from 'reactflow'
import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../utils'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { useNodesReadOnly } from './use-workflow'
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history'
export const useEdgesInteractions = () => {
const store = useStoreApi()

View File

@ -1,22 +1,21 @@
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { useStoreApi } from 'reactflow'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { Node } from '@/app/components/workflow/types'
import { fetchAllInspectVars } from '@/service/workflow'
import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow'
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
import type { FlowType } from '@/types/common'
import useMatchSchemaType, { getMatchedSchemaType } from '../nodes/_base/components/variable/use-match-schema-type'
import { toNodeOutputVars } from '../nodes/_base/components/variable/utils'
import type { Node, ToolWithProvider } from '@/app/components/workflow/types'
import type { SchemaTypeDefinition } from '@/service/use-common'
import type { FlowType } from '@/types/common'
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import {
useAllBuiltInTools,
useAllCustomTools,
useAllMCPTools,
useAllWorkflowTools,
} from '@/service/use-tools'
import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow'
import { fetchAllInspectVars } from '@/service/workflow'
import useMatchSchemaType, { getMatchedSchemaType } from '../nodes/_base/components/variable/use-match-schema-type'
import { toNodeOutputVars } from '../nodes/_base/components/variable/utils'
type Params = {
flowType: FlowType
@ -99,9 +98,9 @@ export const useSetWorkflowVarsWithValue = ({
}
const fetchInspectVars = useCallback(async (params: {
passInVars?: boolean,
vars?: VarInInspect[],
passedInAllPluginInfoList?: Record<string, ToolWithProvider[]>,
passInVars?: boolean
vars?: VarInInspect[]
passedInAllPluginInfoList?: Record<string, ToolWithProvider[]>
passedInSchemaTypeDefinitions?: SchemaTypeDefinition[]
}) => {
const { passInVars, vars, passedInAllPluginInfoList, passedInSchemaTypeDefinitions } = params

View File

@ -1,8 +1,8 @@
import type { Node } from '../types'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import type { Node } from '../types'
import { BlockEnum, isTriggerNode } from '../types'
import { useWorkflowStore } from '../store'
import { BlockEnum, isTriggerNode } from '../types'
// Entry node (Start/Trigger) wrapper offsets
// The EntryNodeContainer adds a wrapper with status indicator above the actual node

View File

@ -1,29 +1,28 @@
import { fetchNodeInspectVars } from '@/service/workflow'
import { useWorkflowStore } from '@/app/components/workflow/store'
import type { ValueSelector } from '@/app/components/workflow/types'
import type { Node, ValueSelector } from '@/app/components/workflow/types'
import type { SchemaTypeDefinition } from '@/service/use-common'
import type { FlowType } from '@/types/common'
import type { VarInInspect } from '@/types/workflow'
import { VarInInspectType } from '@/types/workflow'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync'
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
import {
isConversationVar,
isENV,
isSystemVar,
toNodeOutputVars,
} from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { produce } from 'immer'
import type { Node } from '@/app/components/workflow/types'
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync'
import type { FlowType } from '@/types/common'
import { useWorkflowStore } from '@/app/components/workflow/store'
import useFLow from '@/service/use-flow'
import { useStoreApi } from 'reactflow'
import type { SchemaTypeDefinition } from '@/service/use-common'
import {
useAllBuiltInTools,
useAllCustomTools,
useAllMCPTools,
useAllWorkflowTools,
} from '@/service/use-tools'
import { fetchNodeInspectVars } from '@/service/workflow'
import { VarInInspectType } from '@/types/workflow'
type Params = {
flowId: string

View File

@ -1,11 +1,11 @@
import { useStore } from '../store'
import { produce } from 'immer'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import {
useConversationVarValues,
useSysVarValues,
} from '@/service/use-workflow'
import { FlowType } from '@/types/common'
import { produce } from 'immer'
import { useStore } from '../store'
import { BlockEnum } from '../types'
const varsAppendStartNodeKeys = ['query', 'files']
@ -16,19 +16,19 @@ const useInspectVarsCrud = () => {
const { data: conversationVars } = useConversationVarValues(configsMap?.flowType, !isRagPipeline ? configsMap?.flowId : '')
const { data: allSystemVars } = useSysVarValues(configsMap?.flowType, !isRagPipeline ? configsMap?.flowId : '')
const { varsAppendStartNode, systemVars } = (() => {
if(allSystemVars?.length === 0)
if (allSystemVars?.length === 0)
return { varsAppendStartNode: [], systemVars: [] }
const varsAppendStartNode = allSystemVars?.filter(({ name }) => varsAppendStartNodeKeys.includes(name)) || []
const systemVars = allSystemVars?.filter(({ name }) => !varsAppendStartNodeKeys.includes(name)) || []
return { varsAppendStartNode, systemVars }
})()
const nodesWithInspectVars = (() => {
if(!partOfNodesWithInspectVars || partOfNodesWithInspectVars.length === 0)
if (!partOfNodesWithInspectVars || partOfNodesWithInspectVars.length === 0)
return []
const nodesWithInspectVars = produce(partOfNodesWithInspectVars, (draft) => {
draft.forEach((nodeWithVars) => {
if(nodeWithVars.nodeType === BlockEnum.Start)
if (nodeWithVars.nodeType === BlockEnum.Start)
nodeWithVars.vars = [...nodeWithVars.vars, ...varsAppendStartNode]
})
})

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import { useStoreApi } from 'reactflow'
import type { SyncCallback } from './use-nodes-sync-draft'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { useNodesReadOnly } from './use-workflow'

View File

@ -1,9 +1,10 @@
import { useCallback, useMemo } from 'react'
import { BlockEnum, type CommonNodeType } from '../types'
import type { DataSourceNodeType } from '../nodes/data-source/types'
import type { ToolNodeType } from '../nodes/tool/types'
import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types'
import type { DataSourceNodeType } from '../nodes/data-source/types'
import type { CommonNodeType } from '../types'
import { useCallback, useMemo } from 'react'
import { CollectionType } from '@/app/components/tools/types'
import { useInvalidDataSourceList } from '@/service/use-pipeline'
import {
useAllBuiltInTools,
useAllCustomTools,
@ -15,9 +16,9 @@ import {
useAllTriggerPlugins,
useInvalidateAllTriggerPlugins,
} from '@/service/use-triggers'
import { useInvalidDataSourceList } from '@/service/use-pipeline'
import { useStore } from '../store'
import { canFindTool } from '@/utils'
import { useStore } from '../store'
import { BlockEnum } from '../types'
type InstallationState = {
isChecking: boolean

View File

@ -1,10 +1,12 @@
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import { useCallback } from 'react'
import {
useIsChatMode,
useWorkflow,
useWorkflowVariables,
} from '@/app/components/workflow/hooks'
import { BlockEnum, type Node, type NodeOutPutVar, type ValueSelector, type Var } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
type Params = {
onlyLeafNodeVar?: boolean
hideEnv?: boolean

View File

@ -1,5 +1,5 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { NodeRunningStatus } from '../types'
@ -31,7 +31,7 @@ export const useNodesInteractionsWithoutSync = () => {
const nodes = getNodes()
const newNodes = produce(nodes, (draft) => {
draft.forEach((node) => {
if(node.data._runningStatus === NodeRunningStatus.Succeeded)
if (node.data._runningStatus === NodeRunningStatus.Succeeded)
node.data._runningStatus = undefined
})
})

View File

@ -1,7 +1,4 @@
import type { MouseEvent } from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import type {
NodeDragHandler,
NodeMouseHandler,
@ -10,16 +7,21 @@ import type {
OnConnectStart,
ResizeParamsWithDirection,
} from 'reactflow'
import type { PluginDefaultValue } from '../block-selector/types'
import type { IterationNodeType } from '../nodes/iteration/types'
import type { LoopNodeType } from '../nodes/loop/types'
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
import type { Edge, Node, OnNodeAdd } from '../types'
import type { RAGPipelineVariables } from '@/models/pipeline'
import { produce } from 'immer'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
getConnectedEdges,
getOutgoers,
useReactFlow,
useStoreApi,
} from 'reactflow'
import type { PluginDefaultValue } from '../block-selector/types'
import type { Edge, Node, OnNodeAdd } from '../types'
import { BlockEnum, isTriggerNode } from '../types'
import { useWorkflowStore } from '../store'
import {
CUSTOM_EDGE,
ITERATION_CHILDREN_Z_INDEX,
@ -30,39 +32,37 @@ import {
X_OFFSET,
Y_OFFSET,
} from '../constants'
import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
import { useNodeLoopInteractions } from '../nodes/loop/use-interactions'
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import { useWorkflowStore } from '../store'
import { BlockEnum, isTriggerNode } from '../types'
import {
genNewNodeTitleFromOld,
generateNewNode,
genNewNodeTitleFromOld,
getNestedNodePosition,
getNodeCustomTypeByNodeDataType,
getNodesConnectedSourceOrTargetHandleIdsMap,
getTopLeftNodePosition,
} from '../utils'
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import type { IterationNodeType } from '../nodes/iteration/types'
import type { LoopNodeType } from '../nodes/loop/types'
import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
import { useNodeLoopInteractions } from '../nodes/loop/use-interactions'
import { useWorkflowHistoryStore } from '../workflow-history-store'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { useAutoGenerateWebhookUrl } from './use-auto-generate-webhook-url'
import { useHelpline } from './use-helpline'
import useInspectVarsCrud from './use-inspect-vars-crud'
import { useNodesMetaData } from './use-nodes-meta-data'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import {
useNodesReadOnly,
useWorkflow,
useWorkflowReadOnly,
} from './use-workflow'
import {
WorkflowHistoryEvent,
useWorkflowHistory,
WorkflowHistoryEvent,
} from './use-workflow-history'
import { useNodesMetaData } from './use-nodes-meta-data'
import { useAutoGenerateWebhookUrl } from './use-auto-generate-webhook-url'
import type { RAGPipelineVariables } from '@/models/pipeline'
import useInspectVarsCrud from './use-inspect-vars-crud'
import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
// Entry node deletion restriction has been removed to allow empty workflows
@ -89,8 +89,8 @@ export const useNodesInteractions = () => {
const { handleNodeLoopChildDrag, handleNodeLoopChildrenCopy }
= useNodeLoopInteractions()
const dragNodeStartPosition = useRef({ x: 0, y: 0 } as {
x: number;
y: number;
x: number
y: number
})
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
@ -101,19 +101,22 @@ export const useNodesInteractions = () => {
(_, node) => {
workflowStore.setState({ nodeAnimation: false })
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
if (
node.type === CUSTOM_ITERATION_START_NODE
|| node.type === CUSTOM_NOTE_NODE
)
) {
return
}
if (
node.type === CUSTOM_LOOP_START_NODE
|| node.type === CUSTOM_NOTE_NODE
)
) {
return
}
dragNodeStartPosition.current = {
x: node.position.x,
@ -125,11 +128,14 @@ export const useNodesInteractions = () => {
const handleNodeDrag = useCallback<NodeDragHandler>(
(e, node: Node) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
if (node.type === CUSTOM_ITERATION_START_NODE) return
if (node.type === CUSTOM_ITERATION_START_NODE)
return
if (node.type === CUSTOM_LOOP_START_NODE) return
if (node.type === CUSTOM_LOOP_START_NODE)
return
const { getNodes, setNodes } = store.getState()
e.stopPropagation()
@ -211,7 +217,8 @@ export const useNodesInteractions = () => {
const { setHelpLineHorizontal, setHelpLineVertical }
= workflowStore.getState()
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { x, y } = dragNodeStartPosition.current
if (!(x === node.position.x && y === node.position.y)) {
@ -237,19 +244,22 @@ export const useNodesInteractions = () => {
const handleNodeEnter = useCallback<NodeMouseHandler>(
(_, node) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
if (
node.type === CUSTOM_NOTE_NODE
|| node.type === CUSTOM_ITERATION_START_NODE
)
) {
return
}
if (
node.type === CUSTOM_LOOP_START_NODE
|| node.type === CUSTOM_NOTE_NODE
)
) {
return
}
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()
@ -257,7 +267,8 @@ export const useNodesInteractions = () => {
= workflowStore.getState()
if (connectingNodePayload) {
if (connectingNodePayload.nodeId === node.id) return
if (connectingNodePayload.nodeId === node.id)
return
const connectingNode: Node = nodes.find(
n => n.id === connectingNodePayload.nodeId,
)!
@ -288,8 +299,9 @@ export const useNodesInteractions = () => {
|| connectingNode.data.type === BlockEnum.VariableAggregator)
&& node.data.type !== BlockEnum.IfElse
&& node.data.type !== BlockEnum.QuestionClassifier
)
) {
n.data._isEntering = true
}
})
})
setNodes(newNodes)
@ -300,7 +312,8 @@ export const useNodesInteractions = () => {
connectedEdges.forEach((edge) => {
const currentEdge = draft.find(e => e.id === edge.id)
if (currentEdge) currentEdge.data._connectedNodeIsHovering = true
if (currentEdge)
currentEdge.data._connectedNodeIsHovering = true
})
})
setEdges(newEdges)
@ -310,19 +323,22 @@ export const useNodesInteractions = () => {
const handleNodeLeave = useCallback<NodeMouseHandler>(
(_, node) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
if (
node.type === CUSTOM_NOTE_NODE
|| node.type === CUSTOM_ITERATION_START_NODE
)
) {
return
}
if (
node.type === CUSTOM_NOTE_NODE
|| node.type === CUSTOM_LOOP_START_NODE
)
) {
return
}
const { setEnteringNodePayload } = workflowStore.getState()
setEnteringNodePayload(undefined)
@ -356,11 +372,13 @@ export const useNodesInteractions = () => {
const nodes = getNodes()
const selectedNode = nodes.find(node => node.data.selected)
if (!cancelSelection && selectedNode?.id === nodeId) return
if (!cancelSelection && selectedNode?.id === nodeId)
return
const newNodes = produce(nodes, (draft) => {
draft.forEach((node) => {
if (node.id === nodeId) node.data.selected = !cancelSelection
if (node.id === nodeId)
node.data.selected = !cancelSelection
else node.data.selected = false
})
})
@ -395,10 +413,14 @@ export const useNodesInteractions = () => {
const handleNodeClick = useCallback<NodeMouseHandler>(
(_, node) => {
if (node.type === CUSTOM_ITERATION_START_NODE) return
if (node.type === CUSTOM_LOOP_START_NODE) return
if (node.data.type === BlockEnum.DataSourceEmpty) return
if (node.data._pluginInstallLocked) return
if (node.type === CUSTOM_ITERATION_START_NODE)
return
if (node.type === CUSTOM_LOOP_START_NODE)
return
if (node.data.type === BlockEnum.DataSourceEmpty)
return
if (node.data._pluginInstallLocked)
return
handleNodeSelect(node.id)
},
[handleNodeSelect],
@ -406,21 +428,25 @@ export const useNodesInteractions = () => {
const handleNodeConnect = useCallback<OnConnect>(
({ source, sourceHandle, target, targetHandle }) => {
if (source === target) return
if (getNodesReadOnly()) return
if (source === target)
return
if (getNodesReadOnly())
return
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()
const targetNode = nodes.find(node => node.id === target!)
const sourceNode = nodes.find(node => node.id === source!)
if (targetNode?.parentId !== sourceNode?.parentId) return
if (targetNode?.parentId !== sourceNode?.parentId)
return
if (
sourceNode?.type === CUSTOM_NOTE_NODE
|| targetNode?.type === CUSTOM_NOTE_NODE
)
) {
return
}
if (
edges.find(
@ -430,8 +456,9 @@ export const useNodesInteractions = () => {
&& edge.target === target
&& edge.targetHandle === targetHandle,
)
)
) {
return
}
const parendNode = nodes.find(node => node.id === targetNode?.parentId)
const isInIteration
@ -497,20 +524,24 @@ export const useNodesInteractions = () => {
const handleNodeConnectStart = useCallback<OnConnectStart>(
(_, { nodeId, handleType, handleId }) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
if (nodeId && handleType) {
const { setConnectingNodePayload } = workflowStore.getState()
const { getNodes } = store.getState()
const node = getNodes().find(n => n.id === nodeId)!
if (node.type === CUSTOM_NOTE_NODE) return
if (node.type === CUSTOM_NOTE_NODE)
return
if (
node.data.type === BlockEnum.VariableAggregator
|| node.data.type === BlockEnum.VariableAssigner
)
if (handleType === 'target') return
) {
if (handleType === 'target')
return
}
setConnectingNodePayload({
nodeId,
@ -525,7 +556,8 @@ export const useNodesInteractions = () => {
const handleNodeConnectEnd = useCallback<OnConnectEnd>(
(e: any) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const {
connectingNodePayload,
@ -547,7 +579,8 @@ export const useNodesInteractions = () => {
const toNode = nodes.find(n => n.id === enteringNodePayload.nodeId)!
const toParentNode = nodes.find(n => n.id === toNode.parentId)
if (fromNode.parentId !== toNode.parentId) return
if (fromNode.parentId !== toNode.parentId)
return
const { x, y } = screenToFlowPosition({ x: e.x, y: e.y })
@ -602,7 +635,8 @@ export const useNodesInteractions = () => {
const handleNodeDelete = useCallback(
(nodeId: string) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { getNodes, setNodes, edges, setEdges } = store.getState()
@ -610,13 +644,15 @@ export const useNodesInteractions = () => {
const currentNodeIndex = nodes.findIndex(node => node.id === nodeId)
const currentNode = nodes[currentNodeIndex]
if (!currentNode) return
if (!currentNode)
return
if (
nodesMetaDataMap?.[currentNode.data.type as BlockEnum]?.metaData
.isUndeletable
)
) {
return
}
deleteNodeInspectorVars(nodeId)
if (currentNode.data.type === BlockEnum.Iteration) {
@ -706,7 +742,8 @@ export const useNodesInteractions = () => {
if (ragPipelineVariables && setRagPipelineVariables) {
const newRagPipelineVariables: RAGPipelineVariables = []
ragPipelineVariables.forEach((variable) => {
if (variable.belong_to_node_id === id) return
if (variable.belong_to_node_id === id)
return
newRagPipelineVariables.push(variable)
})
setRagPipelineVariables(newRagPipelineVariables)
@ -781,7 +818,8 @@ export const useNodesInteractions = () => {
},
{ prevNodeId, prevNodeSourceHandle, nextNodeId, nextNodeTargetHandle },
) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()
@ -935,9 +973,11 @@ export const useNodesInteractions = () => {
})
draft.push(newNode)
if (newIterationStartNode) draft.push(newIterationStartNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode) draft.push(newLoopStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
if (
@ -964,7 +1004,8 @@ export const useNodesInteractions = () => {
_connectedNodeIsSelected: false,
}
})
if (newEdge) draft.push(newEdge)
if (newEdge)
draft.push(newEdge)
})
setNodes(newNodes)
@ -976,8 +1017,9 @@ export const useNodesInteractions = () => {
if (
nodeType !== BlockEnum.IfElse
&& nodeType !== BlockEnum.QuestionClassifier
)
) {
newNode.data._connectedSourceHandleIds = [sourceHandle]
}
newNode.data._connectedTargetHandleIds = []
newNode.position = {
x: nextNode.position.x,
@ -1101,8 +1143,10 @@ export const useNodesInteractions = () => {
}
})
draft.push(newNode)
if (newIterationStartNode) draft.push(newIterationStartNode)
if (newLoopStartNode) draft.push(newLoopStartNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
if (newEdge) {
const newEdges = produce(edges, (draft) => {
@ -1192,10 +1236,10 @@ export const useNodesInteractions = () => {
= nodes.find(node => node.id === nextNode.parentId) || null
const isNextNodeInIteration
= !!nextNodeParentNode
&& nextNodeParentNode.data.type === BlockEnum.Iteration
&& nextNodeParentNode.data.type === BlockEnum.Iteration
const isNextNodeInLoop
= !!nextNodeParentNode
&& nextNodeParentNode.data.type === BlockEnum.Loop
&& nextNodeParentNode.data.type === BlockEnum.Loop
if (
nodeType !== BlockEnum.IfElse
@ -1274,8 +1318,10 @@ export const useNodesInteractions = () => {
}
})
draft.push(newNode)
if (newIterationStartNode) draft.push(newIterationStartNode)
if (newLoopStartNode) draft.push(newLoopStartNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
setNodes(newNodes)
if (
@ -1303,9 +1349,11 @@ export const useNodesInteractions = () => {
_connectedNodeIsSelected: false,
}
})
if (newPrevEdge) draft.push(newPrevEdge)
if (newPrevEdge)
draft.push(newPrevEdge)
if (newNextEdge) draft.push(newNextEdge)
if (newNextEdge)
draft.push(newNextEdge)
})
setEdges(newEdges)
}
@ -1330,7 +1378,8 @@ export const useNodesInteractions = () => {
sourceHandle: string,
pluginDefaultValue?: PluginDefaultValue,
) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()
@ -1388,8 +1437,10 @@ export const useNodesInteractions = () => {
const index = draft.findIndex(node => node.id === currentNodeId)
draft.splice(index, 1, newCurrentNode)
if (newIterationStartNode) draft.push(newIterationStartNode)
if (newLoopStartNode) draft.push(newLoopStartNode)
if (newIterationStartNode)
draft.push(newIterationStartNode)
if (newLoopStartNode)
draft.push(newLoopStartNode)
})
setNodes(newNodes)
const newEdges = produce(edges, (draft) => {
@ -1443,14 +1494,16 @@ export const useNodesInteractions = () => {
if (
node.type === CUSTOM_NOTE_NODE
|| node.type === CUSTOM_ITERATION_START_NODE
)
) {
return
}
if (
node.type === CUSTOM_NOTE_NODE
|| node.type === CUSTOM_LOOP_START_NODE
)
) {
return
}
e.preventDefault()
const container = document.querySelector('#workflow-container')
@ -1469,7 +1522,8 @@ export const useNodesInteractions = () => {
const handleNodesCopy = useCallback(
(nodeId?: string) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { setClipboardElements } = workflowStore.getState()
@ -1489,15 +1543,19 @@ export const useNodesInteractions = () => {
&& node.data.type !== BlockEnum.KnowledgeBase
&& node.data.type !== BlockEnum.DataSourceEmpty,
)
if (nodeToCopy) setClipboardElements([nodeToCopy])
if (nodeToCopy)
setClipboardElements([nodeToCopy])
}
else {
// If no nodeId is provided, fall back to the current behavior
const bundledNodes = nodes.filter((node) => {
if (!node.data._isBundled) return false
if (node.type === CUSTOM_NOTE_NODE) return true
if (!node.data._isBundled)
return false
if (node.type === CUSTOM_NOTE_NODE)
return true
const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum]
if (metaData.isSingleton) return false
if (metaData.isSingleton)
return false
return !node.data.isInIteration && !node.data.isInLoop
})
@ -1507,20 +1565,24 @@ export const useNodesInteractions = () => {
}
const selectedNode = nodes.find((node) => {
if (!node.data.selected) return false
if (node.type === CUSTOM_NOTE_NODE) return true
if (!node.data.selected)
return false
if (node.type === CUSTOM_NOTE_NODE)
return true
const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum]
return !metaData.isSingleton
})
if (selectedNode) setClipboardElements([selectedNode])
if (selectedNode)
setClipboardElements([selectedNode])
}
},
[getNodesReadOnly, store, workflowStore],
)
const handleNodesPaste = useCallback(() => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { clipboardElements, mousePosition } = workflowStore.getState()
@ -1647,7 +1709,8 @@ export const useNodesInteractions = () => {
nodesToPaste.push(newNode)
if (newChildren.length) nodesToPaste.push(...newChildren)
if (newChildren.length)
nodesToPaste.push(...newChildren)
})
// only handle edge when paste nested block
@ -1691,7 +1754,8 @@ export const useNodesInteractions = () => {
const handleNodesDuplicate = useCallback(
(nodeId?: string) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
handleNodesCopy(nodeId)
handleNodesPaste()
@ -1700,7 +1764,8 @@ export const useNodesInteractions = () => {
)
const handleNodesDelete = useCallback(() => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { getNodes, edges } = store.getState()
@ -1716,18 +1781,21 @@ export const useNodesInteractions = () => {
}
const edgeSelected = edges.some(edge => edge.selected)
if (edgeSelected) return
if (edgeSelected)
return
const selectedNode = nodes.find(
node => node.data.selected,
)
if (selectedNode) handleNodeDelete(selectedNode.id)
if (selectedNode)
handleNodeDelete(selectedNode.id)
}, [store, getNodesReadOnly, handleNodeDelete])
const handleNodeResize = useCallback(
(nodeId: string, params: ResizeParamsWithDirection) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { getNodes, setNodes } = store.getState()
const { x, y, width, height } = params
@ -1752,8 +1820,9 @@ export const useNodesInteractions = () => {
if (
n.position.y + n.height!
> bottomNode.position.y + bottomNode.height!
)
) {
bottomNode = n
}
}
else {
bottomNode = n
@ -1772,8 +1841,9 @@ export const useNodesInteractions = () => {
if (
height
< bottomNode.position.y + bottomNode.height! + paddingMap.bottom
)
) {
return
}
}
const newNodes = produce(nodes, (draft) => {
draft.forEach((n) => {
@ -1796,7 +1866,8 @@ export const useNodesInteractions = () => {
const handleNodeDisconnect = useCallback(
(nodeId: string) => {
if (getNodesReadOnly()) return
if (getNodesReadOnly())
return
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()
@ -1834,13 +1905,15 @@ export const useNodesInteractions = () => {
)
const handleHistoryBack = useCallback(() => {
if (getNodesReadOnly() || getWorkflowReadOnly()) return
if (getNodesReadOnly() || getWorkflowReadOnly())
return
const { setEdges, setNodes } = store.getState()
undo()
const { edges, nodes } = workflowHistoryStore.getState()
if (edges.length === 0 && nodes.length === 0) return
if (edges.length === 0 && nodes.length === 0)
return
setEdges(edges)
setNodes(nodes)
@ -1853,13 +1926,15 @@ export const useNodesInteractions = () => {
])
const handleHistoryForward = useCallback(() => {
if (getNodesReadOnly() || getWorkflowReadOnly()) return
if (getNodesReadOnly() || getWorkflowReadOnly())
return
const { setEdges, setNodes } = store.getState()
redo()
const { edges, nodes } = workflowHistoryStore.getState()
if (edges.length === 0 && nodes.length === 0) return
if (edges.length === 0 && nodes.length === 0)
return
setEdges(edges)
setNodes(nodes)
@ -1874,12 +1949,14 @@ export const useNodesInteractions = () => {
const [isDimming, setIsDimming] = useState(false)
/** Add opacity-30 to all nodes except the nodeId */
const dimOtherNodes = useCallback(() => {
if (isDimming) return
if (isDimming)
return
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()
const selectedNode = nodes.find(n => n.data.selected)
if (!selectedNode) return
if (!selectedNode)
return
setIsDimming(true)
@ -1890,8 +1967,10 @@ export const useNodesInteractions = () => {
const dependencyNodes: Node[] = []
usedVars.forEach((valueSelector) => {
const node = workflowNodes.find(node => node.id === valueSelector?.[0])
if (node)
if (!dependencyNodes.includes(node)) dependencyNodes.push(node)
if (node) {
if (!dependencyNodes.includes(node))
dependencyNodes.push(node)
}
})
const outgoers = getOutgoers(selectedNode as Node, nodes as Node[], edges)
@ -1900,7 +1979,8 @@ export const useNodesInteractions = () => {
const outgoersForNode = getOutgoers(node, nodes as Node[], edges)
outgoersForNode.forEach((item) => {
const existed = outgoers.some(v => v.id === item.id)
if (!existed) outgoers.push(item)
if (!existed)
outgoers.push(item)
})
}
@ -1910,7 +1990,8 @@ export const useNodesInteractions = () => {
const used = usedVars.some(v => v?.[0] === selectedNode.id)
if (used) {
const existed = dependentNodes.some(v => v.id === node.id)
if (!existed) dependentNodes.push(node)
if (!existed)
dependentNodes.push(node)
}
})
@ -1919,7 +2000,8 @@ export const useNodesInteractions = () => {
const newNodes = produce(nodes, (draft) => {
draft.forEach((n) => {
const dimNode = dimNodes.find(v => v.id === n.id)
if (!dimNode) n.data._dimmed = true
if (!dimNode)
n.data._dimmed = true
})
})

View File

@ -1,16 +1,16 @@
import { useCallback } from 'react'
import ELK from 'elkjs/lib/elk.bundled.js'
import {
useReactFlow,
useStoreApi,
} from 'reactflow'
import { cloneDeep } from 'lodash-es'
import type {
Edge,
Node,
} from '../types'
import { useWorkflowStore } from '../store'
import ELK from 'elkjs/lib/elk.bundled.js'
import { cloneDeep } from 'es-toolkit/compat'
import { useCallback } from 'react'
import {
useReactFlow,
useStoreApi,
} from 'reactflow'
import { AUTO_LAYOUT_OFFSET } from '../constants'
import { useWorkflowStore } from '../store'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
const layoutOptions = {

View File

@ -1,17 +1,17 @@
import { useMemo } from 'react'
import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { BlockEnum } from '@/app/components/workflow/types'
import type { Node } from '@/app/components/workflow/types'
import { useMemo } from 'react'
import { CollectionType } from '@/app/components/tools/types'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { useStore } from '@/app/components/workflow/store'
import { canFindTool } from '@/utils'
import { BlockEnum } from '@/app/components/workflow/types'
import { useGetLanguage } from '@/context/i18n'
import {
useAllBuiltInTools,
useAllCustomTools,
useAllWorkflowTools,
} from '@/service/use-tools'
import { canFindTool } from '@/utils'
export const useNodesMetaData = () => {
const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData)

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { useStore } from '../store'
import { useNodesReadOnly } from './use-workflow'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
export type SyncCallback = {
onSuccess?: () => void

View File

@ -1,14 +1,14 @@
import type { MouseEvent } from 'react'
import {
useCallback,
} from 'react'
import { produce } from 'immer'
import type {
OnSelectionChangeFunc,
} from 'reactflow'
import type { Node } from '../types'
import { produce } from 'immer'
import {
useCallback,
} from 'react'
import { useStoreApi } from 'reactflow'
import { useWorkflowStore } from '../store'
import type { Node } from '../types'
export const useSelectionInteractions = () => {
const store = useStoreApi()

View File

@ -1,13 +1,7 @@
import { useReactFlow } from 'reactflow'
import { useKeyPress } from 'ahooks'
import { useCallback, useEffect } from 'react'
import { useReactFlow } from 'reactflow'
import { ZEN_TOGGLE_EVENT } from '@/app/components/goto-anything/actions/commands/zen'
import {
getKeyboardKeyCodeBySystem,
isEventTargetInputArea,
} from '../utils'
import { useWorkflowHistoryStore } from '../workflow-history-store'
import { useWorkflowStore } from '../store'
import {
useEdgesInteractions,
useNodesInteractions,
@ -16,6 +10,12 @@ import {
useWorkflowMoveMode,
useWorkflowOrganize,
} from '.'
import { useWorkflowStore } from '../store'
import {
getKeyboardKeyCodeBySystem,
isEventTargetInputArea,
} from '../utils'
import { useWorkflowHistoryStore } from '../workflow-history-store'
export const useShortcuts = (): void => {
const {

View File

@ -1,9 +1,11 @@
import { useCallback, useMemo } from 'react'
import type { TriggerWithProvider } from '../block-selector/types'
import type { DataSourceNodeType } from '../nodes/data-source/types'
import type { ToolNodeType } from '../nodes/tool/types'
import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types'
import type { Node, ToolWithProvider } from '../types'
import { BlockEnum } from '../types'
import { useStore, useWorkflowStore } from '../store'
import { useCallback, useMemo } from 'react'
import { CollectionType } from '@/app/components/tools/types'
import { canFindTool } from '@/utils'
import useTheme from '@/hooks/use-theme'
import {
useAllBuiltInTools,
useAllCustomTools,
@ -11,11 +13,9 @@ import {
useAllWorkflowTools,
} from '@/service/use-tools'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types'
import type { ToolNodeType } from '../nodes/tool/types'
import type { DataSourceNodeType } from '../nodes/data-source/types'
import type { TriggerWithProvider } from '../block-selector/types'
import useTheme from '@/hooks/use-theme'
import { canFindTool } from '@/utils'
import { useStore, useWorkflowStore } from '../store'
import { BlockEnum } from '../types'
const isTriggerPluginNode = (data: Node['data']): data is PluginTriggerNodeType => data.type === BlockEnum.TriggerPlugin

View File

@ -1,14 +1,15 @@
import type { WorkflowHistoryEventMeta } from '../workflow-history-store'
import { debounce } from 'es-toolkit/compat'
import {
useCallback,
useRef, useState,
useRef,
useState,
} from 'react'
import { debounce } from 'lodash-es'
import { useTranslation } from 'react-i18next'
import {
useStoreApi,
} from 'reactflow'
import { useTranslation } from 'react-i18next'
import { useWorkflowHistoryStore } from '../workflow-history-store'
import type { WorkflowHistoryEventMeta } from '../workflow-history-store'
/**
* All supported Events that create a new history state.

View File

@ -1,16 +1,23 @@
import type { WorkflowDataUpdater } from '../types'
import type { LayoutResult } from '../utils'
import { produce } from 'immer'
import {
useCallback,
} from 'react'
import { useReactFlow, useStoreApi } from 'reactflow'
import { produce } from 'immer'
import { useStore, useWorkflowStore } from '../store'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import {
CUSTOM_NODE,
NODE_LAYOUT_HORIZONTAL_PADDING,
NODE_LAYOUT_VERTICAL_PADDING,
WORKFLOW_DATA_UPDATE,
} from '../constants'
import type { WorkflowDataUpdater } from '../types'
import {
useNodesReadOnly,
useSelectionInteractions,
useWorkflowReadOnly,
} from '../hooks'
import { useStore, useWorkflowStore } from '../store'
import { BlockEnum, ControlMode } from '../types'
import {
getLayoutByDagre,
@ -18,17 +25,10 @@ import {
initialEdges,
initialNodes,
} from '../utils'
import type { LayoutResult } from '../utils'
import {
useNodesReadOnly,
useSelectionInteractions,
useWorkflowReadOnly,
} from '../hooks'
import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync'
import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-without-sync'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history'
export const useWorkflowInteractions = () => {
const workflowStore = useWorkflowStore()
@ -99,8 +99,8 @@ export const useWorkflowOrganize = () => {
const loopAndIterationNodes = nodes.filter(
node => (node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration)
&& !node.parentId
&& node.type === CUSTOM_NODE,
&& !node.parentId
&& node.type === CUSTOM_NODE,
)
const childLayoutEntries = await Promise.all(
@ -119,7 +119,8 @@ export const useWorkflowOrganize = () => {
loopAndIterationNodes.forEach((parentNode) => {
const childLayout = childLayoutsMap[parentNode.id]
if (!childLayout) return
if (!childLayout)
return
const {
bounds,
@ -141,7 +142,7 @@ export const useWorkflowOrganize = () => {
const nodesWithUpdatedSizes = produce(nodes, (draft) => {
draft.forEach((node) => {
if ((node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration)
&& containerSizeChanges[node.id]) {
&& containerSizeChanges[node.id]) {
node.width = containerSizeChanges[node.id].width
node.height = containerSizeChanges[node.id].height
@ -160,7 +161,7 @@ export const useWorkflowOrganize = () => {
const layout = await getLayoutByDagre(nodesWithUpdatedSizes, edges)
// Build layer map for vertical alignment - nodes in the same layer should align
const layerMap = new Map<number, { minY: number; maxHeight: number }>()
const layerMap = new Map<number, { minY: number, maxHeight: number }>()
layout.nodes.forEach((layoutInfo) => {
if (layoutInfo.layer !== undefined) {
const existing = layerMap.get(layoutInfo.layer)

View File

@ -1,17 +1,17 @@
export * from './use-workflow-started'
export * from './use-workflow-finished'
export * from './use-workflow-agent-log'
export * from './use-workflow-failed'
export * from './use-workflow-node-started'
export * from './use-workflow-finished'
export * from './use-workflow-node-finished'
export * from './use-workflow-node-iteration-started'
export * from './use-workflow-node-iteration-next'
export * from './use-workflow-node-human-input-required'
export * from './use-workflow-node-iteration-finished'
export * from './use-workflow-node-loop-started'
export * from './use-workflow-node-loop-next'
export * from './use-workflow-node-iteration-next'
export * from './use-workflow-node-iteration-started'
export * from './use-workflow-node-loop-finished'
export * from './use-workflow-node-loop-next'
export * from './use-workflow-node-loop-started'
export * from './use-workflow-node-retry'
export * from './use-workflow-node-started'
export * from './use-workflow-started'
export * from './use-workflow-suspended'
export * from './use-workflow-text-chunk'
export * from './use-workflow-text-replace'
export * from './use-workflow-agent-log'
export * from './use-workflow-suspended'
export * from './use-workflow-node-human-input-required'

View File

@ -1,6 +1,6 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import type { AgentLogResponse } from '@/types/workflow'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
export const useWorkflowAgentLog = () => {

View File

@ -1,5 +1,5 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'

View File

@ -1,8 +1,8 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import type { WorkflowFinishedResponse } from '@/types/workflow'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { produce } from 'immer'
import { useCallback } from 'react'
import { getFilesInLogs } from '@/app/components/base/file-uploader/utils'
import { useWorkflowStore } from '@/app/components/workflow/store'
export const useWorkflowFinished = () => {
const workflowStore = useWorkflowStore()

View File

@ -1,13 +1,13 @@
import type { NodeFinishedResponse } from '@/types/workflow'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { produce } from 'immer'
import type { NodeFinishedResponse } from '@/types/workflow'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import { useWorkflowStore } from '@/app/components/workflow/store'
import {
BlockEnum,
NodeRunningStatus,
} from '@/app/components/workflow/types'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import { useWorkflowStore } from '@/app/components/workflow/store'
export const useWorkflowNodeFinished = () => {
const store = useStoreApi()

View File

@ -1,10 +1,10 @@
import type { HumanInputRequiredResponse } from '@/types/workflow'
import { produce } from 'immer'
import { useCallback } from 'react'
import {
useStoreApi,
} from 'reactflow'
import { produce } from 'immer'
import { useWorkflowStore } from '@/app/components/workflow/store'
import type { HumanInputRequiredResponse } from '@/types/workflow'
import { NodeRunningStatus } from '@/app/components/workflow/types'
// import { WorkflowRunningStatus } from '@/app/components/workflow/types'

Some files were not shown because too many files have changed in this diff Show More