Преглед на файлове

Chore: update app detail panel (#13337)

Yi Xiao преди 3 месеца
родител
ревизия
ae6f67420c

+ 2 - 2
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx

@@ -161,9 +161,9 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
   }
 
   return (
-    <div className={cn(s.app, 'flex', 'overflow-hidden')}>
+    <div className={cn(s.app, 'flex relative', 'overflow-hidden')}>
       {appDetail && (
-        <AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background} desc={appDetail.mode} navigation={navigation} />
+        <AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background as string} desc={appDetail.mode} navigation={navigation} />
       )}
       <div className="bg-components-panel-bg grow overflow-hidden">
         {children}

+ 6 - 2
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx

@@ -24,9 +24,11 @@ import AppContext from '@/context/app-context'
 
 export type ICardViewProps = {
   appId: string
+  isInPanel?: boolean
+  className?: string
 }
 
-const CardView: FC<ICardViewProps> = ({ appId }) => {
+const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
   const { t } = useTranslation()
   const { notify } = useContext(ToastContext)
   const appDetail = useAppStore(state => state.appDetail)
@@ -120,10 +122,11 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
     return <Loading />
 
   return (
-    <div className="grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6">
+    <div className={className || 'grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'}>
       <AppCard
         appInfo={appDetail}
         cardType="webapp"
+        isInPanel={isInPanel}
         onChangeStatus={onChangeSiteStatus}
         onGenerateCode={onGenerateCode}
         onSaveSiteConfig={onSaveSiteConfig}
@@ -131,6 +134,7 @@ const CardView: FC<ICardViewProps> = ({ appId }) => {
       <AppCard
         cardType="api"
         appInfo={appDetail}
+        isInPanel={isInPanel}
         onChangeStatus={onChangeApiStatus}
       />
     </div>

+ 0 - 2
web/app/(commonLayout)/datasets/ApiServer.tsx

@@ -31,8 +31,6 @@ const ApiServer: FC<ApiServerProps> = ({
       </div>
       <SecretKeyButton
         className='flex-shrink-0 !h-8 bg-white'
-        textCls='!text-gray-700 font-medium'
-        iconCls='stroke-[1.2px]'
       />
     </div>
   )

+ 192 - 284
web/app/components/app-sidebar/app-info.tsx

@@ -1,18 +1,18 @@
 import { useTranslation } from 'react-i18next'
 import { useRouter } from 'next/navigation'
 import { useContext, useContextSelector } from 'use-context-selector'
-import { RiArrowDownSLine } from '@remixicon/react'
 import React, { useCallback, useState } from 'react'
+import {
+  RiDeleteBinLine,
+  RiEditLine,
+  RiEqualizer2Line,
+  RiFileCopy2Line,
+  RiFileDownloadLine,
+  RiFileUploadLine,
+} from '@remixicon/react'
 import AppIcon from '../base/app-icon'
 import SwitchAppModal from '../app/switch-app-modal'
-import s from './style.module.css'
 import cn from '@/utils/classnames'
-import {
-  PortalToFollowElem,
-  PortalToFollowElemContent,
-  PortalToFollowElemTrigger,
-} from '@/app/components/base/portal-to-follow-elem'
-import Divider from '@/app/components/base/divider'
 import Confirm from '@/app/components/base/confirm'
 import { useStore as useAppStore } from '@/app/components/app/store'
 import { ToastContext } from '@/app/components/base/toast'
@@ -22,8 +22,6 @@ import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/ap
 import DuplicateAppModal from '@/app/components/app/duplicate-modal'
 import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
 import CreateAppModal from '@/app/components/explore/create-app-modal'
-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 { NEED_REFRESH_APP_LIST_KEY } from '@/config'
 import { getRedirection } from '@/utils/app-redirection'
@@ -31,6 +29,9 @@ import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
 import type { EnvironmentVariable } from '@/app/components/workflow/types'
 import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
 import { fetchWorkflowDraft } from '@/service/workflow'
+import ContentDialog from '@/app/components/base/content-dialog'
+import Button from '@/app/components/base/button'
+import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView'
 
 export type IAppInfoProps = {
   expand: boolean
@@ -47,7 +48,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
   const [showEditModal, setShowEditModal] = useState(false)
   const [showDuplicateModal, setShowDuplicateModal] = useState(false)
   const [showConfirmDelete, setShowConfirmDelete] = useState(false)
-  const [showSwitchTip, setShowSwitchTip] = useState<string>('')
   const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false)
   const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false)
   const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
@@ -183,291 +183,199 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
     return null
 
   return (
-    <PortalToFollowElem
-      open={open}
-      onOpenChange={setOpen}
-      placement='bottom-start'
-      offset={4}
-    >
-      <div className='relative'>
-        <PortalToFollowElemTrigger
-          onClick={() => {
-            if (isCurrentWorkspaceEditor)
-              setOpen(v => !v)
-          }}
-          className='block'
-        >
-          <div className={cn('flex p-1 rounded-lg', open && 'bg-gray-100', isCurrentWorkspaceEditor && 'hover:bg-gray-100 cursor-pointer')}>
-            <div className='relative shrink-0 mr-2'>
-              <AppIcon
-                size={expand ? 'large' : 'small'}
-                iconType={appDetail.icon_type}
-                icon={appDetail.icon}
-                background={appDetail.icon_background}
-                imageUrl={appDetail.icon_url}
-              />
-              <span className={cn(
-                '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',
-                !expand && '!w-3.5 !h-3.5 !bottom-[-2px] !right-[-2px]',
-              )}>
-                {appDetail.mode === 'advanced-chat' && (
-                  <ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} />
-                )}
-                {appDetail.mode === 'agent-chat' && (
-                  <CuteRobot className={cn('w-3 h-3 text-indigo-600', !expand && '!w-2.5 !h-2.5')} />
-                )}
-                {appDetail.mode === 'chat' && (
-                  <ChatBot className={cn('w-3 h-3 text-[#1570EF]', !expand && '!w-2.5 !h-2.5')} />
-                )}
-                {appDetail.mode === 'completion' && (
-                  <AiText className={cn('w-3 h-3 text-[#0E9384]', !expand && '!w-2.5 !h-2.5')} />
-                )}
-                {appDetail.mode === 'workflow' && (
-                  <Route className={cn('w-3 h-3 text-[#f79009]', !expand && '!w-2.5 !h-2.5')} />
-                )}
-              </span>
-            </div>
-            {expand && (
-              <div className="grow w-0">
-                <div className='flex justify-between items-center text-sm leading-5 font-medium text-text-secondary'>
-                  <div className='truncate' title={appDetail.name}>{appDetail.name}</div>
-                  {isCurrentWorkspaceEditor && <RiArrowDownSLine className='shrink-0 ml-[2px] w-3 h-3 text-gray-500' />}
-                </div>
-                <div className='flex items-center text-[10px] leading-[18px] font-medium text-gray-500 gap-1'>
-                  {appDetail.mode === 'advanced-chat' && (
-                    <>
-                      <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
-                      <div title={t('app.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' && (
-                    <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.agent').toUpperCase()}</div>
-                  )}
-                  {appDetail.mode === 'chat' && (
-                    <>
-                      <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
-                      <div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
-                    </>
-                  )}
-                  {appDetail.mode === 'completion' && (
-                    <>
-                      <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div>
-                      <div title={t('app.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' && (
-                    <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.workflow').toUpperCase()}</div>
-                  )}
-                </div>
+    <div>
+      <button
+        onClick={() => {
+          if (isCurrentWorkspaceEditor)
+            setOpen(v => !v)
+        }}
+        className='block w-full'
+      >
+        <div className={cn('flex rounded-lg', expand ? 'p-2 pb-2.5 flex-col gap-2' : 'p-1 gap-1 justify-center items-start', open && 'bg-state-base-hover', isCurrentWorkspaceEditor && 'hover:bg-state-base-hover cursor-pointer')}>
+          <div className={`flex items-center self-stretch ${expand ? 'justify-between' : 'flex-col gap-1'}`}>
+            <AppIcon
+              size={expand ? 'large' : 'small'}
+              iconType={appDetail.icon_type}
+              icon={appDetail.icon}
+              background={appDetail.icon_background}
+              imageUrl={appDetail.icon_url}
+            />
+            <div className='flex p-0.5 justify-center items-center rounded-md'>
+              <div className='flex w-5 h-5 justify-center items-center'>
+                <RiEqualizer2Line className='w-4 h-4 text-text-tertiary' />
               </div>
-            )}
+            </div>
           </div>
-        </PortalToFollowElemTrigger>
-        <PortalToFollowElemContent className='z-[1002]'>
-          <div className='relative w-[320px] bg-white rounded-2xl shadow-xl'>
-            {/* header */}
-            <div className={cn('flex pl-4 pt-3 pr-3', !appDetail.description && 'pb-2')}>
-              <div className='relative shrink-0 mr-2'>
-                <AppIcon
-                  size="large"
-                  iconType={appDetail.icon_type}
-                  icon={appDetail.icon}
-                  background={appDetail.icon_background}
-                  imageUrl={appDetail.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'>
-                  {appDetail.mode === 'advanced-chat' && (
-                    <ChatBot className='w-3 h-3 text-[#1570EF]' />
-                  )}
-                  {appDetail.mode === 'agent-chat' && (
-                    <CuteRobot className='w-3 h-3 text-indigo-600' />
-                  )}
-                  {appDetail.mode === 'chat' && (
-                    <ChatBot className='w-3 h-3 text-[#1570EF]' />
-                  )}
-                  {appDetail.mode === 'completion' && (
-                    <AiText className='w-3 h-3 text-[#0E9384]' />
-                  )}
-                  {appDetail.mode === 'workflow' && (
-                    <Route className='w-3 h-3 text-[#f79009]' />
-                  )}
-                </span>
-              </div>
-              <div className='grow w-0'>
-                <div title={appDetail.name} className='flex justify-between items-center text-sm leading-5 font-medium text-gray-900 truncate'>{appDetail.name}</div>
-                <div className='flex items-center text-[10px] leading-[18px] font-medium text-gray-500 gap-1'>
-                  {appDetail.mode === 'advanced-chat' && (
-                    <>
-                      <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
-                      <div title={t('app.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' && (
-                    <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.agent').toUpperCase()}</div>
-                  )}
-                  {appDetail.mode === 'chat' && (
-                    <>
-                      <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.chatbot').toUpperCase()}</div>
-                      <div title={t('app.types.basic') || ''} className='px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{(t('app.types.basic').toUpperCase())}</div>
-                    </>
-                  )}
-                  {appDetail.mode === 'completion' && (
-                    <>
-                      <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.completion').toUpperCase()}</div>
-                      <div title={t('app.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' && (
-                    <div className='shrink-0 px-1 border bg-white border-[rgba(0,0,0,0.08)] rounded-[5px] truncate'>{t('app.types.workflow').toUpperCase()}</div>
-                  )}
+          {
+            expand && (
+              <div className='flex flex-col items-start gap-1'>
+                <div className='flex w-full'>
+                  <div className='text-text-secondary system-md-semibold truncate'>{appDetail.name}</div>
                 </div>
+                <div className='text-text-tertiary system-2xs-medium-uppercase'>{appDetail.mode === 'advanced-chat' ? t('app.types.chatbot') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
               </div>
+            )
+          }
+        </div>
+      </button>
+      <ContentDialog
+        show={open}
+        onClose={() => setOpen(false)}
+        className='!p-0 flex flex-col absolute left-2 top-2 bottom-2 w-[420px] rounded-2xl'
+      >
+        <div className='flex p-4 flex-col justify-center items-start gap-3 self-stretch shrink-0'>
+          <div className='flex items-center gap-3 self-stretch'>
+            <AppIcon
+              size="large"
+              iconType={appDetail.icon_type}
+              icon={appDetail.icon}
+              background={appDetail.icon_background}
+              imageUrl={appDetail.icon_url}
+            />
+            <div className='flex flex-col justify-center items-start grow w-full'>
+              <div className='text-text-secondary system-md-semibold truncate w-full'>{appDetail.name}</div>
+              <div className='text-text-tertiary system-2xs-medium-uppercase'>{appDetail.mode === 'advanced-chat' ? t('app.types.chatbot') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
             </div>
-            {/* description */}
-            {appDetail.description && (
-              <div className='px-4 py-2 text-gray-500 text-xs leading-[18px]'>{appDetail.description}</div>
-            )}
-            {/* operations */}
-            <Divider className="!my-1" />
-            <div className="w-full py-1">
-              <div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={() => {
+          </div>
+          {/* description */}
+          {appDetail.description && (
+            <div className='text-text-tertiary system-xs-regular'>{appDetail.description}</div>
+          )}
+          {/* operations */}
+          <div className='flex items-center gap-1 self-stretch'>
+            <Button
+              size={'small'}
+              variant={'secondary'}
+              className='gap-[1px]'
+              onClick={() => {
                 setOpen(false)
                 setShowEditModal(true)
-              }}>
-                <span className='text-gray-700 text-sm leading-5'>{t('app.editApp')}</span>
-              </div>
-              <div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={() => {
+              }}
+            >
+              <RiEditLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
+              <span className='text-components-button-secondary-text system-xs-medium'>{t('app.editApp')}</span>
+            </Button>
+            <Button
+              size={'small'}
+              variant={'secondary'}
+              className='gap-[1px]'
+              onClick={() => {
                 setOpen(false)
                 setShowDuplicateModal(true)
-              }}>
-                <span className='text-gray-700 text-sm leading-5'>{t('app.duplicate')}</span>
-              </div>
-              {(appDetail.mode === 'completion' || appDetail.mode === 'chat') && (
-                <>
-                  <Divider className="!my-1" />
-                  <div
-                    className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
-                    onMouseEnter={() => setShowSwitchTip(appDetail.mode)}
-                    onMouseLeave={() => setShowSwitchTip('')}
-                    onClick={() => {
-                      setOpen(false)
-                      setShowSwitchModal(true)
-                    }}
-                  >
-                    <span className='text-gray-700 text-sm leading-5'>{t('app.switch')}</span>
-                  </div>
-                </>
-              )}
-              <Divider className="!my-1" />
-              <div className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' onClick={exportCheck}>
-                <span className='text-gray-700 text-sm leading-5'>{t('app.export')}</span>
-              </div>
-              {
-                (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (
-                  <div
-                    className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
-                    onClick={() => {
-                      setOpen(false)
-                      setShowImportDSLModal(true)
-                    }}>
-                    <span className='text-gray-700 text-sm leading-5'>{t('workflow.common.importDSL')}</span>
-                  </div>
-                )
-              }
-              <Divider className="!my-1" />
-              <div className='group h-9 py-2 px-3 mx-1 flex items-center hover:bg-red-50 rounded-lg cursor-pointer' onClick={() => {
-                setOpen(false)
-                setShowConfirmDelete(true)
-              }}>
-                <span className='text-gray-700 text-sm leading-5 group-hover:text-red-500'>
-                  {t('common.operation.delete')}
-                </span>
-              </div>
-            </div>
-            {/* switch tip */}
-            <div
-              className={cn(
-                'hidden absolute left-[324px] top-0 w-[376px] rounded-xl bg-white border-[0.5px] border-[rgba(0,0,0,0.05)] shadow-lg',
-                showSwitchTip && '!block',
-              )}
+              }}
             >
-              <div className={cn(
-                'w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl',
-                showSwitchTip === 'chat' && s.expertPic,
-                showSwitchTip === 'completion' && s.completionPic,
-              )} />
-              <div className='px-4 pb-2'>
-                <div className='flex items-center gap-1 text-gray-700 text-md leading-6 font-semibold'>
-                  {showSwitchTip === 'chat' ? t('app.types.advanced') : t('app.types.workflow')}
-                  <span className='px-1 rounded-[5px] bg-white border border-black/8 text-gray-500 text-[10px] leading-[18px] font-medium'>BETA</span>
-                </div>
-                <div className='text-orange-500 text-xs leading-[18px] font-medium'>{t('app.newApp.advancedFor').toLocaleUpperCase()}</div>
-                <div className='mt-1 text-gray-500 text-sm leading-5'>{t('app.newApp.advancedDescription')}</div>
-              </div>
-            </div>
+              <RiFileCopy2Line className='w-3.5 h-3.5 text-components-button-secondary-text' />
+              <span className='text-components-button-secondary-text system-xs-medium'>{t('app.duplicate')}</span>
+            </Button>
+            <Button
+              size={'small'}
+              variant={'secondary'}
+              className='gap-[1px]'
+              onClick={exportCheck}
+            >
+              <RiFileDownloadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
+              <span className='text-components-button-secondary-text system-xs-medium'>{t('app.export')}</span>
+            </Button>
+            {
+              (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (
+                <Button
+                  size={'small'}
+                  variant={'secondary'}
+                  className='gap-[1px]'
+                  onClick={() => {
+                    setOpen(false)
+                    setShowImportDSLModal(true)
+                  }}
+                >
+                  <RiFileUploadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
+                  <span className='text-components-button-secondary-text system-xs-medium'>{t('workflow.common.importDSL')}</span>
+                </Button>
+              )
+            }
           </div>
-        </PortalToFollowElemContent>
-        {showSwitchModal && (
-          <SwitchAppModal
-            inAppDetail
-            show={showSwitchModal}
-            appDetail={appDetail}
-            onClose={() => setShowSwitchModal(false)}
-            onSuccess={() => setShowSwitchModal(false)}
-          />
-        )}
-        {showEditModal && (
-          <CreateAppModal
-            isEditModal
-            appName={appDetail.name}
-            appIconType={appDetail.icon_type}
-            appIcon={appDetail.icon}
-            appIconBackground={appDetail.icon_background}
-            appIconUrl={appDetail.icon_url}
-            appDescription={appDetail.description}
-            appMode={appDetail.mode}
-            appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon}
-            show={showEditModal}
-            onConfirm={onEdit}
-            onHide={() => setShowEditModal(false)}
-          />
-        )}
-        {showDuplicateModal && (
-          <DuplicateAppModal
-            appName={appDetail.name}
-            icon_type={appDetail.icon_type}
-            icon={appDetail.icon}
-            icon_background={appDetail.icon_background}
-            icon_url={appDetail.icon_url}
-            show={showDuplicateModal}
-            onConfirm={onCopy}
-            onHide={() => setShowDuplicateModal(false)}
-          />
-        )}
-        {showConfirmDelete && (
-          <Confirm
-            title={t('app.deleteAppConfirmTitle')}
-            content={t('app.deleteAppConfirmContent')}
-            isShow={showConfirmDelete}
-            onConfirm={onConfirmDelete}
-            onCancel={() => setShowConfirmDelete(false)}
-          />
-        )}
-        {showImportDSLModal && (
-          <UpdateDSLModal
-            onCancel={() => setShowImportDSLModal(false)}
-            onBackup={exportCheck}
-          />
-        )}
-        {secretEnvList.length > 0 && (
-          <DSLExportConfirmModal
-            envList={secretEnvList}
-            onConfirm={onExport}
-            onClose={() => setSecretEnvList([])}
+        </div>
+        <div className='flex flex-1'>
+          <CardView
+            appId={appDetail.id}
+            isInPanel={true}
+            className='flex flex-col px-2 py-1 gap-2 grow overflow-auto'
           />
-        )}
-      </div>
-    </PortalToFollowElem>
+        </div>
+        <div className='flex p-2 flex-col justify-center items-start gap-3 self-stretch border-t-[0.5px] border-divider-subtle shrink-0 min-h-fit'>
+          <Button
+            size={'medium'}
+            variant={'ghost'}
+            className='gap-0.5'
+            onClick={() => {
+              setOpen(false)
+              setShowConfirmDelete(true)
+            }}
+          >
+            <RiDeleteBinLine className='w-4 h-4 text-text-tertiary' />
+            <span className='text-text-tertiary system-sm-medium'>{t('common.operation.deleteApp')}</span>
+          </Button>
+        </div>
+      </ContentDialog>
+      {showSwitchModal && (
+        <SwitchAppModal
+          inAppDetail
+          show={showSwitchModal}
+          appDetail={appDetail}
+          onClose={() => setShowSwitchModal(false)}
+          onSuccess={() => setShowSwitchModal(false)}
+        />
+      )}
+      {showEditModal && (
+        <CreateAppModal
+          isEditModal
+          appName={appDetail.name}
+          appIconType={appDetail.icon_type}
+          appIcon={appDetail.icon}
+          appIconBackground={appDetail.icon_background}
+          appIconUrl={appDetail.icon_url}
+          appDescription={appDetail.description}
+          appMode={appDetail.mode}
+          appUseIconAsAnswerIcon={appDetail.use_icon_as_answer_icon}
+          show={showEditModal}
+          onConfirm={onEdit}
+          onHide={() => setShowEditModal(false)}
+        />
+      )}
+      {showDuplicateModal && (
+        <DuplicateAppModal
+          appName={appDetail.name}
+          icon_type={appDetail.icon_type}
+          icon={appDetail.icon}
+          icon_background={appDetail.icon_background}
+          icon_url={appDetail.icon_url}
+          show={showDuplicateModal}
+          onConfirm={onCopy}
+          onHide={() => setShowDuplicateModal(false)}
+        />
+      )}
+      {showConfirmDelete && (
+        <Confirm
+          title={t('app.deleteAppConfirmTitle')}
+          content={t('app.deleteAppConfirmContent')}
+          isShow={showConfirmDelete}
+          onConfirm={onConfirmDelete}
+          onCancel={() => setShowConfirmDelete(false)}
+        />
+      )}
+      {showImportDSLModal && (
+        <UpdateDSLModal
+          onCancel={() => setShowImportDSLModal(false)}
+          onBackup={exportCheck}
+        />
+      )}
+      {secretEnvList.length > 0 && (
+        <DSLExportConfirmModal
+          envList={secretEnvList}
+          onConfirm={onExport}
+          onClose={() => setSecretEnvList([])}
+        />
+      )}
+    </div>
   )
 }
 

+ 5 - 4
web/app/components/app-sidebar/basic.tsx

@@ -58,7 +58,7 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
   const { t } = useTranslation()
 
   return (
-    <div className="flex items-start p-1">
+    <div className="flex items-center grow">
       {icon && icon_background && iconType === 'app' && (
         <div className='flex-shrink-0 mr-3'>
           <AppIcon icon={icon} background={icon_background} />
@@ -71,8 +71,10 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
 
       }
       {mode === 'expand' && <div className="group">
-        <div className={`flex flex-row items-center text-sm font-semibold text-gray-700 group-hover:text-gray-900 break-all ${textStyle?.main ?? ''}`}>
-          {name}
+        <div className={`flex flex-row items-center system-md-semibold text-text-secondary group-hover:text-text-primary ${textStyle?.main ?? ''}`}>
+          <div className="max-w-[180px] truncate">
+            {name}
+          </div>
           {hoverTip
             && <Tooltip
               popupContent={
@@ -86,7 +88,6 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
             />
           }
         </div>
-        <div className={`text-xs font-normal text-gray-500 group-hover:text-gray-700 break-all ${textStyle?.extra ?? ''}`}>{type}</div>
         <div className='text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : ''}</div>
       </div>}
     </div>

+ 1 - 1
web/app/components/app-sidebar/index.tsx

@@ -57,7 +57,7 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
       <div
         className={`
           shrink-0
-          ${expand ? 'p-3' : 'p-2'}
+          ${expand ? 'p-2' : 'p-1'}
         `}
       >
         {iconType === 'app' && (

+ 57 - 59
web/app/components/app/overview/appCard.tsx

@@ -1,14 +1,14 @@
 'use client'
-import type { HTMLProps } from 'react'
 import React, { useMemo, useState } from 'react'
-import {
-  Cog8ToothIcon,
-  DocumentTextIcon,
-  PaintBrushIcon,
-  RocketLaunchIcon,
-} from '@heroicons/react/24/outline'
 import { usePathname, useRouter } from 'next/navigation'
 import { useTranslation } from 'react-i18next'
+import {
+  RiBookOpenLine,
+  RiEqualizer2Line,
+  RiExternalLinkLine,
+  RiPaintBrushLine,
+  RiWindowLine,
+} from '@remixicon/react'
 import SettingsModal from './settings'
 import EmbeddedModal from './embedded'
 import CustomizeModal from './customize'
@@ -18,7 +18,6 @@ import Tooltip from '@/app/components/base/tooltip'
 import AppBasic from '@/app/components/app-sidebar/basic'
 import { asyncRunSafe, randomString } from '@/utils'
 import Button from '@/app/components/base/button'
-import Tag from '@/app/components/base/tag'
 import Switch from '@/app/components/base/switch'
 import Divider from '@/app/components/base/divider'
 import CopyFeedback from '@/app/components/base/copy-feedback'
@@ -28,10 +27,12 @@ import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-butt
 import type { AppDetailResponse } from '@/models/app'
 import { useAppContext } from '@/context/app-context'
 import type { AppSSO } from '@/types/app'
+import Indicator from '@/app/components/header/indicator'
 
 export type IAppCardProps = {
   className?: string
   appInfo: AppDetailResponse & Partial<AppSSO>
+  isInPanel?: boolean
   cardType?: 'api' | 'webapp'
   customBgColor?: string
   onChangeStatus: (val: boolean) => Promise<void>
@@ -39,12 +40,9 @@ export type IAppCardProps = {
   onGenerateCode?: () => Promise<void>
 }
 
-const EmbedIcon = ({ className = '' }: HTMLProps<HTMLDivElement>) => {
-  return <div className={`${style.codeBrowserIcon} ${className}`}></div>
-}
-
 function AppCard({
   appInfo,
+  isInPanel,
   cardType = 'webapp',
   customBgColor,
   onChangeStatus,
@@ -66,17 +64,18 @@ function AppCard({
   const OPERATIONS_MAP = useMemo(() => {
     const operationsMap = {
       webapp: [
-        { opName: t('appOverview.overview.appInfo.preview'), opIcon: RocketLaunchIcon },
-        { opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: PaintBrushIcon },
+        { opName: t('appOverview.overview.appInfo.launch'), opIcon: RiExternalLinkLine },
       ] as { opName: string; opIcon: any }[],
-      api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: DocumentTextIcon }],
+      api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: RiBookOpenLine }],
       app: [],
     }
     if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow')
-      operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon })
+      operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: RiWindowLine })
+
+    operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: RiPaintBrushLine })
 
     if (isCurrentWorkspaceEditor)
-      operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon })
+      operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: RiEqualizer2Line })
 
     return operationsMap
   }, [isCurrentWorkspaceEditor, appInfo, t])
@@ -92,13 +91,9 @@ function AppCard({
   const appUrl = `${app_base_url}/${appMode}/${access_token}`
   const apiUrl = appInfo?.api_base_url
 
-  let bgColor = 'bg-primary-50 bg-opacity-40'
-  if (cardType === 'api')
-    bgColor = 'bg-purple-50'
-
   const genClickFuncByName = (opName: string) => {
     switch (opName) {
-      case t('appOverview.overview.appInfo.preview'):
+      case t('appOverview.overview.appInfo.launch'):
         return () => {
           window.open(appUrl, '_blank')
         }
@@ -135,49 +130,50 @@ function AppCard({
   return (
     <div
       className={
-        `shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''}`}
+        `${isInPanel ? 'border-l-[0.5px] border-t' : 'shadow-xs border-[0.5px]'} rounded-xl border-effects-highlight w-full max-w-full ${className ?? ''}`}
     >
-      <div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}>
-        <div className="mb-2.5 flex flex-row items-start justify-between">
-          <AppBasic
-            iconType={cardType}
-            icon={appInfo.icon}
-            icon_background={appInfo.icon_background}
-            name={basicName}
-            type={
-              isApp
-                ? t('appOverview.overview.appInfo.explanation')
-                : t('appOverview.overview.apiInfo.explanation')
-            }
-          />
-          <div className="flex flex-row items-center h-9">
-            <Tag className="mr-2" color={runningStatus ? 'green' : 'yellow'}>
-              {runningStatus
-                ? t('appOverview.overview.status.running')
-                : t('appOverview.overview.status.disable')}
-            </Tag>
+      <div className={`${customBgColor ?? 'bg-background-default'} rounded-xl`}>
+        <div className='flex flex-col p-3 justify-center items-start gap-3 self-stretch border-b-[0.5px] border-divider-subtle w-full'>
+          <div className='flex items-center gap-3 self-stretch w-full'>
+            <AppBasic
+              iconType={cardType}
+              icon={appInfo.icon}
+              icon_background={appInfo.icon_background}
+              name={basicName}
+              type={
+                isApp
+                  ? t('appOverview.overview.appInfo.explanation')
+                  : t('appOverview.overview.apiInfo.explanation')
+              }
+            />
+            <div className='flex items-center gap-1'>
+              <Indicator color={runningStatus ? 'green' : 'yellow'} />
+              <div className={`${runningStatus ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase`}>
+                {runningStatus
+                  ? t('appOverview.overview.status.running')
+                  : t('appOverview.overview.status.disable')}
+              </div>
+            </div>
             <Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={toggleDisabled} />
           </div>
-        </div>
-        <div className="flex flex-col justify-center py-2">
-          <div className="py-1">
-            <div className="pb-1 text-xs text-gray-500">
+          <div className='flex flex-col justify-center items-start self-stretch'>
+            <div className="pb-1 system-xs-medium text-text-tertiary">
               {isApp
                 ? t('appOverview.overview.appInfo.accessibleAddress')
                 : t('appOverview.overview.apiInfo.accessibleAddress')}
             </div>
-            <div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-2 rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex">
-              <div className="h-4 px-2 justify-start items-start gap-2 flex flex-1 min-w-0">
-                <div className="text-gray-700 text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap">
+            <div className="w-full h-9 pl-2 p-1 bg-components-input-bg-normal rounded-lg items-center inline-flex gap-0.5">
+              <div className="h-4 px-1 justify-start items-start gap-2 flex flex-1 min-w-0">
+                <div className="text-text-secondary text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap">
                   {isApp ? appUrl : apiUrl}
                 </div>
               </div>
-              <Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" />
-              {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} selectorId={randomString(8)} className={'hover:bg-gray-200'} />}
               <CopyFeedback
                 content={isApp ? appUrl : apiUrl}
-                className={'hover:bg-gray-200'}
+                className={'!size-6'}
               />
+              {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} className='z-50 !size-6 hover:bg-state-base-hover rounded-md' selectorId={randomString(8)} />}
+              {isApp && <Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" />}
               {/* button copy link/ button regenerate */}
               {showConfirmDelete && (
                 <Confirm
@@ -197,7 +193,7 @@ function AppCard({
                   popupContent={t('appOverview.overview.appInfo.regenerate') || ''}
                 >
                   <div
-                    className="w-8 h-8 ml-0.5 cursor-pointer hover:bg-gray-200 rounded-lg"
+                    className="w-6 h-6 cursor-pointer hover:bg-state-base-hover rounded-md"
                     onClick={() => setShowConfirmDelete(true)}
                   >
                     <div
@@ -210,8 +206,8 @@ function AppCard({
             </div>
           </div>
         </div>
-        <div className={'pt-2 flex flex-row items-center flex-wrap gap-y-2'}>
-          {!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />}
+        <div className={'flex p-3 items-center gap-1 self-stretch'}>
+          {!isApp && <SecretKeyButton appId={appInfo.id} />}
           {OPERATIONS_MAP[cardType].map((op) => {
             const disabled
               = op.opName === t('appOverview.overview.appInfo.settings.entry')
@@ -219,7 +215,9 @@ function AppCard({
                 : !runningStatus
             return (
               <Button
-                className="mr-2"
+                className="mr-1 min-w-[88px]"
+                size="small"
+                variant={'ghost'}
                 key={op.opName}
                 onClick={genClickFuncByName(op.opName)}
                 disabled={disabled}
@@ -230,9 +228,9 @@ function AppCard({
                   }
                   popupClassName={disabled ? 'mt-[-8px]' : '!hidden'}
                 >
-                  <div className="flex flex-row items-center">
-                    <op.opIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
-                    <span className="text-[13px]">{op.opName}</span>
+                  <div className="flex items-center justify-center gap-[1px]">
+                    <op.opIcon className="h-3.5 w-3.5" />
+                    <div className={`${runningStatus ? 'text-text-tertiary' : 'text-components-button-ghost-text-disabled'} system-xs-medium px-[3px]`}>{op.opName}</div>
                   </div>
                 </Tooltip>
               </Button>

+ 59 - 0
web/app/components/base/content-dialog/index.tsx

@@ -0,0 +1,59 @@
+import { Fragment, type ReactNode } from 'react'
+import { Transition } from '@headlessui/react'
+import classNames from '@/utils/classnames'
+
+type ContentDialogProps = {
+  className?: string
+  show: boolean
+  onClose?: () => void
+  children: ReactNode
+}
+
+const ContentDialog = ({
+  className,
+  show,
+  onClose,
+  children,
+}: ContentDialogProps) => {
+  return (
+    <Transition
+      show={show}
+      as="div"
+      className="absolute left-0 top-0 w-full h-full z-20 p-2 box-border"
+    >
+      <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="absolute left-0 inset-0 w-full bg-app-detail-overlay-bg"
+          onClick={onClose}
+        />
+      </Transition.Child>
+
+      <Transition.Child
+        as={Fragment}
+        enter="transform transition ease-out duration-300"
+        enterFrom="-translate-x-full"
+        enterTo="translate-x-0"
+        leave="transform transition ease-in duration-200"
+        leaveFrom="translate-x-0"
+        leaveTo="-translate-x-full"
+      >
+        <div className={classNames(
+          'absolute left-0 w-full bg-app-detail-bg border-r border-divider-burn',
+          className,
+        )}>
+          {children}
+        </div>
+      </Transition.Child>
+    </Transition>
+  )
+}
+
+export default ContentDialog

+ 1 - 1
web/app/components/base/copy-feedback/index.tsx

@@ -35,7 +35,7 @@ const CopyFeedback = ({ content, className }: Props) => {
       }
     >
       <div
-        className={`w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg ${
+        className={`w-8 h-8 cursor-pointer hover:bg-state-base-hover rounded-md ${
           className ?? ''
         }`}
       >

+ 6 - 6
web/app/components/base/qrcode/index.tsx

@@ -1,7 +1,7 @@
 'use client'
 import React, { useEffect, useRef, useState } from 'react'
 import { useTranslation } from 'react-i18next'
-import QRCode from 'qrcode.react'
+import { QRCodeSVG } from 'qrcode.react'
 import QrcodeStyle from './style.module.css'
 import Tooltip from '@/app/components/base/tooltip'
 
@@ -54,20 +54,20 @@ const ShareQRCode = ({ content, selectorId, className }: Props) => {
       popupContent={t(`${prefixEmbedded}`) || ''}
     >
       <div
-        className={`w-8 h-8 cursor-pointer rounded-lg ${className ?? ''}`}
+        className={`w-8 h-8 cursor-pointer rounded-lg relative ${className ?? ''}`}
         onClick={toggleQRCode}
       >
         <div className={`w-full h-full ${QrcodeStyle.QrcodeIcon} ${isShow ? QrcodeStyle.show : ''}`} />
         {isShow && (
           <div
             ref={qrCodeRef}
-            className={QrcodeStyle.qrcodeform}
+            className={`${QrcodeStyle.qrcodeform} !absolute right-0 top-0`}
             onClick={handlePanelClick}
           >
-            <QRCode size={160} value={content} className={QrcodeStyle.qrcodeimage}/>
+            <QRCodeSVG size={160} value={content} className={QrcodeStyle.qrcodeimage}/>
             <div className={QrcodeStyle.text}>
-              <div className={`text-gray-500 ${QrcodeStyle.scan}`}>{t('appOverview.overview.appInfo.qrcode.scan')}</div>
-              <div className={`text-gray-500 ${QrcodeStyle.scan}`}>·</div>
+              <div className={`text-text-tertiary ${QrcodeStyle.scan}`}>{t('appOverview.overview.appInfo.qrcode.scan')}</div>
+              <div className={`text-text-tertiary ${QrcodeStyle.scan}`}>·</div>
               <div className={QrcodeStyle.download} onClick={downloadQR}>{t('appOverview.overview.appInfo.qrcode.download')}</div>
             </div>
           </div>

Файловите разлики са ограничени, защото са твърде много
+ 11 - 9
web/app/components/develop/secret-key/secret-key-button.tsx


+ 1 - 0
web/i18n/en-US/app-overview.ts

@@ -33,6 +33,7 @@ const translation = {
       explanation: 'Ready-to-use AI WebApp',
       accessibleAddress: 'Public URL',
       preview: 'Preview',
+      launch: 'Launch',
       regenerate: 'Regenerate',
       regenerateNotice: 'Do you want to regenerate the public URL?',
       preUseReminder: 'Please enable WebApp before continuing.',

+ 1 - 0
web/i18n/en-US/common.ts

@@ -27,6 +27,7 @@ const translation = {
     sure: 'I\'m sure',
     download: 'Download',
     delete: 'Delete',
+    deleteApp: 'Delete App',
     settings: 'Settings',
     setup: 'Setup',
     getForFree: 'Get for free',

+ 1 - 0
web/i18n/zh-Hans/app-overview.ts

@@ -33,6 +33,7 @@ const translation = {
       explanation: '开箱即用的 AI WebApp',
       accessibleAddress: '公开访问 URL',
       preview: '预览',
+      launch: '启动',
       regenerate: '重新生成',
       regenerateNotice: '您是否要重新生成公开访问 URL?',
       preUseReminder: '使用前请先打开开关',

+ 1 - 0
web/i18n/zh-Hans/common.ts

@@ -27,6 +27,7 @@ const translation = {
     sure: '我确定',
     download: '下载',
     delete: '删除',
+    deleteApp: '删除应用',
     settings: '设置',
     setup: '设置',
     getForFree: '免费获取',

+ 2 - 0
web/tailwind.config.js

@@ -99,6 +99,8 @@ const config = {
         'chatbot-bg': 'var(--color-chatbot-bg)',
         'chat-bubble-bg': 'var(--color-chat-bubble-bg)',
         'workflow-process-bg': 'var(--color-workflow-process-bg)',
+        'app-detail-bg': 'var(--color-app-detail-bg)',
+        'app-detail-overlay-bg': 'var(--color-app-detail-overlay-bg)',
         'dataset-chunk-process-success-bg': 'var(--color-dataset-chunk-process-success-bg)',
         'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)',
         'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)',

+ 11 - 0
web/themes/manual-dark.css

@@ -19,6 +19,17 @@ html[data-theme="dark"] {
     rgba(34, 34, 37, 0.9) -0.1%,
     rgba(29, 29, 32, 0.9) 98.26%
   );
+  --color-app-detail-bg: linear-gradient(
+    169deg,
+    #1D1D20 1.18%,
+    #222225 99.52%
+  );
+  --color-app-detail-overlay-bg: linear-gradient(
+    270deg,
+    rgba(0, 0, 0, 0.00) 0%,
+    rgba(24, 24, 27, 0.02) 8%,
+    rgba(24, 24, 27, 0.54) 100%
+  );
   --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%);
   --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%);
   --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #1D1D20 0%, #222225 100%);

+ 11 - 0
web/themes/manual-light.css

@@ -19,6 +19,17 @@ html[data-theme="light"] {
     rgba(249, 250, 251, 0.9) -0.1%,
     rgba(242, 244, 247, 0.9) 98.26%
   );
+  --color-app-detail-bg: linear-gradient(
+    169deg,
+    #F2F4F7 1.18%,
+    #F9FAFB 99.52%
+  );
+  --color-app-detail-overlay-bg: linear-gradient(
+    270deg,
+    rgba(0, 0, 0, 0.00) 0%,
+    rgba(16, 24, 40, 0.01) 8%,
+    rgba(16, 24, 40, 0.18) 100%
+  );
   --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%);
   --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%);
   --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%);