Skip to content

Commit

Permalink
Fix scheduling issue.
Browse files Browse the repository at this point in the history
  • Loading branch information
RickCarlino committed Feb 19, 2024
1 parent 0400b77 commit eb8efea
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 61 deletions.
28 changes: 13 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
</p>
<a href="https://codeclimate.com/github/RickCarlino/KoalaSRS/maintainability"><img src="https://api.codeclimate.com/v1/badges/b7666624c14bf8dcfb9b/maintainability" /></a>

Hey there! Welcome to KoalaSRS, a fun and friendly Korean-only [spaced repetition system](https://en.wikipedia.org/wiki/Spaced_repetition) that's all about listening and speaking skills. KoalaSRS captures your voice input using speech-to-text and uses the super-smart GPT-4 for human-like test assessments and corrections. This has two advantages over traditional spaced repetition systems:
Hey there! Welcome to KoalaSRS, a fun and friendly Korean-only [spaced repetition system](https://en.wikipedia.org/wiki/Spaced_repetition) that's all about listening and speaking skills. KoalaSRS captures your voice input using speech-to-text and GPT-4 for human-like test assessments and corrections. This has two advantages over traditional spaced repetition systems:

1. There is no self-grading of quizzes. Results are recorded objectively.
2. Your quizzes will not be marked incorrect for small variations in word choice. "Close enough" answers that match the meaning of the target sentence will always be accepted.
2. Your quizzes will not be marked incorrect for small variations in word choice. "Close enough" answers that match the meaning of the target sentence will be accepted.

## Demo Video

Expand Down Expand Up @@ -42,26 +42,24 @@ The app is now stable enough to be used for serious studying. If you want to use

## Features 💡

KoalaSRS rocks a minimal GUI because the focus is on what you can _hear_, not what you can see. 🎧

Here's how the app works:

1. Korean sentences with English translations are loaded into a database.
1. The app schedules a queue of sentences using the FSRS scheduling algorithm.
1. The app asks the user to take one of three quizzes. All quizzes involve listening to Korean speech or speaking Korean sentences into the microphone. 🎤
1. You input Korean sentences with English translations into the app.
1. The app schedules a queue of sentences using the [FSRS scheduling algorithm](https://github.com/open-spaced-repetition/fsrs4anki).
1. The app asks the user to take a listening or speaking quiz. All quizzes involve listening to Korean speech or speaking Korean sentences into the microphone. 🎤
1. The user must pass a quiz to move on to the next card.
1. Once the quiz is complete, the sentence is played back in Korean and English. The user's audio is also played back to help with pronunciation.
1. The process goes on until the queue is empty.

The app has three types of quizzes:
The app has two types of quizzes:

- **Dictation quiz:** You read a Korean phrase aloud, and the app transcribes it using speech-to-text technology. GPT-3 grades your answer. This quiz is the easiest one and focuses on pronunciation and memorization. 🗣️
- **Listening quiz:** You listen to a Korean phrase and then translate it to English. This quiz comes after the dictation phase. 🎶
- **Speaking quiz:** You get an English text and are asked to say it in Korean. The program transcribes your phrase via speech-to-text, and GPT-3 grades your answer. 📣

Other types of quizzes, such as listening comprehension, may be added in the future.

## Why Another SRS? 🤔

I studied Korean at university and did self-study for many years before that. I saw lots of flaws in existing solutions but couldn't build alternatives because software tools weren't ready yet. I've been dreaming about building this spaced repetition system for over a decade! Finally, in 2023, it's possible thanks to large language models (LLMs) like GPT and affordable, high-quality text-to-speech and speech-to-text APIs. 🎉
I studied Korean at university and did self-study for many years before that. I saw lots of flaws in existing solutions but couldn't build alternatives because software tools weren't ready yet. I've been dreaming about building this spaced repetition system for over a decade! Finally, in 2023, it became possible thanks to large language models (LLMs) like GPT and affordable, high-quality text-to-speech and speech-to-text APIs. 🎉

Check out the [whitepaper](https://github.com/RickCarlino/gpt-language-learning-experiments) I wrote that explains the main idea. I also wrote a [blog article back in 2019](https://rickcarlino.com/2019/problems-and-solutions-for-spaced-repetition-software.html) about some problems and solutions with SRS systems.

Expand All @@ -85,7 +83,7 @@ The project is in a semi-public alpha phase. If you don't understand the instruc

## Contribution Guidelines 🤝

The source code is permissively licensed and open for review by software developers. The project is in a very early state and not ready for the general public. Got questions? Raise an issue! You can also reach me via DM on Reddit for general discussion (GitHub and Reddit usernames are the same).
The source code is permissively licensed and open for review by software developers. Got questions? Raise an issue! You can also reach me via DM on Reddit for general discussion (GitHub and Reddit usernames are the same).

## Project Status and Limitations ⚠️

Expand All @@ -98,6 +96,6 @@ The source code is permissively licensed and open for review by software develop
The project could use help in the following areas:

1. I'd like to create a large library of example sentences that are appropriate for use with the app. For this, I'd need the help of a Korean native speaker who can curate and moderate a large corpus of AI-generated phrases containing target grammar/vocab.
1. UI/UX needs an overall and has not been a priority due to time constraints.
1. Would love to hear input from folks with a background in linguistics or Korean language education. Please reach out.
1. The app relies heavily on Google Cloud and OpenAI for text-to-speech and AI features. I would be interested in exploring other options, such as Bark TTS or custom Llama models.
1. UI/UX needs an overall and has not been a priority due to time constraints. If you would like to improve the UI, please reach out.
1. If you have a background in linguistics or Korean language education, please reach out.
1. The app relies heavily on Google Cloud and OpenAI for text-to-speech and AI features. I would be interested in exploring other options, such as different TTS or LLM providers.
2 changes: 1 addition & 1 deletion pages/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const CreateCardPage: React.FC = () => {
<pre>그는 나를 웃게 했어요. He made me laugh.</pre>
<Textarea
minRows={10}
placeholder="Korean sentence <tab> English translation or example sentence"
placeholder="Foreign language sentence <tab> English translation or example sentence"
value={input}
onChange={handleInputChange}
/>
Expand Down
2 changes: 1 addition & 1 deletion pages/study.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const HEADER_STYLES = {
marginBottom: "20px",
};
const HEADER: Record<string, string> = {
speaking: "Say in Korean",
speaking: "Say in target language",
listening: "Translate to English",
};

Expand Down
3 changes: 2 additions & 1 deletion server/quiz-evaluators/speaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const GRAMMAR_PROMPT = `Grade a sentence from a language learning
app. Answer YES if the sentence is grammatically correct and
in the specified language (ISO 639-1:2002 code '{{langCode}}').
Answer NO if it doesn't follow the language's syntax and semantics
or isn't in the specified language. Avoid vague responses.`;
or isn't in the specified language. Avoid vague responses.
Incomplete sentences are OK if they are grammatically correct.`;

const MEANING_PROMPT = `Grade the equivalence of a translation
in a language learning app, given the ISO 639-1:2002 language
Expand Down
5 changes: 3 additions & 2 deletions server/routers/grade-quiz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ const PASS = z.object({
});

const performExamOutput = z.union([PASS, FAIL, ERROR]);

function processFailure(ctx: ResultContext): z.infer<typeof FAIL> {
type FailResult = z.infer<typeof FAIL>;
async function processFailure(ctx: ResultContext): Promise<FailResult> {
await setGrade(ctx.quiz, Grade.AGAIN);
return {
result: "fail",
grade: 0,
Expand Down
4 changes: 4 additions & 0 deletions utils/fetch-lesson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import path from "path";
import { draw, map, template } from "radash";
import { errorReport } from "./error-report";
import { prismaClient } from "@/server/prisma-client";
import { timeUntil } from "./time-until";

type LessonType = "listening" | "speaking";

Expand Down Expand Up @@ -134,6 +135,9 @@ export default async function getLessons(p: GetLessonInputParams) {
orderBy: {
nextReview: "asc",
},
// Don't select quizzes from the same card.
// Prevents hinting.
distinct: ["cardId"],
take: p.take || 10,
include: {
Card: true, // Include related Card data in the result
Expand Down
80 changes: 39 additions & 41 deletions utils/time-until.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
export function timeUntil(
timestamp: number,
now = new Date().getTime(),
): string {
let difference = timestamp - now;

if (difference < 0) {
return "already past";
}

const secondsInYear = 31536000;
const secondsInDay = 86400;
const secondsInHour = 3600;
const secondsInMinute = 60;

// Convert milliseconds to seconds
difference = Math.floor(difference / 1000);

const years = Math.floor(difference / secondsInYear);
difference -= years * secondsInYear;

const days = Math.floor(difference / secondsInDay);
difference -= days * secondsInDay;

const hours = Math.floor(difference / secondsInHour);
difference -= hours * secondsInHour;

const minutes = Math.floor(difference / secondsInMinute);
difference -= minutes * secondsInMinute;

const seconds = difference;

let result = "";
if (years > 0) result += `${years} years `;
if (days > 0) result += `${days} days `;
if (hours > 0) result += `${hours} hours `;
if (minutes > 0) result += `${minutes} minutes `;
if (seconds > 0) result += `${seconds} seconds `;

return result.trim();
}

timestamp: number,
now = new Date().getTime(),
): string {
let difference = timestamp - now;

const isPast = difference < 0;
difference = Math.abs(difference);

const secondsInYear = 31536000;
const secondsInDay = 86400;
const secondsInHour = 3600;
const secondsInMinute = 60;

// Convert milliseconds to seconds
difference = Math.floor(difference / 1000);

const years = Math.floor(difference / secondsInYear);
difference -= years * secondsInYear;

const days = Math.floor(difference / secondsInDay);
difference -= days * secondsInDay;

const hours = Math.floor(difference / secondsInHour);
difference -= hours * secondsInHour;

const minutes = Math.floor(difference / secondsInMinute);
difference -= minutes * secondsInMinute;

const seconds = difference;

let result = "";
if (years > 0) result += `${years} years `;
if (days > 0) result += `${days} days `;
if (hours > 0) result += `${hours} hours `;
if (minutes > 0) result += `${minutes} minutes `;
if (seconds > 0) result += `${seconds} seconds `;

return isPast ? `${result.trim()} ago` : result.trim();
}

0 comments on commit eb8efea

Please sign in to comment.