|
|
@@ -81,7 +81,12 @@ export const ChatBotPage = () => {
|
|
|
const [showCancelTooltip, setShowCancelTooltip] = useState(false);
|
|
|
const [showSendTooltip, setShowSendTooltip] = useState(false);
|
|
|
const [showProgressBarTooltip, setShowProgressBarTooltip] = useState(false);
|
|
|
+ const [hasShownProgressBarTooltip, setHasShownProgressBarTooltip] = useState(() => {
|
|
|
+ const stored = getStorage(STORAGE_KEYS.HAS_SHOWN_PROGRESS_BAR_TOOLTIP);
|
|
|
+ return stored === 'true';
|
|
|
+ });
|
|
|
const progressBarTooltipTimerRef = useRef<number | null>(null);
|
|
|
+ const progressBarTooltipRef = useRef<HTMLImageElement | null>(null);
|
|
|
const [isAvatarCUnlocked, setIsAvatarCUnlocked] = useState(() => {
|
|
|
const stored = getStorage(STORAGE_KEYS.AVATAR_C_UNLOCKED);
|
|
|
return stored === 'true';
|
|
|
@@ -125,6 +130,7 @@ export const ChatBotPage = () => {
|
|
|
const initialLayout = calculateLayout();
|
|
|
return initialLayout;
|
|
|
});
|
|
|
+ const [isAvatarGifReady, setIsAvatarGifReady] = useState(false);
|
|
|
|
|
|
const timingRef = useRef({
|
|
|
recordStart: 0,
|
|
|
@@ -156,6 +162,38 @@ export const ChatBotPage = () => {
|
|
|
[unlockAvatarC]
|
|
|
);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ let isCanceled = false;
|
|
|
+ const preloader = new Image();
|
|
|
+
|
|
|
+ const handleLoad = () => {
|
|
|
+ if (!isCanceled) {
|
|
|
+ setIsAvatarGifReady(true);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleError = () => {
|
|
|
+ if (!isCanceled) {
|
|
|
+ setIsAvatarGifReady(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ preloader.addEventListener('load', handleLoad);
|
|
|
+ preloader.addEventListener('error', handleError);
|
|
|
+ preloader.decoding = 'sync';
|
|
|
+ preloader.src = animaZoneAvatarGif;
|
|
|
+
|
|
|
+ if (preloader.complete) {
|
|
|
+ handleLoad();
|
|
|
+ }
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ isCanceled = true;
|
|
|
+ preloader.removeEventListener('load', handleLoad);
|
|
|
+ preloader.removeEventListener('error', handleError);
|
|
|
+ };
|
|
|
+ }, []);
|
|
|
+
|
|
|
const emitStoneEvent = useCallback(
|
|
|
(stoneId: string, event: 'click' | 'timeout', position: EnergyStoneEventPayload) => {
|
|
|
if (!wsRef.current) {
|
|
|
@@ -372,36 +410,70 @@ export const ChatBotPage = () => {
|
|
|
}
|
|
|
}, [currentState]);
|
|
|
|
|
|
- // 监听进度条变化,首次有进度时显示进度条提示(2秒后自动隐藏)
|
|
|
- // 🔧 临时修改:为了UI调试,进度更新时始终显示提示
|
|
|
+ const dismissProgressBarTooltip = useCallback(() => {
|
|
|
+ if (progressBarTooltipTimerRef.current) {
|
|
|
+ clearTimeout(progressBarTooltipTimerRef.current);
|
|
|
+ progressBarTooltipTimerRef.current = null;
|
|
|
+ }
|
|
|
+ if (progressBarTooltipRef.current) {
|
|
|
+ progressBarTooltipRef.current.style.opacity = '0';
|
|
|
+ progressBarTooltipRef.current.style.pointerEvents = 'none';
|
|
|
+ progressBarTooltipRef.current.style.display = 'none';
|
|
|
+ }
|
|
|
+ setShowProgressBarTooltip(false);
|
|
|
+ }, []);
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
- // const hasShownProgressBar = getStorage(STORAGE_KEYS.HAS_SHOWN_PROGRESS_BAR_TOOLTIP);
|
|
|
-
|
|
|
- // // 如果已经显示过,不再处理
|
|
|
- // if (hasShownProgressBar) {
|
|
|
- // return;
|
|
|
- // }
|
|
|
-
|
|
|
- // 当进度条第一次大于0时显示提示(只显示一次)
|
|
|
- const hasShownProgressBarTooltip = getStorage(STORAGE_KEYS.HAS_SHOWN_PROGRESS_BAR_TOOLTIP);
|
|
|
- if (displayProgress > 0 && !showProgressBarTooltip && !hasShownProgressBarTooltip) {
|
|
|
+ if (showProgressBarTooltip && progressBarTooltipRef.current) {
|
|
|
+ progressBarTooltipRef.current.style.display = 'block';
|
|
|
+ progressBarTooltipRef.current.style.opacity = '1';
|
|
|
+ progressBarTooltipRef.current.style.pointerEvents = 'none';
|
|
|
+ }
|
|
|
+ }, [showProgressBarTooltip]);
|
|
|
+
|
|
|
+ // 监听进度条变化,首次有进度时显示进度条提示(只出现一次)
|
|
|
+ useEffect(() => {
|
|
|
+ if (
|
|
|
+ displayProgress > 0 &&
|
|
|
+ !hasShownProgressBarTooltip &&
|
|
|
+ isConnected &&
|
|
|
+ currentState !== 'recording'
|
|
|
+ ) {
|
|
|
setShowProgressBarTooltip(true);
|
|
|
setStorage(STORAGE_KEYS.HAS_SHOWN_PROGRESS_BAR_TOOLTIP, 'true');
|
|
|
-
|
|
|
- // 2秒后自动隐藏
|
|
|
- progressBarTooltipTimerRef.current = window.setTimeout(() => {
|
|
|
- setShowProgressBarTooltip(false);
|
|
|
+ setHasShownProgressBarTooltip(true);
|
|
|
+ }
|
|
|
+ }, [displayProgress, hasShownProgressBarTooltip, isConnected, currentState]);
|
|
|
+
|
|
|
+ // 进度条提示显示后自动隐藏(2 秒)
|
|
|
+ useEffect(() => {
|
|
|
+ if (!showProgressBarTooltip) {
|
|
|
+ if (progressBarTooltipTimerRef.current) {
|
|
|
+ clearTimeout(progressBarTooltipTimerRef.current);
|
|
|
progressBarTooltipTimerRef.current = null;
|
|
|
- }, 2000);
|
|
|
+ }
|
|
|
+ return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ progressBarTooltipTimerRef.current = window.setTimeout(() => {
|
|
|
+ setShowProgressBarTooltip(false);
|
|
|
+ progressBarTooltipTimerRef.current = null;
|
|
|
+ }, 2000);
|
|
|
+
|
|
|
return () => {
|
|
|
if (progressBarTooltipTimerRef.current) {
|
|
|
clearTimeout(progressBarTooltipTimerRef.current);
|
|
|
progressBarTooltipTimerRef.current = null;
|
|
|
}
|
|
|
};
|
|
|
- }, [displayProgress, showProgressBarTooltip]);
|
|
|
+ }, [showProgressBarTooltip]);
|
|
|
+
|
|
|
+ // 录音状态下强制隐藏进度条提示,避免在用户开始录音后残留
|
|
|
+ useEffect(() => {
|
|
|
+ if (currentState === 'recording') {
|
|
|
+ dismissProgressBarTooltip();
|
|
|
+ }
|
|
|
+ }, [currentState, dismissProgressBarTooltip]);
|
|
|
|
|
|
// 监听屏幕尺寸变化
|
|
|
useEffect(() => {
|
|
|
@@ -650,13 +722,7 @@ export const ChatBotPage = () => {
|
|
|
}
|
|
|
|
|
|
// 点击录音按钮时,隐藏进度条提示
|
|
|
- if (showProgressBarTooltip) {
|
|
|
- setShowProgressBarTooltip(false);
|
|
|
- if (progressBarTooltipTimerRef.current) {
|
|
|
- clearTimeout(progressBarTooltipTimerRef.current);
|
|
|
- progressBarTooltipTimerRef.current = null;
|
|
|
- }
|
|
|
- }
|
|
|
+ dismissProgressBarTooltip();
|
|
|
|
|
|
if (isMobileDevice() && !mobileAudioPreparedRef.current) {
|
|
|
try {
|
|
|
@@ -791,7 +857,8 @@ export const ChatBotPage = () => {
|
|
|
const avatarHintSize = Math.round(layout.avatarCSize * 1.8);
|
|
|
|
|
|
const isAvatarAnimating = currentState === 'playing';
|
|
|
- const currentAvatarSrc = isAvatarAnimating ? animaZoneAvatarGif : animaZoneAvatar;
|
|
|
+ const currentAvatarSrc =
|
|
|
+ isAvatarAnimating && isAvatarGifReady ? animaZoneAvatarGif : animaZoneAvatar;
|
|
|
const currentAvatarAlt = isAvatarAnimating ? 'AnimaZone Avatar Animated' : 'AnimaZone Avatar';
|
|
|
const energyStoneAssets: Record<'mission' | 'conversation', string> = {
|
|
|
mission: missionEnergyStone,
|
|
|
@@ -958,6 +1025,8 @@ export const ChatBotPage = () => {
|
|
|
<img
|
|
|
src={currentAvatarSrc}
|
|
|
alt={currentAvatarAlt}
|
|
|
+ loading="eager"
|
|
|
+ decoding="sync"
|
|
|
style={{
|
|
|
width: '100%',
|
|
|
height: '100%',
|
|
|
@@ -1096,6 +1165,7 @@ export const ChatBotPage = () => {
|
|
|
{/* 进度条提示 - 相对于进度环定位 - 首次有进度时显示 */}
|
|
|
{showProgressBarTooltip && isConnected && (
|
|
|
<img
|
|
|
+ ref={progressBarTooltipRef}
|
|
|
src={tooltipsProgressBar}
|
|
|
alt="Progress Bar Tooltip"
|
|
|
className="chatbot-tooltips absolute pointer-events-none"
|