Fix memory leak: throttle streaming updates, remove console.logs
This commit is contained in:
+28
-26
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef, useCallback } from "react";
|
||||||
import { useParams, Link, useSearchParams } from "react-router-dom";
|
import { useParams, Link, useSearchParams } from "react-router-dom";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
@@ -69,6 +69,26 @@ export default function GamePage() {
|
|||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
// Refs for throttled streaming updates
|
||||||
|
const streamingBufferRef = useRef("");
|
||||||
|
const lastUpdateRef = useRef(0);
|
||||||
|
|
||||||
|
// Throttled streaming update (every 50ms instead of every chunk)
|
||||||
|
const updateStreamingContent = useCallback((chunk: string) => {
|
||||||
|
streamingBufferRef.current += chunk;
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastUpdateRef.current > 50) {
|
||||||
|
setStreamingContent(streamingBufferRef.current);
|
||||||
|
lastUpdateRef.current = now;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Flush remaining content
|
||||||
|
const flushStreamingContent = useCallback(() => {
|
||||||
|
if (streamingBufferRef.current) {
|
||||||
|
setStreamingContent(streamingBufferRef.current);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadGame = async () => {
|
const loadGame = async () => {
|
||||||
@@ -87,7 +107,6 @@ export default function GamePage() {
|
|||||||
|
|
||||||
// Загружаем список сессий
|
// Загружаем список сессий
|
||||||
const sessions = await getSessionsList(id);
|
const sessions = await getSessionsList(id);
|
||||||
console.log("[GamePage] Sessions list:", sessions);
|
|
||||||
setSessionsList(sessions);
|
setSessionsList(sessions);
|
||||||
|
|
||||||
const characterId = searchParams.get("character");
|
const characterId = searchParams.get("character");
|
||||||
@@ -109,10 +128,8 @@ export default function GamePage() {
|
|||||||
// Если есть сессии, загружаем последнюю (или создаём новую)
|
// Если есть сессии, загружаем последнюю (или создаём новую)
|
||||||
if (sessions.length > 0) {
|
if (sessions.length > 0) {
|
||||||
const latestSession = sessions[0];
|
const latestSession = sessions[0];
|
||||||
console.log("[GamePage] Loading latest session:", latestSession);
|
|
||||||
setCurrentSessionId(latestSession.id);
|
setCurrentSessionId(latestSession.id);
|
||||||
const sessionData = await getSession(id, latestSession.id);
|
const sessionData = await getSession(id, latestSession.id);
|
||||||
console.log("[GamePage] Session data loaded:", sessionData);
|
|
||||||
if (sessionData) {
|
if (sessionData) {
|
||||||
setSession(sessionData);
|
setSession(sessionData);
|
||||||
// Загружаем персонажа: приоритет URL > сессия
|
// Загружаем персонажа: приоритет URL > сессия
|
||||||
@@ -170,7 +187,6 @@ export default function GamePage() {
|
|||||||
id: (updatedStory as any)._id || updatedStory.id,
|
id: (updatedStory as any)._id || updatedStory.id,
|
||||||
};
|
};
|
||||||
setStory(normalizedStory);
|
setStory(normalizedStory);
|
||||||
console.log("[GamePage] История обновлена после возврата на страницу");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -285,6 +301,7 @@ export default function GamePage() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setStreamingContent("");
|
setStreamingContent("");
|
||||||
|
streamingBufferRef.current = "";
|
||||||
|
|
||||||
// Создаём AbortController для возможности отмены
|
// Создаём AbortController для возможности отмены
|
||||||
abortControllerRef.current = new AbortController();
|
abortControllerRef.current = new AbortController();
|
||||||
@@ -295,13 +312,12 @@ export default function GamePage() {
|
|||||||
story,
|
story,
|
||||||
session.messages,
|
session.messages,
|
||||||
input.trim(),
|
input.trim(),
|
||||||
(chunk) => {
|
updateStreamingContent,
|
||||||
setStreamingContent((prev) => prev + chunk);
|
|
||||||
},
|
|
||||||
playerCharacter || undefined,
|
playerCharacter || undefined,
|
||||||
session,
|
session,
|
||||||
abortControllerRef.current.signal,
|
abortControllerRef.current.signal,
|
||||||
);
|
);
|
||||||
|
flushStreamingContent();
|
||||||
|
|
||||||
const assistantMessage: ChatMessage = {
|
const assistantMessage: ChatMessage = {
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
@@ -321,7 +337,6 @@ export default function GamePage() {
|
|||||||
// Генерируем сводку каждые 20 сообщений
|
// Генерируем сводку каждые 20 сообщений
|
||||||
let newSummary = session.storySummary;
|
let newSummary = session.storySummary;
|
||||||
if (allMessages.length % 20 === 0 && allMessages.length > 0) {
|
if (allMessages.length % 20 === 0 && allMessages.length > 0) {
|
||||||
console.log("[GamePage] Generating story summary...");
|
|
||||||
newSummary = await generateStorySummary(
|
newSummary = await generateStorySummary(
|
||||||
story,
|
story,
|
||||||
allMessages,
|
allMessages,
|
||||||
@@ -336,17 +351,11 @@ export default function GamePage() {
|
|||||||
storySummary: newSummary,
|
storySummary: newSummary,
|
||||||
};
|
};
|
||||||
|
|
||||||
const saved = await apiSaveSession(
|
await apiSaveSession(
|
||||||
story.id,
|
story.id,
|
||||||
currentSessionId,
|
currentSessionId,
|
||||||
finalSession,
|
finalSession,
|
||||||
);
|
);
|
||||||
console.log(
|
|
||||||
"[GamePage] Session saved:",
|
|
||||||
saved,
|
|
||||||
"Messages:",
|
|
||||||
allMessages.length,
|
|
||||||
);
|
|
||||||
setSession(finalSession);
|
setSession(finalSession);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error && err.name === "AbortError") {
|
if (err instanceof Error && err.name === "AbortError") {
|
||||||
@@ -466,6 +475,7 @@ export default function GamePage() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setStreamingContent("");
|
setStreamingContent("");
|
||||||
|
streamingBufferRef.current = "";
|
||||||
|
|
||||||
abortControllerRef.current = new AbortController();
|
abortControllerRef.current = new AbortController();
|
||||||
|
|
||||||
@@ -475,13 +485,12 @@ export default function GamePage() {
|
|||||||
story,
|
story,
|
||||||
messagesUpToEdit,
|
messagesUpToEdit,
|
||||||
editContent.trim(),
|
editContent.trim(),
|
||||||
(chunk) => {
|
updateStreamingContent,
|
||||||
setStreamingContent((prev) => prev + chunk);
|
|
||||||
},
|
|
||||||
playerCharacter || undefined,
|
playerCharacter || undefined,
|
||||||
session,
|
session,
|
||||||
abortControllerRef.current.signal,
|
abortControllerRef.current.signal,
|
||||||
);
|
);
|
||||||
|
flushStreamingContent();
|
||||||
|
|
||||||
// Сохраняем ответ ИИ в текущую версию
|
// Сохраняем ответ ИИ в текущую версию
|
||||||
const finalVersions: MessageVersion[] = [...newVersions];
|
const finalVersions: MessageVersion[] = [...newVersions];
|
||||||
@@ -653,12 +662,6 @@ export default function GamePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSwitchSession = async (sessionId: string) => {
|
const handleSwitchSession = async (sessionId: string) => {
|
||||||
console.log(
|
|
||||||
"[GamePage] Switching to session:",
|
|
||||||
sessionId,
|
|
||||||
"current:",
|
|
||||||
currentSessionId,
|
|
||||||
);
|
|
||||||
if (!id || sessionId === currentSessionId) {
|
if (!id || sessionId === currentSessionId) {
|
||||||
setShowSessionMenu(false);
|
setShowSessionMenu(false);
|
||||||
return;
|
return;
|
||||||
@@ -668,7 +671,6 @@ export default function GamePage() {
|
|||||||
setIsInitialLoading(true);
|
setIsInitialLoading(true);
|
||||||
|
|
||||||
const sessionData = await getSession(id, sessionId);
|
const sessionData = await getSession(id, sessionId);
|
||||||
console.log("[GamePage] Loaded session data:", sessionData);
|
|
||||||
if (sessionData) {
|
if (sessionData) {
|
||||||
setCurrentSessionId(sessionId);
|
setCurrentSessionId(sessionId);
|
||||||
setSession(sessionData);
|
setSession(sessionData);
|
||||||
|
|||||||
@@ -67,19 +67,6 @@ export async function sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data: DeepSeekResponse = await response.json();
|
const data: DeepSeekResponse = await response.json();
|
||||||
|
|
||||||
// Log cache usage (for debugging)
|
|
||||||
if (data.usage) {
|
|
||||||
console.log(
|
|
||||||
`[DeepSeek] Tokens - Prompt: ${data.usage.prompt_tokens}, Completion: ${data.usage.completion_tokens}`,
|
|
||||||
);
|
|
||||||
if (data.usage.prompt_cache_hit_tokens !== undefined) {
|
|
||||||
console.log(
|
|
||||||
`[DeepSeek] Cache - Hit: ${data.usage.prompt_cache_hit_tokens}, Miss: ${data.usage.prompt_cache_miss_tokens}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.choices[0]?.message?.content || "";
|
return data.choices[0]?.message?.content || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user