|
import gradio as gr |
|
import pytz |
|
import os |
|
import shutil |
|
import re |
|
import matplotlib.pyplot as plt |
|
from datetime import datetime |
|
from markdown import instructions_markdown, faq_markdown |
|
from fsrs_optimizer import Optimizer |
|
from pathlib import Path |
|
from utilities import cleanup |
|
|
|
with open("./requirements.txt", "r") as f: |
|
txt = f.read().strip() |
|
version = re.search(r"FSRS-Optimizer==(.*)", txt).group(1) |
|
|
|
home_path = os.getcwd() |
|
|
|
|
|
def get_w_markdown(w): |
|
return f""" |
|
# Updated Parameters |
|
Copy and paste these as shown in step 5 of the instructions: |
|
|
|
`{w}` |
|
|
|
Check out the Analysis tab for more detailed information. |
|
|
|
**Note**: These values should be used with FSRS scheduler v4.0.0 or above. |
|
""" |
|
|
|
|
|
def optimizer( |
|
file: gr.File, |
|
timezone, |
|
next_day_starts_at, |
|
revlog_start_date, |
|
filter_out_suspended_cards, |
|
requestRetention, |
|
progress=gr.Progress(track_tqdm=True), |
|
): |
|
os.chdir(home_path) |
|
if file is None: |
|
raise ValueError("Please upload a deck/collection/csv file.") |
|
if file.name.endswith(".apkg") or file.name.endswith(".colpkg"): |
|
mode = "anki" |
|
elif file.name.endswith(".csv"): |
|
mode = "csv" |
|
else: |
|
raise ValueError( |
|
"File must be an Anki deck/collection file (.apkg or .colpkg) or a csv file." |
|
) |
|
if timezone == "": |
|
raise ValueError("Please select a timezone.") |
|
now = datetime.now() |
|
files = [ |
|
"prediction.tsv", |
|
"revlog.csv", |
|
"revlog_history.tsv", |
|
"stability_for_analysis.tsv", |
|
"expected_time.csv", |
|
"evaluation.tsv", |
|
] |
|
prefix = now.strftime(f"%Y_%m_%d_%H_%M_%S") |
|
suffix = file.name.split("/")[-1].replace(".", "_").replace("@", "_") |
|
proj_dir = Path(f"projects/{prefix}/{suffix}") |
|
proj_dir.mkdir(parents=True, exist_ok=True) |
|
os.chdir(proj_dir) |
|
optimizer = Optimizer() |
|
if mode == "anki": |
|
optimizer.anki_extract(file.name, filter_out_suspended_cards) |
|
else: |
|
print(file.name) |
|
shutil.copyfile(file.name, "./revlog.csv") |
|
analysis_markdown = optimizer.create_time_series( |
|
timezone, revlog_start_date, next_day_starts_at |
|
).replace("\n", "\n\n") |
|
optimizer.define_model() |
|
optimizer.pretrain(verbose=False) |
|
optimizer.train(verbose=False) |
|
print(optimizer.w) |
|
w_markdown = get_w_markdown(optimizer.w) |
|
optimizer.predict_memory_states() |
|
difficulty_distribution = optimizer.difficulty_distribution.to_string().replace( |
|
"\n", "\n\n" |
|
) |
|
try: |
|
plot_output = optimizer.find_optimal_retention( |
|
learn_span=365, |
|
max_ivl=36500, |
|
loss_aversion=2.5, |
|
)[0] |
|
except: |
|
print("Failed to find optimal retention") |
|
optimizer.optimal_retention = 0.9 |
|
plot_output = None |
|
suggested_retention_markdown = ( |
|
f"""# Suggested Retention: `{optimizer.optimal_retention:.2f}`""" |
|
) |
|
rating_markdown = optimizer.preview(requestRetention).replace("\n", "\n\n") |
|
loss_before, loss_after = optimizer.evaluate() |
|
loss_markdown = f""" |
|
**Loss before training**: {loss_before} |
|
|
|
**Loss after training**: {loss_after} |
|
""" |
|
|
|
|
|
markdown_out = f"""{suggested_retention_markdown} |
|
|
|
# Loss Information |
|
{loss_markdown} |
|
|
|
# Difficulty Distribution |
|
{difficulty_distribution} |
|
|
|
# Ratings |
|
{rating_markdown} |
|
""" |
|
os.chdir(home_path) |
|
files_out = [str(proj_dir / file) for file in files if (proj_dir / file).exists()] |
|
cleanup(proj_dir, files) |
|
plt.close("all") |
|
return w_markdown, markdown_out, plot_output, files_out |
|
|
|
|
|
description = f""" |
|
# FSRS Optimizer - v{version} |
|
Based on the [tutorial](https://medium.com/@JarrettYe/how-to-use-the-next-generation-spaced-repetition-algorithm-fsrs-on-anki-5a591ca562e2) |
|
of [Jarrett Ye](https://github.com/L-M-Sherlock). This application can give you personalized anki parameters without having to code. |
|
|
|
Read the `Instructions` if its your first time using the app. |
|
""" |
|
|
|
with gr.Blocks() as demo: |
|
with gr.Tab("FSRS Optimizer"): |
|
with gr.Group(): |
|
gr.Markdown(description) |
|
with gr.Group(): |
|
with gr.Row(): |
|
with gr.Column(): |
|
file = gr.File(label="Review Logs (Step 1)") |
|
with gr.Column(): |
|
next_day_starts_at = gr.Number( |
|
value=4, label="Next Day Starts at (Step 2)", precision=0 |
|
) |
|
timezone = gr.Dropdown( |
|
label="Timezone (Step 3.1)", choices=pytz.all_timezones |
|
) |
|
filter_out_suspended_cards = gr.Checkbox( |
|
value=False, label="Filter out suspended cards" |
|
) |
|
with gr.Accordion(label="Advanced Settings (Step 3.2)", open=False): |
|
requestRetention = gr.Number( |
|
value=0.9, |
|
label="Desired Retention: Recommended to set between 0.8 0.9", |
|
) |
|
revlog_start_date = gr.Textbox( |
|
value="2006-10-05", |
|
label="Revlog Start Date: Optimize review logs after this date.", |
|
) |
|
with gr.Row(): |
|
btn_plot = gr.Button("Optimize!") |
|
with gr.Row(): |
|
w_output = gr.Markdown() |
|
with gr.Tab("Instructions"): |
|
with gr.Group(): |
|
gr.Markdown(instructions_markdown) |
|
with gr.Tab("Analysis"): |
|
with gr.Row(): |
|
markdown_output = gr.Markdown() |
|
with gr.Column(): |
|
plot_output = gr.Plot() |
|
files_output = gr.Files(label="Analysis Files") |
|
with gr.Tab("FAQ"): |
|
gr.Markdown(faq_markdown) |
|
|
|
btn_plot.click( |
|
optimizer, |
|
inputs=[ |
|
file, |
|
timezone, |
|
next_day_starts_at, |
|
revlog_start_date, |
|
filter_out_suspended_cards, |
|
requestRetention, |
|
], |
|
outputs=[w_output, markdown_output, plot_output, files_output], |
|
) |
|
|
|
demo.queue().launch(show_error=True) |
|
|