Spaces:
Paused
Paused
Feat: Automatically ask AI to regenerate the card's back (translation, explanation, example, image) when the vocabulary word on the front is changed
Browse files- index.html +1 -1
- src/views/learn/learn-view.js +87 -2
index.html
CHANGED
|
@@ -51,7 +51,7 @@
|
|
| 51 |
</head>
|
| 52 |
<body class="dark-theme in-chat-view">
|
| 53 |
|
| 54 |
-
<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.1.
|
| 55 |
|
| 56 |
<!-- SVG Filters for Liquid Glass Refraction -->
|
| 57 |
<svg width="0" height="0" style="position: absolute; pointer-events: none;">
|
|
|
|
| 51 |
</head>
|
| 52 |
<body class="dark-theme in-chat-view">
|
| 53 |
|
| 54 |
+
<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.1.97</div>
|
| 55 |
|
| 56 |
<!-- SVG Filters for Liquid Glass Refraction -->
|
| 57 |
<svg width="0" height="0" style="position: absolute; pointer-events: none;">
|
src/views/learn/learn-view.js
CHANGED
|
@@ -291,11 +291,96 @@ export class LearnView {
|
|
| 291 |
};
|
| 292 |
}
|
| 293 |
|
| 294 |
-
updateCardFront(cardId, deckId, newFront) {
|
| 295 |
if (!newFront || newFront.trim() === '') return;
|
| 296 |
-
const res = db.exec("SELECT back FROM flashcards WHERE id = ?", [cardId]);
|
| 297 |
if (res && res.length > 0) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
db.editFlashcard(cardId, newFront.trim(), res[0].back);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}
|
| 300 |
}
|
| 301 |
|
|
|
|
| 291 |
};
|
| 292 |
}
|
| 293 |
|
| 294 |
+
async updateCardFront(cardId, deckId, newFront) {
|
| 295 |
if (!newFront || newFront.trim() === '') return;
|
| 296 |
+
const res = db.exec("SELECT front, back FROM flashcards WHERE id = ?", [cardId]);
|
| 297 |
if (res && res.length > 0) {
|
| 298 |
+
const oldFront = res[0].front;
|
| 299 |
+
if (oldFront.trim() === newFront.trim()) return;
|
| 300 |
+
|
| 301 |
+
// Front has changed, update it
|
| 302 |
db.editFlashcard(cardId, newFront.trim(), res[0].back);
|
| 303 |
+
this.renderDeckDetails(deckId);
|
| 304 |
+
|
| 305 |
+
// Show loading overlay on the back of the card
|
| 306 |
+
const previewModal = document.getElementById('preview-modal-overlay');
|
| 307 |
+
if (previewModal) {
|
| 308 |
+
const backEl = previewModal.querySelector('.flashcard-back');
|
| 309 |
+
if (backEl) {
|
| 310 |
+
const loader = document.createElement('div');
|
| 311 |
+
loader.id = 'ai-regenerate-loader';
|
| 312 |
+
loader.style = 'position: absolute; top:0; left:0; width:100%; height:100%; background: rgba(10,10,12,0.85); display: flex; flex-direction: column; align-items:center; justify-content:center; z-index: 10; color: var(--primary); font-weight: 500; font-size: 14px; backdrop-filter: blur(4px);';
|
| 313 |
+
loader.innerHTML = `
|
| 314 |
+
<style>@keyframes pulseAI { 0% { opacity:0.5; transform: scale(0.9); } 50% { opacity:1; transform: scale(1.1); } 100% { opacity:0.5; transform: scale(0.9); } }</style>
|
| 315 |
+
<div style="margin-bottom: 12px; font-size: 24px; animation: pulseAI 1.5s infinite;">✨</div>
|
| 316 |
+
<div>KI generiert neue Karte...</div>
|
| 317 |
+
`;
|
| 318 |
+
backEl.appendChild(loader);
|
| 319 |
+
|
| 320 |
+
// Flip the card automatically so the user sees the generation
|
| 321 |
+
const inner = previewModal.querySelector('.flashcard-inner');
|
| 322 |
+
if (inner && !inner.classList.contains('flipped')) {
|
| 323 |
+
inner.classList.add('flipped');
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
try {
|
| 329 |
+
const prompt = `Du bist ein KI-Tutor. Generiere die Rückseite für eine Vokabelkarte.
|
| 330 |
+
Wort: "${newFront.trim()}"
|
| 331 |
+
Gib AUSSCHLIESSLICH ein valides JSON-Objekt zurück, ohne Markdown-Formatierung wie \`\`\`json. Format:
|
| 332 |
+
{
|
| 333 |
+
"translation": "Die kurze Übersetzung",
|
| 334 |
+
"explanation": "Eine kurze Erklärung (1-2 Sätze, zwingend in der Zielsprache der Vokabel)",
|
| 335 |
+
"example": "Ein spezifischer, anspruchsvoller Beispielsatz (zwingend in der Zielsprache)",
|
| 336 |
+
"search_query": "Ein simpler englischer Suchbegriff für ein passendes Bild"
|
| 337 |
+
}`;
|
| 338 |
+
|
| 339 |
+
const aiResponse = await aiRouter.sendMessage(prompt, { history: [] });
|
| 340 |
+
let jsonStr = aiResponse;
|
| 341 |
+
if (jsonStr.includes('\`\`\`json')) {
|
| 342 |
+
jsonStr = jsonStr.split('\`\`\`json')[1].split('\`\`\`')[0];
|
| 343 |
+
} else if (jsonStr.includes('\`\`\`')) {
|
| 344 |
+
jsonStr = jsonStr.split('\`\`\`')[1].split('\`\`\`')[0];
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
const data = JSON.parse(jsonStr.trim());
|
| 348 |
+
const newBack = `${data.translation}\n\n**Erklärung:** ${data.explanation}\n\n**Beispiel:** ${data.example}`;
|
| 349 |
+
|
| 350 |
+
let newImageUrl = null;
|
| 351 |
+
if (data.search_query) {
|
| 352 |
+
const encodedQuery = encodeURIComponent(data.search_query + ' single photo, clear focus, high quality');
|
| 353 |
+
newImageUrl = `https://tse1.mm.bing.net/th?q=${encodedQuery}&w=800&h=1000&c=7&rs=1`;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
db.editFlashcard(cardId, newFront.trim(), newBack);
|
| 357 |
+
if (newImageUrl) {
|
| 358 |
+
db.exec("UPDATE flashcards SET image_url = ? WHERE id = ?", [newImageUrl, cardId]);
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
this.renderDeckDetails(deckId);
|
| 362 |
+
|
| 363 |
+
const currentModal = document.getElementById('preview-modal-overlay');
|
| 364 |
+
if (currentModal) {
|
| 365 |
+
const updatedRes = db.exec("SELECT * FROM flashcards WHERE id = ?", [cardId]);
|
| 366 |
+
if (updatedRes && updatedRes.length > 0) {
|
| 367 |
+
currentModal.remove();
|
| 368 |
+
this.previewCard(updatedRes[0], deckId);
|
| 369 |
+
// Ensure it stays flipped to show the result
|
| 370 |
+
setTimeout(() => {
|
| 371 |
+
const newInner = document.querySelector('#preview-modal-overlay .flashcard-inner');
|
| 372 |
+
if (newInner) newInner.classList.add('flipped');
|
| 373 |
+
}, 50);
|
| 374 |
+
}
|
| 375 |
+
}
|
| 376 |
+
} catch (e) {
|
| 377 |
+
console.error("Failed to regenerate card:", e);
|
| 378 |
+
const currentModal = document.getElementById('preview-modal-overlay');
|
| 379 |
+
if (currentModal) {
|
| 380 |
+
const loader = currentModal.querySelector('#ai-regenerate-loader');
|
| 381 |
+
if (loader) loader.remove();
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
}
|
| 385 |
}
|
| 386 |
|