ai-paywall-notification.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import Notification from '@/shared/components/notification'
  2. import UpgradeButton from '@/features/ide-react/components/toolbar/upgrade-button'
  3. import { useEditorContext } from '@/shared/context/editor-context'
  4. import { useUserFeaturesContext } from '@/shared/context/user-features-context'
  5. import { useTranslation } from 'react-i18next'
  6. import { formatSecondsToHoursAndMinutes } from '@/shared/utils/time'
  7. import { useFeatureFlag } from '@/shared/context/split-test-context'
  8. import getMeta from '@/utils/meta'
  9. const onAiFreeTrial = getMeta('ol-onAiFreeTrial')
  10. type aiFeatureLocations = 'errorAssist' | 'workbench'
  11. function AiPaywallNotification({
  12. isActionBelowContent = false,
  13. featureLocation,
  14. }: {
  15. isActionBelowContent?: boolean
  16. featureLocation: aiFeatureLocations
  17. }) {
  18. const {
  19. hasSuggestionsLeft,
  20. hasTokensLeft,
  21. tokenResetDate,
  22. premiumSuggestionResetDate,
  23. } = useEditorContext()
  24. const { t } = useTranslation()
  25. const features = useUserFeaturesContext()
  26. const inQuotaRollout = useFeatureFlag('plans-2026-phase-1')
  27. if (!getMeta('ol-showAiFeatures')) {
  28. return null
  29. }
  30. // todo: quota clean-up: remove once we are transitioned off aiErrorAssistant naming and replace with just !onAiFreeTrial, also remove null FF check
  31. const hasAddOn = !onAiFreeTrial || Boolean(features?.aiErrorAssistant)
  32. // error assist only needs usage quota
  33. const canUseErrorAssist = hasSuggestionsLeft
  34. if (canUseErrorAssist && featureLocation === 'errorAssist') {
  35. return null
  36. }
  37. // workbench needs both tokens and usage quota
  38. const canUseWorkbench = hasSuggestionsLeft && hasTokensLeft
  39. if (canUseWorkbench && featureLocation === 'workbench') {
  40. return null
  41. }
  42. const exceededQuotaDates = [
  43. ...(hasSuggestionsLeft ? [] : [premiumSuggestionResetDate]),
  44. ...(hasTokensLeft ? [] : [tokenResetDate]),
  45. ]
  46. const longestResetDate = exceededQuotaDates.reduce((latest, date) =>
  47. date > latest ? date : latest
  48. )
  49. const secondsTillReset =
  50. (longestResetDate.getTime() - new Date().getTime()) / 1000
  51. // if we should have refreshed already remove paywall
  52. if (secondsTillReset <= 0) {
  53. return null
  54. }
  55. // if not in rollout, use old paywalls for free users
  56. if (!inQuotaRollout && !hasAddOn) {
  57. return null
  58. }
  59. if (!inQuotaRollout) {
  60. // if not in rollout, we can still use our fair usage messages
  61. const chattingMessage = t(
  62. 'youve_reached_the_fair_usage_limit_on_your_plan_you_can_start_chatting_again_in_time',
  63. {
  64. time: formatSecondsToHoursAndMinutes(t, secondsTillReset),
  65. }
  66. )
  67. const assistMessage = t('youve_reached_the_ai_fair_usage')
  68. return (
  69. <Notification
  70. type="info"
  71. title={t('usage_limit_reached')}
  72. content={
  73. featureLocation === 'workbench' ? chattingMessage : assistMessage
  74. }
  75. isDismissible={false}
  76. />
  77. )
  78. }
  79. if (hasAddOn) {
  80. return (
  81. <FairUseLimit
  82. secondsTillReset={secondsTillReset}
  83. featureLocation={featureLocation}
  84. />
  85. )
  86. }
  87. const message = t('upgrade_for_unlimited_access_to_ai', {
  88. time: formatSecondsToHoursAndMinutes(t, secondsTillReset),
  89. })
  90. return (
  91. <>
  92. <Notification
  93. type="info"
  94. title={t('youve_hit_your_overleaf_ai_limit')}
  95. content={message}
  96. isDismissible={false}
  97. customIcon={null}
  98. isActionBelowContent={isActionBelowContent}
  99. action={
  100. <UpgradeButton
  101. className="px-2.5 py-2"
  102. referrer="ai"
  103. source={featureLocation}
  104. />
  105. }
  106. className="ai-upgrade-paywall-btn"
  107. />
  108. </>
  109. )
  110. }
  111. function FairUseLimit({
  112. secondsTillReset,
  113. featureLocation,
  114. }: {
  115. secondsTillReset: number
  116. featureLocation: aiFeatureLocations
  117. }) {
  118. const { t } = useTranslation()
  119. const title =
  120. featureLocation === 'workbench'
  121. ? t('usage_limit_reached')
  122. : t('youve_reached_the_ai_fair_usage')
  123. const workbenchMessage = t(
  124. 'youve_reached_the_fair_usage_limit_on_your_plan_you_can_start_chatting_again_in_time',
  125. {
  126. time: formatSecondsToHoursAndMinutes(t, secondsTillReset),
  127. }
  128. )
  129. const assistMessage = t('this_will_reset_in', {
  130. time: formatSecondsToHoursAndMinutes(t, secondsTillReset),
  131. })
  132. const message =
  133. featureLocation === 'workbench' ? workbenchMessage : assistMessage
  134. return (
  135. <>
  136. <Notification
  137. type="info"
  138. title={title}
  139. content={message}
  140. isDismissible={false}
  141. />
  142. </>
  143. )
  144. }
  145. export default AiPaywallNotification