| 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") |