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({