anki-generator/anki_generator/card_generator.py

132 lines
5.2 KiB
Python

import os
import random
import genanki
from datetime import datetime
from typing import List, Tuple, Optional
from .models import GermanWord, UnsplashImage
from .clients.llm import AnthropicClient
from .clients.unsplash import UnsplashClient
class GermanDeckPackage(genanki.Package):
"""Custom Package class to include media files"""
def __init__(self, deck, media_files):
super().__init__(deck)
self.media_files = media_files
class CardGenerator:
def __init__(self, llm_client: AnthropicClient, unsplash_client: UnsplashClient):
self.llm_client = llm_client
self.unsplash_client = unsplash_client
self.model = self._create_model()
def _create_model(self):
"""Create the Anki note model"""
return genanki.Model(
random.randrange(1 << 30, 1 << 31),
'German Vocabulary Model',
fields=[
{'name': 'German_Word'},
{'name': 'Part_of_Speech'},
{'name': 'Source'},
{'name': 'English_Meaning'},
{'name': 'Article'},
{'name': 'Plural_Form'},
{'name': 'Example_Sentence'},
{'name': 'Sentence_Translation'},
{'name': 'Usage_Notes'},
{'name': 'Related_Words'},
{'name': 'Image'},
{'name': 'Image_Credit'},
{'name': 'Tags'}
],
templates=[
{
'name': 'German Vocabulary Card',
'qfmt': '''
<div style="font-size: 24px;">{{German_Word}}</div>
<div style="font-size: 18px;">Part of speech: {{Part_of_Speech}}</div>
<div style="font-size: 14px;">Source: {{Source}}</div>
{{#Image}}<div><img src="{{text:Image}}" style="max-width: 300px; max-height: 200px;"></div>{{/Image}}
''',
'afmt': '''
{{FrontSide}}
<hr id="answer">
<div><b>English meaning:</b> {{English_Meaning}}</div>
<div><b>Article:</b> {{Article}}</div>
<div><b>Plural Form:</b> {{Plural_Form}}</div>
<div><b>Example:</b> {{Example_Sentence}}</div>
<div><b>Translation:</b> {{Sentence_Translation}}</div>
<div><b>Usage notes:</b> {{Usage_Notes}}</div>
<div><b>Related words:</b> {{Related_Words}}</div>
{{#Image_Credit}}<div><small>Photo: {{Image_Credit}}</small></div>{{/Image_Credit}}
<div><small>Tags: {{Tags}}</small></div>
'''
}
],
css='''
.card {
font-family: arial;
font-size: 16px;
text-align: center;
color: black;
background-color: white;
}
img {
margin: 20px auto;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
'''
)
def create_deck(self, word_list: List[Tuple[str, str]], deck_name: str = "German Vocabulary") -> Tuple[genanki.Deck, List[str]]:
"""Create an Anki deck from a list of words"""
deck_id = random.randrange(1 << 30, 1 << 31)
deck = genanki.Deck(deck_id, deck_name)
media_files = []
for word, source in word_list:
try:
card_info_dict = self.llm_client.get_card_info(word, source)
card_info = GermanWord(**card_info_dict)
# Get image
image_filename = f"media/{word.lower().replace(' ', '_')}.jpg"
image = self.unsplash_client.get_image(
card_info.image_search_term,
image_filename
)
if image and image.local_path:
media_files.append(image.local_path)
image_filename = os.path.basename(image.local_path)
else:
image_filename = ""
# Create note
note = genanki.Note(
model=self.model,
fields=[
card_info.german_word,
card_info.part_of_speech,
source,
card_info.english_meaning,
card_info.article or "",
card_info.plural_form,
card_info.example_sentence,
card_info.sentence_translation,
card_info.usage_notes,
card_info.related_words,
image_filename,
image.photographer if image else "",
' '.join(card_info.tags)
]
)
deck.add_note(note)
print(f"Added card for: {word}")
except Exception as e:
print(f"Error creating card for {word}: {str(e)}")
return deck, media_files