From 9a501d5d2ba389a5863ec77970c8e7d1888e5d39 Mon Sep 17 00:00:00 2001 From: Zhang Minghan Date: Mon, 22 Jan 2024 20:14:46 +0800 Subject: [PATCH] feat: support model market sync upstream (#50) --- app/src/api/v1.ts | 31 +- app/src/assets/admin/market.less | 8 + app/src/components/PopupDialog.tsx | 9 +- app/src/components/app/Announcement.tsx | 2 +- app/src/resources/i18n/cn.json | 7 +- app/src/resources/i18n/en.json | 9 +- app/src/resources/i18n/ja.json | 9 +- app/src/resources/i18n/ru.json | 9 +- app/src/routes/admin/Market.tsx | 565 ++++++++++++++++-------- 9 files changed, 454 insertions(+), 195 deletions(-) diff --git a/app/src/api/v1.ts b/app/src/api/v1.ts index c6db12b..3c3cd81 100644 --- a/app/src/api/v1.ts +++ b/app/src/api/v1.ts @@ -2,9 +2,20 @@ import axios from "axios"; import { Model, Plan } from "@/api/types.ts"; import { ChargeProps } from "@/admin/charge.ts"; -export async function getApiModels(): Promise { +type v1Options = { + endpoint?: string; +}; + +export function getV1Path(path: string, options?: v1Options): string { + let endpoint = options && options.endpoint ? options.endpoint : ""; + if (endpoint.endsWith("/")) endpoint = endpoint.slice(0, -1); + + return endpoint + path; +} + +export async function getApiModels(options?: v1Options): Promise { try { - const res = await axios.get("/v1/models"); + const res = await axios.get(getV1Path("/v1/models", options)); return res.data as string[]; } catch (e) { console.warn(e); @@ -12,9 +23,9 @@ export async function getApiModels(): Promise { } } -export async function getApiPlans(): Promise { +export async function getApiPlans(options?: v1Options): Promise { try { - const res = await axios.get("/v1/plans"); + const res = await axios.get(getV1Path("/v1/plans", options)); const plans = res.data as Plan[]; return plans.filter((plan: Plan) => plan.level !== 0); } catch (e) { @@ -23,19 +34,21 @@ export async function getApiPlans(): Promise { } } -export async function getApiMarket(): Promise { +export async function getApiMarket(options?: v1Options): Promise { try { - const res = await axios.get("/v1/market"); - return res.data as Model[]; + const res = await axios.get(getV1Path("/v1/market", options)); + return (res.data || []) as Model[]; } catch (e) { console.warn(e); return []; } } -export async function getApiCharge(): Promise { +export async function getApiCharge( + options?: v1Options, +): Promise { try { - const res = await axios.get("/v1/charge"); + const res = await axios.get(getV1Path("/v1/charge", options)); return res.data as ChargeProps[]; } catch (e) { console.warn(e); diff --git a/app/src/assets/admin/market.less b/app/src/assets/admin/market.less index f44f8d0..c1c6f70 100644 --- a/app/src/assets/admin/market.less +++ b/app/src/assets/admin/market.less @@ -43,6 +43,14 @@ } } + .empty { + display: flex; + text-align: center; + color: hsl(var(--text-secondary)); + user-select: none; + margin: 1.5rem auto; + } + .market-item { display: flex; flex-direction: row; diff --git a/app/src/components/PopupDialog.tsx b/app/src/components/PopupDialog.tsx index b8eef39..12e3ba3 100644 --- a/app/src/components/PopupDialog.tsx +++ b/app/src/components/PopupDialog.tsx @@ -22,6 +22,9 @@ export type PopupDialogProps = { open: boolean; setOpen: (open: boolean) => void; + + cancelLabel?: string; + confirmLabel?: string; }; export type PopupFieldProps = { @@ -51,6 +54,8 @@ function PopupDialog({ onSubmit, open, setOpen, + cancelLabel, + confirmLabel, }: PopupDialogProps) { const { t } = useTranslation(); const [value, setValue] = useState(defaultValue || ""); @@ -78,7 +83,7 @@ function PopupDialog({ diff --git a/app/src/components/app/Announcement.tsx b/app/src/components/app/Announcement.tsx index aeb96a7..23bcd57 100644 --- a/app/src/components/app/Announcement.tsx +++ b/app/src/components/app/Announcement.tsx @@ -27,7 +27,7 @@ function Announcement() { open={announcement !== ""} onOpenChange={() => setAnnouncement("")} > - + ({ + id: model.id || "", + name: model.name || "", + free: false, + auth: false, + description: model.description || "", + high_context: model.high_context || false, + default: model.default || false, + tag: model.tag || [], + avatar: model.avatar || modelImages[0], + seed: generateSeed(), + })), + ]; case "new": return [ ...state, @@ -320,11 +355,324 @@ function MarketImage({ image, idx, dispatch }: MarketImageProps) { ); } +type MarketItemProps = { + model: Model; + form: MarketForm; + dispatch: Dispatch; + index: number; +}; + +function MarketItem({ model, form, dispatch, index }: MarketItemProps) { + const { t } = useTranslation(); + + const checked = useMemo( + (): boolean => model.id.trim().length > 0 && model.name.trim().length > 0, + [model], + ); + + return ( +
+
+
+ + + {t("admin.market.model-name")} + + { + dispatch({ + type: "update-name", + payload: { + idx: index, + name: e.target.value, + }, + }); + }} + /> +
+
+ + + {t("admin.market.model-id")} + + { + dispatch({ + type: "update-id", + payload: { idx: index, id }, + }); + }} + className={`model-combobox`} + list={channelModels} + placeholder={t("admin.market.model-id-placeholder")} + /> +
+
+ {t("admin.market.model-description")} +