feat: auto-resize textarea, persistent token stats

This commit is contained in:
Alexej Wolff
2026-02-11 02:05:28 +01:00
parent 8c6d6591f8
commit 863cf7f6b6
3 changed files with 96 additions and 19 deletions
+79 -13
View File
@@ -409,6 +409,13 @@ app.put("/api/sessions/:storyId/:sessionId", requireAuth, async (req, res) => {
try {
const sessions = db.collection("game_sessions");
// Получаем старую сессию для подсчёта новых токенов
const oldSession = await sessions.findOne({
_id: new ObjectId(req.params.sessionId),
userId: req.session.userId,
});
const oldMessageCount = oldSession?.messages?.length || 0;
// Преобразуем timestamp строки обратно в Date
const messages = (req.body.messages || []).map((msg) => ({
...msg,
@@ -437,6 +444,26 @@ app.put("/api/sessions/:storyId/:sessionId", requireAuth, async (req, res) => {
return res.status(404).json({ error: "Session not found" });
}
// Логируем новые токены (только для новых сообщений)
const newMessages = messages.slice(oldMessageCount);
if (newMessages.length > 0) {
const tokenUsage = db.collection("token_usage");
const newTokens = newMessages.reduce((sum, msg) => {
return sum + Math.round((msg.content?.length || 0) / 3);
}, 0);
if (newTokens > 0) {
await tokenUsage.insertOne({
userId: req.session.userId,
storyId: req.params.storyId,
sessionId: req.params.sessionId,
tokens: newTokens,
messageCount: newMessages.length,
createdAt: new Date(),
});
}
}
res.json({ success: true });
} catch (error) {
console.error("Update session error:", error);
@@ -582,6 +609,7 @@ app.get("/api/admin/stats", requireAuth, async (req, res) => {
try {
const stories = db.collection("stories");
const gameSessions = db.collection("game_sessions");
const tokenUsage = db.collection("token_usage");
// Получаем все истории пользователя
const userStories = await stories
@@ -590,39 +618,77 @@ app.get("/api/admin/stats", requireAuth, async (req, res) => {
// Получаем все сессии пользователя
const userSessions = await gameSessions
.find({ storyId: { $in: userStories.map((s) => s._id.toString()) } })
.find({ userId: req.session.userId })
.toArray();
// Получаем общее количество токенов из лога (не зависит от удалённых сессий)
const tokenLogs = await tokenUsage
.find({ userId: req.session.userId })
.toArray();
const totalTokensLogged = tokenLogs.reduce(
(sum, log) => sum + (log.tokens || 0),
0,
);
// Считаем токены в текущих сессиях (для сравнения)
const currentTokens = userSessions.reduce((sum, session) => {
const chars = (session.messages || []).reduce(
(s, msg) => s + (msg.content?.length || 0),
0,
);
return sum + Math.round(chars / 3);
}, 0);
// Берём максимум: залогированные или текущие (для обратной совместимости)
const totalTokens = Math.max(totalTokensLogged, currentTokens);
// Считаем статистику для каждой истории
const storyStats = userStories.map((story) => {
const session = userSessions.find(
const storySessions = userSessions.filter(
(s) => s.storyId === story._id.toString(),
);
const messages = session?.messages || [];
const messageCount = messages.length;
// Примерный подсчёт токенов (1 токен ≈ 3 символа для русского)
const totalChars = messages.reduce(
(sum, msg) => sum + (msg.content?.length || 0),
const messageCount = storySessions.reduce(
(sum, s) => sum + (s.messages?.length || 0),
0,
);
const tokens = Math.round(totalChars / 3);
// Токены из лога для этой истории
const storyTokenLogs = tokenLogs.filter(
(l) => l.storyId === story._id.toString(),
);
const loggedTokens = storyTokenLogs.reduce(
(sum, l) => sum + (l.tokens || 0),
0,
);
// Текущие токены в сессиях
const currentStoryTokens = storySessions.reduce((sum, session) => {
const chars = (session.messages || []).reduce(
(s, msg) => s + (msg.content?.length || 0),
0,
);
return sum + Math.round(chars / 3);
}, 0);
const tokens = Math.max(loggedTokens, currentStoryTokens);
const lastSession = storySessions.sort(
(a, b) => new Date(b.updatedAt) - new Date(a.updatedAt),
)[0];
return {
id: story._id.toString(),
title: story.title,
sessionsCount: storySessions.length,
messageCount,
tokens,
lastPlayed: session?.updatedAt || null,
lastPlayed: lastSession?.updatedAt || null,
};
});
// Сортируем по токенам (больше сверху)
storyStats.sort((a, b) => b.tokens - a.tokens);
// Общая статистика
const totalTokens = storyStats.reduce((sum, s) => sum + s.tokens, 0);
res.json({
totalStories: userStories.length,
totalSessions: userSessions.length,