From 302b56bd5f2fc425979839601906a7e1dd301446 Mon Sep 17 00:00:00 2001 From: Alexej Wolff Date: Mon, 4 May 2026 01:13:28 +0200 Subject: [PATCH] Fix memory leak: throttle streaming updates, remove console.logs --- src/pages/GamePage.tsx | 54 +++++++++++++++++++++------------------- src/services/deepseek.ts | 13 ---------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/pages/GamePage.tsx b/src/pages/GamePage.tsx index 784818c..65dfd27 100644 --- a/src/pages/GamePage.tsx +++ b/src/pages/GamePage.tsx @@ -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 ReactMarkdown from "react-markdown"; import { useAuth } from "../contexts/AuthContext"; @@ -69,6 +69,26 @@ export default function GamePage() { const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); const inputRef = useRef(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(() => { const loadGame = async () => { @@ -87,7 +107,6 @@ export default function GamePage() { // Загружаем список сессий const sessions = await getSessionsList(id); - console.log("[GamePage] Sessions list:", sessions); setSessionsList(sessions); const characterId = searchParams.get("character"); @@ -109,10 +128,8 @@ export default function GamePage() { // Если есть сессии, загружаем последнюю (или создаём новую) if (sessions.length > 0) { const latestSession = sessions[0]; - console.log("[GamePage] Loading latest session:", latestSession); setCurrentSessionId(latestSession.id); const sessionData = await getSession(id, latestSession.id); - console.log("[GamePage] Session data loaded:", sessionData); if (sessionData) { setSession(sessionData); // Загружаем персонажа: приоритет URL > сессия @@ -170,7 +187,6 @@ export default function GamePage() { id: (updatedStory as any)._id || updatedStory.id, }; setStory(normalizedStory); - console.log("[GamePage] История обновлена после возврата на страницу"); } }; @@ -285,6 +301,7 @@ export default function GamePage() { setIsLoading(true); setError(null); setStreamingContent(""); + streamingBufferRef.current = ""; // Создаём AbortController для возможности отмены abortControllerRef.current = new AbortController(); @@ -295,13 +312,12 @@ export default function GamePage() { story, session.messages, input.trim(), - (chunk) => { - setStreamingContent((prev) => prev + chunk); - }, + updateStreamingContent, playerCharacter || undefined, session, abortControllerRef.current.signal, ); + flushStreamingContent(); const assistantMessage: ChatMessage = { id: generateId(), @@ -321,7 +337,6 @@ export default function GamePage() { // Генерируем сводку каждые 20 сообщений let newSummary = session.storySummary; if (allMessages.length % 20 === 0 && allMessages.length > 0) { - console.log("[GamePage] Generating story summary..."); newSummary = await generateStorySummary( story, allMessages, @@ -336,17 +351,11 @@ export default function GamePage() { storySummary: newSummary, }; - const saved = await apiSaveSession( + await apiSaveSession( story.id, currentSessionId, finalSession, ); - console.log( - "[GamePage] Session saved:", - saved, - "Messages:", - allMessages.length, - ); setSession(finalSession); } catch (err) { if (err instanceof Error && err.name === "AbortError") { @@ -466,6 +475,7 @@ export default function GamePage() { setIsLoading(true); setError(null); setStreamingContent(""); + streamingBufferRef.current = ""; abortControllerRef.current = new AbortController(); @@ -475,13 +485,12 @@ export default function GamePage() { story, messagesUpToEdit, editContent.trim(), - (chunk) => { - setStreamingContent((prev) => prev + chunk); - }, + updateStreamingContent, playerCharacter || undefined, session, abortControllerRef.current.signal, ); + flushStreamingContent(); // Сохраняем ответ ИИ в текущую версию const finalVersions: MessageVersion[] = [...newVersions]; @@ -653,12 +662,6 @@ export default function GamePage() { }; const handleSwitchSession = async (sessionId: string) => { - console.log( - "[GamePage] Switching to session:", - sessionId, - "current:", - currentSessionId, - ); if (!id || sessionId === currentSessionId) { setShowSessionMenu(false); return; @@ -668,7 +671,6 @@ export default function GamePage() { setIsInitialLoading(true); const sessionData = await getSession(id, sessionId); - console.log("[GamePage] Loaded session data:", sessionData); if (sessionData) { setCurrentSessionId(sessionId); setSession(sessionData); diff --git a/src/services/deepseek.ts b/src/services/deepseek.ts index fb299d6..ea17e7e 100644 --- a/src/services/deepseek.ts +++ b/src/services/deepseek.ts @@ -67,19 +67,6 @@ export async function sendMessage( } 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 || ""; }