Skip to content

Commit

Permalink
feat: support plan config sync upstream and edit folding support
Browse files Browse the repository at this point in the history
  • Loading branch information
zmh-program committed Feb 28, 2024
1 parent 7432ae6 commit 7ba52d6
Show file tree
Hide file tree
Showing 12 changed files with 393 additions and 101 deletions.
8 changes: 8 additions & 0 deletions app/src/admin/api/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Plan } from "@/api/types";
import axios from "axios";
import { CommonResponse } from "@/api/common.ts";
import { getErrorMessage } from "@/utils/base.ts";
import { getApiPlans } from "@/api/v1.ts";

export type PlanConfig = {
enabled: boolean;
Expand All @@ -24,6 +25,13 @@ export async function getPlanConfig(): Promise<PlanConfig> {
}
}

export async function getExternalPlanConfig(
endpoint: string,
): Promise<PlanConfig> {
const response = await getApiPlans({ endpoint });
return { enabled: response.length > 0, plans: response };
}

export async function setPlanConfig(
config: PlanConfig,
): Promise<CommonResponse> {
Expand Down
35 changes: 35 additions & 0 deletions app/src/admin/hook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMemo, useState } from "react";
import { getUniqueList } from "@/utils/base.ts";
import { defaultChannelModels } from "@/admin/channel.ts";
import { getApiModels } from "@/api/v1.ts";
import { useEffectAsync } from "@/utils/hook.ts";

export const useSupportModels = () => {
const [supportModels, setSupportModels] = useState<string[]>([]);

const update = async () => {
const models = await getApiModels();
setSupportModels(models.data);
};

useEffectAsync(update, []);

return {
supportModels,
update,
};
};

export const useChannelModels = () => {
const { supportModels, update } = useSupportModels();

const channelModels = useMemo(
() => getUniqueList([...supportModels, ...defaultChannelModels]),
[supportModels],
);

return {
channelModels,
update,
};
};
26 changes: 26 additions & 0 deletions app/src/assets/admin/subscription.less
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,32 @@
display: flex;
flex-direction: column;

&.stacked {
flex-direction: row;

.plan-editor-row {
margin-bottom: 0;
flex-grow: 1;
margin-right: 1rem;

.plan-editor-label {
min-width: 0;
margin-right: 0.75rem;
flex-shrink: 0;

@media (max-width: 768px) {
svg {
display: none;
}
}

svg {
margin: 0 0.25rem;
}
}
}
}

.plan-editor-row > p {
min-width: 4.25rem;
}
Expand Down
65 changes: 65 additions & 0 deletions app/src/components/PopupDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ import { NumberInput } from "@/components/ui/number-input.tsx";
import { Switch } from "@/components/ui/switch.tsx";
import { Alert, AlertDescription } from "./ui/alert";
import { AlertCircle } from "lucide-react";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";

export enum popupTypes {
Text = "text",
Expand Down Expand Up @@ -170,4 +179,60 @@ function PopupDialog(props: PopupDialogProps) {
);
}

type PopupAlertDialogProps = {
title: string;
description?: string;
open: boolean;
setOpen: (open: boolean) => void;
cancelLabel?: string;
confirmLabel?: string;
destructive?: boolean;
disabled?: boolean;
onSubmit?: () => Promise<boolean>;
};

export function PopupAlertDialog({
title,
description,
open,
setOpen,
cancelLabel,
confirmLabel,
destructive,
disabled,
onSubmit,
}: PopupAlertDialogProps) {
const { t } = useTranslation();

return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
{description && (
<AlertDialogDescription>{description}</AlertDialogDescription>
)}
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{cancelLabel || t("cancel")}</AlertDialogCancel>
<Button
disabled={disabled}
variant={destructive ? `destructive` : `default`}
loading={true}
onClick={async () => {
if (!onSubmit) return;
const status: boolean = await onSubmit();
if (status) {
setOpen(false);
}
}}
>
{confirmLabel || t("confirm")}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}

export default PopupDialog;
14 changes: 7 additions & 7 deletions app/src/components/admin/ChargeWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
import Tips from "@/components/Tips.tsx";
import { getQuerySelector, scrollUp } from "@/utils/dom.ts";
import PopupDialog, { popupTypes } from "@/components/PopupDialog.tsx";
import { getApiCharge, getApiModels, getV1Path } from "@/api/v1.ts";
import { getApiCharge, getV1Path } from "@/api/v1.ts";
import {
Dialog,
DialogContent,
Expand All @@ -77,6 +77,7 @@ import {
import { getUniqueList, parseNumber } from "@/utils/base.ts";
import { defaultChannelModels } from "@/admin/channel.ts";
import { getPricing } from "@/admin/datasets/charge.ts";
import { useSupportModels } from "@/admin/hook.tsx";

const initialState: ChargeProps = {
id: -1,
Expand Down Expand Up @@ -199,7 +200,7 @@ function SyncDialog({
const pricing = getPricing(currency);

setSiteCharge(pricing);
setSiteOpen(true)
setSiteOpen(true);

return true;
}}
Expand Down Expand Up @@ -718,7 +719,7 @@ function ChargeWidget() {
const [form, dispatch] = useReducer(reducer, initialState);
const [loading, setLoading] = useState(false);

const [supportModels, setSupportModels] = useState<string[]>([]);
const { supportModels, update } = useSupportModels();

const currentModels = useMemo(() => {
return data.flatMap((charge) => charge.models);
Expand All @@ -735,18 +736,17 @@ function ChargeWidget() {
);
}, [loading, supportModels, usedModels]);

async function refresh() {
async function refresh(ignoreUpdate?: boolean) {
setLoading(true);
const resp = await listCharge();
const models = await getApiModels();
setSupportModels(models.data);
if (!ignoreUpdate) await update();

setLoading(false);
toastState(toast, t, resp);
setData(resp.data);
}

useEffectAsync(refresh, []);
useEffectAsync(async () => await refresh(true), []);

return (
<div className={`charge-widget`}>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/admin/assemblies/ChannelTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ function SyncDialog({ dispatch, open, setOpen }: SyncDialogProps) {
<Input
value={secret}
onChange={(e) => setSecret(e.target.value)}
placeholder={t("admin.channels.secret-placeholder")}
placeholder={t("admin.channels.sync-secret-placeholder")}
/>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions app/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(

const child = useMemo(() => {
if (asChild) return children;
if (size === "icon" || size === "icon-sm") {
if (loading && working) {
return <Loader2 className={`animate-spin w-4 h-4`} />;
}
}

return (
<>
{loading && working && (
Expand Down
10 changes: 7 additions & 3 deletions app/src/resources/i18n/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,12 @@
"item-models-search-placeholder": "搜索模型 ID",
"item-models-placeholder": "已选 {{length}} 个模型",
"add-item": "添加",
"import-item": "导入"
"import-item": "导入",
"sync": "同步上游",
"sync-option": "同步选项",
"sync-site": "上游地址",
"sync-placeholder": "请输入上游 Chat Nio 的 API 地址,如:https://api.chatnio.net",
"sync-result": "发现上游订阅规则数 {{length}} 个,涵盖模型 {{models}} 个, 是否覆盖本站点订阅规则?"
},
"channels": {
"id": "渠道 ID",
Expand Down Expand Up @@ -585,8 +590,7 @@
"joint-endpoint": "上游地址",
"joint-endpoint-placeholder": "请输入上游 Chat Nio 的 API 地址,如:https://api.chatnio.net",
"upstream-endpoint-placeholder": "请输入上游 OpenAI 地址,如:https://api.openai.com",
"secret": "密钥",
"secret-placeholder": "请输入上游渠道的 API 密钥",
"sync-secret-placeholder": "请输入上游渠道的 API 密钥",
"joint-secret": "API 秘钥",
"joint-secret-placeholder": "请输入上游 Chat Nio 的 API 秘钥",
"sync-failed": "同步失败",
Expand Down
10 changes: 8 additions & 2 deletions app/src/resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,8 @@
"sync-failed-prompt": "Address could not be requested or model market model is empty\n(Endpoint: {{endpoint}})",
"sync-success": "Sync successfully.",
"sync-success-prompt": "{{length}} models were added from upstream synchronization.",
"upstream-endpoint-placeholder": "Please enter the upstream OpenAI address, e.g. https://api.openai.com"
"upstream-endpoint-placeholder": "Please enter the upstream OpenAI address, e.g. https://api.openai.com",
"sync-secret-placeholder": "Please enter API key for upstream channel"
},
"charge": {
"id": "ID",
Expand Down Expand Up @@ -624,7 +625,12 @@
"item-models-search-placeholder": "Search Model ID",
"item-models-placeholder": "{{length}} models selected",
"add-item": "add",
"import-item": "Import"
"import-item": "Import",
"sync": "Sync upstream",
"sync-option": "Synchronization Options",
"sync-site": "Upstream address",
"sync-placeholder": "Please enter the API address of the upstream Chat Nio, for example: https://api.chatnio.net",
"sync-result": "The number of upstream subscription rules was found to be {{length}}, covering {{models}} models. Do you want to overwrite the subscription rules on this site?"
},
"model-usage-chart": "Proportion of models used",
"user-type-chart": "Proportion of user types",
Expand Down
10 changes: 8 additions & 2 deletions app/src/resources/i18n/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,8 @@
"sync-failed-prompt": "住所をリクエストできなかったか、モデルマーケットモデルが空です\n(エンドポイント:{{ endpoint }})",
"sync-success": "同期成功",
"sync-success-prompt": "{{length}}モデルがアップストリーム同期から追加されました。",
"upstream-endpoint-placeholder": "上流のOpenAIアドレスを入力してください。例: https://api.openai.com"
"upstream-endpoint-placeholder": "上流のOpenAIアドレスを入力してください。例: https://api.openai.com",
"sync-secret-placeholder": "アップストリームチャネルのAPIキーを入力してください"
},
"charge": {
"id": "ID",
Expand Down Expand Up @@ -624,7 +625,12 @@
"item-models-search-placeholder": "モデルIDを検索",
"item-models-placeholder": "{{length}}モデルが選択されました",
"add-item": "登録",
"import-item": "導入"
"import-item": "導入",
"sync": "アップストリームを同期",
"sync-option": "同期のオプション",
"sync-site": "アップストリームアドレス",
"sync-placeholder": "アップストリームのChat NioのAPIアドレスを入力してください。例: https://api.chatnio.net",
"sync-result": "アップストリームサブスクリプションルールの数は{{length}}で、{{models}}モデルをカバーしています。このサイトのサブスクリプションルールを上書きしますか?"
},
"model-usage-chart": "使用機種の割合",
"user-type-chart": "ユーザータイプの割合",
Expand Down
10 changes: 8 additions & 2 deletions app/src/resources/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,8 @@
"sync-failed-prompt": "Адрес не может быть запрошен или модель рынка пуста\n(Конечная точка: {{endpoint}})",
"sync-success": "Успешная синхронизация",
"sync-success-prompt": "{{length}} модели были добавлены из синхронизации восходящего потока.",
"upstream-endpoint-placeholder": "Введите вышестоящий адрес OpenAI, например, https://api.openai.com"
"upstream-endpoint-placeholder": "Введите вышестоящий адрес OpenAI, например, https://api.openai.com",
"sync-secret-placeholder": "Пожалуйста, введите ключ API для восходящего канала"
},
"charge": {
"id": "ID",
Expand Down Expand Up @@ -624,7 +625,12 @@
"item-models-search-placeholder": "Поиск по идентификатору модели",
"item-models-placeholder": "Выбрано моделей: {{length}}",
"add-item": "Добавить",
"import-item": "Импорт"
"import-item": "Импорт",
"sync": "Синхронизация выше ПО потоку",
"sync-option": "Параметры синхронизации",
"sync-site": "Адрес выше по потоку",
"sync-placeholder": "Введите API-адрес вышестоящего Chat Nio, например: https://api.chatnio.net",
"sync-result": "Было обнаружено, что количество правил подписки выше по потоку составляет {{length}}, охватывая {{models}} моделей. Перезаписать правила подписки на этом сайте?"
},
"model-usage-chart": "Доля используемых моделей",
"user-type-chart": "Доля типов пользователей",
Expand Down
Loading

0 comments on commit 7ba52d6

Please sign in to comment.