feat: add dynamic canvas placeholder with shared admin config
This commit is contained in:
parent
a4dca610aa
commit
0c44d498d1
@ -3781,9 +3781,9 @@ export function AdminSettingsClient() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 rounded-lg border border-dashed p-4">
|
<div className="grid gap-4 rounded-lg border border-dashed p-4">
|
||||||
<div className="text-sm font-medium">新版首页文案配置</div>
|
<div className="text-sm font-medium">首页 / 无限画布对话框文案配置</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="lovartComposerPrefix">对话框固定前缀文案</Label>
|
<Label htmlFor="lovartComposerPrefix">对话框固定前缀文案(首页与无限画布共用)</Label>
|
||||||
<Input
|
<Input
|
||||||
id="lovartComposerPrefix"
|
id="lovartComposerPrefix"
|
||||||
value={siteSettings.lovartComposerPrefix}
|
value={siteSettings.lovartComposerPrefix}
|
||||||
@ -3801,7 +3801,7 @@ export function AdminSettingsClient() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="lovartComposerPreviewTexts">
|
<Label htmlFor="lovartComposerPreviewTexts">
|
||||||
对话框预览文案(每行一条,按顺序轮播打字显示)
|
对话框预览文案(首页与无限画布共用,每行一条,按顺序轮播打字显示)
|
||||||
</Label>
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="lovartComposerPreviewTexts"
|
id="lovartComposerPreviewTexts"
|
||||||
|
|||||||
@ -89,6 +89,32 @@ interface CanvasChatPanelProps {
|
|||||||
|
|
||||||
// 建议卡片数据 - 已移除
|
// 建议卡片数据 - 已移除
|
||||||
const SUGGESTIONS: { title: string; desc: string; image: string }[] = [];
|
const SUGGESTIONS: { title: string; desc: string; image: string }[] = [];
|
||||||
|
const CANVAS_COMPOSER_PREFIX_DEFAULT = "让Buyi-ai";
|
||||||
|
const CANVAS_COMPOSER_PREVIEW_TEXTS_DEFAULT = [
|
||||||
|
"打造引人注目的社交媒体视觉",
|
||||||
|
"帮我把这张产品图做成高级电商海报",
|
||||||
|
"基于参考图延展 3 套同风格方案",
|
||||||
|
];
|
||||||
|
|
||||||
|
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 getImageDimensions(url: string): Promise<{
|
function getImageDimensions(url: string): Promise<{
|
||||||
@ -2248,6 +2274,8 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
bannerEnabled?: boolean;
|
bannerEnabled?: boolean;
|
||||||
bannerText?: string;
|
bannerText?: string;
|
||||||
bannerLink?: string | null;
|
bannerLink?: string | null;
|
||||||
|
lovartComposerPrefix?: string | null;
|
||||||
|
lovartComposerPreviewTexts?: string[];
|
||||||
canvasEnabled?: boolean;
|
canvasEnabled?: boolean;
|
||||||
aiDetailGenerationEnabled?: boolean;
|
aiDetailGenerationEnabled?: boolean;
|
||||||
canvasDefaultModelId?: string | null;
|
canvasDefaultModelId?: string | null;
|
||||||
@ -2292,6 +2320,8 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}, [session?.user?.email, session?.user?.name]);
|
}, [session?.user?.email, session?.user?.name]);
|
||||||
|
const [canvasTypedPreviewText, setCanvasTypedPreviewText] = useState("");
|
||||||
|
const [canvasPreviewCursorVisible, setCanvasPreviewCursorVisible] = useState(true);
|
||||||
const [greetingPeriodText, setGreetingPeriodText] = useState("中午好!");
|
const [greetingPeriodText, setGreetingPeriodText] = useState("中午好!");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
@ -2309,6 +2339,95 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
}
|
}
|
||||||
setGreetingPeriodText("深夜好!");
|
setGreetingPeriodText("深夜好!");
|
||||||
}, []);
|
}, []);
|
||||||
|
const canvasComposerPrefix = useMemo(
|
||||||
|
() =>
|
||||||
|
String(siteSettings?.lovartComposerPrefix || "").trim() ||
|
||||||
|
CANVAS_COMPOSER_PREFIX_DEFAULT,
|
||||||
|
[siteSettings?.lovartComposerPrefix]
|
||||||
|
);
|
||||||
|
const canvasComposerPreviewTexts = useMemo(() => {
|
||||||
|
const configured = (siteSettings?.lovartComposerPreviewTexts || [])
|
||||||
|
.map((item) => String(item || "").trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
return configured.length > 0
|
||||||
|
? configured
|
||||||
|
: CANVAS_COMPOSER_PREVIEW_TEXTS_DEFAULT;
|
||||||
|
}, [siteSettings?.lovartComposerPreviewTexts]);
|
||||||
|
const canvasComposerPreviewTails = useMemo(
|
||||||
|
() =>
|
||||||
|
canvasComposerPreviewTexts
|
||||||
|
.map((text) => normalizeComposerPreviewTail(text, canvasComposerPrefix))
|
||||||
|
.filter(Boolean),
|
||||||
|
[canvasComposerPrefix, canvasComposerPreviewTexts]
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
let disposed = false;
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
if (canvasComposerPreviewTails.length === 0) {
|
||||||
|
setCanvasTypedPreviewText("");
|
||||||
|
return () => {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let textIndex = 0;
|
||||||
|
let charIndex = 0;
|
||||||
|
|
||||||
|
const typeForward = () => {
|
||||||
|
if (disposed) return;
|
||||||
|
const currentText = canvasComposerPreviewTails[textIndex] || "";
|
||||||
|
|
||||||
|
if (charIndex < currentText.length) {
|
||||||
|
charIndex += 1;
|
||||||
|
setCanvasTypedPreviewText(currentText.slice(0, charIndex));
|
||||||
|
timer = setTimeout(typeForward, 55);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canvasComposerPreviewTails.length === 1) {
|
||||||
|
setCanvasTypedPreviewText(currentText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = setTimeout(typeBackward, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeBackward = () => {
|
||||||
|
if (disposed) return;
|
||||||
|
const currentText = canvasComposerPreviewTails[textIndex] || "";
|
||||||
|
|
||||||
|
if (charIndex > 0) {
|
||||||
|
charIndex -= 1;
|
||||||
|
setCanvasTypedPreviewText(currentText.slice(0, charIndex));
|
||||||
|
timer = setTimeout(typeBackward, 35);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
textIndex = (textIndex + 1) % canvasComposerPreviewTails.length;
|
||||||
|
timer = setTimeout(typeForward, 260);
|
||||||
|
};
|
||||||
|
|
||||||
|
setCanvasTypedPreviewText("");
|
||||||
|
timer = setTimeout(typeForward, 260);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
disposed = true;
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, [canvasComposerPreviewTails]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const blinkTimer = setInterval(() => {
|
||||||
|
setCanvasPreviewCursorVisible((value) => !value);
|
||||||
|
}, 450);
|
||||||
|
|
||||||
|
return () => clearInterval(blinkTimer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const canvasDynamicPlaceholder = `${canvasComposerPrefix}${canvasTypedPreviewText}${
|
||||||
|
canvasPreviewCursorVisible ? " |" : ""
|
||||||
|
}`;
|
||||||
const totalGeneratedImages = Number(userImageStats?.totalImages ?? NaN);
|
const totalGeneratedImages = Number(userImageStats?.totalImages ?? NaN);
|
||||||
const aiDetailGenerationEnabled = siteSettings?.aiDetailGenerationEnabled === true;
|
const aiDetailGenerationEnabled = siteSettings?.aiDetailGenerationEnabled === true;
|
||||||
const shouldShowCanvasBanner =
|
const shouldShowCanvasBanner =
|
||||||
@ -6008,7 +6127,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
: selectedSkillTag?.placeholder?.trim() ||
|
: selectedSkillTag?.placeholder?.trim() ||
|
||||||
(selectedSkillTag
|
(selectedSkillTag
|
||||||
? `请输入「${selectedSkillTag.name}」相关需求`
|
? `请输入「${selectedSkillTag.name}」相关需求`
|
||||||
: "请输入你的设计需求,例如:绿色椅子 替换 Image");
|
: canvasDynamicPlaceholder || "请输入你的设计需求,例如:绿色椅子 替换 Image");
|
||||||
const detailProductCount = DETAIL_PRODUCT_SLOT_INDEXES.filter((index) => Boolean(detailSlots[index]?.url)).length;
|
const detailProductCount = DETAIL_PRODUCT_SLOT_INDEXES.filter((index) => Boolean(detailSlots[index]?.url)).length;
|
||||||
const detailModelReady = DETAIL_MODEL_SLOT_INDEX >= 0 ? Boolean(detailSlots[DETAIL_MODEL_SLOT_INDEX]?.url) : false;
|
const detailModelReady = DETAIL_MODEL_SLOT_INDEX >= 0 ? Boolean(detailSlots[DETAIL_MODEL_SLOT_INDEX]?.url) : false;
|
||||||
const detailBundleCount = DETAIL_BUNDLE_SLOT_INDEXES.filter((index) => Boolean(detailSlots[index]?.url)).length;
|
const detailBundleCount = DETAIL_BUNDLE_SLOT_INDEXES.filter((index) => Boolean(detailSlots[index]?.url)).length;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user