Browse Source

Feat: new entry point for app creation (#10847)

NFish 4 months ago
parent
commit
a725b8bb6e
93 changed files with 1349 additions and 550 deletions
  1. 9 26
      web/app/(commonLayout)/apps/AppCard.tsx
  2. 33 9
      web/app/(commonLayout)/apps/Apps.tsx
  3. 19 9
      web/app/(commonLayout)/apps/NewAppCard.tsx
  4. 10 5
      web/app/(commonLayout)/apps/page.tsx
  5. 0 8
      web/app/(commonLayout)/list.module.css
  6. 7 7
      web/app/components/app-sidebar/app-info.tsx
  7. 60 0
      web/app/components/app/create-app-dialog/app-card/index.tsx
  8. 247 0
      web/app/components/app/create-app-dialog/app-list/index.tsx
  9. 91 0
      web/app/components/app/create-app-dialog/app-list/sidebar.tsx
  10. 13 23
      web/app/components/app/create-app-dialog/index.tsx
  11. BIN
      web/app/components/app/create-app-modal/advanced.png
  12. BIN
      web/app/components/app/create-app-modal/basic.png
  13. 0 6
      web/app/components/app/create-app-modal/grid-bg-agent-chat.svg
  14. 0 6
      web/app/components/app/create-app-modal/grid-bg-chat.svg
  15. 0 6
      web/app/components/app/create-app-modal/grid-bg-completion.svg
  16. 0 6
      web/app/components/app/create-app-modal/grid-bg-workflow.svg
  17. 272 229
      web/app/components/app/create-app-modal/index.tsx
  18. 0 23
      web/app/components/app/create-app-modal/style.module.css
  19. 133 93
      web/app/components/app/type-selector/index.tsx
  20. 28 14
      web/app/components/base/app-icon/index.tsx
  21. 0 23
      web/app/components/base/app-icon/style.module.css
  22. 82 0
      web/app/components/base/fullscreen-modal/index.tsx
  23. 3 0
      web/app/components/base/icons/assets/vender/solid/communication/bubble-text-mod.svg
  24. 6 0
      web/app/components/base/icons/assets/vender/solid/communication/list-sparkle.svg
  25. 4 0
      web/app/components/base/icons/assets/vender/solid/communication/logic.svg
  26. 38 0
      web/app/components/base/icons/src/public/common/Lock.json
  27. 16 0
      web/app/components/base/icons/src/public/common/Lock.tsx
  28. 1 0
      web/app/components/base/icons/src/public/common/index.ts
  29. 28 0
      web/app/components/base/icons/src/vender/solid/communication/BubbleTextMod.json
  30. 16 0
      web/app/components/base/icons/src/vender/solid/communication/BubbleTextMod.tsx
  31. 53 0
      web/app/components/base/icons/src/vender/solid/communication/ListSparkle.json
  32. 16 0
      web/app/components/base/icons/src/vender/solid/communication/ListSparkle.tsx
  33. 40 0
      web/app/components/base/icons/src/vender/solid/communication/Logic.json
  34. 16 0
      web/app/components/base/icons/src/vender/solid/communication/Logic.tsx
  35. 3 0
      web/app/components/base/icons/src/vender/solid/communication/index.ts
  36. 2 2
      web/app/components/base/tab-slider-new/index.tsx
  37. 15 16
      web/app/components/base/tag-management/filter.tsx
  38. 1 1
      web/app/components/header/header-wrapper.tsx
  39. 38 15
      web/i18n/en-US/app.ts
  40. 38 13
      web/i18n/zh-Hans/app.ts
  41. 1 1
      web/i18n/zh-Hant/app.ts
  42. 1 1
      web/models/explore.ts
  43. 1 1
      web/package.json
  44. BIN
      web/public/screenshots/Light/Agent.png
  45. BIN
      web/public/screenshots/Light/Agent@2x.png
  46. BIN
      web/public/screenshots/Light/Agent@3x.png
  47. BIN
      web/public/screenshots/Light/ChatFlow.png
  48. BIN
      web/public/screenshots/Light/ChatFlow@2x.png
  49. BIN
      web/public/screenshots/Light/ChatFlow@3x.png
  50. BIN
      web/public/screenshots/Light/Chatbot.png
  51. BIN
      web/public/screenshots/Light/Chatbot@2x.png
  52. BIN
      web/public/screenshots/Light/Chatbot@3x.png
  53. BIN
      web/public/screenshots/Light/Chatflow.png
  54. BIN
      web/public/screenshots/Light/Chatflow@2x.png
  55. BIN
      web/public/screenshots/Light/Chatflow@3x.png
  56. BIN
      web/public/screenshots/Light/TextGenerator.png
  57. BIN
      web/public/screenshots/Light/TextGenerator@2x.png
  58. BIN
      web/public/screenshots/Light/TextGenerator@3x.png
  59. BIN
      web/public/screenshots/Light/Workflow.png
  60. BIN
      web/public/screenshots/Light/Workflow@2x.png
  61. BIN
      web/public/screenshots/Light/Workflow@3x.png
  62. BIN
      web/public/screenshots/dark/Agent.png
  63. BIN
      web/public/screenshots/dark/Agent@2x.png
  64. BIN
      web/public/screenshots/dark/Agent@3x.png
  65. BIN
      web/public/screenshots/dark/Chatbot.png
  66. BIN
      web/public/screenshots/dark/Chatbot@2x.png
  67. BIN
      web/public/screenshots/dark/Chatbot@3x.png
  68. BIN
      web/public/screenshots/dark/Chatflow.png
  69. BIN
      web/public/screenshots/dark/Chatflow@2x.png
  70. BIN
      web/public/screenshots/dark/Chatflow@3x.png
  71. BIN
      web/public/screenshots/dark/TextGenerator.png
  72. BIN
      web/public/screenshots/dark/TextGenerator@2x.png
  73. BIN
      web/public/screenshots/dark/TextGenerator@3x.png
  74. BIN
      web/public/screenshots/dark/Workflow.png
  75. BIN
      web/public/screenshots/dark/Workflow@2x.png
  76. BIN
      web/public/screenshots/dark/Workflow@3x.png
  77. BIN
      web/public/screenshots/light/Agent.png
  78. BIN
      web/public/screenshots/light/Agent@2x.png
  79. BIN
      web/public/screenshots/light/Agent@3x.png
  80. BIN
      web/public/screenshots/light/Chatbot.png
  81. BIN
      web/public/screenshots/light/Chatbot@2x.png
  82. BIN
      web/public/screenshots/light/Chatbot@3x.png
  83. BIN
      web/public/screenshots/light/Chatflow.png
  84. BIN
      web/public/screenshots/light/Chatflow@2x.png
  85. BIN
      web/public/screenshots/light/Chatflow@3x.png
  86. BIN
      web/public/screenshots/light/TextGenerator.png
  87. BIN
      web/public/screenshots/light/TextGenerator@2x.png
  88. BIN
      web/public/screenshots/light/TextGenerator@3x.png
  89. BIN
      web/public/screenshots/light/Workflow.png
  90. BIN
      web/public/screenshots/light/Workflow@2x.png
  91. BIN
      web/public/screenshots/light/Workflow@3x.png
  92. 4 3
      web/tailwind.config.js
  93. 4 4
      web/yarn.lock

+ 9 - 26
web/app/(commonLayout)/apps/AppCard.tsx

@@ -21,8 +21,6 @@ import Divider from '@/app/components/base/divider'
 import { getRedirection } from '@/utils/app-redirection'
 import { getRedirection } from '@/utils/app-redirection'
 import { useProviderContext } from '@/context/provider-context'
 import { useProviderContext } from '@/context/provider-context'
 import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
 import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
-import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
-import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
 import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
 import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
 import EditAppModal from '@/app/components/explore/create-app-modal'
 import EditAppModal from '@/app/components/explore/create-app-modal'
 import SwitchAppModal from '@/app/components/app/switch-app-modal'
 import SwitchAppModal from '@/app/components/app/switch-app-modal'
@@ -32,6 +30,7 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types'
 import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
 import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
 import { fetchWorkflowDraft } from '@/service/workflow'
 import { fetchWorkflowDraft } from '@/service/workflow'
 import { fetchInstalledAppList } from '@/service/explore'
 import { fetchInstalledAppList } from '@/service/explore'
+import { AppTypeIcon } from '@/app/components/app/type-selector'
 
 
 export type AppCardProps = {
 export type AppCardProps = {
   app: App
   app: App
@@ -277,7 +276,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
           e.preventDefault()
           e.preventDefault()
           getRedirection(isCurrentWorkspaceEditor, app, push)
           getRedirection(isCurrentWorkspaceEditor, app, push)
         }}
         }}
-        className='relative group col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-sm flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
+        className='relative h-[160px] group col-span-1 bg-components-card-bg border-[1px] border-solid border-components-card-border rounded-xl shadow-sm inline-flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
       >
       >
         <div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
         <div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
           <div className='relative shrink-0'>
           <div className='relative shrink-0'>
@@ -288,30 +287,14 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
               background={app.icon_background}
               background={app.icon_background}
               imageUrl={app.icon_url}
               imageUrl={app.icon_url}
             />
             />
-            <span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
-              {app.mode === 'advanced-chat' && (
-                <ChatBot className='w-3 h-3 text-[#1570EF]' />
-              )}
-              {app.mode === 'agent-chat' && (
-                <CuteRobot className='w-3 h-3 text-indigo-600' />
-              )}
-              {app.mode === 'chat' && (
-                <ChatBot className='w-3 h-3 text-[#1570EF]' />
-              )}
-              {app.mode === 'completion' && (
-                <AiText className='w-3 h-3 text-[#0E9384]' />
-              )}
-              {app.mode === 'workflow' && (
-                <Route className='w-3 h-3 text-[#f79009]' />
-              )}
-            </span>
+            <AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='w-3 h-3' />
           </div>
           </div>
           <div className='grow w-0 py-[1px]'>
           <div className='grow w-0 py-[1px]'>
-            <div className='flex items-center text-sm leading-5 font-semibold text-gray-800'>
+            <div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'>
               <div className='truncate' title={app.name}>{app.name}</div>
               <div className='truncate' title={app.name}>{app.name}</div>
             </div>
             </div>
-            <div className='flex items-center text-[10px] leading-[18px] text-gray-500 font-medium'>
-              {app.mode === 'advanced-chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>}
+            <div className='flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium'>
+              {app.mode === 'advanced-chat' && <div className='truncate'>{t('app.types.advanced').toUpperCase()}</div>}
               {app.mode === 'chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>}
               {app.mode === 'chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>}
               {app.mode === 'agent-chat' && <div className='truncate'>{t('app.types.agent').toUpperCase()}</div>}
               {app.mode === 'agent-chat' && <div className='truncate'>{t('app.types.agent').toUpperCase()}</div>}
               {app.mode === 'workflow' && <div className='truncate'>{t('app.types.workflow').toUpperCase()}</div>}
               {app.mode === 'workflow' && <div className='truncate'>{t('app.types.workflow').toUpperCase()}</div>}
@@ -319,7 +302,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
-        <div className='title-wrapper h-[90px] px-[14px] text-xs leading-normal text-gray-500'>
+        <div className='title-wrapper h-[90px] px-[14px] text-xs leading-normal text-text-tertiary'>
           <div
           <div
             className={cn(tags.length ? 'line-clamp-2' : 'line-clamp-4', 'group-hover:line-clamp-2')}
             className={cn(tags.length ? 'line-clamp-2' : 'line-clamp-4', 'group-hover:line-clamp-2')}
             title={app.description}
             title={app.description}
@@ -352,7 +335,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
                   />
                   />
                 </div>
                 </div>
               </div>
               </div>
-              <div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-gray-200' />
+              <div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px]' />
               <div className='!hidden group-hover:!flex shrink-0'>
               <div className='!hidden group-hover:!flex shrink-0'>
                 <CustomPopover
                 <CustomPopover
                   htmlContent={<Operations />}
                   htmlContent={<Operations />}
@@ -362,7 +345,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
                     <div
                     <div
                       className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
                       className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
                     >
                     >
-                      <RiMoreFill className='w-4 h-4 text-gray-700' />
+                      <RiMoreFill className='w-4 h-4 text-text-tertiary' />
                     </div>
                     </div>
                   }
                   }
                   btnClassName={open =>
                   btnClassName={open =>

+ 33 - 9
web/app/(commonLayout)/apps/Apps.tsx

@@ -125,7 +125,7 @@ const Apps = () => {
 
 
   return (
   return (
     <>
     <>
-      <div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-10 flex-wrap gap-y-2'>
+      <div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
         <TabSliderNew
         <TabSliderNew
           value={activeTab}
           value={activeTab}
           onChange={setActiveTab}
           onChange={setActiveTab}
@@ -143,14 +143,20 @@ const Apps = () => {
           />
           />
         </div>
         </div>
       </div>
       </div>
-      <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
-        {isCurrentWorkspaceEditor
-          && <NewAppCard onSuccess={mutate} />}
-        {data?.map(({ data: apps }) => apps.map(app => (
-          <AppCard key={app.id} app={app} onRefresh={mutate} />
-        )))}
-        <CheckModal />
-      </nav>
+      {(data && data[0].total > 0)
+        ? <div className='grid content-start grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6 gap-4 px-12 pt-2 grow relative'>
+          {isCurrentWorkspaceEditor
+            && <NewAppCard onSuccess={mutate} />}
+          {data.map(({ data: apps }) => apps.map(app => (
+            <AppCard key={app.id} app={app} onRefresh={mutate} />
+          )))}
+        </div>
+        : <div className='grid content-start grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6 gap-4 px-12 pt-2 grow relative overflow-hidden'>
+          {isCurrentWorkspaceEditor
+            && <NewAppCard className='z-10' onSuccess={mutate} />}
+          <NoAppsFound />
+        </div>}
+      <CheckModal />
       <div ref={anchorRef} className='h-0'> </div>
       <div ref={anchorRef} className='h-0'> </div>
       {showTagManagementModal && (
       {showTagManagementModal && (
         <TagManagementModal type='app' show={showTagManagementModal} />
         <TagManagementModal type='app' show={showTagManagementModal} />
@@ -160,3 +166,21 @@ const Apps = () => {
 }
 }
 
 
 export default Apps
 export default Apps
+
+function NoAppsFound() {
+  const { t } = useTranslation()
+  function renderDefaultCard() {
+    const defaultCards = Array.from({ length: 36 }, (_, index) => (
+      <div key={index} className='h-[160px] inline-flex rounded-xl bg-background-default-lighter'></div>
+    ))
+    return defaultCards
+  }
+  return (
+    <>
+      {renderDefaultCard()}
+      <div className='absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent'>
+        <span className='system-md-medium text-text-tertiary'>{t('app.newApp.noAppsFound')}</span>
+      </div>
+    </>
+  )
+}

+ 19 - 9
web/app/(commonLayout)/apps/NewAppCard.tsx

@@ -11,13 +11,15 @@ import CreateAppModal from '@/app/components/app/create-app-modal'
 import CreateFromDSLModal, { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal'
 import CreateFromDSLModal, { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal'
 import { useProviderContext } from '@/context/provider-context'
 import { useProviderContext } from '@/context/provider-context'
 import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
 import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
+import cn from '@/utils/classnames'
 
 
 export type CreateAppCardProps = {
 export type CreateAppCardProps = {
+  className?: string
   onSuccess?: () => void
   onSuccess?: () => void
 }
 }
 
 
 // eslint-disable-next-line react/display-name
 // eslint-disable-next-line react/display-name
-const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuccess }, ref) => {
+const CreateAppCard = forwardRef<HTMLDivElement, CreateAppCardProps>(({ className, onSuccess }, ref) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { onPlanInfoChanged } = useProviderContext()
   const { onPlanInfoChanged } = useProviderContext()
   const searchParams = useSearchParams()
   const searchParams = useSearchParams()
@@ -36,26 +38,26 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
   }, [dslUrl])
   }, [dslUrl])
 
 
   return (
   return (
-    <a
+    <div
       ref={ref}
       ref={ref}
-      className='relative col-span-1 flex flex-col justify-between min-h-[160px] bg-gray-200 rounded-xl border-[0.5px] border-black/5'
+      className={cn('relative col-span-1 inline-flex flex-col justify-between h-[160px] bg-components-card-bg rounded-xl border-[0.5px] border-components-card-border', className)}
     >
     >
       <div className='grow p-2 rounded-t-xl'>
       <div className='grow p-2 rounded-t-xl'>
-        <div className='px-6 pt-2 pb-1 text-xs font-medium leading-[18px] text-gray-500'>{t('app.createApp')}</div>
-        <div className='flex items-center mb-1 px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-gray-600 cursor-pointer hover:text-primary-600 hover:bg-white' onClick={() => setShowNewAppModal(true)}>
+        <div className='px-6 pt-2 pb-1 text-xs font-medium leading-[18px] text-text-tertiary'>{t('app.createApp')}</div>
+        <div className='flex items-center mb-1 px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover' onClick={() => setShowNewAppModal(true)}>
           <FilePlus01 className='shrink-0 mr-2 w-4 h-4' />
           <FilePlus01 className='shrink-0 mr-2 w-4 h-4' />
           {t('app.newApp.startFromBlank')}
           {t('app.newApp.startFromBlank')}
         </div>
         </div>
-        <div className='flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-gray-600 cursor-pointer hover:text-primary-600 hover:bg-white' onClick={() => setShowNewAppTemplateDialog(true)}>
+        <div className='flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover' onClick={() => setShowNewAppTemplateDialog(true)}>
           <FilePlus02 className='shrink-0 mr-2 w-4 h-4' />
           <FilePlus02 className='shrink-0 mr-2 w-4 h-4' />
           {t('app.newApp.startFromTemplate')}
           {t('app.newApp.startFromTemplate')}
         </div>
         </div>
       </div>
       </div>
       <div
       <div
-        className='p-2 border-t-[0.5px] border-black/5 rounded-b-xl'
+        className='p-2 border-t-[0.5px] border-components-card-border rounded-b-xl'
         onClick={() => setShowCreateFromDSLModal(true)}
         onClick={() => setShowCreateFromDSLModal(true)}
       >
       >
-        <div className='flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-gray-600 cursor-pointer hover:text-primary-600 hover:bg-white'>
+        <div className='flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover'>
           <FileArrow01 className='shrink-0 mr-2 w-4 h-4' />
           <FileArrow01 className='shrink-0 mr-2 w-4 h-4' />
           {t('app.importDSL')}
           {t('app.importDSL')}
         </div>
         </div>
@@ -68,6 +70,10 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
           if (onSuccess)
           if (onSuccess)
             onSuccess()
             onSuccess()
         }}
         }}
+        onCreateFromTemplate={() => {
+          setShowNewAppTemplateDialog(true)
+          setShowNewAppModal(false)
+        }}
       />
       />
       <CreateAppTemplateDialog
       <CreateAppTemplateDialog
         show={showNewAppTemplateDialog}
         show={showNewAppTemplateDialog}
@@ -77,6 +83,10 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
           if (onSuccess)
           if (onSuccess)
             onSuccess()
             onSuccess()
         }}
         }}
+        onCreateFromBlank={() => {
+          setShowNewAppModal(true)
+          setShowNewAppTemplateDialog(false)
+        }}
       />
       />
       <CreateFromDSLModal
       <CreateFromDSLModal
         show={showCreateFromDSLModal}
         show={showCreateFromDSLModal}
@@ -94,7 +104,7 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
             onSuccess()
             onSuccess()
         }}
         }}
       />
       />
-    </a>
+    </div>
   )
   )
 })
 })
 
 

+ 10 - 5
web/app/(commonLayout)/apps/page.tsx

@@ -1,9 +1,10 @@
 'use client'
 'use client'
 import { useContextSelector } from 'use-context-selector'
 import { useContextSelector } from 'use-context-selector'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
+import { RiDiscordFill, RiGithubFill } from '@remixicon/react'
+import Link from 'next/link'
 import style from '../list.module.css'
 import style from '../list.module.css'
 import Apps from './Apps'
 import Apps from './Apps'
-import classNames from '@/utils/classnames'
 import AppContext from '@/context/app-context'
 import AppContext from '@/context/app-context'
 import { LicenseStatus } from '@/types/feature'
 import { LicenseStatus } from '@/types/feature'
 
 
@@ -12,14 +13,18 @@ const AppList = () => {
   const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
   const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
 
 
   return (
   return (
-    <div className='relative flex flex-col overflow-y-auto bg-gray-100 shrink-0 h-0 grow'>
+    <div className='relative flex flex-col overflow-y-auto bg-background-body shrink-0 h-0 grow'>
       <Apps />
       <Apps />
       {systemFeatures.license.status === LicenseStatus.NONE && <footer className='px-12 py-6 grow-0 shrink-0'>
       {systemFeatures.license.status === LicenseStatus.NONE && <footer className='px-12 py-6 grow-0 shrink-0'>
         <h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3>
         <h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3>
-        <p className='mt-1 text-sm font-normal leading-tight text-gray-700'>{t('app.communityIntro')}</p>
+        <p className='mt-1 system-sm-regular text-text-tertiary'>{t('app.communityIntro')}</p>
         <div className='flex items-center gap-2 mt-3'>
         <div className='flex items-center gap-2 mt-3'>
-          <a className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://github.com/langgenius/dify'><span className={classNames(style.socialMediaIcon, style.githubIcon)} /></a>
-          <a className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://discord.gg/FngNHpbcY7'><span className={classNames(style.socialMediaIcon, style.discordIcon)} /></a>
+          <Link className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://github.com/langgenius/dify'>
+            <RiGithubFill className='w-5 h-5 text-text-tertiary' />
+          </Link>
+          <Link className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://discord.gg/FngNHpbcY7'>
+            <RiDiscordFill className='w-5 h-5 text-text-tertiary' />
+          </Link>
         </div>
         </div>
       </footer>}
       </footer>}
     </div >
     </div >

+ 0 - 8
web/app/(commonLayout)/list.module.css

@@ -201,14 +201,6 @@
   @apply block w-6 h-6 bg-center bg-contain;
   @apply block w-6 h-6 bg-center bg-contain;
 }
 }
 
 
-.githubIcon {
-  background-image: url("./apps/assets/github.svg");
-}
-
-.discordIcon {
-  background-image: url("./apps/assets/discord.svg");
-}
-
 /* #region new app dialog */
 /* #region new app dialog */
 .newItemCaption {
 .newItemCaption {
   @apply inline-flex items-center mb-2 text-sm font-medium;
   @apply inline-flex items-center mb-2 text-sm font-medium;

+ 7 - 7
web/app/components/app-sidebar/app-info.tsx

@@ -237,7 +237,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
                   {appDetail.mode === 'advanced-chat' && (
                   {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 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' && (
                   {appDetail.mode === 'agent-chat' && (
@@ -246,13 +246,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
                   {appDetail.mode === 'chat' && (
                   {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 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' && (
                   {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 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' && (
                   {appDetail.mode === 'workflow' && (
@@ -299,7 +299,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
                   {appDetail.mode === 'advanced-chat' && (
                   {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 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' && (
                   {appDetail.mode === 'agent-chat' && (
@@ -308,13 +308,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
                   {appDetail.mode === 'chat' && (
                   {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 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' && (
                   {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 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' && (
                   {appDetail.mode === 'workflow' && (
@@ -398,7 +398,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
               )} />
               )} />
               <div className='px-4 pb-2'>
               <div className='px-4 pb-2'>
                 <div className='flex items-center gap-1 text-gray-700 text-md leading-6 font-semibold'>
                 <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>
                   <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>
                 <div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div>
                 <div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div>

+ 60 - 0
web/app/components/app/create-app-dialog/app-card/index.tsx

@@ -0,0 +1,60 @@
+'use client'
+import { useTranslation } from 'react-i18next'
+import { PlusIcon } from '@heroicons/react/20/solid'
+import { AppTypeIcon, AppTypeLabel } from '../../type-selector'
+import Button from '@/app/components/base/button'
+import cn from '@/utils/classnames'
+import type { App } from '@/models/explore'
+import AppIcon from '@/app/components/base/app-icon'
+
+export type AppCardProps = {
+  app: App
+  canCreate: boolean
+  onCreate: () => void
+}
+
+const AppCard = ({
+  app,
+  onCreate,
+}: AppCardProps) => {
+  const { t } = useTranslation()
+  const { app: appBasicInfo } = app
+  return (
+    <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 - 0
web/app/components/app/create-app-dialog/app-list/index.tsx

@@ -0,0 +1,247 @@
+'use client'
+
+import React, { useMemo, useState } from 'react'
+import { useRouter } from 'next/navigation'
+import { useTranslation } from 'react-i18next'
+import { useContext } from 'use-context-selector'
+import useSWR from 'swr'
+import { useDebounceFn } from 'ahooks'
+import { RiRobot2Line } from '@remixicon/react'
+import AppCard from '../app-card'
+import Sidebar, { AppCategories, AppCategoryLabel } from './sidebar'
+import Toast from '@/app/components/base/toast'
+import Divider from '@/app/components/base/divider'
+import cn from '@/utils/classnames'
+import ExploreContext from '@/context/explore-context'
+import type { App } from '@/models/explore'
+import { fetchAppDetail, fetchAppList } from '@/service/explore'
+import { importDSL } from '@/service/apps'
+import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
+import CreateAppModal from '@/app/components/explore/create-app-modal'
+import AppTypeSelector from '@/app/components/app/type-selector'
+import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
+import Loading from '@/app/components/base/loading'
+import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
+import { useAppContext } from '@/context/app-context'
+import { getRedirection } from '@/utils/app-redirection'
+import Input from '@/app/components/base/input'
+import type { AppMode } from '@/types/app'
+import { DSLImportMode } from '@/models/app'
+
+type AppsProps = {
+  onSuccess?: () => void
+  onCreateFromBlank?: () => void
+}
+
+// export enum PageType {
+//   EXPLORE = 'explore',
+//   CREATE = 'create',
+// }
+
+const Apps = ({
+  onSuccess,
+  onCreateFromBlank,
+}: AppsProps) => {
+  const { t } = useTranslation()
+  const { isCurrentWorkspaceEditor } = useAppContext()
+  const { push } = useRouter()
+  const { hasEditPermission } = useContext(ExploreContext)
+  const allCategoriesEn = AppCategories.RECOMMENDED
+
+  const [keywords, setKeywords] = useState('')
+  const [searchKeywords, setSearchKeywords] = useState('')
+
+  const { run: handleSearch } = useDebounceFn(() => {
+    setSearchKeywords(keywords)
+  }, { wait: 500 })
+
+  const handleKeywordsChange = (value: string) => {
+    setKeywords(value)
+    handleSearch()
+  }
+
+  const [currentType, setCurrentType] = useState<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>
+}

+ 91 - 0
web/app/components/app/create-app-dialog/app-list/sidebar.tsx

@@ -0,0 +1,91 @@
+'use client'
+import { RiAppsFill, RiChatSmileAiFill, RiExchange2Fill, RiPassPendingFill, RiQuillPenAiFill, RiSpeakAiFill, RiStickyNoteAddLine, RiTerminalBoxFill, RiThumbUpFill } from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+import classNames from '@/utils/classnames'
+import Divider from '@/app/components/base/divider'
+
+export enum AppCategories {
+  RECOMMENDED = 'Recommended',
+  ASSISTANT = 'Assistant',
+  AGENT = 'Agent',
+  HR = 'HR',
+  PROGRAMMING = 'Programming',
+  WORKFLOW = 'Workflow',
+  WRITING = 'Writing',
+}
+
+type SidebarProps = {
+  current: AppCategories
+  onClick?: (category: AppCategories) => void
+  onCreateFromBlank?: () => void
+}
+
+export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) {
+  const { t } = useTranslation()
+  return <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' />
+}

+ 13 - 23
web/app/components/app/create-app-dialog/index.tsx

@@ -1,36 +1,26 @@
 'use client'
 '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 = {
 type CreateAppDialogProps = {
   show: boolean
   show: boolean
   onSuccess: () => void
   onSuccess: () => void
   onClose: () => void
   onClose: () => void
+  onCreateFromBlank?: () => void
 }
 }
 
 
-const CreateAppTemplateDialog = ({ show, onSuccess, onClose }: CreateAppDialogProps) => {
-  const { t } = useTranslation()
-
+const CreateAppTemplateDialog = ({ show, onSuccess, onClose, onCreateFromBlank }: CreateAppDialogProps) => {
   return (
   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>
   )
   )
 }
 }
 
 

BIN
web/app/components/app/create-app-modal/advanced.png


BIN
web/app/components/app/create-app-modal/basic.png


File diff suppressed because it is too large
+ 0 - 6
web/app/components/app/create-app-modal/grid-bg-agent-chat.svg


File diff suppressed because it is too large
+ 0 - 6
web/app/components/app/create-app-modal/grid-bg-chat.svg


File diff suppressed because it is too large
+ 0 - 6
web/app/components/app/create-app-modal/grid-bg-completion.svg


File diff suppressed because it is too large
+ 0 - 6
web/app/components/app/create-app-modal/grid-bg-workflow.svg


+ 272 - 229
web/app/components/app/create-app-modal/index.tsx

@@ -1,48 +1,46 @@
 'use client'
 'use client'
-import type { MouseEventHandler } from 'react'
+
 import { useCallback, useRef, useState } from 'react'
 import { useCallback, useRef, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
-import {
-  RiCloseLine,
-  RiQuestionLine,
-} from '@remixicon/react'
+
 import { useRouter } from 'next/navigation'
 import { useRouter } from 'next/navigation'
 import { useContext, useContextSelector } from 'use-context-selector'
 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 AppIconPicker from '../../base/app-icon-picker'
 import type { AppIconSelection } 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 cn from '@/utils/classnames'
 import AppsContext, { useAppContext } from '@/context/app-context'
 import AppsContext, { useAppContext } from '@/context/app-context'
 import { useProviderContext } from '@/context/provider-context'
 import { useProviderContext } from '@/context/provider-context'
 import { ToastContext } from '@/app/components/base/toast'
 import { ToastContext } from '@/app/components/base/toast'
 import type { AppMode } from '@/types/app'
 import type { AppMode } from '@/types/app'
 import { createApp } from '@/service/apps'
 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 Input from '@/app/components/base/input'
 import Textarea from '@/app/components/base/textarea'
 import Textarea from '@/app/components/base/textarea'
 import AppIcon from '@/app/components/base/app-icon'
 import AppIcon from '@/app/components/base/app-icon'
 import AppsFull from '@/app/components/billing/apps-full-in-dialog'
 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 { NEED_REFRESH_APP_LIST_KEY } from '@/config'
 import { getRedirection } from '@/utils/app-redirection'
 import { getRedirection } from '@/utils/app-redirection'
+import FullScreenModal from '@/app/components/base/fullscreen-modal'
 
 
-type CreateAppDialogProps = {
-  show: boolean
+type CreateAppProps = {
   onSuccess: () => void
   onSuccess: () => void
   onClose: () => void
   onClose: () => void
+  onCreateFromTemplate?: () => void
 }
 }
 
 
-const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => {
+function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { push } = useRouter()
   const { push } = useRouter()
   const { notify } = useContext(ToastContext)
   const { notify } = useContext(ToastContext)
   const mutateApps = useContextSelector(AppsContext, state => state.mutateApps)
   const mutateApps = useContextSelector(AppsContext, state => state.mutateApps)
 
 
   const [appMode, setAppMode] = useState<AppMode>('chat')
   const [appMode, setAppMode] = useState<AppMode>('chat')
-  const [showChatBotType, setShowChatBotType] = useState<boolean>(true)
   const [appIcon, setAppIcon] = useState<AppIconSelection>({ type: 'emoji', icon: '🤖', background: '#FFEAD5' })
   const [appIcon, setAppIcon] = useState<AppIconSelection>({ type: 'emoji', icon: '🤖', background: '#FFEAD5' })
   const [showAppIconPicker, setShowAppIconPicker] = useState(false)
   const [showAppIconPicker, setShowAppIconPicker] = useState(false)
   const [name, setName] = useState('')
   const [name, setName] = useState('')
@@ -53,7 +51,8 @@ const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => {
   const { isCurrentWorkspaceEditor } = useAppContext()
   const { isCurrentWorkspaceEditor } = useAppContext()
 
 
   const isCreatingRef = useRef(false)
   const isCreatingRef = useRef(false)
-  const onCreate: MouseEventHandler = useCallback(async () => {
+
+  const onCreate = useCallback(async () => {
     if (!appMode) {
     if (!appMode) {
       notify({ type: 'error', message: t('app.newApp.appTypeRequired') })
       notify({ type: 'error', message: t('app.newApp.appTypeRequired') })
       return
       return
@@ -87,237 +86,281 @@ const CreateAppModal = ({ show, onSuccess, onClose }: CreateAppDialogProps) => {
     isCreatingRef.current = false
     isCreatingRef.current = false
   }, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceEditor])
   }, [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>
+  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='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>
             </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>
+            <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>
-            }
-          >
-            <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>
             </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>
+            <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>
+              <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>
-          </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>
+              <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>
-            }
-          >
-            <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>
+              <Textarea
+                className='resize-none'
+                placeholder={t('app.newApp.appDescriptionPlaceholder') || ''}
+                value={description}
+                onChange={e => setDescription(e.target.value)}
+              />
             </div>
             </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>
+          <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='mt-[2px] text-gray-500 text-xs leading-[18px]'>{t('app.newApp.basicTip')}</div>
             </div>
             </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 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>
                 </div>
-              </div>
-              <div className='mt-[2px] text-gray-500 text-xs leading-[18px]'>{t('app.newApp.advancedFor')}</div>
+              </Button>
             </div>
             </div>
           </div>
           </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>
-        {showAppIconPicker && <AppIconPicker
-          onSelect={(payload) => {
-            setAppIcon(payload)
-            setShowAppIconPicker(false)
-          }}
-          onClose={() => {
-            setShowAppIconPicker(false)
-          }}
-        />}
       </div>
       </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 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>
       </div>
       </div>
-      {isAppsFull && (
+    </div>
+    {
+      isAppsFull && (
         <div className='px-8 py-2'>
         <div className='px-8 py-2'>
           <AppsFull loc='app-create' />
           <AppsFull loc='app-create' />
         </div>
         </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
 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>
+}

+ 0 - 23
web/app/components/app/create-app-modal/style.module.css

@@ -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')
-}

+ 133 - 93
web/app/components/app/type-selector/index.tsx

@@ -1,23 +1,23 @@
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import React, { useState } from 'react'
 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 cn from '@/utils/classnames'
 import {
 import {
   PortalToFollowElem,
   PortalToFollowElem,
   PortalToFollowElemContent,
   PortalToFollowElemContent,
   PortalToFollowElemTrigger,
   PortalToFollowElemTrigger,
 } from '@/app/components/base/portal-to-follow-elem'
 } 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 = {
 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 AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
-  const { t } = useTranslation()
   const [open, setOpen] = useState(false)
   const [open, setOpen] = useState(false)
 
 
   return (
   return (
@@ -33,96 +33,136 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
           className='block'
           className='block'
         >
         >
           <div className={cn(
           <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>
           </div>
         </PortalToFollowElemTrigger>
         </PortalToFollowElemTrigger>
         <PortalToFollowElemContent className='z-[1002]'>
         <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>
         </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>
+}

+ 28 - 14
web/app/components/base/app-icon/index.tsx

@@ -3,14 +3,15 @@
 import type { FC } from 'react'
 import type { FC } from 'react'
 import { init } from 'emoji-mart'
 import { init } from 'emoji-mart'
 import data from '@emoji-mart/data'
 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 type { AppIconType } from '@/types/app'
+import classNames from '@/utils/classnames'
 
 
 init({ data })
 init({ data })
 
 
 export type AppIconProps = {
 export type AppIconProps = {
-  size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
+  size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large' | 'xl' | 'xxl'
   rounded?: boolean
   rounded?: boolean
   iconType?: AppIconType | null
   iconType?: AppIconType | null
   icon?: string
   icon?: string
@@ -20,7 +21,28 @@ export type AppIconProps = {
   innerIcon?: React.ReactNode
   innerIcon?: React.ReactNode
   onClick?: () => void
   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> = ({
 const AppIcon: FC<AppIconProps> = ({
   size = 'medium',
   size = 'medium',
   rounded = false,
   rounded = false,
@@ -32,23 +54,15 @@ const AppIcon: FC<AppIconProps> = ({
   innerIcon,
   innerIcon,
   onClick,
   onClick,
 }) => {
 }) => {
-  const wrapperClassName = classNames(
-    style.appIcon,
-    size !== 'medium' && style[size],
-    rounded && style.rounded,
-    className ?? '',
-    'overflow-hidden',
-  )
-
   const isValidImageIcon = iconType === 'image' && imageUrl
   const isValidImageIcon = iconType === 'image' && imageUrl
 
 
   return <span
   return <span
-    className={wrapperClassName}
+    className={classNames(appIconVariants({ size, rounded }), className)}
     style={{ background: isValidImageIcon ? undefined : (background || '#FFEAD5') }}
     style={{ background: isValidImageIcon ? undefined : (background || '#FFEAD5') }}
     onClick={onClick}
     onClick={onClick}
   >
   >
     {isValidImageIcon
     {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='🤖' />))
       : (innerIcon || ((icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />))
     }
     }
   </span>
   </span>

+ 0 - 23
web/app/components/base/app-icon/style.module.css

@@ -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 - 0
web/app/components/base/fullscreen-modal/index.tsx

@@ -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>
+  )
+}

+ 3 - 0
web/app/components/base/icons/assets/vender/solid/communication/bubble-text-mod.svg

@@ -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>

+ 6 - 0
web/app/components/base/icons/assets/vender/solid/communication/list-sparkle.svg

@@ -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>

File diff suppressed because it is too large
+ 4 - 0
web/app/components/base/icons/assets/vender/solid/communication/logic.svg


+ 38 - 0
web/app/components/base/icons/src/public/common/Lock.json

@@ -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 - 0
web/app/components/base/icons/src/public/common/Lock.tsx

@@ -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

+ 1 - 0
web/app/components/base/icons/src/public/common/index.ts

@@ -3,6 +3,7 @@ export { default as DiagonalDividingLine } from './DiagonalDividingLine'
 export { default as Dify } from './Dify'
 export { default as Dify } from './Dify'
 export { default as Github } from './Github'
 export { default as Github } from './Github'
 export { default as Line3 } from './Line3'
 export { default as Line3 } from './Line3'
+export { default as Lock } from './Lock'
 export { default as MessageChatSquare } from './MessageChatSquare'
 export { default as MessageChatSquare } from './MessageChatSquare'
 export { default as MultiPathRetrieval } from './MultiPathRetrieval'
 export { default as MultiPathRetrieval } from './MultiPathRetrieval'
 export { default as NTo1Retrieval } from './NTo1Retrieval'
 export { default as NTo1Retrieval } from './NTo1Retrieval'

+ 28 - 0
web/app/components/base/icons/src/vender/solid/communication/BubbleTextMod.json

@@ -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"
+}

+ 16 - 0
web/app/components/base/icons/src/vender/solid/communication/BubbleTextMod.tsx

@@ -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

+ 53 - 0
web/app/components/base/icons/src/vender/solid/communication/ListSparkle.json

@@ -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"
+}

+ 16 - 0
web/app/components/base/icons/src/vender/solid/communication/ListSparkle.tsx

@@ -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

File diff suppressed because it is too large
+ 40 - 0
web/app/components/base/icons/src/vender/solid/communication/Logic.json


+ 16 - 0
web/app/components/base/icons/src/vender/solid/communication/Logic.tsx

@@ -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

+ 3 - 0
web/app/components/base/icons/src/vender/solid/communication/index.ts

@@ -1,7 +1,10 @@
 export { default as AiText } from './AiText'
 export { default as AiText } from './AiText'
+export { default as BubbleTextMod } from './BubbleTextMod'
 export { default as ChatBot } from './ChatBot'
 export { default as ChatBot } from './ChatBot'
 export { default as CuteRobot } from './CuteRobot'
 export { default as CuteRobot } from './CuteRobot'
 export { default as EditList } from './EditList'
 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 MessageDotsCircle } from './MessageDotsCircle'
 export { default as MessageFast } from './MessageFast'
 export { default as MessageFast } from './MessageFast'
 export { default as MessageHeartCircle } from './MessageHeartCircle'
 export { default as MessageHeartCircle } from './MessageHeartCircle'

+ 2 - 2
web/app/components/base/tab-slider-new/index.tsx

@@ -25,8 +25,8 @@ const TabSliderNew: FC<TabSliderProps> = ({
           key={option.value}
           key={option.value}
           onClick={() => onChange(option.value)}
           onClick={() => onChange(option.value)}
           className={cn(
           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}
           {option.icon}

+ 15 - 16
web/app/components/base/tag-management/filter.tsx

@@ -78,24 +78,23 @@ const TagFilter: FC<TagFilterProps> = ({
           className='block'
           className='block'
         >
         >
           <div className={cn(
           <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]'>
             <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>
-            <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 && t('common.tag.placeholder')}
               {!!value.length && currentTag?.name}
               {!!value.length && currentTag?.name}
             </div>
             </div>
             {value.length > 1 && (
             {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 && (
             {!value.length && (
               <div className='p-[1px]'>
               <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>
               </div>
             )}
             )}
             {!!value.length && (
             {!!value.length && (
@@ -103,14 +102,14 @@ const TagFilter: FC<TagFilterProps> = ({
                 e.stopPropagation()
                 e.stopPropagation()
                 onChange([])
                 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>
             )}
             )}
           </div>
           </div>
         </PortalToFollowElemTrigger>
         </PortalToFollowElemTrigger>
         <PortalToFollowElemContent className='z-[1002]'>
         <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
               <Input
                 showLeftIcon
                 showLeftIcon
                 showClearIcon
                 showClearIcon
@@ -123,17 +122,17 @@ const TagFilter: FC<TagFilterProps> = ({
               {filteredTagList.map(tag => (
               {filteredTagList.map(tag => (
                 <div
                 <div
                   key={tag.id}
                   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)}
                   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>
                 </div>
               ))}
               ))}
               {!filteredTagList.length && (
               {!filteredTagList.length && (
                 <div className='p-3 flex flex-col items-center gap-1'>
                 <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>
               )}
               )}
             </div>
             </div>

+ 1 - 1
web/app/components/header/header-wrapper.tsx

@@ -17,7 +17,7 @@ const HeaderWrapper = ({
     <div className={classNames(
     <div className={classNames(
       'sticky top-0 left-0 right-0 z-30 flex flex-col grow-0 shrink-0 basis-auto min-h-[56px]',
       'sticky top-0 left-0 right-0 z-30 flex flex-col grow-0 shrink-0 basis-auto min-h-[56px]',
       s.header,
       s.header,
-      isBordered ? 'border-b border-gray-200' : '',
+      isBordered ? 'border-b border-divider-regular' : '',
     )}
     )}
     >
     >
       {children}
       {children}

+ 38 - 15
web/i18n/en-US/app.ts

@@ -6,6 +6,8 @@ const translation = {
     agent: 'Agent',
     agent: 'Agent',
     workflow: 'Workflow',
     workflow: 'Workflow',
     completion: 'Completion',
     completion: 'Completion',
+    advanced: 'Chatflow',
+    basic: 'Basic',
   },
   },
   duplicate: 'Duplicate',
   duplicate: 'Duplicate',
   mermaid: {
   mermaid: {
@@ -31,26 +33,33 @@ const translation = {
     'Discuss with team members, contributors and developers on different channels.',
     'Discuss with team members, contributors and developers on different channels.',
   roadmap: 'See our roadmap',
   roadmap: 'See our roadmap',
   newApp: {
   newApp: {
+    learnMore: 'Learn more',
     startFromBlank: 'Create from Blank',
     startFromBlank: 'Create from Blank',
     startFromTemplate: 'Create from Template',
     startFromTemplate: 'Create from Template',
-    captionAppType: 'What type of app do you want to create?',
-    chatbotDescription: 'Build a chat-based application. This app uses a question-and-answer format, allowing for multiple rounds of continuous conversation.',
-    completionDescription: 'Build an application that generates high-quality text based on prompts, such as generating articles, summaries, translations, and more.',
-    completionWarning: 'This type of app will no longer be supported.',
-    agentDescription: 'Build an intelligent Agent which can autonomously choose tools to complete the tasks',
-    workflowDescription: 'Build an application that generates high-quality text based on workflow orchestrates with a high degree of customization. It is suitable for experienced users.',
+    foundResult: '{{count}} Result',
+    foundResults: '{{count}} Results',
+    noAppsFound: 'No apps found',
+    noTemplateFound: 'No templates found',
+    noTemplateFoundTip: 'Try searching using different keywords.',
+    chatbotShortDescription: 'LLM-based chatbot with simple setup',
+    chatbotUserDescription: 'Quickly build an LLM-based chatbot with simple configuration. You can switch to Chatflow later.',
+    completionShortDescription: 'AI assistant for text generation tasks',
+    completionUserDescription: 'Quickly build an AI assistant for text generation tasks with simple configuration.',
+    agentShortDescription: 'Intelligent agent with reasoning and autonomous tool use',
+    agentUserDescription: 'An intelligent agent capable of iterative reasoning and autonomous tool use to achieve task goals.',
+    workflowShortDescription: 'Orchestration for single-turn automation tasks',
+    workflowUserDescription: 'Workflow orchestration for single-round tasks like automation and batch processing.',
     workflowWarning: 'Currently in beta',
     workflowWarning: 'Currently in beta',
-    chatbotType: 'Chatbot orchestrate method',
-    basic: 'Basic',
-    basicTip: 'For beginners, can switch to Chatflow later',
-    basicFor: 'FOR BEGINNERS',
-    basicDescription: 'Basic Orchestrate allows for the orchestration of a Chatbot app using simple settings, without the ability to modify built-in prompts. It is suitable for beginners.',
-    advanced: 'Chatflow',
-    advancedFor: 'For advanced users',
-    advancedDescription: 'Workflow Orchestrate orchestrates Chatbots in the form of workflows, offering a high degree of customization, including the ability to edit built-in prompts. It is suitable for experienced users.',
-    captionName: 'App icon & name',
+    advancedShortDescription: 'Workflow for complex multi-turn dialogues with memory',
+    advancedUserDescription: 'Workflow orchestration for multi-round complex dialogue tasks with memory capabilities.',
+    chooseAppType: 'Choose App Type',
+    forBeginners: 'FOR BEGINNERS',
+    forAdvanced: 'FOR ADVANCED USERS',
+    noIdeaTip: 'No ideas? Check out our templates',
+    captionName: 'App Name & Icon',
     appNamePlaceholder: 'Give your app a name',
     appNamePlaceholder: 'Give your app a name',
     captionDescription: 'Description',
     captionDescription: 'Description',
+    optional: 'Optional',
     appDescriptionPlaceholder: 'Enter the description of the app',
     appDescriptionPlaceholder: 'Enter the description of the app',
     useTemplate: 'Use this template',
     useTemplate: 'Use this template',
     previewDemo: 'Preview demo',
     previewDemo: 'Preview demo',
@@ -79,6 +88,19 @@ const translation = {
     appCreateDSLErrorPart4: 'System-supported DSL version: ',
     appCreateDSLErrorPart4: 'System-supported DSL version: ',
     appCreateFailed: 'Failed to create app',
     appCreateFailed: 'Failed to create app',
   },
   },
+  newAppFromTemplate: {
+    byCategories: 'BY CATEGORIES',
+    searchAllTemplate: 'Search all templates...',
+    sidebar: {
+      Recommended: 'Recommended',
+      Agent: 'Agent',
+      Assistant: 'Assistant',
+      HR: 'HR',
+      Workflow: 'Workflow',
+      Writing: 'Writing',
+      Programming: 'Programming',
+    },
+  },
   editApp: 'Edit Info',
   editApp: 'Edit Info',
   editAppTitle: 'Edit App Info',
   editAppTitle: 'Edit App Info',
   editDone: 'App info updated',
   editDone: 'App info updated',
@@ -108,6 +130,7 @@ const translation = {
     agent: 'Agent',
     agent: 'Agent',
     workflow: 'Workflow',
     workflow: 'Workflow',
     completion: 'Completion',
     completion: 'Completion',
+    advanced: 'Chatflow',
   },
   },
   tracing: {
   tracing: {
     title: 'Tracing app performance',
     title: 'Tracing app performance',

+ 38 - 13
web/i18n/zh-Hans/app.ts

@@ -6,6 +6,8 @@ const translation = {
     agent: 'Agent',
     agent: 'Agent',
     workflow: '工作流',
     workflow: '工作流',
     completion: '文本生成',
     completion: '文本生成',
+    advanced: 'Chatflow',
+    basic: '基础编排',
   },
   },
   duplicate: '复制',
   duplicate: '复制',
   mermaid: {
   mermaid: {
@@ -30,25 +32,34 @@ const translation = {
   communityIntro: '与团队成员、贡献者和开发者在不同频道中交流',
   communityIntro: '与团队成员、贡献者和开发者在不同频道中交流',
   roadmap: '产品路线图',
   roadmap: '产品路线图',
   newApp: {
   newApp: {
+    learnMore: '了解更多',
     startFromBlank: '创建空白应用',
     startFromBlank: '创建空白应用',
     startFromTemplate: '从应用模版创建',
     startFromTemplate: '从应用模版创建',
     captionAppType: '想要哪种应用类型?',
     captionAppType: '想要哪种应用类型?',
-    chatbotDescription: '使用大型语言模型构建基于聊天的助手',
-    completionDescription: '构建一个根据提示生成高质量文本的应用程序,例如生成文章、摘要、翻译等。',
+    foundResult: '{{count}} 个结果',
+    foundResults: '{{count}} 个结果',
+    noAppsFound: '未找到应用',
+    noTemplateFound: '未找到模板',
+    noTemplateFoundTip: '请尝试使用不同的关键字进行搜索。',
+    chatbotShortDescription: '简单配置即可构建基于 LLM 的对话机器人',
+    chatbotUserDescription: '通过简单的配置快速搭建一个基于 LLM 的对话机器人。支持切换为 Chatflow 编排。',
+    completionShortDescription: '用于文本生成任务的 AI 助手',
+    completionUserDescription: '通过简单的配置快速搭建一个面向文本生成类任务的 AI 助手。',
     completionWarning: '该类型不久后将不再支持创建',
     completionWarning: '该类型不久后将不再支持创建',
-    agentDescription: '构建一个智能Agent,可以自主选择工具来完成任务',
-    workflowDescription: '以工作流的形式编排生成型应用,提供更多的自定义能力。 它适合有经验的用户。',
+    agentShortDescription: '具备推理与自主工具调用的智能助手',
+    agentUserDescription: '能够迭代式的规划推理、自主工具调用,直至完成任务目标的智能助手。',
+    workflowShortDescription: '面向单轮自动化任务的编排工作流',
+    workflowUserDescription: '基于工作流编排,适用于自动化、批处理等单轮生成类任务的场景。',
     workflowWarning: '正在进行 Beta 测试',
     workflowWarning: '正在进行 Beta 测试',
-    chatbotType: '聊天助手编排方法',
-    basic: '基础编排',
-    basicTip: '新手适用,可以切换成工作流编排',
-    basicFor: '新手适用',
-    basicDescription: '基本编排允许使用简单的设置编排聊天机器人应用程序,而无需修改内置提示。 它适合初学者。',
-    advanced: '工作流编排',
-    advancedFor: '进阶用户适用',
-    advancedDescription: '工作流编排以工作流的形式编排聊天机器人,提供高度的自定义,包括编辑内置提示的能力。 它适合有经验的用户。',
-    captionName: '图标 & 名称',
+    advancedShortDescription: '支持记忆的复杂多轮对话工作流',
+    advancedUserDescription: '基于工作流编排,适用于定义等复杂流程的多轮对话场景,具有记忆功能。',
+    chooseAppType: '选择应用类型',
+    forBeginners: '新手适用',
+    forAdvanced: '进阶用户适用',
+    noIdeaTip: '没有想法?试试我们的模板',
+    captionName: '应用名称 & 图标',
     appNamePlaceholder: '给你的应用起个名字',
     appNamePlaceholder: '给你的应用起个名字',
+    optional: '可选',
     captionDescription: '描述',
     captionDescription: '描述',
     appDescriptionPlaceholder: '输入应用的描述',
     appDescriptionPlaceholder: '输入应用的描述',
     useTemplate: '使用该模板',
     useTemplate: '使用该模板',
@@ -78,6 +89,19 @@ const translation = {
     appCreateFailed: '应用创建失败',
     appCreateFailed: '应用创建失败',
     Confirm: '确认',
     Confirm: '确认',
   },
   },
+  newAppFromTemplate: {
+    byCategories: '分类',
+    searchAllTemplate: '搜索所有模版...',
+    sidebar: {
+      Recommended: '推荐',
+      Agent: 'Agent',
+      Assistant: '助手',
+      HR: '人力资源',
+      Workflow: '工作流',
+      Writing: '写作',
+      Programming: '编程',
+    },
+  },
   editApp: '编辑信息',
   editApp: '编辑信息',
   editAppTitle: '编辑应用信息',
   editAppTitle: '编辑应用信息',
   editDone: '应用信息已更新',
   editDone: '应用信息已更新',
@@ -106,6 +130,7 @@ const translation = {
     agent: 'Agent',
     agent: 'Agent',
     workflow: '工作流',
     workflow: '工作流',
     completion: '文本生成',
     completion: '文本生成',
+    advanced: 'Chatflow',
   },
   },
   tracing: {
   tracing: {
     title: '追踪应用性能',
     title: '追踪应用性能',

+ 1 - 1
web/i18n/zh-Hant/app.ts

@@ -39,7 +39,7 @@ const translation = {
     advanced: '工作流編排',
     advanced: '工作流編排',
     advancedFor: '進階使用者適用',
     advancedFor: '進階使用者適用',
     advancedDescription: '工作流編排以工作流的形式編排聊天機器人,提供高度的自定義,包括編輯內建提示的能力。 它適合有經驗的使用者。',
     advancedDescription: '工作流編排以工作流的形式編排聊天機器人,提供高度的自定義,包括編輯內建提示的能力。 它適合有經驗的使用者。',
-    captionName: '圖示 & 名稱',
+    captionName: '應用名稱 & 圖示',
     appNamePlaceholder: '給你的應用起個名字',
     appNamePlaceholder: '給你的應用起個名字',
     captionDescription: '描述',
     captionDescription: '描述',
     appDescriptionPlaceholder: '輸入應用的描述',
     appDescriptionPlaceholder: '輸入應用的描述',

+ 1 - 1
web/models/explore.ts

@@ -11,7 +11,7 @@ export type AppBasicInfo = {
   use_icon_as_answer_icon: boolean
   use_icon_as_answer_icon: boolean
 }
 }
 
 
-export type AppCategory = 'Writing' | 'Translate' | 'HR' | 'Programming' | 'Assistant'
+export type AppCategory = 'Writing' | 'Translate' | 'HR' | 'Programming' | 'Assistant' | 'Agent' | 'Recommended' | 'Workflow'
 
 
 export type App = {
 export type App = {
   app: AppBasicInfo
   app: AppBasicInfo

+ 1 - 1
web/package.json

@@ -36,7 +36,7 @@
     "@mdx-js/react": "^2.3.0",
     "@mdx-js/react": "^2.3.0",
     "@monaco-editor/react": "^4.6.0",
     "@monaco-editor/react": "^4.6.0",
     "@next/mdx": "^14.0.4",
     "@next/mdx": "^14.0.4",
-    "@remixicon/react": "^4.2.0",
+    "@remixicon/react": "^4.5.0",
     "@sentry/react": "^7.54.0",
     "@sentry/react": "^7.54.0",
     "@sentry/utils": "^7.54.0",
     "@sentry/utils": "^7.54.0",
     "@svgdotjs/svg.js": "^3.2.4",
     "@svgdotjs/svg.js": "^3.2.4",

BIN
web/public/screenshots/Light/Agent.png


BIN
web/public/screenshots/Light/Agent@2x.png


BIN
web/public/screenshots/Light/Agent@3x.png


BIN
web/public/screenshots/Light/ChatFlow.png


BIN
web/public/screenshots/Light/ChatFlow@2x.png


BIN
web/public/screenshots/Light/ChatFlow@3x.png


BIN
web/public/screenshots/Light/Chatbot.png


BIN
web/public/screenshots/Light/Chatbot@2x.png


BIN
web/public/screenshots/Light/Chatbot@3x.png


BIN
web/public/screenshots/Light/Chatflow.png


BIN
web/public/screenshots/Light/Chatflow@2x.png


BIN
web/public/screenshots/Light/Chatflow@3x.png


BIN
web/public/screenshots/Light/TextGenerator.png


BIN
web/public/screenshots/Light/TextGenerator@2x.png


BIN
web/public/screenshots/Light/TextGenerator@3x.png


BIN
web/public/screenshots/Light/Workflow.png


BIN
web/public/screenshots/Light/Workflow@2x.png


BIN
web/public/screenshots/Light/Workflow@3x.png


BIN
web/public/screenshots/dark/Agent.png


BIN
web/public/screenshots/dark/Agent@2x.png


BIN
web/public/screenshots/dark/Agent@3x.png


BIN
web/public/screenshots/dark/Chatbot.png


BIN
web/public/screenshots/dark/Chatbot@2x.png


BIN
web/public/screenshots/dark/Chatbot@3x.png


BIN
web/public/screenshots/dark/Chatflow.png


BIN
web/public/screenshots/dark/Chatflow@2x.png


BIN
web/public/screenshots/dark/Chatflow@3x.png


BIN
web/public/screenshots/dark/TextGenerator.png


BIN
web/public/screenshots/dark/TextGenerator@2x.png


BIN
web/public/screenshots/dark/TextGenerator@3x.png


BIN
web/public/screenshots/dark/Workflow.png


BIN
web/public/screenshots/dark/Workflow@2x.png


BIN
web/public/screenshots/dark/Workflow@3x.png


BIN
web/public/screenshots/light/Agent.png


BIN
web/public/screenshots/light/Agent@2x.png


BIN
web/public/screenshots/light/Agent@3x.png


BIN
web/public/screenshots/light/Chatbot.png


BIN
web/public/screenshots/light/Chatbot@2x.png


BIN
web/public/screenshots/light/Chatbot@3x.png


BIN
web/public/screenshots/light/Chatflow.png


BIN
web/public/screenshots/light/Chatflow@2x.png


BIN
web/public/screenshots/light/Chatflow@3x.png


BIN
web/public/screenshots/light/TextGenerator.png


BIN
web/public/screenshots/light/TextGenerator@2x.png


BIN
web/public/screenshots/light/TextGenerator@3x.png


BIN
web/public/screenshots/light/Workflow.png


BIN
web/public/screenshots/light/Workflow@2x.png


BIN
web/public/screenshots/light/Workflow@3x.png


+ 4 - 3
web/tailwind.config.js

@@ -65,12 +65,13 @@ module.exports = {
         ...tailwindThemeVarDefine,
         ...tailwindThemeVarDefine,
       },
       },
       screens: {
       screens: {
-        mobile: '100px',
+        'mobile': '100px',
         // => @media (min-width: 100px) { ... }
         // => @media (min-width: 100px) { ... }
-        tablet: '640px', // 391
+        'tablet': '640px', // 391
         // => @media (min-width: 600px) { ... }
         // => @media (min-width: 600px) { ... }
-        pc: '769px',
+        'pc': '769px',
         // => @media (min-width: 769px) { ... }
         // => @media (min-width: 769px) { ... }
+        '2k': '2560px',
       },
       },
       boxShadow: {
       boxShadow: {
         'xs': '0px 1px 2px 0px rgba(16, 24, 40, 0.05)',
         'xs': '0px 1px 2px 0px rgba(16, 24, 40, 0.05)',

+ 4 - 4
web/yarn.lock

@@ -2242,10 +2242,10 @@
     classcat "^5.0.3"
     classcat "^5.0.3"
     zustand "^4.4.1"
     zustand "^4.4.1"
 
 
-"@remixicon/react@^4.2.0":
-  version "4.2.0"
-  resolved "https://registry.npmjs.org/@remixicon/react/-/react-4.2.0.tgz"
-  integrity sha512-eGhKpZ88OU0qkcY9pJu6khBmItDV82nU130E6C68yc+FbljueHlUYy/4CrJsmf860RIDMay2Rpzl27OSJ81miw==
+"@remixicon/react@^4.5.0":
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/@remixicon/react/-/react-4.5.0.tgz#5600d122ee4995bff2c4442cb056eeb4f11ecb5a"
+  integrity sha512-Xr20SxMpRNlgXZnoF5BCMyZuQEhXY3yJCyms8kxB/vJCCiV1nWdiO48XqRG5LBd1192iSHC4m658AIWi6rmBFg==
 
 
 "@rgrove/parse-xml@^4.1.0":
 "@rgrove/parse-xml@^4.1.0":
   version "4.1.0"
   version "4.1.0"

Some files were not shown because too many files changed in this diff