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",
dalle: "#e4e5e5",
"dall-e-2": "#e4e5e5",
"dall-e-3": "#e4e5e5",
midjourney: "#7300ff",
"midjourney-fast": "#7300ff",

View File

@ -13,7 +13,7 @@ import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import { ConversationInstance } from "@/conversation/types.ts";
import { useState } from "react";
import {closeMarket} from "@/store/chat.ts";
import { closeMarket } from "@/store/chat.ts";
type ConversationSegmentProps = {
conversation: ConversationInstance;

View File

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

View File

@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
import {closeMarket, selectCurrent, selectHistory} from "@/store/chat.ts";
import { closeMarket, selectCurrent, selectHistory } from "@/store/chat.ts";
import { useRef, useState } from "react";
import { ConversationInstance } from "@/conversation/types.ts";
import { useToast } from "@/components/ui/use-toast.ts";

View File

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

View File

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

View File

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

View File

@ -130,6 +130,7 @@ const resources = {
"free-web": "联网搜索功能",
"free-conversation": "对话存储记录",
"free-api": "API 调用",
"pro-mj": "Midjourney 每日免费绘图 10 次",
"pro-gpt4": "GPT-4 每日请求 100 次",
"pro-gpt4-desc": "(包含 GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
"pro-claude": "Claude 100k 每日请求 100 次",
@ -337,6 +338,25 @@ const resources = {
pricing: "See model pricing for more details",
true: "Yes",
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: {
title: "Conversation",
empty: "Empty",
@ -423,6 +443,7 @@ const resources = {
"free-web": "web searching feature",
"free-conversation": "conversation storage",
"free-api": "API calls",
"pro-mj": "Midjourney 10 times free image generation per day",
"pro-gpt4": "GPT-4 100 requests per day",
"pro-gpt4-desc": "(including GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
"pro-claude": "Claude 100k 100 requests per day",
@ -641,6 +662,20 @@ const resources = {
"См. ценообразование моделей для получения дополнительной информации",
true: "Да",
false: "Нет",
tag: {
free: "Бесплатно",
official: "Официальный",
unstable: "Нестабильный",
web: "Веб",
"high-quality": "Высокое качество",
"high-context": "Высокий контекст",
"high-price": "Высокая цена",
"open-source": "Открытый исходный код",
"image-generation": "Генерация изображений",
"multi-modal": "Мульти Модальный",
fast: "Быстрый",
"english-model": "Английская модель",
},
conversation: {
title: "Разговор",
empty: "Пусто",
@ -728,6 +763,7 @@ const resources = {
"free-web": "веб-поиск",
"free-conversation": "хранение разговоров",
"free-api": "API вызовы",
"pro-mj": "Midjourney 10 раз бесплатно генерировать изображения в день",
"pro-gpt4": "GPT-4 100 запросов в день",
"pro-gpt4-desc": "(включая GPT 4 Turbo, GPT 4V, GPT 4 DALLE)",
"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) {
// independent channel for subscription users
return subscription
}
if globals.IsGPT4NativeModel(model) {
} else if globals.IsGPT4NativeModel(model) {
return subscription && IncreaseSubscriptionUsage(cache, user, globals.GPT4, 100)
} else if globals.IsClaude100KModel(model) {
if subscription || user.HasTeenagerPackage(db) {
return IncreaseSubscriptionUsage(cache, user, globals.Claude2100k, 100)
}
} else if model == globals.MidjourneyFast {
return subscription && IncreaseSubscriptionUsage(cache, user, globals.MidjourneyFast, 10)
}
return false

View File

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