File size: 6,863 Bytes
1f7d5fc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
import streamlit as st
import genanki
import uuid
import base64
import fitz # PyMuPDF
from youtube_transcript_api import YouTubeTranscriptApi
from PIL import Image
import io
# === CONFIG ===
st.set_page_config(page_title="FlashCardX", layout="wide")
# === THEME TOGGLE ===
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)
# === STATE ===
if 'deck' not in st.session_state:
st.session_state.deck = []
# === HEADER ===
st.title("๐ง FlashCardX โ Flashka Clone (Offline, Free)")
# === MANUAL Q/A FLASHCARD ENTRY ===
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.")
# === MCQ FLASHCARDS ===
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.")
# === PDF UPLOAD TO FLASHCARDS ===
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.")
# === YOUTUBE TO FLASHCARDS ===
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]: # limit to 10 cards
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}")
# === IMAGE OCCLUSION ===
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.")
# === SEARCH ===
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()]
# === DECK VIEW ===
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.")
# === GAMIFIED REVIEW ===
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']}")
# === EXPORT TO ANKI ===
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)
# === FOOTER ===
st.markdown("---")
st.caption("๐ง FlashCardX ยท A clean Flashka-style clone ยท Powered by Streamlit") |