diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index fa5bcb596a..dabe75ee62 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -21,8 +21,6 @@ import Divider from '@/app/components/base/divider' import { getRedirection } from '@/utils/app-redirection' import { useProviderContext } from '@/context/provider-context' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' -import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' -import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import EditAppModal from '@/app/components/explore/create-app-modal' import SwitchAppModal from '@/app/components/app/switch-app-modal' @@ -32,6 +30,7 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types' import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal' import { fetchWorkflowDraft } from '@/service/workflow' import { fetchInstalledAppList } from '@/service/explore' +import { AppTypeIcon } from '@/app/components/app/type-selector' export type AppCardProps = { app: App @@ -277,7 +276,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { e.preventDefault() getRedirection(isCurrentWorkspaceEditor, app, push) }} - className='relative group col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg' + className='relative h-[160px] group col-span-1 bg-components-card-bg border-[1px] border-solid border-components-card-border rounded-xl shadow-sm inline-flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg' >
@@ -288,30 +287,14 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { background={app.icon_background} imageUrl={app.icon_url} /> - - {app.mode === 'advanced-chat' && ( - - )} - {app.mode === 'agent-chat' && ( - - )} - {app.mode === 'chat' && ( - - )} - {app.mode === 'completion' && ( - - )} - {app.mode === 'workflow' && ( - - )} - +
-
+
{app.name}
-
- {app.mode === 'advanced-chat' &&
{t('app.types.chatbot').toUpperCase()}
} +
+ {app.mode === 'advanced-chat' &&
{t('app.types.advanced').toUpperCase()}
} {app.mode === 'chat' &&
{t('app.types.chatbot').toUpperCase()}
} {app.mode === 'agent-chat' &&
{t('app.types.agent').toUpperCase()}
} {app.mode === 'workflow' &&
{t('app.types.workflow').toUpperCase()}
} @@ -319,7 +302,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
-
+
{ />
-
+
} @@ -362,7 +345,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
- +
} btnClassName={open => diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index 9d6345aa6c..5269571c21 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -125,7 +125,7 @@ const Apps = () => { return ( <> -
+
{ />
- + {(data && data[0].total > 0) + ?
+ {isCurrentWorkspaceEditor + && } + {data.map(({ data: apps }) => apps.map(app => ( + + )))} +
+ :
+ {isCurrentWorkspaceEditor + && } + +
} +
{showTagManagementModal && ( @@ -160,3 +166,21 @@ const Apps = () => { } export default Apps + +function NoAppsFound() { + const { t } = useTranslation() + function renderDefaultCard() { + const defaultCards = Array.from({ length: 36 }, (_, index) => ( +
+ )) + return defaultCards + } + return ( + <> + {renderDefaultCard()} +
+ {t('app.newApp.noAppsFound')} +
+ + ) +} diff --git a/web/app/(commonLayout)/apps/NewAppCard.tsx b/web/app/(commonLayout)/apps/NewAppCard.tsx index c0dffa99ab..d353cf2394 100644 --- a/web/app/(commonLayout)/apps/NewAppCard.tsx +++ b/web/app/(commonLayout)/apps/NewAppCard.tsx @@ -11,13 +11,15 @@ import CreateAppModal from '@/app/components/app/create-app-modal' import CreateFromDSLModal, { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal' import { useProviderContext } from '@/context/provider-context' import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files' +import cn from '@/utils/classnames' export type CreateAppCardProps = { + className?: string onSuccess?: () => void } // eslint-disable-next-line react/display-name -const CreateAppCard = forwardRef(({ onSuccess }, ref) => { +const CreateAppCard = forwardRef(({ className, onSuccess }, ref) => { const { t } = useTranslation() const { onPlanInfoChanged } = useProviderContext() const searchParams = useSearchParams() @@ -36,26 +38,26 @@ const CreateAppCard = forwardRef(({ onSuc }, [dslUrl]) return ( -
-
{t('app.createApp')}
-
setShowNewAppModal(true)}> +
{t('app.createApp')}
+
setShowNewAppModal(true)}> {t('app.newApp.startFromBlank')}
-
setShowNewAppTemplateDialog(true)}> +
setShowNewAppTemplateDialog(true)}> {t('app.newApp.startFromTemplate')}
setShowCreateFromDSLModal(true)} > - ) }) diff --git a/web/app/(commonLayout)/apps/page.tsx b/web/app/(commonLayout)/apps/page.tsx index ab9852e462..972aabc8bc 100644 --- a/web/app/(commonLayout)/apps/page.tsx +++ b/web/app/(commonLayout)/apps/page.tsx @@ -1,9 +1,10 @@ 'use client' import { useContextSelector } from 'use-context-selector' import { useTranslation } from 'react-i18next' +import { RiDiscordFill, RiGithubFill } from '@remixicon/react' +import Link from 'next/link' import style from '../list.module.css' import Apps from './Apps' -import classNames from '@/utils/classnames' import AppContext from '@/context/app-context' import { LicenseStatus } from '@/types/feature' @@ -12,14 +13,18 @@ const AppList = () => { const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) return ( -
+
{systemFeatures.license.status === LicenseStatus.NONE &&

{t('app.join')}

-

{t('app.communityIntro')}

+

{t('app.communityIntro')}

- - + + + + + +
}
diff --git a/web/app/(commonLayout)/list.module.css b/web/app/(commonLayout)/list.module.css index bb2aa8606c..2fc6469a6d 100644 --- a/web/app/(commonLayout)/list.module.css +++ b/web/app/(commonLayout)/list.module.css @@ -201,14 +201,6 @@ @apply block w-6 h-6 bg-center bg-contain; } -.githubIcon { - background-image: url("./apps/assets/github.svg"); -} - -.discordIcon { - background-image: url("./apps/assets/discord.svg"); -} - /* #region new app dialog */ .newItemCaption { @apply inline-flex items-center mb-2 text-sm font-medium; diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 12fe5cba46..12f9c59cd1 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -237,7 +237,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { {appDetail.mode === 'advanced-chat' && ( <>
{t('app.types.chatbot').toUpperCase()}
-
{t('app.newApp.advanced').toUpperCase()}
+
{t('app.types.advanced').toUpperCase()}
)} {appDetail.mode === 'agent-chat' && ( @@ -246,13 +246,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => { {appDetail.mode === 'chat' && ( <>
{t('app.types.chatbot').toUpperCase()}
-
{(t('app.newApp.basic').toUpperCase())}
+
{(t('app.types.basic').toUpperCase())}
)} {appDetail.mode === 'completion' && ( <>
{t('app.types.completion').toUpperCase()}
-
{(t('app.newApp.basic').toUpperCase())}
+
{(t('app.types.basic').toUpperCase())}
)} {appDetail.mode === 'workflow' && ( @@ -299,7 +299,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { {appDetail.mode === 'advanced-chat' && ( <>
{t('app.types.chatbot').toUpperCase()}
-
{t('app.newApp.advanced').toUpperCase()}
+
{t('app.types.advanced').toUpperCase()}
)} {appDetail.mode === 'agent-chat' && ( @@ -308,13 +308,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => { {appDetail.mode === 'chat' && ( <>
{t('app.types.chatbot').toUpperCase()}
-
{(t('app.newApp.basic').toUpperCase())}
+
{(t('app.types.basic').toUpperCase())}
)} {appDetail.mode === 'completion' && ( <>
{t('app.types.completion').toUpperCase()}
-
{(t('app.newApp.basic').toUpperCase())}
+
{(t('app.types.basic').toUpperCase())}
)} {appDetail.mode === 'workflow' && ( @@ -398,7 +398,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { )} />
- {showSwitchTip === 'chat' ? t('app.newApp.advanced') : t('app.types.workflow')} + {showSwitchTip === 'chat' ? t('app.types.advanced') : t('app.types.workflow')} BETA
{t('app.newApp.advancedFor').toLocaleUpperCase()}
diff --git a/web/app/components/app/create-app-dialog/app-card/index.tsx b/web/app/components/app/create-app-dialog/app-card/index.tsx new file mode 100644 index 0000000000..254d67c923 --- /dev/null +++ b/web/app/components/app/create-app-dialog/app-card/index.tsx @@ -0,0 +1,60 @@ +'use client' +import { useTranslation } from 'react-i18next' +import { PlusIcon } from '@heroicons/react/20/solid' +import { AppTypeIcon, AppTypeLabel } from '../../type-selector' +import Button from '@/app/components/base/button' +import cn from '@/utils/classnames' +import type { App } from '@/models/explore' +import AppIcon from '@/app/components/base/app-icon' + +export type AppCardProps = { + app: App + canCreate: boolean + onCreate: () => void +} + +const AppCard = ({ + app, + onCreate, +}: AppCardProps) => { + const { t } = useTranslation() + const { app: appBasicInfo } = app + return ( +
+
+
+ + +
+
+
+ {appBasicInfo.name} +
+ +
+
+
+
+ {app.description} +
+
+
+
+ +
+
+
+ ) +} + +export default AppCard diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx new file mode 100644 index 0000000000..c9354ce2e1 --- /dev/null +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -0,0 +1,247 @@ +'use client' + +import React, { useMemo, useState } from 'react' +import { useRouter } from 'next/navigation' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import useSWR from 'swr' +import { useDebounceFn } from 'ahooks' +import { RiRobot2Line } from '@remixicon/react' +import AppCard from '../app-card' +import Sidebar, { AppCategories, AppCategoryLabel } from './sidebar' +import Toast from '@/app/components/base/toast' +import Divider from '@/app/components/base/divider' +import cn from '@/utils/classnames' +import ExploreContext from '@/context/explore-context' +import type { App } from '@/models/explore' +import { fetchAppDetail, fetchAppList } from '@/service/explore' +import { importDSL } from '@/service/apps' +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' +import CreateAppModal from '@/app/components/explore/create-app-modal' +import AppTypeSelector from '@/app/components/app/type-selector' +import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' +import Loading from '@/app/components/base/loading' +import { NEED_REFRESH_APP_LIST_KEY } from '@/config' +import { useAppContext } from '@/context/app-context' +import { getRedirection } from '@/utils/app-redirection' +import Input from '@/app/components/base/input' +import type { AppMode } from '@/types/app' +import { DSLImportMode } from '@/models/app' + +type AppsProps = { + onSuccess?: () => void + onCreateFromBlank?: () => void +} + +// export enum PageType { +// EXPLORE = 'explore', +// CREATE = 'create', +// } + +const Apps = ({ + onSuccess, + onCreateFromBlank, +}: AppsProps) => { + const { t } = useTranslation() + const { isCurrentWorkspaceEditor } = useAppContext() + const { push } = useRouter() + const { hasEditPermission } = useContext(ExploreContext) + const allCategoriesEn = AppCategories.RECOMMENDED + + const [keywords, setKeywords] = useState('') + const [searchKeywords, setSearchKeywords] = useState('') + + const { run: handleSearch } = useDebounceFn(() => { + setSearchKeywords(keywords) + }, { wait: 500 }) + + const handleKeywordsChange = (value: string) => { + setKeywords(value) + handleSearch() + } + + const [currentType, setCurrentType] = useState([]) + const [currCategory, setCurrCategory] = useTabSearchParams({ + defaultTab: allCategoriesEn, + disableSearchParams: true, + }) + + const { + data: { categories, allList }, + } = useSWR( + ['/explore/apps'], + () => + fetchAppList().then(({ categories, recommended_apps }) => ({ + categories, + allList: recommended_apps.sort((a, b) => a.position - b.position), + })), + { + fallbackData: { + categories: [], + allList: [], + }, + }, + ) + + const filteredList = useMemo(() => { + const filteredByCategory = allList.filter((item) => { + if (currCategory === allCategoriesEn) + return true + return item.category === currCategory + }) + if (currentType.length === 0) + return filteredByCategory + return filteredByCategory.filter((item) => { + if (currentType.includes('chat') && item.app.mode === 'chat') + return true + if (currentType.includes('advanced-chat') && item.app.mode === 'advanced-chat') + return true + if (currentType.includes('agent-chat') && item.app.mode === 'agent-chat') + return true + if (currentType.includes('completion') && item.app.mode === 'completion') + return true + if (currentType.includes('workflow') && item.app.mode === 'workflow') + return true + return false + }) + }, [currentType, currCategory, allCategoriesEn, allList]) + + const searchFilteredList = useMemo(() => { + if (!searchKeywords || !filteredList || filteredList.length === 0) + return filteredList + + const lowerCaseSearchKeywords = searchKeywords.toLowerCase() + + return filteredList.filter(item => + item.app && item.app.name && item.app.name.toLowerCase().includes(lowerCaseSearchKeywords), + ) + }, [searchKeywords, filteredList]) + + const [currApp, setCurrApp] = React.useState(null) + const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) + const onCreate: CreateAppModalProps['onConfirm'] = async ({ + name, + icon_type, + icon, + icon_background, + description, + }) => { + const { export_data } = await fetchAppDetail( + currApp?.app.id as string, + ) + try { + const app = await importDSL({ + mode: DSLImportMode.YAML_CONTENT, + yaml_content: export_data, + name, + icon_type, + icon, + icon_background, + description, + }) + setIsShowCreateModal(false) + Toast.notify({ + type: 'success', + message: t('app.newApp.appCreated'), + }) + if (onSuccess) + onSuccess() + localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + getRedirection(isCurrentWorkspaceEditor, app, push) + } + catch (e) { + Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + } + } + + if (!categories || categories.length === 0) { + return ( +
+ +
+ ) + } + + return ( +
+
+
+ {t('app.newApp.startFromTemplate')} +
+
+ +
+ +
+ handleKeywordsChange(e.target.value)} + onClear={() => handleKeywordsChange('')} + /> +
+
+
+
+ {!searchKeywords &&
+ { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> +
} +
+ {searchFilteredList && searchFilteredList.length > 0 && <> +
+ {searchKeywords + ?

{searchFilteredList.length > 1 ? t('app.newApp.foundResults', { count: searchFilteredList.length }) : t('app.newApp.foundResult', { count: searchFilteredList.length })}

+ : } +
+
+ {searchFilteredList.map(app => ( + { + setCurrApp(app) + setIsShowCreateModal(true) + }} + /> + ))} +
+ } + {(!searchFilteredList || searchFilteredList.length === 0) && } +
+
+ {isShowCreateModal && ( + setIsShowCreateModal(false)} + /> + )} +
+ ) +} + +export default React.memo(Apps) + +function NoTemplateFound() { + const { t } = useTranslation() + return
+
+ +
+

{t('app.newApp.noTemplateFound')}

+

{t('app.newApp.noTemplateFoundTip')}

+
+} diff --git a/web/app/components/app/create-app-dialog/app-list/sidebar.tsx b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx new file mode 100644 index 0000000000..73b34c7d02 --- /dev/null +++ b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx @@ -0,0 +1,91 @@ +'use client' +import { RiAppsFill, RiChatSmileAiFill, RiExchange2Fill, RiPassPendingFill, RiQuillPenAiFill, RiSpeakAiFill, RiStickyNoteAddLine, RiTerminalBoxFill, RiThumbUpFill } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import classNames from '@/utils/classnames' +import Divider from '@/app/components/base/divider' + +export enum AppCategories { + RECOMMENDED = 'Recommended', + ASSISTANT = 'Assistant', + AGENT = 'Agent', + HR = 'HR', + PROGRAMMING = 'Programming', + WORKFLOW = 'Workflow', + WRITING = 'Writing', +} + +type SidebarProps = { + current: AppCategories + onClick?: (category: AppCategories) => void + onCreateFromBlank?: () => void +} + +export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) { + const { t } = useTranslation() + return
+
    + +
+
{t('app.newAppFromTemplate.byCategories')}
+
    + + + + + + +
+ +
+ + {t('app.newApp.startFromBlank')} +
+
+} + +type CategoryItemProps = { + active: boolean + category: AppCategories + onClick?: (category: AppCategories) => void +} +function CategoryItem({ category, active, onClick }: CategoryItemProps) { + return
  • { onClick?.(category) }}> +
    + +
    + +
  • +} + +type AppCategoryLabelProps = { + category: AppCategories + className?: string +} +export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) { + const { t } = useTranslation() + return {t(`app.newAppFromTemplate.sidebar.${category}`)} +} + +type AppCategoryIconProps = { + category: AppCategories +} +function AppCategoryIcon({ category }: AppCategoryIconProps) { + if (category === AppCategories.AGENT) + return + if (category === AppCategories.ASSISTANT) + return + if (category === AppCategories.HR) + return + if (category === AppCategories.PROGRAMMING) + return + if (category === AppCategories.RECOMMENDED) + return + if (category === AppCategories.WRITING) + return + if (category === AppCategories.WORKFLOW) + return + return +} diff --git a/web/app/components/app/create-app-dialog/index.tsx b/web/app/components/app/create-app-dialog/index.tsx index 13620a36f3..acc3650211 100644 --- a/web/app/components/app/create-app-dialog/index.tsx +++ b/web/app/components/app/create-app-dialog/index.tsx @@ -1,36 +1,26 @@ 'use client' -import { useTranslation } from 'react-i18next' -import { RiCloseLine } from '@remixicon/react' -import NewAppDialog from './newAppDialog' -import AppList, { PageType } from '@/app/components/explore/app-list' +import AppList from './app-list' +import FullScreenModal from '@/app/components/base/fullscreen-modal' type CreateAppDialogProps = { show: boolean onSuccess: () => void onClose: () => void + onCreateFromBlank?: () => void } -const CreateAppTemplateDialog = ({ show, onSuccess, onClose }: CreateAppDialogProps) => { - const { t } = useTranslation() - +const CreateAppTemplateDialog = ({ show, onSuccess, onClose, onCreateFromBlank }: CreateAppDialogProps) => { return ( - {}} + - {/* template list */} -
    -
    {t('app.newApp.startFromTemplate')}
    - { - onSuccess() - onClose() - }} pageType={PageType.CREATE} /> -
    -
    - -
    -
    + { + onSuccess() + onClose() + }} /> + ) } diff --git a/web/app/components/app/create-app-modal/advanced.png b/web/app/components/app/create-app-modal/advanced.png deleted file mode 100644 index 384e29831c..0000000000 Binary files a/web/app/components/app/create-app-modal/advanced.png and /dev/null differ diff --git a/web/app/components/app/create-app-modal/basic.png b/web/app/components/app/create-app-modal/basic.png deleted file mode 100644 index 6f45192b02..0000000000 Binary files a/web/app/components/app/create-app-modal/basic.png and /dev/null differ diff --git a/web/app/components/app/create-app-modal/grid-bg-agent-chat.svg b/web/app/components/app/create-app-modal/grid-bg-agent-chat.svg deleted file mode 100644 index 971d5c39ab..0000000000 --- a/web/app/components/app/create-app-modal/grid-bg-agent-chat.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/web/app/components/app/create-app-modal/grid-bg-chat.svg b/web/app/components/app/create-app-modal/grid-bg-chat.svg deleted file mode 100644 index 7a3e1a83ec..0000000000 --- a/web/app/components/app/create-app-modal/grid-bg-chat.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/web/app/components/app/create-app-modal/grid-bg-completion.svg b/web/app/components/app/create-app-modal/grid-bg-completion.svg deleted file mode 100644 index 9f80a6c440..0000000000 --- a/web/app/components/app/create-app-modal/grid-bg-completion.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/web/app/components/app/create-app-modal/grid-bg-workflow.svg b/web/app/components/app/create-app-modal/grid-bg-workflow.svg deleted file mode 100644 index 144beda82c..0000000000 --- a/web/app/components/app/create-app-modal/grid-bg-workflow.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index 496de58c19..eb78258cc8 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -1,48 +1,46 @@ 'use client' -import type { MouseEventHandler } from 'react' + import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - RiCloseLine, - RiQuestionLine, -} from '@remixicon/react' + import { useRouter } from 'next/navigation' import { useContext, useContextSelector } from 'use-context-selector' +import { RiArrowRightLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react' +import Link from 'next/link' +import { useDebounceFn, useKeyPress } from 'ahooks' +import Image from 'next/image' import AppIconPicker from '../../base/app-icon-picker' import type { AppIconSelection } from '../../base/app-icon-picker' -import s from './style.module.css' +import Button from '@/app/components/base/button' +import Divider from '@/app/components/base/divider' import cn from '@/utils/classnames' import AppsContext, { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { ToastContext } from '@/app/components/base/toast' import type { AppMode } from '@/types/app' import { createApp } from '@/service/apps' -import Modal from '@/app/components/base/modal' -import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Textarea from '@/app/components/base/textarea' import AppIcon from '@/app/components/base/app-icon' import AppsFull from '@/app/components/billing/apps-full-in-dialog' -import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' -import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' -import Tooltip from '@/app/components/base/tooltip' +import { BubbleTextMod, ChatBot, ListSparkle, Logic } from '@/app/components/base/icons/src/vender/solid/communication' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { getRedirection } from '@/utils/app-redirection' +import FullScreenModal from '@/app/components/base/fullscreen-modal' -type CreateAppDialogProps = { - show: boolean +type CreateAppProps = { onSuccess: () => void onClose: () => void + onCreateFromTemplate?: () => void } -const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => { +function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) { const { t } = useTranslation() const { push } = useRouter() const { notify } = useContext(ToastContext) const mutateApps = useContextSelector(AppsContext, state => state.mutateApps) const [appMode, setAppMode] = useState('chat') - const [showChatBotType, setShowChatBotType] = useState(true) const [appIcon, setAppIcon] = useState({ type: 'emoji', icon: '🤖', background: '#FFEAD5' }) const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [name, setName] = useState('') @@ -53,7 +51,8 @@ const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => { const { isCurrentWorkspaceEditor } = useAppContext() const isCreatingRef = useRef(false) - const onCreate: MouseEventHandler = useCallback(async () => { + + const onCreate = useCallback(async () => { if (!appMode) { notify({ type: 'error', message: t('app.newApp.appTypeRequired') }) return @@ -87,237 +86,281 @@ const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => { isCreatingRef.current = false }, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceEditor]) - return ( - { }} - > - {/* Heading */} -
    -
    {t('app.newApp.startFromBlank')}
    -
    - {/* app type */} -
    -
    {t('app.newApp.captionAppType')}
    -
    - {t('app.newApp.chatbotDescription')}
    - } - > -
    { - setAppMode('chat') - setShowChatBotType(true) - }} - > - -
    {t('app.types.chatbot')}
    -
    - - -
    {t('app.newApp.completionDescription')}
    + const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 }) + useKeyPress(['meta.enter', 'ctrl.enter'], () => { + if (isAppsFull) + return + handleCreateApp() + }) + return <> +
    +
    +
    +
    +
    + {t('app.newApp.startFromBlank')} +
    +
    + {t('app.newApp.chooseAppType')} +
    +
    +
    +
    + {t('app.newApp.forBeginners')}
    - } - > -
    { - setAppMode('completion') - setShowChatBotType(false) - }} - > - -
    {t('app.newApp.completeApp')}
    -
    - - {t('app.newApp.agentDescription')}
    - } - > -
    { - setAppMode('agent-chat') - setShowChatBotType(false) - }} - > - -
    {t('app.types.agent')}
    -
    - - -
    {t('app.newApp.workflowDescription')}
    +
    + + +
    } + onClick={() => { + setAppMode('chat') + }} /> + + +
    } + onClick={() => { + setAppMode('agent-chat') + }} /> + + +
    } + onClick={() => { + setAppMode('completion') + }} />
    - } - > -
    { - setAppMode('workflow') - setShowChatBotType(false) - }} - > - -
    {t('app.types.workflow')}
    - BETA
    - -
    -
    - {showChatBotType && ( -
    -
    {t('app.newApp.chatbotType')}
    -
    -
    { - setAppMode('chat') - }} - > -
    -
    {t('app.newApp.basic')}
    -
    - -
    -
    -
    -
    -
    {t('app.newApp.basic')}
    -
    {t('app.newApp.basicFor')}
    -
    -
    {t('app.newApp.basicDescription')}
    -
    -
    +
    +
    + {t('app.newApp.forAdvanced')} +
    +
    + + +
    } + onClick={() => { + setAppMode('advanced-chat') + }} /> + + +
    } + onClick={() => { + setAppMode('workflow') + }} /> +
    +
    + +
    +
    +
    +
    + setName(e.target.value)} + placeholder={t('app.newApp.appNamePlaceholder') || ''} + />
    -
    {t('app.newApp.basicTip')}
    + { setShowAppIconPicker(true) }} + /> + {showAppIconPicker && { + setAppIcon(payload) + setShowAppIconPicker(false) + }} + onClose={() => { + setShowAppIconPicker(false) + }} + />}
    -
    { - setAppMode('advanced-chat') - }} - > -
    -
    -
    {t('app.newApp.advanced')}
    - BETA -
    -
    - -
    -
    -
    -
    -
    -
    {t('app.newApp.advanced')}
    - BETA -
    -
    {t('app.newApp.advancedFor').toLocaleUpperCase()}
    -
    -
    {t('app.newApp.advancedDescription')}
    -
    -
    -
    +
    +
    + + ({t('app.newApp.optional')})
    -
    {t('app.newApp.advancedFor')}
    +