diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 889404b51..d8a850a74 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -176,6 +176,7 @@ type SiteSettings = { canvasEnabled: boolean; canvasDefaultModelId: string | null; canvasAutoModeDefaultModelId: string | null; + lovartComposerPrefix: string; lovartComposerPreviewTexts: string[]; lovartPromoText: string; lovartPromoActionText: string; @@ -408,8 +409,9 @@ export function AdminSettingsClient() { canvasEnabled: true, canvasDefaultModelId: null, canvasAutoModeDefaultModelId: null, + lovartComposerPrefix: "让Buyi-ai", lovartComposerPreviewTexts: [ - "让 Lovart 打造引人注目的社交媒体视觉", + "打造引人注目的社交媒体视觉", "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ], @@ -462,12 +464,15 @@ export function AdminSettingsClient() { canvasEnabled: siteSettingsData.canvasEnabled ?? true, canvasDefaultModelId: siteSettingsData.canvasDefaultModelId || null, canvasAutoModeDefaultModelId: siteSettingsData.canvasAutoModeDefaultModelId || null, + lovartComposerPrefix: + String((siteSettingsData as any).lovartComposerPrefix || "").trim() || + "让Buyi-ai", lovartComposerPreviewTexts: Array.isArray(siteSettingsData.lovartComposerPreviewTexts) ? siteSettingsData.lovartComposerPreviewTexts .map((item: any) => String(item).trim()) .filter(Boolean) : [ - "让 Lovart 打造引人注目的社交媒体视觉", + "打造引人注目的社交媒体视觉", "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ], @@ -2713,6 +2718,23 @@ export function AdminSettingsClient() {
新版首页文案配置
+
+ + + setSiteSettings({ + ...siteSettings, + lovartComposerPrefix: e.target.value.slice(0, 40), + }) + } + placeholder="让Buyi-ai" + /> +
+ 该前缀会固定显示在打字文案前面,切换文案时不删除。 +
+
diff --git a/app/api/site-settings/route.ts b/app/api/site-settings/route.ts index 048314328..ae906571a 100644 --- a/app/api/site-settings/route.ts +++ b/app/api/site-settings/route.ts @@ -15,6 +15,7 @@ const HOME_VARIANT_KEY = "home_variant"; // Historically stored as "home_surprise_categories" but used for the featured gallery categories. const HOME_SURPRISE_CATEGORIES_KEY = "home_surprise_categories"; const LOVART_COMPOSER_PREVIEW_TEXTS_KEY = "lovart_composer_preview_texts"; +const LOVART_COMPOSER_PREFIX_KEY = "lovart_composer_prefix"; const MEMBERSHIP_CONFIG_KEY = "membership_config"; const MEMBERSHIP_USER_OVERRIDES_KEY = "membership_user_plan_overrides"; @@ -48,7 +49,7 @@ function normalizeSurpriseCategories(value: unknown): string[] { function normalizeLovartComposerPreviewTexts(value: unknown): string[] { const defaults = [ - "让 Lovart 打造引人注目的社交媒体视觉", + "打造引人注目的社交媒体视觉", "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ]; @@ -65,6 +66,14 @@ function normalizeLovartComposerPreviewTexts(value: unknown): string[] { return normalized.length > 0 ? normalized : defaults; } +function normalizeLovartComposerPrefix(value: unknown): string { + const normalized = String(value || "").trim(); + if (!normalized) { + return "让Buyi-ai"; + } + return normalized.slice(0, 40); +} + // GET /api/site-settings - 获取网站设置 (公开) export async function GET() { try { @@ -74,6 +83,7 @@ export async function GET() { homeVariantSetting, surpriseCategoriesSetting, lovartComposerPreviewTextsSetting, + lovartComposerPrefixSetting, membershipConfigSetting, membershipOverridesSetting, ] = await Promise.all([ @@ -102,6 +112,12 @@ export async function GET() { .where(eq(systemSettings.key, LOVART_COMPOSER_PREVIEW_TEXTS_KEY)) .limit(1) .then(rows => rows[0]), + db + .select() + .from(systemSettings) + .where(eq(systemSettings.key, LOVART_COMPOSER_PREFIX_KEY)) + .limit(1) + .then(rows => rows[0]), db .select() .from(systemSettings) @@ -122,6 +138,9 @@ export async function GET() { const lovartComposerPreviewTexts = normalizeLovartComposerPreviewTexts( lovartComposerPreviewTextsSetting?.value ); + const lovartComposerPrefix = normalizeLovartComposerPrefix( + lovartComposerPrefixSetting?.value + ); const membershipConfig = normalizeMembershipConfig(membershipConfigSetting?.value); const membershipUserOverrides = normalizeMembershipUserOverrides( membershipOverridesSetting?.value @@ -158,6 +177,7 @@ export async function GET() { lovartHeroSubtitle: "懂你的设计代理,帮你搞定一切", homeVariant, surpriseCategories, + lovartComposerPrefix, lovartComposerPreviewTexts, membershipConfig, membershipUserOverrides, @@ -174,6 +194,7 @@ export async function GET() { notification, homeVariant, surpriseCategories, + lovartComposerPrefix, lovartComposerPreviewTexts, membershipConfig, membershipUserOverrides, @@ -224,6 +245,7 @@ const updateSchema = z.object({ lovartHeroSubtitle: z.string().max(300).optional(), homeVariant: z.enum(["classic", "lovart"]).optional(), surpriseCategories: z.array(z.string().trim().min(1).max(20)).max(12).optional(), + lovartComposerPrefix: z.string().trim().min(1).max(40).optional(), lovartComposerPreviewTexts: z.array(z.string().trim().min(1).max(120)).max(20).optional(), membershipConfig: z.unknown().optional(), membershipUserOverrides: z.unknown().optional(), @@ -247,6 +269,7 @@ export async function PUT(request: NextRequest) { const { homeVariant, surpriseCategories, + lovartComposerPrefix, lovartComposerPreviewTexts, membershipConfig, membershipUserOverrides, @@ -327,6 +350,26 @@ export async function PUT(request: NextRequest) { }); } + if (lovartComposerPrefix !== undefined) { + const normalized = normalizeLovartComposerPrefix(lovartComposerPrefix); + await db + .insert(systemSettings) + .values({ + key: LOVART_COMPOSER_PREFIX_KEY, + value: normalized, + updatedAt: new Date(), + updatedBy: session.user.id, + }) + .onConflictDoUpdate({ + target: systemSettings.key, + set: { + value: normalized, + updatedAt: new Date(), + updatedBy: session.user.id, + }, + }); + } + if (membershipConfig !== undefined) { const normalized = normalizeMembershipConfig(membershipConfig); await db @@ -374,6 +417,10 @@ export async function PUT(request: NextRequest) { surpriseCategories !== undefined ? normalizeSurpriseCategories(surpriseCategories) : normalizeSurpriseCategories(undefined), + lovartComposerPrefix: + lovartComposerPrefix !== undefined + ? normalizeLovartComposerPrefix(lovartComposerPrefix) + : normalizeLovartComposerPrefix(undefined), lovartComposerPreviewTexts: lovartComposerPreviewTexts !== undefined ? normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts) @@ -484,6 +531,26 @@ export async function PUT(request: NextRequest) { }); } + if (lovartComposerPrefix !== undefined) { + const normalized = normalizeLovartComposerPrefix(lovartComposerPrefix); + await db + .insert(systemSettings) + .values({ + key: LOVART_COMPOSER_PREFIX_KEY, + value: normalized, + updatedAt: new Date(), + updatedBy: session.user.id, + }) + .onConflictDoUpdate({ + target: systemSettings.key, + set: { + value: normalized, + updatedAt: new Date(), + updatedBy: session.user.id, + }, + }); + } + if (membershipConfig !== undefined) { const normalized = normalizeMembershipConfig(membershipConfig); await db @@ -531,6 +598,10 @@ export async function PUT(request: NextRequest) { surpriseCategories !== undefined ? normalizeSurpriseCategories(surpriseCategories) : normalizeSurpriseCategories(undefined), + lovartComposerPrefix: + lovartComposerPrefix !== undefined + ? normalizeLovartComposerPrefix(lovartComposerPrefix) + : normalizeLovartComposerPrefix(undefined), lovartComposerPreviewTexts: lovartComposerPreviewTexts !== undefined ? normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts) diff --git a/components/home/lovart-home.tsx b/components/home/lovart-home.tsx index 43f1d156c..6fd4bbf29 100644 --- a/components/home/lovart-home.tsx +++ b/components/home/lovart-home.tsx @@ -12,6 +12,7 @@ import { ChevronDown, Clapperboard, Folder, + Heart, Home, ImageIcon, Info, @@ -162,6 +163,27 @@ const PROMPT_TAGS = [ const MASONRY_HEIGHTS = [420, 290, 340, 470, 300, 360, 430, 320]; const FEATURED_PAGE_SIZE = 24; +const COMPOSER_PREFIX_DEFAULT = "让Buyi-ai"; + +function normalizeComposerPreviewTail(text: string, fixedPrefix: string) { + const trimmed = text.trim(); + if (!trimmed) return ""; + + const sanitizedPrefix = fixedPrefix.trim(); + if (sanitizedPrefix && trimmed.startsWith(sanitizedPrefix)) { + return trimmed.slice(sanitizedPrefix.length).trimStart(); + } + + const legacyPrefixRemoved = trimmed + .replace(/^让\s*buyi[-_ ]?ai\s*/i, "") + .replace(/^让\s*lovart\s*/i, "") + .trimStart(); + if (legacyPrefixRemoved !== trimmed) { + return legacyPrefixRemoved; + } + + return trimmed; +} function getUserInitials(user: HomeUser | null) { const source = (user?.name || user?.email || "mu").trim(); @@ -280,6 +302,7 @@ export function LovartHome({ const { data: siteSettingsData } = useSWR<{ canvasDefaultModelId?: string | null; + lovartComposerPrefix?: string | null; lovartComposerPreviewTexts?: string[]; }>("/api/site-settings", fetcher); @@ -358,12 +381,25 @@ export function LovartHome({ } return [ - "让 Lovart 打造引人注目的社交媒体视觉", + "打造引人注目的社交媒体视觉", "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ]; }, [siteSettingsData?.lovartComposerPreviewTexts]); + const composerFixedPrefix = useMemo(() => { + return ( + String(siteSettingsData?.lovartComposerPrefix || "").trim() || + COMPOSER_PREFIX_DEFAULT + ); + }, [siteSettingsData?.lovartComposerPrefix]); + + const composerPreviewTails = useMemo(() => { + return composerPreviewTexts + .map((text) => normalizeComposerPreviewTail(text, composerFixedPrefix)) + .filter(Boolean); + }, [composerFixedPrefix, composerPreviewTexts]); + const loadedFeaturedImages = useMemo(() => { return (featuredPages || []).flatMap((page) => page.images || []); }, [featuredPages]); @@ -474,44 +510,58 @@ export function LovartHome({ let disposed = false; let timer: ReturnType | null = null; - if (composerPreviewTexts.length === 0) { + if (composerPreviewTails.length === 0) { setTypedPreviewText(""); return () => { if (timer) clearTimeout(timer); }; } - const runTyping = (textIndex: number) => { + let textIndex = 0; + let charIndex = 0; + + const typeForward = () => { if (disposed) return; - const currentText = composerPreviewTexts[textIndex] || ""; - let charIndex = 0; + const currentText = composerPreviewTails[textIndex] || ""; - const typeNextChar = () => { - if (disposed) return; - - setTypedPreviewText(currentText.slice(0, charIndex)); + if (charIndex < currentText.length) { charIndex += 1; + setTypedPreviewText(currentText.slice(0, charIndex)); + timer = setTimeout(typeForward, 55); + return; + } - if (charIndex <= currentText.length) { - timer = setTimeout(typeNextChar, 55); - return; - } + if (composerPreviewTails.length === 1) { + setTypedPreviewText(currentText); + return; + } - timer = setTimeout(() => { - runTyping((textIndex + 1) % composerPreviewTexts.length); - }, 1300); - }; - - typeNextChar(); + timer = setTimeout(typeBackward, 1200); }; - runTyping(0); + const typeBackward = () => { + if (disposed) return; + const currentText = composerPreviewTails[textIndex] || ""; + + if (charIndex > 0) { + charIndex -= 1; + setTypedPreviewText(currentText.slice(0, charIndex)); + timer = setTimeout(typeBackward, 35); + return; + } + + textIndex = (textIndex + 1) % composerPreviewTails.length; + timer = setTimeout(typeForward, 260); + }; + + setTypedPreviewText(""); + timer = setTimeout(typeForward, 260); return () => { disposed = true; if (timer) clearTimeout(timer); }; - }, [composerPreviewTexts]); + }, [composerPreviewTails]); useEffect(() => { const blinkTimer = setInterval(() => { @@ -708,7 +758,7 @@ export function LovartHome({ projectSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); }; - const composerPlaceholder = `${typedPreviewText || composerPreviewTexts[0] || "描述你想创作的内容"}${ + const composerPlaceholder = `${composerFixedPrefix}${typedPreviewText}${ previewCursorVisible ? " |" : "" }`; @@ -1111,7 +1161,7 @@ export function LovartHome({