fixed reasoning
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { FormEvent, useEffect, useRef, useState } from "react";
|
||||
import { FormEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { api, ChatMessage, ChatSession } from "../api/client";
|
||||
import PomodoroWidget from "../components/PomodoroWidget";
|
||||
@@ -42,7 +42,9 @@ export default function Chat() {
|
||||
"thinking",
|
||||
);
|
||||
const [liveNotices, setLiveNotices] = useState<string[]>([]);
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
const messagesRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const scrollRafRef = useRef<number | null>(null);
|
||||
const { status: pomodoroStatus, refresh: refreshPomodoro } = usePomodoro();
|
||||
const [lastNotifySeq, setLastNotifySeq] = useState(0);
|
||||
|
||||
@@ -69,9 +71,33 @@ export default function Chat() {
|
||||
}
|
||||
}, [activeId]);
|
||||
|
||||
const scrollToBottom = useCallback((smooth = false) => {
|
||||
const container = messagesRef.current;
|
||||
if (!container) return;
|
||||
container.scrollTo({
|
||||
top: container.scrollHeight,
|
||||
behavior: smooth ? "smooth" : "auto",
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages, streaming, liveNotices, loading]);
|
||||
if (scrollRafRef.current !== null) {
|
||||
cancelAnimationFrame(scrollRafRef.current);
|
||||
}
|
||||
scrollRafRef.current = requestAnimationFrame(() => {
|
||||
scrollToBottom(!streaming);
|
||||
scrollRafRef.current = null;
|
||||
});
|
||||
return () => {
|
||||
if (scrollRafRef.current !== null) {
|
||||
cancelAnimationFrame(scrollRafRef.current);
|
||||
}
|
||||
};
|
||||
}, [messages, streaming, liveNotices, loading, scrollToBottom]);
|
||||
|
||||
const dismissKeyboard = useCallback(() => {
|
||||
inputRef.current?.blur();
|
||||
}, []);
|
||||
|
||||
const waitingForStream = loading && !streaming;
|
||||
const pendingLabel =
|
||||
@@ -119,6 +145,7 @@ export default function Chat() {
|
||||
|
||||
const text = input.trim();
|
||||
setInput("");
|
||||
dismissKeyboard();
|
||||
setLoading(true);
|
||||
setStreaming("");
|
||||
setPendingPhase("thinking");
|
||||
@@ -215,7 +242,29 @@ export default function Chat() {
|
||||
<div className="chat-empty">Создайте новый чат, чтобы начать</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="messages">
|
||||
<div className="chat-mobile-bar">
|
||||
<select
|
||||
className="chat-session-select"
|
||||
value={activeId}
|
||||
onChange={(e) => setActiveId(Number(e.target.value))}
|
||||
aria-label="Выбор чата"
|
||||
>
|
||||
{sessions.map((session) => (
|
||||
<option key={session.id} value={session.id}>
|
||||
{session.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button type="button" className="chat-mobile-new" onClick={handleNewChat}>
|
||||
+ Новый
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="messages"
|
||||
ref={messagesRef}
|
||||
onClick={dismissKeyboard}
|
||||
>
|
||||
{visibleMessages.map((msg) => (
|
||||
<div key={msg.id} className={`message message-${msg.role}`}>
|
||||
<div className="message-role">{roleLabel(msg.role, msg.content)}</div>
|
||||
@@ -260,15 +309,18 @@ export default function Chat() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div ref={bottomRef} />
|
||||
<div className="messages-bottom-anchor" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<form className="chat-input" onSubmit={handleSubmit}>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Напишите сообщение..."
|
||||
rows={2}
|
||||
enterKeyHint="send"
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user