mirror of
https://github.com/coaidev/coai.git
synced 2025-06-07 06:10:22 +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. **丰富且美观的仪表盘**, 包含本日和当月入账信息, 订阅人数, 模型使用统计折线图, 饼状图分析, 收入统计, 用户类型统计, 模型使用统计, 请求次数和模型错误数量统计图表等
|
1. **丰富且美观的仪表盘**, 包含本日和当月入账信息, 订阅人数, 模型使用统计折线图, 饼状图分析, 收入统计, 用户类型统计, 模型使用统计, 请求次数和模型错误数量统计图表等
|
||||||

|

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

|

|
||||||

|

|
||||||
@ -213,9 +213,11 @@ _🚀 **Next Generation AI One-Stop Solution**_
|
|||||||
- 后端域名用于(且目前仅限于) Midjourney Proxy 服务的后端回调地址, 如无需使用 Midjourney Proxy 服务, 请忽略此设置。
|
- 后端域名用于(且目前仅限于) Midjourney Proxy 服务的后端回调地址, 如无需使用 Midjourney Proxy 服务, 请忽略此设置。
|
||||||
7. **如何配置支付方式?**
|
7. **如何配置支付方式?**
|
||||||
- Chat Nio 开源版支持发卡模式, 设置系统设置中的购买链接为你的发卡地址即可。卡密可通过用户管理中兑换码管理中批量生成。
|
- Chat Nio 开源版支持发卡模式, 设置系统设置中的购买链接为你的发卡地址即可。卡密可通过用户管理中兑换码管理中批量生成。
|
||||||
8. **邀请码和兑换码有什么区别?**
|
8. **礼品码和兑换码有什么区别?**
|
||||||
- 邀请码一种类型只能一个用户只能绑定一次, 发福利等方式可使用邀请码, 可在头像下拉菜单中的邀请码中兑换。
|
- 礼品码一种类型只能一个用户只能绑定一次, 而非 aff code, 发福利等方式可使用礼品码, 可在头像下拉菜单中的礼品码中兑换。
|
||||||
- 兑换码一种类型可以多个用户绑定, 可作为正常购买和发卡使用, 可在用户管理中的兑换码管理中批量生成, 在头像下拉菜单的点数(菜单第一个)内输入兑换码进行兑换。
|
- 兑换码一种类型可以多个用户绑定, 可作为正常购买和发卡使用, 可在用户管理中的兑换码管理中批量生成, 在头像下拉菜单的点数(菜单第一个)内输入兑换码进行兑换。
|
||||||
|
- 一个例子:比如我发了一个类型为 *新年快乐* 的福利, 此时推荐使用礼品码, 假设发放 100 个 66 点数, 如果为兑换码, 手快的一个用户就批量把所有兑换码的 6600 点数都用完了, 而礼品码则可以保证每个用户只能使用一次 (获得 66 点数)。
|
||||||
|
- 而搭建发卡的时, 如果用礼品码, 因为一个类型只能兑换一次, 购买多个礼品码会导致兑换失败, 而兑换码则可以在此场景下使用。
|
||||||
9. **该项目支持 Vercel 部署吗?**
|
9. **该项目支持 Vercel 部署吗?**
|
||||||
- Chat Nio 本身并不支持 Vercel 部署, 但是你可以使用前后端分离模式, Vercel 部署前端部分, 后端部分使用 Docker 部署或编译部署。
|
- Chat Nio 本身并不支持 Vercel 部署, 但是你可以使用前后端分离模式, Vercel 部署前端部分, 后端部分使用 Docker 部署或编译部署。
|
||||||
10. **前后端分离部署模式是什么?**
|
10. **前后端分离部署模式是什么?**
|
||||||
|
@ -26,7 +26,7 @@ import { Plus } from "lucide-react";
|
|||||||
import { ToastAction } from "@/components/ui/toast.tsx";
|
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||||
import { deeptrainEndpoint, useDeeptrain } from "@/conf/env.ts";
|
import { deeptrainEndpoint, useDeeptrain } from "@/conf/env.ts";
|
||||||
import { AppDispatch } from "@/store";
|
import { AppDispatch } from "@/store";
|
||||||
import { openDialog } from "@/store/quota.ts";
|
import { openDialog, quotaSelector } from "@/store/quota.ts";
|
||||||
import { getPlanPrice } from "@/conf/subscription.tsx";
|
import { getPlanPrice } from "@/conf/subscription.tsx";
|
||||||
import { Plans } from "@/api/types.tsx";
|
import { Plans } from "@/api/types.tsx";
|
||||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||||
@ -52,7 +52,7 @@ function countUpgradePrice(
|
|||||||
): number {
|
): number {
|
||||||
const bias = getPlanPrice(data, target) - getPlanPrice(data, level);
|
const bias = getPlanPrice(data, target) - getPlanPrice(data, level);
|
||||||
const v = (bias / 30) * days;
|
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 = {
|
type UpgradeProps = {
|
||||||
@ -66,6 +66,7 @@ async function callBuyAction(
|
|||||||
dispatch: AppDispatch,
|
dispatch: AppDispatch,
|
||||||
month: number,
|
month: number,
|
||||||
level: number,
|
level: number,
|
||||||
|
current: number,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const res = await buySubscription(month, level);
|
const res = await buySubscription(month, level);
|
||||||
if (res.status) {
|
if (res.status) {
|
||||||
@ -78,7 +79,11 @@ async function callBuyAction(
|
|||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: t("sub.failed"),
|
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: (
|
action: (
|
||||||
<ToastAction
|
<ToastAction
|
||||||
altText={t("buy.go")}
|
altText={t("buy.go")}
|
||||||
@ -116,7 +121,7 @@ async function callMigrateAction(
|
|||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: t("sub.migrate-failed"),
|
title: t("sub.migrate-failed"),
|
||||||
description: t("sub.migrate-failed-prompt"),
|
description: t("sub.sub-migrate-failed-prompt", { reason: res.error }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res.status;
|
return res.status;
|
||||||
@ -130,6 +135,8 @@ export function Upgrade({ level, current }: UpgradeProps) {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const quota = useSelector(quotaSelector);
|
||||||
|
|
||||||
const subscriptionData = useSelector(subscriptionDataSelector);
|
const subscriptionData = useSelector(subscriptionDataSelector);
|
||||||
|
|
||||||
const isCurrent = useMemo(() => current === level, [current, level]);
|
const isCurrent = useMemo(() => current === level, [current, level]);
|
||||||
@ -199,7 +206,14 @@ export function Upgrade({ level, current }: UpgradeProps) {
|
|||||||
<Button
|
<Button
|
||||||
className={`mb-1.5`}
|
className={`mb-1.5`}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const res = await callBuyAction(t, toast, dispatch, month, level);
|
const res = await callBuyAction(
|
||||||
|
t,
|
||||||
|
toast,
|
||||||
|
dispatch,
|
||||||
|
month,
|
||||||
|
level,
|
||||||
|
quota,
|
||||||
|
);
|
||||||
if (res) {
|
if (res) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
await refreshSubscription(dispatch);
|
await refreshSubscription(dispatch);
|
||||||
|
@ -263,8 +263,9 @@
|
|||||||
"migrate-success-prompt": "您已成功变更订阅计划。",
|
"migrate-success-prompt": "您已成功变更订阅计划。",
|
||||||
"failed": "订阅失败",
|
"failed": "订阅失败",
|
||||||
"failed-prompt": "订阅失败,请确保您有足够的余额。",
|
"failed-prompt": "订阅失败,请确保您有足够的余额。",
|
||||||
|
"failed-quota-prompt": "订阅失败,您的余额不足 ({{quota}} 点数)",
|
||||||
"migrate-failed": "变更失败",
|
"migrate-failed": "变更失败",
|
||||||
"migrate-failed-prompt": "您的订阅变更失败。"
|
"sub-migrate-failed-prompt": "您的订阅变更失败,原因:{{reason}}"
|
||||||
},
|
},
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"confirm": "确认",
|
"confirm": "确认",
|
||||||
@ -340,8 +341,8 @@
|
|||||||
},
|
},
|
||||||
"invitation": {
|
"invitation": {
|
||||||
"title": "兑换码",
|
"title": "兑换码",
|
||||||
"invitation": "邀请码",
|
"invitation": "礼品码",
|
||||||
"input-placeholder": "请输入邀请码",
|
"input-placeholder": "请输入礼品码",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"check": "验证",
|
"check": "验证",
|
||||||
"check-success": "兑换成功",
|
"check-success": "兑换成功",
|
||||||
@ -410,7 +411,7 @@
|
|||||||
"model-chart-tip": "Token 用量",
|
"model-chart-tip": "Token 用量",
|
||||||
"model-usage-chart": "模型使用占比",
|
"model-usage-chart": "模型使用占比",
|
||||||
"user-type-chart": "用户类型占比",
|
"user-type-chart": "用户类型占比",
|
||||||
"user-type-chart-tip": "其他付费用户:指订阅过期用户或点数超过当前初始点数的用户(使用邀请码等操作也会被算作点数增加的变更)",
|
"user-type-chart-tip": "其他付费用户:指订阅过期用户或点数超过当前初始点数的用户(使用礼品码等操作也会被算作点数增加的变更)",
|
||||||
"user-type-chart-info": "总共 {{total}} 用户",
|
"user-type-chart-info": "总共 {{total}} 用户",
|
||||||
"request-chart": "请求量统计",
|
"request-chart": "请求量统计",
|
||||||
"billing-chart": "收入统计",
|
"billing-chart": "收入统计",
|
||||||
@ -422,9 +423,9 @@
|
|||||||
"confirm": "确认",
|
"confirm": "确认",
|
||||||
"invitation": "兑换码管理",
|
"invitation": "兑换码管理",
|
||||||
"code": "兑换码",
|
"code": "兑换码",
|
||||||
"invitation-code": "邀请码",
|
"invitation-code": "礼品码",
|
||||||
"invitation-manage": "邀请码管理",
|
"invitation-manage": "礼品码管理",
|
||||||
"invitation-tips": "邀请码用于兑换点数,每一类邀请码一个用户只能使用一次(可作宣传使用)",
|
"invitation-tips": "礼品码用于兑换点数,每一类礼品码一个用户只能使用一次(可作宣传使用)",
|
||||||
"redeem-tips": "兑换码用于兑换点数,可用于支付发卡等",
|
"redeem-tips": "兑换码用于兑换点数,可用于支付发卡等",
|
||||||
"quota": "点数",
|
"quota": "点数",
|
||||||
"type": "类型",
|
"type": "类型",
|
||||||
|
@ -208,7 +208,9 @@
|
|||||||
"plan-tip": "Callable Model",
|
"plan-tip": "Callable Model",
|
||||||
"disable": "This site's subscription feature has been turned off",
|
"disable": "This site's subscription feature has been turned off",
|
||||||
"plan-unlimited-usage": "{{name}} has unlimited uses",
|
"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",
|
"cancel": "Cancel",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
@ -208,7 +208,9 @@
|
|||||||
"plan-tip": "呼び出し可能なモデル",
|
"plan-tip": "呼び出し可能なモデル",
|
||||||
"disable": "このサイトのサブスクリプション機能はオフになっています",
|
"disable": "このサイトのサブスクリプション機能はオフになっています",
|
||||||
"plan-unlimited-usage": "{{name}}は無制限に使用できます",
|
"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": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"confirm": "確認",
|
"confirm": "確認",
|
||||||
|
@ -208,7 +208,9 @@
|
|||||||
"plan-tip": "Вызываемая модель",
|
"plan-tip": "Вызываемая модель",
|
||||||
"disable": "Функция подписки на этом сайте отключена",
|
"disable": "Функция подписки на этом сайте отключена",
|
||||||
"plan-unlimited-usage": "{{name}} имеет неограниченное количество пользователей",
|
"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": "Отмена",
|
"cancel": "Отмена",
|
||||||
"confirm": "Подтвердить",
|
"confirm": "Подтвердить",
|
||||||
|
@ -65,7 +65,11 @@ func ThrottleMiddleware() gin.HandlerFunc {
|
|||||||
|
|
||||||
limiter := GetPrefixMap[Limiter](path, limits)
|
limiter := GetPrefixMap[Limiter](path, limits)
|
||||||
if limiter != nil && limiter.RateLimit(cache, ip, path) {
|
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()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user