Quellcode durchsuchen

feat: show more usage info in billing page (#4808)

Joel vor 10 Monaten
Ursprung
Commit
a7fb1ffcd8

+ 6 - 0
web/app/components/base/icons/assets/vender/line/files/file-upload.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="file-upload">
+<path id="Icon" d="M20 10.5V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H12M14 11H8M10 15H8M16 7H8" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path id="Icon_2" d="M15 18L18 15M18 15L21 18M18 15L18 21" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</svg>

+ 52 - 0
web/app/components/base/icons/src/vender/line/files/FileUpload.json

@@ -0,0 +1,52 @@
+{
+	"icon": {
+		"type": "element",
+		"isRootNode": true,
+		"name": "svg",
+		"attributes": {
+			"width": "24",
+			"height": "24",
+			"viewBox": "0 0 24 24",
+			"fill": "none",
+			"xmlns": "http://www.w3.org/2000/svg"
+		},
+		"children": [
+			{
+				"type": "element",
+				"name": "g",
+				"attributes": {
+					"id": "file-upload"
+				},
+				"children": [
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon",
+							"d": "M20 10.5V6.8C20 5.11984 20 4.27976 19.673 3.63803C19.3854 3.07354 18.9265 2.6146 18.362 2.32698C17.7202 2 16.8802 2 15.2 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H12M14 11H8M10 15H8M16 7H8",
+							"stroke": "currentColor",
+							"stroke-width": "2",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					},
+					{
+						"type": "element",
+						"name": "path",
+						"attributes": {
+							"id": "Icon_2",
+							"d": "M15 18L18 15M18 15L21 18M18 15L18 21",
+							"stroke": "currentColor",
+							"stroke-width": "2",
+							"stroke-linecap": "round",
+							"stroke-linejoin": "round"
+						},
+						"children": []
+					}
+				]
+			}
+		]
+	},
+	"name": "FileUpload"
+}

+ 16 - 0
web/app/components/base/icons/src/vender/line/files/FileUpload.tsx

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

+ 1 - 0
web/app/components/base/icons/src/vender/line/files/index.ts

@@ -7,4 +7,5 @@ export { default as FileDownload02 } from './FileDownload02'
 export { default as FilePlus01 } from './FilePlus01'
 export { default as FilePlus02 } from './FilePlus02'
 export { default as FileText } from './FileText'
+export { default as FileUpload } from './FileUpload'
 export { default as Folder } from './Folder'

+ 2 - 0
web/app/components/billing/config.ts

@@ -86,11 +86,13 @@ export const defaultPlan = {
     buildApps: 1,
     teamMembers: 1,
     annotatedResponse: 1,
+    documentsUploadQuota: 1,
   },
   total: {
     vectorSpace: 10,
     buildApps: 10,
     teamMembers: 1,
     annotatedResponse: 10,
+    documentsUploadQuota: 50,
   },
 }

+ 32 - 1
web/app/components/billing/plan/index.tsx

@@ -7,7 +7,11 @@ import { Plan } from '../type'
 import VectorSpaceInfo from '../usage-info/vector-space-info'
 import AppsInfo from '../usage-info/apps-info'
 import UpgradeBtn from '../upgrade-btn'
+import { User01 } from '../../base/icons/src/vender/line/users'
+import { MessageFastPlus } from '../../base/icons/src/vender/line/communication'
+import { FileUpload } from '../../base/icons/src/vender/line/files'
 import { useProviderContext } from '@/context/provider-context'
+import UsageInfo from '@/app/components/billing/usage-info'
 
 const typeStyle = {
   [Plan.sandbox]: {
@@ -41,6 +45,11 @@ const PlanComp: FC<Props> = ({
     type,
   } = plan
 
+  const {
+    usage,
+    total,
+  } = plan
+
   const isInHeader = loc === 'header'
 
   return (
@@ -76,8 +85,30 @@ const PlanComp: FC<Props> = ({
 
       {/* Plan detail */}
       <div className='rounded-xl bg-white px-6 py-3'>
-        <VectorSpaceInfo className='py-3' />
+
+        <UsageInfo
+          className='py-3'
+          Icon={User01}
+          name={t('billing.plansCommon.teamMembers')}
+          usage={usage.teamMembers}
+          total={total.teamMembers}
+        />
         <AppsInfo className='py-3' />
+        <VectorSpaceInfo className='py-3' />
+        <UsageInfo
+          className='py-3'
+          Icon={MessageFastPlus}
+          name={t('billing.plansCommon.annotationQuota')}
+          usage={usage.annotatedResponse}
+          total={total.annotatedResponse}
+        />
+        <UsageInfo
+          className='py-3'
+          Icon={FileUpload}
+          name={t('billing.plansCommon.documentsUploadQuota')}
+          usage={usage.documentsUploadQuota}
+          total={total.documentsUploadQuota}
+        />
         {isInHeader && type === Plan.sandbox && (
           <UpgradeBtn
             className='flex-shrink-0 my-3'

+ 5 - 1
web/app/components/billing/type.ts

@@ -28,7 +28,7 @@ export type PlanInfo = {
   annotatedResponse: number
 }
 
-export type UsagePlanInfo = Pick<PlanInfo, 'vectorSpace' | 'buildApps' | 'teamMembers' | 'annotatedResponse'>
+export type UsagePlanInfo = Pick<PlanInfo, 'vectorSpace' | 'buildApps' | 'teamMembers' | 'annotatedResponse' | 'documentsUploadQuota'>
 
 export enum DocumentProcessingPriority {
   standard = 'standard',
@@ -59,6 +59,10 @@ export type CurrentPlanInfoBackend = {
     size: number
     limit: number // total. 0 means unlimited
   }
+  documents_upload_quota: {
+    size: number
+    limit: number // total. 0 means unlimited
+  }
   docs_processing: DocumentProcessingPriority
   can_replace_logo: boolean
 }

+ 2 - 0
web/app/components/billing/utils/index.ts

@@ -16,12 +16,14 @@ export const parseCurrentPlan = (data: CurrentPlanInfoBackend) => {
       buildApps: data.apps?.size || 0,
       teamMembers: data.members.size,
       annotatedResponse: data.annotation_quota_limit.size,
+      documentsUploadQuota: data.documents_upload_quota.size,
     },
     total: {
       vectorSpace: parseLimit(data.vector_space.limit),
       buildApps: parseLimit(data.apps?.limit) || 0,
       teamMembers: parseLimit(data.members.limit),
       annotatedResponse: parseLimit(data.annotation_quota_limit.limit),
+      documentsUploadQuota: parseLimit(data.documents_upload_quota.limit),
     },
   }
 }

+ 14 - 19
web/app/components/header/index.tsx

@@ -1,7 +1,7 @@
 'use client'
-import { useEffect, useRef, useState } from 'react'
+import { useCallback, useEffect } from 'react'
 import Link from 'next/link'
-import { useBoolean, useClickAway } from 'ahooks'
+import { useBoolean } from 'ahooks'
 import { useSelectedLayoutSegment } from 'next/navigation'
 import { Bars3Icon } from '@heroicons/react/20/solid'
 import HeaderBillingBtn from '../billing/header-billing-btn'
@@ -15,9 +15,9 @@ import GithubStar from './github-star'
 import { WorkspaceProvider } from '@/context/workspace-context'
 import { useAppContext } from '@/context/app-context'
 import LogoSite from '@/app/components/base/logo/logo-site'
-import PlanComp from '@/app/components/billing/plan'
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
 import { useProviderContext } from '@/context/provider-context'
+import { useModalContext } from '@/context/modal-context'
 
 const navClassName = `
   flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl
@@ -26,18 +26,21 @@ const navClassName = `
 `
 
 const Header = () => {
-  const { isCurrentWorkspaceManager, langeniusVersionInfo } = useAppContext()
-  const [showUpgradePanel, setShowUpgradePanel] = useState(false)
-  const upgradeBtnRef = useRef<HTMLElement>(null)
-  useClickAway(() => {
-    setShowUpgradePanel(false)
-  }, upgradeBtnRef)
+  const { isCurrentWorkspaceManager } = useAppContext()
 
   const selectedSegment = useSelectedLayoutSegment()
   const media = useBreakpoints()
   const isMobile = media === MediaType.mobile
   const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false)
-  const { enableBilling } = useProviderContext()
+  const { enableBilling, plan } = useProviderContext()
+  const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
+  const isFreePlan = plan.type === 'sandbox'
+  const handlePlanClick = useCallback(() => {
+    if (isFreePlan)
+      setShowPricingModal()
+    else
+      setShowAccountSettingModal({ payload: 'billing' })
+  }, [isFreePlan, setShowAccountSettingModal, setShowPricingModal])
 
   useEffect(() => {
     hideNavMenu()
@@ -79,15 +82,7 @@ const Header = () => {
         <EnvNav />
         {enableBilling && (
           <div className='mr-3 select-none'>
-            <HeaderBillingBtn onClick={() => setShowUpgradePanel(true)} />
-            {showUpgradePanel && (
-              <div
-                ref={upgradeBtnRef as any}
-                className='fixed z-10 top-12 right-1 w-[360px]'
-              >
-                <PlanComp loc='header' />
-              </div>
-            )}
+            <HeaderBillingBtn onClick={handlePlanClick} />
           </div>
         )}
         <WorkspaceProvider>

+ 2 - 2
web/context/modal-context.tsx

@@ -40,7 +40,7 @@ const ModalContext = createContext<{
   setShowApiBasedExtensionModal: Dispatch<SetStateAction<ModalState<ApiBasedExtension> | null>>
   setShowModerationSettingModal: Dispatch<SetStateAction<ModalState<ModerationConfig> | null>>
   setShowExternalDataToolModal: Dispatch<SetStateAction<ModalState<ExternalDataTool> | null>>
-  setShowPricingModal: Dispatch<SetStateAction<any>>
+  setShowPricingModal: () => void
   setShowAnnotationFullModal: () => void
   setShowModelModal: Dispatch<SetStateAction<ModalState<ModelModalType> | null>>
 }>({
@@ -50,7 +50,7 @@ const ModalContext = createContext<{
       setShowExternalDataToolModal: () => { },
       setShowPricingModal: () => { },
       setShowAnnotationFullModal: () => { },
-      setShowModelModal: () => {},
+      setShowModelModal: () => { },
     })
 
 export const useModalContext = () => useContext(ModalContext)

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

@@ -28,6 +28,7 @@ const translation = {
     talkToSales: 'Talk to Sales',
     modelProviders: 'Model Providers',
     teamMembers: 'Team Members',
+    annotationQuota: 'Annotation Quota',
     buildApps: 'Build Apps',
     vectorSpace: 'Vector Space',
     vectorSpaceBillingTooltip: 'Each 1MB can store about 1.2million characters of vectorized data(estimated using OpenAI Embeddings, varies across models).',

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

@@ -29,6 +29,7 @@ const translation = {
     modelProviders: '支持的模型提供商',
     teamMembers: '团队成员',
     buildApps: '构建应用程序数',
+    annotationQuota: '标注回复数',
     vectorSpace: '向量空间',
     vectorSpaceTooltip: '向量空间是 LLMs 理解您的数据所需的长期记忆系统。',
     vectorSpaceBillingTooltip: '向量存储是将知识库向量化处理后为让 LLMs 理解数据而使用的长期记忆存储,1MB 大约能满足1.2 million character 的向量化后数据存储(以 OpenAI Embedding 模型估算,不同模型计算方式有差异)。在向量化过程中,实际的压缩或尺寸减小取决于内容的复杂性和冗余性。',