fix(chat): respect user scroll and tighten markdown

main
sp mac bookpro 2605 2026-05-29 20:43:28 +08:00
parent f7abd99a3e
commit cb115f1a88
3 changed files with 48 additions and 5 deletions

View File

@ -612,6 +612,7 @@ async function consumeSSE(resp: Response, h: StreamEvents, signal?: AbortSignal)
const { value, done } = await reader.read(); const { value, done } = await reader.read();
if (done) break; if (done) break;
buf += decoder.decode(value, { stream: true }); buf += decoder.decode(value, { stream: true });
buf = buf.replace(/\r\n/g, '\n');
let idx; let idx;
while ((idx = buf.indexOf('\n\n')) !== -1) { while ((idx = buf.indexOf('\n\n')) !== -1) {
@ -623,7 +624,11 @@ async function consumeSSE(resp: Response, h: StreamEvents, signal?: AbortSignal)
let dataStr = ''; let dataStr = '';
for (const line of raw.split('\n')) { for (const line of raw.split('\n')) {
if (line.startsWith('event:')) event = line.slice(6).trim(); if (line.startsWith('event:')) event = line.slice(6).trim();
else if (line.startsWith('data:')) dataStr += line.slice(5).trim(); else if (line.startsWith('data:')) {
let part = line.slice(5);
if (part.startsWith(' ')) part = part.slice(1);
dataStr += (dataStr ? '\n' : '') + part;
}
} }
if (!dataStr) continue; if (!dataStr) continue;
let data: any; let data: any;

View File

@ -72,6 +72,7 @@ export default function ChatPage() {
const bodyRef = useRef<HTMLDivElement>(null); const bodyRef = useRef<HTMLDivElement>(null);
const abortRef = useRef<AbortController | null>(null); const abortRef = useRef<AbortController | null>(null);
const creatingSessionRef = useRef(false); const creatingSessionRef = useRef(false);
const autoScrollRef = useRef(true);
// URL 参数 ?session=xxx&msg=yyy // URL 参数 ?session=xxx&msg=yyy
useEffect(() => { useEffect(() => {
@ -88,12 +89,25 @@ export default function ChatPage() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]); }, [searchParams]);
const scrollBottom = () => { const scrollBottom = (force = false) => {
if (!force && !autoScrollRef.current) return;
requestAnimationFrame(() => { requestAnimationFrame(() => {
bodyRef.current?.scrollTo({ top: bodyRef.current.scrollHeight, behavior: 'smooth' }); bodyRef.current?.scrollTo({ top: bodyRef.current.scrollHeight, behavior: 'smooth' });
}); });
}; };
useEffect(() => {
const el = bodyRef.current;
if (!el) return;
const onScroll = () => {
const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
autoScrollRef.current = distance < 32;
};
el.addEventListener('scroll', onScroll, { passive: true });
onScroll();
return () => el.removeEventListener('scroll', onScroll);
}, []);
const loadAgent = async () => { const loadAgent = async () => {
if (!id) { if (!id) {
setAgent(null); setAgent(null);
@ -198,7 +212,7 @@ export default function ChatPage() {
retrieved: [], retrieved: [],
toolCalls: [] toolCalls: []
}); });
scrollBottom(); scrollBottom(true);
abortRef.current?.abort(); abortRef.current?.abort();
const ctrl = new AbortController(); const ctrl = new AbortController();
@ -265,7 +279,7 @@ export default function ChatPage() {
setSessionRefresh((t) => t + 1); setSessionRefresh((t) => t + 1);
setAttachments([]); // 用完即清 setAttachments([]); // 用完即清
setImageUrls([]); setImageUrls([]);
scrollBottom(); scrollBottom(true);
}, },
onAborted: (data) => { onAborted: (data) => {
// 已停止保留已生成内容user 消息也留下(用 tempUser 占位) // 已停止保留已生成内容user 消息也留下(用 tempUser 占位)
@ -322,7 +336,7 @@ export default function ChatPage() {
createdAt: Date.now() createdAt: Date.now()
}; };
setMessages((m) => [...(m || []), tempUser]); setMessages((m) => [...(m || []), tempUser]);
scrollBottom(); scrollBottom(true);
const attText = buildAttachmentsText(); const attText = buildAttachmentsText();
const content = attText ? `${text}\n\n${attText}` : text; const content = attText ? `${text}\n\n${attText}` : text;
const model = overrides.model || parseAgentModels(agent?.model)[0] || ''; const model = overrides.model || parseAgentModels(agent?.model)[0] || '';

View File

@ -539,6 +539,30 @@ body {
margin-bottom: 0; margin-bottom: 0;
} }
.bubble.assistant h1,
.bubble.assistant h2,
.bubble.assistant h3,
.bubble.assistant h4,
.bubble.assistant h5,
.bubble.assistant h6 {
margin: 0.5em 0 0.25em;
line-height: 1.3;
}
.bubble.assistant ol,
.bubble.assistant ul {
margin: 0.4em 0;
padding-left: 1.25em;
}
.bubble.assistant li {
margin: 0.15em 0;
}
.bubble.assistant hr {
margin: 0.8em 0;
}
.chat-input-wrapper { .chat-input-wrapper {
width: 100%; width: 100%;
max-width: 820px; max-width: 820px;