Skip to content

Database Schema

Chatty the Lab uses a single Supabase PostgreSQL database with the pgvector extension for semantic search. The schema is defined in two migration files:

  • 20260323000001_initial_schema.sql — Core tables (profiles, sessions, chats, messages)
  • 20260323000002_pgvector.sql — Context and query items with vector embeddings
erDiagram
auth_users ||--o| profiles : "trigger creates"
profiles ||--o{ sessions : "has many"
sessions ||--o{ chats : "has many"
sessions ||--o{ session_prompts : "has many"
chats ||--o{ messages : "has many"
context_items }o--o{ chats : "attached via context_text"

Automatically created when a user signs up via the handle_new_user() trigger on auth.users.

ColumnTypeDescription
idUUID PKReferences auth.users(id), cascade delete
display_nameTEXTUser’s full name from Google
emailTEXT UNIQUEEmail address
avatar_urlTEXTGoogle profile picture URL
created_atTIMESTAMPTZDefaults to now()

A session groups multiple chats for comparison.

ColumnTypeDescription
idUUID PKAuto-generated
user_idUUID FKReferences profiles(id), cascade delete
titleTEXTSession name, defaults to 'New Session'
master_chat_indexINTEGERPosition of the master chat (default 0)
created_atTIMESTAMPTZAuto-set
updated_atTIMESTAMPTZAuto-updated via trigger

Each chat represents one conversation with a specific model within a session.

ColumnTypeDescription
idUUID PKAuto-generated
session_idUUID FKReferences sessions(id), cascade delete
positionINTEGEROrder within the session (unique per session)
model_nameTEXTName of the LLM model
context_textTEXTSystem prompt / injected context (default '')
titleTEXTDisplay title for the chat tab
created_atTIMESTAMPTZAuto-set

Individual messages within a chat conversation.

ColumnTypeDescription
idUUID PKAuto-generated
chat_idUUID FKReferences chats(id), cascade delete
positionINTEGEROrder within the chat (unique per chat)
roleTEXTOne of 'system', 'user', 'assistant'
contentTEXTMessage content
created_atTIMESTAMPTZAuto-set

Tracks prompts sent via the master chat for session-level history.

ColumnTypeDescription
idUUID PKAuto-generated
session_idUUID FKReferences sessions(id), cascade delete
positionINTEGEROrder within the session (unique per session)
contentTEXTThe prompt text
created_atTIMESTAMPTZAuto-set

Reusable context snippets with optional vector embeddings for semantic search.

ColumnTypeDescription
idUUID PKAuto-generated
titleTEXTDisplay title
categoryTEXTCategory label
tagsTEXT[]Array of tags (default '{}')
ownerTEXTUser ID or 'all' for shared items
contentTEXTThe context content
embeddingvector(768)Embedding from nomic-embed-text
created_atTIMESTAMPTZAuto-set

Pre-populated query suggestions with embeddings for semantic matching.

ColumnTypeDescription
idUUID PKAuto-generated
queryTEXTThe suggested query text
embeddingvector(768)Embedding vector
created_atTIMESTAMPTZAuto-set

Optional registry of LLM models (not actively used by the backend, which uses the in-memory plugin registry).

ColumnTypeDescription
idUUID PKAuto-generated
nameTEXT UNIQUEDisplay name
providerTEXTProvider identifier
model_idTEXTAPI model identifier
enabledBOOLEANDefault true
created_atTIMESTAMPTZAuto-set

The vector extension is enabled in migration 20260323000002_pgvector.sql:

CREATE EXTENSION IF NOT EXISTS vector;

Embeddings are 768-dimensional vectors generated by Ollama’s nomic-embed-text model. Two IVFFlat indexes are created for cosine distance search:

-- Context items index
CREATE INDEX idx_context_items_embedding ON public.context_items
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- Query items index
CREATE INDEX idx_query_items_embedding ON public.query_items
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 50);

Similarity is computed as 1 - (embedding <=> query_embedding) where <=> is the cosine distance operator.

All tables have RLS enabled. Policies ensure users can only access their own data:

TablePolicyRule
profilesSELECT, UPDATE ownid = auth.uid()
sessionsALL ownuser_id = auth.uid()
chatsALL ownsession_id belongs to user’s session
messagesALL ownchat_id belongs to user’s chat via session
session_promptsALL ownsession_id belongs to user
context_itemsSELECT shared + ownowner = 'all' OR owner = auth.uid()::text
context_itemsINSERT, UPDATE, DELETE ownowner = auth.uid()::text

Two SQL functions are defined for semantic search:

  • search_context_items(query_embedding, match_limit, owner_filter) — Returns context items ordered by cosine similarity
  • search_query_items(query_embedding, match_limit) — Returns query suggestions ordered by cosine similarity

These can be called via Supabase’s RPC interface, though the backend currently uses direct SQL queries with pgvector operators instead.