From c723bd2c961837ff2d2cd29e4bd819cf4e24c162 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 14 Nov 2024 13:02:12 +0800 Subject: [PATCH 01/20] app selector trigger --- .../model-provider-page/model-modal/Form.tsx | 2 +- .../app-selector/app-trigger.tsx | 49 +++++++ .../app-selector/index.tsx | 129 ++++++++++++++++++ .../plugin-detail-panel/endpoint-modal.tsx | 9 ++ .../tool-selector/index.tsx | 4 +- .../tool-selector/tool-credentials-form.tsx | 0 .../tool-selector/tool-trigger.tsx | 0 web/i18n/en-US/app.ts | 5 + web/i18n/zh-Hans/app.ts | 5 + 9 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx create mode 100644 web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx rename web/app/components/{tools => plugins/plugin-detail-panel}/tool-selector/index.tsx (96%) rename web/app/components/{tools => plugins/plugin-detail-panel}/tool-selector/tool-credentials-form.tsx (100%) rename web/app/components/{tools => plugins/plugin-detail-panel}/tool-selector/tool-trigger.tsx (100%) diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index cef2146daa..bb6f8f19d8 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -18,7 +18,7 @@ import { SimpleSelect } from '@/app/components/base/select' import Tooltip from '@/app/components/base/tooltip' import Radio from '@/app/components/base/radio' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -import ToolSelector from '@/app/components/tools/tool-selector' +import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' type FormProps = { className?: string 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 new file mode 100644 index 0000000000..845a69d7e7 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx @@ -0,0 +1,49 @@ +'use client' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { + RiArrowDownSLine, +} from '@remixicon/react' +import AppIcon from '@/app/components/base/app-icon' +import type { App } from '@/types/app' +import cn from '@/utils/classnames' + +type Props = { + open: boolean + appDetail?: App +} + +const AppTrigger = ({ + open, + appDetail, +}: Props) => { + const { t } = useTranslation() + return ( +
+ {appDetail && ( +
+ +
+ )} + {appDetail && ( +
{appDetail.name}
+ )} + {!appDetail && ( +
{t('app.appSelector.placeholder')}
+ )} + +
+ ) +} + +export default AppTrigger 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 new file mode 100644 index 0000000000..2407526b75 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx @@ -0,0 +1,129 @@ +'use client' +import type { FC } from 'react' +import React, { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + PortalToFollowElem, + PortalToFollowElemContent, + 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 { + 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 type { + OffsetOptions, + Placement, +} from '@floating-ui/react' +import cn from '@/utils/classnames' + +type Props = { + value?: { + provider: string + tool_name: string + } + disabled?: boolean + placement?: Placement + offset?: OffsetOptions + onSelect: (tool: { + provider: string + tool_name: string + }) => void + supportAddCustomTool?: boolean +} +const AppSelector: FC = ({ + value, + disabled, + placement = 'bottom', + offset = 4, + onSelect, +}) => { + const { t } = useTranslation() + const [isShow, onShowChange] = useState(false) + const handleTriggerClick = () => { + if (disabled) return + 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 [isShowChooseApp, setIsShowChooseApp] = useState(false) + const handleSelectTool = (tool: ToolDefaultValue) => { + const toolValue = { + provider: tool.provider_id, + tool_name: tool.tool_name, + } + onSelect(toolValue) + setIsShowChooseApp(false) + if (tool.provider_type === CollectionType.builtIn && tool.is_team_authorization) + onShowChange(false) + } + + return ( + <> + + + + + +
+
+
{t('tools.toolSelector.label')}
+ + } + isShow={isShowChooseApp} + onShowChange={setIsShowChooseApp} + disabled={false} + supportAddCustomTool + onSelect={handleSelectTool} + /> +
+ {/* app inputs config panel */} +
+ +
+
+
+
+ + ) +} +export default React.memo(AppSelector) diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx index ca4813d3c6..704ef9dd41 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx @@ -11,6 +11,8 @@ import Toast from '@/app/components/base/toast' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import cn from '@/utils/classnames' +import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' + type Props = { formSchemas: any defaultValues?: any @@ -38,6 +40,8 @@ const EndpointModal: FC = ({ onSaved(tempCredential) } + const [mockApp, setMockApp] = React.useState(null) + return ( = ({ ) : null} /> +
diff --git a/web/app/components/tools/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx similarity index 96% rename from web/app/components/tools/tool-selector/index.tsx rename to web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index 0b13ab9bc2..eab0dd94d2 100644 --- a/web/app/components/tools/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -7,11 +7,11 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import ToolTrigger from '@/app/components/tools/tool-selector/tool-trigger' +import ToolTrigger from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' import Button from '@/app/components/base/button' import Indicator from '@/app/components/header/indicator' -import ToolCredentialForm from '@/app/components/tools/tool-selector/tool-credentials-form' +import ToolCredentialForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form' import Toast from '@/app/components/base/toast' import { useAppContext } from '@/context/app-context' diff --git a/web/app/components/tools/tool-selector/tool-credentials-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form.tsx similarity index 100% rename from web/app/components/tools/tool-selector/tool-credentials-form.tsx rename to web/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form.tsx diff --git a/web/app/components/tools/tool-selector/tool-trigger.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx similarity index 100% rename from web/app/components/tools/tool-selector/tool-trigger.tsx rename to web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 3377a9b2f3..c39b173fea 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -133,6 +133,11 @@ const translation = { removeConfirmContent: 'The current configuration is in use, removing it will turn off the Tracing feature.', }, }, + appSelector: { + label: 'APP', + placeholder: 'Select an app...', + params: 'APP PARAMETERS', + }, } export default translation diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index ee316200fa..625b87acec 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -132,6 +132,11 @@ const translation = { removeConfirmContent: '当前配置正在使用中,删除它将关闭追踪功能。', }, }, + appSelector: { + label: '应用', + placeholder: '选择一个应用', + params: '应用参数', + }, } export default translation From 7446244147fb730f3faacd5325cefa072301eb5d Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 14 Nov 2024 14:11:55 +0800 Subject: [PATCH 02/20] app picker --- .../app-selector/app-picker.tsx | 116 ++++++++++++++++++ .../app-selector/app-trigger.tsx | 18 ++- .../app-selector/index.tsx | 63 ++++------ .../tool-selector/tool-trigger.tsx | 2 +- web/service/use-apps.ts | 26 ++++ 5 files changed, 172 insertions(+), 53 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx create mode 100644 web/service/use-apps.ts 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}`), + }) +} From 7b4d67d72f29f7eb4e1ac14b43459bc2c5d4e6aa Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 14 Nov 2024 15:08:58 +0800 Subject: [PATCH 03/20] app list filter --- .../app-selector/app-picker.tsx | 3 +- .../app-selector/app-trigger.tsx | 1 + .../app-selector/index.tsx | 6 +-- .../plugin-detail-panel/endpoint-modal.tsx | 2 +- .../tool-selector/tool-credentials-form.tsx | 2 +- web/service/use-apps.ts | 11 +++-- web/types/app.ts | 41 +++++++++++-------- 7 files changed, 39 insertions(+), 27 deletions(-) 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 index 32a0f365f0..4ede53c277 100644 --- 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 @@ -40,7 +40,7 @@ const AppPicker: FC = ({ const [searchText, setSearchText] = useState('') const { data: appList } = useAppFullList() const filteredAppList = useMemo(() => { - return (appList || []).filter(app => app.name.toLowerCase().includes(searchText.toLowerCase())) + return (appList?.data || []).filter(app => app.name.toLowerCase().includes(searchText.toLowerCase())).filter(app => (app.mode !== 'advanced-chat' && app.mode !== 'workflow') || !!app.workflow) }, [appList, searchText]) const getAppType = (app: App) => { switch (app.mode) { @@ -81,7 +81,6 @@ const AppPicker: FC = ({ setSearchText(e.target.value)} onClear={() => setSearchText('')} 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 1a308b3d9b..2706597a86 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 @@ -26,6 +26,7 @@ const AppTrigger = ({ )}> {appDetail && ( = ({ onShowChange(true) } - const { data: currentApp } = useAppDetail(value?.app_id || '') + const { data: currentApp } = useAppDetail(value?.app_id || 'empty') const [isShowChooseApp, setIsShowChooseApp] = useState(false) const handleSelectApp = (app: App) => { const appValue = { @@ -74,7 +74,7 @@ const AppSelector: FC = ({ > @@ -87,7 +87,7 @@ const AppSelector: FC = ({ trigger={ } isShow={isShowChooseApp} diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx index 704ef9dd41..f6e4a0d0bc 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx @@ -73,7 +73,7 @@ const EndpointModal: FC = ({ isEditMode={true} showOnVariableMap={{}} validating={false} - inputClassName='bg-components-input-bg-normal hover:bg-state-base-hover-alt' + inputClassName='bg-components-input-bg-normal hover:bg-components-input-bg-hover' fieldMoreInfo={item => item.url ? ( = ({ isEditMode={true} showOnVariableMap={{}} validating={false} - inputClassName='bg-components-input-bg-normal hover:bg-state-base-hover-alt' + inputClassName='bg-components-input-bg-normal hover:bg-components-input-bg-hover' fieldMoreInfo={item => item.url ? ( { - return useQuery({ + return useQuery({ queryKey: useAppFullListKey, - queryFn: () => get('/apps', { params: { page: 1, limit: 100 } }), + queryFn: () => get('/apps', { params: { page: 1, limit: 100 } }), }) } @@ -21,6 +22,10 @@ export const useInvalidateAppFullList = () => { export const useAppDetail = (appID: string) => { return useQuery({ queryKey: [NAME_SPACE, 'detail', appID], - queryFn: () => get(`/apps/${appID}`), + queryFn: () => { + if (appID === 'empty') + return Promise.resolve(undefined as unknown as App) + return get(`/apps/${appID}`) + }, }) } diff --git a/web/types/app.ts b/web/types/app.ts index 93e4e20506..2f5f6254ff 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -48,7 +48,7 @@ export enum RETRIEVE_METHOD { keywordSearch = 'keyword_search', } -export interface VariableInput { +export type VariableInput = { key: string name: string value: string @@ -69,7 +69,7 @@ export type VariableType = typeof VariableTypes[number] /** * Prompt variable parameter */ -export interface PromptVariable { +export type PromptVariable = { /** Variable key */ key: string /** Variable name */ @@ -82,7 +82,7 @@ export interface PromptVariable { max_length?: number } -export interface TextTypeFormItem { +export type TextTypeFormItem = { default: string label: string variable: string @@ -90,7 +90,7 @@ export interface TextTypeFormItem { max_length: number } -export interface SelectTypeFormItem { +export type SelectTypeFormItem = { default: string label: string variable: string @@ -98,7 +98,7 @@ export interface SelectTypeFormItem { options: string[] } -export interface ParagraphTypeFormItem { +export type ParagraphTypeFormItem = { default: string label: string variable: string @@ -115,7 +115,7 @@ export type UserInputFormItem = { paragraph: TextTypeFormItem } -export interface AgentTool { +export type AgentTool = { provider_id: string provider_type: CollectionType provider_name: string @@ -145,7 +145,7 @@ export enum AgentStrategy { react = 'react', } -export interface CompletionParams { +export type CompletionParams = { /** Maximum number of tokens in the answer message returned by Completion */ max_tokens: number /** @@ -193,7 +193,7 @@ export interface CompletionParams { /** * Model configuration. The backend type. */ -export interface Model { +export type Model = { /** LLM provider, e.g., OPENAI */ provider: string /** Model name, e.g, gpt-3.5.turbo */ @@ -203,7 +203,7 @@ export interface Model { completion_params: CompletionParams } -export interface ModelConfig { +export type ModelConfig = { opening_statement: string suggested_questions?: string[] pre_prompt: string @@ -254,7 +254,7 @@ export type Language = typeof LanguagesSupported[number] /** * Web Application Configuration */ -export interface SiteConfig { +export type SiteConfig = { /** Application URL Identifier: `http://dify.app/{access_token}` */ access_token: string /** Public Title */ @@ -307,7 +307,7 @@ export type AppIconType = 'image' | 'emoji' /** * App */ -export interface App { +export type App = { /** App ID */ id: string /** Name */ @@ -351,16 +351,23 @@ export interface App { /** api site url */ api_base_url: string tags: Tag[] + workflow?: { + id: string + created_at: number + created_by?: string + updated_at: number + updated_by?: string + } } -export interface AppSSO { +export type AppSSO = { enable_sso: boolean } /** * App Template */ -export interface AppTemplate { +export type AppTemplate = { /** Name */ name: string /** Description */ @@ -389,7 +396,7 @@ export enum TtsAutoPlay { export const ALLOW_FILE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif'] -export interface VisionSettings { +export type VisionSettings = { enabled: boolean number_limits: number detail: Resolution @@ -397,7 +404,7 @@ export interface VisionSettings { image_file_size_limit?: number | string } -export interface ImageFile { +export type ImageFile = { type: TransferMethod _id: string fileId: string @@ -408,7 +415,7 @@ export interface ImageFile { deleted?: boolean } -export interface VisionFile { +export type VisionFile = { id?: string type: string transfer_method: TransferMethod @@ -417,7 +424,7 @@ export interface VisionFile { belongs_to?: string } -export interface RetrievalConfig { +export type RetrievalConfig = { search_method: RETRIEVE_METHOD reranking_enable: boolean reranking_model: { From f9f2e68bd8db46c448c618d21c09a07a84f4841d Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 14 Nov 2024 15:53:20 +0800 Subject: [PATCH 04/20] app selector in form --- .../model-provider-page/model-modal/Form.tsx | 28 ++++++++++++++++++- .../app-selector/app-picker.tsx | 8 ++---- .../app-selector/index.tsx | 20 +++++++++---- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index bb6f8f19d8..f4e23b566b 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -19,6 +19,7 @@ import Tooltip from '@/app/components/base/tooltip' import Radio from '@/app/components/base/radio' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' +import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' type FormProps = { className?: string @@ -347,7 +348,32 @@ const Form: FC = ({ } if (formSchema.type === FormTypeEnum.appSelector) { - // TODO + const { + variable, + label, + required, + } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) + + return ( +
+
+ {label[language] || label.en_US} + { + required && ( + * + ) + } + {tooltipContent} +
+ handleFormChange(variable, item as any)} + /> + {fieldMoreInfo?.(formSchema)} + {validating && changeKey === variable && } +
+ ) } } 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 index 4ede53c277..a5be6ffbdd 100644 --- 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 @@ -13,12 +13,10 @@ import type { } 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 = { + appList: App[] disabled: boolean trigger: React.ReactNode placement?: Placement @@ -29,6 +27,7 @@ type Props = { } const AppPicker: FC = ({ + appList, disabled, trigger, placement = 'right-start', @@ -38,9 +37,8 @@ const AppPicker: FC = ({ onSelect, }) => { const [searchText, setSearchText] = useState('') - const { data: appList } = useAppFullList() const filteredAppList = useMemo(() => { - return (appList?.data || []).filter(app => app.name.toLowerCase().includes(searchText.toLowerCase())).filter(app => (app.mode !== 'advanced-chat' && app.mode !== 'workflow') || !!app.workflow) + return (appList || []).filter(app => app.name.toLowerCase().includes(searchText.toLowerCase())).filter(app => (app.mode !== 'advanced-chat' && app.mode !== 'workflow') || !!app.workflow) }, [appList, searchText]) const getAppType = (app: App) => { switch (app.mode) { 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 1cbaa85174..3c08e7abda 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, { useState } from 'react' +import React, { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, @@ -11,7 +11,7 @@ import AppTrigger from '@/app/components/plugins/plugin-detail-panel/app-selecto import AppPicker from '@/app/components/plugins/plugin-detail-panel/app-selector/app-picker' // import Button from '@/app/components/base/button' -import { useAppDetail } from '@/service/use-apps' +import { useAppFullList } from '@/service/use-apps' import type { App } from '@/types/app' import type { OffsetOptions, @@ -48,7 +48,12 @@ const AppSelector: FC = ({ onShowChange(true) } - const { data: currentApp } = useAppDetail(value?.app_id || 'empty') + const { data: appList } = useAppFullList() + const currentAppInfo = useMemo(() => { + if (!appList?.data || !value) + return undefined + return appList.data.find(app => app.id === value.app_id) + }, [appList?.data, value]) const [isShowChooseApp, setIsShowChooseApp] = useState(false) const handleSelectApp = (app: App) => { const appValue = { @@ -60,6 +65,8 @@ const AppSelector: FC = ({ setIsShowChooseApp(false) } + // const { data: currentApp } = useAppDetail(value?.app_id || 'empty') + return ( <> = ({ >
-
{t('tools.toolSelector.label')}
+
{t('app.appSelector.label')}
} isShow={isShowChooseApp} onShowChange={setIsShowChooseApp} disabled={false} + appList={appList?.data || []} onSelect={handleSelectApp} />
From e53c4fc0ad5de4f9f7b471739d2db826b413b295 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 14 Nov 2024 16:42:26 +0800 Subject: [PATCH 05/20] empty inputs form of app selector --- .../model-provider-page/model-modal/Form.tsx | 56 +++++++++---------- .../app-selector/app-inputs-panel.tsx | 36 ++++++++++++ .../app-selector/index.tsx | 12 ++-- web/i18n/en-US/app.ts | 1 + web/i18n/zh-Hans/app.ts | 1 + 5 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index f4e23b566b..711cb39007 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -18,7 +18,7 @@ import { SimpleSelect } from '@/app/components/base/select' import Tooltip from '@/app/components/base/tooltip' import Radio from '@/app/components/base/radio' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' +// import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' type FormProps = { @@ -318,34 +318,34 @@ const Form: FC = ({ ) } - if (formSchema.type === FormTypeEnum.toolSelector) { - const { - variable, - label, - required, - } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) + // if (formSchema.type === FormTypeEnum.toolSelector) { + // const { + // variable, + // label, + // required, + // } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) - return ( -
-
- {label[language] || label.en_US} - { - required && ( - * - ) - } - {tooltipContent} -
- handleFormChange(variable, item as any)} - /> - {fieldMoreInfo?.(formSchema)} - {validating && changeKey === variable && } -
- ) - } + // return ( + //
+ //
+ // {label[language] || label.en_US} + // { + // required && ( + // * + // ) + // } + // {tooltipContent} + //
+ // handleFormChange(variable, item as any)} + // /> + // {fieldMoreInfo?.(formSchema)} + // {validating && changeKey === variable && } + //
+ // ) + // } if (formSchema.type === FormTypeEnum.appSelector) { const { diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx new file mode 100644 index 0000000000..1d3fe1c183 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx @@ -0,0 +1,36 @@ +'use client' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Loading from '@/app/components/base/loading' +import { useAppDetail } from '@/service/use-apps' +import type { App } from '@/types/app' +import cn from '@/utils/classnames' + +type Props = { + appDetail: App +} + +const AppInputsPanel = ({ + appDetail, +}: Props) => { + const { t } = useTranslation() + const { data: currentApp, isFetching: isLoading } = useAppDetail(appDetail.id) + + const inputs = [] + + return ( +
+ {isLoading &&
} + {!isLoading && ( +
{t('app.appSelector.params')}
+ )} + {!isLoading && !inputs.length && ( +
+
{t('app.appSelector.noParams')}
+
+ )} +
+ ) +} + +export default AppInputsPanel 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 3c08e7abda..53b2152ea4 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 @@ -9,8 +9,7 @@ import { } from '@/app/components/base/portal-to-follow-elem' import AppTrigger from '@/app/components/plugins/plugin-detail-panel/app-selector/app-trigger' import AppPicker from '@/app/components/plugins/plugin-detail-panel/app-selector/app-picker' -// import Button from '@/app/components/base/button' - +import AppInputsPanel from '@/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel' import { useAppFullList } from '@/service/use-apps' import type { App } from '@/types/app' import type { @@ -65,8 +64,6 @@ const AppSelector: FC = ({ setIsShowChooseApp(false) } - // const { data: currentApp } = useAppDetail(value?.app_id || 'empty') - return ( <> = ({ />
{/* app inputs config panel */} -
-
+ {currentAppInfo && ( + + )}
diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index c39b173fea..639ca289eb 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -137,6 +137,7 @@ const translation = { label: 'APP', placeholder: 'Select an app...', params: 'APP PARAMETERS', + noParams: 'No parameters needed', }, } diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index 625b87acec..b7249b4af8 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -136,6 +136,7 @@ const translation = { label: '应用', placeholder: '选择一个应用', params: '应用参数', + noParams: '无需参数', }, } From 8f14881aff0549c4a130fe79dbf8098806e60d81 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 14 Nov 2024 21:21:50 +0800 Subject: [PATCH 06/20] app parameters --- .../file-from-link-or-local/index.tsx | 2 +- .../model-provider-page/model-modal/Form.tsx | 56 +++---- .../app-selector/app-inputs-form.tsx | 125 ++++++++++++++ .../app-selector/app-inputs-panel.tsx | 156 +++++++++++++++++- .../app-selector/index.tsx | 27 ++- .../plugin-detail-panel/endpoint-modal.tsx | 9 - web/service/use-apps.ts | 6 +- web/service/use-common.ts | 14 ++ web/service/use-workflow.ts | 18 ++ 9 files changed, 362 insertions(+), 51 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-form.tsx create mode 100644 web/service/use-common.ts create mode 100644 web/service/use-workflow.ts diff --git a/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx b/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx index 1ff2bdd174..8ae8bb0538 100644 --- a/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx +++ b/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx @@ -59,7 +59,7 @@ const FileFromLinkOrLocal = ({ setOpen(v => !v)} asChild> {trigger(open)} - +
{ showFromLink && ( diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index 711cb39007..f4e23b566b 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -18,7 +18,7 @@ import { SimpleSelect } from '@/app/components/base/select' import Tooltip from '@/app/components/base/tooltip' import Radio from '@/app/components/base/radio' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -// import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' +import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' type FormProps = { @@ -318,34 +318,34 @@ const Form: FC = ({ ) } - // if (formSchema.type === FormTypeEnum.toolSelector) { - // const { - // variable, - // label, - // required, - // } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) + if (formSchema.type === FormTypeEnum.toolSelector) { + const { + variable, + label, + required, + } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) - // return ( - //
- //
- // {label[language] || label.en_US} - // { - // required && ( - // * - // ) - // } - // {tooltipContent} - //
- // handleFormChange(variable, item as any)} - // /> - // {fieldMoreInfo?.(formSchema)} - // {validating && changeKey === variable && } - //
- // ) - // } + return ( +
+
+ {label[language] || label.en_US} + { + required && ( + * + ) + } + {tooltipContent} +
+ handleFormChange(variable, item as any)} + /> + {fieldMoreInfo?.(formSchema)} + {validating && changeKey === variable && } +
+ ) + } if (formSchema.type === FormTypeEnum.appSelector) { const { diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-form.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-form.tsx new file mode 100644 index 0000000000..6dd9926d35 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-form.tsx @@ -0,0 +1,125 @@ +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import Input from '@/app/components/base/input' +import Textarea from '@/app/components/base/textarea' +import { PortalSelect } from '@/app/components/base/select' +import { InputVarType } from '@/app/components/workflow/types' +import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' + +type Props = { + inputsForms: any[] + inputs: Record + inputsRef: any + onFormChange: (value: Record) => void +} +const AppInputsForm = ({ + inputsForms, + inputs, + inputsRef, + onFormChange, +}: Props) => { + const { t } = useTranslation() + + const handleFormChange = useCallback((variable: string, value: any) => { + onFormChange({ + ...inputsRef.current, + [variable]: value, + }) + }, [onFormChange, inputsRef]) + + const renderField = (form: any) => { + const { + label, + variable, + options, + } = form + if (form.type === InputVarType.textInput) { + return ( + handleFormChange(variable, e.target.value)} + placeholder={label} + /> + ) + } + if (form.type === InputVarType.number) { + return ( + handleFormChange(variable, e.target.value)} + placeholder={label} + /> + ) + } + if (form.type === InputVarType.paragraph) { + return ( +