feat: last run frontend (#21369)
The frontend of feat: Persist Variables for Enhanced Debugging Workflow (#20699). Co-authored-by: jZonG <jzongcode@gmail.com>
This commit is contained in:
parent
10b738a296
commit
1a1bfd4048
122 changed files with 5888 additions and 2061 deletions
|
|
@ -39,16 +39,19 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigge
|
||||||
|
|
||||||
export type IAppInfoProps = {
|
export type IAppInfoProps = {
|
||||||
expand: boolean
|
expand: boolean
|
||||||
|
onlyShowDetail?: boolean
|
||||||
|
openState?: boolean
|
||||||
|
onDetailExpand?: (expand: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppInfo = ({ expand }: IAppInfoProps) => {
|
const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailExpand }: IAppInfoProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const { replace } = useRouter()
|
const { replace } = useRouter()
|
||||||
const { onPlanInfoChanged } = useProviderContext()
|
const { onPlanInfoChanged } = useProviderContext()
|
||||||
const appDetail = useAppStore(state => state.appDetail)
|
const appDetail = useAppStore(state => state.appDetail)
|
||||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(openState)
|
||||||
const [showEditModal, setShowEditModal] = useState(false)
|
const [showEditModal, setShowEditModal] = useState(false)
|
||||||
const [showDuplicateModal, setShowDuplicateModal] = useState(false)
|
const [showDuplicateModal, setShowDuplicateModal] = useState(false)
|
||||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||||
|
|
@ -193,6 +196,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{!onlyShowDetail && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isCurrentWorkspaceEditor)
|
if (isCurrentWorkspaceEditor)
|
||||||
|
|
@ -227,9 +231,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
<ContentDialog
|
<ContentDialog
|
||||||
show={open}
|
show={onlyShowDetail ? openState : open}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => {
|
||||||
|
setOpen(false)
|
||||||
|
onDetailExpand?.(false)
|
||||||
|
}}
|
||||||
className='absolute bottom-2 left-2 top-2 flex w-[420px] flex-col rounded-2xl !p-0'
|
className='absolute bottom-2 left-2 top-2 flex w-[420px] flex-col rounded-2xl !p-0'
|
||||||
>
|
>
|
||||||
<div className='flex shrink-0 flex-col items-start justify-center gap-3 self-stretch p-4'>
|
<div className='flex shrink-0 flex-col items-start justify-center gap-3 self-stretch p-4'>
|
||||||
|
|
@ -258,6 +266,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||||
className='gap-[1px]'
|
className='gap-[1px]'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
onDetailExpand?.(false)
|
||||||
setShowEditModal(true)
|
setShowEditModal(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -270,6 +279,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||||
className='gap-[1px]'
|
className='gap-[1px]'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
onDetailExpand?.(false)
|
||||||
setShowDuplicateModal(true)
|
setShowDuplicateModal(true)
|
||||||
}}>
|
}}>
|
||||||
<RiFileCopy2Line className='h-3.5 w-3.5 text-components-button-secondary-text' />
|
<RiFileCopy2Line className='h-3.5 w-3.5 text-components-button-secondary-text' />
|
||||||
|
|
@ -308,6 +318,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||||
&& <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover'
|
&& <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
onDetailExpand?.(false)
|
||||||
setShowImportDSLModal(true)
|
setShowImportDSLModal(true)
|
||||||
}}>
|
}}>
|
||||||
<RiFileUploadLine className='h-4 w-4 text-text-tertiary' />
|
<RiFileUploadLine className='h-4 w-4 text-text-tertiary' />
|
||||||
|
|
@ -319,6 +330,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||||
&& <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover'
|
&& <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
onDetailExpand?.(false)
|
||||||
setShowSwitchModal(true)
|
setShowSwitchModal(true)
|
||||||
}}>
|
}}>
|
||||||
<RiExchange2Line className='h-4 w-4 text-text-tertiary' />
|
<RiExchange2Line className='h-4 w-4 text-text-tertiary' />
|
||||||
|
|
@ -345,6 +357,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||||
className='gap-0.5'
|
className='gap-0.5'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
onDetailExpand?.(false)
|
||||||
setShowConfirmDelete(true)
|
setShowConfirmDelete(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
125
web/app/components/app-sidebar/app-sidebar-dropdown.tsx
Normal file
125
web/app/components/app-sidebar/app-sidebar-dropdown.tsx
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import {
|
||||||
|
RiEqualizer2Line,
|
||||||
|
RiMenuLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
PortalToFollowElemTrigger,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
import AppIcon from '../base/app-icon'
|
||||||
|
import Divider from '../base/divider'
|
||||||
|
import AppInfo from './app-info'
|
||||||
|
import NavLink from './navLink'
|
||||||
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
|
import type { NavIcon } from './navLink'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
navigation: Array<{
|
||||||
|
name: string
|
||||||
|
href: string
|
||||||
|
icon: NavIcon
|
||||||
|
selectedIcon: NavIcon
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppSidebarDropdown = ({ navigation }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||||
|
const appDetail = useAppStore(state => state.appDetail)
|
||||||
|
const [detailExpand, setDetailExpand] = useState(false)
|
||||||
|
|
||||||
|
const [open, doSetOpen] = useState(false)
|
||||||
|
const openRef = useRef(open)
|
||||||
|
const setOpen = useCallback((v: boolean) => {
|
||||||
|
doSetOpen(v)
|
||||||
|
openRef.current = v
|
||||||
|
}, [doSetOpen])
|
||||||
|
const handleTrigger = useCallback(() => {
|
||||||
|
setOpen(!openRef.current)
|
||||||
|
}, [setOpen])
|
||||||
|
|
||||||
|
if (!appDetail)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='fixed left-2 top-2 z-20'>
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
placement='bottom-start'
|
||||||
|
offset={{
|
||||||
|
mainAxis: -41,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||||
|
<div className={cn('flex cursor-pointer items-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-1 shadow-lg backdrop-blur-sm hover:bg-background-default-hover', open && 'bg-background-default-hover')}>
|
||||||
|
<AppIcon
|
||||||
|
size='small'
|
||||||
|
iconType={appDetail.icon_type}
|
||||||
|
icon={appDetail.icon}
|
||||||
|
background={appDetail.icon_background}
|
||||||
|
imageUrl={appDetail.icon_url}
|
||||||
|
/>
|
||||||
|
<RiMenuLine className='h-4 w-4 text-text-tertiary' />
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[1000]'>
|
||||||
|
<div className={cn('w-[305px] rounded-xl border-[0.5px] border-components-panel-border bg-background-default-subtle shadow-lg')}>
|
||||||
|
<div className='p-2'>
|
||||||
|
<div
|
||||||
|
className={cn('flex flex-col gap-2 rounded-lg p-2 pb-2.5', isCurrentWorkspaceEditor && 'cursor-pointer hover:bg-state-base-hover')}
|
||||||
|
onClick={() => {
|
||||||
|
setDetailExpand(true)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='flex items-center justify-between self-stretch'>
|
||||||
|
<AppIcon
|
||||||
|
size='large'
|
||||||
|
iconType={appDetail.icon_type}
|
||||||
|
icon={appDetail.icon}
|
||||||
|
background={appDetail.icon_background}
|
||||||
|
imageUrl={appDetail.icon_url}
|
||||||
|
/>
|
||||||
|
<div className='flex items-center justify-center rounded-md p-0.5'>
|
||||||
|
<div className='flex h-5 w-5 items-center justify-center'>
|
||||||
|
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col items-start gap-1'>
|
||||||
|
<div className='flex w-full'>
|
||||||
|
<div className='system-md-semibold truncate text-text-secondary'>{appDetail.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className='system-2xs-medium-uppercase text-text-tertiary'>{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='px-4'>
|
||||||
|
<Divider bgStyle='gradient' />
|
||||||
|
</div>
|
||||||
|
<nav className='space-y-0.5 px-3 pb-6 pt-4'>
|
||||||
|
{navigation.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<NavLink key={index} mode='expand' iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
</div>
|
||||||
|
<div className='z-20'>
|
||||||
|
<AppInfo expand onlyShowDetail openState={detailExpand} onDetailExpand={setDetailExpand} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppSidebarDropdown
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
import { useShallow } from 'zustand/react/shallow'
|
import { useShallow } from 'zustand/react/shallow'
|
||||||
import { RiLayoutLeft2Line, RiLayoutRight2Line } from '@remixicon/react'
|
import { RiLayoutLeft2Line, RiLayoutRight2Line } from '@remixicon/react'
|
||||||
import NavLink from './navLink'
|
import NavLink from './navLink'
|
||||||
|
|
@ -6,8 +7,10 @@ import type { NavIcon } from './navLink'
|
||||||
import AppBasic from './basic'
|
import AppBasic from './basic'
|
||||||
import AppInfo from './app-info'
|
import AppInfo from './app-info'
|
||||||
import DatasetInfo from './dataset-info'
|
import DatasetInfo from './dataset-info'
|
||||||
|
import AppSidebarDropdown from './app-sidebar-dropdown'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
export type IAppDetailNavProps = {
|
export type IAppDetailNavProps = {
|
||||||
|
|
@ -39,6 +42,18 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
|
||||||
setAppSiderbarExpand(state === 'expand' ? 'collapse' : 'expand')
|
setAppSiderbarExpand(state === 'expand' ? 'collapse' : 'expand')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // Check if the current path is a workflow canvas & fullscreen
|
||||||
|
const pathname = usePathname()
|
||||||
|
const inWorkflowCanvas = pathname.endsWith('/workflow')
|
||||||
|
const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true'
|
||||||
|
const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize)
|
||||||
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
|
|
||||||
|
eventEmitter?.useSubscription((v: any) => {
|
||||||
|
if (v?.type === 'workflow-canvas-maximize')
|
||||||
|
setHideHeader(v.payload)
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appSidebarExpand) {
|
if (appSidebarExpand) {
|
||||||
localStorage.setItem('app-detail-collapse-or-expand', appSidebarExpand)
|
localStorage.setItem('app-detail-collapse-or-expand', appSidebarExpand)
|
||||||
|
|
@ -46,6 +61,14 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
|
||||||
}
|
}
|
||||||
}, [appSidebarExpand, setAppSiderbarExpand])
|
}, [appSidebarExpand, setAppSiderbarExpand])
|
||||||
|
|
||||||
|
if (inWorkflowCanvas && hideHeader) {
|
||||||
|
return (
|
||||||
|
<div className='flex w-0 shrink-0'>
|
||||||
|
<AppSidebarDropdown navigation={navigation} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@ type Option = {
|
||||||
icon: React.JSX.Element
|
icon: React.JSX.Element
|
||||||
}
|
}
|
||||||
type FileUploaderInAttachmentProps = {
|
type FileUploaderInAttachmentProps = {
|
||||||
|
isDisabled?: boolean
|
||||||
fileConfig: FileUpload
|
fileConfig: FileUpload
|
||||||
}
|
}
|
||||||
const FileUploaderInAttachment = ({
|
const FileUploaderInAttachment = ({
|
||||||
|
isDisabled,
|
||||||
fileConfig,
|
fileConfig,
|
||||||
}: FileUploaderInAttachmentProps) => {
|
}: FileUploaderInAttachmentProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -89,16 +91,18 @@ const FileUploaderInAttachment = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{!isDisabled && (
|
||||||
<div className='flex items-center space-x-1'>
|
<div className='flex items-center space-x-1'>
|
||||||
{options.map(renderOption)}
|
{options.map(renderOption)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className='mt-1 space-y-1'>
|
<div className='mt-1 space-y-1'>
|
||||||
{
|
{
|
||||||
files.map(file => (
|
files.map(file => (
|
||||||
<FileItem
|
<FileItem
|
||||||
key={file.id}
|
key={file.id}
|
||||||
file={file}
|
file={file}
|
||||||
showDeleteAction
|
showDeleteAction={!isDisabled}
|
||||||
showDownloadAction={false}
|
showDownloadAction={false}
|
||||||
onRemove={() => handleRemoveFile(file.id)}
|
onRemove={() => handleRemoveFile(file.id)}
|
||||||
onReUpload={() => handleReUploadFile(file.id)}
|
onReUpload={() => handleReUploadFile(file.id)}
|
||||||
|
|
@ -114,18 +118,20 @@ type FileUploaderInAttachmentWrapperProps = {
|
||||||
value?: FileEntity[]
|
value?: FileEntity[]
|
||||||
onChange: (files: FileEntity[]) => void
|
onChange: (files: FileEntity[]) => void
|
||||||
fileConfig: FileUpload
|
fileConfig: FileUpload
|
||||||
|
isDisabled?: boolean
|
||||||
}
|
}
|
||||||
const FileUploaderInAttachmentWrapper = ({
|
const FileUploaderInAttachmentWrapper = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
fileConfig,
|
fileConfig,
|
||||||
|
isDisabled,
|
||||||
}: FileUploaderInAttachmentWrapperProps) => {
|
}: FileUploaderInAttachmentWrapperProps) => {
|
||||||
return (
|
return (
|
||||||
<FileContextProvider
|
<FileContextProvider
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
>
|
>
|
||||||
<FileUploaderInAttachment fileConfig={fileConfig} />
|
<FileUploaderInAttachment isDisabled={isDisabled} fileConfig={fileConfig} />
|
||||||
</FileContextProvider>
|
</FileContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ export const getProcessedFilesFromResponse = (files: FileResponse[]) => {
|
||||||
transferMethod: fileItem.transfer_method,
|
transferMethod: fileItem.transfer_method,
|
||||||
supportFileType: fileItem.type,
|
supportFileType: fileItem.type,
|
||||||
uploadedId: fileItem.upload_file_id || fileItem.related_id,
|
uploadedId: fileItem.upload_file_id || fileItem.related_id,
|
||||||
url: fileItem.url,
|
url: fileItem.url || fileItem.remote_url,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,30 +9,34 @@ type Item = {
|
||||||
isRight?: boolean
|
isRight?: boolean
|
||||||
icon?: React.ReactNode
|
icon?: React.ReactNode
|
||||||
extra?: React.ReactNode
|
extra?: React.ReactNode
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ITabHeaderProps = {
|
export type ITabHeaderProps = {
|
||||||
items: Item[]
|
items: Item[]
|
||||||
value: string
|
value: string
|
||||||
|
itemClassName?: string
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const TabHeader: FC<ITabHeaderProps> = ({
|
const TabHeader: FC<ITabHeaderProps> = ({
|
||||||
items,
|
items,
|
||||||
value,
|
value,
|
||||||
|
itemClassName,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const renderItem = ({ id, name, icon, extra }: Item) => (
|
const renderItem = ({ id, name, icon, extra, disabled }: Item) => (
|
||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'system-md-semibold relative flex cursor-pointer items-center border-b-2 border-transparent pb-2 pt-2.5',
|
'system-md-semibold relative flex cursor-pointer items-center border-b-2 border-transparent pb-2 pt-2.5',
|
||||||
id === value ? 'border-components-tab-active text-text-primary' : 'text-text-tertiary',
|
id === value ? 'border-components-tab-active text-text-primary' : 'text-text-tertiary',
|
||||||
|
disabled && 'cursor-not-allowed opacity-30',
|
||||||
)}
|
)}
|
||||||
onClick={() => onChange(id)}
|
onClick={() => !disabled && onChange(id)}
|
||||||
>
|
>
|
||||||
{icon || ''}
|
{icon || ''}
|
||||||
<div className='ml-2'>{name}</div>
|
<div className={cn('ml-2', itemClassName)}>{name}</div>
|
||||||
{extra || ''}
|
{extra || ''}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
import React, { useState } from 'react'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import s from './index.module.css'
|
import s from './index.module.css'
|
||||||
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
|
|
||||||
type HeaderWrapperProps = {
|
type HeaderWrapperProps = {
|
||||||
|
|
@ -12,6 +14,19 @@ const HeaderWrapper = ({
|
||||||
}: HeaderWrapperProps) => {
|
}: HeaderWrapperProps) => {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools'].includes(pathname)
|
const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools'].includes(pathname)
|
||||||
|
// // Check if the current path is a workflow canvas & fullscreen
|
||||||
|
const inWorkflowCanvas = pathname.endsWith('/workflow')
|
||||||
|
const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true'
|
||||||
|
const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize)
|
||||||
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
|
|
||||||
|
eventEmitter?.useSubscription((v: any) => {
|
||||||
|
if (v?.type === 'workflow-canvas-maximize')
|
||||||
|
setHideHeader(v.payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hideHeader && inWorkflowCanvas)
|
||||||
|
return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(
|
<div className={classNames(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
|
||||||
|
import { useWorkflowStore } from '../../workflow/store'
|
||||||
|
import { useStoreApi } from 'reactflow'
|
||||||
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
|
import { fetchAllInspectVars } from '@/service/workflow'
|
||||||
|
import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow'
|
||||||
|
import { useNodesInteractionsWithoutSync } from '../../workflow/hooks/use-nodes-interactions-without-sync'
|
||||||
|
const useSetWorkflowVarsWithValue = () => {
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
const { setNodesWithInspectVars, appId } = workflowStore.getState()
|
||||||
|
const store = useStoreApi()
|
||||||
|
const invalidateConversationVarValues = useInvalidateConversationVarValues(appId)
|
||||||
|
const invalidateSysVarValues = useInvalidateSysVarValues(appId)
|
||||||
|
const { handleCancelAllNodeSuccessStatus } = useNodesInteractionsWithoutSync()
|
||||||
|
|
||||||
|
const setInspectVarsToStore = (inspectVars: VarInInspect[]) => {
|
||||||
|
const { getNodes } = store.getState()
|
||||||
|
const nodeArr = getNodes()
|
||||||
|
const nodesKeyValue: Record<string, Node> = {}
|
||||||
|
nodeArr.forEach((node) => {
|
||||||
|
nodesKeyValue[node.id] = node
|
||||||
|
})
|
||||||
|
|
||||||
|
const withValueNodeIds: Record<string, boolean> = {}
|
||||||
|
inspectVars.forEach((varItem) => {
|
||||||
|
const nodeId = varItem.selector[0]
|
||||||
|
|
||||||
|
const node = nodesKeyValue[nodeId]
|
||||||
|
if (!node)
|
||||||
|
return
|
||||||
|
withValueNodeIds[nodeId] = true
|
||||||
|
})
|
||||||
|
const withValueNodes = Object.keys(withValueNodeIds).map((nodeId) => {
|
||||||
|
return nodesKeyValue[nodeId]
|
||||||
|
})
|
||||||
|
|
||||||
|
const res: NodeWithVar[] = withValueNodes.map((node) => {
|
||||||
|
const nodeId = node.id
|
||||||
|
const varsUnderTheNode = inspectVars.filter((varItem) => {
|
||||||
|
return varItem.selector[0] === nodeId
|
||||||
|
})
|
||||||
|
const nodeWithVar = {
|
||||||
|
nodeId,
|
||||||
|
nodePayload: node.data,
|
||||||
|
nodeType: node.data.type,
|
||||||
|
title: node.data.title,
|
||||||
|
vars: varsUnderTheNode,
|
||||||
|
isSingRunRunning: false,
|
||||||
|
isValueFetched: false,
|
||||||
|
}
|
||||||
|
return nodeWithVar
|
||||||
|
})
|
||||||
|
setNodesWithInspectVars(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchInspectVars = async () => {
|
||||||
|
invalidateConversationVarValues()
|
||||||
|
invalidateSysVarValues()
|
||||||
|
const data = await fetchAllInspectVars(appId)
|
||||||
|
setInspectVarsToStore(data)
|
||||||
|
handleCancelAllNodeSuccessStatus() // to make sure clear node output show the unset status
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fetchInspectVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSetWorkflowVarsWithValue
|
||||||
|
|
@ -17,7 +17,6 @@ import {
|
||||||
} from '@/service/workflow'
|
} from '@/service/workflow'
|
||||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||||
import { useWorkflowConfig } from '@/service/use-workflow'
|
import { useWorkflowConfig } from '@/service/use-workflow'
|
||||||
|
|
||||||
export const useWorkflowInit = () => {
|
export const useWorkflowInit = () => {
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player
|
||||||
import type { VersionHistory } from '@/types/workflow'
|
import type { VersionHistory } from '@/types/workflow'
|
||||||
import { noop } from 'lodash-es'
|
import { noop } from 'lodash-es'
|
||||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||||
|
import { useInvalidAllLastRun } from '@/service/use-workflow'
|
||||||
|
import useSetWorkflowVarsWithValue from './use-fetch-workflow-inspect-vars'
|
||||||
|
|
||||||
export const useWorkflowRun = () => {
|
export const useWorkflowRun = () => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
|
|
@ -28,6 +30,9 @@ export const useWorkflowRun = () => {
|
||||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
|
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
const appId = useAppStore.getState().appDetail?.id
|
||||||
|
const invalidAllLastRun = useInvalidAllLastRun(appId as string)
|
||||||
|
const { fetchInspectVars } = useSetWorkflowVarsWithValue()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleWorkflowStarted,
|
handleWorkflowStarted,
|
||||||
|
|
@ -140,11 +145,13 @@ export const useWorkflowRun = () => {
|
||||||
clientHeight,
|
clientHeight,
|
||||||
} = workflowContainer!
|
} = workflowContainer!
|
||||||
|
|
||||||
|
const isInWorkflowDebug = appDetail?.mode === 'workflow'
|
||||||
|
|
||||||
let url = ''
|
let url = ''
|
||||||
if (appDetail?.mode === 'advanced-chat')
|
if (appDetail?.mode === 'advanced-chat')
|
||||||
url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run`
|
url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run`
|
||||||
|
|
||||||
if (appDetail?.mode === 'workflow')
|
if (isInWorkflowDebug)
|
||||||
url = `/apps/${appDetail.id}/workflows/draft/run`
|
url = `/apps/${appDetail.id}/workflows/draft/run`
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -189,6 +196,10 @@ export const useWorkflowRun = () => {
|
||||||
|
|
||||||
if (onWorkflowFinished)
|
if (onWorkflowFinished)
|
||||||
onWorkflowFinished(params)
|
onWorkflowFinished(params)
|
||||||
|
if (isInWorkflowDebug) {
|
||||||
|
fetchInspectVars()
|
||||||
|
invalidAllLastRun()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (params) => {
|
onError: (params) => {
|
||||||
handleWorkflowFailed()
|
handleWorkflowFailed()
|
||||||
|
|
@ -292,26 +303,7 @@ export const useWorkflowRun = () => {
|
||||||
...restCallback,
|
...restCallback,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}, [
|
}, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace],
|
||||||
store,
|
|
||||||
workflowStore,
|
|
||||||
doSyncWorkflowDraft,
|
|
||||||
handleWorkflowStarted,
|
|
||||||
handleWorkflowFinished,
|
|
||||||
handleWorkflowFailed,
|
|
||||||
handleWorkflowNodeStarted,
|
|
||||||
handleWorkflowNodeFinished,
|
|
||||||
handleWorkflowNodeIterationStarted,
|
|
||||||
handleWorkflowNodeIterationNext,
|
|
||||||
handleWorkflowNodeIterationFinished,
|
|
||||||
handleWorkflowNodeLoopStarted,
|
|
||||||
handleWorkflowNodeLoopNext,
|
|
||||||
handleWorkflowNodeLoopFinished,
|
|
||||||
handleWorkflowNodeRetry,
|
|
||||||
handleWorkflowTextChunk,
|
|
||||||
handleWorkflowTextReplace,
|
|
||||||
handleWorkflowAgentLog,
|
|
||||||
pathname],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleStopRun = useCallback((taskId: string) => {
|
const handleStopRun = useCallback((taskId: string) => {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ type NodesExtraData = {
|
||||||
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
|
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
|
||||||
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
|
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
|
||||||
checkValid: any
|
checkValid: any
|
||||||
|
defaultRunInputData?: Record<string, any>
|
||||||
}
|
}
|
||||||
export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
||||||
[BlockEnum.Start]: {
|
[BlockEnum.Start]: {
|
||||||
|
|
@ -68,6 +69,7 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
||||||
getAvailablePrevNodes: LLMDefault.getAvailablePrevNodes,
|
getAvailablePrevNodes: LLMDefault.getAvailablePrevNodes,
|
||||||
getAvailableNextNodes: LLMDefault.getAvailableNextNodes,
|
getAvailableNextNodes: LLMDefault.getAvailableNextNodes,
|
||||||
checkValid: LLMDefault.checkValid,
|
checkValid: LLMDefault.checkValid,
|
||||||
|
defaultRunInputData: LLMDefault.defaultRunInputData,
|
||||||
},
|
},
|
||||||
[BlockEnum.KnowledgeRetrieval]: {
|
[BlockEnum.KnowledgeRetrieval]: {
|
||||||
author: 'Dify',
|
author: 'Dify',
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import {
|
||||||
import Toast from '../../base/toast'
|
import Toast from '../../base/toast'
|
||||||
import RestoringTitle from './restoring-title'
|
import RestoringTitle from './restoring-title'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
|
import { useInvalidAllLastRun } from '@/service/use-workflow'
|
||||||
|
|
||||||
export type HeaderInRestoringProps = {
|
export type HeaderInRestoringProps = {
|
||||||
onRestoreSettled?: () => void
|
onRestoreSettled?: () => void
|
||||||
|
|
@ -26,6 +28,12 @@ const HeaderInRestoring = ({
|
||||||
}: HeaderInRestoringProps) => {
|
}: HeaderInRestoringProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
|
const appDetail = useAppStore.getState().appDetail
|
||||||
|
|
||||||
|
const invalidAllLastRun = useInvalidAllLastRun(appDetail!.id)
|
||||||
|
const {
|
||||||
|
deleteAllInspectVars,
|
||||||
|
} = workflowStore.getState()
|
||||||
const currentVersion = useStore(s => s.currentVersion)
|
const currentVersion = useStore(s => s.currentVersion)
|
||||||
const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
|
const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
|
||||||
|
|
||||||
|
|
@ -61,7 +69,9 @@ const HeaderInRestoring = ({
|
||||||
onRestoreSettled?.()
|
onRestoreSettled?.()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [handleSyncWorkflowDraft, workflowStore, setShowWorkflowVersionHistoryPanel, onRestoreSettled, t])
|
deleteAllInspectVars()
|
||||||
|
invalidAllLastRun()
|
||||||
|
}, [setShowWorkflowVersionHistoryPanel, workflowStore, handleSyncWorkflowDraft, deleteAllInspectVars, invalidAllLastRun, t, onRestoreSettled])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
import {
|
import {
|
||||||
useWorkflowMode,
|
useWorkflowMode,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
|
|
@ -6,7 +7,7 @@ import HeaderInNormal from './header-in-normal'
|
||||||
import HeaderInHistory from './header-in-view-history'
|
import HeaderInHistory from './header-in-view-history'
|
||||||
import type { HeaderInRestoringProps } from './header-in-restoring'
|
import type { HeaderInRestoringProps } from './header-in-restoring'
|
||||||
import HeaderInRestoring from './header-in-restoring'
|
import HeaderInRestoring from './header-in-restoring'
|
||||||
|
import { useStore } from '../store'
|
||||||
export type HeaderProps = {
|
export type HeaderProps = {
|
||||||
normal?: HeaderInNormalProps
|
normal?: HeaderInNormalProps
|
||||||
restoring?: HeaderInRestoringProps
|
restoring?: HeaderInRestoringProps
|
||||||
|
|
@ -15,16 +16,20 @@ const Header = ({
|
||||||
normal: normalProps,
|
normal: normalProps,
|
||||||
restoring: restoringProps,
|
restoring: restoringProps,
|
||||||
}: HeaderProps) => {
|
}: HeaderProps) => {
|
||||||
|
const pathname = usePathname()
|
||||||
|
const inWorkflowCanvas = pathname.endsWith('/workflow')
|
||||||
const {
|
const {
|
||||||
normal,
|
normal,
|
||||||
restoring,
|
restoring,
|
||||||
viewHistory,
|
viewHistory,
|
||||||
} = useWorkflowMode()
|
} = useWorkflowMode()
|
||||||
|
const maximizeCanvas = useStore(s => s.maximizeCanvas)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='absolute left-0 top-0 z-10 flex h-14 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3'
|
className='absolute left-0 top-0 z-10 flex h-14 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3'
|
||||||
>
|
>
|
||||||
|
{inWorkflowCanvas && maximizeCanvas && <div className='h-14 w-[52px]' />}
|
||||||
{
|
{
|
||||||
normal && (
|
normal && (
|
||||||
<HeaderInNormal
|
<HeaderInNormal
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import cn from '@/utils/classnames'
|
||||||
import {
|
import {
|
||||||
StopCircle,
|
StopCircle,
|
||||||
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||||
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
|
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
||||||
|
|
||||||
const RunMode = memo(() => {
|
const RunMode = memo(() => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -27,6 +29,16 @@ const RunMode = memo(() => {
|
||||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||||
|
|
||||||
|
const handleStop = () => {
|
||||||
|
handleStopRun(workflowRunningData?.task_id || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
|
eventEmitter?.useSubscription((v: any) => {
|
||||||
|
if (v.type === EVENT_WORKFLOW_STOP)
|
||||||
|
handleStop()
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
@ -59,7 +71,7 @@ const RunMode = memo(() => {
|
||||||
isRunning && (
|
isRunning && (
|
||||||
<div
|
<div
|
||||||
className='ml-0.5 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-black/5'
|
className='ml-0.5 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-black/5'
|
||||||
onClick={() => handleStopRun(workflowRunningData?.task_id || '')}
|
onClick={handleStop}
|
||||||
>
|
>
|
||||||
<StopCircle className='h-4 w-4 text-components-button-ghost-text' />
|
<StopCircle className='h-4 w-4 text-components-button-ghost-text' />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
241
web/app/components/workflow/hooks/use-inspect-vars-crud.ts
Normal file
241
web/app/components/workflow/hooks/use-inspect-vars-crud.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
||||||
|
import { fetchNodeInspectVars } from '@/service/workflow'
|
||||||
|
import { useStore, useWorkflowStore } from '../store'
|
||||||
|
import type { ValueSelector } from '../types'
|
||||||
|
import type { VarInInspect } from '@/types/workflow'
|
||||||
|
import { VarInInspectType } from '@/types/workflow'
|
||||||
|
import {
|
||||||
|
useConversationVarValues,
|
||||||
|
useDeleteAllInspectorVars,
|
||||||
|
useDeleteInspectVar,
|
||||||
|
useDeleteNodeInspectorVars,
|
||||||
|
useEditInspectorVar,
|
||||||
|
useInvalidateConversationVarValues,
|
||||||
|
useInvalidateSysVarValues,
|
||||||
|
useLastRun,
|
||||||
|
useResetConversationVar,
|
||||||
|
useResetToLastRunValue,
|
||||||
|
useSysVarValues,
|
||||||
|
} from '@/service/use-workflow'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { isConversationVar, isENV, isSystemVar } from '../nodes/_base/components/variable/utils'
|
||||||
|
import produce from 'immer'
|
||||||
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
|
import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-without-sync'
|
||||||
|
import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync'
|
||||||
|
|
||||||
|
const useInspectVarsCrud = () => {
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
const nodesWithInspectVars = useStore(s => s.nodesWithInspectVars)
|
||||||
|
const {
|
||||||
|
appId,
|
||||||
|
setNodeInspectVars,
|
||||||
|
setInspectVarValue,
|
||||||
|
renameInspectVarName: renameInspectVarNameInStore,
|
||||||
|
deleteAllInspectVars: deleteAllInspectVarsInStore,
|
||||||
|
deleteNodeInspectVars: deleteNodeInspectVarsInStore,
|
||||||
|
deleteInspectVar: deleteInspectVarInStore,
|
||||||
|
setNodesWithInspectVars,
|
||||||
|
resetToLastRunVar: resetToLastRunVarInStore,
|
||||||
|
} = workflowStore.getState()
|
||||||
|
|
||||||
|
const { data: conversationVars } = useConversationVarValues(appId)
|
||||||
|
const invalidateConversationVarValues = useInvalidateConversationVarValues(appId)
|
||||||
|
const { mutateAsync: doResetConversationVar } = useResetConversationVar(appId)
|
||||||
|
const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(appId)
|
||||||
|
const { data: systemVars } = useSysVarValues(appId)
|
||||||
|
const invalidateSysVarValues = useInvalidateSysVarValues(appId)
|
||||||
|
|
||||||
|
const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(appId)
|
||||||
|
const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(appId)
|
||||||
|
const { mutate: doDeleteInspectVar } = useDeleteInspectVar(appId)
|
||||||
|
|
||||||
|
const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(appId)
|
||||||
|
const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync()
|
||||||
|
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
|
||||||
|
const getNodeInspectVars = useCallback((nodeId: string) => {
|
||||||
|
const node = nodesWithInspectVars.find(node => node.nodeId === nodeId)
|
||||||
|
return node
|
||||||
|
}, [nodesWithInspectVars])
|
||||||
|
|
||||||
|
const getVarId = useCallback((nodeId: string, varName: string) => {
|
||||||
|
const node = getNodeInspectVars(nodeId)
|
||||||
|
if (!node)
|
||||||
|
return undefined
|
||||||
|
const varId = node.vars.find((varItem) => {
|
||||||
|
return varItem.selector[1] === varName
|
||||||
|
})?.id
|
||||||
|
return varId
|
||||||
|
}, [getNodeInspectVars])
|
||||||
|
|
||||||
|
const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => {
|
||||||
|
const node = getNodeInspectVars(nodeId)
|
||||||
|
if (!node)
|
||||||
|
return undefined
|
||||||
|
|
||||||
|
const variable = node.vars.find((varItem) => {
|
||||||
|
return varItem.name === name
|
||||||
|
})
|
||||||
|
return variable
|
||||||
|
}, [getNodeInspectVars])
|
||||||
|
|
||||||
|
const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => {
|
||||||
|
const isEnv = isENV([nodeId])
|
||||||
|
if (isEnv) // always have value
|
||||||
|
return true
|
||||||
|
const isSys = isSystemVar([nodeId])
|
||||||
|
if (isSys)
|
||||||
|
return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name)
|
||||||
|
const isChatVar = isConversationVar([nodeId])
|
||||||
|
if (isChatVar)
|
||||||
|
return conversationVars.some(varItem => varItem.selector?.[1] === name)
|
||||||
|
return getInspectVar(nodeId, name) !== undefined
|
||||||
|
}, [getInspectVar])
|
||||||
|
|
||||||
|
const hasNodeInspectVars = useCallback((nodeId: string) => {
|
||||||
|
return !!getNodeInspectVars(nodeId)
|
||||||
|
}, [getNodeInspectVars])
|
||||||
|
|
||||||
|
const fetchInspectVarValue = async (selector: ValueSelector) => {
|
||||||
|
const nodeId = selector[0]
|
||||||
|
const isSystemVar = nodeId === 'sys'
|
||||||
|
const isConversationVar = nodeId === 'conversation'
|
||||||
|
if (isSystemVar) {
|
||||||
|
invalidateSysVarValues()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isConversationVar) {
|
||||||
|
invalidateConversationVarValues()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const vars = await fetchNodeInspectVars(appId, nodeId)
|
||||||
|
setNodeInspectVars(nodeId, vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
// after last run would call this
|
||||||
|
const appendNodeInspectVars = (nodeId: string, payload: VarInInspect[], allNodes: Node[]) => {
|
||||||
|
const nodes = produce(nodesWithInspectVars, (draft) => {
|
||||||
|
const nodeInfo = allNodes.find(node => node.id === nodeId)
|
||||||
|
if (nodeInfo) {
|
||||||
|
const index = draft.findIndex(node => node.nodeId === nodeId)
|
||||||
|
if (index === -1) {
|
||||||
|
draft.push({
|
||||||
|
nodeId,
|
||||||
|
nodeType: nodeInfo.data.type,
|
||||||
|
title: nodeInfo.data.title,
|
||||||
|
vars: payload,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
draft[index].vars = payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setNodesWithInspectVars(nodes)
|
||||||
|
handleCancelNodeSuccessStatus(nodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasNodeInspectVar = (nodeId: string, varId: string) => {
|
||||||
|
const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId)
|
||||||
|
if(!targetNode || !targetNode.vars)
|
||||||
|
return false
|
||||||
|
return targetNode.vars.some(item => item.id === varId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteInspectVar = async (nodeId: string, varId: string) => {
|
||||||
|
if(hasNodeInspectVar(nodeId, varId)) {
|
||||||
|
await doDeleteInspectVar(varId)
|
||||||
|
deleteInspectVarInStore(nodeId, varId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetConversationVar = async (varId: string) => {
|
||||||
|
await doResetConversationVar(varId)
|
||||||
|
invalidateConversationVarValues()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteNodeInspectorVars = async (nodeId: string) => {
|
||||||
|
if (hasNodeInspectVars(nodeId)) {
|
||||||
|
await doDeleteNodeInspectorVars(nodeId)
|
||||||
|
deleteNodeInspectVarsInStore(nodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAllInspectorVars = async () => {
|
||||||
|
await doDeleteAllInspectorVars()
|
||||||
|
await invalidateConversationVarValues()
|
||||||
|
await invalidateSysVarValues()
|
||||||
|
deleteAllInspectVarsInStore()
|
||||||
|
handleEdgeCancelRunningStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => {
|
||||||
|
await doEditInspectorVar({
|
||||||
|
varId,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
setInspectVarValue(nodeId, varId, value)
|
||||||
|
if (nodeId === VarInInspectType.conversation)
|
||||||
|
invalidateConversationVarValues()
|
||||||
|
if (nodeId === VarInInspectType.system)
|
||||||
|
invalidateSysVarValues()
|
||||||
|
}, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, setInspectVarValue])
|
||||||
|
|
||||||
|
const [currNodeId, setCurrNodeId] = useState<string | null>(null)
|
||||||
|
const [currEditVarId, setCurrEditVarId] = useState<string | null>(null)
|
||||||
|
const { data } = useLastRun(appId, currNodeId || '', !!currNodeId)
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && currNodeId && currEditVarId) {
|
||||||
|
const inspectVar = getNodeInspectVars(currNodeId)?.vars?.find(item => item.id === currEditVarId)
|
||||||
|
resetToLastRunVarInStore(currNodeId, currEditVarId, data.outputs?.[inspectVar?.selector?.[1] || ''])
|
||||||
|
}
|
||||||
|
}, [data, currNodeId, currEditVarId, getNodeInspectVars, editInspectVarValue, resetToLastRunVarInStore])
|
||||||
|
|
||||||
|
const renameInspectVarName = async (nodeId: string, oldName: string, newName: string) => {
|
||||||
|
const varId = getVarId(nodeId, oldName)
|
||||||
|
if (!varId)
|
||||||
|
return
|
||||||
|
|
||||||
|
const newSelector = [nodeId, newName]
|
||||||
|
await doEditInspectorVar({
|
||||||
|
varId,
|
||||||
|
name: newName,
|
||||||
|
})
|
||||||
|
renameInspectVarNameInStore(nodeId, varId, newSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isInspectVarEdited = useCallback((nodeId: string, name: string) => {
|
||||||
|
const inspectVar = getInspectVar(nodeId, name)
|
||||||
|
if (!inspectVar)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return inspectVar.edited
|
||||||
|
}, [getInspectVar])
|
||||||
|
|
||||||
|
const resetToLastRunVar = async (nodeId: string, varId: string) => {
|
||||||
|
await doResetToLastRunValue(varId)
|
||||||
|
setCurrNodeId(nodeId)
|
||||||
|
setCurrEditVarId(varId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversationVars: conversationVars || [],
|
||||||
|
systemVars: systemVars || [],
|
||||||
|
nodesWithInspectVars,
|
||||||
|
hasNodeInspectVars,
|
||||||
|
hasSetInspectVar,
|
||||||
|
fetchInspectVarValue,
|
||||||
|
editInspectVarValue,
|
||||||
|
renameInspectVarName,
|
||||||
|
appendNodeInspectVars,
|
||||||
|
deleteInspectVar,
|
||||||
|
deleteNodeInspectorVars,
|
||||||
|
deleteAllInspectorVars,
|
||||||
|
isInspectVarEdited,
|
||||||
|
resetToLastRunVar,
|
||||||
|
invalidateSysVarValues,
|
||||||
|
resetConversationVar,
|
||||||
|
invalidateConversationVarValues,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useInspectVarsCrud
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useStoreApi } from 'reactflow'
|
import { useStoreApi } from 'reactflow'
|
||||||
|
import { NodeRunningStatus } from '../types'
|
||||||
|
|
||||||
export const useNodesInteractionsWithoutSync = () => {
|
export const useNodesInteractionsWithoutSync = () => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
|
|
@ -21,7 +22,41 @@ export const useNodesInteractionsWithoutSync = () => {
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
|
const handleCancelAllNodeSuccessStatus = useCallback(() => {
|
||||||
|
const {
|
||||||
|
getNodes,
|
||||||
|
setNodes,
|
||||||
|
} = store.getState()
|
||||||
|
|
||||||
|
const nodes = getNodes()
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
draft.forEach((node) => {
|
||||||
|
if(node.data._runningStatus === NodeRunningStatus.Succeeded)
|
||||||
|
node.data._runningStatus = undefined
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setNodes(newNodes)
|
||||||
|
}, [store])
|
||||||
|
|
||||||
|
const handleCancelNodeSuccessStatus = useCallback((nodeId: string) => {
|
||||||
|
const {
|
||||||
|
getNodes,
|
||||||
|
setNodes,
|
||||||
|
} = store.getState()
|
||||||
|
|
||||||
|
const newNodes = produce(getNodes(), (draft) => {
|
||||||
|
const node = draft.find(n => n.id === nodeId)
|
||||||
|
if (node && node.data._runningStatus === NodeRunningStatus.Succeeded) {
|
||||||
|
node.data._runningStatus = undefined
|
||||||
|
node.data._waitingRun = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setNodes(newNodes)
|
||||||
|
}, [store])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleNodeCancelRunningStatus,
|
handleNodeCancelRunningStatus,
|
||||||
|
handleCancelAllNodeSuccessStatus,
|
||||||
|
handleCancelNodeSuccessStatus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ import {
|
||||||
useWorkflowReadOnly,
|
useWorkflowReadOnly,
|
||||||
} from './use-workflow'
|
} from './use-workflow'
|
||||||
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
|
||||||
|
import useInspectVarsCrud from './use-inspect-vars-crud'
|
||||||
|
|
||||||
export const useNodesInteractions = () => {
|
export const useNodesInteractions = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -288,7 +289,9 @@ export const useNodesInteractions = () => {
|
||||||
setEdges(newEdges)
|
setEdges(newEdges)
|
||||||
}, [store, workflowStore, getNodesReadOnly])
|
}, [store, workflowStore, getNodesReadOnly])
|
||||||
|
|
||||||
const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean) => {
|
const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean, initShowLastRunTab?: boolean) => {
|
||||||
|
if(initShowLastRunTab)
|
||||||
|
workflowStore.setState({ initShowLastRunTab: true })
|
||||||
const {
|
const {
|
||||||
getNodes,
|
getNodes,
|
||||||
setNodes,
|
setNodes,
|
||||||
|
|
@ -530,6 +533,8 @@ export const useNodesInteractions = () => {
|
||||||
setEnteringNodePayload(undefined)
|
setEnteringNodePayload(undefined)
|
||||||
}, [store, handleNodeConnect, getNodesReadOnly, workflowStore, reactflow])
|
}, [store, handleNodeConnect, getNodesReadOnly, workflowStore, reactflow])
|
||||||
|
|
||||||
|
const { deleteNodeInspectorVars } = useInspectVarsCrud()
|
||||||
|
|
||||||
const handleNodeDelete = useCallback((nodeId: string) => {
|
const handleNodeDelete = useCallback((nodeId: string) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
|
|
@ -551,6 +556,7 @@ export const useNodesInteractions = () => {
|
||||||
if (currentNode.data.type === BlockEnum.Start)
|
if (currentNode.data.type === BlockEnum.Start)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
deleteNodeInspectorVars(nodeId)
|
||||||
if (currentNode.data.type === BlockEnum.Iteration) {
|
if (currentNode.data.type === BlockEnum.Iteration) {
|
||||||
const iterationChildren = nodes.filter(node => node.parentId === currentNode.id)
|
const iterationChildren = nodes.filter(node => node.parentId === currentNode.id)
|
||||||
|
|
||||||
|
|
@ -655,7 +661,7 @@ export const useNodesInteractions = () => {
|
||||||
|
|
||||||
else
|
else
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeDelete)
|
saveStateToHistory(WorkflowHistoryEvent.NodeDelete)
|
||||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t])
|
}, [getNodesReadOnly, store, deleteNodeInspectorVars, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t])
|
||||||
|
|
||||||
const handleNodeAdd = useCallback<OnNodeAdd>((
|
const handleNodeAdd = useCallback<OnNodeAdd>((
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
useEdgesInteractions,
|
useEdgesInteractions,
|
||||||
useNodesInteractions,
|
useNodesInteractions,
|
||||||
useNodesSyncDraft,
|
useNodesSyncDraft,
|
||||||
|
useWorkflowCanvasMaximize,
|
||||||
useWorkflowMoveMode,
|
useWorkflowMoveMode,
|
||||||
useWorkflowOrganize,
|
useWorkflowOrganize,
|
||||||
useWorkflowStartRun,
|
useWorkflowStartRun,
|
||||||
|
|
@ -35,6 +36,7 @@ export const useShortcuts = (): void => {
|
||||||
handleModePointer,
|
handleModePointer,
|
||||||
} = useWorkflowMoveMode()
|
} = useWorkflowMoveMode()
|
||||||
const { handleLayout } = useWorkflowOrganize()
|
const { handleLayout } = useWorkflowOrganize()
|
||||||
|
const { handleToggleMaximizeCanvas } = useWorkflowCanvasMaximize()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
zoomTo,
|
zoomTo,
|
||||||
|
|
@ -145,6 +147,16 @@ export const useShortcuts = (): void => {
|
||||||
}
|
}
|
||||||
}, { exactMatch: true, useCapture: true })
|
}, { exactMatch: true, useCapture: true })
|
||||||
|
|
||||||
|
useKeyPress('f', (e) => {
|
||||||
|
if (shouldHandleShortcut(e)) {
|
||||||
|
e.preventDefault()
|
||||||
|
handleToggleMaximizeCanvas()
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
exactMatch: true,
|
||||||
|
useCapture: true,
|
||||||
|
})
|
||||||
|
|
||||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.1`, (e) => {
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.1`, (e) => {
|
||||||
if (shouldHandleShortcut(e)) {
|
if (shouldHandleShortcut(e)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
||||||
|
|
@ -401,3 +401,29 @@ export const useDSL = () => {
|
||||||
handleExportDSL,
|
handleExportDSL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useWorkflowCanvasMaximize = () => {
|
||||||
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
|
|
||||||
|
const maximizeCanvas = useStore(s => s.maximizeCanvas)
|
||||||
|
const setMaximizeCanvas = useStore(s => s.setMaximizeCanvas)
|
||||||
|
const {
|
||||||
|
getNodesReadOnly,
|
||||||
|
} = useNodesReadOnly()
|
||||||
|
|
||||||
|
const handleToggleMaximizeCanvas = useCallback(() => {
|
||||||
|
if (getNodesReadOnly())
|
||||||
|
return
|
||||||
|
|
||||||
|
setMaximizeCanvas(!maximizeCanvas)
|
||||||
|
localStorage.setItem('workflow-canvas-maximize', String(!maximizeCanvas))
|
||||||
|
eventEmitter?.emit({
|
||||||
|
type: 'workflow-canvas-maximize',
|
||||||
|
payload: !maximizeCanvas,
|
||||||
|
} as any)
|
||||||
|
}, [eventEmitter, getNodesReadOnly, maximizeCanvas, setMaximizeCanvas])
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleToggleMaximizeCanvas,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,6 @@ export const useWorkflow = () => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const nodesExtraData = useNodesExtraData()
|
const nodesExtraData = useNodesExtraData()
|
||||||
const setPanelWidth = useCallback((width: number) => {
|
|
||||||
localStorage.setItem('workflow-node-panel-width', `${width}`)
|
|
||||||
workflowStore.setState({ panelWidth: width })
|
|
||||||
}, [workflowStore])
|
|
||||||
|
|
||||||
const getTreeLeafNodes = useCallback((nodeId: string) => {
|
const getTreeLeafNodes = useCallback((nodeId: string) => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -399,7 +395,6 @@ export const useWorkflow = () => {
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setPanelWidth,
|
|
||||||
getTreeLeafNodes,
|
getTreeLeafNodes,
|
||||||
getBeforeNodesInSameBranch,
|
getBeforeNodesInSameBranch,
|
||||||
getBeforeNodesInSameBranchIncludeParent,
|
getBeforeNodesInSameBranchIncludeParent,
|
||||||
|
|
@ -497,6 +492,8 @@ export const useToolIcon = (data: Node['data']) => {
|
||||||
const customTools = useStore(s => s.customTools)
|
const customTools = useStore(s => s.customTools)
|
||||||
const workflowTools = useStore(s => s.workflowTools)
|
const workflowTools = useStore(s => s.workflowTools)
|
||||||
const toolIcon = useMemo(() => {
|
const toolIcon = useMemo(() => {
|
||||||
|
if(!data)
|
||||||
|
return ''
|
||||||
if (data.type === BlockEnum.Tool) {
|
if (data.type === BlockEnum.Tool) {
|
||||||
let targetTools = buildInTools
|
let targetTools = buildInTools
|
||||||
if (data.provider_type === CollectionType.builtIn)
|
if (data.provider_type === CollectionType.builtIn)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { setAutoFreeze } from 'immer'
|
import { setAutoFreeze } from 'immer'
|
||||||
|
|
@ -56,6 +57,7 @@ import { CUSTOM_LOOP_START_NODE } from './nodes/loop-start/constants'
|
||||||
import CustomSimpleNode from './simple-node'
|
import CustomSimpleNode from './simple-node'
|
||||||
import { CUSTOM_SIMPLE_NODE } from './simple-node/constants'
|
import { CUSTOM_SIMPLE_NODE } from './simple-node/constants'
|
||||||
import Operator from './operator'
|
import Operator from './operator'
|
||||||
|
import Control from './operator/control'
|
||||||
import CustomEdge from './custom-edge'
|
import CustomEdge from './custom-edge'
|
||||||
import CustomConnectionLine from './custom-connection-line'
|
import CustomConnectionLine from './custom-connection-line'
|
||||||
import HelpLine from './help-line'
|
import HelpLine from './help-line'
|
||||||
|
|
@ -80,6 +82,7 @@ import Confirm from '@/app/components/base/confirm'
|
||||||
import DatasetsDetailProvider from './datasets-detail-store/provider'
|
import DatasetsDetailProvider from './datasets-detail-store/provider'
|
||||||
import { HooksStoreContextProvider } from './hooks-store'
|
import { HooksStoreContextProvider } from './hooks-store'
|
||||||
import type { Shape as HooksStoreShape } from './hooks-store'
|
import type { Shape as HooksStoreShape } from './hooks-store'
|
||||||
|
import useSetWorkflowVarsWithValue from '../workflow-app/hooks/use-fetch-workflow-inspect-vars'
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
[CUSTOM_NODE]: CustomNode,
|
[CUSTOM_NODE]: CustomNode,
|
||||||
|
|
@ -114,6 +117,32 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||||
const controlMode = useStore(s => s.controlMode)
|
const controlMode = useStore(s => s.controlMode)
|
||||||
const nodeAnimation = useStore(s => s.nodeAnimation)
|
const nodeAnimation = useStore(s => s.nodeAnimation)
|
||||||
const showConfirm = useStore(s => s.showConfirm)
|
const showConfirm = useStore(s => s.showConfirm)
|
||||||
|
const workflowCanvasHeight = useStore(s => s.workflowCanvasHeight)
|
||||||
|
const bottomPanelHeight = useStore(s => s.bottomPanelHeight)
|
||||||
|
const setWorkflowCanvasWidth = useStore(s => s.setWorkflowCanvasWidth)
|
||||||
|
const setWorkflowCanvasHeight = useStore(s => s.setWorkflowCanvasHeight)
|
||||||
|
const controlHeight = useMemo(() => {
|
||||||
|
if (!workflowCanvasHeight)
|
||||||
|
return '100%'
|
||||||
|
return workflowCanvasHeight - bottomPanelHeight
|
||||||
|
}, [workflowCanvasHeight, bottomPanelHeight])
|
||||||
|
|
||||||
|
// update workflow Canvas width and height
|
||||||
|
useEffect(() => {
|
||||||
|
if (workflowContainerRef.current) {
|
||||||
|
const resizeContainerObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const { inlineSize, blockSize } = entry.borderBoxSize[0]
|
||||||
|
setWorkflowCanvasWidth(inlineSize)
|
||||||
|
setWorkflowCanvasHeight(blockSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resizeContainerObserver.observe(workflowContainerRef.current)
|
||||||
|
return () => {
|
||||||
|
resizeContainerObserver.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [setWorkflowCanvasHeight, setWorkflowCanvasWidth])
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setShowConfirm,
|
setShowConfirm,
|
||||||
|
|
@ -245,6 +274,11 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||||
})
|
})
|
||||||
|
|
||||||
useShortcuts()
|
useShortcuts()
|
||||||
|
const { fetchInspectVars } = useSetWorkflowVarsWithValue()
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInspectVars()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
|
@ -267,6 +301,12 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||||
>
|
>
|
||||||
<SyncingDataModal />
|
<SyncingDataModal />
|
||||||
<CandidateNode />
|
<CandidateNode />
|
||||||
|
<div
|
||||||
|
className='absolute left-0 top-0 z-10 flex w-12 items-center justify-center p-1 pl-2'
|
||||||
|
style={{ height: controlHeight }}
|
||||||
|
>
|
||||||
|
<Control />
|
||||||
|
</div>
|
||||||
<Operator handleRedo={handleHistoryForward} handleUndo={handleHistoryBack} />
|
<Operator handleRedo={handleHistoryForward} handleUndo={handleHistoryBack} />
|
||||||
<PanelContextmenu />
|
<PanelContextmenu />
|
||||||
<NodeContextmenu />
|
<NodeContextmenu />
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ const FormItem: FC<Props> = ({
|
||||||
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type)
|
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type)
|
||||||
const isContext = type === InputVarType.contexts
|
const isContext = type === InputVarType.contexts
|
||||||
const isIterator = type === InputVarType.iterator
|
const isIterator = type === InputVarType.iterator
|
||||||
|
const isIteratorItemFile = isIterator && payload.isFileItem
|
||||||
const singleFileValue = useMemo(() => {
|
const singleFileValue = useMemo(() => {
|
||||||
if (payload.variable === '#files#')
|
if (payload.variable === '#files#')
|
||||||
return value?.[0] || []
|
return value?.[0] || []
|
||||||
|
|
@ -202,12 +203,12 @@ const FormItem: FC<Props> = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(type === InputVarType.multiFiles) && (
|
{(type === InputVarType.multiFiles || isIteratorItemFile) && (
|
||||||
<FileUploaderInAttachmentWrapper
|
<FileUploaderInAttachmentWrapper
|
||||||
value={value}
|
value={value}
|
||||||
onChange={files => onChange(files)}
|
onChange={files => onChange(files)}
|
||||||
fileConfig={{
|
fileConfig={{
|
||||||
allowed_file_types: inStepRun
|
allowed_file_types: (inStepRun || isIteratorItemFile)
|
||||||
? [
|
? [
|
||||||
SupportUploadFileTypes.image,
|
SupportUploadFileTypes.image,
|
||||||
SupportUploadFileTypes.document,
|
SupportUploadFileTypes.document,
|
||||||
|
|
@ -215,7 +216,7 @@ const FormItem: FC<Props> = ({
|
||||||
SupportUploadFileTypes.video,
|
SupportUploadFileTypes.video,
|
||||||
]
|
]
|
||||||
: payload.allowed_file_types,
|
: payload.allowed_file_types,
|
||||||
allowed_file_extensions: inStepRun
|
allowed_file_extensions: (inStepRun || isIteratorItemFile)
|
||||||
? [
|
? [
|
||||||
...FILE_EXTS[SupportUploadFileTypes.image],
|
...FILE_EXTS[SupportUploadFileTypes.image],
|
||||||
...FILE_EXTS[SupportUploadFileTypes.document],
|
...FILE_EXTS[SupportUploadFileTypes.document],
|
||||||
|
|
@ -223,8 +224,8 @@ const FormItem: FC<Props> = ({
|
||||||
...FILE_EXTS[SupportUploadFileTypes.video],
|
...FILE_EXTS[SupportUploadFileTypes.video],
|
||||||
]
|
]
|
||||||
: payload.allowed_file_extensions,
|
: payload.allowed_file_extensions,
|
||||||
allowed_file_upload_methods: inStepRun ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods,
|
allowed_file_upload_methods: (inStepRun || isIteratorItemFile) ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods,
|
||||||
number_limits: inStepRun ? 5 : payload.max_length,
|
number_limits: (inStepRun || isIteratorItemFile) ? 5 : payload.max_length,
|
||||||
fileUploadConfig: fileSettings?.fileUploadConfig,
|
fileUploadConfig: fileSettings?.fileUploadConfig,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -272,7 +273,7 @@ const FormItem: FC<Props> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isIterator && (
|
(isIterator && !isIteratorItemFile) && (
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
{(value || []).map((item: any, index: number) => (
|
{(value || []).map((item: any, index: number) => (
|
||||||
<TextEditor
|
<TextEditor
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,14 @@ const Form: FC<Props> = ({
|
||||||
}
|
}
|
||||||
}, [valuesRef, onChange, mapKeysWithSameValueSelector])
|
}, [valuesRef, onChange, mapKeysWithSameValueSelector])
|
||||||
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(inputs[0]?.type)
|
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(inputs[0]?.type)
|
||||||
|
const isIteratorItemFile = inputs[0]?.type === InputVarType.iterator && inputs[0]?.isFileItem
|
||||||
|
|
||||||
const isContext = inputs[0]?.type === InputVarType.contexts
|
const isContext = inputs[0]?.type === InputVarType.contexts
|
||||||
const handleAddContext = useCallback(() => {
|
const handleAddContext = useCallback(() => {
|
||||||
const newValues = produce(values, (draft: any) => {
|
const newValues = produce(values, (draft: any) => {
|
||||||
const key = inputs[0].variable
|
const key = inputs[0].variable
|
||||||
|
if (!draft[key])
|
||||||
|
draft[key] = []
|
||||||
draft[key].push(isContext ? RETRIEVAL_OUTPUT_STRUCT : '')
|
draft[key].push(isContext ? RETRIEVAL_OUTPUT_STRUCT : '')
|
||||||
})
|
})
|
||||||
onChange(newValues)
|
onChange(newValues)
|
||||||
|
|
@ -75,7 +79,7 @@ const Form: FC<Props> = ({
|
||||||
{label && (
|
{label && (
|
||||||
<div className='mb-1 flex items-center justify-between'>
|
<div className='mb-1 flex items-center justify-between'>
|
||||||
<div className='system-xs-medium-uppercase flex h-6 items-center text-text-tertiary'>{label}</div>
|
<div className='system-xs-medium-uppercase flex h-6 items-center text-text-tertiary'>{label}</div>
|
||||||
{isArrayLikeType && (
|
{isArrayLikeType && !isIteratorItemFile && (
|
||||||
<AddButton onClick={handleAddContext} />
|
<AddButton onClick={handleAddContext} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,23 @@
|
||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
|
||||||
RiCloseLine,
|
|
||||||
RiLoader2Line,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
import type { Props as FormProps } from './form'
|
import type { Props as FormProps } from './form'
|
||||||
import Form from './form'
|
import Form from './form'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import { InputVarType, NodeRunningStatus } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
import type { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types'
|
||||||
import type { Emoji } from '@/app/components/tools/types'
|
import type { Emoji } from '@/app/components/tools/types'
|
||||||
import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel'
|
import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel'
|
||||||
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
import PanelWrap from './panel-wrap'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.singleRun'
|
const i18nPrefix = 'workflow.singleRun'
|
||||||
|
|
||||||
type BeforeRunFormProps = {
|
export type BeforeRunFormProps = {
|
||||||
nodeName: string
|
nodeName: string
|
||||||
nodeType?: BlockEnum
|
nodeType?: BlockEnum
|
||||||
toolIcon?: string | Emoji
|
toolIcon?: string | Emoji
|
||||||
|
|
@ -32,12 +25,15 @@ type BeforeRunFormProps = {
|
||||||
onRun: (submitData: Record<string, any>) => void
|
onRun: (submitData: Record<string, any>) => void
|
||||||
onStop: () => void
|
onStop: () => void
|
||||||
runningStatus: NodeRunningStatus
|
runningStatus: NodeRunningStatus
|
||||||
result?: React.JSX.Element
|
|
||||||
forms: FormProps[]
|
forms: FormProps[]
|
||||||
showSpecialResultPanel?: boolean
|
showSpecialResultPanel?: boolean
|
||||||
|
existVarValuesInForms: Record<string, any>[]
|
||||||
|
filteredExistVarForms: FormProps[]
|
||||||
} & Partial<SpecialResultPanelProps>
|
} & Partial<SpecialResultPanelProps>
|
||||||
|
|
||||||
function formatValue(value: string | any, type: InputVarType) {
|
function formatValue(value: string | any, type: InputVarType) {
|
||||||
|
if(value === undefined || value === null)
|
||||||
|
return value
|
||||||
if (type === InputVarType.number)
|
if (type === InputVarType.number)
|
||||||
return Number.parseFloat(value)
|
return Number.parseFloat(value)
|
||||||
if (type === InputVarType.json)
|
if (type === InputVarType.json)
|
||||||
|
|
@ -53,6 +49,8 @@ function formatValue(value: string | any, type: InputVarType) {
|
||||||
if (type === InputVarType.singleFile) {
|
if (type === InputVarType.singleFile) {
|
||||||
if (Array.isArray(value))
|
if (Array.isArray(value))
|
||||||
return getProcessedFiles(value)
|
return getProcessedFiles(value)
|
||||||
|
if (!value)
|
||||||
|
return undefined
|
||||||
return getProcessedFiles([value])[0]
|
return getProcessedFiles([value])[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,22 +58,17 @@ function formatValue(value: string | any, type: InputVarType) {
|
||||||
}
|
}
|
||||||
const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||||
nodeName,
|
nodeName,
|
||||||
nodeType,
|
|
||||||
toolIcon,
|
|
||||||
onHide,
|
onHide,
|
||||||
onRun,
|
onRun,
|
||||||
onStop,
|
|
||||||
runningStatus,
|
|
||||||
result,
|
|
||||||
forms,
|
forms,
|
||||||
showSpecialResultPanel,
|
filteredExistVarForms,
|
||||||
...restResultPanelParams
|
existVarValuesInForms,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed || runningStatus === NodeRunningStatus.Exception
|
|
||||||
const isRunning = runningStatus === NodeRunningStatus.Running
|
|
||||||
const isFileLoaded = (() => {
|
const isFileLoaded = (() => {
|
||||||
|
if (!forms || forms.length === 0)
|
||||||
|
return true
|
||||||
// system files
|
// system files
|
||||||
const filesForm = forms.find(item => !!item.values['#files#'])
|
const filesForm = forms.find(item => !!item.values['#files#'])
|
||||||
if (!filesForm)
|
if (!filesForm)
|
||||||
|
|
@ -87,12 +80,14 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})()
|
})()
|
||||||
const handleRun = useCallback(() => {
|
const handleRun = () => {
|
||||||
let errMsg = ''
|
let errMsg = ''
|
||||||
forms.forEach((form) => {
|
forms.forEach((form, i) => {
|
||||||
|
const existVarValuesInForm = existVarValuesInForms[i]
|
||||||
|
|
||||||
form.inputs.forEach((input) => {
|
form.inputs.forEach((input) => {
|
||||||
const value = form.values[input.variable] as any
|
const value = form.values[input.variable] as any
|
||||||
if (!errMsg && input.required && (value === '' || value === undefined || value === null || (input.type === InputVarType.files && value.length === 0)))
|
if (!errMsg && input.required && !(input.variable in existVarValuesInForm) && (value === '' || value === undefined || value === null || (input.type === InputVarType.files && value.length === 0)))
|
||||||
errMsg = t('workflow.errorMsg.fieldRequired', { field: typeof input.label === 'object' ? input.label.variable : input.label })
|
errMsg = t('workflow.errorMsg.fieldRequired', { field: typeof input.label === 'object' ? input.label.variable : input.label })
|
||||||
|
|
||||||
if (!errMsg && (input.type === InputVarType.singleFile || input.type === InputVarType.multiFiles) && value) {
|
if (!errMsg && (input.type === InputVarType.singleFile || input.type === InputVarType.multiFiles) && value) {
|
||||||
|
|
@ -137,32 +132,28 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
onRun(submitData)
|
onRun(submitData)
|
||||||
}, [forms, onRun, t])
|
|
||||||
return (
|
|
||||||
<div className='absolute inset-0 z-10 rounded-2xl bg-background-overlay-alt pt-10'>
|
|
||||||
<div className='flex h-full flex-col rounded-2xl bg-components-panel-bg'>
|
|
||||||
<div className='flex h-8 shrink-0 items-center justify-between pl-4 pr-3 pt-3'>
|
|
||||||
<div className='truncate text-base font-semibold text-text-primary'>
|
|
||||||
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
|
||||||
</div>
|
|
||||||
<div className='ml-2 shrink-0 cursor-pointer p-1' onClick={() => {
|
|
||||||
onHide()
|
|
||||||
}}>
|
|
||||||
<RiCloseLine className='h-4 w-4 text-text-tertiary ' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
showSpecialResultPanel && (
|
|
||||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
|
||||||
<SpecialResultPanel {...restResultPanelParams} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
{
|
const hasRun = useRef(false)
|
||||||
!showSpecialResultPanel && (
|
useEffect(() => {
|
||||||
|
// React 18 run twice in dev mode
|
||||||
|
if(hasRun.current)
|
||||||
|
return
|
||||||
|
hasRun.current = true
|
||||||
|
if(filteredExistVarForms.length === 0)
|
||||||
|
onRun({})
|
||||||
|
}, [filteredExistVarForms, onRun])
|
||||||
|
|
||||||
|
if(filteredExistVarForms.length === 0)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelWrap
|
||||||
|
nodeName={nodeName}
|
||||||
|
onHide={onHide}
|
||||||
|
>
|
||||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||||
<div className='mt-3 space-y-4 px-4'>
|
<div className='mt-3 space-y-4 px-4'>
|
||||||
{forms.map((form, index) => (
|
{filteredExistVarForms.map((form, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<Form
|
<Form
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -174,32 +165,12 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||||
{isRunning && (
|
<Button disabled={!isFileLoaded} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
||||||
<div
|
<div>{t(`${i18nPrefix}.startRun`)}</div>
|
||||||
className='cursor-pointer rounded-lg border border-divider-regular bg-components-button-secondary-bg p-2 shadow-xs'
|
|
||||||
onClick={onStop}
|
|
||||||
>
|
|
||||||
<StopCircle className='h-4 w-4 text-text-tertiary' />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button disabled={!isFileLoaded || isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
|
||||||
{isRunning && <RiLoader2Line className='h-4 w-4 animate-spin' />}
|
|
||||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{isRunning && (
|
|
||||||
<ResultPanel status='running' showSteps={false} />
|
|
||||||
)}
|
|
||||||
{isFinished && (
|
|
||||||
<>
|
|
||||||
{result}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</PanelWrap>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(BeforeRunForm)
|
export default React.memo(BeforeRunForm)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiCloseLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
|
||||||
|
const i18nPrefix = 'workflow.singleRun'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
nodeName: string
|
||||||
|
onHide: () => void
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const PanelWrap: FC<Props> = ({
|
||||||
|
nodeName,
|
||||||
|
onHide,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className='absolute inset-0 z-10 rounded-2xl bg-background-overlay-alt'>
|
||||||
|
<div className='flex h-full flex-col rounded-2xl bg-components-panel-bg'>
|
||||||
|
<div className='flex h-8 shrink-0 items-center justify-between pl-4 pr-3 pt-3'>
|
||||||
|
<div className='truncate text-base font-semibold text-text-primary'>
|
||||||
|
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
||||||
|
</div>
|
||||||
|
<div className='ml-2 shrink-0 cursor-pointer p-1' onClick={() => {
|
||||||
|
onHide()
|
||||||
|
}}>
|
||||||
|
<RiCloseLine className='h-4 w-4 text-text-tertiary ' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(PanelWrap)
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
useNodesInteractions,
|
useNodesInteractions,
|
||||||
useNodesSyncDraft,
|
useNodesSyncDraft,
|
||||||
} from '../../../hooks'
|
} from '../../../hooks'
|
||||||
import type { Node } from '../../../types'
|
import { type Node, NodeRunningStatus } from '../../../types'
|
||||||
import { canRunBySingle } from '../../../utils'
|
import { canRunBySingle } from '../../../utils'
|
||||||
import PanelOperator from './panel-operator'
|
import PanelOperator from './panel-operator'
|
||||||
import {
|
import {
|
||||||
|
|
@ -31,11 +31,12 @@ const NodeControl: FC<NodeControlProps> = ({
|
||||||
const { handleNodeDataUpdate } = useNodeDataUpdate()
|
const { handleNodeDataUpdate } = useNodeDataUpdate()
|
||||||
const { handleNodeSelect } = useNodesInteractions()
|
const { handleNodeSelect } = useNodesInteractions()
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const isSingleRunning = data._singleRunningStatus === NodeRunningStatus.Running
|
||||||
const handleOpenChange = useCallback((newOpen: boolean) => {
|
const handleOpenChange = useCallback((newOpen: boolean) => {
|
||||||
setOpen(newOpen)
|
setOpen(newOpen)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
|
|
@ -49,23 +50,25 @@ const NodeControl: FC<NodeControlProps> = ({
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
canRunBySingle(data.type) && (
|
canRunBySingle(data.type, isChildNode) && (
|
||||||
<div
|
<div
|
||||||
className='flex h-5 w-5 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
className='flex h-5 w-5 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
const nextData: Record<string, any> = {
|
||||||
|
_isSingleRun: !isSingleRunning,
|
||||||
|
}
|
||||||
|
if(isSingleRunning)
|
||||||
|
nextData._singleRunningStatus = undefined
|
||||||
|
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: nextData,
|
||||||
_isSingleRun: !data._isSingleRun,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
handleNodeSelect(id)
|
handleNodeSelect(id)
|
||||||
if (!data._isSingleRun)
|
|
||||||
handleSyncWorkflowDraft(true)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
data._isSingleRun
|
isSingleRunning
|
||||||
? <Stop className='h-3 w-3' />
|
? <Stop className='h-3 w-3' />
|
||||||
: (
|
: (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|
|
||||||
|
|
@ -83,14 +83,16 @@ const PanelOperatorPopup = ({
|
||||||
|
|
||||||
const link = useNodeHelpLink(data.type)
|
const link = useNodeHelpLink(data.type)
|
||||||
|
|
||||||
|
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
|
<div className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
|
||||||
{
|
{
|
||||||
(showChangeBlock || canRunBySingle(data.type)) && (
|
(showChangeBlock || canRunBySingle(data.type, isChildNode)) && (
|
||||||
<>
|
<>
|
||||||
<div className='p-1'>
|
<div className='p-1'>
|
||||||
{
|
{
|
||||||
canRunBySingle(data.type) && (
|
canRunBySingle(data.type, isChildNode) && (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary
|
flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,429 @@
|
||||||
|
import type {
|
||||||
|
FC,
|
||||||
|
ReactNode,
|
||||||
|
} from 'react'
|
||||||
|
import {
|
||||||
|
cloneElement,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import {
|
||||||
|
RiCloseLine,
|
||||||
|
RiPlayLargeLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import { useShallow } from 'zustand/react/shallow'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import NextStep from '../next-step'
|
||||||
|
import PanelOperator from '../panel-operator'
|
||||||
|
import NodePosition from '@/app/components/workflow/nodes/_base/components/node-position'
|
||||||
|
import HelpLink from '../help-link'
|
||||||
|
import {
|
||||||
|
DescriptionInput,
|
||||||
|
TitleInput,
|
||||||
|
} from '../title-description-input'
|
||||||
|
import ErrorHandleOnPanel from '../error-handle/error-handle-on-panel'
|
||||||
|
import RetryOnPanel from '../retry/retry-on-panel'
|
||||||
|
import { useResizePanel } from '../../hooks/use-resize-panel'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
|
import {
|
||||||
|
WorkflowHistoryEvent,
|
||||||
|
useAvailableBlocks,
|
||||||
|
useNodeDataUpdate,
|
||||||
|
useNodesInteractions,
|
||||||
|
useNodesReadOnly,
|
||||||
|
useToolIcon,
|
||||||
|
useWorkflowHistory,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import {
|
||||||
|
canRunBySingle,
|
||||||
|
hasErrorHandleNode,
|
||||||
|
hasRetryNode,
|
||||||
|
} from '@/app/components/workflow/utils'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import { BlockEnum, type Node, NodeRunningStatus } from '@/app/components/workflow/types'
|
||||||
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
|
import Tab, { TabType } from './tab'
|
||||||
|
import LastRun from './last-run'
|
||||||
|
import useLastRun from './last-run/use-last-run'
|
||||||
|
import BeforeRunForm from '../before-run-form'
|
||||||
|
import { debounce } from 'lodash-es'
|
||||||
|
import { NODES_EXTRA_DATA } from '@/app/components/workflow/constants'
|
||||||
|
import { useLogs } from '@/app/components/workflow/run/hooks'
|
||||||
|
import PanelWrap from '../before-run-form/panel-wrap'
|
||||||
|
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
||||||
|
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||||
|
|
||||||
|
type BasePanelProps = {
|
||||||
|
children: ReactNode
|
||||||
|
} & Node
|
||||||
|
|
||||||
|
const BasePanel: FC<BasePanelProps> = ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
children,
|
||||||
|
position,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { showMessageLogModal } = useAppStore(useShallow(state => ({
|
||||||
|
showMessageLogModal: state.showMessageLogModal,
|
||||||
|
})))
|
||||||
|
const isSingleRunning = data._singleRunningStatus === NodeRunningStatus.Running
|
||||||
|
|
||||||
|
const showSingleRunPanel = useStore(s => s.showSingleRunPanel)
|
||||||
|
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
|
||||||
|
const nodePanelWidth = useStore(s => s.nodePanelWidth)
|
||||||
|
const otherPanelWidth = useStore(s => s.otherPanelWidth)
|
||||||
|
const setNodePanelWidth = useStore(s => s.setNodePanelWidth)
|
||||||
|
|
||||||
|
const maxNodePanelWidth = useMemo(() => {
|
||||||
|
if (!workflowCanvasWidth)
|
||||||
|
return 720
|
||||||
|
if (!otherPanelWidth)
|
||||||
|
return workflowCanvasWidth - 400
|
||||||
|
|
||||||
|
return workflowCanvasWidth - otherPanelWidth - 400
|
||||||
|
}, [workflowCanvasWidth, otherPanelWidth])
|
||||||
|
|
||||||
|
const updateNodePanelWidth = useCallback((width: number) => {
|
||||||
|
// Ensure the width is within the min and max range
|
||||||
|
const newValue = Math.min(Math.max(width, 400), maxNodePanelWidth)
|
||||||
|
localStorage.setItem('workflow-node-panel-width', `${newValue}`)
|
||||||
|
setNodePanelWidth(newValue)
|
||||||
|
}, [maxNodePanelWidth, setNodePanelWidth])
|
||||||
|
|
||||||
|
const handleResize = useCallback((width: number) => {
|
||||||
|
updateNodePanelWidth(width)
|
||||||
|
}, [updateNodePanelWidth])
|
||||||
|
|
||||||
|
const {
|
||||||
|
triggerRef,
|
||||||
|
containerRef,
|
||||||
|
} = useResizePanel({
|
||||||
|
direction: 'horizontal',
|
||||||
|
triggerDirection: 'left',
|
||||||
|
minWidth: 400,
|
||||||
|
maxWidth: maxNodePanelWidth,
|
||||||
|
onResize: debounce(handleResize),
|
||||||
|
})
|
||||||
|
|
||||||
|
const debounceUpdate = debounce(updateNodePanelWidth)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!workflowCanvasWidth)
|
||||||
|
return
|
||||||
|
if (workflowCanvasWidth - 400 <= nodePanelWidth + otherPanelWidth)
|
||||||
|
debounceUpdate(workflowCanvasWidth - 400 - otherPanelWidth)
|
||||||
|
}, [nodePanelWidth, otherPanelWidth, workflowCanvasWidth, updateNodePanelWidth])
|
||||||
|
|
||||||
|
const { handleNodeSelect } = useNodesInteractions()
|
||||||
|
const { nodesReadOnly } = useNodesReadOnly()
|
||||||
|
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
||||||
|
const toolIcon = useToolIcon(data)
|
||||||
|
|
||||||
|
const { saveStateToHistory } = useWorkflowHistory()
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleNodeDataUpdate,
|
||||||
|
handleNodeDataUpdateWithSyncDraft,
|
||||||
|
} = useNodeDataUpdate()
|
||||||
|
|
||||||
|
const handleTitleBlur = useCallback((title: string) => {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
|
||||||
|
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange)
|
||||||
|
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||||
|
const handleDescriptionChange = useCallback((desc: string) => {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
|
||||||
|
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange)
|
||||||
|
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||||
|
|
||||||
|
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||||
|
const isSupportSingleRun = canRunBySingle(data.type, isChildNode)
|
||||||
|
const appDetail = useAppStore(state => state.appDetail)
|
||||||
|
|
||||||
|
const hasClickRunning = useRef(false)
|
||||||
|
const [isPaused, setIsPaused] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(data._singleRunningStatus === NodeRunningStatus.Running) {
|
||||||
|
hasClickRunning.current = true
|
||||||
|
setIsPaused(false)
|
||||||
|
}
|
||||||
|
else if(data._isSingleRun && data._singleRunningStatus === undefined && hasClickRunning) {
|
||||||
|
setIsPaused(true)
|
||||||
|
hasClickRunning.current = false
|
||||||
|
}
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
const updateNodeRunningStatus = useCallback((status: NodeRunningStatus) => {
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
_singleRunningStatus: status,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [handleNodeDataUpdate, id, data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log(`id changed: ${id}, hasClickRunning: ${hasClickRunning.current}`)
|
||||||
|
hasClickRunning.current = false
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const {
|
||||||
|
isShowSingleRun,
|
||||||
|
hideSingleRun,
|
||||||
|
runningStatus,
|
||||||
|
handleStop,
|
||||||
|
runInputData,
|
||||||
|
runInputDataRef,
|
||||||
|
runResult,
|
||||||
|
getInputVars,
|
||||||
|
toVarInputs,
|
||||||
|
tabType,
|
||||||
|
isRunAfterSingleRun,
|
||||||
|
setTabType,
|
||||||
|
singleRunParams,
|
||||||
|
nodeInfo,
|
||||||
|
setRunInputData,
|
||||||
|
handleSingleRun,
|
||||||
|
handleRunWithParams,
|
||||||
|
getExistVarValuesInForms,
|
||||||
|
getFilteredExistVarForms,
|
||||||
|
} = useLastRun<typeof data>({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
defaultRunInputData: NODES_EXTRA_DATA[data.type]?.defaultRunInputData || {},
|
||||||
|
isPaused,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsPaused(false)
|
||||||
|
}, [tabType])
|
||||||
|
|
||||||
|
const logParams = useLogs()
|
||||||
|
const passedLogParams = (() => {
|
||||||
|
if ([BlockEnum.Tool, BlockEnum.Agent, BlockEnum.Iteration, BlockEnum.Loop].includes(data.type))
|
||||||
|
return logParams
|
||||||
|
|
||||||
|
return {}
|
||||||
|
})()
|
||||||
|
|
||||||
|
if(logParams.showSpecialResultPanel) {
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'relative mr-1 h-full',
|
||||||
|
)}>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||||
|
style={{
|
||||||
|
width: `${nodePanelWidth}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PanelWrap
|
||||||
|
nodeName={data.title}
|
||||||
|
onHide={hideSingleRun}
|
||||||
|
>
|
||||||
|
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||||
|
<SpecialResultPanel {...passedLogParams} />
|
||||||
|
</div>
|
||||||
|
</PanelWrap>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShowSingleRun) {
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'relative mr-1 h-full',
|
||||||
|
)}>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||||
|
style={{
|
||||||
|
width: `${nodePanelWidth}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BeforeRunForm
|
||||||
|
nodeName={data.title}
|
||||||
|
nodeType={data.type}
|
||||||
|
onHide={hideSingleRun}
|
||||||
|
onRun={handleRunWithParams}
|
||||||
|
{...singleRunParams!}
|
||||||
|
{...passedLogParams}
|
||||||
|
existVarValuesInForms={getExistVarValuesInForms(singleRunParams?.forms as any)}
|
||||||
|
filteredExistVarForms={getFilteredExistVarForms(singleRunParams?.forms as any)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'relative mr-1 h-full',
|
||||||
|
showMessageLogModal && '!absolute -top-[5px] right-[416px] z-0 !mr-0 w-[384px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border shadow-lg transition-all',
|
||||||
|
)}>
|
||||||
|
<div
|
||||||
|
ref={triggerRef}
|
||||||
|
className='absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center'>
|
||||||
|
<div className='h-10 w-0.5 rounded-sm bg-state-base-handle hover:h-full hover:bg-state-accent-solid active:h-full active:bg-state-accent-solid'></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||||
|
style={{
|
||||||
|
width: `${nodePanelWidth}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='sticky top-0 z-10 shrink-0 border-b-[0.5px] border-divider-regular bg-components-panel-bg'>
|
||||||
|
<div className='flex items-center px-4 pb-1 pt-4'>
|
||||||
|
<BlockIcon
|
||||||
|
className='mr-1 shrink-0'
|
||||||
|
type={data.type}
|
||||||
|
toolIcon={toolIcon}
|
||||||
|
size='md'
|
||||||
|
/>
|
||||||
|
<TitleInput
|
||||||
|
value={data.title || ''}
|
||||||
|
onBlur={handleTitleBlur}
|
||||||
|
/>
|
||||||
|
<div className='flex shrink-0 items-center text-text-tertiary'>
|
||||||
|
{
|
||||||
|
isSupportSingleRun && !nodesReadOnly && (
|
||||||
|
<Tooltip
|
||||||
|
popupContent={t('workflow.panel.runThisStep')}
|
||||||
|
popupClassName='mr-1'
|
||||||
|
disabled={isSingleRunning}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||||
|
onClick={() => {
|
||||||
|
if(isSingleRunning) {
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
_isSingleRun: false,
|
||||||
|
_singleRunningStatus: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleSingleRun()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
isSingleRunning ? <Stop className='h-4 w-4 text-text-tertiary' />
|
||||||
|
: <RiPlayLargeLine className='h-4 w-4 text-text-tertiary' />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<NodePosition nodePosition={position} nodeWidth={width} nodeHeight={height}></NodePosition>
|
||||||
|
<HelpLink nodeType={data.type} />
|
||||||
|
<PanelOperator id={id} data={data} showHelpLink={false} />
|
||||||
|
<div className='mx-3 h-3.5 w-[1px] bg-divider-regular' />
|
||||||
|
<div
|
||||||
|
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
||||||
|
onClick={() => handleNodeSelect(id, true)}
|
||||||
|
>
|
||||||
|
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='p-2'>
|
||||||
|
<DescriptionInput
|
||||||
|
value={data.desc || ''}
|
||||||
|
onChange={handleDescriptionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='pl-4'>
|
||||||
|
<Tab
|
||||||
|
value={tabType}
|
||||||
|
onChange={setTabType}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Split />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{tabType === TabType.settings && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
{cloneElement(children as any, {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
panelProps: {
|
||||||
|
getInputVars,
|
||||||
|
toVarInputs,
|
||||||
|
runInputData,
|
||||||
|
setRunInputData,
|
||||||
|
runResult,
|
||||||
|
runInputDataRef,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Split />
|
||||||
|
{
|
||||||
|
hasRetryNode(data.type) && (
|
||||||
|
<RetryOnPanel
|
||||||
|
id={id}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hasErrorHandleNode(data.type) && (
|
||||||
|
<ErrorHandleOnPanel
|
||||||
|
id={id}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!!availableNextBlocks.length && (
|
||||||
|
<div className='border-t-[0.5px] border-divider-regular p-4'>
|
||||||
|
<div className='system-sm-semibold-uppercase mb-1 flex items-center text-text-secondary'>
|
||||||
|
{t('workflow.panel.nextStep').toLocaleUpperCase()}
|
||||||
|
</div>
|
||||||
|
<div className='system-xs-regular mb-2 text-text-tertiary'>
|
||||||
|
{t('workflow.panel.addNextStep')}
|
||||||
|
</div>
|
||||||
|
<NextStep selectedNode={{ id, data } as Node} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tabType === TabType.lastRun && (
|
||||||
|
<LastRun
|
||||||
|
appId={appDetail?.id || ''}
|
||||||
|
nodeId={id}
|
||||||
|
canSingleRun={isSupportSingleRun}
|
||||||
|
runningStatus={runningStatus}
|
||||||
|
isRunAfterSingleRun={isRunAfterSingleRun}
|
||||||
|
updateNodeRunningStatus={updateNodeRunningStatus}
|
||||||
|
onSingleRunClicked={handleSingleRun}
|
||||||
|
nodeInfo={nodeInfo}
|
||||||
|
singleRunResult={runResult!}
|
||||||
|
isPaused={isPaused}
|
||||||
|
{...passedLogParams}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(BasePanel)
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
'use client'
|
||||||
|
import type { ResultPanelProps } from '@/app/components/workflow/run/result-panel'
|
||||||
|
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||||
|
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import NoData from './no-data'
|
||||||
|
import { useLastRun } from '@/service/use-workflow'
|
||||||
|
import { RiLoader2Line } from '@remixicon/react'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
appId: string
|
||||||
|
nodeId: string
|
||||||
|
canSingleRun: boolean
|
||||||
|
isRunAfterSingleRun: boolean
|
||||||
|
updateNodeRunningStatus: (status: NodeRunningStatus) => void
|
||||||
|
nodeInfo?: NodeTracing
|
||||||
|
runningStatus?: NodeRunningStatus
|
||||||
|
onSingleRunClicked: () => void
|
||||||
|
singleRunResult?: NodeTracing
|
||||||
|
isPaused?: boolean
|
||||||
|
} & Partial<ResultPanelProps>
|
||||||
|
|
||||||
|
const LastRun: FC<Props> = ({
|
||||||
|
appId,
|
||||||
|
nodeId,
|
||||||
|
canSingleRun,
|
||||||
|
isRunAfterSingleRun,
|
||||||
|
updateNodeRunningStatus,
|
||||||
|
nodeInfo,
|
||||||
|
runningStatus: oneStepRunRunningStatus,
|
||||||
|
onSingleRunClicked,
|
||||||
|
singleRunResult,
|
||||||
|
isPaused,
|
||||||
|
...otherResultPanelProps
|
||||||
|
}) => {
|
||||||
|
const isOneStepRunSucceed = oneStepRunRunningStatus === NodeRunningStatus.Succeeded
|
||||||
|
const isOneStepRunFailed = oneStepRunRunningStatus === NodeRunningStatus.Failed
|
||||||
|
// hide page and return to page would lost the oneStepRunRunningStatus
|
||||||
|
const [hidePageOneStepFinishedStatus, setHidePageOneStepFinishedStatus] = React.useState<NodeRunningStatus | null>(null)
|
||||||
|
const [pageHasHide, setPageHasHide] = useState(false)
|
||||||
|
const [pageShowed, setPageShowed] = useState(false)
|
||||||
|
|
||||||
|
const hidePageOneStepRunFinished = [NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(hidePageOneStepFinishedStatus!)
|
||||||
|
const canRunLastRun = !isRunAfterSingleRun || isOneStepRunSucceed || isOneStepRunFailed || (pageHasHide && hidePageOneStepRunFinished)
|
||||||
|
const { data: lastRunResult, isFetching, error } = useLastRun(appId, nodeId, canRunLastRun)
|
||||||
|
const isRunning = useMemo(() => {
|
||||||
|
if(isPaused)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if(!isRunAfterSingleRun)
|
||||||
|
return isFetching
|
||||||
|
return [NodeRunningStatus.Running, NodeRunningStatus.NotStart].includes(oneStepRunRunningStatus!)
|
||||||
|
}, [isFetching, isPaused, isRunAfterSingleRun, oneStepRunRunningStatus])
|
||||||
|
|
||||||
|
const noLastRun = (error as any)?.status === 404
|
||||||
|
const runResult = (canRunLastRun ? lastRunResult : singleRunResult) || lastRunResult || {}
|
||||||
|
|
||||||
|
const resetHidePageStatus = useCallback(() => {
|
||||||
|
setPageHasHide(false)
|
||||||
|
setPageShowed(false)
|
||||||
|
setHidePageOneStepFinishedStatus(null)
|
||||||
|
}, [])
|
||||||
|
useEffect(() => {
|
||||||
|
if (pageShowed && hidePageOneStepFinishedStatus && (!oneStepRunRunningStatus || oneStepRunRunningStatus === NodeRunningStatus.NotStart)) {
|
||||||
|
updateNodeRunningStatus(hidePageOneStepFinishedStatus)
|
||||||
|
resetHidePageStatus()
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isOneStepRunSucceed, isOneStepRunFailed, oneStepRunRunningStatus])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if([NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(oneStepRunRunningStatus!))
|
||||||
|
setHidePageOneStepFinishedStatus(oneStepRunRunningStatus!)
|
||||||
|
}, [oneStepRunRunningStatus])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
resetHidePageStatus()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [nodeId])
|
||||||
|
|
||||||
|
const handlePageVisibilityChange = useCallback(() => {
|
||||||
|
if (document.visibilityState === 'hidden')
|
||||||
|
setPageHasHide(true)
|
||||||
|
else
|
||||||
|
setPageShowed(true)
|
||||||
|
}, [])
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('visibilitychange', handlePageVisibilityChange)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('visibilitychange', handlePageVisibilityChange)
|
||||||
|
}
|
||||||
|
}, [handlePageVisibilityChange])
|
||||||
|
|
||||||
|
if (isFetching && !isRunAfterSingleRun) {
|
||||||
|
return (
|
||||||
|
<div className='flex h-0 grow flex-col items-center justify-center'>
|
||||||
|
<RiLoader2Line className='size-4 animate-spin text-text-tertiary' />
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRunning)
|
||||||
|
return <ResultPanel status='running' showSteps={false} />
|
||||||
|
|
||||||
|
if (!isPaused && (noLastRun || !runResult)) {
|
||||||
|
return (
|
||||||
|
<NoData canSingleRun={canSingleRun} onSingleRun={onSingleRunClicked} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ResultPanel
|
||||||
|
{...runResult as any}
|
||||||
|
{...otherResultPanelProps}
|
||||||
|
status={isPaused ? NodeRunningStatus.Stopped : ((runResult as any).status || otherResultPanelProps.status)}
|
||||||
|
total_tokens={(runResult as any)?.execution_metadata?.total_tokens || otherResultPanelProps?.total_tokens}
|
||||||
|
created_by={(runResult as any)?.created_by_account?.created_by || otherResultPanelProps?.created_by}
|
||||||
|
nodeInfo={nodeInfo}
|
||||||
|
showSteps={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(LastRun)
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { RiPlayLine } from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
canSingleRun: boolean
|
||||||
|
onSingleRun: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoData: FC<Props> = ({
|
||||||
|
canSingleRun,
|
||||||
|
onSingleRun,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className='flex h-0 grow flex-col items-center justify-center'>
|
||||||
|
<ClockPlay className='h-8 w-8 text-text-quaternary' />
|
||||||
|
<div className='system-xs-regular my-2 text-text-tertiary'>{t('workflow.debug.noData.description')}</div>
|
||||||
|
{canSingleRun && (
|
||||||
|
<Button
|
||||||
|
className='flex'
|
||||||
|
size='small'
|
||||||
|
onClick={onSingleRun}
|
||||||
|
>
|
||||||
|
<RiPlayLine className='mr-1 h-3.5 w-3.5' />
|
||||||
|
<div>{t('workflow.debug.noData.runThisNode')}</div>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(NoData)
|
||||||
|
|
@ -0,0 +1,330 @@
|
||||||
|
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||||
|
import type { Params as OneStepRunParams } from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { TabType } from '../tab'
|
||||||
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
|
import useStartSingleRunFormParams from '@/app/components/workflow/nodes/start/use-single-run-form-params'
|
||||||
|
import useLLMSingleRunFormParams from '@/app/components/workflow/nodes/llm/use-single-run-form-params'
|
||||||
|
import useKnowledgeRetrievalSingleRunFormParams from '@/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params'
|
||||||
|
import useCodeSingleRunFormParams from '@/app/components/workflow/nodes/code/use-single-run-form-params'
|
||||||
|
import useTemplateTransformSingleRunFormParams from '@/app/components/workflow/nodes/template-transform/use-single-run-form-params'
|
||||||
|
import useQuestionClassifierSingleRunFormParams from '@/app/components/workflow/nodes/question-classifier/use-single-run-form-params'
|
||||||
|
import useParameterExtractorSingleRunFormParams from '@/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params'
|
||||||
|
import useHttpRequestSingleRunFormParams from '@/app/components/workflow/nodes/http/use-single-run-form-params'
|
||||||
|
import useToolSingleRunFormParams from '@/app/components/workflow/nodes/tool/use-single-run-form-params'
|
||||||
|
import useIterationSingleRunFormParams from '@/app/components/workflow/nodes/iteration/use-single-run-form-params'
|
||||||
|
import useAgentSingleRunFormParams from '@/app/components/workflow/nodes/agent/use-single-run-form-params'
|
||||||
|
import useDocExtractorSingleRunFormParams from '@/app/components/workflow/nodes/document-extractor/use-single-run-form-params'
|
||||||
|
import useLoopSingleRunFormParams from '@/app/components/workflow/nodes/loop/use-single-run-form-params'
|
||||||
|
import useIfElseSingleRunFormParams from '@/app/components/workflow/nodes/if-else/use-single-run-form-params'
|
||||||
|
import useVariableAggregatorSingleRunFormParams from '@/app/components/workflow/nodes/variable-assigner/use-single-run-form-params'
|
||||||
|
import useVariableAssignerSingleRunFormParams from '@/app/components/workflow/nodes/assigner/use-single-run-form-params'
|
||||||
|
|
||||||
|
import useToolGetDataForCheckMore from '@/app/components/workflow/nodes/tool/use-get-data-for-check-more'
|
||||||
|
import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config'
|
||||||
|
|
||||||
|
// import
|
||||||
|
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
|
||||||
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
import {
|
||||||
|
useNodesSyncDraft,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
|
||||||
|
import { useInvalidLastRun } from '@/service/use-workflow'
|
||||||
|
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
|
|
||||||
|
const singleRunFormParamsHooks: Record<BlockEnum, any> = {
|
||||||
|
[BlockEnum.LLM]: useLLMSingleRunFormParams,
|
||||||
|
[BlockEnum.KnowledgeRetrieval]: useKnowledgeRetrievalSingleRunFormParams,
|
||||||
|
[BlockEnum.Code]: useCodeSingleRunFormParams,
|
||||||
|
[BlockEnum.TemplateTransform]: useTemplateTransformSingleRunFormParams,
|
||||||
|
[BlockEnum.QuestionClassifier]: useQuestionClassifierSingleRunFormParams,
|
||||||
|
[BlockEnum.HttpRequest]: useHttpRequestSingleRunFormParams,
|
||||||
|
[BlockEnum.Tool]: useToolSingleRunFormParams,
|
||||||
|
[BlockEnum.ParameterExtractor]: useParameterExtractorSingleRunFormParams,
|
||||||
|
[BlockEnum.Iteration]: useIterationSingleRunFormParams,
|
||||||
|
[BlockEnum.Agent]: useAgentSingleRunFormParams,
|
||||||
|
[BlockEnum.DocExtractor]: useDocExtractorSingleRunFormParams,
|
||||||
|
[BlockEnum.Loop]: useLoopSingleRunFormParams,
|
||||||
|
[BlockEnum.Start]: useStartSingleRunFormParams,
|
||||||
|
[BlockEnum.IfElse]: useIfElseSingleRunFormParams,
|
||||||
|
[BlockEnum.VariableAggregator]: useVariableAggregatorSingleRunFormParams,
|
||||||
|
[BlockEnum.Assigner]: useVariableAssignerSingleRunFormParams,
|
||||||
|
[BlockEnum.VariableAssigner]: undefined,
|
||||||
|
[BlockEnum.End]: undefined,
|
||||||
|
[BlockEnum.Answer]: undefined,
|
||||||
|
[BlockEnum.ListFilter]: undefined,
|
||||||
|
[BlockEnum.IterationStart]: undefined,
|
||||||
|
[BlockEnum.LoopStart]: undefined,
|
||||||
|
[BlockEnum.LoopEnd]: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSingleRunFormParamsHooks = (nodeType: BlockEnum) => {
|
||||||
|
return (params: any) => {
|
||||||
|
return singleRunFormParamsHooks[nodeType]?.(params) || {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDataForCheckMoreHooks: Record<BlockEnum, any> = {
|
||||||
|
[BlockEnum.Tool]: useToolGetDataForCheckMore,
|
||||||
|
[BlockEnum.LLM]: undefined,
|
||||||
|
[BlockEnum.KnowledgeRetrieval]: undefined,
|
||||||
|
[BlockEnum.Code]: undefined,
|
||||||
|
[BlockEnum.TemplateTransform]: undefined,
|
||||||
|
[BlockEnum.QuestionClassifier]: undefined,
|
||||||
|
[BlockEnum.HttpRequest]: undefined,
|
||||||
|
[BlockEnum.ParameterExtractor]: undefined,
|
||||||
|
[BlockEnum.Iteration]: undefined,
|
||||||
|
[BlockEnum.Agent]: undefined,
|
||||||
|
[BlockEnum.DocExtractor]: undefined,
|
||||||
|
[BlockEnum.Loop]: undefined,
|
||||||
|
[BlockEnum.Start]: undefined,
|
||||||
|
[BlockEnum.IfElse]: undefined,
|
||||||
|
[BlockEnum.VariableAggregator]: undefined,
|
||||||
|
[BlockEnum.End]: undefined,
|
||||||
|
[BlockEnum.Answer]: undefined,
|
||||||
|
[BlockEnum.VariableAssigner]: undefined,
|
||||||
|
[BlockEnum.ListFilter]: undefined,
|
||||||
|
[BlockEnum.IterationStart]: undefined,
|
||||||
|
[BlockEnum.Assigner]: undefined,
|
||||||
|
[BlockEnum.LoopStart]: undefined,
|
||||||
|
[BlockEnum.LoopEnd]: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const useGetDataForCheckMoreHooks = <T>(nodeType: BlockEnum) => {
|
||||||
|
return (id: string, payload: CommonNodeType<T>) => {
|
||||||
|
return getDataForCheckMoreHooks[nodeType]?.({ id, payload }) || {
|
||||||
|
getData: () => {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params<T> = Omit<OneStepRunParams<T>, 'isRunAfterSingleRun'>
|
||||||
|
const useLastRun = <T>({
|
||||||
|
...oneStepRunParams
|
||||||
|
}: Params<T>) => {
|
||||||
|
const { conversationVars, systemVars, hasSetInspectVar } = useInspectVarsCrud()
|
||||||
|
const blockType = oneStepRunParams.data.type
|
||||||
|
const isStartNode = blockType === BlockEnum.Start
|
||||||
|
const isIterationNode = blockType === BlockEnum.Iteration
|
||||||
|
const isLoopNode = blockType === BlockEnum.Loop
|
||||||
|
const isAggregatorNode = blockType === BlockEnum.VariableAggregator
|
||||||
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const {
|
||||||
|
getData: getDataForCheckMore,
|
||||||
|
} = useGetDataForCheckMoreHooks<T>(blockType)(oneStepRunParams.id, oneStepRunParams.data)
|
||||||
|
const [isRunAfterSingleRun, setIsRunAfterSingleRun] = useState(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
} = oneStepRunParams
|
||||||
|
const oneStepRunRes = useOneStepRun({
|
||||||
|
...oneStepRunParams,
|
||||||
|
iteratorInputKey: blockType === BlockEnum.Iteration ? `${id}.input_selector` : '',
|
||||||
|
moreDataForCheckValid: getDataForCheckMore(),
|
||||||
|
isRunAfterSingleRun,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
appId,
|
||||||
|
hideSingleRun,
|
||||||
|
handleRun: doCallRunApi,
|
||||||
|
getInputVars,
|
||||||
|
toVarInputs,
|
||||||
|
varSelectorsToVarInputs,
|
||||||
|
runInputData,
|
||||||
|
runInputDataRef,
|
||||||
|
setRunInputData,
|
||||||
|
showSingleRun,
|
||||||
|
runResult,
|
||||||
|
iterationRunResult,
|
||||||
|
loopRunResult,
|
||||||
|
setNodeRunning,
|
||||||
|
checkValid,
|
||||||
|
} = oneStepRunRes
|
||||||
|
|
||||||
|
const {
|
||||||
|
nodeInfo,
|
||||||
|
...singleRunParams
|
||||||
|
} = useSingleRunFormParamsHooks(blockType)({
|
||||||
|
id,
|
||||||
|
payload: data,
|
||||||
|
runInputData,
|
||||||
|
runInputDataRef,
|
||||||
|
getInputVars,
|
||||||
|
setRunInputData,
|
||||||
|
toVarInputs,
|
||||||
|
varSelectorsToVarInputs,
|
||||||
|
runResult,
|
||||||
|
iterationRunResult,
|
||||||
|
loopRunResult,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toSubmitData = useCallback((data: Record<string, any>) => {
|
||||||
|
if(!isIterationNode && !isLoopNode)
|
||||||
|
return data
|
||||||
|
|
||||||
|
const allVarObject = singleRunParams?.allVarObject || {}
|
||||||
|
const formattedData: Record<string, any> = {}
|
||||||
|
Object.keys(allVarObject).forEach((key) => {
|
||||||
|
const [varSectorStr, nodeId] = key.split(DELIMITER)
|
||||||
|
formattedData[`${nodeId}.${allVarObject[key].inSingleRunPassedKey}`] = data[varSectorStr]
|
||||||
|
})
|
||||||
|
if(isIterationNode) {
|
||||||
|
const iteratorInputKey = `${id}.input_selector`
|
||||||
|
formattedData[iteratorInputKey] = data[iteratorInputKey]
|
||||||
|
}
|
||||||
|
return formattedData
|
||||||
|
}, [isIterationNode, isLoopNode, singleRunParams?.allVarObject, id])
|
||||||
|
|
||||||
|
const callRunApi = (data: Record<string, any>, cb?: () => void) => {
|
||||||
|
handleSyncWorkflowDraft(true, true, {
|
||||||
|
onSuccess() {
|
||||||
|
doCallRunApi(toSubmitData(data))
|
||||||
|
cb?.()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
const { setInitShowLastRunTab } = workflowStore.getState()
|
||||||
|
const initShowLastRunTab = useStore(s => s.initShowLastRunTab)
|
||||||
|
const [tabType, setTabType] = useState<TabType>(initShowLastRunTab ? TabType.lastRun : TabType.settings)
|
||||||
|
useEffect(() => {
|
||||||
|
if(initShowLastRunTab)
|
||||||
|
setTabType(TabType.lastRun)
|
||||||
|
|
||||||
|
setInitShowLastRunTab(false)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [initShowLastRunTab])
|
||||||
|
const invalidLastRun = useInvalidLastRun(appId!, id)
|
||||||
|
|
||||||
|
const handleRunWithParams = async (data: Record<string, any>) => {
|
||||||
|
const { isValid } = checkValid()
|
||||||
|
if(!isValid)
|
||||||
|
return
|
||||||
|
setNodeRunning()
|
||||||
|
setIsRunAfterSingleRun(true)
|
||||||
|
setTabType(TabType.lastRun)
|
||||||
|
callRunApi(data, () => {
|
||||||
|
invalidLastRun()
|
||||||
|
})
|
||||||
|
hideSingleRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTabClicked = useCallback((type: TabType) => {
|
||||||
|
setIsRunAfterSingleRun(false)
|
||||||
|
setTabType(type)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getExistVarValuesInForms = (forms: FormProps[]) => {
|
||||||
|
if (!forms || forms.length === 0)
|
||||||
|
return []
|
||||||
|
|
||||||
|
const valuesArr = forms.map((form) => {
|
||||||
|
const values: Record<string, boolean> = {}
|
||||||
|
form.inputs.forEach(({ variable, getVarValueFromDependent }) => {
|
||||||
|
const isGetValueFromDependent = getVarValueFromDependent || !variable.includes('.')
|
||||||
|
if(isGetValueFromDependent && !singleRunParams?.getDependentVar)
|
||||||
|
return
|
||||||
|
|
||||||
|
const selector = isGetValueFromDependent ? (singleRunParams?.getDependentVar(variable) || []) : variable.slice(1, -1).split('.')
|
||||||
|
if(!selector || selector.length === 0)
|
||||||
|
return
|
||||||
|
const [nodeId, varName] = selector.slice(0, 2)
|
||||||
|
if(!isStartNode && nodeId === id) { // inner vars like loop vars
|
||||||
|
values[variable] = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const inspectVarValue = hasSetInspectVar(nodeId, varName, systemVars, conversationVars) // also detect system var , env and conversation var
|
||||||
|
if (inspectVarValue)
|
||||||
|
values[variable] = true
|
||||||
|
})
|
||||||
|
return values
|
||||||
|
})
|
||||||
|
return valuesArr
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAllVarsHasValue = (vars?: ValueSelector[]) => {
|
||||||
|
if(!vars || vars.length === 0)
|
||||||
|
return true
|
||||||
|
return vars.every((varItem) => {
|
||||||
|
const [nodeId, varName] = varItem.slice(0, 2)
|
||||||
|
const inspectVarValue = hasSetInspectVar(nodeId, varName, systemVars, conversationVars) // also detect system var , env and conversation var
|
||||||
|
return inspectVarValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSomeVarsHasValue = (vars?: ValueSelector[]) => {
|
||||||
|
if(!vars || vars.length === 0)
|
||||||
|
return true
|
||||||
|
return vars.some((varItem) => {
|
||||||
|
const [nodeId, varName] = varItem.slice(0, 2)
|
||||||
|
const inspectVarValue = hasSetInspectVar(nodeId, varName, systemVars, conversationVars) // also detect system var , env and conversation var
|
||||||
|
return inspectVarValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const getFilteredExistVarForms = (forms: FormProps[]) => {
|
||||||
|
if (!forms || forms.length === 0)
|
||||||
|
return []
|
||||||
|
|
||||||
|
const existVarValuesInForms = getExistVarValuesInForms(forms)
|
||||||
|
|
||||||
|
const res = forms.map((form, i) => {
|
||||||
|
const existVarValuesInForm = existVarValuesInForms[i]
|
||||||
|
const newForm = { ...form }
|
||||||
|
const inputs = form.inputs.filter((input) => {
|
||||||
|
return !(input.variable in existVarValuesInForm)
|
||||||
|
})
|
||||||
|
newForm.inputs = inputs
|
||||||
|
return newForm
|
||||||
|
}).filter(form => form.inputs.length > 0)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkAggregatorVarsSet = (vars: ValueSelector[][]) => {
|
||||||
|
if(!vars || vars.length === 0)
|
||||||
|
return true
|
||||||
|
// in each group, at last one set is ok
|
||||||
|
return vars.every((varItem) => {
|
||||||
|
return isSomeVarsHasValue(varItem)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSingleRun = () => {
|
||||||
|
const { isValid } = checkValid()
|
||||||
|
if(!isValid)
|
||||||
|
return
|
||||||
|
const vars = singleRunParams?.getDependentVars?.()
|
||||||
|
// no need to input params
|
||||||
|
if (isAggregatorNode ? checkAggregatorVarsSet(vars) : isAllVarsHasValue(vars)) {
|
||||||
|
callRunApi({}, async () => {
|
||||||
|
setIsRunAfterSingleRun(true)
|
||||||
|
setNodeRunning()
|
||||||
|
invalidLastRun()
|
||||||
|
setTabType(TabType.lastRun)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showSingleRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...oneStepRunRes,
|
||||||
|
tabType,
|
||||||
|
isRunAfterSingleRun,
|
||||||
|
setTabType: handleTabClicked,
|
||||||
|
singleRunParams,
|
||||||
|
nodeInfo,
|
||||||
|
setRunInputData,
|
||||||
|
handleSingleRun,
|
||||||
|
handleRunWithParams,
|
||||||
|
getExistVarValuesInForms,
|
||||||
|
getFilteredExistVarForms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLastRun
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
'use client'
|
||||||
|
import TabHeader from '@/app/components/base/tab-header'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export enum TabType {
|
||||||
|
settings = 'settings',
|
||||||
|
lastRun = 'lastRun',
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: TabType,
|
||||||
|
onChange: (value: TabType) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tab: FC<Props> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<TabHeader
|
||||||
|
items={[
|
||||||
|
{ id: TabType.settings, name: t('workflow.debug.settingsTab').toLocaleUpperCase() },
|
||||||
|
{ id: TabType.lastRun, name: t('workflow.debug.lastRunTab').toLocaleUpperCase() },
|
||||||
|
]}
|
||||||
|
itemClassName='ml-0'
|
||||||
|
value={value}
|
||||||
|
onChange={onChange as any}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Tab)
|
||||||
|
|
@ -13,7 +13,7 @@ import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/a
|
||||||
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
import { getIterationSingleNodeRunUrl, getLoopSingleNodeRunUrl, singleNodeRun } from '@/service/workflow'
|
import { fetchNodeInspectVars, getIterationSingleNodeRunUrl, getLoopSingleNodeRunUrl, singleNodeRun } from '@/service/workflow'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import LLMDefault from '@/app/components/workflow/nodes/llm/default'
|
import LLMDefault from '@/app/components/workflow/nodes/llm/default'
|
||||||
import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
|
import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
|
||||||
|
|
@ -32,7 +32,7 @@ import LoopDefault from '@/app/components/workflow/nodes/loop/default'
|
||||||
import { ssePost } from '@/service/base'
|
import { ssePost } from '@/service/base'
|
||||||
import { noop } from 'lodash-es'
|
import { noop } from 'lodash-es'
|
||||||
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
|
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
|
||||||
import type { NodeTracing } from '@/types/workflow'
|
import type { NodeRunResult, NodeTracing } from '@/types/workflow'
|
||||||
const { checkValid: checkLLMValid } = LLMDefault
|
const { checkValid: checkLLMValid } = LLMDefault
|
||||||
const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault
|
const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault
|
||||||
const { checkValid: checkIfElseValid } = IfElseDefault
|
const { checkValid: checkIfElseValid } = IfElseDefault
|
||||||
|
|
@ -47,7 +47,11 @@ const { checkValid: checkParameterExtractorValid } = ParameterExtractorDefault
|
||||||
const { checkValid: checkIterationValid } = IterationDefault
|
const { checkValid: checkIterationValid } = IterationDefault
|
||||||
const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault
|
const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault
|
||||||
const { checkValid: checkLoopValid } = LoopDefault
|
const { checkValid: checkLoopValid } = LoopDefault
|
||||||
|
import {
|
||||||
|
useStoreApi,
|
||||||
|
} from 'reactflow'
|
||||||
|
import { useInvalidLastRun } from '@/service/use-workflow'
|
||||||
|
import useInspectVarsCrud from '../../../hooks/use-inspect-vars-crud'
|
||||||
// eslint-disable-next-line ts/no-unsafe-function-type
|
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||||
const checkValidFns: Record<BlockEnum, Function> = {
|
const checkValidFns: Record<BlockEnum, Function> = {
|
||||||
[BlockEnum.LLM]: checkLLMValid,
|
[BlockEnum.LLM]: checkLLMValid,
|
||||||
|
|
@ -66,13 +70,15 @@ const checkValidFns: Record<BlockEnum, Function> = {
|
||||||
[BlockEnum.Loop]: checkLoopValid,
|
[BlockEnum.Loop]: checkLoopValid,
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
type Params<T> = {
|
export type Params<T> = {
|
||||||
id: string
|
id: string
|
||||||
data: CommonNodeType<T>
|
data: CommonNodeType<T>
|
||||||
defaultRunInputData: Record<string, any>
|
defaultRunInputData: Record<string, any>
|
||||||
moreDataForCheckValid?: any
|
moreDataForCheckValid?: any
|
||||||
iteratorInputKey?: string
|
iteratorInputKey?: string
|
||||||
loopInputKey?: string
|
loopInputKey?: string
|
||||||
|
isRunAfterSingleRun: boolean
|
||||||
|
isPaused: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const varTypeToInputVarType = (type: VarType, {
|
const varTypeToInputVarType = (type: VarType, {
|
||||||
|
|
@ -105,6 +111,8 @@ const useOneStepRun = <T>({
|
||||||
moreDataForCheckValid,
|
moreDataForCheckValid,
|
||||||
iteratorInputKey,
|
iteratorInputKey,
|
||||||
loopInputKey,
|
loopInputKey,
|
||||||
|
isRunAfterSingleRun,
|
||||||
|
isPaused,
|
||||||
}: Params<T>) => {
|
}: Params<T>) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
|
const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
|
||||||
|
|
@ -112,6 +120,7 @@ const useOneStepRun = <T>({
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
const isIteration = data.type === BlockEnum.Iteration
|
const isIteration = data.type === BlockEnum.Iteration
|
||||||
const isLoop = data.type === BlockEnum.Loop
|
const isLoop = data.type === BlockEnum.Loop
|
||||||
|
const isStartNode = data.type === BlockEnum.Start
|
||||||
|
|
||||||
const availableNodes = getBeforeNodesInSameBranch(id)
|
const availableNodes = getBeforeNodesInSameBranch(id)
|
||||||
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
||||||
|
|
@ -143,6 +152,7 @@ const useOneStepRun = <T>({
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkValid = checkValidFns[data.type]
|
const checkValid = checkValidFns[data.type]
|
||||||
|
|
||||||
const appId = useAppStore.getState().appDetail?.id
|
const appId = useAppStore.getState().appDetail?.id
|
||||||
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
||||||
const runInputDataRef = useRef(runInputData)
|
const runInputDataRef = useRef(runInputData)
|
||||||
|
|
@ -150,11 +160,82 @@ const useOneStepRun = <T>({
|
||||||
runInputDataRef.current = data
|
runInputDataRef.current = data
|
||||||
setRunInputData(data)
|
setRunInputData(data)
|
||||||
}, [])
|
}, [])
|
||||||
const iterationTimes = iteratorInputKey ? runInputData[iteratorInputKey].length : 0
|
const iterationTimes = iteratorInputKey ? runInputData[iteratorInputKey]?.length : 0
|
||||||
const loopTimes = loopInputKey ? runInputData[loopInputKey].length : 0
|
const loopTimes = loopInputKey ? runInputData[loopInputKey]?.length : 0
|
||||||
const [runResult, setRunResult] = useState<any>(null)
|
|
||||||
|
const store = useStoreApi()
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
const {
|
||||||
|
setShowSingleRunPanel,
|
||||||
|
} = workflowStore.getState()
|
||||||
|
const invalidLastRun = useInvalidLastRun(appId!, id)
|
||||||
|
const [runResult, doSetRunResult] = useState<NodeRunResult | null>(null)
|
||||||
|
const {
|
||||||
|
appendNodeInspectVars,
|
||||||
|
invalidateSysVarValues,
|
||||||
|
invalidateConversationVarValues,
|
||||||
|
} = useInspectVarsCrud()
|
||||||
|
const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
|
||||||
|
const isPausedRef = useRef(isPaused)
|
||||||
|
useEffect(() => {
|
||||||
|
isPausedRef.current = isPaused
|
||||||
|
}, [isPaused])
|
||||||
|
|
||||||
|
const setRunResult = useCallback(async (data: NodeRunResult | null) => {
|
||||||
|
const isPaused = isPausedRef.current
|
||||||
|
|
||||||
|
// The backend don't support pause the single run, so the frontend handle the pause state.
|
||||||
|
if(isPaused)
|
||||||
|
return
|
||||||
|
|
||||||
|
const canRunLastRun = !isRunAfterSingleRun || runningStatus === NodeRunningStatus.Succeeded
|
||||||
|
if(!canRunLastRun) {
|
||||||
|
doSetRunResult(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// run fail may also update the inspect vars when the node set the error default output.
|
||||||
|
const vars = await fetchNodeInspectVars(appId!, id)
|
||||||
|
const { getNodes } = store.getState()
|
||||||
|
const nodes = getNodes()
|
||||||
|
appendNodeInspectVars(id, vars, nodes)
|
||||||
|
if(data?.status === NodeRunningStatus.Succeeded) {
|
||||||
|
invalidLastRun()
|
||||||
|
if(isStartNode)
|
||||||
|
invalidateSysVarValues()
|
||||||
|
invalidateConversationVarValues() // loop, iteration, variable assigner node can update the conversation variables, but to simple the logic(some nodes may also can update in the future), all nodes refresh.
|
||||||
|
}
|
||||||
|
}, [isRunAfterSingleRun, runningStatus, appId, id, store, appendNodeInspectVars, invalidLastRun, isStartNode, invalidateSysVarValues, invalidateConversationVarValues])
|
||||||
|
|
||||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||||
|
const setNodeRunning = () => {
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
_singleRunningStatus: NodeRunningStatus.Running,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const checkValidWrap = () => {
|
||||||
|
if(!checkValid)
|
||||||
|
return { isValid: true, errorMessage: '' }
|
||||||
|
const res = checkValid(data, t, moreDataForCheckValid)
|
||||||
|
if(!res.isValid) {
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: res.errorMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
const [canShowSingleRun, setCanShowSingleRun] = useState(false)
|
const [canShowSingleRun, setCanShowSingleRun] = useState(false)
|
||||||
const isShowSingleRun = data._isSingleRun && canShowSingleRun
|
const isShowSingleRun = data._isSingleRun && canShowSingleRun
|
||||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[]>([])
|
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[]>([])
|
||||||
|
|
@ -167,29 +248,15 @@ const useOneStepRun = <T>({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data._isSingleRun) {
|
if (data._isSingleRun) {
|
||||||
const { isValid, errorMessage } = checkValid(data, t, moreDataForCheckValid)
|
const { isValid } = checkValidWrap()
|
||||||
setCanShowSingleRun(isValid)
|
setCanShowSingleRun(isValid)
|
||||||
if (!isValid) {
|
|
||||||
handleNodeDataUpdate({
|
|
||||||
id,
|
|
||||||
data: {
|
|
||||||
...data,
|
|
||||||
_isSingleRun: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
Toast.notify({
|
|
||||||
type: 'error',
|
|
||||||
message: errorMessage,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [data._isSingleRun])
|
}, [data._isSingleRun])
|
||||||
|
|
||||||
const workflowStore = useWorkflowStore()
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
workflowStore.getState().setShowSingleRunPanel(!!isShowSingleRun)
|
setShowSingleRunPanel(!!isShowSingleRun)
|
||||||
}, [isShowSingleRun, workflowStore])
|
}, [isShowSingleRun, setShowSingleRunPanel])
|
||||||
|
|
||||||
const hideSingleRun = () => {
|
const hideSingleRun = () => {
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
|
|
@ -209,7 +276,6 @@ const useOneStepRun = <T>({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
|
|
||||||
const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
||||||
|
|
||||||
const handleRun = async (submitData: Record<string, any>) => {
|
const handleRun = async (submitData: Record<string, any>) => {
|
||||||
|
|
@ -217,13 +283,29 @@ const useOneStepRun = <T>({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
_singleRunningStatus: NodeRunningStatus.Running,
|
_singleRunningStatus: NodeRunningStatus.Running,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
let res: any
|
let res: any
|
||||||
|
let hasError = false
|
||||||
try {
|
try {
|
||||||
if (!isIteration && !isLoop) {
|
if (!isIteration && !isLoop) {
|
||||||
res = await singleNodeRun(appId!, id, { inputs: submitData }) as any
|
const isStartNode = data.type === BlockEnum.Start
|
||||||
|
const postData: Record<string, any> = {}
|
||||||
|
if(isStartNode) {
|
||||||
|
const { '#sys.query#': query, '#sys.files#': files, ...inputs } = submitData
|
||||||
|
if(isChatMode)
|
||||||
|
postData.conversation_id = ''
|
||||||
|
|
||||||
|
postData.inputs = inputs
|
||||||
|
postData.query = query
|
||||||
|
postData.files = files || []
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
postData.inputs = submitData
|
||||||
|
}
|
||||||
|
res = await singleNodeRun(appId!, id, postData) as any
|
||||||
}
|
}
|
||||||
else if (isIteration) {
|
else if (isIteration) {
|
||||||
setIterationRunResult([])
|
setIterationRunResult([])
|
||||||
|
|
@ -235,10 +317,13 @@ const useOneStepRun = <T>({
|
||||||
{
|
{
|
||||||
onWorkflowStarted: noop,
|
onWorkflowStarted: noop,
|
||||||
onWorkflowFinished: (params) => {
|
onWorkflowFinished: (params) => {
|
||||||
|
if(isPausedRef.current)
|
||||||
|
return
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -311,10 +396,13 @@ const useOneStepRun = <T>({
|
||||||
setIterationRunResult(newIterationRunResult)
|
setIterationRunResult(newIterationRunResult)
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
if(isPausedRef.current)
|
||||||
|
return
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -332,10 +420,13 @@ const useOneStepRun = <T>({
|
||||||
{
|
{
|
||||||
onWorkflowStarted: noop,
|
onWorkflowStarted: noop,
|
||||||
onWorkflowFinished: (params) => {
|
onWorkflowFinished: (params) => {
|
||||||
|
if(isPausedRef.current)
|
||||||
|
return
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -409,10 +500,13 @@ const useOneStepRun = <T>({
|
||||||
setLoopRunResult(newLoopRunResult)
|
setLoopRunResult(newLoopRunResult)
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
if(isPausedRef.current)
|
||||||
|
return
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -425,11 +519,16 @@ const useOneStepRun = <T>({
|
||||||
}
|
}
|
||||||
catch (e: any) {
|
catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
hasError = true
|
||||||
|
invalidLastRun()
|
||||||
if (!isIteration && !isLoop) {
|
if (!isIteration && !isLoop) {
|
||||||
|
if(isPausedRef.current)
|
||||||
|
return
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -437,7 +536,7 @@ const useOneStepRun = <T>({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (!isIteration && !isLoop) {
|
if (!isPausedRef.current && !isIteration && !isLoop && res) {
|
||||||
setRunResult({
|
setRunResult({
|
||||||
...res,
|
...res,
|
||||||
total_tokens: res.execution_metadata?.total_tokens || 0,
|
total_tokens: res.execution_metadata?.total_tokens || 0,
|
||||||
|
|
@ -445,11 +544,17 @@ const useOneStepRun = <T>({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isIteration && !isLoop) {
|
if(isPausedRef.current)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (!isIteration && !isLoop && !hasError) {
|
||||||
|
if(isPausedRef.current)
|
||||||
|
return
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -521,11 +626,19 @@ const useOneStepRun = <T>({
|
||||||
return varInputs
|
return varInputs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const varSelectorsToVarInputs = (valueSelectors: ValueSelector[] | string[]): InputVar[] => {
|
||||||
|
return valueSelectors.filter(item => !!item).map((item) => {
|
||||||
|
return getInputVars([`{{#${typeof item === 'string' ? item : item.join('.')}#}}`])[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
appId,
|
||||||
isShowSingleRun,
|
isShowSingleRun,
|
||||||
hideSingleRun,
|
hideSingleRun,
|
||||||
showSingleRun,
|
showSingleRun,
|
||||||
toVarInputs,
|
toVarInputs,
|
||||||
|
varSelectorsToVarInputs,
|
||||||
getInputVars,
|
getInputVars,
|
||||||
runningStatus,
|
runningStatus,
|
||||||
isCompleted,
|
isCompleted,
|
||||||
|
|
@ -537,6 +650,8 @@ const useOneStepRun = <T>({
|
||||||
runResult,
|
runResult,
|
||||||
iterationRunResult,
|
iterationRunResult,
|
||||||
loopRunResult,
|
loopRunResult,
|
||||||
|
setNodeRunning,
|
||||||
|
checkValid: checkValidWrap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean, useDebounceFn } from 'ahooks'
|
||||||
import type {
|
import type {
|
||||||
CodeNodeType,
|
CodeNodeType,
|
||||||
OutputVar,
|
OutputVar,
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
import { getDefaultValue } from '@/app/components/workflow/nodes/_base/components/error-handle/utils'
|
import { getDefaultValue } from '@/app/components/workflow/nodes/_base/components/error-handle/utils'
|
||||||
|
import useInspectVarsCrud from '../../../hooks/use-inspect-vars-crud'
|
||||||
|
|
||||||
type Params<T> = {
|
type Params<T> = {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -34,8 +35,27 @@ function useOutputVarList<T>({
|
||||||
outputKeyOrders = [],
|
outputKeyOrders = [],
|
||||||
onOutputKeyOrdersChange,
|
onOutputKeyOrdersChange,
|
||||||
}: Params<T>) {
|
}: Params<T>) {
|
||||||
|
const {
|
||||||
|
renameInspectVarName,
|
||||||
|
deleteInspectVar,
|
||||||
|
nodesWithInspectVars,
|
||||||
|
} = useInspectVarsCrud()
|
||||||
|
|
||||||
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
|
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
|
||||||
|
|
||||||
|
// record the first old name value
|
||||||
|
const oldNameRecord = useRef<Record<string, string>>({})
|
||||||
|
|
||||||
|
const {
|
||||||
|
run: renameInspectNameWithDebounce,
|
||||||
|
} = useDebounceFn(
|
||||||
|
(id: string, newName: string) => {
|
||||||
|
const oldName = oldNameRecord.current[id]
|
||||||
|
renameInspectVarName(id, oldName, newName)
|
||||||
|
delete oldNameRecord.current[id]
|
||||||
|
},
|
||||||
|
{ wait: 500 },
|
||||||
|
)
|
||||||
const handleVarsChange = useCallback((newVars: OutputVar, changedIndex?: number, newKey?: string) => {
|
const handleVarsChange = useCallback((newVars: OutputVar, changedIndex?: number, newKey?: string) => {
|
||||||
const newInputs = produce(inputs, (draft: any) => {
|
const newInputs = produce(inputs, (draft: any) => {
|
||||||
draft[varKey] = newVars
|
draft[varKey] = newVars
|
||||||
|
|
@ -52,9 +72,20 @@ function useOutputVarList<T>({
|
||||||
onOutputKeyOrdersChange(newOutputKeyOrders)
|
onOutputKeyOrdersChange(newOutputKeyOrders)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newKey)
|
if (newKey) {
|
||||||
handleOutVarRenameChange(id, [id, outputKeyOrders[changedIndex!]], [id, newKey])
|
handleOutVarRenameChange(id, [id, outputKeyOrders[changedIndex!]], [id, newKey])
|
||||||
}, [inputs, setInputs, handleOutVarRenameChange, id, outputKeyOrders, varKey, onOutputKeyOrdersChange])
|
if(!(id in oldNameRecord.current))
|
||||||
|
oldNameRecord.current[id] = outputKeyOrders[changedIndex!]
|
||||||
|
renameInspectNameWithDebounce(id, newKey)
|
||||||
|
}
|
||||||
|
else if (changedIndex === undefined) {
|
||||||
|
const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => {
|
||||||
|
return varItem.name === Object.keys(newVars)[0]
|
||||||
|
})?.id
|
||||||
|
if(varId)
|
||||||
|
deleteInspectVar(id, varId)
|
||||||
|
}
|
||||||
|
}, [inputs, setInputs, varKey, outputKeyOrders, onOutputKeyOrdersChange, handleOutVarRenameChange, id, renameInspectNameWithDebounce, nodesWithInspectVars, deleteInspectVar])
|
||||||
|
|
||||||
const generateNewKey = useCallback(() => {
|
const generateNewKey = useCallback(() => {
|
||||||
let keyIndex = Object.keys((inputs as any)[varKey]).length + 1
|
let keyIndex = Object.keys((inputs as any)[varKey]).length + 1
|
||||||
|
|
@ -86,9 +117,14 @@ function useOutputVarList<T>({
|
||||||
}] = useBoolean(false)
|
}] = useBoolean(false)
|
||||||
const [removedVar, setRemovedVar] = useState<ValueSelector>([])
|
const [removedVar, setRemovedVar] = useState<ValueSelector>([])
|
||||||
const removeVarInNode = useCallback(() => {
|
const removeVarInNode = useCallback(() => {
|
||||||
|
const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => {
|
||||||
|
return varItem.name === removedVar[1]
|
||||||
|
})?.id
|
||||||
|
if(varId)
|
||||||
|
deleteInspectVar(id, varId)
|
||||||
removeUsedVarInNodes(removedVar)
|
removeUsedVarInNodes(removedVar)
|
||||||
hideRemoveVarConfirm()
|
hideRemoveVarConfirm()
|
||||||
}, [hideRemoveVarConfirm, removeUsedVarInNodes, removedVar])
|
}, [deleteInspectVar, hideRemoveVarConfirm, id, nodesWithInspectVars, removeUsedVarInNodes, removedVar])
|
||||||
const handleRemoveVariable = useCallback((index: number) => {
|
const handleRemoveVariable = useCallback((index: number) => {
|
||||||
const key = outputKeyOrders[index]
|
const key = outputKeyOrders[index]
|
||||||
|
|
||||||
|
|
@ -106,7 +142,12 @@ function useOutputVarList<T>({
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index))
|
onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index))
|
||||||
}, [outputKeyOrders, isVarUsedInNodes, id, inputs, setInputs, onOutputKeyOrdersChange, showRemoveVarConfirm, varKey])
|
const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => {
|
||||||
|
return varItem.name === key
|
||||||
|
})?.id
|
||||||
|
if(varId)
|
||||||
|
deleteInspectVar(id, varId)
|
||||||
|
}, [outputKeyOrders, isVarUsedInNodes, id, inputs, setInputs, onOutputKeyOrdersChange, nodesWithInspectVars, deleteInspectVar, showRemoveVarConfirm, varKey])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleVarsChange,
|
handleVarsChange,
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import AddVariablePopupWithPosition from './components/add-variable-popup-with-p
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||||
|
|
||||||
type BaseNodeProps = {
|
type BaseNodeProps = {
|
||||||
children: ReactElement
|
children: ReactElement
|
||||||
|
|
@ -89,6 +90,9 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||||
}
|
}
|
||||||
}, [data.isInLoop, data.selected, id, handleNodeLoopChildSizeChange])
|
}, [data.isInLoop, data.selected, id, handleNodeLoopChildSizeChange])
|
||||||
|
|
||||||
|
const { hasNodeInspectVars } = useInspectVarsCrud()
|
||||||
|
const isLoading = data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running
|
||||||
|
const hasVarValue = hasNodeInspectVars(id)
|
||||||
const showSelectedBorder = data.selected || data._isBundled || data._isEntering
|
const showSelectedBorder = data.selected || data._isBundled || data._isEntering
|
||||||
const {
|
const {
|
||||||
showRunningBorder,
|
showRunningBorder,
|
||||||
|
|
@ -98,11 +102,11 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||||
} = useMemo(() => {
|
} = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
||||||
showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
showSuccessBorder: (data._runningStatus === NodeRunningStatus.Succeeded || hasVarValue) && !showSelectedBorder,
|
||||||
showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
||||||
showExceptionBorder: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
showExceptionBorder: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
||||||
}
|
}
|
||||||
}, [data._runningStatus, showSelectedBorder])
|
}, [data._runningStatus, hasVarValue, showSelectedBorder])
|
||||||
|
|
||||||
const LoopIndex = useMemo(() => {
|
const LoopIndex = useMemo(() => {
|
||||||
let text = ''
|
let text = ''
|
||||||
|
|
@ -260,12 +264,12 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||||
data.type === BlockEnum.Loop && data._loopIndex && LoopIndex
|
data.type === BlockEnum.Loop && data._loopIndex && LoopIndex
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && (
|
isLoading && (
|
||||||
<RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-accent' />
|
<RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-accent' />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
data._runningStatus === NodeRunningStatus.Succeeded && (
|
(!isLoading && (data._runningStatus === NodeRunningStatus.Succeeded || hasVarValue)) && (
|
||||||
<RiCheckboxCircleFill className='h-3.5 w-3.5 text-text-success' />
|
<RiCheckboxCircleFill className='h-3.5 w-3.5 text-text-success' />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,214 +0,0 @@
|
||||||
import type {
|
|
||||||
FC,
|
|
||||||
ReactNode,
|
|
||||||
} from 'react'
|
|
||||||
import {
|
|
||||||
cloneElement,
|
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
} from 'react'
|
|
||||||
import {
|
|
||||||
RiCloseLine,
|
|
||||||
RiPlayLargeLine,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
import { useShallow } from 'zustand/react/shallow'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import NextStep from './components/next-step'
|
|
||||||
import PanelOperator from './components/panel-operator'
|
|
||||||
import HelpLink from './components/help-link'
|
|
||||||
import NodePosition from './components/node-position'
|
|
||||||
import {
|
|
||||||
DescriptionInput,
|
|
||||||
TitleInput,
|
|
||||||
} from './components/title-description-input'
|
|
||||||
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
|
||||||
import RetryOnPanel from './components/retry/retry-on-panel'
|
|
||||||
import { useResizePanel } from './hooks/use-resize-panel'
|
|
||||||
import cn from '@/utils/classnames'
|
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
|
||||||
import {
|
|
||||||
WorkflowHistoryEvent,
|
|
||||||
useAvailableBlocks,
|
|
||||||
useNodeDataUpdate,
|
|
||||||
useNodesInteractions,
|
|
||||||
useNodesReadOnly,
|
|
||||||
useNodesSyncDraft,
|
|
||||||
useToolIcon,
|
|
||||||
useWorkflow,
|
|
||||||
useWorkflowHistory,
|
|
||||||
} from '@/app/components/workflow/hooks'
|
|
||||||
import {
|
|
||||||
canRunBySingle,
|
|
||||||
hasErrorHandleNode,
|
|
||||||
hasRetryNode,
|
|
||||||
} from '@/app/components/workflow/utils'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
|
||||||
import type { Node } from '@/app/components/workflow/types'
|
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
|
||||||
|
|
||||||
type BasePanelProps = {
|
|
||||||
children: ReactNode
|
|
||||||
} & Node
|
|
||||||
|
|
||||||
const BasePanel: FC<BasePanelProps> = ({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
children,
|
|
||||||
position,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { showMessageLogModal } = useAppStore(useShallow(state => ({
|
|
||||||
showMessageLogModal: state.showMessageLogModal,
|
|
||||||
})))
|
|
||||||
const showSingleRunPanel = useStore(s => s.showSingleRunPanel)
|
|
||||||
const panelWidth = localStorage.getItem('workflow-node-panel-width') ? Number.parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
|
|
||||||
const {
|
|
||||||
setPanelWidth,
|
|
||||||
} = useWorkflow()
|
|
||||||
const { handleNodeSelect } = useNodesInteractions()
|
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
|
||||||
const { nodesReadOnly } = useNodesReadOnly()
|
|
||||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
|
||||||
const toolIcon = useToolIcon(data)
|
|
||||||
|
|
||||||
const handleResize = useCallback((width: number) => {
|
|
||||||
setPanelWidth(width)
|
|
||||||
}, [setPanelWidth])
|
|
||||||
|
|
||||||
const {
|
|
||||||
triggerRef,
|
|
||||||
containerRef,
|
|
||||||
} = useResizePanel({
|
|
||||||
direction: 'horizontal',
|
|
||||||
triggerDirection: 'left',
|
|
||||||
minWidth: 420,
|
|
||||||
maxWidth: 720,
|
|
||||||
onResize: handleResize,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { saveStateToHistory } = useWorkflowHistory()
|
|
||||||
|
|
||||||
const {
|
|
||||||
handleNodeDataUpdate,
|
|
||||||
handleNodeDataUpdateWithSyncDraft,
|
|
||||||
} = useNodeDataUpdate()
|
|
||||||
|
|
||||||
const handleTitleBlur = useCallback((title: string) => {
|
|
||||||
handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
|
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange)
|
|
||||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
|
||||||
const handleDescriptionChange = useCallback((desc: string) => {
|
|
||||||
handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
|
|
||||||
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange)
|
|
||||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn(
|
|
||||||
'relative mr-2 h-full',
|
|
||||||
showMessageLogModal && '!absolute -top-[5px] right-[416px] z-0 !mr-0 w-[384px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border shadow-lg transition-all',
|
|
||||||
)}>
|
|
||||||
<div
|
|
||||||
ref={triggerRef}
|
|
||||||
className='absolute -left-2 top-1/2 h-6 w-3 -translate-y-1/2 cursor-col-resize resize-x'>
|
|
||||||
<div className='h-6 w-1 rounded-sm bg-divider-regular'></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
ref={containerRef}
|
|
||||||
className={cn('h-full rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
|
||||||
style={{
|
|
||||||
width: `${panelWidth}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='sticky top-0 z-10 border-b-[0.5px] border-divider-regular bg-components-panel-bg'>
|
|
||||||
<div className='flex items-center px-4 pb-1 pt-4'>
|
|
||||||
<BlockIcon
|
|
||||||
className='mr-1 shrink-0'
|
|
||||||
type={data.type}
|
|
||||||
toolIcon={toolIcon}
|
|
||||||
size='md'
|
|
||||||
/>
|
|
||||||
<TitleInput
|
|
||||||
value={data.title || ''}
|
|
||||||
onBlur={handleTitleBlur}
|
|
||||||
/>
|
|
||||||
<div className='flex shrink-0 items-center text-text-tertiary'>
|
|
||||||
{
|
|
||||||
canRunBySingle(data.type) && !nodesReadOnly && (
|
|
||||||
<Tooltip
|
|
||||||
popupContent={t('workflow.panel.runThisStep')}
|
|
||||||
popupClassName='mr-1'
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
|
||||||
onClick={() => {
|
|
||||||
handleNodeDataUpdate({ id, data: { _isSingleRun: true } })
|
|
||||||
handleSyncWorkflowDraft(true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RiPlayLargeLine className='h-4 w-4 text-text-tertiary' />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<NodePosition nodePosition={position} nodeWidth={width} nodeHeight={height}></NodePosition>
|
|
||||||
<HelpLink nodeType={data.type} />
|
|
||||||
<PanelOperator id={id} data={data} showHelpLink={false} />
|
|
||||||
<div className='mx-3 h-3.5 w-[1px] bg-divider-regular' />
|
|
||||||
<div
|
|
||||||
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
|
||||||
onClick={() => handleNodeSelect(id, true)}
|
|
||||||
>
|
|
||||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='p-2'>
|
|
||||||
<DescriptionInput
|
|
||||||
value={data.desc || ''}
|
|
||||||
onChange={handleDescriptionChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{cloneElement(children as any, { id, data })}
|
|
||||||
</div>
|
|
||||||
<Split />
|
|
||||||
{
|
|
||||||
hasRetryNode(data.type) && (
|
|
||||||
<RetryOnPanel
|
|
||||||
id={id}
|
|
||||||
data={data}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
hasErrorHandleNode(data.type) && (
|
|
||||||
<ErrorHandleOnPanel
|
|
||||||
id={id}
|
|
||||||
data={data}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!!availableNextBlocks.length && (
|
|
||||||
<div className='border-t-[0.5px] border-divider-regular p-4'>
|
|
||||||
<div className='system-sm-semibold-uppercase mb-1 flex items-center text-text-secondary'>
|
|
||||||
{t('workflow.panel.nextStep').toLocaleUpperCase()}
|
|
||||||
</div>
|
|
||||||
<div className='system-xs-regular mb-2 text-text-tertiary'>
|
|
||||||
{t('workflow.panel.addNextStep')}
|
|
||||||
</div>
|
|
||||||
<NextStep selectedNode={{ id, data } as Node} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(BasePanel)
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { memo, useMemo } from 'react'
|
import { memo } from 'react'
|
||||||
import type { NodePanelProps } from '../../types'
|
import type { NodePanelProps } from '../../types'
|
||||||
import { AgentFeature, type AgentNodeType } from './types'
|
import { AgentFeature, type AgentNodeType } from './types'
|
||||||
import Field from '../_base/components/field'
|
import Field from '../_base/components/field'
|
||||||
|
|
@ -9,16 +9,10 @@ import { useTranslation } from 'react-i18next'
|
||||||
import OutputVars, { VarItem } from '../_base/components/output-vars'
|
import OutputVars, { VarItem } from '../_base/components/output-vars'
|
||||||
import type { StrategyParamItem } from '@/app/components/plugins/types'
|
import type { StrategyParamItem } from '@/app/components/plugins/types'
|
||||||
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
|
||||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
|
||||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
|
||||||
import { toType } from '@/app/components/tools/utils/to-form-schema'
|
import { toType } from '@/app/components/tools/utils/to-form-schema'
|
||||||
import { useStore } from '../../store'
|
import { useStore } from '../../store'
|
||||||
import Split from '../_base/components/split'
|
import Split from '../_base/components/split'
|
||||||
import MemoryConfig from '../_base/components/memory-config'
|
import MemoryConfig from '../_base/components/memory-config'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.agent'
|
const i18nPrefix = 'workflow.nodes.agent'
|
||||||
|
|
||||||
export function strategyParamToCredientialForm(param: StrategyParamItem): CredentialFormSchema {
|
export function strategyParamToCredientialForm(param: StrategyParamItem): CredentialFormSchema {
|
||||||
|
|
@ -42,41 +36,10 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
availableVars,
|
availableVars,
|
||||||
readOnly,
|
readOnly,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
varInputs,
|
|
||||||
outputSchema,
|
outputSchema,
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
} = useConfig(props.id, props.data)
|
} = useConfig(props.id, props.data)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const nodeInfo = useMemo(() => {
|
|
||||||
if (!runResult)
|
|
||||||
return
|
|
||||||
return formatTracing([runResult], t)[0]
|
|
||||||
}, [runResult, t])
|
|
||||||
const logsParams = useLogs()
|
|
||||||
const singleRunForms = (() => {
|
|
||||||
const forms: FormProps[] = []
|
|
||||||
|
|
||||||
if (varInputs.length > 0) {
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t(`${i18nPrefix}.singleRun.variable`)!,
|
|
||||||
inputs: varInputs,
|
|
||||||
values: runInputData,
|
|
||||||
onChange: setRunInputData,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return forms
|
|
||||||
})()
|
|
||||||
|
|
||||||
const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey)
|
const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey)
|
||||||
|
|
||||||
|
|
@ -154,21 +117,6 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
|
||||||
))}
|
))}
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
nodeType={inputs.type}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={singleRunForms}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
{...logsParams}
|
|
||||||
result={<ResultPanel {...runResult} nodeInfo={nodeInfo} showSteps={false} {...logsParams} />}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useStrategyProviderDetail } from '@/service/use-strategy'
|
import { useStrategyProviderDetail } from '@/service/use-strategy'
|
||||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
import useVarList from '../_base/hooks/use-var-list'
|
import useVarList from '../_base/hooks/use-var-list'
|
||||||
import useOneStepRun from '../_base/hooks/use-one-step-run'
|
|
||||||
import type { AgentNodeType } from './types'
|
import type { AgentNodeType } from './types'
|
||||||
import {
|
import {
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
|
|
@ -131,35 +130,6 @@ const useConfig = (id: string, payload: AgentNodeType) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// single run
|
// single run
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
showSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
toVarInputs,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
getInputVars,
|
|
||||||
} = useOneStepRun<AgentNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {},
|
|
||||||
})
|
|
||||||
const allVarStrArr = (() => {
|
|
||||||
const arr = currentStrategy?.parameters.filter(item => item.type === 'string').map((item) => {
|
|
||||||
return formData[item.name]
|
|
||||||
}) || []
|
|
||||||
|
|
||||||
return arr
|
|
||||||
})()
|
|
||||||
const varInputs = (() => {
|
|
||||||
const vars = getInputVars(allVarStrArr)
|
|
||||||
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const outputSchema = useMemo(() => {
|
const outputSchema = useMemo(() => {
|
||||||
const res: any[] = []
|
const res: any[] = []
|
||||||
|
|
@ -199,18 +169,6 @@ const useConfig = (id: string, payload: AgentNodeType) => {
|
||||||
pluginDetail: pluginDetail.data?.plugins.at(0),
|
pluginDetail: pluginDetail.data?.plugins.at(0),
|
||||||
availableVars,
|
availableVars,
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
|
|
||||||
isShowSingleRun,
|
|
||||||
showSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
toVarInputs,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
varInputs,
|
|
||||||
outputSchema,
|
outputSchema,
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
isChatMode,
|
isChatMode,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import type { AgentNodeType } from './types'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
|
import { useStrategyInfo } from './use-config'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: AgentNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
runResult: NodeTracing
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
getInputVars,
|
||||||
|
setRunInputData,
|
||||||
|
runResult,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { inputs } = useNodeCrud<AgentNodeType>(id, payload)
|
||||||
|
|
||||||
|
const formData = useMemo(() => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(inputs.agent_parameters || {}).map(([key, value]) => {
|
||||||
|
return [key, value.value]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}, [inputs.agent_parameters])
|
||||||
|
|
||||||
|
const {
|
||||||
|
strategy: currentStrategy,
|
||||||
|
} = useStrategyInfo(
|
||||||
|
inputs.agent_strategy_provider_name,
|
||||||
|
inputs.agent_strategy_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
const allVarStrArr = (() => {
|
||||||
|
const arr = currentStrategy?.parameters.filter(item => item.type === 'string').map((item) => {
|
||||||
|
return formData[item.name]
|
||||||
|
}) || []
|
||||||
|
return arr
|
||||||
|
})()
|
||||||
|
|
||||||
|
const varInputs = getInputVars?.(allVarStrArr)
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
const forms: FormProps[] = []
|
||||||
|
|
||||||
|
if (varInputs!.length > 0) {
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||||
|
inputs: varInputs!,
|
||||||
|
values: runInputData,
|
||||||
|
onChange: setRunInputData,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return forms
|
||||||
|
}, [runInputData, setRunInputData, t, varInputs])
|
||||||
|
|
||||||
|
const nodeInfo = useMemo(() => {
|
||||||
|
if (!runResult)
|
||||||
|
return
|
||||||
|
return formatTracing([runResult], t)[0]
|
||||||
|
}, [runResult, t])
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
nodeInfo,
|
||||||
|
getDependentVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -52,6 +52,7 @@ const VarList: FC<Props> = ({
|
||||||
const newList = produce(list, (draft) => {
|
const newList = produce(list, (draft) => {
|
||||||
draft[index].variable_selector = value as ValueSelector
|
draft[index].variable_selector = value as ValueSelector
|
||||||
draft[index].operation = WriteMode.overwrite
|
draft[index].operation = WriteMode.overwrite
|
||||||
|
draft[index].input_type = AssignerNodeInputType.variable
|
||||||
draft[index].value = undefined
|
draft[index].value = undefined
|
||||||
})
|
})
|
||||||
onChange(newList, value as ValueSelector)
|
onChange(newList, value as ValueSelector)
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,5 @@ export type AssignerNodeType = CommonNodeType & {
|
||||||
version?: '1' | '2'
|
version?: '1' | '2'
|
||||||
items: AssignerNodeOperation[]
|
items: AssignerNodeOperation[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const writeModeTypesNum = [WriteMode.increment, WriteMode.decrement, WriteMode.multiply, WriteMode.divide]
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { VarType } from '../../types'
|
||||||
import type { ValueSelector, Var } from '../../types'
|
import type { ValueSelector, Var } from '../../types'
|
||||||
import { WriteMode } from './types'
|
import { WriteMode } from './types'
|
||||||
import type { AssignerNodeOperation, AssignerNodeType } from './types'
|
import type { AssignerNodeOperation, AssignerNodeType } from './types'
|
||||||
|
import { writeModeTypesNum } from './types'
|
||||||
import { useGetAvailableVars } from './hooks'
|
import { useGetAvailableVars } from './hooks'
|
||||||
import { convertV1ToV2 } from './utils'
|
import { convertV1ToV2 } from './utils'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
|
|
@ -71,7 +72,6 @@ const useConfig = (id: string, rawPayload: AssignerNodeType) => {
|
||||||
|
|
||||||
const writeModeTypesArr = [WriteMode.overwrite, WriteMode.clear, WriteMode.append, WriteMode.extend, WriteMode.removeFirst, WriteMode.removeLast]
|
const writeModeTypesArr = [WriteMode.overwrite, WriteMode.clear, WriteMode.append, WriteMode.extend, WriteMode.removeFirst, WriteMode.removeLast]
|
||||||
const writeModeTypes = [WriteMode.overwrite, WriteMode.clear, WriteMode.set]
|
const writeModeTypes = [WriteMode.overwrite, WriteMode.clear, WriteMode.set]
|
||||||
const writeModeTypesNum = [WriteMode.increment, WriteMode.decrement, WriteMode.multiply, WriteMode.divide]
|
|
||||||
|
|
||||||
const getToAssignedVarType = useCallback((assignedVarType: VarType, write_mode: WriteMode) => {
|
const getToAssignedVarType = useCallback((assignedVarType: VarType, write_mode: WriteMode) => {
|
||||||
if (write_mode === WriteMode.overwrite || write_mode === WriteMode.increment || write_mode === WriteMode.decrement
|
if (write_mode === WriteMode.overwrite || write_mode === WriteMode.increment || write_mode === WriteMode.decrement
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import { type AssignerNodeType, WriteMode } from './types'
|
||||||
|
import { writeModeTypesNum } from './types'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: AssignerNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
setRunInputData,
|
||||||
|
varSelectorsToVarInputs,
|
||||||
|
}: Params) => {
|
||||||
|
const { inputs } = useNodeCrud<AssignerNodeType>(id, payload)
|
||||||
|
|
||||||
|
const vars = inputs.items.filter((item) => {
|
||||||
|
return item.operation !== WriteMode.clear && item.operation !== WriteMode.set
|
||||||
|
&& item.operation !== WriteMode.removeFirst && item.operation !== WriteMode.removeLast
|
||||||
|
&& !writeModeTypesNum.includes(item.operation)
|
||||||
|
}).map(item => item.value as ValueSelector)
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
const varInputs = varSelectorsToVarInputs(vars)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: varInputs,
|
||||||
|
values: runInputData,
|
||||||
|
onChange: setRunInputData,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [runInputData, setRunInputData, varSelectorsToVarInputs, vars])
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -14,8 +14,6 @@ import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
const i18nPrefix = 'workflow.nodes.code'
|
const i18nPrefix = 'workflow.nodes.code'
|
||||||
|
|
||||||
const codeLanguages = [
|
const codeLanguages = [
|
||||||
|
|
@ -50,16 +48,6 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||||
isShowRemoveVarConfirm,
|
isShowRemoveVarConfirm,
|
||||||
hideRemoveVarConfirm,
|
hideRemoveVarConfirm,
|
||||||
onRemoveVarConfirm,
|
onRemoveVarConfirm,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
const handleGeneratedCode = (value: string) => {
|
const handleGeneratedCode = (value: string) => {
|
||||||
|
|
@ -128,25 +116,6 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={[
|
|
||||||
{
|
|
||||||
inputs: varInputs,
|
|
||||||
values: inputVarValues,
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<RemoveEffectVarConfirm
|
<RemoveEffectVarConfirm
|
||||||
isShow={isShowRemoveVarConfirm}
|
isShow={isShowRemoveVarConfirm}
|
||||||
onCancel={hideRemoveVarConfirm}
|
onCancel={hideRemoveVarConfirm}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { useStore } from '../../store'
|
||||||
import type { CodeNodeType, OutputVar } from './types'
|
import type { CodeNodeType, OutputVar } from './types'
|
||||||
import { CodeLanguage } from './types'
|
import { CodeLanguage } from './types'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import { fetchNodeDefault } from '@/service/workflow'
|
import { fetchNodeDefault } from '@/service/workflow'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import {
|
import {
|
||||||
|
|
@ -104,38 +103,6 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||||
return [VarType.string, VarType.number, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
return [VarType.string, VarType.number, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
toVarInputs,
|
|
||||||
runningStatus,
|
|
||||||
isCompleted,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<CodeNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
const varInputs = toVarInputs(inputs.variables)
|
|
||||||
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
setRunInputData(newPayload)
|
|
||||||
}, [setRunInputData])
|
|
||||||
const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => {
|
const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.code = code
|
draft.code = code
|
||||||
|
|
@ -160,17 +127,6 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||||
isShowRemoveVarConfirm,
|
isShowRemoveVarConfirm,
|
||||||
hideRemoveVarConfirm,
|
hideRemoveVarConfirm,
|
||||||
onRemoveVarConfirm,
|
onRemoveVarConfirm,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
isCompleted,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
runResult,
|
|
||||||
handleCodeAndVarsChange,
|
handleCodeAndVarsChange,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import type { CodeNodeType } from './types'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: CodeNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
toVarInputs,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { inputs } = useNodeCrud<CodeNodeType>(id, payload)
|
||||||
|
|
||||||
|
const varInputs = toVarInputs(inputs.variables)
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
setRunInputData(newPayload)
|
||||||
|
}, [setRunInputData])
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: varInputs,
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [inputVarValues, setInputVarValues, varInputs])
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return payload.variables.map(v => v.value_selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
const varItem = payload.variables.find(v => v.variable === variable)
|
||||||
|
if (varItem)
|
||||||
|
return varItem.value_selector
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -11,11 +11,9 @@ import useConfig from './use-config'
|
||||||
import type { DocExtractorNodeType } from './types'
|
import type { DocExtractorNodeType } from './types'
|
||||||
import { fetchSupportFileTypes } from '@/service/datasets'
|
import { fetchSupportFileTypes } from '@/service/datasets'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import { BlockEnum, InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
import { BlockEnum, type NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import I18n from '@/context/i18n'
|
import I18n from '@/context/i18n'
|
||||||
import { LanguagesSupported } from '@/i18n/language'
|
import { LanguagesSupported } from '@/i18n/language'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.docExtractor'
|
const i18nPrefix = 'workflow.nodes.docExtractor'
|
||||||
|
|
||||||
|
|
@ -48,15 +46,6 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({
|
||||||
inputs,
|
inputs,
|
||||||
handleVarChanges,
|
handleVarChanges,
|
||||||
filterVar,
|
filterVar,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
files,
|
|
||||||
setFiles,
|
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -93,30 +82,6 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({
|
||||||
/>
|
/>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={[
|
|
||||||
{
|
|
||||||
inputs: [{
|
|
||||||
label: t(`${i18nPrefix}.inputVar`)!,
|
|
||||||
variable: 'files',
|
|
||||||
type: InputVarType.multiFiles,
|
|
||||||
required: true,
|
|
||||||
}],
|
|
||||||
values: { files },
|
|
||||||
onChange: keyValue => setFiles(keyValue.files),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useStoreApi } from 'reactflow'
|
import { useStoreApi } from 'reactflow'
|
||||||
|
|
||||||
import type { ValueSelector, Var } from '../../types'
|
import type { ValueSelector, Var } from '../../types'
|
||||||
import { InputVarType, VarType } from '../../types'
|
import { VarType } from '../../types'
|
||||||
import type { DocExtractorNodeType } from './types'
|
import type { DocExtractorNodeType } from './types'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import {
|
import {
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
|
|
@ -58,53 +56,11 @@ const useConfig = (id: string, payload: DocExtractorNodeType) => {
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [getType, inputs, setInputs])
|
}, [getType, inputs, setInputs])
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
isCompleted,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<DocExtractorNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: { files: [] },
|
|
||||||
})
|
|
||||||
const varInputs = [{
|
|
||||||
label: inputs.title,
|
|
||||||
variable: 'files',
|
|
||||||
type: InputVarType.multiFiles,
|
|
||||||
required: true,
|
|
||||||
}]
|
|
||||||
|
|
||||||
const files = runInputData.files
|
|
||||||
const setFiles = useCallback((newFiles: []) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputData,
|
|
||||||
files: newFiles,
|
|
||||||
})
|
|
||||||
}, [runInputData, setRunInputData])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readOnly,
|
readOnly,
|
||||||
inputs,
|
inputs,
|
||||||
filterVar,
|
filterVar,
|
||||||
handleVarChanges,
|
handleVarChanges,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
isCompleted,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
varInputs,
|
|
||||||
files,
|
|
||||||
setFiles,
|
|
||||||
runResult,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import type { DocExtractorNodeType } from './types'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
|
const i18nPrefix = 'workflow.nodes.docExtractor'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: DocExtractorNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const files = runInputData.files
|
||||||
|
const setFiles = useCallback((newFiles: []) => {
|
||||||
|
setRunInputData({
|
||||||
|
...runInputData,
|
||||||
|
files: newFiles,
|
||||||
|
})
|
||||||
|
}, [runInputData, setRunInputData])
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: [{
|
||||||
|
label: t(`${i18nPrefix}.inputVar`)!,
|
||||||
|
variable: 'files',
|
||||||
|
type: InputVarType.multiFiles,
|
||||||
|
required: true,
|
||||||
|
}],
|
||||||
|
values: { files },
|
||||||
|
onChange: (keyValue: Record<string, any>) => setFiles(keyValue.files),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [files, setFiles, t])
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return [payload.variable_selector]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
if(variable === 'files')
|
||||||
|
return payload.variable_selector
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -16,8 +16,6 @@ import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/compo
|
||||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
|
import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
|
||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.http'
|
const i18nPrefix = 'workflow.nodes.http'
|
||||||
|
|
||||||
|
|
@ -45,16 +43,6 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||||
hideAuthorization,
|
hideAuthorization,
|
||||||
setAuthorization,
|
setAuthorization,
|
||||||
setTimeout,
|
setTimeout,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
runResult,
|
|
||||||
isShowCurlPanel,
|
isShowCurlPanel,
|
||||||
showCurlPanel,
|
showCurlPanel,
|
||||||
hideCurlPanel,
|
hideCurlPanel,
|
||||||
|
|
@ -180,24 +168,6 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
</div>
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
nodeType={inputs.type}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={[
|
|
||||||
{
|
|
||||||
inputs: varInputs,
|
|
||||||
values: inputVarValues,
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(isShowCurlPanel && !readOnly) && (
|
{(isShowCurlPanel && !readOnly) && (
|
||||||
<CurlPanel
|
<CurlPanel
|
||||||
nodeId={id}
|
nodeId={id}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import useVarList from '../_base/hooks/use-var-list'
|
import useVarList from '../_base/hooks/use-var-list'
|
||||||
|
|
@ -9,7 +9,6 @@ import { type Authorization, type Body, BodyType, type HttpNodeType, type Method
|
||||||
import useKeyValueList from './hooks/use-key-value-list'
|
import useKeyValueList from './hooks/use-key-value-list'
|
||||||
import { transformToBodyPayload } from './utils'
|
import { transformToBodyPayload } from './utils'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import {
|
import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
|
|
@ -125,55 +124,6 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
getInputVars,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<HttpNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
const fileVarInputs = useMemo(() => {
|
|
||||||
if (!Array.isArray(inputs.body.data))
|
|
||||||
return ''
|
|
||||||
|
|
||||||
const res = inputs.body.data
|
|
||||||
.filter(item => item.file?.length)
|
|
||||||
.map(item => item.file ? `{{#${item.file.join('.')}#}}` : '')
|
|
||||||
.join(' ')
|
|
||||||
return res
|
|
||||||
}, [inputs.body.data])
|
|
||||||
|
|
||||||
const varInputs = getInputVars([
|
|
||||||
inputs.url,
|
|
||||||
inputs.headers,
|
|
||||||
inputs.params,
|
|
||||||
typeof inputs.body.data === 'string' ? inputs.body.data : inputs.body.data?.map(item => item.value).join(''),
|
|
||||||
fileVarInputs,
|
|
||||||
])
|
|
||||||
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
setRunInputData(newPayload)
|
|
||||||
}, [setRunInputData])
|
|
||||||
|
|
||||||
// curl import panel
|
// curl import panel
|
||||||
const [isShowCurlPanel, {
|
const [isShowCurlPanel, {
|
||||||
setTrue: showCurlPanel,
|
setTrue: showCurlPanel,
|
||||||
|
|
@ -220,16 +170,6 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||||
hideAuthorization,
|
hideAuthorization,
|
||||||
setAuthorization,
|
setAuthorization,
|
||||||
setTimeout,
|
setTimeout,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
runResult,
|
|
||||||
// curl import
|
// curl import
|
||||||
isShowCurlPanel,
|
isShowCurlPanel,
|
||||||
showCurlPanel,
|
showCurlPanel,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import type { HttpNodeType } from './types'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: HttpNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
getInputVars,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { inputs } = useNodeCrud<HttpNodeType>(id, payload)
|
||||||
|
|
||||||
|
const fileVarInputs = useMemo(() => {
|
||||||
|
if (!Array.isArray(inputs.body.data))
|
||||||
|
return ''
|
||||||
|
|
||||||
|
const res = inputs.body.data
|
||||||
|
.filter(item => item.file?.length)
|
||||||
|
.map(item => item.file ? `{{#${item.file.join('.')}#}}` : '')
|
||||||
|
.join(' ')
|
||||||
|
return res
|
||||||
|
}, [inputs.body.data])
|
||||||
|
const varInputs = getInputVars([
|
||||||
|
inputs.url,
|
||||||
|
inputs.headers,
|
||||||
|
inputs.params,
|
||||||
|
typeof inputs.body.data === 'string' ? inputs.body.data : inputs.body.data?.map(item => item.value).join(''),
|
||||||
|
fileVarInputs,
|
||||||
|
])
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
setRunInputData(newPayload)
|
||||||
|
}, [setRunInputData])
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: varInputs,
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [inputVarValues, setInputVarValues, varInputs])
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -69,7 +69,7 @@ const ConditionOperator = ({
|
||||||
<RiArrowDownSLine className='ml-1 h-3.5 w-3.5' />
|
<RiArrowDownSLine className='ml-1 h-3.5 w-3.5' />
|
||||||
</Button>
|
</Button>
|
||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className='z-10'>
|
<PortalToFollowElemContent className='z-[11]'>
|
||||||
<div className='rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
|
<div className='rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
|
||||||
{
|
{
|
||||||
options.map(option => (
|
options.map(option => (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { CaseItem, Condition, IfElseNodeType } from './types'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: IfElseNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
setRunInputData,
|
||||||
|
getInputVars,
|
||||||
|
varSelectorsToVarInputs,
|
||||||
|
}: Params) => {
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
setRunInputData(newPayload)
|
||||||
|
}, [setRunInputData])
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getVarSelectorsFromCase = (caseItem: CaseItem): ValueSelector[] => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (caseItem.conditions && caseItem.conditions.length) {
|
||||||
|
caseItem.conditions.forEach((condition) => {
|
||||||
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
|
const conditionVars = getVarSelectorsFromCondition(condition)
|
||||||
|
vars.push(...conditionVars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVarSelectorsFromCondition = (condition: Condition) => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (condition.variable_selector)
|
||||||
|
vars.push(condition.variable_selector)
|
||||||
|
|
||||||
|
if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||||
|
vars.push(...getVarSelectorsFromCase(condition.sub_variable_condition))
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInputVarsFromCase = (caseItem: CaseItem): InputVar[] => {
|
||||||
|
const vars: InputVar[] = []
|
||||||
|
if (caseItem.conditions && caseItem.conditions.length) {
|
||||||
|
caseItem.conditions.forEach((condition) => {
|
||||||
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
|
const conditionVars = getInputVarsFromConditionValue(condition)
|
||||||
|
vars.push(...conditionVars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInputVarsFromConditionValue = (condition: Condition): InputVar[] => {
|
||||||
|
const vars: InputVar[] = []
|
||||||
|
if (condition.value && typeof condition.value === 'string') {
|
||||||
|
const inputVars = getInputVars([condition.value])
|
||||||
|
vars.push(...inputVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||||
|
vars.push(...getInputVarsFromCase(condition.sub_variable_condition))
|
||||||
|
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const forms = (() => {
|
||||||
|
const allInputs: ValueSelector[] = []
|
||||||
|
const inputVarsFromValue: InputVar[] = []
|
||||||
|
if (payload.cases && payload.cases.length) {
|
||||||
|
payload.cases.forEach((caseItem) => {
|
||||||
|
const caseVars = getVarSelectorsFromCase(caseItem)
|
||||||
|
allInputs.push(...caseVars)
|
||||||
|
inputVarsFromValue.push(...getInputVarsFromCase(caseItem))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.conditions && payload.conditions.length) {
|
||||||
|
payload.conditions.forEach((condition) => {
|
||||||
|
const conditionVars = getVarSelectorsFromCondition(condition)
|
||||||
|
allInputs.push(...conditionVars)
|
||||||
|
inputVarsFromValue.push(...getInputVarsFromConditionValue(condition))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const varInputs = [...varSelectorsToVarInputs(allInputs), ...inputVarsFromValue]
|
||||||
|
// remove duplicate inputs
|
||||||
|
const existVarsKey: Record<string, boolean> = {}
|
||||||
|
const uniqueVarInputs: InputVar[] = []
|
||||||
|
varInputs.forEach((input) => {
|
||||||
|
if(!input)
|
||||||
|
return
|
||||||
|
if (!existVarsKey[input.variable]) {
|
||||||
|
existVarsKey[input.variable] = true
|
||||||
|
uniqueVarInputs.push(input)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: uniqueVarInputs,
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getVarFromCaseItem = (caseItem: CaseItem): ValueSelector[] => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (caseItem.conditions && caseItem.conditions.length) {
|
||||||
|
caseItem.conditions.forEach((condition) => {
|
||||||
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
|
const conditionVars = getVarFromCondition(condition)
|
||||||
|
vars.push(...conditionVars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
const getVarFromCondition = (condition: Condition): ValueSelector[] => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (condition.variable_selector)
|
||||||
|
vars.push(condition.variable_selector)
|
||||||
|
|
||||||
|
if(condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||||
|
vars.push(...getVarFromCaseItem(condition.sub_variable_condition))
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (payload.cases && payload.cases.length) {
|
||||||
|
payload.cases.forEach((caseItem) => {
|
||||||
|
const caseVars = getVarFromCaseItem(caseItem)
|
||||||
|
vars.push(...caseVars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.conditions && payload.conditions.length) {
|
||||||
|
payload.conditions.forEach((condition) => {
|
||||||
|
const conditionVars = getVarFromCondition(condition)
|
||||||
|
vars.push(...conditionVars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
PanelComponentMap,
|
PanelComponentMap,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
import BaseNode from './_base/node'
|
import BaseNode from './_base/node'
|
||||||
import BasePanel from './_base/panel'
|
import BasePanel from './_base/components/workflow-panel'
|
||||||
|
|
||||||
const CustomNode = (props: NodeProps) => {
|
const CustomNode = (props: NodeProps) => {
|
||||||
const nodeData = props.data
|
const nodeData = props.data
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,15 @@ import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
||||||
import Split from '../_base/components/split'
|
import Split from '../_base/components/split'
|
||||||
import ResultPanel from '../../run/result-panel'
|
|
||||||
import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants'
|
import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants'
|
||||||
import type { IterationNodeType } from './types'
|
import type { IterationNodeType } from './types'
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import { ErrorHandleMode, InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
import { ErrorHandleMode, type NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import Select from '@/app/components/base/select'
|
import Select from '@/app/components/base/select'
|
||||||
import Slider from '@/app/components/base/slider'
|
import Slider from '@/app/components/base/slider'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
|
||||||
|
|
||||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.iteration'
|
const i18nPrefix = 'workflow.nodes.iteration'
|
||||||
|
|
||||||
|
|
@ -47,27 +42,11 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||||
childrenNodeVars,
|
childrenNodeVars,
|
||||||
iterationChildrenNodes,
|
iterationChildrenNodes,
|
||||||
handleOutputVarChange,
|
handleOutputVarChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
usedOutVars,
|
|
||||||
iterator,
|
|
||||||
setIterator,
|
|
||||||
iteratorInputKey,
|
|
||||||
iterationRunResult,
|
|
||||||
changeParallel,
|
changeParallel,
|
||||||
changeErrorResponseMode,
|
changeErrorResponseMode,
|
||||||
changeParallelNums,
|
changeParallelNums,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
const nodeInfo = formatTracing(iterationRunResult, t)[0]
|
|
||||||
const logsParams = useLogs()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='pb-2 pt-2'>
|
<div className='pb-2 pt-2'>
|
||||||
<div className='space-y-4 px-4 pb-4'>
|
<div className='space-y-4 px-4 pb-4'>
|
||||||
|
|
@ -137,38 +116,6 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||||
<Select items={responseMethod} defaultValue={inputs.error_handle_mode} onSelect={changeErrorResponseMode} allowSearch={false} />
|
<Select items={responseMethod} defaultValue={inputs.error_handle_mode} onSelect={changeErrorResponseMode} allowSearch={false} />
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={[
|
|
||||||
{
|
|
||||||
inputs: [...usedOutVars],
|
|
||||||
values: inputVarValues,
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t(`${i18nPrefix}.input`)!,
|
|
||||||
inputs: [{
|
|
||||||
label: '',
|
|
||||||
variable: iteratorInputKey,
|
|
||||||
type: InputVarType.iterator,
|
|
||||||
required: false,
|
|
||||||
}],
|
|
||||||
values: { [iteratorInputKey]: iterator },
|
|
||||||
onChange: keyValue => setIterator(keyValue[iteratorInputKey]),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
{...logsParams}
|
|
||||||
result={
|
|
||||||
<ResultPanel {...runResult} showSteps={false} nodeInfo={nodeInfo} {...logsParams} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export type IterationNodeType = CommonNodeType & {
|
||||||
start_node_id: string // start node id in the iteration
|
start_node_id: string // start node id in the iteration
|
||||||
iteration_id?: string
|
iteration_id?: string
|
||||||
iterator_selector: ValueSelector
|
iterator_selector: ValueSelector
|
||||||
|
iterator_input_type: VarType
|
||||||
output_selector: ValueSelector
|
output_selector: ValueSelector
|
||||||
output_type: VarType // output type.
|
output_type: VarType // output type.
|
||||||
is_parallel: boolean // open the parallel mode or not
|
is_parallel: boolean // open the parallel mode or not
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useBoolean } from 'ahooks'
|
|
||||||
import {
|
import {
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
useIsNodeInIteration,
|
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
} from '../../hooks'
|
} from '../../hooks'
|
||||||
import { VarType } from '../../types'
|
import { VarType } from '../../types'
|
||||||
import type { ErrorHandleMode, ValueSelector, Var } from '../../types'
|
import type { ErrorHandleMode, ValueSelector, Var } from '../../types'
|
||||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar, toNodeOutputVars } from '../_base/components/variable/utils'
|
|
||||||
import useOneStepRun from '../_base/hooks/use-one-step-run'
|
|
||||||
import type { IterationNodeType } from './types'
|
import type { IterationNodeType } from './types'
|
||||||
|
import { toNodeOutputVars } from '../_base/components/variable/utils'
|
||||||
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||||
import type { Item } from '@/app/components/base/select'
|
import type { Item } from '@/app/components/base/select'
|
||||||
|
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||||
|
import { isEqual } from 'lodash-es'
|
||||||
|
|
||||||
const DELIMITER = '@@@@@'
|
|
||||||
const useConfig = (id: string, payload: IterationNodeType) => {
|
const useConfig = (id: string, payload: IterationNodeType) => {
|
||||||
|
const {
|
||||||
|
deleteNodeInspectorVars,
|
||||||
|
} = useInspectVarsCrud()
|
||||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
const { isNodeInIteration } = useIsNodeInIteration(id)
|
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
|
|
||||||
const { inputs, setInputs } = useNodeCrud<IterationNodeType>(id, payload)
|
const { inputs, setInputs } = useNodeCrud<IterationNodeType>(id, payload)
|
||||||
|
|
@ -28,21 +28,23 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||||
return [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type)
|
return [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleInputChange = useCallback((input: ValueSelector | string) => {
|
const handleInputChange = useCallback((input: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.iterator_selector = input as ValueSelector || []
|
draft.iterator_selector = input as ValueSelector || []
|
||||||
|
draft.iterator_input_type = varInfo?.type || VarType.arrayString
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
}, [inputs, setInputs])
|
||||||
|
|
||||||
// output
|
// output
|
||||||
const { getIterationNodeChildren, getBeforeNodesInSameBranch } = useWorkflow()
|
const { getIterationNodeChildren } = useWorkflow()
|
||||||
const beforeNodes = getBeforeNodesInSameBranch(id)
|
|
||||||
const iterationChildrenNodes = getIterationNodeChildren(id)
|
const iterationChildrenNodes = getIterationNodeChildren(id)
|
||||||
const canChooseVarNodes = [...beforeNodes, ...iterationChildrenNodes]
|
|
||||||
const childrenNodeVars = toNodeOutputVars(iterationChildrenNodes, isChatMode)
|
const childrenNodeVars = toNodeOutputVars(iterationChildrenNodes, isChatMode)
|
||||||
|
|
||||||
const handleOutputVarChange = useCallback((output: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {
|
const handleOutputVarChange = useCallback((output: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {
|
||||||
|
if (isEqual(inputs.output_selector, output as ValueSelector))
|
||||||
|
return
|
||||||
|
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.output_selector = output as ValueSelector || []
|
draft.output_selector = output as ValueSelector || []
|
||||||
const outputItemType = varInfo?.type || VarType.string
|
const outputItemType = varInfo?.type || VarType.string
|
||||||
|
|
@ -61,135 +63,8 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||||
} as Record<VarType, VarType>)[outputItemType] || VarType.arrayString
|
} as Record<VarType, VarType>)[outputItemType] || VarType.arrayString
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
deleteNodeInspectorVars(id)
|
||||||
|
}, [deleteNodeInspectorVars, id, inputs, setInputs])
|
||||||
// single run
|
|
||||||
const iteratorInputKey = `${id}.input_selector`
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
showSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
toVarInputs,
|
|
||||||
runningStatus,
|
|
||||||
handleRun: doHandleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
iterationRunResult,
|
|
||||||
} = useOneStepRun<IterationNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
iteratorInputKey,
|
|
||||||
defaultRunInputData: {
|
|
||||||
[iteratorInputKey]: [''],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const [isShowIterationDetail, {
|
|
||||||
setTrue: doShowIterationDetail,
|
|
||||||
setFalse: doHideIterationDetail,
|
|
||||||
}] = useBoolean(false)
|
|
||||||
|
|
||||||
const hideIterationDetail = useCallback(() => {
|
|
||||||
hideSingleRun()
|
|
||||||
doHideIterationDetail()
|
|
||||||
}, [doHideIterationDetail, hideSingleRun])
|
|
||||||
|
|
||||||
const showIterationDetail = useCallback(() => {
|
|
||||||
doShowIterationDetail()
|
|
||||||
}, [doShowIterationDetail])
|
|
||||||
|
|
||||||
const backToSingleRun = useCallback(() => {
|
|
||||||
hideIterationDetail()
|
|
||||||
showSingleRun()
|
|
||||||
}, [hideIterationDetail, showSingleRun])
|
|
||||||
|
|
||||||
const { usedOutVars, allVarObject } = (() => {
|
|
||||||
const vars: ValueSelector[] = []
|
|
||||||
const varObjs: Record<string, boolean> = {}
|
|
||||||
const allVarObject: Record<string, {
|
|
||||||
inSingleRunPassedKey: string
|
|
||||||
}> = {}
|
|
||||||
iterationChildrenNodes.forEach((node) => {
|
|
||||||
const nodeVars = getNodeUsedVars(node).filter(item => item && item.length > 0)
|
|
||||||
nodeVars.forEach((varSelector) => {
|
|
||||||
if (varSelector[0] === id) { // skip iteration node itself variable: item, index
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const isInIteration = isNodeInIteration(varSelector[0])
|
|
||||||
if (isInIteration) // not pass iteration inner variable
|
|
||||||
return
|
|
||||||
|
|
||||||
const varSectorStr = varSelector.join('.')
|
|
||||||
if (!varObjs[varSectorStr]) {
|
|
||||||
varObjs[varSectorStr] = true
|
|
||||||
vars.push(varSelector)
|
|
||||||
}
|
|
||||||
let passToServerKeys = getNodeUsedVarPassToServerKey(node, varSelector)
|
|
||||||
if (typeof passToServerKeys === 'string')
|
|
||||||
passToServerKeys = [passToServerKeys]
|
|
||||||
|
|
||||||
passToServerKeys.forEach((key: string, index: number) => {
|
|
||||||
allVarObject[[varSectorStr, node.id, index].join(DELIMITER)] = {
|
|
||||||
inSingleRunPassedKey: key,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const res = toVarInputs(vars.map((item) => {
|
|
||||||
const varInfo = getNodeInfoById(canChooseVarNodes, item[0])
|
|
||||||
return {
|
|
||||||
label: {
|
|
||||||
nodeType: varInfo?.data.type,
|
|
||||||
nodeName: varInfo?.data.title || canChooseVarNodes[0]?.data.title, // default start node title
|
|
||||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
|
||||||
},
|
|
||||||
variable: `${item.join('.')}`,
|
|
||||||
value_selector: item,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
return {
|
|
||||||
usedOutVars: res,
|
|
||||||
allVarObject,
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
const handleRun = useCallback((data: Record<string, any>) => {
|
|
||||||
const formattedData: Record<string, any> = {}
|
|
||||||
Object.keys(allVarObject).forEach((key) => {
|
|
||||||
const [varSectorStr, nodeId] = key.split(DELIMITER)
|
|
||||||
formattedData[`${nodeId}.${allVarObject[key].inSingleRunPassedKey}`] = data[varSectorStr]
|
|
||||||
})
|
|
||||||
formattedData[iteratorInputKey] = data[iteratorInputKey]
|
|
||||||
doHandleRun(formattedData)
|
|
||||||
}, [allVarObject, doHandleRun, iteratorInputKey])
|
|
||||||
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.filter(key => ![iteratorInputKey].includes(key))
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
const newVars = {
|
|
||||||
...newPayload,
|
|
||||||
[iteratorInputKey]: runInputData[iteratorInputKey],
|
|
||||||
}
|
|
||||||
setRunInputData(newVars)
|
|
||||||
}, [iteratorInputKey, runInputData, setRunInputData])
|
|
||||||
|
|
||||||
const iterator = runInputData[iteratorInputKey]
|
|
||||||
const setIterator = useCallback((newIterator: string[]) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputData,
|
|
||||||
[iteratorInputKey]: newIterator,
|
|
||||||
})
|
|
||||||
}, [iteratorInputKey, runInputData, setRunInputData])
|
|
||||||
|
|
||||||
const changeParallel = useCallback((value: boolean) => {
|
const changeParallel = useCallback((value: boolean) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
|
@ -218,24 +93,6 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||||
childrenNodeVars,
|
childrenNodeVars,
|
||||||
iterationChildrenNodes,
|
iterationChildrenNodes,
|
||||||
handleOutputVarChange,
|
handleOutputVarChange,
|
||||||
isShowSingleRun,
|
|
||||||
showSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
isShowIterationDetail,
|
|
||||||
showIterationDetail,
|
|
||||||
hideIterationDetail,
|
|
||||||
backToSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
usedOutVars,
|
|
||||||
iterator,
|
|
||||||
setIterator,
|
|
||||||
iteratorInputKey,
|
|
||||||
iterationRunResult,
|
|
||||||
changeParallel,
|
changeParallel,
|
||||||
changeErrorResponseMode,
|
changeErrorResponseMode,
|
||||||
changeParallelNums,
|
changeParallelNums,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import type { IterationNodeType } from './types'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useIsNodeInIteration, useWorkflow } from '../../hooks'
|
||||||
|
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar } from '../_base/components/variable/utils'
|
||||||
|
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||||
|
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config'
|
||||||
|
|
||||||
|
const i18nPrefix = 'workflow.nodes.iteration'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: IterationNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
iterationRunResult: NodeTracing[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
toVarInputs,
|
||||||
|
setRunInputData,
|
||||||
|
iterationRunResult,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { isNodeInIteration } = useIsNodeInIteration(id)
|
||||||
|
|
||||||
|
const { getIterationNodeChildren, getBeforeNodesInSameBranch } = useWorkflow()
|
||||||
|
const iterationChildrenNodes = getIterationNodeChildren(id)
|
||||||
|
const beforeNodes = getBeforeNodesInSameBranch(id)
|
||||||
|
const canChooseVarNodes = [...beforeNodes, ...iterationChildrenNodes]
|
||||||
|
|
||||||
|
const iteratorInputKey = `${id}.input_selector`
|
||||||
|
const iterator = runInputData[iteratorInputKey]
|
||||||
|
const setIterator = useCallback((newIterator: string[]) => {
|
||||||
|
setRunInputData({
|
||||||
|
...runInputData,
|
||||||
|
[iteratorInputKey]: newIterator,
|
||||||
|
})
|
||||||
|
}, [iteratorInputKey, runInputData, setRunInputData])
|
||||||
|
|
||||||
|
const { usedOutVars, allVarObject } = (() => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
const varObjs: Record<string, boolean> = {}
|
||||||
|
const allVarObject: Record<string, {
|
||||||
|
inSingleRunPassedKey: string
|
||||||
|
}> = {}
|
||||||
|
iterationChildrenNodes.forEach((node) => {
|
||||||
|
const nodeVars = getNodeUsedVars(node).filter(item => item && item.length > 0)
|
||||||
|
nodeVars.forEach((varSelector) => {
|
||||||
|
if (varSelector[0] === id) { // skip iteration node itself variable: item, index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const isInIteration = isNodeInIteration(varSelector[0])
|
||||||
|
if (isInIteration) // not pass iteration inner variable
|
||||||
|
return
|
||||||
|
|
||||||
|
const varSectorStr = varSelector.join('.')
|
||||||
|
if (!varObjs[varSectorStr]) {
|
||||||
|
varObjs[varSectorStr] = true
|
||||||
|
vars.push(varSelector)
|
||||||
|
}
|
||||||
|
let passToServerKeys = getNodeUsedVarPassToServerKey(node, varSelector)
|
||||||
|
if (typeof passToServerKeys === 'string')
|
||||||
|
passToServerKeys = [passToServerKeys]
|
||||||
|
|
||||||
|
passToServerKeys.forEach((key: string, index: number) => {
|
||||||
|
allVarObject[[varSectorStr, node.id, index].join(DELIMITER)] = {
|
||||||
|
inSingleRunPassedKey: key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const res = toVarInputs(vars.map((item) => {
|
||||||
|
const varInfo = getNodeInfoById(canChooseVarNodes, item[0])
|
||||||
|
return {
|
||||||
|
label: {
|
||||||
|
nodeType: varInfo?.data.type,
|
||||||
|
nodeName: varInfo?.data.title || canChooseVarNodes[0]?.data.title, // default start node title
|
||||||
|
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||||
|
},
|
||||||
|
variable: `${item.join('.')}`,
|
||||||
|
value_selector: item,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
usedOutVars: res,
|
||||||
|
allVarObject,
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
setRunInputData(newPayload)
|
||||||
|
}, [setRunInputData])
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: [...usedOutVars],
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(`${i18nPrefix}.input`)!,
|
||||||
|
inputs: [{
|
||||||
|
label: '',
|
||||||
|
variable: iteratorInputKey,
|
||||||
|
type: InputVarType.iterator,
|
||||||
|
required: false,
|
||||||
|
getVarValueFromDependent: true,
|
||||||
|
isFileItem: payload.iterator_input_type === VarType.arrayFile,
|
||||||
|
}],
|
||||||
|
values: { [iteratorInputKey]: iterator },
|
||||||
|
onChange: (keyValue: Record<string, any>) => setIterator(keyValue[iteratorInputKey]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [inputVarValues, iterator, iteratorInputKey, payload.iterator_input_type, setInputVarValues, setIterator, t, usedOutVars])
|
||||||
|
|
||||||
|
const nodeInfo = formatTracing(iterationRunResult, t)[0]
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return [payload.iterator_selector]
|
||||||
|
}
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
if(variable === iteratorInputKey)
|
||||||
|
return payload.iterator_selector
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
nodeInfo,
|
||||||
|
allVarObject,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -16,9 +16,7 @@ import type { KnowledgeRetrievalNodeType } from './types'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.knowledgeRetrieval'
|
const i18nPrefix = 'workflow.nodes.knowledgeRetrieval'
|
||||||
|
|
||||||
|
|
@ -40,14 +38,6 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
||||||
selectedDatasets,
|
selectedDatasets,
|
||||||
selectedDatasetsLoaded,
|
selectedDatasetsLoaded,
|
||||||
handleOnDatasetsChange,
|
handleOnDatasetsChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
query,
|
|
||||||
setQuery,
|
|
||||||
runResult,
|
|
||||||
rerankModelOpen,
|
rerankModelOpen,
|
||||||
setRerankModelOpen,
|
setRerankModelOpen,
|
||||||
handleAddCondition,
|
handleAddCondition,
|
||||||
|
|
@ -191,28 +181,6 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
||||||
|
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={[
|
|
||||||
{
|
|
||||||
inputs: [{
|
|
||||||
label: t(`${i18nPrefix}.queryVariable`)!,
|
|
||||||
variable: 'query',
|
|
||||||
type: InputVarType.paragraph,
|
|
||||||
required: true,
|
|
||||||
}],
|
|
||||||
values: { query },
|
|
||||||
onChange: keyValue => setQuery(keyValue.query),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ import { DATASET_DEFAULT } from '@/config'
|
||||||
import type { DataSet } from '@/models/datasets'
|
import type { DataSet } from '@/models/datasets'
|
||||||
import { fetchDatasets } from '@/service/datasets'
|
import { fetchDatasets } from '@/service/datasets'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||||
|
|
@ -280,32 +279,6 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||||
return varPayload.type === VarType.string
|
return varPayload.type === VarType.string
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<KnowledgeRetrievalNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {
|
|
||||||
query: '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const query = runInputData.query
|
|
||||||
const setQuery = useCallback((newQuery: string) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputData,
|
|
||||||
query: newQuery,
|
|
||||||
})
|
|
||||||
}, [runInputData, setRunInputData])
|
|
||||||
|
|
||||||
const handleMetadataFilterModeChange = useCallback((newMode: MetadataFilteringModeEnum) => {
|
const handleMetadataFilterModeChange = useCallback((newMode: MetadataFilteringModeEnum) => {
|
||||||
setInputs(produce(inputRef.current, (draft) => {
|
setInputs(produce(inputRef.current, (draft) => {
|
||||||
draft.metadata_filtering_mode = newMode
|
draft.metadata_filtering_mode = newMode
|
||||||
|
|
@ -425,14 +398,6 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||||
selectedDatasets: selectedDatasets.filter(d => d.name),
|
selectedDatasets: selectedDatasets.filter(d => d.name),
|
||||||
selectedDatasetsLoaded,
|
selectedDatasetsLoaded,
|
||||||
handleOnDatasetsChange,
|
handleOnDatasetsChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
query,
|
|
||||||
setQuery,
|
|
||||||
runResult,
|
|
||||||
rerankModelOpen,
|
rerankModelOpen,
|
||||||
setRerankModelOpen,
|
setRerankModelOpen,
|
||||||
handleMetadataFilterModeChange,
|
handleMetadataFilterModeChange,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import type { KnowledgeRetrievalNodeType } from './types'
|
||||||
|
|
||||||
|
const i18nPrefix = 'workflow.nodes.knowledgeRetrieval'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: KnowledgeRetrievalNodeType
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const query = runInputData.query
|
||||||
|
const setQuery = useCallback((newQuery: string) => {
|
||||||
|
setRunInputData({
|
||||||
|
...runInputData,
|
||||||
|
query: newQuery,
|
||||||
|
})
|
||||||
|
}, [runInputData, setRunInputData])
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: [{
|
||||||
|
label: t(`${i18nPrefix}.queryVariable`)!,
|
||||||
|
variable: 'query',
|
||||||
|
type: InputVarType.paragraph,
|
||||||
|
required: true,
|
||||||
|
}],
|
||||||
|
values: { query },
|
||||||
|
onChange: (keyValue: Record<string, any>) => setQuery(keyValue.query),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [query, setQuery, t])
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return [payload.query_variable_selector]
|
||||||
|
}
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
if(variable === 'query')
|
||||||
|
return payload.query_variable_selector
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { type FC, useCallback, useEffect, useRef } from 'react'
|
import React, { type FC, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import useTheme from '@/hooks/use-theme'
|
import useTheme from '@/hooks/use-theme'
|
||||||
import { Theme } from '@/types/app'
|
import { Theme } from '@/types/app'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
|
|
@ -14,6 +14,7 @@ type CodeEditorProps = {
|
||||||
showFormatButton?: boolean
|
showFormatButton?: boolean
|
||||||
editorWrapperClassName?: string
|
editorWrapperClassName?: string
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
|
hideTopMenu?: boolean
|
||||||
} & React.HTMLAttributes<HTMLDivElement>
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
const CodeEditor: FC<CodeEditorProps> = ({
|
const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
|
|
@ -22,12 +23,14 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
showFormatButton = true,
|
showFormatButton = true,
|
||||||
editorWrapperClassName,
|
editorWrapperClassName,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
hideTopMenu = false,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const monacoRef = useRef<any>(null)
|
const monacoRef = useRef<any>(null)
|
||||||
const editorRef = useRef<any>(null)
|
const editorRef = useRef<any>(null)
|
||||||
|
const [isMounted, setIsMounted] = React.useState(false)
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -63,6 +66,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
monaco.editor.setTheme('light-theme')
|
monaco.editor.setTheme('light-theme')
|
||||||
|
setIsMounted(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const formatJsonContent = useCallback(() => {
|
const formatJsonContent = useCallback(() => {
|
||||||
|
|
@ -75,6 +79,11 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
onUpdate?.(value)
|
onUpdate?.(value)
|
||||||
}, [onUpdate])
|
}, [onUpdate])
|
||||||
|
|
||||||
|
const editorTheme = useMemo(() => {
|
||||||
|
if (theme === Theme.light)
|
||||||
|
return 'light-theme'
|
||||||
|
return 'dark-theme'
|
||||||
|
}, [theme])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
editorRef.current?.layout()
|
editorRef.current?.layout()
|
||||||
|
|
@ -89,7 +98,8 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', className)}>
|
<div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', hideTopMenu && 'pt-2', className)}>
|
||||||
|
{!hideTopMenu && (
|
||||||
<div className='flex items-center justify-between pl-2 pr-1 pt-1'>
|
<div className='flex items-center justify-between pl-2 pr-1 pt-1'>
|
||||||
<div className='system-xs-semibold-uppercase py-0.5 text-text-secondary'>
|
<div className='system-xs-semibold-uppercase py-0.5 text-text-secondary'>
|
||||||
<span className='px-1 py-0.5'>JSON</span>
|
<span className='px-1 py-0.5'>JSON</span>
|
||||||
|
|
@ -116,12 +126,11 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
)}
|
||||||
ref={containerRef}
|
<div className={classNames('relative overflow-hidden', editorWrapperClassName)}>
|
||||||
className={classNames('relative overflow-hidden', editorWrapperClassName)}
|
|
||||||
>
|
|
||||||
<Editor
|
<Editor
|
||||||
defaultLanguage='json'
|
defaultLanguage='json'
|
||||||
|
theme={isMounted ? editorTheme : 'default-theme'} // sometimes not load the default theme
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleEditorChange}
|
onChange={handleEditorChange}
|
||||||
onMount={handleEditorDidMount}
|
onMount={handleEditorDidMount}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,30 @@
|
||||||
import React, { type FC } from 'react'
|
import React, { type FC } from 'react'
|
||||||
import CodeEditor from './code-editor'
|
import CodeEditor from './code-editor'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type SchemaEditorProps = {
|
type SchemaEditorProps = {
|
||||||
schema: string
|
schema: string
|
||||||
onUpdate: (schema: string) => void
|
onUpdate: (schema: string) => void
|
||||||
|
hideTopMenu?: boolean
|
||||||
|
className?: string
|
||||||
|
readonly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const SchemaEditor: FC<SchemaEditorProps> = ({
|
const SchemaEditor: FC<SchemaEditorProps> = ({
|
||||||
schema,
|
schema,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
hideTopMenu,
|
||||||
|
className,
|
||||||
|
readonly = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
className='grow rounded-xl'
|
readOnly={readonly}
|
||||||
|
className={cn('grow rounded-xl', className)}
|
||||||
editorWrapperClassName='grow'
|
editorWrapperClassName='grow'
|
||||||
value={schema}
|
value={schema}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
|
hideTopMenu={hideTopMenu}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,29 @@
|
||||||
|
// import { RETRIEVAL_OUTPUT_STRUCT } from '../../constants'
|
||||||
import { BlockEnum, EditionType } from '../../types'
|
import { BlockEnum, EditionType } from '../../types'
|
||||||
import { type NodeDefault, type PromptItem, PromptRole } from '../../types'
|
import { type NodeDefault, type PromptItem, PromptRole } from '../../types'
|
||||||
import type { LLMNodeType } from './types'
|
import type { LLMNodeType } from './types'
|
||||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
||||||
|
|
||||||
|
const RETRIEVAL_OUTPUT_STRUCT = `{
|
||||||
|
"content": "",
|
||||||
|
"title": "",
|
||||||
|
"url": "",
|
||||||
|
"icon": "",
|
||||||
|
"metadata": {
|
||||||
|
"dataset_id": "",
|
||||||
|
"dataset_name": "",
|
||||||
|
"document_id": [],
|
||||||
|
"document_name": "",
|
||||||
|
"document_data_source_type": "",
|
||||||
|
"segment_id": "",
|
||||||
|
"segment_position": "",
|
||||||
|
"segment_word_count": "",
|
||||||
|
"segment_hit_count": "",
|
||||||
|
"segment_index_node_hash": "",
|
||||||
|
"score": ""
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.errorMsg'
|
const i18nPrefix = 'workflow.errorMsg'
|
||||||
|
|
||||||
const nodeDefault: NodeDefault<LLMNodeType> = {
|
const nodeDefault: NodeDefault<LLMNodeType> = {
|
||||||
|
|
@ -27,6 +48,10 @@ const nodeDefault: NodeDefault<LLMNodeType> = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
defaultRunInputData: {
|
||||||
|
'#context#': [RETRIEVAL_OUTPUT_STRUCT],
|
||||||
|
'#files#': [],
|
||||||
|
},
|
||||||
getAvailablePrevNodes(isChatMode: boolean) {
|
getAvailablePrevNodes(isChatMode: boolean) {
|
||||||
const nodes = isChatMode
|
const nodes = isChatMode
|
||||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import MemoryConfig from '../_base/components/memory-config'
|
||||||
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
||||||
import ConfigVision from '../_base/components/config-vision'
|
import ConfigVision from '../_base/components/config-vision'
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import { findVariableWhenOnLLMVision } from '../utils'
|
|
||||||
import type { LLMNodeType } from './types'
|
import type { LLMNodeType } from './types'
|
||||||
import ConfigPrompt from './components/config-prompt'
|
import ConfigPrompt from './components/config-prompt'
|
||||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||||
|
|
@ -14,10 +13,7 @@ import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||||
import StructureOutput from './components/structure-output'
|
import StructureOutput from './components/structure-output'
|
||||||
|
|
@ -31,7 +27,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
readOnly,
|
readOnly,
|
||||||
inputs,
|
inputs,
|
||||||
|
|
@ -58,80 +53,16 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
visionFiles,
|
|
||||||
setVisionFiles,
|
|
||||||
contexts,
|
|
||||||
setContexts,
|
|
||||||
runningStatus,
|
|
||||||
isModelSupportStructuredOutput,
|
isModelSupportStructuredOutput,
|
||||||
structuredOutputCollapsed,
|
structuredOutputCollapsed,
|
||||||
setStructuredOutputCollapsed,
|
setStructuredOutputCollapsed,
|
||||||
handleStructureOutputEnableChange,
|
handleStructureOutputEnableChange,
|
||||||
handleStructureOutputChange,
|
handleStructureOutputChange,
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
varInputs,
|
|
||||||
runResult,
|
|
||||||
filterJinjia2InputVar,
|
filterJinjia2InputVar,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
const model = inputs.model
|
const model = inputs.model
|
||||||
|
|
||||||
const singleRunForms = (() => {
|
|
||||||
const forms: FormProps[] = []
|
|
||||||
|
|
||||||
if (varInputs.length > 0) {
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t(`${i18nPrefix}.singleRun.variable`)!,
|
|
||||||
inputs: varInputs,
|
|
||||||
values: inputVarValues,
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputs.context?.variable_selector && inputs.context?.variable_selector.length > 0) {
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t(`${i18nPrefix}.context`)!,
|
|
||||||
inputs: [{
|
|
||||||
label: '',
|
|
||||||
variable: '#context#',
|
|
||||||
type: InputVarType.contexts,
|
|
||||||
required: false,
|
|
||||||
}],
|
|
||||||
values: { '#context#': contexts },
|
|
||||||
onChange: keyValue => setContexts(keyValue['#context#']),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVisionModel && data.vision?.enabled && data.vision?.configs?.variable_selector) {
|
|
||||||
const currentVariable = findVariableWhenOnLLMVision(data.vision.configs.variable_selector, availableVars)
|
|
||||||
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t(`${i18nPrefix}.vision`)!,
|
|
||||||
inputs: [{
|
|
||||||
label: currentVariable?.variable as any,
|
|
||||||
variable: '#files#',
|
|
||||||
type: currentVariable?.formType as any,
|
|
||||||
required: false,
|
|
||||||
}],
|
|
||||||
values: { '#files#': visionFiles },
|
|
||||||
onChange: keyValue => setVisionFiles((keyValue as any)['#files#']),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return forms
|
|
||||||
})()
|
|
||||||
|
|
||||||
const handleModelChange = useCallback((model: {
|
const handleModelChange = useCallback((model: {
|
||||||
provider: string
|
provider: string
|
||||||
modelId: string
|
modelId: string
|
||||||
|
|
@ -344,18 +275,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
nodeType={inputs.type}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={singleRunForms}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,8 @@ import {
|
||||||
ModelTypeEnum,
|
ModelTypeEnum,
|
||||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
|
|
||||||
import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
||||||
|
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
|
||||||
|
|
||||||
const useConfig = (id: string, payload: LLMNodeType) => {
|
const useConfig = (id: string, payload: LLMNodeType) => {
|
||||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
|
|
@ -29,6 +28,8 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<LLMNodeType>(id, payload)
|
const { inputs, setInputs: doSetInputs } = useNodeCrud<LLMNodeType>(id, payload)
|
||||||
const inputRef = useRef(inputs)
|
const inputRef = useRef(inputs)
|
||||||
|
|
||||||
|
const { deleteNodeInspectorVars } = useInspectVarsCrud()
|
||||||
|
|
||||||
const setInputs = useCallback((newInputs: LLMNodeType) => {
|
const setInputs = useCallback((newInputs: LLMNodeType) => {
|
||||||
if (newInputs.memory && !newInputs.memory.role_prefix) {
|
if (newInputs.memory && !newInputs.memory.role_prefix) {
|
||||||
const newPayload = produce(newInputs, (draft) => {
|
const newPayload = produce(newInputs, (draft) => {
|
||||||
|
|
@ -293,14 +294,16 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
if (enabled)
|
if (enabled)
|
||||||
setStructuredOutputCollapsed(false)
|
setStructuredOutputCollapsed(false)
|
||||||
}, [inputs, setInputs])
|
deleteNodeInspectorVars(id)
|
||||||
|
}, [inputs, setInputs, deleteNodeInspectorVars, id])
|
||||||
|
|
||||||
const handleStructureOutputChange = useCallback((newOutput: StructuredOutput) => {
|
const handleStructureOutputChange = useCallback((newOutput: StructuredOutput) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.structured_output = newOutput
|
draft.structured_output = newOutput
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
deleteNodeInspectorVars(id)
|
||||||
|
}, [inputs, setInputs, deleteNodeInspectorVars, id])
|
||||||
|
|
||||||
const filterInputVar = useCallback((varPayload: Var) => {
|
const filterInputVar = useCallback((varPayload: Var) => {
|
||||||
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||||
|
|
@ -322,81 +325,6 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||||
filterVar: filterMemoryPromptVar,
|
filterVar: filterMemoryPromptVar,
|
||||||
})
|
})
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
getInputVars,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
runInputDataRef,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
toVarInputs,
|
|
||||||
} = useOneStepRun<LLMNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {
|
|
||||||
'#context#': [RETRIEVAL_OUTPUT_STRUCT],
|
|
||||||
'#files#': [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.filter(key => !['#context#', '#files#'].includes(key))
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
const newVars = {
|
|
||||||
...newPayload,
|
|
||||||
'#context#': runInputDataRef.current['#context#'],
|
|
||||||
'#files#': runInputDataRef.current['#files#'],
|
|
||||||
}
|
|
||||||
setRunInputData(newVars)
|
|
||||||
}, [runInputDataRef, setRunInputData])
|
|
||||||
|
|
||||||
const contexts = runInputData['#context#']
|
|
||||||
const setContexts = useCallback((newContexts: string[]) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputDataRef.current,
|
|
||||||
'#context#': newContexts,
|
|
||||||
})
|
|
||||||
}, [runInputDataRef, setRunInputData])
|
|
||||||
|
|
||||||
const visionFiles = runInputData['#files#']
|
|
||||||
const setVisionFiles = useCallback((newFiles: any[]) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputDataRef.current,
|
|
||||||
'#files#': newFiles,
|
|
||||||
})
|
|
||||||
}, [runInputDataRef, setRunInputData])
|
|
||||||
|
|
||||||
const allVarStrArr = (() => {
|
|
||||||
const arr = isChatModel ? (inputs.prompt_template as PromptItem[]).filter(item => item.edition_type !== EditionType.jinja2).map(item => item.text) : [(inputs.prompt_template as PromptItem).text]
|
|
||||||
if (isChatMode && isChatModel && !!inputs.memory) {
|
|
||||||
arr.push('{{#sys.query#}}')
|
|
||||||
arr.push(inputs.memory.query_prompt_template)
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr
|
|
||||||
})()
|
|
||||||
|
|
||||||
const varInputs = (() => {
|
|
||||||
const vars = getInputVars(allVarStrArr)
|
|
||||||
if (isShowVars)
|
|
||||||
return [...vars, ...toVarInputs(inputs.prompt_config?.jinja2_variables || [])]
|
|
||||||
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readOnly,
|
readOnly,
|
||||||
isChatMode,
|
isChatMode,
|
||||||
|
|
@ -423,24 +351,11 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||||
handleSyeQueryChange,
|
handleSyeQueryChange,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
visionFiles,
|
|
||||||
setVisionFiles,
|
|
||||||
contexts,
|
|
||||||
setContexts,
|
|
||||||
varInputs,
|
|
||||||
runningStatus,
|
|
||||||
isModelSupportStructuredOutput,
|
isModelSupportStructuredOutput,
|
||||||
handleStructureOutputChange,
|
handleStructureOutputChange,
|
||||||
structuredOutputCollapsed,
|
structuredOutputCollapsed,
|
||||||
setStructuredOutputCollapsed,
|
setStructuredOutputCollapsed,
|
||||||
handleStructureOutputEnableChange,
|
handleStructureOutputEnableChange,
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
filterJinjia2InputVar,
|
filterJinjia2InputVar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
|
import type { InputVar, PromptItem, Var, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||||
|
import type { LLMNodeType } from './types'
|
||||||
|
import { EditionType } from '../../types'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import { useIsChatMode } from '../../hooks'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useConfigVision from '../../hooks/use-config-vision'
|
||||||
|
import { noop } from 'lodash-es'
|
||||||
|
import { findVariableWhenOnLLMVision } from '../utils'
|
||||||
|
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||||
|
|
||||||
|
const i18nPrefix = 'workflow.nodes.llm'
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: LLMNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
runInputDataRef,
|
||||||
|
getInputVars,
|
||||||
|
setRunInputData,
|
||||||
|
toVarInputs,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { inputs } = useNodeCrud<LLMNodeType>(id, payload)
|
||||||
|
const getVarInputs = getInputVars
|
||||||
|
const isChatMode = useIsChatMode()
|
||||||
|
|
||||||
|
const contexts = runInputData['#context#']
|
||||||
|
const setContexts = useCallback((newContexts: string[]) => {
|
||||||
|
setRunInputData?.({
|
||||||
|
...runInputDataRef.current,
|
||||||
|
'#context#': newContexts,
|
||||||
|
})
|
||||||
|
}, [runInputDataRef, setRunInputData])
|
||||||
|
|
||||||
|
const visionFiles = runInputData['#files#']
|
||||||
|
const setVisionFiles = useCallback((newFiles: any[]) => {
|
||||||
|
setRunInputData?.({
|
||||||
|
...runInputDataRef.current,
|
||||||
|
'#files#': newFiles,
|
||||||
|
})
|
||||||
|
}, [runInputDataRef, setRunInputData])
|
||||||
|
|
||||||
|
// model
|
||||||
|
const model = inputs.model
|
||||||
|
const modelMode = inputs.model?.mode
|
||||||
|
const isChatModel = modelMode === 'chat'
|
||||||
|
const {
|
||||||
|
isVisionModel,
|
||||||
|
} = useConfigVision(model, {
|
||||||
|
payload: inputs.vision,
|
||||||
|
onChange: noop,
|
||||||
|
})
|
||||||
|
|
||||||
|
const isShowVars = (() => {
|
||||||
|
if (isChatModel)
|
||||||
|
return (inputs.prompt_template as PromptItem[]).some(item => item.edition_type === EditionType.jinja2)
|
||||||
|
|
||||||
|
return (inputs.prompt_template as PromptItem).edition_type === EditionType.jinja2
|
||||||
|
})()
|
||||||
|
|
||||||
|
const filterMemoryPromptVar = useCallback((varPayload: Var) => {
|
||||||
|
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const {
|
||||||
|
availableVars,
|
||||||
|
} = useAvailableVarList(id, {
|
||||||
|
onlyLeafNodeVar: false,
|
||||||
|
filterVar: filterMemoryPromptVar,
|
||||||
|
})
|
||||||
|
|
||||||
|
const allVarStrArr = (() => {
|
||||||
|
const arr = isChatModel ? (inputs.prompt_template as PromptItem[]).filter(item => item.edition_type !== EditionType.jinja2).map(item => item.text) : [(inputs.prompt_template as PromptItem).text]
|
||||||
|
if (isChatMode && isChatModel && !!inputs.memory) {
|
||||||
|
arr.push('{{#sys.query#}}')
|
||||||
|
arr.push(inputs.memory.query_prompt_template)
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr
|
||||||
|
})()
|
||||||
|
const varInputs = (() => {
|
||||||
|
const vars = getVarInputs(allVarStrArr) || []
|
||||||
|
if (isShowVars)
|
||||||
|
return [...vars, ...(toVarInputs ? (toVarInputs(inputs.prompt_config?.jinja2_variables || [])) : [])]
|
||||||
|
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.filter(key => !['#context#', '#files#'].includes(key))
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
const newVars = {
|
||||||
|
...newPayload,
|
||||||
|
'#context#': runInputDataRef.current['#context#'],
|
||||||
|
'#files#': runInputDataRef.current['#files#'],
|
||||||
|
}
|
||||||
|
setRunInputData?.(newVars)
|
||||||
|
}, [runInputDataRef, setRunInputData])
|
||||||
|
|
||||||
|
const forms = (() => {
|
||||||
|
const forms: FormProps[] = []
|
||||||
|
|
||||||
|
if (varInputs.length > 0) {
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t(`${i18nPrefix}.singleRun.variable`)!,
|
||||||
|
inputs: varInputs,
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs.context?.variable_selector && inputs.context?.variable_selector.length > 0) {
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t(`${i18nPrefix}.context`)!,
|
||||||
|
inputs: [{
|
||||||
|
label: '',
|
||||||
|
variable: '#context#',
|
||||||
|
type: InputVarType.contexts,
|
||||||
|
required: false,
|
||||||
|
}],
|
||||||
|
values: { '#context#': contexts },
|
||||||
|
onChange: keyValue => setContexts(keyValue['#context#']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||||
|
const currentVariable = findVariableWhenOnLLMVision(payload.vision.configs.variable_selector, availableVars)
|
||||||
|
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t(`${i18nPrefix}.vision`)!,
|
||||||
|
inputs: [{
|
||||||
|
label: currentVariable?.variable as any,
|
||||||
|
variable: '#files#',
|
||||||
|
type: currentVariable?.formType as any,
|
||||||
|
required: false,
|
||||||
|
}],
|
||||||
|
values: { '#files#': visionFiles },
|
||||||
|
onChange: keyValue => setVisionFiles((keyValue as any)['#files#']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return forms
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||||
|
const contextVar = payload.context.variable_selector
|
||||||
|
const vars = [...promptVars, contextVar]
|
||||||
|
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||||
|
const visionVar = payload.vision.configs.variable_selector
|
||||||
|
vars.push(visionVar)
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
if(variable === '#context#')
|
||||||
|
return payload.context.variable_selector
|
||||||
|
|
||||||
|
if(variable === '#files#')
|
||||||
|
return payload.vision.configs?.variable_selector
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useMemo } from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RiAddLine } from '@remixicon/react'
|
import { RiAddLine } from '@remixicon/react'
|
||||||
import Split from '../_base/components/split'
|
import Split from '../_base/components/split'
|
||||||
import ResultPanel from '../../run/result-panel'
|
|
||||||
import InputNumberWithSlider from '../_base/components/input-number-with-slider'
|
import InputNumberWithSlider from '../_base/components/input-number-with-slider'
|
||||||
import type { LoopNodeType } from './types'
|
import type { LoopNodeType } from './types'
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
|
|
@ -11,10 +10,7 @@ import ConditionWrap from './components/condition-wrap'
|
||||||
import LoopVariable from './components/loop-variables'
|
import LoopVariable from './components/loop-variables'
|
||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
|
||||||
|
|
||||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
|
||||||
import { LOOP_NODE_MAX_COUNT } from '@/config'
|
import { LOOP_NODE_MAX_COUNT } from '@/config'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.loop'
|
const i18nPrefix = 'workflow.nodes.loop'
|
||||||
|
|
@ -30,13 +26,6 @@ const Panel: FC<NodePanelProps<LoopNodeType>> = ({
|
||||||
inputs,
|
inputs,
|
||||||
childrenNodeVars,
|
childrenNodeVars,
|
||||||
loopChildrenNodes,
|
loopChildrenNodes,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
loopRunResult,
|
|
||||||
handleAddCondition,
|
handleAddCondition,
|
||||||
handleUpdateCondition,
|
handleUpdateCondition,
|
||||||
handleRemoveCondition,
|
handleRemoveCondition,
|
||||||
|
|
@ -51,23 +40,6 @@ const Panel: FC<NodePanelProps<LoopNodeType>> = ({
|
||||||
handleUpdateLoopVariable,
|
handleUpdateLoopVariable,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
const nodeInfo = useMemo(() => {
|
|
||||||
const formattedNodeInfo = formatTracing(loopRunResult, t)[0]
|
|
||||||
|
|
||||||
if (runResult && formattedNodeInfo) {
|
|
||||||
return {
|
|
||||||
...formattedNodeInfo,
|
|
||||||
execution_metadata: {
|
|
||||||
...runResult.execution_metadata,
|
|
||||||
...formattedNodeInfo.execution_metadata,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedNodeInfo
|
|
||||||
}, [runResult, loopRunResult, t])
|
|
||||||
const logsParams = useLogs()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-2'>
|
<div className='mt-2'>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -139,20 +111,6 @@ const Panel: FC<NodePanelProps<LoopNodeType>> = ({
|
||||||
</Select>
|
</Select>
|
||||||
</Field>
|
</Field>
|
||||||
</div> */}
|
</div> */}
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={[]}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
{...logsParams}
|
|
||||||
result={
|
|
||||||
<ResultPanel {...runResult} showSteps={false} nodeInfo={nodeInfo} {...logsParams} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useBoolean } from 'ahooks'
|
|
||||||
import { v4 as uuid4 } from 'uuid'
|
import { v4 as uuid4 } from 'uuid'
|
||||||
import {
|
import {
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
|
|
@ -12,10 +11,9 @@ import {
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
} from '../../hooks'
|
} from '../../hooks'
|
||||||
import { ValueType, VarType } from '../../types'
|
import { ValueType, VarType } from '../../types'
|
||||||
import type { ErrorHandleMode, ValueSelector, Var } from '../../types'
|
import type { ErrorHandleMode, Var } from '../../types'
|
||||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar, toNodeOutputVars } from '../_base/components/variable/utils'
|
import { toNodeOutputVars } from '../_base/components/variable/utils'
|
||||||
import useOneStepRun from '../_base/hooks/use-one-step-run'
|
|
||||||
import { getOperators } from './utils'
|
import { getOperators } from './utils'
|
||||||
import { LogicalOperator } from './types'
|
import { LogicalOperator } from './types'
|
||||||
import type { HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, LoopNodeType } from './types'
|
import type { HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, LoopNodeType } from './types'
|
||||||
|
|
@ -47,140 +45,12 @@ const useConfig = (id: string, payload: LoopNodeType) => {
|
||||||
const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes]
|
const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes]
|
||||||
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables)
|
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables)
|
||||||
|
|
||||||
// single run
|
|
||||||
const loopInputKey = `${id}.input_selector`
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
showSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
toVarInputs,
|
|
||||||
runningStatus,
|
|
||||||
handleRun: doHandleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
loopRunResult,
|
|
||||||
} = useOneStepRun<LoopNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
loopInputKey,
|
|
||||||
defaultRunInputData: {
|
|
||||||
[loopInputKey]: [''],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const [isShowLoopDetail, {
|
|
||||||
setTrue: doShowLoopDetail,
|
|
||||||
setFalse: doHideLoopDetail,
|
|
||||||
}] = useBoolean(false)
|
|
||||||
|
|
||||||
const hideLoopDetail = useCallback(() => {
|
|
||||||
hideSingleRun()
|
|
||||||
doHideLoopDetail()
|
|
||||||
}, [doHideLoopDetail, hideSingleRun])
|
|
||||||
|
|
||||||
const showLoopDetail = useCallback(() => {
|
|
||||||
doShowLoopDetail()
|
|
||||||
}, [doShowLoopDetail])
|
|
||||||
|
|
||||||
const backToSingleRun = useCallback(() => {
|
|
||||||
hideLoopDetail()
|
|
||||||
showSingleRun()
|
|
||||||
}, [hideLoopDetail, showSingleRun])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getIsVarFileAttribute,
|
getIsVarFileAttribute,
|
||||||
} = useIsVarFileAttribute({
|
} = useIsVarFileAttribute({
|
||||||
nodeId: id,
|
nodeId: id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { usedOutVars, allVarObject } = (() => {
|
|
||||||
const vars: ValueSelector[] = []
|
|
||||||
const varObjs: Record<string, boolean> = {}
|
|
||||||
const allVarObject: Record<string, {
|
|
||||||
inSingleRunPassedKey: string
|
|
||||||
}> = {}
|
|
||||||
loopChildrenNodes.forEach((node) => {
|
|
||||||
const nodeVars = getNodeUsedVars(node).filter(item => item && item.length > 0)
|
|
||||||
nodeVars.forEach((varSelector) => {
|
|
||||||
if (varSelector[0] === id) { // skip Loop node itself variable: item, index
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const isInLoop = isNodeInLoop(varSelector[0])
|
|
||||||
if (isInLoop) // not pass loop inner variable
|
|
||||||
return
|
|
||||||
|
|
||||||
const varSectorStr = varSelector.join('.')
|
|
||||||
if (!varObjs[varSectorStr]) {
|
|
||||||
varObjs[varSectorStr] = true
|
|
||||||
vars.push(varSelector)
|
|
||||||
}
|
|
||||||
let passToServerKeys = getNodeUsedVarPassToServerKey(node, varSelector)
|
|
||||||
if (typeof passToServerKeys === 'string')
|
|
||||||
passToServerKeys = [passToServerKeys]
|
|
||||||
|
|
||||||
passToServerKeys.forEach((key: string, index: number) => {
|
|
||||||
allVarObject[[varSectorStr, node.id, index].join(DELIMITER)] = {
|
|
||||||
inSingleRunPassedKey: key,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const res = toVarInputs(vars.map((item) => {
|
|
||||||
const varInfo = getNodeInfoById(canChooseVarNodes, item[0])
|
|
||||||
return {
|
|
||||||
label: {
|
|
||||||
nodeType: varInfo?.data.type,
|
|
||||||
nodeName: varInfo?.data.title || canChooseVarNodes[0]?.data.title, // default start node title
|
|
||||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
|
||||||
},
|
|
||||||
variable: `${item.join('.')}`,
|
|
||||||
value_selector: item,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
return {
|
|
||||||
usedOutVars: res,
|
|
||||||
allVarObject,
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
const handleRun = useCallback((data: Record<string, any>) => {
|
|
||||||
const formattedData: Record<string, any> = {}
|
|
||||||
Object.keys(allVarObject).forEach((key) => {
|
|
||||||
const [varSectorStr, nodeId] = key.split(DELIMITER)
|
|
||||||
formattedData[`${nodeId}.${allVarObject[key].inSingleRunPassedKey}`] = data[varSectorStr]
|
|
||||||
})
|
|
||||||
formattedData[loopInputKey] = data[loopInputKey]
|
|
||||||
doHandleRun(formattedData)
|
|
||||||
}, [allVarObject, doHandleRun, loopInputKey])
|
|
||||||
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.filter(key => ![loopInputKey].includes(key))
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
const newVars = {
|
|
||||||
...newPayload,
|
|
||||||
[loopInputKey]: runInputData[loopInputKey],
|
|
||||||
}
|
|
||||||
setRunInputData(newVars)
|
|
||||||
}, [loopInputKey, runInputData, setRunInputData])
|
|
||||||
|
|
||||||
const loop = runInputData[loopInputKey]
|
|
||||||
const setLoop = useCallback((newLoop: string[]) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputData,
|
|
||||||
[loopInputKey]: newLoop,
|
|
||||||
})
|
|
||||||
}, [loopInputKey, runInputData, setRunInputData])
|
|
||||||
|
|
||||||
const changeErrorResponseMode = useCallback((item: { value: unknown }) => {
|
const changeErrorResponseMode = useCallback((item: { value: unknown }) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.error_handle_mode = item.value as ErrorHandleMode
|
draft.error_handle_mode = item.value as ErrorHandleMode
|
||||||
|
|
@ -342,24 +212,6 @@ const useConfig = (id: string, payload: LoopNodeType) => {
|
||||||
filterInputVar,
|
filterInputVar,
|
||||||
childrenNodeVars,
|
childrenNodeVars,
|
||||||
loopChildrenNodes,
|
loopChildrenNodes,
|
||||||
isShowSingleRun,
|
|
||||||
showSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
isShowLoopDetail,
|
|
||||||
showLoopDetail,
|
|
||||||
hideLoopDetail,
|
|
||||||
backToSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
usedOutVars,
|
|
||||||
loop,
|
|
||||||
setLoop,
|
|
||||||
loopInputKey,
|
|
||||||
loopRunResult,
|
|
||||||
handleAddCondition,
|
handleAddCondition,
|
||||||
handleRemoveCondition,
|
handleRemoveCondition,
|
||||||
handleUpdateCondition,
|
handleUpdateCondition,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useIsNodeInLoop, useWorkflow } from '../../hooks'
|
||||||
|
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar } from '../_base/components/variable/utils'
|
||||||
|
import type { InputVar, ValueSelector, Variable } from '../../types'
|
||||||
|
import type { CaseItem, Condition, LoopNodeType } from './types'
|
||||||
|
import { ValueType } from '@/app/components/workflow/types'
|
||||||
|
import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string
|
||||||
|
payload: LoopNodeType
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runResult: NodeTracing
|
||||||
|
loopRunResult: NodeTracing[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
runResult,
|
||||||
|
loopRunResult,
|
||||||
|
setRunInputData,
|
||||||
|
toVarInputs,
|
||||||
|
varSelectorsToVarInputs,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { isNodeInLoop } = useIsNodeInLoop(id)
|
||||||
|
|
||||||
|
const { getLoopNodeChildren, getBeforeNodesInSameBranch } = useWorkflow()
|
||||||
|
const loopChildrenNodes = getLoopNodeChildren(id)
|
||||||
|
const beforeNodes = getBeforeNodesInSameBranch(id)
|
||||||
|
const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes]
|
||||||
|
|
||||||
|
const { usedOutVars, allVarObject } = (() => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
const varObjs: Record<string, boolean> = {}
|
||||||
|
const allVarObject: Record<string, {
|
||||||
|
inSingleRunPassedKey: string
|
||||||
|
}> = {}
|
||||||
|
loopChildrenNodes.forEach((node) => {
|
||||||
|
const nodeVars = getNodeUsedVars(node).filter(item => item && item.length > 0)
|
||||||
|
nodeVars.forEach((varSelector) => {
|
||||||
|
if (varSelector[0] === id) { // skip loop node itself variable: item, index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const isInLoop = isNodeInLoop(varSelector[0])
|
||||||
|
if (isInLoop) // not pass loop inner variable
|
||||||
|
return
|
||||||
|
|
||||||
|
const varSectorStr = varSelector.join('.')
|
||||||
|
if (!varObjs[varSectorStr]) {
|
||||||
|
varObjs[varSectorStr] = true
|
||||||
|
vars.push(varSelector)
|
||||||
|
}
|
||||||
|
let passToServerKeys = getNodeUsedVarPassToServerKey(node, varSelector)
|
||||||
|
if (typeof passToServerKeys === 'string')
|
||||||
|
passToServerKeys = [passToServerKeys]
|
||||||
|
|
||||||
|
passToServerKeys.forEach((key: string, index: number) => {
|
||||||
|
allVarObject[[varSectorStr, node.id, index].join(DELIMITER)] = {
|
||||||
|
inSingleRunPassedKey: key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = toVarInputs(vars.map((item) => {
|
||||||
|
const varInfo = getNodeInfoById(canChooseVarNodes, item[0])
|
||||||
|
return {
|
||||||
|
label: {
|
||||||
|
nodeType: varInfo?.data.type,
|
||||||
|
nodeName: varInfo?.data.title || canChooseVarNodes[0]?.data.title, // default start node title
|
||||||
|
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||||
|
},
|
||||||
|
variable: `${item.join('.')}`,
|
||||||
|
value_selector: item,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
usedOutVars: res,
|
||||||
|
allVarObject,
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
const nodeInfo = useMemo(() => {
|
||||||
|
const formattedNodeInfo = formatTracing(loopRunResult, t)[0]
|
||||||
|
|
||||||
|
if (runResult && formattedNodeInfo) {
|
||||||
|
return {
|
||||||
|
...formattedNodeInfo,
|
||||||
|
execution_metadata: {
|
||||||
|
...runResult.execution_metadata,
|
||||||
|
...formattedNodeInfo.execution_metadata,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedNodeInfo
|
||||||
|
}, [runResult, loopRunResult, t])
|
||||||
|
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
setRunInputData(newPayload)
|
||||||
|
}, [setRunInputData])
|
||||||
|
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getVarSelectorsFromCase = (caseItem: CaseItem): ValueSelector[] => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (caseItem.conditions && caseItem.conditions.length) {
|
||||||
|
caseItem.conditions.forEach((condition) => {
|
||||||
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
|
const conditionVars = getVarSelectorsFromCondition(condition)
|
||||||
|
vars.push(...conditionVars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVarSelectorsFromCondition = (condition: Condition) => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (condition.variable_selector)
|
||||||
|
vars.push(condition.variable_selector)
|
||||||
|
|
||||||
|
if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||||
|
vars.push(...getVarSelectorsFromCase(condition.sub_variable_condition))
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const forms = (() => {
|
||||||
|
const allInputs: ValueSelector[] = []
|
||||||
|
payload.break_conditions?.forEach((condition) => {
|
||||||
|
const vars = getVarSelectorsFromCondition(condition)
|
||||||
|
allInputs.push(...vars)
|
||||||
|
})
|
||||||
|
|
||||||
|
payload.loop_variables?.forEach((loopVariable) => {
|
||||||
|
if(loopVariable.value_type === ValueType.variable)
|
||||||
|
allInputs.push(loopVariable.value)
|
||||||
|
})
|
||||||
|
const inputVarsFromValue: InputVar[] = []
|
||||||
|
const varInputs = [...varSelectorsToVarInputs(allInputs), ...inputVarsFromValue]
|
||||||
|
|
||||||
|
const existVarsKey: Record<string, boolean> = {}
|
||||||
|
const uniqueVarInputs: InputVar[] = []
|
||||||
|
varInputs.forEach((input) => {
|
||||||
|
if(!input)
|
||||||
|
return
|
||||||
|
if (!existVarsKey[input.variable]) {
|
||||||
|
existVarsKey[input.variable] = true
|
||||||
|
uniqueVarInputs.push(input)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: [...usedOutVars, ...uniqueVarInputs],
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getVarFromCaseItem = (caseItem: CaseItem): ValueSelector[] => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (caseItem.conditions && caseItem.conditions.length) {
|
||||||
|
caseItem.conditions.forEach((condition) => {
|
||||||
|
// eslint-disable-next-line ts/no-use-before-define
|
||||||
|
const conditionVars = getVarFromCondition(condition)
|
||||||
|
vars.push(...conditionVars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVarFromCondition = (condition: Condition): ValueSelector[] => {
|
||||||
|
const vars: ValueSelector[] = []
|
||||||
|
if (condition.variable_selector)
|
||||||
|
vars.push(condition.variable_selector)
|
||||||
|
|
||||||
|
if(condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||||
|
vars.push(...getVarFromCaseItem(condition.sub_variable_condition))
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
const vars: ValueSelector[] = usedOutVars.map(item => item.variable.split('.'))
|
||||||
|
payload.break_conditions?.forEach((condition) => {
|
||||||
|
const conditionVars = getVarFromCondition(condition)
|
||||||
|
vars.push(...conditionVars)
|
||||||
|
})
|
||||||
|
payload.loop_variables?.forEach((loopVariable) => {
|
||||||
|
if(loopVariable.value_type === ValueType.variable)
|
||||||
|
vars.push(loopVariable.value)
|
||||||
|
})
|
||||||
|
const hasFilterLoopVars = vars.filter(item => item[0] !== id)
|
||||||
|
return hasFilterLoopVars
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
nodeInfo,
|
||||||
|
allVarObject,
|
||||||
|
getDependentVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -4,9 +4,7 @@ import { useTranslation } from 'react-i18next'
|
||||||
import MemoryConfig from '../_base/components/memory-config'
|
import MemoryConfig from '../_base/components/memory-config'
|
||||||
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
||||||
import Editor from '../_base/components/prompt/editor'
|
import Editor from '../_base/components/prompt/editor'
|
||||||
import ResultPanel from '../../run/result-panel'
|
|
||||||
import ConfigVision from '../_base/components/config-vision'
|
import ConfigVision from '../_base/components/config-vision'
|
||||||
import { findVariableWhenOnLLMVision } from '../utils'
|
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import type { ParameterExtractorNodeType } from './types'
|
import type { ParameterExtractorNodeType } from './types'
|
||||||
import ExtractParameter from './components/extract-parameter/list'
|
import ExtractParameter from './components/extract-parameter/list'
|
||||||
|
|
@ -17,12 +15,10 @@ import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import { VarType } from '@/app/components/workflow/types'
|
import { VarType } from '@/app/components/workflow/types'
|
||||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.parameterExtractor'
|
const i18nPrefix = 'workflow.nodes.parameterExtractor'
|
||||||
const i18nCommonPrefix = 'workflow.common'
|
const i18nCommonPrefix = 'workflow.common'
|
||||||
|
|
@ -53,63 +49,13 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
||||||
handleReasoningModeChange,
|
handleReasoningModeChange,
|
||||||
availableVars,
|
availableVars,
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
availableVisionVars,
|
|
||||||
inputVarValues,
|
|
||||||
varInputs,
|
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
setInputVarValues,
|
|
||||||
visionFiles,
|
|
||||||
setVisionFiles,
|
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
const model = inputs.model
|
const model = inputs.model
|
||||||
|
|
||||||
const singleRunForms = (() => {
|
|
||||||
const forms: FormProps[] = []
|
|
||||||
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t('workflow.nodes.llm.singleRun.variable')!,
|
|
||||||
inputs: [{
|
|
||||||
label: t(`${i18nPrefix}.inputVar`)!,
|
|
||||||
variable: 'query',
|
|
||||||
type: InputVarType.paragraph,
|
|
||||||
required: true,
|
|
||||||
}, ...varInputs],
|
|
||||||
values: inputVarValues,
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isVisionModel && data.vision?.enabled && data.vision?.configs?.variable_selector) {
|
|
||||||
const currentVariable = findVariableWhenOnLLMVision(data.vision.configs.variable_selector, availableVisionVars)
|
|
||||||
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t('workflow.nodes.llm.vision')!,
|
|
||||||
inputs: [{
|
|
||||||
label: currentVariable?.variable as any,
|
|
||||||
variable: '#files#',
|
|
||||||
type: currentVariable?.formType as any,
|
|
||||||
required: false,
|
|
||||||
}],
|
|
||||||
values: { '#files#': visionFiles },
|
|
||||||
onChange: keyValue => setVisionFiles((keyValue as any)['#files#']),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return forms
|
|
||||||
})()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='pt-2'>
|
<div className='pt-2'>
|
||||||
<div className='space-y-4 px-4'>
|
<div className='space-y-4 px-4'>
|
||||||
|
|
@ -255,17 +201,6 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
</div>
|
||||||
</>)}
|
</>)}
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={singleRunForms}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
} from '../../hooks'
|
} from '../../hooks'
|
||||||
import useOneStepRun from '../_base/hooks/use-one-step-run'
|
|
||||||
import useConfigVision from '../../hooks/use-config-vision'
|
import useConfigVision from '../../hooks/use-config-vision'
|
||||||
import type { Param, ParameterExtractorNodeType, ReasoningModeType } from './types'
|
import type { Param, ParameterExtractorNodeType, ReasoningModeType } from './types'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel, useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel, useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
|
|
@ -17,8 +16,13 @@ import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-cr
|
||||||
import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
||||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||||
import { supportFunctionCall } from '@/utils/tool-call'
|
import { supportFunctionCall } from '@/utils/tool-call'
|
||||||
|
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||||
|
|
||||||
const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||||
|
const {
|
||||||
|
deleteNodeInspectorVars,
|
||||||
|
renameInspectVarName,
|
||||||
|
} = useInspectVarsCrud()
|
||||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
const { handleOutVarRenameChange } = useWorkflow()
|
const { handleOutVarRenameChange } = useWorkflow()
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
|
|
@ -59,9 +63,14 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
|
|
||||||
if (moreInfo && moreInfo?.type === ChangeType.changeVarName && moreInfo.payload)
|
if (moreInfo && moreInfo?.type === ChangeType.changeVarName && moreInfo.payload) {
|
||||||
handleOutVarRenameChange(id, [id, moreInfo.payload.beforeKey], [id, moreInfo.payload.afterKey!])
|
handleOutVarRenameChange(id, [id, moreInfo.payload.beforeKey], [id, moreInfo.payload.afterKey!])
|
||||||
}, [handleOutVarRenameChange, id, inputs, setInputs])
|
renameInspectVarName(id, moreInfo.payload.beforeKey, moreInfo.payload.afterKey!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
deleteNodeInspectorVars(id)
|
||||||
|
}
|
||||||
|
}, [deleteNodeInspectorVars, handleOutVarRenameChange, id, inputs, renameInspectVarName, setInputs])
|
||||||
|
|
||||||
const addExtractParameter = useCallback((payload: Param) => {
|
const addExtractParameter = useCallback((payload: Param) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
|
@ -70,7 +79,8 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||||
draft.parameters.push(payload)
|
draft.parameters.push(payload)
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
deleteNodeInspectorVars(id)
|
||||||
|
}, [deleteNodeInspectorVars, id, inputs, setInputs])
|
||||||
|
|
||||||
// model
|
// model
|
||||||
const model = inputs.model || {
|
const model = inputs.model || {
|
||||||
|
|
@ -163,10 +173,6 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||||
return [VarType.number, VarType.string].includes(varPayload.type)
|
return [VarType.number, VarType.string].includes(varPayload.type)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const filterVisionInputVar = useCallback((varPayload: Var) => {
|
|
||||||
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
availableVars,
|
availableVars,
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
|
|
@ -175,13 +181,6 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||||
filterVar: filterInputVar,
|
filterVar: filterInputVar,
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
|
||||||
availableVars: availableVisionVars,
|
|
||||||
} = useAvailableVarList(id, {
|
|
||||||
onlyLeafNodeVar: false,
|
|
||||||
filterVar: filterVisionInputVar,
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleCompletionParamsChange = useCallback((newParams: Record<string, any>) => {
|
const handleCompletionParamsChange = useCallback((newParams: Record<string, any>) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.model.completion_params = newParams
|
draft.model.completion_params = newParams
|
||||||
|
|
@ -223,49 +222,6 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
}, [inputs, setInputs])
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
getInputVars,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
runInputDataRef,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<ParameterExtractorNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {
|
|
||||||
'query': '',
|
|
||||||
'#files#': [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const varInputs = getInputVars([inputs.instruction])
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
setRunInputData(newPayload)
|
|
||||||
}, [setRunInputData])
|
|
||||||
|
|
||||||
const visionFiles = runInputData['#files#']
|
|
||||||
const setVisionFiles = useCallback((newFiles: any[]) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputDataRef.current,
|
|
||||||
'#files#': newFiles,
|
|
||||||
})
|
|
||||||
}, [runInputDataRef, setRunInputData])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readOnly,
|
readOnly,
|
||||||
handleInputVarChange,
|
handleInputVarChange,
|
||||||
|
|
@ -283,24 +239,12 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||||
hasSetBlockStatus,
|
hasSetBlockStatus,
|
||||||
availableVars,
|
availableVars,
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
availableVisionVars,
|
|
||||||
isSupportFunctionCall,
|
isSupportFunctionCall,
|
||||||
handleReasoningModeChange,
|
handleReasoningModeChange,
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
setInputVarValues,
|
|
||||||
visionFiles,
|
|
||||||
setVisionFiles,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
|
import type { InputVar, Var, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||||
|
import type { ParameterExtractorNodeType } from './types'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useConfigVision from '../../hooks/use-config-vision'
|
||||||
|
import { noop } from 'lodash-es'
|
||||||
|
import { findVariableWhenOnLLMVision } from '../utils'
|
||||||
|
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||||
|
|
||||||
|
const i18nPrefix = 'workflow.nodes.parameterExtractor'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: ParameterExtractorNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
runInputDataRef,
|
||||||
|
getInputVars,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { inputs } = useNodeCrud<ParameterExtractorNodeType>(id, payload)
|
||||||
|
|
||||||
|
const model = inputs.model
|
||||||
|
|
||||||
|
const {
|
||||||
|
isVisionModel,
|
||||||
|
} = useConfigVision(model, {
|
||||||
|
payload: inputs.vision,
|
||||||
|
onChange: noop,
|
||||||
|
})
|
||||||
|
|
||||||
|
const visionFiles = runInputData['#files#']
|
||||||
|
const setVisionFiles = useCallback((newFiles: any[]) => {
|
||||||
|
setRunInputData?.({
|
||||||
|
...runInputDataRef.current,
|
||||||
|
'#files#': newFiles,
|
||||||
|
})
|
||||||
|
}, [runInputDataRef, setRunInputData])
|
||||||
|
|
||||||
|
const varInputs = getInputVars([inputs.instruction])
|
||||||
|
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.filter(key => !['#context#', '#files#'].includes(key))
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
const newVars = {
|
||||||
|
...newPayload,
|
||||||
|
'#context#': runInputDataRef.current['#context#'],
|
||||||
|
'#files#': runInputDataRef.current['#files#'],
|
||||||
|
}
|
||||||
|
setRunInputData?.(newVars)
|
||||||
|
}, [runInputDataRef, setRunInputData])
|
||||||
|
|
||||||
|
const filterVisionInputVar = useCallback((varPayload: Var) => {
|
||||||
|
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||||
|
}, [])
|
||||||
|
const {
|
||||||
|
availableVars: availableVisionVars,
|
||||||
|
} = useAvailableVarList(id, {
|
||||||
|
onlyLeafNodeVar: false,
|
||||||
|
filterVar: filterVisionInputVar,
|
||||||
|
})
|
||||||
|
|
||||||
|
const forms = (() => {
|
||||||
|
const forms: FormProps[] = []
|
||||||
|
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||||
|
inputs: [{
|
||||||
|
label: t(`${i18nPrefix}.inputVar`)!,
|
||||||
|
variable: 'query',
|
||||||
|
type: InputVarType.paragraph,
|
||||||
|
required: true,
|
||||||
|
}, ...varInputs],
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||||
|
const currentVariable = findVariableWhenOnLLMVision(payload.vision.configs.variable_selector, availableVisionVars)
|
||||||
|
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t('workflow.nodes.llm.vision')!,
|
||||||
|
inputs: [{
|
||||||
|
label: currentVariable?.variable as any,
|
||||||
|
variable: '#files#',
|
||||||
|
type: currentVariable?.formType as any,
|
||||||
|
required: false,
|
||||||
|
}],
|
||||||
|
values: { '#files#': visionFiles },
|
||||||
|
onChange: keyValue => setVisionFiles((keyValue as any)['#files#']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return forms
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||||
|
const vars = [payload.query, ...promptVars]
|
||||||
|
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||||
|
const visionVar = payload.vision.configs.variable_selector
|
||||||
|
vars.push(visionVar)
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
if(variable === 'query')
|
||||||
|
return payload.query
|
||||||
|
if(variable === '#files#')
|
||||||
|
return payload.vision.configs?.variable_selector
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -3,20 +3,16 @@ import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
||||||
import ConfigVision from '../_base/components/config-vision'
|
import ConfigVision from '../_base/components/config-vision'
|
||||||
import { findVariableWhenOnLLMVision } from '../utils'
|
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import ClassList from './components/class-list'
|
import ClassList from './components/class-list'
|
||||||
import AdvancedSetting from './components/advanced-setting'
|
import AdvancedSetting from './components/advanced-setting'
|
||||||
import type { QuestionClassifierNodeType } from './types'
|
import type { QuestionClassifierNodeType } from './types'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||||
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||||
|
|
||||||
|
|
@ -38,66 +34,16 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||||
hasSetBlockStatus,
|
hasSetBlockStatus,
|
||||||
availableVars,
|
availableVars,
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
availableVisionVars,
|
|
||||||
handleInstructionChange,
|
handleInstructionChange,
|
||||||
inputVarValues,
|
|
||||||
varInputs,
|
|
||||||
setInputVarValues,
|
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
filterVar,
|
filterVar,
|
||||||
visionFiles,
|
|
||||||
setVisionFiles,
|
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
const model = inputs.model
|
const model = inputs.model
|
||||||
|
|
||||||
const singleRunForms = (() => {
|
|
||||||
const forms: FormProps[] = []
|
|
||||||
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t('workflow.nodes.llm.singleRun.variable')!,
|
|
||||||
inputs: [{
|
|
||||||
label: t(`${i18nPrefix}.inputVars`)!,
|
|
||||||
variable: 'query',
|
|
||||||
type: InputVarType.paragraph,
|
|
||||||
required: true,
|
|
||||||
}, ...varInputs],
|
|
||||||
values: inputVarValues,
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isVisionModel && data.vision?.enabled && data.vision?.configs?.variable_selector) {
|
|
||||||
const currentVariable = findVariableWhenOnLLMVision(data.vision.configs.variable_selector, availableVisionVars)
|
|
||||||
|
|
||||||
forms.push(
|
|
||||||
{
|
|
||||||
label: t('workflow.nodes.llm.vision')!,
|
|
||||||
inputs: [{
|
|
||||||
label: currentVariable?.variable as any,
|
|
||||||
variable: '#files#',
|
|
||||||
type: currentVariable?.formType as any,
|
|
||||||
required: false,
|
|
||||||
}],
|
|
||||||
values: { '#files#': visionFiles },
|
|
||||||
onChange: keyValue => setVisionFiles(keyValue['#files#']),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return forms
|
|
||||||
})()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='pt-2'>
|
<div className='pt-2'>
|
||||||
<div className='space-y-4 px-4'>
|
<div className='space-y-4 px-4'>
|
||||||
|
|
@ -186,17 +132,6 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
</div>
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={singleRunForms}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||||
import useConfigVision from '../../hooks/use-config-vision'
|
import useConfigVision from '../../hooks/use-config-vision'
|
||||||
import type { QuestionClassifierNodeType } from './types'
|
import type { QuestionClassifierNodeType } from './types'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
||||||
|
|
@ -163,59 +162,6 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => {
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
}, [inputs, setInputs])
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
getInputVars,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
runInputDataRef,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<QuestionClassifierNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {
|
|
||||||
'query': '',
|
|
||||||
'#files#': [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const query = runInputData.query
|
|
||||||
const setQuery = useCallback((newQuery: string) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputData,
|
|
||||||
query: newQuery,
|
|
||||||
})
|
|
||||||
}, [runInputData, setRunInputData])
|
|
||||||
|
|
||||||
const varInputs = getInputVars([inputs.instruction])
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {
|
|
||||||
query,
|
|
||||||
}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
setRunInputData(newPayload)
|
|
||||||
}, [setRunInputData])
|
|
||||||
|
|
||||||
const visionFiles = runInputData['#files#']
|
|
||||||
const setVisionFiles = useCallback((newFiles: any[]) => {
|
|
||||||
setRunInputData({
|
|
||||||
...runInputDataRef.current,
|
|
||||||
'#files#': newFiles,
|
|
||||||
})
|
|
||||||
}, [runInputDataRef, setRunInputData])
|
|
||||||
|
|
||||||
const filterVar = useCallback((varPayload: Var) => {
|
const filterVar = useCallback((varPayload: Var) => {
|
||||||
return varPayload.type === VarType.string
|
return varPayload.type === VarType.string
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -235,23 +181,10 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => {
|
||||||
availableNodesWithParent,
|
availableNodesWithParent,
|
||||||
availableVisionVars,
|
availableVisionVars,
|
||||||
handleInstructionChange,
|
handleInstructionChange,
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
handleMemoryChange,
|
handleMemoryChange,
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
query,
|
|
||||||
setQuery,
|
|
||||||
runResult,
|
|
||||||
visionFiles,
|
|
||||||
setVisionFiles,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
|
import type { InputVar, Var, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||||
|
import type { QuestionClassifierNodeType } from './types'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useConfigVision from '../../hooks/use-config-vision'
|
||||||
|
import { noop } from 'lodash-es'
|
||||||
|
import { findVariableWhenOnLLMVision } from '../utils'
|
||||||
|
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||||
|
|
||||||
|
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: QuestionClassifierNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
runInputDataRef,
|
||||||
|
getInputVars,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { inputs } = useNodeCrud<QuestionClassifierNodeType>(id, payload)
|
||||||
|
|
||||||
|
const model = inputs.model
|
||||||
|
|
||||||
|
const {
|
||||||
|
isVisionModel,
|
||||||
|
} = useConfigVision(model, {
|
||||||
|
payload: inputs.vision,
|
||||||
|
onChange: noop,
|
||||||
|
})
|
||||||
|
|
||||||
|
const visionFiles = runInputData['#files#']
|
||||||
|
const setVisionFiles = useCallback((newFiles: any[]) => {
|
||||||
|
setRunInputData?.({
|
||||||
|
...runInputDataRef.current,
|
||||||
|
'#files#': newFiles,
|
||||||
|
})
|
||||||
|
}, [runInputDataRef, setRunInputData])
|
||||||
|
|
||||||
|
const varInputs = getInputVars([inputs.instruction])
|
||||||
|
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.filter(key => !['#files#'].includes(key))
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
const newVars = {
|
||||||
|
...newPayload,
|
||||||
|
'#files#': runInputDataRef.current['#files#'],
|
||||||
|
}
|
||||||
|
setRunInputData?.(newVars)
|
||||||
|
}, [runInputDataRef, setRunInputData])
|
||||||
|
|
||||||
|
const filterVisionInputVar = useCallback((varPayload: Var) => {
|
||||||
|
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||||
|
}, [])
|
||||||
|
const {
|
||||||
|
availableVars: availableVisionVars,
|
||||||
|
} = useAvailableVarList(id, {
|
||||||
|
onlyLeafNodeVar: false,
|
||||||
|
filterVar: filterVisionInputVar,
|
||||||
|
})
|
||||||
|
|
||||||
|
const forms = (() => {
|
||||||
|
const forms: FormProps[] = []
|
||||||
|
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||||
|
inputs: [{
|
||||||
|
label: t(`${i18nPrefix}.inputVars`)!,
|
||||||
|
variable: 'query',
|
||||||
|
type: InputVarType.paragraph,
|
||||||
|
required: true,
|
||||||
|
}, ...varInputs],
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||||
|
const currentVariable = findVariableWhenOnLLMVision(payload.vision.configs.variable_selector, availableVisionVars)
|
||||||
|
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t('workflow.nodes.llm.vision')!,
|
||||||
|
inputs: [{
|
||||||
|
label: currentVariable?.variable as any,
|
||||||
|
variable: '#files#',
|
||||||
|
type: currentVariable?.formType as any,
|
||||||
|
required: false,
|
||||||
|
}],
|
||||||
|
values: { '#files#': visionFiles },
|
||||||
|
onChange: keyValue => setVisionFiles(keyValue['#files#']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return forms
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||||
|
const vars = [payload.query_variable_selector, ...promptVars]
|
||||||
|
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||||
|
const visionVar = payload.vision.configs.variable_selector
|
||||||
|
vars.push(visionVar)
|
||||||
|
}
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
if(variable === 'query')
|
||||||
|
return payload.query_variable_selector
|
||||||
|
if(variable === '#files#')
|
||||||
|
return payload.vision.configs?.variable_selector
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||||
|
|
||||||
const useConfig = (id: string, payload: StartNodeType) => {
|
const useConfig = (id: string, payload: StartNodeType) => {
|
||||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
|
|
@ -18,6 +19,13 @@ const useConfig = (id: string, payload: StartNodeType) => {
|
||||||
|
|
||||||
const { inputs, setInputs } = useNodeCrud<StartNodeType>(id, payload)
|
const { inputs, setInputs } = useNodeCrud<StartNodeType>(id, payload)
|
||||||
|
|
||||||
|
const {
|
||||||
|
deleteNodeInspectorVars,
|
||||||
|
renameInspectVarName,
|
||||||
|
nodesWithInspectVars,
|
||||||
|
deleteInspectVar,
|
||||||
|
} = useInspectVarsCrud()
|
||||||
|
|
||||||
const [isShowAddVarModal, {
|
const [isShowAddVarModal, {
|
||||||
setTrue: showAddVarModal,
|
setTrue: showAddVarModal,
|
||||||
setFalse: hideAddVarModal,
|
setFalse: hideAddVarModal,
|
||||||
|
|
@ -31,6 +39,12 @@ const useConfig = (id: string, payload: StartNodeType) => {
|
||||||
const [removedIndex, setRemoveIndex] = useState(0)
|
const [removedIndex, setRemoveIndex] = useState(0)
|
||||||
const handleVarListChange = useCallback((newList: InputVar[], moreInfo?: { index: number; payload: MoreInfo }) => {
|
const handleVarListChange = useCallback((newList: InputVar[], moreInfo?: { index: number; payload: MoreInfo }) => {
|
||||||
if (moreInfo?.payload?.type === ChangeType.remove) {
|
if (moreInfo?.payload?.type === ChangeType.remove) {
|
||||||
|
const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => {
|
||||||
|
return varItem.name === moreInfo?.payload?.payload?.beforeKey
|
||||||
|
})?.id
|
||||||
|
if(varId)
|
||||||
|
deleteInspectVar(id, varId)
|
||||||
|
|
||||||
if (isVarUsedInNodes([id, moreInfo?.payload?.payload?.beforeKey || ''])) {
|
if (isVarUsedInNodes([id, moreInfo?.payload?.payload?.beforeKey || ''])) {
|
||||||
showRemoveVarConfirm()
|
showRemoveVarConfirm()
|
||||||
setRemovedVar([id, moreInfo?.payload?.payload?.beforeKey || ''])
|
setRemovedVar([id, moreInfo?.payload?.payload?.beforeKey || ''])
|
||||||
|
|
@ -46,8 +60,12 @@ const useConfig = (id: string, payload: StartNodeType) => {
|
||||||
if (moreInfo?.payload?.type === ChangeType.changeVarName) {
|
if (moreInfo?.payload?.type === ChangeType.changeVarName) {
|
||||||
const changedVar = newList[moreInfo.index]
|
const changedVar = newList[moreInfo.index]
|
||||||
handleOutVarRenameChange(id, [id, inputs.variables[moreInfo.index].variable], [id, changedVar.variable])
|
handleOutVarRenameChange(id, [id, inputs.variables[moreInfo.index].variable], [id, changedVar.variable])
|
||||||
|
renameInspectVarName(id, inputs.variables[moreInfo.index].variable, changedVar.variable)
|
||||||
}
|
}
|
||||||
}, [handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm])
|
else if(moreInfo?.payload?.type !== ChangeType.remove) { // edit var type
|
||||||
|
deleteNodeInspectorVars(id)
|
||||||
|
}
|
||||||
|
}, [deleteInspectVar, deleteNodeInspectorVars, handleOutVarRenameChange, id, inputs, isVarUsedInNodes, nodesWithInspectVars, renameInspectVarName, setInputs, showRemoveVarConfirm])
|
||||||
|
|
||||||
const removeVarInNode = useCallback(() => {
|
const removeVarInNode = useCallback(() => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
|
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||||
|
import { type InputVar, InputVarType, type Variable } from '@/app/components/workflow/types'
|
||||||
|
import type { StartNodeType } from './types'
|
||||||
|
import { useIsChatMode } from '../../hooks'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: StartNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const isChatMode = useIsChatMode()
|
||||||
|
|
||||||
|
const forms = (() => {
|
||||||
|
const forms: FormProps[] = []
|
||||||
|
const inputs: InputVar[] = payload.variables.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
getVarValueFromDependent: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isChatMode) {
|
||||||
|
inputs.push({
|
||||||
|
label: 'sys.query',
|
||||||
|
variable: '#sys.query#',
|
||||||
|
type: InputVarType.textInput,
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.push({
|
||||||
|
label: 'sys.files',
|
||||||
|
variable: '#sys.files#',
|
||||||
|
type: InputVarType.multiFiles,
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
forms.push(
|
||||||
|
{
|
||||||
|
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||||
|
inputs,
|
||||||
|
values: runInputData,
|
||||||
|
onChange: setRunInputData,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return forms
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
const inputVars = payload.variables.map((item) => {
|
||||||
|
return [id, item.variable]
|
||||||
|
})
|
||||||
|
const vars: ValueSelector[] = [...inputVars, ['sys', 'files']]
|
||||||
|
|
||||||
|
if (isChatMode)
|
||||||
|
vars.push(['sys', 'query'])
|
||||||
|
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
return [id, variable]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -14,8 +14,6 @@ import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars'
|
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.templateTransform'
|
const i18nPrefix = 'workflow.nodes.templateTransform'
|
||||||
|
|
||||||
|
|
@ -35,16 +33,6 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
|
||||||
handleAddEmptyVariable,
|
handleAddEmptyVariable,
|
||||||
handleCodeChange,
|
handleCodeChange,
|
||||||
filterVar,
|
filterVar,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
runResult,
|
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -106,23 +94,6 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
</div>
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={[
|
|
||||||
{
|
|
||||||
inputs: varInputs,
|
|
||||||
values: inputVarValues,
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { VarType } from '../../types'
|
||||||
import { useStore } from '../../store'
|
import { useStore } from '../../store'
|
||||||
import type { TemplateTransformNodeType } from './types'
|
import type { TemplateTransformNodeType } from './types'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import {
|
import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
|
|
@ -76,37 +75,6 @@ const useConfig = (id: string, payload: TemplateTransformNodeType) => {
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [setInputs])
|
}, [setInputs])
|
||||||
|
|
||||||
// single run
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
toVarInputs,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runInputData,
|
|
||||||
setRunInputData,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<TemplateTransformNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {},
|
|
||||||
})
|
|
||||||
const varInputs = toVarInputs(inputs.variables)
|
|
||||||
|
|
||||||
const inputVarValues = (() => {
|
|
||||||
const vars: Record<string, any> = {}
|
|
||||||
Object.keys(runInputData)
|
|
||||||
.forEach((key) => {
|
|
||||||
vars[key] = runInputData[key]
|
|
||||||
})
|
|
||||||
return vars
|
|
||||||
})()
|
|
||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
|
||||||
setRunInputData(newPayload)
|
|
||||||
}, [setRunInputData])
|
|
||||||
|
|
||||||
const filterVar = useCallback((varPayload: Var) => {
|
const filterVar = useCallback((varPayload: Var) => {
|
||||||
return [VarType.string, VarType.number, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(varPayload.type)
|
return [VarType.string, VarType.number, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(varPayload.type)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -121,16 +89,6 @@ const useConfig = (id: string, payload: TemplateTransformNodeType) => {
|
||||||
handleAddEmptyVariable,
|
handleAddEmptyVariable,
|
||||||
handleCodeChange,
|
handleCodeChange,
|
||||||
filterVar,
|
filterVar,
|
||||||
// single run
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
varInputs,
|
|
||||||
inputVarValues,
|
|
||||||
setInputVarValues,
|
|
||||||
runResult,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import type { TemplateTransformNodeType } from './types'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: TemplateTransformNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
toVarInputs,
|
||||||
|
setRunInputData,
|
||||||
|
}: Params) => {
|
||||||
|
const { inputs } = useNodeCrud<TemplateTransformNodeType>(id, payload)
|
||||||
|
|
||||||
|
const varInputs = toVarInputs(inputs.variables)
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
setRunInputData(newPayload)
|
||||||
|
}, [setRunInputData])
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: varInputs,
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [inputVarValues, setInputVarValues, varInputs])
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return payload.variables.map(v => v.value_selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDependentVar = (variable: string) => {
|
||||||
|
const varItem = payload.variables.find(v => v.variable === variable)
|
||||||
|
if (varItem)
|
||||||
|
return varItem.value_selector
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
getDependentVar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useMemo } from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Split from '../_base/components/split'
|
import Split from '../_base/components/split'
|
||||||
import type { ToolNodeType } from './types'
|
import type { ToolNodeType } from './types'
|
||||||
|
|
@ -11,12 +11,7 @@ import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
||||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|
||||||
import { useToolIcon } from '@/app/components/workflow/hooks'
|
|
||||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
|
||||||
import formatToTracingNodeList from '@/app/components/workflow/run/utils/format-log'
|
|
||||||
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
|
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
|
||||||
import { Type } from '../llm/types'
|
import { Type } from '../llm/types'
|
||||||
|
|
||||||
|
|
@ -45,23 +40,9 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||||
hideSetAuthModal,
|
hideSetAuthModal,
|
||||||
handleSaveAuth,
|
handleSaveAuth,
|
||||||
isLoading,
|
isLoading,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
singleRunForms,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
outputSchema,
|
outputSchema,
|
||||||
hasObjectOutput,
|
hasObjectOutput,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
const toolIcon = useToolIcon(data)
|
|
||||||
const logsParams = useLogs()
|
|
||||||
const nodeInfo = useMemo(() => {
|
|
||||||
if (!runResult)
|
|
||||||
return null
|
|
||||||
return formatToTracingNodeList([runResult], t)[0]
|
|
||||||
}, [runResult, t])
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div className='flex h-[200px] items-center justify-center'>
|
return <div className='flex h-[200px] items-center justify-center'>
|
||||||
|
|
@ -180,21 +161,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isShowSingleRun && (
|
|
||||||
<BeforeRunForm
|
|
||||||
nodeName={inputs.title}
|
|
||||||
nodeType={inputs.type}
|
|
||||||
toolIcon={toolIcon}
|
|
||||||
onHide={hideSingleRun}
|
|
||||||
forms={singleRunForms}
|
|
||||||
runningStatus={runningStatus}
|
|
||||||
onRun={handleRun}
|
|
||||||
onStop={handleStop}
|
|
||||||
{...logsParams}
|
|
||||||
result={<ResultPanel {...runResult} showSteps={false} {...logsParams} nodeInfo={nodeInfo} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,15 @@ import { useTranslation } from 'react-i18next'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import { useStore } from '../../store'
|
import { useStore } from '../../store'
|
||||||
import { type ToolNodeType, type ToolVarInputs, VarType } from './types'
|
import type { ToolNodeType, ToolVarInputs } from './types'
|
||||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import { CollectionType } from '@/app/components/tools/types'
|
import { CollectionType } from '@/app/components/tools/types'
|
||||||
import { updateBuiltInToolCredential } from '@/service/tools'
|
import { updateBuiltInToolCredential } from '@/service/tools'
|
||||||
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
|
||||||
import { VarType as VarVarType } from '@/app/components/workflow/types'
|
import { VarType as VarVarType } from '@/app/components/workflow/types'
|
||||||
import type { InputVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
import type { InputVar, Var } from '@/app/components/workflow/types'
|
||||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
|
||||||
import {
|
import {
|
||||||
useFetchToolsData,
|
useFetchToolsData,
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
|
|
@ -160,39 +158,8 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
||||||
|
|
||||||
const isLoading = currTool && (isBuiltIn ? !currCollection : false)
|
const isLoading = currTool && (isBuiltIn ? !currCollection : false)
|
||||||
|
|
||||||
// single run
|
const getMoreDataForCheckValid = () => {
|
||||||
const [inputVarValues, doSetInputVarValues] = useState<Record<string, any>>({})
|
return {
|
||||||
const setInputVarValues = (value: Record<string, any>) => {
|
|
||||||
doSetInputVarValues(value)
|
|
||||||
// eslint-disable-next-line ts/no-use-before-define
|
|
||||||
setRunInputData(value)
|
|
||||||
}
|
|
||||||
// fill single run form variable with constant value first time
|
|
||||||
const inputVarValuesWithConstantValue = () => {
|
|
||||||
const res = produce(inputVarValues, (draft) => {
|
|
||||||
Object.keys(inputs.tool_parameters).forEach((key: string) => {
|
|
||||||
const { type, value } = inputs.tool_parameters[key]
|
|
||||||
if (type === VarType.constant && (value === undefined || value === null))
|
|
||||||
draft.tool_parameters[key].value = value
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
getInputVars,
|
|
||||||
runningStatus,
|
|
||||||
setRunInputData,
|
|
||||||
handleRun: doHandleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
} = useOneStepRun<ToolNodeType>({
|
|
||||||
id,
|
|
||||||
data: inputs,
|
|
||||||
defaultRunInputData: {},
|
|
||||||
moreDataForCheckValid: {
|
|
||||||
toolInputsSchema: (() => {
|
toolInputsSchema: (() => {
|
||||||
const formInputs: InputVar[] = []
|
const formInputs: InputVar[] = []
|
||||||
toolInputVarSchema.forEach((item: any) => {
|
toolInputVarSchema.forEach((item: any) => {
|
||||||
|
|
@ -208,52 +175,7 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
||||||
notAuthed: isShowAuthBtn,
|
notAuthed: isShowAuthBtn,
|
||||||
toolSettingSchema,
|
toolSettingSchema,
|
||||||
language,
|
language,
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const hadVarParams = Object.keys(inputs.tool_parameters)
|
|
||||||
.filter(key => inputs.tool_parameters[key].type !== VarType.constant)
|
|
||||||
.map(k => inputs.tool_parameters[k])
|
|
||||||
|
|
||||||
const varInputs = getInputVars(hadVarParams.map((p) => {
|
|
||||||
if (p.type === VarType.variable) {
|
|
||||||
// handle the old wrong value not crash the page
|
|
||||||
if (!(p.value as any).join)
|
|
||||||
return `{{#${p.value}#}}`
|
|
||||||
|
|
||||||
return `{{#${(p.value as ValueSelector).join('.')}#}}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.value as string
|
|
||||||
}))
|
|
||||||
|
|
||||||
const singleRunForms = (() => {
|
|
||||||
const forms: FormProps[] = [{
|
|
||||||
inputs: varInputs,
|
|
||||||
values: inputVarValuesWithConstantValue(),
|
|
||||||
onChange: setInputVarValues,
|
|
||||||
}]
|
|
||||||
return forms
|
|
||||||
})()
|
|
||||||
|
|
||||||
const handleRun = (submitData: Record<string, any>) => {
|
|
||||||
const varTypeInputKeys = Object.keys(inputs.tool_parameters)
|
|
||||||
.filter(key => inputs.tool_parameters[key].type === VarType.variable)
|
|
||||||
const shouldAdd = varTypeInputKeys.length > 0
|
|
||||||
if (!shouldAdd) {
|
|
||||||
doHandleRun(submitData)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const addMissedVarData = { ...submitData }
|
|
||||||
Object.keys(submitData).forEach((key) => {
|
|
||||||
const value = submitData[key]
|
|
||||||
varTypeInputKeys.forEach((inputKey) => {
|
|
||||||
const inputValue = inputs.tool_parameters[inputKey].value as ValueSelector
|
|
||||||
if (`#${inputValue.join('.')}#` === key)
|
|
||||||
addMissedVarData[inputKey] = value
|
|
||||||
})
|
|
||||||
})
|
|
||||||
doHandleRun(addMissedVarData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputSchema = useMemo(() => {
|
const outputSchema = useMemo(() => {
|
||||||
|
|
@ -307,18 +229,9 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
||||||
hideSetAuthModal,
|
hideSetAuthModal,
|
||||||
handleSaveAuth,
|
handleSaveAuth,
|
||||||
isLoading,
|
isLoading,
|
||||||
isShowSingleRun,
|
|
||||||
hideSingleRun,
|
|
||||||
inputVarValues,
|
|
||||||
varInputs,
|
|
||||||
setInputVarValues,
|
|
||||||
singleRunForms,
|
|
||||||
runningStatus,
|
|
||||||
handleRun,
|
|
||||||
handleStop,
|
|
||||||
runResult,
|
|
||||||
outputSchema,
|
outputSchema,
|
||||||
hasObjectOutput,
|
hasObjectOutput,
|
||||||
|
getMoreDataForCheckValid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { ToolNodeType } from './types'
|
||||||
|
import useConfig from './use-config'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string
|
||||||
|
payload: ToolNodeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
const useGetDataForCheckMore = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
}: Params) => {
|
||||||
|
const { getMoreDataForCheckValid } = useConfig(id, payload)
|
||||||
|
|
||||||
|
return {
|
||||||
|
getData: getMoreDataForCheckValid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useGetDataForCheckMore
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||||
|
import { type ToolNodeType, VarType } from './types'
|
||||||
|
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||||
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
|
import produce from 'immer'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import formatToTracingNodeList from '@/app/components/workflow/run/utils/format-log'
|
||||||
|
import { useToolIcon } from '../../hooks'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: ToolNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
runResult: NodeTracing
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
getInputVars,
|
||||||
|
setRunInputData,
|
||||||
|
runResult,
|
||||||
|
}: Params) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { inputs } = useNodeCrud<ToolNodeType>(id, payload)
|
||||||
|
|
||||||
|
const hadVarParams = Object.keys(inputs.tool_parameters)
|
||||||
|
.filter(key => inputs.tool_parameters[key].type !== VarType.constant)
|
||||||
|
.map(k => inputs.tool_parameters[k])
|
||||||
|
const varInputs = getInputVars(hadVarParams.map((p) => {
|
||||||
|
if (p.type === VarType.variable) {
|
||||||
|
// handle the old wrong value not crash the page
|
||||||
|
if (!(p.value as any).join)
|
||||||
|
return `{{#${p.value}#}}`
|
||||||
|
|
||||||
|
return `{{#${(p.value as ValueSelector).join('.')}#}}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.value as string
|
||||||
|
}))
|
||||||
|
const [inputVarValues, doSetInputVarValues] = useState<Record<string, any>>({})
|
||||||
|
const setInputVarValues = useCallback((value: Record<string, any>) => {
|
||||||
|
doSetInputVarValues(value)
|
||||||
|
setRunInputData(value)
|
||||||
|
}, [setRunInputData])
|
||||||
|
|
||||||
|
const inputVarValuesWithConstantValue = useCallback(() => {
|
||||||
|
const res = produce(inputVarValues, (draft) => {
|
||||||
|
Object.keys(inputs.tool_parameters).forEach((key: string) => {
|
||||||
|
const { type, value } = inputs.tool_parameters[key]
|
||||||
|
if (type === VarType.constant && (value === undefined || value === null))
|
||||||
|
draft[key] = value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}, [inputs.tool_parameters, inputVarValues])
|
||||||
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
|
const forms: FormProps[] = [{
|
||||||
|
inputs: varInputs,
|
||||||
|
values: inputVarValuesWithConstantValue(),
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
}]
|
||||||
|
return forms
|
||||||
|
}, [inputVarValuesWithConstantValue, setInputVarValues, varInputs])
|
||||||
|
|
||||||
|
const nodeInfo = useMemo(() => {
|
||||||
|
if (!runResult)
|
||||||
|
return null
|
||||||
|
return formatToTracingNodeList([runResult], t)[0]
|
||||||
|
}, [runResult, t])
|
||||||
|
|
||||||
|
const toolIcon = useToolIcon(payload)
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
return varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
nodeInfo,
|
||||||
|
toolIcon,
|
||||||
|
getDependentVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean, useDebounceFn } from 'ahooks'
|
||||||
import { v4 as uuid4 } from 'uuid'
|
import { v4 as uuid4 } from 'uuid'
|
||||||
import type { ValueSelector, Var } from '../../types'
|
import type { ValueSelector, Var } from '../../types'
|
||||||
import { VarType } from '../../types'
|
import { VarType } from '../../types'
|
||||||
|
|
@ -12,8 +12,13 @@ import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useWorkflow,
|
useWorkflow,
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||||
|
|
||||||
const useConfig = (id: string, payload: VariableAssignerNodeType) => {
|
const useConfig = (id: string, payload: VariableAssignerNodeType) => {
|
||||||
|
const {
|
||||||
|
deleteNodeInspectorVars,
|
||||||
|
renameInspectVarName,
|
||||||
|
} = useInspectVarsCrud()
|
||||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
|
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
|
||||||
|
|
||||||
|
|
@ -113,7 +118,8 @@ const useConfig = (id: string, payload: VariableAssignerNodeType) => {
|
||||||
draft.advanced_settings.group_enabled = enabled
|
draft.advanced_settings.group_enabled = enabled
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm])
|
deleteNodeInspectorVars(id)
|
||||||
|
}, [deleteNodeInspectorVars, handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm])
|
||||||
|
|
||||||
const handleAddGroup = useCallback(() => {
|
const handleAddGroup = useCallback(() => {
|
||||||
let maxInGroupName = 1
|
let maxInGroupName = 1
|
||||||
|
|
@ -134,7 +140,22 @@ const useConfig = (id: string, payload: VariableAssignerNodeType) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
deleteNodeInspectorVars(id)
|
||||||
|
}, [deleteNodeInspectorVars, id, inputs, setInputs])
|
||||||
|
|
||||||
|
// record the first old name value
|
||||||
|
const oldNameRecord = useRef<Record<string, string>>({})
|
||||||
|
|
||||||
|
const {
|
||||||
|
run: renameInspectNameWithDebounce,
|
||||||
|
} = useDebounceFn(
|
||||||
|
(id: string, newName: string) => {
|
||||||
|
const oldName = oldNameRecord.current[id]
|
||||||
|
renameInspectVarName(id, oldName, newName)
|
||||||
|
delete oldNameRecord.current[id]
|
||||||
|
},
|
||||||
|
{ wait: 500 },
|
||||||
|
)
|
||||||
|
|
||||||
const handleVarGroupNameChange = useCallback((groupId: string) => {
|
const handleVarGroupNameChange = useCallback((groupId: string) => {
|
||||||
return (name: string) => {
|
return (name: string) => {
|
||||||
|
|
@ -144,8 +165,11 @@ const useConfig = (id: string, payload: VariableAssignerNodeType) => {
|
||||||
})
|
})
|
||||||
handleOutVarRenameChange(id, [id, inputs.advanced_settings.groups[index].group_name, 'output'], [id, name, 'output'])
|
handleOutVarRenameChange(id, [id, inputs.advanced_settings.groups[index].group_name, 'output'], [id, name, 'output'])
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
|
if(!(id in oldNameRecord.current))
|
||||||
|
oldNameRecord.current[id] = inputs.advanced_settings.groups[index].group_name
|
||||||
|
renameInspectNameWithDebounce(id, name)
|
||||||
}
|
}
|
||||||
}, [handleOutVarRenameChange, id, inputs, setInputs])
|
}, [handleOutVarRenameChange, id, inputs, renameInspectNameWithDebounce, setInputs])
|
||||||
|
|
||||||
const onRemoveVarConfirm = useCallback(() => {
|
const onRemoveVarConfirm = useCallback(() => {
|
||||||
removedVars.forEach((v) => {
|
removedVars.forEach((v) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
import type { MutableRefObject } from 'react'
|
||||||
|
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { VariableAssignerNodeType } from './types'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string,
|
||||||
|
payload: VariableAssignerNodeType,
|
||||||
|
runInputData: Record<string, any>
|
||||||
|
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||||
|
getInputVars: (textList: string[]) => InputVar[]
|
||||||
|
setRunInputData: (data: Record<string, any>) => void
|
||||||
|
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||||
|
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||||
|
}
|
||||||
|
const useSingleRunFormParams = ({
|
||||||
|
payload,
|
||||||
|
runInputData,
|
||||||
|
setRunInputData,
|
||||||
|
varSelectorsToVarInputs,
|
||||||
|
}: Params) => {
|
||||||
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
|
setRunInputData(newPayload)
|
||||||
|
}, [setRunInputData])
|
||||||
|
const inputVarValues = (() => {
|
||||||
|
const vars: Record<string, any> = {}
|
||||||
|
Object.keys(runInputData)
|
||||||
|
.forEach((key) => {
|
||||||
|
vars[key] = runInputData[key]
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
})()
|
||||||
|
|
||||||
|
const forms = (() => {
|
||||||
|
const allInputs: ValueSelector[] = []
|
||||||
|
const isGroupEnabled = !!payload.advanced_settings?.group_enabled
|
||||||
|
if (!isGroupEnabled && payload.variables && payload.variables.length) {
|
||||||
|
payload.variables.forEach((varSelector) => {
|
||||||
|
allInputs.push(varSelector)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (isGroupEnabled && payload.advanced_settings && payload.advanced_settings.groups && payload.advanced_settings.groups.length) {
|
||||||
|
payload.advanced_settings.groups.forEach((group) => {
|
||||||
|
group.variables?.forEach((varSelector) => {
|
||||||
|
allInputs.push(varSelector)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const varInputs = varSelectorsToVarInputs(allInputs)
|
||||||
|
// remove duplicate inputs
|
||||||
|
const existVarsKey: Record<string, boolean> = {}
|
||||||
|
const uniqueVarInputs: InputVar[] = []
|
||||||
|
varInputs.forEach((input) => {
|
||||||
|
if(!input)
|
||||||
|
return
|
||||||
|
if (!existVarsKey[input.variable]) {
|
||||||
|
existVarsKey[input.variable] = true
|
||||||
|
uniqueVarInputs.push({
|
||||||
|
...input,
|
||||||
|
required: false, // just one of the inputs is required
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
inputs: uniqueVarInputs,
|
||||||
|
values: inputVarValues,
|
||||||
|
onChange: setInputVarValues,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})()
|
||||||
|
|
||||||
|
const getDependentVars = () => {
|
||||||
|
if(payload.advanced_settings?.group_enabled) {
|
||||||
|
const vars: ValueSelector[][] = []
|
||||||
|
payload.advanced_settings.groups.forEach((group) => {
|
||||||
|
if(group.variables)
|
||||||
|
vars.push([...group.variables])
|
||||||
|
})
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
return [payload.variables]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forms,
|
||||||
|
getDependentVars,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSingleRunFormParams
|
||||||
|
|
@ -96,7 +96,7 @@ const AddBlock = ({
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
disabled={nodesReadOnly}
|
disabled={nodesReadOnly}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
placement='top-start'
|
placement='right-start'
|
||||||
offset={offset ?? {
|
offset={offset ?? {
|
||||||
mainAxis: 4,
|
mainAxis: 4,
|
||||||
crossAxis: -8,
|
crossAxis: -8,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import {
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
|
RiAspectRatioFill,
|
||||||
|
RiAspectRatioLine,
|
||||||
RiCursorLine,
|
RiCursorLine,
|
||||||
RiFunctionAddLine,
|
RiFunctionAddLine,
|
||||||
RiHand,
|
RiHand,
|
||||||
|
|
@ -11,6 +13,7 @@ import {
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import {
|
import {
|
||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
|
useWorkflowCanvasMaximize,
|
||||||
useWorkflowMoveMode,
|
useWorkflowMoveMode,
|
||||||
useWorkflowOrganize,
|
useWorkflowOrganize,
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
|
|
@ -28,6 +31,7 @@ import cn from '@/utils/classnames'
|
||||||
const Control = () => {
|
const Control = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const controlMode = useStore(s => s.controlMode)
|
const controlMode = useStore(s => s.controlMode)
|
||||||
|
const maximizeCanvas = useStore(s => s.maximizeCanvas)
|
||||||
const { handleModePointer, handleModeHand } = useWorkflowMoveMode()
|
const { handleModePointer, handleModeHand } = useWorkflowMoveMode()
|
||||||
const { handleLayout } = useWorkflowOrganize()
|
const { handleLayout } = useWorkflowOrganize()
|
||||||
const { handleAddNote } = useOperator()
|
const { handleAddNote } = useOperator()
|
||||||
|
|
@ -35,6 +39,7 @@ const Control = () => {
|
||||||
nodesReadOnly,
|
nodesReadOnly,
|
||||||
getNodesReadOnly,
|
getNodesReadOnly,
|
||||||
} = useNodesReadOnly()
|
} = useNodesReadOnly()
|
||||||
|
const { handleToggleMaximizeCanvas } = useWorkflowCanvasMaximize()
|
||||||
|
|
||||||
const addNote = (e: MouseEvent<HTMLDivElement>) => {
|
const addNote = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
|
|
@ -45,7 +50,7 @@ const Control = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 text-text-tertiary shadow-lg'>
|
<div className='flex flex-col items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 text-text-tertiary shadow-lg'>
|
||||||
<AddBlock />
|
<AddBlock />
|
||||||
<TipPopup title={t('workflow.nodes.note.addNote')}>
|
<TipPopup title={t('workflow.nodes.note.addNote')}>
|
||||||
<div
|
<div
|
||||||
|
|
@ -58,7 +63,7 @@ const Control = () => {
|
||||||
<RiStickyNoteAddLine className='h-4 w-4' />
|
<RiStickyNoteAddLine className='h-4 w-4' />
|
||||||
</div>
|
</div>
|
||||||
</TipPopup>
|
</TipPopup>
|
||||||
<Divider type='vertical' className='mx-0.5 h-3.5' />
|
<Divider className='my-1 w-3.5' />
|
||||||
<TipPopup title={t('workflow.common.pointerMode')} shortcuts={['v']}>
|
<TipPopup title={t('workflow.common.pointerMode')} shortcuts={['v']}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -83,7 +88,7 @@ const Control = () => {
|
||||||
<RiHand className='h-4 w-4' />
|
<RiHand className='h-4 w-4' />
|
||||||
</div>
|
</div>
|
||||||
</TipPopup>
|
</TipPopup>
|
||||||
<Divider type='vertical' className='mx-0.5 h-3.5' />
|
<Divider className='my-1 w-3.5' />
|
||||||
<ExportImage />
|
<ExportImage />
|
||||||
<TipPopup title={t('workflow.panel.organizeBlocks')} shortcuts={['ctrl', 'o']}>
|
<TipPopup title={t('workflow.panel.organizeBlocks')} shortcuts={['ctrl', 'o']}>
|
||||||
<div
|
<div
|
||||||
|
|
@ -96,6 +101,19 @@ const Control = () => {
|
||||||
<RiFunctionAddLine className='h-4 w-4' />
|
<RiFunctionAddLine className='h-4 w-4' />
|
||||||
</div>
|
</div>
|
||||||
</TipPopup>
|
</TipPopup>
|
||||||
|
<TipPopup title={maximizeCanvas ? t('workflow.panel.minimize') : t('workflow.panel.maximize')} shortcuts={['f']}>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover hover:text-text-secondary',
|
||||||
|
maximizeCanvas ? 'bg-state-accent-active text-text-accent hover:text-text-accent' : 'hover:bg-state-base-hover hover:text-text-secondary',
|
||||||
|
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||||
|
)}
|
||||||
|
onClick={handleToggleMaximizeCanvas}
|
||||||
|
>
|
||||||
|
{maximizeCanvas && <RiAspectRatioFill className='h-4 w-4' />}
|
||||||
|
{!maximizeCanvas && <RiAspectRatioLine className='h-4 w-4' />}
|
||||||
|
</div>
|
||||||
|
</TipPopup>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { memo } from 'react'
|
import { memo, useEffect, useMemo, useRef } from 'react'
|
||||||
import { MiniMap } from 'reactflow'
|
import { MiniMap } from 'reactflow'
|
||||||
import UndoRedo from '../header/undo-redo'
|
import UndoRedo from '../header/undo-redo'
|
||||||
import ZoomInOut from './zoom-in-out'
|
import ZoomInOut from './zoom-in-out'
|
||||||
import Control from './control'
|
import VariableTrigger from '../variable-inspect/trigger'
|
||||||
|
import VariableInspectPanel from '../variable-inspect'
|
||||||
|
import { useStore } from '../store'
|
||||||
|
|
||||||
export type OperatorProps = {
|
export type OperatorProps = {
|
||||||
handleUndo: () => void
|
handleUndo: () => void
|
||||||
|
|
@ -10,8 +12,49 @@ export type OperatorProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Operator = ({ handleUndo, handleRedo }: OperatorProps) => {
|
const Operator = ({ handleUndo, handleRedo }: OperatorProps) => {
|
||||||
|
const bottomPanelRef = useRef<HTMLDivElement>(null)
|
||||||
|
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
|
||||||
|
const rightPanelWidth = useStore(s => s.rightPanelWidth)
|
||||||
|
const setBottomPanelWidth = useStore(s => s.setBottomPanelWidth)
|
||||||
|
const setBottomPanelHeight = useStore(s => s.setBottomPanelHeight)
|
||||||
|
|
||||||
|
const bottomPanelWidth = useMemo(() => {
|
||||||
|
if (!workflowCanvasWidth || !rightPanelWidth)
|
||||||
|
return 'auto'
|
||||||
|
return Math.max((workflowCanvasWidth - rightPanelWidth), 400)
|
||||||
|
}, [workflowCanvasWidth, rightPanelWidth])
|
||||||
|
|
||||||
|
// update bottom panel height
|
||||||
|
useEffect(() => {
|
||||||
|
if (bottomPanelRef.current) {
|
||||||
|
const resizeContainerObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const { inlineSize, blockSize } = entry.borderBoxSize[0]
|
||||||
|
setBottomPanelWidth(inlineSize)
|
||||||
|
setBottomPanelHeight(blockSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resizeContainerObserver.observe(bottomPanelRef.current)
|
||||||
|
return () => {
|
||||||
|
resizeContainerObserver.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [setBottomPanelHeight, setBottomPanelWidth])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
|
ref={bottomPanelRef}
|
||||||
|
className='absolute bottom-0 left-0 right-0 z-10 px-1'
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
width: bottomPanelWidth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='flex justify-between px-1 pb-2'>
|
||||||
|
<UndoRedo handleUndo={handleUndo} handleRedo={handleRedo} />
|
||||||
|
<VariableTrigger />
|
||||||
|
<div className='relative'>
|
||||||
<MiniMap
|
<MiniMap
|
||||||
pannable
|
pannable
|
||||||
zoomable
|
zoomable
|
||||||
|
|
@ -20,15 +63,14 @@ const Operator = ({ handleUndo, handleRedo }: OperatorProps) => {
|
||||||
height: 72,
|
height: 72,
|
||||||
}}
|
}}
|
||||||
maskColor='var(--color-workflow-minimap-bg)'
|
maskColor='var(--color-workflow-minimap-bg)'
|
||||||
className='!absolute !bottom-14 !left-4 z-[9] !m-0 !h-[72px] !w-[102px] !rounded-lg !border-[0.5px]
|
className='!absolute !bottom-10 z-[9] !m-0 !h-[73px] !w-[103px] !rounded-lg !border-[0.5px]
|
||||||
!border-divider-subtle !bg-background-default-subtle !shadow-md !shadow-shadow-shadow-5'
|
!border-divider-subtle !bg-background-default-subtle !shadow-md !shadow-shadow-shadow-5'
|
||||||
/>
|
/>
|
||||||
<div className='absolute bottom-4 left-4 z-[9] mt-1 flex items-center gap-2'>
|
|
||||||
<ZoomInOut />
|
<ZoomInOut />
|
||||||
<UndoRedo handleUndo={handleUndo} handleRedo={handleRedo} />
|
|
||||||
<Control />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
|
<VariableInspectPanel />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,7 @@ const ChatVariableModal = ({
|
||||||
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div>
|
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div>
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<textarea
|
<textarea
|
||||||
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
||||||
value={des}
|
value={des}
|
||||||
placeholder={t('workflow.chatVariable.modal.descriptionPlaceholder') || ''}
|
placeholder={t('workflow.chatVariable.modal.descriptionPlaceholder') || ''}
|
||||||
onChange={e => setDes(e.target.value)}
|
onChange={e => setDes(e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-syn
|
||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
import { useDocLink } from '@/context/i18n'
|
import { useDocLink } from '@/context/i18n'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||||
|
|
||||||
const ChatVariablePanel = () => {
|
const ChatVariablePanel = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -32,6 +33,16 @@ const ChatVariablePanel = () => {
|
||||||
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
|
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
|
||||||
const updateChatVarList = useStore(s => s.setConversationVariables)
|
const updateChatVarList = useStore(s => s.setConversationVariables)
|
||||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const {
|
||||||
|
invalidateConversationVarValues,
|
||||||
|
} = useInspectVarsCrud()
|
||||||
|
const handleVarChanged = useCallback(() => {
|
||||||
|
doSyncWorkflowDraft(false, {
|
||||||
|
onSuccess() {
|
||||||
|
invalidateConversationVarValues()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [doSyncWorkflowDraft, invalidateConversationVarValues])
|
||||||
|
|
||||||
const [showTip, setShowTip] = useState(true)
|
const [showTip, setShowTip] = useState(true)
|
||||||
const [showVariableModal, setShowVariableModal] = useState(false)
|
const [showVariableModal, setShowVariableModal] = useState(false)
|
||||||
|
|
@ -71,8 +82,8 @@ const ChatVariablePanel = () => {
|
||||||
updateChatVarList(varList.filter(v => v.id !== chatVar.id))
|
updateChatVarList(varList.filter(v => v.id !== chatVar.id))
|
||||||
setCacheForDelete(undefined)
|
setCacheForDelete(undefined)
|
||||||
setShowRemoveConfirm(false)
|
setShowRemoveConfirm(false)
|
||||||
doSyncWorkflowDraft()
|
handleVarChanged()
|
||||||
}, [doSyncWorkflowDraft, removeUsedVarInNodes, updateChatVarList, varList])
|
}, [handleVarChanged, removeUsedVarInNodes, updateChatVarList, varList])
|
||||||
|
|
||||||
const deleteCheck = useCallback((chatVar: ConversationVariable) => {
|
const deleteCheck = useCallback((chatVar: ConversationVariable) => {
|
||||||
const effectedNodes = getEffectedNodes(chatVar)
|
const effectedNodes = getEffectedNodes(chatVar)
|
||||||
|
|
@ -90,7 +101,7 @@ const ChatVariablePanel = () => {
|
||||||
if (!currentVar) {
|
if (!currentVar) {
|
||||||
const newList = [chatVar, ...varList]
|
const newList = [chatVar, ...varList]
|
||||||
updateChatVarList(newList)
|
updateChatVarList(newList)
|
||||||
doSyncWorkflowDraft()
|
handleVarChanged()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// edit chatVar
|
// edit chatVar
|
||||||
|
|
@ -108,8 +119,8 @@ const ChatVariablePanel = () => {
|
||||||
})
|
})
|
||||||
setNodes(newNodes)
|
setNodes(newNodes)
|
||||||
}
|
}
|
||||||
doSyncWorkflowDraft()
|
handleVarChanged()
|
||||||
}, [currentVar, doSyncWorkflowDraft, getEffectedNodes, store, updateChatVarList, varList])
|
}, [currentVar, getEffectedNodes, handleVarChanged, store, updateChatVarList, varList])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import {
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils'
|
import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils'
|
||||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||||
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
|
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
||||||
|
|
||||||
type ChatWrapperProps = {
|
type ChatWrapperProps = {
|
||||||
showConversationVariableModal: boolean
|
showConversationVariableModal: boolean
|
||||||
|
|
@ -105,6 +107,12 @@ const ChatWrapper = (
|
||||||
)
|
)
|
||||||
}, [chatList, doSend])
|
}, [chatList, doSend])
|
||||||
|
|
||||||
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
|
eventEmitter?.useSubscription((v: any) => {
|
||||||
|
if (v.type === EVENT_WORKFLOW_STOP)
|
||||||
|
handleStop()
|
||||||
|
})
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
return {
|
return {
|
||||||
handleRestart,
|
handleRestart,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ import {
|
||||||
} from '@/app/components/base/file-uploader/utils'
|
} from '@/app/components/base/file-uploader/utils'
|
||||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||||
import { getThreadMessages } from '@/app/components/base/chat/utils'
|
import { getThreadMessages } from '@/app/components/base/chat/utils'
|
||||||
|
import { useInvalidAllLastRun } from '@/service/use-workflow'
|
||||||
|
import { useParams } from 'next/navigation'
|
||||||
|
import useSetWorkflowVarsWithValue from '@/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars'
|
||||||
|
|
||||||
type GetAbortController = (abortController: AbortController) => void
|
type GetAbortController = (abortController: AbortController) => void
|
||||||
type SendCallback = {
|
type SendCallback = {
|
||||||
|
|
@ -53,6 +56,9 @@ export const useChat = (
|
||||||
const taskIdRef = useRef('')
|
const taskIdRef = useRef('')
|
||||||
const [isResponding, setIsResponding] = useState(false)
|
const [isResponding, setIsResponding] = useState(false)
|
||||||
const isRespondingRef = useRef(false)
|
const isRespondingRef = useRef(false)
|
||||||
|
const { appId } = useParams()
|
||||||
|
const invalidAllLastRun = useInvalidAllLastRun(appId as string)
|
||||||
|
const { fetchInspectVars } = useSetWorkflowVarsWithValue()
|
||||||
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
|
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
|
||||||
const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
|
const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
|
||||||
const {
|
const {
|
||||||
|
|
@ -288,6 +294,8 @@ export const useChat = (
|
||||||
},
|
},
|
||||||
async onCompleted(hasError?: boolean, errorMessage?: string) {
|
async onCompleted(hasError?: boolean, errorMessage?: string) {
|
||||||
handleResponding(false)
|
handleResponding(false)
|
||||||
|
fetchInspectVars()
|
||||||
|
invalidAllLastRun()
|
||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
@ -16,14 +16,14 @@ import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks
|
||||||
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
|
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
|
||||||
import { BlockEnum } from '../../types'
|
import { BlockEnum } from '../../types'
|
||||||
import type { StartNodeType } from '../../nodes/start/types'
|
import type { StartNodeType } from '../../nodes/start/types'
|
||||||
|
import { useResizePanel } from '../../nodes/_base/hooks/use-resize-panel'
|
||||||
import ChatWrapper from './chat-wrapper'
|
import ChatWrapper from './chat-wrapper'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
|
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
import { noop } from 'lodash-es'
|
import { debounce, noop } from 'lodash-es'
|
||||||
|
|
||||||
export type ChatWrapperRefType = {
|
export type ChatWrapperRefType = {
|
||||||
handleRestart: () => void
|
handleRestart: () => void
|
||||||
|
|
@ -34,9 +34,9 @@ const DebugAndPreview = () => {
|
||||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||||
const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync()
|
const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync()
|
||||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
|
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
|
||||||
const varList = useStore(s => s.conversationVariables)
|
|
||||||
const [expanded, setExpanded] = useState(true)
|
const [expanded, setExpanded] = useState(true)
|
||||||
const nodes = useNodes<StartNodeType>()
|
const nodes = useNodes<StartNodeType>()
|
||||||
|
const selectedNode = nodes.find(node => node.data.selected)
|
||||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||||
const variables = startNode?.data.variables || []
|
const variables = startNode?.data.variables || []
|
||||||
const visibleVariables = variables.filter(v => v.hide !== true)
|
const visibleVariables = variables.filter(v => v.hide !== true)
|
||||||
|
|
@ -49,46 +49,46 @@ const DebugAndPreview = () => {
|
||||||
chatRef.current.handleRestart()
|
chatRef.current.handleRestart()
|
||||||
}
|
}
|
||||||
|
|
||||||
const [panelWidth, setPanelWidth] = useState(420)
|
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
|
||||||
const [isResizing, setIsResizing] = useState(false)
|
const nodePanelWidth = useStore(s => s.nodePanelWidth)
|
||||||
|
const [panelWidth, setPanelWidth] = useState(400)
|
||||||
|
const handleResize = useCallback((width: number) => {
|
||||||
|
setPanelWidth(width)
|
||||||
|
}, [setPanelWidth])
|
||||||
|
const maxPanelWidth = useMemo(() => {
|
||||||
|
if (!workflowCanvasWidth)
|
||||||
|
return 720
|
||||||
|
|
||||||
const startResizing = useCallback((e: React.MouseEvent) => {
|
if (!selectedNode)
|
||||||
e.preventDefault()
|
return workflowCanvasWidth - 400
|
||||||
setIsResizing(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const stopResizing = useCallback(() => {
|
return workflowCanvasWidth - 400 - 400
|
||||||
setIsResizing(false)
|
}, [workflowCanvasWidth, selectedNode, nodePanelWidth])
|
||||||
}, [])
|
const {
|
||||||
|
triggerRef,
|
||||||
const resize = useCallback((e: MouseEvent) => {
|
containerRef,
|
||||||
if (isResizing) {
|
} = useResizePanel({
|
||||||
const newWidth = window.innerWidth - e.clientX
|
direction: 'horizontal',
|
||||||
if (newWidth > 420 && newWidth < 1024)
|
triggerDirection: 'left',
|
||||||
setPanelWidth(newWidth)
|
minWidth: 400,
|
||||||
}
|
maxWidth: maxPanelWidth,
|
||||||
}, [isResizing])
|
onResize: debounce(handleResize),
|
||||||
|
})
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('mousemove', resize)
|
|
||||||
window.addEventListener('mouseup', stopResizing)
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('mousemove', resize)
|
|
||||||
window.removeEventListener('mouseup', stopResizing)
|
|
||||||
}
|
|
||||||
}, [resize, stopResizing])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className='relative h-full'>
|
||||||
<div
|
<div
|
||||||
|
ref={triggerRef}
|
||||||
|
className='absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center'>
|
||||||
|
<div className='h-10 w-0.5 rounded-sm bg-state-base-handle hover:h-full hover:bg-state-accent-solid active:h-full active:bg-state-accent-solid'></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex h-full flex-col rounded-l-2xl border border-r-0 border-components-panel-border bg-chatbot-bg shadow-xl',
|
'relative flex h-full flex-col rounded-l-2xl border border-r-0 border-components-panel-border bg-chatbot-bg shadow-xl',
|
||||||
)}
|
)}
|
||||||
style={{ width: `${panelWidth}px` }}
|
style={{ width: `${panelWidth}px` }}
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="absolute bottom-0 left-[3px] top-1/2 z-50 h-6 w-[3px] cursor-col-resize rounded bg-gray-300"
|
|
||||||
onMouseDown={startResizing}
|
|
||||||
/>
|
|
||||||
<div className='system-xl-semibold flex shrink-0 items-center justify-between px-4 pb-2 pt-3 text-text-primary'>
|
<div className='system-xl-semibold flex shrink-0 items-center justify-between px-4 pb-2 pt-3 text-text-primary'>
|
||||||
<div className='h-8'>{t('workflow.common.debugAndPreview').toLocaleUpperCase()}</div>
|
<div className='h-8'>{t('workflow.common.debugAndPreview').toLocaleUpperCase()}</div>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
|
|
@ -99,15 +99,6 @@ const DebugAndPreview = () => {
|
||||||
<RefreshCcw01 className='h-4 w-4' />
|
<RefreshCcw01 className='h-4 w-4' />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{varList.length > 0 && (
|
|
||||||
<Tooltip
|
|
||||||
popupContent={t('workflow.chatVariable.panelTitle')}
|
|
||||||
>
|
|
||||||
<ActionButton onClick={() => setShowConversationVariableModal(true)}>
|
|
||||||
<BubbleX className='h-4 w-4' />
|
|
||||||
</ActionButton>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{visibleVariables.length > 0 && (
|
{visibleVariables.length > 0 && (
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|
@ -139,6 +130,7 @@ const DebugAndPreview = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { memo } from 'react'
|
import { memo, useEffect, useRef } from 'react'
|
||||||
import { useNodes } from 'reactflow'
|
import { useNodes } from 'reactflow'
|
||||||
import type { CommonNodeType } from '../types'
|
import type { CommonNodeType } from '../types'
|
||||||
import { Panel as NodePanel } from '../nodes'
|
import { Panel as NodePanel } from '../nodes'
|
||||||
|
|
@ -21,10 +21,48 @@ const Panel: FC<PanelProps> = ({
|
||||||
const showEnvPanel = useStore(s => s.showEnvPanel)
|
const showEnvPanel = useStore(s => s.showEnvPanel)
|
||||||
const isRestoring = useStore(s => s.isRestoring)
|
const isRestoring = useStore(s => s.isRestoring)
|
||||||
|
|
||||||
|
const rightPanelRef = useRef<HTMLDivElement>(null)
|
||||||
|
const setRightPanelWidth = useStore(s => s.setRightPanelWidth)
|
||||||
|
|
||||||
|
// get right panel width
|
||||||
|
useEffect(() => {
|
||||||
|
if (rightPanelRef.current) {
|
||||||
|
const resizeRightPanelObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const { inlineSize } = entry.borderBoxSize[0]
|
||||||
|
setRightPanelWidth(inlineSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resizeRightPanelObserver.observe(rightPanelRef.current)
|
||||||
|
return () => {
|
||||||
|
resizeRightPanelObserver.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [setRightPanelWidth])
|
||||||
|
|
||||||
|
const otherPanelRef = useRef<HTMLDivElement>(null)
|
||||||
|
const setOtherPanelWidth = useStore(s => s.setOtherPanelWidth)
|
||||||
|
|
||||||
|
// get other panel width
|
||||||
|
useEffect(() => {
|
||||||
|
if (otherPanelRef.current) {
|
||||||
|
const resizeOtherPanelObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const { inlineSize } = entry.borderBoxSize[0]
|
||||||
|
setOtherPanelWidth(inlineSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resizeOtherPanelObserver.observe(otherPanelRef.current)
|
||||||
|
return () => {
|
||||||
|
resizeOtherPanelObserver.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [setOtherPanelWidth])
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={rightPanelRef}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className={cn('absolute bottom-2 right-0 top-14 z-10 flex outline-none')}
|
className={cn('absolute bottom-1 right-0 top-14 z-10 flex outline-none')}
|
||||||
key={`${isRestoring}`}
|
key={`${isRestoring}`}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|
@ -35,6 +73,10 @@ const Panel: FC<PanelProps> = ({
|
||||||
<NodePanel {...selectedNode!} />
|
<NodePanel {...selectedNode!} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<div
|
||||||
|
className='relative'
|
||||||
|
ref={otherPanelRef}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
components?.right
|
components?.right
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +86,7 @@ const Panel: FC<PanelProps> = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import VersionHistoryItem from './version-history-item'
|
||||||
import Filter from './filter'
|
import Filter from './filter'
|
||||||
import type { VersionHistory } from '@/types/workflow'
|
import type { VersionHistory } from '@/types/workflow'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { useDeleteWorkflow, useResetWorkflowVersionHistory, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow'
|
import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import Loading from './loading'
|
import Loading from './loading'
|
||||||
import Empty from './empty'
|
import Empty from './empty'
|
||||||
|
|
@ -37,6 +37,10 @@ const VersionHistoryPanel = () => {
|
||||||
const currentVersion = useStore(s => s.currentVersion)
|
const currentVersion = useStore(s => s.currentVersion)
|
||||||
const setCurrentVersion = useStore(s => s.setCurrentVersion)
|
const setCurrentVersion = useStore(s => s.setCurrentVersion)
|
||||||
const userProfile = useAppContextSelector(s => s.userProfile)
|
const userProfile = useAppContextSelector(s => s.userProfile)
|
||||||
|
const invalidAllLastRun = useInvalidAllLastRun(appDetail!.id)
|
||||||
|
const {
|
||||||
|
deleteAllInspectVars,
|
||||||
|
} = workflowStore.getState()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -125,6 +129,8 @@ const VersionHistoryPanel = () => {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: t('workflow.versionHistory.action.restoreSuccess'),
|
message: t('workflow.versionHistory.action.restoreSuccess'),
|
||||||
})
|
})
|
||||||
|
deleteAllInspectVars()
|
||||||
|
invalidAllLastRun()
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Toast.notify({
|
Toast.notify({
|
||||||
|
|
@ -136,7 +142,7 @@ const VersionHistoryPanel = () => {
|
||||||
resetWorkflowVersionHistory()
|
resetWorkflowVersionHistory()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [setShowWorkflowVersionHistoryPanel, handleSyncWorkflowDraft, workflowStore, handleRestoreFromPublishedWorkflow, resetWorkflowVersionHistory, t])
|
}, [setShowWorkflowVersionHistoryPanel, handleRestoreFromPublishedWorkflow, workflowStore, handleSyncWorkflowDraft, deleteAllInspectVars, invalidAllLastRun, t, resetWorkflowVersionHistory])
|
||||||
|
|
||||||
const { mutateAsync: deleteWorkflow } = useDeleteWorkflow(appDetail!.id)
|
const { mutateAsync: deleteWorkflow } = useDeleteWorkflow(appDetail!.id)
|
||||||
|
|
||||||
|
|
@ -149,6 +155,8 @@ const VersionHistoryPanel = () => {
|
||||||
message: t('workflow.versionHistory.action.deleteSuccess'),
|
message: t('workflow.versionHistory.action.deleteSuccess'),
|
||||||
})
|
})
|
||||||
resetWorkflowVersionHistory()
|
resetWorkflowVersionHistory()
|
||||||
|
deleteAllInspectVars()
|
||||||
|
invalidAllLastRun()
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Toast.notify({
|
Toast.notify({
|
||||||
|
|
@ -160,7 +168,7 @@ const VersionHistoryPanel = () => {
|
||||||
setDeleteConfirmOpen(false)
|
setDeleteConfirmOpen(false)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [t, deleteWorkflow, resetWorkflowVersionHistory])
|
}, [deleteWorkflow, t, resetWorkflowVersionHistory, deleteAllInspectVars, invalidAllLastRun])
|
||||||
|
|
||||||
const { mutateAsync: updateWorkflow } = useUpdateWorkflow(appDetail!.id)
|
const { mutateAsync: updateWorkflow } = useUpdateWorkflow(appDetail!.id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ import { LoopLogTrigger } from '@/app/components/workflow/run/loop-log'
|
||||||
import { RetryLogTrigger } from '@/app/components/workflow/run/retry-log'
|
import { RetryLogTrigger } from '@/app/components/workflow/run/retry-log'
|
||||||
import { AgentLogTrigger } from '@/app/components/workflow/run/agent-log'
|
import { AgentLogTrigger } from '@/app/components/workflow/run/agent-log'
|
||||||
|
|
||||||
type ResultPanelProps = {
|
export type ResultPanelProps = {
|
||||||
nodeInfo?: NodeTracing
|
nodeInfo?: NodeTracing
|
||||||
inputs?: string
|
inputs?: string
|
||||||
process_data?: string
|
process_data?: string
|
||||||
outputs?: string
|
outputs?: string | Record<string, any>
|
||||||
status: string
|
status: string
|
||||||
error?: string
|
error?: string
|
||||||
elapsed_time?: number
|
elapsed_time?: number
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
import type { StateCreator } from 'zustand'
|
||||||
|
import produce from 'immer'
|
||||||
|
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
|
||||||
|
import type { ValueSelector } from '../../../types'
|
||||||
|
|
||||||
|
type InspectVarsState = {
|
||||||
|
currentFocusNodeId: string | null
|
||||||
|
nodesWithInspectVars: NodeWithVar[] // the nodes have data
|
||||||
|
conversationVars: VarInInspect[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type InspectVarsActions = {
|
||||||
|
setCurrentFocusNodeId: (nodeId: string | null) => void
|
||||||
|
setNodesWithInspectVars: (payload: NodeWithVar[]) => void
|
||||||
|
deleteAllInspectVars: () => void
|
||||||
|
setNodeInspectVars: (nodeId: string, payload: VarInInspect[]) => void
|
||||||
|
deleteNodeInspectVars: (nodeId: string) => void
|
||||||
|
setInspectVarValue: (nodeId: string, name: string, value: any) => void
|
||||||
|
resetToLastRunVar: (nodeId: string, varId: string, value: any) => void
|
||||||
|
renameInspectVarName: (nodeId: string, varId: string, selector: ValueSelector) => void
|
||||||
|
deleteInspectVar: (nodeId: string, varId: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InspectVarsSliceShape = InspectVarsState & InspectVarsActions
|
||||||
|
|
||||||
|
export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set, get) => {
|
||||||
|
return ({
|
||||||
|
currentFocusNodeId: null,
|
||||||
|
nodesWithInspectVars: [],
|
||||||
|
conversationVars: [],
|
||||||
|
setCurrentFocusNodeId: (nodeId) => {
|
||||||
|
set(() => ({
|
||||||
|
currentFocusNodeId: nodeId,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
setNodesWithInspectVars: (payload) => {
|
||||||
|
set(() => ({
|
||||||
|
nodesWithInspectVars: payload,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
deleteAllInspectVars: () => {
|
||||||
|
set(() => ({
|
||||||
|
nodesWithInspectVars: [],
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
setNodeInspectVars: (nodeId, payload) => {
|
||||||
|
set((state) => {
|
||||||
|
const prevNodes = state.nodesWithInspectVars
|
||||||
|
const nodes = produce(prevNodes, (draft) => {
|
||||||
|
const index = prevNodes.findIndex(node => node.nodeId === nodeId)
|
||||||
|
if (index !== -1) {
|
||||||
|
draft[index].vars = payload
|
||||||
|
draft[index].isValueFetched = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodesWithInspectVars: nodes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteNodeInspectVars: (nodeId) => {
|
||||||
|
set((state: InspectVarsSliceShape) => {
|
||||||
|
const nodes = state.nodesWithInspectVars.filter(node => node.nodeId !== nodeId)
|
||||||
|
return {
|
||||||
|
nodesWithInspectVars: nodes,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
setInspectVarValue: (nodeId, varId, value) => {
|
||||||
|
set((state: InspectVarsSliceShape) => {
|
||||||
|
const nodes = produce(state.nodesWithInspectVars, (draft) => {
|
||||||
|
const targetNode = draft.find(node => node.nodeId === nodeId)
|
||||||
|
if (!targetNode)
|
||||||
|
return
|
||||||
|
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
||||||
|
if(!targetVar)
|
||||||
|
return
|
||||||
|
targetVar.value = value
|
||||||
|
targetVar.edited = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
nodesWithInspectVars: nodes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetToLastRunVar: (nodeId, varId, value) => {
|
||||||
|
set((state: InspectVarsSliceShape) => {
|
||||||
|
const nodes = produce(state.nodesWithInspectVars, (draft) => {
|
||||||
|
const targetNode = draft.find(node => node.nodeId === nodeId)
|
||||||
|
if (!targetNode)
|
||||||
|
return
|
||||||
|
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
||||||
|
if(!targetVar)
|
||||||
|
return
|
||||||
|
targetVar.value = value
|
||||||
|
targetVar.edited = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
nodesWithInspectVars: nodes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
renameInspectVarName: (nodeId, varId, selector) => {
|
||||||
|
set((state: InspectVarsSliceShape) => {
|
||||||
|
const nodes = produce(state.nodesWithInspectVars, (draft) => {
|
||||||
|
const targetNode = draft.find(node => node.nodeId === nodeId)
|
||||||
|
if (!targetNode)
|
||||||
|
return
|
||||||
|
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
|
||||||
|
if(!targetVar)
|
||||||
|
return
|
||||||
|
targetVar.name = selector[1]
|
||||||
|
targetVar.selector = selector
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
nodesWithInspectVars: nodes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteInspectVar: (nodeId, varId) => {
|
||||||
|
set((state: InspectVarsSliceShape) => {
|
||||||
|
const nodes = produce(state.nodesWithInspectVars, (draft) => {
|
||||||
|
const targetNode = draft.find(node => node.nodeId === nodeId)
|
||||||
|
if (!targetNode)
|
||||||
|
return
|
||||||
|
const needChangeVarIndex = targetNode.vars.findIndex(varItem => varItem.id === varId)
|
||||||
|
if (needChangeVarIndex !== -1)
|
||||||
|
targetNode.vars.splice(needChangeVarIndex, 1)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
nodesWithInspectVars: nodes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue