| | |
| | | 'use client' |
| | | import React, { useCallback, useState } from 'react' |
| | | import React, { useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react' |
| | | import { useDebounceFn, useKeyPress } from 'ahooks' |
| | | import { RiCloseLine } from '@remixicon/react' |
| | | import AppIconPicker from '../../base/app-icon-picker' |
| | | import Modal from '@/app/components/base/modal' |
| | | import Button from '@/app/components/base/button' |
| | |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import AppsFull from '@/app/components/billing/apps-full-in-dialog' |
| | | import type { AppIconType } from '@/types/app' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | export type CreateAppModalProps = { |
| | | show: boolean |
| | |
| | | description: string |
| | | use_icon_as_answer_icon?: boolean |
| | | }) => Promise<void> |
| | | confirmDisabled?: boolean |
| | | onHide: () => void |
| | | } |
| | | |
| | |
| | | appMode, |
| | | appUseIconAsAnswerIcon, |
| | | onConfirm, |
| | | confirmDisabled, |
| | | onHide, |
| | | }: CreateAppModalProps) => { |
| | | const { t } = useTranslation() |
| | |
| | | const { plan, enableBilling } = useProviderContext() |
| | | const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) |
| | | |
| | | const submit = useCallback(() => { |
| | | const submit = () => { |
| | | if (!name.trim()) { |
| | | Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') }) |
| | | return |
| | |
| | | use_icon_as_answer_icon: useIconAsAnswerIcon, |
| | | }) |
| | | onHide() |
| | | }, [name, appIcon, description, useIconAsAnswerIcon, onConfirm, onHide, t]) |
| | | |
| | | const { run: handleSubmit } = useDebounceFn(submit, { wait: 300 }) |
| | | |
| | | useKeyPress(['meta.enter', 'ctrl.enter'], () => { |
| | | if (show && !(!isEditModal && isAppsFull) && name.trim()) |
| | | handleSubmit() |
| | | }) |
| | | |
| | | useKeyPress('esc', () => { |
| | | if (show) |
| | | onHide() |
| | | }) |
| | | } |
| | | |
| | | return ( |
| | | <> |
| | | <Modal |
| | | isShow={show} |
| | | onClose={noop} |
| | | onClose={() => {}} |
| | | className='relative !max-w-[480px] px-8' |
| | | > |
| | | <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onHide}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onHide}> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | {isEditModal && ( |
| | | <div className='mb-9 text-xl font-semibold leading-[30px] text-text-primary'>{t('app.editAppTitle')}</div> |
| | | <div className='mb-9 font-semibold text-xl leading-[30px] text-gray-900'>{t('app.editAppTitle')}</div> |
| | | )} |
| | | {!isEditModal && ( |
| | | <div className='mb-9 text-xl font-semibold leading-[30px] text-text-primary'>{t('explore.appCustomize.title', { name: appName })}</div> |
| | | <div className='mb-9 font-semibold text-xl leading-[30px] text-gray-900'>{t('explore.appCustomize.title', { name: appName })}</div> |
| | | )} |
| | | <div className='mb-9'> |
| | | {/* icon & name */} |
| | | <div className='pt-2'> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-text-primary'>{t('app.newApp.captionName')}</div> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-gray-900'>{t('app.newApp.captionName')}</div> |
| | | <div className='flex items-center justify-between space-x-2'> |
| | | <AppIcon |
| | | size='large' |
| | |
| | | value={name} |
| | | onChange={e => setName(e.target.value)} |
| | | placeholder={t('app.newApp.appNamePlaceholder') || ''} |
| | | className='h-10 grow' |
| | | className='grow h-10' |
| | | /> |
| | | </div> |
| | | </div> |
| | | {/* description */} |
| | | <div className='pt-2'> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-text-primary'>{t('app.newApp.captionDescription')}</div> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-gray-900'>{t('app.newApp.captionDescription')}</div> |
| | | <Textarea |
| | | className='resize-none' |
| | | placeholder={t('app.newApp.appDescriptionPlaceholder') || ''} |
| | |
| | | {/* answer icon */} |
| | | {isEditModal && (appMode === 'chat' || appMode === 'advanced-chat' || appMode === 'agent-chat') && ( |
| | | <div className='pt-2'> |
| | | <div className='flex items-center justify-between'> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-text-primary'>{t('app.answerIcon.title')}</div> |
| | | <div className='flex justify-between items-center'> |
| | | <div className='py-2 text-sm font-medium leading-[20px] text-gray-900'>{t('app.answerIcon.title')}</div> |
| | | <Switch |
| | | defaultValue={useIconAsAnswerIcon} |
| | | onChange={v => setUseIconAsAnswerIcon(v)} |
| | | /> |
| | | </div> |
| | | <p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p> |
| | | <p className='body-xs-regular text-gray-500'>{t('app.answerIcon.descriptionInExplore')}</p> |
| | | </div> |
| | | )} |
| | | {!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />} |
| | | {!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />} |
| | | </div> |
| | | <div className='flex flex-row-reverse'> |
| | | <Button |
| | | disabled={(!isEditModal && isAppsFull) || !name.trim() || confirmDisabled} |
| | | className='ml-2 w-24 gap-1' |
| | | variant='primary' |
| | | onClick={handleSubmit} |
| | | > |
| | | <span>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</span> |
| | | <div className='flex gap-0.5'> |
| | | <RiCommandLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' /> |
| | | <RiCornerDownLeftLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' /> |
| | | </div> |
| | | </Button> |
| | | <Button disabled={!isEditModal && isAppsFull} className='w-24 ml-2' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button> |
| | | <Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | </div> |
| | | </Modal> |