mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 21:10:18 +09:00
feat: support customize subscription usage cover relay api
This commit is contained in:
parent
86198ea5fa
commit
304e071718
@ -22,6 +22,7 @@ export type SiteInfo = {
|
|||||||
auth_footer: boolean;
|
auth_footer: boolean;
|
||||||
article: string[];
|
article: string[];
|
||||||
generation: string[];
|
generation: string[];
|
||||||
|
relay_plan: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getSiteInfo(): Promise<SiteInfo> {
|
export async function getSiteInfo(): Promise<SiteInfo> {
|
||||||
@ -43,6 +44,7 @@ export async function getSiteInfo(): Promise<SiteInfo> {
|
|||||||
mail: false,
|
mail: false,
|
||||||
article: [],
|
article: [],
|
||||||
generation: [],
|
generation: [],
|
||||||
|
relay_plan: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ export type SearchState = {
|
|||||||
|
|
||||||
export type SiteState = {
|
export type SiteState = {
|
||||||
close_register: boolean;
|
close_register: boolean;
|
||||||
|
relay_plan: boolean;
|
||||||
quota: number;
|
quota: number;
|
||||||
buy_link: string;
|
buy_link: string;
|
||||||
announcement: string;
|
announcement: string;
|
||||||
@ -118,6 +119,7 @@ export const initialSystemState: SystemProps = {
|
|||||||
file: "",
|
file: "",
|
||||||
},
|
},
|
||||||
site: {
|
site: {
|
||||||
|
relay_plan: false,
|
||||||
close_register: false,
|
close_register: false,
|
||||||
quota: 0,
|
quota: 0,
|
||||||
buy_link: "",
|
buy_link: "",
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
openDialog as openQuotaDialog,
|
openDialog as openQuotaDialog,
|
||||||
dialogSelector as quotaDialogSelector,
|
dialogSelector as quotaDialogSelector,
|
||||||
} from "@/store/quota.ts";
|
} from "@/store/quota.ts";
|
||||||
import { Calendar } from "lucide-react";
|
import { Calendar, Info } from "lucide-react";
|
||||||
import { useEffectAsync } from "@/utils/hook.ts";
|
import { useEffectAsync } from "@/utils/hook.ts";
|
||||||
import { selectAuthenticated } from "@/store/auth.ts";
|
import { selectAuthenticated } from "@/store/auth.ts";
|
||||||
import SubscriptionUsage from "@/components/home/subscription/SubscriptionUsage.tsx";
|
import SubscriptionUsage from "@/components/home/subscription/SubscriptionUsage.tsx";
|
||||||
@ -38,6 +38,7 @@ import {
|
|||||||
import { cn } from "@/components/ui/lib/utils.ts";
|
import { cn } from "@/components/ui/lib/utils.ts";
|
||||||
import { Badge } from "@/components/ui/badge.tsx";
|
import { Badge } from "@/components/ui/badge.tsx";
|
||||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||||
|
import { infoRelayPlanSelector } from "@/store/info.ts";
|
||||||
|
|
||||||
type PlanItemProps = {
|
type PlanItemProps = {
|
||||||
level: number;
|
level: number;
|
||||||
@ -104,6 +105,8 @@ function SubscriptionDialog() {
|
|||||||
|
|
||||||
const subscriptionData = useSelector(subscriptionDataSelector);
|
const subscriptionData = useSelector(subscriptionDataSelector);
|
||||||
|
|
||||||
|
const relayPlan = useSelector(infoRelayPlanSelector);
|
||||||
|
|
||||||
const plan = useMemo(
|
const plan = useMemo(
|
||||||
() => getPlan(subscriptionData, level),
|
() => getPlan(subscriptionData, level),
|
||||||
[subscriptionData, level],
|
[subscriptionData, level],
|
||||||
@ -139,6 +142,14 @@ function SubscriptionDialog() {
|
|||||||
>
|
>
|
||||||
{t("sub.quota-link")}
|
{t("sub.quota-link")}
|
||||||
</p>
|
</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 && (
|
{subscription && (
|
||||||
<div className={`sub-row`}>
|
<div className={`sub-row`}>
|
||||||
<SubscriptionUsage
|
<SubscriptionUsage
|
||||||
|
@ -7,6 +7,7 @@ export type InfoForm = {
|
|||||||
auth_footer: boolean;
|
auth_footer: boolean;
|
||||||
article: string[];
|
article: string[];
|
||||||
generation: string[];
|
generation: string[];
|
||||||
|
relay_plan: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const infoEvent = new EventCommitter<InfoForm>({
|
export const infoEvent = new EventCommitter<InfoForm>({
|
||||||
|
@ -198,6 +198,7 @@
|
|||||||
"title": "订阅",
|
"title": "订阅",
|
||||||
"disable": "本站订阅功能已被关闭",
|
"disable": "本站订阅功能已被关闭",
|
||||||
"quota-link": "寻求弹性计费?购买点数",
|
"quota-link": "寻求弹性计费?购买点数",
|
||||||
|
"plan-not-support-relay": "站点订阅配额不涵盖中转 API , 中转 API 请使用弹性计费点数",
|
||||||
"subscription-link": "寻求固定计费?订阅计划",
|
"subscription-link": "寻求固定计费?订阅计划",
|
||||||
"dialog-title": "订阅计划",
|
"dialog-title": "订阅计划",
|
||||||
"free": "免费版",
|
"free": "免费版",
|
||||||
@ -658,6 +659,8 @@
|
|||||||
"searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能(速度较慢)。\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。",
|
"searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能(速度较慢)。\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。",
|
||||||
"closeRegistration": "暂停注册",
|
"closeRegistration": "暂停注册",
|
||||||
"closeRegistrationTip": "暂停注册,关闭后新用户将无法注册",
|
"closeRegistrationTip": "暂停注册,关闭后新用户将无法注册",
|
||||||
|
"relayPlan": "订阅配额支持中转 API",
|
||||||
|
"relayPlanTip": "订阅配额支持中转 API,开启后中转 API 计费会优先考虑使用用户订阅配额\n(提示:订阅为次数配额,对 Token 计费的模型可能会影响成本)",
|
||||||
"quota": "用户初始点数",
|
"quota": "用户初始点数",
|
||||||
"quotaTip": "用户注册后赠送的点数",
|
"quotaTip": "用户注册后赠送的点数",
|
||||||
"buyLink": "购买链接",
|
"buyLink": "购买链接",
|
||||||
|
@ -193,7 +193,8 @@
|
|||||||
"plan-usage": "{{name}} uses {{times}} times per month",
|
"plan-usage": "{{name}} uses {{times}} times per month",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
@ -531,7 +532,9 @@
|
|||||||
"closeRegistrationTip": "Registration is paused, new users will not be able to register after closing",
|
"closeRegistrationTip": "Registration is paused, new users will not be able to register after closing",
|
||||||
"footer": "Footer Infor",
|
"footer": "Footer Infor",
|
||||||
"footerPlaceholder": "Please enter footer information (Markdown/HTML format supported)",
|
"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",
|
"user": "User Management",
|
||||||
"invitation-code": "Invitation Code",
|
"invitation-code": "Invitation Code",
|
||||||
|
@ -193,7 +193,8 @@
|
|||||||
"plan-usage": "{{name}}は月に{{times}}回使用",
|
"plan-usage": "{{name}}は月に{{times}}回使用",
|
||||||
"plan-tip": "呼び出し可能なモデル",
|
"plan-tip": "呼び出し可能なモデル",
|
||||||
"disable": "このサイトのサブスクリプション機能はオフになっています",
|
"disable": "このサイトのサブスクリプション機能はオフになっています",
|
||||||
"plan-unlimited-usage": "{{name}}は無制限に使用できます"
|
"plan-unlimited-usage": "{{name}}は無制限に使用できます",
|
||||||
|
"plan-not-support-relay": "サイトサブスクリプションクォータはステージングAPIをカバーしていません。ステージングAPIに柔軟な請求クレジットを使用してください"
|
||||||
},
|
},
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"confirm": "確認",
|
"confirm": "確認",
|
||||||
@ -531,7 +532,9 @@
|
|||||||
"closeRegistrationTip": "登録が一時停止されています。新規ユーザーは閉じると登録できなくなります",
|
"closeRegistrationTip": "登録が一時停止されています。新規ユーザーは閉じると登録できなくなります",
|
||||||
"footer": "フッター情報",
|
"footer": "フッター情報",
|
||||||
"footerPlaceholder": "フッター情報を入力してください( Markdown/HTML形式に対応)",
|
"footerPlaceholder": "フッター情報を入力してください( Markdown/HTML形式に対応)",
|
||||||
"authFooter": "ログイン後にフッターを非表示にする"
|
"authFooter": "ログイン後にフッターを非表示にする",
|
||||||
|
"relayPlan": "サブスクリプションクォータサポートステージングAPI",
|
||||||
|
"relayPlanTip": "サブスクリプションクォータはトランジットAPIをサポートしています。トランジットAPI請求を開いた後、ユーザーサブスクリプションクォータの使用が優先されます\n(ヒント:サブスクリプションは時間のクォータであり、トークンの請求モデルはコストに影響する可能性があります)"
|
||||||
},
|
},
|
||||||
"user": "ユーザー管理",
|
"user": "ユーザー管理",
|
||||||
"invitation-code": "招待コード",
|
"invitation-code": "招待コード",
|
||||||
|
@ -193,7 +193,8 @@
|
|||||||
"plan-usage": "{{name}} использует {{times}} раз в месяц",
|
"plan-usage": "{{name}} использует {{times}} раз в месяц",
|
||||||
"plan-tip": "Вызываемая модель",
|
"plan-tip": "Вызываемая модель",
|
||||||
"disable": "Функция подписки на этом сайте отключена",
|
"disable": "Функция подписки на этом сайте отключена",
|
||||||
"plan-unlimited-usage": "{{name}} имеет неограниченное количество пользователей"
|
"plan-unlimited-usage": "{{name}} имеет неограниченное количество пользователей",
|
||||||
|
"plan-not-support-relay": "Квота подписки на сайт не распространяется на промежуточный API, пожалуйста, используйте гибкие биллинговые кредиты для промежуточного API"
|
||||||
},
|
},
|
||||||
"cancel": "Отмена",
|
"cancel": "Отмена",
|
||||||
"confirm": "Подтвердить",
|
"confirm": "Подтвердить",
|
||||||
@ -531,7 +532,9 @@
|
|||||||
"closeRegistrationTip": "Регистрация приостановлена, новые пользователи не смогут зарегистрироваться после закрытия",
|
"closeRegistrationTip": "Регистрация приостановлена, новые пользователи не смогут зарегистрироваться после закрытия",
|
||||||
"footer": "Информация нижнего колонтитула",
|
"footer": "Информация нижнего колонтитула",
|
||||||
"footerPlaceholder": "Пожалуйста, введите информацию нижнего колонтитула (поддерживается формат Markdown/HTML)",
|
"footerPlaceholder": "Пожалуйста, введите информацию нижнего колонтитула (поддерживается формат Markdown/HTML)",
|
||||||
"authFooter": "Скрыть нижний колонтитул после входа в систему"
|
"authFooter": "Скрыть нижний колонтитул после входа в систему",
|
||||||
|
"relayPlan": "API промежуточной поддержки квот подписки",
|
||||||
|
"relayPlanTip": "Квота подписки поддерживает транзитный API, после открытия транзитного API биллинг будет отдавать приоритет использованию пользовательской квоты подписки\n(Совет: Подписка - это квота раз, модель биллинга для токенов может повлиять на стоимость)"
|
||||||
},
|
},
|
||||||
"user": "Управление пользователями",
|
"user": "Управление пользователями",
|
||||||
"invitation-code": "Код приглашения",
|
"invitation-code": "Код приглашения",
|
||||||
|
@ -482,6 +482,21 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ParagraphItem>
|
</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>
|
<ParagraphItem>
|
||||||
<Label className={`flex flex-row items-center`}>
|
<Label className={`flex flex-row items-center`}>
|
||||||
{t("admin.system.quota")}
|
{t("admin.system.quota")}
|
||||||
|
@ -19,6 +19,7 @@ export const infoSlice = createSlice({
|
|||||||
generation: getArrayMemory("generation"),
|
generation: getArrayMemory("generation"),
|
||||||
footer: getMemory("footer"),
|
footer: getMemory("footer"),
|
||||||
auth_footer: getBooleanMemory("auth_footer", false),
|
auth_footer: getBooleanMemory("auth_footer", false),
|
||||||
|
relay_plan: getBooleanMemory("relay_plan", false),
|
||||||
} as InfoForm,
|
} as InfoForm,
|
||||||
reducers: {
|
reducers: {
|
||||||
setForm: (state, action) => {
|
setForm: (state, action) => {
|
||||||
@ -29,6 +30,7 @@ export const infoSlice = createSlice({
|
|||||||
state.generation = form.generation ?? [];
|
state.generation = form.generation ?? [];
|
||||||
state.footer = form.footer ?? "";
|
state.footer = form.footer ?? "";
|
||||||
state.auth_footer = form.auth_footer ?? false;
|
state.auth_footer = form.auth_footer ?? false;
|
||||||
|
state.relay_plan = form.relay_plan ?? false;
|
||||||
|
|
||||||
setBooleanMemory("mail", state.mail);
|
setBooleanMemory("mail", state.mail);
|
||||||
setMemory("contact", state.contact);
|
setMemory("contact", state.contact);
|
||||||
@ -36,6 +38,7 @@ export const infoSlice = createSlice({
|
|||||||
setArrayMemory("generation", state.generation);
|
setArrayMemory("generation", state.generation);
|
||||||
setMemory("footer", state.footer);
|
setMemory("footer", state.footer);
|
||||||
setBooleanMemory("auth_footer", state.auth_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;
|
state.info.footer;
|
||||||
export const infoAuthFooterSelector = (state: RootState): boolean =>
|
export const infoAuthFooterSelector = (state: RootState): boolean =>
|
||||||
state.info.auth_footer;
|
state.info.auth_footer;
|
||||||
|
export const infoRelayPlanSelector = (state: RootState): boolean =>
|
||||||
|
state.info.relay_plan;
|
||||||
|
@ -21,6 +21,7 @@ type ApiInfo struct {
|
|||||||
Mail bool `json:"mail"`
|
Mail bool `json:"mail"`
|
||||||
Article []string `json:"article"`
|
Article []string `json:"article"`
|
||||||
Generation []string `json:"generation"`
|
Generation []string `json:"generation"`
|
||||||
|
RelayPlan bool `json:"relay_plan"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type generalState struct {
|
type generalState struct {
|
||||||
@ -33,6 +34,7 @@ type generalState struct {
|
|||||||
|
|
||||||
type siteState struct {
|
type siteState struct {
|
||||||
CloseRegister bool `json:"close_register" mapstructure:"closeregister"`
|
CloseRegister bool `json:"close_register" mapstructure:"closeregister"`
|
||||||
|
RelayPlan bool `json:"relay_plan" mapstructure:"relayplan"`
|
||||||
Quota float64 `json:"quota" mapstructure:"quota"`
|
Quota float64 `json:"quota" mapstructure:"quota"`
|
||||||
BuyLink string `json:"buy_link" mapstructure:"buylink"`
|
BuyLink string `json:"buy_link" mapstructure:"buylink"`
|
||||||
Announcement string `json:"announcement" mapstructure:"announcement"`
|
Announcement string `json:"announcement" mapstructure:"announcement"`
|
||||||
@ -119,6 +121,7 @@ func (c *SystemConfig) AsInfo() ApiInfo {
|
|||||||
Mail: c.IsMailValid(),
|
Mail: c.IsMailValid(),
|
||||||
Article: c.Common.Article,
|
Article: c.Common.Article,
|
||||||
Generation: c.Common.Generation,
|
Generation: c.Common.Generation,
|
||||||
|
RelayPlan: c.Site.RelayPlan,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,3 +259,7 @@ func (c *SystemConfig) GetCacheAcceptedSize() int64 {
|
|||||||
func (c *SystemConfig) IsCloseRegister() bool {
|
func (c *SystemConfig) IsCloseRegister() bool {
|
||||||
return c.Site.CloseRegister
|
return c.Site.CloseRegister
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *SystemConfig) SupportRelayPlan() bool {
|
||||||
|
return c.Site.RelayPlan
|
||||||
|
}
|
||||||
|
@ -16,6 +16,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func supportRelayPlan() bool {
|
||||||
|
return channel.SystemInstance.SupportRelayPlan()
|
||||||
|
}
|
||||||
|
|
||||||
func ChatRelayAPI(c *gin.Context) {
|
func ChatRelayAPI(c *gin.Context) {
|
||||||
username := utils.GetUserFromContext(c)
|
username := utils.GetUserFromContext(c)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
@ -61,9 +65,9 @@ func ChatRelayAPI(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if form.Stream {
|
if form.Stream {
|
||||||
sendStreamTranshipmentResponse(c, form, messages, id, created, user, false)
|
sendStreamTranshipmentResponse(c, form, messages, id, created, user, supportRelayPlan())
|
||||||
} else {
|
} else {
|
||||||
sendTranshipmentResponse(c, form, messages, id, created, user, false)
|
sendTranshipmentResponse(c, form, messages, id, created, user, supportRelayPlan())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ func ImagesRelayAPI(c *gin.Context) {
|
|||||||
return
|
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 {
|
func getImageProps(form RelayImageForm, messages []globals.Message, buffer *utils.Buffer) *adapter.ChatProps {
|
||||||
|
Loading…
Reference in New Issue
Block a user