From 9b1de1a9f3ac275224bb8507e2352070aa0e2a9a Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 19:20:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=B0=E7=89=88=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2=E4=B8=8E=E4=BC=9A?= =?UTF-8?q?=E5=91=98=E5=8D=87=E7=BA=A7=E9=85=8D=E7=BD=AE=E4=BD=93=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/admin-settings-client.tsx | 156 +++- app/(chat)/layout.tsx | 94 +-- app/(chat)/page.tsx | 39 +- app/api/site-settings/route.ts | 354 ++++++++- app/api/user/membership-summary/route.ts | 86 +++ components/canvas/CanvasChatPanel.tsx | 120 +++ components/chat-shell.tsx | 92 +++ components/home/lovart-home.tsx | 693 ++++++++++++++++++ lib/canvas-launch.ts | 16 + lib/membership/config.ts | 306 ++++++++ 10 files changed, 1873 insertions(+), 83 deletions(-) create mode 100644 app/api/user/membership-summary/route.ts create mode 100644 components/chat-shell.tsx create mode 100644 components/home/lovart-home.tsx create mode 100644 lib/canvas-launch.ts create mode 100644 lib/membership/config.ts diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 0fbdcdf48..16d43265f 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -67,6 +67,12 @@ import { } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; +import { + defaultMembershipConfig, + defaultMembershipUserOverrides, + normalizeMembershipConfig, + normalizeMembershipUserOverrides, +} from "@/lib/membership/config"; const fetcher = (url: string) => fetch(url).then((res) => res.json()); @@ -170,6 +176,10 @@ type SiteSettings = { canvasEnabled: boolean; canvasDefaultModelId: string | null; canvasAutoModeDefaultModelId: string | null; + homeVariant: "classic" | "lovart"; + surpriseCategories: string[]; + membershipConfig: ReturnType; + membershipUserOverrides: ReturnType; }; type NavLink = { @@ -388,10 +398,27 @@ export function AdminSettingsClient() { canvasEnabled: true, canvasDefaultModelId: null, canvasAutoModeDefaultModelId: null, + homeVariant: "classic", + surpriseCategories: ["全部", "惊喜"], + membershipConfig: defaultMembershipConfig, + membershipUserOverrides: defaultMembershipUserOverrides, }); + const [membershipConfigDraft, setMembershipConfigDraft] = useState( + JSON.stringify(defaultMembershipConfig, null, 2) + ); + const [membershipOverridesDraft, setMembershipOverridesDraft] = useState( + JSON.stringify(defaultMembershipUserOverrides, null, 2) + ); useEffect(() => { if (siteSettingsData) { + const normalizedMembershipConfig = normalizeMembershipConfig( + (siteSettingsData as any).membershipConfig + ); + const normalizedMembershipOverrides = normalizeMembershipUserOverrides( + (siteSettingsData as any).membershipUserOverrides + ); + setSiteSettings({ siteName: siteSettingsData.siteName || "AI Chatbot", siteLogo: siteSettingsData.siteLogo || "", @@ -416,7 +443,15 @@ export function AdminSettingsClient() { canvasEnabled: siteSettingsData.canvasEnabled ?? true, canvasDefaultModelId: siteSettingsData.canvasDefaultModelId || null, canvasAutoModeDefaultModelId: siteSettingsData.canvasAutoModeDefaultModelId || null, + homeVariant: siteSettingsData.homeVariant === "lovart" ? "lovart" : "classic", + surpriseCategories: Array.isArray(siteSettingsData.surpriseCategories) + ? siteSettingsData.surpriseCategories.map((item: any) => String(item).trim()).filter(Boolean) + : ["全部", "惊喜"], + membershipConfig: normalizedMembershipConfig, + membershipUserOverrides: normalizedMembershipOverrides, }); + setMembershipConfigDraft(JSON.stringify(normalizedMembershipConfig, null, 2)); + setMembershipOverridesDraft(JSON.stringify(normalizedMembershipOverrides, null, 2)); } }, [siteSettingsData]); @@ -895,12 +930,42 @@ export function AdminSettingsClient() { // --- 网站设置逻辑 --- const handleSaveSiteSettings = async () => { try { + let parsedMembershipConfig: ReturnType; + let parsedMembershipOverrides: ReturnType; + + try { + parsedMembershipConfig = normalizeMembershipConfig( + JSON.parse(membershipConfigDraft) + ); + } catch { + toast.error("会员配置 JSON 格式错误,请检查后再保存"); + return; + } + + try { + parsedMembershipOverrides = normalizeMembershipUserOverrides( + JSON.parse(membershipOverridesDraft) + ); + } catch { + toast.error("会员用户覆盖 JSON 格式错误,请检查后再保存"); + return; + } + + const payload = { + ...siteSettings, + membershipConfig: parsedMembershipConfig, + membershipUserOverrides: parsedMembershipOverrides, + }; + const response = await fetch("/api/site-settings", { method: "PUT", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(siteSettings), + body: JSON.stringify(payload), }); if (!response.ok) throw new Error("保存失败"); + setSiteSettings(payload); + setMembershipConfigDraft(JSON.stringify(parsedMembershipConfig, null, 2)); + setMembershipOverridesDraft(JSON.stringify(parsedMembershipOverrides, null, 2)); toast.success("网站设置保存成功"); mutateSiteSettings(); } catch (error) { @@ -2500,6 +2565,95 @@ export function AdminSettingsClient() { 设置新用户首次访问时的默认主题模式 +
+ + +
+ 新版项目首页会启用全新的项目入口、文件列表与精选展示。 +
+
+
+ + { + const parsed = e.target.value + .split(",") + .map((item) => item.trim()) + .filter(Boolean); + const unique = Array.from(new Set(parsed)); + setSiteSettings({ + ...siteSettings, + surpriseCategories: unique.length > 0 ? unique : ["全部", "惊喜"], + }); + }} + placeholder="全部, 惊喜, 海报, 电商" + /> +
+ 新版首页“精选”模块会按这些分类显示筛选标签。 +
+
+
+
+
+ +
+ 支持配置套餐、FAQ、模型额度、用户套餐覆盖。保存后新版首页“升级”弹窗实时生效。 +
+
+ +
+
+ +