diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx new file mode 100644 index 0000000000..32a0f365f0 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx @@ -0,0 +1,116 @@ +'use client' +import type { FC } from 'react' +import React, { useMemo } from 'react' +import { useState } from 'react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import type { + OffsetOptions, + Placement, +} from '@floating-ui/react' +import Input from '@/app/components/base/input' +import AppIcon from '@/app/components/base/app-icon' +import { + useAppFullList, +} from '@/service/use-apps' +import type { App } from '@/types/app' + +type Props = { + disabled: boolean + trigger: React.ReactNode + placement?: Placement + offset?: OffsetOptions + isShow: boolean + onShowChange: (isShow: boolean) => void + onSelect: (app: App) => void +} + +const AppPicker: FC = ({ + disabled, + trigger, + placement = 'right-start', + offset = 0, + isShow, + onShowChange, + onSelect, +}) => { + const [searchText, setSearchText] = useState('') + const { data: appList } = useAppFullList() + const filteredAppList = useMemo(() => { + return (appList || []).filter(app => app.name.toLowerCase().includes(searchText.toLowerCase())) + }, [appList, searchText]) + const getAppType = (app: App) => { + switch (app.mode) { + case 'advanced-chat': + return 'chatflow' + case 'agent-chat': + return 'agent' + case 'chat': + return 'chat' + case 'completion': + return 'completion' + case 'workflow': + return 'workflow' + } + } + + const handleTriggerClick = () => { + if (disabled) return + onShowChange(true) + } + + return ( + + + {trigger} + + + +
+
+ setSearchText(e.target.value)} + onClear={() => setSearchText('')} + /> +
+
+ {filteredAppList.map(app => ( +
onSelect(app)} + > + +
{app.name}
+
{getAppType(app)}
+
+ ))} +
+
+
+
+ ) +} + +export default React.memo(AppPicker) diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx index 845a69d7e7..1a308b3d9b 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx @@ -25,18 +25,16 @@ const AppTrigger = ({ appDetail && 'pl-1.5 py-1.5', )}> {appDetail && ( -
- -
+ )} {appDetail && ( -
{appDetail.name}
+
{appDetail.name}
)} {!appDetail && (
{t('app.appSelector.placeholder')}
diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx index 2407526b75..44112e6f9b 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useMemo, useState } from 'react' +import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, @@ -8,33 +8,29 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import AppTrigger from '@/app/components/plugins/plugin-detail-panel/app-selector/app-trigger' -import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' -import Button from '@/app/components/base/button' +import AppPicker from '@/app/components/plugins/plugin-detail-panel/app-selector/app-picker' +// import Button from '@/app/components/base/button' -import { - useAllBuiltInTools, - useAllCustomTools, - useAllWorkflowTools, -} from '@/service/use-tools' -import { CollectionType } from '@/app/components/tools/types' -import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' +import { useAppDetail } from '@/service/use-apps' +import type { App } from '@/types/app' import type { OffsetOptions, Placement, } from '@floating-ui/react' -import cn from '@/utils/classnames' type Props = { value?: { - provider: string - tool_name: string + app_id: string + inputs: Record + files?: any[] } disabled?: boolean placement?: Placement offset?: OffsetOptions - onSelect: (tool: { - provider: string - tool_name: string + onSelect: (app: { + app_id: string + inputs: Record + files?: any[] }) => void supportAddCustomTool?: boolean } @@ -52,25 +48,16 @@ const AppSelector: FC = ({ onShowChange(true) } - const { data: buildInTools } = useAllBuiltInTools() - const { data: customTools } = useAllCustomTools() - const { data: workflowTools } = useAllWorkflowTools() - const currentProvider = useMemo(() => { - const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] - return mergedTools.find((toolWithProvider) => { - return toolWithProvider.id === value?.provider && toolWithProvider.tools.some(tool => tool.name === value?.tool_name) - }) - }, [value, buildInTools, customTools, workflowTools]) + const { data: currentApp } = useAppDetail(value?.app_id || '') const [isShowChooseApp, setIsShowChooseApp] = useState(false) - const handleSelectTool = (tool: ToolDefaultValue) => { - const toolValue = { - provider: tool.provider_id, - tool_name: tool.tool_name, + const handleSelectApp = (app: App) => { + const appValue = { + app_id: app.id, + inputs: value?.inputs || {}, + files: value?.files || [], } - onSelect(toolValue) + onSelect(appValue) setIsShowChooseApp(false) - if (tool.provider_type === CollectionType.builtIn && tool.is_team_authorization) - onShowChange(false) } return ( @@ -94,7 +81,7 @@ const AppSelector: FC = ({
{t('tools.toolSelector.label')}
- = ({ isShow={isShowChooseApp} onShowChange={setIsShowChooseApp} disabled={false} - supportAddCustomTool - onSelect={handleSelectTool} + onSelect={handleSelectApp} />
{/* app inputs config panel */}
-
diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx index e481448bcd..9a1a2a221c 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx @@ -40,7 +40,7 @@ const ToolTrigger = ({ )} {value && ( -
{value.tool_name}
+
{value.tool_name}
)} {!value && (
{t('tools.toolSelector.placeholder')}
diff --git a/web/service/use-apps.ts b/web/service/use-apps.ts new file mode 100644 index 0000000000..1b9513f221 --- /dev/null +++ b/web/service/use-apps.ts @@ -0,0 +1,26 @@ +import { get } from './base' +import type { App } from '@/types/app' +import { useInvalid } from './use-base' +import { useQuery } from '@tanstack/react-query' + +const NAME_SPACE = 'apps' + +// TODO paging for list +const useAppFullListKey = [NAME_SPACE, 'full-list'] +export const useAppFullList = () => { + return useQuery({ + queryKey: useAppFullListKey, + queryFn: () => get('/apps', { params: { page: 1, limit: 100 } }), + }) +} + +export const useInvalidateAppFullList = () => { + return useInvalid(useAppFullListKey) +} + +export const useAppDetail = (appID: string) => { + return useQuery({ + queryKey: [NAME_SPACE, 'detail', appID], + queryFn: () => get(`/apps/${appID}`), + }) +}