feat: SaaS price plan frontend (#1683)
Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
@ -10,7 +10,6 @@ import { fetchAppList } from '@/service/apps'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { CheckModal } from '@/hooks/use-pay'
|
||||
|
||||
const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
|
||||
if (!pageIndex || previousPageData.has_more)
|
||||
return { url: 'apps', params: { page: pageIndex + 1, limit: 30 } }
|
||||
|
@ -16,8 +16,9 @@ import { ToastContext } from '@/app/components/base/toast'
|
||||
import { createApp, fetchAppTemplates } from '@/service/apps'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import AppsContext from '@/context/app-context'
|
||||
|
||||
import EmojiPicker from '@/app/components/base/emoji-picker'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
|
||||
type NewAppDialogProps = {
|
||||
show: boolean
|
||||
@ -54,6 +55,9 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
||||
}
|
||||
}, [mutateTemplates, show])
|
||||
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
|
||||
const isCreatingRef = useRef(false)
|
||||
const onCreate: MouseEventHandler = useCallback(async () => {
|
||||
const name = nameInputRef.current?.value
|
||||
@ -111,7 +115,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button type="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
<Button disabled={isAppsFull} type="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
@ -208,6 +212,7 @@ const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isAppsFull && <AppsFull />}
|
||||
</Dialog>
|
||||
</>
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ const Settings = async ({
|
||||
params: { datasetId },
|
||||
}: Props) => {
|
||||
const locale = getLocaleOnServer()
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { t } = await useTranslation(locale, 'dataset-settings')
|
||||
|
||||
return (
|
||||
|
93
web/app/components/base/grid-mask/index.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import type { FC } from 'react'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
type GridMaskProps = {
|
||||
children: React.ReactNode
|
||||
wrapperClassName?: string
|
||||
canvasClassName?: string
|
||||
gradientClassName?: string
|
||||
}
|
||||
const GridMask: FC<GridMaskProps> = ({
|
||||
children,
|
||||
wrapperClassName,
|
||||
canvasClassName,
|
||||
gradientClassName,
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||
const ctxRef = useRef<CanvasRenderingContext2D | null>(null)
|
||||
const initCanvas = () => {
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
|
||||
if (canvasRef.current) {
|
||||
const { width: cssWidth, height: cssHeight } = canvasRef.current?.getBoundingClientRect()
|
||||
|
||||
canvasRef.current.width = dpr * cssWidth
|
||||
canvasRef.current.height = dpr * cssHeight
|
||||
|
||||
const ctx = canvasRef.current.getContext('2d')
|
||||
if (ctx) {
|
||||
ctx.scale(dpr, dpr)
|
||||
ctx.strokeStyle = '#D1E0FF'
|
||||
ctxRef.current = ctx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const drawRecord = useCallback(() => {
|
||||
const canvas = canvasRef.current!
|
||||
const ctx = ctxRef.current!
|
||||
const rowNumber = parseInt(`${canvas.width / 24}`)
|
||||
const colNumber = parseInt(`${canvas.height / 24}`)
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.beginPath()
|
||||
for (let i = 0; i < rowNumber; i++) {
|
||||
for (let j = 0; j < colNumber; j++) {
|
||||
const x = i * 24
|
||||
const y = j * 24
|
||||
if (j === 0) {
|
||||
ctx.moveTo(x, y + 2)
|
||||
ctx.arc(x + 2, y + 2, 2, Math.PI, Math.PI * 1.5)
|
||||
ctx.lineTo(x + 22, y)
|
||||
ctx.arc(x + 22, y + 2, 2, Math.PI * 1.5, Math.PI * 2)
|
||||
ctx.lineTo(x + 24, y + 22)
|
||||
ctx.arc(x + 22, y + 22, 2, 0, Math.PI * 0.5)
|
||||
ctx.lineTo(x + 2, y + 24)
|
||||
ctx.arc(x + 2, y + 22, 2, Math.PI * 0.5, Math.PI)
|
||||
}
|
||||
else {
|
||||
ctx.moveTo(x + 2, y)
|
||||
ctx.arc(x + 2, y + 2, 2, Math.PI * 1.5, Math.PI, true)
|
||||
ctx.lineTo(x, y + 22)
|
||||
ctx.arc(x + 2, y + 22, 2, Math.PI, Math.PI * 0.5, true)
|
||||
ctx.lineTo(x + 22, y + 24)
|
||||
ctx.arc(x + 22, y + 22, 2, Math.PI * 0.5, 0, true)
|
||||
ctx.lineTo(x + 24, y + 2)
|
||||
ctx.arc(x + 22, y + 2, 2, 0, Math.PI * 1.5, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}, [])
|
||||
|
||||
const handleStartDraw = () => {
|
||||
if (canvasRef.current && ctxRef.current)
|
||||
drawRecord()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initCanvas()
|
||||
handleStartDraw()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`relative bg-white ${wrapperClassName}`}>
|
||||
<canvas ref={canvasRef} className={`absolute inset-0 w-full h-full ${canvasClassName}`} />
|
||||
<div className={`absolute w-full h-full z-[1] bg-gradient-to-b from-white/80 to-white rounded-lg ${gradientClassName}`} />
|
||||
<div className='relative z-[2]'>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GridMask
|
After Width: | Height: | Size: 364 KiB |
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.1818 11.0909H3.81818C2.81364 11.0909 2 11.9045 2 12.9091V20.1818C2 21.1864 2.81364 22 3.81818 22H16.5455C17.55 22 18.3636 21.1864 18.3636 20.1818V12.9091M6.54545 7.99989V5.63636M6.54545 5.63636C7.04753 5.63636 7.45455 5.22935 7.45455 4.72727C7.45455 4.2252 7.04753 3.81818 6.54545 3.81818C6.04338 3.81818 5.63636 4.2252 5.63636 4.72727C5.63636 5.22935 6.04338 5.63636 6.54545 5.63636ZM13.8182 2.90909C13.8182 2.40727 14.2255 2 14.7273 2H21.0909C21.5927 2 22 2.40727 22 2.90909V7.45455C22 7.95636 21.5927 8.36364 21.0909 8.36364H16.5455L13.8182 10.1818V2.90909Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.00011 16.5454C7.75323 16.5454 8.36375 15.9349 8.36375 15.1818C8.36375 14.4286 7.75323 13.8181 7.00011 13.8181C6.247 13.8181 5.63647 14.4286 5.63647 15.1818C5.63647 15.9349 6.247 16.5454 7.00011 16.5454Z" fill="black"/>
|
||||
<path d="M13.3637 16.5454C14.1169 16.5454 14.7274 15.9349 14.7274 15.1818C14.7274 14.4286 14.1169 13.8181 13.3637 13.8181C12.6106 13.8181 12.0001 14.4286 12.0001 15.1818C12.0001 15.9349 12.6106 16.5454 13.3637 16.5454Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.4542 11.9996H11.9999V13.8177M17.4542 11.9996C17.4542 13.0037 18.2682 13.8177 19.2724 13.8177C20.2765 13.8177 21.0905 13.0037 21.0905 11.9996C21.0905 10.9955 20.2765 10.1815 19.2724 10.1815C18.2682 10.1815 17.4542 10.9955 17.4542 11.9996ZM6.54554 12.9087C5.318 12.9012 4.14258 12.4115 3.27293 11.5451M6.54554 12.9087C6.53904 13.471 6.71172 14.0207 7.03861 14.4783C7.36549 14.936 7.82958 15.2776 8.36365 15.4539M6.54554 12.9087C6.54223 12.5292 6.62185 12.1534 6.77888 11.808C6.9359 11.4625 7.16652 11.1556 7.45459 10.9086M3.27293 11.5451C2.8848 11.7842 2.56415 12.1184 2.34142 12.5161C2.1187 12.9139 2.00125 13.3619 2.00022 13.8177C1.99583 14.2518 2.10201 14.6799 2.30876 15.0616C2.51552 15.4433 2.81603 15.766 3.182 15.9995C3.00399 16.4639 2.91159 16.9567 2.90928 17.454C2.90333 18.0525 3.01683 18.6463 3.24315 19.2004C3.46946 19.7546 3.80404 20.258 4.2273 20.6813C4.65056 21.1045 5.154 21.4391 5.70815 21.6654C6.2623 21.8917 6.85603 22.0052 7.45458 21.9993C8.05314 22.0052 8.64686 21.8917 9.20101 21.6654C9.75516 21.4391 10.2586 21.1045 10.6819 20.6813C11.1051 20.258 11.4397 19.7546 11.666 19.2004C11.8923 18.6463 12.0058 18.0525 11.9999 17.454V16.5449H14.7271L16.1688 17.9867M3.27293 11.5451C2.44984 10.6912 1.9931 9.54938 2.00022 8.36339C1.99427 7.76484 2.10777 7.17111 2.33409 6.61696C2.5604 6.06281 2.89498 5.55937 3.31824 5.13611C3.7415 4.71285 4.24494 4.37827 4.79909 4.15195C5.35324 3.92564 5.94697 3.81214 6.54552 3.81809H6.72733C6.90356 3.28402 7.24525 2.81993 7.70289 2.49304C8.16052 2.16616 8.71035 1.99346 9.2727 1.99997C9.63267 1.99331 9.99029 2.0593 10.3242 2.19399C10.6581 2.32869 10.9614 2.52933 11.2159 2.78391C11.4705 3.03849 11.6712 3.34179 11.8059 3.67567C11.9406 4.00956 12.0065 4.36718 11.9999 4.72715M16.1688 6.0126L14.7271 7.45437H11.9999V9.27249M19.2724 19.2721C19.2724 20.2762 18.4584 21.0902 17.4542 21.0902C16.4501 21.0902 15.6361 20.2762 15.6361 19.2721C15.6361 18.268 16.4501 17.454 17.4542 17.454C18.4584 17.454 19.2724 18.268 19.2724 19.2721ZM19.2724 4.72714C19.2724 5.73126 18.4584 6.54526 17.4542 6.54526C16.4501 6.54526 15.6361 5.73126 15.6361 4.72714C15.6361 3.72302 16.4501 2.90902 17.4542 2.90902C18.4584 2.90902 19.2724 3.72302 19.2724 4.72714Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,16 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7056_1808)">
|
||||
<path d="M8.00003 4.82855L8.93639 6.72613L11.0303 7.03037L9.51518 8.50734L9.87276 10.5928L8.00003 9.60795L6.1273 10.5928L6.48488 8.50734L4.96973 7.03037L7.06367 6.72613L8.00003 4.82855Z" stroke="#344054" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.00016 14.6666C11.6821 14.6666 14.6668 11.6819 14.6668 7.99998C14.6668 4.31808 11.6821 1.33331 8.00016 1.33331C4.31826 1.33331 1.3335 4.31808 1.3335 7.99998C1.3335 11.6819 4.31826 14.6666 8.00016 14.6666Z" stroke="#344054" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.0001 12.8485C8.33482 12.8485 8.60616 12.5771 8.60616 12.2424C8.60616 11.9077 8.33482 11.6364 8.0001 11.6364C7.66539 11.6364 7.39404 11.9077 7.39404 12.2424C7.39404 12.5771 7.66539 12.8485 8.0001 12.8485Z" fill="#344054"/>
|
||||
<path d="M12.0348 9.91702C12.3695 9.91702 12.6408 9.64567 12.6408 9.31096C12.6408 8.97624 12.3695 8.7049 12.0348 8.7049C11.7001 8.7049 11.4287 8.97624 11.4287 9.31096C11.4287 9.64567 11.7001 9.91702 12.0348 9.91702Z" fill="#344054"/>
|
||||
<path d="M10.4933 5.17391C10.828 5.17391 11.0993 4.90257 11.0993 4.56785C11.0993 4.23313 10.828 3.96179 10.4933 3.96179C10.1585 3.96179 9.88721 4.23313 9.88721 4.56785C9.88721 4.90257 10.1585 5.17391 10.4933 5.17391Z" fill="#344054"/>
|
||||
<path d="M5.50645 5.17391C5.84117 5.17391 6.11251 4.90257 6.11251 4.56785C6.11251 4.23313 5.84117 3.96179 5.50645 3.96179C5.17173 3.96179 4.90039 4.23313 4.90039 4.56785C4.90039 4.90257 5.17173 5.17391 5.50645 5.17391Z" fill="#344054"/>
|
||||
<path d="M3.96544 9.91702C4.30015 9.91702 4.5715 9.64567 4.5715 9.31096C4.5715 8.97624 4.30015 8.7049 3.96544 8.7049C3.63072 8.7049 3.35938 8.97624 3.35938 9.31096C3.35938 9.64567 3.63072 9.91702 3.96544 9.91702Z" fill="#344054"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7056_1808">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.55556 8.33333H12M15.5556 8.33333H16.4444M7.55556 11.8889H12M15.5556 11.8889H16.4444M7.55556 15.4444H12M15.5556 15.4444H16.4444M20 21.6667V5C20 3.89543 19.1046 3 18 3H6C4.89543 3 4 3.89543 4 5V21.6667L6.66667 19.8889L9.33333 21.6667L12 19.8889L14.6667 21.6667L17.3333 19.8889L20 21.6667Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 1C9.82441 1 7.69767 1.64514 5.88873 2.85383C4.07979 4.06253 2.66989 5.7805 1.83733 7.79048C1.00477 9.80047 0.786929 12.0122 1.21137 14.146C1.6358 16.2798 2.68345 18.2398 4.22183 19.7782C5.76021 21.3166 7.72022 22.3642 9.85401 22.7886C11.9878 23.2131 14.1995 22.9952 16.2095 22.1627C18.2195 21.3301 19.9375 19.9202 21.1462 18.1113C22.3549 16.3023 23 14.1756 23 12C23 9.08262 21.8411 6.28473 19.7782 4.22183C17.7153 2.15893 14.9174 1 12 1ZM15.0296 6.26992L16.1076 4.78675C16.1784 4.6893 16.2677 4.60675 16.3703 4.54381C16.473 4.48087 16.5871 4.43877 16.7061 4.41992C16.825 4.40106 16.9465 4.40582 17.0636 4.43393C17.1807 4.46203 17.2912 4.51293 17.3886 4.58371C17.4861 4.65449 17.5686 4.74377 17.6316 4.84646C17.6945 4.94915 17.7366 5.06322 17.7555 5.18218C17.7743 5.30113 17.7696 5.42264 17.7415 5.53975C17.7134 5.65687 17.6625 5.7673 17.5917 5.86475L16.5137 7.34792C16.3707 7.54472 16.1554 7.67667 15.9152 7.71475C15.675 7.75283 15.4294 7.69391 15.2326 7.55096C15.0358 7.40801 14.9039 7.19273 14.8658 6.95249C14.8277 6.71225 14.8866 6.46672 15.0296 6.26992ZM6.61184 4.58417C6.70931 4.51294 6.81989 4.46167 6.93722 4.4333C7.05456 4.40493 7.17635 4.40002 7.29559 4.41884C7.41484 4.43766 7.52919 4.47985 7.63208 4.54299C7.73497 4.60613 7.82438 4.68897 7.89517 4.78675L8.97501 6.26992C9.11796 6.46733 9.17663 6.71344 9.13813 6.95411C9.09962 7.19478 8.96708 7.4103 8.76967 7.55325C8.57226 7.6962 8.32615 7.75488 8.08548 7.71637C7.84481 7.67786 7.62929 7.54533 7.48634 7.34792L6.40834 5.86475C6.33759 5.76731 6.28673 5.65689 6.25867 5.5398C6.23061 5.4227 6.22589 5.30122 6.24479 5.1823C6.26368 5.06338 6.30583 4.94935 6.36881 4.84672C6.43179 4.74409 6.51437 4.65487 6.61184 4.58417ZM6.18101 14.8508L4.43934 15.4173C4.32353 15.4604 4.2002 15.4797 4.07677 15.4739C3.95333 15.4681 3.83234 15.4375 3.72106 15.3837C3.60978 15.33 3.51051 15.2544 3.42922 15.1613C3.34793 15.0682 3.28629 14.9597 3.24801 14.8422C3.20973 14.7247 3.19561 14.6007 3.20648 14.4776C3.21735 14.3545 3.253 14.2349 3.31128 14.1259C3.36955 14.017 3.44926 13.9209 3.54561 13.8435C3.64195 13.7662 3.75295 13.7091 3.87192 13.6757L5.61359 13.1092C5.72952 13.0656 5.85308 13.046 5.9768 13.0515C6.10053 13.057 6.22185 13.0875 6.33345 13.1412C6.44505 13.1949 6.54461 13.2707 6.62613 13.3639C6.70764 13.4572 6.76941 13.566 6.80772 13.6837C6.84603 13.8015 6.86007 13.9258 6.84901 14.0492C6.83794 14.1725 6.802 14.2923 6.74334 14.4014C6.68468 14.5105 6.60453 14.6065 6.50773 14.6838C6.41092 14.761 6.30038 14.8179 6.18101 14.8508ZM12.9167 20.25C12.9167 20.4931 12.8201 20.7263 12.6482 20.8982C12.4763 21.0701 12.2431 21.1667 12 21.1667C11.7569 21.1667 11.5237 21.0701 11.3518 20.8982C11.1799 20.7263 11.0833 20.4931 11.0833 20.25V18.4167C11.0833 18.1736 11.1799 17.9404 11.3518 17.7685C11.5237 17.5966 11.7569 17.5 12 17.5C12.2431 17.5 12.4763 17.5966 12.6482 17.7685C12.8201 17.9404 12.9167 18.1736 12.9167 18.4167V20.25ZM12 14.9333L8.54967 16.7483L9.20876 12.9066L6.4175 10.1859L10.2748 9.62583L12 6.13333L13.7252 9.62583L17.5825 10.1859L14.7913 12.9066L15.4503 16.7483L12 14.9333ZM19.5625 15.4192L17.8208 14.8527C17.7015 14.8197 17.59 14.7629 17.4932 14.6856C17.3964 14.6084 17.3162 14.5123 17.2576 14.4032C17.1989 14.2942 17.163 14.1743 17.1519 14.051C17.1409 13.9276 17.1549 13.8033 17.1932 13.6856C17.2315 13.5678 17.2933 13.459 17.3748 13.3658C17.4563 13.2725 17.5559 13.1968 17.6675 13.1431C17.7791 13.0894 17.9004 13.0588 18.0241 13.0533C18.1479 13.0478 18.2714 13.0674 18.3873 13.111L20.129 13.6775C20.248 13.7109 20.359 13.768 20.4553 13.8454C20.5517 13.9227 20.6314 14.0188 20.6897 14.1278C20.7479 14.2367 20.7836 14.3563 20.7944 14.4794C20.8053 14.6025 20.7912 14.7265 20.7529 14.844C20.7146 14.9615 20.653 15.0701 20.5717 15.1631C20.4904 15.2562 20.3911 15.3319 20.2799 15.3856C20.1686 15.4393 20.0476 15.47 19.9242 15.4757C19.8007 15.4815 19.6783 15.4623 19.5625 15.4192Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="zap-fast">
|
||||
<g id="Solid">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25 8.75004C1.25 8.4739 1.47386 8.25004 1.75 8.25004H4.5C4.77614 8.25004 5 8.4739 5 8.75004C5 9.02618 4.77614 9.25004 4.5 9.25004H1.75C1.47386 9.25004 1.25 9.02618 1.25 8.75004Z" fill="#3538CD"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 6.00004C0.5 5.7239 0.723858 5.50004 1 5.50004H3.25C3.52614 5.50004 3.75 5.7239 3.75 6.00004C3.75 6.27618 3.52614 6.50004 3.25 6.50004H1C0.723858 6.50004 0.5 6.27618 0.5 6.00004Z" fill="#3538CD"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 3.25004C1.5 2.9739 1.72386 2.75004 2 2.75004H4.5C4.77614 2.75004 5 2.9739 5 3.25004C5 3.52618 4.77614 3.75004 4.5 3.75004H2C1.72386 3.75004 1.5 3.52618 1.5 3.25004Z" fill="#3538CD"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.68379 1.03505C8.89736 1.11946 9.02596 1.33849 8.99561 1.56612L8.57109 4.75004H10.4727C10.4785 4.75004 10.4842 4.75004 10.49 4.75004C10.6003 4.75002 10.7147 4.74999 10.8092 4.75863C10.9022 4.76713 11.0713 4.78965 11.2224 4.90631C11.3987 5.04237 11.5054 5.24972 11.5137 5.47225C11.5208 5.66306 11.4408 5.81376 11.3937 5.89434C11.3458 5.97625 11.2793 6.06932 11.2151 6.15912C11.2118 6.16381 11.2084 6.16849 11.2051 6.17316L7.90687 10.7907C7.77339 10.9775 7.52978 11.0495 7.31621 10.965C7.10264 10.8806 6.97404 10.6616 7.00439 10.434L7.42891 7.25004H5.52728C5.52154 7.25004 5.51579 7.25004 5.51003 7.25004C5.39966 7.25007 5.28526 7.25009 5.19077 7.24145C5.09782 7.23296 4.92871 7.21044 4.77755 7.09377C4.60127 6.95771 4.49456 6.75036 4.48631 6.52783C4.47924 6.33702 4.5592 6.18632 4.60631 6.10575C4.65421 6.02383 4.72072 5.93076 4.78489 5.84097C4.78824 5.83628 4.79158 5.8316 4.79492 5.82693L8.09313 1.20942C8.22661 1.02255 8.47022 0.950633 8.68379 1.03505Z" fill="#3538CD"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="zap-narrow">
|
||||
<path id="Solid" fill-rule="evenodd" clip-rule="evenodd" d="M6.69792 1.03505C6.91148 1.11946 7.04009 1.33849 7.00974 1.56612L6.58522 4.75004H8.48685C8.49259 4.75004 8.49834 4.75004 8.5041 4.75004C8.61447 4.75002 8.72887 4.74999 8.82336 4.75863C8.91631 4.76713 9.08541 4.78965 9.23657 4.90631C9.41286 5.04237 9.51956 5.24972 9.52781 5.47225C9.53489 5.66306 9.45493 5.81376 9.40781 5.89434C9.35992 5.97625 9.29341 6.06932 9.22924 6.15912C9.22589 6.16381 9.22255 6.16849 9.21921 6.17316L5.92099 10.7907C5.78752 10.9775 5.54391 11.0495 5.33034 10.965C5.11677 10.8806 4.98816 10.6616 5.01851 10.434L5.44304 7.25004H3.5414C3.53567 7.25004 3.52992 7.25004 3.52416 7.25004C3.41378 7.25007 3.29939 7.25009 3.2049 7.24145C3.11194 7.23296 2.94284 7.21044 2.79168 7.09377C2.6154 6.95771 2.50869 6.75036 2.50044 6.52783C2.49336 6.33702 2.57333 6.18632 2.62044 6.10575C2.66833 6.02383 2.73484 5.93076 2.79901 5.84097C2.80236 5.83628 2.80571 5.8316 2.80904 5.82693L6.10726 1.20942C6.24074 1.02255 6.48435 0.950633 6.69792 1.03505Z" fill="#3538CD"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Sparkles.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 = 'Sparkles'
|
||||
|
||||
export default Icon
|
@ -0,0 +1 @@
|
||||
export { default as Sparkles } from './Sparkles'
|
@ -0,0 +1,47 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M10.1818 11.0909H3.81818C2.81364 11.0909 2 11.9045 2 12.9091V20.1818C2 21.1864 2.81364 22 3.81818 22H16.5455C17.55 22 18.3636 21.1864 18.3636 20.1818V12.9091M6.54545 7.99989V5.63636M6.54545 5.63636C7.04753 5.63636 7.45455 5.22935 7.45455 4.72727C7.45455 4.2252 7.04753 3.81818 6.54545 3.81818C6.04338 3.81818 5.63636 4.2252 5.63636 4.72727C5.63636 5.22935 6.04338 5.63636 6.54545 5.63636ZM13.8182 2.90909C13.8182 2.40727 14.2255 2 14.7273 2H21.0909C21.5927 2 22 2.40727 22 2.90909V7.45455C22 7.95636 21.5927 8.36364 21.0909 8.36364H16.5455L13.8182 10.1818V2.90909Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M7.00011 16.5454C7.75323 16.5454 8.36375 15.9349 8.36375 15.1818C8.36375 14.4286 7.75323 13.8181 7.00011 13.8181C6.247 13.8181 5.63647 14.4286 5.63647 15.1818C5.63647 15.9349 6.247 16.5454 7.00011 16.5454Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M13.3637 16.5454C14.1169 16.5454 14.7274 15.9349 14.7274 15.1818C14.7274 14.4286 14.1169 13.8181 13.3637 13.8181C12.6106 13.8181 12.0001 14.4286 12.0001 15.1818C12.0001 15.9349 12.6106 16.5454 13.3637 16.5454Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ChatBot"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ChatBot.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 = 'ChatBot'
|
||||
|
||||
export default Icon
|
@ -0,0 +1 @@
|
||||
export { default as ChatBot } from './ChatBot'
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M17.4542 11.9996H11.9999V13.8177M17.4542 11.9996C17.4542 13.0037 18.2682 13.8177 19.2724 13.8177C20.2765 13.8177 21.0905 13.0037 21.0905 11.9996C21.0905 10.9955 20.2765 10.1815 19.2724 10.1815C18.2682 10.1815 17.4542 10.9955 17.4542 11.9996ZM6.54554 12.9087C5.318 12.9012 4.14258 12.4115 3.27293 11.5451M6.54554 12.9087C6.53904 13.471 6.71172 14.0207 7.03861 14.4783C7.36549 14.936 7.82958 15.2776 8.36365 15.4539M6.54554 12.9087C6.54223 12.5292 6.62185 12.1534 6.77888 11.808C6.9359 11.4625 7.16652 11.1556 7.45459 10.9086M3.27293 11.5451C2.8848 11.7842 2.56415 12.1184 2.34142 12.5161C2.1187 12.9139 2.00125 13.3619 2.00022 13.8177C1.99583 14.2518 2.10201 14.6799 2.30876 15.0616C2.51552 15.4433 2.81603 15.766 3.182 15.9995C3.00399 16.4639 2.91159 16.9567 2.90928 17.454C2.90333 18.0525 3.01683 18.6463 3.24315 19.2004C3.46946 19.7546 3.80404 20.258 4.2273 20.6813C4.65056 21.1045 5.154 21.4391 5.70815 21.6654C6.2623 21.8917 6.85603 22.0052 7.45458 21.9993C8.05314 22.0052 8.64686 21.8917 9.20101 21.6654C9.75516 21.4391 10.2586 21.1045 10.6819 20.6813C11.1051 20.258 11.4397 19.7546 11.666 19.2004C11.8923 18.6463 12.0058 18.0525 11.9999 17.454V16.5449H14.7271L16.1688 17.9867M3.27293 11.5451C2.44984 10.6912 1.9931 9.54938 2.00022 8.36339C1.99427 7.76484 2.10777 7.17111 2.33409 6.61696C2.5604 6.06281 2.89498 5.55937 3.31824 5.13611C3.7415 4.71285 4.24494 4.37827 4.79909 4.15195C5.35324 3.92564 5.94697 3.81214 6.54552 3.81809H6.72733C6.90356 3.28402 7.24525 2.81993 7.70289 2.49304C8.16052 2.16616 8.71035 1.99346 9.2727 1.99997C9.63267 1.99331 9.99029 2.0593 10.3242 2.19399C10.6581 2.32869 10.9614 2.52933 11.2159 2.78391C11.4705 3.03849 11.6712 3.34179 11.8059 3.67567C11.9406 4.00956 12.0065 4.36718 11.9999 4.72715M16.1688 6.0126L14.7271 7.45437H11.9999V9.27249M19.2724 19.2721C19.2724 20.2762 18.4584 21.0902 17.4542 21.0902C16.4501 21.0902 15.6361 20.2762 15.6361 19.2721C15.6361 18.268 16.4501 17.454 17.4542 17.454C18.4584 17.454 19.2724 18.268 19.2724 19.2721ZM19.2724 4.72714C19.2724 5.73126 18.4584 6.54526 17.4542 6.54526C16.4501 6.54526 15.6361 5.73126 15.6361 4.72714C15.6361 3.72302 16.4501 2.90902 17.4542 2.90902C18.4584 2.90902 19.2724 3.72302 19.2724 4.72714Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ArtificialBrain"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ArtificialBrain.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 = 'ArtificialBrain'
|
||||
|
||||
export default Icon
|
@ -1,3 +1,4 @@
|
||||
export { default as ArtificialBrain } from './ArtificialBrain'
|
||||
export { default as BracketsX } from './BracketsX'
|
||||
export { default as Container } from './Container'
|
||||
export { default as Database01 } from './Database01'
|
||||
|
@ -0,0 +1,120 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"clip-path": "url(#clip0_7056_1808)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M8.00003 4.82855L8.93639 6.72613L11.0303 7.03037L9.51518 8.50734L9.87276 10.5928L8.00003 9.60795L6.1273 10.5928L6.48488 8.50734L4.96973 7.03037L7.06367 6.72613L8.00003 4.82855Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M8.00016 14.6666C11.6821 14.6666 14.6668 11.6819 14.6668 7.99998C14.6668 4.31808 11.6821 1.33331 8.00016 1.33331C4.31826 1.33331 1.3335 4.31808 1.3335 7.99998C1.3335 11.6819 4.31826 14.6666 8.00016 14.6666Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "1.25",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M8.0001 12.8485C8.33482 12.8485 8.60616 12.5771 8.60616 12.2424C8.60616 11.9077 8.33482 11.6364 8.0001 11.6364C7.66539 11.6364 7.39404 11.9077 7.39404 12.2424C7.39404 12.5771 7.66539 12.8485 8.0001 12.8485Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12.0348 9.91702C12.3695 9.91702 12.6408 9.64567 12.6408 9.31096C12.6408 8.97624 12.3695 8.7049 12.0348 8.7049C11.7001 8.7049 11.4287 8.97624 11.4287 9.31096C11.4287 9.64567 11.7001 9.91702 12.0348 9.91702Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M10.4933 5.17391C10.828 5.17391 11.0993 4.90257 11.0993 4.56785C11.0993 4.23313 10.828 3.96179 10.4933 3.96179C10.1585 3.96179 9.88721 4.23313 9.88721 4.56785C9.88721 4.90257 10.1585 5.17391 10.4933 5.17391Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M5.50645 5.17391C5.84117 5.17391 6.11251 4.90257 6.11251 4.56785C6.11251 4.23313 5.84117 3.96179 5.50645 3.96179C5.17173 3.96179 4.90039 4.23313 4.90039 4.56785C4.90039 4.90257 5.17173 5.17391 5.50645 5.17391Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M3.96544 9.91702C4.30015 9.91702 4.5715 9.64567 4.5715 9.31096C4.5715 8.97624 4.30015 8.7049 3.96544 8.7049C3.63072 8.7049 3.35938 8.97624 3.35938 9.31096C3.35938 9.64567 3.63072 9.91702 3.96544 9.91702Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "defs",
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "clipPath",
|
||||
"attributes": {
|
||||
"id": "clip0_7056_1808"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "GoldCoin"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './GoldCoin.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 = 'GoldCoin'
|
||||
|
||||
export default Icon
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M7.55556 8.33333H12M15.5556 8.33333H16.4444M7.55556 11.8889H12M15.5556 11.8889H16.4444M7.55556 15.4444H12M15.5556 15.4444H16.4444M20 21.6667V5C20 3.89543 19.1046 3 18 3H6C4.89543 3 4 3.89543 4 5V21.6667L6.66667 19.8889L9.33333 21.6667L12 19.8889L14.6667 21.6667L17.3333 19.8889L20 21.6667Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ReceiptList"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ReceiptList.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 = 'ReceiptList'
|
||||
|
||||
export default Icon
|
@ -0,0 +1,2 @@
|
||||
export { default as GoldCoin } from './GoldCoin'
|
||||
export { default as ReceiptList } from './ReceiptList'
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12 1C9.82441 1 7.69767 1.64514 5.88873 2.85383C4.07979 4.06253 2.66989 5.7805 1.83733 7.79048C1.00477 9.80047 0.786929 12.0122 1.21137 14.146C1.6358 16.2798 2.68345 18.2398 4.22183 19.7782C5.76021 21.3166 7.72022 22.3642 9.85401 22.7886C11.9878 23.2131 14.1995 22.9952 16.2095 22.1627C18.2195 21.3301 19.9375 19.9202 21.1462 18.1113C22.3549 16.3023 23 14.1756 23 12C23 9.08262 21.8411 6.28473 19.7782 4.22183C17.7153 2.15893 14.9174 1 12 1ZM15.0296 6.26992L16.1076 4.78675C16.1784 4.6893 16.2677 4.60675 16.3703 4.54381C16.473 4.48087 16.5871 4.43877 16.7061 4.41992C16.825 4.40106 16.9465 4.40582 17.0636 4.43393C17.1807 4.46203 17.2912 4.51293 17.3886 4.58371C17.4861 4.65449 17.5686 4.74377 17.6316 4.84646C17.6945 4.94915 17.7366 5.06322 17.7555 5.18218C17.7743 5.30113 17.7696 5.42264 17.7415 5.53975C17.7134 5.65687 17.6625 5.7673 17.5917 5.86475L16.5137 7.34792C16.3707 7.54472 16.1554 7.67667 15.9152 7.71475C15.675 7.75283 15.4294 7.69391 15.2326 7.55096C15.0358 7.40801 14.9039 7.19273 14.8658 6.95249C14.8277 6.71225 14.8866 6.46672 15.0296 6.26992ZM6.61184 4.58417C6.70931 4.51294 6.81989 4.46167 6.93722 4.4333C7.05456 4.40493 7.17635 4.40002 7.29559 4.41884C7.41484 4.43766 7.52919 4.47985 7.63208 4.54299C7.73497 4.60613 7.82438 4.68897 7.89517 4.78675L8.97501 6.26992C9.11796 6.46733 9.17663 6.71344 9.13813 6.95411C9.09962 7.19478 8.96708 7.4103 8.76967 7.55325C8.57226 7.6962 8.32615 7.75488 8.08548 7.71637C7.84481 7.67786 7.62929 7.54533 7.48634 7.34792L6.40834 5.86475C6.33759 5.76731 6.28673 5.65689 6.25867 5.5398C6.23061 5.4227 6.22589 5.30122 6.24479 5.1823C6.26368 5.06338 6.30583 4.94935 6.36881 4.84672C6.43179 4.74409 6.51437 4.65487 6.61184 4.58417ZM6.18101 14.8508L4.43934 15.4173C4.32353 15.4604 4.2002 15.4797 4.07677 15.4739C3.95333 15.4681 3.83234 15.4375 3.72106 15.3837C3.60978 15.33 3.51051 15.2544 3.42922 15.1613C3.34793 15.0682 3.28629 14.9597 3.24801 14.8422C3.20973 14.7247 3.19561 14.6007 3.20648 14.4776C3.21735 14.3545 3.253 14.2349 3.31128 14.1259C3.36955 14.017 3.44926 13.9209 3.54561 13.8435C3.64195 13.7662 3.75295 13.7091 3.87192 13.6757L5.61359 13.1092C5.72952 13.0656 5.85308 13.046 5.9768 13.0515C6.10053 13.057 6.22185 13.0875 6.33345 13.1412C6.44505 13.1949 6.54461 13.2707 6.62613 13.3639C6.70764 13.4572 6.76941 13.566 6.80772 13.6837C6.84603 13.8015 6.86007 13.9258 6.84901 14.0492C6.83794 14.1725 6.802 14.2923 6.74334 14.4014C6.68468 14.5105 6.60453 14.6065 6.50773 14.6838C6.41092 14.761 6.30038 14.8179 6.18101 14.8508ZM12.9167 20.25C12.9167 20.4931 12.8201 20.7263 12.6482 20.8982C12.4763 21.0701 12.2431 21.1667 12 21.1667C11.7569 21.1667 11.5237 21.0701 11.3518 20.8982C11.1799 20.7263 11.0833 20.4931 11.0833 20.25V18.4167C11.0833 18.1736 11.1799 17.9404 11.3518 17.7685C11.5237 17.5966 11.7569 17.5 12 17.5C12.2431 17.5 12.4763 17.5966 12.6482 17.7685C12.8201 17.9404 12.9167 18.1736 12.9167 18.4167V20.25ZM12 14.9333L8.54967 16.7483L9.20876 12.9066L6.4175 10.1859L10.2748 9.62583L12 6.13333L13.7252 9.62583L17.5825 10.1859L14.7913 12.9066L15.4503 16.7483L12 14.9333ZM19.5625 15.4192L17.8208 14.8527C17.7015 14.8197 17.59 14.7629 17.4932 14.6856C17.3964 14.6084 17.3162 14.5123 17.2576 14.4032C17.1989 14.2942 17.163 14.1743 17.1519 14.051C17.1409 13.9276 17.1549 13.8033 17.1932 13.6856C17.2315 13.5678 17.2933 13.459 17.3748 13.3658C17.4563 13.2725 17.5559 13.1968 17.6675 13.1431C17.7791 13.0894 17.9004 13.0588 18.0241 13.0533C18.1479 13.0478 18.2714 13.0674 18.3873 13.111L20.129 13.6775C20.248 13.7109 20.359 13.768 20.4553 13.8454C20.5517 13.9227 20.6314 14.0188 20.6897 14.1278C20.7479 14.2367 20.7836 14.3563 20.7944 14.4794C20.8053 14.6025 20.7912 14.7265 20.7529 14.844C20.7146 14.9615 20.653 15.0701 20.5717 15.1631C20.4904 15.2562 20.3911 15.3319 20.2799 15.3856C20.1686 15.4393 20.0476 15.47 19.9242 15.4757C19.8007 15.4815 19.6783 15.4623 19.5625 15.4192Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "GoldCoin"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './GoldCoin.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 = 'GoldCoin'
|
||||
|
||||
export default Icon
|
@ -1 +1,2 @@
|
||||
export { default as GoldCoin } from './GoldCoin'
|
||||
export { default as Scales02 } from './Scales02'
|
||||
|
@ -0,0 +1,79 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "12",
|
||||
"height": "12",
|
||||
"viewBox": "0 0 12 12",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "zap-fast"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Solid"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M1.25 8.75004C1.25 8.4739 1.47386 8.25004 1.75 8.25004H4.5C4.77614 8.25004 5 8.4739 5 8.75004C5 9.02618 4.77614 9.25004 4.5 9.25004H1.75C1.47386 9.25004 1.25 9.02618 1.25 8.75004Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M0.5 6.00004C0.5 5.7239 0.723858 5.50004 1 5.50004H3.25C3.52614 5.50004 3.75 5.7239 3.75 6.00004C3.75 6.27618 3.52614 6.50004 3.25 6.50004H1C0.723858 6.50004 0.5 6.27618 0.5 6.00004Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M1.5 3.25004C1.5 2.9739 1.72386 2.75004 2 2.75004H4.5C4.77614 2.75004 5 2.9739 5 3.25004C5 3.52618 4.77614 3.75004 4.5 3.75004H2C1.72386 3.75004 1.5 3.52618 1.5 3.25004Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M8.68379 1.03505C8.89736 1.11946 9.02596 1.33849 8.99561 1.56612L8.57109 4.75004H10.4727C10.4785 4.75004 10.4842 4.75004 10.49 4.75004C10.6003 4.75002 10.7147 4.74999 10.8092 4.75863C10.9022 4.76713 11.0713 4.78965 11.2224 4.90631C11.3987 5.04237 11.5054 5.24972 11.5137 5.47225C11.5208 5.66306 11.4408 5.81376 11.3937 5.89434C11.3458 5.97625 11.2793 6.06932 11.2151 6.15912C11.2118 6.16381 11.2084 6.16849 11.2051 6.17316L7.90687 10.7907C7.77339 10.9775 7.52978 11.0495 7.31621 10.965C7.10264 10.8806 6.97404 10.6616 7.00439 10.434L7.42891 7.25004H5.52728C5.52154 7.25004 5.51579 7.25004 5.51003 7.25004C5.39966 7.25007 5.28526 7.25009 5.19077 7.24145C5.09782 7.23296 4.92871 7.21044 4.77755 7.09377C4.60127 6.95771 4.49456 6.75036 4.48631 6.52783C4.47924 6.33702 4.5592 6.18632 4.60631 6.10575C4.65421 6.02383 4.72072 5.93076 4.78489 5.84097C4.78824 5.83628 4.79158 5.8316 4.79492 5.82693L8.09313 1.20942C8.22661 1.02255 8.47022 0.950633 8.68379 1.03505Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ZapFast"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ZapFast.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 = 'ZapFast'
|
||||
|
||||
export default Icon
|
@ -0,0 +1,38 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "12",
|
||||
"height": "12",
|
||||
"viewBox": "0 0 12 12",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "zap-narrow"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Solid",
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M6.69792 1.03505C6.91148 1.11946 7.04009 1.33849 7.00974 1.56612L6.58522 4.75004H8.48685C8.49259 4.75004 8.49834 4.75004 8.5041 4.75004C8.61447 4.75002 8.72887 4.74999 8.82336 4.75863C8.91631 4.76713 9.08541 4.78965 9.23657 4.90631C9.41286 5.04237 9.51956 5.24972 9.52781 5.47225C9.53489 5.66306 9.45493 5.81376 9.40781 5.89434C9.35992 5.97625 9.29341 6.06932 9.22924 6.15912C9.22589 6.16381 9.22255 6.16849 9.21921 6.17316L5.92099 10.7907C5.78752 10.9775 5.54391 11.0495 5.33034 10.965C5.11677 10.8806 4.98816 10.6616 5.01851 10.434L5.44304 7.25004H3.5414C3.53567 7.25004 3.52992 7.25004 3.52416 7.25004C3.41378 7.25007 3.29939 7.25009 3.2049 7.24145C3.11194 7.23296 2.94284 7.21044 2.79168 7.09377C2.6154 6.95771 2.50869 6.75036 2.50044 6.52783C2.49336 6.33702 2.57333 6.18632 2.62044 6.10575C2.66833 6.02383 2.73484 5.93076 2.79901 5.84097C2.80236 5.83628 2.80571 5.8316 2.80904 5.82693L6.10726 1.20942C6.24074 1.02255 6.48435 0.950633 6.69792 1.03505Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ZapNarrow"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ZapNarrow.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 = 'ZapNarrow'
|
||||
|
||||
export default Icon
|
@ -6,3 +6,5 @@ export { default as MessageClockCircle } from './MessageClockCircle'
|
||||
export { default as Target04 } from './Target04'
|
||||
export { default as Tool03 } from './Tool03'
|
||||
export { default as XCircle } from './XCircle'
|
||||
export { default as ZapFast } from './ZapFast'
|
||||
export { default as ZapNarrow } from './ZapNarrow'
|
||||
|
@ -17,6 +17,7 @@ export type IToastProps = {
|
||||
message: string
|
||||
children?: ReactNode
|
||||
onClose?: () => void
|
||||
className?: string
|
||||
}
|
||||
type IToastContext = {
|
||||
notify: (props: IToastProps) => void
|
||||
@ -30,12 +31,14 @@ const Toast = ({
|
||||
duration,
|
||||
message,
|
||||
children,
|
||||
className,
|
||||
}: IToastProps) => {
|
||||
// sometimes message is react node array. Not handle it.
|
||||
if (typeof message !== 'string')
|
||||
return null
|
||||
|
||||
return <div className={classNames(
|
||||
className,
|
||||
'fixed rounded-md p-4 my-4 mx-8 z-50',
|
||||
'top-0',
|
||||
'right-0',
|
||||
@ -115,12 +118,13 @@ Toast.notify = ({
|
||||
type,
|
||||
message,
|
||||
duration,
|
||||
}: Pick<IToastProps, 'type' | 'message' | 'duration'>) => {
|
||||
className,
|
||||
}: Pick<IToastProps, 'type' | 'message' | 'duration' | 'className'>) => {
|
||||
if (typeof window === 'object') {
|
||||
const holder = document.createElement('div')
|
||||
const root = createRoot(holder)
|
||||
|
||||
root.render(<Toast type={type} message={message} duration={duration} />)
|
||||
root.render(<Toast type={type} message={message} duration={duration} className={className} />)
|
||||
document.body.appendChild(holder)
|
||||
setTimeout(() => {
|
||||
if (holder)
|
||||
|
31
web/app/components/billing/apps-full-in-dialog/index.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import UpgradeBtn from '../upgrade-btn'
|
||||
import AppsInfo from '../usage-info/apps-info'
|
||||
import s from './style.module.css'
|
||||
import GridMask from '@/app/components/base/grid-mask'
|
||||
|
||||
const AppsFull: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
|
||||
<div className='mt-6 px-3.5 py-4 border-2 border-solid border-transparent rounded-lg shadow-md flex flex-col transition-all duration-200 ease-in-out cursor-pointer'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className={cn(s.textGradient, 'leading-[24px] text-base font-semibold')}>
|
||||
<div>{t('billing.apps.fullTipLine1')}</div>
|
||||
<div>{t('billing.apps.fullTipLine2')}</div>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<UpgradeBtn />
|
||||
</div>
|
||||
</div>
|
||||
<AppsInfo className='mt-4' />
|
||||
</div>
|
||||
</GridMask>
|
||||
)
|
||||
}
|
||||
export default React.memo(AppsFull)
|
@ -0,0 +1,7 @@
|
||||
.textGradient {
|
||||
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
27
web/app/components/billing/apps-full/index.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import UpgradeBtn from '../upgrade-btn'
|
||||
import s from './style.module.css'
|
||||
import GridMask from '@/app/components/base/grid-mask'
|
||||
|
||||
const AppsFull: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
|
||||
<div className='col-span-1 px-3.5 pt-3.5 border-2 border-solid border-transparent rounded-lg shadow-xs min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'>
|
||||
<div className={cn(s.textGradient, 'leading-[24px] text-base font-semibold')}>
|
||||
<div>{t('billing.apps.fullTipLine1')}</div>
|
||||
<div>{t('billing.apps.fullTipLine2')}</div>
|
||||
</div>
|
||||
<div className='flex mt-8'>
|
||||
<UpgradeBtn />
|
||||
</div>
|
||||
</div>
|
||||
</GridMask>
|
||||
)
|
||||
}
|
||||
export default React.memo(AppsFull)
|
7
web/app/components/billing/apps-full/style.module.css
Normal file
@ -0,0 +1,7 @@
|
||||
.textGradient {
|
||||
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
42
web/app/components/billing/billing-page/index.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PlanComp from '../plan'
|
||||
import { ReceiptList } from '../../base/icons/src/vender/line/financeAndECommerce'
|
||||
import { LinkExternal01 } from '../../base/icons/src/vender/line/general'
|
||||
import { fetchBillingUrl } from '@/service/billing'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
const Billing: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const [billingUrl, setBillingUrl] = React.useState('')
|
||||
const { enableBilling } = useProviderContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableBilling && !isCurrentWorkspaceManager)
|
||||
return
|
||||
(async () => {
|
||||
const { url } = await fetchBillingUrl()
|
||||
setBillingUrl(url)
|
||||
})()
|
||||
}, [isCurrentWorkspaceManager])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PlanComp />
|
||||
{enableBilling && isCurrentWorkspaceManager && billingUrl && (
|
||||
<a className='mt-5 flex px-6 justify-between h-12 items-center bg-gray-50 rounded-xl cursor-pointer' href={billingUrl} target='_blank'>
|
||||
<div className='flex items-center'>
|
||||
<ReceiptList className='w-4 h-4 text-gray-700' />
|
||||
<div className='ml-2 text-sm font-normal text-gray-700'>{t('billing.viewBilling')}</div>
|
||||
</div>
|
||||
<LinkExternal01 className='w-3 h-3' />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Billing)
|
64
web/app/components/billing/config.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Plan, type PlanInfo, Priority } from '@/app/components/billing/type'
|
||||
|
||||
const supportModelProviders = 'OpenAI/Anthropic/Azure OpenAI/ Llama2/Hugging Face/Replicate'
|
||||
|
||||
export const NUM_INFINITE = 99999999
|
||||
|
||||
export const contactSalesUrl = 'mailto:business@dify.ai'
|
||||
|
||||
export const ALL_PLANS: Record<Plan, PlanInfo> = {
|
||||
sandbox: {
|
||||
level: 1,
|
||||
price: 0,
|
||||
modelProviders: supportModelProviders,
|
||||
teamMembers: 1,
|
||||
buildApps: 10,
|
||||
vectorSpace: 10,
|
||||
documentProcessingPriority: Priority.standard,
|
||||
logHistory: 30,
|
||||
},
|
||||
professional: {
|
||||
level: 2,
|
||||
price: 59,
|
||||
modelProviders: supportModelProviders,
|
||||
teamMembers: 3,
|
||||
buildApps: 50,
|
||||
vectorSpace: 200,
|
||||
documentProcessingPriority: Priority.priority,
|
||||
logHistory: NUM_INFINITE,
|
||||
},
|
||||
team: {
|
||||
level: 3,
|
||||
price: 159,
|
||||
modelProviders: supportModelProviders,
|
||||
teamMembers: NUM_INFINITE,
|
||||
buildApps: NUM_INFINITE,
|
||||
vectorSpace: 1000,
|
||||
documentProcessingPriority: Priority.topPriority,
|
||||
logHistory: NUM_INFINITE,
|
||||
},
|
||||
enterprise: {
|
||||
level: 4,
|
||||
price: 0,
|
||||
modelProviders: supportModelProviders,
|
||||
teamMembers: NUM_INFINITE,
|
||||
buildApps: NUM_INFINITE,
|
||||
vectorSpace: NUM_INFINITE,
|
||||
documentProcessingPriority: Priority.topPriority,
|
||||
logHistory: NUM_INFINITE,
|
||||
},
|
||||
}
|
||||
|
||||
export const defaultPlan = {
|
||||
type: Plan.sandbox,
|
||||
usage: {
|
||||
vectorSpace: 1,
|
||||
buildApps: 1,
|
||||
teamMembers: 1,
|
||||
},
|
||||
total: {
|
||||
vectorSpace: 10,
|
||||
buildApps: 10,
|
||||
teamMembers: 1,
|
||||
},
|
||||
}
|
46
web/app/components/billing/header-billing-btn/index.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import UpgradeBtn from '../upgrade-btn'
|
||||
import { Plan } from '../type'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
type Props = {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const HeaderBillingBtn: FC<Props> = ({
|
||||
onClick,
|
||||
}) => {
|
||||
const { plan, enableBilling, isFetchedPlan } = useProviderContext()
|
||||
const {
|
||||
type,
|
||||
} = plan
|
||||
|
||||
const name = (() => {
|
||||
if (type === Plan.professional)
|
||||
return 'pro'
|
||||
return type
|
||||
})()
|
||||
const classNames = (() => {
|
||||
if (type === Plan.professional)
|
||||
return 'border-[#E0F2FE] hover:border-[#B9E6FE] bg-[#E0F2FE] text-[#026AA2]'
|
||||
if (type === Plan.team)
|
||||
return 'border-[#E0EAFF] hover:border-[#C7D7FE] bg-[#E0EAFF] text-[#3538CD]'
|
||||
return ''
|
||||
})()
|
||||
|
||||
if (!enableBilling || !isFetchedPlan)
|
||||
return null
|
||||
|
||||
if (type === Plan.sandbox)
|
||||
return <UpgradeBtn onClick={onClick} isShort />
|
||||
|
||||
return (
|
||||
<div onClick={onClick} className={cn(classNames, 'flex items-center h-[22px] px-2 rounded-md border text-xs font-semibold uppercase cursor-pointer')}>
|
||||
{name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(HeaderBillingBtn)
|
92
web/app/components/billing/plan/index.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
const typeStyle = {
|
||||
[Plan.sandbox]: {
|
||||
textClassNames: 'text-gray-900',
|
||||
bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #EAECF0',
|
||||
},
|
||||
[Plan.professional]: {
|
||||
textClassNames: 'text-[#026AA2]',
|
||||
bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #E0F2FE',
|
||||
},
|
||||
[Plan.team]: {
|
||||
textClassNames: 'text-[#3538CD]',
|
||||
bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #E0EAFF',
|
||||
},
|
||||
[Plan.enterprise]: {
|
||||
textClassNames: 'text-[#DC6803]',
|
||||
bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #FFEED3',
|
||||
},
|
||||
}
|
||||
|
||||
type Props = {
|
||||
loc?: string
|
||||
}
|
||||
|
||||
const PlanComp: FC<Props> = ({
|
||||
loc,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const {
|
||||
type,
|
||||
} = plan
|
||||
|
||||
const isInHeader = loc === 'header'
|
||||
|
||||
return (
|
||||
<div
|
||||
className='rounded-xl border border-white select-none'
|
||||
style={{
|
||||
background: typeStyle[type].bg,
|
||||
boxShadow: '5px 7px 12px 0px rgba(0, 0, 0, 0.06)',
|
||||
}}
|
||||
>
|
||||
<div className='flex justify-between px-6 py-5 items-center'>
|
||||
<div>
|
||||
<div
|
||||
className='leading-[18px] text-xs font-normal opacity-70'
|
||||
style={{
|
||||
color: 'rgba(0, 0, 0, 0.64)',
|
||||
}}
|
||||
>
|
||||
{t('billing.currentPlan')}
|
||||
</div>
|
||||
<div className={cn(typeStyle[type].textClassNames, 'leading-[125%] text-lg font-semibold uppercase')}>
|
||||
{t(`billing.plans.${type}.name`)}
|
||||
</div>
|
||||
</div>
|
||||
{(!isInHeader || (isInHeader && type !== Plan.sandbox)) && (
|
||||
<UpgradeBtn
|
||||
className='flex-shrink-0'
|
||||
isPlain={type !== Plan.sandbox}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Plan detail */}
|
||||
<div className='rounded-xl bg-white px-6 py-3'>
|
||||
<VectorSpaceInfo className='py-3' />
|
||||
<AppsInfo className='py-3' />
|
||||
{isInHeader && type === Plan.sandbox && (
|
||||
<UpgradeBtn
|
||||
className='flex-shrink-0 my-3'
|
||||
isFull
|
||||
size='lg'
|
||||
isPlain={type !== Plan.sandbox}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(PlanComp)
|
74
web/app/components/billing/pricing/index.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Plan } from '../type'
|
||||
import SelectPlanRange, { PlanRange } from './select-plan-range'
|
||||
import PlanItem from './plan-item'
|
||||
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import GridMask from '@/app/components/base/grid-mask'
|
||||
|
||||
type Props = {
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const Pricing: FC<Props> = ({
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
|
||||
const [planRange, setPlanRange] = React.useState<PlanRange>(PlanRange.monthly)
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className='fixed inset-0 flex bg-white z-[1000] overflow-auto'
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<GridMask wrapperClassName='grow'>
|
||||
<div className='grow width-[0] mt-6 p-6 flex flex-col items-center'>
|
||||
<div className='mb-3 leading-[38px] text-[30px] font-semibold text-gray-900'>
|
||||
{t('billing.plansCommon.title')}
|
||||
</div>
|
||||
<SelectPlanRange
|
||||
value={planRange}
|
||||
onChange={setPlanRange}
|
||||
/>
|
||||
<div className='mt-8 pb-6 w-full justify-center flex-nowrap flex space-x-3'>
|
||||
<PlanItem
|
||||
currentPlan={plan.type}
|
||||
plan={Plan.sandbox}
|
||||
planRange={planRange}
|
||||
/>
|
||||
<PlanItem
|
||||
currentPlan={plan.type}
|
||||
plan={Plan.professional}
|
||||
planRange={planRange}
|
||||
/>
|
||||
<PlanItem
|
||||
currentPlan={plan.type}
|
||||
plan={Plan.team}
|
||||
planRange={planRange}
|
||||
/>
|
||||
<PlanItem
|
||||
currentPlan={plan.type}
|
||||
plan={Plan.enterprise}
|
||||
planRange={planRange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</GridMask>
|
||||
|
||||
<div
|
||||
className='fixed top-6 right-6 flex items-center justify-center w-10 h-10 bg-black/[0.05] rounded-full backdrop-blur-[2px] cursor-pointer z-[1001]'
|
||||
onClick={onCancel}
|
||||
>
|
||||
<XClose className='w-4 h-4 text-gray-900' />
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
export default React.memo(Pricing)
|
221
web/app/components/billing/pricing/plan-item.tsx
Normal file
@ -0,0 +1,221 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import { Plan } from '../type'
|
||||
import { ALL_PLANS, NUM_INFINITE, contactSalesUrl } from '../config'
|
||||
import Toast from '../../base/toast'
|
||||
import { PlanRange } from './select-plan-range'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { fetchSubscriptionUrls } from '@/service/billing'
|
||||
|
||||
type Props = {
|
||||
currentPlan: Plan
|
||||
plan: Plan
|
||||
planRange: PlanRange
|
||||
}
|
||||
|
||||
const KeyValue = ({ label, value }: { label: string; value: string | number | JSX.Element }) => {
|
||||
return (
|
||||
<div className='mt-3.5 leading-[125%] text-[13px] font-medium'>
|
||||
<div className='text-gray-500'>{label}</div>
|
||||
<div className='mt-0.5 text-gray-900'>{value}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const priceClassName = 'leading-[32px] text-[28px] font-bold text-gray-900'
|
||||
const style = {
|
||||
[Plan.sandbox]: {
|
||||
bg: 'bg-[#F2F4F7]',
|
||||
title: 'text-gray-900',
|
||||
hoverAndActive: '',
|
||||
},
|
||||
[Plan.professional]: {
|
||||
bg: 'bg-[#E0F2FE]',
|
||||
title: 'text-[#026AA2]',
|
||||
hoverAndActive: 'hover:shadow-lg hover:!text-white hover:!bg-[#0086C9] hover:!border-[#026AA2] active:!text-white active:!bg-[#026AA2] active:!border-[#026AA2]',
|
||||
},
|
||||
[Plan.team]: {
|
||||
bg: 'bg-[#E0EAFF]',
|
||||
title: 'text-[#3538CD]',
|
||||
hoverAndActive: 'hover:shadow-lg hover:!text-white hover:!bg-[#444CE7] hover:!border-[#3538CD] active:!text-white active:!bg-[#3538CD] active:!border-[#3538CD]',
|
||||
},
|
||||
[Plan.enterprise]: {
|
||||
bg: 'bg-[#FFEED3]',
|
||||
title: 'text-[#DC6803]',
|
||||
hoverAndActive: 'hover:shadow-lg hover:!text-white hover:!bg-[#F79009] hover:!border-[#DC6803] active:!text-white active:!bg-[#DC6803] active:!border-[#DC6803]',
|
||||
},
|
||||
}
|
||||
const PlanItem: FC<Props> = ({
|
||||
plan,
|
||||
currentPlan,
|
||||
planRange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const i18nPrefix = `billing.plans.${plan}`
|
||||
const isFreePlan = plan === Plan.sandbox
|
||||
const isEnterprisePlan = plan === Plan.enterprise
|
||||
const isMostPopularPlan = plan === Plan.professional
|
||||
const planInfo = ALL_PLANS[plan]
|
||||
const isYear = planRange === PlanRange.yearly
|
||||
const isCurrent = plan === currentPlan
|
||||
const isPlanDisabled = planInfo.level <= ALL_PLANS[currentPlan].level
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
|
||||
const btnText = (() => {
|
||||
if (isCurrent)
|
||||
return t('billing.plansCommon.currentPlan')
|
||||
|
||||
return ({
|
||||
[Plan.sandbox]: t('billing.plansCommon.startForFree'),
|
||||
[Plan.professional]: <>{t('billing.plansCommon.getStartedWith')}<span className='capitalize'> {plan}</span></>,
|
||||
[Plan.team]: <>{t('billing.plansCommon.getStartedWith')}<span className='capitalize'> {plan}</span></>,
|
||||
[Plan.enterprise]: t('billing.plansCommon.talkToSales'),
|
||||
})[plan]
|
||||
})()
|
||||
const comingSoon = (
|
||||
<div className='leading-[12px] text-[9px] font-semibold text-[#3538CD] uppercase'>{t('billing.plansCommon.comingSoon')}</div>
|
||||
)
|
||||
const supportContent = (() => {
|
||||
switch (plan) {
|
||||
case Plan.sandbox:
|
||||
return t('billing.plansCommon.supportItems.communityForums')
|
||||
case Plan.professional:
|
||||
return t('billing.plansCommon.supportItems.emailSupport')
|
||||
case Plan.team:
|
||||
return (
|
||||
<div>
|
||||
<div>{t('billing.plansCommon.supportItems.priorityEmail')}</div>
|
||||
<div className='mt-3.5 flex items-center space-x-1'>
|
||||
<div>+ {t('billing.plansCommon.supportItems.logoChange')}</div>
|
||||
<div>{comingSoon}</div>
|
||||
</div>
|
||||
<div className='mt-3.5 flex items-center space-x-1'>
|
||||
<div>+ {t('billing.plansCommon.supportItems.personalizedSupport')}</div>
|
||||
<div>{comingSoon}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case Plan.enterprise:
|
||||
return (
|
||||
<div>
|
||||
<div>{t('billing.plansCommon.supportItems.personalizedSupport')}</div>
|
||||
<div className='mt-3.5 flex items-center space-x-1'>
|
||||
<div>+ {t('billing.plansCommon.supportItems.dedicatedAPISupport')}</div>
|
||||
</div>
|
||||
<div className='mt-3.5 flex items-center space-x-1'>
|
||||
<div>+ {t('billing.plansCommon.supportItems.customIntegration')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})()
|
||||
const handleGetPayUrl = async () => {
|
||||
if (loading)
|
||||
return
|
||||
|
||||
if (isPlanDisabled)
|
||||
return
|
||||
|
||||
if (isFreePlan)
|
||||
return
|
||||
|
||||
if (isEnterprisePlan) {
|
||||
window.location.href = contactSalesUrl
|
||||
return
|
||||
}
|
||||
// Only workspace manager can buy plan
|
||||
if (!isCurrentWorkspaceManager) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('billing.buyPermissionDeniedTip'),
|
||||
className: 'z-[1001]',
|
||||
})
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await fetchSubscriptionUrls(plan, isYear ? 'year' : 'month')
|
||||
|
||||
window.location.href = res.url
|
||||
}
|
||||
finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={cn(isMostPopularPlan ? 'bg-[#0086C9] p-0.5' : 'pt-7', 'flex flex-col min-w-[290px] w-[290px] h-[712px] rounded-xl')}>
|
||||
{isMostPopularPlan && (
|
||||
<div className='flex items-center h-7 justify-center leading-[12px] text-xs font-medium text-[#F5F8FF]'>{t('billing.plansCommon.mostPopular')}</div>
|
||||
)}
|
||||
<div className={cn(style[plan].bg, 'grow px-6 pt-6 rounded-[10px]')}>
|
||||
<div className={cn(style[plan].title, 'mb-1 leading-[125%] text-lg font-semibold')}>{t(`${i18nPrefix}.name`)}</div>
|
||||
<div className={cn(isFreePlan ? 'text-[#FB6514]' : 'text-gray-500', 'mb-4 h-8 leading-[125%] text-[13px] font-normal')}>{t(`${i18nPrefix}.description`)}</div>
|
||||
|
||||
{/* Price */}
|
||||
{isFreePlan && (
|
||||
<div className={priceClassName}>{t('billing.plansCommon.free')}</div>
|
||||
)}
|
||||
{isEnterprisePlan && (
|
||||
<div className={priceClassName}>{t('billing.plansCommon.contactSales')}</div>
|
||||
)}
|
||||
{!isFreePlan && !isEnterprisePlan && (
|
||||
<div className='flex items-end h-9'>
|
||||
<div className={priceClassName}>${isYear ? planInfo.price * 10 : planInfo.price}</div>
|
||||
<div className='ml-1'>
|
||||
{isYear && <div className='leading-[18px] text-xs font-medium text-[#F26725]'>{t('billing.plansCommon.save')}${planInfo.price * 2}</div>}
|
||||
<div className='leading-[18px] text-[15px] font-normal text-gray-500'>/{t(`billing.plansCommon.${!isYear ? 'month' : 'year'}`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(isMostPopularPlan && !isCurrent && '!bg-[#444CE7] !text-white !border !border-[#3538CD] shadow-sm', isPlanDisabled ? 'opacity-30' : `${style[plan].hoverAndActive} cursor-pointer`, 'mt-4 flex h-11 items-center justify-center border-[2px] border-gray-900 rounded-3xl text-sm font-semibold text-gray-900')}
|
||||
onClick={handleGetPayUrl}
|
||||
>
|
||||
{btnText}
|
||||
</div>
|
||||
|
||||
<div className='my-4 h-[1px] bg-black/5'></div>
|
||||
|
||||
<div className='leading-[125%] text-[13px] font-normal text-gray-900'>
|
||||
{t(`${i18nPrefix}.includesTitle`)}
|
||||
</div>
|
||||
<KeyValue
|
||||
label={t('billing.plansCommon.modelProviders')}
|
||||
value={planInfo.modelProviders}
|
||||
/>
|
||||
<KeyValue
|
||||
label={t('billing.plansCommon.teamMembers')}
|
||||
value={planInfo.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : planInfo.teamMembers}
|
||||
/>
|
||||
<KeyValue
|
||||
label={t('billing.plansCommon.buildApps')}
|
||||
value={planInfo.buildApps === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : planInfo.buildApps}
|
||||
/>
|
||||
<KeyValue
|
||||
label={t('billing.plansCommon.vectorSpace')}
|
||||
value={planInfo.vectorSpace === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : (planInfo.vectorSpace >= 1000 ? `${planInfo.vectorSpace / 1000}G` : `${planInfo.vectorSpace}MB`)}
|
||||
/>
|
||||
<KeyValue
|
||||
label={t('billing.plansCommon.documentProcessingPriority')}
|
||||
value={t(`billing.plansCommon.priority.${planInfo.documentProcessingPriority}`) as string}
|
||||
/>
|
||||
<KeyValue
|
||||
label={t('billing.plansCommon.logsHistory')}
|
||||
value={planInfo.logHistory === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : `${planInfo.logHistory} ${t('billing.plansCommon.days')}`}
|
||||
/>
|
||||
<KeyValue
|
||||
label={t('billing.plansCommon.support')}
|
||||
value={supportContent}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(PlanItem)
|
55
web/app/components/billing/pricing/select-plan-range.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
export enum PlanRange {
|
||||
monthly = 'monthly',
|
||||
yearly = 'yearly',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
value: PlanRange
|
||||
onChange: (value: PlanRange) => void
|
||||
}
|
||||
|
||||
const ITem: FC<{ isActive: boolean; value: PlanRange; text: string; onClick: (value: PlanRange) => void }> = ({ isActive, value, text, onClick }) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(isActive ? 'bg-[#155EEF] text-white' : 'text-gray-900', 'flex items-center px-8 h-11 rounded-[32px] cursor-pointer text-[15px] font-medium')}
|
||||
onClick={() => onClick(value)}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ArrowIcon = (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="38" viewBox="0 0 26 38" fill="none">
|
||||
<path d="M20.5005 3.49991C23.5 18 18.7571 25.2595 2.92348 31.9599" stroke="#F26725" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M2.21996 32.2756L8.37216 33.5812" stroke="#F26725" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M2.22168 32.2764L3.90351 27.4459" stroke="#F26725" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const SelectPlanRange: FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-4 leading-[18px] text-sm font-medium text-[#F26725]'>{t('billing.plansCommon.yearlyTip')}</div>
|
||||
|
||||
<div className='inline-flex relative p-1 rounded-full bg-[#F5F8FF] border border-black/5'>
|
||||
<ITem isActive={value === PlanRange.monthly} value={PlanRange.monthly} text={t('billing.plansCommon.planRange.monthly') as string} onClick={onChange} />
|
||||
<ITem isActive={value === PlanRange.yearly} value={PlanRange.yearly} text={t('billing.plansCommon.planRange.yearly') as string} onClick={onChange} />
|
||||
<div className='absolute right-0 top-[-16px] '>
|
||||
{ArrowIcon}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(SelectPlanRange)
|
60
web/app/components/billing/priority-label/index.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
DocumentProcessingPriority,
|
||||
Plan,
|
||||
} from '../type'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
ZapFast,
|
||||
ZapNarrow,
|
||||
} from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
|
||||
const PriorityLabel = () => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
|
||||
const priority = useMemo(() => {
|
||||
if (plan.type === Plan.sandbox)
|
||||
return DocumentProcessingPriority.standard
|
||||
|
||||
if (plan.type === Plan.professional)
|
||||
return DocumentProcessingPriority.priority
|
||||
|
||||
if (plan.type === Plan.team || plan.type === Plan.enterprise)
|
||||
return DocumentProcessingPriority.topPriority
|
||||
}, [plan])
|
||||
|
||||
return (
|
||||
<TooltipPlus popupContent={
|
||||
<div>
|
||||
<div className='mb-1 text-xs font-semibold text-gray-700'>{`${t('billing.plansCommon.documentProcessingPriority')}: ${t(`billing.plansCommon.priority.${priority}`)}`}</div>
|
||||
{
|
||||
priority !== DocumentProcessingPriority.topPriority && (
|
||||
<div className='text-xs text-gray-500'>{t('billing.plansCommon.documentProcessingPriorityTip')}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}>
|
||||
<span className={`
|
||||
flex items-center ml-1 px-[5px] h-[18px] rounded border border-[#C7D7FE]
|
||||
text-[10px] font-medium text-[#3538CD]
|
||||
`}>
|
||||
{
|
||||
plan.type === Plan.professional && (
|
||||
<ZapNarrow className='mr-0.5 w-3 h-3' />
|
||||
)
|
||||
}
|
||||
{
|
||||
(plan.type === Plan.team || plan.type === Plan.enterprise) && (
|
||||
<ZapFast className='mr-0.5 w-3 h-3' />
|
||||
)
|
||||
}
|
||||
{t(`billing.plansCommon.priority.${priority}`)}
|
||||
</span>
|
||||
</TooltipPlus>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriorityLabel
|
22
web/app/components/billing/progress-bar/index.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
type ProgressBarProps = {
|
||||
percent: number
|
||||
color: string
|
||||
}
|
||||
const ProgressBar = ({
|
||||
percent = 0,
|
||||
color = '#2970FF',
|
||||
}: ProgressBarProps) => {
|
||||
return (
|
||||
<div className='bg-[#F2F4F7] rounded-[4px]'>
|
||||
<div
|
||||
className='h-2 rounded-[4px]'
|
||||
style={{
|
||||
width: `${Math.min(percent, 100)}%`,
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProgressBar
|
59
web/app/components/billing/type.ts
Normal file
@ -0,0 +1,59 @@
|
||||
export enum Plan {
|
||||
sandbox = 'sandbox',
|
||||
professional = 'professional',
|
||||
team = 'team',
|
||||
enterprise = 'enterprise',
|
||||
}
|
||||
|
||||
export enum Priority {
|
||||
standard = 'standard',
|
||||
priority = 'priority',
|
||||
topPriority = 'top-priority',
|
||||
}
|
||||
export type PlanInfo = {
|
||||
level: number
|
||||
price: number
|
||||
modelProviders: string
|
||||
teamMembers: number
|
||||
buildApps: number
|
||||
vectorSpace: number
|
||||
documentProcessingPriority: Priority
|
||||
logHistory: number
|
||||
}
|
||||
|
||||
export type UsagePlanInfo = Pick<PlanInfo, 'vectorSpace' | 'buildApps' | 'teamMembers'>
|
||||
|
||||
export enum DocumentProcessingPriority {
|
||||
standard = 'standard',
|
||||
priority = 'priority',
|
||||
topPriority = 'top-priority',
|
||||
}
|
||||
|
||||
export type CurrentPlanInfoBackend = {
|
||||
enabled: boolean
|
||||
subscription: {
|
||||
plan: Plan
|
||||
}
|
||||
members: {
|
||||
size: number
|
||||
limit: number // total. 0 means unlimited
|
||||
}
|
||||
apps: {
|
||||
size: number
|
||||
limit: number // total. 0 means unlimited
|
||||
}
|
||||
vector_space: {
|
||||
size: number
|
||||
limit: number // total. 0 means unlimited
|
||||
}
|
||||
docs_processing: DocumentProcessingPriority
|
||||
}
|
||||
|
||||
export type SubscriptionItem = {
|
||||
plan: Plan
|
||||
url: string
|
||||
}
|
||||
|
||||
export type SubscriptionUrlsBackend = {
|
||||
url: string
|
||||
}
|
68
web/app/components/billing/upgrade-btn/index.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import { GoldCoin } from '../../base/icons/src/vender/solid/FinanceAndECommerce'
|
||||
import { Sparkles } from '../../base/icons/src/public/billing'
|
||||
import s from './style.module.css'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
isFull?: boolean
|
||||
size?: 'md' | 'lg'
|
||||
isPlain?: boolean
|
||||
isShort?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const PlainBtn = ({ className, onClick }: { className?: string; onClick: () => {} }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(className, 'flex items-center h-8 px-3 rounded-lg border border-gray-200 bg-white shadow-sm cursor-pointer')}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className='leading-[18px] text-[13px] font-medium text-gray-700'>
|
||||
{t('billing.upgradeBtn.plain')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const UpgradeBtn: FC<Props> = ({
|
||||
className,
|
||||
isPlain = false,
|
||||
isFull = false,
|
||||
isShort = false,
|
||||
size = 'md',
|
||||
onClick,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { setShowPricingModal } = useModalContext()
|
||||
|
||||
if (isPlain)
|
||||
return <PlainBtn onClick={onClick || setShowPricingModal as any} className={className} />
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
s.upgradeBtn,
|
||||
className,
|
||||
isFull ? 'justify-center' : 'px-3',
|
||||
size === 'lg' ? 'h-10' : 'h-9',
|
||||
'relative flex items-center cursor-pointer border rounded-[20px] border-[#0096EA] text-white',
|
||||
)}
|
||||
onClick={onClick || setShowPricingModal}
|
||||
>
|
||||
<GoldCoin className='mr-1 w-3.5 h-3.5' />
|
||||
<div className='text-xs font-normal'>{t(`billing.upgradeBtn.${isShort ? 'encourageShort' : 'encourage'}`)}</div>
|
||||
<Sparkles
|
||||
className='absolute -right-1 -top-2 w-4 h-5 bg-cover'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(UpgradeBtn)
|
9
web/app/components/billing/upgrade-btn/style.module.css
Normal file
@ -0,0 +1,9 @@
|
||||
.upgradeBtn {
|
||||
background: linear-gradient(99deg, rgba(255, 255, 255, 0.12) 7.16%, rgba(255, 255, 255, 0.00) 85.47%), linear-gradient(280deg, #00B2FF 12.96%, #132BFF 90.95%);
|
||||
box-shadow: 0px 2px 4px -2px rgba(16, 24, 40, 0.06), 0px 4px 8px -2px rgba(0, 162, 253, 0.12);
|
||||
|
||||
}
|
||||
.upgradeBtn:hover {
|
||||
background: linear-gradient(99deg, rgba(255, 255, 255, 0.12) 7.16%, rgba(255, 255, 255, 0.00) 85.47%), linear-gradient(280deg, #02C2FF 12.96%, #001AFF 90.95%);
|
||||
box-shadow: 0px 4px 6px -2px rgba(16, 18, 40, 0.08), 0px 12px 16px -4px rgba(0, 209, 255, 0.08);
|
||||
}
|
32
web/app/components/billing/usage-info/apps-info.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ChatBot } from '../../base/icons/src/vender/line/communication'
|
||||
import UsageInfo from '../usage-info'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AppsInfo: FC<Props> = ({
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const {
|
||||
usage,
|
||||
total,
|
||||
} = plan
|
||||
return (
|
||||
<UsageInfo
|
||||
className={className}
|
||||
Icon={ChatBot}
|
||||
name={t('billing.plansCommon.buildApps')}
|
||||
usage={usage.buildApps}
|
||||
total={total.buildApps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(AppsInfo)
|
75
web/app/components/billing/usage-info/index.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { InfoCircle } from '../../base/icons/src/vender/line/general'
|
||||
import ProgressBar from '../progress-bar'
|
||||
import { NUM_INFINITE } from '../config'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
Icon: any
|
||||
name: string
|
||||
tooltip?: string
|
||||
usage: number
|
||||
total: number
|
||||
unit?: string
|
||||
}
|
||||
|
||||
const LOW = 50
|
||||
const MIDDLE = 80
|
||||
|
||||
const UsageInfo: FC<Props> = ({
|
||||
className,
|
||||
Icon,
|
||||
name,
|
||||
tooltip,
|
||||
usage,
|
||||
total,
|
||||
unit = '',
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const percent = usage / total * 100
|
||||
const color = (() => {
|
||||
if (percent < LOW)
|
||||
return '#155EEF'
|
||||
|
||||
if (percent < MIDDLE)
|
||||
return '#F79009'
|
||||
|
||||
return '#F04438'
|
||||
})()
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='flex justify-between h-5 items-center'>
|
||||
<div className='flex items-center'>
|
||||
<Icon className='w-4 h-4 text-gray-700' />
|
||||
<div className='mx-1 leading-5 text-sm font-medium text-gray-700'>{name}</div>
|
||||
{tooltip && (
|
||||
<Tooltip htmlContent={<div className='w-[180px]'>
|
||||
{tooltip}
|
||||
</div>} selector='config-var-tooltip'>
|
||||
<InfoCircle className='w-[14px] h-[14px] text-gray-400' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center leading-[18px] text-[13px] font-normal'>
|
||||
<div style={{
|
||||
color: percent < LOW ? '#344054' : color,
|
||||
}}>{usage}{unit}</div>
|
||||
<div className='mx-1 text-gray-300'>/</div>
|
||||
<div className='text-gray-500'>{total === NUM_INFINITE ? t('billing.plansCommon.unlimited') : `${total}${unit}`}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-2'>
|
||||
<ProgressBar
|
||||
percent={percent}
|
||||
color={color}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(UsageInfo)
|
34
web/app/components/billing/usage-info/vector-space-info.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ArtificialBrain } from '../../base/icons/src/vender/line/development'
|
||||
import UsageInfo from '../usage-info'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const VectorSpaceInfo: FC<Props> = ({
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const {
|
||||
usage,
|
||||
total,
|
||||
} = plan
|
||||
return (
|
||||
<UsageInfo
|
||||
className={className}
|
||||
Icon={ArtificialBrain}
|
||||
name={t('billing.plansCommon.vectorSpace')}
|
||||
tooltip={t('billing.plansCommon.vectorSpaceTooltip') as string}
|
||||
usage={usage.vectorSpace}
|
||||
total={total.vectorSpace}
|
||||
unit='MB'
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(VectorSpaceInfo)
|
25
web/app/components/billing/utils/index.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import type { CurrentPlanInfoBackend } from '../type'
|
||||
import { NUM_INFINITE } from '@/app/components/billing/config'
|
||||
|
||||
const parseLimit = (limit: number) => {
|
||||
if (limit === 0)
|
||||
return NUM_INFINITE
|
||||
|
||||
return limit
|
||||
}
|
||||
|
||||
export const parseCurrentPlan = (data: CurrentPlanInfoBackend) => {
|
||||
return {
|
||||
type: data.subscription.plan,
|
||||
usage: {
|
||||
vectorSpace: data.vector_space.size,
|
||||
buildApps: data.apps?.size || 0,
|
||||
teamMembers: data.members.size,
|
||||
},
|
||||
total: {
|
||||
vectorSpace: parseLimit(data.vector_space.limit),
|
||||
buildApps: parseLimit(data.apps?.limit) || 0,
|
||||
teamMembers: parseLimit(data.members.limit),
|
||||
},
|
||||
}
|
||||
}
|
32
web/app/components/billing/vector-space-full/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import UpgradeBtn from '../upgrade-btn'
|
||||
import VectorSpaceInfo from '../usage-info/vector-space-info'
|
||||
import s from './style.module.css'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import GridMask from '@/app/components/base/grid-mask'
|
||||
|
||||
const VectorSpaceFull: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const { total } = plan
|
||||
|
||||
return (
|
||||
<GridMask wrapperClassName='border border-gray-200 rounded-xl' canvasClassName='rounded-xl' gradientClassName='rounded-xl'>
|
||||
<div className='py-5 px-6'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className={cn(s.textGradient, 'leading-[24px] text-base font-semibold')}>
|
||||
<div>{t('billing.vectorSpace.fullTip')}</div>
|
||||
<div>{t('billing.vectorSpace.fullSolution')}</div>
|
||||
</div>
|
||||
<UpgradeBtn />
|
||||
</div>
|
||||
<VectorSpaceInfo className='pt-4' />
|
||||
</div>
|
||||
</GridMask>
|
||||
)
|
||||
}
|
||||
export default React.memo(VectorSpaceFull)
|
@ -0,0 +1,7 @@
|
||||
.textGradient {
|
||||
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
@ -15,6 +15,10 @@ import { formatNumber } from '@/utils/format'
|
||||
import { fetchIndexingStatusBatch as doFetchIndexingStatus, fetchIndexingEstimateBatch, fetchProcessRule } from '@/service/datasets'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import NotionIcon from '@/app/components/base/notion-icon'
|
||||
import PriorityLabel from '@/app/components/billing/priority-label'
|
||||
import { ZapFast } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
type Props = {
|
||||
datasetId: string
|
||||
@ -78,6 +82,7 @@ const RuleDetail: FC<{ sourceData?: ProcessRuleResponse }> = ({ sourceData }) =>
|
||||
|
||||
const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], indexingType }) => {
|
||||
const { t } = useTranslation()
|
||||
const { enableBilling } = useProviderContext()
|
||||
|
||||
const getFirstDocument = documents[0]
|
||||
|
||||
@ -115,14 +120,14 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
|
||||
}, [])
|
||||
|
||||
// get rule
|
||||
const { data: ruleDetail, error: ruleError } = useSWR({
|
||||
const { data: ruleDetail } = useSWR({
|
||||
action: 'fetchProcessRule',
|
||||
params: { documentId: getFirstDocument.id },
|
||||
}, apiParams => fetchProcessRule(omit(apiParams, 'action')), {
|
||||
revalidateOnFocus: false,
|
||||
})
|
||||
// get cost
|
||||
const { data: indexingEstimateDetail, error: indexingEstimateErr } = useSWR({
|
||||
const { data: indexingEstimateDetail } = useSWR({
|
||||
action: 'fetchIndexingEstimateBatch',
|
||||
datasetId,
|
||||
batchId,
|
||||
@ -175,7 +180,7 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
|
||||
{isEmbeddingCompleted && t('datasetDocuments.embedding.completed')}
|
||||
</div>
|
||||
<div className={s.cost}>
|
||||
{indexingType === 'high_quaility' && (
|
||||
{indexingType === 'high_quality' && (
|
||||
<div className='flex items-center'>
|
||||
<div className={cn(s.commonIcon, s.highIcon)} />
|
||||
{t('datasetDocuments.embedding.highQuality')} · {t('datasetDocuments.embedding.estimate')}
|
||||
@ -192,6 +197,19 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
enableBilling && (
|
||||
<div className='flex items-center mb-3 p-3 h-14 bg-white border-[0.5px] border-black/5 shadow-md rounded-xl'>
|
||||
<div className='shrink-0 flex items-center justify-center w-8 h-8 bg-[#FFF6ED] rounded-lg'>
|
||||
<ZapFast className='w-4 h-4 text-[#FB6514]' />
|
||||
</div>
|
||||
<div className='grow mx-3 text-[13px] font-medium text-gray-700'>
|
||||
{t('billing.plansCommon.documentProcessingPriorityUpgrade')}
|
||||
</div>
|
||||
<UpgradeBtn />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className={s.progressContainer}>
|
||||
{indexingStatusBatchDetail.map(indexingStatusDetail => (
|
||||
<div key={indexingStatusDetail.id} className={cn(
|
||||
@ -202,7 +220,7 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
|
||||
{isSourceEmbedding(indexingStatusDetail) && (
|
||||
<div className={s.progressbar} style={{ width: `${getSourcePercent(indexingStatusDetail)}%` }}/>
|
||||
)}
|
||||
<div className={s.info}>
|
||||
<div className={`${s.info} grow`}>
|
||||
{getSourceType(indexingStatusDetail.id) === DataSourceType.FILE && (
|
||||
<div className={cn(s.fileIcon, s[getFileType(getSourceName(indexingStatusDetail.id))])}/>
|
||||
)}
|
||||
@ -213,7 +231,12 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
|
||||
src={getIcon(indexingStatusDetail.id)}
|
||||
/>
|
||||
)}
|
||||
<div className={s.name}>{getSourceName(indexingStatusDetail.id)}</div>
|
||||
<div className={`${s.name} truncate`} title={getSourceName(indexingStatusDetail.id)}>{getSourceName(indexingStatusDetail.id)}</div>
|
||||
{
|
||||
enableBilling && (
|
||||
<PriorityLabel />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='shrink-0'>
|
||||
{isSourceEmbedding(indexingStatusDetail) && (
|
||||
|
@ -15,6 +15,8 @@ import Button from '@/app/components/base/button'
|
||||
import { NotionPageSelector } from '@/app/components/base/notion-page-selector'
|
||||
import { useDatasetDetailContext } from '@/context/dataset-detail'
|
||||
import { fetchDocumentsLimit } from '@/service/common'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import VectorSpaceFull from '@/app/components/billing/vector-space-full'
|
||||
|
||||
type IStepOneProps = {
|
||||
datasetId?: string
|
||||
@ -88,11 +90,20 @@ const StepOne = ({
|
||||
|
||||
const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type)
|
||||
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const allFileLoaded = (files.length > 0 && files.every(file => file.file.id))
|
||||
const hasNotin = notionPages.length > 0
|
||||
const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
|
||||
const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling
|
||||
|
||||
const nextDisabled = useMemo(() => {
|
||||
if (!files.length)
|
||||
return true
|
||||
if (files.some(file => !file.file.id))
|
||||
return true
|
||||
if (isShowVectorSpaceFull)
|
||||
return true
|
||||
|
||||
return false
|
||||
}, [files])
|
||||
return (
|
||||
@ -164,6 +175,11 @@ const StepOne = ({
|
||||
countLimit={limitsData.documents_limit}
|
||||
countUsed={limitsData.documents_count}
|
||||
/>
|
||||
{isShowVectorSpaceFull && (
|
||||
<div className='max-w-[640px] mb-4'>
|
||||
<VectorSpaceFull />
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={nextDisabled} className={s.submitButton} type='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
|
||||
</>
|
||||
)}
|
||||
@ -181,7 +197,12 @@ const StepOne = ({
|
||||
countUsed={limitsData.documents_count}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={!notionPages.length} className={s.submitButton} type='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
|
||||
{isShowVectorSpaceFull && (
|
||||
<div className='max-w-[640px] mb-4'>
|
||||
<VectorSpaceFull />
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={isShowVectorSpaceFull || !notionPages.length} className={s.submitButton} type='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -8,7 +8,8 @@ import Button from '@/app/components/base/button'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import EmojiPicker from '@/app/components/base/emoji-picker'
|
||||
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
export type CreateAppModalProps = {
|
||||
appName: string
|
||||
show: boolean
|
||||
@ -33,6 +34,9 @@ const CreateAppModal = ({
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||
const [emoji, setEmoji] = useState({ icon: '🤖', icon_background: '#FFEAD5' })
|
||||
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
|
||||
const submit = () => {
|
||||
if (!name.trim()) {
|
||||
Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') })
|
||||
@ -64,9 +68,10 @@ const CreateAppModal = ({
|
||||
className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow'
|
||||
/>
|
||||
</div>
|
||||
{isAppsFull && <AppsFull />}
|
||||
</div>
|
||||
<div className='flex flex-row-reverse'>
|
||||
<Button className='w-24 ml-2' type='primary' onClick={submit}>{t('common.operation.create')}</Button>
|
||||
<Button disabled={isAppsFull} className='w-24 ml-2' type='primary' onClick={submit}>{t('common.operation.create')}</Button>
|
||||
<Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
@ -2,6 +2,8 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { GoldCoin } from '../../base/icons/src/vender/solid/FinanceAndECommerce'
|
||||
import { GoldCoin as GoldCoinOutLine } from '../../base/icons/src/vender/line/financeAndECommerce'
|
||||
import AccountPage from './account-page'
|
||||
import MembersPage from './members-page'
|
||||
import IntegrationsPage from './Integrations-page'
|
||||
@ -11,6 +13,7 @@ import ApiBasedExtensionPage from './api-based-extension-page'
|
||||
import DataSourcePage from './data-source-page'
|
||||
import ModelPage from './model-page'
|
||||
import s from './index.module.css'
|
||||
import BillingPage from '@/app/components/billing/billing-page'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import {
|
||||
Database03,
|
||||
@ -24,6 +27,7 @@ import { Globe01 } from '@/app/components/base/icons/src/vender/line/mapsAndTrav
|
||||
import { AtSign, XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
const iconClassName = `
|
||||
w-4 h-4 ml-3 mr-2
|
||||
@ -37,12 +41,63 @@ type IAccountSettingProps = {
|
||||
onCancel: () => void
|
||||
activeTab?: string
|
||||
}
|
||||
|
||||
type GroupItem = {
|
||||
key: string
|
||||
name: string
|
||||
icon: JSX.Element
|
||||
activeIcon: JSX.Element
|
||||
}
|
||||
|
||||
export default function AccountSetting({
|
||||
onCancel,
|
||||
activeTab = 'account',
|
||||
}: IAccountSettingProps) {
|
||||
const [activeMenu, setActiveMenu] = useState(activeTab)
|
||||
const { t } = useTranslation()
|
||||
const { enableBilling } = useProviderContext()
|
||||
|
||||
const workplaceGroupItems = (() => {
|
||||
return [
|
||||
{
|
||||
key: 'provider',
|
||||
name: t('common.settings.provider'),
|
||||
icon: <CubeOutline className={iconClassName} />,
|
||||
activeIcon: <CubeOutline className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'members',
|
||||
name: t('common.settings.members'),
|
||||
icon: <Users01 className={iconClassName} />,
|
||||
activeIcon: <Users01Solid className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
// Use key false to hide this item
|
||||
key: enableBilling ? 'billing' : false,
|
||||
name: t('common.settings.billing'),
|
||||
icon: <GoldCoinOutLine className={iconClassName} />,
|
||||
activeIcon: <GoldCoin className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'data-source',
|
||||
name: t('common.settings.dataSource'),
|
||||
icon: <Database03 className={iconClassName} />,
|
||||
activeIcon: <Database03Solid className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'plugin',
|
||||
name: t('common.settings.plugin'),
|
||||
icon: <PuzzlePiece01 className={iconClassName} />,
|
||||
activeIcon: <PuzzlePiece01Solid className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'api-based-extension',
|
||||
name: t('common.settings.apiBasedExtension'),
|
||||
icon: <Webhooks className={iconClassName} />,
|
||||
activeIcon: <Webhooks className={iconClassName} />,
|
||||
},
|
||||
].filter(item => !!item.key) as GroupItem[]
|
||||
})()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
@ -51,38 +106,7 @@ export default function AccountSetting({
|
||||
{
|
||||
key: 'workspace-group',
|
||||
name: t('common.settings.workplaceGroup'),
|
||||
items: [
|
||||
{
|
||||
key: 'members',
|
||||
name: t('common.settings.members'),
|
||||
icon: <Users01 className={iconClassName} />,
|
||||
activeIcon: <Users01Solid className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'provider',
|
||||
name: t('common.settings.provider'),
|
||||
icon: <CubeOutline className={iconClassName} />,
|
||||
activeIcon: <CubeOutline className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'data-source',
|
||||
name: t('common.settings.dataSource'),
|
||||
icon: <Database03 className={iconClassName} />,
|
||||
activeIcon: <Database03Solid className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'plugin',
|
||||
name: t('common.settings.plugin'),
|
||||
icon: <PuzzlePiece01 className={iconClassName} />,
|
||||
activeIcon: <PuzzlePiece01Solid className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'api-based-extension',
|
||||
name: t('common.settings.apiBasedExtension'),
|
||||
icon: <Webhooks className={iconClassName} />,
|
||||
activeIcon: <Webhooks className={iconClassName} />,
|
||||
},
|
||||
],
|
||||
items: workplaceGroupItems,
|
||||
},
|
||||
{
|
||||
key: 'account-group',
|
||||
@ -175,6 +199,7 @@ export default function AccountSetting({
|
||||
<div className='px-4 sm:px-8 pt-2'>
|
||||
{activeMenu === 'account' && <AccountPage />}
|
||||
{activeMenu === 'members' && <MembersPage />}
|
||||
{activeMenu === 'billing' && <BillingPage />}
|
||||
{activeMenu === 'integrations' && <IntegrationsPage />}
|
||||
{activeMenu === 'language' && <LanguagePage />}
|
||||
{activeMenu === 'provider' && <ModelPage />}
|
||||
|
@ -15,6 +15,11 @@ import I18n from '@/context/i18n'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import Avatar from '@/app/components/base/avatar'
|
||||
import type { InvitationResult } from '@/models/common'
|
||||
import LogoEmbededChatHeader from '@/app/components/base/logo/logo-embeded-chat-header'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||
import { NUM_INFINITE } from '@/app/components/billing/config'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
@ -33,20 +38,46 @@ const MembersPage = () => {
|
||||
const [invitedModalVisible, setInvitedModalVisible] = useState(false)
|
||||
const accounts = data?.accounts || []
|
||||
const owner = accounts.filter(account => account.role === 'owner')?.[0]?.email === userProfile.email
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const isNotUnlimitedMemberPlan = enableBilling && plan.type !== Plan.team && plan.type !== Plan.enterprise
|
||||
const isMemberFull = enableBilling && isNotUnlimitedMemberPlan && accounts.length >= plan.total.teamMembers
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className='flex items-center mb-4 p-3 bg-gray-50 rounded-2xl'>
|
||||
<LogoEmbededChatHeader className='!w-10 !h-10' />
|
||||
<div className='grow mx-2'>
|
||||
<div className='text-sm font-medium text-gray-900'>{currentWorkspace?.name}</div>
|
||||
<div className='text-xs text-gray-500'>{t('common.userProfile.workspace')}</div>
|
||||
{enableBilling && (
|
||||
<div className='text-xs text-gray-500'>
|
||||
{isNotUnlimitedMemberPlan
|
||||
? (
|
||||
<div className='flex space-x-1'>
|
||||
<div>{t('billing.plansCommon.member')}{locale === 'en' && accounts.length > 1 && 's'}</div>
|
||||
<div className='text-gray-700'>{accounts.length}</div>
|
||||
<div>/</div>
|
||||
<div>{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='flex space-x-1'>
|
||||
<div>{accounts.length}</div>
|
||||
<div>{t('billing.plansCommon.memberAfter')}{locale === 'en' && accounts.length > 1 && 's'}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
{isMemberFull && (
|
||||
<UpgradeBtn className='mr-2' />
|
||||
)}
|
||||
<div className={
|
||||
`shrink-0 flex items-center py-[7px] px-3 border-[0.5px] border-gray-200
|
||||
text-[13px] font-medium text-primary-600 bg-white
|
||||
shadow-xs rounded-lg ${isCurrentWorkspaceManager ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
} onClick={() => isCurrentWorkspaceManager && setInviteModalVisible(true)}>
|
||||
shadow-xs rounded-lg ${(isCurrentWorkspaceManager && !isMemberFull) ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
} onClick={() => (isCurrentWorkspaceManager && !isMemberFull) && setInviteModalVisible(true)}>
|
||||
<UserPlusIcon className='w-4 h-4 mr-2 ' />
|
||||
{t('common.members.invite')}
|
||||
</div>
|
||||
|
@ -1,22 +1,22 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useBoolean, useClickAway } from 'ahooks'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
import classNames from 'classnames'
|
||||
import { useEffect } from 'react'
|
||||
import { Bars3Icon } from '@heroicons/react/20/solid'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import HeaderBillingBtn from '../billing/header-billing-btn'
|
||||
import AccountDropdown from './account-dropdown'
|
||||
import AppNav from './app-nav'
|
||||
import DatasetNav from './dataset-nav'
|
||||
import EnvNav from './env-nav'
|
||||
import ExploreNav from './explore-nav'
|
||||
import GithubStar from './github-star'
|
||||
import s from './index.module.css'
|
||||
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'
|
||||
|
||||
const navClassName = `
|
||||
flex items-center relative mr-0 sm:mr-3 px-3 h-9 rounded-xl
|
||||
@ -25,58 +25,72 @@ const navClassName = `
|
||||
`
|
||||
|
||||
const Header = () => {
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const { isCurrentWorkspaceManager, langeniusVersionInfo } = useAppContext()
|
||||
const [showUpgradePanel, setShowUpgradePanel] = useState(false)
|
||||
const upgradeBtnRef = useRef<HTMLElement>(null)
|
||||
useClickAway(() => {
|
||||
setShowUpgradePanel(false)
|
||||
}, upgradeBtnRef)
|
||||
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false)
|
||||
const { enableBilling } = useProviderContext()
|
||||
|
||||
useEffect(() => {
|
||||
hideNavMenu()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedSegment])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classNames(
|
||||
s[`header-${langeniusVersionInfo.current_env}`],
|
||||
'flex flex-1 items-center justify-between px-4',
|
||||
)}>
|
||||
<div className='flex flex-1 items-center justify-between px-4'>
|
||||
<div className='flex items-center'>
|
||||
{isMobile && <div
|
||||
className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
||||
onClick={toggle}
|
||||
>
|
||||
<Bars3Icon className="h-4 w-4 text-gray-500" />
|
||||
</div>}
|
||||
{!isMobile && <>
|
||||
<Link href="/apps" className='flex items-center mr-4'>
|
||||
<LogoSite />
|
||||
</Link>
|
||||
<GithubStar />
|
||||
</>}
|
||||
</div>
|
||||
{isMobile && (
|
||||
<div className='flex'>
|
||||
<Link href="/apps" className='flex items-center mr-4'>
|
||||
<LogoSite />
|
||||
</Link>
|
||||
<GithubStar />
|
||||
</div>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<div className='flex items-center'>
|
||||
{isMobile && <div
|
||||
className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
||||
onClick={toggle}
|
||||
>
|
||||
<Bars3Icon className="h-4 w-4 text-gray-500" />
|
||||
</div>}
|
||||
{!isMobile && <>
|
||||
<Link href="/apps" className='flex items-center mr-4'>
|
||||
<LogoSite />
|
||||
</Link>
|
||||
<GithubStar />
|
||||
</>}
|
||||
<ExploreNav className={navClassName} />
|
||||
<AppNav />
|
||||
{isCurrentWorkspaceManager && <DatasetNav />}
|
||||
</div>
|
||||
{isMobile && (
|
||||
<div className='flex'>
|
||||
<Link href="/apps" className='flex items-center mr-4'>
|
||||
<LogoSite />
|
||||
</Link>
|
||||
<GithubStar />
|
||||
)}
|
||||
<div className='flex items-center flex-shrink-0'>
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<div className='flex items-center'>
|
||||
<ExploreNav className={navClassName} />
|
||||
<AppNav />
|
||||
{isCurrentWorkspaceManager && <DatasetNav />}
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center flex-shrink-0'>
|
||||
<EnvNav />
|
||||
<WorkspaceProvider>
|
||||
<AccountDropdown isMobile={isMobile} />
|
||||
</WorkspaceProvider>
|
||||
</div>
|
||||
<WorkspaceProvider>
|
||||
<AccountDropdown isMobile={isMobile}/>
|
||||
</WorkspaceProvider>
|
||||
</div>
|
||||
{(isMobile && isShowNavMenu) && (
|
||||
<div className='w-full flex flex-col p-2 gap-y-1'>
|
||||
@ -85,7 +99,7 @@ const Header = () => {
|
||||
{isCurrentWorkspaceManager && <DatasetNav />}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Header
|
||||
|
@ -3,10 +3,12 @@
|
||||
import type { Dispatch, SetStateAction } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import AccountSetting from '@/app/components/header/account-setting'
|
||||
import ApiBasedExtensionModal from '@/app/components/header/account-setting/api-based-extension-page/modal'
|
||||
import ModerationSettingModal from '@/app/components/app/configuration/toolbox/moderation/moderation-setting-modal'
|
||||
import ExternalDataToolModal from '@/app/components/app/configuration/tools/external-data-tool-modal'
|
||||
import Pricing from '@/app/components/billing/pricing'
|
||||
import type { ModerationConfig } from '@/models/debug'
|
||||
import type {
|
||||
ApiBasedExtension,
|
||||
@ -25,11 +27,13 @@ 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>>
|
||||
}>({
|
||||
setShowAccountSettingModal: () => {},
|
||||
setShowApiBasedExtensionModal: () => {},
|
||||
setShowModerationSettingModal: () => {},
|
||||
setShowExternalDataToolModal: () => {},
|
||||
setShowPricingModal: () => {},
|
||||
})
|
||||
|
||||
export const useModalContext = () => useContext(ModalContext)
|
||||
@ -44,6 +48,9 @@ export const ModalContextProvider = ({
|
||||
const [showApiBasedExtensionModal, setShowApiBasedExtensionModal] = useState<ModalState<ApiBasedExtension> | null>(null)
|
||||
const [showModerationSettingModal, setShowModerationSettingModal] = useState<ModalState<ModerationConfig> | null>(null)
|
||||
const [showExternalDataToolModal, setShowExternalDataToolModal] = useState<ModalState<ExternalDataTool> | null>(null)
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const [showPricingModal, setShowPricingModal] = useState(searchParams.get('show-pricing') === '1')
|
||||
|
||||
const handleCancelAccountSettingModal = () => {
|
||||
setShowAccountSettingModal(null)
|
||||
@ -93,6 +100,7 @@ export const ModalContextProvider = ({
|
||||
setShowApiBasedExtensionModal,
|
||||
setShowModerationSettingModal,
|
||||
setShowExternalDataToolModal,
|
||||
setShowPricingModal: () => setShowPricingModal(true),
|
||||
}}>
|
||||
<>
|
||||
{children}
|
||||
@ -104,6 +112,7 @@ export const ModalContextProvider = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!!showApiBasedExtensionModal && (
|
||||
<ApiBasedExtensionModal
|
||||
@ -132,6 +141,17 @@ export const ModalContextProvider = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!!showPricingModal && (
|
||||
<Pricing onCancel={() => {
|
||||
if (searchParams.get('show-pricing') === '1')
|
||||
router.push(location.pathname, { forceOptimisticNavigation: true })
|
||||
|
||||
setShowPricingModal(false)
|
||||
}} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
</ModalContext.Provider>
|
||||
)
|
||||
|
@ -2,10 +2,16 @@
|
||||
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import useSWR from 'swr'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { fetchDefaultModal, fetchModelList, fetchSupportRetrievalMethods } from '@/service/common'
|
||||
import { ModelFeature, ModelType } from '@/app/components/header/account-setting/model-page/declarations'
|
||||
import type { BackendModel } from '@/app/components/header/account-setting/model-page/declarations'
|
||||
import type { RETRIEVE_METHOD } from '@/types/app'
|
||||
import { Plan, type UsagePlanInfo } from '@/app/components/billing/type'
|
||||
import { fetchCurrentPlanInfo } from '@/service/billing'
|
||||
import { parseCurrentPlan } from '@/app/components/billing/utils'
|
||||
import { defaultPlan } from '@/app/components/billing/config'
|
||||
|
||||
const ProviderContext = createContext<{
|
||||
textGenerationModelList: BackendModel[]
|
||||
embeddingsModelList: BackendModel[]
|
||||
@ -23,6 +29,13 @@ const ProviderContext = createContext<{
|
||||
isRerankDefaultModelVaild: boolean
|
||||
mutateRerankDefaultModel: () => void
|
||||
supportRetrievalMethods: RETRIEVE_METHOD[]
|
||||
plan: {
|
||||
type: Plan
|
||||
usage: UsagePlanInfo
|
||||
total: UsagePlanInfo
|
||||
}
|
||||
isFetchedPlan: boolean
|
||||
enableBilling: boolean
|
||||
}>({
|
||||
textGenerationModelList: [],
|
||||
embeddingsModelList: [],
|
||||
@ -40,6 +53,21 @@ const ProviderContext = createContext<{
|
||||
isRerankDefaultModelVaild: false,
|
||||
mutateRerankDefaultModel: () => {},
|
||||
supportRetrievalMethods: [],
|
||||
plan: {
|
||||
type: Plan.sandbox,
|
||||
usage: {
|
||||
vectorSpace: 32,
|
||||
buildApps: 12,
|
||||
teamMembers: 1,
|
||||
},
|
||||
total: {
|
||||
vectorSpace: 200,
|
||||
buildApps: 50,
|
||||
teamMembers: 1,
|
||||
},
|
||||
},
|
||||
isFetchedPlan: false,
|
||||
enableBilling: false,
|
||||
})
|
||||
|
||||
export const useProviderContext = () => useContext(ProviderContext)
|
||||
@ -80,6 +108,21 @@ export const ProviderContextProvider = ({
|
||||
mutateRerankModelList()
|
||||
}
|
||||
|
||||
const [plan, setPlan] = useState(defaultPlan)
|
||||
const [isFetchedPlan, setIsFetchedPlan] = useState(false)
|
||||
const [enableBilling, setEnableBilling] = useState(true)
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await fetchCurrentPlanInfo()
|
||||
const enabled = data.enabled
|
||||
setEnableBilling(enabled)
|
||||
if (enabled) {
|
||||
setPlan(parseCurrentPlan(data))
|
||||
setIsFetchedPlan(true)
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ProviderContext.Provider value={{
|
||||
textGenerationModelList: textGenerationModelList || [],
|
||||
@ -98,6 +141,9 @@ export const ProviderContextProvider = ({
|
||||
isRerankDefaultModelVaild,
|
||||
mutateRerankDefaultModel,
|
||||
supportRetrievalMethods: supportRetrievalMethods?.retrieval_method || [],
|
||||
plan,
|
||||
isFetchedPlan,
|
||||
enableBilling,
|
||||
}}>
|
||||
{children}
|
||||
</ProviderContext.Provider>
|
||||
|
@ -35,6 +35,25 @@ export const useAnthropicCheckPay = () => {
|
||||
return confirm
|
||||
}
|
||||
|
||||
export const useBillingPay = () => {
|
||||
const { t } = useTranslation()
|
||||
const [confirm, setConfirm] = useState<ConfirmType | null>(null)
|
||||
const searchParams = useSearchParams()
|
||||
const paymentType = searchParams.get('payment_type')
|
||||
const paymentResult = searchParams.get('payment_result')
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentType === 'billing' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) {
|
||||
setConfirm({
|
||||
type: paymentResult === 'succeeded' ? 'success' : 'danger',
|
||||
title: paymentResult === 'succeeded' ? t('common.actionMsg.paySucceeded') : t('common.actionMsg.payCancelled'),
|
||||
})
|
||||
}
|
||||
}, [paymentType, paymentResult, t])
|
||||
|
||||
return confirm
|
||||
}
|
||||
|
||||
const QUOTA_RECEIVE_STATUS = {
|
||||
[ProviderEnum.spark]: {
|
||||
success: {
|
||||
@ -138,13 +157,14 @@ export const CheckModal = () => {
|
||||
const anthropicConfirmInfo = useAnthropicCheckPay()
|
||||
const freeQuotaConfirmInfo = useCheckFreeQuota()
|
||||
const notionConfirmInfo = useCheckNotion()
|
||||
const billingConfirmInfo = useBillingPay()
|
||||
|
||||
const handleCancelShowPayStatusModal = useCallback(() => {
|
||||
setShowPayStatusModal(false)
|
||||
router.replace('/', { forceOptimisticNavigation: false })
|
||||
}, [router])
|
||||
|
||||
const confirmInfo = anthropicConfirmInfo || freeQuotaConfirmInfo || notionConfirmInfo
|
||||
const confirmInfo = anthropicConfirmInfo || freeQuotaConfirmInfo || notionConfirmInfo || billingConfirmInfo
|
||||
|
||||
if (!confirmInfo || !showPayStatusModal)
|
||||
return null
|
||||
|
@ -33,6 +33,8 @@ import datasetCreationEn from './lang/dataset-creation.en'
|
||||
import datasetCreationZh from './lang/dataset-creation.zh'
|
||||
import exploreEn from './lang/explore.en'
|
||||
import exploreZh from './lang/explore.zh'
|
||||
import billingEn from './lang/billing.en'
|
||||
import billingZh from './lang/billing.zh'
|
||||
|
||||
const resources = {
|
||||
'en': {
|
||||
@ -55,6 +57,8 @@ const resources = {
|
||||
datasetSettings: datasetSettingsEn,
|
||||
datasetCreation: datasetCreationEn,
|
||||
explore: exploreEn,
|
||||
// billing
|
||||
billing: billingEn,
|
||||
},
|
||||
},
|
||||
'zh-Hans': {
|
||||
@ -77,6 +81,7 @@ const resources = {
|
||||
datasetSettings: datasetSettingsZh,
|
||||
datasetCreation: datasetCreationZh,
|
||||
explore: exploreZh,
|
||||
billing: billingZh,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
90
web/i18n/lang/billing.en.ts
Normal file
@ -0,0 +1,90 @@
|
||||
const translation = {
|
||||
currentPlan: 'Current Plan',
|
||||
upgradeBtn: {
|
||||
plain: 'Upgrade Plan',
|
||||
encourage: 'Upgrade Now',
|
||||
encourageShort: 'Upgrade',
|
||||
},
|
||||
viewBilling: 'View billing information',
|
||||
buyPermissionDeniedTip: 'Please contact your enterprise administrator to subscribe',
|
||||
plansCommon: {
|
||||
title: 'Choose a plan that’s right for you',
|
||||
yearlyTip: 'Get 2 months for free by subscribing yearly!',
|
||||
mostPopular: 'Most Popular',
|
||||
planRange: {
|
||||
monthly: 'Monthly',
|
||||
yearly: 'Yearly',
|
||||
},
|
||||
month: 'month',
|
||||
year: 'year',
|
||||
save: 'Save ',
|
||||
free: 'Free',
|
||||
currentPlan: 'current plan',
|
||||
startForFree: 'Start for free',
|
||||
getStartedWith: 'Get started with ',
|
||||
contactSales: 'Contact Sales',
|
||||
talkToSales: 'Talk to Sales',
|
||||
modelProviders: 'Model Providers',
|
||||
teamMembers: 'Team Members',
|
||||
buildApps: 'Build Apps',
|
||||
vectorSpace: 'Vector Space',
|
||||
vectorSpaceTooltip: 'Vector Space is the long-term memory system required for LLMs to comprehend your data.',
|
||||
documentProcessingPriority: 'Document Processing Priority',
|
||||
documentProcessingPriorityTip: 'For higher document processing priority, please upgrade your plan.',
|
||||
documentProcessingPriorityUpgrade: 'Process more data with higher accuracy at faster speeds.',
|
||||
priority: {
|
||||
'standard': 'Standard',
|
||||
'priority': 'Priority',
|
||||
'top-priority': 'Top Priority',
|
||||
},
|
||||
logsHistory: 'Logs history',
|
||||
days: 'days',
|
||||
unlimited: 'Unlimited',
|
||||
support: 'Support',
|
||||
supportItems: {
|
||||
communityForums: 'Community forums',
|
||||
emailSupport: 'Email support',
|
||||
priorityEmail: 'Priority email & chat support',
|
||||
logoChange: 'Logo change',
|
||||
SSOAuthentication: 'SSO authentication',
|
||||
personalizedSupport: 'Personalized support',
|
||||
dedicatedAPISupport: 'Dedicated API support',
|
||||
customIntegration: 'Custom integration and support',
|
||||
},
|
||||
comingSoon: 'Coming soon',
|
||||
member: 'Member',
|
||||
memberAfter: 'Member',
|
||||
},
|
||||
plans: {
|
||||
sandbox: {
|
||||
name: 'Sandbox',
|
||||
description: '200 times GPT free trial',
|
||||
includesTitle: 'Includes:',
|
||||
},
|
||||
professional: {
|
||||
name: 'Professional',
|
||||
description: 'For individuals and small teams to unlock more power affordably.',
|
||||
includesTitle: 'Everything in free plan, plus:',
|
||||
},
|
||||
team: {
|
||||
name: 'Team',
|
||||
description: 'Collaborate without limits and enjoy top-tier performance.',
|
||||
includesTitle: 'Everything in Professional plan, plus:',
|
||||
},
|
||||
enterprise: {
|
||||
name: 'Enterprise',
|
||||
description: 'Get full capabilities and support for large-scale mission-critical systems.',
|
||||
includesTitle: 'Everything in Team plan, plus:',
|
||||
},
|
||||
},
|
||||
vectorSpace: {
|
||||
fullTip: 'Vector Space is full.',
|
||||
fullSolution: 'Upgrade your plan to get more space.',
|
||||
},
|
||||
apps: {
|
||||
fullTipLine1: 'Upgrade your plan to',
|
||||
fullTipLine2: 'build more apps.',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
90
web/i18n/lang/billing.zh.ts
Normal file
@ -0,0 +1,90 @@
|
||||
const translation = {
|
||||
currentPlan: '当前套餐',
|
||||
upgradeBtn: {
|
||||
plain: '升级套餐',
|
||||
encourage: '立即升级',
|
||||
encourageShort: '升级',
|
||||
},
|
||||
viewBilling: '查看账单信息',
|
||||
buyPermissionDeniedTip: '请联系企业管理员订阅',
|
||||
plansCommon: {
|
||||
title: '选择适合您的套餐',
|
||||
yearlyTip: '订阅年度计划可免费获得 2个月!',
|
||||
mostPopular: '最受欢迎',
|
||||
planRange: {
|
||||
monthly: '按月',
|
||||
yearly: '按年',
|
||||
},
|
||||
month: '月',
|
||||
year: '年',
|
||||
save: '节省',
|
||||
currentPlan: '当前计划',
|
||||
free: '免费',
|
||||
startForFree: '免费开始',
|
||||
getStartedWith: '开始使用',
|
||||
contactSales: '联系销售',
|
||||
talkToSales: '联系销售',
|
||||
modelProviders: '支持的模型提供商',
|
||||
teamMembers: '团队成员',
|
||||
buildApps: '构建应用程序数',
|
||||
vectorSpace: '向量空间',
|
||||
vectorSpaceTooltip: '向量空间是 LLMs 理解您的数据所需的长期记忆系统。',
|
||||
documentProcessingPriority: '文档处理优先级',
|
||||
documentProcessingPriorityTip: '如需更高的文档处理优先级,请升级您的套餐',
|
||||
documentProcessingPriorityUpgrade: '以更快的速度、更高的精度处理更多的数据。',
|
||||
priority: {
|
||||
'standard': '标准',
|
||||
'priority': '优先',
|
||||
'top-priority': '最高优先级',
|
||||
},
|
||||
logsHistory: '日志历史',
|
||||
days: '天',
|
||||
unlimited: '无限制',
|
||||
support: '支持',
|
||||
supportItems: {
|
||||
communityForums: '社区论坛',
|
||||
emailSupport: '电子邮件支持',
|
||||
priorityEmail: '优先电子邮件和聊天支持',
|
||||
logoChange: 'Logo更改',
|
||||
SSOAuthentication: 'SSO 认证',
|
||||
personalizedSupport: '个性化支持',
|
||||
dedicatedAPISupport: '专用 API 支持',
|
||||
customIntegration: '自定义集成和支持',
|
||||
},
|
||||
comingSoon: '即将推出',
|
||||
member: '成员',
|
||||
memberAfter: '个成员',
|
||||
},
|
||||
plans: {
|
||||
sandbox: {
|
||||
name: 'Sandbox',
|
||||
description: '200次 GPT 免费试用',
|
||||
includesTitle: '包括:',
|
||||
},
|
||||
professional: {
|
||||
name: 'Professional',
|
||||
description: '让个人和小团队能够以经济实惠的方式释放更多能力。',
|
||||
includesTitle: 'Sandbox 计划中的一切,加上:',
|
||||
},
|
||||
team: {
|
||||
name: 'Team',
|
||||
description: '协作无限制并享受顶级性能。',
|
||||
includesTitle: 'Professional 计划中的一切,加上:',
|
||||
},
|
||||
enterprise: {
|
||||
name: 'Enterprise',
|
||||
description: '获得大规模关键任务系统的完整功能和支持。',
|
||||
includesTitle: 'Team 计划中的一切,加上:',
|
||||
},
|
||||
},
|
||||
vectorSpace: {
|
||||
fullTip: '向量空间已满。',
|
||||
fullSolution: '升级您的套餐以获得更多空间。',
|
||||
},
|
||||
apps: {
|
||||
fullTipLine1: '升级您的套餐以',
|
||||
fullTipLine2: '构建更多的程序。',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
@ -104,6 +104,7 @@ const translation = {
|
||||
workplaceGroup: 'WORKPLACE',
|
||||
account: 'My account',
|
||||
members: 'Members',
|
||||
billing: 'Billing',
|
||||
integrations: 'Integrations',
|
||||
language: 'Language',
|
||||
provider: 'Model Provider',
|
||||
|
@ -104,6 +104,7 @@ const translation = {
|
||||
workplaceGroup: '工作空间',
|
||||
account: '我的账户',
|
||||
members: '成员',
|
||||
billing: '账单',
|
||||
integrations: '集成',
|
||||
language: '语言',
|
||||
provider: '模型供应商',
|
||||
|
14
web/service/billing.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { get } from './base'
|
||||
import type { CurrentPlanInfoBackend, SubscriptionUrlsBackend } from '@/app/components/billing/type'
|
||||
|
||||
export const fetchCurrentPlanInfo = () => {
|
||||
return get<Promise<CurrentPlanInfoBackend>>('/billing/info')
|
||||
}
|
||||
|
||||
export const fetchSubscriptionUrls = (plan: string, interval: string) => {
|
||||
return get<Promise<SubscriptionUrlsBackend>>(`/billing/subscription?plan=${plan}&interval=${interval}`)
|
||||
}
|
||||
|
||||
export const fetchBillingUrl = () => {
|
||||
return get<Promise<{ url: string }>>('/billing/invoices')
|
||||
}
|