Pavaas commited on
Commit
1f7d5fc
ยท
verified ยท
1 Parent(s): 5a54e85

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -0
app.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import genanki
3
+ import uuid
4
+ import base64
5
+ import fitz # PyMuPDF
6
+ from youtube_transcript_api import YouTubeTranscriptApi
7
+ from PIL import Image
8
+ import io
9
+
10
+ # === CONFIG ===
11
+ st.set_page_config(page_title="FlashCardX", layout="wide")
12
+
13
+ # === THEME TOGGLE ===
14
+ if 'dark_mode' not in st.session_state:
15
+ st.session_state.dark_mode = False
16
+
17
+ def toggle_theme():
18
+ st.session_state.dark_mode = not st.session_state.dark_mode
19
+
20
+ theme_btn = "๐ŸŒ™ Dark Mode" if not st.session_state.dark_mode else "โ˜€๏ธ Light Mode"
21
+ st.sidebar.button(theme_btn, on_click=toggle_theme)
22
+
23
+ bg_color = "#1e1e1e" if st.session_state.dark_mode else "#ffffff"
24
+ txt_color = "#f1f1f1" if st.session_state.dark_mode else "#000000"
25
+ st.markdown(f"""<style>.stApp {{ background-color: {bg_color}; color: {txt_color}; }}</style>""", unsafe_allow_html=True)
26
+
27
+ # === STATE ===
28
+ if 'deck' not in st.session_state:
29
+ st.session_state.deck = []
30
+
31
+ # === HEADER ===
32
+ st.title("๐Ÿง  FlashCardX โ€” Flashka Clone (Offline, Free)")
33
+
34
+ # === MANUAL Q/A FLASHCARD ENTRY ===
35
+ st.header("โž• Add Flashcard (Manual)")
36
+ q = st.text_area("Question")
37
+ a = st.text_area("Answer")
38
+ if st.button("Add Flashcard"):
39
+ if q.strip() and a.strip():
40
+ st.session_state.deck.append({'type': 'basic', 'q': q.strip(), 'a': a.strip()})
41
+ st.success("Card added.")
42
+ else:
43
+ st.warning("Both fields are required.")
44
+
45
+ # === MCQ FLASHCARDS ===
46
+ st.header("๐Ÿ“ Add MCQ Card")
47
+ mcq_q = st.text_input("MCQ Question")
48
+ opt1 = st.text_input("Option A")
49
+ opt2 = st.text_input("Option B")
50
+ opt3 = st.text_input("Option C")
51
+ opt4 = st.text_input("Option D")
52
+ mcq_ans = st.selectbox("Correct Answer", ["A", "B", "C", "D"])
53
+ if st.button("Add MCQ"):
54
+ if all([mcq_q, opt1, opt2, opt3, opt4]):
55
+ st.session_state.deck.append({
56
+ 'type': 'mcq',
57
+ 'q': mcq_q,
58
+ 'options': [opt1, opt2, opt3, opt4],
59
+ 'a': mcq_ans
60
+ })
61
+ st.success("MCQ added.")
62
+ else:
63
+ st.warning("All options must be filled.")
64
+
65
+ # === PDF UPLOAD TO FLASHCARDS ===
66
+ st.header("๐Ÿ“„ Upload PDF to Generate Flashcards")
67
+ pdf_file = st.file_uploader("Upload PDF", type=['pdf'])
68
+ if pdf_file:
69
+ doc = fitz.open(stream=pdf_file.read(), filetype="pdf")
70
+ for page in doc:
71
+ text = page.get_text()
72
+ lines = [l.strip() for l in text.split('\n') if l.strip()]
73
+ for i in range(0, len(lines) - 1, 2):
74
+ st.session_state.deck.append({'type': 'basic', 'q': lines[i], 'a': lines[i + 1]})
75
+ st.success("PDF parsed and flashcards added.")
76
+
77
+ # === YOUTUBE TO FLASHCARDS ===
78
+ st.header("๐ŸŽฅ YouTube โ†’ Flashcards")
79
+ yt_url = st.text_input("YouTube video link")
80
+ if st.button("Extract Transcript Cards"):
81
+ try:
82
+ video_id = yt_url.split("v=")[-1].split("&")[0]
83
+ transcript = YouTubeTranscriptApi.get_transcript(video_id)
84
+ for item in transcript[:10]: # limit to 10 cards
85
+ text = item['text']
86
+ st.session_state.deck.append({'type': 'basic', 'q': f"What is said here?", 'a': text})
87
+ st.success("Transcript converted to flashcards.")
88
+ except Exception as e:
89
+ st.error(f"Error: {e}")
90
+
91
+ # === IMAGE OCCLUSION ===
92
+ st.header("๐Ÿ–ผ๏ธ Image Occlusion Flashcard")
93
+ img_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg'])
94
+ if img_file:
95
+ image = Image.open(img_file)
96
+ st.image(image, caption="Original Image")
97
+ label = st.text_input("Describe hidden info")
98
+ if st.button("Add Image Occlusion Card"):
99
+ img_bytes = io.BytesIO()
100
+ image.save(img_bytes, format='PNG')
101
+ b64_img = base64.b64encode(img_bytes.getvalue()).decode()
102
+ q = f'<img src="data:image/png;base64,{b64_img}"><br><b>What is hidden?</b>'
103
+ a = label
104
+ st.session_state.deck.append({'type': 'basic', 'q': q, 'a': a})
105
+ st.success("Image occlusion card added.")
106
+
107
+ # === SEARCH ===
108
+ st.header("๐Ÿ” Search Deck")
109
+ search_term = st.text_input("Enter keyword to filter")
110
+ filtered_deck = [c for c in st.session_state.deck if search_term.lower() in c['q'].lower()]
111
+
112
+ # === DECK VIEW ===
113
+ st.header("๐Ÿ“š Flashcard Deck Viewer")
114
+ if filtered_deck:
115
+ for i, card in enumerate(filtered_deck):
116
+ with st.expander(f"Card {i+1}"):
117
+ if card['type'] == 'basic':
118
+ st.markdown(f"**Q:** {card['q']}", unsafe_allow_html=True)
119
+ st.markdown(f"**A:** {card['a']}", unsafe_allow_html=True)
120
+ elif card['type'] == 'mcq':
121
+ st.markdown(f"**Q:** {card['q']}")
122
+ for idx, opt in zip(['A', 'B', 'C', 'D'], card['options']):
123
+ st.markdown(f"- {idx}. {opt}")
124
+ st.markdown(f"**Answer:** {card['a']}")
125
+ if st.button(f"๐Ÿ—‘๏ธ Delete", key=f"del_{i}"):
126
+ st.session_state.deck.remove(card)
127
+ st.experimental_rerun()
128
+ else:
129
+ st.info("No matching flashcards.")
130
+
131
+ # === GAMIFIED REVIEW ===
132
+ st.header("๐ŸŽฎ Gamified Review")
133
+ if st.button("Start Review"):
134
+ for card in st.session_state.deck:
135
+ if card['type'] == 'basic':
136
+ st.info(f"Q: {card['q']}")
137
+ st.text_input("Your Answer")
138
+ st.markdown(f"**Correct:** {card['a']}")
139
+ elif card['type'] == 'mcq':
140
+ st.info(card['q'])
141
+ choice = st.radio("Choose", ["A", "B", "C", "D"], key=uuid.uuid4())
142
+ st.markdown(f"**Answer:** {card['a']}")
143
+
144
+ # === EXPORT TO ANKI ===
145
+ st.sidebar.header("๐Ÿ“ฅ Export to Anki")
146
+ deck_name = st.sidebar.text_input("Deck Name", value="MyFlashDeck")
147
+ if st.sidebar.button("Download .apkg"):
148
+ model_id = int(uuid.uuid4()) >> 64
149
+ model = genanki.Model(
150
+ model_id,
151
+ 'FlashCardX Model',
152
+ fields=[{'name': 'Question'}, {'name': 'Answer'}],
153
+ templates=[{
154
+ 'name': 'Card Template',
155
+ 'qfmt': '{{Question}}',
156
+ 'afmt': '{{FrontSide}}<hr>{{Answer}}',
157
+ }],
158
+ )
159
+ my_deck = genanki.Deck(int(uuid.uuid4()) >> 64, deck_name)
160
+ for card in st.session_state.deck:
161
+ if card['type'] == 'basic':
162
+ my_deck.add_note(genanki.Note(model=model, fields=[card['q'], card['a']]))
163
+ elif card['type'] == 'mcq':
164
+ opts = "\n".join([f"{i}. {opt}" for i, opt in zip("ABCD", card['options'])])
165
+ q = f"{card['q']}\n\n{opts}"
166
+ a = f"Correct Answer: {card['a']}"
167
+ my_deck.add_note(genanki.Note(model=model, fields=[q, a]))
168
+ pkg = genanki.Package(my_deck)
169
+ fname = f"{deck_name.replace(' ', '_')}.apkg"
170
+ pkg.write_to_file(fname)
171
+ with open(fname, "rb") as f:
172
+ b64 = base64.b64encode(f.read()).decode()
173
+ href = f'<a href="data:application/octet-stream;base64,{b64}" download="{fname}">๐Ÿ“ฆ Download {fname}</a>'
174
+ st.sidebar.markdown(href, unsafe_allow_html=True)
175
+
176
+ # === FOOTER ===
177
+ st.markdown("---")
178
+ st.caption("๐Ÿง  FlashCardX ยท A clean Flashka-style clone ยท Powered by Streamlit")