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.
- GameSentenceMiner/ai/ai_prompting.py +6 -6
- GameSentenceMiner/anki.py +236 -152
- GameSentenceMiner/gametext.py +7 -4
- GameSentenceMiner/gsm.py +49 -10
- GameSentenceMiner/locales/en_us.json +7 -3
- GameSentenceMiner/locales/ja_jp.json +8 -4
- GameSentenceMiner/locales/zh_cn.json +8 -4
- GameSentenceMiner/obs.py +238 -59
- GameSentenceMiner/ocr/owocr_helper.py +1 -1
- GameSentenceMiner/tools/ss_selector.py +7 -8
- GameSentenceMiner/ui/__init__.py +0 -0
- GameSentenceMiner/ui/anki_confirmation.py +187 -0
- GameSentenceMiner/{config_gui.py → ui/config_gui.py} +100 -35
- GameSentenceMiner/ui/screenshot_selector.py +215 -0
- GameSentenceMiner/util/configuration.py +124 -22
- GameSentenceMiner/util/db.py +22 -13
- GameSentenceMiner/util/downloader/download_tools.py +2 -2
- GameSentenceMiner/util/ffmpeg.py +24 -30
- GameSentenceMiner/util/get_overlay_coords.py +34 -34
- GameSentenceMiner/util/gsm_utils.py +31 -1
- GameSentenceMiner/util/text_log.py +11 -9
- GameSentenceMiner/vad.py +31 -12
- GameSentenceMiner/web/database_api.py +742 -123
- GameSentenceMiner/web/static/css/dashboard-shared.css +241 -0
- GameSentenceMiner/web/static/css/kanji-grid.css +94 -2
- GameSentenceMiner/web/static/css/overview.css +850 -0
- GameSentenceMiner/web/static/css/popups-shared.css +126 -0
- GameSentenceMiner/web/static/css/shared.css +97 -0
- GameSentenceMiner/web/static/css/stats.css +192 -597
- GameSentenceMiner/web/static/js/anki_stats.js +6 -4
- GameSentenceMiner/web/static/js/database.js +209 -5
- GameSentenceMiner/web/static/js/goals.js +610 -0
- GameSentenceMiner/web/static/js/kanji-grid.js +267 -4
- GameSentenceMiner/web/static/js/overview.js +1176 -0
- GameSentenceMiner/web/static/js/shared.js +25 -0
- GameSentenceMiner/web/static/js/stats.js +154 -1459
- GameSentenceMiner/web/stats.py +2 -2
- GameSentenceMiner/web/templates/anki_stats.html +5 -0
- GameSentenceMiner/web/templates/components/kanji_grid/basic_kanji_book_bkb_v1_v2.json +17 -0
- GameSentenceMiner/web/templates/components/kanji_grid/duolingo_kanji.json +29 -0
- GameSentenceMiner/web/templates/components/kanji_grid/grade.json +17 -0
- GameSentenceMiner/web/templates/components/kanji_grid/hk_primary_learning.json +17 -0
- GameSentenceMiner/web/templates/components/kanji_grid/hkscs2016.json +13 -0
- GameSentenceMiner/web/templates/components/kanji_grid/hsk_levels.json +33 -0
- GameSentenceMiner/web/templates/components/kanji_grid/humanum_frequency_list.json +41 -0
- GameSentenceMiner/web/templates/components/kanji_grid/jis_levels.json +25 -0
- GameSentenceMiner/web/templates/components/kanji_grid/jlpt_level.json +29 -0
- GameSentenceMiner/web/templates/components/kanji_grid/jpdb_kanji_frequency_list.json +37 -0
- GameSentenceMiner/web/templates/components/kanji_grid/jpdbv2_kanji_frequency_list.json +161 -0
- GameSentenceMiner/web/templates/components/kanji_grid/jun_das_modern_chinese_character_frequency_list.json +13 -0
- GameSentenceMiner/web/templates/components/kanji_grid/kanji_in_context_revised_edition.json +37 -0
- GameSentenceMiner/web/templates/components/kanji_grid/kanji_kentei_level.json +61 -0
- GameSentenceMiner/web/templates/components/kanji_grid/mainland_china_elementary_textbook_characters.json +33 -0
- GameSentenceMiner/web/templates/components/kanji_grid/moe_way_quiz.json +47 -0
- GameSentenceMiner/web/templates/components/kanji_grid/official_kanji.json +25 -0
- GameSentenceMiner/web/templates/components/kanji_grid/remembering_the_kanji.json +25 -0
- GameSentenceMiner/web/templates/components/kanji_grid/standard_form_of_national_characters.json +25 -0
- GameSentenceMiner/web/templates/components/kanji_grid/table_of_general_standard_chinese_characters.json +21 -0
- GameSentenceMiner/web/templates/components/kanji_grid/the_kodansha_kanji_learners_course_klc.json +45 -0
- GameSentenceMiner/web/templates/components/kanji_grid/thousand_character_classic.json +13 -0
- GameSentenceMiner/web/templates/components/kanji_grid/wanikani_levels.json +249 -0
- GameSentenceMiner/web/templates/components/kanji_grid/words_hk_frequency_list.json +33 -0
- GameSentenceMiner/web/templates/components/navigation.html +3 -1
- GameSentenceMiner/web/templates/database.html +73 -1
- GameSentenceMiner/web/templates/goals.html +376 -0
- GameSentenceMiner/web/templates/index.html +13 -11
- GameSentenceMiner/web/templates/overview.html +416 -0
- GameSentenceMiner/web/templates/stats.html +46 -251
- GameSentenceMiner/web/texthooking_page.py +18 -0
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/METADATA +5 -1
- gamesentenceminer-2.18.1.dist-info/RECORD +132 -0
- gamesentenceminer-2.17.7.dist-info/RECORD +0 -98
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
130
|
+
const toDate = today.toLocaleDateString('en-CA');
|
|
131
|
+
toDateInput.value = toDate;
|
|
130
132
|
|
|
131
133
|
// Save in sessionStorage
|
|
132
|
-
sessionStorage.setItem("fromDateAnki",
|
|
133
|
-
sessionStorage.setItem("toDateAnki",
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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() {
|