diff --git a/web/index.html b/web/index.html
index 78b3465..c596841 100644
--- a/web/index.html
+++ b/web/index.html
@@ -4,6 +4,7 @@
PicoBot
+
diff --git a/web/public/favicon.svg b/web/public/favicon.svg
new file mode 100644
index 0000000..db7bc7a
--- /dev/null
+++ b/web/public/favicon.svg
@@ -0,0 +1,3 @@
+
diff --git a/web/src/components/Chat/MessageList.tsx b/web/src/components/Chat/MessageList.tsx
index 3f5ddb4..fcef3f3 100644
--- a/web/src/components/Chat/MessageList.tsx
+++ b/web/src/components/Chat/MessageList.tsx
@@ -15,6 +15,8 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
const prevShowBottomRef = useRef(false)
const prevShowTopRef = useRef(false)
const lastMessageCountRef = useRef(0)
+ const prevMessageCountRef = useRef(0)
+ const stickyLockUntilRef = useRef(0)
const isProgrammaticScrollRef = useRef(false)
const scrollTimerRef = useRef(0)
@@ -28,6 +30,12 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
const el = containerRef.current
if (!el) return
+ // sticky lock 期间:防止中间 DOM 状态的 scroll 事件打断历史加载
+ if (Date.now() < stickyLockUntilRef.current) {
+ isStickyRef.current = true
+ return
+ }
+
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight
const nearBottom = distanceFromBottom < 120
@@ -81,13 +89,31 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
// ---- auto-scroll effect ----
useEffect(() => {
- if (messages.length === 0) return
+ // 消息清空 → 重置所有追踪状态(话题/会话切换)
+ if (messages.length === 0) {
+ prevMessageCountRef.current = 0
+ lastMessageCountRef.current = 0
+ isStickyRef.current = true
+ return
+ }
const lastMessage = messages[messages.length - 1]
+ const isFreshLoad = prevMessageCountRef.current === 0 && messages.length > 0
// 用户自己发的消息 → 始终滚到底部
if (lastMessage.role === 'user') {
scrollToBottom('instant')
+ prevMessageCountRef.current = messages.length
+ return
+ }
+
+ // 新加载(话题切换后首次收到消息)→ 强制滚到底部
+ if (isFreshLoad) {
+ isStickyRef.current = true
+ lastMessageCountRef.current = 0
+ stickyLockUntilRef.current = Date.now() + 1500
+ scrollToBottom('instant')
+ prevMessageCountRef.current = messages.length
return
}
@@ -102,6 +128,7 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
}
lastMessageCountRef.current = messages.length
+ prevMessageCountRef.current = messages.length
}, [messages, scrollToBottom])
// ---- ResizeObserver: 窗口大小变化时保持底部对齐 ----
@@ -119,6 +146,15 @@ export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps
return () => observer.disconnect()
}, [])
+ // ---- 组件挂载时滚动到底部 ----
+
+ useEffect(() => {
+ if (messages.length > 0) {
+ scrollToBottom('instant')
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
// ---- 清理定时器 ----
useEffect(() => {