refactor: migrate img tags to next/image and fix hook usage

This commit is contained in:
2026-06-10 19:16:41 +08:00
parent 47591be41c
commit 2bc2e1b691
30 changed files with 62 additions and 55 deletions
@@ -12,7 +12,7 @@
* - 生产环境仅在应用内浏览器中显示
* - 视觉:右上角 👆 + 模糊背景气泡
*/
import { useEffect, useState } from "react";
import { useState } from "react";
import styles from "./browser-hint-overlay.module.css";
@@ -36,11 +36,8 @@ export function BrowserHintOverlay({
text = DEFAULT_TEXT,
forceShow = false,
}: BrowserHintOverlayProps) {
const [isInAppBrowser, setIsInAppBrowser] = useState(false);
useEffect(() => {
setIsInAppBrowser(detectInAppBrowser());
}, []);
// lazy initializer:首次渲染即计算(避免 useEffect 中调 setState 的 lint 错)
const [isInAppBrowser] = useState(detectInAppBrowser);
// 开发模式或应用内浏览器才显示
const shouldShow = forceShow || isInAppBrowser;
+6 -8
View File
@@ -75,11 +75,11 @@ function renderMessagesWithDateHeaders(messages: readonly UiMessage[]) {
<DateHeader key={item.key} date={item.date} />
) : (
<MessageBubble
key={item.key}
content={item.message.content}
imageUrl={item.message.imageUrl}
isFromAI={item.message.isFromAI}
/>
key={item.key}
content={item.message.content}
imageUrl={item.message.imageUrl}
isFromAI={item.message.isFromAI}
/>
),
);
}
@@ -87,7 +87,7 @@ function renderMessagesWithDateHeaders(messages: readonly UiMessage[]) {
function AiDisclosure() {
return (
<div className={styles.aiDisclosure}>
You're chatting with an AI companion.
{'You' + String.fromCharCode(39) + 're chatting with an AI companion.'}
</div>
);
}
@@ -101,5 +101,3 @@ function EmptyState({ isGuest }: { isGuest: boolean }) {
</div>
);
}
@@ -9,6 +9,7 @@
* - 点击空白 / 下滑手势 → 关闭
* - 双指缩放(CSS `touch-action: pinch-zoom`
*/
import Image from "next/image";
import { useEffect } from "react";
import styles from "./fullscreen-image-viewer.module.css";
@@ -34,10 +35,12 @@ export function FullscreenImageViewer({ imageUrl, onClose }: FullscreenImageView
role="dialog"
aria-label="Fullscreen image"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
<Image
src={imageUrl.startsWith("data:") || imageUrl.startsWith("http") ? imageUrl : `data:image/png;base64,${imageUrl}`}
alt=""
width={800}
height={800}
className={styles.viewerImage}
onError={(e) => {
(e.currentTarget as HTMLImageElement).style.display = "none";
(e.currentTarget.parentElement as HTMLElement).innerHTML =
+4 -2
View File
@@ -9,6 +9,7 @@
* - 点击 → 打开全屏查看器
* - 错误占位符(broken image icon
*/
import Image from "next/image";
import { useState } from "react";
import { FullscreenImageViewer } from "./fullscreen-image-viewer";
@@ -42,11 +43,12 @@ export function ImageBubble({ imageUrl }: ImageBubbleProps) {
{error ? (
<div className={styles.errorFallback}>🖼</div>
) : (
// eslint-disable-next-line @next/next/no-img-element
<img
<Image
className={styles.image}
src={src}
alt=""
width={240}
height={240}
onError={() => setError(true)}
/>
)}
+1 -2
View File
@@ -36,8 +36,7 @@ export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) {
if (userAvatarUrl && userAvatarUrl.length > 0) {
return (
<div className={styles.avatar} aria-label="User avatar">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={userAvatarUrl} alt="" />
<Image src={userAvatarUrl} alt="" width={43} height={43} />
</div>
);
}