mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2024-12-15 02:47:16 +01:00
333 lines
11 KiB
JavaScript
333 lines
11 KiB
JavaScript
// statsHelper.js
|
|
import { moment } from '../lib.js';
|
|
import { getRequestHeaders, callPopup, characters, this_chid } from '../script.js';
|
|
import { humanizeGenTime } from './RossAscends-mods.js';
|
|
import { registerDebugFunction } from './power-user.js';
|
|
|
|
let charStats = {};
|
|
|
|
/**
|
|
* Creates an HTML stat block.
|
|
*
|
|
* @param {string} statName - The name of the stat to be displayed.
|
|
* @param {number|string} statValue - The value of the stat to be displayed.
|
|
* @returns {string} - An HTML string representing the stat block.
|
|
*/
|
|
function createStatBlock(statName, statValue) {
|
|
return `<div class="rm_stat_block">
|
|
<div class="rm_stat_name">${statName}:</div>
|
|
<div class="rm_stat_value">${statValue}</div>
|
|
</div>`;
|
|
}
|
|
|
|
/**
|
|
* Verifies and returns a numerical stat value. If the provided stat is not a number, returns 0.
|
|
*
|
|
* @param {number|string} stat - The stat value to be checked and returned.
|
|
* @returns {number} - The stat value if it is a number, otherwise 0.
|
|
*/
|
|
function verifyStatValue(stat) {
|
|
return isNaN(Number(stat)) ? 0 : Number(stat);
|
|
}
|
|
|
|
/**
|
|
* Calculates total stats from character statistics.
|
|
*
|
|
* @returns {Object} - Object containing total statistics.
|
|
*/
|
|
function calculateTotalStats() {
|
|
let totalStats = {
|
|
total_gen_time: 0,
|
|
user_msg_count: 0,
|
|
non_user_msg_count: 0,
|
|
user_word_count: 0,
|
|
non_user_word_count: 0,
|
|
total_swipe_count: 0,
|
|
date_last_chat: 0,
|
|
date_first_chat: new Date('9999-12-31T23:59:59.999Z').getTime(),
|
|
};
|
|
|
|
for (let stats of Object.values(charStats)) {
|
|
totalStats.total_gen_time += verifyStatValue(stats.total_gen_time);
|
|
totalStats.user_msg_count += verifyStatValue(stats.user_msg_count);
|
|
totalStats.non_user_msg_count += verifyStatValue(
|
|
stats.non_user_msg_count,
|
|
);
|
|
totalStats.user_word_count += verifyStatValue(stats.user_word_count);
|
|
totalStats.non_user_word_count += verifyStatValue(
|
|
stats.non_user_word_count,
|
|
);
|
|
totalStats.total_swipe_count += verifyStatValue(
|
|
stats.total_swipe_count,
|
|
);
|
|
|
|
if (verifyStatValue(stats.date_last_chat) != 0) {
|
|
totalStats.date_last_chat = Math.max(
|
|
totalStats.date_last_chat,
|
|
stats.date_last_chat,
|
|
);
|
|
}
|
|
if (verifyStatValue(stats.date_first_chat) != 0) {
|
|
totalStats.date_first_chat = Math.min(
|
|
totalStats.date_first_chat,
|
|
stats.date_first_chat,
|
|
);
|
|
}
|
|
}
|
|
|
|
return totalStats;
|
|
}
|
|
|
|
/**
|
|
* Generates an HTML report of stats.
|
|
*
|
|
* This function creates an HTML report from the provided stats, including chat age,
|
|
* chat time, number of user messages and character messages, word count, and swipe count.
|
|
* The stat blocks are tailored depending on the stats type ("User" or "Character").
|
|
*
|
|
* @param {string} statsType - The type of stats (e.g., "User", "Character").
|
|
* @param {Object} stats - The stats data. Expected keys in this object include:
|
|
* total_gen_time - total generation time
|
|
* date_first_chat - timestamp of the first chat
|
|
* date_last_chat - timestamp of the most recent chat
|
|
* user_msg_count - count of user messages
|
|
* non_user_msg_count - count of non-user messages
|
|
* user_word_count - count of words used by the user
|
|
* non_user_word_count - count of words used by the non-user
|
|
* total_swipe_count - total swipe count
|
|
*/
|
|
function createHtml(statsType, stats) {
|
|
// Get time string
|
|
let timeStirng = humanizeGenTime(stats.total_gen_time);
|
|
let chatAge = 'Never';
|
|
if (stats.date_first_chat < Date.now()) {
|
|
chatAge = moment
|
|
.duration(stats.date_last_chat - stats.date_first_chat)
|
|
.humanize();
|
|
}
|
|
|
|
// Create popup HTML with stats
|
|
let html = `<h3>${statsType} Stats</h3>`;
|
|
if (statsType === 'User') {
|
|
html += createStatBlock('Chatting Since', `${chatAge} ago`);
|
|
} else {
|
|
html += createStatBlock('First Interaction', `${chatAge} ago`);
|
|
}
|
|
html += createStatBlock('Chat Time', timeStirng);
|
|
html += createStatBlock('User Messages', stats.user_msg_count);
|
|
html += createStatBlock(
|
|
'Character Messages',
|
|
stats.non_user_msg_count - stats.total_swipe_count,
|
|
);
|
|
html += createStatBlock('User Words', stats.user_word_count);
|
|
html += createStatBlock('Character Words', stats.non_user_word_count);
|
|
html += createStatBlock('Swipes', stats.total_swipe_count);
|
|
|
|
callPopup(html, 'text');
|
|
}
|
|
|
|
/**
|
|
* Handles the user stats by getting them from the server, calculating the total and generating the HTML report.
|
|
*/
|
|
async function userStatsHandler() {
|
|
// Get stats from server
|
|
await getStats();
|
|
|
|
// Calculate total stats
|
|
let totalStats = calculateTotalStats();
|
|
|
|
// Create HTML with stats
|
|
createHtml('User', totalStats);
|
|
}
|
|
|
|
/**
|
|
* Handles the character stats by getting them from the server and generating the HTML report.
|
|
*
|
|
* @param {Object} characters - Object containing character data.
|
|
* @param {string} this_chid - The character id.
|
|
*/
|
|
async function characterStatsHandler(characters, this_chid) {
|
|
// Get stats from server
|
|
await getStats();
|
|
// Get character stats
|
|
let myStats = charStats[characters[this_chid].avatar];
|
|
if (myStats === undefined) {
|
|
myStats = {
|
|
total_gen_time: 0,
|
|
user_msg_count: 0,
|
|
non_user_msg_count: 0,
|
|
user_word_count: 0,
|
|
non_user_word_count: countWords(characters[this_chid].first_mes),
|
|
total_swipe_count: 0,
|
|
date_last_chat: 0,
|
|
date_first_chat: new Date('9999-12-31T23:59:59.999Z').getTime(),
|
|
};
|
|
charStats[characters[this_chid].avatar] = myStats;
|
|
updateStats();
|
|
}
|
|
// Create HTML with stats
|
|
createHtml('Character', myStats);
|
|
}
|
|
|
|
/**
|
|
* Fetches the character stats from the server.
|
|
*/
|
|
async function getStats() {
|
|
const response = await fetch('/api/stats/get', {
|
|
method: 'POST',
|
|
headers: getRequestHeaders(),
|
|
body: JSON.stringify({}),
|
|
cache: 'no-cache',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
toastr.error('Stats could not be loaded. Try reloading the page.');
|
|
throw new Error('Error getting stats');
|
|
}
|
|
charStats = await response.json();
|
|
}
|
|
|
|
/**
|
|
* Asynchronously recreates the stats file from chat files.
|
|
*
|
|
* Sends a POST request to the "/api/stats/recreate" endpoint. If the request fails,
|
|
* it displays an error notification and throws an error.
|
|
*
|
|
* @throws {Error} If the request to recreate stats is unsuccessful.
|
|
*/
|
|
async function recreateStats() {
|
|
const response = await fetch('/api/stats/recreate', {
|
|
method: 'POST',
|
|
headers: getRequestHeaders(),
|
|
body: JSON.stringify({}),
|
|
cache: 'no-cache',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
toastr.error('Stats could not be loaded. Try reloading the page.');
|
|
throw new Error('Error getting stats');
|
|
}
|
|
else {
|
|
toastr.success('Stats file recreated successfully!');
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Calculates the generation time based on start and finish times.
|
|
*
|
|
* @param {string} gen_started - The start time in ISO 8601 format.
|
|
* @param {string} gen_finished - The finish time in ISO 8601 format.
|
|
* @returns {number} - The difference in time in milliseconds.
|
|
*/
|
|
function calculateGenTime(gen_started, gen_finished) {
|
|
if (gen_started === undefined || gen_finished === undefined) {
|
|
return 0;
|
|
}
|
|
let startDate = new Date(gen_started);
|
|
let endDate = new Date(gen_finished);
|
|
return endDate.getTime() - startDate.getTime();
|
|
}
|
|
|
|
/**
|
|
* Sends a POST request to the server to update the statistics.
|
|
*/
|
|
async function updateStats() {
|
|
const response = await fetch('/api/stats/update', {
|
|
method: 'POST',
|
|
headers: getRequestHeaders(),
|
|
body: JSON.stringify(charStats),
|
|
});
|
|
|
|
if (response.status !== 200) {
|
|
console.error('Failed to update stats');
|
|
console.log(response.status);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the count of words in the given string.
|
|
* A word is a sequence of alphanumeric characters (including underscore).
|
|
*
|
|
* @param {string} str - The string to count words in.
|
|
* @returns {number} - Number of words.
|
|
*/
|
|
function countWords(str) {
|
|
const match = str.match(/\b\w+\b/g);
|
|
return match ? match.length : 0;
|
|
}
|
|
|
|
/**
|
|
* Handles stat processing for messages.
|
|
*
|
|
* @param {Object} line - Object containing message data.
|
|
* @param {string} type - The type of the message processing (e.g., 'append', 'continue', 'appendFinal', 'swipe').
|
|
* @param {Object} characters - Object containing character data.
|
|
* @param {string} this_chid - The character id.
|
|
* @param {string} oldMesssage - The old message that's being processed.
|
|
*/
|
|
async function statMesProcess(line, type, characters, this_chid, oldMesssage) {
|
|
if (this_chid === undefined || characters[this_chid] === undefined) {
|
|
return;
|
|
}
|
|
await getStats();
|
|
|
|
let stat = charStats[characters[this_chid].avatar];
|
|
|
|
if (!stat) {
|
|
stat = {
|
|
total_gen_time: 0,
|
|
user_word_count: 0,
|
|
non_user_msg_count: 0,
|
|
user_msg_count: 0,
|
|
total_swipe_count: 0,
|
|
date_first_chat: Date.now(),
|
|
date_last_chat: Date.now(),
|
|
};
|
|
}
|
|
|
|
stat.total_gen_time += calculateGenTime(
|
|
line.gen_started,
|
|
line.gen_finished,
|
|
);
|
|
if (line.is_user) {
|
|
if (type != 'append' && type != 'continue' && type != 'appendFinal') {
|
|
stat.user_msg_count++;
|
|
stat.user_word_count += countWords(line.mes);
|
|
} else {
|
|
let oldLen = oldMesssage.split(' ').length;
|
|
stat.user_word_count += countWords(line.mes) - oldLen;
|
|
}
|
|
} else {
|
|
// if continue, don't add a message, get the last message and subtract it from the word count of
|
|
// the new message
|
|
if (type != 'append' && type != 'continue' && type != 'appendFinal') {
|
|
stat.non_user_msg_count++;
|
|
stat.non_user_word_count += countWords(line.mes);
|
|
} else {
|
|
let oldLen = oldMesssage.split(' ').length;
|
|
stat.non_user_word_count += countWords(line.mes) - oldLen;
|
|
}
|
|
}
|
|
|
|
if (type === 'swipe') {
|
|
stat.total_swipe_count++;
|
|
}
|
|
stat.date_last_chat = Date.now();
|
|
stat.date_first_chat = Math.min(
|
|
stat.date_first_chat ?? new Date('9999-12-31T23:59:59.999Z').getTime(),
|
|
Date.now(),
|
|
);
|
|
updateStats();
|
|
}
|
|
|
|
export function initStats() {
|
|
$('.rm_stats_button').on('click', function () {
|
|
characterStatsHandler(characters, this_chid);
|
|
});
|
|
// Wait for debug functions to load, then add the refresh stats function
|
|
registerDebugFunction('refreshStats', 'Refresh Stat File', 'Recreates the stats file based on existing chat files', recreateStats);
|
|
}
|
|
|
|
export { userStatsHandler, characterStatsHandler, getStats, statMesProcess, charStats };
|