GameSentenceMiner 2.16.10__tar.gz → 2.16.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/gsm.py +14 -11
  2. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/js/stats.js +61 -22
  3. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/stats.py +9 -4
  4. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/stats.html +22 -0
  5. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/texthooking_page.py +5 -1
  6. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  7. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/PKG-INFO +1 -1
  8. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/pyproject.toml +1 -1
  9. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/__init__.py +0 -0
  10. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ai/__init__.py +0 -0
  11. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  12. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/anki.py +0 -0
  13. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/__init__.py +0 -0
  14. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/icon.png +0 -0
  15. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/icon128.png +0 -0
  16. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/icon256.png +0 -0
  17. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/icon32.png +0 -0
  18. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/icon512.png +0 -0
  19. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/icon64.png +0 -0
  20. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/assets/pickaxe.png +0 -0
  21. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/config_gui.py +0 -0
  22. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/gametext.py +0 -0
  23. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/locales/en_us.json +0 -0
  24. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/locales/ja_jp.json +0 -0
  25. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/locales/zh_cn.json +0 -0
  26. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/obs.py +0 -0
  27. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ocr/__init__.py +0 -0
  28. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  29. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  30. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
  31. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  32. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  33. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  34. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  35. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  36. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  37. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
  38. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/owocr/owocr/run.py +0 -0
  39. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  40. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/tools/__init__.py +0 -0
  41. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/tools/audio_offset_selector.py +0 -0
  42. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/tools/furigana_filter_preview.py +0 -0
  43. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/tools/ss_selector.py +0 -0
  44. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/tools/window_transparency.py +0 -0
  45. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/__init__.py +0 -0
  46. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/communication/__init__.py +0 -0
  47. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/communication/send.py +0 -0
  48. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/communication/websocket.py +0 -0
  49. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/configuration.py +0 -0
  50. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/db.py +0 -0
  51. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  52. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  53. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
  54. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  55. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/electron_config.py +0 -0
  56. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/ffmpeg.py +0 -0
  57. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/get_overlay_coords.py +0 -0
  58. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/gsm_utils.py +0 -0
  59. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/model.py +0 -0
  60. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/notification.py +0 -0
  61. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/text_log.py +0 -0
  62. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/win10toast/__init__.py +0 -0
  63. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/util/win10toast/__main__.py +0 -0
  64. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/vad.py +0 -0
  65. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/__init__.py +0 -0
  66. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/database_api.py +0 -0
  67. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/events.py +0 -0
  68. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/gsm_websocket.py +0 -0
  69. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/service.py +0 -0
  70. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/__init__.py +0 -0
  71. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  72. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/css/kanji-grid.css +0 -0
  73. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/css/search.css +0 -0
  74. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/css/shared.css +0 -0
  75. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/css/stats.css +0 -0
  76. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  77. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/favicon.ico +0 -0
  78. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/favicon.svg +0 -0
  79. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/js/anki_stats.js +0 -0
  80. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/js/database.js +0 -0
  81. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/js/kanji-grid.js +0 -0
  82. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/js/search.js +0 -0
  83. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/js/shared.js +0 -0
  84. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  85. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/style.css +0 -0
  86. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  87. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  88. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/anki_stats.html +0 -0
  89. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/components/navigation.html +0 -0
  90. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/components/theme-styles.html +0 -0
  91. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/database.html +0 -0
  92. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/index.html +0 -0
  93. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/search.html +0 -0
  94. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/web/templates/utility.html +0 -0
  95. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner/wip/__init___.py +0 -0
  96. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  97. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  98. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  99. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner.egg-info/requires.txt +0 -0
  100. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  101. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/LICENSE +0 -0
  102. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/README.md +0 -0
  103. {gamesentenceminer-2.16.10 → gamesentenceminer-2.16.12}/setup.cfg +0 -0
@@ -314,17 +314,20 @@ class VideoToAudioHandler(FileSystemEventHandler):
314
314
  logger.info("No voice activity detected, using full audio.")
315
315
  vad_result.output_audio = trimmed_audio
316
316
  elif get_config().vad.use_tts_as_fallback:
317
- logger.info(
318
- "No voice activity detected, using TTS as fallback.")
319
- text_to_tts = full_text if full_text else game_line.text
320
- url = get_config().vad.tts_url.replace("$s", text_to_tts)
321
- tts_resp = requests.get(url)
322
- if not tts_resp.ok:
323
- logger.error(
324
- f"Error fetching TTS audio from {url}. Is it running?: {tts_resp.status_code} {tts_resp.text}")
325
- with tempfile.NamedTemporaryFile(dir=get_temporary_directory(), delete=False, suffix=".opus") as tmpfile:
326
- tmpfile.write(tts_resp.content)
327
- vad_result.output_audio = tmpfile.name
317
+ try:
318
+ logger.info(
319
+ "No voice activity detected, using TTS as fallback.")
320
+ text_to_tts = full_text if full_text else game_line.text
321
+ url = get_config().vad.tts_url.replace("$s", text_to_tts)
322
+ tts_resp = requests.get(url)
323
+ if not tts_resp.ok:
324
+ logger.error(
325
+ f"Error fetching TTS audio from {url}. Is it running?: {tts_resp.status_code} {tts_resp.text}")
326
+ with tempfile.NamedTemporaryFile(dir=get_temporary_directory(), delete=False, suffix=".opus") as tmpfile:
327
+ tmpfile.write(tts_resp.content)
328
+ vad_result.output_audio = tmpfile.name
329
+ except Exception as e:
330
+ logger.error(f"Error getting TTS audio: {e}, skipping audio.")
328
331
  else:
329
332
  logger.info(vad_result.trim_successful_string())
330
333
  if vad_result.output_audio:
@@ -152,11 +152,12 @@ document.addEventListener('DOMContentLoaded', function () {
152
152
  tempStreak = 0;
153
153
  }
154
154
  }
155
-
156
- // Calculate current streak from today backwards
155
+
156
+ // Calculate current streak from today backwards, using streak requirement hours from config
157
157
  const date = new Date();
158
158
  const today = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
159
-
159
+ const streakRequirement = window.statsConfig ? window.statsConfig.streakRequirementHours : 1.0;
160
+
160
161
  // Find today's index or the most recent date before today
161
162
  let todayIndex = -1;
162
163
  for (let i = dates.length - 1; i >= 0; i--) {
@@ -165,11 +166,11 @@ document.addEventListener('DOMContentLoaded', function () {
165
166
  break;
166
167
  }
167
168
  }
168
-
169
+
169
170
  // Count backwards from today (or most recent date)
170
171
  if (todayIndex >= 0) {
171
172
  for (let i = todayIndex; i >= 0; i--) {
172
- if (dates[i].activity > 0) {
173
+ if (dates[i].activity >= streakRequirement) {
173
174
  currentStreak++;
174
175
  } else {
175
176
  break;
@@ -196,18 +197,24 @@ document.addEventListener('DOMContentLoaded', function () {
196
197
  // Calculate reading time for each day with activity
197
198
  let totalHours = 0;
198
199
  let activeDays = 0;
199
- const afkTimerSeconds = 120; // Default AFK timer - should be fetched from settings
200
-
200
+ let afkTimerSeconds = window.statsConfig ? window.statsConfig.afkTimerSeconds : 120;
201
+ // Try to get AFK timer from settings modal if available and valid
202
+ const afkTimerInput = document.getElementById('afkTimer');
203
+ if (afkTimerInput && afkTimerInput.value) {
204
+ const parsed = parseInt(afkTimerInput.value, 10);
205
+ if (!isNaN(parsed) && parsed > 0) afkTimerSeconds = parsed;
206
+ }
207
+
201
208
  for (const [dateStr, timestamps] of Object.entries(dailyTimestamps)) {
202
209
  if (timestamps.length >= 2) {
203
210
  timestamps.sort((a, b) => a - b);
204
211
  let dayReadingTime = 0;
205
-
212
+
206
213
  for (let i = 1; i < timestamps.length; i++) {
207
214
  const gap = timestamps[i] - timestamps[i-1];
208
215
  dayReadingTime += Math.min(gap, afkTimerSeconds);
209
216
  }
210
-
217
+
211
218
  if (dayReadingTime > 0) {
212
219
  totalHours += dayReadingTime / 3600;
213
220
  activeDays++;
@@ -837,14 +844,20 @@ document.addEventListener('DOMContentLoaded', function () {
837
844
  }
838
845
 
839
846
  // Goal Progress Chart functionality
840
- let goalSettings = {
841
- reading_hours_target: 1500,
842
- character_count_target: 25000000,
843
- games_target: 100
844
- };
847
+ let goalSettings = window.statsConfig || {};
848
+ if (!goalSettings.reading_hours_target) goalSettings.reading_hours_target = 1500;
849
+ if (!goalSettings.character_count_target) goalSettings.character_count_target = 25000000;
850
+ if (!goalSettings.games_target) goalSettings.games_target = 100;
845
851
 
846
- // Function to load goal settings from API
852
+ // Function to load goal settings from API (fallback)
847
853
  async function loadGoalSettings() {
854
+ // Use global config if available, otherwise fetch
855
+ if (window.statsConfig) {
856
+ goalSettings.reading_hours_target = window.statsConfig.readingHoursTarget || 1500;
857
+ goalSettings.character_count_target = window.statsConfig.characterCountTarget || 25000000;
858
+ goalSettings.games_target = window.statsConfig.gamesTarget || 100;
859
+ return;
860
+ }
848
861
  try {
849
862
  const response = await fetch('/api/settings');
850
863
  if (response.ok) {
@@ -899,8 +912,14 @@ document.addEventListener('DOMContentLoaded', function () {
899
912
  if (timestamps.length >= 2) {
900
913
  timestamps.sort((a, b) => a - b);
901
914
  let dayHours = 0;
902
- const afkTimerSeconds = 120; // Default AFK timer
903
-
915
+ let afkTimerSeconds = window.statsConfig ? window.statsConfig.afkTimerSeconds : 120;
916
+ // Try to get AFK timer from settings modal if available and valid
917
+ const afkTimerInput = document.getElementById('afkTimer');
918
+ if (afkTimerInput && afkTimerInput.value) {
919
+ const parsed = parseInt(afkTimerInput.value, 10);
920
+ if (!isNaN(parsed) && parsed > 0) afkTimerSeconds = parsed;
921
+ }
922
+
904
923
  for (let i = 1; i < timestamps.length; i++) {
905
924
  const gap = timestamps[i] - timestamps[i-1];
906
925
  dayHours += Math.min(gap, afkTimerSeconds) / 3600;
@@ -1099,9 +1118,30 @@ document.addEventListener('DOMContentLoaded', function () {
1099
1118
  }
1100
1119
 
1101
1120
  // Initial load with saved year preference
1102
- const savedYear = localStorage.getItem('selectedHeatmapYear') || 'all';
1121
+ const savedYear = localStorage.getItem('selectedHeatmapYear') || window.statsConfig?.heatmapDisplayYear || 'all';
1103
1122
  loadStatsData(savedYear);
1104
1123
 
1124
+ // Populate settings modal with global config values on load
1125
+ if (window.statsConfig) {
1126
+ const sessionGapInput = document.getElementById('sessionGap');
1127
+ if (sessionGapInput) sessionGapInput.value = window.statsConfig.sessionGapSeconds || 3600;
1128
+
1129
+ const streakReqInput = document.getElementById('streakRequirement');
1130
+ if (streakReqInput) streakReqInput.value = window.statsConfig.streakRequirementHours || 1.0;
1131
+
1132
+ const heatmapYearSelect = document.getElementById('heatmapYear');
1133
+ if (heatmapYearSelect) heatmapYearSelect.value = window.statsConfig.heatmapDisplayYear || 'all';
1134
+
1135
+ const hoursTargetInput = document.getElementById('readingHoursTarget');
1136
+ if (hoursTargetInput) hoursTargetInput.value = window.statsConfig.readingHoursTarget || 1500;
1137
+
1138
+ const charsTargetInput = document.getElementById('characterCountTarget');
1139
+ if (charsTargetInput) charsTargetInput.value = window.statsConfig.characterCountTarget || 25000000;
1140
+
1141
+ const gamesTargetInput = document.getElementById('gamesTarget');
1142
+ if (gamesTargetInput) gamesTargetInput.value = window.statsConfig.gamesTarget || 100;
1143
+ }
1144
+
1105
1145
  // Function to update goal progress using existing stats data
1106
1146
  async function updateGoalProgressWithData(statsData) {
1107
1147
  const goalProgressChart = document.getElementById('goalProgressChart');
@@ -1178,9 +1218,8 @@ document.addEventListener('DOMContentLoaded', function () {
1178
1218
 
1179
1219
  // Calculate sessions (count gaps > session threshold as new sessions)
1180
1220
  let sessions = 0;
1181
- const sessionGapDefault = 3600; // 1 hour in seconds
1182
- // Try to get session gap from settings modal if available
1183
- let sessionGap = sessionGapDefault;
1221
+ let sessionGap = window.statsConfig ? window.statsConfig.sessionGapSeconds : 3600;
1222
+ // Try to get session gap from settings modal if available and valid
1184
1223
  const sessionGapInput = document.getElementById('sessionGap');
1185
1224
  if (sessionGapInput && sessionGapInput.value) {
1186
1225
  const parsed = parseInt(sessionGapInput.value, 10);
@@ -1214,7 +1253,7 @@ document.addEventListener('DOMContentLoaded', function () {
1214
1253
  .filter(ts => !isNaN(ts))
1215
1254
  .sort((a, b) => a - b);
1216
1255
  // Get AFK timer from settings modal if available
1217
- let afkTimerSeconds = 120; // default
1256
+ let afkTimerSeconds = window.statsConfig ? window.statsConfig.afkTimerSeconds : 120;
1218
1257
  const afkTimerInput = document.getElementById('afkTimer');
1219
1258
  if (afkTimerInput && afkTimerInput.value) {
1220
1259
  const parsed = parseInt(afkTimerInput.value, 10);
@@ -345,8 +345,11 @@ def calculate_time_based_streak(lines, streak_requirement_hours=None):
345
345
  int: Current streak in days
346
346
  """
347
347
  if streak_requirement_hours is None:
348
- streak_requirement_hours = getattr(get_config().advanced, 'streak_requirement_hours', 1.0)
349
-
348
+ # Prefer stats_config if available, fallback to config.advanced, then 1.0
349
+ try:
350
+ streak_requirement_hours = get_stats_config().streak_requirement_hours
351
+ except AttributeError:
352
+ streak_requirement_hours = getattr(get_config().advanced, 'streak_requirement_hours', 1.0)
350
353
  # Add debug logging
351
354
  logger.debug(f"Calculating streak with requirement: {streak_requirement_hours} hours")
352
355
  logger.debug(f"Processing {len(lines)} lines for streak calculation")
@@ -440,9 +443,10 @@ def calculate_current_game_stats(all_lines):
440
443
  # Calculate sessions (gaps of more than session_gap_seconds = new session)
441
444
  sorted_timestamps = sorted(timestamps)
442
445
  sessions = 1
446
+ session_gap = get_stats_config().session_gap_seconds
443
447
  for i in range(1, len(sorted_timestamps)):
444
448
  time_gap = sorted_timestamps[i] - sorted_timestamps[i-1]
445
- if time_gap > get_stats_config().session_gap_seconds:
449
+ if time_gap > session_gap:
446
450
  sessions += 1
447
451
 
448
452
  # Calculate daily activity for progress trend
@@ -533,9 +537,10 @@ def calculate_all_games_stats(all_lines):
533
537
  # Calculate sessions across all games (gaps of more than 1 hour = new session)
534
538
  sorted_timestamps = sorted(timestamps)
535
539
  sessions = 1
540
+ session_gap = get_stats_config().session_gap_seconds
536
541
  for i in range(1, len(sorted_timestamps)):
537
542
  time_gap = sorted_timestamps[i] - sorted_timestamps[i-1]
538
- if time_gap > 3600: # 1 hour gap
543
+ if time_gap > session_gap:
539
544
  sessions += 1
540
545
 
541
546
  # Calculate daily activity for progress trend
@@ -520,6 +520,28 @@
520
520
  </div>
521
521
 
522
522
 
523
+ {% set afk_timer = stats_config.afk_timer_seconds | default(120) %}
524
+ {% set session_gap = stats_config.session_gap_seconds | default(3600) %}
525
+ {% set streak_req = config.advanced.streak_requirement_hours | default(1.0) %}
526
+ {% set hours_target = stats_config.reading_hours_target | default(1500) %}
527
+ {% set chars_target = stats_config.character_count_target | default(25000000) %}
528
+ {% set games_target = stats_config.games_target | default(100) %}
529
+ {% set heatmap_year = 'all' %}
530
+
531
+ {% set stats_config = {
532
+ 'afkTimerSeconds': afk_timer,
533
+ 'sessionGapSeconds': session_gap,
534
+ 'streakRequirementHours': streak_req,
535
+ 'readingHoursTarget': hours_target,
536
+ 'characterCountTarget': chars_target,
537
+ 'gamesTarget': games_target,
538
+ 'heatmapDisplayYear': heatmap_year
539
+ } %}
540
+
541
+ <!-- Inject stats config values for frontend -->
542
+ <script>
543
+ window.statsConfig = {{ stats_config | tojson }};
544
+ </script>
523
545
  <!-- Include shared JavaScript first (required dependency for stats.js) -->
524
546
  <script src="/static/js/shared.js"></script>
525
547
  <script src="/static/js/kanji-grid.js"></script>
@@ -261,7 +261,11 @@ def datetimeformat(value, format='%Y-%m-%d %H:%M:%S'):
261
261
  @app.route('/stats')
262
262
  def stats():
263
263
  """Renders the stats page."""
264
- return render_template('stats.html')
264
+ from GameSentenceMiner.util.configuration import get_master_config, get_stats_config
265
+ return render_template('stats.html',
266
+ config=get_config(),
267
+ master_config=get_master_config(),
268
+ stats_config=get_stats_config())
265
269
 
266
270
  @app.route('/api/anki_stats')
267
271
  def api_anki_stats():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.16.10
3
+ Version: 2.16.12
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.16.10
3
+ Version: 2.16.12
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GameSentenceMiner"
10
- version = "2.16.10"
10
+ version = "2.16.12"
11
11
  description = "A tool for mining sentences from games. Update: Overlay?"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"