diff --git a/web/app/(commonLayout)/apps/NewAppCard.tsx b/web/app/(commonLayout)/apps/NewAppCard.tsx index b1b9aeb3cf..43bdbdc6bf 100644 --- a/web/app/(commonLayout)/apps/NewAppCard.tsx +++ b/web/app/(commonLayout)/apps/NewAppCard.tsx @@ -2,7 +2,7 @@ import { forwardRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import NewAppDialog from './NewAppDialog' +import CreateAppDialog from '@/app/components/app/create-app-dialog' import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal' import { useProviderContext } from '@/context/provider-context' import { Plus } from '@/app/components/base/icons/src/vender/line/general' @@ -17,7 +17,7 @@ const CreateAppCard = forwardRef(({ onSuc const { t } = useTranslation() const { onPlanInfoChanged } = useProviderContext() - const [showNewAppDialog, setShowNewAppDialog] = useState(false) + const [showNewAppDialog, setShowNewAppDialog] = useState(true) const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false) return ( (({ onSuc onSuccess() }} /> - { + setShowNewAppDialog(false)} + onSuccess={() => { onPlanInfoChanged() if (onSuccess) onSuccess() - }} onClose={() => setShowNewAppDialog(false)} /> + }} + /> ) }) diff --git a/web/app/components/app/create-app-dialog/appForm.tsx b/web/app/components/app/create-app-dialog/appForm.tsx new file mode 100644 index 0000000000..8eda9186ff --- /dev/null +++ b/web/app/components/app/create-app-dialog/appForm.tsx @@ -0,0 +1,170 @@ +'use client' + +import type { MouseEventHandler } from 'react' +import { useCallback, useRef, useState } from 'react' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import { useRouter } from 'next/navigation' +import { useContext, useContextSelector } from 'use-context-selector' +import { useProviderContext } from '@/context/provider-context' +import { ToastContext } from '@/app/components/base/toast' +import AppsContext, { useAppContext } from '@/context/app-context' +import type { AppMode } from '@/types/app' +import { createApp } from '@/service/apps' +import Button from '@/app/components/base/button' +import AppIcon from '@/app/components/base/app-icon' +import EmojiPicker from '@/app/components/base/emoji-picker' +import AppsFull from '@/app/components/billing/apps-full-in-dialog' +import { BubbleText } from '@/app/components/base/icons/src/vender/solid/education' +import { CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication' +import { Route } from '@/app/components/base/icons/src/vender/line/mapsAndTravel' + +export type AppFormProps = { + onConfirm: () => void + onHide: () => void +} + +const AppForm = ({ + onConfirm, + onHide, +}: AppFormProps) => { + const { t } = useTranslation() + const router = useRouter() + const { notify } = useContext(ToastContext) + + const mutateApps = useContextSelector(AppsContext, state => state.mutateApps) + + const [appMode, setAppMode] = useState('chat') + const [emoji, setEmoji] = useState({ icon: '🤖', icon_background: '#FFEAD5' }) + const [showEmojiPicker, setShowEmojiPicker] = useState(false) + const [name, setName] = useState('') + const [description, setDescription] = useState('') + + const { plan, enableBilling } = useProviderContext() + const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) + const { isCurrentWorkspaceManager } = useAppContext() + + const isCreatingRef = useRef(false) + const onCreate: MouseEventHandler = useCallback(async () => { + if (!name.trim()) { + notify({ type: 'error', message: t('app.newApp.nameNotEmpty') }) + return + } + if (isCreatingRef.current) + return + isCreatingRef.current = true + try { + const app = await createApp({ + mode: appMode, + name, + icon: emoji.icon, + icon_background: emoji.icon_background, + description, + }) + notify({ type: 'success', message: t('app.newApp.appCreated') }) + onConfirm() + onHide() + mutateApps() + router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`) + } + catch (e) { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + } + isCreatingRef.current = false + }, [name, notify, t, appMode, emoji.icon, emoji.icon_background, description, onConfirm, onHide, mutateApps, router, isCurrentWorkspaceManager]) + + return ( + <> + {/* app type */} +
+
{t('app.newApp.captionAppType')}
+
+
setAppMode('chat')} + > +
+ +
+
{t('app.types.chatbot')}
+
{t('app.newApp.chatbotDescription')}
+
+
setAppMode('agent')} + > +
+ +
+
{t('app.types.agent')}
+
{t('app.newApp.agentDescription')}
+
+
setAppMode('workflow')} + > +
+ +
+
{t('app.types.workflow')}
+
{t('app.newApp.workflowDescription')}
+
+
+
+ {/* icon & name */} +
+
{t('app.newApp.captionName')}
+
+ { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} /> + setName(e.target.value)} + placeholder={t('app.newApp.appNamePlaceholder') || ''} + className='grow h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg border border-transparent outline-none appearance-none caret-primary-600 placeholder:text-gray-400 hover:bg-gray-50 hover:border hover:border-gray-300 focus:bg-gray-50 focus:border focus:border-gray-300 focus:shadow-xs' + /> +
+ {showEmojiPicker && { + setEmoji({ icon, icon_background }) + setShowEmojiPicker(false) + }} + onClose={() => { + setEmoji({ icon: '🤖', icon_background: '#FFEAD5' }) + setShowEmojiPicker(false) + }} + />} +
+ {/* description */} +
+
{t('app.newApp.captionDescription')}
+