Feat: new entry point for app creation (#10847)
@@ -237,7 +237,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
{appDetail.mode === 'advanced-chat' && (
|
||||
<>
|
||||
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
|
||||
<div title={t('app.newApp.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.newApp.advanced').toUpperCase()}</div>
|
||||
<div title={t('app.types.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.advanced').toUpperCase()}</div>
|
||||
</>
|
||||
)}
|
||||
{appDetail.mode === 'agent-chat' && (
|
||||
@@ -246,13 +246,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
{appDetail.mode === 'chat' && (
|
||||
<>
|
||||
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
|
||||
<div title={t('app.newApp.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.newApp.basic').toUpperCase())}</div>
|
||||
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
|
||||
</>
|
||||
)}
|
||||
{appDetail.mode === 'completion' && (
|
||||
<>
|
||||
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div>
|
||||
<div title={t('app.newApp.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.newApp.basic').toUpperCase())}</div>
|
||||
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
|
||||
</>
|
||||
)}
|
||||
{appDetail.mode === 'workflow' && (
|
||||
@@ -299,7 +299,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
{appDetail.mode === 'advanced-chat' && (
|
||||
<>
|
||||
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
|
||||
<div title={t('app.newApp.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.newApp.advanced').toUpperCase()}</div>
|
||||
<div title={t('app.types.advanced') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.advanced').toUpperCase()}</div>
|
||||
</>
|
||||
)}
|
||||
{appDetail.mode === 'agent-chat' && (
|
||||
@@ -308,13 +308,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
{appDetail.mode === 'chat' && (
|
||||
<>
|
||||
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
|
||||
<div title={t('app.newApp.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.newApp.basic').toUpperCase())}</div>
|
||||
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
|
||||
</>
|
||||
)}
|
||||
{appDetail.mode === 'completion' && (
|
||||
<>
|
||||
<div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div>
|
||||
<div title={t('app.newApp.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.newApp.basic').toUpperCase())}</div>
|
||||
<div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
|
||||
</>
|
||||
)}
|
||||
{appDetail.mode === 'workflow' && (
|
||||
@@ -398,7 +398,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
)} />
|
||||
<div className='px-4 pb-2'>
|
||||
<div className='flex items-center gap-1 text-gray-700 text-md leading-6 font-semibold'>
|
||||
{showSwitchTip === 'chat' ? t('app.newApp.advanced') : t('app.types.workflow')}
|
||||
{showSwitchTip === 'chat' ? t('app.types.advanced') : t('app.types.workflow')}
|
||||
<span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span>
|
||||
</div>
|
||||
<div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div>
|
||||
|
||||
60
web/app/components/app/create-app-dialog/app-card/index.tsx
Normal file
@@ -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 (
|
||||
<div className={cn('p-4 h-[132px] relative overflow-hidden flex flex-col group bg-components-panel-on-panel-item-bg border-[0.5px] border-components-panel-border rounded-xl shadow-xs hover:shadow-lg cursor-pointer')}>
|
||||
<div className='flex items-center gap-3 pb-2 grow-0 shrink-0'>
|
||||
<div className='relative shrink-0'>
|
||||
<AppIcon
|
||||
size='large'
|
||||
iconType={app.app.icon_type}
|
||||
icon={app.app.icon}
|
||||
background={app.app.icon_background}
|
||||
imageUrl={app.app.icon_url}
|
||||
/>
|
||||
<AppTypeIcon wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-[4px] border border-divider-regular outline outline-components-panel-on-panel-item-bg'
|
||||
className='w-3 h-3' type={appBasicInfo.mode} />
|
||||
</div>
|
||||
<div className='grow flex flex-col gap-1'>
|
||||
<div className='line-clamp-1'>
|
||||
<span className='system-md-semibold text-text-secondary' title={appBasicInfo.name}>{appBasicInfo.name}</span>
|
||||
</div>
|
||||
<AppTypeLabel className='system-2xs-medium-uppercase text-text-tertiary' type={app.app.mode} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-1 system-xs-regular text-text-tertiary">
|
||||
<div className='line-clamp-3'>
|
||||
{app.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn('hidden absolute bottom-0 left-0 right-0 p-4 pt-8 group-hover:flex bg-gradient-to-t from-[60.27%] from-components-panel-gradient-2 to-transparent')}>
|
||||
<div className={cn('flex items-center w-full space-x-2 h-8')}>
|
||||
<Button variant='primary' className='grow' onClick={() => onCreate()}>
|
||||
<PlusIcon className='w-4 h-4 mr-1' />
|
||||
<span className='text-xs'>{t('app.newApp.useTemplate')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AppCard
|
||||
247
web/app/components/app/create-app-dialog/app-list/index.tsx
Normal file
@@ -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<AppMode[]>([])
|
||||
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<App | null>(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 (
|
||||
<div className="flex h-full items-center">
|
||||
<Loading type="area" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full flex flex-col'>
|
||||
<div className='flex justify-between items-center py-3 border-b border-divider-burn'>
|
||||
<div className='min-w-[180px] pl-5'>
|
||||
<span className='title-xl-semi-bold text-text-primary'>{t('app.newApp.startFromTemplate')}</span>
|
||||
</div>
|
||||
<div className='flex-1 max-w-[548px] p-1.5 flex items-center rounded-xl shadow-md bg-components-panel-bg-blur border border-components-panel-border'>
|
||||
<AppTypeSelector value={currentType} onChange={setCurrentType} />
|
||||
<div className='h-[14px]'>
|
||||
<Divider type='vertical' />
|
||||
</div>
|
||||
<Input
|
||||
showClearIcon
|
||||
wrapperClassName='w-full flex-1'
|
||||
className='bg-transparent hover:bg-transparent focus:bg-transparent hover:border-transparent focus:border-transparent focus:shadow-none'
|
||||
placeholder={t('app.newAppFromTemplate.searchAllTemplate') as string}
|
||||
value={keywords}
|
||||
onChange={e => handleKeywordsChange(e.target.value)}
|
||||
onClear={() => handleKeywordsChange('')}
|
||||
/>
|
||||
</div>
|
||||
<div className='w-[180px] h-8'></div>
|
||||
</div>
|
||||
<div className='relative flex flex-1 overflow-y-auto'>
|
||||
{!searchKeywords && <div className='w-[200px] h-full p-4'>
|
||||
<Sidebar current={currCategory as AppCategories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
|
||||
</div>}
|
||||
<div className='flex-1 h-full overflow-auto shrink-0 grow p-6 pt-2 border-l border-divider-burn'>
|
||||
{searchFilteredList && searchFilteredList.length > 0 && <>
|
||||
<div className='pt-4 pb-1'>
|
||||
{searchKeywords
|
||||
? <p className='title-md-semi-bold text-text-tertiary'>{searchFilteredList.length > 1 ? t('app.newApp.foundResults', { count: searchFilteredList.length }) : t('app.newApp.foundResult', { count: searchFilteredList.length })}</p>
|
||||
: <AppCategoryLabel category={currCategory as AppCategories} className='title-md-semi-bold text-text-primary' />}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'grid content-start shrink-0 gap-3 grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6',
|
||||
)}>
|
||||
{searchFilteredList.map(app => (
|
||||
<AppCard
|
||||
key={app.app_id}
|
||||
app={app}
|
||||
canCreate={hasEditPermission}
|
||||
onCreate={() => {
|
||||
setCurrApp(app)
|
||||
setIsShowCreateModal(true)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>}
|
||||
{(!searchFilteredList || searchFilteredList.length === 0) && <NoTemplateFound />}
|
||||
</div>
|
||||
</div>
|
||||
{isShowCreateModal && (
|
||||
<CreateAppModal
|
||||
appIconType={currApp?.app.icon_type || 'emoji'}
|
||||
appIcon={currApp?.app.icon || ''}
|
||||
appIconBackground={currApp?.app.icon_background || ''}
|
||||
appIconUrl={currApp?.app.icon_url}
|
||||
appName={currApp?.app.name || ''}
|
||||
appDescription={currApp?.app.description || ''}
|
||||
show={isShowCreateModal}
|
||||
onConfirm={onCreate}
|
||||
onHide={() => setIsShowCreateModal(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Apps)
|
||||
|
||||
function NoTemplateFound() {
|
||||
const { t } = useTranslation()
|
||||
return <div className='p-4 rounded-lg w-full bg-workflow-process-bg'>
|
||||
<div className='w-8 h-8 rounded-lg inline-flex items-center justify-center mb-2 shadow-lg bg-components-card-bg'>
|
||||
<RiRobot2Line className='w-5 h-5 text-text-tertiary' />
|
||||
</div>
|
||||
<p className='title-md-semi-bold text-text-primary'>{t('app.newApp.noTemplateFound')}</p>
|
||||
<p className='system-sm-regular text-text-tertiary'>{t('app.newApp.noTemplateFoundTip')}</p>
|
||||
</div>
|
||||
}
|
||||
@@ -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 <div className="w-full h-full flex flex-col">
|
||||
<ul>
|
||||
<CategoryItem category={AppCategories.RECOMMENDED} active={current === AppCategories.RECOMMENDED} onClick={onClick} />
|
||||
</ul>
|
||||
<div className='px-3 pt-2 pb-1 system-xs-medium-uppercase text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div>
|
||||
<ul className='flex-grow flex flex-col gap-0.5'>
|
||||
<CategoryItem category={AppCategories.ASSISTANT} active={current === AppCategories.ASSISTANT} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.AGENT} active={current === AppCategories.AGENT} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.HR} active={current === AppCategories.HR} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.PROGRAMMING} active={current === AppCategories.PROGRAMMING} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.WORKFLOW} active={current === AppCategories.WORKFLOW} onClick={onClick} />
|
||||
<CategoryItem category={AppCategories.WRITING} active={current === AppCategories.WRITING} onClick={onClick} />
|
||||
</ul>
|
||||
<Divider bgStyle='gradient' />
|
||||
<div className='px-3 py-1 flex items-center gap-1 text-text-tertiary cursor-pointer' onClick={onCreateFromBlank}>
|
||||
<RiStickyNoteAddLine className='w-3.5 h-3.5' />
|
||||
<span className='system-xs-regular'>{t('app.newApp.startFromBlank')}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
type CategoryItemProps = {
|
||||
active: boolean
|
||||
category: AppCategories
|
||||
onClick?: (category: AppCategories) => void
|
||||
}
|
||||
function CategoryItem({ category, active, onClick }: CategoryItemProps) {
|
||||
return <li
|
||||
className={classNames('p-1 pl-3 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')}
|
||||
onClick={() => { onClick?.(category) }}>
|
||||
<div className='w-5 h-5 inline-flex items-center justify-center rounded-md border border-divider-regular bg-components-icon-bg-midnight-solid group-[.active]:bg-components-icon-bg-blue-solid'>
|
||||
<AppCategoryIcon category={category} />
|
||||
</div>
|
||||
<AppCategoryLabel category={category}
|
||||
className={classNames('system-sm-medium text-components-menu-item-text group-[.active]:text-components-menu-item-text-active group-hover:text-components-menu-item-text-hover', active && 'system-sm-semibold')} />
|
||||
</li >
|
||||
}
|
||||
|
||||
type AppCategoryLabelProps = {
|
||||
category: AppCategories
|
||||
className?: string
|
||||
}
|
||||
export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) {
|
||||
const { t } = useTranslation()
|
||||
return <span className={className}>{t(`app.newAppFromTemplate.sidebar.${category}`)}</span>
|
||||
}
|
||||
|
||||
type AppCategoryIconProps = {
|
||||
category: AppCategories
|
||||
}
|
||||
function AppCategoryIcon({ category }: AppCategoryIconProps) {
|
||||
if (category === AppCategories.AGENT)
|
||||
return <RiSpeakAiFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.ASSISTANT)
|
||||
return <RiChatSmileAiFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.HR)
|
||||
return <RiPassPendingFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.PROGRAMMING)
|
||||
return <RiTerminalBoxFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.RECOMMENDED)
|
||||
return <RiThumbUpFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.WRITING)
|
||||
return <RiQuillPenAiFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
if (category === AppCategories.WORKFLOW)
|
||||
return <RiExchange2Fill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
return <RiAppsFill className='w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100' />
|
||||
}
|
||||
@@ -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 (
|
||||
<NewAppDialog
|
||||
className='flex'
|
||||
show={show}
|
||||
onClose={() => {}}
|
||||
<FullScreenModal
|
||||
open={show}
|
||||
closable
|
||||
onClose={onClose}
|
||||
>
|
||||
{/* template list */}
|
||||
<div className='grow flex flex-col h-full bg-gray-100'>
|
||||
<div className='shrink-0 pl-8 pr-6 pt-6 pb-3 bg-gray-100 rounded-se-xl text-xl leading-[30px] font-semibold text-gray-900 z-10'>{t('app.newApp.startFromTemplate')}</div>
|
||||
<AppList onSuccess={() => {
|
||||
onSuccess()
|
||||
onClose()
|
||||
}} pageType={PageType.CREATE} />
|
||||
</div>
|
||||
<div className='absolute right-6 top-6 p-2 cursor-pointer z-20' onClick={onClose}>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</NewAppDialog>
|
||||
<AppList onCreateFromBlank={onCreateFromBlank} onSuccess={() => {
|
||||
onSuccess()
|
||||
onClose()
|
||||
}} />
|
||||
</FullScreenModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
@@ -1,16 +0,0 @@
|
||||
<svg width="125" height="74" viewBox="0 0 125 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Grid BG">
|
||||
<mask id="mask0_3169_30051" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="125" height="74">
|
||||
<rect id="Mask" width="125" height="74" fill="url(#paint0_linear_3169_30051)"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3169_30051)">
|
||||
<path id="Grid" d="M7 -3H-1V5M7 -3V5M7 -3H15M7 5H-1M7 5H15M7 5V13M-1 5V13M15 -3V5M15 -3H23M15 5H23M15 5V13M23 -3V5M23 -3H31M23 5H31M23 5V13M31 -3V5M31 -3H39M31 5H39M31 5V13M39 -3V5M39 -3H47M39 5H47M39 5V13M47 -3V5M47 -3H55M47 5H55M47 5V13M55 -3V5M55 -3H63M55 5H63M55 5V13M63 -3V5M63 -3H71M63 5H71M63 5V13M71 -3V5M71 -3H79M71 5H79M71 5V13M79 -3V5M79 -3H87M79 5H87M79 5V13M87 -3V5M87 -3H95M87 5H95M87 5V13M95 -3V5M95 -3H103M95 5H103M95 5V13M103 -3V5M103 -3H111M103 5H111M103 5V13M111 -3V5M111 -3H119M111 5H119M111 5V13M119 -3V5M119 -3H127V5M119 5H127M119 5V13M127 5V13M7 13H-1M7 13H15M7 13V21M-1 13V21M15 13H23M15 13V21M23 13H31M23 13V21M31 13H39M31 13V21M39 13H47M39 13V21M47 13H55M47 13V21M55 13H63M55 13V21M63 13H71M63 13V21M71 13H79M71 13V21M79 13H87M79 13V21M87 13H95M87 13V21M95 13H103M95 13V21M103 13H111M103 13V21M111 13H119M111 13V21M119 13H127M119 13V21M127 13V21M7 21H-1M7 21H15M7 21V29M-1 21V29M15 21H23M15 21V29M23 21H31M23 21V29M31 21H39M31 21V29M39 21H47M39 21V29M47 21H55M47 21V29M55 21H63M55 21V29M63 21H71M63 21V29M71 21H79M71 21V29M79 21H87M79 21V29M87 21H95M87 21V29M95 21H103M95 21V29M103 21H111M103 21V29M111 21H119M111 21V29M119 21H127M119 21V29M127 21V29M7 29H-1M7 29H15M7 29V37M-1 29V37M15 29H23M15 29V37M23 29H31M23 29V37M31 29H39M31 29V37M39 29H47M39 29V37M47 29H55M47 29V37M55 29H63M55 29V37M63 29H71M63 29V37M71 29H79M71 29V37M79 29H87M79 29V37M87 29H95M87 29V37M95 29H103M95 29V37M103 29H111M103 29V37M111 29H119M111 29V37M119 29H127M119 29V37M127 29V37M7 37H-1M7 37H15M7 37V45M-1 37V45M15 37H23M15 37V45M23 37H31M23 37V45M31 37H39M31 37V45M39 37H47M39 37V45M47 37H55M47 37V45M55 37H63M55 37V45M63 37H71M63 37V45M71 37H79M71 37V45M79 37H87M79 37V45M87 37H95M87 37V45M95 37H103M95 37V45M103 37H111M103 37V45M111 37H119M111 37V45M119 37H127M119 37V45M127 37V45M7 45H-1M7 45H15M7 45V53M-1 45V53M15 45H23M15 45V53M23 45H31M23 45V53M31 45H39M31 45V53M39 45H47M39 45V53M47 45H55M47 45V53M55 45H63M55 45V53M63 45H71M63 45V53M71 45H79M71 45V53M79 45H87M79 45V53M87 45H95M87 45V53M95 45H103M95 45V53M103 45H111M103 45V53M111 45H119M111 45V53M119 45H127M119 45V53M127 45V53M7 53H-1M7 53H15M7 53V61M-1 53V61M15 53H23M15 53V61M23 53H31M23 53V61M31 53H39M31 53V61M39 53H47M39 53V61M47 53H55M47 53V61M55 53H63M55 53V61M63 53H71M63 53V61M71 53H79M71 53V61M79 53H87M79 53V61M87 53H95M87 53V61M95 53H103M95 53V61M103 53H111M103 53V61M111 53H119M111 53V61M119 53H127M119 53V61M127 53V61M7 61H-1M7 61H15M7 61V69M-1 61V69M15 61H23M15 61V69M23 61H31M23 61V69M31 61H39M31 61V69M39 61H47M39 61V69M47 61H55M47 61V69M55 61H63M55 61V69M63 61H71M63 61V69M71 61H79M71 61V69M79 61H87M79 61V69M87 61H95M87 61V69M95 61H103M95 61V69M103 61H111M103 61V69M111 61H119M111 61V69M119 61H127M119 61V69M127 61V69M7 69H-1M7 69H15M7 69V77M-1 69V77H7M15 69H23M15 69V77M23 69H31M23 69V77M31 69H39M31 69V77M39 69H47M39 69V77M47 69H55M47 69V77M55 69H63M55 69V77M63 69H71M63 69V77M71 69H79M71 69V77M79 69H87M79 69V77M87 69H95M87 69V77M95 69H103M95 69V77M103 69H111M103 69V77M111 69H119M111 69V77M119 69H127M119 69V77M127 69V77H119M7 77H15M15 77H23M23 77H31M31 77H39M39 77H47M47 77H55M55 77H63M63 77H71M71 77H79M79 77H87M87 77H95M95 77H103M103 77H111M111 77H119" stroke="#1570EF" stroke-width="0.5"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_3169_30051" x1="62.5" y1="0" x2="62.5" y2="74" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D9D9D9" stop-opacity="0.08"/>
|
||||
<stop offset="1" stop-color="#737373" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
@@ -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<AppMode>('chat')
|
||||
const [showChatBotType, setShowChatBotType] = useState<boolean>(true)
|
||||
const [appIcon, setAppIcon] = useState<AppIconSelection>({ 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 (
|
||||
<Modal
|
||||
overflowVisible
|
||||
className='!p-0 !max-w-[720px] !w-[720px] rounded-xl'
|
||||
isShow={show}
|
||||
onClose={() => { }}
|
||||
>
|
||||
{/* Heading */}
|
||||
<div className='shrink-0 flex flex-col h-full bg-white rounded-t-xl'>
|
||||
<div className='shrink-0 pl-8 pr-6 pt-6 pb-3 bg-white text-xl rounded-t-xl leading-[30px] font-semibold text-gray-900 z-10'>{t('app.newApp.startFromBlank')}</div>
|
||||
</div>
|
||||
{/* app type */}
|
||||
<div className='py-2 px-8'>
|
||||
<div className='py-2 text-sm leading-[20px] font-medium text-gray-900'>{t('app.newApp.captionAppType')}</div>
|
||||
<div className='flex'>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='max-w-[280px] leading-[18px] text-xs text-gray-700'>{t('app.newApp.chatbotDescription')}</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'relative grow box-border w-[158px] mr-2 px-0.5 pt-3 pb-2 flex flex-col items-center justify-center gap-1 rounded-lg border border-gray-100 bg-white text-gray-700 cursor-pointer shadow-xs hover:border-gray-300',
|
||||
showChatBotType && 'border-[1.5px] border-primary-400 hover:border-[1.5px] hover:border-primary-400',
|
||||
s['grid-bg-chat'],
|
||||
)}
|
||||
onClick={() => {
|
||||
setAppMode('chat')
|
||||
setShowChatBotType(true)
|
||||
}}
|
||||
>
|
||||
<ChatBot className='w-6 h-6 text-[#1570EF]' />
|
||||
<div className='h-5 text-[13px] font-medium leading-[18px]'>{t('app.types.chatbot')}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='flex flex-col max-w-[320px] leading-[18px] text-xs'>
|
||||
<div className='text-gray-700'>{t('app.newApp.completionDescription')}</div>
|
||||
const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 })
|
||||
useKeyPress(['meta.enter', 'ctrl.enter'], () => {
|
||||
if (isAppsFull)
|
||||
return
|
||||
handleCreateApp()
|
||||
})
|
||||
return <>
|
||||
<div className='flex justify-center h-full overflow-y-auto overflow-x-hidden'>
|
||||
<div className='flex-1 shrink-0 flex justify-end'>
|
||||
<div className='px-10'>
|
||||
<div className='w-full h-6 2xl:h-[139px]' />
|
||||
<div className='pt-1 pb-6'>
|
||||
<span className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.startFromBlank')}</span>
|
||||
</div>
|
||||
<div className='leading-6 mb-2'>
|
||||
<span className='system-sm-semibold text-text-secondary'>{t('app.newApp.chooseAppType')}</span>
|
||||
</div>
|
||||
<div className='flex flex-col w-[660px] gap-4'>
|
||||
<div>
|
||||
<div className='mb-2'>
|
||||
<span className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.newApp.forBeginners')}</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'relative grow box-border w-[158px] mr-2 px-0.5 pt-3 pb-2 flex flex-col items-center justify-center gap-1 rounded-lg border border-gray-100 text-gray-700 cursor-pointer bg-white shadow-xs hover:border-gray-300',
|
||||
s['grid-bg-completion'],
|
||||
appMode === 'completion' && 'border-[1.5px] border-primary-400 hover:border-[1.5px] hover:border-primary-400',
|
||||
)}
|
||||
onClick={() => {
|
||||
setAppMode('completion')
|
||||
setShowChatBotType(false)
|
||||
}}
|
||||
>
|
||||
<AiText className='w-6 h-6 text-[#0E9384]' />
|
||||
<div className='h-5 text-[13px] font-medium leading-[18px]'>{t('app.newApp.completeApp')}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='max-w-[280px] leading-[18px] text-xs text-gray-700'>{t('app.newApp.agentDescription')}</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'relative grow box-border w-[158px] mr-2 px-0.5 pt-3 pb-2 flex flex-col items-center justify-center gap-1 rounded-lg border border-gray-100 text-gray-700 cursor-pointer bg-white shadow-xs hover:border-gray-300',
|
||||
s['grid-bg-agent-chat'],
|
||||
appMode === 'agent-chat' && 'border-[1.5px] border-primary-400 hover:border-[1.5px] hover:border-primary-400',
|
||||
)}
|
||||
onClick={() => {
|
||||
setAppMode('agent-chat')
|
||||
setShowChatBotType(false)
|
||||
}}
|
||||
>
|
||||
<CuteRobot className='w-6 h-6 text-indigo-600' />
|
||||
<div className='h-5 text-[13px] font-medium leading-[18px]'>{t('app.types.agent')}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='flex flex-col max-w-[320px] leading-[18px] text-xs'>
|
||||
<div className='text-gray-700'>{t('app.newApp.workflowDescription')}</div>
|
||||
<div className='flex flex-row gap-2'>
|
||||
<AppTypeCard
|
||||
active={appMode === 'chat'}
|
||||
title={t('app.types.chatbot')}
|
||||
description={t('app.newApp.chatbotShortDescription')}
|
||||
icon={<div className='w-6 h-6 bg-components-icon-bg-blue-solid rounded-md flex items-center justify-center'>
|
||||
<ChatBot className='w-4 h-4 text-components-avatar-shape-fill-stop-100' />
|
||||
</div>}
|
||||
onClick={() => {
|
||||
setAppMode('chat')
|
||||
}} />
|
||||
<AppTypeCard
|
||||
active={appMode === 'agent-chat'}
|
||||
title={t('app.types.agent')}
|
||||
description={t('app.newApp.agentShortDescription')}
|
||||
icon={<div className='w-6 h-6 bg-components-icon-bg-violet-solid rounded-md flex items-center justify-center'>
|
||||
<Logic className='w-4 h-4 text-components-avatar-shape-fill-stop-100' />
|
||||
</div>}
|
||||
onClick={() => {
|
||||
setAppMode('agent-chat')
|
||||
}} />
|
||||
<AppTypeCard
|
||||
active={appMode === 'completion'}
|
||||
title={t('app.newApp.completeApp')}
|
||||
description={t('app.newApp.completionShortDescription')}
|
||||
icon={<div className='w-6 h-6 bg-components-icon-bg-teal-solid rounded-md flex items-center justify-center'>
|
||||
<ListSparkle className='w-4 h-4 text-components-avatar-shape-fill-stop-100' />
|
||||
</div>}
|
||||
onClick={() => {
|
||||
setAppMode('completion')
|
||||
}} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'relative grow box-border w-[158px] px-0.5 pt-3 pb-2 flex flex-col items-center justify-center gap-1 rounded-lg border border-gray-100 text-gray-700 cursor-pointer bg-white shadow-xs hover:border-gray-300',
|
||||
s['grid-bg-workflow'],
|
||||
appMode === 'workflow' && 'border-[1.5px] border-primary-400 hover:border-[1.5px] hover:border-primary-400',
|
||||
)}
|
||||
onClick={() => {
|
||||
setAppMode('workflow')
|
||||
setShowChatBotType(false)
|
||||
}}
|
||||
>
|
||||
<Route className='w-6 h-6 text-[#f79009]' />
|
||||
<div className='h-5 text-[13px] font-medium leading-[18px]'>{t('app.types.workflow')}</div>
|
||||
<span className='absolute top-[-3px] right-[-3px] px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{showChatBotType && (
|
||||
<div className='py-2 px-8'>
|
||||
<div className='py-2 text-sm leading-[20px] font-medium text-gray-900'>{t('app.newApp.chatbotType')}</div>
|
||||
<div className='flex gap-2'>
|
||||
<div
|
||||
className={cn(
|
||||
'relative grow flex-[50%] pl-4 py-[10px] pr-[10px] rounded-lg border border-gray-100 bg-gray-25 text-gray-700 cursor-pointer hover:bg-white hover:shadow-xs hover:border-gray-300',
|
||||
appMode === 'chat' && 'bg-white shadow-xs border-[1.5px] border-primary-400 hover:border-[1.5px] hover:border-primary-400',
|
||||
)}
|
||||
onClick={() => {
|
||||
setAppMode('chat')
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='h-5 text-sm font-medium leading-5'>{t('app.newApp.basic')}</div>
|
||||
<div className='group'>
|
||||
<RiQuestionLine className='w-[14px] h-[14px] text-gray-400 hover:text-gray-500' />
|
||||
<div
|
||||
className={cn(
|
||||
'hidden z-20 absolute left-[327px] top-[-158px] w-[376px] rounded-xl bg-white border-[0.5px] border-[rgba(0,0,0,0.05)] shadow-lg group-hover:block',
|
||||
)}
|
||||
>
|
||||
<div className={cn('w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl', s.basicPic)} />
|
||||
<div className='px-4 pb-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='text-gray-700 text-md leading-6 font-semibold'>{t('app.newApp.basic')}</div>
|
||||
<div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.basicFor')}</div>
|
||||
</div>
|
||||
<div className='mt-1 text-gray-500 text-sm leading-5'>{t('app.newApp.basicDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='mb-2'>
|
||||
<span className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.newApp.forAdvanced')}</span>
|
||||
</div>
|
||||
<div className='flex flex-row gap-2'>
|
||||
<AppTypeCard
|
||||
beta
|
||||
active={appMode === 'advanced-chat'}
|
||||
title={t('app.types.advanced')}
|
||||
description={t('app.newApp.advancedShortDescription')}
|
||||
icon={<div className='w-6 h-6 bg-components-icon-bg-blue-light-solid rounded-md flex items-center justify-center'>
|
||||
<BubbleTextMod className='w-4 h-4 text-components-avatar-shape-fill-stop-100' />
|
||||
</div>}
|
||||
onClick={() => {
|
||||
setAppMode('advanced-chat')
|
||||
}} />
|
||||
<AppTypeCard
|
||||
beta
|
||||
active={appMode === 'workflow'}
|
||||
title={t('app.types.workflow')}
|
||||
description={t('app.newApp.workflowShortDescription')}
|
||||
icon={<div className='w-6 h-6 bg-components-icon-bg-indigo-solid rounded-md flex items-center justify-center'>
|
||||
<RiExchange2Fill className='w-4 h-4 text-components-avatar-shape-fill-stop-100' />
|
||||
</div>}
|
||||
onClick={() => {
|
||||
setAppMode('workflow')
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<div className='flex space-x-3 items-center'>
|
||||
<div className='flex-1'>
|
||||
<div className='h-6 flex items-center mb-1'>
|
||||
<label className='system-sm-semibold text-text-secondary'>{t('app.newApp.captionName')}</label>
|
||||
</div>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder={t('app.newApp.appNamePlaceholder') || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-[2px] text-gray-500 text-xs leading-[18px]'>{t('app.newApp.basicTip')}</div>
|
||||
<AppIcon
|
||||
iconType={appIcon.type}
|
||||
icon={appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId}
|
||||
background={appIcon.type === 'emoji' ? appIcon.background : undefined}
|
||||
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
||||
size='xxl' className='cursor-pointer rounded-2xl'
|
||||
onClick={() => { setShowAppIconPicker(true) }}
|
||||
/>
|
||||
{showAppIconPicker && <AppIconPicker
|
||||
onSelect={(payload) => {
|
||||
setAppIcon(payload)
|
||||
setShowAppIconPicker(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowAppIconPicker(false)
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'relative grow flex-[50%] pl-3 py-2 pr-2 rounded-lg border border-gray-100 bg-gray-25 text-gray-700 cursor-pointer hover:bg-white hover:shadow-xs hover:border-gray-300',
|
||||
appMode === 'advanced-chat' && 'bg-white shadow-xs border-[1.5px] border-primary-400 hover:border-[1.5px] hover:border-primary-400',
|
||||
)}
|
||||
onClick={() => {
|
||||
setAppMode('advanced-chat')
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-1 h-5 text-sm font-medium leading-5'>{t('app.newApp.advanced')}</div>
|
||||
<span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span>
|
||||
</div>
|
||||
<div className='group'>
|
||||
<RiQuestionLine className='w-[14px] h-[14px] text-gray-400 hover:text-gray-500' />
|
||||
<div
|
||||
className={cn(
|
||||
'hidden z-20 absolute right-[26px] top-[-158px] w-[376px] rounded-xl bg-white border-[0.5px] border-[rgba(0,0,0,0.05)] shadow-lg group-hover:block',
|
||||
)}
|
||||
>
|
||||
<div className={cn('w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl', s.advancedPic)} />
|
||||
<div className='px-4 pb-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-1 text-gray-700 text-md leading-6 font-semibold'>{t('app.newApp.advanced')}</div>
|
||||
<span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span>
|
||||
</div>
|
||||
<div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div>
|
||||
</div>
|
||||
<div className='mt-1 text-gray-500 text-sm leading-5'>{t('app.newApp.advancedDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='h-6 flex items-center mb-1'>
|
||||
<label className='system-sm-semibold text-text-secondary'>{t('app.newApp.captionDescription')}</label>
|
||||
<span className='system-xs-regular text-text-tertiary ml-1'>({t('app.newApp.optional')})</span>
|
||||
</div>
|
||||
<div className='mt-[2px] text-gray-500 text-xs leading-[18px]'>{t('app.newApp.advancedFor')}</div>
|
||||
<Textarea
|
||||
className='resize-none'
|
||||
placeholder={t('app.newApp.appDescriptionPlaceholder') || ''}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='pt-5 pb-10 flex justify-between items-center'>
|
||||
<div className='flex gap-1 items-center system-xs-regular text-text-tertiary cursor-pointer' onClick={onCreateFromTemplate}>
|
||||
<span>{t('app.newApp.noIdeaTip')}</span>
|
||||
<div className='p-[1px]'>
|
||||
<RiArrowRightLine className='w-3.5 h-3.5' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<Button onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button disabled={isAppsFull || !name} className='gap-1' variant="primary" onClick={handleCreateApp}>
|
||||
<span>{t('app.newApp.Create')}</span>
|
||||
<div className='flex gap-0.5'>
|
||||
<RiCommandLine size={14} className='p-0.5 system-kbd bg-components-kbd-bg-white rounded-sm' />
|
||||
<RiCornerDownLeftLine size={14} className='p-0.5 system-kbd bg-components-kbd-bg-white rounded-sm' />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* icon & name */}
|
||||
<div className='pt-2 px-8'>
|
||||
<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
|
||||
iconType={appIcon.type}
|
||||
icon={appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId}
|
||||
background={appIcon.type === 'emoji' ? appIcon.background : undefined}
|
||||
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
||||
size='large' className='cursor-pointer'
|
||||
onClick={() => { setShowAppIconPicker(true) }}
|
||||
/>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder={t('app.newApp.appNamePlaceholder') || ''}
|
||||
className='grow h-10'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-1 shrink h-full flex justify-start relative overflow-hidden'>
|
||||
<div className='h-6 2xl:h-[139px] absolute left-0 top-0 right-0 border-b border-b-divider-subtle'></div>
|
||||
<div className='max-w-[760px] border-x border-x-divider-subtle'>
|
||||
<div className='h-6 2xl:h-[139px]' />
|
||||
<AppPreview mode={appMode} />
|
||||
<div className='absolute left-0 right-0 border-b border-b-divider-subtle'></div>
|
||||
<div className='w-[664px] h-[448px] flex items-center justify-center' style={{ background: 'repeating-linear-gradient(135deg, transparent, transparent 2px, rgba(16,24,40,0.04) 4px,transparent 3px, transparent 6px)' }}>
|
||||
<AppScreenShot show={appMode === 'chat'} mode='chat' />
|
||||
<AppScreenShot show={appMode === 'advanced-chat'} mode='advanced-chat' />
|
||||
<AppScreenShot show={appMode === 'agent-chat'} mode='agent-chat' />
|
||||
<AppScreenShot show={appMode === 'completion'} mode='completion' />
|
||||
<AppScreenShot show={appMode === 'workflow'} mode='workflow' />
|
||||
</div>
|
||||
<div className='absolute left-0 right-0 border-b border-b-divider-subtle'></div>
|
||||
</div>
|
||||
{showAppIconPicker && <AppIconPicker
|
||||
onSelect={(payload) => {
|
||||
setAppIcon(payload)
|
||||
setShowAppIconPicker(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowAppIconPicker(false)
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
{/* description */}
|
||||
<div className='pt-2 px-8'>
|
||||
<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') || ''}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{isAppsFull && (
|
||||
</div>
|
||||
{
|
||||
isAppsFull && (
|
||||
<div className='px-8 py-2'>
|
||||
<AppsFull loc='app-create' />
|
||||
</div>
|
||||
)}
|
||||
<div className='px-8 py-6 flex justify-end'>
|
||||
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button disabled={isAppsFull || !name} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
</div>
|
||||
<div className='absolute right-6 top-6 p-2 cursor-pointer z-20' onClick={onClose}>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
</>
|
||||
}
|
||||
type CreateAppDialogProps = CreateAppProps & {
|
||||
show: boolean
|
||||
}
|
||||
const CreateAppModal = ({ show, onClose, onSuccess, onCreateFromTemplate }: CreateAppDialogProps) => {
|
||||
return (
|
||||
<FullScreenModal
|
||||
overflowVisible
|
||||
closable
|
||||
open={show}
|
||||
onClose={onClose}
|
||||
>
|
||||
<CreateApp onClose={onClose} onSuccess={onSuccess} onCreateFromTemplate={onCreateFromTemplate} />
|
||||
</FullScreenModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateAppModal
|
||||
|
||||
type AppTypeCardProps = {
|
||||
icon: JSX.Element
|
||||
beta?: boolean
|
||||
title: string
|
||||
description: string
|
||||
active: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
function AppTypeCard({ icon, title, beta = false, description, active, onClick }: AppTypeCardProps) {
|
||||
const { t } = useTranslation()
|
||||
return <div
|
||||
className={
|
||||
cn(`w-[191px] h-[84px] p-3 border-[0.5px] relative box-content
|
||||
rounded-xl border-components-option-card-option-border
|
||||
bg-components-panel-on-panel-item-bg shadow-xs cursor-pointer hover:shadow-md`, active
|
||||
? 'outline outline-[1.5px] outline-components-option-card-option-selected-border shadow-md'
|
||||
: '')
|
||||
}
|
||||
onClick={onClick}
|
||||
>
|
||||
{beta && <div className='px-[5px] py-[3px]
|
||||
rounded-[5px] min-w-[18px] absolute top-3 right-3
|
||||
border border-divider-deep system-2xs-medium-uppercase text-text-tertiary'>{t('common.menus.status')}</div>}
|
||||
{icon}
|
||||
<div className='system-sm-semibold text-text-secondary mt-2 mb-0.5'>{title}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{description}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function AppPreview({ mode }: { mode: AppMode }) {
|
||||
const { t } = useTranslation()
|
||||
const modeToPreviewInfoMap = {
|
||||
'chat': {
|
||||
title: t('app.types.chatbot'),
|
||||
description: t('app.newApp.chatbotUserDescription'),
|
||||
link: 'https://docs.dify.ai/guides/application-orchestrate/conversation-application?fallback=true',
|
||||
},
|
||||
'advanced-chat': {
|
||||
title: t('app.types.advanced'),
|
||||
description: t('app.newApp.advancedUserDescription'),
|
||||
link: 'https://docs.dify.ai/guides/workflow',
|
||||
},
|
||||
'agent-chat': {
|
||||
title: t('app.types.agent'),
|
||||
description: t('app.newApp.agentUserDescription'),
|
||||
link: 'https://docs.dify.ai/guides/application-orchestrate/agent',
|
||||
},
|
||||
'completion': {
|
||||
title: t('app.newApp.completeApp'),
|
||||
description: t('app.newApp.completionUserDescription'),
|
||||
link: null,
|
||||
},
|
||||
'workflow': {
|
||||
title: t('app.types.workflow'),
|
||||
description: t('app.newApp.workflowUserDescription'),
|
||||
link: 'https://docs.dify.ai/guides/workflow',
|
||||
},
|
||||
}
|
||||
const previewInfo = modeToPreviewInfoMap[mode]
|
||||
return <div className='px-8 py-4'>
|
||||
<h4 className='system-sm-semibold-uppercase text-text-secondary'>{previewInfo.title}</h4>
|
||||
<div className='mt-1 system-xs-regular text-text-tertiary max-w-96 min-h-8'>
|
||||
<span>{previewInfo.description}</span>
|
||||
{previewInfo.link && <Link target='_blank' href={previewInfo.link} className='text-text-accent ml-1'>{t('app.newApp.learnMore')}</Link>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function AppScreenShot({ mode, show }: { mode: AppMode; show: boolean }) {
|
||||
const theme = useContextSelector(AppsContext, state => state.theme)
|
||||
const modeToImageMap = {
|
||||
'chat': 'Chatbot',
|
||||
'advanced-chat': 'Chatflow',
|
||||
'agent-chat': 'Agent',
|
||||
'completion': 'TextGenerator',
|
||||
'workflow': 'Workflow',
|
||||
}
|
||||
return <picture>
|
||||
<source media="(resolution: 1x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}.png`} />
|
||||
<source media="(resolution: 2x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}@2x.png`} />
|
||||
<source media="(resolution: 3x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}@3x.png`} />
|
||||
<Image className={show ? '' : 'hidden'}
|
||||
src={`/screenshots/${theme}/${modeToImageMap[mode]}.png`}
|
||||
alt='App Screen Shot'
|
||||
width={664} height={448} />
|
||||
</picture>
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
.grid-bg-chat {
|
||||
background-image: url('./grid-bg-chat.svg');
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.grid-bg-completion {
|
||||
background-image: url('./grid-bg-completion.svg');
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.grid-bg-agent-chat {
|
||||
background-image: url('./grid-bg-agent-chat.svg');
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.grid-bg-workflow {
|
||||
background-image: url('./grid-bg-workflow.svg');
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.basicPic {
|
||||
background-image: url('./basic.png')
|
||||
}
|
||||
|
||||
.advancedPic {
|
||||
background-image: url('./advanced.png')
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import React, { useState } from 'react'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { RiArrowDownSLine, RiCloseCircleFill, RiExchange2Fill, RiFilter3Line } from '@remixicon/react'
|
||||
import Checkbox from '../../base/checkbox'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { Check, DotsGrid } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
|
||||
import { BubbleTextMod, ChatBot, ListSparkle, Logic } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import { type AppMode } from '@/types/app'
|
||||
export type AppSelectorProps = {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
value: Array<AppMode>
|
||||
onChange: (value: AppSelectorProps['value']) => void
|
||||
}
|
||||
|
||||
const allTypes: AppMode[] = ['chat', 'agent-chat', 'completion', 'advanced-chat', 'workflow']
|
||||
|
||||
const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
@@ -33,96 +33,136 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
|
||||
className='block'
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center gap-1 h-8 text-gray-700 text-[13px] leading-[18px] cursor-pointer px-2 rounded-lg bg-white shadow-xs hover:bg-gray-200',
|
||||
open && !value && '!bg-gray-200 hover:!bg-gray-200',
|
||||
!!value && '!bg-white hover:!bg-white',
|
||||
'flex items-center justify-between rounded-md cursor-pointer px-2 space-x-1 hover:bg-state-base-hover',
|
||||
)}>
|
||||
{!value && (
|
||||
<>
|
||||
<div className='w-4 h-4 p-[1px]'>
|
||||
<DotsGrid className='w-3.5 h-3.5' />
|
||||
</div>
|
||||
<div className=''>{t('app.typeSelector.all')}</div>
|
||||
<div className='w-4 h-4 p-[1px]'>
|
||||
<RiArrowDownSLine className='w-3.5 h-3.5' />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{value === 'chatbot' && (
|
||||
<>
|
||||
<div className='w-4 h-4 p-[1px]'>
|
||||
<ChatBot className='w-3.5 h-3.5 text-[#1570EF]' />
|
||||
</div>
|
||||
<div className=''>{t('app.typeSelector.chatbot')}</div>
|
||||
<div className='w-4 h-4 p-[1px]' onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onChange('')
|
||||
}}>
|
||||
<XCircle className='w-3.5 h-3.5 text-gray-400 cursor-pointer hover:text-gray-600' />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{value === 'agent' && (
|
||||
<>
|
||||
<div className='w-4 h-4 p-[1px]'>
|
||||
<CuteRobot className='w-3.5 h-3.5 text-indigo-600' />
|
||||
</div>
|
||||
<div className=''>{t('app.typeSelector.agent')}</div>
|
||||
<div className='w-4 h-4 p-[1px]' onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onChange('')
|
||||
}}>
|
||||
<XCircle className='w-3.5 h-3.5 text-gray-400 cursor-pointer hover:text-gray-600' />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{value === 'workflow' && (
|
||||
<>
|
||||
<div className='w-4 h-4 p-[1px]'>
|
||||
<Route className='w-3.5 h-3.5 text-[#F79009]' />
|
||||
</div>
|
||||
<div className=''>{t('app.typeSelector.workflow')}</div>
|
||||
<div className='w-4 h-4 p-[1px]' onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onChange('')
|
||||
}}>
|
||||
<XCircle className='w-3.5 h-3.5 text-gray-400 cursor-pointer hover:text-gray-600' />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<AppTypeSelectTrigger values={value} />
|
||||
{value && value.length > 0 && <div className='w-4 h-4' onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onChange([])
|
||||
}}>
|
||||
<RiCloseCircleFill className='w-3.5 h-3.5 text-text-quaternary hover:text-text-tertiary cursor-pointer' />
|
||||
</div>}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='relative p-1 w-[180px] bg-white rounded-lg shadow-xl'>
|
||||
<div className='flex items-center pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-gray-50' onClick={() => {
|
||||
onChange('chatbot')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<ChatBot className='mr-2 w-4 h-4 text-[#1570EF]' />
|
||||
<div className='grow text-gray-700 text-[13px] font-medium leading-[18px]'>{t('app.typeSelector.chatbot')}</div>
|
||||
{value === 'chatbot' && <Check className='w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
<div className='flex items-center pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-gray-50' onClick={() => {
|
||||
onChange('agent')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<CuteRobot className='mr-2 w-4 h-4 text-indigo-600' />
|
||||
<div className='grow text-gray-700 text-[13px] font-medium leading-[18px]'>{t('app.typeSelector.agent')}</div>
|
||||
{value === 'agent' && <Check className='w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
<div className='flex items-center pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-gray-50' onClick={() => {
|
||||
onChange('workflow')
|
||||
setOpen(false)
|
||||
}}>
|
||||
<Route className='mr-2 w-4 h-4 text-[#F79009]' />
|
||||
<div className='grow text-gray-700 text-[13px] font-medium leading-[18px]'>{t('app.typeSelector.workflow')}</div>
|
||||
{value === 'workflow' && <Check className='w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
</div>
|
||||
<ul className='relative p-1 w-[240px] bg-components-panel-bg-blur backdrop-blur-[5px] rounded-xl shadow-lg border border-components-panel-border'>
|
||||
{allTypes.map(mode => (
|
||||
<AppTypeSelectorItem key={mode} type={mode}
|
||||
checked={Boolean(value.length > 0 && value?.indexOf(mode) !== -1)}
|
||||
onClick={() => {
|
||||
if (value?.indexOf(mode) !== -1)
|
||||
onChange(value?.filter(v => v !== mode) ?? [])
|
||||
else
|
||||
onChange([...(value || []), mode])
|
||||
}} />
|
||||
))}
|
||||
</ul>
|
||||
</PortalToFollowElemContent>
|
||||
</div>
|
||||
</PortalToFollowElem>
|
||||
</div >
|
||||
</PortalToFollowElem >
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(AppTypeSelector)
|
||||
export default AppTypeSelector
|
||||
|
||||
function AppTypeSelectTrigger({ values }: { values: AppSelectorProps['value'] }) {
|
||||
const { t } = useTranslation()
|
||||
if (!values || values.length === 0) {
|
||||
return <div className={cn(
|
||||
'flex items-center justify-between gap-1 h-8',
|
||||
)}>
|
||||
<RiFilter3Line className='w-4 h-4 text-text-tertiary' />
|
||||
<div className='grow min-w-[65px] text-center system-sm-medium text-text-tertiary'>{t('app.typeSelector.all')}</div>
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
}
|
||||
if (values.length === 1) {
|
||||
return <div className={cn(
|
||||
'flex items-center justify-between gap-1 h-8 flex-nowrap',
|
||||
)}>
|
||||
<AppTypeIcon type={values[0]} />
|
||||
<div className='flex flex-1 items-center text-center line-clamp-1'>
|
||||
<AppTypeLabel type={values[0]} className="system-sm-medium text-components-menu-item-text" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
return <div className={cn(
|
||||
'flex items-center justify-between h-8 -space-x-2 relative',
|
||||
)}>
|
||||
{values.map((mode, index) => (<AppTypeIcon key={mode} type={mode} wrapperClassName='border border-components-panel-on-panel-item-bg' style={{ zIndex: 5 - index }} />))}
|
||||
</div>
|
||||
}
|
||||
|
||||
type AppTypeSelectorItemProps = {
|
||||
checked: boolean
|
||||
type: AppMode
|
||||
onClick: () => void
|
||||
}
|
||||
function AppTypeSelectorItem({ checked, type, onClick }: AppTypeSelectorItemProps) {
|
||||
return <li className='flex items-center space-x-2 pl-2 py-1 pr-1 rounded-lg cursor-pointer hover:bg-state-base-hover' onClick={onClick}>
|
||||
<Checkbox checked={checked} />
|
||||
<AppTypeIcon type={type} />
|
||||
<div className='grow p-1 pl-0'>
|
||||
<AppTypeLabel type={type} className="system-sm-medium text-components-menu-item-text" />
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
|
||||
type AppTypeIconProps = {
|
||||
type: AppMode
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
wrapperClassName?: string
|
||||
}
|
||||
|
||||
export function AppTypeIcon({ type, className, wrapperClassName, style }: AppTypeIconProps) {
|
||||
const wrapperClassNames = cn('w-5 h-5 inline-flex items-center justify-center rounded-md border border-divider-regular', wrapperClassName)
|
||||
const iconClassNames = cn('w-3.5 h-3.5 text-components-avatar-shape-fill-stop-100', className)
|
||||
if (type === 'chat') {
|
||||
return <div style={style} className={cn(wrapperClassNames, 'bg-components-icon-bg-blue-solid')}>
|
||||
<ChatBot className={iconClassNames} />
|
||||
</div>
|
||||
}
|
||||
if (type === 'agent-chat') {
|
||||
return <div style={style} className={cn(wrapperClassNames, 'bg-components-icon-bg-violet-solid')}>
|
||||
<Logic className={iconClassNames} />
|
||||
</div>
|
||||
}
|
||||
if (type === 'advanced-chat') {
|
||||
return <div style={style} className={cn(wrapperClassNames, 'bg-components-icon-bg-blue-light-solid')}>
|
||||
<BubbleTextMod className={iconClassNames} />
|
||||
</div>
|
||||
}
|
||||
if (type === 'workflow') {
|
||||
return <div style={style} className={cn(wrapperClassNames, 'bg-components-icon-bg-indigo-solid')}>
|
||||
<RiExchange2Fill className={iconClassNames} />
|
||||
</div>
|
||||
}
|
||||
if (type === 'completion') {
|
||||
return <div style={style} className={cn(wrapperClassNames, 'bg-components-icon-bg-teal-solid')}>
|
||||
<ListSparkle className={iconClassNames} />
|
||||
</div>
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
type AppTypeLabelProps = {
|
||||
type: AppMode
|
||||
className?: string
|
||||
}
|
||||
export function AppTypeLabel({ type, className }: AppTypeLabelProps) {
|
||||
const { t } = useTranslation()
|
||||
let label = ''
|
||||
if (type === 'chat')
|
||||
label = t('app.typeSelector.chatbot')
|
||||
if (type === 'agent-chat')
|
||||
label = t('app.typeSelector.agent')
|
||||
if (type === 'completion')
|
||||
label = t('app.typeSelector.completion')
|
||||
if (type === 'advanced-chat')
|
||||
label = t('app.typeSelector.advanced')
|
||||
if (type === 'workflow')
|
||||
label = t('app.typeSelector.workflow')
|
||||
|
||||
return <span className={className}>{label}</span>
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
import type { FC } from 'react'
|
||||
import { init } from 'emoji-mart'
|
||||
import data from '@emoji-mart/data'
|
||||
import style from './style.module.css'
|
||||
import classNames from '@/utils/classnames'
|
||||
import Image from 'next/image'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
init({ data })
|
||||
|
||||
export type AppIconProps = {
|
||||
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
|
||||
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large' | 'xl' | 'xxl'
|
||||
rounded?: boolean
|
||||
iconType?: AppIconType | null
|
||||
icon?: string
|
||||
@@ -20,7 +21,28 @@ export type AppIconProps = {
|
||||
innerIcon?: React.ReactNode
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const appIconVariants = cva(
|
||||
'flex items-center justify-center relative text-lg rounded-lg grow-0 shrink-0 overflow-hidden leading-none',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: 'w-4 h-4 text-xs',
|
||||
tiny: 'w-6 h-6 text-base',
|
||||
small: 'w-8 h-8 text-xl',
|
||||
medium: 'w-9 h-9 text-[22px]',
|
||||
large: 'w-10 h-10 text-[24px]',
|
||||
xl: 'w-12 h-12 text-[28px]',
|
||||
xxl: 'w-14 h-14 text-[32px]',
|
||||
},
|
||||
rounded: {
|
||||
true: 'rounded-full',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'medium',
|
||||
rounded: false,
|
||||
},
|
||||
})
|
||||
const AppIcon: FC<AppIconProps> = ({
|
||||
size = 'medium',
|
||||
rounded = false,
|
||||
@@ -32,23 +54,15 @@ const AppIcon: FC<AppIconProps> = ({
|
||||
innerIcon,
|
||||
onClick,
|
||||
}) => {
|
||||
const wrapperClassName = classNames(
|
||||
style.appIcon,
|
||||
size !== 'medium' && style[size],
|
||||
rounded && style.rounded,
|
||||
className ?? '',
|
||||
'overflow-hidden',
|
||||
)
|
||||
|
||||
const isValidImageIcon = iconType === 'image' && imageUrl
|
||||
|
||||
return <span
|
||||
className={wrapperClassName}
|
||||
className={classNames(appIconVariants({ size, rounded }), className)}
|
||||
style={{ background: isValidImageIcon ? undefined : (background || '#FFEAD5') }}
|
||||
onClick={onClick}
|
||||
>
|
||||
{isValidImageIcon
|
||||
? <img src={imageUrl} className="w-full h-full" alt="app icon" />
|
||||
? <Image src={imageUrl} className="w-full h-full" alt="app icon" />
|
||||
: (innerIcon || ((icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />))
|
||||
}
|
||||
</span>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
.appIcon {
|
||||
@apply flex items-center justify-center relative w-9 h-9 text-lg rounded-lg grow-0 shrink-0;
|
||||
}
|
||||
|
||||
.appIcon.large {
|
||||
@apply w-10 h-10;
|
||||
}
|
||||
|
||||
.appIcon.small {
|
||||
@apply w-8 h-8;
|
||||
}
|
||||
|
||||
.appIcon.tiny {
|
||||
@apply w-6 h-6 text-base;
|
||||
}
|
||||
|
||||
.appIcon.xs {
|
||||
@apply w-3 h-3 text-base;
|
||||
}
|
||||
|
||||
.appIcon.rounded {
|
||||
@apply rounded-full;
|
||||
}
|
||||
82
web/app/components/base/fullscreen-modal/index.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { Fragment } from 'react'
|
||||
import { RiCloseLargeLine } from '@remixicon/react'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
type IModal = {
|
||||
className?: string
|
||||
wrapperClassName?: string
|
||||
open: boolean
|
||||
onClose?: () => void
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
closable?: boolean
|
||||
overflowVisible?: boolean
|
||||
}
|
||||
|
||||
export default function FullScreenModal({
|
||||
className,
|
||||
wrapperClassName,
|
||||
open,
|
||||
onClose = () => { },
|
||||
children,
|
||||
closable = false,
|
||||
overflowVisible = false,
|
||||
}: IModal) {
|
||||
return (
|
||||
<Transition show={open} as={Fragment}>
|
||||
<Dialog as="div" className={classNames('modal-dialog', wrapperClassName)} onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-background-overlay-backdrop backdrop-blur-[6px]" />
|
||||
</Transition.Child>
|
||||
|
||||
<div
|
||||
className="fixed inset-0 h-screen w-screen p-4"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<div className="w-full h-full bg-background-default-subtle rounded-2xl border border-effects-highlight relative">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className={classNames(
|
||||
'h-full',
|
||||
overflowVisible ? 'overflow-visible' : 'overflow-hidden',
|
||||
className,
|
||||
)}>
|
||||
{closable
|
||||
&& <div
|
||||
className='absolute z-50 top-3 right-3 w-9 h-9 flex items-center justify-center rounded-[10px]
|
||||
bg-components-button-tertiary-bg hover:bg-components-button-tertiary-bg-hover cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onClose()
|
||||
}}>
|
||||
<RiCloseLargeLine className='w-3.5 h-3.5 text-components-button-tertiary-text' />
|
||||
</div>}
|
||||
{children}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 9C2 5.68629 4.68629 3 8 3H16C19.3137 3 22 5.68629 22 9V15C22 18.3137 19.3137 21 16 21H3C2.44772 21 2 20.5523 2 20V9ZM9 9C8.44772 9 8 9.44772 8 10C8 10.5523 8.44772 11 9 11H15C15.5523 11 16 10.5523 16 10C16 9.44772 15.5523 9 15 9H9ZM9 13C8.44772 13 8 13.4477 8 14C8 14.5523 8.44772 15 9 15H12C12.5523 15 13 14.5523 13 14C13 13.4477 12.5523 13 12 13H9Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 522 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 5C3.44772 5 3 5.44772 3 6C3 6.55228 3.44772 7 4 7H20C20.5523 7 21 6.55228 21 6C21 5.44772 20.5523 5 20 5H4Z" fill="black"/>
|
||||
<path d="M17.9191 9.60608C17.7616 9.2384 17.4 9 17 9C16.6 9 16.2384 9.2384 16.0809 9.60608L14.7384 12.7384L11.6061 14.0809C11.2384 14.2384 11 14.6 11 15C11 15.4 11.2384 15.7616 11.6061 15.9191L14.7384 17.2616L16.0809 20.3939C16.2384 20.7616 16.6 21 17 21C17.4 21 17.7616 20.7616 17.9191 20.3939L19.2616 17.2616L22.3939 15.9191C22.7616 15.7616 23 15.4 23 15C23 14.6 22.7616 14.2384 22.3939 14.0809L19.2616 12.7384L17.9191 9.60608Z" fill="black"/>
|
||||
<path d="M4 11C3.44772 11 3 11.4477 3 12C3 12.5523 3.44772 13 4 13H9C9.55228 13 10 12.5523 10 12C10 11.4477 9.55228 11 9 11H4Z" fill="black"/>
|
||||
<path d="M4 17C3.44772 17 3 17.4477 3 18C3 18.5523 3.44772 19 4 19H7C7.55228 19 8 18.5523 8 18C8 17.4477 7.55228 17 7 17H4Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 968 B |
@@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="logic">
|
||||
<g id="Vector">
|
||||
<path d="M12.9089 11.9999C13.913 11.9999 14.727 11.186 14.727 10.1819C14.727 9.17775 13.913 8.36376 12.9089 8.36376C11.9048 8.36376 11.0908 9.17775 11.0908 10.1819C11.0908 11.186 11.9048 11.9999 12.9089 11.9999Z" fill="black"/>
|
||||
<path d="M12.2871 1.11229C9.95219 1.3228 7.78275 2.40696 6.21264 4.14796C4.64254 5.88897 3.78749 8.15849 3.81849 10.5027V10.8763L2.09676 14.3207C2.04261 14.4277 2.01016 14.5444 2.00129 14.6639C1.99241 14.7835 2.00729 14.9037 2.04506 15.0175C2.08283 15.1313 2.14275 15.2366 2.22136 15.3271C2.29997 15.4177 2.39573 15.4918 2.50311 15.5452L3.81849 16.1979V18.3632C3.81849 19.0865 4.10581 19.7802 4.61725 20.2916C5.12869 20.803 5.82234 21.0904 6.54562 21.0904H9.27276V22.9084H19.2722V16.6606C20.5995 15.3604 21.496 13.6844 21.8409 11.8588C22.1858 10.0331 21.9625 8.14562 21.2012 6.45084C20.4398 4.75606 19.1769 3.33556 17.583 2.38094C15.989 1.42633 14.1406 0.983541 12.2871 1.11229ZM17.4542 11.0909H16.416C16.3316 11.4163 16.2016 11.7282 16.0297 12.0172L16.7651 12.7526C16.8519 12.8365 16.9212 12.9368 16.9688 13.0477C17.0165 13.1586 17.0415 13.2779 17.0426 13.3986C17.0436 13.5193 17.0206 13.639 16.9749 13.7507C16.9292 13.8624 16.8617 13.9639 16.7764 14.0493C16.691 14.1346 16.5895 14.2021 16.4778 14.2478C16.3661 14.2935 16.2464 14.3165 16.1257 14.3155C16.005 14.3144 15.8857 14.2893 15.7748 14.2417C15.6639 14.1941 15.5636 14.1248 15.4797 14.038L14.7443 13.3026C14.4553 13.4745 14.1434 13.6045 13.818 13.6889V14.727C13.818 14.9681 13.7222 15.1994 13.5517 15.3698C13.3812 15.5403 13.15 15.6361 12.9089 15.6361C12.6678 15.6361 12.4366 15.5403 12.2661 15.3698C12.0957 15.1994 11.9999 14.9681 11.9999 14.727V13.6889C11.6744 13.6045 11.3625 13.4745 11.0736 13.3026L10.3382 14.038C10.1667 14.2036 9.93708 14.2952 9.69873 14.2931C9.46038 14.2911 9.23239 14.1955 9.06384 14.0269C8.8953 13.8584 8.79969 13.6304 8.79762 13.392C8.79555 13.1537 8.88718 12.924 9.05277 12.7526L9.78818 12.0172C9.61629 11.7282 9.48622 11.4163 9.40184 11.0909H8.36371C8.12262 11.0909 7.8914 10.9951 7.72092 10.8246C7.55044 10.6541 7.45467 10.4229 7.45467 10.1818C7.45467 9.94073 7.55044 9.70951 7.72092 9.53903C7.8914 9.36855 8.12262 9.27278 8.36371 9.27278H9.40184C9.48622 8.94731 9.61629 8.63544 9.78818 8.34647L9.05277 7.61105C8.88718 7.4396 8.79555 7.20997 8.79762 6.97163C8.79969 6.73328 8.8953 6.50528 9.06384 6.33673C9.23239 6.16819 9.46038 6.07259 9.69873 6.07052C9.93708 6.06844 10.1667 6.16007 10.3382 6.32566L11.0736 7.06108C11.3625 6.88918 11.6744 6.75911 11.9999 6.67473V5.63661C11.9999 5.39551 12.0957 5.16429 12.2661 4.99381C12.4366 4.82334 12.6678 4.72756 12.9089 4.72756C13.15 4.72756 13.3812 4.82334 13.5517 4.99381C13.7222 5.16429 13.818 5.39551 13.818 5.63661V6.67473C14.1434 6.75911 14.4553 6.88918 14.7443 7.06108L15.4797 6.32566C15.5636 6.23884 15.6639 6.16958 15.7748 6.12194C15.8857 6.0743 16.005 6.04922 16.1257 6.04817C16.2464 6.04713 16.3661 6.07013 16.4778 6.11583C16.5895 6.16154 16.691 6.22904 16.7764 6.31439C16.8617 6.39975 16.9292 6.50124 16.9749 6.61296C17.0206 6.72468 17.0436 6.84438 17.0426 6.96508C17.0415 7.08578 17.0165 7.20507 16.9688 7.31598C16.9212 7.42688 16.8519 7.52719 16.7651 7.61105L16.0297 8.34647C16.2016 8.63544 16.3316 8.94731 16.416 9.27278H17.4542C17.6952 9.27278 17.9265 9.36855 18.0969 9.53903C18.2674 9.70951 18.3632 9.94073 18.3632 10.1818C18.3632 10.4229 18.2674 10.6541 18.0969 10.8246C17.9265 10.9951 17.6952 11.0909 17.4542 11.0909Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
38
web/app/components/base/icons/src/public/common/Lock.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "lock"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M8 1.75C6.27411 1.75 4.875 3.14911 4.875 4.875V6.125C3.83947 6.125 3 6.96444 3 8V12.375C3 13.4106 3.83947 14.25 4.875 14.25H11.125C12.1606 14.25 13 13.4106 13 12.375V8C13 6.96444 12.1606 6.125 11.125 6.125V4.875C11.125 3.14911 9.72587 1.75 8 1.75ZM9.875 6.125V4.875C9.875 3.83947 9.03556 3 8 3C6.96444 3 6.125 3.83947 6.125 4.875V6.125H9.875ZM8 8.625C8.34519 8.625 8.625 8.90481 8.625 9.25V11.125C8.625 11.4702 8.34519 11.75 8 11.75C7.65481 11.75 7.375 11.4702 7.375 11.125V9.25C7.375 8.90481 7.65481 8.625 8 8.625Z",
|
||||
"fill": "#155AEF"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Lock"
|
||||
}
|
||||
16
web/app/components/base/icons/src/public/common/Lock.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Lock.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Lock'
|
||||
|
||||
export default Icon
|
||||
@@ -3,6 +3,7 @@ export { default as DiagonalDividingLine } from './DiagonalDividingLine'
|
||||
export { default as Dify } from './Dify'
|
||||
export { default as Github } from './Github'
|
||||
export { default as Line3 } from './Line3'
|
||||
export { default as Lock } from './Lock'
|
||||
export { default as MessageChatSquare } from './MessageChatSquare'
|
||||
export { default as MultiPathRetrieval } from './MultiPathRetrieval'
|
||||
export { default as NTo1Retrieval } from './NTo1Retrieval'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M2 9C2 5.68629 4.68629 3 8 3H16C19.3137 3 22 5.68629 22 9V15C22 18.3137 19.3137 21 16 21H3C2.44772 21 2 20.5523 2 20V9ZM9 9C8.44772 9 8 9.44772 8 10C8 10.5523 8.44772 11 9 11H15C15.5523 11 16 10.5523 16 10C16 9.44772 15.5523 9 15 9H9ZM9 13C8.44772 13 8 13.4477 8 14C8 14.5523 8.44772 15 9 15H12C12.5523 15 13 14.5523 13 14C13 13.4477 12.5523 13 12 13H9Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "BubbleTextMod"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './BubbleTextMod.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'BubbleTextMod'
|
||||
|
||||
export default Icon
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M4 5C3.44772 5 3 5.44772 3 6C3 6.55228 3.44772 7 4 7H20C20.5523 7 21 6.55228 21 6C21 5.44772 20.5523 5 20 5H4Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M17.9191 9.60608C17.7616 9.2384 17.4 9 17 9C16.6 9 16.2384 9.2384 16.0809 9.60608L14.7384 12.7384L11.6061 14.0809C11.2384 14.2384 11 14.6 11 15C11 15.4 11.2384 15.7616 11.6061 15.9191L14.7384 17.2616L16.0809 20.3939C16.2384 20.7616 16.6 21 17 21C17.4 21 17.7616 20.7616 17.9191 20.3939L19.2616 17.2616L22.3939 15.9191C22.7616 15.7616 23 15.4 23 15C23 14.6 22.7616 14.2384 22.3939 14.0809L19.2616 12.7384L17.9191 9.60608Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M4 11C3.44772 11 3 11.4477 3 12C3 12.5523 3.44772 13 4 13H9C9.55228 13 10 12.5523 10 12C10 11.4477 9.55228 11 9 11H4Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M4 17C3.44772 17 3 17.4477 3 18C3 18.5523 3.44772 19 4 19H7C7.55228 19 8 18.5523 8 18C8 17.4477 7.55228 17 7 17H4Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ListSparkle"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ListSparkle.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'ListSparkle'
|
||||
|
||||
export default Icon
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "logic"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Vector"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12.9089 11.9999C13.913 11.9999 14.727 11.186 14.727 10.1819C14.727 9.17775 13.913 8.36376 12.9089 8.36376C11.9048 8.36376 11.0908 9.17775 11.0908 10.1819C11.0908 11.186 11.9048 11.9999 12.9089 11.9999Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12.2871 1.11229C9.95219 1.3228 7.78275 2.40696 6.21264 4.14796C4.64254 5.88897 3.78749 8.15849 3.81849 10.5027V10.8763L2.09676 14.3207C2.04261 14.4277 2.01016 14.5444 2.00129 14.6639C1.99241 14.7835 2.00729 14.9037 2.04506 15.0175C2.08283 15.1313 2.14275 15.2366 2.22136 15.3271C2.29997 15.4177 2.39573 15.4918 2.50311 15.5452L3.81849 16.1979V18.3632C3.81849 19.0865 4.10581 19.7802 4.61725 20.2916C5.12869 20.803 5.82234 21.0904 6.54562 21.0904H9.27276V22.9084H19.2722V16.6606C20.5995 15.3604 21.496 13.6844 21.8409 11.8588C22.1858 10.0331 21.9625 8.14562 21.2012 6.45084C20.4398 4.75606 19.1769 3.33556 17.583 2.38094C15.989 1.42633 14.1406 0.983541 12.2871 1.11229ZM17.4542 11.0909H16.416C16.3316 11.4163 16.2016 11.7282 16.0297 12.0172L16.7651 12.7526C16.8519 12.8365 16.9212 12.9368 16.9688 13.0477C17.0165 13.1586 17.0415 13.2779 17.0426 13.3986C17.0436 13.5193 17.0206 13.639 16.9749 13.7507C16.9292 13.8624 16.8617 13.9639 16.7764 14.0493C16.691 14.1346 16.5895 14.2021 16.4778 14.2478C16.3661 14.2935 16.2464 14.3165 16.1257 14.3155C16.005 14.3144 15.8857 14.2893 15.7748 14.2417C15.6639 14.1941 15.5636 14.1248 15.4797 14.038L14.7443 13.3026C14.4553 13.4745 14.1434 13.6045 13.818 13.6889V14.727C13.818 14.9681 13.7222 15.1994 13.5517 15.3698C13.3812 15.5403 13.15 15.6361 12.9089 15.6361C12.6678 15.6361 12.4366 15.5403 12.2661 15.3698C12.0957 15.1994 11.9999 14.9681 11.9999 14.727V13.6889C11.6744 13.6045 11.3625 13.4745 11.0736 13.3026L10.3382 14.038C10.1667 14.2036 9.93708 14.2952 9.69873 14.2931C9.46038 14.2911 9.23239 14.1955 9.06384 14.0269C8.8953 13.8584 8.79969 13.6304 8.79762 13.392C8.79555 13.1537 8.88718 12.924 9.05277 12.7526L9.78818 12.0172C9.61629 11.7282 9.48622 11.4163 9.40184 11.0909H8.36371C8.12262 11.0909 7.8914 10.9951 7.72092 10.8246C7.55044 10.6541 7.45467 10.4229 7.45467 10.1818C7.45467 9.94073 7.55044 9.70951 7.72092 9.53903C7.8914 9.36855 8.12262 9.27278 8.36371 9.27278H9.40184C9.48622 8.94731 9.61629 8.63544 9.78818 8.34647L9.05277 7.61105C8.88718 7.4396 8.79555 7.20997 8.79762 6.97163C8.79969 6.73328 8.8953 6.50528 9.06384 6.33673C9.23239 6.16819 9.46038 6.07259 9.69873 6.07052C9.93708 6.06844 10.1667 6.16007 10.3382 6.32566L11.0736 7.06108C11.3625 6.88918 11.6744 6.75911 11.9999 6.67473V5.63661C11.9999 5.39551 12.0957 5.16429 12.2661 4.99381C12.4366 4.82334 12.6678 4.72756 12.9089 4.72756C13.15 4.72756 13.3812 4.82334 13.5517 4.99381C13.7222 5.16429 13.818 5.39551 13.818 5.63661V6.67473C14.1434 6.75911 14.4553 6.88918 14.7443 7.06108L15.4797 6.32566C15.5636 6.23884 15.6639 6.16958 15.7748 6.12194C15.8857 6.0743 16.005 6.04922 16.1257 6.04817C16.2464 6.04713 16.3661 6.07013 16.4778 6.11583C16.5895 6.16154 16.691 6.22904 16.7764 6.31439C16.8617 6.39975 16.9292 6.50124 16.9749 6.61296C17.0206 6.72468 17.0436 6.84438 17.0426 6.96508C17.0415 7.08578 17.0165 7.20507 16.9688 7.31598C16.9212 7.42688 16.8519 7.52719 16.7651 7.61105L16.0297 8.34647C16.2016 8.63544 16.3316 8.94731 16.416 9.27278H17.4542C17.6952 9.27278 17.9265 9.36855 18.0969 9.53903C18.2674 9.70951 18.3632 9.94073 18.3632 10.1818C18.3632 10.4229 18.2674 10.6541 18.0969 10.8246C17.9265 10.9951 17.6952 11.0909 17.4542 11.0909Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Logic"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Logic.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Logic'
|
||||
|
||||
export default Icon
|
||||
@@ -1,7 +1,10 @@
|
||||
export { default as AiText } from './AiText'
|
||||
export { default as BubbleTextMod } from './BubbleTextMod'
|
||||
export { default as ChatBot } from './ChatBot'
|
||||
export { default as CuteRobot } from './CuteRobot'
|
||||
export { default as EditList } from './EditList'
|
||||
export { default as ListSparkle } from './ListSparkle'
|
||||
export { default as Logic } from './Logic'
|
||||
export { default as MessageDotsCircle } from './MessageDotsCircle'
|
||||
export { default as MessageFast } from './MessageFast'
|
||||
export { default as MessageHeartCircle } from './MessageHeartCircle'
|
||||
|
||||
@@ -25,8 +25,8 @@ const TabSliderNew: FC<TabSliderProps> = ({
|
||||
key={option.value}
|
||||
onClick={() => onChange(option.value)}
|
||||
className={cn(
|
||||
'mr-1 px-3 py-[7px] h-[32px] flex items-center rounded-lg border-[0.5px] border-transparent text-gray-700 text-[13px] font-medium leading-[18px] cursor-pointer hover:bg-gray-200',
|
||||
value === option.value && 'bg-white border-gray-200 shadow-xs text-primary-600 hover:bg-white',
|
||||
'mr-1 px-3 py-[7px] h-[32px] flex items-center rounded-lg border-[0.5px] border-transparent text-text-tertiary text-[13px] font-medium leading-[18px] cursor-pointer hover:bg-components-main-nav-nav-button-bg-active',
|
||||
value === option.value && 'bg-components-main-nav-nav-button-bg-active border-components-main-nav-nav-button-border shadow-xs text-components-main-nav-nav-button-text-active',
|
||||
)}
|
||||
>
|
||||
{option.icon}
|
||||
|
||||
@@ -78,24 +78,23 @@ const TagFilter: FC<TagFilterProps> = ({
|
||||
className='block'
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center gap-1 px-2 h-8 rounded-lg border-[0.5px] border-transparent bg-gray-200 cursor-pointer hover:bg-gray-300',
|
||||
open && !value.length && '!bg-gray-300 hover:bg-gray-300',
|
||||
!open && !!value.length && '!bg-white/80 shadow-xs !border-black/5 hover:!bg-gray-200',
|
||||
open && !!value.length && '!bg-gray-200 !border-black/5 shadow-xs hover:!bg-gray-200',
|
||||
'flex items-center gap-1 px-2 h-8 rounded-lg border-[0.5px] border-transparent bg-components-input-bg-normal cursor-pointer',
|
||||
!open && !!value.length && 'shadow-xs',
|
||||
open && !!value.length && 'shadow-xs',
|
||||
)}>
|
||||
<div className='p-[1px]'>
|
||||
<Tag01 className='h-3.5 w-3.5 text-gray-700' />
|
||||
<Tag01 className='h-3.5 w-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='text-[13px] leading-[18px] text-gray-700'>
|
||||
<div className='text-[13px] leading-[18px] text-text-secondary'>
|
||||
{!value.length && t('common.tag.placeholder')}
|
||||
{!!value.length && currentTag?.name}
|
||||
</div>
|
||||
{value.length > 1 && (
|
||||
<div className='text-xs font-medium leading-[18px] text-gray-500'>{`+${value.length - 1}`}</div>
|
||||
<div className='text-xs font-medium leading-[18px] text-text-tertiary'>{`+${value.length - 1}`}</div>
|
||||
)}
|
||||
{!value.length && (
|
||||
<div className='p-[1px]'>
|
||||
<RiArrowDownSLine className='h-3.5 w-3.5 text-gray-700' />
|
||||
<RiArrowDownSLine className='h-3.5 w-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
)}
|
||||
{!!value.length && (
|
||||
@@ -103,14 +102,14 @@ const TagFilter: FC<TagFilterProps> = ({
|
||||
e.stopPropagation()
|
||||
onChange([])
|
||||
}}>
|
||||
<XCircle className='h-3.5 w-3.5 text-gray-400 group-hover/clear:text-gray-600' />
|
||||
<XCircle className='h-3.5 w-3.5 text-text-tertiary group-hover/clear:text-text-secondary' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='relative w-[240px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg'>
|
||||
<div className='p-2 border-b-[0.5px] border-black/5'>
|
||||
<div className='relative w-[240px] bg-components-panel-bg-blur backdrop-blur-[5px] rounded-lg border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<div className='p-2'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
@@ -123,17 +122,17 @@ const TagFilter: FC<TagFilterProps> = ({
|
||||
{filteredTagList.map(tag => (
|
||||
<div
|
||||
key={tag.id}
|
||||
className='flex items-center gap-2 pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-gray-100'
|
||||
className='flex items-center gap-2 pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => selectTag(tag)}
|
||||
>
|
||||
<div title={tag.name} className='grow text-sm text-gray-700 leading-5 truncate'>{tag.name}</div>
|
||||
{value.includes(tag.id) && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
|
||||
<div title={tag.name} className='grow text-sm text-text-tertiary leading-5 truncate'>{tag.name}</div>
|
||||
{value.includes(tag.id) && <Check className='shrink-0 w-4 h-4 text-text-secondary' />}
|
||||
</div>
|
||||
))}
|
||||
{!filteredTagList.length && (
|
||||
<div className='p-3 flex flex-col items-center gap-1'>
|
||||
<Tag03 className='h-6 w-6 text-gray-300' />
|
||||
<div className='text-gray-500 text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
|
||||
<Tag03 className='h-6 w-6 text-text-tertiary' />
|
||||
<div className='text-text-tertiary text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ const HeaderWrapper = ({
|
||||
<div className={classNames(
|
||||
'sticky top-0 left-0 right-0 z-30 flex flex-col grow-0 shrink-0 basis-auto min-h-[56px]',
|
||||
s.header,
|
||||
isBordered ? 'border-b border-gray-200' : '',
|
||||
isBordered ? 'border-b border-divider-regular' : '',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||