mirror of
https://github.com/coaidev/coai.git
synced 2025-05-23 23:10:13 +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",
|
||||
dalle: "#e4e5e5",
|
||||
"dall-e-2": "#e4e5e5",
|
||||
"dall-e-3": "#e4e5e5",
|
||||
|
||||
midjourney: "#7300ff",
|
||||
"midjourney-fast": "#7300ff",
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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";
|
||||
|
@ -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 = [
|
||||
|
@ -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: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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")}
|
||||
|
@ -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 запросов в день",
|
||||
|
@ -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
|
||||
|
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))
|
||||
}
|
||||
|
||||
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))),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user