diff --git a/README.md b/README.md index d1c773b..e09906d 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,25 @@ The KoalaSRS Logo (for now)

-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. We teach vocabulary using fully-formed sentences, not just boring word/definition pairs. KoalaSRS captures your voice input using speech-to-text and uses the super-smart GPT-4 for human-like test assessments and corrections. That means that the app is clever enough to mark your answers as "close enough" and can even give you optional feedback about _why_ you were wrong. It can explain sentences that don't make sense or have unknown vocabulary (WIP). 🧠 +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. We teach vocabulary using fully-formed sentences, not just boring word/definition pairs. KoalaSRS captures your voice input using speech-to-text and uses the super-smart GPT-4 for human-like test assessments and corrections. That means that the app is clever enough to mark your answers as "close enough" and can even give you optional feedback about _why_ you were wrong. 🧠 ## Demo

- The KoalaSRS Logo (for now) + The KoalaSRS Logo (for now)

-[Watch a short YouTube demo of the app as of 2023-09-16.](https://www.youtube.com/watch?v=0H2MufXrYl8) +[Watch a short YouTube demo of the app as of 2023-09-16 (older v1.1 release- new video coming soon).](https://www.youtube.com/watch?v=0H2MufXrYl8) +## ~~ALPHA~~ Beta Software -## Demo - -[Watch a short YouTube demo of the app as of 2023-09-16.](https://www.youtube.com/watch?v=0H2MufXrYl8) - -## Private Alpha +The app is now stable enough to be used for serious studying, but there are still stability issues and many features are still pending. If you want to use the app but are hitting stability issues, please reach out. -I am privately hosting an instance of KoalaSRS. Please contact me via my blog/LinkedIn/Mastodon/etc if you are interested in trying it out or want to help with alpha testing. There is no free public instance at the moment due to hosting/API costs. +I am also privately hosting an instance of KoalaSRS. Please contact me via my blog/LinkedIn/Mastodon/etc if you are interested in trying it out or want to help with alpha testing. There is no free public instance at the moment due to hosting/API costs. I've created a [group on Club House](https://www.clubhouse.com/c/join/B2Tyn13w) to discuss KoalaSRS development in an informal manner. Feel free to drop in and say "hi" or suggest new ideas. -## UNDER CONSTRUCTION ⚠️ - -The app started as a command line app while I experimented with different quiz strategies. Now that the details are resolved, I am converting the app to run in a browser so that it can be used by non-technical users. I am working directly off of the `main` branch for now since it seems like we don't have a ton of users. If you want to use the app but are hitting stability issues, please reach out. - ## Table of Contents 📑 - [Demo](#Demo) @@ -48,11 +41,11 @@ KoalaSRS rocks a minimal GUI because the focus is on what you can _hear_, not wh Here's how the app works: 1. Korean sentences with English translations are loaded into a SQLite database (I use `seeds.ts` for now but an editor is in the works). -1. The app creates a queue of sentences, sorted by difficulty. Difficulty comes from past quiz fails. More on quizzes soon. +1. The app creates a queue of sentences, sorted by scheduling need, which is calculated via a cards age and difficulty. 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. The user has to pass a quiz to move on. +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 or the user quits the program. +1. The process goes on until the queue is empty. The app has three types of quizzes: @@ -60,7 +53,7 @@ The app has three types of quizzes: - **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. 📣 -Please note that this app is not ready for non-technical users just yet. If you want to try the app, you'll need to clone this repo and build it on your local machine. There's no public demo available, but we'd love your feedback! 😊 +Please note that this app is not ready for non-technical users just yet. If you want to try the app, you'll need to clone this repo and build it on your local machine (or ask nicely for an invitation to a private instance). There's no public demo available, but we'd love your feedback! 😊 The program also comes with helper functions for formatting prompts, grading responses, and inspecting results. @@ -72,7 +65,7 @@ Check out the [whitepaper](https://github.com/RickCarlino/gpt-language-learning- ## Developer Setup 🛠️ -**Prerequisites:** NodeJS is required. I've tested it on v18 of node, and newer versions will probably work too. +**Prerequisites:** NodeJS is required. I've tested it on v20 of node. The project is in a semi-public alpha phase. If you don't understand the instructions below, you might want to wait for the project to mature before proceeding. **These instructions may be out of date. Please raise an issue if things don't work!** @@ -92,7 +85,7 @@ The source code is permissively licensed and open for review by software develop ## Project Status and Limitations ⚠️ -- I use the app every day for studying, but the documentation is, well, not great. If you really want to use this app, consider DMing me on Reddit for help. +- I use the app every day for studying, but the documentation is, well, not great. If you really want to use this app, consider DMing me on Reddit/LinkedIn/ClubHouse for help. - By design, the app won't quiz on reading or writing. This is a speaking/listening app. - The target user is English speakers trying to learn Korean. I can add other language pairs later, but the main focus right now is EN/KO. diff --git a/pages/_study_reducer.ts b/pages/_study_reducer.ts index 8b30305..5ce34f2 100644 --- a/pages/_study_reducer.ts +++ b/pages/_study_reducer.ts @@ -1,5 +1,3 @@ -import { unique } from "radash"; - export type Quiz = { id: number; ko: string; @@ -12,11 +10,25 @@ export type Quiz = { }; }; +type Failure = { + id: number; + ko: string; + en: string; + lessonType: string; + userTranscription: string; + rejectionText: string; +}; + type State = { numQuizzesAwaitingServerResponse: number; errors: string[]; quizIDsForLesson: number[]; phrasesById: Record; + isRecording: boolean; + failure: Failure | null; + totalCards: number; + quizzesDue: number; + newCards: number; }; type LessonType = keyof Quiz["audio"]; @@ -28,7 +40,15 @@ type Action = | { type: "USER_GAVE_UP"; id: number } | { type: "FLAG_QUIZ"; id: number } | { type: "DID_GRADE"; id: number; result: QuizResult } - | { type: "ADD_MORE"; quizzes: Quiz[] }; + | { type: "SET_RECORDING"; value: boolean } + | { type: "SET_FAILURE"; value: null | Failure } + | { + type: "ADD_MORE"; + quizzes: Quiz[]; + totalCards: number; + quizzesDue: number; + newCards: number; + }; export type CurrentQuiz = { id: number; @@ -53,6 +73,11 @@ export const newQuizState = (state: Partial = {}): State => { phrasesById, quizIDsForLesson: remainingQuizIDs, errors: [], + isRecording: false, + failure: null, + totalCards: 0, + quizzesDue: 0, + newCards: 0, ...state, }; }; @@ -109,10 +134,10 @@ function reduce(state: State, action: Action): State { action.id, ); case "ADD_MORE": - const nextQuizIDsForLesson = unique([ + const nextQuizIDsForLesson = [ ...state.quizIDsForLesson, ...action.quizzes.map((x) => x.id), - ]); + ]; const nextphrasesById: Record = action.quizzes.reduce( (acc, x) => { @@ -130,6 +155,9 @@ function reduce(state: State, action: Action): State { ...state, phrasesById: nextphrasesById, quizIDsForLesson: nextQuizIDsForLesson, + totalCards: action.totalCards, + quizzesDue: action.quizzesDue, + newCards: action.newCards, }; default: console.warn("Unhandled action", action); diff --git a/pages/study.tsx b/pages/study.tsx index a6f0025..9716e01 100644 --- a/pages/study.tsx +++ b/pages/study.tsx @@ -5,7 +5,7 @@ import { trpc } from "@/utils/trpc"; import { Button, Container, Grid, Header, Paper } from "@mantine/core"; import { useHotkeys } from "@mantine/hooks"; import { notifications } from "@mantine/notifications"; -import { useEffect, useReducer, useState } from "react"; +import { useEffect, useReducer } from "react"; import Authed from "../components/authed"; import { CurrentQuiz, @@ -76,26 +76,22 @@ function Failure(props: { ); } -function Study({ quizzes, totalCards, quizzesDue, newCards }: Props) { - const phrasesById = quizzes.reduce((acc, quiz) => { +function Study(props: Props) { + const phrasesById = props.quizzes.reduce((acc, quiz) => { acc[quiz.id] = quiz; return acc; }, {} as Record); - const newState = newQuizState({ phrasesById }); + const newState = newQuizState({ + phrasesById, + totalCards: props.totalCards, + quizzesDue: props.quizzesDue, + newCards: props.newCards, + }); const [state, dispatch] = useReducer(quizReducer, newState); const performExam = trpc.performExam.useMutation(); const failPhrase = trpc.failPhrase.useMutation(); const flagPhrase = trpc.flagPhrase.useMutation(); const getNextQuiz = trpc.getNextQuiz.useMutation(); - const [failure, setFailure] = useState<{ - id: number; - ko: string; - en: string; - lessonType: string; - userTranscription: string; - rejectionText: string; - } | null>(null); - const [isRecording, setIsRecording] = useState(false); const needBetterErrorHandler = (error: any) => { console.error(error); }; @@ -113,7 +109,7 @@ function Study({ quizzes, totalCards, quizzesDue, newCards }: Props) { return (

Session Complete.

- {failure && } + {state.failure && }
); } @@ -123,7 +119,7 @@ function Study({ quizzes, totalCards, quizzesDue, newCards }: Props) { performExam .mutateAsync({ id, audio, lessonType }) .then((data) => { - setFailure(null); + dispatch({ type: "SET_FAILURE", value: null }); switch (data.result) { case "success": notifications.show({ @@ -138,13 +134,16 @@ function Study({ quizzes, totalCards, quizzesDue, newCards }: Props) { message: "Try again!", color: "red", }); - setFailure({ - id, - ko: quiz.ko, - en: quiz.en, - lessonType: quiz.lessonType, - userTranscription: data.userTranscription, - rejectionText: data.rejectionText, + dispatch({ + type: "SET_FAILURE", + value: { + id, + ko: quiz.ko, + en: quiz.en, + lessonType: quiz.lessonType, + userTranscription: data.userTranscription, + rejectionText: data.rejectionText, + }, }); break; case "error": @@ -176,7 +175,13 @@ function Study({ quizzes, totalCards, quizzesDue, newCards }: Props) { .then((data) => { console.log("TODO: Update new/due/total card stats"); if (!data) return; - dispatch({ type: "ADD_MORE", quizzes: data.quizzes }); + dispatch({ + type: "ADD_MORE", + quizzes: data.quizzes, + totalCards: data.totalCards, + quizzesDue: data.quizzesDue, + newCards: data.newCards, + }); }); }); }; @@ -196,18 +201,13 @@ function Study({ quizzes, totalCards, quizzesDue, newCards }: Props) { {state.numQuizzesAwaitingServerResponse ? "🔄" : "☑️"}Study - - Card #{quiz.id} ({quiz.repetitions} repetitions) Due: {quizzesDue} - New: {newCards} - Total: {totalCards} -