feat: auto-resize textarea, persistent token stats
This commit is contained in:
+79
-13
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user