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;
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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: "",
|
||||
|
@ -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
|
||||
|
@ -7,6 +7,7 @@ export type InfoForm = {
|
||||
auth_footer: boolean;
|
||||
article: string[];
|
||||
generation: string[];
|
||||
relay_plan: boolean;
|
||||
};
|
||||
|
||||
export const infoEvent = new EventCommitter<InfoForm>({
|
||||
|
@ -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": "购买链接",
|
||||
|
@ -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",
|
||||
|
@ -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": "招待コード",
|
||||
|
@ -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": "Код приглашения",
|
||||
|
@ -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")}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user