Commit 8bc9927d by luoqi

fix(web): 助手流式输出"贴底跟随,上滚即停"— 不再和用户滚动竞争

之前每次 messages 变化都无条件 scrollInto, 流式时把上滚阅读的用户一直拽回底部。
改为 stick-to-bottom:onScroll 据是否接近底部(<80px)维护 stickRef,仅贴底时才
scrollTop=scrollHeight 跟随;用户上滚 → 停在阅读位;滚回底部 → 恢复跟随;发起新提问 → 重新贴底。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 0e168281
......@@ -257,16 +257,33 @@ function ModelSelect({
export function AssistantChat() {
const { messages, status, model, setModel, send, stop } = useAssistantChat();
const [input, setInput] = useState('');
const bottomRef = useRef<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const taRef = useRef<HTMLTextAreaElement>(null);
// 是否"贴底":用户在底部附近时跟随自动滚;一旦上滚阅读则停,直到滚回底部。
const stickRef = useRef(true);
// 流式/新消息时,仅在贴底状态才滚到底(否则尊重用户当前阅读位置)。
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
const el = scrollRef.current;
if (el && stickRef.current) el.scrollTop = el.scrollHeight;
}, [messages]);
// 用户滚动 → 据是否接近底部(<80px)更新贴底状态。
const onScroll = () => {
const el = scrollRef.current;
if (!el) return;
stickRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
};
// 发起新提问 → 重新贴底(让用户看到自己的消息 + 回答)。
const fire = (text: string) => {
stickRef.current = true;
void send(text);
};
const submit = () => {
if (!input.trim() || status === 'streaming') return;
void send(input);
fire(input);
setInput('');
if (taRef.current) taRef.current.style.height = 'auto';
};
......@@ -280,10 +297,6 @@ export function AssistantChat() {
<Bot className="h-4 w-4" />
</span>
<h1 className="text-[14px] font-semibold text-slate-900">PAC 助手</h1>
<span className="inline-flex items-center gap-1 rounded-full bg-teal-50 px-1.5 py-0.5 text-[10.5px] text-teal-700 ring-1 ring-inset ring-teal-200">
<Sparkles className="h-3 w-3" />
MCP 患者工具
</span>
<div className="ml-auto">
<ModelSelect model={model} setModel={setModel} disabled={status === 'streaming'} />
</div>
......@@ -291,7 +304,7 @@ export function AssistantChat() {
</header>
{/* Messages */}
<div className="flex-1 overflow-y-auto">
<div ref={scrollRef} onScroll={onScroll} className="flex-1 overflow-y-auto">
<div className="mx-auto max-w-3xl space-y-5 px-4 py-5">
{messages.length === 0 ? (
<div className="flex flex-col items-center justify-center gap-4 py-20 text-center">
......@@ -306,7 +319,7 @@ export function AssistantChat() {
<button
key={ex}
type="button"
onClick={() => send(ex)}
onClick={() => fire(ex)}
className="rounded-full border border-slate-200 bg-white px-3 py-1.5 text-[11.5px] text-slate-600 transition-colors hover:border-teal-300 hover:bg-teal-50/50 hover:text-teal-700"
>
{ex}
......@@ -317,7 +330,6 @@ export function AssistantChat() {
) : (
messages.map((m) => <MessageView key={m.id} message={m} />)
)}
<div ref={bottomRef} />
</div>
</div>
......@@ -368,7 +380,7 @@ export function AssistantChat() {
)}
</div>
<p className="mt-1.5 text-center text-[10.5px] text-slate-400">
PAC 助手经 MCP 调用患者画像 / 事实 / 召回工具 · 结果仅供参考,请核对后使用
结果仅供参考,请核对后使用
</p>
</div>
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment