mirror of
https://github.com/langgenius/dify.git
synced 2026-04-30 15:38:08 +08:00
Merge branch 'main' into feat/rag-2
This commit is contained in:
@ -13,12 +13,12 @@ import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
export default function EducationApply() {
|
||||
const router = useRouter()
|
||||
const { enableEducationPlan, isEducationAccount } = useProviderContext()
|
||||
const { enableEducationPlan } = useProviderContext()
|
||||
const searchParams = useSearchParams()
|
||||
const token = searchParams.get('token')
|
||||
const showEducationApplyPage = useMemo(() => {
|
||||
return enableEducationPlan && !isEducationAccount && token
|
||||
}, [enableEducationPlan, isEducationAccount, token])
|
||||
return enableEducationPlan && token
|
||||
}, [enableEducationPlan, token])
|
||||
|
||||
useEffect(() => {
|
||||
if (!showEducationApplyPage)
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.5" d="M12.5674 1.56341C12.5532 1.43246 12.4469 1.33346 12.3203 1.33333C12.1938 1.3332 12.0873 1.43196 12.0728 1.56288C12.0053 2.1724 11.8316 2.59056 11.5593 2.87418C11.287 3.1578 10.8856 3.33881 10.3004 3.40911C10.1747 3.42421 10.08 3.53514 10.0801 3.66693C10.0802 3.79872 10.1752 3.90944 10.3009 3.92427C10.8762 3.99215 11.2868 4.17312 11.566 4.45869C11.8437 4.74271 12.0207 5.16027 12.0721 5.76368C12.0836 5.89756 12.1913 6.00015 12.3203 6C12.4494 5.99984 12.5569 5.897 12.568 5.7631C12.6174 5.16988 12.7943 4.74291 13.0737 4.45176C13.3533 4.1606 13.7632 3.9763 14.3326 3.92496C14.4612 3.91336 14.56 3.80136 14.5601 3.66696C14.5602 3.53255 14.4617 3.42032 14.3332 3.40842C13.7539 3.35482 13.3531 3.17038 13.0804 2.88113C12.8063 2.5903 12.6325 2.16262 12.5674 1.56341Z" fill="#155AEF"/>
|
||||
<path d="M8.15567 3.25831C8.11906 2.92157 7.84578 2.66702 7.52041 2.66667C7.19509 2.66633 6.92124 2.92029 6.88399 3.25695C6.71042 4.8243 6.2636 5.89953 5.56346 6.62885C4.86332 7.35814 3.83109 7.82361 2.32643 8.00441C2.00323 8.04321 1.75943 8.32847 1.75977 8.66734C1.7601 9.00627 2.00446 9.29094 2.32773 9.32907C3.80694 9.50361 4.86268 9.96901 5.58062 10.7033C6.29465 11.4337 6.74997 12.5073 6.88226 14.059C6.91164 14.4033 7.18869 14.6671 7.52047 14.6667C7.85231 14.6663 8.12879 14.4018 8.1574 14.0575C8.28412 12.5321 8.73909 11.4342 9.45781 10.6855C10.1766 9.93681 11.2305 9.46287 12.6949 9.33087C13.0255 9.30107 13.2794 9.01307 13.2798 8.66741C13.2801 8.32181 13.0269 8.03321 12.6964 8.00261C11.2068 7.86481 10.1761 7.39054 9.47497 6.64673C8.77001 5.89887 8.32322 4.79915 8.15567 3.25831Z" fill="#155AEF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,36 @@
|
||||
{
|
||||
"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": "path",
|
||||
"attributes": {
|
||||
"opacity": "0.5",
|
||||
"d": "M12.5674 1.56341C12.5532 1.43246 12.4469 1.33346 12.3203 1.33333C12.1938 1.3332 12.0873 1.43196 12.0728 1.56288C12.0053 2.1724 11.8316 2.59056 11.5593 2.87418C11.287 3.1578 10.8856 3.33881 10.3004 3.40911C10.1747 3.42421 10.08 3.53514 10.0801 3.66693C10.0802 3.79872 10.1752 3.90944 10.3009 3.92427C10.8762 3.99215 11.2868 4.17312 11.566 4.45869C11.8437 4.74271 12.0207 5.16027 12.0721 5.76368C12.0836 5.89756 12.1913 6.00015 12.3203 6C12.4494 5.99984 12.5569 5.897 12.568 5.7631C12.6174 5.16988 12.7943 4.74291 13.0737 4.45176C13.3533 4.1606 13.7632 3.9763 14.3326 3.92496C14.4612 3.91336 14.56 3.80136 14.5601 3.66696C14.5602 3.53255 14.4617 3.42032 14.3332 3.40842C13.7539 3.35482 13.3531 3.17038 13.0804 2.88113C12.8063 2.5903 12.6325 2.16262 12.5674 1.56341Z",
|
||||
"fill": "#155AEF"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M8.15567 3.25831C8.11906 2.92157 7.84578 2.66702 7.52041 2.66667C7.19509 2.66633 6.92124 2.92029 6.88399 3.25695C6.71042 4.8243 6.2636 5.89953 5.56346 6.62885C4.86332 7.35814 3.83109 7.82361 2.32643 8.00441C2.00323 8.04321 1.75943 8.32847 1.75977 8.66734C1.7601 9.00627 2.00446 9.29094 2.32773 9.32907C3.80694 9.50361 4.86268 9.96901 5.58062 10.7033C6.29465 11.4337 6.74997 12.5073 6.88226 14.059C6.91164 14.4033 7.18869 14.6671 7.52047 14.6667C7.85231 14.6663 8.12879 14.4018 8.1574 14.0575C8.28412 12.5321 8.73909 11.4342 9.45781 10.6855C10.1766 9.93681 11.2305 9.46287 12.6949 9.33087C13.0255 9.30107 13.2794 9.01307 13.2798 8.66741C13.2801 8.32181 13.0269 8.03321 12.6964 8.00261C11.2068 7.86481 10.1761 7.39054 9.47497 6.64673C8.77001 5.89887 8.32322 4.79915 8.15567 3.25831Z",
|
||||
"fill": "#155AEF"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "SparklesSoftAccent"
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './SparklesSoftAccent.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'SparklesSoftAccent'
|
||||
|
||||
export default Icon
|
||||
@ -12,4 +12,5 @@ export { default as MultiPathRetrieval } from './MultiPathRetrieval'
|
||||
export { default as NTo1Retrieval } from './NTo1Retrieval'
|
||||
export { default as Notion } from './Notion'
|
||||
export { default as Soc2 } from './Soc2'
|
||||
export { default as SparklesSoftAccent } from './SparklesSoftAccent'
|
||||
export { default as SparklesSoft } from './SparklesSoft'
|
||||
|
||||
@ -33,7 +33,8 @@ const PlanComp: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const { userProfile } = useAppContext()
|
||||
const { plan, enableEducationPlan, isEducationAccount } = useProviderContext()
|
||||
const { plan, enableEducationPlan, allowRefreshEducationVerify, isEducationAccount } = useProviderContext()
|
||||
const isAboutToExpire = allowRefreshEducationVerify
|
||||
const {
|
||||
type,
|
||||
} = plan
|
||||
@ -79,7 +80,7 @@ const PlanComp: FC<Props> = ({
|
||||
<div className='system-xs-regular text-util-colors-gray-gray-600'>{t(`billing.plans.${type}.for`)}</div>
|
||||
</div>
|
||||
<div className='flex shrink-0 items-center gap-1'>
|
||||
{enableEducationPlan && !isEducationAccount && (
|
||||
{enableEducationPlan && (!isEducationAccount || isAboutToExpire) && (
|
||||
<Button variant='ghost' onClick={handleVerify}>
|
||||
<RiGraduationCapLine className='mr-1 h-4 w-4' />
|
||||
{t('education.toVerified')}
|
||||
|
||||
@ -59,6 +59,11 @@ export default function AppSelector() {
|
||||
localStorage.removeItem('console_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
|
||||
// To avoid use other account's education notice info
|
||||
localStorage.removeItem('education-reverify-prev-expire-at')
|
||||
localStorage.removeItem('education-reverify-has-noticed')
|
||||
localStorage.removeItem('education-expired-has-noticed')
|
||||
|
||||
router.push('/signin')
|
||||
}
|
||||
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export const EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION = 'getEducationVerify'
|
||||
export const EDUCATION_VERIFYING_LOCALSTORAGE_ITEM = 'educationVerifying'
|
||||
export const EDUCATION_PRICING_SHOW_ACTION = 'educationPricing'
|
||||
export const EDUCATION_RE_VERIFY_ACTION = 'educationReVerify'
|
||||
|
||||
96
web/app/education-apply/expire-notice-modal.tsx
Normal file
96
web/app/education-apply/expire-notice-modal.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiExternalLinkLine } from '@remixicon/react'
|
||||
import { SparklesSoftAccent } from '../components/base/icons/src/public/common'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useEducationVerify } from '@/service/use-education'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export type ExpireNoticeModalPayloadProps = {
|
||||
expireAt: number
|
||||
expired: boolean
|
||||
}
|
||||
export type Props = {
|
||||
onClose: () => void
|
||||
} & ExpireNoticeModalPayloadProps
|
||||
|
||||
const i18nPrefix = 'education.notice'
|
||||
|
||||
const ExpireNoticeModal: React.FC<Props> = ({ expireAt, expired, onClose }) => {
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
const eduDocLink = docLink('/getting-started/dify-for-education')
|
||||
const { formatTime } = useTimestamp()
|
||||
const setShowPricingModal = useModalContextSelector(s => s.setShowPricingModal)
|
||||
const { mutateAsync } = useEducationVerify()
|
||||
const router = useRouter()
|
||||
const handleVerify = async () => {
|
||||
const { token } = await mutateAsync()
|
||||
if (token)
|
||||
router.push(`/education-apply?token=${token}`)
|
||||
}
|
||||
const handleConfirm = async () => {
|
||||
await handleVerify()
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={onClose}
|
||||
title={expired ? t(`${i18nPrefix}.expired.title`) : t(`${i18nPrefix}.isAboutToExpire.title`, { date: formatTime(expireAt, t(`${i18nPrefix}.dateFormat`) as string), interpolation: { escapeValue: false } })}
|
||||
closable
|
||||
className='max-w-[600px]'
|
||||
>
|
||||
<div className='body-md-regular mt-5 space-y-5 text-text-secondary'>
|
||||
<div>
|
||||
{expired ? (<>
|
||||
<div>{t(`${i18nPrefix}.expired.summary.line1`)}</div>
|
||||
<div>{t(`${i18nPrefix}.expired.summary.line2`)}</div>
|
||||
</>
|
||||
) : t(`${i18nPrefix}.isAboutToExpire.summary`)}
|
||||
</div>
|
||||
<div>
|
||||
<strong className='title-md-semi-bold block'>{t(`${i18nPrefix}.stillInEducation.title`)}</strong>
|
||||
{t(`${i18nPrefix}.stillInEducation.${expired ? 'expired' : 'isAboutToExpire'}`)}
|
||||
</div>
|
||||
<div>
|
||||
<strong className='title-md-semi-bold block'>{t(`${i18nPrefix}.alreadyGraduated.title`)}</strong>
|
||||
{t(`${i18nPrefix}.alreadyGraduated.${expired ? 'expired' : 'isAboutToExpire'}`)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-7 flex items-center justify-between space-x-2">
|
||||
<Link className='system-xs-regular flex items-center space-x-1 text-text-accent' href={eduDocLink} target="_blank" rel="noopener noreferrer">
|
||||
<div>{t('education.learn')}</div>
|
||||
<RiExternalLinkLine className='size-3' />
|
||||
</Link>
|
||||
<div className='flex space-x-2'>
|
||||
{expired ? (
|
||||
<Button onClick={() => {
|
||||
onClose()
|
||||
setShowPricingModal()
|
||||
}} className='flex items-center space-x-1'>
|
||||
<SparklesSoftAccent className='size-4' />
|
||||
<div className='text-components-button-secondary-accent-text'>{t(`${i18nPrefix}.action.upgrade`)}</div>
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={onClose}>
|
||||
{t(`${i18nPrefix}.action.dismiss`)}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant='primary' onClick={handleConfirm}>
|
||||
{t(`${i18nPrefix}.action.reVerify`)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ExpireNoticeModal)
|
||||
@ -3,16 +3,26 @@ import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useDebounceFn, useLocalStorageState } from 'ahooks'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import type { SearchParams } from './types'
|
||||
import {
|
||||
EDUCATION_PRICING_SHOW_ACTION,
|
||||
EDUCATION_RE_VERIFY_ACTION,
|
||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
|
||||
} from './constants'
|
||||
import { useEducationAutocomplete } from '@/service/use-education'
|
||||
import { useEducationAutocomplete, useEducationVerify } from '@/service/use-education'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
export const useEducation = () => {
|
||||
const {
|
||||
mutateAsync,
|
||||
@ -50,12 +60,99 @@ export const useEducation = () => {
|
||||
}
|
||||
}
|
||||
|
||||
type useEducationReverifyNoticeParams = {
|
||||
onNotice: ({
|
||||
expireAt,
|
||||
expired,
|
||||
}: {
|
||||
expireAt: number
|
||||
expired: boolean
|
||||
}) => void
|
||||
}
|
||||
|
||||
const isExpired = (expireAt?: number, timezone?: string) => {
|
||||
if (!expireAt || !timezone)
|
||||
return false
|
||||
const today = dayjs().tz(timezone).startOf('day')
|
||||
const expiredDay = dayjs.unix(expireAt).tz(timezone).startOf('day')
|
||||
return today.isSame(expiredDay) || today.isAfter(expiredDay)
|
||||
}
|
||||
|
||||
const useEducationReverifyNotice = ({
|
||||
onNotice,
|
||||
}: useEducationReverifyNoticeParams) => {
|
||||
const { userProfile: { timezone } } = useAppContext()
|
||||
// const [educationInfo, setEducationInfo] = useState<{ is_student: boolean, allow_refresh: boolean, expire_at: number | null } | null>(null)
|
||||
// const isLoading = !educationInfo
|
||||
const { educationAccountExpireAt, allowRefreshEducationVerify, isLoadingEducationAccountInfo: isLoading } = useProviderContext()
|
||||
const [prevExpireAt, setPrevExpireAt] = useLocalStorageState<number | undefined>('education-reverify-prev-expire-at', {
|
||||
defaultValue: 0,
|
||||
})
|
||||
const [reverifyHasNoticed, setReverifyHasNoticed] = useLocalStorageState<boolean | undefined>('education-reverify-has-noticed', {
|
||||
defaultValue: false,
|
||||
})
|
||||
const [expiredHasNoticed, setExpiredHasNoticed] = useLocalStorageState<boolean | undefined>('education-expired-has-noticed', {
|
||||
defaultValue: false,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || !timezone)
|
||||
return
|
||||
if (allowRefreshEducationVerify) {
|
||||
const expired = isExpired(educationAccountExpireAt!, timezone)
|
||||
const isExpireAtChanged = prevExpireAt !== educationAccountExpireAt
|
||||
if (isExpireAtChanged) {
|
||||
setPrevExpireAt(educationAccountExpireAt!)
|
||||
setReverifyHasNoticed(false)
|
||||
setExpiredHasNoticed(false)
|
||||
}
|
||||
const shouldNotice = (() => {
|
||||
if (isExpireAtChanged)
|
||||
return true
|
||||
return expired ? !expiredHasNoticed : !reverifyHasNoticed
|
||||
})()
|
||||
if (shouldNotice) {
|
||||
onNotice({
|
||||
expireAt: educationAccountExpireAt!,
|
||||
expired,
|
||||
})
|
||||
if (expired)
|
||||
setExpiredHasNoticed(true)
|
||||
else
|
||||
setReverifyHasNoticed(true)
|
||||
}
|
||||
}
|
||||
}, [allowRefreshEducationVerify, timezone])
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
expireAt: educationAccountExpireAt!,
|
||||
expired: isExpired(educationAccountExpireAt!, timezone),
|
||||
}
|
||||
}
|
||||
|
||||
export const useEducationInit = () => {
|
||||
const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal)
|
||||
const setShowPricingModal = useModalContextSelector(s => s.setShowPricingModal)
|
||||
const setShowEducationExpireNoticeModal = useModalContextSelector(s => s.setShowEducationExpireNoticeModal)
|
||||
const educationVerifying = localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM)
|
||||
const searchParams = useSearchParams()
|
||||
const educationVerifyAction = searchParams.get('action')
|
||||
|
||||
useEducationReverifyNotice({
|
||||
onNotice: (payload) => {
|
||||
setShowEducationExpireNoticeModal({ payload })
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const { mutateAsync } = useEducationVerify()
|
||||
const handleVerify = async () => {
|
||||
const { token } = await mutateAsync()
|
||||
if (token)
|
||||
router.push(`/education-apply?token=${token}`)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (educationVerifying === 'yes' || educationVerifyAction === EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION) {
|
||||
setShowAccountSettingModal({ payload: 'billing' })
|
||||
@ -63,5 +160,9 @@ export const useEducationInit = () => {
|
||||
if (educationVerifyAction === EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION)
|
||||
localStorage.setItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, 'yes')
|
||||
}
|
||||
if (educationVerifyAction === EDUCATION_PRICING_SHOW_ACTION)
|
||||
setShowPricingModal()
|
||||
if (educationVerifyAction === EDUCATION_RE_VERIFY_ACTION)
|
||||
handleVerify()
|
||||
}, [setShowAccountSettingModal, educationVerifying, educationVerifyAction])
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export default function RoutePrefixHandle() {
|
||||
const addPrefixToImg = (e: HTMLImageElement) => {
|
||||
const url = new URL(e.src)
|
||||
const prefix = url.pathname.slice(0, basePath.length)
|
||||
if (prefix !== basePath && !url.href.startsWith('blob:') && !url.href.startsWith('data:')) {
|
||||
if (prefix !== basePath && !url.href.startsWith('blob:') && !url.href.startsWith('data:') && !url.href.startsWith('http')) {
|
||||
url.pathname = basePath + url.pathname
|
||||
e.src = url.toString()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user