chore: update gift code

This commit is contained in:
Zhang Minghan 2024-03-09 12:34:33 +08:00
parent 98690e093a
commit 6136a10725
7 changed files with 46 additions and 19 deletions

View File

@ -52,7 +52,7 @@ _🚀 **Next Generation AI One-Stop Solution**_
1. **丰富且美观的仪表盘**, 包含本日和当月入账信息, 订阅人数, 模型使用统计折线图, 饼状图分析, 收入统计, 用户类型统计, 模型使用统计, 请求次数和模型错误数量统计图表等
![仪表盘](/screenshot/admin.png)
2. **支持用户管理**, *用户列表*, *用户详情*, *管理操作* (*修改密码*, *修改邮箱*, *封禁 / 解封用户*, *设为管理员*, *点数变更*, *点数设置*, *订阅管理*, *订阅等级设置*, *释放订阅用量* 等操作)
3. **支持邀请码和兑换码管理** 支持管理操作, 支持批量生成和保存为文件
3. **支持礼品码和兑换码管理** 支持管理操作, 支持批量生成和保存为文件
4. **价格设定**, 支持模型价格设定 (_**次数计费**_, **_Token 弹性计费_**, _**不计费**_ 等类型), 支持同步上游 Chat Nio 站点的价格设定 (可选是否覆盖本站已有模型价格规则), 未设定价格模型检测 (如果非管理员将自动检测并停止使用模型进而防止金额损失)
![购买点数](/screenshot/shop.png)
![价格设定](/screenshot/charge.png)
@ -213,9 +213,11 @@ _🚀 **Next Generation AI One-Stop Solution**_
- 后端域名用于(且目前仅限于) Midjourney Proxy 服务的后端回调地址, 如无需使用 Midjourney Proxy 服务, 请忽略此设置。
7. **如何配置支付方式?**
- Chat Nio 开源版支持发卡模式, 设置系统设置中的购买链接为你的发卡地址即可。卡密可通过用户管理中兑换码管理中批量生成。
8. **邀请码和兑换码有什么区别?**
- 邀请码一种类型只能一个用户只能绑定一次, 发福利等方式可使用邀请码, 可在头像下拉菜单中的邀请码中兑换。
8. **礼品码和兑换码有什么区别?**
- 礼品码一种类型只能一个用户只能绑定一次, 而非 aff code, 发福利等方式可使用礼品码, 可在头像下拉菜单中的礼品码中兑换。
- 兑换码一种类型可以多个用户绑定, 可作为正常购买和发卡使用, 可在用户管理中的兑换码管理中批量生成, 在头像下拉菜单的点数(菜单第一个)内输入兑换码进行兑换。
- 一个例子:比如我发了一个类型为 *新年快乐* 的福利, 此时推荐使用礼品码, 假设发放 100 个 66 点数, 如果为兑换码, 手快的一个用户就批量把所有兑换码的 6600 点数都用完了, 而礼品码则可以保证每个用户只能使用一次 (获得 66 点数)。
- 而搭建发卡的时, 如果用礼品码, 因为一个类型只能兑换一次, 购买多个礼品码会导致兑换失败, 而兑换码则可以在此场景下使用。
9. **该项目支持 Vercel 部署吗?**
- Chat Nio 本身并不支持 Vercel 部署, 但是你可以使用前后端分离模式, Vercel 部署前端部分, 后端部分使用 Docker 部署或编译部署。
10. **前后端分离部署模式是什么?**

View File

@ -26,7 +26,7 @@ import { Plus } from "lucide-react";
import { ToastAction } from "@/components/ui/toast.tsx";
import { deeptrainEndpoint, useDeeptrain } from "@/conf/env.ts";
import { AppDispatch } from "@/store";
import { openDialog } from "@/store/quota.ts";
import { openDialog, quotaSelector } from "@/store/quota.ts";
import { getPlanPrice } from "@/conf/subscription.tsx";
import { Plans } from "@/api/types.tsx";
import { subscriptionDataSelector } from "@/store/globals.ts";
@ -52,7 +52,7 @@ function countUpgradePrice(
): number {
const bias = getPlanPrice(data, target) - getPlanPrice(data, level);
const v = (bias / 30) * days;
return v > 0 ? v + 1 : 0; // time count offset
return (v > 0 ? v + 1 : 0) + 1; // time count offset
}
type UpgradeProps = {
@ -66,6 +66,7 @@ async function callBuyAction(
dispatch: AppDispatch,
month: number,
level: number,
current: number,
): Promise<boolean> {
const res = await buySubscription(month, level);
if (res.status) {
@ -78,7 +79,11 @@ async function callBuyAction(
} else {
toast({
title: t("sub.failed"),
description: t("sub.failed-prompt"),
description: useDeeptrain
? t("sub.failed-prompt")
: t("sub.failed-quota-prompt", {
quota: current.toFixed(2),
}),
action: (
<ToastAction
altText={t("buy.go")}
@ -116,7 +121,7 @@ async function callMigrateAction(
} else {
toast({
title: t("sub.migrate-failed"),
description: t("sub.migrate-failed-prompt"),
description: t("sub.sub-migrate-failed-prompt", { reason: res.error }),
});
}
return res.status;
@ -130,6 +135,8 @@ export function Upgrade({ level, current }: UpgradeProps) {
const dispatch = useDispatch();
const { toast } = useToast();
const quota = useSelector(quotaSelector);
const subscriptionData = useSelector(subscriptionDataSelector);
const isCurrent = useMemo(() => current === level, [current, level]);
@ -199,7 +206,14 @@ export function Upgrade({ level, current }: UpgradeProps) {
<Button
className={`mb-1.5`}
onClick={async () => {
const res = await callBuyAction(t, toast, dispatch, month, level);
const res = await callBuyAction(
t,
toast,
dispatch,
month,
level,
quota,
);
if (res) {
setOpen(false);
await refreshSubscription(dispatch);

View File

@ -263,8 +263,9 @@
"migrate-success-prompt": "您已成功变更订阅计划。",
"failed": "订阅失败",
"failed-prompt": "订阅失败,请确保您有足够的余额。",
"failed-quota-prompt": "订阅失败,您的余额不足 ({{quota}} 点数)",
"migrate-failed": "变更失败",
"migrate-failed-prompt": "您的订阅变更失败"
"sub-migrate-failed-prompt": "您的订阅变更失败,原因:{{reason}}"
},
"cancel": "取消",
"confirm": "确认",
@ -340,8 +341,8 @@
},
"invitation": {
"title": "兑换码",
"invitation": "邀请码",
"input-placeholder": "请输入邀请码",
"invitation": "礼品码",
"input-placeholder": "请输入礼品码",
"cancel": "取消",
"check": "验证",
"check-success": "兑换成功",
@ -410,7 +411,7 @@
"model-chart-tip": "Token 用量",
"model-usage-chart": "模型使用占比",
"user-type-chart": "用户类型占比",
"user-type-chart-tip": "其他付费用户:指订阅过期用户或点数超过当前初始点数的用户(使用邀请码等操作也会被算作点数增加的变更)",
"user-type-chart-tip": "其他付费用户:指订阅过期用户或点数超过当前初始点数的用户(使用礼品码等操作也会被算作点数增加的变更)",
"user-type-chart-info": "总共 {{total}} 用户",
"request-chart": "请求量统计",
"billing-chart": "收入统计",
@ -422,9 +423,9 @@
"confirm": "确认",
"invitation": "兑换码管理",
"code": "兑换码",
"invitation-code": "邀请码",
"invitation-manage": "邀请码管理",
"invitation-tips": "邀请码用于兑换点数,每一类邀请码一个用户只能使用一次(可作宣传使用)",
"invitation-code": "礼品码",
"invitation-manage": "礼品码管理",
"invitation-tips": "礼品码用于兑换点数,每一类礼品码一个用户只能使用一次(可作宣传使用)",
"redeem-tips": "兑换码用于兑换点数,可用于支付发卡等",
"quota": "点数",
"type": "类型",

View File

@ -208,7 +208,9 @@
"plan-tip": "Callable Model",
"disable": "This site's subscription feature has been turned off",
"plan-unlimited-usage": "{{name}} has unlimited uses",
"plan-not-support-relay": "Site subscription quota does not cover staging API, please use flexible billing credits for staging API"
"plan-not-support-relay": "Site subscription quota does not cover staging API, please use flexible billing credits for staging API",
"failed-quota-prompt": "Subscription failed, your balance is insufficient ({{quota}} credits)",
"sub-migrate-failed-prompt": "Your subscription change failed for {{reason}}"
},
"cancel": "Cancel",
"confirm": "Confirm",

View File

@ -208,7 +208,9 @@
"plan-tip": "呼び出し可能なモデル",
"disable": "このサイトのサブスクリプション機能はオフになっています",
"plan-unlimited-usage": "{{name}}は無制限に使用できます",
"plan-not-support-relay": "サイトサブスクリプションクォータはステージングAPIをカバーしていません。ステージングAPIに柔軟な請求クレジットを使用してください"
"plan-not-support-relay": "サイトサブスクリプションクォータはステージングAPIをカバーしていません。ステージングAPIに柔軟な請求クレジットを使用してください",
"failed-quota-prompt": "サブスクリプションに失敗しました。残高が不足しています({{ quota}}クレジット)",
"sub-migrate-failed-prompt": "サブスクリプションの変更が{{reason}}で失敗しました"
},
"cancel": "キャンセル",
"confirm": "確認",

View File

@ -208,7 +208,9 @@
"plan-tip": "Вызываемая модель",
"disable": "Функция подписки на этом сайте отключена",
"plan-unlimited-usage": "{{name}} имеет неограниченное количество пользователей",
"plan-not-support-relay": "Квота подписки на сайт не распространяется на промежуточный API, пожалуйста, используйте гибкие биллинговые кредиты для промежуточного API"
"plan-not-support-relay": "Квота подписки на сайт не распространяется на промежуточный API, пожалуйста, используйте гибкие биллинговые кредиты для промежуточного API",
"failed-quota-prompt": "Не удалось оформить подписку, недостаточно средств ({{quota}} кредитов)",
"sub-migrate-failed-prompt": "Изменение подписки не выполнено по {{reason}}"
},
"cancel": "Отмена",
"confirm": "Подтвердить",

View File

@ -65,7 +65,11 @@ func ThrottleMiddleware() gin.HandlerFunc {
limiter := GetPrefixMap[Limiter](path, limits)
if limiter != nil && limiter.RateLimit(cache, ip, path) {
c.JSON(200, gin.H{"status": false, "reason": "You have sent too many requests. Please try again later."})
c.JSON(200, gin.H{
"status": false,
"reason": "You have sent too many requests. Please try again later.",
"error": "request_throttled",
})
c.Abort()
return
}