mirror of
https://github.com/coaidev/coai.git
synced 2025-05-25 16:00:15 +09:00
update i18n source and subscription usage
This commit is contained in:
parent
e7fae44268
commit
655f8c3137
@ -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",
|
||||||
|
@ -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() {
|
||||||
|
@ -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 = [
|
||||||
|
@ -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: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")}
|
||||||
|
@ -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 запросов в день",
|
||||||
|
@ -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
|
||||||
|
10
auth/user.go
10
auth/user.go
@ -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))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user