NishantD commited on
Commit
5af59c4
·
verified ·
1 Parent(s): 80fec09

Upload 7 files

Browse files
Files changed (7) hide show
  1. LICENSE +24 -0
  2. README.md +97 -7
  3. app.py +270 -0
  4. example.png +0 -0
  5. favicon.ico +0 -0
  6. pyproject.toml +25 -0
  7. requirements.txt +2 -0
LICENSE ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2024, justin
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.md CHANGED
@@ -1,12 +1,102 @@
1
  ---
2
  title: AnkiGen
3
- emoji: 🐨
4
- colorFrom: gray
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 4.44.1
8
  app_file: app.py
9
- pinned: false
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: AnkiGen
3
+ emoji: 📚
 
 
 
 
4
  app_file: app.py
5
+ requirements: requirements.txt
6
+ python: 3.12
7
+ sdk: gradio
8
+ sdk_version: 4.44.0
9
  ---
10
 
11
+ # AnkiGen - Anki Card Generator
12
+
13
+ AnkiGen is a Gradio-based web application that generates Anki-compatible CSV files using Large Language Models (LLMs) based on user-specified subjects and preferences.
14
+
15
+ ## Features
16
+
17
+ - Generate Anki cards for various subjects
18
+ - Customizable number of topics and cards per topic
19
+ - User-friendly interface powered by Gradio
20
+ - Exports to CSV format compatible with Anki import
21
+ - Utilizes LLMs for high-quality content generation
22
+
23
+ ## TODO
24
+
25
+ - [ ] model dropdown - uses gpt4o-mini by default
26
+ - [ ] cloze (checkbox?)
27
+
28
+ ## Screenshot
29
+
30
+ ![AnkiGen Screenshot](example.png)
31
+
32
+
33
+ ## Installation for Local Use
34
+
35
+ 1. Clone this repository:
36
+
37
+ ```
38
+ git clone https://github.com/brickfrog/ankigen.git
39
+ cd ankigen
40
+ ```
41
+
42
+
43
+ 2. Install the required dependencies:
44
+
45
+ ```
46
+ pip install -r requirements.txt
47
+ ```
48
+
49
+ 3. Set up your OpenAI API key (required for LLM functionality).
50
+
51
+ ## Usage
52
+
53
+ 1. Run the application:
54
+
55
+ ```
56
+ gradio app.py --demo-name ankigen
57
+ ```
58
+
59
+ 2. Open your web browser and navigate to the provided local URL (typically `http://127.0.0.1:7860`).
60
+
61
+ 3. In the application interface:
62
+ - Enter your OpenAI API key
63
+ - Specify the subject you want to create cards for
64
+ - Adjust the number of topics and cards per topic
65
+ - (Optional) Add any preference prompts
66
+ - Click "Generate Cards"
67
+
68
+ 4. Review the generated cards in the interface.
69
+
70
+ 5. Click "Export to CSV" to download the Anki-compatible file.
71
+
72
+ ## CSV Format
73
+
74
+ The generated CSV file includes the following fields:
75
+ - Index
76
+ - Topic
77
+ - Question
78
+ - Answer
79
+ - Explanation
80
+ - Example
81
+
82
+ You can create a new note type in Anki with these fields to handle importing.
83
+
84
+ ## Development
85
+
86
+ This project is built with:
87
+ - Python 3.12
88
+ - Gradio 4.44.0
89
+
90
+ To contribute or modify:
91
+ 1. Make your changes in `app.py`
92
+ 2. Update `requirements.txt` if you add new dependencies
93
+ 3. Test thoroughly before submitting pull requests
94
+
95
+ ## License
96
+
97
+ BSD 2.0
98
+
99
+ ## Acknowledgments
100
+
101
+ - This project uses the Gradio library (https://gradio.app/) for the web interface
102
+ - Card generation is powered by OpenAI's language models
app.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import OpenAI
2
+ from pydantic import BaseModel
3
+ from typing import List, Optional
4
+ import gradio as gr
5
+
6
+
7
+ class Step(BaseModel):
8
+ explanation: str
9
+ output: str
10
+
11
+
12
+ class Subtopics(BaseModel):
13
+ steps: List[Step]
14
+ result: List[str]
15
+
16
+
17
+ class Topics(BaseModel):
18
+ result: List[Subtopics]
19
+
20
+
21
+ class CardFront(BaseModel):
22
+ question: Optional[str] = None
23
+
24
+
25
+ class CardBack(BaseModel):
26
+ answer: Optional[str] = None
27
+ explanation: str
28
+ example: str
29
+
30
+
31
+ class Card(BaseModel):
32
+ front: CardFront
33
+ back: CardBack
34
+
35
+
36
+ class CardList(BaseModel):
37
+ topic: str
38
+ cards: List[Card]
39
+
40
+
41
+ def structured_output_completion(
42
+ client, model, response_format, system_prompt, user_prompt
43
+ ):
44
+ try:
45
+ completion = client.beta.chat.completions.parse(
46
+ model=model,
47
+ messages=[
48
+ {"role": "system", "content": system_prompt.strip()},
49
+ {"role": "user", "content": user_prompt.strip()},
50
+ ],
51
+ response_format=response_format,
52
+ )
53
+
54
+ except Exception as e:
55
+ print(f"An error occurred during the API call: {e}")
56
+ return None
57
+
58
+ try:
59
+ if not hasattr(completion, "choices") or not completion.choices:
60
+ print("No choices returned in the completion.")
61
+ return None
62
+
63
+ first_choice = completion.choices[0]
64
+ if not hasattr(first_choice, "message"):
65
+ print("No message found in the first choice.")
66
+ return None
67
+
68
+ if not hasattr(first_choice.message, "parsed"):
69
+ print("Parsed message not available in the first choice.")
70
+ return None
71
+
72
+ return first_choice.message.parsed
73
+
74
+ except Exception as e:
75
+ print(f"An error occurred while processing the completion: {e}")
76
+ raise gr.Error(f"Processing error: {e}")
77
+
78
+
79
+ def generate_cards(
80
+ api_key_input,
81
+ subject,
82
+ topic_number=1,
83
+ cards_per_topic=2,
84
+ preference_prompt="assume I'm a beginner",
85
+ ):
86
+ """
87
+ Generates flashcards for a given subject.
88
+
89
+ Parameters:
90
+ - subject (str): The subject to generate cards for.
91
+ - topic_number (int): Number of topics to generate.
92
+ - cards_per_topic (int): Number of cards per topic.
93
+ - preference_prompt (str): User preferences to consider.
94
+
95
+ Returns:
96
+ - List[List[str]]: A list of rows containing
97
+ [topic, question, answer, explanation, example].
98
+ """
99
+
100
+ gr.Info("Starting process")
101
+
102
+ if not api_key_input:
103
+ return gr.Error("Error: OpenAI API key is required.")
104
+
105
+ client = OpenAI(api_key=api_key_input)
106
+ model = "gpt-4o-mini"
107
+
108
+ all_card_lists = []
109
+
110
+ system_prompt = f"""
111
+ You are an expert in {subject}, assisting the user to master the topic while
112
+ keeping in mind the user's preferences: {preference_prompt}.
113
+ """
114
+
115
+ topic_prompt = f"""
116
+ Generate the top {topic_number} important subjects to know on {subject} in
117
+ order of ascending difficulty.
118
+ """
119
+
120
+ try:
121
+ topics_response = structured_output_completion(
122
+ client, model, Topics, system_prompt, topic_prompt
123
+ )
124
+ if topics_response is None:
125
+ print("Failed to generate topics.")
126
+ return []
127
+ if not hasattr(topics_response, "result") or not topics_response.result:
128
+ print("Invalid topics response format.")
129
+ return []
130
+ topic_list = [
131
+ item for subtopic in topics_response.result for item in subtopic.result
132
+ ][:topic_number]
133
+ except Exception as e:
134
+ raise gr.Error(f"Topic generation failed due to {e}")
135
+
136
+ for topic in topic_list:
137
+ card_prompt = f"""
138
+ You are to generate {cards_per_topic} cards on {subject}: "{topic}"
139
+ keeping in mind the user's preferences: {preference_prompt}.
140
+
141
+ Questions should cover both sample problems and concepts.
142
+
143
+ Use the explanation field to help the user understand the reason behind things
144
+ and maximize learning. Additionally, offer tips (performance, gotchas, etc.).
145
+ """
146
+
147
+ try:
148
+ cards = structured_output_completion(
149
+ client, model, CardList, system_prompt, card_prompt
150
+ )
151
+ if cards is None:
152
+ print(f"Failed to generate cards for topic '{topic}'.")
153
+ continue
154
+ if not hasattr(cards, "topic") or not hasattr(cards, "cards"):
155
+ print(f"Invalid card response format for topic '{topic}'.")
156
+ continue
157
+ all_card_lists.append(cards)
158
+ except Exception as e:
159
+ print(f"An error occurred while generating cards for topic '{topic}': {e}")
160
+ continue
161
+
162
+ flattened_data = []
163
+
164
+ for card_list_index, card_list in enumerate(all_card_lists, start=1):
165
+ try:
166
+ topic = card_list.topic
167
+ # Get the total number of cards in this list to determine padding
168
+ total_cards = len(card_list.cards)
169
+ # Calculate the number of digits needed for padding
170
+ padding = len(str(total_cards))
171
+
172
+ for card_index, card in enumerate(card_list.cards, start=1):
173
+ # Format the index with zero-padding
174
+ index = f"{card_list_index}.{card_index:0{padding}}"
175
+ question = card.front.question
176
+ answer = card.back.answer
177
+ explanation = card.back.explanation
178
+ example = card.back.example
179
+ row = [index, topic, question, answer, explanation, example]
180
+ flattened_data.append(row)
181
+ except Exception as e:
182
+ print(f"An error occurred while processing card {index}: {e}")
183
+ continue
184
+
185
+ return flattened_data
186
+
187
+
188
+ def export_csv(d):
189
+ MIN_ROWS = 2
190
+
191
+ if len(d) < MIN_ROWS:
192
+ gr.Warning(f"The dataframe has fewer than {MIN_ROWS} rows. Nothing to export.")
193
+ return None
194
+
195
+ gr.Info("Exporting...")
196
+ d.to_csv("anki_deck.csv", index=False)
197
+ return gr.File(value="anki_deck.csv", visible=True)
198
+
199
+
200
+ with gr.Blocks(
201
+ gr.themes.Soft(), title="AnkiGen", css="footer{display:none !important}"
202
+ ) as ankigen:
203
+ gr.Markdown("# 📚 AnkiGen - Anki Card Generator")
204
+ gr.Markdown("#### Generate an LLM generated Anki comptible csv based on your subject and preferences.") #noqa
205
+
206
+ with gr.Row():
207
+ with gr.Column(scale=1):
208
+ gr.Markdown("### Configuration")
209
+
210
+ api_key_input = gr.Textbox(
211
+ label="OpenAI API Key",
212
+ type="password",
213
+ placeholder="Enter your OpenAI API key",
214
+ )
215
+ subject = gr.Textbox(
216
+ label="Subject",
217
+ placeholder="Enter the subject, e.g., 'Basic SQL Concepts'",
218
+ )
219
+ topic_number = gr.Slider(
220
+ label="Number of Topics", minimum=2, maximum=20, step=1, value=2
221
+ )
222
+ cards_per_topic = gr.Slider(
223
+ label="Cards per Topic", minimum=2, maximum=30, step=1, value=3
224
+ )
225
+ preference_prompt = gr.Textbox(
226
+ label="Preference Prompt",
227
+ placeholder=
228
+ """Any preferences? For example: Learning level, e.g., "Assume I'm a beginner" or "Target an advanced audience" Content scope, e.g., "Only cover up until subqueries in SQL" or "Focus on organic chemistry basics""", #noqa
229
+ )
230
+ generate_button = gr.Button("Generate Cards")
231
+ with gr.Column(scale=2):
232
+ gr.Markdown("### Generated Cards")
233
+ gr.Markdown(
234
+ """
235
+ Subject to change: currently exports a .csv with the following fields, you can
236
+ create a new note type with these fields to handle importing.:
237
+ <b>Index, Topic, Question, Answer, Explanation, Example</b>
238
+ """
239
+ )
240
+ output = gr.Dataframe(
241
+ headers=[
242
+ "Index",
243
+ "Topic",
244
+ "Question",
245
+ "Answer",
246
+ "Explanation",
247
+ "Example",
248
+ ],
249
+ interactive=False,
250
+ height=800,
251
+ )
252
+ export_button = gr.Button("Export to CSV")
253
+ download_link = gr.File(interactive=False, visible=False)
254
+
255
+ generate_button.click(
256
+ fn=generate_cards,
257
+ inputs=[
258
+ api_key_input,
259
+ subject,
260
+ topic_number,
261
+ cards_per_topic,
262
+ preference_prompt,
263
+ ],
264
+ outputs=output,
265
+ )
266
+
267
+ export_button.click(fn=export_csv, inputs=output, outputs=download_link)
268
+
269
+ if __name__ == "__main__":
270
+ ankigen.launch(share=False, favicon_path="./favicon.ico")
example.png ADDED
favicon.ico ADDED
pyproject.toml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ankigen"
7
+ version = "0.1.0"
8
+ description = ""
9
+ authors = [
10
+ {name = "Justin", email = "9146678+brickfrog@users.noreply.github.com"}
11
+ ]
12
+ readme = "README.md"
13
+ requires-python = ">=3.12"
14
+ dependencies = [
15
+ "openai>=1.35.10",
16
+ "gradio>=4.44.1",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "ipykernel>=6.29.5",
22
+ ]
23
+
24
+ [tool.setuptools]
25
+ py-modules = ["app"]
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio
2
+ openai