feat: strategy form init

This commit is contained in:
AkaraChen 2024-12-25 11:24:24 +08:00
parent 07d7965e3b
commit 03520a5a81
9 changed files with 359 additions and 48 deletions

View File

@ -2,6 +2,7 @@ import { pinyin } from 'pinyin-pro'
import type { FC, RefObject } from 'react'
import type { ToolWithProvider } from '../types'
import { CollectionType } from '../../tools/types'
import classNames from '@/utils/classnames'
export const CUSTOM_GROUP_NAME = '@@@custom@@@'
export const WORKFLOW_GROUP_NAME = '@@@workflow@@@'
@ -69,16 +70,17 @@ export const groupItems = (items: ToolWithProvider[], getFirstChar: (item: ToolW
type IndexBarProps = {
letters: string[]
itemRefs: RefObject<{ [key: string]: HTMLElement | null }>
className?: string
}
const IndexBar: FC<IndexBarProps> = ({ letters, itemRefs }) => {
const IndexBar: FC<IndexBarProps> = ({ letters, itemRefs, className }) => {
const handleIndexClick = (letter: string) => {
const element = itemRefs.current?.[letter]
if (element)
element.scrollIntoView({ behavior: 'smooth' })
}
return (
<div className="index-bar absolute right-0 top-36 flex flex-col items-center w-6 justify-center text-xs font-medium text-text-quaternary ">
<div className={classNames('index-bar absolute right-0 top-36 flex flex-col items-center w-6 justify-center text-xs font-medium text-text-quaternary', className)}>
<div className='absolute left-0 top-0 h-full w-px bg-[linear-gradient(270deg,rgba(255,255,255,0)_0%,rgba(16,24,40,0.08)_30%,rgba(16,24,40,0.08)_50%,rgba(16,24,40,0.08)_70.5%,rgba(255,255,255,0)_100%)]'></div>
{letters.map(letter => (
<div className="hover:text-text-secondary cursor-pointer" key={letter} onClick={() => handleIndexClick(letter)}>

View File

@ -12,6 +12,7 @@ import Empty from '@/app/components/tools/add-tool-modal/empty'
import { useGetLanguage } from '@/context/i18n'
import ToolListTreeView from './tool/tool-list-tree-view/list'
import ToolListFlatView from './tool/tool-list-flat-view/list'
import classNames from '@/utils/classnames'
type ToolsProps = {
showWorkflowEmpty: boolean
@ -19,6 +20,8 @@ type ToolsProps = {
tools: ToolWithProvider[]
viewType: ViewType
hasSearchText: boolean
className?: string
indexBarClassName?: string
}
const Blocks = ({
showWorkflowEmpty,
@ -26,6 +29,8 @@ const Blocks = ({
tools,
viewType,
hasSearchText,
className,
indexBarClassName,
}: ToolsProps) => {
const { t } = useTranslation()
const language = useGetLanguage()
@ -75,7 +80,7 @@ const Blocks = ({
const toolRefs = useRef({})
return (
<div className='p-1 max-w-[320px]'>
<div className={classNames('p-1 max-w-[320px]', className)}>
{
!tools.length && !showWorkflowEmpty && (
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div>
@ -103,7 +108,7 @@ const Blocks = ({
)
)}
{isShowLetterIndex && <IndexBar letters={letters} itemRefs={toolRefs} />}
{isShowLetterIndex && <IndexBar letters={letters} itemRefs={toolRefs} className={indexBarClassName} />}
</div>
)
}

View File

@ -1,5 +1,5 @@
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import { useState } from 'react'
import { useMemo, useState } from 'react'
import type { Strategy } from './agent-strategy'
import classNames from '@/utils/classnames'
import { RiArrowDownSLine, RiArrowRightUpLine, RiErrorWarningFill } from '@remixicon/react'
@ -7,10 +7,10 @@ import { useAllBuiltInTools } from '@/service/use-tools'
import Tooltip from '@/app/components/base/tooltip'
import Link from 'next/link'
import { InstallPluginButton } from './install-plugin-button'
import SearchInput from '@/app/components/base/search-input'
import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select'
import Tools from '../../../block-selector/tools'
import SearchInput from '@/app/components/base/search-input'
import { MARKETPLACE_URL_PREFIX } from '@/config'
import Tools from '../../../block-selector/tools'
const ExternalNotInstallWarn = () => {
// TODO: add i18n label
@ -40,18 +40,23 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => {
const [open, setOpen] = useState(false)
const list = useAllBuiltInTools()
const [viewType, setViewType] = useState<ViewType>(ViewType.flat)
const [query, setQuery] = useState('')
const filteredTools = useMemo(() => {
if (!list.data) return []
return list.data.filter(tool => tool.name.toLowerCase().includes(query.toLowerCase()))
}, [query, list.data])
// TODO: should be replaced by real data
const isExternalInstalled = true
return <PortalToFollowElem open={open} onOpenChange={setOpen} placement='bottom'>
<PortalToFollowElemTrigger className='w-full'>
<div className='py-2 pl-3 pr-2 flex items-center rounded-lg bg-components-input-bg-normal w-full hover:bg-state-base-hover-alt' onClick={() => setOpen(true)}>
<div className='py-2 pl-3 pr-2 flex items-center rounded-lg bg-components-input-bg-normal w-full hover:bg-state-base-hover-alt select-none' onClick={() => setOpen(o => !o)}>
{/* eslint-disable-next-line @next/next/no-img-element */}
{list.data && <img
{list.data && value && <img
src={list.data.find(
coll => coll,
)?.icon as string}
width={24}
height={24}
width={20}
height={20}
className='rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'
alt='icon'
/>}
@ -60,21 +65,21 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => {
>
{value?.agent_strategy_name || 'Select agentic strategy'}
</p>
<div className='ml-auto flex items-center gap-1'>
{value && <div className='ml-auto flex items-center gap-1'>
<InstallPluginButton onClick={e => e.stopPropagation()} size={'small'} />
{isExternalInstalled ? <ExternalNotInstallWarn /> : <RiArrowDownSLine className='size-4 text-text-tertiary' />}
</div>
</div>}
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='bg-components-panel-bg-blur border-components-panel-border border-[0.5px] rounded-md shadow overflow-hidden'>
<PortalToFollowElemContent className='z-10'>
<div className='bg-components-panel-bg-blur border-components-panel-border border-[0.5px] rounded-md shadow overflow-hidden w-[388px]'>
<header className='p-2 gap-1 flex'>
<SearchInput placeholder='Search agentic strategy' value='' onChange={console.error} />
<SearchInput placeholder='Search agentic strategy' value={query} onChange={setQuery} className={'w-full'} />
<ViewTypeSelect viewType={viewType} onChange={setViewType} />
</header>
<main className="h-96 relative overflow-hidden">
<main className="md:h-[300px] xl:h-[400px] 2xl:h-[564px] relative overflow-hidden">
<Tools
tools={list.data || []}
tools={filteredTools}
viewType={viewType}
onSelect={(_, tool) => {
onChange({
@ -86,16 +91,20 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => {
}}
hasSearchText={false}
showWorkflowEmpty
className='max-w-none'
indexBarClassName='top-0 xl:top-36'
/>
<div className='sticky bottom-0 px-4 py-2 flex items-center justify-center border-t border-divider-subtle text-text-accent-light-mode-only bg-components-panel-bg text-xs'>
<div className='absolute bottom-0 px-4 py-2 flex items-center justify-center border-t border-divider-subtle text-text-accent-light-mode-only bg-components-panel-bg text-xs'>
Find more in
{/** //TODO: replace URL */}
<Link href={MARKETPLACE_URL_PREFIX} className='flex ml-1'>
Marketplace <RiArrowRightUpLine className='size-3' />
</Link>
</div>
</main>
</div>
{/* <div>
aaa
</div> */}
</PortalToFollowElemContent>
</PortalToFollowElem>
}

View File

@ -3,6 +3,8 @@ 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 InputVarList from '../../tool/components/input-var-list'
export type Strategy = {
agent_strategy_provider_name: string
@ -17,21 +19,36 @@ export type AgentStrategyProps = {
formSchema: CredentialFormSchema[]
formValue: ToolVarInputs
onFormValueChange: (value: ToolVarInputs) => void
/**
* @description use for get available vars
*/
nodeId: string
}
export const AgentStrategy = (props: AgentStrategyProps) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeId } = props
const { t } = useTranslation()
return <div className='space-y-2'>
<AgentStrategySelector value={strategy} onChange={onStrategyChange} />
{
strategy
? <div></div>
? <div>
<InputVarList
readOnly={false}
nodeId={nodeId}
schema={formSchema}
value={formValue}
onChange={onFormValueChange}
/>
</div>
// TODO: list empty need a icon
: <ListEmpty
title='Please configure agentic strategy.'
title={t('workflow.nodes.agent.strategy.configureTip')}
description={<div className='text-text-tertiary text-xs'>
After configuring the agentic strategy, this node will automatically load the remaining configurations. The strategy will affect the mechanism of multi-step tool reasoning. <br />
<Link href={'/'} className='text-text-accent-secondary'>Learn more</Link>
{t('workflow.nodes.agent.strategy.configureTipDesc')} <br />
<Link href={'/'} className='text-text-accent-secondary'>
{t('workflow.nodes.agent.learnMore')}
</Link>
</div>}
/>
}

View File

@ -1,19 +1,26 @@
import Tooltip from '@/app/components/base/tooltip'
import Indicator from '@/app/components/header/indicator'
import type { ComponentProps, PropsWithChildren } from 'react'
import classNames from '@/utils/classnames'
import type { ComponentProps, PropsWithChildren, ReactNode } from 'react'
export type SettingItemProps = PropsWithChildren<{
label: string
indicator?: ComponentProps<typeof Indicator>['color']
status?: 'error' | 'warning'
tooltip?: ReactNode
}>
export const SettingItem = ({ label, children, indicator }: SettingItemProps) => {
return <div className='flex items-center h-6 justify-between bg-gray-100 rounded-md px-1 space-x-1 text-xs font-normal relative'>
<div className='max-w-[100px] shrink-0 truncate text-xs font-medium text-text-tertiary uppercase'>
export const SettingItem = ({ 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='flex items-center h-6 justify-between bg-workflow-block-parma-bg rounded-md px-1 space-x-1 text-xs font-normal relative'>
<div className={classNames('shrink-0 truncate text-xs font-medium text-text-tertiary uppercase', !!children && 'max-w-[100px]')}>
{label}
</div>
<div className='grow w-0 shrink-0 truncate text-right text-xs font-normal text-text-secondary'>
{children}
</div>
<Tooltip popupContent={tooltip} disabled={!needTooltip}>
<div className='truncate text-right text-xs font-normal text-text-secondary'>
{children}
</div>
</Tooltip>
{indicator && <Indicator color={indicator} className='absolute -right-0.5 -top-0.5' />}
</div>
}

View File

@ -5,20 +5,21 @@ import { SettingItem } from '../_base/components/setting-item'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { Group, GroupLabel } from '../_base/components/group'
import { ToolIcon } from './components/tool-icon'
import useConfig from './use-config'
const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
const strategySelected = true
const { inputs } = useConfig(props.id, props.data)
return <div className='mb-1 px-3 py-1 space-y-1'>
{strategySelected
// TODO: add tooltip for this
? <SettingItem label='Strategy' indicator='red'>
ReAct
{inputs.agent_strategy_name
? <SettingItem label='Strategy' status='error' tooltip='ReAct is not installed'>
{inputs.agent_strategy_name}
</SettingItem>
: <SettingItem label='Agentic strategy Not Set' />}
<Group label={
<GroupLabel className='mt-1'>
<Group
label={<GroupLabel className='mt-1'>
Model
</GroupLabel>}>
</GroupLabel>}
>
<ModelSelector
modelList={[]}
readonly

View File

@ -7,6 +7,258 @@ import Slider from '@/app/components/base/slider'
import useNodeCrud from '../_base/hooks/use-node-crud'
import { AgentStrategy } from '../_base/components/agent-strategy'
const mockSchema = [
{
name: 'format',
label: {
en_US: 'Format',
zh_Hans: '格式',
pt_BR: 'Format',
ja_JP: 'Format',
},
placeholder: null,
scope: null,
required: false,
default: '%Y-%m-%d %H:%M:%S',
min: null,
max: null,
options: [],
type: 'text-input',
human_description: {
en_US: 'Time format in strftime standard.',
zh_Hans: 'strftime 标准的时间格式。',
pt_BR: 'Time format in strftime standard.',
ja_JP: 'Time format in strftime standard.',
},
form: 'form',
llm_description: null,
variable: 'format',
_type: 'string',
show_on: [],
tooltip: {
en_US: 'Time format in strftime standard.',
zh_Hans: 'strftime 标准的时间格式。',
pt_BR: 'Time format in strftime standard.',
ja_JP: 'Time format in strftime standard.',
},
},
{
name: 'timezone',
label: {
en_US: 'Timezone',
zh_Hans: '时区',
pt_BR: 'Timezone',
ja_JP: 'Timezone',
},
placeholder: null,
scope: null,
required: false,
default: 'UTC',
min: null,
max: null,
options: [
{
value: 'UTC',
label: {
en_US: 'UTC',
zh_Hans: 'UTC',
pt_BR: 'UTC',
ja_JP: 'UTC',
},
show_on: [],
},
{
value: 'America/New_York',
label: {
en_US: 'America/New_York',
zh_Hans: '美洲/纽约',
pt_BR: 'America/New_York',
ja_JP: 'America/New_York',
},
show_on: [],
},
{
value: 'America/Los_Angeles',
label: {
en_US: 'America/Los_Angeles',
zh_Hans: '美洲/洛杉矶',
pt_BR: 'America/Los_Angeles',
ja_JP: 'America/Los_Angeles',
},
show_on: [],
},
{
value: 'America/Chicago',
label: {
en_US: 'America/Chicago',
zh_Hans: '美洲/芝加哥',
pt_BR: 'America/Chicago',
ja_JP: 'America/Chicago',
},
show_on: [],
},
{
value: 'America/Sao_Paulo',
label: {
en_US: 'America/Sao_Paulo',
zh_Hans: '美洲/圣保罗',
pt_BR: 'América/São Paulo',
ja_JP: 'America/Sao_Paulo',
},
show_on: [],
},
{
value: 'Asia/Shanghai',
label: {
en_US: 'Asia/Shanghai',
zh_Hans: '亚洲/上海',
pt_BR: 'Asia/Shanghai',
ja_JP: 'Asia/Shanghai',
},
show_on: [],
},
{
value: 'Asia/Ho_Chi_Minh',
label: {
en_US: 'Asia/Ho_Chi_Minh',
zh_Hans: '亚洲/胡志明市',
pt_BR: 'Ásia/Ho Chi Minh',
ja_JP: 'Asia/Ho_Chi_Minh',
},
show_on: [],
},
{
value: 'Asia/Tokyo',
label: {
en_US: 'Asia/Tokyo',
zh_Hans: '亚洲/东京',
pt_BR: 'Asia/Tokyo',
ja_JP: 'Asia/Tokyo',
},
show_on: [],
},
{
value: 'Asia/Dubai',
label: {
en_US: 'Asia/Dubai',
zh_Hans: '亚洲/迪拜',
pt_BR: 'Asia/Dubai',
ja_JP: 'Asia/Dubai',
},
show_on: [],
},
{
value: 'Asia/Kolkata',
label: {
en_US: 'Asia/Kolkata',
zh_Hans: '亚洲/加尔各答',
pt_BR: 'Asia/Kolkata',
ja_JP: 'Asia/Kolkata',
},
show_on: [],
},
{
value: 'Asia/Seoul',
label: {
en_US: 'Asia/Seoul',
zh_Hans: '亚洲/首尔',
pt_BR: 'Asia/Seoul',
ja_JP: 'Asia/Seoul',
},
show_on: [],
},
{
value: 'Asia/Singapore',
label: {
en_US: 'Asia/Singapore',
zh_Hans: '亚洲/新加坡',
pt_BR: 'Asia/Singapore',
ja_JP: 'Asia/Singapore',
},
show_on: [],
},
{
value: 'Europe/London',
label: {
en_US: 'Europe/London',
zh_Hans: '欧洲/伦敦',
pt_BR: 'Europe/London',
ja_JP: 'Europe/London',
},
show_on: [],
},
{
value: 'Europe/Berlin',
label: {
en_US: 'Europe/Berlin',
zh_Hans: '欧洲/柏林',
pt_BR: 'Europe/Berlin',
ja_JP: 'Europe/Berlin',
},
show_on: [],
},
{
value: 'Europe/Moscow',
label: {
en_US: 'Europe/Moscow',
zh_Hans: '欧洲/莫斯科',
pt_BR: 'Europe/Moscow',
ja_JP: 'Europe/Moscow',
},
show_on: [],
},
{
value: 'Australia/Sydney',
label: {
en_US: 'Australia/Sydney',
zh_Hans: '澳大利亚/悉尼',
pt_BR: 'Australia/Sydney',
ja_JP: 'Australia/Sydney',
},
show_on: [],
},
{
value: 'Pacific/Auckland',
label: {
en_US: 'Pacific/Auckland',
zh_Hans: '太平洋/奥克兰',
pt_BR: 'Pacific/Auckland',
ja_JP: 'Pacific/Auckland',
},
show_on: [],
},
{
value: 'Africa/Cairo',
label: {
en_US: 'Africa/Cairo',
zh_Hans: '非洲/开罗',
pt_BR: 'Africa/Cairo',
ja_JP: 'Africa/Cairo',
},
show_on: [],
},
],
type: 'select',
human_description: {
en_US: 'Timezone',
zh_Hans: '时区',
pt_BR: 'Timezone',
ja_JP: 'Timezone',
},
form: 'form',
llm_description: null,
variable: 'timezone',
_type: 'select',
show_on: [],
tooltip: {
en_US: 'Timezone',
zh_Hans: '时区',
pt_BR: 'Timezone',
ja_JP: 'Timezone',
},
},
] as const
const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
const { inputs, setInputs } = useNodeCrud(props.id, props.data)
const [iter, setIter] = [inputs.max_iterations, (value: number) => {
@ -15,7 +267,7 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
max_iterations: value,
})
}]
return <>
return <div className='space-y-2 my-2'>
<Field title={'Strategy'} className='px-4' >
<AgentStrategy
strategy={inputs.agent_strategy_name ? {
@ -31,9 +283,13 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
agent_parameters: strategy?.agent_parameters,
})
}}
formSchema={[]}
formValue={{}}
onFormValueChange={console.error}
formSchema={mockSchema as any}
formValue={inputs.agent_parameters || {}}
onFormValueChange={value => setInputs({
...inputs,
agent_parameters: value,
})}
nodeId={props.id}
/>
</Field>
<Field title={'tools'} className='px-4'>
@ -55,7 +311,7 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
/>
</div>
</Field>
</>
</div>
}
export default AgentPanel

View File

@ -256,7 +256,7 @@ const translation = {
'parameter-extractor': 'Use LLM to extract structured parameters from natural language for tool invocations or HTTP requests.',
'document-extractor': 'Used to parse uploaded documents into text content that is easily understandable by LLM.',
'list-operator': 'Used to filter or sort array content.',
'agent': 'TODO: add text here',
'agent': 'Invoking large language models to answer questions or process natural language',
},
operator: {
zoomIn: 'Zoom In',
@ -697,6 +697,13 @@ const translation = {
last_record: 'Last record',
},
},
agent: {
strategy: {
configureTip: 'Please configure agentic strategy.',
configureTipDesc: 'After configuring the agentic strategy, this node will automatically load the remaining configurations. The strategy will affect the mechanism of multi-step tool reasoning. ',
},
learnMore: 'Learn more',
},
},
tracing: {
stopBy: 'Stop by {{user}}',

View File

@ -256,7 +256,7 @@ const translation = {
'parameter-extractor': '利用 LLM 从自然语言内推理提取出结构化参数,用于后置的工具调用或 HTTP 请求。',
'document-extractor': '用于将用户上传的文档解析为 LLM 便于理解的文本内容。',
'list-operator': '用于过滤或排序数组内容。',
'agent': 'TODO: Agent',
'agent': '调用大型语言模型回答问题或处理自然语言',
},
operator: {
zoomIn: '放大',
@ -697,6 +697,13 @@ const translation = {
last_record: '最后一条记录',
},
},
agent: {
strategy: {
configureTip: '请配置 Agent 策略。',
configureTipDesc: '配置完成后,此节点将自动加载剩余配置。策略将影响多步工具推理的机制。',
},
learnMore: '了解更多',
},
},
tracing: {
stopBy: '由{{user}}终止',