feat: support customize subscription usage cover relay api

This commit is contained in:
Zhang Minghan 2024-02-14 09:09:05 +08:00
parent 86198ea5fa
commit 304e071718
13 changed files with 69 additions and 10 deletions

View File

@ -22,6 +22,7 @@ export type SiteInfo = {
auth_footer: boolean;
article: string[];
generation: string[];
relay_plan: boolean;
};
export async function getSiteInfo(): Promise<SiteInfo> {
@ -43,6 +44,7 @@ export async function getSiteInfo(): Promise<SiteInfo> {
mail: false,
article: [],
generation: [],
relay_plan: false,
};
}
}

View File

@ -32,6 +32,7 @@ export type SearchState = {
export type SiteState = {
close_register: boolean;
relay_plan: boolean;
quota: number;
buy_link: string;
announcement: string;
@ -118,6 +119,7 @@ export const initialSystemState: SystemProps = {
file: "",
},
site: {
relay_plan: false,
close_register: false,
quota: 0,
buy_link: "",

View File

@ -22,7 +22,7 @@ import {
openDialog as openQuotaDialog,
dialogSelector as quotaDialogSelector,
} from "@/store/quota.ts";
import { Calendar } from "lucide-react";
import { Calendar, Info } from "lucide-react";
import { useEffectAsync } from "@/utils/hook.ts";
import { selectAuthenticated } from "@/store/auth.ts";
import SubscriptionUsage from "@/components/home/subscription/SubscriptionUsage.tsx";
@ -38,6 +38,7 @@ import {
import { cn } from "@/components/ui/lib/utils.ts";
import { Badge } from "@/components/ui/badge.tsx";
import { subscriptionDataSelector } from "@/store/globals.ts";
import { infoRelayPlanSelector } from "@/store/info.ts";
type PlanItemProps = {
level: number;
@ -104,6 +105,8 @@ function SubscriptionDialog() {
const subscriptionData = useSelector(subscriptionDataSelector);
const relayPlan = useSelector(infoRelayPlanSelector);
const plan = useMemo(
() => getPlan(subscriptionData, level),
[subscriptionData, level],
@ -139,6 +142,14 @@ function SubscriptionDialog() {
>
{t("sub.quota-link")}
</p>
{!relayPlan && (
<div
className={`sub-tip flex flex-row items-center w-max mx-auto select-none`}
>
<Info className={`h-4 w-4 mr-1.5`} />
{t("sub.plan-not-support-relay")}
</div>
)}
{subscription && (
<div className={`sub-row`}>
<SubscriptionUsage

View File

@ -7,6 +7,7 @@ export type InfoForm = {
auth_footer: boolean;
article: string[];
generation: string[];
relay_plan: boolean;
};
export const infoEvent = new EventCommitter<InfoForm>({

View File

@ -198,6 +198,7 @@
"title": "订阅",
"disable": "本站订阅功能已被关闭",
"quota-link": "寻求弹性计费?购买点数",
"plan-not-support-relay": "站点订阅配额不涵盖中转 API , 中转 API 请使用弹性计费点数",
"subscription-link": "寻求固定计费?订阅计划",
"dialog-title": "订阅计划",
"free": "免费版",
@ -658,6 +659,8 @@
"searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能(速度较慢)。\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。",
"closeRegistration": "暂停注册",
"closeRegistrationTip": "暂停注册,关闭后新用户将无法注册",
"relayPlan": "订阅配额支持中转 API",
"relayPlanTip": "订阅配额支持中转 API开启后中转 API 计费会优先考虑使用用户订阅配额\n提示订阅为次数配额对 Token 计费的模型可能会影响成本)",
"quota": "用户初始点数",
"quotaTip": "用户注册后赠送的点数",
"buyLink": "购买链接",

View File

@ -193,7 +193,8 @@
"plan-usage": "{{name}} uses {{times}} times per month",
"plan-tip": "Callable Model",
"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"
},
"cancel": "Cancel",
"confirm": "Confirm",
@ -531,7 +532,9 @@
"closeRegistrationTip": "Registration is paused, new users will not be able to register after closing",
"footer": "Footer Infor",
"footerPlaceholder": "Please enter footer information (Markdown/HTML format supported)",
"authFooter": "Hide footer after login"
"authFooter": "Hide footer after login",
"relayPlan": "Subscription Quota Support Staging API",
"relayPlanTip": "Subscription quota supports the transit API, after opening the transit API billing will give priority to the use of user subscription quota\n(Tip: Subscription is a quota of times, the model of billing for tokens may affect the cost)"
},
"user": "User Management",
"invitation-code": "Invitation Code",

View File

@ -193,7 +193,8 @@
"plan-usage": "{{name}}は月に{{times}}回使用",
"plan-tip": "呼び出し可能なモデル",
"disable": "このサイトのサブスクリプション機能はオフになっています",
"plan-unlimited-usage": "{{name}}は無制限に使用できます"
"plan-unlimited-usage": "{{name}}は無制限に使用できます",
"plan-not-support-relay": "サイトサブスクリプションクォータはステージングAPIをカバーしていません。ステージングAPIに柔軟な請求クレジットを使用してください"
},
"cancel": "キャンセル",
"confirm": "確認",
@ -531,7 +532,9 @@
"closeRegistrationTip": "登録が一時停止されています。新規ユーザーは閉じると登録できなくなります",
"footer": "フッター情報",
"footerPlaceholder": "フッター情報を入力してください( Markdown/HTML形式に対応",
"authFooter": "ログイン後にフッターを非表示にする"
"authFooter": "ログイン後にフッターを非表示にする",
"relayPlan": "サブスクリプションクォータサポートステージングAPI",
"relayPlanTip": "サブスクリプションクォータはトランジットAPIをサポートしています。トランジットAPI請求を開いた後、ユーザーサブスクリプションクォータの使用が優先されます\nヒントサブスクリプションは時間のクォータであり、トークンの請求モデルはコストに影響する可能性があります"
},
"user": "ユーザー管理",
"invitation-code": "招待コード",

View File

@ -193,7 +193,8 @@
"plan-usage": "{{name}} использует {{times}} раз в месяц",
"plan-tip": "Вызываемая модель",
"disable": "Функция подписки на этом сайте отключена",
"plan-unlimited-usage": "{{name}} имеет неограниченное количество пользователей"
"plan-unlimited-usage": "{{name}} имеет неограниченное количество пользователей",
"plan-not-support-relay": "Квота подписки на сайт не распространяется на промежуточный API, пожалуйста, используйте гибкие биллинговые кредиты для промежуточного API"
},
"cancel": "Отмена",
"confirm": "Подтвердить",
@ -531,7 +532,9 @@
"closeRegistrationTip": "Регистрация приостановлена, новые пользователи не смогут зарегистрироваться после закрытия",
"footer": "Информация нижнего колонтитула",
"footerPlaceholder": "Пожалуйста, введите информацию нижнего колонтитула (поддерживается формат Markdown/HTML)",
"authFooter": "Скрыть нижний колонтитул после входа в систему"
"authFooter": "Скрыть нижний колонтитул после входа в систему",
"relayPlan": "API промежуточной поддержки квот подписки",
"relayPlanTip": "Квота подписки поддерживает транзитный API, после открытия транзитного API биллинг будет отдавать приоритет использованию пользовательской квоты подписки\n(Совет: Подписка - это квота раз, модель биллинга для токенов может повлиять на стоимость)"
},
"user": "Управление пользователями",
"invitation-code": "Код приглашения",

View File

@ -482,6 +482,21 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
}}
/>
</ParagraphItem>
<ParagraphItem>
<Label>
{t("admin.system.relayPlan")}
<Tips
className={`inline-block`}
content={t("admin.system.relayPlanTip")}
/>
</Label>
<Switch
checked={data.relay_plan}
onCheckedChange={(value) => {
dispatch({ type: "update:site.relay_plan", value });
}}
/>
</ParagraphItem>
<ParagraphItem>
<Label className={`flex flex-row items-center`}>
{t("admin.system.quota")}

View File

@ -19,6 +19,7 @@ export const infoSlice = createSlice({
generation: getArrayMemory("generation"),
footer: getMemory("footer"),
auth_footer: getBooleanMemory("auth_footer", false),
relay_plan: getBooleanMemory("relay_plan", false),
} as InfoForm,
reducers: {
setForm: (state, action) => {
@ -29,6 +30,7 @@ export const infoSlice = createSlice({
state.generation = form.generation ?? [];
state.footer = form.footer ?? "";
state.auth_footer = form.auth_footer ?? false;
state.relay_plan = form.relay_plan ?? false;
setBooleanMemory("mail", state.mail);
setMemory("contact", state.contact);
@ -36,6 +38,7 @@ export const infoSlice = createSlice({
setArrayMemory("generation", state.generation);
setMemory("footer", state.footer);
setBooleanMemory("auth_footer", state.auth_footer);
setBooleanMemory("relay_plan", state.relay_plan);
},
},
});
@ -56,3 +59,5 @@ export const infoFooterSelector = (state: RootState): string =>
state.info.footer;
export const infoAuthFooterSelector = (state: RootState): boolean =>
state.info.auth_footer;
export const infoRelayPlanSelector = (state: RootState): boolean =>
state.info.relay_plan;

View File

@ -21,6 +21,7 @@ type ApiInfo struct {
Mail bool `json:"mail"`
Article []string `json:"article"`
Generation []string `json:"generation"`
RelayPlan bool `json:"relay_plan"`
}
type generalState struct {
@ -33,6 +34,7 @@ type generalState struct {
type siteState struct {
CloseRegister bool `json:"close_register" mapstructure:"closeregister"`
RelayPlan bool `json:"relay_plan" mapstructure:"relayplan"`
Quota float64 `json:"quota" mapstructure:"quota"`
BuyLink string `json:"buy_link" mapstructure:"buylink"`
Announcement string `json:"announcement" mapstructure:"announcement"`
@ -119,6 +121,7 @@ func (c *SystemConfig) AsInfo() ApiInfo {
Mail: c.IsMailValid(),
Article: c.Common.Article,
Generation: c.Common.Generation,
RelayPlan: c.Site.RelayPlan,
}
}
@ -256,3 +259,7 @@ func (c *SystemConfig) GetCacheAcceptedSize() int64 {
func (c *SystemConfig) IsCloseRegister() bool {
return c.Site.CloseRegister
}
func (c *SystemConfig) SupportRelayPlan() bool {
return c.Site.RelayPlan
}

View File

@ -16,6 +16,10 @@ import (
"time"
)
func supportRelayPlan() bool {
return channel.SystemInstance.SupportRelayPlan()
}
func ChatRelayAPI(c *gin.Context) {
username := utils.GetUserFromContext(c)
if username == "" {
@ -61,9 +65,9 @@ func ChatRelayAPI(c *gin.Context) {
}
if form.Stream {
sendStreamTranshipmentResponse(c, form, messages, id, created, user, false)
sendStreamTranshipmentResponse(c, form, messages, id, created, user, supportRelayPlan())
} else {
sendTranshipmentResponse(c, form, messages, id, created, user, false)
sendTranshipmentResponse(c, form, messages, id, created, user, supportRelayPlan())
}
}

View File

@ -54,7 +54,7 @@ func ImagesRelayAPI(c *gin.Context) {
return
}
createRelayImageObject(c, form, prompt, created, user, false)
createRelayImageObject(c, form, prompt, created, user, supportRelayPlan())
}
func getImageProps(form RelayImageForm, messages []globals.Message, buffer *utils.Buffer) *adapter.ChatProps {