mirror of
https://github.com/langgenius/dify.git
synced 2026-03-02 13:35:10 +00:00
Compare commits
31 Commits
fix/agent-
...
feat/knowl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3107ec878a | ||
|
|
7d5fcfef4c | ||
|
|
e862ab0def | ||
|
|
15f80a72b8 | ||
|
|
1a7de23864 | ||
|
|
10fccd2b3f | ||
|
|
b568947e00 | ||
|
|
7dcbb75839 | ||
|
|
ffdfcdd4a4 | ||
|
|
f4604bf6d0 | ||
|
|
3a72b76c32 | ||
|
|
49dd77e219 | ||
|
|
b9f223d9d4 | ||
|
|
3c4da03575 | ||
|
|
7692476097 | ||
|
|
428438eeca | ||
|
|
b7c546f2ad | ||
|
|
0ed892a747 | ||
|
|
5e2bd407a8 | ||
|
|
a4668e0ffc | ||
|
|
1ca79ea729 | ||
|
|
ebb6de5f52 | ||
|
|
2adc704463 | ||
|
|
b74f1b3c07 | ||
|
|
f60e650400 | ||
|
|
83d0142641 | ||
|
|
56c7f49625 | ||
|
|
7c1d842cfe | ||
|
|
2ea3b64a45 | ||
|
|
824f8d8994 | ||
|
|
31c17e6378 |
@@ -20,6 +20,7 @@ from core.model_runtime.model_providers.__base.text_embedding_model import TextE
|
|||||||
from core.model_runtime.model_providers.__base.tts_model import TTSModel
|
from core.model_runtime.model_providers.__base.tts_model import TTSModel
|
||||||
from core.model_runtime.schema_validators.model_credential_schema_validator import ModelCredentialSchemaValidator
|
from core.model_runtime.schema_validators.model_credential_schema_validator import ModelCredentialSchemaValidator
|
||||||
from core.model_runtime.schema_validators.provider_credential_schema_validator import ProviderCredentialSchemaValidator
|
from core.model_runtime.schema_validators.provider_credential_schema_validator import ProviderCredentialSchemaValidator
|
||||||
|
from core.plugin.entities.plugin import ModelProviderID
|
||||||
from core.plugin.entities.plugin_daemon import PluginModelProviderEntity
|
from core.plugin.entities.plugin_daemon import PluginModelProviderEntity
|
||||||
from core.plugin.manager.asset import PluginAssetManager
|
from core.plugin.manager.asset import PluginAssetManager
|
||||||
from core.plugin.manager.model import PluginModelManager
|
from core.plugin.manager.model import PluginModelManager
|
||||||
@@ -112,6 +113,9 @@ class ModelProviderFactory:
|
|||||||
:param provider: provider name
|
:param provider: provider name
|
||||||
:return: provider schema
|
:return: provider schema
|
||||||
"""
|
"""
|
||||||
|
if "/" not in provider:
|
||||||
|
provider = str(ModelProviderID(provider))
|
||||||
|
|
||||||
# fetch plugin model providers
|
# fetch plugin model providers
|
||||||
plugin_model_provider_entities = self.get_plugin_model_providers()
|
plugin_model_provider_entities = self.get_plugin_model_providers()
|
||||||
|
|
||||||
@@ -363,4 +367,4 @@ class ModelProviderFactory:
|
|||||||
plugin_id = "/".join(provider.split("/")[:-1])
|
plugin_id = "/".join(provider.split("/")[:-1])
|
||||||
provider_name = provider.split("/")[-1]
|
provider_name = provider.split("/")[-1]
|
||||||
|
|
||||||
return plugin_id, provider_name
|
return str(plugin_id), provider_name
|
||||||
|
|||||||
@@ -169,6 +169,21 @@ class GenericProviderID:
|
|||||||
return f"{self.organization}/{self.plugin_name}"
|
return f"{self.organization}/{self.plugin_name}"
|
||||||
|
|
||||||
|
|
||||||
|
class ModelProviderID(GenericProviderID):
|
||||||
|
def __init__(self, value: str, is_hardcoded: bool = False) -> None:
|
||||||
|
super().__init__(value, is_hardcoded)
|
||||||
|
if self.organization == "langgenius" and self.provider_name == "google":
|
||||||
|
self.provider_name = "gemini"
|
||||||
|
|
||||||
|
|
||||||
|
class ToolProviderID(GenericProviderID):
|
||||||
|
def __init__(self, value: str, is_hardcoded: bool = False) -> None:
|
||||||
|
super().__init__(value, is_hardcoded)
|
||||||
|
if self.organization == "langgenius":
|
||||||
|
if self.provider_name in ["jina", "siliconflow"]:
|
||||||
|
self.provider_name = f"{self.provider_name}_tool"
|
||||||
|
|
||||||
|
|
||||||
class PluginDependency(BaseModel):
|
class PluginDependency(BaseModel):
|
||||||
class Type(enum.StrEnum):
|
class Type(enum.StrEnum):
|
||||||
Github = PluginInstallationSource.Github.value
|
Github = PluginInstallationSource.Github.value
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
@@ -418,8 +417,6 @@ class PluginMigration:
|
|||||||
|
|
||||||
logger.info("Uninstall plugins")
|
logger.info("Uninstall plugins")
|
||||||
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
# get installation
|
# get installation
|
||||||
try:
|
try:
|
||||||
installation = manager.list_plugins(fake_tenant_id)
|
installation = manager.list_plugins(fake_tenant_id)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from sqlalchemy.orm import Session
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from core.helper.position_helper import is_filtered
|
from core.helper.position_helper import is_filtered
|
||||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||||
from core.plugin.entities.plugin import GenericProviderID
|
from core.plugin.entities.plugin import GenericProviderID, ToolProviderID
|
||||||
from core.plugin.manager.exc import PluginDaemonClientSideError
|
from core.plugin.manager.exc import PluginDaemonClientSideError
|
||||||
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
|
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
|
||||||
from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity
|
from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity
|
||||||
@@ -240,10 +240,7 @@ class BuiltinToolManageService:
|
|||||||
|
|
||||||
# rewrite db_providers
|
# rewrite db_providers
|
||||||
for db_provider in db_providers:
|
for db_provider in db_providers:
|
||||||
try:
|
db_provider.provider = str(ToolProviderID(db_provider.provider))
|
||||||
GenericProviderID(db_provider.provider)
|
|
||||||
except Exception:
|
|
||||||
db_provider.provider = f"langgenius/{db_provider.provider}/{db_provider.provider}"
|
|
||||||
|
|
||||||
# find provider
|
# find provider
|
||||||
def find_provider(provider):
|
def find_provider(provider):
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import Button from '@/app/components/base/button'
|
|||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||||
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
|
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
|
||||||
|
// import DatasetMetadataDrawer from '@/app/components/datasets/metadata/dataset-metadata-drawer'
|
||||||
|
// import MetaDataDocument from '@/app/components/datasets/metadata/metadata-document'
|
||||||
|
import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal'
|
||||||
// Services
|
// Services
|
||||||
import { fetchDatasetApiBaseUrl } from '@/service/datasets'
|
import { fetchDatasetApiBaseUrl } from '@/service/datasets'
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
|||||||
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
||||||
|
import { DataType } from '@/app/components/datasets/metadata/types'
|
||||||
|
|
||||||
const Container = () => {
|
const Container = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -81,8 +84,54 @@ const Container = () => {
|
|||||||
return router.replace('/apps')
|
return router.replace('/apps')
|
||||||
}, [currentWorkspace, router])
|
}, [currentWorkspace, router])
|
||||||
|
|
||||||
|
const [isBuiltInEnabled, setIsBuiltInEnabled] = useState(false)
|
||||||
|
const [userMetadata, setUserMetadata] = useState([
|
||||||
|
{ id: '1', name: 'name1', type: DataType.string, valueLength: 1 },
|
||||||
|
{ id: '2', name: 'name2', type: DataType.number, valueLength: 2 },
|
||||||
|
{ id: '3', name: 'name3', type: DataType.time, valueLength: 3 },
|
||||||
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className='grow relative flex flex-col bg-background-body overflow-y-auto scroll-container'>
|
<div ref={containerRef} className='grow relative flex flex-col bg-background-body overflow-y-auto scroll-container'>
|
||||||
|
<div className='flex justify-end mt-[300px] mr-[100px]'>
|
||||||
|
{/* <MetaDataDocument /> */}
|
||||||
|
{/* <SelectMetadataModal trigger={<Button className='w-[200px]'>select</Button>} onSave={(data) => { console.log(data) }} />
|
||||||
|
<CreateModal trigger={<Button className='w-[200px]'>add</Button>} hasBack onSave={(data) => { console.log(data) }} />
|
||||||
|
<Button className='flex w-[200px]' size="medium" onClick={() => setShowExternalApiPanel(true)}>
|
||||||
|
Metadata
|
||||||
|
</Button> */}
|
||||||
|
{/* <DatasetMetadataDrawer
|
||||||
|
userMetadata={userMetadata}
|
||||||
|
onChange={setUserMetadata}
|
||||||
|
builtInMetadata={[
|
||||||
|
{ id: '1', name: 'name1', type: DataType.string, valueLength: 1 },
|
||||||
|
{ id: '2', name: 'name2', type: DataType.number, valueLength: 2 },
|
||||||
|
{ id: '3', name: 'name3', type: DataType.time, valueLength: 3 },
|
||||||
|
]}
|
||||||
|
isBuiltInEnabled={isBuiltInEnabled}
|
||||||
|
onIsBuiltInEnabledChange={setIsBuiltInEnabled}
|
||||||
|
onClose={() => { }}
|
||||||
|
/> */}
|
||||||
|
<EditMetadataBatchModal
|
||||||
|
documentNum={20}
|
||||||
|
list={[
|
||||||
|
{
|
||||||
|
id: '1', name: 'name1', type: DataType.string, value: 'aaa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2', name: 'name2', type: DataType.number, value: 'ccc', isMultipleValue: true, isUpdated: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2.1', name: 'num v', type: DataType.number, value: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3', name: 'name3', type: DataType.time, value: '', isUpdated: true, // updateType: UpdateType.delete,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onHide={() => { }}
|
||||||
|
onChange={(list, newList, isApplyToAllSelectDocument) => { console.log(list, newList, isApplyToAllSelectDocument) }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
|
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
|
||||||
<TabSliderNew
|
<TabSliderNew
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
|
|||||||
@@ -53,15 +53,18 @@ export default function Drawer({
|
|||||||
/>
|
/>
|
||||||
<div className={cn('relative z-50 flex flex-col justify-between bg-components-panel-bg w-full max-w-sm p-6 overflow-hidden text-left align-middle shadow-xl', panelClassname)}>
|
<div className={cn('relative z-50 flex flex-col justify-between bg-components-panel-bg w-full max-w-sm p-6 overflow-hidden text-left align-middle shadow-xl', panelClassname)}>
|
||||||
<>
|
<>
|
||||||
{title && <Dialog.Title
|
<div className='flex justify-between'>
|
||||||
as="h3"
|
{title && <Dialog.Title
|
||||||
className="text-lg font-medium leading-6 text-text-primary"
|
as="h3"
|
||||||
>
|
className="text-lg font-medium leading-6 text-text-primary"
|
||||||
{title}
|
>
|
||||||
</Dialog.Title>}
|
{title}
|
||||||
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
|
</Dialog.Title>}
|
||||||
<XMarkIcon className='w-4 h-4 text-text-tertiary' onClick={onClose} />
|
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
|
||||||
</Dialog.Title>}
|
<XMarkIcon className='w-4 h-4 text-text-tertiary cursor-pointer' onClick={onClose} />
|
||||||
|
</Dialog.Title>}
|
||||||
|
</div>
|
||||||
|
|
||||||
{description && <Dialog.Description className='text-text-tertiary text-xs font-normal mt-2'>{description}</Dialog.Description>}
|
{description && <Dialog.Description className='text-text-tertiary text-xs font-normal mt-2'>{description}</Dialog.Description>}
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ export type InputNumberProps = {
|
|||||||
max?: number
|
max?: number
|
||||||
min?: number
|
min?: number
|
||||||
defaultValue?: number
|
defaultValue?: number
|
||||||
|
wrapClassName?: string
|
||||||
|
controlWrapClassName?: string
|
||||||
|
controlClassName?: string
|
||||||
} & Omit<InputProps, 'value' | 'onChange' | 'size' | 'min' | 'max' | 'defaultValue'>
|
} & Omit<InputProps, 'value' | 'onChange' | 'size' | 'min' | 'max' | 'defaultValue'>
|
||||||
|
|
||||||
export const InputNumber: FC<InputNumberProps> = (props) => {
|
export const InputNumber: FC<InputNumberProps> = (props) => {
|
||||||
const { unit, className, onChange, amount = 1, value, size = 'md', max, min, defaultValue, ...rest } = props
|
const { unit, className, onChange, amount = 1, value, size = 'md', max, min, defaultValue, wrapClassName, controlWrapClassName, controlClassName, ...rest } = props
|
||||||
|
|
||||||
const isValidValue = (v: number) => {
|
const isValidValue = (v: number) => {
|
||||||
if (max && v > max)
|
if (max && v > max)
|
||||||
@@ -46,7 +49,7 @@ export const InputNumber: FC<InputNumberProps> = (props) => {
|
|||||||
onChange(newValue)
|
onChange(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className='flex'>
|
return <div className={classNames('flex', wrapClassName)}>
|
||||||
<Input {...rest}
|
<Input {...rest}
|
||||||
// disable default controller
|
// disable default controller
|
||||||
type='text'
|
type='text'
|
||||||
@@ -68,16 +71,18 @@ export const InputNumber: FC<InputNumberProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
/>
|
/>
|
||||||
<div className='flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs'>
|
<div className={classNames('flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs', controlWrapClassName)}>
|
||||||
<button onClick={inc} className={classNames(
|
<button onClick={inc} className={classNames(
|
||||||
size === 'sm' ? 'pt-1' : 'pt-1.5',
|
size === 'sm' ? 'pt-1' : 'pt-1.5',
|
||||||
'px-1.5 hover:bg-components-input-bg-hover',
|
'px-1.5 hover:bg-components-input-bg-hover',
|
||||||
|
controlClassName,
|
||||||
)}>
|
)}>
|
||||||
<RiArrowUpSLine className='size-3' />
|
<RiArrowUpSLine className='size-3' />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={dec} className={classNames(
|
<button onClick={dec} className={classNames(
|
||||||
size === 'sm' ? 'pb-1' : 'pb-1.5',
|
size === 'sm' ? 'pb-1' : 'pb-1.5',
|
||||||
'px-1.5 hover:bg-components-input-bg-hover',
|
'px-1.5 hover:bg-components-input-bg-hover',
|
||||||
|
controlClassName,
|
||||||
)}>
|
)}>
|
||||||
<RiArrowDownSLine className='size-3' />
|
<RiArrowDownSLine className='size-3' />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
58
web/app/components/base/modal-like-wrap/index.tsx
Normal file
58
web/app/components/base/modal-like-wrap/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Button from '../button'
|
||||||
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string
|
||||||
|
className?: string
|
||||||
|
beforeHeader?: React.ReactNode
|
||||||
|
onClose: () => void
|
||||||
|
hideCloseBtn?: boolean
|
||||||
|
onConfirm: () => void
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalLikeWrap: FC<Props> = ({
|
||||||
|
title,
|
||||||
|
className,
|
||||||
|
beforeHeader,
|
||||||
|
children,
|
||||||
|
onClose,
|
||||||
|
hideCloseBtn,
|
||||||
|
onConfirm,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('w-[320px] px-3 pt-3.5 pb-4 bg-components-panel-bg shadow-xl rounded-2xl border-[0.5px] border-components-panel-border', className)}>
|
||||||
|
{beforeHeader || null}
|
||||||
|
<div className='mb-1 flex h-6 items-center justify-between'>
|
||||||
|
<div className='system-xl-semibold text-text-primary'>{title}</div>
|
||||||
|
{!hideCloseBtn && (
|
||||||
|
<div
|
||||||
|
className='p-1.5 text-text-tertiary cursor-pointer'
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<RiCloseLine className='size-4' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='mt-2'>{children}</div>
|
||||||
|
<div className='mt-4 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
className='mr-2'
|
||||||
|
onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={onConfirm}
|
||||||
|
variant='primary'
|
||||||
|
>{t('common.operation.save')}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(ModalLikeWrap)
|
||||||
@@ -5,7 +5,7 @@ import classNames from '@/utils/classnames'
|
|||||||
|
|
||||||
type SwitchProps = {
|
type SwitchProps = {
|
||||||
onChange?: (value: boolean) => void
|
onChange?: (value: boolean) => void
|
||||||
size?: 'sm' | 'md' | 'lg' | 'l'
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'l'
|
||||||
defaultValue?: boolean
|
defaultValue?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
@@ -23,6 +23,7 @@ const Switch = React.forwardRef(
|
|||||||
l: 'h-5 w-9',
|
l: 'h-5 w-9',
|
||||||
md: 'h-4 w-7',
|
md: 'h-4 w-7',
|
||||||
sm: 'h-3 w-5',
|
sm: 'h-3 w-5',
|
||||||
|
xs: 'h-2.5 w-3.5',
|
||||||
}
|
}
|
||||||
|
|
||||||
const circleStyle = {
|
const circleStyle = {
|
||||||
@@ -30,6 +31,7 @@ const Switch = React.forwardRef(
|
|||||||
l: 'h-4 w-4',
|
l: 'h-4 w-4',
|
||||||
md: 'h-3 w-3',
|
md: 'h-3 w-3',
|
||||||
sm: 'h-2 w-2',
|
sm: 'h-2 w-2',
|
||||||
|
xs: 'h-1.5 w-1',
|
||||||
}
|
}
|
||||||
|
|
||||||
const translateLeft = {
|
const translateLeft = {
|
||||||
@@ -37,6 +39,7 @@ const Switch = React.forwardRef(
|
|||||||
l: 'translate-x-4',
|
l: 'translate-x-4',
|
||||||
md: 'translate-x-3',
|
md: 'translate-x-3',
|
||||||
sm: 'translate-x-2',
|
sm: 'translate-x-2',
|
||||||
|
xs: 'translate-x-1.5',
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<OriginalSwitch
|
<OriginalSwitch
|
||||||
@@ -53,6 +56,7 @@ const Switch = React.forwardRef(
|
|||||||
enabled ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked',
|
enabled ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked',
|
||||||
'relative inline-flex flex-shrink-0 cursor-pointer rounded-[5px] border-2 border-transparent transition-colors duration-200 ease-in-out',
|
'relative inline-flex flex-shrink-0 cursor-pointer rounded-[5px] border-2 border-transparent transition-colors duration-200 ease-in-out',
|
||||||
disabled ? '!opacity-50 !cursor-not-allowed' : '',
|
disabled ? '!opacity-50 !cursor-not-allowed' : '',
|
||||||
|
size === 'xs' && 'rounded-sm',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -61,6 +65,7 @@ const Switch = React.forwardRef(
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
circleStyle[size],
|
circleStyle[size],
|
||||||
enabled ? translateLeft[size] : 'translate-x-0',
|
enabled ? translateLeft[size] : 'translate-x-0',
|
||||||
|
size === 'xs' && 'rounded-[1px]',
|
||||||
'pointer-events-none inline-block transform rounded-[3px] bg-components-toggle-knob shadow ring-0 transition duration-200 ease-in-out',
|
'pointer-events-none inline-block transform rounded-[3px] bg-components-toggle-knob shadow ring-0 transition duration-200 ease-in-out',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,47 +3,51 @@ import type { FC } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
interface Option {
|
type Option = {
|
||||||
value: string
|
value: string
|
||||||
text: string | JSX.Element
|
text: string | JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ItemProps {
|
type ItemProps = {
|
||||||
className?: string
|
className?: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
onClick: (v: string) => void
|
onClick: (v: string) => void
|
||||||
option: Option
|
option: Option
|
||||||
|
smallItem?: boolean
|
||||||
}
|
}
|
||||||
const Item: FC<ItemProps> = ({
|
const Item: FC<ItemProps> = ({
|
||||||
className,
|
className,
|
||||||
isActive,
|
isActive,
|
||||||
onClick,
|
onClick,
|
||||||
option,
|
option,
|
||||||
|
smallItem,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative pb-2.5 system-xl-semibold',
|
'relative pb-2.5 ',
|
||||||
!isActive && 'cursor-pointer',
|
!isActive && 'cursor-pointer',
|
||||||
|
smallItem ? 'system-sm-semibold-uppercase' : 'system-xl-semibold',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
onClick={() => !isActive && onClick(option.value)}
|
onClick={() => !isActive && onClick(option.value)}
|
||||||
>
|
>
|
||||||
<div className={cn(isActive ? 'text-text-primary' : 'text-text-tertiary')}>{option.text}</div>
|
<div className={cn(isActive ? 'text-text-primary' : 'text-text-tertiary')}>{option.text}</div>
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<div className='absolute bottom-0 left-0 right-0 h-0.5 bg-util-colors-blue-blue-500'></div>
|
<div className='absolute bottom-0 left-0 right-0 h-0.5 bg-util-colors-blue-brand-blue-brand-600'></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
value: string
|
value: string
|
||||||
onChange: (v: string) => void
|
onChange: (v: string) => void
|
||||||
options: Option[]
|
options: Option[]
|
||||||
noBorderBottom?: boolean
|
noBorderBottom?: boolean
|
||||||
|
smallItem?: boolean
|
||||||
itemClassName?: string
|
itemClassName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +58,7 @@ const TabSlider: FC<Props> = ({
|
|||||||
options,
|
options,
|
||||||
noBorderBottom,
|
noBorderBottom,
|
||||||
itemClassName,
|
itemClassName,
|
||||||
|
smallItem,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn(className, !noBorderBottom && 'border-b border-divider-subtle', 'flex space-x-6')}>
|
<div className={cn(className, !noBorderBottom && 'border-b border-divider-subtle', 'flex space-x-6')}>
|
||||||
@@ -64,6 +69,7 @@ const TabSlider: FC<Props> = ({
|
|||||||
onClick={onChange}
|
onClick={onChange}
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={itemClassName}
|
className={itemClassName}
|
||||||
|
smallItem={smallItem}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
31
web/app/components/datasets/metadata/add-metadata-button.tsx
Normal file
31
web/app/components/datasets/metadata/add-metadata-button.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import Button from '../../base/button'
|
||||||
|
import { RiAddLine } from '@remixicon/react'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddedMetadataButton: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={cn('w-full flex items-center', className)}
|
||||||
|
size='small'
|
||||||
|
variant='tertiary'
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<RiAddLine className='mr-1 size-3.5' />
|
||||||
|
<div>{t('dataset.metadata.addMetadata')}</div>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(AddedMetadataButton)
|
||||||
87
web/app/components/datasets/metadata/create-content.tsx
Normal file
87
web/app/components/datasets/metadata/create-content.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useState } from 'react'
|
||||||
|
import { DataType } from './types'
|
||||||
|
import ModalLikeWrap from '../../base/modal-like-wrap'
|
||||||
|
import Field from './field'
|
||||||
|
import OptionCard from '../../workflow/nodes/_base/components/option-card'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import { RiArrowLeftLine } from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const i18nPrefix = 'dataset.metadata.createMetadata'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
onSave: (data: any) => void
|
||||||
|
hasBack?: boolean
|
||||||
|
onBack?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateContent: FC<Props> = ({
|
||||||
|
hasBack,
|
||||||
|
onBack,
|
||||||
|
onSave,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [type, setType] = useState(DataType.string)
|
||||||
|
|
||||||
|
const handleTypeChange = useCallback((newType: DataType) => {
|
||||||
|
return () => setType(newType)
|
||||||
|
}, [setType])
|
||||||
|
const [name, setName] = useState('')
|
||||||
|
const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setName(e.target.value)
|
||||||
|
}, [setName])
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
onSave({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}, [onSave, type, name])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalLikeWrap
|
||||||
|
title={t(`${i18nPrefix}.title`)}
|
||||||
|
onClose={() => { }}
|
||||||
|
onConfirm={handleSave}
|
||||||
|
hideCloseBtn={hasBack}
|
||||||
|
beforeHeader={hasBack && (
|
||||||
|
<div className='relative left-[-4px] mb-1 flex items-center py-1 space-x-1 text-text-accent cursor-pointer' onClick={onBack}>
|
||||||
|
<RiArrowLeftLine className='size-4' />
|
||||||
|
<div className='system-xs-semibold-uppercase'>{t(`${i18nPrefix}.back`)}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='space-y-3'>
|
||||||
|
<Field label={t(`${i18nPrefix}.type`)}>
|
||||||
|
<div className='grid grid-cols-3 gap-2'>
|
||||||
|
<OptionCard
|
||||||
|
title='String'
|
||||||
|
selected={type === DataType.string}
|
||||||
|
onSelect={handleTypeChange(DataType.string)}
|
||||||
|
/>
|
||||||
|
<OptionCard
|
||||||
|
title='Number'
|
||||||
|
selected={type === DataType.number}
|
||||||
|
onSelect={handleTypeChange(DataType.number)}
|
||||||
|
/>
|
||||||
|
<OptionCard
|
||||||
|
title='Time'
|
||||||
|
selected={type === DataType.time}
|
||||||
|
onSelect={handleTypeChange(DataType.time)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
<Field label={t(`${i18nPrefix}.name`)}>
|
||||||
|
<Input
|
||||||
|
value={name}
|
||||||
|
onChange={handleNameChange}
|
||||||
|
placeholder={t(`${i18nPrefix}.namePlaceholder`)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
</ModalLikeWrap>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(CreateContent)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import type { Props as CreateContentProps } from './create-content'
|
||||||
|
import CreateContent from './create-content'
|
||||||
|
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSave: (data: any) => void
|
||||||
|
trigger: React.ReactNode
|
||||||
|
popupLeft?: number
|
||||||
|
} & CreateContentProps
|
||||||
|
|
||||||
|
const CreateMetadataModal: FC<Props> = ({
|
||||||
|
trigger,
|
||||||
|
popupLeft = 20,
|
||||||
|
...createContentProps
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
placement='left-start'
|
||||||
|
offset={{
|
||||||
|
mainAxis: popupLeft,
|
||||||
|
crossAxis: -38,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
>
|
||||||
|
{trigger}
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[1000]'>
|
||||||
|
<CreateContent {...createContentProps} />
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem >
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(CreateMetadataModal)
|
||||||
228
web/app/components/datasets/metadata/dataset-metadata-drawer.tsx
Normal file
228
web/app/components/datasets/metadata/dataset-metadata-drawer.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
|
import type { MetadataItemWithValueLength } from './types'
|
||||||
|
import Drawer from '../../base/drawer'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||||
|
import { getIcon } from './utils/get-icon'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import Modal from '../../base/modal'
|
||||||
|
import Field from './field'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import produce from 'immer'
|
||||||
|
import Switch from '../../base/switch'
|
||||||
|
import Tooltip from '../../base/tooltip'
|
||||||
|
import CreateModal from '@/app/components/datasets/metadata/create-metadata-modal'
|
||||||
|
import { useBoolean, useHover } from 'ahooks'
|
||||||
|
import Confirm from '@/app/components/base/confirm'
|
||||||
|
|
||||||
|
const i18nPrefix = 'dataset.metadata.datasetMetadata'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
userMetadata: MetadataItemWithValueLength[]
|
||||||
|
builtInMetadata: MetadataItemWithValueLength[]
|
||||||
|
isBuiltInEnabled: boolean
|
||||||
|
onIsBuiltInEnabledChange: (value: boolean) => void
|
||||||
|
onClose: () => void
|
||||||
|
onChange: (data: MetadataItemWithValueLength[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemProps = {
|
||||||
|
readonly?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
payload: MetadataItemWithValueLength
|
||||||
|
onRename?: () => void
|
||||||
|
onDelete?: () => void
|
||||||
|
}
|
||||||
|
const Item: FC<ItemProps> = ({
|
||||||
|
readonly,
|
||||||
|
disabled,
|
||||||
|
payload,
|
||||||
|
onRename,
|
||||||
|
onDelete,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const Icon = getIcon(payload.type)
|
||||||
|
|
||||||
|
const handleRename = useCallback(() => {
|
||||||
|
onRename?.()
|
||||||
|
}, [onRename])
|
||||||
|
|
||||||
|
const deleteBtnRef = useRef<HTMLDivElement>(null)
|
||||||
|
const isDeleteHovering = useHover(deleteBtnRef)
|
||||||
|
const [isShowDeleteConfirm, {
|
||||||
|
setTrue: showDeleteConfirm,
|
||||||
|
setFalse: hideDeleteConfirm,
|
||||||
|
}] = useBoolean(false)
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
hideDeleteConfirm()
|
||||||
|
onDelete?.()
|
||||||
|
}, [hideDeleteConfirm, onDelete])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={payload.id}
|
||||||
|
className={cn(
|
||||||
|
!readonly && !disabled && 'group/item hover:shadow-xs cursor-pointer',
|
||||||
|
'border border-components-panel-border-subtle rounded-md bg-components-panel-on-panel-item-bg',
|
||||||
|
isDeleteHovering && 'border border-state-destructive-border bg-state-destructive-hover',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex items-center h-8 px-2 justify-between',
|
||||||
|
disabled && 'opacity-30', // not include border and bg
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='flex items-center h-full text-text-tertiary space-x-1'>
|
||||||
|
<Icon className='shrink-0 size-4' />
|
||||||
|
<div className='max-w-[250px] truncate system-sm-medium text-text-primary'>{payload.name}</div>
|
||||||
|
<div className='shrink-0 system-xs-regular'>{payload.type}</div>
|
||||||
|
</div>
|
||||||
|
<div className='group-hover/item:hidden ml-2 shrink-0 system-xs-regular text-text-tertiary'>
|
||||||
|
{disabled ? t(`${i18nPrefix}.disabled`) : t(`${i18nPrefix}.values`, { num: payload.valueLength || 0 })}
|
||||||
|
</div>
|
||||||
|
<div className='group-hover/item:flex hidden ml-2 items-center text-text-tertiary space-x-1'>
|
||||||
|
<RiEditLine className='size-4 cursor-pointer' onClick={handleRename} />
|
||||||
|
<div ref={deleteBtnRef} className='hover:text-text-destructive'>
|
||||||
|
<RiDeleteBinLine className='size-4 cursor-pointer' onClick={showDeleteConfirm} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isShowDeleteConfirm && (
|
||||||
|
<Confirm
|
||||||
|
isShow
|
||||||
|
type='warning'
|
||||||
|
title={'Confirm to delete'}
|
||||||
|
content={`Are you sure you want to delete the metadata "${payload.name}"?`}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
onCancel={hideDeleteConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DatasetMetadataDrawer: FC<Props> = ({
|
||||||
|
userMetadata,
|
||||||
|
builtInMetadata,
|
||||||
|
isBuiltInEnabled,
|
||||||
|
onIsBuiltInEnabledChange,
|
||||||
|
onClose,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [isShowRenameModal, setIsShowRenameModal] = useState(false)
|
||||||
|
const [currPayload, setCurrPayload] = useState<MetadataItemWithValueLength | null>(null)
|
||||||
|
const [templeName, setTempleName] = useState('')
|
||||||
|
const handleRename = useCallback((payload: MetadataItemWithValueLength) => {
|
||||||
|
return () => {
|
||||||
|
setCurrPayload(payload)
|
||||||
|
setTempleName(payload.name)
|
||||||
|
setIsShowRenameModal(true)
|
||||||
|
}
|
||||||
|
}, [setCurrPayload, setIsShowRenameModal])
|
||||||
|
|
||||||
|
const handleAdd = useCallback((data: MetadataItemWithValueLength) => {
|
||||||
|
const nextUserMetadata = produce(userMetadata, (draft) => {
|
||||||
|
draft.push(data)
|
||||||
|
})
|
||||||
|
onChange(nextUserMetadata)
|
||||||
|
}, [userMetadata, onChange])
|
||||||
|
|
||||||
|
const handleRenamed = useCallback(() => {
|
||||||
|
const nextUserMetadata = produce(userMetadata, (draft) => {
|
||||||
|
const index = draft.findIndex(p => p.id === currPayload?.id)
|
||||||
|
if (index !== -1)
|
||||||
|
draft[index].name = templeName!
|
||||||
|
})
|
||||||
|
|
||||||
|
onChange(nextUserMetadata)
|
||||||
|
setIsShowRenameModal(false)
|
||||||
|
}, [currPayload, templeName, userMetadata, onChange])
|
||||||
|
|
||||||
|
const handleDelete = useCallback((payload: MetadataItemWithValueLength) => {
|
||||||
|
return () => {
|
||||||
|
const nextUserMetadata = userMetadata.filter(p => p.id !== payload.id)
|
||||||
|
onChange(nextUserMetadata)
|
||||||
|
}
|
||||||
|
}, [userMetadata, onChange])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
showClose
|
||||||
|
title={t('dataset.metadata.metadata')}
|
||||||
|
footer={null}
|
||||||
|
panelClassname='px-4 block !max-w-[420px] my-2 rounded-l-2xl'
|
||||||
|
>
|
||||||
|
<div className='system-sm-regular text-text-tertiary'>{t(`${i18nPrefix}.description`)}</div>
|
||||||
|
|
||||||
|
<CreateModal trigger={<Button variant='primary' className='mt-3'>
|
||||||
|
<RiAddLine className='mr-1' />
|
||||||
|
{t(`${i18nPrefix}.addMetaData`)}
|
||||||
|
</Button>} hasBack onSave={handleAdd} />
|
||||||
|
|
||||||
|
<div className='mt-3 space-y-1'>
|
||||||
|
{userMetadata.map(payload => (
|
||||||
|
<Item
|
||||||
|
key={payload.id}
|
||||||
|
payload={payload}
|
||||||
|
onRename={handleRename(payload)}
|
||||||
|
onDelete={handleDelete(payload)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-3 flex h-6 items-center'>
|
||||||
|
<Switch
|
||||||
|
defaultValue={isBuiltInEnabled}
|
||||||
|
onChange={onIsBuiltInEnabledChange}
|
||||||
|
/>
|
||||||
|
<div className='ml-2 mr-0.5 system-sm-semibold text-text-secondary'>{t(`${i18nPrefix}.builtIn`)}</div>
|
||||||
|
<Tooltip popupContent={<div className='max-w-[100px]'>{t(`${i18nPrefix}.builtInDescription`)}</div>} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-1 space-y-1'>
|
||||||
|
{builtInMetadata.map(payload => (
|
||||||
|
<Item
|
||||||
|
key={payload.id}
|
||||||
|
readonly
|
||||||
|
disabled={!isBuiltInEnabled}
|
||||||
|
payload={payload}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isShowRenameModal && (
|
||||||
|
<Modal isShow title={t(`${i18nPrefix}.rename`)} onClose={() => setIsShowRenameModal(false)}>
|
||||||
|
<Field label={t(`${i18nPrefix}.name`)}>
|
||||||
|
<Input
|
||||||
|
value={templeName}
|
||||||
|
onChange={e => setTempleName(e.target.value)}
|
||||||
|
placeholder={t(`${i18nPrefix}.namePlaceholder`)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<div className='mt-4 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
className='mr-2'
|
||||||
|
onClick={() => {
|
||||||
|
setIsShowRenameModal(false)
|
||||||
|
setTempleName(currPayload!.name)
|
||||||
|
}}>{t('common.operation.cancel')}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleRenamed}
|
||||||
|
variant='primary'
|
||||||
|
disabled={!templeName}
|
||||||
|
>{t('common.operation.save')}</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(DatasetMetadataDrawer)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import type { MetadataItemWithEdit } from '../types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import Label from './label'
|
||||||
|
import InputCombined from './input-combined'
|
||||||
|
import { RiIndeterminateCircleLine } from '@remixicon/react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
payload: MetadataItemWithEdit
|
||||||
|
onChange: (value: any) => void
|
||||||
|
onRemove: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddRow: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
payload,
|
||||||
|
onChange,
|
||||||
|
onRemove,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('flex h-6 items-center space-x-0.5', className)}>
|
||||||
|
<Label text={payload.name} />
|
||||||
|
<InputCombined
|
||||||
|
type={payload.type}
|
||||||
|
value={payload.value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
cn(
|
||||||
|
'p-1 rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive cursor-pointer',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={onRemove}
|
||||||
|
>
|
||||||
|
<RiIndeterminateCircleLine className='size-4' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(AddRow)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { type MetadataItemWithEdit, UpdateType } from '../types'
|
||||||
|
import Label from './label'
|
||||||
|
import { RiDeleteBinLine } from '@remixicon/react'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import InputHasSetMultipleValue from './input-has-set-multiple-value'
|
||||||
|
import InputCombined from './input-combined'
|
||||||
|
import EditedBeacon from './edited-beacon'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
payload: MetadataItemWithEdit
|
||||||
|
onChange: (payload: MetadataItemWithEdit) => void
|
||||||
|
onRemove: (id: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditMetadatabatchItem: FC<Props> = ({
|
||||||
|
payload,
|
||||||
|
onChange,
|
||||||
|
onRemove,
|
||||||
|
}) => {
|
||||||
|
const isUpdated = payload.isUpdated
|
||||||
|
const isDeleted = payload.updateType === UpdateType.delete
|
||||||
|
return (
|
||||||
|
<div className='flex h-6 items-center space-x-0.5'>
|
||||||
|
{isUpdated ? <EditedBeacon onReset={() => { }} /> : <div className='shrink-0 size-4' />}
|
||||||
|
<Label text={payload.name} isDeleted={isDeleted} />
|
||||||
|
{payload.isMultipleValue
|
||||||
|
? <InputHasSetMultipleValue onClear={() => onChange({ ...payload, isMultipleValue: false })} />
|
||||||
|
: <InputCombined
|
||||||
|
type={payload.type}
|
||||||
|
value={payload.value}
|
||||||
|
onChange={v => onChange({ ...payload, value: v as string })
|
||||||
|
} />}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
cn(
|
||||||
|
'p-1 rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive cursor-pointer',
|
||||||
|
isDeleted && 'cursor-default bg-state-destructive-hover text-text-destructive')
|
||||||
|
}
|
||||||
|
onClick={() => onRemove(payload.id)}
|
||||||
|
>
|
||||||
|
<RiDeleteBinLine className='size-4' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(EditMetadatabatchItem)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useRef } from 'react'
|
||||||
|
import { useHover } from 'ahooks'
|
||||||
|
import { RiResetLeftLine } from '@remixicon/react'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onReset: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditedBeacon: FC<Props> = ({
|
||||||
|
onReset,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const ref = useRef(null)
|
||||||
|
const isHovering = useHover(ref)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className='size-4 cursor-pointer'>
|
||||||
|
{isHovering ? (
|
||||||
|
<Tooltip popupContent={t('common.operation.reset')}>
|
||||||
|
<div className='flex justify-center items-center size-4 bg-text-accent-secondary rounded-full' onClick={onReset}>
|
||||||
|
<RiResetLeftLine className='size-[10px] text-text-primary-on-surface' />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<div className='flex items-center justify-center size-4'>
|
||||||
|
<div className='size-1 rounded-full bg-text-accent-secondary'></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(EditedBeacon)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { DataType } from '../types'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import { InputNumber } from '@/app/components/base/input-number'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
type: DataType
|
||||||
|
value: any
|
||||||
|
onChange: (value: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputCombined: FC<Props> = ({
|
||||||
|
className: configClassName,
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
// TODO: configClassName...
|
||||||
|
const className = cn('grow p-0.5 h-6 text-xs')
|
||||||
|
if (type === DataType.time)
|
||||||
|
return <div className='grow text-xs'>Datepicker placeholder</div>
|
||||||
|
|
||||||
|
if (type === DataType.number) {
|
||||||
|
return (
|
||||||
|
<div className='grow text-[0]'>
|
||||||
|
<InputNumber
|
||||||
|
className={cn(className, 'rounded-l-md')}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
size='sm'
|
||||||
|
controlWrapClassName='overflow-hidden'
|
||||||
|
controlClassName='pt-0 pb-0'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
wrapperClassName={configClassName}
|
||||||
|
className={cn(className, 'rounded-md')}
|
||||||
|
value={value}
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
</Input>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(InputCombined)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
'use client'
|
||||||
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClear: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputHasSetMultipleValue: FC<Props> = ({
|
||||||
|
onClear,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className='grow h-6 p-0.5 rounded-md bg-components-input-bg-normal text-[0]'>
|
||||||
|
<div className='inline-flex rounded-[5px] items-center h-5 pl-1.5 pr-0.5 bg-components-badge-white-to-dark border-[0.5px] border-components-panel-border shadow-xs space-x-0.5'>
|
||||||
|
<div className='system-xs-regular text-text-secondary'>{t('dataset.metadata.batchEditMetadata.multipleValue')}</div>
|
||||||
|
<div className='p-0.5rounded-sm text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer'>
|
||||||
|
<RiCloseLine
|
||||||
|
className='size-3.5 '
|
||||||
|
onClick={onClear}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(InputHasSetMultipleValue)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isDeleted?: boolean,
|
||||||
|
className?: string,
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Label: FC<Props> = ({
|
||||||
|
isDeleted,
|
||||||
|
className,
|
||||||
|
text,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'shrink-0 w-[136px] system-xs-medium text-text-tertiary truncate',
|
||||||
|
isDeleted && 'line-through text-text-quaternary',
|
||||||
|
className,
|
||||||
|
)}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Label)
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useState } from 'react'
|
||||||
|
import Modal from '../../../base/modal'
|
||||||
|
import { DataType, type MetadataItemWithEdit } from '../types'
|
||||||
|
import EditMetadataBatchItem from './edit-row'
|
||||||
|
import AddedMetadataItem from './add-row'
|
||||||
|
import Button from '../../../base/button'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Checkbox from '../../../base/checkbox'
|
||||||
|
import Tooltip from '../../../base/tooltip'
|
||||||
|
import SelectMetadataModal from '../select-metadata-modal'
|
||||||
|
import { RiQuestionLine } from '@remixicon/react'
|
||||||
|
import Divider from '@/app/components/base/divider'
|
||||||
|
import AddMetadataButton from '../add-metadata-button'
|
||||||
|
|
||||||
|
const i18nPrefix = 'dataset.metadata.batchEditMetadata'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
documentNum: number
|
||||||
|
list: MetadataItemWithEdit[]
|
||||||
|
onChange: (list: MetadataItemWithEdit[], addedList: MetadataItemWithEdit[], isApplyToAllSelectDocument: boolean) => void
|
||||||
|
onHide: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditMetadataBatchModal: FC<Props> = ({
|
||||||
|
documentNum,
|
||||||
|
list,
|
||||||
|
onChange,
|
||||||
|
onHide,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [templeList, setTempleList] = useState<MetadataItemWithEdit[]>(list)
|
||||||
|
const handleTemplesChange = useCallback((payload: MetadataItemWithEdit) => {
|
||||||
|
const newTempleList = templeList.map(i => i.id === payload.id ? payload : i)
|
||||||
|
setTempleList(newTempleList)
|
||||||
|
}, [templeList])
|
||||||
|
const handleTempleItemRemove = useCallback((id: string) => {
|
||||||
|
const newTempleList = templeList.filter(i => i.id !== id)
|
||||||
|
setTempleList(newTempleList)
|
||||||
|
}, [templeList])
|
||||||
|
|
||||||
|
const testAddedList: MetadataItemWithEdit[] = [
|
||||||
|
{
|
||||||
|
id: '1', name: 'name1', type: DataType.string, value: 'aaa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2.1', name: 'num v', type: DataType.number, value: 10,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const [addedList, setAddedList] = useState<MetadataItemWithEdit[]>(testAddedList)
|
||||||
|
const handleAddedListChange = useCallback((payload: MetadataItemWithEdit) => {
|
||||||
|
const newAddedList = addedList.map(i => i.id === payload.id ? payload : i)
|
||||||
|
setAddedList(newAddedList)
|
||||||
|
}, [addedList])
|
||||||
|
const handleAddedItemRemove = useCallback((removeIndex: number) => {
|
||||||
|
return () => {
|
||||||
|
const newAddedList = addedList.filter((i, index) => index !== removeIndex)
|
||||||
|
setAddedList(newAddedList)
|
||||||
|
}
|
||||||
|
}, [addedList])
|
||||||
|
|
||||||
|
const [isApplyToAllSelectDocument, setIsApplyToAllSelectDocument] = useState(false)
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
onChange(templeList, addedList, isApplyToAllSelectDocument)
|
||||||
|
}, [templeList, addedList, isApplyToAllSelectDocument, onChange])
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t(`${i18nPrefix}.editMetadata`)}
|
||||||
|
isShow
|
||||||
|
closable
|
||||||
|
onClose={onHide}
|
||||||
|
className='!max-w-[640px]'
|
||||||
|
>
|
||||||
|
<div className='system-xs-medium text-text-accent'>{t(`${i18nPrefix}.editDocumentsNum`, { num: documentNum })}</div>
|
||||||
|
{/* TODO handle list scroll. There is two list. */}
|
||||||
|
<div className='mt-4 space-y-2'>
|
||||||
|
{templeList.map(item => (
|
||||||
|
<EditMetadataBatchItem
|
||||||
|
key={item.id}
|
||||||
|
payload={item}
|
||||||
|
onChange={handleTemplesChange}
|
||||||
|
onRemove={handleTempleItemRemove}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='mt-4 pl-[18px]'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='mr-2 shrink-0 system-xs-medium-uppercase text-text-tertiary'>{t('dataset.metadata.createMetadata.title')}</div>
|
||||||
|
<Divider bgStyle='gradient' />
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 space-y-2'>
|
||||||
|
{addedList.map((item, i) => (
|
||||||
|
<AddedMetadataItem
|
||||||
|
key={i}
|
||||||
|
payload={item}
|
||||||
|
onChange={handleAddedListChange}
|
||||||
|
onRemove={handleAddedItemRemove(i)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='mt-3'>
|
||||||
|
<SelectMetadataModal
|
||||||
|
popupPlacement='top-start'
|
||||||
|
popupOffset={{ mainAxis: 4, crossAxis: 0 }}
|
||||||
|
trigger={
|
||||||
|
<AddMetadataButton />
|
||||||
|
}
|
||||||
|
onSave={data => setAddedList([...addedList, data])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4 flex items-center justify-between'>
|
||||||
|
<div className='flex items-center select-none'>
|
||||||
|
<Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} />
|
||||||
|
<div className='ml-2 mr-1 system-xs-medium text-text-secondary'>{t(`${i18nPrefix}.applyToAllSelectDocument`)}</div>
|
||||||
|
<Tooltip popupContent={
|
||||||
|
<div className='max-w-[240px]'>{t(`${i18nPrefix}.applyToAllSelectDocumentTip`)}</div>
|
||||||
|
} >
|
||||||
|
<div className='p-px cursor-pointer'>
|
||||||
|
<RiQuestionLine className='size-3.5 text-text-tertiary' />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
<Button
|
||||||
|
onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
variant='primary'
|
||||||
|
>{t('common.operation.save')}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(EditMetadataBatchModal)
|
||||||
23
web/app/components/datasets/metadata/field.tsx
Normal file
23
web/app/components/datasets/metadata/field.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
label: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const Field: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className='py-1 system-sm-semibold text-text-secondary'>{label}</div>
|
||||||
|
<div className='mt-1'>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Field)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const Field: FC<Props> = ({
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className='flex items-start space-x-2'>
|
||||||
|
<div className='shrink-0 w-[128px] truncate py-1 items-center text-text-tertiary system-xs-medium'>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
<div className='shrink-0 w-[244px]'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(Field)
|
||||||
127
web/app/components/datasets/metadata/metadata-document/index.tsx
Normal file
127
web/app/components/datasets/metadata/metadata-document/index.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { DataType, type MetadataItemWithValue } from '../types'
|
||||||
|
import InfoGroup from './info-group'
|
||||||
|
import NoData from './no-data'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { RiEditLine } from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Divider from '@/app/components/base/divider'
|
||||||
|
|
||||||
|
const i18nPrefix = 'dataset.metadata.documentMetadata'
|
||||||
|
|
||||||
|
const MetadataDocument: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [isEdit, setIsEdit] = useState(true)
|
||||||
|
|
||||||
|
const [list, setList] = useState<MetadataItemWithValue[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Doc type',
|
||||||
|
value: 'PDF',
|
||||||
|
type: DataType.string,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Title',
|
||||||
|
value: 'PDF',
|
||||||
|
type: DataType.string,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
const [tempList, setTempList] = useState<MetadataItemWithValue[]>(list)
|
||||||
|
const builtInEnabled = true
|
||||||
|
const builtList = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'OriginalfileNmae',
|
||||||
|
value: 'Steve Jobs The Man Who Thought Different.pdf',
|
||||||
|
type: DataType.string,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Title',
|
||||||
|
value: 'PDF',
|
||||||
|
type: DataType.string,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const hasData = list.length > 0
|
||||||
|
|
||||||
|
const documentInfoList = builtList
|
||||||
|
const technicalParams = builtList
|
||||||
|
return (
|
||||||
|
<div className='w-[388px] space-y-4'>
|
||||||
|
{!hasData ? (
|
||||||
|
<div className='pl-2'>
|
||||||
|
<InfoGroup
|
||||||
|
title={t('dataset.metadata.metadata')}
|
||||||
|
uppercaseTitle={false}
|
||||||
|
titleTooltip={t(`${i18nPrefix}.metadataToolTip`)}
|
||||||
|
list={isEdit ? tempList : list}
|
||||||
|
headerRight={isEdit ? (
|
||||||
|
<div className='flex space-x-1'>
|
||||||
|
<Button variant='ghost' size='small' onClick={() => {
|
||||||
|
setTempList(list)
|
||||||
|
setIsEdit(false)
|
||||||
|
}}>
|
||||||
|
<div>{t('common.operation.cancel')}</div>
|
||||||
|
</Button>
|
||||||
|
<Button variant='primary' size='small' onClick={() => {
|
||||||
|
setIsEdit(false)
|
||||||
|
setList(tempList)
|
||||||
|
}}>
|
||||||
|
<div>{t('common.operation.save')}</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button variant='ghost' size='small' onClick={() => {
|
||||||
|
setTempList(list)
|
||||||
|
setIsEdit(true)
|
||||||
|
}}>
|
||||||
|
<RiEditLine className='mr-1 size-3.5 text-text-tertiary cursor-pointer' />
|
||||||
|
<div>{t('common.operation.edit')}</div>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
isEdit={isEdit}
|
||||||
|
contentClassName='mt-5'
|
||||||
|
onChange={(item) => {
|
||||||
|
const newList = tempList.map(i => (i.name === item.name ? item : i))
|
||||||
|
setList(newList)
|
||||||
|
}}
|
||||||
|
onDelete={(item) => {
|
||||||
|
const newList = tempList.filter(i => i.name !== item.name)
|
||||||
|
setList(newList)
|
||||||
|
}}
|
||||||
|
onAdd={() => {
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{builtInEnabled && (
|
||||||
|
<>
|
||||||
|
<Divider className='my-3' bgStyle='gradient' />
|
||||||
|
<InfoGroup
|
||||||
|
noHeader
|
||||||
|
titleTooltip='Built-in metadata is system-generated metadata that is automatically added to the document. You can enable or disable built-in metadata here.'
|
||||||
|
list={builtList}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<NoData onStart={() => { }} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<InfoGroup
|
||||||
|
className='pl-2'
|
||||||
|
title={t(`${i18nPrefix}.documentInformation`)}
|
||||||
|
list={documentInfoList}
|
||||||
|
/>
|
||||||
|
<InfoGroup
|
||||||
|
className='pl-2'
|
||||||
|
title={t(`${i18nPrefix}.technicalParameters`)}
|
||||||
|
list={technicalParams}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(MetadataDocument)
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import type { MetadataItemWithValue } from '../types'
|
||||||
|
import Field from './field'
|
||||||
|
import InputCombined from '../edit-metadata-batch/input-combined'
|
||||||
|
import { RiDeleteBinLine, RiQuestionLine } from '@remixicon/react'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import Divider from '@/app/components/base/divider'
|
||||||
|
import SelectMetadataModal from '../select-metadata-modal'
|
||||||
|
import AddMetadataButton from '../add-metadata-button'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
noHeader?: boolean
|
||||||
|
title?: string
|
||||||
|
uppercaseTitle?: boolean
|
||||||
|
titleTooltip?: string
|
||||||
|
headerRight?: React.ReactNode
|
||||||
|
contentClassName?: string
|
||||||
|
list: MetadataItemWithValue[]
|
||||||
|
isEdit?: boolean
|
||||||
|
onChange?: (item: MetadataItemWithValue) => void
|
||||||
|
onDelete?: (item: MetadataItemWithValue) => void
|
||||||
|
onAdd?: (item: MetadataItemWithValue) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const InfoGroup: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
noHeader,
|
||||||
|
title,
|
||||||
|
uppercaseTitle = true,
|
||||||
|
titleTooltip,
|
||||||
|
headerRight,
|
||||||
|
contentClassName,
|
||||||
|
list,
|
||||||
|
isEdit,
|
||||||
|
onChange,
|
||||||
|
onDelete,
|
||||||
|
onAdd,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('bg-white', className)}>
|
||||||
|
{!noHeader && (
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='flex items-center space-x-1'>
|
||||||
|
<div className={cn('text-text-secondary', uppercaseTitle ? 'system-xs-semibold-uppercase' : 'system-md-semibold')}>{title}</div>
|
||||||
|
{titleTooltip && (
|
||||||
|
<Tooltip popupContent={<div className='max-w-[240px]'>{titleTooltip}</div>}>
|
||||||
|
<RiQuestionLine className='size-3.5 text-text-tertiary' />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{headerRight}
|
||||||
|
{/* <div className='flex px-1.5 rounded-md hover:bg-components-button-tertiary-bg-hover items-center h-6 space-x-1 cursor-pointer' onClick={() => setIsEdit(true)}>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={cn('mt-3 space-y-1', !noHeader && 'mt-0', contentClassName)}>
|
||||||
|
{isEdit && (
|
||||||
|
<div>
|
||||||
|
<SelectMetadataModal
|
||||||
|
trigger={
|
||||||
|
<AddMetadataButton />
|
||||||
|
}
|
||||||
|
onSave={() => { }}
|
||||||
|
/>
|
||||||
|
<Divider className='my-3 ' bgStyle='gradient' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{list.map((item, i) => (
|
||||||
|
<Field key={item.id || `${i}`} label={item.name}>
|
||||||
|
{isEdit ? (
|
||||||
|
<div className='flex items-center space-x-0.5'>
|
||||||
|
<InputCombined
|
||||||
|
className='h-6'
|
||||||
|
type={item.type}
|
||||||
|
value={item.value}
|
||||||
|
onChange={value => onChange?.({ ...item, value })}
|
||||||
|
/>
|
||||||
|
<div className='shrink-0 p-1 rounded-md text-text-tertiary hover:text-text-destructive hover:bg-state-destructive-hover cursor-pointer'>
|
||||||
|
<RiDeleteBinLine className='size-4' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (<div className='py-1 system-xs-regular text-text-secondary'>{item.value}</div>)}
|
||||||
|
</Field>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(InfoGroup)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
'use client'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { RiArrowRightLine } from '@remixicon/react'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onStart: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoData: FC<Props> = ({
|
||||||
|
onStart,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className='p-4 pt-3 rounded-xl bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2'>
|
||||||
|
<div className='text-text-secondary text-xs font-semibold leading-5'>{t('dataset.metadata.metadata')}</div>
|
||||||
|
<div className='mt-1 system-xs-regular text-text-tertiary'>{t('dataset.metadata.documentMetadata.metadataToolTip')}</div>
|
||||||
|
<Button variant='primary' className='mt-2' onClick={onStart}>
|
||||||
|
<div>{t('dataset.metadata.documentMetadata.startLabeling')}</div>
|
||||||
|
<RiArrowRightLine className='ml-1 size-4' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(NoData)
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback, useState } from 'react'
|
||||||
|
import type { Props as CreateContentProps } from './create-content'
|
||||||
|
import CreateContent from './create-content'
|
||||||
|
import SelectMetadata from './select-metadata'
|
||||||
|
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem'
|
||||||
|
import type { MetadataItem } from './types'
|
||||||
|
import type { Placement } from '@floating-ui/react'
|
||||||
|
import { DataType } from './types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
popupPlacement?: Placement
|
||||||
|
popupOffset?: { mainAxis: number, crossAxis: number }
|
||||||
|
onSave: (data: any) => void
|
||||||
|
trigger: React.ReactNode
|
||||||
|
} & CreateContentProps
|
||||||
|
|
||||||
|
enum Step {
|
||||||
|
select = 'select',
|
||||||
|
create = 'create',
|
||||||
|
}
|
||||||
|
|
||||||
|
const testMetadataList: MetadataItem[] = [
|
||||||
|
{ id: '1', name: 'name1', type: DataType.string },
|
||||||
|
{ id: '2', name: 'name2', type: DataType.number },
|
||||||
|
{ id: '3', name: 'name3', type: DataType.time },
|
||||||
|
]
|
||||||
|
|
||||||
|
const SelectMetadataModal: FC<Props> = ({
|
||||||
|
popupPlacement = 'left-start',
|
||||||
|
popupOffset = { mainAxis: -38, crossAxis: 4 },
|
||||||
|
trigger,
|
||||||
|
onSave,
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [step, setStep] = useState(Step.select)
|
||||||
|
|
||||||
|
const handleSave = useCallback((data: MetadataItem) => {
|
||||||
|
onSave(data)
|
||||||
|
setOpen(false)
|
||||||
|
}, [onSave])
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
placement={popupPlacement}
|
||||||
|
offset={popupOffset}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
className='block'
|
||||||
|
>
|
||||||
|
{trigger}
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
<PortalToFollowElemContent className='z-[1000]'>
|
||||||
|
{step === Step.select ? (
|
||||||
|
<SelectMetadata
|
||||||
|
onSelect={handleSave}
|
||||||
|
list={testMetadataList}
|
||||||
|
onNew={() => setStep(Step.create)}
|
||||||
|
onManage={() => { }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CreateContent
|
||||||
|
onSave={handleSave}
|
||||||
|
hasBack
|
||||||
|
onBack={() => setStep(Step.select)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem >
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(SelectMetadataModal)
|
||||||
72
web/app/components/datasets/metadata/select-metadata.tsx
Normal file
72
web/app/components/datasets/metadata/select-metadata.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import type { MetadataItem } from './types'
|
||||||
|
import SearchInput from '../../base/search-input'
|
||||||
|
import { RiAddLine, RiArrowRightUpLine } from '@remixicon/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { getIcon } from './utils/get-icon'
|
||||||
|
|
||||||
|
const i18nPrefix = 'dataset.metadata.selectMetadata'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
list: MetadataItem[]
|
||||||
|
onSelect: (data: any) => void
|
||||||
|
onNew: () => void
|
||||||
|
onManage: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectMetadata: FC<Props> = ({
|
||||||
|
list,
|
||||||
|
onSelect,
|
||||||
|
onNew,
|
||||||
|
onManage,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const [query, setQuery] = useState('')
|
||||||
|
return (
|
||||||
|
<div className='w-[320px] pt-2 pb-0 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg backdrop-blur-[5px]'>
|
||||||
|
<SearchInput
|
||||||
|
className='mx-2'
|
||||||
|
value={query}
|
||||||
|
onChange={setQuery}
|
||||||
|
placeholder={t(`${i18nPrefix}.search`)}
|
||||||
|
/>
|
||||||
|
<div className='mt-2'>
|
||||||
|
{list.map((item) => {
|
||||||
|
const Icon = getIcon(item.type)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className='mx-1 flex items-center h-6 px-3 justify-between rounded-md hover:bg-state-base-hover cursor-pointer'
|
||||||
|
onClick={() => onSelect(item)}
|
||||||
|
>
|
||||||
|
<div className='w-0 grow flex items-center h-full text-text-secondary'>
|
||||||
|
<Icon className='shrink-0 mr-[5px] size-3.5' />
|
||||||
|
<div className='w-0 grow truncate system-sm-medium'>{item.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className='ml-1 shrink-0 system-xs-regular text-text-tertiary'>
|
||||||
|
{item.type}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className='mt-1 flex justify-between p-1 border-t border-divider-subtle'>
|
||||||
|
<div className='flex items-center h-6 px-3 text-text-secondary rounded-md hover:bg-state-base-hover cursor-pointer space-x-1' onClick={onNew}>
|
||||||
|
<RiAddLine className='size-3.5' />
|
||||||
|
<div className='system-sm-medium'>{t(`${i18nPrefix}.newAction`)}</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center h-6 text-text-secondary '>
|
||||||
|
<div className='mr-[3px] w-px h-3 bg-divider-regular'></div>
|
||||||
|
<div className='flex h-full items-center px-1.5 hover:bg-state-base-hover rounded-md cursor-pointer' onClick={onManage}>
|
||||||
|
<div className='mr-1 system-sm-medium'>{t(`${i18nPrefix}.manageAction`)}</div>
|
||||||
|
<RiArrowRightUpLine className='size-3.5' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(SelectMetadata)
|
||||||
28
web/app/components/datasets/metadata/types.ts
Normal file
28
web/app/components/datasets/metadata/types.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
export enum DataType {
|
||||||
|
string = 'string',
|
||||||
|
number = 'number',
|
||||||
|
time = 'time',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MetadataItem = {
|
||||||
|
id: string
|
||||||
|
type: DataType
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MetadataItemWithValue = MetadataItem & {
|
||||||
|
value: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MetadataItemWithValueLength = MetadataItem & {
|
||||||
|
valueLength: number
|
||||||
|
}
|
||||||
|
export enum UpdateType {
|
||||||
|
changeValue = 'changeValue',
|
||||||
|
delete = 'delete',
|
||||||
|
}
|
||||||
|
export type MetadataItemWithEdit = MetadataItemWithValue & {
|
||||||
|
isMultipleValue?: boolean
|
||||||
|
isUpdated?: boolean
|
||||||
|
updateType?: UpdateType
|
||||||
|
}
|
||||||
10
web/app/components/datasets/metadata/utils/get-icon.ts
Normal file
10
web/app/components/datasets/metadata/utils/get-icon.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { DataType } from '../types'
|
||||||
|
import { RiHashtag, RiTextSnippet, RiTimeLine } from '@remixicon/react'
|
||||||
|
|
||||||
|
export const getIcon = (type: DataType) => {
|
||||||
|
return ({
|
||||||
|
[DataType.string]: RiTextSnippet,
|
||||||
|
[DataType.number]: RiHashtag,
|
||||||
|
[DataType.time]: RiTimeLine,
|
||||||
|
}[type] || RiTextSnippet)
|
||||||
|
}
|
||||||
@@ -22,6 +22,10 @@ import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-sele
|
|||||||
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
|
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
|
||||||
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
||||||
import RadioE from '@/app/components/base/radio/ui'
|
import RadioE from '@/app/components/base/radio/ui'
|
||||||
|
import type {
|
||||||
|
NodeOutPutVar,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import type { Node } from 'reactflow'
|
||||||
|
|
||||||
type FormProps<
|
type FormProps<
|
||||||
CustomFormSchema extends Omit<CredentialFormSchema, 'type'> & { type: string } = never,
|
CustomFormSchema extends Omit<CredentialFormSchema, 'type'> & { type: string } = never,
|
||||||
@@ -47,6 +51,9 @@ type FormProps<
|
|||||||
) => ReactNode
|
) => ReactNode
|
||||||
// If return falsy value, this field will fallback to default render
|
// If return falsy value, this field will fallback to default render
|
||||||
override?: [Array<FormTypeEnum>, (formSchema: CredentialFormSchema, props: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'>) => ReactNode]
|
override?: [Array<FormTypeEnum>, (formSchema: CredentialFormSchema, props: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'>) => ReactNode]
|
||||||
|
nodeId?: string
|
||||||
|
nodeOutputVars?: NodeOutPutVar[],
|
||||||
|
availableNodes?: Node[],
|
||||||
}
|
}
|
||||||
|
|
||||||
function Form<
|
function Form<
|
||||||
@@ -69,6 +76,9 @@ function Form<
|
|||||||
fieldMoreInfo,
|
fieldMoreInfo,
|
||||||
customRenderField,
|
customRenderField,
|
||||||
override,
|
override,
|
||||||
|
nodeId,
|
||||||
|
nodeOutputVars,
|
||||||
|
availableNodes,
|
||||||
}: FormProps<CustomFormSchema>) {
|
}: FormProps<CustomFormSchema>) {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const [changeKey, setChangeKey] = useState('')
|
const [changeKey, setChangeKey] = useState('')
|
||||||
@@ -326,6 +336,9 @@ function Form<
|
|||||||
</div>
|
</div>
|
||||||
<ToolSelector
|
<ToolSelector
|
||||||
scope={scope}
|
scope={scope}
|
||||||
|
nodeId={nodeId}
|
||||||
|
nodeOutputVars={nodeOutputVars || []}
|
||||||
|
availableNodes={availableNodes || []}
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
value={value[variable]}
|
value={value[variable]}
|
||||||
// selectedTools={value[variable] ? [value[variable]] : []}
|
// selectedTools={value[variable] ? [value[variable]] : []}
|
||||||
@@ -351,6 +364,9 @@ function Form<
|
|||||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||||
<MultipleToolSelector
|
<MultipleToolSelector
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
|
nodeId={nodeId}
|
||||||
|
nodeOutputVars={nodeOutputVars || []}
|
||||||
|
availableNodes={availableNodes || []}
|
||||||
scope={scope}
|
scope={scope}
|
||||||
label={label[language] || label.en_US}
|
label={label[language] || label.en_US}
|
||||||
required={required}
|
required={required}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useUpdateModelProviders } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||||
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools'
|
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools'
|
||||||
@@ -8,7 +9,9 @@ import { PluginType } from '../../types'
|
|||||||
|
|
||||||
const useRefreshPluginList = () => {
|
const useRefreshPluginList = () => {
|
||||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||||
const updateModelProviders = useUpdateModelProviders()
|
const { mutate: refetchLLMModelList } = useModelList(ModelTypeEnum.textGeneration)
|
||||||
|
const { mutate: refetchEmbeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||||
|
const { mutate: refetchRerankModelList } = useModelList(ModelTypeEnum.rerank)
|
||||||
const { refreshModelProviders } = useProviderContext()
|
const { refreshModelProviders } = useProviderContext()
|
||||||
|
|
||||||
const invalidateAllToolProviders = useInvalidateAllToolProviders()
|
const invalidateAllToolProviders = useInvalidateAllToolProviders()
|
||||||
@@ -31,8 +34,10 @@ const useRefreshPluginList = () => {
|
|||||||
|
|
||||||
// model select
|
// model select
|
||||||
if (PluginType.model.includes(manifest.category) || refreshAllType) {
|
if (PluginType.model.includes(manifest.category) || refreshAllType) {
|
||||||
updateModelProviders()
|
|
||||||
refreshModelProviders()
|
refreshModelProviders()
|
||||||
|
refetchLLMModelList()
|
||||||
|
refetchEmbeddingModelList()
|
||||||
|
refetchRerankModelList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// agent select
|
// agent select
|
||||||
|
|||||||
@@ -87,10 +87,13 @@ const InstallByDSLList: FC<Props> = ({
|
|||||||
const failedIndex: number[] = []
|
const failedIndex: number[] = []
|
||||||
const nextPlugins = produce(pluginsRef.current, (draft) => {
|
const nextPlugins = produce(pluginsRef.current, (draft) => {
|
||||||
marketPlaceInDSLIndex.forEach((index, i) => {
|
marketPlaceInDSLIndex.forEach((index, i) => {
|
||||||
if (payloads[i])
|
if (payloads[i]) {
|
||||||
draft[index] = payloads[i]
|
draft[index] = {
|
||||||
else
|
...payloads[i],
|
||||||
failedIndex.push(index)
|
version: payloads[i].version || payloads[i].latest_version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { failedIndex.push(index) }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
setPlugins(nextPlugins)
|
setPlugins(nextPlugins)
|
||||||
@@ -192,8 +195,8 @@ const InstallByDSLList: FC<Props> = ({
|
|||||||
key={index}
|
key={index}
|
||||||
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
||||||
onCheckedChange={handleSelect(index)}
|
onCheckedChange={handleSelect(index)}
|
||||||
payload={plugins[index] as Plugin}
|
payload={plugin}
|
||||||
version={(d as GitHubItemAndMarketPlaceDependency).value.version!}
|
version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''}
|
||||||
versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
|
versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -136,9 +136,10 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
|||||||
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.uploadFailed }))
|
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.uploadFailed }))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleInstalled = useCallback(() => {
|
const handleInstalled = useCallback((notRefresh?: boolean) => {
|
||||||
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed }))
|
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed }))
|
||||||
refreshPluginList(manifest)
|
if (!notRefresh)
|
||||||
|
refreshPluginList(manifest)
|
||||||
setIsInstalling(false)
|
setIsInstalling(false)
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}, [manifest, onSuccess, refreshPluginList, setIsInstalling])
|
}, [manifest, onSuccess, refreshPluginList, setIsInstalling])
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type LoadedProps = {
|
|||||||
selectedPackage: string
|
selectedPackage: string
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
onStartToInstall?: () => void
|
onStartToInstall?: () => void
|
||||||
onInstalled: () => void
|
onInstalled: (notRefresh?: boolean) => void
|
||||||
onFailed: (message?: string) => void
|
onFailed: (message?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ const Loaded: React.FC<LoadedProps> = ({
|
|||||||
|
|
||||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||||
const { mutateAsync: installPackageFromGitHub } = useInstallPackageFromGitHub()
|
const { mutateAsync: installPackageFromGitHub } = useInstallPackageFromGitHub()
|
||||||
const { handleRefetch } = usePluginTaskList()
|
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||||
const { check } = checkTaskStatus()
|
const { check } = checkTaskStatus()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -127,7 +127,7 @@ const Loaded: React.FC<LoadedProps> = ({
|
|||||||
onFailed(error)
|
onFailed(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onInstalled()
|
onInstalled(true)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (typeof e === 'string') {
|
if (typeof e === 'string') {
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ const ReadyToInstall: FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { refreshPluginList } = useRefreshPluginList()
|
const { refreshPluginList } = useRefreshPluginList()
|
||||||
|
|
||||||
const handleInstalled = useCallback(() => {
|
const handleInstalled = useCallback((notRefresh?: boolean) => {
|
||||||
onStepChange(InstallStep.installed)
|
onStepChange(InstallStep.installed)
|
||||||
refreshPluginList(manifest)
|
if (!notRefresh)
|
||||||
|
refreshPluginList(manifest)
|
||||||
setIsInstalling(false)
|
setIsInstalling(false)
|
||||||
}, [manifest, onStepChange, refreshPluginList, setIsInstalling])
|
}, [manifest, onStepChange, refreshPluginList, setIsInstalling])
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type Props = {
|
|||||||
payload: PluginDeclaration
|
payload: PluginDeclaration
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
onStartToInstall?: () => void
|
onStartToInstall?: () => void
|
||||||
onInstalled: () => void
|
onInstalled: (notRefresh?: boolean) => void
|
||||||
onFailed: (message?: string) => void
|
onFailed: (message?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ const Installed: FC<Props> = ({
|
|||||||
onCancel()
|
onCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { handleRefetch } = usePluginTaskList()
|
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||||
const handleInstall = async () => {
|
const handleInstall = async () => {
|
||||||
if (isInstalling) return
|
if (isInstalling) return
|
||||||
setIsInstalling(true)
|
setIsInstalling(true)
|
||||||
@@ -92,7 +92,7 @@ const Installed: FC<Props> = ({
|
|||||||
onFailed(error)
|
onFailed(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onInstalled()
|
onInstalled(true)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (typeof e === 'string') {
|
if (typeof e === 'string') {
|
||||||
|
|||||||
@@ -54,9 +54,10 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
|||||||
return t(`${i18nPrefix}.installPlugin`)
|
return t(`${i18nPrefix}.installPlugin`)
|
||||||
}, [isBundle, step, t])
|
}, [isBundle, step, t])
|
||||||
|
|
||||||
const handleInstalled = useCallback(() => {
|
const handleInstalled = useCallback((notRefresh?: boolean) => {
|
||||||
setStep(InstallStep.installed)
|
setStep(InstallStep.installed)
|
||||||
refreshPluginList(manifest)
|
if (!notRefresh)
|
||||||
|
refreshPluginList(manifest)
|
||||||
setIsInstalling(false)
|
setIsInstalling(false)
|
||||||
}, [manifest, refreshPluginList, setIsInstalling])
|
}, [manifest, refreshPluginList, setIsInstalling])
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type Props = {
|
|||||||
payload: PluginManifestInMarket | Plugin
|
payload: PluginManifestInMarket | Plugin
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
onStartToInstall?: () => void
|
onStartToInstall?: () => void
|
||||||
onInstalled: () => void
|
onInstalled: (notRefresh?: boolean) => void
|
||||||
onFailed: (message?: string) => void
|
onFailed: (message?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ const Installed: FC<Props> = ({
|
|||||||
check,
|
check,
|
||||||
stop,
|
stop,
|
||||||
} = checkTaskStatus()
|
} = checkTaskStatus()
|
||||||
const { handleRefetch } = usePluginTaskList()
|
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier)
|
if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier)
|
||||||
@@ -106,7 +106,7 @@ const Installed: FC<Props> = ({
|
|||||||
onFailed(error)
|
onFailed(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onInstalled()
|
onInstalled(true)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (typeof e === 'string') {
|
if (typeof e === 'string') {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ const DetailHeader = ({
|
|||||||
},
|
},
|
||||||
payload: {
|
payload: {
|
||||||
type: PluginSource.github,
|
type: PluginSource.github,
|
||||||
|
category: detail.declaration.category,
|
||||||
github: {
|
github: {
|
||||||
originalPackageInfo: {
|
originalPackageInfo: {
|
||||||
id: detail.plugin_unique_identifier,
|
id: detail.plugin_unique_identifier,
|
||||||
@@ -287,6 +288,7 @@ const DetailHeader = ({
|
|||||||
isShowUpdateModal && (
|
isShowUpdateModal && (
|
||||||
<UpdateFromMarketplace
|
<UpdateFromMarketplace
|
||||||
payload={{
|
payload={{
|
||||||
|
category: detail.declaration.category,
|
||||||
originalPackageInfo: {
|
originalPackageInfo: {
|
||||||
id: detail.plugin_unique_identifier,
|
id: detail.plugin_unique_identifier,
|
||||||
payload: detail.declaration,
|
payload: detail.declaration,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import ActionButton from '@/app/components/base/action-button'
|
|||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
|
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
|
||||||
|
import type { Node } from 'reactflow'
|
||||||
|
import type { NodeOutPutVar } from '@/app/components/workflow/types'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -21,6 +23,9 @@ type Props = {
|
|||||||
supportCollapse?: boolean
|
supportCollapse?: boolean
|
||||||
scope?: string
|
scope?: string
|
||||||
onChange: (value: ToolValue[]) => void
|
onChange: (value: ToolValue[]) => void
|
||||||
|
nodeOutputVars: NodeOutPutVar[],
|
||||||
|
availableNodes: Node[],
|
||||||
|
nodeId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const MultipleToolSelector = ({
|
const MultipleToolSelector = ({
|
||||||
@@ -32,6 +37,9 @@ const MultipleToolSelector = ({
|
|||||||
supportCollapse,
|
supportCollapse,
|
||||||
scope,
|
scope,
|
||||||
onChange,
|
onChange,
|
||||||
|
nodeOutputVars,
|
||||||
|
availableNodes,
|
||||||
|
nodeId,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const enabledCount = value.filter(item => item.enabled).length
|
const enabledCount = value.filter(item => item.enabled).length
|
||||||
@@ -121,6 +129,9 @@ const MultipleToolSelector = ({
|
|||||||
{!collapse && (
|
{!collapse && (
|
||||||
<>
|
<>
|
||||||
<ToolSelector
|
<ToolSelector
|
||||||
|
nodeId={nodeId}
|
||||||
|
nodeOutputVars={nodeOutputVars}
|
||||||
|
availableNodes={availableNodes}
|
||||||
scope={scope}
|
scope={scope}
|
||||||
value={undefined}
|
value={undefined}
|
||||||
selectedTools={value}
|
selectedTools={value}
|
||||||
@@ -140,6 +151,9 @@ const MultipleToolSelector = ({
|
|||||||
{value.length > 0 && value.map((item, index) => (
|
{value.length > 0 && value.map((item, index) => (
|
||||||
<div className='mb-1' key={index}>
|
<div className='mb-1' key={index}>
|
||||||
<ToolSelector
|
<ToolSelector
|
||||||
|
nodeId={nodeId}
|
||||||
|
nodeOutputVars={nodeOutputVars}
|
||||||
|
availableNodes={availableNodes}
|
||||||
scope={scope}
|
scope={scope}
|
||||||
value={item}
|
value={item}
|
||||||
selectedTools={value}
|
selectedTools={value}
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import ToolCredentialForm from '@/app/components/plugins/plugin-detail-panel/too
|
|||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
|
import TabSlider from '@/app/components/base/tab-slider-plain'
|
||||||
|
import ReasoningConfigForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form'
|
||||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
||||||
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||||
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import {
|
import {
|
||||||
@@ -41,6 +43,8 @@ import type {
|
|||||||
Placement,
|
Placement,
|
||||||
} from '@floating-ui/react'
|
} from '@floating-ui/react'
|
||||||
import { MARKETPLACE_API_PREFIX } from '@/config'
|
import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||||
|
import type { Node } from 'reactflow'
|
||||||
|
import type { NodeOutPutVar } from '@/app/components/workflow/types'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -54,6 +58,7 @@ type Props = {
|
|||||||
provider_name: string
|
provider_name: string
|
||||||
tool_name: string
|
tool_name: string
|
||||||
tool_label: string
|
tool_label: string
|
||||||
|
settings?: Record<string, any>
|
||||||
parameters?: Record<string, any>
|
parameters?: Record<string, any>
|
||||||
extra?: Record<string, any>
|
extra?: Record<string, any>
|
||||||
}) => void
|
}) => void
|
||||||
@@ -65,6 +70,9 @@ type Props = {
|
|||||||
onControlledStateChange?: (state: boolean) => void
|
onControlledStateChange?: (state: boolean) => void
|
||||||
panelShowState?: boolean
|
panelShowState?: boolean
|
||||||
onPanelShowStateChange?: (state: boolean) => void
|
onPanelShowStateChange?: (state: boolean) => void
|
||||||
|
nodeOutputVars: NodeOutPutVar[],
|
||||||
|
availableNodes: Node[],
|
||||||
|
nodeId?: string,
|
||||||
}
|
}
|
||||||
const ToolSelector: FC<Props> = ({
|
const ToolSelector: FC<Props> = ({
|
||||||
value,
|
value,
|
||||||
@@ -81,6 +89,9 @@ const ToolSelector: FC<Props> = ({
|
|||||||
onControlledStateChange,
|
onControlledStateChange,
|
||||||
panelShowState,
|
panelShowState,
|
||||||
onPanelShowStateChange,
|
onPanelShowStateChange,
|
||||||
|
nodeOutputVars,
|
||||||
|
availableNodes,
|
||||||
|
nodeId = '',
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isShow, onShowChange] = useState(false)
|
const [isShow, onShowChange] = useState(false)
|
||||||
@@ -107,17 +118,20 @@ const ToolSelector: FC<Props> = ({
|
|||||||
|
|
||||||
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
|
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
|
||||||
const handleSelectTool = (tool: ToolDefaultValue) => {
|
const handleSelectTool = (tool: ToolDefaultValue) => {
|
||||||
const paramValues = addDefaultValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any))
|
const settingValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any))
|
||||||
|
const paramValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form === 'llm') as any), true)
|
||||||
const toolValue = {
|
const toolValue = {
|
||||||
provider_name: tool.provider_id,
|
provider_name: tool.provider_id,
|
||||||
type: tool.provider_type,
|
type: tool.provider_type,
|
||||||
tool_name: tool.tool_name,
|
tool_name: tool.tool_name,
|
||||||
tool_label: tool.tool_label,
|
tool_label: tool.tool_label,
|
||||||
|
settings: settingValues,
|
||||||
parameters: paramValues,
|
parameters: paramValues,
|
||||||
enabled: tool.is_team_authorization,
|
enabled: tool.is_team_authorization,
|
||||||
extra: {
|
extra: {
|
||||||
description: '',
|
description: '',
|
||||||
},
|
},
|
||||||
|
schemas: tool.paramSchemas,
|
||||||
}
|
}
|
||||||
onSelect(toolValue)
|
onSelect(toolValue)
|
||||||
// setIsShowChooseTool(false)
|
// setIsShowChooseTool(false)
|
||||||
@@ -133,14 +147,33 @@ const ToolSelector: FC<Props> = ({
|
|||||||
} as any)
|
} as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentToolParams = useMemo(() => {
|
// tool settings & params
|
||||||
|
const currentToolSettings = useMemo(() => {
|
||||||
if (!currentProvider) return []
|
if (!currentProvider) return []
|
||||||
return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form !== 'llm') || []
|
return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form !== 'llm') || []
|
||||||
}, [currentProvider, value])
|
}, [currentProvider, value])
|
||||||
|
const currentToolParams = useMemo(() => {
|
||||||
|
if (!currentProvider) return []
|
||||||
|
return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form === 'llm') || []
|
||||||
|
}, [currentProvider, value])
|
||||||
|
const [currType, setCurrType] = useState('settings')
|
||||||
|
const showTabSlider = currentToolSettings.length > 0 && currentToolParams.length > 0
|
||||||
|
const userSettingsOnly = currentToolSettings.length > 0 && !currentToolParams.length
|
||||||
|
const reasoningConfigOnly = currentToolParams.length > 0 && !currentToolSettings.length
|
||||||
|
|
||||||
const formSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams])
|
const settingsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolSettings), [currentToolSettings])
|
||||||
|
const paramsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams])
|
||||||
|
|
||||||
const handleFormChange = (v: Record<string, any>) => {
|
const handleSettingsFormChange = (v: Record<string, any>) => {
|
||||||
|
const newValue = getStructureValue(v)
|
||||||
|
|
||||||
|
const toolValue = {
|
||||||
|
...value,
|
||||||
|
settings: newValue,
|
||||||
|
}
|
||||||
|
onSelect(toolValue as any)
|
||||||
|
}
|
||||||
|
const handleParamsFormChange = (v: Record<string, any>) => {
|
||||||
const toolValue = {
|
const toolValue = {
|
||||||
...value,
|
...value,
|
||||||
parameters: v,
|
parameters: v,
|
||||||
@@ -281,12 +314,9 @@ const ToolSelector: FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
{/* authorization */}
|
{/* authorization */}
|
||||||
{currentProvider && currentProvider.type === CollectionType.builtIn && currentProvider.allow_delete && (
|
{currentProvider && currentProvider.type === CollectionType.builtIn && currentProvider.allow_delete && (
|
||||||
<div className='px-4 pt-3 flex flex-col'>
|
<>
|
||||||
<div className='flex items-center gap-2'>
|
<Divider className='my-1 w-full' />
|
||||||
<div className='shrink-0 text-text-tertiary system-xs-medium-uppercase'>{t('plugin.detailPanel.toolSelector.auth')}</div>
|
<div className='px-4 py-2'>
|
||||||
<Divider bgStyle='gradient' className='grow' />
|
|
||||||
</div>
|
|
||||||
<div className='py-2'>
|
|
||||||
{!currentProvider.is_team_authorization && (
|
{!currentProvider.is_team_authorization && (
|
||||||
<Button
|
<Button
|
||||||
variant='primary'
|
variant='primary'
|
||||||
@@ -309,37 +339,87 @@ const ToolSelector: FC<Props> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* tool settings */}
|
{/* tool settings */}
|
||||||
{currentToolParams.length > 0 && currentProvider?.is_team_authorization && (
|
{(currentToolSettings.length > 0 || currentToolParams.length > 0) && currentProvider?.is_team_authorization && (
|
||||||
<div className='px-4 pt-3'>
|
<>
|
||||||
<div className='flex items-center gap-2'>
|
<Divider className='my-1 w-full' />
|
||||||
<div className='shrink-0 text-text-tertiary system-xs-medium-uppercase'>{t('plugin.detailPanel.toolSelector.settings')}</div>
|
{/* tabs */}
|
||||||
<Divider bgStyle='gradient' className='grow' />
|
{nodeId && showTabSlider && (
|
||||||
</div>
|
<TabSlider
|
||||||
<div className='py-2'>
|
className='shrink-0 mt-1 px-4'
|
||||||
<Form
|
itemClassName='py-3'
|
||||||
value={value?.parameters || {}}
|
noBorderBottom
|
||||||
onChange={handleFormChange}
|
smallItem
|
||||||
formSchemas={formSchemas as any}
|
value={currType}
|
||||||
isEditMode={true}
|
onChange={(value) => {
|
||||||
showOnVariableMap={{}}
|
setCurrType(value)
|
||||||
validating={false}
|
}}
|
||||||
inputClassName='bg-components-input-bg-normal hover:bg-components-input-bg-hover'
|
options={[
|
||||||
fieldMoreInfo={item => item.url
|
{ value: 'settings', text: t('plugin.detailPanel.toolSelector.settings')! },
|
||||||
? (<a
|
{ value: 'params', text: t('plugin.detailPanel.toolSelector.params')! },
|
||||||
href={item.url}
|
]}
|
||||||
target='_blank' rel='noopener noreferrer'
|
|
||||||
className='inline-flex items-center text-xs text-text-accent'
|
|
||||||
>
|
|
||||||
{t('tools.howToGet')}
|
|
||||||
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
|
|
||||||
</a>)
|
|
||||||
: null}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
</div>
|
{nodeId && showTabSlider && currType === 'params' && (
|
||||||
|
<div className='px-4 py-2'>
|
||||||
|
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip1')}</div>
|
||||||
|
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip2')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* user settings only */}
|
||||||
|
{userSettingsOnly && (
|
||||||
|
<div className='p-4 pb-1'>
|
||||||
|
<div className='text-text-primary system-sm-semibold-uppercase'>{t('plugin.detailPanel.toolSelector.settings')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* reasoning config only */}
|
||||||
|
{nodeId && reasoningConfigOnly && (
|
||||||
|
<div className='mb-1 p-4 pb-1'>
|
||||||
|
<div className='text-text-primary system-sm-semibold-uppercase'>{t('plugin.detailPanel.toolSelector.params')}</div>
|
||||||
|
<div className='pb-1'>
|
||||||
|
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip1')}</div>
|
||||||
|
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip2')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* user settings form */}
|
||||||
|
{(currType === 'settings' || userSettingsOnly) && (
|
||||||
|
<div className='px-4 py-2'>
|
||||||
|
<Form
|
||||||
|
value={getPlainValue(value?.settings || {})}
|
||||||
|
onChange={handleSettingsFormChange}
|
||||||
|
formSchemas={settingsFormSchemas as any}
|
||||||
|
isEditMode={true}
|
||||||
|
showOnVariableMap={{}}
|
||||||
|
validating={false}
|
||||||
|
inputClassName='bg-components-input-bg-normal hover:bg-components-input-bg-hover'
|
||||||
|
fieldMoreInfo={item => item.url
|
||||||
|
? (<a
|
||||||
|
href={item.url}
|
||||||
|
target='_blank' rel='noopener noreferrer'
|
||||||
|
className='inline-flex items-center text-xs text-text-accent'
|
||||||
|
>
|
||||||
|
{t('tools.howToGet')}
|
||||||
|
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
|
||||||
|
</a>)
|
||||||
|
: null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* reasoning config form */}
|
||||||
|
{nodeId && (currType === 'params' || reasoningConfigOnly) && (
|
||||||
|
<ReasoningConfigForm
|
||||||
|
value={value?.parameters || {}}
|
||||||
|
onChange={handleParamsFormChange}
|
||||||
|
schemas={paramsFormSchemas as any}
|
||||||
|
nodeOutputVars={nodeOutputVars}
|
||||||
|
availableNodes={availableNodes}
|
||||||
|
nodeId={nodeId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import produce from 'immer'
|
||||||
|
import {
|
||||||
|
RiArrowRightUpLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import Switch from '@/app/components/base/switch'
|
||||||
|
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||||
|
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
||||||
|
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
||||||
|
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
|
||||||
|
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
|
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
|
import type { Node } from 'reactflow'
|
||||||
|
import type {
|
||||||
|
NodeOutPutVar,
|
||||||
|
ValueSelector,
|
||||||
|
Var,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types'
|
||||||
|
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||||
|
import { VarType } from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: Record<string, any>
|
||||||
|
onChange: (val: Record<string, any>) => void
|
||||||
|
schemas: any[]
|
||||||
|
nodeOutputVars: NodeOutPutVar[],
|
||||||
|
availableNodes: Node[],
|
||||||
|
nodeId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReasoningConfigForm: React.FC<Props> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
schemas,
|
||||||
|
nodeOutputVars,
|
||||||
|
availableNodes,
|
||||||
|
nodeId,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const language = useLanguage()
|
||||||
|
const handleAutomatic = (key: string, val: any) => {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
[key]: {
|
||||||
|
value: val ? null : value[key]?.value,
|
||||||
|
auto: val ? 1 : 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const [inputsIsFocus, setInputsIsFocus] = useState<Record<string, boolean>>({})
|
||||||
|
const handleInputFocus = useCallback((variable: string) => {
|
||||||
|
return (value: boolean) => {
|
||||||
|
setInputsIsFocus((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[variable]: value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
const handleNotMixedTypeChange = useCallback((variable: string) => {
|
||||||
|
return (varValue: ValueSelector | string, varKindType: VarKindType) => {
|
||||||
|
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||||
|
const target = draft[variable].value
|
||||||
|
if (target) {
|
||||||
|
target.type = varKindType
|
||||||
|
target.value = varValue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
draft[variable].value = {
|
||||||
|
type: varKindType,
|
||||||
|
value: varValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onChange(newValue)
|
||||||
|
}
|
||||||
|
}, [value, onChange])
|
||||||
|
const handleMixedTypeChange = useCallback((variable: string) => {
|
||||||
|
return (itemValue: string) => {
|
||||||
|
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||||
|
const target = draft[variable].value
|
||||||
|
if (target) {
|
||||||
|
target.value = itemValue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
draft[variable].value = {
|
||||||
|
type: VarKindType.mixed,
|
||||||
|
value: itemValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onChange(newValue)
|
||||||
|
}
|
||||||
|
}, [value, onChange])
|
||||||
|
const handleFileChange = useCallback((variable: string) => {
|
||||||
|
return (varValue: ValueSelector | string) => {
|
||||||
|
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||||
|
draft[variable].value = {
|
||||||
|
type: VarKindType.variable,
|
||||||
|
value: varValue,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onChange(newValue)
|
||||||
|
}
|
||||||
|
}, [value, onChange])
|
||||||
|
const handleAppChange = useCallback((variable: string) => {
|
||||||
|
return (app: {
|
||||||
|
app_id: string
|
||||||
|
inputs: Record<string, any>
|
||||||
|
files?: any[]
|
||||||
|
}) => {
|
||||||
|
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||||
|
draft[variable].value = app as any
|
||||||
|
})
|
||||||
|
onChange(newValue)
|
||||||
|
}
|
||||||
|
}, [onChange, value])
|
||||||
|
const handleModelChange = useCallback((variable: string) => {
|
||||||
|
return (model: any) => {
|
||||||
|
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||||
|
draft[variable].value = {
|
||||||
|
...draft[variable].value,
|
||||||
|
...model,
|
||||||
|
} as any
|
||||||
|
})
|
||||||
|
onChange(newValue)
|
||||||
|
}
|
||||||
|
}, [onChange, value])
|
||||||
|
|
||||||
|
const renderField = (schema: any) => {
|
||||||
|
const {
|
||||||
|
variable,
|
||||||
|
label,
|
||||||
|
required,
|
||||||
|
tooltip,
|
||||||
|
type,
|
||||||
|
scope,
|
||||||
|
url,
|
||||||
|
} = schema
|
||||||
|
const auto = value[variable]?.auto
|
||||||
|
const tooltipContent = (tooltip && (
|
||||||
|
<Tooltip
|
||||||
|
popupContent={<div className='w-[200px]'>
|
||||||
|
{tooltip[language] || tooltip.en_US}
|
||||||
|
</div>}
|
||||||
|
triggerClassName='ml-1 w-4 h-4'
|
||||||
|
asChild={false} />
|
||||||
|
))
|
||||||
|
const varInput = value[variable].value
|
||||||
|
const isNumber = type === FormTypeEnum.textNumber
|
||||||
|
const isSelect = type === FormTypeEnum.select
|
||||||
|
const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files
|
||||||
|
const isAppSelector = type === FormTypeEnum.appSelector
|
||||||
|
const isModelSelector = type === FormTypeEnum.modelSelector
|
||||||
|
// const isToolSelector = type === FormTypeEnum.toolSelector
|
||||||
|
const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector
|
||||||
|
return (
|
||||||
|
<div key={variable} className='space-y-1'>
|
||||||
|
<div className='flex items-center justify-between py-2 system-sm-semibold text-text-secondary'>
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
<span className={cn('text-text-secondary code-sm-semibold')}>{label[language] || label.en_US}</span>
|
||||||
|
{required && (
|
||||||
|
<span className='ml-1 text-red-500'>*</span>
|
||||||
|
)}
|
||||||
|
{tooltipContent}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-1 px-2 py-1 rounded-[6px] border border-divider-subtle bg-background-default-lighter cursor-pointer hover:bg-state-base-hover' onClick={() => handleAutomatic(variable, !auto)}>
|
||||||
|
<span className='text-text-secondary system-xs-medium'>{t('plugin.detailPanel.toolSelector.auto')}</span>
|
||||||
|
<Switch
|
||||||
|
size='xs'
|
||||||
|
defaultValue={!!auto}
|
||||||
|
onChange={val => handleAutomatic(variable, val)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{auto === 0 && (
|
||||||
|
<>
|
||||||
|
{isString && (
|
||||||
|
<Input
|
||||||
|
className={cn(inputsIsFocus[variable] ? 'shadow-xs bg-gray-50 border-gray-300' : 'bg-gray-100 border-gray-100', 'rounded-lg px-3 py-[6px] border')}
|
||||||
|
value={varInput?.value as string || ''}
|
||||||
|
onChange={handleMixedTypeChange(variable)}
|
||||||
|
nodesOutputVars={nodeOutputVars}
|
||||||
|
availableNodes={availableNodes}
|
||||||
|
onFocusChange={handleInputFocus(variable)}
|
||||||
|
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||||
|
placeholderClassName='!leading-[21px]'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* {isString && (
|
||||||
|
<VarReferencePicker
|
||||||
|
zIndex={1001}
|
||||||
|
readonly={false}
|
||||||
|
isShowNodeName
|
||||||
|
nodeId={nodeId}
|
||||||
|
value={varInput?.value || ''}
|
||||||
|
onChange={handleNotMixedTypeChange(variable)}
|
||||||
|
defaultVarKindType={VarKindType.variable}
|
||||||
|
filterVar={(varPayload: Var) => varPayload.type === VarType.number || varPayload.type === VarType.secret || varPayload.type === VarType.string}
|
||||||
|
/>
|
||||||
|
)} */}
|
||||||
|
{(isNumber || isSelect) && (
|
||||||
|
<VarReferencePicker
|
||||||
|
zIndex={1001}
|
||||||
|
readonly={false}
|
||||||
|
isShowNodeName
|
||||||
|
nodeId={nodeId}
|
||||||
|
value={varInput?.type === VarKindType.constant ? (varInput?.value ?? '') : (varInput?.value ?? [])}
|
||||||
|
onChange={handleNotMixedTypeChange(variable)}
|
||||||
|
defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)}
|
||||||
|
isSupportConstantValue
|
||||||
|
filterVar={isNumber ? (varPayload: Var) => varPayload.type === schema._type : undefined}
|
||||||
|
availableVars={isSelect ? nodeOutputVars : undefined}
|
||||||
|
schema={schema}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isFile && (
|
||||||
|
<VarReferencePicker
|
||||||
|
zIndex={1001}
|
||||||
|
readonly={false}
|
||||||
|
isShowNodeName
|
||||||
|
nodeId={nodeId}
|
||||||
|
value={varInput?.value || []}
|
||||||
|
onChange={handleFileChange(variable)}
|
||||||
|
defaultVarKindType={VarKindType.variable}
|
||||||
|
filterVar={(varPayload: Var) => varPayload.type === VarType.file || varPayload.type === VarType.arrayFile}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAppSelector && (
|
||||||
|
<AppSelector
|
||||||
|
disabled={false}
|
||||||
|
scope={scope || 'all'}
|
||||||
|
value={varInput as any}
|
||||||
|
onSelect={handleAppChange(variable)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isModelSelector && (
|
||||||
|
<ModelParameterModal
|
||||||
|
popupClassName='!w-[387px]'
|
||||||
|
isAdvancedMode
|
||||||
|
isInWorkflow
|
||||||
|
value={varInput as any}
|
||||||
|
setModel={handleModelChange(variable)}
|
||||||
|
scope={scope}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{url && (
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target='_blank' rel='noopener noreferrer'
|
||||||
|
className='inline-flex items-center text-xs text-text-accent'
|
||||||
|
>
|
||||||
|
{t('tools.howToGet')}
|
||||||
|
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='px-4 py-2 space-y-3'>
|
||||||
|
{schemas.map(schema => renderField(schema))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReasoningConfigForm
|
||||||
@@ -14,6 +14,7 @@ import { useGitHubReleases } from '../install-plugin/hooks'
|
|||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||||
|
import type { PluginType } from '@/app/components/plugins/types'
|
||||||
|
|
||||||
const i18nPrefix = 'plugin.action'
|
const i18nPrefix = 'plugin.action'
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ type Props = {
|
|||||||
installationId: string
|
installationId: string
|
||||||
pluginUniqueIdentifier: string
|
pluginUniqueIdentifier: string
|
||||||
pluginName: string
|
pluginName: string
|
||||||
|
category: PluginType
|
||||||
usedInApps: number
|
usedInApps: number
|
||||||
isShowFetchNewVersion: boolean
|
isShowFetchNewVersion: boolean
|
||||||
isShowInfo: boolean
|
isShowInfo: boolean
|
||||||
@@ -34,6 +36,7 @@ const Action: FC<Props> = ({
|
|||||||
installationId,
|
installationId,
|
||||||
pluginUniqueIdentifier,
|
pluginUniqueIdentifier,
|
||||||
pluginName,
|
pluginName,
|
||||||
|
category,
|
||||||
isShowFetchNewVersion,
|
isShowFetchNewVersion,
|
||||||
isShowInfo,
|
isShowInfo,
|
||||||
isShowDelete,
|
isShowDelete,
|
||||||
@@ -67,6 +70,7 @@ const Action: FC<Props> = ({
|
|||||||
},
|
},
|
||||||
payload: {
|
payload: {
|
||||||
type: PluginSource.github,
|
type: PluginSource.github,
|
||||||
|
category,
|
||||||
github: {
|
github: {
|
||||||
originalPackageInfo: {
|
originalPackageInfo: {
|
||||||
id: pluginUniqueIdentifier,
|
id: pluginUniqueIdentifier,
|
||||||
|
|||||||
@@ -20,11 +20,9 @@ import Title from '../card/base/title'
|
|||||||
import Action from './action'
|
import Action from './action'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
|
||||||
import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/service/use-tools'
|
|
||||||
import { useSingleCategories } from '../hooks'
|
import { useSingleCategories } from '../hooks'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
|
||||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||||
|
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@@ -39,10 +37,7 @@ const PluginItem: FC<Props> = ({
|
|||||||
const { categoriesMap } = useSingleCategories()
|
const { categoriesMap } = useSingleCategories()
|
||||||
const currentPluginID = usePluginPageContext(v => v.currentPluginID)
|
const currentPluginID = usePluginPageContext(v => v.currentPluginID)
|
||||||
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
|
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
|
||||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
const { refreshPluginList } = useRefreshPluginList()
|
||||||
const invalidateAllToolProviders = useInvalidateAllToolProviders()
|
|
||||||
const invalidateAllBuiltinTools = useInvalidateAllBuiltInTools()
|
|
||||||
const { refreshModelProviders } = useProviderContext()
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
source,
|
source,
|
||||||
@@ -60,13 +55,7 @@ const PluginItem: FC<Props> = ({
|
|||||||
}, [source, author])
|
}, [source, author])
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
invalidateInstalledPluginList()
|
refreshPluginList({ category } as any)
|
||||||
if (PluginType.model.includes(category))
|
|
||||||
refreshModelProviders()
|
|
||||||
if (PluginType.tool.includes(category)) {
|
|
||||||
invalidateAllToolProviders()
|
|
||||||
invalidateAllBuiltinTools()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const getValueFromI18nObject = useRenderI18nObject()
|
const getValueFromI18nObject = useRenderI18nObject()
|
||||||
const title = getValueFromI18nObject(label)
|
const title = getValueFromI18nObject(label)
|
||||||
@@ -116,6 +105,7 @@ const PluginItem: FC<Props> = ({
|
|||||||
isShowDelete
|
isShowDelete
|
||||||
meta={meta}
|
meta={meta}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
category={category}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ const DebugInfo: FC = () => {
|
|||||||
popupContent={
|
popupContent={
|
||||||
<>
|
<>
|
||||||
<div className='flex items-center gap-1 self-stretch'>
|
<div className='flex items-center gap-1 self-stretch'>
|
||||||
<span className='flex flex-col justify-center items-start flex-grow flex-shrink-0 basis-0 text-text-secondary system-sm-semibold'>{t(`${i18nPrefix}.title`)}</span>
|
<span className='flex flex-col justify-center items-start grow shrink-0 basis-0 text-text-secondary system-sm-semibold'>{t(`${i18nPrefix}.title`)}</span>
|
||||||
<a href='' target='_blank' className='flex items-center gap-0.5 text-text-accent-light-mode-only cursor-pointer'>
|
<a href='https://docs.dify.ai/plugins/quick-start/develop-plugins/debug-plugin' target='_blank' className='flex items-center gap-0.5 text-text-accent-light-mode-only cursor-pointer'>
|
||||||
<span className='system-xs-medium'>{t(`${i18nPrefix}.viewDocs`)}</span>
|
<span className='system-xs-medium'>{t(`${i18nPrefix}.viewDocs`)}</span>
|
||||||
<RiArrowRightUpLine className='w-3 h-3' />
|
<RiArrowRightUpLine className='w-3 h-3' />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ export type Permissions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateFromMarketPlacePayload = {
|
export type UpdateFromMarketPlacePayload = {
|
||||||
|
category: PluginType
|
||||||
originalPackageInfo: {
|
originalPackageInfo: {
|
||||||
id: string
|
id: string
|
||||||
payload: PluginDeclaration
|
payload: PluginDeclaration
|
||||||
@@ -173,6 +174,7 @@ export type UpdateFromGitHubPayload = {
|
|||||||
|
|
||||||
export type UpdatePluginPayload = {
|
export type UpdatePluginPayload = {
|
||||||
type: PluginSource
|
type: PluginSource
|
||||||
|
category: PluginType
|
||||||
marketPlace?: UpdateFromMarketPlacePayload
|
marketPlace?: UpdateFromMarketPlacePayload
|
||||||
github?: UpdateFromGitHubPayload
|
github?: UpdateFromGitHubPayload
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const UpdatePluginModal: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [uploadStep, setUploadStep] = useState<UploadStep>(UploadStep.notStarted)
|
const [uploadStep, setUploadStep] = useState<UploadStep>(UploadStep.notStarted)
|
||||||
const { handleRefetch } = usePluginTaskList()
|
const { handleRefetch } = usePluginTaskList(payload.category)
|
||||||
|
|
||||||
const configBtnText = useMemo(() => {
|
const configBtnText = useMemo(() => {
|
||||||
return ({
|
return ({
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const ProviderList = () => {
|
|||||||
className='relative flex flex-col overflow-y-auto bg-background-body grow'
|
className='relative flex flex-col overflow-y-auto bg-background-body grow'
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] z-20 flex-wrap gap-y-2',
|
'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-20 flex-wrap gap-y-2',
|
||||||
currentProvider && 'pr-6',
|
currentProvider && 'pr-6',
|
||||||
)}>
|
)}>
|
||||||
<TabSliderNew
|
<TabSliderNew
|
||||||
|
|||||||
@@ -63,3 +63,34 @@ export const addDefaultValue = (value: Record<string, any>, formSchemas: { varia
|
|||||||
})
|
})
|
||||||
return newValues
|
return newValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const generateFormValue = (value: Record<string, any>, formSchemas: { variable: string; default?: any }[], isReasoning = false) => {
|
||||||
|
const newValues = {} as any
|
||||||
|
formSchemas.forEach((formSchema) => {
|
||||||
|
const itemValue = value[formSchema.variable]
|
||||||
|
if ((formSchema.default !== undefined) && (value === undefined || itemValue === null || itemValue === '' || itemValue === undefined)) {
|
||||||
|
newValues[formSchema.variable] = {
|
||||||
|
...(isReasoning ? { value: null, auto: 1 } : { value: formSchema.default }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newValues
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPlainValue = (value: Record<string, any>) => {
|
||||||
|
const plainValue = { ...value } as any
|
||||||
|
Object.keys(plainValue).forEach((key) => {
|
||||||
|
plainValue[key] = value[key].value
|
||||||
|
})
|
||||||
|
return plainValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStructureValue = (value: Record<string, any>) => {
|
||||||
|
const newValue = { ...value } as any
|
||||||
|
Object.keys(newValue).forEach((key) => {
|
||||||
|
newValue[key] = {
|
||||||
|
value: value[key],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newValue
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type ToolValue = {
|
|||||||
provider_name: string
|
provider_name: string
|
||||||
tool_name: string
|
tool_name: string
|
||||||
tool_label: string
|
tool_label: string
|
||||||
|
settings?: Record<string, any>
|
||||||
parameters?: Record<string, any>
|
parameters?: Record<string, any>
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
extra?: Record<string, any>
|
extra?: Record<string, any>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export const useChecklistBeforePublish = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}, [nodesExtraData, notify, t, store, isChatMode, buildInTools, customTools, workflowTools, language])
|
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleCheckBeforePublish,
|
handleCheckBeforePublish,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type AgentStrategyProps = {
|
|||||||
onFormValueChange: (value: ToolVarInputs) => void
|
onFormValueChange: (value: ToolVarInputs) => void
|
||||||
nodeOutputVars?: NodeOutPutVar[],
|
nodeOutputVars?: NodeOutPutVar[],
|
||||||
availableNodes?: Node[],
|
availableNodes?: Node[],
|
||||||
|
nodeId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomSchema<Type, Field = {}> = Omit<CredentialFormSchema, 'type'> & { type: Type } & Field
|
type CustomSchema<Type, Field = {}> = Omit<CredentialFormSchema, 'type'> & { type: Type } & Field
|
||||||
@@ -46,7 +47,7 @@ type MultipleToolSelectorSchema = CustomSchema<'array[tools]'>
|
|||||||
type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
|
type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
|
||||||
|
|
||||||
export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
||||||
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes } = props
|
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration)
|
const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration)
|
||||||
const renderI18nObject = useRenderI18nObject()
|
const renderI18nObject = useRenderI18nObject()
|
||||||
@@ -141,7 +142,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
|||||||
]
|
]
|
||||||
const renderField: ComponentProps<typeof Form<CustomField>>['customRenderField'] = (schema, props) => {
|
const renderField: ComponentProps<typeof Form<CustomField>>['customRenderField'] = (schema, props) => {
|
||||||
switch (schema.type) {
|
switch (schema.type) {
|
||||||
case 'tool-selector': {
|
case FormTypeEnum.toolSelector: {
|
||||||
const value = props.value[schema.variable]
|
const value = props.value[schema.variable]
|
||||||
const onChange = (value: any) => {
|
const onChange = (value: any) => {
|
||||||
props.onChange({ ...props.value, [schema.variable]: value })
|
props.onChange({ ...props.value, [schema.variable]: value })
|
||||||
@@ -154,6 +155,9 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
|||||||
tooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
|
tooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
|
||||||
>
|
>
|
||||||
<ToolSelector
|
<ToolSelector
|
||||||
|
nodeId={props.nodeId || ''}
|
||||||
|
nodeOutputVars={props.nodeOutputVars || []}
|
||||||
|
availableNodes={props.availableNodes || []}
|
||||||
scope={schema.scope}
|
scope={schema.scope}
|
||||||
value={value}
|
value={value}
|
||||||
onSelect={item => onChange(item)}
|
onSelect={item => onChange(item)}
|
||||||
@@ -162,13 +166,16 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
|||||||
</Field>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case 'array[tools]': {
|
case FormTypeEnum.multiToolSelector: {
|
||||||
const value = props.value[schema.variable]
|
const value = props.value[schema.variable]
|
||||||
const onChange = (value: any) => {
|
const onChange = (value: any) => {
|
||||||
props.onChange({ ...props.value, [schema.variable]: value })
|
props.onChange({ ...props.value, [schema.variable]: value })
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<MultipleToolSelector
|
<MultipleToolSelector
|
||||||
|
nodeId={props.nodeId || ''}
|
||||||
|
nodeOutputVars={props.nodeOutputVars || []}
|
||||||
|
availableNodes={props.availableNodes || []}
|
||||||
scope={schema.scope}
|
scope={schema.scope}
|
||||||
value={value || []}
|
value={value || []}
|
||||||
label={renderI18nObject(schema.label)}
|
label={renderI18nObject(schema.label)}
|
||||||
@@ -199,6 +206,9 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
|||||||
fieldLabelClassName='uppercase'
|
fieldLabelClassName='uppercase'
|
||||||
customRenderField={renderField}
|
customRenderField={renderField}
|
||||||
override={override}
|
override={override}
|
||||||
|
nodeId={nodeId}
|
||||||
|
nodeOutputVars={nodeOutputVars || []}
|
||||||
|
availableNodes={availableNodes || []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
: <ListEmpty
|
: <ListEmpty
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import type { ToolNodeType } from '../../../tool/types'
|
|||||||
import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types'
|
import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types'
|
||||||
import type { IterationNodeType } from '../../../iteration/types'
|
import type { IterationNodeType } from '../../../iteration/types'
|
||||||
import type { ListFilterNodeType } from '../../../list-operator/types'
|
import type { ListFilterNodeType } from '../../../list-operator/types'
|
||||||
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../if-else/default'
|
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants'
|
||||||
import type { DocExtractorNodeType } from '../../../document-extractor/types'
|
import type { DocExtractorNodeType } from '../../../document-extractor/types'
|
||||||
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
|
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
|
||||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ type Props = {
|
|||||||
placeholder?: string
|
placeholder?: string
|
||||||
minWidth?: number
|
minWidth?: number
|
||||||
popupFor?: 'assigned' | 'toAssigned'
|
popupFor?: 'assigned' | 'toAssigned'
|
||||||
|
zIndex?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const VarReferencePicker: FC<Props> = ({
|
const VarReferencePicker: FC<Props> = ({
|
||||||
@@ -90,6 +91,7 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
placeholder,
|
placeholder,
|
||||||
minWidth,
|
minWidth,
|
||||||
popupFor,
|
popupFor,
|
||||||
|
zIndex,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
@@ -386,7 +388,7 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
</>
|
</>
|
||||||
</WrapElem>
|
</WrapElem>
|
||||||
<PortalToFollowElemContent style={{
|
<PortalToFollowElemContent style={{
|
||||||
zIndex: 100,
|
zIndex: zIndex || 100,
|
||||||
}} className='mt-1'>
|
}} className='mt-1'>
|
||||||
{!isConstant && (
|
{!isConstant && (
|
||||||
<VarReferencePopup
|
<VarReferencePopup
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plug
|
|||||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
||||||
import type { NodeDefault } from '../../types'
|
import type { NodeDefault } from '../../types'
|
||||||
import type { AgentNodeType } from './types'
|
import type { AgentNodeType } from './types'
|
||||||
|
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { renderI18nObject } from '@/hooks/use-i18n'
|
import { renderI18nObject } from '@/hooks/use-i18n'
|
||||||
|
|
||||||
const nodeDefault: NodeDefault<AgentNodeType> = {
|
const nodeDefault: NodeDefault<AgentNodeType> = {
|
||||||
@@ -37,6 +38,94 @@ const nodeDefault: NodeDefault<AgentNodeType> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const param of strategy.parameters) {
|
for (const param of strategy.parameters) {
|
||||||
|
// single tool
|
||||||
|
if (param.required && param.type === FormTypeEnum.toolSelector) {
|
||||||
|
// no value
|
||||||
|
const toolValue = payload.agent_parameters?.[param.name]?.value
|
||||||
|
if (!toolValue) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.fieldRequired', { field: renderI18nObject(param.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// not enabled
|
||||||
|
else if (!toolValue.enabled) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.noValidTool', { field: renderI18nObject(param.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check form of tool
|
||||||
|
else {
|
||||||
|
const schemas = toolValue.schemas || []
|
||||||
|
const userSettings = toolValue.settings
|
||||||
|
const reasoningConfig = toolValue.parameters
|
||||||
|
schemas.forEach((schema: any) => {
|
||||||
|
if (schema?.required) {
|
||||||
|
if (schema.form === 'form' && !userSettings[schema.name]?.value) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schema.form === 'llm' && reasoningConfig[schema.name].auto === 0 && !userSettings[schema.name]?.value) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// multiple tools
|
||||||
|
if (param.required && param.type === FormTypeEnum.multiToolSelector) {
|
||||||
|
const tools = payload.agent_parameters?.[param.name]?.value || []
|
||||||
|
// no value
|
||||||
|
if (!tools.length) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.fieldRequired', { field: renderI18nObject(param.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// not enabled
|
||||||
|
else if (tools.every((tool: any) => !tool.enabled)) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.noValidTool', { field: renderI18nObject(param.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check form of tools
|
||||||
|
else {
|
||||||
|
let validState = {
|
||||||
|
isValid: true,
|
||||||
|
errorMessage: '',
|
||||||
|
}
|
||||||
|
for (const tool of tools) {
|
||||||
|
const schemas = tool.schemas || []
|
||||||
|
const userSettings = tool.settings
|
||||||
|
const reasoningConfig = tool.parameters
|
||||||
|
schemas.forEach((schema: any) => {
|
||||||
|
if (schema?.required) {
|
||||||
|
if (schema.form === 'form' && !userSettings[schema.name]?.value) {
|
||||||
|
return validState = {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schema.form === 'llm' && reasoningConfig[schema.name]?.auto === 0 && !reasoningConfig[schema.name]?.value) {
|
||||||
|
return validState = {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return validState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// common params
|
||||||
if (param.required && !payload.agent_parameters?.[param.name]?.value) {
|
if (param.required && !payload.agent_parameters?.[param.name]?.value) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
|
|||||||
onFormValueChange={onFormChange}
|
onFormValueChange={onFormChange}
|
||||||
nodeOutputVars={availableVars}
|
nodeOutputVars={availableVars}
|
||||||
availableNodes={availableNodesWithParent}
|
availableNodes={availableNodesWithParent}
|
||||||
|
nodeId={props.id}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import ListFilterNode from './list-operator/node'
|
|||||||
import ListFilterPanel from './list-operator/panel'
|
import ListFilterPanel from './list-operator/panel'
|
||||||
import AgentNode from './agent/node'
|
import AgentNode from './agent/node'
|
||||||
import AgentPanel from './agent/panel'
|
import AgentPanel from './agent/panel'
|
||||||
|
import { TransferMethod } from '@/types/app'
|
||||||
|
|
||||||
export const NodeComponentMap: Record<string, ComponentType<any>> = {
|
export const NodeComponentMap: Record<string, ComponentType<any>> = {
|
||||||
[BlockEnum.Start]: StartNode,
|
[BlockEnum.Start]: StartNode,
|
||||||
@@ -82,3 +83,18 @@ export const PanelComponentMap: Record<string, ComponentType<any>> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CUSTOM_NODE_TYPE = 'custom'
|
export const CUSTOM_NODE_TYPE = 'custom'
|
||||||
|
|
||||||
|
export const FILE_TYPE_OPTIONS = [
|
||||||
|
{ value: 'image', i18nKey: 'image' },
|
||||||
|
{ value: 'document', i18nKey: 'doc' },
|
||||||
|
{ value: 'audio', i18nKey: 'audio' },
|
||||||
|
{ value: 'video', i18nKey: 'video' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const TRANSFER_METHOD = [
|
||||||
|
{ value: TransferMethod.local_file, i18nKey: 'localUpload' },
|
||||||
|
{ value: TransferMethod.remote_url, i18nKey: 'url' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method']
|
||||||
|
export const OUTPUT_FILE_SUB_VARIABLES = SUB_VARIABLES.filter(key => key !== 'transfer_method')
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
isComparisonOperatorNeedTranslate,
|
isComparisonOperatorNeedTranslate,
|
||||||
isEmptyRelatedOperator,
|
isEmptyRelatedOperator,
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../default'
|
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants'
|
||||||
import type { ValueSelector } from '../../../types'
|
import type { ValueSelector } from '../../../types'
|
||||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { comparisonOperatorNotRequireValue, getOperators } from '../../utils'
|
import { comparisonOperatorNotRequireValue, getOperators } from '../../utils'
|
||||||
import ConditionNumberInput from '../condition-number-input'
|
import ConditionNumberInput from '../condition-number-input'
|
||||||
import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from '../../default'
|
import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from '../../../constants'
|
||||||
import ConditionWrap from '../condition-wrap'
|
import ConditionWrap from '../condition-wrap'
|
||||||
import ConditionOperator from './condition-operator'
|
import ConditionOperator from './condition-operator'
|
||||||
import ConditionInput from './condition-input'
|
import ConditionInput from './condition-input'
|
||||||
@@ -39,7 +39,7 @@ import { SimpleSelect as Select } from '@/app/components/base/select'
|
|||||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||||
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
||||||
|
|
||||||
interface ConditionItemProps {
|
type ConditionItemProps = {
|
||||||
className?: string
|
className?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
caseId: string
|
caseId: string
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
comparisonOperatorNotRequireValue,
|
comparisonOperatorNotRequireValue,
|
||||||
isComparisonOperatorNeedTranslate,
|
isComparisonOperatorNeedTranslate,
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../default'
|
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants'
|
||||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
@@ -20,7 +20,7 @@ import type {
|
|||||||
Node,
|
Node,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
|
|
||||||
interface ConditionValueProps {
|
type ConditionValueProps = {
|
||||||
variableSelector: string[]
|
variableSelector: string[]
|
||||||
labelName?: string
|
labelName?: string
|
||||||
operator: ComparisonOperator
|
operator: ComparisonOperator
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { CaseItem, HandleAddCondition, HandleAddSubVariableCondition, Handl
|
|||||||
import type { Node, NodeOutPutVar, Var } from '../../../types'
|
import type { Node, NodeOutPutVar, Var } from '../../../types'
|
||||||
import { VarType } from '../../../types'
|
import { VarType } from '../../../types'
|
||||||
import { useGetAvailableVars } from '../../variable-assigner/hooks'
|
import { useGetAvailableVars } from '../../variable-assigner/hooks'
|
||||||
import { SUB_VARIABLES } from '../default'
|
import { SUB_VARIABLES } from '../../constants'
|
||||||
import ConditionList from './condition-list'
|
import ConditionList from './condition-list'
|
||||||
import ConditionAdd from './condition-add'
|
import ConditionAdd from './condition-add'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { BlockEnum, type NodeDefault } from '../../types'
|
import { BlockEnum, type NodeDefault } from '../../types'
|
||||||
import { type IfElseNodeType, LogicalOperator } from './types'
|
import { type IfElseNodeType, LogicalOperator } from './types'
|
||||||
import { isEmptyRelatedOperator } from './utils'
|
import { isEmptyRelatedOperator } from './utils'
|
||||||
import { TransferMethod } from '@/types/app'
|
|
||||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
||||||
const i18nPrefix = 'workflow.errorMsg'
|
const i18nPrefix = 'workflow.errorMsg'
|
||||||
|
|
||||||
@@ -79,18 +78,3 @@ const nodeDefault: NodeDefault<IfElseNodeType> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default nodeDefault
|
export default nodeDefault
|
||||||
|
|
||||||
export const FILE_TYPE_OPTIONS = [
|
|
||||||
{ value: 'image', i18nKey: 'image' },
|
|
||||||
{ value: 'document', i18nKey: 'doc' },
|
|
||||||
{ value: 'audio', i18nKey: 'audio' },
|
|
||||||
{ value: 'video', i18nKey: 'video' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const TRANSFER_METHOD = [
|
|
||||||
{ value: TransferMethod.local_file, i18nKey: 'localUpload' },
|
|
||||||
{ value: TransferMethod.remote_url, i18nKey: 'url' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method']
|
|
||||||
export const OUTPUT_FILE_SUB_VARIABLES = SUB_VARIABLES.filter(key => key !== 'transfer_method')
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ComparisonOperator } from '../../if-else/types'
|
|||||||
import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils'
|
import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils'
|
||||||
import SubVariablePicker from './sub-variable-picker'
|
import SubVariablePicker from './sub-variable-picker'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/if-else/default'
|
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants'
|
||||||
import { SimpleSelect as Select } from '@/app/components/base/select'
|
import { SimpleSelect as Select } from '@/app/components/base/select'
|
||||||
|
|
||||||
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { SUB_VARIABLES } from '../../if-else/default'
|
import { SUB_VARIABLES } from '../../constants'
|
||||||
import type { Item } from '@/app/components/base/select'
|
import type { Item } from '@/app/components/base/select'
|
||||||
import { SimpleSelect as Select } from '@/app/components/base/select'
|
import { SimpleSelect as Select } from '@/app/components/base/select'
|
||||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||||
|
|||||||
@@ -168,6 +168,46 @@ const translation = {
|
|||||||
preprocessDocument: '{{num}} Preprocess Documents',
|
preprocessDocument: '{{num}} Preprocess Documents',
|
||||||
allKnowledge: 'All Knowledge',
|
allKnowledge: 'All Knowledge',
|
||||||
allKnowledgeDescription: 'Select to display all knowledge in this workspace. Only the Workspace Owner can manage all knowledge.',
|
allKnowledgeDescription: 'Select to display all knowledge in this workspace. Only the Workspace Owner can manage all knowledge.',
|
||||||
|
metadata: {
|
||||||
|
metadata: 'Metadata',
|
||||||
|
addMetadata: 'Add Metadata',
|
||||||
|
createMetadata: {
|
||||||
|
title: 'New Metadata',
|
||||||
|
back: 'Back',
|
||||||
|
type: 'Type',
|
||||||
|
name: 'Name',
|
||||||
|
namePlaceholder: 'Add metadata name',
|
||||||
|
},
|
||||||
|
batchEditMetadata: {
|
||||||
|
editMetadata: 'Edit Metadata',
|
||||||
|
editDocumentsNum: 'Editing {{num}} documents',
|
||||||
|
applyToAllSelectDocument: 'Apply to all selected documents',
|
||||||
|
applyToAllSelectDocumentTip: 'Automatically create all the above edited and new metadata for all selected documents, otherwise editing metadata will only apply to documents with it.',
|
||||||
|
multipleValue: 'Multiple Value',
|
||||||
|
},
|
||||||
|
selectMetadata: {
|
||||||
|
search: 'Search metadata',
|
||||||
|
newAction: 'New Metadata',
|
||||||
|
manageAction: 'Manage',
|
||||||
|
},
|
||||||
|
datasetMetadata: {
|
||||||
|
description: 'You can manage all metadata in this knowledge here. Modifications will be synchronized to every document.',
|
||||||
|
addMetaData: 'Add Metadata',
|
||||||
|
values: '{{num}} Values',
|
||||||
|
disabled: 'Disabled',
|
||||||
|
rename: 'Rename',
|
||||||
|
name: 'Name',
|
||||||
|
namePlaceholder: 'Metadata name',
|
||||||
|
builtIn: 'Built-in',
|
||||||
|
builtInDescription: 'Built-in metadata is automatically extracted and generated. It must be enabled before use and cannot be edited.',
|
||||||
|
},
|
||||||
|
documentMetadata: {
|
||||||
|
metadataToolTip: 'Metadata serves as a critical filter that enhances the accuracy and relevance of information retrieval. You can modify and add metadata for this document here.',
|
||||||
|
startLabeling: 'Start Labeling',
|
||||||
|
documentInformation: 'Document Information',
|
||||||
|
technicalParameters: 'Technical Parameters',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@@ -78,8 +78,11 @@ const translation = {
|
|||||||
descriptionLabel: 'Tool description',
|
descriptionLabel: 'Tool description',
|
||||||
descriptionPlaceholder: 'Brief description of the tool\'s purpose, e.g., get the temperature for a specific location.',
|
descriptionPlaceholder: 'Brief description of the tool\'s purpose, e.g., get the temperature for a specific location.',
|
||||||
placeholder: 'Select a tool...',
|
placeholder: 'Select a tool...',
|
||||||
auth: 'AUTHORIZATION',
|
settings: 'USER SETTINGS',
|
||||||
settings: 'TOOL SETTINGS',
|
params: 'REASONING CONFIG',
|
||||||
|
paramsTip1: 'Controls LLM inference parameters.',
|
||||||
|
paramsTip2: 'When \'Automatic\' is off, the default value is used.',
|
||||||
|
auto: 'Automatic',
|
||||||
empty: 'Click the \'+\' button to add tools. You can add multiple tools.',
|
empty: 'Click the \'+\' button to add tools. You can add multiple tools.',
|
||||||
uninstalledTitle: 'Tool not installed',
|
uninstalledTitle: 'Tool not installed',
|
||||||
uninstalledContent: 'This plugin is installed from the local/GitHub repository. Please use after installation.',
|
uninstalledContent: 'This plugin is installed from the local/GitHub repository. Please use after installation.',
|
||||||
|
|||||||
@@ -195,6 +195,8 @@ const translation = {
|
|||||||
visionVariable: 'Vision Variable',
|
visionVariable: 'Vision Variable',
|
||||||
},
|
},
|
||||||
invalidVariable: 'Invalid variable',
|
invalidVariable: 'Invalid variable',
|
||||||
|
noValidTool: '{{field}} no valid tool selected',
|
||||||
|
toolParameterRequired: '{{field}}: parameter [{{param}}] is required',
|
||||||
},
|
},
|
||||||
singleRun: {
|
singleRun: {
|
||||||
testRun: 'Test Run ',
|
testRun: 'Test Run ',
|
||||||
|
|||||||
@@ -168,6 +168,46 @@ const translation = {
|
|||||||
preprocessDocument: '{{num}} 个预处理文档',
|
preprocessDocument: '{{num}} 个预处理文档',
|
||||||
allKnowledge: '所有知识库',
|
allKnowledge: '所有知识库',
|
||||||
allKnowledgeDescription: '选择以显示该工作区内所有知识库。只有工作区所有者才能管理所有知识库。',
|
allKnowledgeDescription: '选择以显示该工作区内所有知识库。只有工作区所有者才能管理所有知识库。',
|
||||||
|
metadata: {
|
||||||
|
metadata: '元数据',
|
||||||
|
addMetadata: '添加元数据',
|
||||||
|
createMetadata: {
|
||||||
|
title: '新建元数据',
|
||||||
|
back: '返回',
|
||||||
|
type: '类型',
|
||||||
|
name: '名称',
|
||||||
|
namePlaceholder: '添加元数据名称',
|
||||||
|
},
|
||||||
|
batchEditMetadata: {
|
||||||
|
editMetadata: '编辑元数据',
|
||||||
|
editDocumentsNum: '编辑 {{num}} 个文档',
|
||||||
|
applyToAllSelectDocument: '应用于所有选定文档',
|
||||||
|
applyToAllSelectDocumentTip: '自动为所有选定文档创建上述编辑和新元数据,否则仅对具有元数据的文档应用编辑。',
|
||||||
|
multipleValue: '多个值',
|
||||||
|
},
|
||||||
|
selectMetadata: {
|
||||||
|
search: '搜索元数据',
|
||||||
|
newAction: '新建元数据',
|
||||||
|
manageAction: '管理',
|
||||||
|
},
|
||||||
|
datasetMetadata: {
|
||||||
|
description: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。',
|
||||||
|
addMetaData: '添加元数据',
|
||||||
|
values: '{{num}} 个值',
|
||||||
|
disabled: '已禁用',
|
||||||
|
rename: '重命名',
|
||||||
|
name: '名称',
|
||||||
|
namePlaceholder: '元数据名称',
|
||||||
|
builtIn: '内置',
|
||||||
|
builtInDescription: '内置元数据是系统预定义的元数据,您可以在此处查看和管理内置元数据。',
|
||||||
|
},
|
||||||
|
documentMetadata: {
|
||||||
|
metadataToolTip: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。',
|
||||||
|
startLabeling: '开始标注',
|
||||||
|
documentInformation: '文档信息',
|
||||||
|
technicalParameters: '技术参数',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@@ -78,8 +78,11 @@ const translation = {
|
|||||||
descriptionLabel: '工具描述',
|
descriptionLabel: '工具描述',
|
||||||
descriptionPlaceholder: '简要描述工具目的,例如,获取特定位置的温度。',
|
descriptionPlaceholder: '简要描述工具目的,例如,获取特定位置的温度。',
|
||||||
placeholder: '选择工具',
|
placeholder: '选择工具',
|
||||||
auth: '授权',
|
settings: '用户设置',
|
||||||
settings: '工具设置',
|
params: '推理配置',
|
||||||
|
paramsTip1: '控制 LLM 推理参数。',
|
||||||
|
paramsTip2: '当“自动”关闭时,使用默认值。',
|
||||||
|
auto: '自动',
|
||||||
empty: '点击 "+" 按钮添加工具。您可以添加多个工具。',
|
empty: '点击 "+" 按钮添加工具。您可以添加多个工具。',
|
||||||
uninstalledTitle: '工具未安装',
|
uninstalledTitle: '工具未安装',
|
||||||
uninstalledContent: '此插件安装自 本地 / GitHub 仓库,请安装后使用。',
|
uninstalledContent: '此插件安装自 本地 / GitHub 仓库,请安装后使用。',
|
||||||
|
|||||||
@@ -195,6 +195,9 @@ const translation = {
|
|||||||
visionVariable: '视觉变量',
|
visionVariable: '视觉变量',
|
||||||
},
|
},
|
||||||
invalidVariable: '无效的变量',
|
invalidVariable: '无效的变量',
|
||||||
|
noValidTool: '{{field}} 无可用工具',
|
||||||
|
toolParameterRequired: '{{field}}: 参数 [{{param}}] 不能为空',
|
||||||
|
|
||||||
},
|
},
|
||||||
singleRun: {
|
singleRun: {
|
||||||
testRun: '测试运行 ',
|
testRun: '测试运行 ',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
PluginDetail,
|
PluginDetail,
|
||||||
PluginInfoFromMarketPlace,
|
PluginInfoFromMarketPlace,
|
||||||
PluginTask,
|
PluginTask,
|
||||||
|
PluginType,
|
||||||
PluginsFromMarketplaceByInfoResponse,
|
PluginsFromMarketplaceByInfoResponse,
|
||||||
PluginsFromMarketplaceResponse,
|
PluginsFromMarketplaceResponse,
|
||||||
VersionInfo,
|
VersionInfo,
|
||||||
@@ -31,6 +32,7 @@ import {
|
|||||||
import { useInvalidateAllBuiltInTools } from './use-tools'
|
import { useInvalidateAllBuiltInTools } from './use-tools'
|
||||||
import usePermission from '@/app/components/plugins/plugin-page/use-permission'
|
import usePermission from '@/app/components/plugins/plugin-page/use-permission'
|
||||||
import { uninstallPlugin } from '@/service/plugins'
|
import { uninstallPlugin } from '@/service/plugins'
|
||||||
|
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||||
|
|
||||||
const NAME_SPACE = 'plugins'
|
const NAME_SPACE = 'plugins'
|
||||||
|
|
||||||
@@ -367,10 +369,11 @@ export const useFetchPluginsInMarketPlaceByInfo = (infos: Record<string, any>[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']
|
const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']
|
||||||
export const usePluginTaskList = () => {
|
export const usePluginTaskList = (category?: PluginType) => {
|
||||||
const {
|
const {
|
||||||
canManagement,
|
canManagement,
|
||||||
} = usePermission()
|
} = usePermission()
|
||||||
|
const { refreshPluginList } = useRefreshPluginList()
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isFetched,
|
isFetched,
|
||||||
@@ -383,8 +386,12 @@ export const usePluginTaskList = () => {
|
|||||||
refetchInterval: (lastQuery) => {
|
refetchInterval: (lastQuery) => {
|
||||||
const lastData = lastQuery.state.data
|
const lastData = lastQuery.state.data
|
||||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||||
if (taskDone)
|
const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)
|
||||||
|
if (taskDone) {
|
||||||
|
if (lastData?.tasks.length && !taskAllFailed)
|
||||||
|
refreshPluginList(category ? { category } as any : undefined, !category)
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return 5000
|
return 5000
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user