|
|
from __future__ import annotations |
|
|
import json |
|
|
import random |
|
|
import tempfile |
|
|
from pathlib import Path |
|
|
from typing import Optional |
|
|
|
|
|
import gradio as gr |
|
|
from agent import create_anki_deck |
|
|
|
|
|
try: |
|
|
import genanki |
|
|
|
|
|
GENANKI_AVAILABLE = True |
|
|
except ImportError: |
|
|
GENANKI_AVAILABLE = False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_package_file(deck_dict: dict, deck_name_override: str | None = None) -> str: |
|
|
deck_title = (deck_name_override or deck_dict.get("name") or "AI_Deck").strip() |
|
|
|
|
|
if GENANKI_AVAILABLE: |
|
|
|
|
|
import genanki |
|
|
basic_model = genanki.Model( |
|
|
1607392319, |
|
|
"AI Basic Model", |
|
|
fields=[{"name": "Front"}, {"name": "Back"}], |
|
|
templates=[{ |
|
|
"name": "Card 1", |
|
|
"qfmt": "{{Front}}", |
|
|
"afmt": "{{FrontSide}}<hr id=answer>{{Back}}", |
|
|
}], |
|
|
) |
|
|
cloze_model = genanki.Model( |
|
|
1091735104, |
|
|
"AI Cloze Model", |
|
|
model_type=genanki.Model.CLOZE, |
|
|
fields=[{"name": "Text"}, {"name": "Back"}], |
|
|
templates=[{ |
|
|
"name": "Cloze Card", |
|
|
"qfmt": "{{cloze:Text}}", |
|
|
"afmt": "{{cloze:Text}}<br>{{Back}}", |
|
|
}], |
|
|
) |
|
|
deck = genanki.Deck(random.getrandbits(32), deck_title) |
|
|
tags = deck_dict.get("tags", []) |
|
|
for card in deck_dict["cards"]: |
|
|
model = cloze_model if card["type"].lower().startswith("cloze") else basic_model |
|
|
note = genanki.Note(model=model, fields=[card["front"], card["back"]], tags=tags) |
|
|
deck.add_note(note) |
|
|
pkg = genanki.Package(deck) |
|
|
tmpf = tempfile.NamedTemporaryFile(delete=False, suffix=".apkg") |
|
|
pkg.write_to_file(tmpf.name) |
|
|
return tmpf.name |
|
|
|
|
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".json", mode="w", encoding="utf-8") as tf: |
|
|
json.dump(deck_dict, tf, ensure_ascii=False, indent=2) |
|
|
return tf.name |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_deck( |
|
|
uploaded_path: Optional[str], |
|
|
url_input: str, |
|
|
card_type_pref: str, |
|
|
deck_name_pref: str, |
|
|
tags_pref: str, |
|
|
user_req_pref: str, |
|
|
): |
|
|
if not uploaded_path and not url_input.strip(): |
|
|
raise gr.Error("Please upload a file or enter a URL.") |
|
|
|
|
|
params: dict[str, object] = { |
|
|
"card_types": card_type_pref, |
|
|
"user_requirements": user_req_pref, |
|
|
} |
|
|
|
|
|
if uploaded_path: |
|
|
path = Path(uploaded_path) |
|
|
params["pdf_file" if path.suffix.lower() == ".pdf" else "img_file"] = path |
|
|
else: |
|
|
params["url"] = url_input.strip() |
|
|
|
|
|
|
|
|
deck_dict = create_anki_deck(**params)["deck"] |
|
|
|
|
|
if deck_name_pref.strip(): |
|
|
deck_dict["name"] = deck_name_pref.strip() |
|
|
if tags_pref.strip(): |
|
|
deck_dict["tags"] = [t.strip() for t in tags_pref.split(",") if t.strip()] |
|
|
|
|
|
output_path = _build_package_file(deck_dict, deck_name_pref) |
|
|
return str(output_path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
demo = gr.Blocks(theme=gr.themes.Soft(), title="Anki Card Generator") |
|
|
|
|
|
with demo: |
|
|
gr.Markdown("# π§ Anki Card Generator") |
|
|
gr.Markdown("Upload an image or PDF, or enter a URL to generate Anki cards.") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
file_input = gr.File(label="π Upload PDF or image", file_types=["image", ".pdf"], type="filepath") |
|
|
url_input = gr.Textbox(label="π Or enter url", placeholder="https://example.com/article") |
|
|
user_requirements_input = gr.Textbox(label="π― Your requirements", lines=4) |
|
|
card_type_input = gr.Textbox(label="π Card type", value="Basic,Cloze") |
|
|
deck_name_input = gr.Textbox(label="π·οΈ Deck name", value="Animal") |
|
|
tags_input = gr.Textbox(label="β¨ Tags (comma-separated)", placeholder="e.g. biology, mammals", value="biology,mammals") |
|
|
generate_button = gr.Button("Generate", variant="primary") |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
anki_output_file = gr.File(label="π₯ Download (.apkg / .json)") |
|
|
|
|
|
generate_button.click( |
|
|
fn=generate_deck, |
|
|
inputs=[file_input, url_input, card_type_input, deck_name_input, tags_input, user_requirements_input], |
|
|
outputs=[anki_output_file], |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown("β
genanki :{}".format("Available" if GENANKI_AVAILABLE else "Not installed (will export JSON)")) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|