chore(web): new lint setup (#30020)

Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
Stephen Zhou
2025-12-23 16:58:55 +08:00
committed by GitHub
parent 9701a2994b
commit f2842da397
3356 changed files with 85046 additions and 81278 deletions

View File

@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import {
RiAddLine,
} from '@remixicon/react'
import { cn } from '@/utils/classnames'
import React from 'react'
import Button from '@/app/components/base/button'
import { cn } from '@/utils/classnames'
type Props = {
className?: string
@ -21,11 +21,11 @@ const AddButton: FC<Props> = ({
return (
<Button
className={cn('w-full', className)}
variant='tertiary'
size='medium'
variant="tertiary"
size="medium"
onClick={onClick}
>
<RiAddLine className='mr-1 h-3.5 w-3.5' />
<RiAddLine className="mr-1 h-3.5 w-3.5" />
<div>{text}</div>
</Button>
)

View File

@ -1,22 +1,22 @@
import type {
ValueSelector,
Var,
VarType,
} from '../../../types'
import { useClickAway } from 'ahooks'
import {
memo,
useCallback,
useMemo,
useRef,
} from 'react'
import { useClickAway } from 'ahooks'
import { useStore } from '../../../store'
import {
useIsChatMode,
useNodeDataUpdate,
useWorkflow,
useWorkflowVariables,
} from '../../../hooks'
import type {
ValueSelector,
Var,
VarType,
} from '../../../types'
import { useStore } from '../../../store'
import { useVariableAssigner } from '../../variable-assigner/hooks'
import { filterVar } from '../../variable-assigner/utils'
import AddVariablePopup from './add-variable-popup'
@ -112,7 +112,7 @@ const AddVariablePopupWithPosition = ({
return (
<div
className='absolute z-10'
className="absolute z-10"
style={{
left: showAssignVariablePopup.x,
top: showAssignVariablePopup.y,

View File

@ -1,11 +1,11 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import type {
NodeOutPutVar,
ValueSelector,
Var,
} from '@/app/components/workflow/types'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
export type AddVariablePopupProps = {
availableVars: NodeOutPutVar[]
@ -18,11 +18,11 @@ export const AddVariablePopup = ({
const { t } = useTranslation()
return (
<div className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg'>
<div className='flex h-[34px] items-center border-b-[0.5px] border-b-divider-regular px-4 text-[13px] font-semibold text-text-secondary'>
<div className="w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg">
<div className="flex h-[34px] items-center border-b-[0.5px] border-b-divider-regular px-4 text-[13px] font-semibold text-text-secondary">
{t('workflow.nodes.variableAssigner.setAssignVariable')}
</div>
<div className='p-1'>
<div className="p-1">
<VarReferenceVars
hideSearch
vars={availableVars}

View File

@ -1,59 +1,62 @@
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import type { ReactNode } from 'react'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import type { Strategy } from './agent-strategy'
import { cn } from '@/utils/classnames'
import { RiArrowDownSLine, RiErrorWarningFill } from '@remixicon/react'
import Tooltip from '@/app/components/base/tooltip'
import Link from 'next/link'
import { InstallPluginButton } from './install-plugin-button'
import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select'
import SearchInput from '@/app/components/base/search-input'
import Tools from '../../../block-selector/tools'
import { useTranslation } from 'react-i18next'
import { useStrategyProviders } from '@/service/use-strategy'
import { PluginCategoryEnum, type StrategyPluginDetail } from '@/app/components/plugins/types'
import type { ToolWithProvider } from '../../../types'
import { CollectionType } from '@/app/components/tools/types'
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
import { useStrategyInfo } from '../../agent/use-config'
import { SwitchPluginVersion } from './switch-plugin-version'
import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import PluginList, { type ListProps } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks'
import type { Strategy } from './agent-strategy'
import type { StrategyPluginDetail } from '@/app/components/plugins/types'
import type { ListProps, ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { RiArrowDownSLine, RiErrorWarningFill } from '@remixicon/react'
import Link from 'next/link'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import SearchInput from '@/app/components/base/search-input'
import Tooltip from '@/app/components/base/tooltip'
import { ToolTipContent } from '@/app/components/base/tooltip/content'
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { CollectionType } from '@/app/components/tools/types'
import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useStrategyProviders } from '@/service/use-strategy'
import { cn } from '@/utils/classnames'
import Tools from '../../../block-selector/tools'
import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select'
import { useStrategyInfo } from '../../agent/use-config'
import { InstallPluginButton } from './install-plugin-button'
import { SwitchPluginVersion } from './switch-plugin-version'
const DEFAULT_TAGS: ListProps['tags'] = []
const NotFoundWarn = (props: {
title: ReactNode,
title: ReactNode
description: ReactNode
}) => {
const { title, description } = props
const { t } = useTranslation()
return <Tooltip
popupContent={
<div className='space-y-1 text-xs'>
<h3 className='font-semibold text-text-primary'>
{title}
</h3>
<p className='tracking-tight text-text-secondary'>
{description}
</p>
<p>
<Link href={'/plugins'} className='tracking-tight text-text-accent'>
{t('workflow.nodes.agent.linkToPlugin')}
</Link>
</p>
return (
<Tooltip
popupContent={(
<div className="space-y-1 text-xs">
<h3 className="font-semibold text-text-primary">
{title}
</h3>
<p className="tracking-tight text-text-secondary">
{description}
</p>
<p>
<Link href="/plugins" className="tracking-tight text-text-accent">
{t('workflow.nodes.agent.linkToPlugin')}
</Link>
</p>
</div>
)}
>
<div>
<RiErrorWarningFill className="size-4 text-text-destructive" />
</div>
}
>
<div>
<RiErrorWarningFill className='size-4 text-text-destructive' />
</div>
</Tooltip>
</Tooltip>
)
}
function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => string): ToolWithProvider[] {
@ -87,9 +90,9 @@ function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => s
}
export type AgentStrategySelectorProps = {
value?: Strategy,
onChange: (value?: Strategy) => void,
canChooseMCPTool: boolean,
value?: Strategy
onChange: (value?: Strategy) => void
canChooseMCPTool: boolean
}
export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => {
@ -103,7 +106,8 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) =>
const { getIconUrl } = useGetIcon()
const list = stra.data ? formatStrategy(stra.data, getIconUrl) : undefined
const filteredTools = useMemo(() => {
if (!list) return []
if (!list)
return []
return list.filter(tool => tool.name.toLowerCase().includes(query.toLowerCase()))
}, [query, list])
const { strategyStatus, refetch: refetchStrategyInfo } = useStrategyInfo(
@ -136,7 +140,8 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) =>
} = useMarketplacePlugins()
useEffect(() => {
if (!enable_marketplace) return
if (!enable_marketplace)
return
if (query) {
fetchPlugins({
query,
@ -147,97 +152,115 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) =>
const pluginRef = useRef<ListRef>(null)
return <PortalToFollowElem open={open} onOpenChange={setOpen} placement='bottom'>
<PortalToFollowElemTrigger className='w-full'>
<div
className='flex h-8 w-full select-none items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 hover:bg-state-base-hover-alt'
onClick={() => setOpen(o => !o)}
>
{ }
{icon && <div className='flex h-6 w-6 items-center justify-center'><img
src={icon}
width={20}
height={20}
className='rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'
alt='icon'
/></div>}
<p
className={cn(value ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder', 'px-1 text-xs')}
return (
<PortalToFollowElem open={open} onOpenChange={setOpen} placement="bottom">
<PortalToFollowElemTrigger className="w-full">
<div
className="flex h-8 w-full select-none items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 hover:bg-state-base-hover-alt"
onClick={() => setOpen(o => !o)}
>
{value?.agent_strategy_label || t('workflow.nodes.agent.strategy.selectTip')}
</p>
<div className='ml-auto flex items-center gap-1'>
{showInstallButton && value && <InstallPluginButton
onClick={e => e.stopPropagation()}
size={'small'}
uniqueIdentifier={value.plugin_unique_identifier}
/>}
{showPluginNotInstalledWarn
? <NotFoundWarn
title={t('workflow.nodes.agent.pluginNotInstalled')}
description={t('workflow.nodes.agent.pluginNotInstalledDesc')}
/>
: showUnsupportedStrategy
? <NotFoundWarn
title={t('workflow.nodes.agent.unsupportedStrategy')}
description={t('workflow.nodes.agent.strategyNotFoundDesc')}
{ }
{icon && (
<div className="flex h-6 w-6 items-center justify-center">
<img
src={icon}
width={20}
height={20}
className="rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge"
alt="icon"
/>
: <RiArrowDownSLine className='size-4 text-text-tertiary' />
}
{showSwitchVersion && <SwitchPluginVersion
uniqueIdentifier={value.plugin_unique_identifier}
tooltip={<ToolTipContent
title={t('workflow.nodes.agent.unsupportedStrategy')}>
{t('workflow.nodes.agent.strategyNotFoundDescAndSwitchVersion')}
</ToolTipContent>}
onChange={() => {
refetchStrategyInfo()
}}
/>}
</div>
)}
<p
className={cn(value ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder', 'px-1 text-xs')}
>
{value?.agent_strategy_label || t('workflow.nodes.agent.strategy.selectTip')}
</p>
<div className="ml-auto flex items-center gap-1">
{showInstallButton && value && (
<InstallPluginButton
onClick={e => e.stopPropagation()}
size="small"
uniqueIdentifier={value.plugin_unique_identifier}
/>
)}
{showPluginNotInstalledWarn
? (
<NotFoundWarn
title={t('workflow.nodes.agent.pluginNotInstalled')}
description={t('workflow.nodes.agent.pluginNotInstalledDesc')}
/>
)
: showUnsupportedStrategy
? (
<NotFoundWarn
title={t('workflow.nodes.agent.unsupportedStrategy')}
description={t('workflow.nodes.agent.strategyNotFoundDesc')}
/>
)
: <RiArrowDownSLine className="size-4 text-text-tertiary" />}
{showSwitchVersion && (
<SwitchPluginVersion
uniqueIdentifier={value.plugin_unique_identifier}
tooltip={(
<ToolTipContent
title={t('workflow.nodes.agent.unsupportedStrategy')}
>
{t('workflow.nodes.agent.strategyNotFoundDescAndSwitchVersion')}
</ToolTipContent>
)}
onChange={() => {
refetchStrategyInfo()
}}
/>
)}
</div>
</div>
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-10'>
<div className='w-[388px] overflow-hidden rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow'>
<header className='flex gap-1 p-2'>
<SearchInput placeholder={t('workflow.nodes.agent.strategy.searchPlaceholder')} value={query} onChange={setQuery} className={'w-full'} />
<ViewTypeSelect viewType={viewType} onChange={setViewType} />
</header>
<main className="relative flex w-full flex-col overflow-hidden md:max-h-[300px] xl:max-h-[400px] 2xl:max-h-[564px]" ref={wrapElemRef}>
<Tools
tools={filteredTools}
viewType={viewType}
onSelect={(_, tool) => {
onChange({
agent_strategy_name: tool!.tool_name,
agent_strategy_provider_name: tool!.provider_name,
agent_strategy_label: tool!.tool_label,
agent_output_schema: tool!.output_schema || {},
plugin_unique_identifier: tool!.provider_id,
meta: tool!.meta,
})
setOpen(false)
}}
className='h-full max-h-full max-w-none overflow-y-auto'
indexBarClassName='top-0 xl:top-36'
hasSearchText={false}
canNotSelectMultiple
canChooseMCPTool={canChooseMCPTool}
isAgent
/>
{enable_marketplace && <PluginList
ref={pluginRef}
wrapElemRef={wrapElemRef}
list={notInstalledPlugins}
searchText={query}
tags={DEFAULT_TAGS}
category={PluginCategoryEnum.agent}
disableMaxWidth
/>}
</main>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-10">
<div className="w-[388px] overflow-hidden rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow">
<header className="flex gap-1 p-2">
<SearchInput placeholder={t('workflow.nodes.agent.strategy.searchPlaceholder')} value={query} onChange={setQuery} className="w-full" />
<ViewTypeSelect viewType={viewType} onChange={setViewType} />
</header>
<main className="relative flex w-full flex-col overflow-hidden md:max-h-[300px] xl:max-h-[400px] 2xl:max-h-[564px]" ref={wrapElemRef}>
<Tools
tools={filteredTools}
viewType={viewType}
onSelect={(_, tool) => {
onChange({
agent_strategy_name: tool!.tool_name,
agent_strategy_provider_name: tool!.provider_name,
agent_strategy_label: tool!.tool_label,
agent_output_schema: tool!.output_schema || {},
plugin_unique_identifier: tool!.provider_id,
meta: tool!.meta,
})
setOpen(false)
}}
className="h-full max-h-full max-w-none overflow-y-auto"
indexBarClassName="top-0 xl:top-36"
hasSearchText={false}
canNotSelectMultiple
canChooseMCPTool={canChooseMCPTool}
isAgent
/>
{enable_marketplace && (
<PluginList
ref={pluginRef}
wrapElemRef={wrapElemRef}
list={notInstalledPlugins}
searchText={query}
tags={DEFAULT_TAGS}
category={PluginCategoryEnum.agent}
disableMaxWidth
/>
)}
</main>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
})
AgentStrategySelector.displayName = 'AgentStrategySelector'

View File

@ -1,28 +1,29 @@
import type { CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { type CredentialFormSchema, FormTypeEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { ToolVarInputs } from '../../tool/types'
import ListEmpty from '@/app/components/base/list-empty'
import { AgentStrategySelector } from './agent-strategy-selector'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import { InputNumber } from '@/app/components/base/input-number'
import Slider from '@/app/components/base/slider'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import Field from './field'
import { type ComponentProps, memo } from 'react'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import Editor from './prompt/editor'
import { useWorkflowStore } from '../../../store'
import { useRenderI18nObject } from '@/hooks/use-i18n'
import type { NodeOutPutVar } from '../../../types'
import type { ComponentProps } from 'react'
import type { Node } from 'reactflow'
import type { NodeOutPutVar } from '../../../types'
import type { ToolVarInputs } from '../../tool/types'
import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { PluginMeta } from '@/app/components/plugins/types'
import { noop } from 'lodash-es'
import Link from 'next/link'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import { InputNumber } from '@/app/components/base/input-number'
import ListEmpty from '@/app/components/base/list-empty'
import Slider from '@/app/components/base/slider'
import { FormTypeEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import { useDocLink } from '@/context/i18n'
import { useRenderI18nObject } from '@/hooks/use-i18n'
import { AppModeEnum } from '@/types/app'
import { useWorkflowStore } from '../../../store'
import { AgentStrategySelector } from './agent-strategy-selector'
import Field from './field'
import Editor from './prompt/editor'
export type Strategy = {
agent_strategy_provider_name: string
@ -39,8 +40,8 @@ export type AgentStrategyProps = {
formSchema: CredentialFormSchema[]
formValue: ToolVarInputs
onFormValueChange: (value: ToolVarInputs) => void
nodeOutputVars?: NodeOutPutVar[],
availableNodes?: Node[],
nodeOutputVars?: NodeOutPutVar[]
availableNodes?: Node[]
nodeId?: string
canChooseMCPTool: boolean
}
@ -78,38 +79,41 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
onChange(value)
setControlPromptEditorRerenderKey(Math.random())
}
return <Editor
value={value}
onChange={onChange}
onGenerated={handleGenerated}
instanceId={instanceId}
key={instanceId}
title={renderI18nObject(schema.label)}
headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase'
containerBackgroundClassName='bg-transparent'
gradientBorder={false}
nodeId={nodeId}
isSupportPromptGenerator={!!def.auto_generate?.type}
titleTooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
editorContainerClassName='px-0 bg-components-input-bg-normal focus-within:bg-components-input-bg-active rounded-lg'
availableNodes={availableNodes}
nodesOutputVars={nodeOutputVars}
isSupportJinja={def.template?.enabled}
required={def.required}
varList={[]}
modelConfig={
defaultModel.data
? {
mode: AppModeEnum.CHAT,
name: defaultModel.data.model,
provider: defaultModel.data.provider.provider,
completion_params: {},
} : undefined
}
placeholderClassName='px-2 py-1'
titleClassName='system-sm-semibold-uppercase text-text-secondary text-[13px]'
inputClassName='px-2 py-1'
/>
return (
<Editor
value={value}
onChange={onChange}
onGenerated={handleGenerated}
instanceId={instanceId}
key={instanceId}
title={renderI18nObject(schema.label)}
headerClassName="bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase"
containerBackgroundClassName="bg-transparent"
gradientBorder={false}
nodeId={nodeId}
isSupportPromptGenerator={!!def.auto_generate?.type}
titleTooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
editorContainerClassName="px-0 bg-components-input-bg-normal focus-within:bg-components-input-bg-active rounded-lg"
availableNodes={availableNodes}
nodesOutputVars={nodeOutputVars}
isSupportJinja={def.template?.enabled}
required={def.required}
varList={[]}
modelConfig={
defaultModel.data
? {
mode: AppModeEnum.CHAT,
name: defaultModel.data.model,
provider: defaultModel.data.provider.provider,
completion_params: {},
}
: undefined
}
placeholderClassName="px-2 py-1"
titleClassName="system-sm-semibold-uppercase text-text-secondary text-[13px]"
inputClassName="px-2 py-1"
/>
)
}
case FormTypeEnum.textNumber: {
const def = schema as CredentialFormSchemaNumberInput
@ -121,34 +125,40 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
const onChange = (value: number) => {
props.onChange({ ...props.value, [schema.variable]: value })
}
return <Field
title={<>
{renderI18nObject(def.label)} {def.required && <span className='text-red-500'>*</span>}
</>}
key={def.variable}
tooltip={def.tooltip && renderI18nObject(def.tooltip)}
inline
>
<div className='flex w-[200px] items-center gap-3'>
<Slider
value={value}
onChange={onChange}
className='w-full'
min={def.min}
max={def.max}
/>
<InputNumber
value={value}
// TODO: maybe empty, handle this
onChange={onChange as any}
defaultValue={defaultValue}
size='regular'
min={def.min}
max={def.max}
className='w-12'
/>
</div>
</Field>
return (
<Field
title={(
<>
{renderI18nObject(def.label)}
{' '}
{def.required && <span className="text-red-500">*</span>}
</>
)}
key={def.variable}
tooltip={def.tooltip && renderI18nObject(def.tooltip)}
inline
>
<div className="flex w-[200px] items-center gap-3">
<Slider
value={value}
onChange={onChange}
className="w-full"
min={def.min}
max={def.max}
/>
<InputNumber
value={value}
// TODO: maybe empty, handle this
onChange={onChange as any}
defaultValue={defaultValue}
size="regular"
min={def.min}
max={def.max}
className="w-12"
/>
</div>
</Field>
)
}
}
},
@ -162,9 +172,13 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
}
return (
<Field
title={<>
{renderI18nObject(schema.label)} {schema.required && <span className='text-red-500'>*</span>}
</>}
title={(
<>
{renderI18nObject(schema.label)}
{' '}
{schema.required && <span className="text-red-500">*</span>}
</>
)}
tooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
>
<ToolSelector
@ -204,46 +218,59 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
}
}
}
return <div className='space-y-2'>
<AgentStrategySelector value={strategy} onChange={onStrategyChange} canChooseMCPTool={canChooseMCPTool} />
{
strategy
? <div>
<Form<CustomField>
formSchemas={[
...formSchema,
]}
value={formValue}
onChange={onFormValueChange}
validating={false}
showOnVariableMap={{}}
isEditMode={true}
isAgentStrategy={true}
fieldLabelClassName='uppercase'
customRenderField={renderField}
override={override}
nodeId={nodeId}
nodeOutputVars={nodeOutputVars || []}
availableNodes={availableNodes || []}
canChooseMCPTool={canChooseMCPTool}
/>
</div>
: <ListEmpty
icon={<Agent className='h-5 w-5 shrink-0 text-text-accent' />}
title={t('workflow.nodes.agent.strategy.configureTip')}
description={<div className='text-xs text-text-tertiary'>
{t('workflow.nodes.agent.strategy.configureTipDesc')} <br />
<Link href={docLink('/guides/workflow/node/agent#select-an-agent-strategy', {
'zh-Hans': '/guides/workflow/node/agent#选择-agent-策略',
'ja-JP': '/guides/workflow/node/agent#エージェント戦略の選択',
})}
className='text-text-accent-secondary' target='_blank'>
{t('workflow.nodes.agent.learnMore')}
</Link>
</div>}
/>
}
</div>
return (
<div className="space-y-2">
<AgentStrategySelector value={strategy} onChange={onStrategyChange} canChooseMCPTool={canChooseMCPTool} />
{
strategy
? (
<div>
<Form<CustomField>
formSchemas={[
...formSchema,
]}
value={formValue}
onChange={onFormValueChange}
validating={false}
showOnVariableMap={{}}
isEditMode={true}
isAgentStrategy={true}
fieldLabelClassName="uppercase"
customRenderField={renderField}
override={override}
nodeId={nodeId}
nodeOutputVars={nodeOutputVars || []}
availableNodes={availableNodes || []}
canChooseMCPTool={canChooseMCPTool}
/>
</div>
)
: (
<ListEmpty
icon={<Agent className="h-5 w-5 shrink-0 text-text-accent" />}
title={t('workflow.nodes.agent.strategy.configureTip')}
description={(
<div className="text-xs text-text-tertiary">
{t('workflow.nodes.agent.strategy.configureTipDesc')}
{' '}
<br />
<Link
href={docLink('/guides/workflow/node/agent#select-an-agent-strategy', {
'zh-Hans': '/guides/workflow/node/agent#选择-agent-策略',
'ja-JP': '/guides/workflow/node/agent#エージェント戦略の選択',
})}
className="text-text-accent-secondary"
target="_blank"
>
{t('workflow.nodes.agent.learnMore')}
</Link>
</div>
)}
/>
)
}
</div>
)
})
AgentStrategy.displayName = 'AgentStrategy'

View File

@ -1,8 +1,8 @@
'use client'
import Checkbox from '@/app/components/base/checkbox'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Checkbox from '@/app/components/base/checkbox'
type Props = {
name: string
@ -22,15 +22,15 @@ const BoolInput: FC<Props> = ({
onChange(!value)
}, [value, onChange])
return (
<div className='flex h-6 items-center gap-2'>
<div className="flex h-6 items-center gap-2">
<Checkbox
className='!h-4 !w-4'
className="!h-4 !w-4"
checked={!!value}
onCheck={handleChange}
/>
<div className='system-sm-medium flex items-center gap-1 text-text-secondary'>
<div className="system-sm-medium flex items-center gap-1 text-text-secondary">
{name}
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
{!required && <span className="system-xs-regular text-text-tertiary">{t('workflow.panel.optional')}</span>}
</div>
</div>
)

View File

@ -1,31 +1,31 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import type { InputVar } from '../../../../types'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import {
RiDeleteBinLine,
} from '@remixicon/react'
import type { InputVar } from '../../../../types'
import { BlockEnum, InputVarType, SupportUploadFileTypes } from '../../../../types'
import CodeEditor from '../editor/code-editor'
import { CodeLanguage } from '../../../code/types'
import TextEditor from '../editor/text-editor'
import Select from '@/app/components/base/select'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import { produce } from 'immer'
import React, { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { Resolution, TransferMethod } from '@/types/app'
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
import { Line3 } from '@/app/components/base/icons/src/public/common'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import Input from '@/app/components/base/input'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { cn } from '@/utils/classnames'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import BoolInput from './bool-input'
import Select from '@/app/components/base/select'
import Textarea from '@/app/components/base/textarea'
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { Resolution, TransferMethod } from '@/types/app'
import { cn } from '@/utils/classnames'
import { BlockEnum, InputVarType, SupportUploadFileTypes } from '../../../../types'
import { CodeLanguage } from '../../../code/types'
import CodeEditor from '../editor/code-editor'
import TextEditor from '../editor/text-editor'
import BoolInput from './bool-input'
type Props = {
payload: InputVar
@ -69,22 +69,22 @@ const FormItem: FC<Props> = ({
if (typeof payload.label === 'object') {
const { nodeType, nodeName, variable, isChatVar } = payload.label
return (
<div className='flex h-full items-center'>
<div className="flex h-full items-center">
{!isChatVar && (
<div className='flex items-center'>
<div className='p-[1px]'>
<div className="flex items-center">
<div className="p-[1px]">
<VarBlockIcon type={nodeType || BlockEnum.Start} />
</div>
<div className='mx-0.5 max-w-[150px] truncate text-xs font-medium text-gray-700' title={nodeName}>
<div className="mx-0.5 max-w-[150px] truncate text-xs font-medium text-gray-700" title={nodeName}>
{nodeName}
</div>
<Line3 className='mr-0.5'></Line3>
<Line3 className="mr-0.5"></Line3>
</div>
)}
<div className='flex items-center text-primary-600'>
{!isChatVar && <Variable02 className='h-3.5 w-3.5' />}
{isChatVar && <BubbleX className='h-3.5 w-3.5 text-util-colors-teal-teal-700' />}
<div className={cn('ml-0.5 max-w-[150px] truncate text-xs font-medium', isChatVar && 'text-text-secondary')} title={variable} >
<div className="flex items-center text-primary-600">
{!isChatVar && <Variable02 className="h-3.5 w-3.5" />}
{isChatVar && <BubbleX className="h-3.5 w-3.5 text-util-colors-teal-teal-700" />}
<div className={cn('ml-0.5 max-w-[150px] truncate text-xs font-medium', isChatVar && 'text-text-secondary')} title={variable}>
{variable}
</div>
</div>
@ -117,24 +117,26 @@ const FormItem: FC<Props> = ({
return (
<div className={cn(className)}>
{!isArrayLikeType && !isBooleanType && (
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
<div className='truncate'>
<div className="system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary">
<div className="truncate">
{typeof payload.label === 'object' ? nodeKey : payload.label}
</div>
{payload.hide === true ? (
<span className='system-xs-regular text-text-tertiary'>
{t('workflow.panel.optional_and_hidden')}
</span>
) : (
!payload.required && (
<span className='system-xs-regular text-text-tertiary'>
{t('workflow.panel.optional')}
</span>
)
)}
{payload.hide === true
? (
<span className="system-xs-regular text-text-tertiary">
{t('workflow.panel.optional_and_hidden')}
</span>
)
: (
!payload.required && (
<span className="system-xs-regular text-text-tertiary">
{t('workflow.panel.optional')}
</span>
)
)}
</div>
)}
<div className='grow'>
<div className="grow">
{
type === InputVarType.textInput && (
<Input
@ -206,9 +208,9 @@ const FormItem: FC<Props> = ({
language={CodeLanguage.json}
onChange={onChange}
noWrapper
className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1'
className="bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1"
placeholder={
<div className='whitespace-pre'>{payload.json_schema}</div>
<div className="whitespace-pre">{payload.json_schema}</div>
}
/>
)}
@ -219,19 +221,19 @@ const FormItem: FC<Props> = ({
fileConfig={{
allowed_file_types: inStepRun && (!payload.allowed_file_types || payload.allowed_file_types.length === 0)
? [
SupportUploadFileTypes.image,
SupportUploadFileTypes.document,
SupportUploadFileTypes.audio,
SupportUploadFileTypes.video,
]
SupportUploadFileTypes.image,
SupportUploadFileTypes.document,
SupportUploadFileTypes.audio,
SupportUploadFileTypes.video,
]
: payload.allowed_file_types,
allowed_file_extensions: inStepRun && (!payload.allowed_file_extensions || payload.allowed_file_extensions.length === 0)
? [
...FILE_EXTS[SupportUploadFileTypes.image],
...FILE_EXTS[SupportUploadFileTypes.document],
...FILE_EXTS[SupportUploadFileTypes.audio],
...FILE_EXTS[SupportUploadFileTypes.video],
]
...FILE_EXTS[SupportUploadFileTypes.image],
...FILE_EXTS[SupportUploadFileTypes.document],
...FILE_EXTS[SupportUploadFileTypes.audio],
...FILE_EXTS[SupportUploadFileTypes.video],
]
: payload.allowed_file_extensions,
allowed_file_upload_methods: inStepRun ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods,
number_limits: 1,
@ -246,19 +248,19 @@ const FormItem: FC<Props> = ({
fileConfig={{
allowed_file_types: (inStepRun || isIteratorItemFile) && (!payload.allowed_file_types || payload.allowed_file_types.length === 0)
? [
SupportUploadFileTypes.image,
SupportUploadFileTypes.document,
SupportUploadFileTypes.audio,
SupportUploadFileTypes.video,
]
SupportUploadFileTypes.image,
SupportUploadFileTypes.document,
SupportUploadFileTypes.audio,
SupportUploadFileTypes.video,
]
: payload.allowed_file_types,
allowed_file_extensions: (inStepRun || isIteratorItemFile) && (!payload.allowed_file_extensions || payload.allowed_file_extensions.length === 0)
? [
...FILE_EXTS[SupportUploadFileTypes.image],
...FILE_EXTS[SupportUploadFileTypes.document],
...FILE_EXTS[SupportUploadFileTypes.audio],
...FILE_EXTS[SupportUploadFileTypes.video],
]
...FILE_EXTS[SupportUploadFileTypes.image],
...FILE_EXTS[SupportUploadFileTypes.document],
...FILE_EXTS[SupportUploadFileTypes.audio],
...FILE_EXTS[SupportUploadFileTypes.video],
]
: payload.allowed_file_extensions,
allowed_file_upload_methods: (inStepRun || isIteratorItemFile) ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods,
number_limits: (inStepRun || isIteratorItemFile) ? 5 : payload.max_length,
@ -286,7 +288,7 @@ const FormItem: FC<Props> = ({
{
isContext && (
<div className='space-y-2'>
<div className="space-y-2">
{(value || []).map((item: any, index: number) => (
<CodeEditor
key={index}
@ -294,10 +296,12 @@ const FormItem: FC<Props> = ({
title={<span>JSON</span>}
headerRight={
(value as any).length > 1
? (<RiDeleteBinLine
onClick={handleArrayItemRemove(index)}
className='mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary'
/>)
? (
<RiDeleteBinLine
onClick={handleArrayItemRemove(index)}
className="mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary"
/>
)
: undefined
}
language={CodeLanguage.json}
@ -310,20 +314,29 @@ const FormItem: FC<Props> = ({
{
(isIterator && !isIteratorItemFile) && (
<div className='space-y-2'>
<div className="space-y-2">
{(value || []).map((item: any, index: number) => (
<TextEditor
key={index}
isInNode
value={item}
title={<span>{t('appDebug.variableConfig.content')} {index + 1} </span>}
title={(
<span>
{t('appDebug.variableConfig.content')}
{' '}
{index + 1}
{' '}
</span>
)}
onChange={handleArrayItemChange(index)}
headerRight={
(value as any).length > 1
? (<RiDeleteBinLine
onClick={handleArrayItemRemove(index)}
className='mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary'
/>)
? (
<RiDeleteBinLine
onClick={handleArrayItemRemove(index)}
className="mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary"
/>
)
: undefined
}
/>

View File

@ -1,13 +1,13 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { produce } from 'immer'
import type { InputVar } from '../../../../types'
import FormItem from './form-item'
import { cn } from '@/utils/classnames'
import { InputVarType } from '@/app/components/workflow/types'
import { produce } from 'immer'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import AddButton from '@/app/components/base/button/add-button'
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
import { InputVarType } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
import FormItem from './form-item'
export type Props = {
className?: string
@ -77,8 +77,8 @@ const Form: FC<Props> = ({
return (
<div className={cn(className, 'space-y-2')}>
{label && (
<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="mb-1 flex items-center justify-between">
<div className="system-xs-medium-uppercase flex h-6 items-center text-text-tertiary">{label}</div>
{isArrayLikeType && !isIteratorItemFile && (
<AddButton onClick={handleAddContext} />
)}

View File

@ -1,20 +1,21 @@
'use client'
import type { FC } from 'react'
import React, { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import type { Props as FormProps } from './form'
import Form from './form'
import { cn } from '@/utils/classnames'
import Button from '@/app/components/base/button'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import { InputVarType } from '@/app/components/workflow/types'
import Toast from '@/app/components/base/toast'
import { TransferMethod } from '@/types/app'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
import type { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types'
import type { Emoji } from '@/app/components/tools/types'
import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel'
import type { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types'
import React, { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
import Toast from '@/app/components/base/toast'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import { InputVarType } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app'
import { cn } from '@/utils/classnames'
import Form from './form'
import PanelWrap from './panel-wrap'
const i18nPrefix = 'workflow.singleRun'
export type BeforeRunFormProps = {
@ -32,9 +33,9 @@ export type BeforeRunFormProps = {
} & Partial<SpecialResultPanelProps>
function formatValue(value: string | any, type: InputVarType) {
if(type === InputVarType.checkbox)
if (type === InputVarType.checkbox)
return !!value
if(value === undefined || value === null)
if (value === undefined || value === null)
return value
if (type === InputVarType.number)
return Number.parseFloat(value)
@ -138,14 +139,14 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
const hasRun = useRef(false)
useEffect(() => {
// React 18 run twice in dev mode
if(hasRun.current)
if (hasRun.current)
return
hasRun.current = true
if(filteredExistVarForms.length === 0)
if (filteredExistVarForms.length === 0)
onRun({})
}, [filteredExistVarForms, onRun])
if(filteredExistVarForms.length === 0)
if (filteredExistVarForms.length === 0)
return null
return (
@ -153,8 +154,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
nodeName={nodeName}
onHide={onHide}
>
<div className='h-0 grow overflow-y-auto pb-4'>
<div className='mt-3 space-y-4 px-4'>
<div className="h-0 grow overflow-y-auto pb-4">
<div className="mt-3 space-y-4 px-4">
{filteredExistVarForms.map((form, index) => (
<div key={index}>
<Form
@ -166,8 +167,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
</div>
))}
</div>
<div className='mt-4 flex justify-between space-x-2 px-4' >
<Button disabled={!isFileLoaded} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
<div className="mt-4 flex justify-between space-x-2 px-4">
<Button disabled={!isFileLoaded} variant="primary" className="w-0 grow space-x-2" onClick={handleRun}>
<div>{t(`${i18nPrefix}.startRun`)}</div>
</Button>
</div>

View File

@ -1,10 +1,10 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import {
RiCloseLine,
} from '@remixicon/react'
import React from 'react'
import { useTranslation } from 'react-i18next'
const i18nPrefix = 'workflow.singleRun'
@ -21,16 +21,21 @@ const PanelWrap: FC<Props> = ({
}) => {
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 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
className="ml-2 shrink-0 cursor-pointer p-1"
onClick={() => {
onHide()
}}
>
<RiCloseLine className="h-4 w-4 text-text-tertiary " />
</div>
</div>
{children}

View File

@ -1,14 +1,14 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useBoolean } from 'ahooks'
import { cn } from '@/utils/classnames'
import type { CodeLanguage } from '../../code/types'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import { ActionButton } from '@/app/components/base/action-button'
import { AppModeEnum } from '@/types/app'
import type { GenRes } from '@/service/debug'
import { useBoolean } from 'ahooks'
import React, { useCallback } from 'react'
import { GetCodeGeneratorResModal } from '@/app/components/app/configuration/config/code-generator/get-code-generator-res'
import { ActionButton } from '@/app/components/base/action-button'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import { AppModeEnum } from '@/types/app'
import { cn } from '@/utils/classnames'
import { useHooksStore } from '../../../hooks-store'
type Props = {
@ -36,9 +36,10 @@ const CodeGenerateBtn: FC<Props> = ({
return (
<div className={cn(className)}>
<ActionButton
className='hover:bg-[#155EFF]/8'
onClick={showAutomaticTrue}>
<Generator className='h-4 w-4 text-primary-600' />
className="hover:bg-[#155EFF]/8"
onClick={showAutomaticTrue}
>
<Generator className="h-4 w-4 text-primary-600" />
</ActionButton>
{showAutomatic && (
<GetCodeGeneratorResModal

View File

@ -16,16 +16,16 @@ const FieldCollapse = ({
operations,
}: FieldCollapseProps) => {
return (
<div className='py-4'>
<div className="py-4">
<Collapse
trigger={
<div className='system-sm-semibold-uppercase flex h-6 cursor-pointer items-center text-text-secondary'>{title}</div>
<div className="system-sm-semibold-uppercase flex h-6 cursor-pointer items-center text-text-secondary">{title}</div>
}
operations={operations}
collapsed={collapsed}
onCollapse={onCollapse}
>
<div className='px-4'>
<div className="px-4">
{children}
</div>
</Collapse>

View File

@ -40,9 +40,9 @@ const Collapse = ({
}, [collapsedMerged, disabled])
return (
<>
<div className='group/collapse flex items-center'>
<div className="group/collapse flex items-center">
<div
className='ml-4 flex grow items-center'
className="ml-4 flex grow items-center"
onClick={() => {
if (!disabled) {
setCollapsedLocal(!collapsedMerged)
@ -52,7 +52,7 @@ const Collapse = ({
>
{typeof trigger === 'function' ? trigger(collapseIcon) : trigger}
{!hideCollapseIcon && (
<div className='h-4 w-4 shrink-0'>
<div className="h-4 w-4 shrink-0">
{collapseIcon}
</div>
)}

View File

@ -1,15 +1,17 @@
'use client'
import type { FC } from 'react'
import type { ValueSelector, Var, VisionSetting } from '@/app/components/workflow/types'
import { produce } from 'immer'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import VarReferencePicker from './variable/var-reference-picker'
import ResolutionPicker from '@/app/components/workflow/nodes/llm/components/resolution-picker'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import Switch from '@/app/components/base/switch'
import { type ValueSelector, type Var, VarType, type VisionSetting } from '@/app/components/workflow/types'
import { Resolution } from '@/types/app'
import Tooltip from '@/app/components/base/tooltip'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import ResolutionPicker from '@/app/components/workflow/nodes/llm/components/resolution-picker'
import { VarType } from '@/app/components/workflow/types'
import { Resolution } from '@/types/app'
import VarReferencePicker from './variable/var-reference-picker'
const i18nPrefix = 'workflow.nodes.llm'
type Props = {
@ -57,32 +59,32 @@ const ConfigVision: FC<Props> = ({
<Field
title={t(`${i18nPrefix}.vision`)}
tooltip={t('appDebug.vision.description')!}
operations={
operations={(
<Tooltip
popupContent={t('appDebug.vision.onlySupportVisionModelTip')!}
disabled={isVisionModel}
>
<Switch disabled={readOnly || !isVisionModel} size='md' defaultValue={!isVisionModel ? false : enabled} onChange={onEnabledChange} />
<Switch disabled={readOnly || !isVisionModel} size="md" defaultValue={!isVisionModel ? false : enabled} onChange={onEnabledChange} />
</Tooltip>
}
)}
>
{(enabled && isVisionModel)
? (
<div>
<VarReferencePicker
className='mb-4'
filterVar={filterVar}
nodeId={nodeId}
value={config.variable_selector || []}
onChange={handleVarSelectorChange}
readonly={readOnly}
/>
<ResolutionPicker
value={config.detail}
onChange={handleVisionResolutionChange}
/>
</div>
)
<div>
<VarReferencePicker
className="mb-4"
filterVar={filterVar}
nodeId={nodeId}
value={config.variable_selector || []}
onChange={handleVarSelectorChange}
readonly={readOnly}
/>
<ResolutionPicker
value={config.detail}
onChange={handleVisionResolutionChange}
/>
</div>
)
: null}
</Field>

View File

@ -1,22 +1,22 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useRef, useState } from 'react'
import copy from 'copy-to-clipboard'
import ToggleExpandBtn from '../toggle-expand-btn'
import CodeGeneratorButton from '../code-generator-button'
import type { CodeLanguage } from '../../../code/types'
import Wrap from './wrap'
import { cn } from '@/utils/classnames'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import copy from 'copy-to-clipboard'
import React, { useCallback, useRef, useState } from 'react'
import PromptEditorHeightResizeWrap from '@/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap'
import ActionButton from '@/app/components/base/action-button'
import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log'
import {
Copy,
CopyCheck,
} from '@/app/components/base/icons/src/vender/line/files'
import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log'
import ActionButton from '@/app/components/base/action-button'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
import CodeGeneratorButton from '../code-generator-button'
import ToggleExpandBtn from '../toggle-expand-btn'
import Wrap from './wrap'
type Props = {
nodeId?: string
@ -84,15 +84,18 @@ const Base: FC<Props> = ({
return (
<Wrap className={cn(wrapClassName)} style={wrapStyle} isInNode={isInNode} isExpand={isExpand}>
<div ref={ref} className={cn(className, isExpand && 'h-full', 'rounded-lg border', !isFocus ? 'border-transparent bg-components-input-bg-normal' : 'overflow-hidden border-components-input-border-hover bg-components-input-bg-hover')}>
<div className='flex h-7 items-center justify-between pl-3 pr-2 pt-1'>
<div className='system-xs-semibold-uppercase text-text-secondary'>{title}</div>
<div className='flex items-center' onClick={(e) => {
e.nativeEvent.stopImmediatePropagation()
e.stopPropagation()
}}>
<div className="flex h-7 items-center justify-between pl-3 pr-2 pt-1">
<div className="system-xs-semibold-uppercase text-text-secondary">{title}</div>
<div
className="flex items-center"
onClick={(e) => {
e.nativeEvent.stopImmediatePropagation()
e.stopPropagation()
}}
>
{headerRight}
{showCodeGenerator && codeLanguages && (
<div className='ml-1'>
<div className="ml-1">
<CodeGeneratorButton
onGenerated={onGenerated}
codeLanguages={codeLanguages}
@ -101,29 +104,28 @@ const Base: FC<Props> = ({
/>
</div>
)}
<ActionButton className='ml-1' onClick={handleCopy}>
<ActionButton className="ml-1" onClick={handleCopy}>
{!isCopied
? (
<Copy className='h-4 w-4 cursor-pointer' />
)
<Copy className="h-4 w-4 cursor-pointer" />
)
: (
<CopyCheck className='h-4 w-4' />
)
}
<CopyCheck className="h-4 w-4" />
)}
</ActionButton>
<div className='ml-1'>
<div className="ml-1">
<ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} />
</div>
</div>
</div>
{tip && <div className='px-1 py-0.5'>{tip}</div>}
{tip && <div className="px-1 py-0.5">{tip}</div>}
<PromptEditorHeightResizeWrap
height={isExpand ? editorExpandHeight : editorContentHeight}
minHeight={editorContentMinHeight}
onHeightChange={setEditorContentHeight}
hideResize={isExpand}
>
<div className='h-full pb-2 pl-2'>
<div className="h-full pb-2 pl-2">
{children}
</div>
</PromptEditorHeightResizeWrap>

View File

@ -1,13 +1,13 @@
'use client'
import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { useBoolean } from 'ahooks'
import { useTranslation } from 'react-i18next'
import type { Props as EditorProps } from '.'
import Editor from '.'
import { cn } from '@/utils/classnames'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import type { NodeOutPutVar, Variable } from '@/app/components/workflow/types'
import { useBoolean } from 'ahooks'
import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import { cn } from '@/utils/classnames'
import Editor from '.'
const TO_WINDOW_OFFSET = 8
@ -149,7 +149,7 @@ const CodeEditor: FC<Props> = ({
{isShowVarPicker && (
<div
ref={popupRef}
className='w-[228px] space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg'
className="w-[228px] space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg"
style={{
position: 'fixed',
top: popupPosition.y,

View File

@ -1,18 +1,18 @@
'use client'
import type { FC } from 'react'
import Editor, { loader } from '@monaco-editor/react'
import { noop } from 'lodash-es'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import Base from '../base'
import { cn } from '@/utils/classnames'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import {
getFilesInLogs,
} from '@/app/components/base/file-uploader/utils'
import { Theme } from '@/types/app'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import useTheme from '@/hooks/use-theme'
import './style.css'
import { noop } from 'lodash-es'
import { Theme } from '@/types/app'
import { cn } from '@/utils/classnames'
import { basePath } from '@/utils/var'
import Base from '../base'
import './style.css'
// load file from local instead of cdn https://github.com/suren-atoyan/monaco-react/issues/482
if (typeof window !== 'undefined')
@ -145,7 +145,7 @@ const CodeEditor: FC<Props> = ({
language={languageMap[language] || 'javascript'}
theme={isMounted ? theme : 'default-theme'} // sometimes not load the default theme
value={outPutValue}
loading={<span className='text-text-primary'>Loading...</span>}
loading={<span className="text-text-primary">Loading...</span>}
onChange={handleEditorChange}
// https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOptions.html
options={{
@ -166,40 +166,45 @@ const CodeEditor: FC<Props> = ({
}}
onMount={handleEditorDidMount}
/>
{!outPutValue && !isFocus && <div className='pointer-events-none absolute left-[36px] top-0 text-[13px] font-normal leading-[18px] text-gray-300'>{placeholder}</div>}
{!outPutValue && !isFocus && <div className="pointer-events-none absolute left-[36px] top-0 text-[13px] font-normal leading-[18px] text-gray-300">{placeholder}</div>}
</>
)
return (
<div className={cn(isExpand && 'h-full', className)}>
{noWrapper
? <div className='no-wrapper relative' style={{
height: isExpand ? '100%' : (editorContentHeight) / 2 + CODE_EDITOR_LINE_HEIGHT, // In IDE, the last line can always be in lop line. So there is some blank space in the bottom.
minHeight: CODE_EDITOR_LINE_HEIGHT,
}}>
{main}
</div>
? (
<div
className="no-wrapper relative"
style={{
height: isExpand ? '100%' : (editorContentHeight) / 2 + CODE_EDITOR_LINE_HEIGHT, // In IDE, the last line can always be in lop line. So there is some blank space in the bottom.
minHeight: CODE_EDITOR_LINE_HEIGHT,
}}
>
{main}
</div>
)
: (
<Base
nodeId={nodeId}
className='relative'
title={title}
value={outPutValue}
headerRight={headerRight}
isFocus={isFocus && !readOnly}
minHeight={minHeight}
isInNode={isInNode}
onGenerated={onGenerated}
codeLanguages={language}
fileList={fileList as any}
showFileList={showFileList}
showCodeGenerator={showCodeGenerator}
tip={tip}
footer={footer}
>
{main}
</Base>
)}
<Base
nodeId={nodeId}
className="relative"
title={title}
value={outPutValue}
headerRight={headerRight}
isFocus={isFocus && !readOnly}
minHeight={minHeight}
isInNode={isInNode}
onGenerated={onGenerated}
codeLanguages={language}
fileList={fileList as any}
showFileList={showFileList}
showCodeGenerator={showCodeGenerator}
tip={tip}
footer={footer}
>
{main}
</Base>
)}
</div>
)
}

View File

@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useBoolean } from 'ahooks'
import React, { useCallback } from 'react'
import Base from './base'
type Props = {
@ -52,7 +52,7 @@ const TextEditor: FC<Props> = ({
onChange={e => onChange(e.target.value)}
onFocus={setIsFocus}
onBlur={handleBlur}
className='h-full w-full resize-none border-none bg-transparent px-3 text-[13px] font-normal leading-[18px] text-gray-900 placeholder:text-gray-300 focus:outline-none'
className="h-full w-full resize-none border-none bg-transparent px-3 text-[13px] font-normal leading-[18px] text-gray-900 placeholder:text-gray-300 focus:outline-none"
placeholder={placeholder}
readOnly={readonly}
/>

View File

@ -1,10 +1,10 @@
import type { DefaultValueForm } from './types'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import type { DefaultValueForm } from './types'
import Input from '@/app/components/base/input'
import { VarType } from '@/app/components/workflow/types'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { VarType } from '@/app/components/workflow/types'
import { useDocLink } from '@/context/i18n'
type DefaultValueProps = {
@ -31,31 +31,31 @@ const DefaultValue = ({
}, [onFormChange])
return (
<div className='px-4 pt-2'>
<div className='body-xs-regular mb-2 text-text-tertiary'>
<div className="px-4 pt-2">
<div className="body-xs-regular mb-2 text-text-tertiary">
{t('workflow.nodes.common.errorHandle.defaultValue.desc')}
&nbsp;
<a
href={docLink('/guides/workflow/error-handling/README', {
'zh-Hans': '/guides/workflow/error-handling/readme',
})}
target='_blank'
className='text-text-accent'
target="_blank"
className="text-text-accent"
>
{t('workflow.common.learnMore')}
</a>
</div>
<div className='space-y-1'>
<div className="space-y-1">
{
forms.map((form, index) => {
return (
<div
key={index}
className='py-1'
className="py-1"
>
<div className='mb-1 flex items-center'>
<div className='system-sm-medium mr-1 text-text-primary'>{form.key}</div>
<div className='system-xs-regular text-text-tertiary'>{form.type}</div>
<div className="mb-1 flex items-center">
<div className="system-sm-medium mr-1 text-text-primary">{form.key}</div>
<div className="system-xs-regular text-text-tertiary">{form.type}</div>
</div>
{
(form.type === VarType.string || form.type === VarType.number) && (

View File

@ -1,11 +1,11 @@
import type { Node } from '@/app/components/workflow/types'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useUpdateNodeInternals } from 'reactflow'
import { NodeSourceHandle } from '../node-handle'
import { ErrorHandleTypeEnum } from './types'
import type { Node } from '@/app/components/workflow/types'
import { NodeRunningStatus } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
import { NodeSourceHandle } from '../node-handle'
import { ErrorHandleTypeEnum } from './types'
type ErrorHandleOnNodeProps = Pick<Node, 'id' | 'data'>
const ErrorHandleOnNode = ({
@ -25,18 +25,20 @@ const ErrorHandleOnNode = ({
return null
return (
<div className='relative px-3 pb-2 pt-1'>
<div className="relative px-3 pb-2 pt-1">
<div className={cn(
'relative flex h-6 items-center justify-between rounded-md bg-workflow-block-parma-bg px-[5px]',
data._runningStatus === NodeRunningStatus.Exception && 'border-[0.5px] border-components-badge-status-light-warning-halo bg-state-warning-hover',
)}>
<div className='system-xs-medium-uppercase text-text-tertiary'>
)}
>
<div className="system-xs-medium-uppercase text-text-tertiary">
{t('workflow.common.onFailure')}
</div>
<div className={cn(
'system-xs-medium text-text-secondary',
data._runningStatus === NodeRunningStatus.Exception && 'text-text-warning',
)}>
)}
>
{
error_strategy === ErrorHandleTypeEnum.defaultValue && (
t('workflow.nodes.common.errorHandle.defaultValue.output')
@ -54,8 +56,8 @@ const ErrorHandleOnNode = ({
id={id}
data={data}
handleId={ErrorHandleTypeEnum.failBranch}
handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg'
nodeSelectorClassName='!bg-workflow-link-line-failure-button-bg'
handleClassName="!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg"
nodeSelectorClassName="!bg-workflow-link-line-failure-button-bg"
/>
)
}

View File

@ -1,20 +1,20 @@
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Collapse from '../collapse'
import { ErrorHandleTypeEnum } from './types'
import ErrorHandleTypeSelector from './error-handle-type-selector'
import FailBranchCard from './fail-branch-card'
import DefaultValue from './default-value'
import {
useDefaultValue,
useErrorHandle,
} from './hooks'
import type { DefaultValueForm } from './types'
import type {
CommonNodeType,
Node,
} from '@/app/components/workflow/types'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import Collapse from '../collapse'
import DefaultValue from './default-value'
import ErrorHandleTypeSelector from './error-handle-type-selector'
import FailBranchCard from './fail-branch-card'
import {
useDefaultValue,
useErrorHandle,
} from './hooks'
import { ErrorHandleTypeEnum } from './types'
type ErrorHandleProps = Pick<Node, 'id' | 'data'>
const ErrorHandle = ({
@ -44,7 +44,7 @@ const ErrorHandle = ({
return (
<>
<div className='py-4'>
<div className="py-4">
<Collapse
disabled={!error_strategy}
collapsed={collapsed}
@ -52,9 +52,9 @@ const ErrorHandle = ({
hideCollapseIcon
trigger={
collapseIcon => (
<div className='flex grow items-center justify-between pr-4'>
<div className='flex items-center'>
<div className='system-sm-semibold-uppercase mr-0.5 text-text-secondary'>
<div className="flex grow items-center justify-between pr-4">
<div className="flex items-center">
<div className="system-sm-semibold-uppercase mr-0.5 text-text-secondary">
{t('workflow.nodes.common.errorHandle.title')}
</div>
<Tooltip popupContent={t('workflow.nodes.common.errorHandle.tip')} />
@ -65,7 +65,8 @@ const ErrorHandle = ({
onSelected={getHandleErrorHandleTypeChange(data)}
/>
</div>
)}
)
}
>
<>
{

View File

@ -1,6 +1,6 @@
import { RiAlertFill } from '@remixicon/react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { RiAlertFill } from '@remixicon/react'
import { ErrorHandleTypeEnum } from './types'
type ErrorHandleTipProps = {
@ -24,16 +24,17 @@ const ErrorHandleTip = ({
return (
<div
className='relative flex rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 pr-[52px] shadow-xs'
className="relative flex rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 pr-[52px] shadow-xs"
>
<div
className='absolute inset-0 rounded-lg opacity-40'
className="absolute inset-0 rounded-lg opacity-40"
style={{
background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)',
}}
></div>
<RiAlertFill className='mr-1 h-4 w-4 shrink-0 text-text-warning-secondary' />
<div className='system-xs-medium grow text-text-primary'>
>
</div>
<RiAlertFill className="mr-1 h-4 w-4 shrink-0 text-text-warning-secondary" />
<div className="system-xs-medium grow text-text-primary">
{text}
</div>
</div>

View File

@ -1,16 +1,16 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowDownSLine,
RiCheckLine,
} from '@remixicon/react'
import { ErrorHandleTypeEnum } from './types'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Button from '@/app/components/base/button'
import { ErrorHandleTypeEnum } from './types'
type ErrorHandleTypeSelectorProps = {
value: ErrorHandleTypeEnum
@ -45,28 +45,29 @@ const ErrorHandleTypeSelector = ({
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
placement="bottom-end"
offset={4}
>
<PortalToFollowElemTrigger onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
setOpen(v => !v)
}}>
}}
>
<Button
size='small'
size="small"
>
{selectedOption?.label}
<RiArrowDownSLine className='h-3.5 w-3.5' />
<RiArrowDownSLine className="h-3.5 w-3.5" />
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[11]'>
<div className='w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<PortalToFollowElemContent className="z-[11]">
<div className="w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg">
{
options.map(option => (
<div
key={option.value}
className='flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover'
className="flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover"
onClick={(e) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
@ -74,16 +75,16 @@ const ErrorHandleTypeSelector = ({
setOpen(false)
}}
>
<div className='mr-1 w-4 shrink-0'>
<div className="mr-1 w-4 shrink-0">
{
value === option.value && (
<RiCheckLine className='h-4 w-4 text-text-accent' />
<RiCheckLine className="h-4 w-4 text-text-accent" />
)
}
</div>
<div className='grow'>
<div className='system-sm-semibold mb-0.5 text-text-secondary'>{option.label}</div>
<div className='system-xs-regular text-text-tertiary'>{option.description}</div>
<div className="grow">
<div className="system-sm-semibold mb-0.5 text-text-secondary">{option.label}</div>
<div className="system-xs-regular text-text-tertiary">{option.description}</div>
</div>
</div>
))

View File

@ -7,21 +7,21 @@ const FailBranchCard = () => {
const docLink = useDocLink()
return (
<div className='px-4 pt-2'>
<div className='rounded-[10px] bg-workflow-process-bg p-4'>
<div className='mb-2 flex h-8 w-8 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg'>
<RiMindMap className='h-5 w-5 text-text-tertiary' />
<div className="px-4 pt-2">
<div className="rounded-[10px] bg-workflow-process-bg p-4">
<div className="mb-2 flex h-8 w-8 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg">
<RiMindMap className="h-5 w-5 text-text-tertiary" />
</div>
<div className='system-sm-medium mb-1 text-text-secondary'>
<div className="system-sm-medium mb-1 text-text-secondary">
{t('workflow.nodes.common.errorHandle.failBranch.customize')}
</div>
<div className='system-xs-regular text-text-tertiary'>
<div className="system-xs-regular text-text-tertiary">
{t('workflow.nodes.common.errorHandle.failBranch.customizeTip')}
&nbsp;
<a
href={docLink('/guides/workflow/error-handling/error-type')}
target='_blank'
className='text-text-accent'
target="_blank"
className="text-text-accent"
>
{t('workflow.common.learnMore')}
</a>

View File

@ -1,18 +1,18 @@
import type { DefaultValueForm } from './types'
import type {
CommonNodeType,
} from '@/app/components/workflow/types'
import {
useCallback,
useMemo,
useState,
} from 'react'
import { ErrorHandleTypeEnum } from './types'
import type { DefaultValueForm } from './types'
import { getDefaultValue } from './utils'
import type {
CommonNodeType,
} from '@/app/components/workflow/types'
import {
useEdgesInteractions,
useNodeDataUpdate,
} from '@/app/components/workflow/hooks'
import { ErrorHandleTypeEnum } from './types'
import { getDefaultValue } from './utils'
export const useDefaultValue = (
id: string,

View File

@ -1,9 +1,9 @@
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
import type { CommonNodeType } from '@/app/components/workflow/types'
import {
BlockEnum,
VarType,
} from '@/app/components/workflow/types'
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
const getDefaultValueByType = (type: VarType) => {
if (type === VarType.string)

View File

@ -1,12 +1,12 @@
'use client'
import type { FC, ReactNode } from 'react'
import React from 'react'
import {
RiArrowDownSLine,
} from '@remixicon/react'
import { useBoolean } from 'ahooks'
import { cn } from '@/utils/classnames'
import React from 'react'
import Tooltip from '@/app/components/base/tooltip'
import { cn } from '@/utils/classnames'
type Props = {
className?: string
@ -38,23 +38,26 @@ const Field: FC<Props> = ({
<div className={cn(className, inline && 'flex w-full items-center justify-between')}>
<div
onClick={() => supportFold && toggleFold()}
className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')}>
<div className='flex h-6 items-center'>
className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')}
>
<div className="flex h-6 items-center">
<div className={cn(isSubTitle ? 'system-xs-medium-uppercase text-text-tertiary' : 'system-sm-semibold-uppercase text-text-secondary')}>
{title} {required && <span className='text-text-destructive'>*</span>}
{title}
{' '}
{required && <span className="text-text-destructive">*</span>}
</div>
{tooltip && (
<Tooltip
popupContent={tooltip}
popupClassName='ml-1'
triggerClassName='w-4 h-4 ml-1'
popupClassName="ml-1"
triggerClassName="w-4 h-4 ml-1"
/>
)}
</div>
<div className='flex'>
<div className="flex">
{operations && <div>{operations}</div>}
{supportFold && (
<RiArrowDownSLine className='h-4 w-4 cursor-pointer text-text-tertiary transition-transform' style={{ transform: fold ? 'rotate(-90deg)' : 'rotate(0deg)' }} />
<RiArrowDownSLine className="h-4 w-4 cursor-pointer text-text-tertiary transition-transform" style={{ transform: fold ? 'rotate(-90deg)' : 'rotate(0deg)' }} />
)}
</div>
</div>

View File

@ -1,14 +1,14 @@
'use client'
import type { FC } from 'react'
import { noop } from 'lodash-es'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { SupportUploadFileTypes } from '../../../types'
import { cn } from '@/utils/classnames'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import TagInput from '@/app/components/base/tag-input'
import Checkbox from '@/app/components/base/checkbox'
import { FileTypeIcon } from '@/app/components/base/file-uploader'
import { noop } from 'lodash-es'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import TagInput from '@/app/components/base/tag-input'
import { cn } from '@/utils/classnames'
import { SupportUploadFileTypes } from '../../../types'
type Props = {
type: SupportUploadFileTypes.image | SupportUploadFileTypes.document | SupportUploadFileTypes.audio | SupportUploadFileTypes.video | SupportUploadFileTypes.custom
@ -45,31 +45,31 @@ const FileTypeItem: FC<Props> = ({
>
{isCustomSelected
? (
<div>
<div className='flex items-center border-b border-divider-subtle p-3 pb-2'>
<FileTypeIcon className='shrink-0' type={type} size='lg' />
<div className='system-sm-medium mx-2 grow text-text-primary'>{t(`appDebug.variableConfig.file.${type}.name`)}</div>
<Checkbox className='shrink-0' checked={selected} />
<div>
<div className="flex items-center border-b border-divider-subtle p-3 pb-2">
<FileTypeIcon className="shrink-0" type={type} size="lg" />
<div className="system-sm-medium mx-2 grow text-text-primary">{t(`appDebug.variableConfig.file.${type}.name`)}</div>
<Checkbox className="shrink-0" checked={selected} />
</div>
<div className="p-3" onClick={e => e.stopPropagation()}>
<TagInput
items={customFileTypes}
onChange={onCustomFileTypesChange}
placeholder={t('appDebug.variableConfig.file.custom.createPlaceholder')!}
/>
</div>
</div>
<div className='p-3' onClick={e => e.stopPropagation()}>
<TagInput
items={customFileTypes}
onChange={onCustomFileTypesChange}
placeholder={t('appDebug.variableConfig.file.custom.createPlaceholder')!}
/>
</div>
</div>
)
)
: (
<div className='flex items-center'>
<FileTypeIcon className='shrink-0' type={type} size='lg' />
<div className='mx-2 grow'>
<div className='system-sm-medium text-text-primary'>{t(`appDebug.variableConfig.file.${type}.name`)}</div>
<div className='system-2xs-regular-uppercase mt-1 text-text-tertiary'>{type !== SupportUploadFileTypes.custom ? FILE_EXTS[type].join(', ') : t('appDebug.variableConfig.file.custom.description')}</div>
<div className="flex items-center">
<FileTypeIcon className="shrink-0" type={type} size="lg" />
<div className="mx-2 grow">
<div className="system-sm-medium text-text-primary">{t(`appDebug.variableConfig.file.${type}.name`)}</div>
<div className="system-2xs-regular-uppercase mt-1 text-text-tertiary">{type !== SupportUploadFileTypes.custom ? FILE_EXTS[type].join(', ') : t('appDebug.variableConfig.file.custom.description')}</div>
</div>
<Checkbox className="shrink-0" checked={selected} />
</div>
<Checkbox className='shrink-0' checked={selected} />
</div>
)}
)}
</div>
)

View File

@ -1,18 +1,18 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { produce } from 'immer'
import { useTranslation } from 'react-i18next'
import type { UploadFileSetting } from '../../../types'
import { produce } from 'immer'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Field from '@/app/components/app/configuration/config-var/config-modal/field'
import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
import { useFileUploadConfig } from '@/service/use-common'
import { TransferMethod } from '@/types/app'
import { formatFileSize } from '@/utils/format'
import { SupportUploadFileTypes } from '../../../types'
import OptionCard from './option-card'
import FileTypeItem from './file-type-item'
import InputNumberWithSlider from './input-number-with-slider'
import Field from '@/app/components/app/configuration/config-var/config-modal/field'
import { TransferMethod } from '@/types/app'
import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
import { formatFileSize } from '@/utils/format'
import { useFileUploadConfig } from '@/service/use-common'
import OptionCard from './option-card'
type Props = {
payload: UploadFileSetting
@ -100,7 +100,7 @@ const FileUploadSetting: FC<Props> = ({
<Field
title={t('appDebug.variableConfig.file.supportFileTypes')}
>
<div className='space-y-1'>
<div className="space-y-1">
{
[SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => (
<FileTypeItem
@ -123,9 +123,9 @@ const FileUploadSetting: FC<Props> = ({
)}
<Field
title={t('appDebug.variableConfig.uploadFileTypes')}
className='mt-4'
className="mt-4"
>
<div className='grid grid-cols-3 gap-2'>
<div className="grid grid-cols-3 gap-2">
<OptionCard
title={t('appDebug.variableConfig.localUpload')}
selected={allowed_file_upload_methods.length === 1 && allowed_file_upload_methods.includes(TransferMethod.local_file)}
@ -145,16 +145,18 @@ const FileUploadSetting: FC<Props> = ({
</Field>
{isMultiple && (
<Field
className='mt-4'
className="mt-4"
title={t('appDebug.variableConfig.maxNumberOfUploads')!}
>
<div>
<div className='body-xs-regular mb-1.5 text-text-tertiary'>{t('appDebug.variableConfig.maxNumberTip', {
imgLimit: formatFileSize(imgSizeLimit),
docLimit: formatFileSize(docSizeLimit),
audioLimit: formatFileSize(audioSizeLimit),
videoLimit: formatFileSize(videoSizeLimit),
})}</div>
<div className="body-xs-regular mb-1.5 text-text-tertiary">
{t('appDebug.variableConfig.maxNumberTip', {
imgLimit: formatFileSize(imgSizeLimit),
docLimit: formatFileSize(docSizeLimit),
audioLimit: formatFileSize(audioSizeLimit),
videoLimit: formatFileSize(videoSizeLimit),
})}
</div>
<InputNumberWithSlider
value={max_length}
@ -168,9 +170,9 @@ const FileUploadSetting: FC<Props> = ({
{inFeaturePanel && !hideSupportFileType && (
<Field
title={t('appDebug.variableConfig.file.supportFileTypes')}
className='mt-4'
className="mt-4"
>
<div className='space-y-1'>
<div className="space-y-1">
{
[SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => (
<FileTypeItem

View File

@ -12,7 +12,7 @@ const FormInputBoolean: FC<Props> = ({
onChange,
}) => {
return (
<div className='flex w-full space-x-1'>
<div className="flex w-full space-x-1">
<div
className={cn(
'system-sm-regular flex h-8 grow cursor-default items-center justify-center rounded-md border border-components-option-card-option-border bg-components-option-card-option-bg px-2 text-text-secondary',
@ -20,7 +20,9 @@ const FormInputBoolean: FC<Props> = ({
value && 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs',
)}
onClick={() => onChange(true)}
>True</div>
>
True
</div>
<div
className={cn(
'system-sm-regular flex h-8 grow cursor-default items-center justify-center rounded-md border border-components-option-card-option-border bg-components-option-card-option-bg px-2 text-text-secondary',
@ -28,7 +30,9 @@ const FormInputBoolean: FC<Props> = ({
!value && 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs',
)}
onClick={() => onChange(false)}
>False</div>
>
False
</div>
</div>
)
}

View File

@ -1,35 +1,35 @@
'use client'
import type { FC } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { type ResourceVarInputs, VarKindType } from '../types'
import type { ResourceVarInputs } from '../types'
import type { CredentialFormSchema, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import type { Event, Tool } from '@/app/components/tools/types'
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { RiCheckLine, RiLoader4Line } from '@remixicon/react'
import { useEffect, useMemo, useState } from 'react'
import CheckboxList from '@/app/components/base/checkbox-list'
import Input from '@/app/components/base/input'
import { SimpleSelect } from '@/app/components/base/select'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input'
import { VarType } from '@/app/components/workflow/types'
import { useFetchDynamicOptions } from '@/service/use-plugins'
import { useTriggerPluginDynamicOptions } from '@/service/use-triggers'
import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import type { Tool } from '@/app/components/tools/types'
import FormInputTypeSwitch from './form-input-type-switch'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
import Input from '@/app/components/base/input'
import { SimpleSelect } from '@/app/components/base/select'
import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { cn } from '@/utils/classnames'
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { RiCheckLine, RiLoader4Line } from '@remixicon/react'
import type { Event } from '@/app/components/tools/types'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import CheckboxList from '@/app/components/base/checkbox-list'
import { VarKindType } from '../types'
import FormInputBoolean from './form-input-boolean'
import FormInputTypeSwitch from './form-input-type-switch'
type Props = {
readOnly: boolean
@ -284,7 +284,7 @@ const FormInputItem: FC<Props> = ({
}
const availableCheckboxOptions = useMemo(() => (
(options || []).filter((option: { show_on?: Array<{ variable: string; value: any }> }) => {
(options || []).filter((option: { show_on?: Array<{ variable: string, value: any }> }) => {
if (option.show_on?.length)
return option.show_on.every(showOnItem => value[showOnItem.variable]?.value === showOnItem.value || value[showOnItem.variable] === showOnItem.value)
return true
@ -292,7 +292,7 @@ const FormInputItem: FC<Props> = ({
), [options, value])
const checkboxListOptions = useMemo(() => (
availableCheckboxOptions.map((option: { value: string; label: Record<string, string> }) => ({
availableCheckboxOptions.map((option: { value: string, label: Record<string, string> }) => ({
value: option.value,
label: option.label?.[language] || option.label?.en_US || option.value,
}))
@ -341,8 +341,8 @@ const FormInputItem: FC<Props> = ({
)}
{isNumber && isConstant && (
<Input
className='h-8 grow'
type='number'
className="h-8 grow"
type="number"
value={Number.isNaN(varInput?.value) ? '' : varInput?.value}
onChange={e => handleValueChange(e.target.value)}
placeholder={placeholder?.[language] || placeholder?.en_US}
@ -355,7 +355,7 @@ const FormInputItem: FC<Props> = ({
onChange={handleCheckboxListChange}
options={checkboxListOptions}
disabled={readOnly}
maxHeight='200px'
maxHeight="200px"
/>
)}
{isBoolean && isConstant && (
@ -366,7 +366,7 @@ const FormInputItem: FC<Props> = ({
)}
{isSelect && isConstant && !isMultipleSelect && (
<SimpleSelect
wrapperClassName='h-8 grow'
wrapperClassName="h-8 grow"
disabled={readOnly}
defaultValue={varInput?.value}
items={options.filter((option: { show_on: any[] }) => {
@ -374,21 +374,23 @@ const FormInputItem: FC<Props> = ({
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return true
}).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ({
}).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ({
value: option.value,
name: option.label[language] || option.label.en_US,
icon: option.icon,
}))}
onSelect={item => handleValueChange(item.value as string)}
placeholder={placeholder?.[language] || placeholder?.en_US}
renderOption={options.some((opt: any) => opt.icon) ? ({ item }) => (
<div className="flex items-center">
{item.icon && (
<img src={item.icon} alt="" className="mr-2 h-4 w-4" />
)}
<span>{item.name}</span>
</div>
) : undefined}
renderOption={options.some((opt: any) => opt.icon)
? ({ item }) => (
<div className="flex items-center">
{item.icon && (
<img src={item.icon} alt="" className="mr-2 h-4 w-4" />
)}
<span>{item.name}</span>
</div>
)
: undefined}
/>
)}
{isSelect && isConstant && isMultipleSelect && (
@ -400,9 +402,7 @@ const FormInputItem: FC<Props> = ({
>
<div className="group/simple-select relative h-8 grow">
<ListboxButton className="flex h-full w-full cursor-pointer items-center rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 focus-visible:bg-state-base-hover-alt focus-visible:outline-none group-hover/simple-select:bg-state-base-hover-alt sm:text-sm sm:leading-6">
<span className={cn('system-sm-regular block truncate text-left',
varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder',
)}>
<span className={cn('system-sm-regular block truncate text-left', varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder')}>
{getSelectedLabels(varInput?.value) || placeholder?.[language] || placeholder?.en_US || 'Select options'}
</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
@ -417,15 +417,12 @@ const FormInputItem: FC<Props> = ({
if (option.show_on?.length)
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return true
}).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => (
}).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => (
<ListboxOption
key={option.value}
value={option.value}
className={({ focus }) =>
cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover',
focus && 'bg-state-base-hover',
)
}
cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover', focus && 'bg-state-base-hover')}
>
{({ selected }) => (
<>
@ -452,7 +449,7 @@ const FormInputItem: FC<Props> = ({
)}
{isDynamicSelect && !isMultipleSelect && (
<SimpleSelect
wrapperClassName='h-8 grow'
wrapperClassName="h-8 grow"
disabled={readOnly || isLoadingOptions}
defaultValue={varInput?.value}
items={(dynamicOptions || options || []).filter((option: { show_on?: any[] }) => {
@ -460,7 +457,7 @@ const FormInputItem: FC<Props> = ({
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return true
}).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ({
}).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ({
value: option.value,
name: option.label[language] || option.label.en_US,
icon: option.icon,
@ -486,23 +483,25 @@ const FormInputItem: FC<Props> = ({
>
<div className="group/simple-select relative h-8 grow">
<ListboxButton className="flex h-full w-full cursor-pointer items-center rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 focus-visible:bg-state-base-hover-alt focus-visible:outline-none group-hover/simple-select:bg-state-base-hover-alt sm:text-sm sm:leading-6">
<span className={cn('system-sm-regular block truncate text-left',
isLoadingOptions ? 'text-components-input-text-placeholder'
: varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder',
)}>
<span className={cn('system-sm-regular block truncate text-left', isLoadingOptions
? 'text-components-input-text-placeholder'
: varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder')}
>
{isLoadingOptions
? 'Loading...'
: getSelectedLabels(varInput?.value) || placeholder?.[language] || placeholder?.en_US || 'Select options'}
</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
{isLoadingOptions ? (
<RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' />
) : (
<ChevronDownIcon
className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary"
aria-hidden="true"
/>
)}
{isLoadingOptions
? (
<RiLoader4Line className="h-3.5 w-3.5 animate-spin text-text-secondary" />
)
: (
<ChevronDownIcon
className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary"
aria-hidden="true"
/>
)}
</span>
</ListboxButton>
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur px-1 py-1 text-base shadow-lg backdrop-blur-sm focus:outline-none sm:text-sm">
@ -510,15 +509,12 @@ const FormInputItem: FC<Props> = ({
if (option.show_on?.length)
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return true
}).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => (
}).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => (
<ListboxOption
key={option.value}
value={option.value}
className={({ focus }) =>
cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover',
focus && 'bg-state-base-hover',
)
}
cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover', focus && 'bg-state-base-hover')}
>
{({ selected }) => (
<>
@ -544,16 +540,16 @@ const FormInputItem: FC<Props> = ({
</Listbox>
)}
{isShowJSONEditor && isConstant && (
<div className='mt-1 w-full'>
<div className="mt-1 w-full">
<CodeEditor
title='JSON'
title="JSON"
value={varInput?.value as any}
isExpand
isInNode
language={CodeLanguage.json}
onChange={handleValueChange}
className='w-full'
placeholder={<div className='whitespace-pre'>{placeholder?.[language] || placeholder?.en_US}</div>}
className="w-full"
placeholder={<div className="whitespace-pre">{placeholder?.[language] || placeholder?.en_US}</div>}
/>
</div>
)}
@ -567,7 +563,7 @@ const FormInputItem: FC<Props> = ({
)}
{isModelSelector && isConstant && (
<ModelParameterModal
popupClassName='!w-[387px]'
popupClassName="!w-[387px]"
isAdvancedMode
isInWorkflow
value={varInput?.value}
@ -579,7 +575,7 @@ const FormInputItem: FC<Props> = ({
{showVariableSelector && (
<VarReferencePicker
zIndex={inPanel ? 1000 : undefined}
className='h-8 grow'
className="h-8 grow"
readonly={readOnly}
isShowNodeName
nodeId={nodeId}

View File

@ -1,9 +1,9 @@
'use client'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiEditLine,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import Tooltip from '@/app/components/base/tooltip'
import { VarType } from '@/app/components/workflow/nodes/tool/types'
@ -20,7 +20,7 @@ const FormInputTypeSwitch: FC<Props> = ({
}) => {
const { t } = useTranslation()
return (
<div className='inline-flex h-8 shrink-0 gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5'>
<div className="inline-flex h-8 shrink-0 gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5">
<Tooltip
popupContent={value === VarType.variable ? '' : t('workflow.nodes.common.typeSwitch.variable')}
>
@ -28,7 +28,7 @@ const FormInputTypeSwitch: FC<Props> = ({
className={cn('cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover', value === VarType.variable && 'bg-components-segmented-control-item-active-bg text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg')}
onClick={() => onChange(VarType.variable)}
>
<Variable02 className='h-4 w-4' />
<Variable02 className="h-4 w-4" />
</div>
</Tooltip>
<Tooltip
@ -38,7 +38,7 @@ const FormInputTypeSwitch: FC<Props> = ({
className={cn('cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover', value === VarType.constant && 'bg-components-segmented-control-item-active-bg text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg')}
onClick={() => onChange(VarType.constant)}
>
<RiEditLine className='h-4 w-4' />
<RiEditLine className="h-4 w-4" />
</div>
</Tooltip>
</div>

View File

@ -1,13 +1,15 @@
import { cn } from '@/utils/classnames'
import type { ComponentProps, FC, PropsWithChildren, ReactNode } from 'react'
import { cn } from '@/utils/classnames'
export type GroupLabelProps = ComponentProps<'div'>
export const GroupLabel: FC<GroupLabelProps> = (props) => {
const { children, className, ...rest } = props
return <div {...rest} className={cn('system-2xs-medium-uppercase mb-1 text-text-tertiary', className)}>
{children}
</div>
return (
<div {...rest} className={cn('system-2xs-medium-uppercase mb-1 text-text-tertiary', className)}>
{children}
</div>
)
}
export type GroupProps = PropsWithChildren<{
@ -16,10 +18,12 @@ export type GroupProps = PropsWithChildren<{
export const Group: FC<GroupProps> = (props) => {
const { children, label } = props
return <div className={cn('py-1')}>
{label}
<div className='space-y-0.5'>
{children}
return (
<div className={cn('py-1')}>
{label}
<div className="space-y-0.5">
{children}
</div>
</div>
</div>
)
}

View File

@ -1,9 +1,9 @@
import type { BlockEnum } from '@/app/components/workflow/types'
import { RiBookOpenLine } from '@remixicon/react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { RiBookOpenLine } from '@remixicon/react'
import { useNodeHelpLink } from '../hooks/use-node-help-link'
import TooltipPlus from '@/app/components/base/tooltip'
import type { BlockEnum } from '@/app/components/workflow/types'
import { useNodeHelpLink } from '../hooks/use-node-help-link'
type HelpLinkProps = {
nodeType: BlockEnum
@ -23,10 +23,10 @@ const HelpLink = ({
>
<a
href={link}
target='_blank'
className='mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover'
target="_blank"
className="mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover"
>
<RiBookOpenLine className='h-4 w-4 text-gray-500' />
<RiBookOpenLine className="h-4 w-4 text-gray-500" />
</a>
</TooltipPlus>

View File

@ -13,11 +13,11 @@ const InfoPanel: FC<Props> = ({
}) => {
return (
<div>
<div className='flex flex-col gap-y-0.5 rounded-md bg-workflow-block-parma-bg px-[5px] py-[3px]'>
<div className='system-2xs-semibold-uppercase uppercase text-text-secondary'>
<div className="flex flex-col gap-y-0.5 rounded-md bg-workflow-block-parma-bg px-[5px] py-[3px]">
<div className="system-2xs-semibold-uppercase uppercase text-text-secondary">
{title}
</div>
<div className='system-xs-regular break-words text-text-tertiary'>
<div className="system-xs-regular break-words text-text-tertiary">
{content}
</div>
</div>

View File

@ -4,7 +4,7 @@ import ActionButton from '@/app/components/base/action-button'
const Add = () => {
return (
<ActionButton>
<RiAddLine className='h-4 w-4' />
<RiAddLine className="h-4 w-4" />
</ActionButton>
)
}

View File

@ -38,11 +38,11 @@ const InputNumberWithSlider: FC<InputNumberWithSliderProps> = ({
}, [onChange])
return (
<div className='flex h-8 items-center justify-between space-x-2'>
<div className="flex h-8 items-center justify-between space-x-2">
<input
value={value}
className='block h-8 w-12 shrink-0 appearance-none rounded-lg bg-components-input-bg-normal pl-3 text-[13px] text-components-input-text-filled outline-none'
type='number'
className="block h-8 w-12 shrink-0 appearance-none rounded-lg bg-components-input-bg-normal pl-3 text-[13px] text-components-input-text-filled outline-none"
type="number"
min={min}
max={max}
step={1}
@ -51,7 +51,7 @@ const InputNumberWithSlider: FC<InputNumberWithSliderProps> = ({
disabled={readonly}
/>
<Slider
className='grow'
className="grow"
value={value}
min={min}
max={max}

View File

@ -1,19 +1,19 @@
'use client'
import type { FC } from 'react'
import React, { useEffect } from 'react'
import { useBoolean } from 'ahooks'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
import type {
Node,
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
import PromptEditor from '@/app/components/base/prompt-editor'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import Tooltip from '@/app/components/base/tooltip'
import { useBoolean } from 'ahooks'
import { noop } from 'lodash-es'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import PromptEditor from '@/app/components/base/prompt-editor'
import Tooltip from '@/app/components/base/tooltip'
import { useStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
type Props = {
instanceId?: string
@ -115,20 +115,20 @@ const Editor: FC<Props> = ({
onFocus={setFocus}
/>
{/* to patch Editor not support dynamic change editable status */}
{readOnly && <div className='absolute inset-0 z-10'></div>}
{readOnly && <div className="absolute inset-0 z-10"></div>}
{isFocus && (
<div className={cn('absolute z-10', insertVarTipToLeft ? 'left-[-12px] top-1.5' : ' right-1 top-[-9px]')}>
<Tooltip
popupContent={`${t('workflow.common.insertVarTip')}`}
>
<div className='cursor-pointer rounded-[5px] border-[0.5px] border-divider-regular bg-components-badge-white-to-dark p-0.5 shadow-lg'>
<Variable02 className='h-3.5 w-3.5 text-components-button-secondary-accent-text' />
<div className="cursor-pointer rounded-[5px] border-[0.5px] border-divider-regular bg-components-badge-white-to-dark p-0.5 shadow-lg">
<Variable02 className="h-3.5 w-3.5 text-components-button-secondary-accent-text" />
</div>
</Tooltip>
</div>
)}
</>
</div >
</div>
)
}
export default React.memo(Editor)

View File

@ -1,6 +1,5 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import {
RiAlignLeft,
RiBracesLine,
@ -11,6 +10,7 @@ import {
RiHashtag,
RiTextSnippet,
} from '@remixicon/react'
import React from 'react'
import { InputVarType } from '../../../types'
type Props = {

View File

@ -1,12 +1,12 @@
import Button from '@/app/components/base/button'
import { RiInstallLine, RiLoader2Line } from '@remixicon/react'
import type { ComponentProps, MouseEventHandler } from 'react'
import { RiInstallLine, RiLoader2Line } from '@remixicon/react'
import { useState } from 'react'
import { cn } from '@/utils/classnames'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-task-status'
import { TaskStatus } from '@/app/components/plugins/types'
import { useCheckInstalled, useInstallPackageFromMarketPlace } from '@/service/use-plugins'
import { cn } from '@/utils/classnames'
type InstallPluginButtonProps = Omit<ComponentProps<typeof Button>, 'children' | 'loading'> & {
uniqueIdentifier: string
@ -83,22 +83,26 @@ export const InstallPluginButton = (props: InstallPluginButtonProps) => {
},
})
}
if (!manifest.data) return null
if (!manifest.data)
return null
const identifierSet = new Set(identifiers)
const isInstalled = manifest.data.plugins.some(plugin => (
identifierSet.has(plugin.id)
|| (plugin.plugin_unique_identifier && identifierSet.has(plugin.plugin_unique_identifier))
|| (plugin.plugin_id && identifierSet.has(plugin.plugin_id))
))
if (isInstalled) return null
return <Button
variant={'secondary'}
disabled={isLoading}
{...rest}
onClick={handleInstall}
className={cn('flex items-center', className)}
>
{!isLoading ? t('workflow.nodes.agent.pluginInstaller.install') : t('workflow.nodes.agent.pluginInstaller.installing')}
{!isLoading ? <RiInstallLine className='ml-1 size-3.5' /> : <RiLoader2Line className='ml-1 size-3.5 animate-spin' />}
</Button>
if (isInstalled)
return null
return (
<Button
variant="secondary"
disabled={isLoading}
{...rest}
onClick={handleInstall}
className={cn('flex items-center', className)}
>
{!isLoading ? t('workflow.nodes.agent.pluginInstaller.install') : t('workflow.nodes.agent.pluginInstaller.installing')}
{!isLoading ? <RiInstallLine className="ml-1 size-3.5" /> : <RiLoader2Line className="ml-1 size-3.5 animate-spin" />}
</Button>
)
}

View File

@ -1,9 +1,9 @@
import type { ReactNode } from 'react'
import { memo } from 'react'
import type {
BoxGroupProps,
FieldProps,
} from '.'
import { memo } from 'react'
import {
BoxGroup,
Field,

View File

@ -1,13 +1,13 @@
import type { ReactNode } from 'react'
import type {
BoxProps,
GroupProps,
} from '.'
import { memo } from 'react'
import {
Box,
Group,
} from '.'
import type {
BoxProps,
GroupProps,
} from '.'
export type BoxGroupProps = {
children?: ReactNode

View File

@ -18,7 +18,8 @@ export const Box = memo(({
'py-2',
withBorderBottom && 'border-b border-divider-subtle',
className,
)}>
)}
>
{children}
</div>
)

View File

@ -33,7 +33,7 @@ export const FieldTitle = memo(({
return (
<div className={cn('mb-0.5', !!subTitle && 'mb-1')}>
<div
className='group/collapse flex items-center justify-between py-1'
className="group/collapse flex items-center justify-between py-1"
onClick={() => {
if (!disabled) {
setCollapsedLocal(!collapsedMerged)
@ -41,7 +41,7 @@ export const FieldTitle = memo(({
}
}}
>
<div className='system-sm-semibold-uppercase flex items-center text-text-secondary'>
<div className="system-sm-semibold-uppercase flex items-center text-text-secondary">
{title}
{
showArrow && (
@ -57,7 +57,7 @@ export const FieldTitle = memo(({
tooltip && (
<Tooltip
popupContent={tooltip}
triggerClassName='w-4 h-4 ml-1'
triggerClassName="w-4 h-4 ml-1"
/>
)
}

View File

@ -1,9 +1,9 @@
import type { ReactNode } from 'react'
import type { FieldTitleProps } from '.'
import {
memo,
useState,
} from 'react'
import type { FieldTitleProps } from '.'
import { FieldTitle } from '.'
export type FieldProps = {

View File

@ -1,9 +1,9 @@
import type { ReactNode } from 'react'
import { memo } from 'react'
import type {
FieldProps,
GroupProps,
} from '.'
import { memo } from 'react'
import {
Field,
Group,

View File

@ -18,7 +18,8 @@ export const Group = memo(({
'px-4 py-2',
withBorderBottom && 'border-b border-divider-subtle',
className,
)}>
)}
>
{children}
</div>
)

View File

@ -1,7 +1,7 @@
export * from './box'
export * from './group'
export * from './box-group'
export * from './field-title'
export * from './field'
export * from './group-field'
export * from './box-group-field'
export * from './field'
export * from './field-title'
export * from './group'
export * from './group-field'

View File

@ -10,7 +10,7 @@ const ListNoDataPlaceholder: FC<Props> = ({
children,
}) => {
return (
<div className='system-xs-regular flex min-h-[42px] w-full items-center justify-center rounded-[10px] bg-background-section text-text-tertiary'>
<div className="system-xs-regular flex min-h-[42px] w-full items-center justify-center rounded-[10px] bg-background-section text-text-tertiary">
{children}
</div>
)

View File

@ -1,21 +1,21 @@
'use client'
import Tooltip from '@/app/components/base/tooltip'
import { RiAlertFill } from '@remixicon/react'
import type { FC } from 'react'
import { RiAlertFill } from '@remixicon/react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
const McpToolNotSupportTooltip: FC = () => {
const { t } = useTranslation()
return (
<Tooltip
popupContent={
<div className='w-[256px]'>
popupContent={(
<div className="w-[256px]">
{t('plugin.detailPanel.toolSelector.unsupportedMCPTool')}
</div>
}
)}
>
<RiAlertFill className='size-4 text-text-warning-secondary' />
<RiAlertFill className="size-4 text-text-warning-secondary" />
</Tooltip>
)
}

View File

@ -1,15 +1,15 @@
'use client'
import type { FC } from 'react'
import type { Memory } from '../../../types'
import { produce } from 'immer'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import type { Memory } from '../../../types'
import { MemoryRole } from '../../../types'
import { cn } from '@/utils/classnames'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import Switch from '@/app/components/base/switch'
import Slider from '@/app/components/base/slider'
import Input from '@/app/components/base/input'
import Slider from '@/app/components/base/slider'
import Switch from '@/app/components/base/switch'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import { cn } from '@/utils/classnames'
import { MemoryRole } from '../../../types'
const i18nPrefix = 'workflow.nodes.common.memory'
const WINDOW_SIZE_MIN = 1
@ -31,14 +31,15 @@ const RoleItem: FC<RoleItemProps> = ({
onChange(e.target.value)
}, [onChange])
return (
<div className='flex items-center justify-between'>
<div className='text-[13px] font-normal text-text-secondary'>{title}</div>
<div className="flex items-center justify-between">
<div className="text-[13px] font-normal text-text-secondary">{title}</div>
<Input
readOnly={readonly}
value={value}
onChange={handleChange}
className='h-8 w-[200px]'
type='text' />
className="h-8 w-[200px]"
type="text"
/>
</div>
)
}
@ -132,31 +133,31 @@ const MemoryConfig: FC<Props> = ({
<Field
title={t(`${i18nPrefix}.memory`)}
tooltip={t(`${i18nPrefix}.memoryTip`)!}
operations={
operations={(
<Switch
defaultValue={!!payload}
onChange={handleMemoryEnabledChange}
size='md'
size="md"
disabled={readonly}
/>
}
)}
>
{payload && (
<>
{/* window size */}
<div className='flex justify-between'>
<div className='flex h-8 items-center space-x-2'>
<div className="flex justify-between">
<div className="flex h-8 items-center space-x-2">
<Switch
defaultValue={payload?.window?.enabled}
onChange={handleWindowEnabledChange}
size='md'
size="md"
disabled={readonly}
/>
<div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${i18nPrefix}.windowSize`)}</div>
<div className="system-xs-medium-uppercase text-text-tertiary">{t(`${i18nPrefix}.windowSize`)}</div>
</div>
<div className='flex h-8 items-center space-x-2'>
<div className="flex h-8 items-center space-x-2">
<Slider
className='w-[144px]'
className="w-[144px]"
value={(payload.window?.size || WINDOW_SIZE_DEFAULT) as number}
min={WINDOW_SIZE_MIN}
max={WINDOW_SIZE_MAX}
@ -166,9 +167,9 @@ const MemoryConfig: FC<Props> = ({
/>
<Input
value={(payload.window?.size || WINDOW_SIZE_DEFAULT) as number}
wrapperClassName='w-12'
className='appearance-none pr-0'
type='number'
wrapperClassName="w-12"
className="appearance-none pr-0"
type="number"
min={WINDOW_SIZE_MIN}
max={WINDOW_SIZE_MAX}
step={1}
@ -179,9 +180,9 @@ const MemoryConfig: FC<Props> = ({
</div>
</div>
{canSetRoleName && (
<div className='mt-4'>
<div className='text-xs font-medium uppercase leading-6 text-text-tertiary'>{t(`${i18nPrefix}.conversationRoleName`)}</div>
<div className='mt-1 space-y-2'>
<div className="mt-4">
<div className="text-xs font-medium uppercase leading-6 text-text-tertiary">{t(`${i18nPrefix}.conversationRoleName`)}</div>
<div className="mt-1 space-y-2">
<RoleItem
readonly={readonly}
title={t(`${i18nPrefix}.user`)}

View File

@ -1,15 +1,15 @@
import type {
Node,
NodeOutPutVar,
} from '@/app/components/workflow/types'
import {
memo,
} from 'react'
import { useTranslation } from 'react-i18next'
import PromptEditor from '@/app/components/base/prompt-editor'
import Placeholder from './placeholder'
import type {
Node,
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
import Placeholder from './placeholder'
type MixedVariableTextInputProps = {
readOnly?: boolean
@ -33,7 +33,7 @@ const MixedVariableTextInput = ({
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs',
)}
className='caret:text-text-accent'
className="caret:text-text-accent"
editable={!readOnly}
value={value}
workflowVariableBlock={{

View File

@ -1,10 +1,9 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $insertNodes, FOCUS_COMMAND } from 'lexical'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { FOCUS_COMMAND } from 'lexical'
import { $insertNodes } from 'lexical'
import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node'
import Badge from '@/app/components/base/badge'
import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node'
const Placeholder = () => {
const { t } = useTranslation()
@ -20,17 +19,17 @@ const Placeholder = () => {
return (
<div
className='pointer-events-auto flex h-full w-full cursor-text items-center px-2'
className="pointer-events-auto flex h-full w-full cursor-text items-center px-2"
onClick={(e) => {
e.stopPropagation()
handleInsert('')
}}
>
<div className='flex grow items-center'>
<div className="flex grow items-center">
{t('workflow.nodes.tool.insertPlaceholder1')}
<div className='system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder'>/</div>
<div className="system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder">/</div>
<div
className='system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary'
className="system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary"
onMouseDown={((e) => {
e.preventDefault()
e.stopPropagation()
@ -41,8 +40,8 @@ const Placeholder = () => {
</div>
</div>
<Badge
className='shrink-0'
text='String'
className="shrink-0"
text="String"
uppercase={false}
/>
</div>

View File

@ -1,3 +1,10 @@
import type {
CommonNodeType,
OnSelectBlock,
} from '@/app/components/workflow/types'
import {
RiAddLine,
} from '@remixicon/react'
import {
memo,
useCallback,
@ -5,19 +12,12 @@ import {
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
RiAddLine,
} from '@remixicon/react'
import BlockSelector from '@/app/components/workflow/block-selector'
import {
useAvailableBlocks,
useNodesInteractions,
useNodesReadOnly,
} from '@/app/components/workflow/hooks'
import BlockSelector from '@/app/components/workflow/block-selector'
import type {
CommonNodeType,
OnSelectBlock,
} from '@/app/components/workflow/types'
type AddProps = {
nodeId: string
@ -75,10 +75,10 @@ const Add = ({
${nodesReadOnly && '!cursor-not-allowed'}
`}
>
<div className='mr-1.5 flex h-5 w-5 items-center justify-center rounded-[5px] bg-background-default-dimmed'>
<RiAddLine className='h-3 w-3' />
<div className="mr-1.5 flex h-5 w-5 items-center justify-center rounded-[5px] bg-background-default-dimmed">
<RiAddLine className="h-3 w-3" />
</div>
<div className='flex items-center uppercase'>
<div className="flex items-center uppercase">
{tip}
</div>
</div>
@ -91,10 +91,10 @@ const Add = ({
onOpenChange={handleOpenChange}
disabled={nodesReadOnly}
onSelect={handleSelect}
placement='top'
placement="top"
offset={0}
trigger={renderTrigger}
popupClassName='!w-[328px]'
popupClassName="!w-[328px]"
availableBlocksTypes={availableNextBlocks}
/>
)

View File

@ -1,10 +1,10 @@
import Add from './add'
import Item from './item'
import type {
CommonNodeType,
Node,
} from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
import Add from './add'
import Item from './item'
type ContainerProps = {
nodeId: string
@ -27,7 +27,8 @@ const Container = ({
<div className={cn(
'space-y-0.5 rounded-[10px] bg-background-section-burn p-0.5',
isFailBranch && 'border-[0.5px] border-state-warning-hover-alt bg-state-warning-hover',
)}>
)}
>
{
branchName && (
<div
@ -47,7 +48,7 @@ const Container = ({
key={nextNode.id}
nodeId={nextNode.id}
data={nextNode.data}
sourceHandle='source'
sourceHandle="source"
/>
))
}

View File

@ -1,21 +1,21 @@
import type {
Node,
} from '../../../../types'
import { isEqual } from 'lodash-es'
import { memo, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { isEqual } from 'lodash-es'
import {
getConnectedEdges,
getOutgoers,
useStore,
} from 'reactflow'
import { useToolIcon } from '../../../../hooks'
import BlockIcon from '../../../../block-icon'
import type {
Node,
} from '../../../../types'
import { BlockEnum } from '../../../../types'
import Line from './line'
import Container from './container'
import { hasErrorHandleNode } from '@/app/components/workflow/utils'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import { hasErrorHandleNode } from '@/app/components/workflow/utils'
import BlockIcon from '../../../../block-icon'
import { useToolIcon } from '../../../../hooks'
import { BlockEnum } from '../../../../types'
import Container from './container'
import Line from './line'
type NextStepProps = {
selectedNode: Node
@ -89,8 +89,8 @@ const NextStep = ({
}, [branches, connectedEdges, data.error_strategy, data.type, outgoers, t])
return (
<div className='flex py-1'>
<div className='relative flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-background-default shadow-xs'>
<div className="flex py-1">
<div className="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-background-default shadow-xs">
<BlockIcon
type={selectedNode!.data.type}
toolIcon={toolIcon}
@ -99,7 +99,7 @@ const NextStep = ({
<Line
list={list.length ? list.map(item => item.nextNodes.length + 1) : [1]}
/>
<div className='grow space-y-2'>
<div className="grow space-y-2">
{
list.map((item, index) => {
return (

View File

@ -1,21 +1,21 @@
import type {
CommonNodeType,
} from '@/app/components/workflow/types'
import {
memo,
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import Operator from './operator'
import type {
CommonNodeType,
} from '@/app/components/workflow/types'
import Button from '@/app/components/base/button'
import BlockIcon from '@/app/components/workflow/block-icon'
import {
useNodesInteractions,
useNodesReadOnly,
useToolIcon,
} from '@/app/components/workflow/hooks'
import Button from '@/app/components/base/button'
import { cn } from '@/utils/classnames'
import Operator from './operator'
type ItemProps = {
nodeId: string
@ -39,15 +39,15 @@ const Item = ({
return (
<div
className='group relative flex h-9 cursor-pointer items-center rounded-lg border-[0.5px] border-divider-regular bg-background-default px-2 text-xs text-text-secondary shadow-xs last-of-type:mb-0 hover:bg-background-default-hover'
className="group relative flex h-9 cursor-pointer items-center rounded-lg border-[0.5px] border-divider-regular bg-background-default px-2 text-xs text-text-secondary shadow-xs last-of-type:mb-0 hover:bg-background-default-hover"
>
<BlockIcon
type={data.type}
toolIcon={toolIcon}
className='mr-1.5 shrink-0'
className="mr-1.5 shrink-0"
/>
<div
className='system-xs-medium grow truncate text-text-secondary'
className="system-xs-medium grow truncate text-text-secondary"
title={data.title}
>
{data.title}
@ -56,8 +56,8 @@ const Item = ({
!nodesReadOnly && (
<>
<Button
className='mr-1 hidden shrink-0 group-hover:flex'
size='small'
className="mr-1 hidden shrink-0 group-hover:flex"
size="small"
onClick={() => handleNodeSelect(nodeId)}
>
{t('workflow.common.jumpToNode')}

View File

@ -19,7 +19,7 @@ const Line = ({
const svgHeight = processedList[processedListLength - 1] + (processedListLength - 1) * 8
return (
<svg className='w-6 shrink-0' style={{ height: svgHeight }}>
<svg className="w-6 shrink-0" style={{ height: svgHeight }}>
{
processedList.map((item, index) => {
const prevItem = index > 0 ? processedList[index - 1] : 0
@ -30,17 +30,17 @@ const Line = ({
index === 0 && (
<>
<path
d='M0,18 L24,18'
d="M0,18 L24,18"
strokeWidth={1}
fill='none'
className='stroke-divider-solid'
fill="none"
className="stroke-divider-solid"
/>
<rect
x={0}
y={16}
width={1}
height={4}
className='fill-divider-solid-alt'
className="fill-divider-solid-alt"
/>
</>
)
@ -50,8 +50,8 @@ const Line = ({
<path
d={`M0,18 Q12,18 12,28 L12,${space - 10 + 2} Q12,${space + 2} 24,${space + 2}`}
strokeWidth={1}
fill='none'
className='stroke-divider-solid'
fill="none"
className="stroke-divider-solid"
/>
)
}
@ -60,7 +60,7 @@ const Line = ({
y={space}
width={1}
height={4}
className='fill-divider-solid-alt'
className="fill-divider-solid-alt"
/>
</g>
)

View File

@ -1,24 +1,24 @@
import type {
CommonNodeType,
OnSelectBlock,
} from '@/app/components/workflow/types'
import { RiMoreFill } from '@remixicon/react'
import { intersection } from 'lodash-es'
import {
useCallback,
} from 'react'
import { useTranslation } from 'react-i18next'
import { RiMoreFill } from '@remixicon/react'
import { intersection } from 'lodash-es'
import Button from '@/app/components/base/button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Button from '@/app/components/base/button'
import BlockSelector from '@/app/components/workflow/block-selector'
import {
useAvailableBlocks,
useNodesInteractions,
} from '@/app/components/workflow/hooks'
import type {
CommonNodeType,
OnSelectBlock,
} from '@/app/components/workflow/types'
type ChangeItemProps = {
data: CommonNodeType
@ -44,7 +44,7 @@ const ChangeItem = ({
const renderTrigger = useCallback(() => {
return (
<div className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'>
<div className="flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover">
{t('workflow.panel.change')}
</div>
)
@ -53,13 +53,13 @@ const ChangeItem = ({
return (
<BlockSelector
onSelect={handleSelect}
placement='top-end'
placement="top-end"
offset={{
mainAxis: 6,
crossAxis: 8,
}}
trigger={renderTrigger}
popupClassName='!w-[328px]'
popupClassName="!w-[328px]"
availableBlocksTypes={intersection(availablePrevBlocks, availableNextBlocks).filter(item => item !== data.type)}
/>
)
@ -87,34 +87,34 @@ const Operator = ({
return (
<PortalToFollowElem
placement='bottom-end'
placement="bottom-end"
offset={{ mainAxis: 4, crossAxis: -4 }}
open={open}
onOpenChange={onOpenChange}
>
<PortalToFollowElemTrigger onClick={() => onOpenChange(!open)}>
<Button className='h-6 w-6 p-0'>
<RiMoreFill className='h-4 w-4' />
<Button className="h-6 w-6 p-0">
<RiMoreFill className="h-4 w-4" />
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-10'>
<div className='system-md-regular min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg'>
<div className='p-1'>
<PortalToFollowElemContent className="z-10">
<div className="system-md-regular min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg">
<div className="p-1">
<ChangeItem
data={data}
nodeId={nodeId}
sourceHandle={sourceHandle}
/>
<div
className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover"
onClick={() => handleNodeDisconnect(nodeId)}
>
{t('workflow.common.disconnect')}
</div>
</div>
<div className='p-1'>
<div className="p-1">
<div
className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover"
onClick={() => handleNodeDelete(nodeId)}
>
{t('common.operation.delete')}

View File

@ -1,24 +1,25 @@
import type { FC } from 'react'
import type { Node } from '../../../types'
import {
RiPlayLargeLine,
} from '@remixicon/react'
import {
memo,
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
RiPlayLargeLine,
} from '@remixicon/react'
import {
useNodesInteractions,
} from '../../../hooks'
import { type Node, NodeRunningStatus } from '../../../types'
import { canRunBySingle } from '../../../utils'
import PanelOperator from './panel-operator'
import {
Stop,
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import Tooltip from '@/app/components/base/tooltip'
import { useWorkflowStore } from '@/app/components/workflow/store'
import {
useNodesInteractions,
} from '../../../hooks'
import { NodeRunningStatus } from '../../../types'
import { canRunBySingle } from '../../../utils'
import PanelOperator from './panel-operator'
type NodeControlProps = Pick<Node, 'id' | 'data'>
const NodeControl: FC<NodeControlProps> = ({
@ -45,7 +46,7 @@ const NodeControl: FC<NodeControlProps> = ({
`}
>
<div
className='flex h-6 items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg px-0.5 text-text-tertiary shadow-md backdrop-blur-[5px]'
className="flex h-6 items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg px-0.5 text-text-tertiary shadow-md backdrop-blur-[5px]"
onClick={e => e.stopPropagation()}
>
{
@ -66,15 +67,15 @@ const NodeControl: FC<NodeControlProps> = ({
>
{
isSingleRunning
? <Stop className='h-3 w-3' />
? <Stop className="h-3 w-3" />
: (
<Tooltip
popupContent={t('workflow.panel.runThisStep')}
asChild={false}
>
<RiPlayLargeLine className='h-3 w-3' />
</Tooltip>
)
<Tooltip
popupContent={t('workflow.panel.runThisStep')}
asChild={false}
>
<RiPlayLargeLine className="h-3 w-3" />
</Tooltip>
)
}
</div>
)
@ -84,7 +85,7 @@ const NodeControl: FC<NodeControlProps> = ({
data={data}
offset={0}
onOpenChange={handleOpenChange}
triggerClassName='!w-5 !h-5'
triggerClassName="!w-5 !h-5"
/>
</div>
</div>

View File

@ -1,22 +1,19 @@
import type { MouseEvent } from 'react'
import type { PluginDefaultValue } from '../../../block-selector/types'
import type { Node } from '../../../types'
import {
memo,
useCallback,
useEffect,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
Handle,
Position,
} from 'reactflow'
import { useTranslation } from 'react-i18next'
import {
BlockEnum,
NodeRunningStatus,
} from '../../../types'
import type { Node } from '../../../types'
import { cn } from '@/utils/classnames'
import BlockSelector from '../../../block-selector'
import type { PluginDefaultValue } from '../../../block-selector/types'
import {
useAvailableBlocks,
useIsChatMode,
@ -27,7 +24,10 @@ import {
useStore,
useWorkflowStore,
} from '../../../store'
import { cn } from '@/utils/classnames'
import {
BlockEnum,
NodeRunningStatus,
} from '../../../types'
type NodeHandleProps = {
handleId: string
@ -75,7 +75,7 @@ export const NodeTargetHandle = memo(({
<>
<Handle
id={handleId}
type='target'
type="target"
position={Position.Left}
className={cn(
'z-[1] !h-4 !w-4 !rounded-none !border-none !bg-transparent !outline-none',
@ -101,7 +101,7 @@ export const NodeTargetHandle = memo(({
onOpenChange={handleOpenChange}
onSelect={handleSelect}
asChild
placement='left'
placement="left"
triggerClassName={open => `
hidden absolute left-0 top-0 pointer-events-none
${nodeSelectorClassName}
@ -186,7 +186,7 @@ export const NodeSourceHandle = memo(({
return (
<Handle
id={handleId}
type='source'
type="source"
position={Position.Right}
className={cn(
'group/handle z-[1] !h-4 !w-4 !rounded-none !border-none !bg-transparent !outline-none',
@ -201,14 +201,14 @@ export const NodeSourceHandle = memo(({
isConnectable={isConnectable}
onClick={handleHandleClick}
>
<div className='absolute -top-1 left-1/2 hidden -translate-x-1/2 -translate-y-full rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg p-1.5 shadow-lg group-hover/handle:block'>
<div className='system-xs-regular text-text-tertiary'>
<div className=' whitespace-nowrap'>
<span className='system-xs-medium text-text-secondary'>{t('workflow.common.parallelTip.click.title')}</span>
<div className="absolute -top-1 left-1/2 hidden -translate-x-1/2 -translate-y-full rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg p-1.5 shadow-lg group-hover/handle:block">
<div className="system-xs-regular text-text-tertiary">
<div className=" whitespace-nowrap">
<span className="system-xs-medium text-text-secondary">{t('workflow.common.parallelTip.click.title')}</span>
{t('workflow.common.parallelTip.click.desc')}
</div>
<div>
<span className='system-xs-medium text-text-secondary'>{t('workflow.common.parallelTip.drag.title')}</span>
<span className="system-xs-medium text-text-secondary">{t('workflow.common.parallelTip.drag.title')}</span>
{t('workflow.common.parallelTip.drag.desc')}
</div>
</div>

View File

@ -1,12 +1,12 @@
import type { OnResize } from 'reactflow'
import type { CommonNodeType } from '../../../types'
import {
memo,
useCallback,
} from 'react'
import type { OnResize } from 'reactflow'
import { NodeResizeControl } from 'reactflow'
import { useNodesInteractions } from '../../../hooks'
import type { CommonNodeType } from '../../../types'
import { cn } from '@/utils/classnames'
import { useNodesInteractions } from '../../../hooks'
const Icon = () => {
return (
@ -42,16 +42,17 @@ const NodeResizer = ({
<div className={cn(
'hidden group-hover:block',
nodeData.selected && '!block',
)}>
)}
>
<NodeResizeControl
position='bottom-right'
className='!border-none !bg-transparent'
position="bottom-right"
className="!border-none !bg-transparent"
onResize={handleResize}
minWidth={minWidth}
minHeight={minHeight}
maxWidth={maxWidth}
>
<div className='absolute bottom-[1px] right-[1px]'>{icon}</div>
<div className="absolute bottom-[1px] right-[1px]">{icon}</div>
</NodeResizeControl>
</div>
)

View File

@ -1,10 +1,10 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import type { VariantProps } from 'class-variance-authority'
import type { FC } from 'react'
import { cva } from 'class-variance-authority'
import { cn } from '@/utils/classnames'
import React, { useCallback } from 'react'
import Tooltip from '@/app/components/base/tooltip'
import { cn } from '@/utils/classnames'
const variants = cva([], {
variants: {
@ -17,8 +17,7 @@ const variants = cva([], {
defaultVariants: {
align: 'center',
},
},
)
})
type Props = {
className?: string
@ -59,14 +58,15 @@ const OptionCard: FC<Props> = ({
>
<span>{title}</span>
{tooltip
&& <Tooltip
popupContent={
<div className='w-[240px]'>
{tooltip}
</div>
}
/>
}
&& (
<Tooltip
popupContent={(
<div className="w-[240px]">
{tooltip}
</div>
)}
/>
)}
</div>
)
}

View File

@ -3,8 +3,8 @@ import type { FC, ReactNode } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
import TreeIndentLine from './variable/object-child-tree-panel/tree-indent-line'
import { cn } from '@/utils/classnames'
import TreeIndentLine from './variable/object-child-tree-panel/tree-indent-line'
type Props = {
className?: string
@ -56,17 +56,17 @@ export const VarItem: FC<VarItemProps> = ({
return (
<div className={cn('flex', isIndent && 'relative left-[-7px]')}>
{isIndent && <TreeIndentLine depth={1} />}
<div className='py-1'>
<div className='flex'>
<div className='flex items-center leading-[18px]'>
<div className='code-sm-semibold text-text-secondary'>{name}</div>
<div className='system-xs-regular ml-2 text-text-tertiary'>{type}</div>
<div className="py-1">
<div className="flex">
<div className="flex items-center leading-[18px]">
<div className="code-sm-semibold text-text-secondary">{name}</div>
<div className="system-xs-regular ml-2 text-text-tertiary">{type}</div>
</div>
</div>
<div className='system-xs-regular mt-0.5 text-text-tertiary'>
<div className="system-xs-regular mt-0.5 text-text-tertiary">
{description}
{subItems && (
<div className='ml-2 border-l border-gray-200 pl-2'>
<div className="ml-2 border-l border-gray-200 pl-2">
{subItems.map((item, index) => (
<VarItem
key={index}

View File

@ -1,10 +1,14 @@
import type {
Node,
OnSelectBlock,
} from '@/app/components/workflow/types'
import { intersection } from 'lodash-es'
import {
memo,
useCallback,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import { intersection } from 'lodash-es'
import BlockSelector from '@/app/components/workflow/block-selector'
import {
useAvailableBlocks,
@ -12,10 +16,6 @@ import {
useNodesInteractions,
} from '@/app/components/workflow/hooks'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import type {
Node,
OnSelectBlock,
} from '@/app/components/workflow/types'
import { BlockEnum, isTriggerNode } from '@/app/components/workflow/types'
import { FlowType } from '@/types/common'
@ -60,7 +60,7 @@ const ChangeBlock = ({
const renderTrigger = useCallback(() => {
return (
<div className='flex h-8 w-[232px] cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'>
<div className="flex h-8 w-[232px] cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover">
{t('workflow.panel.changeBlock')}
</div>
)
@ -68,14 +68,14 @@ const ChangeBlock = ({
return (
<BlockSelector
placement='bottom-end'
placement="bottom-end"
offset={{
mainAxis: -36,
crossAxis: 4,
}}
onSelect={handleSelect}
trigger={renderTrigger}
popupClassName='min-w-[240px]'
popupClassName="min-w-[240px]"
availableBlocksTypes={availableNodes}
showStartTab={showStartTab}
ignoreNodeIds={ignoreNodeIds}

View File

@ -1,17 +1,17 @@
import type { OffsetOptions } from '@floating-ui/react'
import type { Node } from '@/app/components/workflow/types'
import { RiMoreFill } from '@remixicon/react'
import {
memo,
useCallback,
useState,
} from 'react'
import { RiMoreFill } from '@remixicon/react'
import type { OffsetOptions } from '@floating-ui/react'
import PanelOperatorPopup from './panel-operator-popup'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type { Node } from '@/app/components/workflow/types'
import PanelOperatorPopup from './panel-operator-popup'
type PanelOperatorProps = {
id: string
@ -44,7 +44,7 @@ const PanelOperator = ({
return (
<PortalToFollowElem
placement='bottom-end'
placement="bottom-end"
offset={offset}
open={open}
onOpenChange={handleOpenChange}
@ -58,10 +58,10 @@ const PanelOperator = ({
${triggerClassName}
`}
>
<RiMoreFill className={'h-4 w-4 text-text-tertiary'} />
<RiMoreFill className="h-4 w-4 text-text-tertiary" />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[11]'>
<PortalToFollowElemContent className="z-[11]">
<PanelOperatorPopup
id={id}
data={data}

View File

@ -1,13 +1,11 @@
import type { Node } from '@/app/components/workflow/types'
import {
memo,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useEdges } from 'reactflow'
import ChangeBlock from './change-block'
import {
canRunBySingle,
} from '@/app/components/workflow/utils'
import { CollectionType } from '@/app/components/tools/types'
import {
useNodeDataUpdate,
useNodeMetaData,
@ -16,11 +14,13 @@ import {
useNodesSyncDraft,
} from '@/app/components/workflow/hooks'
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
import type { Node } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { CollectionType } from '@/app/components/tools/types'
import {
canRunBySingle,
} from '@/app/components/workflow/utils'
import { useAllWorkflowTools } from '@/service/use-tools'
import { canFindTool } from '@/utils'
import ChangeBlock from './change-block'
type PanelOperatorPopupProps = {
id: string
@ -53,17 +53,18 @@ const PanelOperatorPopup = ({
const { data: workflowTools } = useAllWorkflowTools()
const isWorkflowTool = data.type === BlockEnum.Tool && data.provider_type === CollectionType.workflow
const workflowAppId = useMemo(() => {
if (!isWorkflowTool || !workflowTools || !data.provider_id) return undefined
if (!isWorkflowTool || !workflowTools || !data.provider_id)
return undefined
const workflowTool = workflowTools.find(item => canFindTool(item.id, data.provider_id))
return workflowTool?.workflow_app_id
}, [isWorkflowTool, workflowTools, data.provider_id])
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, isChildNode)) && (
<>
<div className='p-1'>
<div className="p-1">
{
canRunBySingle(data.type, isChildNode) && (
<div
@ -92,7 +93,7 @@ const PanelOperatorPopup = ({
)
}
</div>
<div className='h-px bg-divider-regular'></div>
<div className="h-px bg-divider-regular"></div>
</>
)
}
@ -102,9 +103,9 @@ const PanelOperatorPopup = ({
{
!nodeMetaData.isSingleton && (
<>
<div className='p-1'>
<div className="p-1">
<div
className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover"
onClick={() => {
onClosePopup()
handleNodesCopy(id)
@ -114,7 +115,7 @@ const PanelOperatorPopup = ({
<ShortcutsName keys={['ctrl', 'c']} />
</div>
<div
className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover"
onClick={() => {
onClosePopup()
handleNodesDuplicate(id)
@ -124,14 +125,14 @@ const PanelOperatorPopup = ({
<ShortcutsName keys={['ctrl', 'd']} />
</div>
</div>
<div className='h-px bg-divider-regular'></div>
<div className="h-px bg-divider-regular"></div>
</>
)
}
{
!nodeMetaData.isUndeletable && (
<>
<div className='p-1'>
<div className="p-1">
<div
className={`
flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary
@ -143,7 +144,7 @@ const PanelOperatorPopup = ({
<ShortcutsName keys={['del']} />
</div>
</div>
<div className='h-px bg-divider-regular'></div>
<div className="h-px bg-divider-regular"></div>
</>
)
}
@ -153,43 +154,45 @@ const PanelOperatorPopup = ({
{
isWorkflowTool && workflowAppId && (
<>
<div className='p-1'>
<div className="p-1">
<a
href={`/app/${workflowAppId}/workflow`}
target='_blank'
className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
target="_blank"
className="flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover"
>
{t('workflow.panel.openWorkflow')}
</a>
</div>
<div className='h-px bg-divider-regular'></div>
<div className="h-px bg-divider-regular"></div>
</>
)
}
{
showHelpLink && nodeMetaData.helpLinkUri && (
<>
<div className='p-1'>
<div className="p-1">
<a
href={nodeMetaData.helpLinkUri}
target='_blank'
className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
target="_blank"
className="flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover"
>
{t('workflow.panel.helpLink')}
</a>
</div>
<div className='h-px bg-divider-regular'></div>
<div className="h-px bg-divider-regular"></div>
</>
)
}
<div className='p-1'>
<div className='px-3 py-2 text-xs text-text-tertiary'>
<div className='mb-1 flex h-[22px] items-center font-medium'>
<div className="p-1">
<div className="px-3 py-2 text-xs text-text-tertiary">
<div className="mb-1 flex h-[22px] items-center font-medium">
{t('workflow.panel.about').toLocaleUpperCase()}
</div>
<div className='mb-1 leading-[18px] text-text-secondary'>{nodeMetaData.description}</div>
<div className='leading-[18px]'>
{t('workflow.panel.createdBy')} {nodeMetaData.author}
<div className="mb-1 leading-[18px] text-text-secondary">{nodeMetaData.description}</div>
<div className="leading-[18px]">
{t('workflow.panel.createdBy')}
{' '}
{nodeMetaData.author}
</div>
</div>
</div>

View File

@ -1,41 +1,41 @@
'use client'
import type { FC, ReactNode } from 'react'
import React, { useCallback, useRef } from 'react'
import {
RiDeleteBinLine,
} from '@remixicon/react'
import copy from 'copy-to-clipboard'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import { BlockEnum, EditionType } from '../../../../types'
import type {
ModelConfig,
Node,
NodeOutPutVar,
Variable,
} from '../../../../types'
import {
RiDeleteBinLine,
} from '@remixicon/react'
import { useBoolean } from 'ahooks'
import copy from 'copy-to-clipboard'
import React, { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Wrap from '../editor/wrap'
import { CodeLanguage } from '../../../code/types'
import PromptGeneratorBtn from '../../../llm/components/prompt-generator-btn'
import { cn } from '@/utils/classnames'
import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn'
import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend'
import PromptEditor from '@/app/components/base/prompt-editor'
import {
Copy,
CopyCheck,
} from '@/app/components/base/icons/src/vender/line/files'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars'
import Switch from '@/app/components/base/switch'
import { Jinja } from '@/app/components/base/icons/src/vender/workflow'
import { useStore } from '@/app/components/workflow/store'
import PromptEditor from '@/app/components/base/prompt-editor'
import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block'
import Switch from '@/app/components/base/switch'
import Tooltip from '@/app/components/base/tooltip'
import { useWorkflowVariableType } from '@/app/components/workflow/hooks'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars'
import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn'
import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend'
import { useStore } from '@/app/components/workflow/store'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { cn } from '@/utils/classnames'
import { BlockEnum, EditionType } from '../../../../types'
import { CodeLanguage } from '../../../code/types'
import PromptGeneratorBtn from '../../../llm/components/prompt-generator-btn'
import Wrap from '../editor/wrap'
type Props = {
className?: string
@ -158,39 +158,43 @@ const Editor: FC<Props> = ({
<div ref={ref} className={cn(isFocus ? (gradientBorder && 'bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2') : 'bg-transparent', isExpand && 'h-full', '!rounded-[9px] p-0.5', containerClassName)}>
<div className={cn(isFocus ? 'bg-background-default' : 'bg-components-input-bg-normal', isExpand && 'flex h-full flex-col', 'rounded-lg', containerClassName)}>
<div className={cn('flex items-center justify-between pl-3 pr-2 pt-1', headerClassName)}>
<div className='flex gap-2'>
<div className={cn('text-xs font-semibold uppercase leading-4 text-text-secondary', titleClassName)}>{title} {required && <span className='text-text-destructive'>*</span>}</div>
<div className="flex gap-2">
<div className={cn('text-xs font-semibold uppercase leading-4 text-text-secondary', titleClassName)}>
{title}
{' '}
{required && <span className="text-text-destructive">*</span>}
</div>
{titleTooltip && <Tooltip popupContent={titleTooltip} />}
</div>
<div className='flex items-center'>
<div className='text-xs font-medium leading-[18px] text-text-tertiary'>{value?.length || 0}</div>
<div className="flex items-center">
<div className="text-xs font-medium leading-[18px] text-text-tertiary">{value?.length || 0}</div>
{isSupportPromptGenerator && (
<PromptGeneratorBtn
nodeId={nodeId!}
editorId={editorId}
className='ml-[5px]'
className="ml-[5px]"
onGenerated={onGenerated}
modelConfig={modelConfig}
currentPrompt={value}
/>
)}
<div className='ml-2 mr-2 h-3 w-px bg-divider-regular'></div>
<div className="ml-2 mr-2 h-3 w-px bg-divider-regular"></div>
{/* Operations */}
<div className='flex items-center space-x-[2px]'>
<div className="flex items-center space-x-[2px]">
{isSupportJinja && (
<Tooltip
popupContent={
popupContent={(
<div>
<div>{t('workflow.common.enableJinja')}</div>
<a className='text-text-accent' target='_blank' href='https://jinja.palletsprojects.com/en/2.10.x/'>{t('workflow.common.learnMore')}</a>
<a className="text-text-accent" target="_blank" href="https://jinja.palletsprojects.com/en/2.10.x/">{t('workflow.common.learnMore')}</a>
</div>
}
)}
>
<div className={cn(editionType === EditionType.jinja2 && 'border-components-button-ghost-bg-hover bg-components-button-ghost-bg-hover', 'flex h-[22px] items-center space-x-0.5 rounded-[5px] border border-transparent px-1.5 hover:border-components-button-ghost-bg-hover')}>
<Jinja className='h-3 w-6 text-text-quaternary' />
<Jinja className="h-3 w-6 text-text-quaternary" />
<Switch
size='sm'
size="sm"
defaultValue={editionType === EditionType.jinja2}
onChange={(checked) => {
onEditionTypeChange?.(checked ? EditionType.jinja2 : EditionType.basic)
@ -205,27 +209,26 @@ const Editor: FC<Props> = ({
popupContent={`${t('workflow.common.insertVarTip')}`}
>
<ActionButton onClick={handleInsertVariable}>
<Variable02 className='h-4 w-4' />
<Variable02 className="h-4 w-4" />
</ActionButton>
</Tooltip>
)}
{showRemove && (
<ActionButton onClick={onRemove}>
<RiDeleteBinLine className='h-4 w-4' />
<RiDeleteBinLine className="h-4 w-4" />
</ActionButton>
)}
{!isCopied
? (
<ActionButton onClick={handleCopy}>
<Copy className='h-4 w-4' />
</ActionButton>
)
<ActionButton onClick={handleCopy}>
<Copy className="h-4 w-4" />
</ActionButton>
)
: (
<ActionButton>
<CopyCheck className='h-4 w-4' />
</ActionButton>
)
}
<ActionButton>
<CopyCheck className="h-4 w-4" />
</ActionButton>
)}
<ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} />
</div>
@ -236,83 +239,83 @@ const Editor: FC<Props> = ({
<div className={cn('pb-2', isExpand && 'flex grow flex-col')}>
{!(isSupportJinja && editionType === EditionType.jinja2)
? (
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
<PromptEditor
key={controlPromptEditorRerenderKey}
placeholder={placeholder}
placeholderClassName={placeholderClassName}
instanceId={instanceId}
compact
className={cn('min-h-[56px]', inputClassName)}
style={isExpand ? { height: editorExpandHeight - 5 } : {}}
value={value}
contextBlock={{
show: justVar ? false : isShowContext,
selectable: !hasSetBlockStatus?.context,
canNotAddContext: true,
}}
historyBlock={{
show: justVar ? false : isShowHistory,
selectable: !hasSetBlockStatus?.history,
history: {
user: 'Human',
assistant: 'Assistant',
},
}}
queryBlock={{
show: false, // use [sys.query] instead of query block
selectable: false,
}}
workflowVariableBlock={{
show: true,
variables: nodesOutputVars || [],
getVarType: getVarType as any,
workflowNodesMap: availableNodes.reduce((acc, node) => {
acc[node.id] = {
title: node.data.title,
type: node.data.type,
width: node.width,
height: node.height,
position: node.position,
}
if (node.data.type === BlockEnum.Start) {
acc.sys = {
title: t('workflow.blocks.start'),
type: BlockEnum.Start,
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
<PromptEditor
key={controlPromptEditorRerenderKey}
placeholder={placeholder}
placeholderClassName={placeholderClassName}
instanceId={instanceId}
compact
className={cn('min-h-[56px]', inputClassName)}
style={isExpand ? { height: editorExpandHeight - 5 } : {}}
value={value}
contextBlock={{
show: justVar ? false : isShowContext,
selectable: !hasSetBlockStatus?.context,
canNotAddContext: true,
}}
historyBlock={{
show: justVar ? false : isShowHistory,
selectable: !hasSetBlockStatus?.history,
history: {
user: 'Human',
assistant: 'Assistant',
},
}}
queryBlock={{
show: false, // use [sys.query] instead of query block
selectable: false,
}}
workflowVariableBlock={{
show: true,
variables: nodesOutputVars || [],
getVarType: getVarType as any,
workflowNodesMap: availableNodes.reduce((acc, node) => {
acc[node.id] = {
title: node.data.title,
type: node.data.type,
width: node.width,
height: node.height,
position: node.position,
}
}
return acc
}, {} as any),
showManageInputField: !!pipelineId,
onManageInputField: () => setShowInputFieldPanel?.(true),
}}
onChange={onChange}
onBlur={setBlur}
onFocus={setFocus}
editable={!readOnly}
isSupportFileVar={isSupportFileVar}
/>
{/* to patch Editor not support dynamic change editable status */}
{readOnly && <div className='absolute inset-0 z-10'></div>}
</div>
)
if (node.data.type === BlockEnum.Start) {
acc.sys = {
title: t('workflow.blocks.start'),
type: BlockEnum.Start,
}
}
return acc
}, {} as any),
showManageInputField: !!pipelineId,
onManageInputField: () => setShowInputFieldPanel?.(true),
}}
onChange={onChange}
onBlur={setBlur}
onFocus={setFocus}
editable={!readOnly}
isSupportFileVar={isSupportFileVar}
/>
{/* to patch Editor not support dynamic change editable status */}
{readOnly && <div className="absolute inset-0 z-10"></div>}
</div>
)
: (
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
<CodeEditor
availableVars={nodesOutputVars || []}
varList={varList}
onAddVar={handleAddVariable}
isInNode
readOnly={readOnly}
language={CodeLanguage.python3}
value={value}
onChange={onChange}
noWrapper
isExpand={isExpand}
className={inputClassName}
/>
</div>
)}
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
<CodeEditor
availableVars={nodesOutputVars || []}
varList={varList}
onAddVar={handleAddVariable}
isInNode
readOnly={readOnly}
language={CodeLanguage.python3}
value={value}
onChange={onChange}
noWrapper
isExpand={isExpand}
className={inputClassName}
/>
</div>
)}
</div>
</div>
</div>

View File

@ -1,13 +1,14 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import {
VariableLabelInText,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { cn } from '@/utils/classnames'
import { useWorkflow } from '../../../hooks'
import { BlockEnum } from '../../../types'
import { getNodeInfoById, isSystemVar } from './variable/utils'
import {
VariableLabelInText,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
type Props = {
nodeId: string
value: string
@ -29,29 +30,31 @@ const ReadonlyInputWithSelectVar: FC<Props> = ({
const res = (() => {
const vars: string[] = []
const strWithVarPlaceholder = value.replaceAll(/{{#([^#]*)#}}/g, (_match, p1) => {
const strWithVarPlaceholder = value.replaceAll(/\{\{#([^#]*)#\}\}/g, (_match, p1) => {
vars.push(p1)
return VAR_PLACEHOLDER
})
const html: React.JSX.Element[] = strWithVarPlaceholder.split(VAR_PLACEHOLDER).map((str, index) => {
if (!vars[index])
return <span className='relative top-[-3px] leading-[16px]' key={index}>{str}</span>
return <span className="relative top-[-3px] leading-[16px]" key={index}>{str}</span>
const value = vars[index].split('.')
const isSystem = isSystemVar(value)
const node = (isSystem ? startNode : getNodeInfoById(availableNodes, value[0]))?.data
const isShowAPart = value.length > 2
return (<span key={index}>
<span className='relative top-[-3px] leading-[16px]'>{str}</span>
<VariableLabelInText
nodeTitle={node?.title}
nodeType={node?.type}
notShowFullPath={isShowAPart}
variables={value}
/>
</span>)
return (
<span key={index}>
<span className="relative top-[-3px] leading-[16px]">{str}</span>
<VariableLabelInText
nodeTitle={node?.title}
nodeType={node?.type}
notShowFullPath={isShowAPart}
variables={value}
/>
</span>
)
})
return html
})()

View File

@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { RiDeleteBinLine } from '@remixicon/react'
import React from 'react'
import ActionButton from '@/app/components/base/action-button'
type Props = {
@ -13,8 +13,8 @@ const Remove: FC<Props> = ({
onClick,
}) => {
return (
<ActionButton size='l' className='group shrink-0 hover:!bg-state-destructive-hover' onClick={onClick}>
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary group-hover:text-text-destructive' />
<ActionButton size="l" className="group shrink-0 hover:!bg-state-destructive-hover" onClick={onClick}>
<RiDeleteBinLine className="h-4 w-4 text-text-tertiary group-hover:text-text-destructive" />
</ActionButton>
)
}

View File

@ -1,12 +1,12 @@
import type { WorkflowRetryConfig } from './types'
import type { NodeTracing } from '@/types/workflow'
import {
useCallback,
useState,
} from 'react'
import type { WorkflowRetryConfig } from './types'
import {
useNodeDataUpdate,
} from '@/app/components/workflow/hooks'
import type { NodeTracing } from '@/types/workflow'
export const useRetryConfig = (
id: string,

View File

@ -1,11 +1,11 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import type { Node } from '@/app/components/workflow/types'
import {
RiAlertFill,
RiCheckboxCircleFill,
RiLoader2Line,
} from '@remixicon/react'
import type { Node } from '@/app/components/workflow/types'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { NodeRunningStatus } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
@ -38,14 +38,15 @@ const RetryOnNode = ({
return null
return (
<div className='mb-1 px-3'>
<div className="mb-1 px-3">
<div className={cn(
'system-xs-medium-uppercase flex items-center justify-between rounded-md border-[0.5px] border-transparent bg-workflow-block-parma-bg px-[5px] py-1 text-text-tertiary',
isRunning && 'border-state-accent-active bg-state-accent-hover text-text-accent',
isSuccessful && 'border-state-success-active bg-state-success-hover text-text-success',
(isException || isFailed) && 'border-state-warning-active bg-state-warning-hover text-text-warning',
)}>
<div className='flex items-center'>
)}
>
<div className="flex items-center">
{
showDefault && (
t('workflow.nodes.common.retry.retryTimes', { times: retry_config.max_retries })
@ -54,7 +55,7 @@ const RetryOnNode = ({
{
isRunning && (
<>
<RiLoader2Line className='mr-1 h-3.5 w-3.5 animate-spin' />
<RiLoader2Line className="mr-1 h-3.5 w-3.5 animate-spin" />
{t('workflow.nodes.common.retry.retrying')}
</>
)
@ -62,7 +63,7 @@ const RetryOnNode = ({
{
isSuccessful && (
<>
<RiCheckboxCircleFill className='mr-1 h-3.5 w-3.5' />
<RiCheckboxCircleFill className="mr-1 h-3.5 w-3.5" />
{t('workflow.nodes.common.retry.retrySuccessful')}
</>
)
@ -70,7 +71,7 @@ const RetryOnNode = ({
{
(isFailed || isException) && (
<>
<RiAlertFill className='mr-1 h-3.5 w-3.5' />
<RiAlertFill className="mr-1 h-3.5 w-3.5" />
{t('workflow.nodes.common.retry.retryFailed')}
</>
)
@ -79,7 +80,9 @@ const RetryOnNode = ({
{
!showDefault && !!data._retryIndex && (
<div>
{data._retryIndex}/{data.retry_config?.max_retries}
{data._retryIndex}
/
{data.retry_config?.max_retries}
</div>
)
}

View File

@ -1,13 +1,13 @@
import { useTranslation } from 'react-i18next'
import { useRetryConfig } from './hooks'
import s from './style.module.css'
import Switch from '@/app/components/base/switch'
import Slider from '@/app/components/base/slider'
import Input from '@/app/components/base/input'
import type {
Node,
} from '@/app/components/workflow/types'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import Slider from '@/app/components/base/slider'
import Switch from '@/app/components/base/switch'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import { useRetryConfig } from './hooks'
import s from './style.module.css'
type RetryOnPanelProps = Pick<Node, 'id' | 'data'>
const RetryOnPanel = ({
@ -52,10 +52,10 @@ const RetryOnPanel = ({
return (
<>
<div className='pt-2'>
<div className='flex h-10 items-center justify-between px-4 py-2'>
<div className='flex items-center'>
<div className='system-sm-semibold-uppercase mr-0.5 text-text-secondary'>{t('workflow.nodes.common.retry.retryOnFailure')}</div>
<div className="pt-2">
<div className="flex h-10 items-center justify-between px-4 py-2">
<div className="flex items-center">
<div className="system-sm-semibold-uppercase mr-0.5 text-text-secondary">{t('workflow.nodes.common.retry.retryOnFailure')}</div>
</div>
<Switch
defaultValue={retry_config?.retry_enabled}
@ -64,45 +64,43 @@ const RetryOnPanel = ({
</div>
{
retry_config?.retry_enabled && (
<div className='px-4 pb-2'>
<div className='mb-1 flex w-full items-center'>
<div className='system-xs-medium-uppercase mr-2 grow text-text-secondary'>{t('workflow.nodes.common.retry.maxRetries')}</div>
<div className="px-4 pb-2">
<div className="mb-1 flex w-full items-center">
<div className="system-xs-medium-uppercase mr-2 grow text-text-secondary">{t('workflow.nodes.common.retry.maxRetries')}</div>
<Slider
className='mr-3 w-[108px]'
className="mr-3 w-[108px]"
value={retry_config?.max_retries || 3}
onChange={handleMaxRetriesChange}
min={1}
max={10}
/>
<Input
type='number'
wrapperClassName='w-[100px]'
type="number"
wrapperClassName="w-[100px]"
value={retry_config?.max_retries || 3}
onChange={e =>
handleMaxRetriesChange(Number.parseInt(e.currentTarget.value, 10) || 3)
}
handleMaxRetriesChange(Number.parseInt(e.currentTarget.value, 10) || 3)}
min={1}
max={10}
unit={t('workflow.nodes.common.retry.times') || ''}
className={s.input}
/>
</div>
<div className='flex items-center'>
<div className='system-xs-medium-uppercase mr-2 grow text-text-secondary'>{t('workflow.nodes.common.retry.retryInterval')}</div>
<div className="flex items-center">
<div className="system-xs-medium-uppercase mr-2 grow text-text-secondary">{t('workflow.nodes.common.retry.retryInterval')}</div>
<Slider
className='mr-3 w-[108px]'
className="mr-3 w-[108px]"
value={retry_config?.retry_interval || 1000}
onChange={handleRetryIntervalChange}
min={100}
max={5000}
/>
<Input
type='number'
wrapperClassName='w-[100px]'
type="number"
wrapperClassName="w-[100px]"
value={retry_config?.retry_interval || 1000}
onChange={e =>
handleRetryIntervalChange(Number.parseInt(e.currentTarget.value, 10) || 1000)
}
handleRetryIntervalChange(Number.parseInt(e.currentTarget.value, 10) || 1000)}
min={100}
max={5000}
unit={t('workflow.nodes.common.retry.ms') || ''}
@ -113,7 +111,7 @@ const RetryOnPanel = ({
)
}
</div>
<Split className='mx-4 mt-2' />
<Split className="mx-4 mt-2" />
</>
)
}

View File

@ -1,10 +1,11 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useBoolean, useClickAway } from 'ahooks'
import { cn } from '@/utils/classnames'
import React from 'react'
import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows'
import { Check } from '@/app/components/base/icons/src/vender/line/general'
import { cn } from '@/utils/classnames'
type Item = {
value: string
label: string
@ -55,21 +56,22 @@ const TypeSelector: FC<Props> = ({
<div className={cn(!trigger && !noLeft && 'left-[-8px]', 'relative select-none', className)} ref={ref}>
{trigger
? (
<div
onClick={toggleShow}
className={cn(!readonly && 'cursor-pointer')}
>
{trigger}
</div>
)
<div
onClick={toggleShow}
className={cn(!readonly && 'cursor-pointer')}
>
{trigger}
</div>
)
: (
<div
onClick={toggleShow}
className={cn(showOption && 'bg-state-base-hover', 'flex h-5 cursor-pointer items-center rounded-md pl-1 pr-0.5 text-xs font-semibold text-text-secondary hover:bg-state-base-hover')}>
<div className={cn('text-sm font-semibold', uppercase && 'uppercase', noValue && 'text-text-tertiary', triggerClassName)}>{!noValue ? item?.label : placeholder}</div>
{!readonly && <DropDownIcon className='h-3 w-3 ' />}
</div>
)}
<div
onClick={toggleShow}
className={cn(showOption && 'bg-state-base-hover', 'flex h-5 cursor-pointer items-center rounded-md pl-1 pr-0.5 text-xs font-semibold text-text-secondary hover:bg-state-base-hover')}
>
<div className={cn('text-sm font-semibold', uppercase && 'uppercase', noValue && 'text-text-tertiary', triggerClassName)}>{!noValue ? item?.label : placeholder}</div>
{!readonly && <DropDownIcon className="h-3 w-3 " />}
</div>
)}
{(showOption && !readonly) && (
<div className={cn('absolute top-[24px] z-10 w-[120px] select-none rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg', popupClassName)}>
@ -83,13 +85,11 @@ const TypeSelector: FC<Props> = ({
className={cn(itemClassName, uppercase && 'uppercase', 'flex h-[30px] min-w-[44px] cursor-pointer items-center justify-between rounded-lg px-3 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover')}
>
<div>{item.label}</div>
{showChecked && item.value === value && <Check className='h-4 w-4 text-text-primary' />}
{showChecked && item.value === value && <Check className="h-4 w-4 text-text-primary" />}
</div>
))
}
))}
</div>
)
}
)}
</div>
)
}

View File

@ -1,7 +1,8 @@
import type { ComponentProps, PropsWithChildren, ReactNode } from 'react'
import { memo } from 'react'
import Tooltip from '@/app/components/base/tooltip'
import Indicator from '@/app/components/header/indicator'
import { cn } from '@/utils/classnames'
import { type ComponentProps, type PropsWithChildren, type ReactNode, memo } from 'react'
export type SettingItemProps = PropsWithChildren<{
label: string
@ -12,17 +13,19 @@ export type SettingItemProps = PropsWithChildren<{
export const SettingItem = memo(({ label, children, status, tooltip }: SettingItemProps) => {
const indicator: ComponentProps<typeof Indicator>['color'] = status === 'error' ? 'red' : status === 'warning' ? 'yellow' : undefined
const needTooltip = ['error', 'warning'].includes(status as any)
return <div className='relative flex items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1.5 py-1 text-xs font-normal'>
<div className={cn('system-xs-medium-uppercase max-w-full shrink-0 truncate text-text-tertiary', !!children && 'max-w-[100px]')}>
{label}
</div>
<Tooltip popupContent={tooltip} disabled={!needTooltip}>
<div className='system-xs-medium truncate text-right text-text-secondary'>
{children}
return (
<div className="relative flex items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1.5 py-1 text-xs font-normal">
<div className={cn('system-xs-medium-uppercase max-w-full shrink-0 truncate text-text-tertiary', !!children && 'max-w-[100px]')}>
{label}
</div>
</Tooltip>
{indicator && <Indicator color={indicator} className='absolute -right-0.5 -top-0.5' />}
</div>
<Tooltip popupContent={tooltip} disabled={!needTooltip}>
<div className="system-xs-medium truncate text-right text-text-secondary">
{children}
</div>
</Tooltip>
{indicator && <Indicator color={indicator} className="absolute -right-0.5 -top-0.5" />}
</div>
)
})
SettingItem.displayName = 'SettingItem'

View File

@ -1,8 +1,9 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { cn } from '@/utils/classnames'
import VarHighlight from '@/app/components/app/configuration/base/var-highlight'
import { cn } from '@/utils/classnames'
type Props = {
isFocus?: boolean
onFocus?: () => void
@ -46,20 +47,21 @@ const SupportVarInput: FC<Props> = ({
<div
className={
cn(wrapClassName, 'flex h-full w-full')
} onClick={onFocus}
}
onClick={onFocus}
>
{(isFocus && !readonly && children)
? (
children
)
children
)
: (
<div
className={cn(textClassName, 'h-full w-0 grow truncate whitespace-nowrap')}
title={value}
>
{renderSafeContent(value || '')}
</div>
)}
<div
className={cn(textClassName, 'h-full w-0 grow truncate whitespace-nowrap')}
title={value}
>
{renderSafeContent(value || '')}
</div>
)}
</div>
)
}

View File

@ -1,20 +1,20 @@
'use client'
import Badge from '@/app/components/base/badge'
import Tooltip from '@/app/components/base/tooltip'
import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker'
import type { FC, ReactNode } from 'react'
import { RiArrowLeftRightLine, RiExternalLinkLine } from '@remixicon/react'
import type { ReactNode } from 'react'
import { type FC, useCallback, useState } from 'react'
import { useBoolean } from 'ahooks'
import { useCheckInstalled, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
import { cn } from '@/utils/classnames'
import PluginMutationModel from '@/app/components/plugins/plugin-mutation-model'
import Link from 'next/link'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge'
import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index'
import Tooltip from '@/app/components/base/tooltip'
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
import { pluginManifestToCardPluginProps } from '@/app/components/plugins/install-plugin/utils'
import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import PluginMutationModel from '@/app/components/plugins/plugin-mutation-model'
import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker'
import { useCheckInstalled, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
import { cn } from '@/utils/classnames'
import { getMarketplaceUrl } from '@/utils/var'
export type SwitchPluginVersionProps = {
@ -31,8 +31,8 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => {
const [isShow, setIsShow] = useState(false)
const [isShowUpdateModal, { setTrue: showUpdateModal, setFalse: hideUpdateModal }] = useBoolean(false)
const [target, setTarget] = useState<{
version: string,
pluginUniqueIden: string;
version: string
pluginUniqueIden: string
}>()
const pluginDetails = useCheckInstalled({
pluginIds: [pluginId],
@ -58,7 +58,8 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => {
onSuccess() {
handleUpdatedFromMarketplace()
},
})
},
)
}
const { t } = useTranslation()
@ -66,67 +67,75 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => {
if (!uniqueIdentifier || !pluginId)
return null
return <Tooltip popupContent={!isShow && !isShowUpdateModal && tooltip} triggerMethod='hover'>
<div className={cn('flex w-fit items-center justify-center', className)} onClick={e => e.stopPropagation()}>
{isShowUpdateModal && pluginDetail && <PluginMutationModel
onCancel={hideUpdateModal}
plugin={pluginManifestToCardPluginProps({
...pluginDetail.declaration,
icon: icon!,
})}
mutation={mutation}
mutate={install}
confirmButtonText={t('workflow.nodes.agent.installPlugin.install')}
cancelButtonText={t('workflow.nodes.agent.installPlugin.cancel')}
modelTitle={t('workflow.nodes.agent.installPlugin.title')}
description={t('workflow.nodes.agent.installPlugin.desc')}
cardTitleLeft={<>
<Badge2 className='mx-1' size="s" state={BadgeState.Warning}>
{`${pluginDetail.version} -> ${target!.version}`}
</Badge2>
</>}
modalBottomLeft={
<Link
className='flex items-center justify-center gap-1'
href={getMarketplaceUrl(`/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`)}
target='_blank'
>
<span className='system-xs-regular text-xs text-text-accent'>
{t('workflow.nodes.agent.installPlugin.changelog')}
</span>
<RiExternalLinkLine className='size-3 text-text-accent' />
</Link>
}
/>}
{pluginDetail && <PluginVersionPicker
isShow={isShow}
onShowChange={setIsShow}
pluginID={pluginId}
currentVersion={pluginDetail.version}
onSelect={(state) => {
setTarget({
pluginUniqueIden: state.unique_identifier,
version: state.version,
})
showUpdateModal()
}}
trigger={
<Badge
className={cn(
'mx-1 flex hover:bg-state-base-hover',
isShow && 'bg-state-base-hover',
)}
uppercase={true}
text={
return (
<Tooltip popupContent={!isShow && !isShowUpdateModal && tooltip} triggerMethod="hover">
<div className={cn('flex w-fit items-center justify-center', className)} onClick={e => e.stopPropagation()}>
{isShowUpdateModal && pluginDetail && (
<PluginMutationModel
onCancel={hideUpdateModal}
plugin={pluginManifestToCardPluginProps({
...pluginDetail.declaration,
icon: icon!,
})}
mutation={mutation}
mutate={install}
confirmButtonText={t('workflow.nodes.agent.installPlugin.install')}
cancelButtonText={t('workflow.nodes.agent.installPlugin.cancel')}
modelTitle={t('workflow.nodes.agent.installPlugin.title')}
description={t('workflow.nodes.agent.installPlugin.desc')}
cardTitleLeft={(
<>
<div>{pluginDetail.version}</div>
<RiArrowLeftRightLine className='ml-1 h-3 w-3 text-text-tertiary' />
<Badge2 className="mx-1" size="s" state={BadgeState.Warning}>
{`${pluginDetail.version} -> ${target!.version}`}
</Badge2>
</>
}
hasRedCornerMark={true}
)}
modalBottomLeft={(
<Link
className="flex items-center justify-center gap-1"
href={getMarketplaceUrl(`/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`)}
target="_blank"
>
<span className="system-xs-regular text-xs text-text-accent">
{t('workflow.nodes.agent.installPlugin.changelog')}
</span>
<RiExternalLinkLine className="size-3 text-text-accent" />
</Link>
)}
/>
}
/>}
</div>
</Tooltip>
)}
{pluginDetail && (
<PluginVersionPicker
isShow={isShow}
onShowChange={setIsShow}
pluginID={pluginId}
currentVersion={pluginDetail.version}
onSelect={(state) => {
setTarget({
pluginUniqueIden: state.unique_identifier,
version: state.version,
})
showUpdateModal()
}}
trigger={(
<Badge
className={cn(
'mx-1 flex hover:bg-state-base-hover',
isShow && 'bg-state-base-hover',
)}
uppercase={true}
text={(
<>
<div>{pluginDetail.version}</div>
<RiArrowLeftRightLine className="ml-1 h-3 w-3 text-text-tertiary" />
</>
)}
hasRedCornerMark={true}
/>
)}
/>
)}
</div>
</Tooltip>
)
}

View File

@ -3,8 +3,8 @@ import {
useCallback,
useState,
} from 'react'
import Textarea from 'react-textarea-autosize'
import { useTranslation } from 'react-i18next'
import Textarea from 'react-textarea-autosize'
type TitleInputProps = {
value: string

View File

@ -1,10 +1,10 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import {
RiCollapseDiagonalLine,
RiExpandDiagonalLine,
} from '@remixicon/react'
import React, { useCallback } from 'react'
import ActionButton from '@/app/components/base/action-button'
type Props = {
@ -23,7 +23,7 @@ const ExpandBtn: FC<Props> = ({
const Icon = isExpand ? RiCollapseDiagonalLine : RiExpandDiagonalLine
return (
<ActionButton onClick={handleToggle}>
<Icon className='h-4 w-4' />
<Icon className="h-4 w-4" />
</ActionButton>
)
}

View File

@ -1,18 +1,18 @@
import { useCallback, useMemo } from 'react'
import { useNodes, useReactFlow, useStoreApi } from 'reactflow'
import { useTranslation } from 'react-i18next'
import type {
CommonNodeType,
Node,
ValueSelector,
VarType,
} from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useNodes, useReactFlow, useStoreApi } from 'reactflow'
import { getNodeInfoById, isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { isExceptionVariable } from '@/app/components/workflow/utils'
import {
VariableLabelInSelect,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { BlockEnum } from '@/app/components/workflow/types'
import { isExceptionVariable } from '@/app/components/workflow/utils'
type VariableTagProps = {
valueSelector: ValueSelector

View File

@ -1,10 +1,10 @@
'use client'
import type { FC } from 'react'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import React from 'react'
import { useTranslation } from 'react-i18next'
import VarReferenceVars from './var-reference-vars'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import ListEmpty from '@/app/components/base/list-empty'
import VarReferenceVars from './var-reference-vars'
type Props = {
vars: NodeOutPutVar[]
@ -19,21 +19,24 @@ const AssignedVarReferencePopup: FC<Props> = ({
const { t } = useTranslation()
// max-h-[300px] overflow-y-auto todo: use portal to handle long list
return (
<div className='bg-components-panel-bg-bur w-[352px] rounded-lg border-[0.5px] border-components-panel-border p-1 shadow-lg' >
<div className="bg-components-panel-bg-bur w-[352px] rounded-lg border-[0.5px] border-components-panel-border p-1 shadow-lg">
{(!vars || vars.length === 0)
? <ListEmpty
title={t('workflow.nodes.assigner.noAssignedVars') || ''}
description={t('workflow.nodes.assigner.assignedVarsDescription')}
/>
: <VarReferenceVars
searchBoxClassName='mt-1'
vars={vars}
onChange={onChange}
itemWidth={itemWidth}
isSupportFileVar
/>
}
</div >
? (
<ListEmpty
title={t('workflow.nodes.assigner.noAssignedVars') || ''}
description={t('workflow.nodes.assigner.assignedVarsDescription')}
/>
)
: (
<VarReferenceVars
searchBoxClassName="mt-1"
vars={vars}
onChange={onChange}
itemWidth={itemWidth}
isSupportFileVar
/>
)}
</div>
)
}
export default React.memo(AssignedVarReferencePopup)

View File

@ -1,12 +1,12 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Var } from '@/app/components/workflow/types'
import React, { useCallback } from 'react'
import { SimpleSelect } from '@/app/components/base/select'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import type { Var } from '@/app/components/workflow/types'
import { SimpleSelect } from '@/app/components/base/select'
type Props = {
schema: Partial<CredentialFormSchema>
@ -42,8 +42,8 @@ const ConstantField: FC<Props> = ({
<>
{(schema.type === FormTypeEnum.select || schema.type === FormTypeEnum.dynamicSelect) && (
<SimpleSelect
wrapperClassName='w-full !h-8'
className='flex items-center'
wrapperClassName="w-full !h-8"
className="flex items-center"
disabled={readonly}
defaultValue={value}
items={(schema as CredentialFormSchemaSelect).options.map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))}
@ -55,8 +55,8 @@ const ConstantField: FC<Props> = ({
)}
{schema.type === FormTypeEnum.textNumber && (
<input
type='number'
className='h-8 w-full overflow-hidden rounded-lg bg-workflow-block-parma-bg p-2 text-[13px] font-normal leading-8 text-text-secondary placeholder:text-gray-400 focus:outline-none'
type="number"
className="h-8 w-full overflow-hidden rounded-lg bg-workflow-block-parma-bg p-2 text-[13px] font-normal leading-8 text-text-secondary placeholder:text-gray-400 focus:outline-none"
value={value}
onChange={handleStaticChange}
readOnly={readonly}

View File

@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
import { RiAddLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
type ManageInputFieldProps = {
onManage: () => void
@ -11,22 +11,22 @@ const ManageInputField = ({
const { t } = useTranslation()
return (
<div className='flex items-center border-t border-divider-subtle pt-1'>
<div className="flex items-center border-t border-divider-subtle pt-1">
<div
className='flex h-8 grow cursor-pointer items-center px-3'
className="flex h-8 grow cursor-pointer items-center px-3"
onClick={onManage}
>
<RiAddLine className='mr-1 h-4 w-4 text-text-tertiary' />
<RiAddLine className="mr-1 h-4 w-4 text-text-tertiary" />
<div
className='system-xs-medium truncate text-text-tertiary'
title='Create user input field'
className="system-xs-medium truncate text-text-tertiary"
title="Create user input field"
>
{t('pipeline.inputField.create')}
</div>
</div>
<div className='mx-1 h-3 w-[1px] shrink-0 bg-divider-regular'></div>
<div className="mx-1 h-3 w-[1px] shrink-0 bg-divider-regular"></div>
<div
className='system-xs-medium flex h-8 shrink-0 cursor-pointer items-center justify-center px-3 text-text-tertiary'
className="system-xs-medium flex h-8 shrink-0 cursor-pointer items-center justify-center px-3 text-text-tertiary"
onClick={onManage}
>
{t('pipeline.inputField.manage')}

View File

@ -7,7 +7,7 @@ function matchTheSchemaType(scheme: AnyObj, target: AnyObj): boolean {
const isMatch = (schema: AnyObj, t: AnyObj): boolean => {
const oSchema = isObj(schema)
const oT = isObj(t)
if(!oSchema)
if (!oSchema)
return true
if (!oT) { // ignore the object without type
// deep find oSchema has type
@ -24,14 +24,18 @@ function matchTheSchemaType(scheme: AnyObj, target: AnyObj): boolean {
const ty = (t as any).type
const isTypeValueObj = isObj(tx)
if(!isTypeValueObj) // caution: type can be object, so that it would not be compare by value
if (tx !== ty) return false
if (!isTypeValueObj) { // caution: type can be object, so that it would not be compare by value
if (tx !== ty)
return false
}
// recurse into all keys
const keys = new Set([...Object.keys(schema as object), ...Object.keys(t as object)])
for (const k of keys) {
if (k === 'type' && !isTypeValueObj) continue // already checked
if (!isMatch((schema as any)[k], (t as any)[k])) return false
if (k === 'type' && !isTypeValueObj)
continue // already checked
if (!isMatch((schema as any)[k], (t as any)[k]))
return false
}
return true
}

View File

@ -1,22 +1,22 @@
'use client'
import type { FC } from 'react'
import type { Field as FieldType } from '../../../../../llm/types'
import type { ValueSelector } from '@/app/components/workflow/types'
import { RiMoreFill } from '@remixicon/react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { cn } from '@/utils/classnames'
import { Type } from '../../../../../llm/types'
import { getFieldType } from '../../../../../llm/utils'
import type { Field as FieldType } from '../../../../../llm/types'
import { cn } from '@/utils/classnames'
import TreeIndentLine from '../tree-indent-line'
import { RiMoreFill } from '@remixicon/react'
import Tooltip from '@/app/components/base/tooltip'
import type { ValueSelector } from '@/app/components/workflow/types'
import { useTranslation } from 'react-i18next'
const MAX_DEPTH = 10
type Props = {
valueSelector: ValueSelector
name: string,
payload: FieldType,
name: string
payload: FieldType
depth?: number
readonly?: boolean
onSelect?: (valueSelector: ValueSelector) => void
@ -43,15 +43,17 @@ const Field: FC<Props> = ({
className={cn('flex items-center justify-between rounded-md pr-2', !readonly && 'hover:bg-state-base-hover', depth !== MAX_DEPTH + 1 && 'cursor-pointer')}
onMouseDown={() => !readonly && onSelect?.([...valueSelector, name])}
>
<div className='flex grow items-stretch'>
<div className="flex grow items-stretch">
<TreeIndentLine depth={depth} />
{depth === MAX_DEPTH + 1 ? (
<RiMoreFill className='h-3 w-3 text-text-tertiary' />
) : (<div className={cn('system-sm-medium h-6 w-0 grow truncate leading-6 text-text-secondary', isHighlight && 'text-text-accent')}>{name}</div>)}
{depth === MAX_DEPTH + 1
? (
<RiMoreFill className="h-3 w-3 text-text-tertiary" />
)
: (<div className={cn('system-sm-medium h-6 w-0 grow truncate leading-6 text-text-secondary', isHighlight && 'text-text-accent')}>{name}</div>)}
</div>
{depth < MAX_DEPTH + 1 && (
<div className='system-xs-regular ml-2 shrink-0 text-text-tertiary'>{getFieldType(payload)}</div>
<div className="system-xs-regular ml-2 shrink-0 text-text-tertiary">{getFieldType(payload)}</div>
)}
</div>
</Tooltip>

View File

@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import React, { useRef } from 'react'
import type { StructuredOutput } from '../../../../../llm/types'
import Field from './field'
import { cn } from '@/utils/classnames'
import { useHover } from 'ahooks'
import type { ValueSelector } from '@/app/components/workflow/types'
import { useHover } from 'ahooks'
import React, { useRef } from 'react'
import { cn } from '@/utils/classnames'
import Field from './field'
type Props = {
className?: string
@ -42,17 +42,17 @@ export const PickerPanelMain: FC<Props> = ({
return (
<div className={cn(className)} ref={ref}>
{/* Root info */}
<div className='flex items-center justify-between px-2 py-1'>
<div className='flex'>
<div className="flex items-center justify-between px-2 py-1">
<div className="flex">
{root.nodeName && (
<>
<div className='system-sm-medium max-w-[100px] truncate text-text-tertiary'>{root.nodeName}</div>
<div className='system-sm-medium text-text-tertiary'>.</div>
<div className="system-sm-medium max-w-[100px] truncate text-text-tertiary">{root.nodeName}</div>
<div className="system-sm-medium text-text-tertiary">.</div>
</>
)}
<div className='system-sm-medium text-text-secondary'>{root.attrName}</div>
<div className="system-sm-medium text-text-secondary">{root.attrName}</div>
</div>
<div className='system-xs-regular ml-2 truncate text-text-tertiary' title={root.attrAlias || 'object'}>{root.attrAlias || 'object'}</div>
<div className="system-xs-regular ml-2 truncate text-text-tertiary" title={root.attrAlias || 'object'}>{root.attrAlias || 'object'}</div>
</div>
{fieldNames.map(name => (
<Field

View File

@ -1,20 +1,20 @@
'use client'
import { cn } from '@/utils/classnames'
import type { FC } from 'react'
import type { Field as FieldType } from '../../../../../llm/types'
import { RiArrowDropDownLine } from '@remixicon/react'
import { useBoolean } from 'ahooks'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import type { Field as FieldType } from '../../../../../llm/types'
import { cn } from '@/utils/classnames'
import { Type } from '../../../../../llm/types'
import { getFieldType } from '../../../../../llm/utils'
import TreeIndentLine from '../tree-indent-line'
type Props = {
name: string,
payload: FieldType,
required: boolean,
depth?: number,
name: string
payload: FieldType
required: boolean
depth?: number
rootClassName?: string
}
@ -36,8 +36,8 @@ const Field: FC<Props> = ({
<div>
<div className={cn('flex pr-2')}>
<TreeIndentLine depth={depth} />
<div className='w-0 grow'>
<div className='relative flex select-none'>
<div className="w-0 grow">
<div className="relative flex select-none">
{hasChildren && (
<RiArrowDropDownLine
className={cn('absolute left-[-18px] top-[50%] h-4 w-4 translate-y-[-50%] cursor-pointer bg-components-panel-bg text-text-tertiary', fold && 'rotate-[270deg] text-text-accent')}
@ -45,20 +45,20 @@ const Field: FC<Props> = ({
/>
)}
<div className={cn('system-sm-medium ml-[7px] h-6 truncate leading-6 text-text-secondary', isRoot && rootClassName)}>{name}</div>
<div className='system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary'>
<div className="system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary">
{getFieldType(payload)}
{(payload.schemaType && payload.schemaType !== 'file' && ` (${payload.schemaType})`)}
</div>
{required && <div className='system-2xs-medium-uppercase ml-3 leading-6 text-text-warning'>{t('app.structOutput.required')}</div>}
{required && <div className="system-2xs-medium-uppercase ml-3 leading-6 text-text-warning">{t('app.structOutput.required')}</div>}
</div>
{payload.description && (
<div className='ml-[7px] flex'>
<div className='system-xs-regular w-0 grow truncate text-text-tertiary'>{payload.description}</div>
<div className="ml-[7px] flex">
<div className="system-xs-regular w-0 grow truncate text-text-tertiary">{payload.description}</div>
</div>
)}
{hasEnum && (
<div className='ml-[7px] flex'>
<div className='system-xs-regular w-0 grow text-text-quaternary'>
<div className="ml-[7px] flex">
<div className="system-xs-regular w-0 grow text-text-quaternary">
{payload.enum!.map((value, index) => (
<span key={index}>
{typeof value === 'string' ? `"${value}"` : value}

View File

@ -1,9 +1,9 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { StructuredOutput } from '../../../../../llm/types'
import Field from './field'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Field from './field'
type Props = {
payload: StructuredOutput
@ -23,7 +23,7 @@ const ShowPanel: FC<Props> = ({
},
}
return (
<div className='relative left-[-7px]'>
<div className="relative left-[-7px]">
{Object.keys(schema.schema.properties!).map(name => (
<Field
key={name}

View File

@ -4,8 +4,8 @@ import React from 'react'
import { cn } from '@/utils/classnames'
type Props = {
depth?: number,
className?: string,
depth?: number
className?: string
}
const TreeIndentLine: FC<Props> = ({

View File

@ -1,17 +1,17 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import { produce } from 'immer'
import { useTranslation } from 'react-i18next'
import type { OutputVar } from '../../../code/types'
import type { ToastHandle } from '@/app/components/base/toast'
import type { VarType } from '@/app/components/workflow/types'
import { useDebounceFn } from 'ahooks'
import { produce } from 'immer'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import Toast from '@/app/components/base/toast'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
import RemoveButton from '../remove-button'
import VarTypePicker from './var-type-picker'
import Input from '@/app/components/base/input'
import type { VarType } from '@/app/components/workflow/types'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
import type { ToastHandle } from '@/app/components/base/toast'
import Toast from '@/app/components/base/toast'
import { useDebounceFn } from 'ahooks'
type Props = {
readonly: boolean
@ -93,14 +93,14 @@ const OutputVarList: FC<Props> = ({
}, [onRemove])
return (
<div className='space-y-2'>
<div className="space-y-2">
{list.map((item, index) => (
<div className='flex items-center space-x-1' key={index}>
<div className="flex items-center space-x-1" key={index}>
<Input
readOnly={readonly}
value={item.variable}
onChange={handleVarNameChange(index)}
wrapperClassName='grow'
wrapperClassName="grow"
/>
<VarTypePicker
readonly={readonly}
@ -108,7 +108,7 @@ const OutputVarList: FC<Props> = ({
onChange={handleVarTypeChange(index)}
/>
<RemoveButton
className='!bg-gray-100 !p-2 hover:!bg-gray-200'
className="!bg-gray-100 !p-2 hover:!bg-gray-200"
onClick={handleVarRemove(index)}
/>
</div>

View File

@ -1,10 +1,11 @@
import type { AnyObj } from './match-schema-type'
import type { SchemaTypeDefinition } from '@/service/use-common'
import { useSchemaTypeDefinitions } from '@/service/use-common'
import type { AnyObj } from './match-schema-type'
import matchTheSchemaType from './match-schema-type'
export const getMatchedSchemaType = (obj: AnyObj, schemaTypeDefinitions?: SchemaTypeDefinition[]): string => {
if(!schemaTypeDefinitions || obj === undefined || obj === null) return ''
if (!schemaTypeDefinitions || obj === undefined || obj === null)
return ''
const matched = schemaTypeDefinitions.find(def => matchTheSchemaType(obj, def.schema))
return matched ? matched.name : ''
}

View File

@ -1,32 +1,26 @@
import { produce } from 'immer'
import { isArray, uniq } from 'lodash-es'
import type { CodeNodeType } from '../../../code/types'
import type { EndNodeType } from '../../../end/types'
import type { AgentNodeType } from '../../../agent/types'
import type { AnswerNodeType } from '../../../answer/types'
import {
type LLMNodeType,
type StructuredOutput,
Type,
} from '../../../llm/types'
import type { KnowledgeRetrievalNodeType } from '../../../knowledge-retrieval/types'
import type { IfElseNodeType } from '../../../if-else/types'
import type { TemplateTransformNodeType } from '../../../template-transform/types'
import type { QuestionClassifierNodeType } from '../../../question-classifier/types'
import type { HttpNodeType } from '../../../http/types'
import { VarType as ToolVarType } from '../../../tool/types'
import type { ToolNodeType } from '../../../tool/types'
import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types'
import type { IterationNodeType } from '../../../iteration/types'
import type { LoopNodeType } from '../../../loop/types'
import type { ListFilterNodeType } from '../../../list-operator/types'
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants'
import type { CodeNodeType } from '../../../code/types'
import type { DocExtractorNodeType } from '../../../document-extractor/types'
import {
BlockEnum,
InputVarType,
VarType,
} from '@/app/components/workflow/types'
import type { EndNodeType } from '../../../end/types'
import type { HttpNodeType } from '../../../http/types'
import type { IfElseNodeType } from '../../../if-else/types'
import type { IterationNodeType } from '../../../iteration/types'
import type { KnowledgeRetrievalNodeType } from '../../../knowledge-retrieval/types'
import type { ListFilterNodeType } from '../../../list-operator/types'
import type { LLMNodeType, StructuredOutput } from '../../../llm/types'
import type { LoopNodeType } from '../../../loop/types'
import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types'
import type { QuestionClassifierNodeType } from '../../../question-classifier/types'
import type { TemplateTransformNodeType } from '../../../template-transform/types'
import type { ToolNodeType } from '../../../tool/types'
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
import type { CaseItem, Condition } from '@/app/components/workflow/nodes/if-else/types'
import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types'
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types'
import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types'
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
import type {
ConversationVariable,
EnvironmentVariable,
@ -36,16 +30,15 @@ import type {
ValueSelector,
Var,
} from '@/app/components/workflow/types'
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types'
import type { PromptItem } from '@/models/debug'
import type { RAGPipelineVariable } from '@/models/pipeline'
import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types'
import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types'
import PluginTriggerNodeDefault from '@/app/components/workflow/nodes/trigger-plugin/default'
import type { CaseItem, Condition } from '@/app/components/workflow/nodes/if-else/types'
import type { SchemaTypeDefinition } from '@/service/use-common'
import { produce } from 'immer'
import { isArray, uniq } from 'lodash-es'
import {
AGENT_OUTPUT_STRUCT,
FILE_STRUCT,
getGlobalVars,
HTTP_REQUEST_OUTPUT_STRUCT,
KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT,
LLM_OUTPUT_STRUCT,
@ -54,23 +47,31 @@ import {
SUPPORT_OUTPUT_VARS_NODE,
TEMPLATE_TRANSFORM_OUTPUT_STRUCT,
TOOL_OUTPUT_STRUCT,
getGlobalVars,
} from '@/app/components/workflow/constants'
import ToolNodeDefault from '@/app/components/workflow/nodes/tool/default'
import DataSourceNodeDefault from '@/app/components/workflow/nodes/data-source/default'
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
import type { PromptItem } from '@/models/debug'
import ToolNodeDefault from '@/app/components/workflow/nodes/tool/default'
import PluginTriggerNodeDefault from '@/app/components/workflow/nodes/trigger-plugin/default'
import {
BlockEnum,
InputVarType,
VarType,
} from '@/app/components/workflow/types'
import { VAR_REGEX } from '@/config'
import type { AgentNodeType } from '../../../agent/types'
import type { SchemaTypeDefinition } from '@/service/use-common'
import { AppModeEnum } from '@/types/app'
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants'
import {
Type,
} from '../../../llm/types'
import { VarType as ToolVarType } from '../../../tool/types'
export const isSystemVar = (valueSelector: ValueSelector) => {
return valueSelector[0] === 'sys' || valueSelector[1] === 'sys'
}
export const isGlobalVar = (valueSelector: ValueSelector) => {
if (!isSystemVar(valueSelector)) return false
if (!isSystemVar(valueSelector))
return false
const second = valueSelector[1]
if (['query', 'files'].includes(second))
@ -87,7 +88,8 @@ export const isConversationVar = (valueSelector: ValueSelector) => {
}
export const isRagVariableVar = (valueSelector: ValueSelector) => {
if (!valueSelector) return false
if (!valueSelector)
return false
return valueSelector[0] === 'rag'
}
@ -247,8 +249,7 @@ const findExceptVarInObject = (
isFile?: boolean,
): Var => {
const { children } = obj
const isStructuredOutput = !!(children as StructuredOutput)?.schema
?.properties
const isStructuredOutput = !!(children as StructuredOutput)?.schema?.properties
let childrenResult: Var[] | StructuredOutput | undefined
@ -281,9 +282,12 @@ const findExceptVarInObject = (
if (
(item.type === VarType.object || item.type === VarType.file)
&& itemChildren
)
) {
passesFilter = itemHasValidChildren || filterVar(item, currSelector)
else passesFilter = itemHasValidChildren
}
else {
passesFilter = itemHasValidChildren
}
return {
item,
@ -294,7 +298,8 @@ const findExceptVarInObject = (
.filter(({ passesFilter }) => passesFilter)
.map(({ item, filteredObj }) => {
const { children: itemChildren } = item
if (!itemChildren || !filteredObj) return item
if (!itemChildren || !filteredObj)
return item
return {
...item,
@ -415,11 +420,11 @@ const formatItem = (
const { outputs } = data as CodeNodeType
res.vars = outputs
? Object.keys(outputs).map((key) => {
return {
variable: key,
type: outputs[key].type,
}
})
return {
variable: key,
type: outputs[key].type,
}
})
: []
break
}
@ -562,7 +567,8 @@ const formatItem = (
}
case BlockEnum.ListFilter: {
if (!(data as ListFilterNodeType).var_type) break
if (!(data as ListFilterNodeType).var_type)
break
res.vars = [
{
@ -690,12 +696,14 @@ const formatItem = (
(() => {
const variableArr = v.variable.split('.')
const [first] = variableArr
if (isSpecialVar(first)) return variableArr
if (isSpecialVar(first))
return variableArr
return [...selector, ...variableArr]
})(),
)
if (isCurrentMatched) return true
if (isCurrentMatched)
return true
const isFile = v.type === VarType.file
const children = (() => {
@ -710,7 +718,8 @@ const formatItem = (
}
return v.children
})()
if (!children) return false
if (!children)
return false
const obj = findExceptVarInObject(
isFile ? { ...v, children } : v,
@ -737,7 +746,8 @@ const formatItem = (
return v
})()
if (!children) return v
if (!children)
return v
return findExceptVarInObject(
isFile ? { ...v, children } : v,
@ -813,14 +823,22 @@ export const toNodeOutputVars = (
}
// Sort nodes in reverse chronological order (most recent first)
const sortedNodes = [...nodes].sort((a, b) => {
if (a.data.type === BlockEnum.Start) return 1
if (b.data.type === BlockEnum.Start) return -1
if (a.data.type === 'env') return 1
if (b.data.type === 'env') return -1
if (a.data.type === 'conversation') return 1
if (b.data.type === 'conversation') return -1
if (a.data.type === 'global') return 1
if (b.data.type === 'global') return -1
if (a.data.type === BlockEnum.Start)
return 1
if (b.data.type === BlockEnum.Start)
return -1
if (a.data.type === 'env')
return 1
if (b.data.type === 'env')
return -1
if (a.data.type === 'conversation')
return 1
if (b.data.type === 'conversation')
return -1
if (a.data.type === 'global')
return 1
if (b.data.type === 'global')
return -1
// sort nodes by x position
return (b.position?.x || 0) - (a.position?.x || 0)
})
@ -872,8 +890,8 @@ const getIterationItemType = ({
valueSelector,
beforeNodesOutputVars,
}: {
valueSelector: ValueSelector;
beforeNodesOutputVars: NodeOutPutVar[];
valueSelector: ValueSelector
beforeNodesOutputVars: NodeOutPutVar[]
}): VarType => {
const outputVarNodeId = valueSelector[0]
const isSystem = isSystemVar(valueSelector)
@ -883,7 +901,8 @@ const getIterationItemType = ({
? beforeNodesOutputVars.find(v => v.isStartNode)
: beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId)
if (!targetVar) return VarType.string
if (!targetVar)
return VarType.string
let arrayType: VarType = VarType.string
@ -899,7 +918,8 @@ const getIterationItemType = ({
const isLast = i === valueSelector.length - 1
curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : []
if (isLast) arrayType = curr?.type
if (isLast)
arrayType = curr?.type
else if (curr?.type === VarType.object || curr?.type === VarType.file)
curr = curr.children || []
}
@ -927,8 +947,8 @@ const getLoopItemType = ({
valueSelector,
beforeNodesOutputVars,
}: {
valueSelector: ValueSelector;
beforeNodesOutputVars: NodeOutPutVar[];
valueSelector: ValueSelector
beforeNodesOutputVars: NodeOutPutVar[]
}): VarType => {
const outputVarNodeId = valueSelector[0]
const isSystem = isSystemVar(valueSelector)
@ -936,7 +956,8 @@ const getLoopItemType = ({
const targetVar = isSystem
? beforeNodesOutputVars.find(v => v.isStartNode)
: beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId)
if (!targetVar) return VarType.string
if (!targetVar)
return VarType.string
let arrayType: VarType = VarType.string
@ -993,21 +1014,22 @@ export const getVarType = ({
schemaTypeDefinitions,
preferSchemaType,
}: {
valueSelector: ValueSelector;
parentNode?: Node | null;
isIterationItem?: boolean;
isLoopItem?: boolean;
availableNodes: any[];
isChatMode: boolean;
isConstant?: boolean;
environmentVariables?: EnvironmentVariable[];
conversationVariables?: ConversationVariable[];
ragVariables?: RAGPipelineVariable[];
allPluginInfoList: Record<string, ToolWithProvider[]>;
schemaTypeDefinitions?: SchemaTypeDefinition[];
preferSchemaType?: boolean;
valueSelector: ValueSelector
parentNode?: Node | null
isIterationItem?: boolean
isLoopItem?: boolean
availableNodes: any[]
isChatMode: boolean
isConstant?: boolean
environmentVariables?: EnvironmentVariable[]
conversationVariables?: ConversationVariable[]
ragVariables?: RAGPipelineVariable[]
allPluginInfoList: Record<string, ToolWithProvider[]>
schemaTypeDefinitions?: SchemaTypeDefinition[]
preferSchemaType?: boolean
}): VarType => {
if (isConstant) return VarType.string
if (isConstant)
return VarType.string
const beforeNodesOutputVars = toNodeOutputVars(
availableNodes,
@ -1035,7 +1057,8 @@ export const getVarType = ({
})
return itemType
}
if (valueSelector[1] === 'index') return VarType.number
if (valueSelector[1] === 'index')
return VarType.number
}
const isLoopInnerVar = parentNode?.data.type === BlockEnum.Loop
@ -1053,7 +1076,8 @@ export const getVarType = ({
})
return itemType
}
if (valueSelector[1] === 'index') return VarType.number
if (valueSelector[1] === 'index')
return VarType.number
}
const isGlobal = isGlobalVar(valueSelector)
@ -1070,16 +1094,20 @@ export const getVarType = ({
})
const targetVarNodeId = (() => {
if (isInStartNodeSysVar) return startNode?.id
if (isGlobal) return 'global'
if (isInNodeRagVariable) return valueSelector[1]
if (isInStartNodeSysVar)
return startNode?.id
if (isGlobal)
return 'global'
if (isInNodeRagVariable)
return valueSelector[1]
return valueSelector[0]
})()
const targetVar = beforeNodesOutputVars.find(
v => v.nodeId === targetVarNodeId,
)
if (!targetVar) return VarType.string
if (!targetVar)
return VarType.string
let type: VarType = VarType.string
let curr: any = targetVar.vars
@ -1091,12 +1119,15 @@ export const getVarType = ({
}
else {
const targetVar = curr.find((v: any) => {
if (isInNodeRagVariable) return v.variable === valueSelector.join('.')
if (isInNodeRagVariable)
return v.variable === valueSelector.join('.')
return v.variable === valueSelector[1]
})
if (!targetVar) return VarType.string
if (!targetVar)
return VarType.string
if (isInNodeRagVariable) return targetVar.type
if (isInNodeRagVariable)
return targetVar.type
const isStructuredOutputVar = !!targetVar.children?.schema?.properties
if (isStructuredOutputVar) {
@ -1109,10 +1140,12 @@ export const getVarType = ({
let currProperties = targetVar.children.schema;
(valueSelector as ValueSelector).slice(2).forEach((key, i) => {
const isLast = i === valueSelector.length - 3
if (!currProperties) return
if (!currProperties)
return
currProperties = currProperties.properties[key]
if (isLast) type = structTypeToVarType(currProperties?.type)
if (isLast)
type = structTypeToVarType(currProperties?.type)
})
return type
}
@ -1148,20 +1181,20 @@ export const toNodeAvailableVars = ({
allPluginInfoList,
schemaTypeDefinitions,
}: {
parentNode?: Node | null;
t?: any;
parentNode?: Node | null
t?: any
// to get those nodes output vars
beforeNodes: Node[];
isChatMode: boolean;
beforeNodes: Node[]
isChatMode: boolean
// env
environmentVariables?: EnvironmentVariable[];
environmentVariables?: EnvironmentVariable[]
// chat var
conversationVariables?: ConversationVariable[];
conversationVariables?: ConversationVariable[]
// rag variables
ragVariables?: RAGPipelineVariable[];
filterVar: (payload: Var, selector: ValueSelector) => boolean;
allPluginInfoList: Record<string, ToolWithProvider[]>;
schemaTypeDefinitions?: SchemaTypeDefinition[];
ragVariables?: RAGPipelineVariable[]
filterVar: (payload: Var, selector: ValueSelector) => boolean
allPluginInfoList: Record<string, ToolWithProvider[]>
schemaTypeDefinitions?: SchemaTypeDefinition[]
}): NodeOutPutVar[] => {
const beforeNodesOutputVars = toNodeOutputVars(
beforeNodes,
@ -1190,13 +1223,13 @@ export const toNodeAvailableVars = ({
const itemChildren
= itemType === VarType.file
? {
children: OUTPUT_FILE_SUB_VARIABLES.map((key) => {
return {
variable: key,
type: key === 'size' ? VarType.number : VarType.string,
}
}),
}
children: OUTPUT_FILE_SUB_VARIABLES.map((key) => {
return {
variable: key,
type: key === 'size' ? VarType.number : VarType.string,
}
}),
}
: {}
const iterationVar = {
nodeId: iterationNode?.id,
@ -1216,24 +1249,28 @@ export const toNodeAvailableVars = ({
const iterationIndex = beforeNodesOutputVars.findIndex(
v => v.nodeId === iterationNode?.id,
)
if (iterationIndex > -1) beforeNodesOutputVars.splice(iterationIndex, 1)
if (iterationIndex > -1)
beforeNodesOutputVars.splice(iterationIndex, 1)
beforeNodesOutputVars.unshift(iterationVar)
}
return beforeNodesOutputVars
}
export const getNodeInfoById = (nodes: any, id: string) => {
if (!isArray(nodes)) return
if (!isArray(nodes))
return
return nodes.find((node: any) => node.id === id)
}
const matchNotSystemVars = (prompts: string[]) => {
if (!prompts) return []
if (!prompts)
return []
const allVars: string[] = []
prompts.forEach((prompt) => {
VAR_REGEX.lastIndex = 0
if (typeof prompt !== 'string') return
if (typeof prompt !== 'string')
return
allVars.push(...(prompt.match(VAR_REGEX) || []))
})
const uniqVars = uniq(allVars).map(v =>
@ -1247,9 +1284,11 @@ const replaceOldVarInText = (
oldVar: ValueSelector,
newVar: ValueSelector,
) => {
if (!text || typeof text !== 'string') return text
if (!text || typeof text !== 'string')
return text
if (!newVar || newVar.length === 0) return text
if (!newVar || newVar.length === 0)
return text
return text.replaceAll(
`{{#${oldVar.join('.')}#}}`,
@ -1308,7 +1347,8 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
.flatMap(c => c.conditions || [])
.flatMap((c) => {
const selectors: ValueSelector[] = []
if (c.variable_selector) selectors.push(c.variable_selector)
if (c.variable_selector)
selectors.push(c.variable_selector)
// Handle sub-variable conditions
if (c.sub_variable_condition && c.sub_variable_condition.conditions) {
selectors.push(
@ -1391,7 +1431,7 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
payload.datasource_parameters[key].type === ToolVarType.variable,
)
.map(key => payload.datasource_parameters[key].value as string)
|| []
|| []
res = [...(mixVars as ValueSelector[]), ...(vars as any)]
break
}
@ -1436,7 +1476,8 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
case BlockEnum.Agent: {
const payload = data as AgentNodeType
const valueSelectors: ValueSelector[] = []
if (!payload.agent_parameters) break
if (!payload.agent_parameters)
break
Object.keys(payload.agent_parameters || {}).forEach((key) => {
const { value } = payload.agent_parameters![key]
@ -1490,7 +1531,8 @@ export const getNodeUsedVarPassToServerKey = (
return undefined
}
const targetVar = findConditionInCases((data as IfElseNodeType).cases || [])
if (targetVar) res = `#${valueSelector.join('.')}#`
if (targetVar)
res = `#${valueSelector.join('.')}#`
break
}
case BlockEnum.Code: {
@ -1500,7 +1542,8 @@ export const getNodeUsedVarPassToServerKey = (
&& v.value_selector
&& v.value_selector.join('.') === valueSelector.join('.'),
)
if (targetVar) res = targetVar.variable
if (targetVar)
res = targetVar.variable
break
}
case BlockEnum.TemplateTransform: {
@ -1510,7 +1553,8 @@ export const getNodeUsedVarPassToServerKey = (
&& v.value_selector
&& v.value_selector.join('.') === valueSelector.join('.'),
)
if (targetVar) res = targetVar.variable
if (targetVar)
res = targetVar.variable
break
}
case BlockEnum.QuestionClassifier: {
@ -1552,7 +1596,8 @@ export const findUsedVarNodes = (
const res: Node[] = []
availableNodes.forEach((node) => {
const vars = getNodeUsedVars(node)
if (vars.find(v => v.join('.') === varSelector.join('.'))) res.push(node)
if (vars.find(v => v.join('.') === varSelector.join('.')))
res.push(node)
})
return res
}
@ -1626,8 +1671,9 @@ export const updateNodeVars = (
if (
payload.context?.variable_selector?.join('.')
=== oldVarSelector.join('.')
)
) {
payload.context.variable_selector = newVarSelector
}
break
}
@ -1661,8 +1707,9 @@ export const updateNodeVars = (
if (
subC.variable_selector?.join('.')
=== oldVarSelector.join('.')
)
) {
subC.variable_selector = newVarSelector
}
return subC
})
}
@ -1820,7 +1867,8 @@ export const updateNodeVars = (
const payload = data as VariableAssignerNodeType
if (payload.variables) {
payload.variables = payload.variables.map((v) => {
if (v.join('.') === oldVarSelector.join('.')) v = newVarSelector
if (v.join('.') === oldVarSelector.join('.'))
v = newVarSelector
return v
})
}
@ -1831,7 +1879,8 @@ export const updateNodeVars = (
const payload = data as VariableAssignerNodeType
if (payload.variables) {
payload.variables = payload.variables.map((v) => {
if (v.join('.') === oldVarSelector.join('.')) v = newVarSelector
if (v.join('.') === oldVarSelector.join('.'))
v = newVarSelector
return v
})
}
@ -1882,11 +1931,11 @@ const varToValueSelectorList = (
parentValueSelector: ValueSelector,
res: ValueSelector[],
) => {
if (!v.variable) return
if (!v.variable)
return
res.push([...parentValueSelector, v.variable])
const isStructuredOutput = !!(v.children as StructuredOutput)?.schema
?.properties
const isStructuredOutput = !!(v.children as StructuredOutput)?.schema?.properties
if ((v.children as Var[])?.length > 0) {
(v.children as Var[]).forEach((child) => {
@ -1897,8 +1946,7 @@ const varToValueSelectorList = (
Object.keys(
(v.children as StructuredOutput)?.schema?.properties || {},
).forEach((key) => {
const type = (v.children as StructuredOutput)?.schema?.properties[key]
.type
const type = (v.children as StructuredOutput)?.schema?.properties[key].type
const isArray = type === Type.array
const arrayType = (v.children as StructuredOutput)?.schema?.properties[
key

View File

@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { Field, StructuredOutput, TypeWithArray } from '../../../llm/types'
import { Type } from '../../../llm/types'
import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
import React from 'react'
import BlockIcon from '@/app/components/workflow/block-icon'
import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
import { BlockEnum } from '@/app/components/workflow/types'
import { Type } from '../../../llm/types'
type Props = {
nodeName: string
@ -35,20 +35,20 @@ const VarFullPathPanel: FC<Props> = ({
type: isLast ? varType : Type.object,
properties: {},
} as Field
current = current.properties[name] as { type: Type.object; properties: { [key: string]: Field; }; required: never[]; additionalProperties: false; }
current = current.properties[name] as { type: Type.object, properties: { [key: string]: Field }, required: never[], additionalProperties: false }
}
return {
schema,
}
})()
return (
<div className='w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-0 shadow-lg backdrop-blur-[5px]'>
<div className='flex space-x-1 border-b-[0.5px] border-divider-subtle p-3 pb-2 '>
<BlockIcon size='xs' type={nodeType} />
<div className='system-xs-medium w-0 grow truncate text-text-secondary'>{nodeName}</div>
<div className="w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-0 shadow-lg backdrop-blur-[5px]">
<div className="flex space-x-1 border-b-[0.5px] border-divider-subtle p-3 pb-2 ">
<BlockIcon size="xs" type={nodeType} />
<div className="system-xs-medium w-0 grow truncate text-text-secondary">{nodeName}</div>
</div>
<Panel
className='px-1 pb-3 pt-2'
className="px-1 pb-3 pt-2"
root={{ attrName: path[0] }}
payload={schema}
readonly

View File

@ -1,21 +1,21 @@
'use client'
import type { FC } from 'react'
import type { ToastHandle } from '@/app/components/base/toast'
import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types'
import { RiDraggable } from '@remixicon/react'
import { useDebounceFn } from 'ahooks'
import { produce } from 'immer'
import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import RemoveButton from '../remove-button'
import VarReferencePicker from './var-reference-picker'
import Input from '@/app/components/base/input'
import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
import type { ToastHandle } from '@/app/components/base/toast'
import Toast from '@/app/components/base/toast'
import { ReactSortable } from 'react-sortablejs'
import { v4 as uuid4 } from 'uuid'
import { RiDraggable } from '@remixicon/react'
import Input from '@/app/components/base/input'
import Toast from '@/app/components/base/toast'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { cn } from '@/utils/classnames'
import { useDebounceFn } from 'ahooks'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
import RemoveButton from '../remove-button'
import VarReferencePicker from './var-reference-picker'
type Props = {
nodeId: string
@ -131,11 +131,11 @@ const VarList: FC<Props> = ({
return (
<ReactSortable
className='space-y-2'
className="space-y-2"
list={listWithIds}
setList={(list) => { onChange(list.map(item => item.variable)) }}
handle='.handle'
ghostClass='opacity-50'
handle=".handle"
ghostClass="opacity-50"
animation={150}
>
{list.map((variable, index) => {
@ -147,7 +147,7 @@ const VarList: FC<Props> = ({
return (
<div className={cn('flex items-center space-x-1', 'group relative')} key={index}>
<Input
wrapperClassName='w-[120px]'
wrapperClassName="w-[120px]"
disabled={readonly}
value={variable.variable}
onChange={handleVarNameChange(index)}
@ -157,7 +157,7 @@ const VarList: FC<Props> = ({
nodeId={nodeId}
readonly={readonly}
isShowNodeName
className='grow'
className="grow"
value={variable.variable_type === VarKindType.constant ? (variable.value || '') : (variable.value_selector || [])}
isSupportConstantValue={isSupportConstantValue}
onChange={handleVarReferenceChange(index)}
@ -167,12 +167,15 @@ const VarList: FC<Props> = ({
isSupportFileVar={isSupportFileVar}
/>
{!readonly && (
<RemoveButton onClick={handleVarRemove(index)}/>
<RemoveButton onClick={handleVarRemove(index)} />
)}
{canDrag && (
<RiDraggable className={cn(
'handle absolute -left-4 top-2.5 hidden h-3 w-3 cursor-pointer text-text-quaternary',
'group-hover:block',
)}
/>
)}
{canDrag && <RiDraggable className={cn(
'handle absolute -left-4 top-2.5 hidden h-3 w-3 cursor-pointer text-text-quaternary',
'group-hover:block',
)} />}
</div>
)
})}

View File

@ -1,7 +1,9 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { CredentialFormSchema, CredentialFormSchemaSelect, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Tool } from '@/app/components/tools/types'
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import type { CommonNodeType, Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
import {
RiArrowDownSLine,
RiCloseLine,
@ -10,23 +12,16 @@ import {
RiMoreLine,
} from '@remixicon/react'
import { produce } from 'immer'
import { noop } from 'lodash-es'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
useNodes,
useReactFlow,
useStoreApi,
} from 'reactflow'
import RemoveButton from '../remove-button'
import useAvailableVarList from '../../hooks/use-available-var-list'
import VarReferencePopup from './var-reference-popup'
import { getNodeInfoById, isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar, removeFileVars, varTypeToStructType } from './utils'
import ConstantField from './constant-field'
import { cn } from '@/utils/classnames'
import type { CommonNodeType, Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import type { CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { type CredentialFormSchema, type FormOption, FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { BlockEnum } from '@/app/components/workflow/types'
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
import Badge from '@/app/components/base/badge'
import AddButton from '@/app/components/base/button/add-button'
import { Line3 } from '@/app/components/base/icons/src/public/common'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import {
@ -34,23 +29,28 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Tooltip from '@/app/components/base/tooltip'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants'
import {
useIsChatMode,
useWorkflowVariables,
} from '@/app/components/workflow/hooks'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
// import type { BaseResource, BaseResourceProvider } from '@/app/components/workflow/nodes/_base/types'
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
import AddButton from '@/app/components/base/button/add-button'
import Badge from '@/app/components/base/badge'
import Tooltip from '@/app/components/base/tooltip'
import { isExceptionVariable } from '@/app/components/workflow/utils'
import VarFullPathPanel from './var-full-path-panel'
import { noop } from 'lodash-es'
import type { Tool } from '@/app/components/tools/types'
import { useFetchDynamicOptions } from '@/service/use-plugins'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { isExceptionVariable } from '@/app/components/workflow/utils'
import { useFetchDynamicOptions } from '@/service/use-plugins'
import { cn } from '@/utils/classnames'
import useAvailableVarList from '../../hooks/use-available-var-list'
import RemoveButton from '../remove-button'
import ConstantField from './constant-field'
import { getNodeInfoById, isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar, removeFileVars, varTypeToStructType } from './utils'
import VarFullPathPanel from './var-full-path-panel'
import VarReferencePopup from './var-reference-popup'
const TRIGGER_DEFAULT_WIDTH = 227
@ -207,7 +207,7 @@ const VarReferencePicker: FC<Props> = ({
if (!hasValue)
return ''
const showName = VAR_SHOW_NAME_MAP[(value as ValueSelector).join('.')]
if(showName)
if (showName)
return showName
const isSystem = isSystemVar(value as ValueSelector)
@ -336,7 +336,8 @@ const VarReferencePicker: FC<Props> = ({
path={(value as ValueSelector).slice(1)}
varType={varTypeToStructType(type)}
nodeType={outputVarNode?.type}
/>)
/>
)
}
if (!isValidVar && hasValue)
return t('workflow.errorMsg.invalidVariable')
@ -347,7 +348,10 @@ const VarReferencePicker: FC<Props> = ({
const [dynamicOptions, setDynamicOptions] = useState<FormOption[] | null>(null)
const [isLoading, setIsLoading] = useState(false)
const { mutateAsync: fetchDynamicOptions } = useFetchDynamicOptions(
currentProvider?.plugin_id || '', currentProvider?.name || '', currentTool?.name || '', (schema as CredentialFormSchemaSelect)?.variable || '',
currentProvider?.plugin_id || '',
currentProvider?.name || '',
currentTool?.name || '',
(schema as CredentialFormSchemaSelect)?.variable || '',
'tool',
)
const handleFetchDynamicOptions = async () => {
@ -398,11 +402,16 @@ const VarReferencePicker: FC<Props> = ({
}, [schema, dynamicOptions, isLoading, value])
const variableCategory = useMemo(() => {
if (isEnv) return 'environment'
if (isChatVar) return 'conversation'
if (isGlobal) return 'global'
if (isLoopVar) return 'loop'
if (isRagVar) return 'rag'
if (isEnv)
return 'environment'
if (isChatVar)
return 'conversation'
if (isGlobal)
return 'global'
if (isLoopVar)
return 'loop'
if (isRagVar)
return 'rag'
return 'system'
}, [isEnv, isChatVar, isGlobal, isLoopVar, isRagVar])
@ -413,166 +422,210 @@ const VarReferencePicker: FC<Props> = ({
onOpenChange={setOpen}
placement={isAddBtnTrigger ? 'bottom-end' : 'bottom-start'}
>
<WrapElem onClick={() => {
if (readonly)
return
if (!isConstant)
setOpen(!open)
else
setControlFocus(Date.now())
}} className='group/picker-trigger-wrap relative !flex'>
<WrapElem
onClick={() => {
if (readonly)
return
if (!isConstant)
setOpen(!open)
else
setControlFocus(Date.now())
}}
className="group/picker-trigger-wrap relative !flex"
>
<>
{isAddBtnTrigger
? (
<div>
<AddButton onClick={noop}></AddButton>
</div>
)
: (<div ref={!isSupportConstantValue ? triggerRef : null} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'group/wrap relative flex h-8 w-full items-center', !isSupportConstantValue && 'rounded-lg bg-components-input-bg-normal p-1', isInTable && 'border-none bg-transparent', readonly && 'bg-components-input-bg-disabled')}>
{isSupportConstantValue
? <div onClick={(e) => {
e.stopPropagation()
setOpen(false)
setControlFocus(Date.now())
}} className='mr-1 flex h-full items-center space-x-1'>
<TypeSelector
noLeft
trigger={
<div className='radius-md flex h-8 items-center bg-components-input-bg-normal px-2'>
<div className='system-sm-regular mr-1 text-components-input-text-filled'>{varKindTypes.find(item => item.value === varKindType)?.label}</div>
<RiArrowDownSLine className='h-4 w-4 text-text-quaternary' />
</div>
}
popupClassName='top-8'
readonly={readonly}
value={varKindType}
options={varKindTypes}
onChange={handleVarKindTypeChange}
showChecked
/>
<div>
<AddButton onClick={noop}></AddButton>
</div>
: (!hasValue && <div className='ml-1.5 mr-1'>
<Variable02 className={`h-4 w-4 ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'}`} />
</div>)}
{isConstant
? (
<ConstantField
value={value as string}
onChange={onChange as ((value: string | number, varKindType: VarKindType, varInfo?: Var) => void)}
schema={schemaWithDynamicSelect as CredentialFormSchema}
readonly={readonly}
isLoading={isLoading}
/>
)
: (
<VarPickerWrap
onClick={() => {
if (readonly)
return
if (!isConstant)
setOpen(!open)
else
setControlFocus(Date.now())
}}
className='h-full grow'
>
<div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center rounded-lg bg-components-panel-bg py-1 pl-1')}>
<Tooltip noDecoration={isShowAPart} popupContent={tooltipPopup}>
<div className={cn('h-full items-center rounded-[5px] px-1.5', hasValue ? 'inline-flex bg-components-badge-white-to-dark' : 'flex')}>
{hasValue
? (
<>
{isShowNodeName && !isEnv && !isChatVar && !isGlobal && !isRagVar && (
<div className='flex items-center' onClick={(e) => {
if (e.metaKey || e.ctrlKey) {
e.stopPropagation()
handleVariableJump(outputVarNode?.id)
}
}}>
<div className='h-3 px-[1px]'>
{outputVarNode?.type && <VarBlockIcon
className='!text-text-primary'
type={outputVarNode.type}
/>}
</div>
<div className='mx-0.5 truncate text-xs font-medium text-text-secondary' title={outputVarNode?.title} style={{
maxWidth: maxNodeNameWidth,
}}>{outputVarNode?.title}</div>
<Line3 className='mr-0.5'></Line3>
</div>
)}
{isShowAPart && (
<div className='flex items-center'>
<RiMoreLine className='h-3 w-3 text-text-secondary' />
<Line3 className='mr-0.5 text-divider-deep'></Line3>
</div>
)}
<div className='flex items-center text-text-accent'>
{isLoading && <RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' />}
<VariableIconWithColor
variables={value as ValueSelector}
variableCategory={variableCategory}
isExceptionVariable={isException}
/>
<div className={cn('ml-0.5 truncate text-xs font-medium', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning', isGlobal && 'text-util-colors-orange-orange-600')} title={varName} style={{
maxWidth: maxVarNameWidth,
}}>{varName}</div>
</div>
<div className='system-xs-regular ml-0.5 truncate text-center capitalize text-text-tertiary' title={type} style={{
maxWidth: maxTypeWidth,
}}>{type}</div>
{!isValidVar && <RiErrorWarningFill className='ml-0.5 h-3 w-3 text-text-destructive' />}
</>
)
: <div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} system-sm-regular text-ellipsis`}>
{isLoading ? (
<div className='flex items-center'>
<RiLoader4Line className='mr-1 h-3.5 w-3.5 animate-spin text-text-secondary' />
<span>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</span>
</div>
) : (
placeholder ?? t('workflow.common.setVarValuePlaceholder')
)}
</div>}
)
: (
<div ref={!isSupportConstantValue ? triggerRef : null} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'group/wrap relative flex h-8 w-full items-center', !isSupportConstantValue && 'rounded-lg bg-components-input-bg-normal p-1', isInTable && 'border-none bg-transparent', readonly && 'bg-components-input-bg-disabled')}>
{isSupportConstantValue
? (
<div
onClick={(e) => {
e.stopPropagation()
setOpen(false)
setControlFocus(Date.now())
}}
className="mr-1 flex h-full items-center space-x-1"
>
<TypeSelector
noLeft
trigger={(
<div className="radius-md flex h-8 items-center bg-components-input-bg-normal px-2">
<div className="system-sm-regular mr-1 text-components-input-text-filled">{varKindTypes.find(item => item.value === varKindType)?.label}</div>
<RiArrowDownSLine className="h-4 w-4 text-text-quaternary" />
</div>
)}
popupClassName="top-8"
readonly={readonly}
value={varKindType}
options={varKindTypes}
onChange={handleVarKindTypeChange}
showChecked
/>
</div>
</Tooltip>
</div>
)
: (!hasValue && (
<div className="ml-1.5 mr-1">
<Variable02 className={`h-4 w-4 ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'}`} />
</div>
))}
{isConstant
? (
<ConstantField
value={value as string}
onChange={onChange as ((value: string | number, varKindType: VarKindType, varInfo?: Var) => void)}
schema={schemaWithDynamicSelect as CredentialFormSchema}
readonly={readonly}
isLoading={isLoading}
/>
)
: (
<VarPickerWrap
onClick={() => {
if (readonly)
return
if (!isConstant)
setOpen(!open)
else
setControlFocus(Date.now())
}}
className="h-full grow"
>
<div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center rounded-lg bg-components-panel-bg py-1 pl-1')}>
<Tooltip noDecoration={isShowAPart} popupContent={tooltipPopup}>
<div className={cn('h-full items-center rounded-[5px] px-1.5', hasValue ? 'inline-flex bg-components-badge-white-to-dark' : 'flex')}>
{hasValue
? (
<>
{isShowNodeName && !isEnv && !isChatVar && !isGlobal && !isRagVar && (
<div
className="flex items-center"
onClick={(e) => {
if (e.metaKey || e.ctrlKey) {
e.stopPropagation()
handleVariableJump(outputVarNode?.id)
}
}}
>
<div className="h-3 px-[1px]">
{outputVarNode?.type && (
<VarBlockIcon
className="!text-text-primary"
type={outputVarNode.type}
/>
)}
</div>
<div
className="mx-0.5 truncate text-xs font-medium text-text-secondary"
title={outputVarNode?.title}
style={{
maxWidth: maxNodeNameWidth,
}}
>
{outputVarNode?.title}
</div>
<Line3 className="mr-0.5"></Line3>
</div>
)}
{isShowAPart && (
<div className="flex items-center">
<RiMoreLine className="h-3 w-3 text-text-secondary" />
<Line3 className="mr-0.5 text-divider-deep"></Line3>
</div>
)}
<div className="flex items-center text-text-accent">
{isLoading && <RiLoader4Line className="h-3.5 w-3.5 animate-spin text-text-secondary" />}
<VariableIconWithColor
variables={value as ValueSelector}
variableCategory={variableCategory}
isExceptionVariable={isException}
/>
<div
className={cn('ml-0.5 truncate text-xs font-medium', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning', isGlobal && 'text-util-colors-orange-orange-600')}
title={varName}
style={{
maxWidth: maxVarNameWidth,
}}
>
{varName}
</div>
</div>
<div
className="system-xs-regular ml-0.5 truncate text-center capitalize text-text-tertiary"
title={type}
style={{
maxWidth: maxTypeWidth,
}}
>
{type}
</div>
{!isValidVar && <RiErrorWarningFill className="ml-0.5 h-3 w-3 text-text-destructive" />}
</>
)
: (
<div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} system-sm-regular text-ellipsis`}>
{isLoading
? (
<div className="flex items-center">
<RiLoader4Line className="mr-1 h-3.5 w-3.5 animate-spin text-text-secondary" />
<span>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</span>
</div>
)
: (
placeholder ?? t('workflow.common.setVarValuePlaceholder')
)}
</div>
)}
</div>
</Tooltip>
</div>
</VarPickerWrap>
)}
{(hasValue && !readonly && !isInTable) && (<div
className='group invisible absolute right-1 top-[50%] h-5 translate-y-[-50%] cursor-pointer rounded-md p-1 hover:bg-state-base-hover group-hover/wrap:visible'
onClick={handleClearVar}
>
<RiCloseLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-secondary' />
</div>)}
{!hasValue && valueTypePlaceHolder && (
<Badge
className=' absolute right-1 top-[50%] translate-y-[-50%] capitalize'
text={valueTypePlaceHolder}
uppercase={false}
/>
</VarPickerWrap>
)}
{(hasValue && !readonly && !isInTable) && (
<div
className="group invisible absolute right-1 top-[50%] h-5 translate-y-[-50%] cursor-pointer rounded-md p-1 hover:bg-state-base-hover group-hover/wrap:visible"
onClick={handleClearVar}
>
<RiCloseLine className="h-3.5 w-3.5 text-text-tertiary group-hover:text-text-secondary" />
</div>
)}
{!hasValue && valueTypePlaceHolder && (
<Badge
className=" absolute right-1 top-[50%] translate-y-[-50%] capitalize"
text={valueTypePlaceHolder}
uppercase={false}
/>
)}
</div>
)}
</div>)}
{!readonly && isInTable && (
<RemoveButton
className='absolute right-1 top-0.5 hidden group-hover/picker-trigger-wrap:block'
className="absolute right-1 top-0.5 hidden group-hover/picker-trigger-wrap:block"
onClick={() => onRemove?.()}
/>
)}
{!hasValue && typePlaceHolder && (
<Badge
className='absolute right-2 top-1.5'
className="absolute right-2 top-1.5"
text={typePlaceHolder}
uppercase={false}
/>
)}
</>
</WrapElem>
<PortalToFollowElemContent style={{
zIndex: zIndex || 100,
}} className='mt-1'>
<PortalToFollowElemContent
style={{
zIndex: zIndex || 100,
}}
className="mt-1"
>
{!isConstant && (
<VarReferencePopup
vars={outputVars}
@ -586,7 +639,7 @@ const VarReferencePicker: FC<Props> = ({
)}
</PortalToFollowElemContent>
</PortalToFollowElem>
</div >
</div>
)
}
export default React.memo(VarReferencePicker)

View File

@ -1,12 +1,12 @@
'use client'
import type { FC } from 'react'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import VarReferenceVars from './var-reference-vars'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import ListEmpty from '@/app/components/base/list-empty'
import { useStore } from '@/app/components/workflow/store'
import { useDocLink } from '@/context/i18n'
import VarReferenceVars from './var-reference-vars'
type Props = {
vars: NodeOutPutVar[]
@ -33,48 +33,59 @@ const VarReferencePopup: FC<Props> = ({
const docLink = useDocLink()
// max-h-[300px] overflow-y-auto todo: use portal to handle long list
return (
<div className='space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg' style={{
width: itemWidth || 228,
}}>
<div
className="space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg"
style={{
width: itemWidth || 228,
}}
>
{((!vars || vars.length === 0) && popupFor)
? (popupFor === 'toAssigned'
? (
<ListEmpty
title={t('workflow.variableReference.noAvailableVars') || ''}
description={<div className='system-xs-regular text-text-tertiary'>
{t('workflow.variableReference.noVarsForOperation')}
</div>}
? (
<ListEmpty
title={t('workflow.variableReference.noAvailableVars') || ''}
description={(
<div className="system-xs-regular text-text-tertiary">
{t('workflow.variableReference.noVarsForOperation')}
</div>
)}
/>
)
: (
<ListEmpty
title={t('workflow.variableReference.noAssignedVars') || ''}
description={(
<div className="system-xs-regular text-text-tertiary">
{t('workflow.variableReference.assignedVarsDescription')}
<a
target="_blank"
rel="noopener noreferrer"
className="text-text-accent-secondary"
href={docLink('/guides/workflow/variables#conversation-variables', {
'zh-Hans': '/guides/workflow/variables#会话变量',
'ja-JP': '/guides/workflow/variables#会話変数',
})}
>
{t('workflow.variableReference.conversationVars')}
</a>
</div>
)}
/>
))
: (
<VarReferenceVars
searchBoxClassName="mt-1"
vars={vars}
onChange={onChange}
itemWidth={itemWidth}
isSupportFileVar={isSupportFileVar}
zIndex={zIndex}
showManageInputField={showManageRagInputFields}
onManageInputField={() => setShowInputFieldPanel?.(true)}
preferSchemaType={preferSchemaType}
/>
)
: (
<ListEmpty
title={t('workflow.variableReference.noAssignedVars') || ''}
description={<div className='system-xs-regular text-text-tertiary'>
{t('workflow.variableReference.assignedVarsDescription')}
<a target='_blank' rel='noopener noreferrer'
className='text-text-accent-secondary'
href={docLink('/guides/workflow/variables#conversation-variables', {
'zh-Hans': '/guides/workflow/variables#会话变量',
'ja-JP': '/guides/workflow/variables#会話変数',
})}>
{t('workflow.variableReference.conversationVars')}
</a>
</div>}
/>
))
: <VarReferenceVars
searchBoxClassName='mt-1'
vars={vars}
onChange={onChange}
itemWidth={itemWidth}
isSupportFileVar={isSupportFileVar}
zIndex={zIndex}
showManageInputField={showManageRagInputFields}
onManageInputField={() => setShowInputFieldPanel?.(true)}
preferSchemaType={preferSchemaType}
/>
}
</div >
)}
</div>
)
}
export default React.memo(VarReferencePopup)

View File

@ -1,29 +1,30 @@
'use client'
import type { FC } from 'react'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import type { StructuredOutput } from '../../../llm/types'
import type { Field } from '@/app/components/workflow/nodes/llm/types'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import { useHover } from 'ahooks'
import { noop } from 'lodash-es'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
import { type NodeOutPutVar, type ValueSelector, type Var, VarType } from '@/app/components/workflow/types'
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import Input from '@/app/components/base/input'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Input from '@/app/components/base/input'
import { checkKeys } from '@/utils/var'
import type { StructuredOutput } from '../../../llm/types'
import { Type } from '../../../llm/types'
import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
import { isSpecialVar, varTypeToStructType } from './utils'
import type { Field } from '@/app/components/workflow/nodes/llm/types'
import { noop } from 'lodash-es'
import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general'
import ManageInputField from './manage-input-field'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants'
import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { VarType } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
import { checkKeys } from '@/utils/var'
import { Type } from '../../../llm/types'
import ManageInputField from './manage-input-field'
import { isSpecialVar, varTypeToStructType } from './utils'
type ItemProps = {
nodeId: string
@ -74,16 +75,16 @@ const Item: FC<ItemProps> = ({
switch (variable) {
case 'current':
Icon = isInCodeGeneratorInstructionEditor ? CodeAssistant : MagicEdit
return <Icon className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' />
return <Icon className="h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600" />
case 'error_message':
return <Variable02 className='h-3.5 w-3.5 shrink-0 text-util-colors-orange-dark-orange-dark-600' />
return <Variable02 className="h-3.5 w-3.5 shrink-0 text-util-colors-orange-dark-orange-dark-600" />
default:
return <Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' />
return <Variable02 className="h-3.5 w-3.5 shrink-0 text-text-accent" />
}
}, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable])
const varName = useMemo(() => {
if(VAR_SHOW_NAME_MAP[itemData.variable])
if (VAR_SHOW_NAME_MAP[itemData.variable])
return VAR_SHOW_NAME_MAP[itemData.variable]
if (!isFlat)
@ -95,7 +96,8 @@ const Item: FC<ItemProps> = ({
}, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable])
const objStructuredOutput: StructuredOutput | null = useMemo(() => {
if (!isObj) return null
if (!isObj)
return null
const properties: Record<string, Field> = {}
const childrenVars = (itemData.children as Var[]) || []
childrenVars.forEach((c) => {
@ -160,19 +162,23 @@ const Item: FC<ItemProps> = ({
}
}
const variableCategory = useMemo(() => {
if (isEnv) return 'environment'
if (isChatVar) return 'conversation'
if (isLoopVar) return 'loop'
if (isRagVariable) return 'rag'
if (isEnv)
return 'environment'
if (isChatVar)
return 'conversation'
if (isLoopVar)
return 'loop'
if (isRagVariable)
return 'rag'
return 'system'
}, [isEnv, isChatVar, isSys, isLoopVar, isRagVariable])
return (
<PortalToFollowElem
open={open}
onOpenChange={noop}
placement='left-start'
placement="left-start"
>
<PortalToFollowElemTrigger className='w-full'>
<PortalToFollowElemTrigger className="w-full">
<div
ref={itemRef}
className={cn(
@ -180,43 +186,45 @@ const Item: FC<ItemProps> = ({
isHovering && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'),
'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3',
className,
)
}
)}
onClick={handleChosen}
onMouseDown={e => e.preventDefault()}
>
<div className='flex w-0 grow items-center'>
{!isFlat && <VariableIconWithColor
variables={itemData.variable.split('.')}
variableCategory={variableCategory}
isExceptionVariable={isException}
/>}
<div className="flex w-0 grow items-center">
{!isFlat && (
<VariableIconWithColor
variables={itemData.variable.split('.')}
variableCategory={variableCategory}
isExceptionVariable={isException}
/>
)}
{isFlat && flatVarIcon}
{!isEnv && !isChatVar && !isRagVariable && (
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{varName}</div>
<div title={itemData.variable} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{varName}</div>
)}
{isEnv && (
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('env.', '')}</div>
<div title={itemData.variable} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{itemData.variable.replace('env.', '')}</div>
)}
{isChatVar && (
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('conversation.', '')}</div>
<div title={itemData.des} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{itemData.variable.replace('conversation.', '')}</div>
)}
{isRagVariable && (
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.split('.').slice(-1)[0]}</div>
<div title={itemData.des} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{itemData.variable.split('.').slice(-1)[0]}</div>
)}
</div>
<div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}</div>
<div className="ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary">{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}</div>
{
(isObj || isStructureOutput) && (
<ChevronRight className={cn('ml-0.5 h-3 w-3 text-text-quaternary', isHovering && 'text-text-tertiary')} />
)
}
</div >
</PortalToFollowElemTrigger >
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{
zIndex: zIndex || 100,
}}>
}}
>
{(isStructureOutput || isObj) && (
<PickerStructurePanel
root={{ nodeId, nodeName: title, attrName: itemData.variable, attrAlias: itemData.schemaType }}
@ -228,7 +236,7 @@ const Item: FC<ItemProps> = ({
/>
)}
</PortalToFollowElemContent>
</PortalToFollowElem >
</PortalToFollowElem>
)
}
@ -308,7 +316,7 @@ const VarReferenceVars: FC<Props> = ({
<>
<div className={cn('var-search-input-wrapper mx-2 mb-2 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
<Input
className='var-search-input'
className="var-search-input"
showLeftIcon
showClearIcon
value={searchText}
@ -320,54 +328,63 @@ const VarReferenceVars: FC<Props> = ({
autoFocus={autoFocus}
/>
</div>
<div className='relative left-[-4px] h-[0.5px] bg-black/5' style={{
width: 'calc(100% + 8px)',
}}></div>
<div
className="relative left-[-4px] h-[0.5px] bg-black/5"
style={{
width: 'calc(100% + 8px)',
}}
>
</div>
</>
)
}
{filteredVars.length > 0
? <div className={cn('max-h-[85vh] overflow-y-auto', maxHeightClass)}>
? (
<div className={cn('max-h-[85vh] overflow-y-auto', maxHeightClass)}>
{
filteredVars.map((item, i) => (
<div key={i} className={cn(!item.isFlat && 'mt-3', i === 0 && item.isFlat && 'mt-2')}>
{!item.isFlat && (
<div
className='system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary'
title={item.title}
>{item.title}</div>
)}
{item.vars.map((v, j) => (
<Item
key={j}
title={item.title}
nodeId={item.nodeId}
objPath={[]}
itemData={v}
onChange={onChange}
itemWidth={itemWidth}
isSupportFileVar={isSupportFileVar}
isException={v.isException}
isLoopVar={item.isLoop}
isFlat={item.isFlat}
isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor}
zIndex={zIndex}
preferSchemaType={preferSchemaType}
/>
))}
{item.isFlat && !filteredVars[i + 1]?.isFlat && !!filteredVars.find(item => !item.isFlat) && (
<div className='relative mt-[14px] flex items-center space-x-1'>
<div className='h-0 w-3 shrink-0 border border-divider-subtle'></div>
<div className='system-2xs-semibold-uppercase text-text-tertiary'>{t('workflow.debug.lastOutput')}</div>
<div className='h-0 shrink-0 grow border border-divider-subtle'></div>
{
filteredVars.map((item, i) => (
<div key={i} className={cn(!item.isFlat && 'mt-3', i === 0 && item.isFlat && 'mt-2')}>
{!item.isFlat && (
<div
className="system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary"
title={item.title}
>
{item.title}
</div>
)}
{item.vars.map((v, j) => (
<Item
key={j}
title={item.title}
nodeId={item.nodeId}
objPath={[]}
itemData={v}
onChange={onChange}
itemWidth={itemWidth}
isSupportFileVar={isSupportFileVar}
isException={v.isException}
isLoopVar={item.isLoop}
isFlat={item.isFlat}
isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor}
zIndex={zIndex}
preferSchemaType={preferSchemaType}
/>
))}
{item.isFlat && !filteredVars[i + 1]?.isFlat && !!filteredVars.find(item => !item.isFlat) && (
<div className="relative mt-[14px] flex items-center space-x-1">
<div className="h-0 w-3 shrink-0 border border-divider-subtle"></div>
<div className="system-2xs-semibold-uppercase text-text-tertiary">{t('workflow.debug.lastOutput')}</div>
<div className="h-0 shrink-0 grow border border-divider-subtle"></div>
</div>
)}
</div>
)}
</div>))
}
</div>
: <div className='mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500'>{t('workflow.common.noVar')}</div>}
))
}
</div>
)
: <div className="mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500">{t('workflow.common.noVar')}</div>}
{
showManageInputField && (
<ManageInputField

View File

@ -1,15 +1,15 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import { RiArrowDownSLine } from '@remixicon/react'
import { cn } from '@/utils/classnames'
import React, { useCallback, useState } from 'react'
import { Check } from '@/app/components/base/icons/src/vender/line/general'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { Check } from '@/app/components/base/icons/src/vender/line/general'
import { VarType } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
type Props = {
className?: string
@ -39,27 +39,28 @@ const VarReferencePicker: FC<Props> = ({
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
placement="bottom-start"
offset={4}
>
<PortalToFollowElemTrigger onClick={() => setOpen(!open)} className='w-[120px] cursor-pointer'>
<div className='flex h-8 items-center justify-between rounded-lg border-0 bg-components-input-bg-normal px-2.5 text-[13px] text-text-primary'>
<div className='w-0 grow truncate capitalize' title={value}>{value}</div>
<RiArrowDownSLine className='h-3.5 w-3.5 shrink-0 text-text-secondary' />
<PortalToFollowElemTrigger onClick={() => setOpen(!open)} className="w-[120px] cursor-pointer">
<div className="flex h-8 items-center justify-between rounded-lg border-0 bg-components-input-bg-normal px-2.5 text-[13px] text-text-primary">
<div className="w-0 grow truncate capitalize" title={value}>{value}</div>
<RiArrowDownSLine className="h-3.5 w-3.5 shrink-0 text-text-secondary" />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{
zIndex: 100,
}}>
<div className='w-[120px] rounded-lg bg-components-panel-bg p-1 shadow-sm'>
}}
>
<div className="w-[120px] rounded-lg bg-components-panel-bg p-1 shadow-sm">
{TYPES.map(type => (
<div
key={type}
className='flex h-[30px] cursor-pointer items-center justify-between rounded-lg pl-3 pr-2 text-[13px] text-text-primary hover:bg-state-base-hover'
className="flex h-[30px] cursor-pointer items-center justify-between rounded-lg pl-3 pr-2 text-[13px] text-text-primary hover:bg-state-base-hover"
onClick={handleChange(type)}
>
<div className='w-0 grow truncate capitalize'>{type}</div>
{type === value && <Check className='h-4 w-4 shrink-0 text-text-accent' />}
<div className="w-0 grow truncate capitalize">{type}</div>
{type === value && <Check className="h-4 w-4 shrink-0 text-text-accent" />}
</div>
))}
</div>

View File

@ -1,7 +1,7 @@
import type { VarInInspectType } from '@/types/workflow'
import { memo } from 'react'
import { cn } from '@/utils/classnames'
import { useVarIcon } from '../hooks'
import type { VarInInspectType } from '@/types/workflow'
export type VariableIconProps = {
className?: string

View File

@ -1,17 +1,17 @@
import { memo } from 'react'
import { capitalize } from 'lodash-es'
import type { VariablePayload } from '../types'
import {
RiErrorWarningFill,
RiMoreLine,
} from '@remixicon/react'
import type { VariablePayload } from '../types'
import { capitalize } from 'lodash-es'
import { memo } from 'react'
import Tooltip from '@/app/components/base/tooltip'
import { cn } from '@/utils/classnames'
import { isConversationVar, isENV, isGlobalVar, isRagVariableVar } from '../../utils'
import { useVarColor } from '../hooks'
import VariableNodeLabel from './variable-node-label'
import VariableIcon from './variable-icon'
import VariableName from './variable-name'
import { cn } from '@/utils/classnames'
import Tooltip from '@/app/components/base/tooltip'
import { isConversationVar, isENV, isGlobalVar, isRagVariableVar } from '../../utils'
import VariableNodeLabel from './variable-node-label'
const VariableLabel = ({
nodeType,
@ -46,8 +46,8 @@ const VariableLabel = ({
{
notShowFullPath && (
<>
<RiMoreLine className='h-3 w-3 shrink-0 text-text-secondary' />
<div className='system-xs-regular shrink-0 text-divider-deep'>/</div>
<RiMoreLine className="h-3 w-3 shrink-0 text-text-secondary" />
<div className="system-xs-regular shrink-0 text-divider-deep">/</div>
</>
)
}
@ -62,7 +62,7 @@ const VariableLabel = ({
/>
{
variableType && (
<div className='system-xs-regular shrink-0 text-text-tertiary'>
<div className="system-xs-regular shrink-0 text-text-tertiary">
{capitalize(variableType)}
</div>
)
@ -73,7 +73,7 @@ const VariableLabel = ({
popupContent={errorMsg}
asChild
>
<RiErrorWarningFill className='h-3 w-3 shrink-0 text-text-destructive' />
<RiErrorWarningFill className="h-3 w-3 shrink-0 text-text-destructive" />
</Tooltip>
)
}

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