|
|
import streamlit as st |
|
|
import genanki |
|
|
import uuid |
|
|
import base64 |
|
|
import fitz |
|
|
from youtube_transcript_api import YouTubeTranscriptApi |
|
|
from PIL import Image |
|
|
import io |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="FlashCardX", layout="wide") |
|
|
|
|
|
|
|
|
if 'dark_mode' not in st.session_state: |
|
|
st.session_state.dark_mode = False |
|
|
|
|
|
def toggle_theme(): |
|
|
st.session_state.dark_mode = not st.session_state.dark_mode |
|
|
|
|
|
theme_btn = "๐ Dark Mode" if not st.session_state.dark_mode else "โ๏ธ Light Mode" |
|
|
st.sidebar.button(theme_btn, on_click=toggle_theme) |
|
|
|
|
|
bg_color = "#1e1e1e" if st.session_state.dark_mode else "#ffffff" |
|
|
txt_color = "#f1f1f1" if st.session_state.dark_mode else "#000000" |
|
|
st.markdown(f"""<style>.stApp {{ background-color: {bg_color}; color: {txt_color}; }}</style>""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if 'deck' not in st.session_state: |
|
|
st.session_state.deck = [] |
|
|
|
|
|
|
|
|
st.title("๐ง FlashCardX โ Flashka Clone (Offline, Free)") |
|
|
|
|
|
|
|
|
st.header("โ Add Flashcard (Manual)") |
|
|
q = st.text_area("Question") |
|
|
a = st.text_area("Answer") |
|
|
if st.button("Add Flashcard"): |
|
|
if q.strip() and a.strip(): |
|
|
st.session_state.deck.append({'type': 'basic', 'q': q.strip(), 'a': a.strip()}) |
|
|
st.success("Card added.") |
|
|
else: |
|
|
st.warning("Both fields are required.") |
|
|
|
|
|
|
|
|
st.header("๐ Add MCQ Card") |
|
|
mcq_q = st.text_input("MCQ Question") |
|
|
opt1 = st.text_input("Option A") |
|
|
opt2 = st.text_input("Option B") |
|
|
opt3 = st.text_input("Option C") |
|
|
opt4 = st.text_input("Option D") |
|
|
mcq_ans = st.selectbox("Correct Answer", ["A", "B", "C", "D"]) |
|
|
if st.button("Add MCQ"): |
|
|
if all([mcq_q, opt1, opt2, opt3, opt4]): |
|
|
st.session_state.deck.append({ |
|
|
'type': 'mcq', |
|
|
'q': mcq_q, |
|
|
'options': [opt1, opt2, opt3, opt4], |
|
|
'a': mcq_ans |
|
|
}) |
|
|
st.success("MCQ added.") |
|
|
else: |
|
|
st.warning("All options must be filled.") |
|
|
|
|
|
|
|
|
st.header("๐ Upload PDF to Generate Flashcards") |
|
|
pdf_file = st.file_uploader("Upload PDF", type=['pdf']) |
|
|
if pdf_file: |
|
|
doc = fitz.open(stream=pdf_file.read(), filetype="pdf") |
|
|
for page in doc: |
|
|
text = page.get_text() |
|
|
lines = [l.strip() for l in text.split('\n') if l.strip()] |
|
|
for i in range(0, len(lines) - 1, 2): |
|
|
st.session_state.deck.append({'type': 'basic', 'q': lines[i], 'a': lines[i + 1]}) |
|
|
st.success("PDF parsed and flashcards added.") |
|
|
|
|
|
|
|
|
st.header("๐ฅ YouTube โ Flashcards") |
|
|
yt_url = st.text_input("YouTube video link") |
|
|
if st.button("Extract Transcript Cards"): |
|
|
try: |
|
|
video_id = yt_url.split("v=")[-1].split("&")[0] |
|
|
transcript = YouTubeTranscriptApi.get_transcript(video_id) |
|
|
for item in transcript[:10]: |
|
|
text = item['text'] |
|
|
st.session_state.deck.append({'type': 'basic', 'q': f"What is said here?", 'a': text}) |
|
|
st.success("Transcript converted to flashcards.") |
|
|
except Exception as e: |
|
|
st.error(f"Error: {e}") |
|
|
|
|
|
|
|
|
st.header("๐ผ๏ธ Image Occlusion Flashcard") |
|
|
img_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg']) |
|
|
if img_file: |
|
|
image = Image.open(img_file) |
|
|
st.image(image, caption="Original Image") |
|
|
label = st.text_input("Describe hidden info") |
|
|
if st.button("Add Image Occlusion Card"): |
|
|
img_bytes = io.BytesIO() |
|
|
image.save(img_bytes, format='PNG') |
|
|
b64_img = base64.b64encode(img_bytes.getvalue()).decode() |
|
|
q = f'<img src="data:image/png;base64,{b64_img}"><br><b>What is hidden?</b>' |
|
|
a = label |
|
|
st.session_state.deck.append({'type': 'basic', 'q': q, 'a': a}) |
|
|
st.success("Image occlusion card added.") |
|
|
|
|
|
|
|
|
st.header("๐ Search Deck") |
|
|
search_term = st.text_input("Enter keyword to filter") |
|
|
filtered_deck = [c for c in st.session_state.deck if search_term.lower() in c['q'].lower()] |
|
|
|
|
|
|
|
|
st.header("๐ Flashcard Deck Viewer") |
|
|
if filtered_deck: |
|
|
for i, card in enumerate(filtered_deck): |
|
|
with st.expander(f"Card {i+1}"): |
|
|
if card['type'] == 'basic': |
|
|
st.markdown(f"**Q:** {card['q']}", unsafe_allow_html=True) |
|
|
st.markdown(f"**A:** {card['a']}", unsafe_allow_html=True) |
|
|
elif card['type'] == 'mcq': |
|
|
st.markdown(f"**Q:** {card['q']}") |
|
|
for idx, opt in zip(['A', 'B', 'C', 'D'], card['options']): |
|
|
st.markdown(f"- {idx}. {opt}") |
|
|
st.markdown(f"**Answer:** {card['a']}") |
|
|
if st.button(f"๐๏ธ Delete", key=f"del_{i}"): |
|
|
st.session_state.deck.remove(card) |
|
|
st.experimental_rerun() |
|
|
else: |
|
|
st.info("No matching flashcards.") |
|
|
|
|
|
|
|
|
st.header("๐ฎ Gamified Review") |
|
|
if st.button("Start Review"): |
|
|
for card in st.session_state.deck: |
|
|
if card['type'] == 'basic': |
|
|
st.info(f"Q: {card['q']}") |
|
|
st.text_input("Your Answer") |
|
|
st.markdown(f"**Correct:** {card['a']}") |
|
|
elif card['type'] == 'mcq': |
|
|
st.info(card['q']) |
|
|
choice = st.radio("Choose", ["A", "B", "C", "D"], key=uuid.uuid4()) |
|
|
st.markdown(f"**Answer:** {card['a']}") |
|
|
|
|
|
|
|
|
st.sidebar.header("๐ฅ Export to Anki") |
|
|
deck_name = st.sidebar.text_input("Deck Name", value="MyFlashDeck") |
|
|
if st.sidebar.button("Download .apkg"): |
|
|
model_id = int(uuid.uuid4()) >> 64 |
|
|
model = genanki.Model( |
|
|
model_id, |
|
|
'FlashCardX Model', |
|
|
fields=[{'name': 'Question'}, {'name': 'Answer'}], |
|
|
templates=[{ |
|
|
'name': 'Card Template', |
|
|
'qfmt': '{{Question}}', |
|
|
'afmt': '{{FrontSide}}<hr>{{Answer}}', |
|
|
}], |
|
|
) |
|
|
my_deck = genanki.Deck(int(uuid.uuid4()) >> 64, deck_name) |
|
|
for card in st.session_state.deck: |
|
|
if card['type'] == 'basic': |
|
|
my_deck.add_note(genanki.Note(model=model, fields=[card['q'], card['a']])) |
|
|
elif card['type'] == 'mcq': |
|
|
opts = "\n".join([f"{i}. {opt}" for i, opt in zip("ABCD", card['options'])]) |
|
|
q = f"{card['q']}\n\n{opts}" |
|
|
a = f"Correct Answer: {card['a']}" |
|
|
my_deck.add_note(genanki.Note(model=model, fields=[q, a])) |
|
|
pkg = genanki.Package(my_deck) |
|
|
fname = f"{deck_name.replace(' ', '_')}.apkg" |
|
|
pkg.write_to_file(fname) |
|
|
with open(fname, "rb") as f: |
|
|
b64 = base64.b64encode(f.read()).decode() |
|
|
href = f'<a href="data:application/octet-stream;base64,{b64}" download="{fname}">๐ฆ Download {fname}</a>' |
|
|
st.sidebar.markdown(href, unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
st.caption("๐ง FlashCardX ยท A clean Flashka-style clone ยท Powered by Streamlit") |