update i18n source and subscription usage

This commit is contained in:
Zhang Minghan 2023-11-15 22:50:08 +08:00
parent e7fae44268
commit 655f8c3137
10 changed files with 155 additions and 57 deletions

View File

@ -6,6 +6,7 @@ export const modelColorMapper: Record<string, string> = {
"gpt-3.5-turbo-1106": "#11ba2b", "gpt-3.5-turbo-1106": "#11ba2b",
dalle: "#e4e5e5", dalle: "#e4e5e5",
"dall-e-2": "#e4e5e5", "dall-e-2": "#e4e5e5",
"dall-e-3": "#e4e5e5",
midjourney: "#7300ff", midjourney: "#7300ff",
"midjourney-fast": "#7300ff", "midjourney-fast": "#7300ff",

View File

@ -1,15 +1,40 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Input } from "@/components/ui/input.tsx"; import { Input } from "@/components/ui/input.tsx";
import {ChevronLeft, ChevronRight, Link, Plus, Search, Trash2, X} from "lucide-react"; import {
ChevronLeft,
ChevronRight,
Link,
Plus,
Search,
Trash2,
X,
} from "lucide-react";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import {modelAvatars, modelPricingLink, planModels, studentModels, supportModels} from "@/conf.ts"; import {
login,
modelAvatars,
modelPricingLink,
planModels,
studentModels,
supportModels,
} from "@/conf.ts";
import { splitList } from "@/utils/base.ts"; import { splitList } from "@/utils/base.ts";
import { Model } from "@/conversation/types.ts"; import { Model } from "@/conversation/types.ts";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import {addModelList, closeMarket, removeModelList, selectModel, selectModelList, setModel} from "@/store/chat.ts"; import {
addModelList,
closeMarket,
removeModelList,
selectModel,
selectModelList,
setModel,
} from "@/store/chat.ts";
import { Button } from "@/components/ui/button.tsx"; import { Button } from "@/components/ui/button.tsx";
import { isSubscribedSelector } from "@/store/subscription.ts"; import { isSubscribedSelector } from "@/store/subscription.ts";
import { teenagerSelector } from "@/store/package.ts"; import { teenagerSelector } from "@/store/package.ts";
import { ToastAction } from "@/components/ui/toast.tsx";
import { selectAuthenticated } from "@/store/auth.ts";
import { useToast } from "@/components/ui/use-toast.ts";
type SearchBarProps = { type SearchBarProps = {
value: string; value: string;
@ -46,11 +71,13 @@ type ModelProps = {
function ModelItem({ model, className, style }: ModelProps) { function ModelItem({ model, className, style }: ModelProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { toast } = useToast();
const list = useSelector(selectModelList); const list = useSelector(selectModelList);
const current = useSelector(selectModel); const current = useSelector(selectModel);
const subscription = useSelector(isSubscribedSelector); const subscription = useSelector(isSubscribedSelector);
const student = useSelector(teenagerSelector); const student = useSelector(teenagerSelector);
const auth = useSelector(selectAuthenticated);
const state = useMemo(() => { const state = useMemo(() => {
if (current === model.id) return 0; if (current === model.id) return 0;
@ -59,9 +86,10 @@ function ModelItem({ model, className, style }: ModelProps) {
}, [model, current, list]); }, [model, current, list]);
const pro = useMemo(() => { const pro = useMemo(() => {
if (subscription && planModels.includes(model.id)) return true; return (
if (student && studentModels.includes(model.id)) return true; (subscription && planModels.includes(model.id)) ||
return false; (student && studentModels.includes(model.id))
);
}, [model, subscription, student]); }, [model, subscription, student]);
const avatar = useMemo(() => { const avatar = useMemo(() => {
@ -70,14 +98,31 @@ function ModelItem({ model, className, style }: ModelProps) {
}, [model]); }, [model]);
return ( return (
<div className={`model-item ${className}`} style={style} onClick={() => { <div
className={`model-item ${className}`}
style={style}
onClick={() => {
dispatch(addModelList(model.id)); dispatch(addModelList(model.id));
if (!auth && model.auth) {
toast({
title: t("login-require"),
action: (
<ToastAction altText={t("login")} onClick={login}>
{t("login")}
</ToastAction>
),
});
return;
}
dispatch(setModel(model.id)); dispatch(setModel(model.id));
dispatch(closeMarket()); dispatch(closeMarket());
}}> }}
>
<img className={`model-avatar`} src={avatar} alt={model.name} /> <img className={`model-avatar`} src={avatar} alt={model.name} />
<div className={`model-info`}> <div className={`model-info`}>
<p className={`model-name ${pro ? 'pro' : ''}`}>{model.name}</p> <p className={`model-name ${pro ? "pro" : ""}`}>{model.name}</p>
<div className={`model-tag`}> <div className={`model-tag`}>
{model.tag && {model.tag &&
model.tag.map((tag, index) => { model.tag.map((tag, index) => {
@ -91,25 +136,26 @@ function ModelItem({ model, className, style }: ModelProps) {
</div> </div>
<div className={`grow`} /> <div className={`grow`} />
<div className={`model-action`}> <div className={`model-action`}>
<Button size={`icon`} variant={`ghost`} className={`scale-90`} onClick={(e) => { <Button
size={`icon`}
variant={`ghost`}
className={`scale-90`}
onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (state === 0) dispatch(closeMarket()); if (state === 0) dispatch(closeMarket());
else if (state === 1) dispatch(removeModelList(model.id)); else if (state === 1) dispatch(removeModelList(model.id));
else dispatch(addModelList(model.id)); else dispatch(addModelList(model.id));
}}> }}
{ >
state === 0 ? ( {state === 0 ? (
<ChevronRight className={`h-4 w-4`} /> <ChevronRight className={`h-4 w-4`} />
) : ( ) : state === 1 ? (
state === 1 ? (
<Trash2 className={`w-4 h-4`} /> <Trash2 className={`w-4 h-4`} />
) : ( ) : (
<Plus className={`w-4 h-4`} /> <Plus className={`w-4 h-4`} />
) )}
)
}
</Button> </Button>
</div> </div>
</div> </div>
@ -163,16 +209,21 @@ function MarketHeader() {
return ( return (
<div className={`market-header`}> <div className={`market-header`}>
<Button size={`icon`} variant={`ghost`} className={`close-action`} onClick={() => { <Button
size={`icon`}
variant={`ghost`}
className={`close-action`}
onClick={() => {
dispatch(closeMarket()); dispatch(closeMarket());
}}> }}
>
<ChevronLeft className={`h-4 w-4`} /> <ChevronLeft className={`h-4 w-4`} />
</Button> </Button>
<p className={`title select-none text-center text-primary font-bold`}> <p className={`title select-none text-center text-primary font-bold`}>
{t("market.explore")} {t("market.explore")}
</p> </p>
</div> </div>
) );
} }
function MarketFooter() { function MarketFooter() {

View File

@ -36,7 +36,7 @@ export const supportModels: Model[] = [
id: "gpt-3.5-turbo-1106", id: "gpt-3.5-turbo-1106",
name: "GPT-3.5 1106", name: "GPT-3.5 1106",
free: true, free: true,
auth: false, auth: true,
tag: ["free", "official"], tag: ["free", "official"],
}, },
{ {
@ -324,6 +324,7 @@ export const planModels = [
"gpt-4-dalle", "gpt-4-dalle",
"claude-2", "claude-2",
"claude-2-100k", "claude-2-100k",
"midjourney-fast",
]; ];
export const expensiveModels = [ export const expensiveModels = [

View File

@ -16,10 +16,7 @@ type SubscriptionResponse = {
is_subscribed: boolean; is_subscribed: boolean;
expired: number; expired: number;
enterprise?: boolean; enterprise?: boolean;
usage: { usage: Record<string, number>;
gpt4: number;
claude100k: number;
};
}; };
type BuySubscriptionResponse = { type BuySubscriptionResponse = {
@ -67,7 +64,7 @@ export async function getSubscription(): Promise<SubscriptionResponse> {
status: false, status: false,
is_subscribed: false, is_subscribed: false,
expired: 0, expired: 0,
usage: { gpt4: 0, claude100k: 0 }, usage: {},
}; };
} }
return resp.data as SubscriptionResponse; return resp.data as SubscriptionResponse;
@ -77,7 +74,7 @@ export async function getSubscription(): Promise<SubscriptionResponse> {
status: false, status: false,
is_subscribed: false, is_subscribed: false,
expired: 0, expired: 0,
usage: { gpt4: 0, claude100k: 0 }, usage: {},
}; };
} }
} }

View File

@ -30,6 +30,7 @@ import {
FolderGit2, FolderGit2,
Globe, Globe,
Image, Image,
ImagePlus,
LifeBuoy, LifeBuoy,
MessageSquare, MessageSquare,
MessagesSquare, MessagesSquare,
@ -199,12 +200,20 @@ function Subscription() {
</div> </div>
{!enterprise && ( {!enterprise && (
<> <>
<div className={`sub-column`}>
<Image className={`h-4 w-4 mr-1`} />
Midjourney
<div className={`grow`} />
<div className={`sub-value`}>
<p>{usage?.midjourney || 0}</p> / <p> 10 </p>
</div>
</div>
<div className={`sub-column`}> <div className={`sub-column`}>
<Compass className={`h-4 w-4 mr-1`} /> <Compass className={`h-4 w-4 mr-1`} />
GPT-4 GPT-4
<div className={`grow`} /> <div className={`grow`} />
<div className={`sub-value`}> <div className={`sub-value`}>
<p>{usage?.gpt4}</p> / <p> 100 </p> <p>{usage?.gpt4 || 0}</p> / <p> 100 </p>
</div> </div>
</div> </div>
<div className={`sub-column`}> <div className={`sub-column`}>
@ -212,7 +221,7 @@ function Subscription() {
Claude 100k Claude 100k
<div className={`grow`} /> <div className={`grow`} />
<div className={`sub-value`}> <div className={`sub-value`}>
<p>{usage?.claude100k}</p> / <p> 100 </p> <p>{usage?.claude100k || 0}</p> / <p> 100 </p>
</div> </div>
</div> </div>
</> </>
@ -262,6 +271,10 @@ function Subscription() {
<BookText className={`h-4 w-4 mr-1`} /> <BookText className={`h-4 w-4 mr-1`} />
{t("sub.pro-claude")} {t("sub.pro-claude")}
</div> </div>
<div>
<ImagePlus className={`h-4 w-4 mr-1`} />
{t("sub.pro-mj")}
</div>
<div> <div>
<LifeBuoy className={`h-4 w-4 mr-1`} /> <LifeBuoy className={`h-4 w-4 mr-1`} />
{t("sub.pro-service")} {t("sub.pro-service")}

View File

@ -130,6 +130,7 @@ const resources = {
"free-web": "联网搜索功能", "free-web": "联网搜索功能",
"free-conversation": "对话存储记录", "free-conversation": "对话存储记录",
"free-api": "API 调用", "free-api": "API 调用",
"pro-mj": "Midjourney 每日免费绘图 10 次",
"pro-gpt4": "GPT-4 每日请求 100 次", "pro-gpt4": "GPT-4 每日请求 100 次",
"pro-gpt4-desc": "(包含 GPT 4 Turbo, GPT 4V, GPT 4 DALLE)", "pro-gpt4-desc": "(包含 GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
"pro-claude": "Claude 100k 每日请求 100 次", "pro-claude": "Claude 100k 每日请求 100 次",
@ -337,6 +338,25 @@ const resources = {
pricing: "See model pricing for more details", pricing: "See model pricing for more details",
true: "Yes", true: "Yes",
false: "No", false: "No",
tag: {
free: "Free",
official: "Official",
unstable: "Unstable",
web: "Web",
"high-quality": "High Quality",
"high-context": "High Context",
"high-price": "High Price",
"open-source": "Open Source",
"image-generation": "Image Generation",
"multi-modal": "Multi Modal",
fast: "Fast",
"english-model": "English Model",
},
market: {
model: "Explore more models",
explore: "Explore",
search: "Search model name or description",
},
conversation: { conversation: {
title: "Conversation", title: "Conversation",
empty: "Empty", empty: "Empty",
@ -423,6 +443,7 @@ const resources = {
"free-web": "web searching feature", "free-web": "web searching feature",
"free-conversation": "conversation storage", "free-conversation": "conversation storage",
"free-api": "API calls", "free-api": "API calls",
"pro-mj": "Midjourney 10 times free image generation per day",
"pro-gpt4": "GPT-4 100 requests per day", "pro-gpt4": "GPT-4 100 requests per day",
"pro-gpt4-desc": "(including GPT 4 Turbo, GPT 4V, GPT 4 DALLE)", "pro-gpt4-desc": "(including GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
"pro-claude": "Claude 100k 100 requests per day", "pro-claude": "Claude 100k 100 requests per day",
@ -641,6 +662,20 @@ const resources = {
"См. ценообразование моделей для получения дополнительной информации", "См. ценообразование моделей для получения дополнительной информации",
true: "Да", true: "Да",
false: "Нет", false: "Нет",
tag: {
free: "Бесплатно",
official: "Официальный",
unstable: "Нестабильный",
web: "Веб",
"high-quality": "Высокое качество",
"high-context": "Высокий контекст",
"high-price": "Высокая цена",
"open-source": "Открытый исходный код",
"image-generation": "Генерация изображений",
"multi-modal": "Мульти Модальный",
fast: "Быстрый",
"english-model": "Английская модель",
},
conversation: { conversation: {
title: "Разговор", title: "Разговор",
empty: "Пусто", empty: "Пусто",
@ -728,6 +763,7 @@ const resources = {
"free-web": "веб-поиск", "free-web": "веб-поиск",
"free-conversation": "хранение разговоров", "free-conversation": "хранение разговоров",
"free-api": "API вызовы", "free-api": "API вызовы",
"pro-mj": "Midjourney 10 раз бесплатно генерировать изображения в день",
"pro-gpt4": "GPT-4 100 запросов в день", "pro-gpt4": "GPT-4 100 запросов в день",
"pro-gpt4-desc": "(включая GPT 4 Turbo, GPT 4V, GPT 4 DALLE)", "pro-gpt4-desc": "(включая GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
"pro-claude": "Claude 100k 100 запросов в день", "pro-claude": "Claude 100k 100 запросов в день",

View File

@ -41,13 +41,14 @@ func HandleSubscriptionUsage(db *sql.DB, cache *redis.Client, user *User, model
if globals.IsGPT3TurboModel(model) { if globals.IsGPT3TurboModel(model) {
// independent channel for subscription users // independent channel for subscription users
return subscription return subscription
} } else if globals.IsGPT4NativeModel(model) {
if globals.IsGPT4NativeModel(model) {
return subscription && IncreaseSubscriptionUsage(cache, user, globals.GPT4, 100) return subscription && IncreaseSubscriptionUsage(cache, user, globals.GPT4, 100)
} else if globals.IsClaude100KModel(model) { } else if globals.IsClaude100KModel(model) {
if subscription || user.HasTeenagerPackage(db) { if subscription || user.HasTeenagerPackage(db) {
return IncreaseSubscriptionUsage(cache, user, globals.Claude2100k, 100) return IncreaseSubscriptionUsage(cache, user, globals.Claude2100k, 100)
} }
} else if model == globals.MidjourneyFast {
return subscription && IncreaseSubscriptionUsage(cache, user, globals.MidjourneyFast, 10)
} }
return false return false

View File

@ -204,15 +204,13 @@ func (u *User) GetSubscriptionExpiredDay(db *sql.DB) int {
return int(math.Round(stamp.Hours() / 24)) return int(math.Round(stamp.Hours() / 24))
} }
type Usage struct { type Usage map[string]int64
GPT4 int64 `json:"gpt4"`
Claude100k int64 `json:"claude100k"`
}
func (u *User) GetSubscriptionUsage(db *sql.DB, cache *redis.Client) Usage { func (u *User) GetSubscriptionUsage(db *sql.DB, cache *redis.Client) Usage {
return Usage{ return Usage{
GPT4: utils.MustInt(cache, globals.GetSubscriptionLimitFormat(globals.GPT4, u.GetID(db))), "gpt4": utils.MustInt(cache, globals.GetSubscriptionLimitFormat(globals.GPT4, u.GetID(db))),
Claude100k: utils.MustInt(cache, globals.GetSubscriptionLimitFormat(globals.Claude2100k, u.GetID(db))), "claude100k": utils.MustInt(cache, globals.GetSubscriptionLimitFormat(globals.Claude2100k, u.GetID(db))),
"midjourney": utils.MustInt(cache, globals.GetSubscriptionLimitFormat(globals.MidjourneyFast, u.GetID(db))),
} }
} }