|
@@ -300,13 +300,16 @@ export const ChatBotPage = () => {
|
|
|
pendingGreetingRef.current = false;
|
|
pendingGreetingRef.current = false;
|
|
|
hasGreetingStartedRef.current = true;
|
|
hasGreetingStartedRef.current = true;
|
|
|
const requestId = `greeting_${uuidv4().replace(/-/g, '').slice(0, 8)}`;
|
|
const requestId = `greeting_${uuidv4().replace(/-/g, '').slice(0, 8)}`;
|
|
|
- const forceScene = searchParams.get('scene');
|
|
|
|
|
- const validScene = ['default', 'window', 'reading', 'eating'].includes(forceScene || '');
|
|
|
|
|
- debugLog('[首句] 发送 start_greeting 消息', { requestId, forceScene: validScene ? forceScene : undefined });
|
|
|
|
|
|
|
+ const rawScene = searchParams.get('scene');
|
|
|
|
|
+ const forceScene: 'default' | 'window' | 'reading' | 'eating' | undefined =
|
|
|
|
|
+ rawScene === 'default' || rawScene === 'window' || rawScene === 'reading' || rawScene === 'eating'
|
|
|
|
|
+ ? rawScene
|
|
|
|
|
+ : undefined;
|
|
|
|
|
+ debugLog('[首句] 发送 start_greeting 消息', { requestId, forceScene });
|
|
|
ws.send({
|
|
ws.send({
|
|
|
type: 'start_greeting',
|
|
type: 'start_greeting',
|
|
|
requestId,
|
|
requestId,
|
|
|
- ...(validScene && forceScene ? { data: { forceScene } } : {}),
|
|
|
|
|
|
|
+ ...(forceScene ? { data: { forceScene } } : {}),
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
const [conversationStoneCount, setConversationStoneCount] = useState<number>(0);
|
|
const [conversationStoneCount, setConversationStoneCount] = useState<number>(0);
|
|
@@ -693,25 +696,53 @@ export const ChatBotPage = () => {
|
|
|
prevAvatarStateRef.current = currentState;
|
|
prevAvatarStateRef.current = currentState;
|
|
|
}, [currentState, showDefaultGif]);
|
|
}, [currentState, showDefaultGif]);
|
|
|
|
|
|
|
|
- // 预加载动画形象的 GIF
|
|
|
|
|
|
|
+ // 预加载动画形象的 GIF:首帧优先 default,其余情绪 GIF 延迟加载,减少首屏争抢带宽
|
|
|
const preloadAvatarGifs = useCallback(
|
|
const preloadAvatarGifs = useCallback(
|
|
|
(avatarId: string | null) => {
|
|
(avatarId: string | null) => {
|
|
|
const gifSet = resolveAvatarGifSet(avatarId);
|
|
const gifSet = resolveAvatarGifSet(avatarId);
|
|
|
- const gifUrls = [gifSet.default, gifSet.positive, gifSet.negative, gifSet.talking];
|
|
|
|
|
-
|
|
|
|
|
- // 使用 Image 对象预加载所有 GIF
|
|
|
|
|
- gifUrls.forEach((url) => {
|
|
|
|
|
- if (url) {
|
|
|
|
|
- const img = new Image();
|
|
|
|
|
- img.src = url;
|
|
|
|
|
- // 静默处理加载错误,避免影响主流程
|
|
|
|
|
- img.onerror = () => {
|
|
|
|
|
- console.warn(`[预加载] GIF 加载失败: ${url}`);
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const defaultUrl = gifSet.default;
|
|
|
|
|
+ const emotionUrls = [gifSet.positive, gifSet.negative, gifSet.talking].filter(Boolean);
|
|
|
|
|
+
|
|
|
|
|
+ if (defaultUrl) {
|
|
|
|
|
+ const img = new Image();
|
|
|
|
|
+ img.src = defaultUrl;
|
|
|
|
|
+ img.onerror = () => {
|
|
|
|
|
+ console.warn(`[预加载] 默认 GIF 加载失败: ${defaultUrl}`);
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const scheduleRest = () => {
|
|
|
|
|
+ if (typeof requestIdleCallback !== 'undefined') {
|
|
|
|
|
+ requestIdleCallback(
|
|
|
|
|
+ () => {
|
|
|
|
|
+ emotionUrls.forEach((url) => {
|
|
|
|
|
+ if (url) {
|
|
|
|
|
+ const img = new Image();
|
|
|
|
|
+ img.src = url;
|
|
|
|
|
+ img.onerror = () => {
|
|
|
|
|
+ console.warn(`[预加载] GIF 加载失败: ${url}`);
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ { timeout: 2000 }
|
|
|
|
|
+ );
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ emotionUrls.forEach((url) => {
|
|
|
|
|
+ if (url) {
|
|
|
|
|
+ const img = new Image();
|
|
|
|
|
+ img.src = url;
|
|
|
|
|
+ img.onerror = () => {
|
|
|
|
|
+ console.warn(`[预加载] GIF 加载失败: ${url}`);
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }, 500);
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- debugLog(`[预加载] 已开始预加载形象 ${avatarId || 'default'} 的所有 GIF`);
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ scheduleRest();
|
|
|
|
|
+ debugLog(`[预加载] 已预加载默认 GIF,情绪 GIF 已排队延迟加载`);
|
|
|
},
|
|
},
|
|
|
[resolveAvatarGifSet]
|
|
[resolveAvatarGifSet]
|
|
|
);
|
|
);
|
|
@@ -2665,6 +2696,9 @@ export const ChatBotPage = () => {
|
|
|
const requestId = uuidv4();
|
|
const requestId = uuidv4();
|
|
|
performanceLogger.startRequest(requestId);
|
|
performanceLogger.startRequest(requestId);
|
|
|
updateRequestId(requestId);
|
|
updateRequestId(requestId);
|
|
|
|
|
+ // 录音 UI 会显示 audio-wave.gif,提前预加载避免首次点击录音时闪烁
|
|
|
|
|
+ const waveImg = new Image();
|
|
|
|
|
+ waveImg.src = audioWaveGif;
|
|
|
setState('recording');
|
|
setState('recording');
|
|
|
chunkIndexRef.current = 0;
|
|
chunkIndexRef.current = 0;
|
|
|
// 字幕已在上面点击录音时隐藏并清空
|
|
// 字幕已在上面点击录音时隐藏并清空
|
|
@@ -2961,6 +2995,8 @@ export const ChatBotPage = () => {
|
|
|
alt={tCommon('a11y.trees')}
|
|
alt={tCommon('a11y.trees')}
|
|
|
className="chatbot-trees absolute pointer-events-none"
|
|
className="chatbot-trees absolute pointer-events-none"
|
|
|
style={getTreesImageStyle()}
|
|
style={getTreesImageStyle()}
|
|
|
|
|
+ fetchPriority="low"
|
|
|
|
|
+ loading="lazy"
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
@@ -2969,6 +3005,7 @@ export const ChatBotPage = () => {
|
|
|
src={getOssUrl('stool.png')}
|
|
src={getOssUrl('stool.png')}
|
|
|
alt=""
|
|
alt=""
|
|
|
className="absolute pointer-events-none"
|
|
className="absolute pointer-events-none"
|
|
|
|
|
+ fetchPriority="low"
|
|
|
style={{
|
|
style={{
|
|
|
width: px2vmin(STOOL_LAYOUT.width),
|
|
width: px2vmin(STOOL_LAYOUT.width),
|
|
|
height: px2vmin(STOOL_LAYOUT.height),
|
|
height: px2vmin(STOOL_LAYOUT.height),
|
|
@@ -2984,11 +3021,12 @@ export const ChatBotPage = () => {
|
|
|
{greetingContext?.scene &&
|
|
{greetingContext?.scene &&
|
|
|
greetingContext.scene !== 'default' &&
|
|
greetingContext.scene !== 'default' &&
|
|
|
!specialSceneAvatarDismissed &&
|
|
!specialSceneAvatarDismissed &&
|
|
|
- SPECIAL_SCENE_GIF[greetingContext.scene] && (
|
|
|
|
|
|
|
+ SPECIAL_SCENE_GIF[greetingContext.scene as keyof typeof SPECIAL_SCENE_GIF] && (
|
|
|
<img
|
|
<img
|
|
|
- src={SPECIAL_SCENE_GIF[greetingContext.scene]}
|
|
|
|
|
|
|
+ src={SPECIAL_SCENE_GIF[greetingContext.scene as keyof typeof SPECIAL_SCENE_GIF]}
|
|
|
alt=""
|
|
alt=""
|
|
|
className="absolute pointer-events-none"
|
|
className="absolute pointer-events-none"
|
|
|
|
|
+ loading="lazy"
|
|
|
style={{
|
|
style={{
|
|
|
width: px2vmin(STOOL_LAYOUT.sceneGifSize),
|
|
width: px2vmin(STOOL_LAYOUT.sceneGifSize),
|
|
|
height: px2vmin(STOOL_LAYOUT.sceneGifSize),
|
|
height: px2vmin(STOOL_LAYOUT.sceneGifSize),
|
|
@@ -3325,7 +3363,7 @@ export const ChatBotPage = () => {
|
|
|
);
|
|
);
|
|
|
})}
|
|
})}
|
|
|
|
|
|
|
|
- {/* AnimaZone 动画形象 - 场景未确定前不显示,避免特殊场景下「先出现再消失」;确定后:默认场景直接显示,特殊场景下未点击录音时隐藏、点击录音后显示 */}
|
|
|
|
|
|
|
+ {/* AnimaZone 动画形象 - 仅场景解析后且为默认(或特殊场景已点录音收起)时显示,避免特殊场景下先出现再消失 */}
|
|
|
{hasGreetingScenarioResolved && !(greetingContext?.scene && greetingContext.scene !== 'default' && !specialSceneAvatarDismissed) && (
|
|
{hasGreetingScenarioResolved && !(greetingContext?.scene && greetingContext.scene !== 'default' && !specialSceneAvatarDismissed) && (
|
|
|
<div
|
|
<div
|
|
|
className="chatbot-anima-zone-avatar absolute"
|
|
className="chatbot-anima-zone-avatar absolute"
|
|
@@ -3486,7 +3524,7 @@ export const ChatBotPage = () => {
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* Yp 图片 - 设计稿 393×852,相对动画形象顶部 191,64×14;场景确定后且非特殊场景未点录音时才显示 */}
|
|
|
|
|
|
|
+ {/* Yp 图片 - 设计稿 393×852,相对动画形象顶部 191,64×14;与中间动画同显隐(仅默认或特殊已收起时显示) */}
|
|
|
{hasGreetingScenarioResolved && !(greetingContext?.scene && greetingContext.scene !== 'default' && !specialSceneAvatarDismissed) && (
|
|
{hasGreetingScenarioResolved && !(greetingContext?.scene && greetingContext.scene !== 'default' && !specialSceneAvatarDismissed) && (
|
|
|
<div
|
|
<div
|
|
|
className="absolute"
|
|
className="absolute"
|