mirror of
https://github.com/coaidev/coai.git
synced 2025-06-05 13:20:15 +09:00
chore: update gift code
This commit is contained in:
parent
98690e093a
commit
6136a10725
@ -52,7 +52,7 @@ _🚀 **Next Generation AI One-Stop Solution**_
|
||||
1. **丰富且美观的仪表盘**, 包含本日和当月入账信息, 订阅人数, 模型使用统计折线图, 饼状图分析, 收入统计, 用户类型统计, 模型使用统计, 请求次数和模型错误数量统计图表等
|
||||

|
||||
2. **支持用户管理**, *用户列表*, *用户详情*, *管理操作* (*修改密码*, *修改邮箱*, *封禁 / 解封用户*, *设为管理员*, *点数变更*, *点数设置*, *订阅管理*, *订阅等级设置*, *释放订阅用量* 等操作)
|
||||
3. **支持邀请码和兑换码管理** 支持管理操作, 支持批量生成和保存为文件
|
||||
3. **支持礼品码和兑换码管理** 支持管理操作, 支持批量生成和保存为文件
|
||||
4. **价格设定**, 支持模型价格设定 (_**次数计费**_, **_Token 弹性计费_**, _**不计费**_ 等类型), 支持同步上游 Chat Nio 站点的价格设定 (可选是否覆盖本站已有模型价格规则), 未设定价格模型检测 (如果非管理员将自动检测并停止使用模型进而防止金额损失)
|
||||

|
||||

|
||||
@ -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. **前后端分离部署模式是什么?**
|
||||
|
@ -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);
|
||||
|
@ -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": "类型",
|
||||
|
@ -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",
|
||||
|
@ -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": "確認",
|
||||
|
@ -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": "Подтвердить",
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user