-
Notifications
You must be signed in to change notification settings - Fork 6
docs: add Supabase integration report #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,216 @@ | ||||||
| # Supabase Integration Report: Inworld Language Tutor | ||||||
|
|
||||||
| ## Overview | ||||||
|
|
||||||
| **Inworld Language Tutor** is an AI-powered conversational language learning application. Users practice speaking or typing in a target language with a real-time AI tutor, receive grammar feedback, and build vocabulary via auto-generated flashcards. The app is built with a Node.js/Express backend and a React (Vite) frontend. | ||||||
|
|
||||||
| Supabase serves as the optional cloud layer for the application, providing: | ||||||
|
|
||||||
| - **Authentication** — email/password sign-up and sign-in via Supabase Auth | ||||||
| - **Persistent storage** — conversations, messages, flashcards, and user preferences stored in a PostgreSQL database | ||||||
| - **Semantic memory** — long-term user memories stored with vector embeddings and retrieved via cosine similarity search using the `pgvector` extension | ||||||
|
|
||||||
| The application is designed to run in two modes: | ||||||
|
|
||||||
| | Mode | Auth | Storage | Memory | | ||||||
| |------|------|---------|--------| | ||||||
| | **Anonymous** | None | `localStorage` only | None (ephemeral) | | ||||||
| | **Authenticated** | Supabase Auth | `localStorage` + Supabase (dual-write) | Supabase pgvector | | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Introduction to the Product | ||||||
|
|
||||||
| ### What It Does | ||||||
|
|
||||||
| Users select a target language and start a spoken or text conversation with an AI tutor. The tutor responds in the target language with contextually appropriate sentences. After each exchange, the app: | ||||||
|
|
||||||
| 1. **Provides grammar and usage feedback** on what the user said | ||||||
| 2. **Auto-generates flashcards** for vocabulary encountered during the conversation | ||||||
| 3. **Builds a memory** of the user's learning progress and personal interests, which is surfaced in future conversations to personalise responses | ||||||
|
|
||||||
| ### Use-Cases for Supabase Users | ||||||
|
|
||||||
| | Use-Case | How Supabase Enables It | | ||||||
| |----------|------------------------| | ||||||
| | **Persistent conversations across devices** | Conversations and messages are stored in Supabase Postgres and synced to `localStorage` on login | | ||||||
| | **User accounts & multi-user support** | Supabase Auth handles sign-up, sign-in, and session management; Row Level Security (RLS) ensures strict data isolation between users | | ||||||
| | **Cloud flashcard library** | Flashcards are written to Supabase on creation and merged with local cards on login; the `unique(user_id, conversation_id, target_word)` constraint prevents duplicates | | ||||||
| | **Personalised AI conversations** | Every few turns the backend extracts memorable facts and stores them with vector embeddings; at the start of each turn the `match_memories` RPC retrieves the most semantically relevant memories to inject into the LLM prompt | | ||||||
| | **Anonymous-to-authenticated migration** | When a user signs in for the first time, all `localStorage` data (conversations, messages, flashcards) is migrated to Supabase, so nothing is lost | | ||||||
| | **Language preference persistence** | The selected target language is stored in `user_preferences` and restored on every login | | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Architecture: How Supabase Fits In | ||||||
|
|
||||||
| ``` | ||||||
| ┌───────────────────────────────────────────────────────────┐ | ||||||
| │ Browser (React / Vite) │ | ||||||
| │ │ | ||||||
| │ AuthContext ──── @supabase/supabase-js ──► Supabase Auth │ | ||||||
| │ │ │ | ||||||
| │ HybridStorage ── SupabaseStorage ──────► Supabase Postgres│ | ||||||
| │ (dual-write) conversations, messages, │ | ||||||
| │ flashcards, preferences │ | ||||||
| └───────────────────────────────────────────────────────────┘ | ||||||
| │ WebSocket | ||||||
| ┌───────────────────────────────────────────────────────────┐ | ||||||
| │ Backend (Node.js / Express) │ | ||||||
| │ │ | ||||||
| │ MemoryService ── @supabase/supabase-js ─► Supabase Postgres│ | ||||||
| │ (service-key) user_memories + pgvector embeddings │ | ||||||
| │ │ | ||||||
| │ Inworld Runtime graphs: │ | ||||||
| │ memory-retrieval-node → calls match_memories() RPC │ | ||||||
| │ state-update-node → stores new memories │ | ||||||
| └───────────────────────────────────────────────────────────┘ | ||||||
| ``` | ||||||
|
|
||||||
| ### Key Database Tables | ||||||
|
|
||||||
| | Table | Purpose | | ||||||
| |-------|---------| | ||||||
| | `user_preferences` | Stores the user's preferred target language | | ||||||
| | `conversations` | One row per conversation session, keyed by `(user_id, language_code)` | | ||||||
|
||||||
| | `conversations` | One row per conversation session, keyed by `(user_id, language_code)` | | |
| | `conversations` | One row per conversation session, associated with `(user_id, language_code)` | |
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SQL snippet in the Memory System section hardcodes the similarity threshold as 0.7, but the actual runtime value used by MemoryRetrievalNode is 0.5 (defined as MEMORY_SIMILARITY_THRESHOLD = 0.5 in backend/src/graphs/nodes/memory-retrieval-node.ts, line 26). The match_memories RPC function accepts the threshold as a parameter (match_threshold), and the node passes 0.5 at runtime. The comment -- threshold next to (1 - 0.7) is therefore misleading and does not reflect the actual behaviour of the application.
| AND embedding <=> query_embedding < (1 - 0.7) -- threshold | |
| AND embedding <=> query_embedding < (1 - 0.5) -- similarity threshold |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The architecture diagram attributes memory storage to
state-update-node, but this is incorrect. Looking at the code,state-update-nodeonly updates the connection state with the LLM's response (StateUpdateNode.process()inbackend/src/graphs/nodes/state-update-node.ts). Memory storage is actually performed byMemoryProcessorvia a callback set up inwebsocket-handler.ts(lines 181–201). The diagram entrystate-update-node → stores new memoriesshould be corrected to reflect that a backgroundMemoryProcessorhandles this responsibility.