diff --git a/src/features/profile/ProfilePage.tsx b/src/features/profile/ProfilePage.tsx index 3add68c..fa509d9 100644 --- a/src/features/profile/ProfilePage.tsx +++ b/src/features/profile/ProfilePage.tsx @@ -235,6 +235,9 @@ function ProfilePage({ const packageLabel = session?.user.activePackages?.[0]?.name || "按量积分"; const avatarUrl = session?.user.avatarUrl || localAvatarUrl || null; const displayedBio = profileBio.trim() || "这个人还没有填写个性签名"; + const emailLooksValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); + const phoneLooksValid = /^1[3-9]\d{9}$/.test(phone.trim()); + const passwordLooksReady = password.length >= (mode === "register" ? 6 : 1); useEffect(() => { setLocalAvatarUrl(session?.user.avatarUrl || readLocalProfileValue(userId, "avatar")); @@ -812,12 +815,30 @@ function ProfilePage({
-

OmniAI

-

一句话,从创意到成片

-
- AI 视频生成 - AI 图片创作 - AI 电商素材 +
+
+

OmniAI

+
+

一句话,从创意到成片

+
+ AI 视频生成 + AI 图片创作 + AI 电商素材 +
+
+ + Studio + 创作工作台 + + + Assets + 资产沉淀 + + + Team + 团队协作 + +
@@ -829,6 +850,7 @@ function ProfilePage({ OmniAI + {mode === "login" ? "账户登录" : "新用户注册"}

{mode === "login" ? "欢迎回来" : "创建账号"}

{mode === "login" ? "登录后继续你的 AI 创作之旅" : "注册即可免费体验全部功能"} @@ -868,7 +890,8 @@ function ProfilePage({ 邮箱 @@ -924,6 +947,11 @@ function ProfilePage({ autoComplete={mode === "login" ? "current-password" : "new-password"} /> {fieldErrors.password ? {fieldErrors.password} : null} + {mode === "register" && passwordLooksReady && !fieldErrors.password ? ( + + 密码长度符合要求 + + ) : null} {mode === "login" ? (

@@ -961,6 +989,11 @@ function ProfilePage({ autoComplete="email" /> {fieldErrors.email ? {fieldErrors.email} : null} + {emailLooksValid && !fieldErrors.email ? ( + + 邮箱格式正确 + + ) : null} ) : null} @@ -990,6 +1028,11 @@ function ProfilePage({ { setPhone(event.target.value); clearFieldError("phone"); }} onBlur={() => handleFieldBlur("phone", phone)} placeholder="输入手机号" autoComplete="tel" />
{fieldErrors.phone ? {fieldErrors.phone} : null} + {phoneLooksValid && !fieldErrors.phone ? ( + + 手机号格式正确 + + ) : null} ) : null} diff --git a/src/features/workbench/WorkbenchPage.tsx b/src/features/workbench/WorkbenchPage.tsx index 9777ba8..7979e62 100644 --- a/src/features/workbench/WorkbenchPage.tsx +++ b/src/features/workbench/WorkbenchPage.tsx @@ -236,6 +236,7 @@ function WorkbenchPage({ const keepaliveTasksRef = useRef>(readStoredKeepaliveTasks()); const taskAbortControllersRef = useRef>(new Map()); const lastScrollTopRef = useRef(0); + const scrollActionsHideTimerRef = useRef(null); const shouldFollowNewMessagesRef = useRef(true); const pendingScrollToLatestRef = useRef(true); const renderedMessageIdsRef = useRef([]); @@ -273,6 +274,8 @@ function WorkbenchPage({ const [promptSelectionRange, setPromptSelectionRange] = useState({ start: 0, end: 0 }); const [mentionActiveIndex, setMentionActiveIndex] = useState(0); const [composerHidden, setComposerHidden] = useState(false); + const [scrollActionsVisible, setScrollActionsVisible] = useState(false); + const [scrollActionDirection, setScrollActionDirection] = useState<"top" | "bottom" | null>(null); const [workspaceStarted, setWorkspaceStarted] = useState(false); useEffect(() => { @@ -441,6 +444,27 @@ function WorkbenchPage({ "--accent-glow": `0 0 24px rgba(${accentRgb}, 0.22)`, } as CSSProperties; + const revealScrollActionsTemporarily = useCallback((direction: "top" | "bottom") => { + setScrollActionDirection(direction); + setScrollActionsVisible(true); + if (scrollActionsHideTimerRef.current !== null) { + window.clearTimeout(scrollActionsHideTimerRef.current); + } + scrollActionsHideTimerRef.current = window.setTimeout(() => { + setScrollActionsVisible(false); + setScrollActionDirection(null); + scrollActionsHideTimerRef.current = null; + }, 950); + }, []); + + useEffect(() => { + return () => { + if (scrollActionsHideTimerRef.current !== null) { + window.clearTimeout(scrollActionsHideTimerRef.current); + } + }; + }, []); + const scrollMessagesToLatest = useCallback((behavior: ScrollBehavior = "smooth") => { const scroll = () => { const surface = messagesSurfaceRef.current; @@ -451,6 +475,7 @@ function WorkbenchPage({ setComposerHidden(false); shouldFollowNewMessagesRef.current = true; + revealScrollActionsTemporarily("bottom"); surface.scrollTo({ top: surface.scrollHeight, behavior }); lastScrollTopRef.current = surface.scrollTop; }; @@ -459,7 +484,7 @@ function WorkbenchPage({ scroll(); window.setTimeout(scroll, 80); }); - }, []); + }, [revealScrollActionsTemporarily]); const imageSettingGroups = useMemo( () => [ @@ -1373,6 +1398,9 @@ function WorkbenchPage({ const delta = top - lastScrollTopRef.current; const atTop = top <= edgeThreshold; const atBottom = top + surface.clientHeight >= surface.scrollHeight - edgeThreshold; + if (surface.scrollHeight > surface.clientHeight + edgeThreshold && Math.abs(delta) > 1) { + revealScrollActionsTemporarily(delta > 0 ? "bottom" : "top"); + } shouldFollowNewMessagesRef.current = atBottom; if (atTop || atBottom) { setComposerHidden(false); @@ -1384,7 +1412,7 @@ function WorkbenchPage({ surface.addEventListener("scroll", handleScroll, { passive: true }); return () => surface.removeEventListener("scroll", handleScroll); - }, [hasActivatedWorkspace]); + }, [hasActivatedWorkspace, revealScrollActionsTemporarily]); const scrollMessagesSurface = useCallback((direction: "top" | "bottom") => { const surface = messagesSurfaceRef.current; @@ -1392,8 +1420,9 @@ function WorkbenchPage({ const top = direction === "top" ? 0 : surface.scrollHeight; setComposerHidden(false); + revealScrollActionsTemporarily(direction); surface.scrollTo({ top, behavior: "smooth" }); - }, []); + }, [revealScrollActionsTemporarily]); const closeToolbarMenus = () => setToolbarMenuId(null); const toggleToolbarMenu = (menuId: Exclude) => { @@ -3022,10 +3051,13 @@ function WorkbenchPage({ {renderComposerToolbar(false, isGenerating)} -
+