Skip to content
This repository has been archived by the owner on Jul 28, 2024. It is now read-only.

Add monthly basic plan subscription #220

Open
wants to merge 40 commits into
base: chat-everywhere
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
533532c
Add 'basic' subscription plan to SubscriptionPlan type
1orZero Jan 19, 2024
22bf44e
Add UserPlan class for managing user subscription plans feature access
1orZero Jan 19, 2024
f4dd447
Rename UserPlanFeatures class for managing user plan features
1orZero Jan 19, 2024
3283a04
Add UserPlanFeatures to HomeInitialState and dispatch its value in Ho…
1orZero Jan 19, 2024
11a8dca
Refactor useLimiter to include user plan features
1orZero Jan 19, 2024
720238e
Refactor UserPlanFeatures class to simplify feature checks
1orZero Jan 19, 2024
d11b484
Add UserPlanFeatures and update error handling in ReferralCodeEnter
1orZero Jan 19, 2024
cea85bb
Update SpeechButton component and UserPlanFeatures class
1orZero Jan 19, 2024
819d600
Refactor TokenCounter.tsx and UserPlanFeatures.ts
1orZero Jan 19, 2024
ec0b418
Refactor ModeSelector component to use user plan features
1orZero Jan 19, 2024
615e239
Refactor user plan features in Home component
1orZero Jan 19, 2024
4aa0b4d
Refactor CloudSyncComponent to use userPlanFeatures for cloud sync en…
1orZero Jan 19, 2024
91941f3
Add basic plan constraint to profiles table
1orZero Jan 19, 2024
29f4c7a
Refactor UserAccountBadge component to add support for basic plan
1orZero Jan 19, 2024
084c8d7
Refactor user plan feature checks in App setting
1orZero Jan 19, 2024
906c1fd
Replace isUltra state
1orZero Jan 19, 2024
dd18b21
Remove unnecessary isPaidUser and isUltraUser properties from HomeIni…
1orZero Jan 19, 2024
d596d7a
Remove unnecessary code for setting isPaidUser to true
1orZero Jan 19, 2024
041dc83
Add user_conversations_owner_policy with plan restrictions
1orZero Jan 20, 2024
9d12d23
Refactor UserPlanFeatures class to update feature access based on pla…
1orZero Jan 20, 2024
60c791b
rename the userPlanAccess class to subscriptionPlan class
1orZero Jan 20, 2024
5846cba
Update PlanLevel import and move it to a separate file
1orZero Jan 20, 2024
4448e4c
Refactor UserAccountBadge to use getPlanLevel function
1orZero Jan 20, 2024
25739e4
Add getPlanLevel function and update isPaidUserByAuthToken function
1orZero Jan 20, 2024
7771da5
API access control for basic plan
1orZero Jan 20, 2024
a5f5f77
Add translations for basic plan in Chinese locales
1orZero Jan 20, 2024
aa91996
Update Pricing page
1orZero Jan 20, 2024
f55c574
Add monthly basic plan subscription code
1orZero Jan 20, 2024
22b270a
Update the automatic downgrade mechanism (Cron job)
1orZero Jan 22, 2024
fb01bda
Add support for upgrade to basic plan in stripe handler
1orZero Jan 22, 2024
f83aecd
Completely remove `customer.subscription.updated` event handler
1orZero Jan 22, 2024
2e384a6
Add cn.ts utility function
1orZero Jan 22, 2024
34e4aa4
Update plan content and payment links
1orZero Jan 22, 2024
ffb1bae
Update payment link for Basic plan
1orZero Jan 22, 2024
967ccfd
Add upgrade button for Basic plan users
1orZero Jan 22, 2024
a69d8eb
Add 16k model to model name
thejackwu Jan 18, 2024
14bf73a
Fix deployment name for gpt3.5
thejackwu Jan 18, 2024
6a08850
Add content filter detection
thejackwu Jan 18, 2024
ab4c78f
Fix conditional rendering in Settings_Account component
1orZero Jan 22, 2024
17d0eff
Merge branch 'chat-everywhere' into derek/20240118-implement-basic-plan
1orZero Jan 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ STRIPE_WEBHOOK_SECRET=KEY
# STRIPE PRODUCT PAYMENT LINK METADATA plan_code
STRIPE_PLAN_CODE_ONE_TIME_PRO_PLAN_FOR_1_MONTH=KEY
STRIPE_PLAN_CODE_MONTHLY_PRO_PLAN_SUBSCRIPTION=KEY
STRIPE_PLAN_CODE_MONTHLY_BASIC_PLAN_SUBSCRIPTION=KEY
STRIPE_PLAN_CODE_IMAGE_CREDIT=KEY
STRIPE_PLAN_CODE_GPT4_CREDIT=KEY

Expand Down
9 changes: 6 additions & 3 deletions components/Chat/CreditCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useContext } from 'react';

import { useTranslation } from 'next-i18next';

import { PlanLevel } from '@/utils/app/planLevel';

import { PluginID } from '@/types/plugin';

import HomeContext from '@/pages/api/home/home.context';
Expand All @@ -14,18 +16,19 @@ export const CreditCounter: React.FC<Props> = ({ pluginId }) => {
const { t } = useTranslation('chat');

const {
state: { creditUsage, isUltraUser },
state: { creditUsage, subscriptionPlan },
dispatch: homeDispatch,
} = useContext(HomeContext);

if (
creditUsage === null ||
(pluginId !== PluginID.GPT4 && pluginId !== PluginID.IMAGE_GEN) ||
isUltraUser
subscriptionPlan.planLevel === PlanLevel.Ultra
)
return <></>;

const remainingCredits = pluginId && creditUsage[pluginId].remainingCredits || 0;
const remainingCredits =
(pluginId && creditUsage[pluginId].remainingCredits) || 0;

return (
<div
Expand Down
13 changes: 10 additions & 3 deletions components/Chat/SpeechButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ export const SpeechButton: React.FC<Props> = ({ inputText }) => {
const { logGeneralEvent } = useLogger();

const {
state: { currentSpeechId, isLoading, isPlaying, user, messageIsStreaming, isPaidUser },
state: {
currentSpeechId,
isLoading,
isPlaying,
user,
messageIsStreaming,
subscriptionPlan,
},
playMessage,
stopPlaying,
} = useContext(HomeContext);
Expand All @@ -50,7 +57,7 @@ export const SpeechButton: React.FC<Props> = ({ inputText }) => {
const getPlayerIcon = () => {
if (isComponentCurrentlyBeingPlayed) {
if (isLoading) {
return <IconLoader fill="none" size={18} className='animate-spin' />;
return <IconLoader fill="none" size={18} className="animate-spin" />;
} else if (isPlaying) {
return (
<IconPlayerStop onClick={playStopOnClick} fill="none" size={18} />
Expand All @@ -64,7 +71,7 @@ export const SpeechButton: React.FC<Props> = ({ inputText }) => {
};

// Only enable for paying users
if (!isPaidUser || messageIsStreaming) return <></>;
if (!subscriptionPlan.canUseSpeechBtn() || messageIsStreaming) return <></>;

return (
<div className={`cursor-pointer text-gray-500 hover:text-gray-300`}>
Expand Down
6 changes: 3 additions & 3 deletions components/Chat/components/TokenCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function TokenCounter({
const debouncedValue = useDebounce<string>(value, 500);

const {
state: { currentMessage, isPaidUser },
state: { currentMessage, subscriptionPlan },
} = useContext(HomeContext);

const modelMaxTokenLength = useMemo(() => {
Expand All @@ -54,12 +54,12 @@ function TokenCounter({
);
default:
// Only enable 16k model for pro users
const defaultModel = isPaidUser
const defaultModel = subscriptionPlan.canUseGPT3_5_16KModel()
? OpenAIModels[OpenAIModelID.GPT_3_5_16K]
: OpenAIModels[OpenAIModelID.GPT_3_5];
return defaultModel.tokenLimit - defaultModel.completionTokenLimit;
}
}, [currentMessage?.pluginId, isPaidUser]);
}, [currentMessage?.pluginId, subscriptionPlan]);

const maxToken = useMemo(() => {
return modelMaxTokenLength - promptTokensLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from 'next-i18next';
import { event } from 'nextjs-google-analytics';

import { trackEvent } from '@/utils/app/eventTracking';
import { PlanLevel } from '@/utils/app/planLevel';
import { FeatureItem, PlanDetail } from '@/utils/app/ui';

import HomeContext from '@/pages/api/home/home.context';
Expand All @@ -22,7 +23,7 @@ export const NewConversationMessagesContainer: FC<Props> = ({
const { t } = useTranslation('chat');
const { t: modelTranslate } = useTranslation('model');
const {
state: { user, isUltraUser },
state: { user, subscriptionPlan },
dispatch,
} = useContext(HomeContext);

Expand Down Expand Up @@ -77,12 +78,12 @@ export const NewConversationMessagesContainer: FC<Props> = ({
target="_blank"
rel="noopener noreferrer"
className={`font-semibold font-serif underline select-none ${
isUltraUser
subscriptionPlan.planLevel === PlanLevel.Ultra
? 'bg-gradient-to-r text-white from-[#fd68a6] to-[#6c62f7] rounded bg-gray-700 mr-0 pr-[3px] pb-[3px] dark:from-[#fd68a6] dark:to-[#6c62f7] dark:text-[#343541]'
: ''
}`}
style={
isUltraUser
subscriptionPlan.planLevel === PlanLevel.Ultra
? {
WebkitBackgroundClip: 'text',
WebkitTextStrokeWidth: '3px',
Expand All @@ -92,7 +93,8 @@ export const NewConversationMessagesContainer: FC<Props> = ({
: {}
}
>
Chat Everywhere {isUltraUser ? 'Ultra' : ''}
Chat Everywhere{' '}
{subscriptionPlan.planLevel === PlanLevel.Ultra ? 'Ultra' : ''}
</a>

{/* Ask for support banner */}
Expand Down
55 changes: 30 additions & 25 deletions components/EnhancedMenu/ModeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const ModeSelector = () => {
const { t } = useTranslation('model');

const {
state: { currentMessage, isPaidUser, hasMqttConnection },
state: { currentMessage, subscriptionPlan, hasMqttConnection },
dispatch: homeDispatch,
} = useContext(HomeContext);

Expand Down Expand Up @@ -44,7 +44,10 @@ const ModeSelector = () => {
placeholder={t('Select a lang') || ''}
value={currentSelectedPluginId}
onChange={(e) => {
if (e.target.value === PluginID.LANGCHAIN_CHAT && !isPaidUser) {
if (
e.target.value === PluginID.LANGCHAIN_CHAT &&
!subscriptionPlan.canUseOnlineMode()
) {
alert(
t(
'Sorry online mode is only for Pro user, please sign up and purchase Pro plan to use this feature.',
Expand All @@ -69,29 +72,31 @@ const ModeSelector = () => {
>
{t('Online mode')}
</option>
{isPaidUser && (
<>
<option
value={PluginID.GPT4}
className="dark:bg-[#343541] dark:text-white text-yellow-600"
>
{t('GPT-4')}
</option>
<option
value={PluginID.IMAGE_GEN}
className="dark:bg-[#343541] dark:text-white text-yellow-600"
>
{t('AI Image')}
</option>
{hasMqttConnection && (
<option
value={PluginID.mqtt}
className="dark:bg-[#343541] dark:text-white text-yellow-600"
>
{t('MQTT')}
</option>
)}
</>
{subscriptionPlan.canUseGPT4_Model() && (
<option
value={PluginID.GPT4}
className="dark:bg-[#343541] dark:text-white text-yellow-600"
>
{t('GPT-4')}
</option>
)}
{subscriptionPlan.canUseAiImage() && (
<option
value={PluginID.IMAGE_GEN}
disabled
className="dark:bg-[#343541] dark:text-white text-yellow-600"
>
{t('AI Image')}
</option>
)}

{subscriptionPlan.canUseMQTT() && hasMqttConnection && (
<option
value={PluginID.mqtt}
className="dark:bg-[#343541] dark:text-white text-yellow-600"
>
{t('MQTT')}
</option>
)}
</select>
</div>
Expand Down
4 changes: 2 additions & 2 deletions components/Sidebar/components/CloudSyncComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ const CloudSyncComponent = () => {
const {
state: {
user,
isPaidUser,
subscriptionPlan,
syncingConversation,
syncSuccess,
conversationLastSyncAt,
},
dispatch: dispatch,
} = useContext(HomeContext);

const isCloudSyncEnabled = user && isPaidUser;
const isCloudSyncEnabled = user && subscriptionPlan.canUseCloudSync();

const CloudSyncStatusComponent = () => {
if (syncingConversation) {
Expand Down
7 changes: 4 additions & 3 deletions components/User/ReferralCodeEnter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';

import SubscriptionPlan from '@/utils/app/SubscriptionPlan';
import { trackEvent } from '@/utils/app/eventTracking';
import { userProfileQuery } from '@/utils/server/supabase';

Expand Down Expand Up @@ -33,7 +34,7 @@ export const ReferralCodeEnter = () => {
} = useQuery<{ profile: UserProfile }, Error>(
'redeemReferralCode',
async () => {
if(user === null) throw new Error('User is not logged in');
if (user === null) throw new Error('User is not logged in');

const response = await fetch('/api/referral/redeem-code', {
method: 'POST',
Expand Down Expand Up @@ -81,8 +82,8 @@ export const ReferralCodeEnter = () => {
});

dispatch({
field: 'isPaidUser',
value: true,
field: 'subscriptionPlan',
value: new SubscriptionPlan(profile.plan),
});

toast.success(t('Referral code has been redeemed'));
Expand Down
9 changes: 5 additions & 4 deletions components/User/Settings/LineConnectionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { useSession } from '@supabase/auth-helpers-react';
import React, { useContext, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { getHomeUrl } from '@/utils/app/api';

import Image from 'next/image';

import { getHomeUrl } from '@/utils/app/api';

import HomeContext from '@/pages/api/home/home.context';

export const LineConnectionButton = () => {
const {
state: { user, isPaidUser, isConnectedWithLine },
state: { user, subscriptionPlan, isConnectedWithLine },
dispatch,
} = useContext(HomeContext);
const { t } = useTranslation('model');
Expand Down Expand Up @@ -73,10 +74,10 @@ export const LineConnectionButton = () => {
) : (
<button
className={`border border-neutral-600 hover:bg-gray-200 text-gray-800 py-2 px-4 rounded-md text-sm dark:text-gray-100 dark:hover:bg-transparent ${
!user || (!isPaidUser && '!text-gray-400')
!user || (!subscriptionPlan.canUseLineConnect() && '!text-gray-400')
}`}
onClick={lineConnectOnClick}
disabled={!user || !isPaidUser}
disabled={!user || !subscriptionPlan.canUseLineConnect()}
>
{t('Connect with LINE')}
</button>
Expand Down
6 changes: 3 additions & 3 deletions components/User/Settings/SettingsModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import HomeContext from '@/pages/api/home/home.context';
import Settings_Account from './Settings_Account';
import Settings_App from './Settings_App';
import Settings_Data from './Settings_Data';
import Sidebar from './Sidebar';
import Settings_MQTT from './Settings_MQTT';
import Sidebar from './Sidebar';

type Props = {
onClose: () => void;
Expand All @@ -35,7 +35,7 @@ export default function SettingsModel({ onClose }: Props) {
state: { showing },
} = settingsContext;
const {
state: { user, isPaidUser },
state: { user, subscriptionPlan },
} = useContext(HomeContext);

return (
Expand Down Expand Up @@ -70,7 +70,7 @@ export default function SettingsModel({ onClose }: Props) {
<Dialog.Panel className="w-full max-w-[70vw] xl:max-w-3xl tablet:max-w-[90vw] h-[calc(80vh-100px)] transform overflow-hidden rounded-2xl text-left align-middle shadow-xl transition-all bg-neutral-800 text-neutral-200 flex mobile:h-[100dvh] max-h-[750px] tablet:max-h-[unset] mobile:!max-w-[unset] mobile:!rounded-none">
<Sidebar
className="bg-neutral-800 flex-shrink-0 flex-grow-0"
disableFooterItems={!user || !isPaidUser}
disableFooterItems={!user || !subscriptionPlan.isPaidUser()}
/>
<div className="p-6 bg-neutral-900 flex-grow relative overflow-y-auto">
{showing === 'account' && <Settings_Account />}
Expand Down
Loading