Luca448 commited on
Commit
eb08066
·
1 Parent(s): ff4b14c

Feat: Implement invisible pending queue for quick settings modal to allow background processing without blocking UI

Browse files
Files changed (4) hide show
  1. index.html +1 -1
  2. src/app.js +13 -24
  3. src/db/database.js +6 -2
  4. src/views/vocabulary-modal.js +10 -41
index.html CHANGED
@@ -74,7 +74,7 @@
74
  </head>
75
  <body class="dark-theme in-chat-view" style="visibility: hidden;">
76
 
77
- <div id="live-version-badge" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-secondary); opacity: 0.6; z-index: 99999; pointer-events: none; font-weight: 500; font-family: monospace; letter-spacing: 1px; background: rgba(0,0,0,0.2); padding: 2px 8px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.05);">BUILD 1.8.321</div>
78
 
79
  <!-- SVG Filters for Liquid Glass Refraction -->
80
  <svg width="0" height="0" style="position: absolute; pointer-events: none;">
 
74
  </head>
75
  <body class="dark-theme in-chat-view" style="visibility: hidden;">
76
 
77
+ <div id="live-version-badge" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-secondary); opacity: 0.6; z-index: 99999; pointer-events: none; font-weight: 500; font-family: monospace; letter-spacing: 1px; background: rgba(0,0,0,0.2); padding: 2px 8px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.05);">BUILD 1.8.322</div>
78
 
79
  <!-- SVG Filters for Liquid Glass Refraction -->
80
  <svg width="0" height="0" style="position: absolute; pointer-events: none;">
src/app.js CHANGED
@@ -365,31 +365,20 @@ Zu übersetzen: "${word}"`;
365
  // Only run if we're not in the tiny transparent overlay mode
366
  if (window.isOverlayMode) return;
367
 
368
- let decks = db.getDecks();
369
- if (!decks || decks.length === 0) return;
370
-
371
- let cardsToEnrich = [];
372
- for (const deck of decks) {
373
- const cards = db.getCardsForDeck(deck.id);
374
- cardsToEnrich.push(...cards.filter(c =>
375
- c.back.includes('<!-- NEEDS_ENRICHMENT -->') ||
376
- (!c.back.includes('**Erklärung:**') && !c.back.includes('Erklärung:'))
377
- ));
378
- }
379
-
380
- if (cardsToEnrich.length > 0) {
381
- console.log(`Found ${cardsToEnrich.length} cards needing AI enrichment...`);
382
-
383
- // Delay slightly to not block initial UI render
384
- setTimeout(async () => {
385
- try {
386
- const { aiRouter } = await import('./ai/router.js');
387
 
388
- for (const card of cardsToEnrich) {
389
- const word = card.front;
390
- const pureTranslation = card.back.replace('<!-- NEEDS_ENRICHMENT -->', '').trim();
391
-
392
- const prompt = `Erstelle eine kurze Erklärung und ein passendes Beispiel für diese Vokabelkarte, sowie einen Suchbegriff für ein Bild.
393
  Wort: "${word}"
394
  Übersetzung: "${pureTranslation}"
395
 
 
365
  // Only run if we're not in the tiny transparent overlay mode
366
  if (window.isOverlayMode) return;
367
 
368
+ let pendingCards = db.getPendingCards();
369
+ if (!pendingCards || pendingCards.length === 0) return;
370
+
371
+ if (!window.isEnriching) {
372
+ window.isEnriching = true;
373
+ try {
374
+ const { aiRouter } = await import('./ai/router.js');
375
+
376
+ // Process sequentially
377
+ for (const card of pendingCards) {
378
+ const word = card.front;
379
+ const pureTranslation = card.back.replace('<!-- PENDING_CORRECTION -->', '').replace('<!-- NEEDS_ENRICHMENT -->', '').trim();
 
 
 
 
 
 
 
380
 
381
+ const prompt = `Erstelle eine kurze Erklärung und ein passendes Beispiel für diese Vokabelkarte, sowie einen Suchbegriff für ein Bild.
 
 
 
 
382
  Wort: "${word}"
383
  Übersetzung: "${pureTranslation}"
384
 
src/db/database.js CHANGED
@@ -504,7 +504,7 @@ class Database {
504
  getDueCards(deckId) {
505
  const now = new Date().toISOString();
506
  return this.exec(
507
- "SELECT * FROM flashcards WHERE deck_id = ? AND next_review <= ? ORDER BY next_review ASC",
508
  [deckId, now]
509
  );
510
  }
@@ -519,11 +519,15 @@ class Database {
519
 
520
  getCardsForDeck(deckId) {
521
  return this.exec(
522
- "SELECT * FROM flashcards WHERE deck_id = ? ORDER BY created_at DESC",
523
  [deckId]
524
  );
525
  }
526
 
 
 
 
 
527
  searchFlashcards(query) {
528
  return this.exec(`
529
  SELECT f.*, d.title as deck_title, d.color as deck_color
 
504
  getDueCards(deckId) {
505
  const now = new Date().toISOString();
506
  return this.exec(
507
+ "SELECT * FROM flashcards WHERE deck_id = ? AND next_review <= ? AND back NOT LIKE '%<!-- PENDING_CORRECTION -->%' ORDER BY next_review ASC",
508
  [deckId, now]
509
  );
510
  }
 
519
 
520
  getCardsForDeck(deckId) {
521
  return this.exec(
522
+ "SELECT * FROM flashcards WHERE deck_id = ? AND back NOT LIKE '%<!-- PENDING_CORRECTION -->%' ORDER BY created_at DESC",
523
  [deckId]
524
  );
525
  }
526
 
527
+ getPendingCards() {
528
+ return this.exec("SELECT * FROM flashcards WHERE back LIKE '%<!-- PENDING_CORRECTION -->%' OR back LIKE '%<!-- NEEDS_ENRICHMENT -->%'");
529
+ }
530
+
531
  searchFlashcards(query) {
532
  return this.exec(`
533
  SELECT f.*, d.title as deck_title, d.color as deck_color
src/views/vocabulary-modal.js CHANGED
@@ -271,49 +271,18 @@ Nur das rohe JSON:`;
271
  const isDuplicate = existingCards.some(card => card.front.toLowerCase() === parsed.front.toLowerCase());
272
 
273
  if (!isDuplicate) {
274
- // Run AI correction and enrichment in the background BEFORE saving to DB
275
- // This keeps the modal fast but ensures typos aren't saved
276
- setTimeout(async () => {
277
- try {
278
- const { aiRouter } = await import('../ai/router.js');
279
- const prompt = `Erstelle eine kurze Erklärung und ein passendes Beispiel für diese Vokabelkarte, sowie einen Suchbegriff für ein Bild.
280
- Wort: "${parsed.front}"
281
- Übersetzung: "${parsed.back}"
282
-
283
- WICHTIG: Prüfe, ob das "Wort" offensichtlich falsch geschrieben ist (z.B. phonetisch nach Gehör geschrieben).
284
- Falls es einen Tippfehler enthält, gib das richtig geschriebene Wort im Feld "correctedWord" zurück. Wenn es korrekt geschrieben ist, lass das Feld weg oder setze es auf null.
285
-
286
- Generiere:
287
- - "explanation": 1-2 kurze Sätze in der Sprache der Übersetzung, die genau dieses Wort erklären. WICHTIG: Markiere das Lernwort (und seine Abwandlungen) IMMER fett mit HTML-Tags (z.B. <b>Wort</b>).
288
- - "example": Ein prägnanter Beispielsatz mit der Übersetzung. Das Lernwort MUSS auch hier mit <b>...</b> fett markiert sein.
289
- - "imageQuery": Ein präziser englischer Suchbegriff für die Bing-Bildersuche. Das Bild muss EINDEUTIG zur Übersetzung passen. Füge IMMER 'single photo, clear focus, high quality' am Ende hinzu. Keine abstrakten Cartoons.
290
-
291
- Antworte AUSSCHLIESSLICH mit einem validen JSON-Objekt. Keine Markdown-Formatierung.
292
- Beispiel für Tippfehler bei "abanden":
293
- {"correctedWord": "abandon", "explanation": "Jemanden oder etwas <b>verlassen</b> oder aufgeben.", "example": "Er musste das alte Auto <b>verlassen</b>.", "imageQuery": "abandoned old car single photo clear focus"}`;
294
-
295
- const response = await aiRouter.sendMessage(prompt, {
296
- model: 'gemini-3.1-flash-lite'
297
- });
298
 
299
- let jsonStr = response.trim();
300
- if (jsonStr.startsWith('```json')) jsonStr = jsonStr.replace(/```json/g, '').replace(/```/g, '').trim();
301
- else if (jsonStr.startsWith('```')) jsonStr = jsonStr.replace(/```/g, '').trim();
302
-
303
- const aiData = JSON.parse(jsonStr);
304
- const finalFront = aiData.correctedWord ? aiData.correctedWord : parsed.front;
305
- const finalBack = `${parsed.back}\n\n**Erklärung:** ${aiData.explanation}\n\n**Beispiel:** ${aiData.example}`;
306
- const searchTerms = encodeURIComponent(aiData.imageQuery || finalFront);
307
- const imageUrl = `https://tse1.mm.bing.net/th?q=${searchTerms}&w=800&h=1000&c=7&rs=1`;
308
 
309
- db.addFlashcard(schnelldeck.id, finalFront, finalBack, imageUrl);
310
- window.dispatchEvent(new CustomEvent('flashcards-updated'));
311
- } catch (e) {
312
- console.warn("Background AI enrichment failed, saving original.", e);
313
- db.addFlashcard(schnelldeck.id, parsed.front, parsed.back, '');
314
- window.dispatchEvent(new CustomEvent('flashcards-updated'));
315
- }
316
- }, 100);
317
  }
318
 
319
  // Show success with translation
 
271
  const isDuplicate = existingCards.some(card => card.front.toLowerCase() === parsed.front.toLowerCase());
272
 
273
  if (!isDuplicate) {
274
+ let finalBack = parsed.back;
275
+
276
+ if (parsed.isDeepL) {
277
+ // Add a temporary marker so the background job knows this needs correction and enrichment
278
+ finalBack = `${parsed.back}\n\n<!-- PENDING_CORRECTION -->`;
279
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
+ const searchTerms = encodeURIComponent(parsed.imageQuery || parsed.front);
282
+ const imageUrl = `https://tse1.mm.bing.net/th?q=${searchTerms}&w=800&h=1000&c=7&rs=1`;
 
 
 
 
 
 
 
283
 
284
+ db.addFlashcard(schnelldeck.id, parsed.front, finalBack, imageUrl);
285
+ window.dispatchEvent(new CustomEvent('flashcards-updated'));
 
 
 
 
 
 
286
  }
287
 
288
  // Show success with translation