ChatGPT умирает на длинных разговорах. Не AI-часть — модель отлично держит тысячи сообщений. Умирает фронтенд. Таб зависает, скролл лагает, иногда браузер просто крашится.
Самое обидное — именно длинные разговоры самые ценные. Чем дольше обсуждаешь, тем больше контекста у модели, тем полезнее ответы. А продукт ломается ровно в тот момент, когда начинается максимальная отдача.
Мне это надоело и я полез разбираться.
Когда вы открываете разговор, ChatGPT делает запрос на /backend-api/conversation/{id}. В ответе приходит JSON с полем mapping — объект, где каждый ключ это ID сообщения, а значение — нода с контентом, parent-ссылкой и списком children.
{ "mapping": { "abc-123": { "message": { "content": { "parts": ["..."] }, "author": { "role": "user" } }, "parent": "abc-122", "children": ["abc-124"] } }, "current_node": "abc-999" }
React получает этот объект целиком и рендерит все ноды в DOM. Каждое сообщение — это не один <div>, а дерево: аватар, контент, кнопки, подсветка кода, MathJax-формулы. Одно сообщение ≈ 250 DOM-нод.
Считаем:
500 сообщений → ~125 000 DOM-нод
2 000 сообщений → ~500 000 DOM-нод
Chrome начинает задыхаться на ~100K нод. При полумиллионе таб просто ложится. Виртуализации списка нет.
Идея тривиальная: перехватить ответ API до того как React его получит, и обрезать mapping до последних N сообщений.
1. Перехват fetch. Content script с "world": "MAIN" позволяет подменить window.fetch:
const originalFetch = window.fetch; window.fetch = async function(...args) { const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || ''; if (!/\/backend-api\/conversation\/[0-9a-f-]{36}$/.test(url)) { return originalFetch.apply(this, args); } const response = await originalFetch.apply(this, args); const data = await response.clone().json(); const truncated = truncateMapping(data, 100); return new Response(JSON.stringify(truncated), { status: response.status, headers: response.headers, }); };
2. Обрезка. Идём от current_node по цепочке parent, собираем последние N сообщений:
function truncateMapping(data, max) { const keep = new Set(); let id = data.current_node; let count = 0; while (id && count < max) { const node = data.mapping[id]; if (!node) break; keep.add(id); if (node.message?.content) count++; id = node.parent; } const result = {}; for (const id of keep) { const node = { ...data.mapping[id] }; node.children = node.children?.filter(c => keep.has(c)) || []; if (node.parent && !keep.has(node.parent)) node.parent = null; result[id] = node; } return { ...data, mapping: result }; }
3. Кэш. Полный JSON сохраняется в Map. Кнопка "загрузить ещё" увеличивает окно и пересобирает из кэша — без повторного запроса.
Разговор на 3000+ сообщений грузится мгновенно
AI видит полную историю (обрезка только на стороне рендеринга)
30 КБ, ноль зависимостей, ноль внешних запросов
Задача уровня react-window — библиотеки на 6KB, которая существует с 2018 года. Чинится за вечер. Одним человеком. В Chrome extension.
Скачать: https://inem.gumroad.com/l/unfreeze-for-chatgpt
Chrome Web Store: на ревью
Бесплатно, без трекинга, исходники тут
Если у вас есть длинные разговоры в ChatGPT которые вы боитесь открывать — попробуйте.
* * *
А, чуть не забыл:
Да просто надо как следует разозлиться, упереться рогом, и разобраться как оно устроено. Код за вас напишет AI.
Источник

