fix(splash): reuse latest chat message preview
This commit is contained in:
@@ -35,6 +35,7 @@ import { useChatUnlockNavigationFlow } from "./hooks/use-chat-unlock-navigation-
|
||||
import { useChatGuestLogin } from "./hooks/use-chat-guest-login";
|
||||
import { useChatMessageLimitBanner } from "./hooks/use-chat-message-limit-banner";
|
||||
import { useChatPromotionBootstrap } from "./hooks/use-chat-promotion-bootstrap";
|
||||
import { useSplashLatestMessageSync } from "./hooks/use-splash-latest-message-sync";
|
||||
|
||||
const chatShellStyle = {
|
||||
"--chat-message-font-size": "clamp(16px, 3.333vw, 18px)",
|
||||
@@ -47,6 +48,11 @@ export function ChatScreen() {
|
||||
const state = useChatState();
|
||||
const authState = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
useSplashLatestMessageSync({
|
||||
historyLoaded: state.historyLoaded,
|
||||
loginStatus: authState.loginStatus,
|
||||
messages: state.historyMessages,
|
||||
});
|
||||
const isPromotionBootstrapReady = useChatPromotionBootstrap();
|
||||
const visibleMessages = isPromotionBootstrapReady
|
||||
? state.messages
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { UiMessage } from "@/data/dto/chat";
|
||||
import { resolveSplashLatestMessageCacheIdentity } from "@/lib/chat/splash_latest_message";
|
||||
import { getLatestSplashMessagePreview } from "@/lib/chat/splash_latest_message_preview";
|
||||
import { useSplashLatestMessageCache } from "@/providers/splash-latest-message-provider";
|
||||
|
||||
export interface UseSplashLatestMessageSyncInput {
|
||||
historyLoaded: boolean;
|
||||
loginStatus: LoginStatus;
|
||||
messages: readonly UiMessage[];
|
||||
}
|
||||
|
||||
export function useSplashLatestMessageSync({
|
||||
historyLoaded,
|
||||
loginStatus,
|
||||
messages,
|
||||
}: UseSplashLatestMessageSyncInput): void {
|
||||
const cache = useSplashLatestMessageCache();
|
||||
const hasObservedInitialSnapshotRef = useRef(false);
|
||||
const identityPromiseRef = useRef<Promise<string | null> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
hasObservedInitialSnapshotRef.current = false;
|
||||
identityPromiseRef.current = null;
|
||||
}, [loginStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!historyLoaded) return;
|
||||
|
||||
if (!hasObservedInitialSnapshotRef.current) {
|
||||
hasObservedInitialSnapshotRef.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const preview = getLatestSplashMessagePreview(messages);
|
||||
let cancelled = false;
|
||||
|
||||
identityPromiseRef.current ??= resolveSplashLatestMessageCacheIdentity();
|
||||
void identityPromiseRef.current.then((identity) => {
|
||||
if (!cancelled && identity) {
|
||||
cache.set(identity, preview);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [cache, historyLoaded, messages]);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getSplashLatestMessagePreview } from "@/lib/chat/splash_latest_message_preview";
|
||||
import {
|
||||
getLatestSplashMessagePreview,
|
||||
getSplashLatestMessagePreview,
|
||||
} from "@/lib/chat/splash_latest_message_preview";
|
||||
|
||||
describe("getSplashLatestMessagePreview", () => {
|
||||
it("uses text content for text messages", () => {
|
||||
@@ -34,6 +37,15 @@ describe("getSplashLatestMessagePreview", () => {
|
||||
).toBe("[图片]");
|
||||
});
|
||||
|
||||
it("uses image placeholder for a mapped UI image message", () => {
|
||||
expect(
|
||||
getSplashLatestMessagePreview({
|
||||
content: "",
|
||||
imageUrl: "https://example.com/image.jpg",
|
||||
}),
|
||||
).toBe("[图片]");
|
||||
});
|
||||
|
||||
it("uses voice placeholder for voice messages", () => {
|
||||
expect(
|
||||
getSplashLatestMessagePreview({
|
||||
@@ -54,6 +66,35 @@ describe("getSplashLatestMessagePreview", () => {
|
||||
).toBe("[语音]");
|
||||
});
|
||||
|
||||
it("uses media placeholders for mapped UI lock reasons", () => {
|
||||
expect(
|
||||
getSplashLatestMessagePreview({
|
||||
content: "",
|
||||
lockReason: "voice_message",
|
||||
}),
|
||||
).toBe("[语音]");
|
||||
expect(
|
||||
getSplashLatestMessagePreview({
|
||||
content: "",
|
||||
lockReason: "image_paywall",
|
||||
}),
|
||||
).toBe("[图片]");
|
||||
});
|
||||
|
||||
it("skips synthetic messages when selecting the latest preview", () => {
|
||||
expect(
|
||||
getLatestSplashMessagePreview([
|
||||
{ content: "Real message" },
|
||||
{ content: "Greeting", isSynthetic: true },
|
||||
]),
|
||||
).toBe("Real message");
|
||||
expect(
|
||||
getLatestSplashMessagePreview([
|
||||
{ content: "Greeting", isSynthetic: true },
|
||||
]),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back when the latest message has no displayable content", () => {
|
||||
expect(
|
||||
getSplashLatestMessagePreview({
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import { fetchSplashLatestMessagePreview } from "@/lib/chat/splash_latest_message";
|
||||
import { loadSplashLatestMessagePreview } from "@/lib/chat/splash_latest_message";
|
||||
import { useSplashLatestMessageCache } from "@/providers/splash-latest-message-provider";
|
||||
import { Logger, Result } from "@/utils";
|
||||
|
||||
const log = new Logger("SplashLatestMessage");
|
||||
@@ -30,6 +31,7 @@ export function useSplashLatestMessage({
|
||||
isAuthLoading,
|
||||
loginStatus,
|
||||
}: UseSplashLatestMessageInput): UseSplashLatestMessageOutput {
|
||||
const cache = useSplashLatestMessageCache();
|
||||
const [state, setState] = useState<SplashLatestMessageState>({
|
||||
key: "",
|
||||
loaded: false,
|
||||
@@ -45,7 +47,7 @@ export function useSplashLatestMessage({
|
||||
let cancelled = false;
|
||||
|
||||
const loadLatestMessage = async () => {
|
||||
const result = await fetchSplashLatestMessagePreview();
|
||||
const result = await loadSplashLatestMessagePreview({ cache });
|
||||
if (cancelled) return;
|
||||
|
||||
if (Result.isErr(result)) {
|
||||
@@ -69,7 +71,7 @@ export function useSplashLatestMessage({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [requestKey, shouldLoad]);
|
||||
}, [cache, requestKey, shouldLoad]);
|
||||
|
||||
if (!shouldLoad) return { message: null, isLoading: false };
|
||||
if (state.key !== requestKey || !state.loaded) {
|
||||
|
||||
Reference in New Issue
Block a user