Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c139d82
Feat: add multi language support
cshape Dec 29, 2025
b756438
Feat: port to long-running 0.9 graph
cshape Dec 30, 2025
5a0a505
0.9 working ok end to end in Spanish
cshape Dec 30, 2025
01908e7
remove japanese and add all languages supported by assembly realtime …
cshape Dec 30, 2025
50112cc
0.9 working but want to improve quality and latency
cshape Dec 30, 2025
1fa67e6
removed intro state graph logic
cshape Dec 30, 2025
aff49a9
clean backend implementation
cshape Dec 30, 2025
2d46b7c
code cleanup
cshape Dec 31, 2025
0a55663
code cleanup and added text input
cshape Dec 31, 2025
95be6f5
removed audio debugging code
cshape Dec 31, 2025
18c37b9
filename consistency with kebab case
cshape Dec 31, 2025
1afce2a
added pause and interruption handling
cshape Dec 31, 2025
da477ed
renamed app and updated readme
cshape Dec 31, 2025
2c96ccc
made flashcard graph handle multi language better
cshape Dec 31, 2025
80da209
user response feedback graph and UI
cshape Dec 31, 2025
e23a6df
added logging and updated readme
cshape Dec 31, 2025
c30ff71
added graph export for registration
cshape Dec 31, 2025
0c1ef21
update readme after breaking prompt templates into their own files
cshape Dec 31, 2025
5fb6928
updated flashcard prompt
cshape Dec 31, 2025
a0ee698
conversation prompt update
cshape Dec 31, 2025
a5563ca
updates to support production deployment
cshape Dec 31, 2025
a85075a
linting fixes
cshape Dec 31, 2025
23aad2b
Feat: added DB persistence with supabase
cshape Jan 3, 2026
1ae0ce8
db fixes and lint fixes
cshape Jan 3, 2026
fdf7d31
added button to pronounce flashcard word
cshape Jan 5, 2026
f2a8a60
linter fixes
cshape Jan 5, 2026
9f1ce1a
linter fixes
cshape Jan 5, 2026
65454ef
Added storing and retrieving of memory embeddings - needs polish
cshape Jan 7, 2026
2e7627c
fixed timeout issue
cshape Jan 7, 2026
89181ab
added supabase setup for storing and retrieving memories
cshape Jan 7, 2026
e30a244
linter updates
cshape Jan 7, 2026
855576c
fixed left nav scrolling when you have a lot of chats
cshape Jan 7, 2026
d8424f2
Added start command to frontend
mattkwilson Jan 8, 2026
b41ff12
Restructured files, decoupled frontend and backend, aligned configura…
mattkwilson Jan 8, 2026
046155e
Fixed vulnerabilities
mattkwilson Jan 8, 2026
1efb5bd
Fixed linter issues
mattkwilson Jan 8, 2026
4c07788
Updated lint workflow file
mattkwilson Jan 8, 2026
f918d4b
Fixed bug with message duplicates
mattkwilson Jan 8, 2026
7ba7aa9
Revamped langauge handling to fix issues
mattkwilson Jan 8, 2026
b7eaf44
Audio now interrupted when switching chats
mattkwilson Jan 8, 2026
5e624e9
Fixed english flashcard audio not playing
mattkwilson Jan 9, 2026
d4c7397
Fixed flashcard sizing
mattkwilson Jan 14, 2026
230ddb0
Fix conversation switching race conditions, add memory generation tra…
mattkwilson Jan 15, 2026
d491f08
Fixed flashcard audio playing in wrong language
mattkwilson Jan 15, 2026
3addcc3
Updated sign in and connection UI
mattkwilson Jan 16, 2026
ff99c5a
Removed button functionality from logo
mattkwilson Jan 16, 2026
872ca97
Fixed issue with language set on app refresh
mattkwilson Jan 16, 2026
6faf316
update readme and update to tts-1.5-max
cshape Jan 22, 2026
c69c7ce
readme and migrations cleanup
cshape Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,28 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
- name: Install backend dependencies
working-directory: ./backend
run: npm ci

- name: Run ESLint
- name: Install frontend dependencies
working-directory: ./frontend
run: npm ci

- name: Run ESLint (backend)
working-directory: ./backend
run: npm run lint

- name: Run ESLint (frontend)
working-directory: ./frontend
run: npm run lint

- name: Check Prettier formatting
- name: Check Prettier formatting (backend)
working-directory: ./backend
run: npm run format:check

- name: Check Prettier formatting (frontend)
working-directory: ./frontend
run: npm run format:check

5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,8 @@ backend/audio/
.DS_Store
CLAUDE.md
templates/
.claude

# Deployment files
deploy/
.gcloudignore
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
"arrowParens": "always",
"endOfLine": "lf"
}

1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Thank you for your interest in contributing to the Language Learning App! This d

```bash
INWORLD_API_KEY=your_api_key_here
ASSEMBLY_AI_API_KEY=your_api_key_here
```

5. **Verify the setup**:
Expand Down
170 changes: 139 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# Language Learning - Aprendemo
# Inworld Language Tutor

[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![Powered by Inworld AI](https://img.shields.io/badge/Powered_by-Inworld_AI-orange)](https://inworld.ai/runtime)
[![Documentation](https://img.shields.io/badge/Documentation-Read_Docs-blue)](https://docs.inworld.ai/docs/node/overview)
[![Model Providers](https://img.shields.io/badge/Model_Providers-See_Models-purple)](https://docs.inworld.ai/docs/models#llm)

A Node.js app where you can learn Spanish through conversation and flashcard studying, powered by Inworld AI Runtime. "Aprendemo" combines "Aprender" (Spanish for "to learn") with "demo" - it's both a language learning tool and a demonstration of the Inworld Runtime Node.js SDK.
A conversational language learning app powered by Inworld AI Runtime. Practice speaking with an AI tutor, get real-time feedback on your responses, and build vocabulary with auto-generated flashcards.

![App](screenshot.jpg)

## Prerequisites

- Node.js (v20 or higher)
- npm
- An Inworld AI account and API key
- An AssemblyAI account and API key (for speech-to-text)

## Get Started

Expand All @@ -25,64 +27,170 @@ cd language-learning-node

### Step 2: Install Dependencies


Frontend:
```bash
cd frontend
npm install
```

Backend:
```bash
cd backend
npm install
```

### Step 3: Configure Environment Variables

Create a `.env` file in the root directory:
Create a `.env` file in the /backend directory:

```bash
INWORLD_API_KEY=your_api_key_here
INWORLD_API_KEY=your_inworld_base64_key
ASSEMBLY_AI_API_KEY=your_assemblyai_key
```

Get your Base64 API key from the [Inworld Portal](https://platform.inworld.ai/).
| Service | Get Key From | Purpose |
| -------------- | --------------------------------------------------- | --------------------------------- |
| **Inworld** | [platform.inworld.ai](https://platform.inworld.ai/) | AI conversations (Base64 API key) |
| **AssemblyAI** | [assemblyai.com](https://www.assemblyai.com/) | Speech-to-text |

### Step 4: Run the Application

For development:
**For development** (with auto-reload on file changes):

In frontend dir:
```bash
npm run dev
```

For production:
In backend dir:
```bash
npm run dev
```

Open [http://localhost:5173](http://localhost:5173)

### Step 5 (Optional): Set Up Supabase for Auth & Memory

Without Supabase, the app works in anonymous mode using localStorage.

**a) Create a Supabase project** at [supabase.com](https://supabase.com)

**b) Push the database schema:**

```bash
npx supabase login
npx supabase link --project-ref YOUR_PROJECT_REF
npx supabase db push
```

This creates all tables, indexes, RLS policies, and the `match_memories` function for semantic search.

Find your project ref in the Supabase dashboard URL: `supabase.com/dashboard/project/YOUR_PROJECT_REF`

**c) Add Supabase variables to `.env` (backend):**

```bash
npm run build
npm start
SUPABASE_URL=https://YOUR_PROJECT.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
```

**d) Create `frontend/.env.local`:**

```bash
VITE_SUPABASE_URL=https://YOUR_PROJECT.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=your_anon_key
```

Find these in: Supabase Dashboard > Settings > API

## Repo Structure

```
language-learning-node/
├── backend/
│ ├── graphs/ # Graph definitions
│ │ ├── conversation-graph.ts
│ │ ├── flashcard-graph.ts
│ │ └── introduction-state-graph.ts
│ ├── helpers/ # Helper utilities
│ │ ├── anki-exporter.ts
│ │ ├── audio-buffer.ts
│ │ ├── audio-processor.ts
│ │ ├── flashcard-processor.ts
│ │ ├── introduction-state-processor.ts
│ │ ├── prompt-templates.ts
│ │ └── silero-vad.ts
│ ├── models/ # AI models
│ │ └── silero_vad.onnx
│ └── server.ts # Backend server
├── frontend/ # Frontend application
│ ├── js/
│ ├── styles/
│ └── index.html
├── flashcard-graph.json # Flashcard configuration
├── package.json # Dependencies
└── LICENSE # MIT License
│ ├── src/
│ │ ├── __tests__/ # Backend unit tests
│ │ ├── config/ # Language & server configuration
│ │ ├── graphs/ # Inworld Runtime conversation graphs
│ │ │ ├── configs/ # Graph JSON configurations
│ │ │ └── nodes/ # Custom graph nodes
│ │ ├── helpers/ # Audio utils, connection management
│ │ ├── prompts/ # Nunjucks prompt templates
│ │ ├── services/ # Server components
│ │ ├── utils/ # Logger
│ │ └── server.ts # Entry point
│ ├── .env # Backend environment variables
│ └── vitest.config.ts # Backend test config
├── frontend/
│ ├── src/
│ │ ├── __tests__/ # Frontend unit tests
│ │ ├── components/ # React components
│ │ ├── context/ # App state & auth
│ │ ├── hooks/ # Custom React hooks
│ │ ├── services/ # WebSocket client, audio, storage
│ │ ├── styles/ # CSS
│ │ └── types/ # TypeScript types
│ ├── .env.local # Frontend environment variables
│ └── vitest.config.ts # Frontend test config
├── supabase/
│ └── migrations/ # Database schema
└── deploy/ # Deployment configurations
```

## Architecture

The app uses a real-time audio streaming architecture:

1. **Frontend** captures microphone audio and streams it via WebSocket
2. **Backend** processes audio through an Inworld Runtime graph:
- AssemblyAI handles speech-to-text with voice activity detection
- LLM generates contextual responses in the target language
- TTS converts responses back to audio
3. **Flashcards** are auto-generated from conversation vocabulary
4. **Response feedback** provides grammar and usage corrections

## Memory System

When Supabase is configured, the app stores and retrieves user memories using semantic search:

- **Automatic memory creation**: Every few conversation turns, the system extracts memorable facts
- **Semantic retrieval**: Relevant memories are retrieved using vector similarity search (pgvector)
- **Personalized responses**: The AI uses retrieved memories to personalize conversations

Memory types:

- `learning_progress`: Vocabulary struggles, grammar patterns, learning achievements
- `personal_context`: Interests, goals, preferences shared by the user

Without Supabase, the app works in anonymous mode using localStorage (no memory persistence).

## Environment Variables Reference

| Variable | Required | Description |
| --------------------------- | -------- | ------------------------------------------------------------------ |
| `INWORLD_API_KEY` | Yes | Inworld AI Base64 API key |
| `ASSEMBLY_AI_API_KEY` | Yes | AssemblyAI API key |
| `PORT` | No | Server port (default: 3000) |
| `LOG_LEVEL` | No | `trace`, `debug`, `info`, `warn`, `error`, `fatal` (default: info) |
| `NODE_ENV` | No | `development` or `production` |
| `ASSEMBLY_AI_EAGERNESS` | No | Turn detection: `low`, `medium`, `high` (default: high) |
| `SUPABASE_URL` | No | Supabase project URL (enables memory feature) |
| `SUPABASE_SERVICE_ROLE_KEY` | No | Supabase service role key (for backend memory storage) |

## Testing

Run the test suite to verify core functionality:

```bash
npm test # Run all tests
npm run test:backend # Backend tests only
npm run test:frontend # Frontend tests only
npm run test:watch # Watch mode for backend
```

Tests cover critical paths: audio conversion, language configuration, storage persistence, and flashcard deduplication.

## Troubleshooting

**Bug Reports**: [GitHub Issues](https://github.com/inworld-ai/language-learning-node/issues)
Expand Down
5 changes: 5 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
INWORLD_API_KEY=
ASSEMBLY_AI_API_KEY=

SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
2 changes: 1 addition & 1 deletion eslint.config.mjs → backend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default [
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: true,
project: './tsconfig.eslint.json',
tsconfigRootDir: __dirname,
},
},
Expand Down
Loading