Просмотр исходного кода

Feat: compliance report download (#13282)

NFish месяцев назад: 2
Родитель
Сommit
7f9eb35e1f
26 измененных файлов с 3364 добавлено и 1480 удалено
  1. 53 0
      web/app/components/base/icons/assets/public/common/gdpr.svg
  2. 1 0
      web/app/components/base/icons/assets/public/common/iso.svg
  3. 103 0
      web/app/components/base/icons/assets/public/common/soc2.svg
  4. 340 0
      web/app/components/base/icons/src/public/common/Gdpr.json
  5. 16 0
      web/app/components/base/icons/src/public/common/Gdpr.tsx
  6. 121 0
      web/app/components/base/icons/src/public/common/Iso.json
  7. 16 0
      web/app/components/base/icons/src/public/common/Iso.tsx
  8. 938 0
      web/app/components/base/icons/src/public/common/Soc2.json
  9. 16 0
      web/app/components/base/icons/src/public/common/Soc2.tsx
  10. 3 0
      web/app/components/base/icons/src/public/common/index.ts
  11. 9 9
      web/app/components/base/icons/src/public/knowledge/Chunk.json
  12. 3 3
      web/app/components/base/icons/src/public/knowledge/Collapse.json
  13. 1 1
      web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json
  14. 185 0
      web/app/components/header/account-dropdown/compliance.tsx
  15. 58 65
      web/app/components/header/account-dropdown/index.tsx
  16. 94 0
      web/app/components/header/account-dropdown/support.tsx
  17. 12 13
      web/app/components/header/account-dropdown/workplace-selector/index.tsx
  18. 12 35
      web/app/components/header/github-star/index.tsx
  19. 1 7
      web/app/components/header/index.tsx
  20. 1 0
      web/config/index.ts
  21. 14 1
      web/i18n/en-US/common.ts
  22. 13 0
      web/i18n/zh-Hans/common.ts
  23. 9 6
      web/service/common.ts
  24. 666 664
      web/themes/dark.css
  25. 677 675
      web/themes/light.css
  26. 2 1
      web/themes/tailwind-theme-var-define.ts

Разница между файлами не показана из-за своего большого размера
+ 53 - 0
web/app/components/base/icons/assets/public/common/gdpr.svg


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
web/app/components/base/icons/assets/public/common/iso.svg


Разница между файлами не показана из-за своего большого размера
+ 103 - 0
web/app/components/base/icons/assets/public/common/soc2.svg


Разница между файлами не показана из-за своего большого размера
+ 340 - 0
web/app/components/base/icons/src/public/common/Gdpr.json


+ 16 - 0
web/app/components/base/icons/src/public/common/Gdpr.tsx

@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Gdpr.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 = 'Gdpr'
+
+export default Icon

Разница между файлами не показана из-за своего большого размера
+ 121 - 0
web/app/components/base/icons/src/public/common/Iso.json


+ 16 - 0
web/app/components/base/icons/src/public/common/Iso.tsx

@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Iso.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 = 'Iso'
+
+export default Icon

Разница между файлами не показана из-за своего большого размера
+ 938 - 0
web/app/components/base/icons/src/public/common/Soc2.json


+ 16 - 0
web/app/components/base/icons/src/public/common/Soc2.tsx

@@ -0,0 +1,16 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './Soc2.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 = 'Soc2'
+
+export default Icon

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

@@ -1,12 +1,15 @@
 export { default as D } from './D'
 export { default as DiagonalDividingLine } from './DiagonalDividingLine'
 export { default as Dify } from './Dify'
+export { default as Gdpr } from './Gdpr'
 export { default as Github } from './Github'
 export { default as Highlight } from './Highlight'
+export { default as Iso } from './Iso'
 export { default as Line3 } from './Line3'
 export { default as Lock } from './Lock'
 export { default as MessageChatSquare } from './MessageChatSquare'
 export { default as MultiPathRetrieval } from './MultiPathRetrieval'
 export { default as NTo1Retrieval } from './NTo1Retrieval'
 export { default as Notion } from './Notion'
+export { default as Soc2 } from './Soc2'
 export { default as SparklesSoft } from './SparklesSoft'

+ 9 - 9
web/app/components/base/icons/src/public/knowledge/Chunk.json

@@ -24,7 +24,7 @@
 						"attributes": {
 							"id": "Vector",
 							"d": "M2.5 10H0V7.5H2.5V10Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -34,7 +34,7 @@
 						"attributes": {
 							"id": "Vector_2",
 							"d": "M6.25 6.25H3.75V3.75H6.25V6.25Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -44,7 +44,7 @@
 						"attributes": {
 							"id": "Vector_3",
 							"d": "M2.5 6.25H0V3.75H2.5V6.25Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -54,7 +54,7 @@
 						"attributes": {
 							"id": "Vector_4",
 							"d": "M6.25 2.5H3.75V0H6.25V2.5Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -64,7 +64,7 @@
 						"attributes": {
 							"id": "Vector_5",
 							"d": "M2.5 2.5H0V0H2.5V2.5Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -74,7 +74,7 @@
 						"attributes": {
 							"id": "Vector_6",
 							"d": "M10 2.5H7.5V0H10V2.5Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -84,7 +84,7 @@
 						"attributes": {
 							"id": "Vector_7",
 							"d": "M9.58342 7.91663H7.91675V9.58329H9.58342V7.91663Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -94,7 +94,7 @@
 						"attributes": {
 							"id": "Vector_8",
 							"d": "M9.58342 4.16663H7.91675V5.83329H9.58342V4.16663Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					},
@@ -104,7 +104,7 @@
 						"attributes": {
 							"id": "Vector_9",
 							"d": "M5.83341 7.91663H4.16675V9.58329H5.83341V7.91663Z",
-							"fill": "currentColor"
+							"fill": "#676F83"
 						},
 						"children": []
 					}

+ 3 - 3
web/app/components/base/icons/src/public/knowledge/Collapse.json

@@ -30,7 +30,7 @@
 								"name": "path",
 								"attributes": {
 									"d": "M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z",
-									"fill": "currentColor"
+									"fill": "#354052"
 								},
 								"children": []
 							},
@@ -39,7 +39,7 @@
 								"name": "path",
 								"attributes": {
 									"d": "M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z",
-									"fill": "currentColor"
+									"fill": "#354052"
 								},
 								"children": []
 							},
@@ -48,7 +48,7 @@
 								"name": "path",
 								"attributes": {
 									"d": "M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z",
-									"fill": "currentColor"
+									"fill": "#354052"
 								},
 								"children": []
 							}

+ 1 - 1
web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json

@@ -24,7 +24,7 @@
 						"attributes": {
 							"id": "Vector",
 							"d": "M14.0002 2C14.3684 2 14.6668 2.29848 14.6668 2.66667V13.3333C14.6668 13.7015 14.3684 14 14.0002 14H2.00016C1.63198 14 1.3335 13.7015 1.3335 13.3333V2.66667C1.3335 2.29848 1.63198 2 2.00016 2H14.0002ZM13.3335 3.33333H2.66683V12.6667H13.3335V3.33333ZM14.0002 2.66667V13.3333H10.0002V2.66667H14.0002Z",
-							"fill": "currentColor"
+							"fill": "#354052"
 						},
 						"children": []
 					}

+ 185 - 0
web/app/components/header/account-dropdown/compliance.tsx

@@ -0,0 +1,185 @@
+import { Menu, Transition } from '@headlessui/react'
+import { RiArrowDownCircleLine, RiArrowRightSLine, RiVerifiedBadgeLine } from '@remixicon/react'
+import type { FC, MouseEvent } from 'react'
+import { Fragment, useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useMutation } from '@tanstack/react-query'
+import PremiumBadge from '../../base/premium-badge'
+import SparklesSoft from '../../base/icons/src/public/common/SparklesSoft'
+import Button from '../../base/button'
+import Soc2 from '../../base/icons/src/public/common/Soc2'
+import Iso from '../../base/icons/src/public/common/Iso'
+import Gdpr from '../../base/icons/src/public/common/Gdpr'
+import Toast from '../../base/toast'
+import Tooltip from '../../base/tooltip'
+import cn from '@/utils/classnames'
+import { useProviderContext } from '@/context/provider-context'
+import { Plan } from '@/app/components/billing/type'
+import { useModalContext } from '@/context/modal-context'
+import { getDocDownloadUrl } from '@/service/common'
+
+enum DocName {
+  'SOC2_Type_I' = 'SOC2_Type_I',
+  'SOC2_Type_II' = 'SOC2_Type_II',
+  'ISO_27001' = 'ISO_27001',
+  'GDPR' = 'GDPR',
+}
+
+type UpgradeOrDownloadProps = {
+  doc_name: DocName
+}
+const UpgradeOrDownload: FC<UpgradeOrDownloadProps> = ({ doc_name }) => {
+  const { t } = useTranslation()
+  const { plan } = useProviderContext()
+  const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
+  const isFreePlan = plan.type === Plan.sandbox
+
+  const handlePlanClick = useCallback(() => {
+    if (isFreePlan)
+      setShowPricingModal()
+    else
+      setShowAccountSettingModal({ payload: 'billing' })
+  }, [isFreePlan, setShowAccountSettingModal, setShowPricingModal])
+
+  const { isPending, mutate: downloadCompliance } = useMutation({
+    mutationKey: ['downloadCompliance', doc_name],
+    mutationFn: async () => {
+      try {
+        const ret = await getDocDownloadUrl(doc_name)
+        const a = document.createElement('a')
+        a.href = ret.url
+        a.click()
+        Toast.notify({
+          type: 'success',
+          message: t('common.operation.downloadSuccess'),
+        })
+      }
+      catch (error) {
+        console.error(error)
+        Toast.notify({
+          type: 'error',
+          message: t('common.operation.downloadFailed'),
+        })
+      }
+    },
+  })
+  const whichPlanCanDownloadCompliance = {
+    [DocName.SOC2_Type_I]: [Plan.professional, Plan.team],
+    [DocName.SOC2_Type_II]: [Plan.team],
+    [DocName.ISO_27001]: [Plan.team],
+    [DocName.GDPR]: [Plan.team, Plan.professional, Plan.sandbox],
+  }
+
+  const isCurrentPlanCanDownload = whichPlanCanDownloadCompliance[doc_name].includes(plan.type)
+  const handleDownloadClick = useCallback((e: MouseEvent<HTMLButtonElement>) => {
+    e.preventDefault()
+    downloadCompliance()
+  }, [downloadCompliance])
+  if (isCurrentPlanCanDownload) {
+    return <Button loading={isPending} disabled={isPending} size='small' variant='secondary' className='flex gap-[1px] items-center' onClick={handleDownloadClick}>
+      <RiArrowDownCircleLine className='size-[14px] text-components-button-secondary-text-disabled' />
+      <span className='px-[3px] system-xs-medium text-components-button-secondary-text'>{t('common.operation.download')}</span>
+    </Button>
+  }
+  const upgradeTooltip: Record<Plan, string> = {
+    [Plan.sandbox]: t('common.compliance.sandboxUpgradeTooltip'),
+    [Plan.professional]: t('common.compliance.professionalUpgradeTooltip'),
+    [Plan.team]: '',
+    [Plan.enterprise]: '',
+  }
+  return <Tooltip asChild={false} popupContent={upgradeTooltip[plan.type]}>
+    <PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
+      <SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
+      <div className='system-xs-medium'>
+        <span className='p-1'>
+          {t('billing.upgradeBtn.encourageShort')}
+        </span>
+      </div>
+    </PremiumBadge>
+  </Tooltip>
+}
+
+export default function Compliance() {
+  const itemClassName = `
+  flex items-center w-full h-10 pl-1 pr-2 py-1 text-text-secondary system-md-regular
+  rounded-lg hover:bg-state-base-hover gap-1
+`
+  const { t } = useTranslation()
+
+  return <Menu as="div" className="relative w-full h-full">
+    {
+      ({ open }) => (
+        <>
+          <Menu.Button className={
+            cn('flex items-center pl-3 pr-2 py-2 h-9 w-full group hover:bg-state-base-hover rounded-lg gap-1',
+              open && 'bg-state-base-hover',
+            )}>
+            <RiVerifiedBadgeLine className='flex-shrink-0 size-4 text-text-tertiary' />
+            <div className='flex-grow text-left system-md-regular text-text-secondary px-1'>{t('common.userProfile.compliance')}</div>
+            <RiArrowRightSLine className='shrink-0 size-[14px] text-text-tertiary' />
+          </Menu.Button>
+          <Transition
+            as={Fragment}
+            enter="transition ease-out duration-100"
+            enterFrom="transform opacity-0 scale-95"
+            enterTo="transform opacity-100 scale-100"
+            leave="transition ease-in duration-75"
+            leaveFrom="transform opacity-100 scale-100"
+            leaveTo="transform opacity-0 scale-95"
+          >
+            <Menu.Items
+              className={cn(
+                `absolute top-[1px] w-[337px] max-h-[70vh] overflow-y-scroll z-10 bg-components-panel-bg-blur backdrop-blur-[5px] border-[0.5px] border-components-panel-border
+                divide-y divide-divider-subtle origin-top-right rounded-xl focus:outline-none shadow-lg -translate-x-full
+              `,
+              )}
+            >
+              <div className="px-1 py-1">
+                <Menu.Item>
+                  {({ active }) => <div
+                    className={cn(itemClassName, 'group justify-between',
+                      active && 'bg-state-base-hover',
+                    )}>
+                    <Soc2 className='flex-shrink-0 size-7' />
+                    <div className='system-md-regular flex-grow text-text-secondary px-1 truncate'>{t('common.compliance.soc2Type1')}</div>
+                    <UpgradeOrDownload doc_name={DocName.SOC2_Type_I} />
+                  </div>}
+                </Menu.Item>
+                <Menu.Item>
+                  {({ active }) => <div
+                    className={cn(itemClassName, 'group justify-between',
+                      active && 'bg-state-base-hover',
+                    )}>
+                    <Soc2 className='flex-shrink-0 size-7' />
+                    <div className='system-md-regular flex-grow text-text-secondary px-1 truncate'>{t('common.compliance.soc2Type2')}</div>
+                    <UpgradeOrDownload doc_name={DocName.SOC2_Type_II} />
+                  </div>}
+                </Menu.Item>
+                <Menu.Item>
+                  {({ active }) => <div
+                    className={cn(itemClassName, 'group justify-between',
+                      active && 'bg-state-base-hover',
+                    )}>
+                    <Iso className='flex-shrink-0 size-7' />
+                    <div className='system-md-regular flex-grow text-text-secondary px-1 truncate'>{t('common.compliance.iso27001')}</div>
+                    <UpgradeOrDownload doc_name={DocName.ISO_27001} />
+                  </div>}
+                </Menu.Item>
+                <Menu.Item>
+                  {({ active }) => <div
+                    className={cn(itemClassName, 'group justify-between',
+                      active && 'bg-state-base-hover',
+                    )}>
+                    <Gdpr className='flex-shrink-0 size-7' />
+                    <div className='system-md-regular flex-grow text-text-secondary px-1 truncate'>{t('common.compliance.gdpr')}</div>
+                    <UpgradeOrDownload doc_name={DocName.GDPR} />
+                  </div>}
+                </Menu.Item>
+              </div>
+            </Menu.Items>
+          </Transition>
+        </>
+      )
+    }
+  </Menu>
+}

+ 58 - 65
web/app/components/header/account-dropdown/index.tsx

@@ -2,24 +2,25 @@
 import { useTranslation } from 'react-i18next'
 import { Fragment, useState } from 'react'
 import { useRouter } from 'next/navigation'
-import { useContext } from 'use-context-selector'
-import { RiArrowDownSLine, RiLogoutBoxRLine } from '@remixicon/react'
+import { useContext, useContextSelector } from 'use-context-selector'
+import { RiAccountCircleLine, RiArrowDownSLine, RiArrowRightUpLine, RiBookOpenLine, RiGithubLine, RiInformation2Line, RiLogoutBoxRLine, RiMap2Line, RiSettings3Line, RiStarLine } from '@remixicon/react'
 import Link from 'next/link'
 import { Menu, Transition } from '@headlessui/react'
 import Indicator from '../indicator'
 import AccountAbout from '../account-about'
-import { mailToSupport } from '../utils/util'
+import GithubStar from '../github-star'
 import WorkplaceSelector from './workplace-selector'
+import Support from './support'
+import Compliance from './compliance'
 import classNames from '@/utils/classnames'
 import I18n from '@/context/i18n'
 import Avatar from '@/app/components/base/avatar'
 import { logout } from '@/service/common'
-import { useAppContext } from '@/context/app-context'
-import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
+import AppContext, { useAppContext } from '@/context/app-context'
 import { useModalContext } from '@/context/modal-context'
 import { LanguagesSupported } from '@/i18n/language'
-import { useProviderContext } from '@/context/provider-context'
-import { Plan } from '@/app/components/billing/type'
+import { LicenseStatus } from '@/types/feature'
+import { IS_CLOUD_EDITION } from '@/config'
 
 export type IAppSelector = {
   isMobile: boolean
@@ -27,18 +28,17 @@ export type IAppSelector = {
 
 export default function AppSelector({ isMobile }: IAppSelector) {
   const itemClassName = `
-    flex items-center w-full h-9 px-3 text-text-secondary system-md-regular
-    rounded-lg hover:bg-state-base-hover cursor-pointer
+    flex items-center w-full h-9 pl-3 pr-2 text-text-secondary system-md-regular
+    rounded-lg hover:bg-state-base-hover cursor-pointer gap-1
   `
   const router = useRouter()
   const [aboutVisible, setAboutVisible] = useState(false)
+  const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
 
   const { locale } = useContext(I18n)
   const { t } = useTranslation()
-  const { userProfile, langeniusVersionInfo } = useAppContext()
+  const { userProfile, langeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
   const { setShowAccountSettingModal } = useModalContext()
-  const { plan } = useProviderContext()
-  const canEmailSupport = plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise
 
   const handleLogout = async () => {
     await logout({
@@ -63,15 +63,15 @@ export default function AppSelector({ isMobile }: IAppSelector) {
                 className={`
                     inline-flex items-center
                     rounded-[20px] py-1 pr-2.5 pl-1 text-sm
-                  text-gray-700 hover:bg-gray-200
+                  text-text-secondary hover:bg-state-base-hover
                     mobile:px-1
-                    ${open && 'bg-gray-200'}
+                    ${open && 'bg-state-base-hover'}
                   `}
               >
                 <Avatar avatar={userProfile.avatar_url} name={userProfile.name} className='sm:mr-2 mr-0' size={32} />
                 {!isMobile && <>
                   {userProfile.name}
-                  <RiArrowDownSLine className="w-3 h-3 ml-1 text-gray-700" />
+                  <RiArrowDownSLine className="w-3 h-3 ml-1 text-text-tertiary" />
                 </>}
               </Menu.Button>
               <Transition
@@ -91,102 +91,95 @@ export default function AppSelector({ isMobile }: IAppSelector) {
                   "
                 >
                   <Menu.Item disabled>
-                    <div className='flex flex-nowrap items-center px-4 py-[13px]'>
-                      <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} className='mr-3' />
+                    <div className='flex flex-nowrap items-center pl-3 pr-2 py-[13px]'>
                       <div className='grow'>
                         <div className='system-md-medium text-text-primary break-all'>{userProfile.name}</div>
                         <div className='system-xs-regular text-text-tertiary break-all'>{userProfile.email}</div>
                       </div>
+                      <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} className='mr-3' />
                     </div>
                   </Menu.Item>
-                  <div className='px-1 py-1'>
+                  <div className='p-1'>
                     <div className='mt-2 px-3 text-xs font-medium text-text-tertiary'>{t('common.userProfile.workspace')}</div>
                     <WorkplaceSelector />
                   </div>
-                  <div className="px-1 py-1">
+                  <div className="p-1">
                     <Menu.Item>
                       {({ active }) => <Link
-                        className={classNames(itemClassName, 'group justify-between',
+                        className={classNames(itemClassName, 'group',
                           active && 'bg-state-base-hover',
                         )}
                         href='/account'
                         target='_self' rel='noopener noreferrer'>
-                        <div>{t('common.account.account')}</div>
-                        <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
+                        <RiAccountCircleLine className='size-4 flex-shrink-0 text-text-tertiary' />
+                        <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.account.account')}</div>
+                        <RiArrowRightUpLine className='size-[14px] flex-shrink-0 text-text-tertiary' />
                       </Link>}
                     </Menu.Item>
                     <Menu.Item>
                       {({ active }) => <div className={classNames(itemClassName,
                         active && 'bg-state-base-hover',
                       )} onClick={() => setShowAccountSettingModal({ payload: 'members' })}>
-                        <div>{t('common.userProfile.settings')}</div>
+                        <RiSettings3Line className='size-4 flex-shrink-0 text-text-tertiary' />
+                        <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.settings')}</div>
                       </div>}
                     </Menu.Item>
-                    {canEmailSupport && <Menu.Item>
-                      {({ active }) => <a
-                        className={classNames(itemClassName, 'group justify-between',
-                          active && 'bg-state-base-hover',
-                        )}
-                        href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
-                        target='_blank' rel='noopener noreferrer'>
-                        <div>{t('common.userProfile.emailSupport')}</div>
-                        <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
-                      </a>}
-                    </Menu.Item>}
-                    <Menu.Item>
-                      {({ active }) => <Link
-                        className={classNames(itemClassName, 'group justify-between',
-                          active && 'bg-state-base-hover',
-                        )}
-                        href='https://github.com/langgenius/dify/discussions/categories/feedbacks'
-                        target='_blank' rel='noopener noreferrer'>
-                        <div>{t('common.userProfile.communityFeedback')}</div>
-                        <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
-                      </Link>}
-                    </Menu.Item>
+                  </div>
+                  <div className='p-1'>
                     <Menu.Item>
                       {({ active }) => <Link
                         className={classNames(itemClassName, 'group justify-between',
                           active && 'bg-state-base-hover',
                         )}
-                        href='https://discord.gg/5AEfbxcd9k'
+                        href={
+                          locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
+                        }
                         target='_blank' rel='noopener noreferrer'>
-                        <div>{t('common.userProfile.community')}</div>
-                        <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
+                        <RiBookOpenLine className='flex-shrink-0 size-4 text-text-tertiary' />
+                        <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.helpCenter')}</div>
+                        <RiArrowRightUpLine className='flex-shrink-0 size-[14px] text-text-tertiary' />
                       </Link>}
                     </Menu.Item>
+                    <Support />
+                    {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />}
+                  </div>
+                  <div className='p-1'>
                     <Menu.Item>
                       {({ active }) => <Link
                         className={classNames(itemClassName, 'group justify-between',
                           active && 'bg-state-base-hover',
                         )}
-                        href={
-                          locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
-                        }
+                        href='https://roadmap.dify.ai'
                         target='_blank' rel='noopener noreferrer'>
-                        <div>{t('common.userProfile.helpCenter')}</div>
-                        <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
+                        <RiMap2Line className='flex-shrink-0 size-4 text-text-tertiary' />
+                        <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.roadmap')}</div>
+                        <RiArrowRightUpLine className='flex-shrink-0 size-[14px] text-text-tertiary' />
                       </Link>}
                     </Menu.Item>
-                    <Menu.Item>
+                    {systemFeatures.license.status === LicenseStatus.NONE && <Menu.Item>
                       {({ active }) => <Link
                         className={classNames(itemClassName, 'group justify-between',
                           active && 'bg-state-base-hover',
                         )}
-                        href='https://roadmap.dify.ai'
+                        href='https://github.com/langgenius/dify/stargazers'
                         target='_blank' rel='noopener noreferrer'>
-                        <div>{t('common.userProfile.roadmap')}</div>
-                        <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
+                        <RiGithubLine className='flex-shrink-0 size-4 text-text-tertiary' />
+                        <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.github')}</div>
+                        <div className='flex items-center gap-0.5 px-[5px] py-[3px] border border-divider-deep rounded-[5px] bg-components-badge-bg-dimm'>
+                          <RiStarLine className='flex-shrink-0 size-3 text-text-tertiary' />
+                          <GithubStar className='system-2xs-medium-uppercase text-text-tertiary' />
+                        </div>
                       </Link>}
-                    </Menu.Item>
+                    </Menu.Item>}
                     {
                       document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
                         <Menu.Item>
                           {({ active }) => <div className={classNames(itemClassName, 'justify-between',
                             active && 'bg-state-base-hover',
                           )} onClick={() => setAboutVisible(true)}>
-                            <div>{t('common.userProfile.about')}</div>
-                            <div className='flex items-center'>
+                            <RiInformation2Line className='flex-shrink-0 size-4 text-text-tertiary' />
+                            <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.about')}</div>
+                            <div className='flex-shrink-0 flex items-center'>
                               <div className='mr-2 system-xs-regular text-text-tertiary'>{langeniusVersionInfo.current_version}</div>
                               <Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
                             </div>
@@ -198,12 +191,12 @@ export default function AppSelector({ isMobile }: IAppSelector) {
                   <Menu.Item>
                     {({ active }) => <div className='p-1' onClick={() => handleLogout()}>
                       <div
-                        className={
-                          classNames('flex items-center justify-between h-9 px-3 rounded-lg cursor-pointer group hover:bg-state-base-hover',
-                            active && 'bg-state-base-hover')}
+                        className={classNames(itemClassName, 'group justify-between',
+                          active && 'bg-state-base-hover',
+                        )}
                       >
-                        <div className='system-md-regular text-text-secondary'>{t('common.userProfile.logout')}</div>
-                        <RiLogoutBoxRLine className='hidden w-4 h-4 text-text-tertiary group-hover:flex' />
+                        <RiLogoutBoxRLine className='flex-shrink-0 size-4 text-text-tertiary' />
+                        <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.logout')}</div>
                       </div>
                     </div>}
                   </Menu.Item>

+ 94 - 0
web/app/components/header/account-dropdown/support.tsx

@@ -0,0 +1,94 @@
+import { Menu, Transition } from '@headlessui/react'
+import { RiArrowRightSLine, RiArrowRightUpLine, RiDiscordLine, RiFeedbackLine, RiMailSendLine, RiQuestionLine } from '@remixicon/react'
+import { Fragment } from 'react'
+import Link from 'next/link'
+import { useTranslation } from 'react-i18next'
+import { mailToSupport } from '../utils/util'
+import cn from '@/utils/classnames'
+import { useProviderContext } from '@/context/provider-context'
+import { Plan } from '@/app/components/billing/type'
+import { useAppContext } from '@/context/app-context'
+
+export default function Support() {
+  const itemClassName = `
+  flex items-center w-full h-9 pl-3 pr-2 text-text-secondary system-md-regular
+  rounded-lg hover:bg-state-base-hover cursor-pointer gap-1
+`
+  const { t } = useTranslation()
+  const { plan } = useProviderContext()
+  const { userProfile, langeniusVersionInfo } = useAppContext()
+  const canEmailSupport = plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise
+
+  return <Menu as="div" className="relative w-full h-full">
+    {
+      ({ open }) => (
+        <>
+          <Menu.Button className={
+            cn('flex items-center pl-3 pr-2 py-2 h-9 w-full group hover:bg-state-base-hover rounded-lg gap-1',
+              open && 'bg-state-base-hover',
+            )}>
+            <RiQuestionLine className='flex-shrink-0 size-4 text-text-tertiary' />
+            <div className='flex-grow text-left system-md-regular text-text-secondary px-1'>{t('common.userProfile.support')}</div>
+            <RiArrowRightSLine className='shrink-0 size-[14px] text-text-tertiary' />
+          </Menu.Button>
+          <Transition
+            as={Fragment}
+            enter="transition ease-out duration-100"
+            enterFrom="transform opacity-0 scale-95"
+            enterTo="transform opacity-100 scale-100"
+            leave="transition ease-in duration-75"
+            leaveFrom="transform opacity-100 scale-100"
+            leaveTo="transform opacity-0 scale-95"
+          >
+            <Menu.Items
+              className={cn(
+                `absolute top-[1px] w-[216px] max-h-[70vh] overflow-y-scroll z-10 bg-components-panel-bg-blur backdrop-blur-[5px] border-[0.5px] border-components-panel-border
+                divide-y divide-divider-subtle origin-top-right rounded-xl focus:outline-none shadow-lg -translate-x-full
+              `,
+              )}
+            >
+              <div className="px-1 py-1">
+                {canEmailSupport && <Menu.Item>
+                  {({ active }) => <a
+                    className={cn(itemClassName, 'group justify-between',
+                      active && 'bg-state-base-hover',
+                    )}
+                    href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
+                    target='_blank' rel='noopener noreferrer'>
+                    <RiMailSendLine className='flex-shrink-0 size-4 text-text-tertiary' />
+                    <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.emailSupport')}</div>
+                    <RiArrowRightUpLine className='flex-shrink-0 size-[14px] text-text-tertiary' />
+                  </a>}
+                </Menu.Item>}
+                <Menu.Item>
+                  {({ active }) => <Link
+                    className={cn(itemClassName, 'group justify-between',
+                      active && 'bg-state-base-hover',
+                    )}
+                    href='https://github.com/langgenius/dify/discussions/categories/feedbacks'
+                    target='_blank' rel='noopener noreferrer'>
+                    <RiFeedbackLine className='flex-shrink-0 size-4 text-text-tertiary' />
+                    <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.communityFeedback')}</div>
+                    <RiArrowRightUpLine className='flex-shrink-0 size-[14px] text-text-tertiary' />
+                  </Link>}
+                </Menu.Item>
+                <Menu.Item>
+                  {({ active }) => <Link
+                    className={cn(itemClassName, 'group justify-between',
+                      active && 'bg-state-base-hover',
+                    )}
+                    href='https://discord.gg/5AEfbxcd9k'
+                    target='_blank' rel='noopener noreferrer'>
+                    <RiDiscordLine className='flex-shrink-0 size-4 text-text-tertiary' />
+                    <div className='flex-grow system-md-regular text-text-secondary px-1'>{t('common.userProfile.community')}</div>
+                    <RiArrowRightUpLine className='flex-shrink-0 size-[14px] text-text-tertiary' />
+                  </Link>}
+                </Menu.Item>
+              </div>
+            </Menu.Items>
+          </Transition>
+        </>
+      )
+    }
+  </Menu>
+}

+ 12 - 13
web/app/components/header/account-dropdown/workplace-selector/index.tsx

@@ -9,19 +9,18 @@ import { useWorkspacesContext } from '@/context/workspace-context'
 import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
 import { Check } from '@/app/components/base/icons/src/vender/line/general'
 import { ToastContext } from '@/app/components/base/toast'
-import classNames from '@/utils/classnames'
 
 const itemClassName = `
-  flex items-center px-3 py-2 h-10 cursor-pointer
+  flex items-center px-3 py-2 h-9 cursor-pointer rounded-lg
 `
 const itemIconClassName = `
-  shrink-0 mr-2 flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600
+  shrink-0 mr-2 flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-text-accent
 `
 const itemNameClassName = `
-  grow mr-2 text-sm text-gray-700 text-left
+  grow mr-2 text-sm text-text-secondary text-left
 `
 const itemCheckClassName = `
-  shrink-0 w-4 h-4 text-primary-600
+  shrink-0 w-4 h-4 text-text-accent
 `
 
 const WorkplaceSelector = () => {
@@ -48,15 +47,15 @@ const WorkplaceSelector = () => {
       {
         ({ open }) => (
           <>
-            <Menu.Button className={cn(
+            <Menu.Button className={cn(itemClassName,
               `
-                ${itemClassName} w-full
-                group hover:bg-state-base-hover cursor-pointer ${open && 'bg-state-base-hover'} rounded-lg
+                w-full group hover:bg-state-base-hover pl-3 pr-2
               `,
+              open && 'bg-state-base-hover',
             )}>
               <div className={itemIconClassName}>{currentWorkspace?.name[0].toLocaleUpperCase()}</div>
-              <div className={`${itemNameClassName} truncate`}>{currentWorkspace?.name}</div>
-              <ChevronRight className='shrink-0 w-[14px] h-[14px] text-gray-500' />
+              <div className={`${itemNameClassName} truncate px-1`}>{currentWorkspace?.name}</div>
+              <ChevronRight className='shrink-0 size-[14px] text-text-tertiary' />
             </Menu.Button>
             <Transition
               as={Fragment}
@@ -70,8 +69,8 @@ const WorkplaceSelector = () => {
               <Menu.Items
                 className={cn(
                   `
-                    absolute top-[1px] min-w-[200px] max-h-[70vh] overflow-y-scroll z-10 bg-white border-[0.5px] border-gray-200
-                    divide-y divide-gray-100 origin-top-right rounded-xl focus:outline-none
+                    absolute top-[1px] w-[216px] max-h-[70vh] overflow-y-scroll z-10 bg-components-panel-bg-blur backdrop-blur-[5px] border-[0.5px] border-components-panel-border
+                    divide-y divide-divider-subtle origin-top-right rounded-xl focus:outline-none
                   `,
                   s.popup,
                 )}
@@ -80,7 +79,7 @@ const WorkplaceSelector = () => {
                   {
                     workspaces.map(workspace => (
                       <Menu.Item key={workspace.id}>
-                        {({ active }) => <div className={classNames(itemClassName,
+                        {({ active }) => <div className={cn(itemClassName,
                           active && 'bg-state-base-hover',
                         )} key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
                           <div className={itemIconClassName}>{workspace.name[0].toLocaleUpperCase()}</div>

+ 12 - 35
web/app/components/header/github-star/index.tsx

@@ -1,50 +1,27 @@
 'use client'
-import React, { useEffect, useState } from 'react'
-import { Github } from '@/app/components/base/icons/src/public/common'
+import { useQuery } from '@tanstack/react-query'
+import type { FC } from 'react'
 import type { GithubRepo } from '@/models/common'
 
 const getStar = async () => {
   const res = await fetch('https://api.github.com/repos/langgenius/dify')
 
   if (!res.ok)
-    throw new Error('Failed to fetch data')
+    throw new Error('Failed to fetch github star')
 
   return res.json()
 }
 
-const GithubStar = () => {
-  const [githubRepo, setGithubRepo] = useState<GithubRepo>({ stargazers_count: 6000 })
-  const [isFetched, setIsFetched] = useState(false)
-  useEffect(() => {
-    (async () => {
-      try {
-        if (process.env.NODE_ENV === 'development')
-          return
-
-        await setGithubRepo(await getStar())
-        setIsFetched(true)
-      }
-      catch (e) {
-
-      }
-    })()
-  }, [])
-
-  if (!isFetched)
+const GithubStar: FC<{ className: string }> = (props) => {
+  const { isFetching, data } = useQuery<GithubRepo>({
+    queryKey: ['github-star'],
+    queryFn: getStar,
+    enabled: process.env.NODE_ENV !== 'development',
+    initialData: { stargazers_count: 6000 },
+  })
+  if (isFetching)
     return null
-
-  return (
-    <a
-      href='https://github.com/langgenius/dify'
-      target='_blank' rel='noopener noreferrer'
-      className='flex items-center leading-[18px] border border-gray-200 rounded-md text-xs text-gray-700 font-semibold overflow-hidden'>
-      <div className='flex items-center px-2 py-1 bg-gray-100'>
-        <Github className='mr-1 w-[18px] h-[18px]' />
-        Star
-      </div>
-      <div className='px-2 py-1 bg-white border-l border-gray-200'>{`${githubRepo.stargazers_count}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}</div>
-    </a>
-  )
+  return <span {...props}>{data.stargazers_count.toLocaleString()}</span>
 }
 
 export default GithubStar

+ 1 - 7
web/app/components/header/index.tsx

@@ -4,7 +4,6 @@ import Link from 'next/link'
 import { useBoolean } from 'ahooks'
 import { useSelectedLayoutSegment } from 'next/navigation'
 import { Bars3Icon } from '@heroicons/react/20/solid'
-import { useContextSelector } from 'use-context-selector'
 import HeaderBillingBtn from '../billing/header-billing-btn'
 import AccountDropdown from './account-dropdown'
 import AppNav from './app-nav'
@@ -12,15 +11,13 @@ import DatasetNav from './dataset-nav'
 import EnvNav from './env-nav'
 import ExploreNav from './explore-nav'
 import ToolsNav from './tools-nav'
-import GithubStar from './github-star'
 import LicenseNav from './license-env'
 import { WorkspaceProvider } from '@/context/workspace-context'
-import AppContext, { useAppContext } from '@/context/app-context'
+import { useAppContext } from '@/context/app-context'
 import LogoSite from '@/app/components/base/logo/logo-site'
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
 import { useProviderContext } from '@/context/provider-context'
 import { useModalContext } from '@/context/modal-context'
-import { LicenseStatus } from '@/types/feature'
 
 const navClassName = `
   flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl
@@ -30,7 +27,6 @@ const navClassName = `
 
 const Header = () => {
   const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
-  const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
   const selectedSegment = useSelectedLayoutSegment()
   const media = useBreakpoints()
   const isMobile = media === MediaType.mobile
@@ -62,7 +58,6 @@ const Header = () => {
           <Link href="/apps" className='flex items-center mr-4'>
             <LogoSite className='object-contain' />
           </Link>
-          {systemFeatures.license.status === LicenseStatus.NONE && <GithubStar />}
         </>}
       </div>
       {isMobile && (
@@ -70,7 +65,6 @@ const Header = () => {
           <Link href="/apps" className='flex items-center mr-4'>
             <LogoSite />
           </Link>
-          {systemFeatures.license.status === LicenseStatus.NONE && <GithubStar />}
         </div>
       )}
       {!isMobile && (

+ 1 - 0
web/config/index.ts

@@ -32,6 +32,7 @@ export const PUBLIC_API_PREFIX: string = publicApiPrefix
 
 const EDITION = process.env.NEXT_PUBLIC_EDITION || globalThis.document?.body?.getAttribute('data-public-edition') || 'SELF_HOSTED'
 export const IS_CE_EDITION = EDITION === 'SELF_HOSTED'
+export const IS_CLOUD_EDITION = EDITION === 'CLOUD'
 
 export const SUPPORT_MAIL_LOGIN = !!(process.env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN || globalThis.document?.body?.getAttribute('data-public-support-mail-login'))
 

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

@@ -26,6 +26,8 @@ const translation = {
     lineBreak: 'Line break',
     sure: 'I\'m sure',
     download: 'Download',
+    downloadSuccess: 'Download Completed.',
+    downloadFailed: 'Download failed. Please try again later.',
     delete: 'Delete',
     deleteApp: 'Delete App',
     settings: 'Settings',
@@ -145,13 +147,24 @@ const translation = {
     emailSupport: 'Email Support',
     workspace: 'Workspace',
     createWorkspace: 'Create Workspace',
-    helpCenter: 'Help',
+    helpCenter: 'Docs',
+    support: 'Support',
+    compliance: 'Compliance',
     communityFeedback: 'Feedback',
     roadmap: 'Roadmap',
+    github: 'GitHub',
     community: 'Community',
     about: 'About',
     logout: 'Log out',
   },
+  compliance: {
+    soc2Type1: 'SOC 2 Type I Report',
+    soc2Type2: 'SOC 2 Type II Report',
+    iso27001: 'ISO 27001:2022 Certification',
+    gdpr: 'GDPR DPA',
+    sandboxUpgradeTooltip: 'Only available with a Professional or Team plan.',
+    professionalUpgradeTooltip: 'Only available with a Team plan or above.',
+  },
   settings: {
     accountGroup: 'GENERAL',
     workplaceGroup: 'WORKSPACE',

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

@@ -26,6 +26,8 @@ const translation = {
     lineBreak: '换行',
     sure: '我确定',
     download: '下载',
+    downloadSuccess: '下载完毕',
+    downloadFailed: '下载失败,请稍后重试。',
     delete: '删除',
     deleteApp: '删除应用',
     settings: '设置',
@@ -146,12 +148,23 @@ const translation = {
     workspace: '工作空间',
     createWorkspace: '创建工作空间',
     helpCenter: '帮助文档',
+    support: '支持',
+    compliance: '合规',
     communityFeedback: '用户反馈',
     roadmap: '路线图',
+    github: 'GitHub',
     community: '社区',
     about: '关于',
     logout: '登出',
   },
+  compliance: {
+    soc2Type1: 'SOC 2 Type I Report',
+    soc2Type2: 'SOC 2 Type II Report',
+    iso27001: 'ISO 27001:2022 Certification',
+    gdpr: 'GDPR DPA',
+    sandboxUpgradeTooltip: '仅适用于 Professional 或 Team 版计划。',
+    professionalUpgradeTooltip: '仅适用于 Team 版计划或以上。',
+  },
   settings: {
     accountGroup: '通用',
     workplaceGroup: '工作空间',

+ 9 - 6
web/service/common.ts

@@ -40,7 +40,7 @@ import type { SystemFeatures } from '@/types/feature'
 
 type LoginSuccess = {
   result: 'success'
-  data: { access_token: string;refresh_token: string }
+  data: { access_token: string; refresh_token: string }
 }
 type LoginFail = {
   result: 'fail'
@@ -331,20 +331,23 @@ export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => {
 export const sendEMailLoginCode = (email: string, language = 'en-US') =>
   post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } })
 
-export const emailLoginWithCode = (data: { email: string;code: string;token: string }) =>
+export const emailLoginWithCode = (data: { email: string; code: string; token: string }) =>
   post<LoginResponse>('/email-code-login/validity', { body: data })
 
 export const sendResetPasswordCode = (email: string, language = 'en-US') =>
-  post<CommonResponse & { data: string;message?: string ;code?: string }>('/forgot-password', { body: { email, language } })
+  post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } })
 
-export const verifyResetPasswordCode = (body: { email: string;code: string;token: string }) =>
+export const verifyResetPasswordCode = (body: { email: string; code: string; token: string }) =>
   post<CommonResponse & { is_valid: boolean }>('/forgot-password/validity', { body })
 
 export const sendDeleteAccountCode = () =>
   get<CommonResponse & { data: string }>('/account/delete/verify')
 
-export const verifyDeleteAccountCode = (body: { code: string;token: string }) =>
+export const verifyDeleteAccountCode = (body: { code: string; token: string }) =>
   post<CommonResponse & { is_valid: boolean }>('/account/delete', { body })
 
-export const submitDeleteAccountFeedback = (body: { feedback: string;email: string }) =>
+export const submitDeleteAccountFeedback = (body: { feedback: string; email: string }) =>
   post<CommonResponse>('/account/delete/feedback', { body })
+
+export const getDocDownloadUrl = (doc_name: string) =>
+  get<{ url: string }>('/compliance/download', { params: { doc_name } }, { silent: true })

Разница между файлами не показана из-за своего большого размера
+ 666 - 664
web/themes/dark.css


Разница между файлами не показана из-за своего большого размера
+ 677 - 675
web/themes/light.css


+ 2 - 1
web/themes/tailwind-theme-var-define.ts

@@ -206,6 +206,7 @@ const vars = {
   'components-badge-bg-red-soft': 'var(--color-components-badge-bg-red-soft)',
   'components-badge-bg-blue-light-soft': 'var(--color-components-badge-bg-blue-light-soft)',
   'components-badge-bg-gray-soft': 'var(--color-components-badge-bg-gray-soft)',
+  'components-badge-bg-dimm': 'var(--color-components-badge-bg-dimm)',
 
   'components-chart-line': 'var(--color-components-chart-line)',
   'components-chart-area-1': 'var(--color-components-chart-area-1)',
@@ -399,7 +400,6 @@ const vars = {
   'background-default-burn': 'var(--color-background-default-burn)',
   'background-overlay-fullscreen': 'var(--color-background-overlay-fullscreen)',
   'background-default-lighter': 'var(--color-background-default-lighter)',
-  'background-account-teams-bg': 'var(--color-account-teams-bg)',
   'background-section': 'var(--color-background-section)',
   'background-interaction-from-bg-1': 'var(--color-background-interaction-from-bg-1)',
   'background-interaction-from-bg-2': 'var(--color-background-interaction-from-bg-2)',
@@ -503,6 +503,7 @@ const vars = {
   'state-base-hover-alt': 'var(--color-state-base-hover-alt)',
   'state-base-handle': 'var(--color-state-base-handle)',
   'state-base-handle-hover': 'var(--color-state-base-handle-hover)',
+  'state-base-hover-subtle': 'var(--color-state-base-hover-subtle)',
 
   'state-accent-hover': 'var(--color-state-accent-hover)',
   'state-accent-active': 'var(--color-state-accent-active)',