GameSentenceMiner 2.17.7__py3-none-any.whl → 2.18.1__py3-none-any.whl

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.

Potentially problematic release.


This version of GameSentenceMiner might be problematic. Click here for more details.

Files changed (76) hide show
  1. GameSentenceMiner/ai/ai_prompting.py +6 -6
  2. GameSentenceMiner/anki.py +236 -152
  3. GameSentenceMiner/gametext.py +7 -4
  4. GameSentenceMiner/gsm.py +49 -10
  5. GameSentenceMiner/locales/en_us.json +7 -3
  6. GameSentenceMiner/locales/ja_jp.json +8 -4
  7. GameSentenceMiner/locales/zh_cn.json +8 -4
  8. GameSentenceMiner/obs.py +238 -59
  9. GameSentenceMiner/ocr/owocr_helper.py +1 -1
  10. GameSentenceMiner/tools/ss_selector.py +7 -8
  11. GameSentenceMiner/ui/__init__.py +0 -0
  12. GameSentenceMiner/ui/anki_confirmation.py +187 -0
  13. GameSentenceMiner/{config_gui.py → ui/config_gui.py} +100 -35
  14. GameSentenceMiner/ui/screenshot_selector.py +215 -0
  15. GameSentenceMiner/util/configuration.py +124 -22
  16. GameSentenceMiner/util/db.py +22 -13
  17. GameSentenceMiner/util/downloader/download_tools.py +2 -2
  18. GameSentenceMiner/util/ffmpeg.py +24 -30
  19. GameSentenceMiner/util/get_overlay_coords.py +34 -34
  20. GameSentenceMiner/util/gsm_utils.py +31 -1
  21. GameSentenceMiner/util/text_log.py +11 -9
  22. GameSentenceMiner/vad.py +31 -12
  23. GameSentenceMiner/web/database_api.py +742 -123
  24. GameSentenceMiner/web/static/css/dashboard-shared.css +241 -0
  25. GameSentenceMiner/web/static/css/kanji-grid.css +94 -2
  26. GameSentenceMiner/web/static/css/overview.css +850 -0
  27. GameSentenceMiner/web/static/css/popups-shared.css +126 -0
  28. GameSentenceMiner/web/static/css/shared.css +97 -0
  29. GameSentenceMiner/web/static/css/stats.css +192 -597
  30. GameSentenceMiner/web/static/js/anki_stats.js +6 -4
  31. GameSentenceMiner/web/static/js/database.js +209 -5
  32. GameSentenceMiner/web/static/js/goals.js +610 -0
  33. GameSentenceMiner/web/static/js/kanji-grid.js +267 -4
  34. GameSentenceMiner/web/static/js/overview.js +1176 -0
  35. GameSentenceMiner/web/static/js/shared.js +25 -0
  36. GameSentenceMiner/web/static/js/stats.js +154 -1459
  37. GameSentenceMiner/web/stats.py +2 -2
  38. GameSentenceMiner/web/templates/anki_stats.html +5 -0
  39. GameSentenceMiner/web/templates/components/kanji_grid/basic_kanji_book_bkb_v1_v2.json +17 -0
  40. GameSentenceMiner/web/templates/components/kanji_grid/duolingo_kanji.json +29 -0
  41. GameSentenceMiner/web/templates/components/kanji_grid/grade.json +17 -0
  42. GameSentenceMiner/web/templates/components/kanji_grid/hk_primary_learning.json +17 -0
  43. GameSentenceMiner/web/templates/components/kanji_grid/hkscs2016.json +13 -0
  44. GameSentenceMiner/web/templates/components/kanji_grid/hsk_levels.json +33 -0
  45. GameSentenceMiner/web/templates/components/kanji_grid/humanum_frequency_list.json +41 -0
  46. GameSentenceMiner/web/templates/components/kanji_grid/jis_levels.json +25 -0
  47. GameSentenceMiner/web/templates/components/kanji_grid/jlpt_level.json +29 -0
  48. GameSentenceMiner/web/templates/components/kanji_grid/jpdb_kanji_frequency_list.json +37 -0
  49. GameSentenceMiner/web/templates/components/kanji_grid/jpdbv2_kanji_frequency_list.json +161 -0
  50. GameSentenceMiner/web/templates/components/kanji_grid/jun_das_modern_chinese_character_frequency_list.json +13 -0
  51. GameSentenceMiner/web/templates/components/kanji_grid/kanji_in_context_revised_edition.json +37 -0
  52. GameSentenceMiner/web/templates/components/kanji_grid/kanji_kentei_level.json +61 -0
  53. GameSentenceMiner/web/templates/components/kanji_grid/mainland_china_elementary_textbook_characters.json +33 -0
  54. GameSentenceMiner/web/templates/components/kanji_grid/moe_way_quiz.json +47 -0
  55. GameSentenceMiner/web/templates/components/kanji_grid/official_kanji.json +25 -0
  56. GameSentenceMiner/web/templates/components/kanji_grid/remembering_the_kanji.json +25 -0
  57. GameSentenceMiner/web/templates/components/kanji_grid/standard_form_of_national_characters.json +25 -0
  58. GameSentenceMiner/web/templates/components/kanji_grid/table_of_general_standard_chinese_characters.json +21 -0
  59. GameSentenceMiner/web/templates/components/kanji_grid/the_kodansha_kanji_learners_course_klc.json +45 -0
  60. GameSentenceMiner/web/templates/components/kanji_grid/thousand_character_classic.json +13 -0
  61. GameSentenceMiner/web/templates/components/kanji_grid/wanikani_levels.json +249 -0
  62. GameSentenceMiner/web/templates/components/kanji_grid/words_hk_frequency_list.json +33 -0
  63. GameSentenceMiner/web/templates/components/navigation.html +3 -1
  64. GameSentenceMiner/web/templates/database.html +73 -1
  65. GameSentenceMiner/web/templates/goals.html +376 -0
  66. GameSentenceMiner/web/templates/index.html +13 -11
  67. GameSentenceMiner/web/templates/overview.html +416 -0
  68. GameSentenceMiner/web/templates/stats.html +46 -251
  69. GameSentenceMiner/web/texthooking_page.py +18 -0
  70. {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/METADATA +5 -1
  71. gamesentenceminer-2.18.1.dist-info/RECORD +132 -0
  72. gamesentenceminer-2.17.7.dist-info/RECORD +0 -98
  73. {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/WHEEL +0 -0
  74. {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/entry_points.txt +0 -0
  75. {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/licenses/LICENSE +0 -0
  76. {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/top_level.txt +0 -0
@@ -122,15 +122,17 @@ document.addEventListener('DOMContentLoaded', function () {
122
122
  // Get first date in ms from API
123
123
  const firstDateinMs = response_json.earliest_card;
124
124
  const firstDateObject = new Date(firstDateinMs);
125
- fromDateInput.value = firstDateObject.toISOString().split('T')[0];
125
+ const fromDate = firstDateObject.toLocaleDateString('en-CA');
126
+ fromDateInput.value = fromDate;
126
127
 
127
128
  // Get today's date
128
129
  const today = new Date();
129
- toDateInput.value = today.toISOString().split("T")[0];
130
+ const toDate = today.toLocaleDateString('en-CA');
131
+ toDateInput.value = toDate;
130
132
 
131
133
  // Save in sessionStorage
132
- sessionStorage.setItem("fromDateAnki", firstDateObject.toISOString().split('T')[0]);
133
- sessionStorage.setItem("toDateAnki", today.toISOString().split("T")[0]);
134
+ sessionStorage.setItem("fromDateAnki", fromDate);
135
+ sessionStorage.setItem("toDateAnki", toDate);
134
136
 
135
137
  document.dispatchEvent(new Event("datesSetAnki"));
136
138
  });
@@ -5,6 +5,7 @@
5
5
  class DatabaseManager {
6
6
  constructor() {
7
7
  this.selectedGames = new Set();
8
+ this.mergeTargetGame = null; // Track the first game selected for merge operations
8
9
  this.initializePage();
9
10
  }
10
11
 
@@ -55,6 +56,16 @@ class DatabaseManager {
55
56
  deleteSelectedBtn.addEventListener('click', deleteSelectedGames);
56
57
  }
57
58
 
59
+ const mergeSelectedBtn = document.querySelector('[data-action="mergeSelectedGames"]');
60
+ if (mergeSelectedBtn) {
61
+ mergeSelectedBtn.addEventListener('click', openGameMergeModal);
62
+ }
63
+
64
+ const confirmMergeBtn = document.querySelector('[data-action="confirmGameMerge"]');
65
+ if (confirmMergeBtn) {
66
+ confirmMergeBtn.addEventListener('click', confirmGameMerge);
67
+ }
68
+
58
69
  const presetPatternsSelect = document.getElementById('presetPatterns');
59
70
  if (presetPatternsSelect) {
60
71
  presetPatternsSelect.addEventListener('change', applyPresetPattern);
@@ -137,7 +148,7 @@ async function loadGamesForDeletion() {
137
148
 
138
149
  // Add event listener for the checkbox
139
150
  const checkbox = gameItem.querySelector('.game-checkbox');
140
- checkbox.addEventListener('change', updateGameSelection);
151
+ checkbox.addEventListener('change', (event) => handleGameSelectionChange(event));
141
152
 
142
153
  gamesList.appendChild(gameItem);
143
154
  });
@@ -154,27 +165,89 @@ async function loadGamesForDeletion() {
154
165
  }
155
166
 
156
167
  function selectAllGames() {
157
- document.querySelectorAll('.game-checkbox').forEach(cb => {
168
+ // Clear current merge target
169
+ databaseManager.mergeTargetGame = null;
170
+ document.querySelectorAll('.checkbox-container').forEach(container => {
171
+ container.classList.remove('merge-target');
172
+ });
173
+
174
+ const checkboxes = document.querySelectorAll('.game-checkbox');
175
+ checkboxes.forEach((cb, index) => {
158
176
  cb.checked = true;
177
+ // Mark the first checkbox as merge target
178
+ if (index === 0) {
179
+ databaseManager.mergeTargetGame = cb.dataset.game;
180
+ cb.closest('.checkbox-container').classList.add('merge-target');
181
+ }
159
182
  });
160
183
  updateGameSelection();
161
184
  }
162
185
 
163
186
  function selectNoGames() {
187
+ // Clear merge target
188
+ databaseManager.mergeTargetGame = null;
189
+ document.querySelectorAll('.checkbox-container').forEach(container => {
190
+ container.classList.remove('merge-target');
191
+ });
192
+
164
193
  document.querySelectorAll('.game-checkbox').forEach(cb => {
165
194
  cb.checked = false;
166
195
  });
167
196
  updateGameSelection();
168
197
  }
169
198
 
199
+ function handleGameSelectionChange(event) {
200
+ const checkbox = event.target;
201
+ const gameName = checkbox.dataset.game;
202
+ const isChecked = checkbox.checked;
203
+
204
+ // Get current selection count before updating
205
+ const currentSelectedCount = document.querySelectorAll('.game-checkbox:checked').length - (isChecked ? 1 : 0);
206
+
207
+ if (isChecked) {
208
+ // Game is being selected
209
+ if (currentSelectedCount === 0) {
210
+ // This is the first game being selected, mark it as merge target
211
+ databaseManager.mergeTargetGame = gameName;
212
+ // Add visual indicator
213
+ checkbox.closest('.checkbox-container').classList.add('merge-target');
214
+ }
215
+ } else {
216
+ // Game is being deselected
217
+ if (gameName === databaseManager.mergeTargetGame) {
218
+ // The merge target is being deselected
219
+ databaseManager.mergeTargetGame = null;
220
+ checkbox.closest('.checkbox-container').classList.remove('merge-target');
221
+
222
+ // If there are still other games selected, make the first one the new target
223
+ const remainingSelected = document.querySelectorAll('.game-checkbox:checked');
224
+ if (remainingSelected.length > 0) {
225
+ const newTargetCheckbox = remainingSelected[0];
226
+ const newTargetGame = newTargetCheckbox.dataset.game;
227
+ databaseManager.mergeTargetGame = newTargetGame;
228
+ newTargetCheckbox.closest('.checkbox-container').classList.add('merge-target');
229
+ }
230
+ } else {
231
+ // Remove merge target styling if it exists
232
+ checkbox.closest('.checkbox-container').classList.remove('merge-target');
233
+ }
234
+ }
235
+
236
+ updateGameSelection();
237
+ }
238
+
170
239
  function updateGameSelection() {
171
240
  const selectedCheckboxes = document.querySelectorAll('.game-checkbox:checked');
172
241
  const deleteBtn = document.getElementById('deleteSelectedGamesBtn');
242
+ const mergeBtn = document.getElementById('mergeSelectedGamesBtn');
173
243
 
244
+ // Update delete button
174
245
  deleteBtn.disabled = selectedCheckboxes.length === 0;
175
- deleteBtn.textContent = selectedCheckboxes.length > 0
176
- ? `Delete Selected (${selectedCheckboxes.length})`
177
- : 'Delete Selected';
246
+ deleteBtn.textContent = selectedCheckboxes.length > 0 ? `Delete Selected (${selectedCheckboxes.length})` : 'Delete Selected';
247
+
248
+ // Update merge button - only enable when 2 or more games are selected
249
+ mergeBtn.disabled = selectedCheckboxes.length < 2;
250
+ mergeBtn.textContent = selectedCheckboxes.length >= 2 ? `Merge Selected (${selectedCheckboxes.length})` : 'Merge Selected Games';
178
251
  }
179
252
 
180
253
  async function deleteSelectedGames() {
@@ -534,6 +607,137 @@ async function removeDuplicates() {
534
607
  }
535
608
  }
536
609
 
610
+ // Game Merge Functions
611
+ async function openGameMergeModal() {
612
+ const selectedCheckboxes = document.querySelectorAll('.game-checkbox:checked');
613
+ const gameNames = Array.from(selectedCheckboxes).map(cb => cb.dataset.game);
614
+
615
+ if (gameNames.length < 2) {
616
+ alert('Please select at least 2 games to merge.');
617
+ return;
618
+ }
619
+
620
+ try {
621
+ // Get detailed game information
622
+ const response = await fetch('/api/games-list');
623
+ const data = await response.json();
624
+
625
+ if (response.ok && data.games) {
626
+ const selectedGames = data.games.filter(game => gameNames.includes(game.name));
627
+
628
+ // Use the tracked merge target as primary game, or fall back to first selected
629
+ let primaryGame = selectedGames.find(game => game.name === databaseManager.mergeTargetGame);
630
+ if (!primaryGame) {
631
+ primaryGame = selectedGames[0];
632
+ }
633
+
634
+ // Secondary games are all selected games except the primary
635
+ const secondaryGames = selectedGames.filter(game => game.name !== primaryGame.name);
636
+
637
+ // Calculate totals
638
+ const totalSentences = selectedGames.reduce((sum, game) => sum + game.sentence_count, 0);
639
+ const totalCharacters = selectedGames.reduce((sum, game) => sum + game.total_characters, 0);
640
+
641
+ // Populate primary game info
642
+ document.getElementById('primaryGameName').textContent = primaryGame.name;
643
+ document.getElementById('primaryGameStats').textContent =
644
+ `${primaryGame.sentence_count} sentences, ${primaryGame.total_characters.toLocaleString()} characters`;
645
+
646
+ // Populate secondary games list
647
+ const secondaryList = document.getElementById('secondaryGamesList');
648
+ secondaryList.innerHTML = '';
649
+ secondaryGames.forEach(game => {
650
+ const gameDiv = document.createElement('div');
651
+ gameDiv.className = 'game-item';
652
+ gameDiv.innerHTML = `
653
+ <div class="game-name">${escapeHtml(game.name)}</div>
654
+ <div class="game-stats">${game.sentence_count} sentences, ${game.total_characters.toLocaleString()} characters</div>
655
+ `;
656
+ secondaryList.appendChild(gameDiv);
657
+ });
658
+
659
+ // Update merge statistics
660
+ document.getElementById('totalSentencesAfterMerge').textContent = totalSentences.toLocaleString();
661
+ document.getElementById('totalCharactersAfterMerge').textContent = totalCharacters.toLocaleString();
662
+ document.getElementById('gamesBeingMerged').textContent = gameNames.length;
663
+
664
+ // Reset modal state
665
+ document.getElementById('mergeError').style.display = 'none';
666
+ document.getElementById('mergeSuccess').style.display = 'none';
667
+ document.getElementById('mergeLoadingIndicator').style.display = 'none';
668
+ document.getElementById('confirmMergeBtn').disabled = false;
669
+
670
+ // Store selected games for the merge operation
671
+ window.selectedGamesForMerge = gameNames;
672
+
673
+ openModal('gameMergeModal');
674
+ }
675
+ } catch (error) {
676
+ console.error('Error loading game data for merge:', error);
677
+ alert('Failed to load game data for merge');
678
+ }
679
+ }
680
+
681
+ async function confirmGameMerge() {
682
+ const gameNames = window.selectedGamesForMerge;
683
+
684
+ if (!gameNames || gameNames.length < 2) {
685
+ alert('Invalid game selection for merge');
686
+ return;
687
+ }
688
+
689
+ const errorDiv = document.getElementById('mergeError');
690
+ const successDiv = document.getElementById('mergeSuccess');
691
+ const loadingDiv = document.getElementById('mergeLoadingIndicator');
692
+ const confirmBtn = document.getElementById('confirmMergeBtn');
693
+
694
+ // Reset state
695
+ errorDiv.style.display = 'none';
696
+ successDiv.style.display = 'none';
697
+
698
+ // Show loading state
699
+ loadingDiv.style.display = 'flex';
700
+ confirmBtn.disabled = true;
701
+
702
+ try {
703
+ target_game = databaseManager.mergeTargetGame || gameNames[0];
704
+ const response = await fetch('/api/merge_games', {
705
+ method: 'POST',
706
+ headers: { 'Content-Type': 'application/json' },
707
+ body: JSON.stringify(
708
+ { target_game: target_game, games_to_merge: gameNames.filter(name => name !== target_game) })
709
+ });
710
+
711
+ const result = await response.json();
712
+
713
+ if (response.ok) {
714
+ // Show success message
715
+ successDiv.textContent = `Successfully merged ${result.merged_games.length} games into "${result.primary_game}"! Moved ${result.lines_moved} sentences.`;
716
+ successDiv.style.display = 'block';
717
+
718
+ // Auto-close modal after 2 seconds and refresh
719
+ setTimeout(async () => {
720
+ closeModal('gameMergeModal');
721
+ closeModal('gamesDeletionModal');
722
+ await databaseManager.loadDashboardStats();
723
+ }, 2000);
724
+
725
+ } else {
726
+ // Show error message
727
+ errorDiv.textContent = result.error || 'Failed to merge games';
728
+ errorDiv.style.display = 'block';
729
+ confirmBtn.disabled = false;
730
+ }
731
+ } catch (error) {
732
+ console.error('Error merging games:', error);
733
+ errorDiv.textContent = 'Network error occurred while merging games';
734
+ errorDiv.style.display = 'block';
735
+ confirmBtn.disabled = false;
736
+ } finally {
737
+ loadingDiv.style.display = 'none';
738
+ }
739
+ }
740
+
537
741
  // Initialize page when DOM loads
538
742
  let databaseManager;
539
743
  document.addEventListener('DOMContentLoaded', function() {