← Back to LuvNote Portfolio
LuvNote
AI Support Portal — Ruby on Rails Portfolio
Try the AI-powered support page

Quick Navigation

Jump to any section of this portfolio:

Project Overview

LuvNote AI Support Portal

A standalone AI-powered customer support portal built with Ruby on Rails 8 that serves as the intelligent help system for the LuvNote dating application. The portal uses OpenAI GPT-4o-mini to understand user questions in natural language and route them to the most relevant support articles, in-app deep links, or fallback responses based on confidence scoring.

Key Capabilities:

  • AI-powered question routing using OpenAI GPT-4o-mini Chat Completions API with structured JSON responses
  • Confidence-based response tiers: fallback (<50%), support page links (50-80%), or deep app links (>80%)
  • Session-based conversation tracking with cookie-based visitor fingerprinting
  • Admin analytics dashboard showing conversation metrics, message counts, and session details
  • Support page content management with slug-based routing and markdown rendering
  • Fallback keyword-based matching service using weighted scoring across page fields

7 database migrations 3 service objects 4 controllers 4 models PostgreSQL database

Feature Walkthrough

AI-Powered Chat Interface

OpenAI GPT-4o-mini Chat Completions API Structured JSON Turbo Rails

The main chat interface allows users to ask questions about the LuvNote app in natural language. Each question is processed by the OpenAI GPT-4o-mini model, which analyzes the query against the support page database and returns a structured JSON response with the best matching page, confidence score, and a helpful answer.

How It Works:

  1. User Submits Question: User types a question like "I can't find any events in my calendar" into the chat input
  2. Message Storage: ChatController stores the user message in the messages table linked to their conversation
  3. AI Routing: OpenaiSupportRouter sends the question and all support pages to GPT-4o-mini for analysis
  4. Confidence Scoring: AI returns a confidence score (0.0-1.0) indicating how well it understood the question
  5. Response Tiers: Based on confidence:
    • Below 50%: Returns a fallback "I didn't understand" message
    • 50-80%: Links to the relevant support page for reading
    • Above 80%: Deep links directly into the LuvNote app feature
  6. Response Display: Assistant response appears with confidence percentage, timestamp, and optional action link
Technical Highlight: OpenaiSupportRouter uses the OpenAI Ruby gem to call Chat Completions API. A system prompt defines strict JSON output schema (support_page_slug, confidence, answer, reason). The user payload includes the question and all support pages serialized as JSON. Response parsing handles edge cases including invalid JSON, NaN confidence values, and API errors with graceful fallbacks.

Conversation Session Management

Cookie-Based Sessions UUID Generation Visitor Fingerprinting IP Hashing

The ConversationFinder service implements cookie-based session management that persists across browser sessions. Each visitor gets a unique UUID stored in a permanent cookie, and their conversation history is maintained across visits without requiring user authentication.

Session Flow:

  1. First Visit: ConversationFinder generates a SecureRandom UUID and stores it in a permanent cookie (luvnote_support_visitor_id)
  2. Conversation Creation: A new Conversation record is created with visitor_id, started_at, user_agent, and a SHA-256 hash of the user's IP address
  3. Return Visits: Cookie is read to find existing conversation, and last_activity_at is updated
  4. Clear Conversation: Users can clear their chat history; this sets cleared_at timestamp rather than deleting messages (soft delete for analytics)
  5. Privacy: IP addresses are hashed with SHA-256 before storage — raw IPs are never persisted
Technical Highlight: ConversationFinder uses Rails permanent cookies for persistent visitor tracking. The visitor_id column has a unique database index for fast lookups. IP hashing uses Digest::SHA256 for privacy-compliant analytics. The soft-delete pattern (cleared_at) preserves message data for admin analytics while hiding it from the user's view using a simple WHERE clause.

Admin Analytics Dashboard

ActiveRecord Queries Analytics Metrics Conversation Details Message History

A protected admin dashboard provides real-time analytics on support conversations. Accessible via a client-side passcode modal, the dashboard shows aggregate metrics, recent conversations, and detailed message histories with confidence scores and link metadata.

Dashboard Features:

  1. Stats Grid: Four metric cards showing total conversations, total messages, conversations today, and messages today
  2. Recent Conversations: Up to 50 conversations sorted by last activity, each showing session ID, timestamp, message count, and preview of first user message
  3. Conversation Detail View: Full message history with:
    • Visitor metadata (ID, started, last activity, total messages)
    • Complete chronological message list with role, timestamp, and confidence %
    • Cleared messages shown with reduced opacity and "Hidden from user" tag
    • Link metadata showing type (in_app/support) and URL for each response
  4. Cleared Badge: Conversations where the user cleared their history display a "Cleared" badge with the cleared_at timestamp
Technical Highlight: AdminController queries conversations ordered by last_activity_at DESC with limit(50) for the recent list. Conversation detail view loads all messages including cleared ones (filtered by cleared_at comparison). Stats use ActiveRecord count queries with date-scoped WHERE clauses for today's metrics. The admin modal uses a client-side passcode check with redirect to /admin/dashboard on success.

Support Page Content System

Slug-Based Routing Markdown Content Deep App Links ActiveRecord

Support articles are stored as SupportPage records with slug-based routing, markdown body content, tags for AI matching, and optional in-app deep links. The AI references these pages when routing questions, and users can browse them directly.

Content System:

  1. SupportPage Model: Each article has slug (unique), title, summary, body_md (markdown), in_app_url, and tags fields
  2. Slug Routing: Articles are accessed at /support/:slug with clean URLs (e.g., /support/calendar-events)
  3. AI Integration: When a user asks a question, all support pages are serialized (slug, title, summary, tags) and sent to the AI for matching
  4. Deep Links: Pages with in_app_url values allow the AI to link users directly into the relevant LuvNote app feature
  5. Message Tracking: Each assistant message stores a foreign key (chosen_support_page_id) to track which page was matched
Technical Highlight: SupportPage validates slug presence and uniqueness. The has_many :messages association (via chosen_support_page_id foreign key) enables analytics on which support pages are most commonly matched. Tags field enables fine-grained AI matching beyond just title and slug. The body_md field stores markdown that can be rendered for detailed help articles.

Fallback Keyword Matching Engine

NLP Tokenization Weighted Scoring Stopword Filtering Confidence Calculation

SupportPageMatcher provides a fallback keyword-based matching engine that can route questions without the OpenAI API. It uses tokenization, stopword filtering, weighted field scoring, and margin-based confidence calculation to find the best matching support page.

Matching Algorithm:

  1. Text Normalization: Query is lowercased, stripped, and non-alphanumeric characters are removed
  2. Keyword Extraction: Tokens shorter than 2 characters and English stopwords (40+ words) are filtered out
  3. Weighted Field Scoring: Keywords are matched against each page's fields with different weights:
    • Tags: weight 6 (highest priority)
    • Slug: weight 5
    • Title: weight 4
    • Summary: weight 2
    • Body: weight 1
  4. Overlap Ratio: For each field, the overlap ratio (matched keywords / total keywords) is calculated and multiplied by the field weight
  5. Exact Phrase Bonus: +0.5 bonus if the full keyword phrase appears in the title or slug
  6. Confidence Calculation: Base confidence from best_score / approx_max, plus a margin bonus when the best match is significantly better than the second-best (up to +0.15 for margins >= 2.5)
Technical Highlight: SupportPageMatcher uses a Struct-based Result object for clean return values. The scoring algorithm scores all pages and sorts by score descending. Confidence is derived from both absolute score quality and the margin between best and second-best matches, ensuring high confidence only when there's a clear winner. The tokenizer handles edge cases like single-character tokens and duplicate keywords via .uniq.

Velvet Theme UI Design

CSS Variables Gradient Design Responsive Layout Custom Scrollbars Glassmorphism

The entire support portal uses a cohesive "Velvet Theme" design system built with CSS custom properties. The dark purple/magenta color scheme features gradient backgrounds, glassmorphism effects, custom scrollbars, and smooth transitions throughout.

Design System Features:

  1. CSS Variables: 12+ custom properties (--velvet-background, --velvet-primary, --velvet-secondary, etc.) ensure consistent theming
  2. Chat Bubbles: User messages (right-aligned, pink gradient) and assistant messages (left-aligned, dark gradient) with asymmetric border-radius
  3. Responsive Breakpoints: Three breakpoints (768px, 480px) with adjusted padding, font sizes, and grid layouts
  4. Admin Dashboard Cards: Stats grid with auto-fit responsive columns, conversation cards with hover glow effects
  5. Modal Design: Admin access modal with backdrop blur (5px), fixed positioning, and animated transitions
  6. Custom Scrollbar: Chat container uses themed scrollbar colors matching the velvet palette
Technical Highlight: 813 lines of hand-written CSS with no framework dependencies. The design uses linear-gradient backgrounds throughout for depth, radial-gradient overlays for glow effects, and backdrop-filter: blur() for the glassmorphism modal. All interactive elements use 0.3s ease transitions. Message bubbles use white-space: pre-wrap for preserving AI response formatting.

Technical Architecture

System Architecture

LuvNote Support Portal follows the Rails MVC pattern with a service-oriented architecture. The application separates concerns into controllers, models, services, and views, with the OpenAI integration abstracted into dedicated service objects.

Web Framework

  • Ruby on Rails 8.1 — Latest Rails framework with modern defaults and Turbo Rails for SPA-like navigation
  • Turbo Rails — Hotwire-powered page transitions without full page reloads
  • Stimulus — Lightweight JavaScript controllers for interactive behavior
  • Importmap — ES module bundling without Node.js or webpack

MVC Layer

  • ChatController — Handles chat index, message creation (POST /chat), and conversation clearing (POST /chat/clear)
  • AdminController — Dashboard analytics, conversation detail views, and session management
  • SupportController — Renders individual support pages by slug (/support/:slug)
  • ApplicationController — Base controller with shared configuration

Service Layer

  • OpenaiSupportRouter — Wraps OpenAI Chat Completions API for intelligent question routing with structured JSON responses
  • SupportPageMatcher — Keyword-based fallback matching with weighted scoring, stopword filtering, and confidence calculation
  • ConversationFinder — Cookie-based session management with UUID generation and IP hashing

Data Layer

  • PostgreSQL — Primary relational database with indexed columns for fast lookups
  • ActiveRecord ORM — Rails ORM with associations, validations, and migrations
  • 7 Database Migrations — Version-controlled schema evolution with proper foreign keys and indexes
  • 4 Models — Conversation, Message, SupportPage, AdminSession with has_many/belongs_to associations

AI Integration

  • OpenAI GPT-4o-mini — Primary AI model for question understanding and routing via Chat Completions API
  • Structured JSON Responses — System prompt enforces strict JSON schema (support_page_slug, confidence, answer, reason)
  • Configurable Parameters — Model, temperature, and max_tokens configurable via environment variables or constants
  • Error Handling — Graceful fallbacks for JSON parsing errors, API failures, and NaN confidence values

Database Schema

  • conversations — visitor_id (indexed, unique), started_at, last_activity_at, user_agent, ip_hash, cleared_at
  • messages — conversation_id (FK), role, content, model, confidence, chosen_support_page_id (FK), link_type, link_url, latency_ms, prompt_tokens, completion_tokens
  • support_pages — slug (unique), title, summary, body_md, in_app_url, tags
  • event_logs — conversation_id (FK), event_name, level, data_json (JSONB)
  • admin_sessions — session_token, expires_at

Service Layer Deep Dive

OpenaiSupportRouter

OpenAI Ruby Gem Chat Completions API JSON Parsing Error Recovery

The primary AI service that routes user questions to the most relevant support page. Sends the user's question along with all support pages to GPT-4o-mini and parses the structured JSON response.

Implementation Details:

  1. Initialization: Creates OpenAI::Client with API key sourced from either Rails constants or ENV variables
  2. Page Serialization: All SupportPage records are mapped to {slug, title, summary, tags} hashes for the AI context
  3. System Prompt: Defines the AI as a LuvNote support assistant, enforces strict JSON-only output, and provides the exact response schema
  4. API Call: Uses chat() method with configurable model, temperature, and max_tokens parameters
  5. Response Parsing: Extracts support_page_slug, confidence, answer, and reason from the JSON response
  6. Page Lookup: Finds the matched SupportPage by slug, first checking the in-memory pages array, then falling back to a database query
  7. Confidence Clamping: Values are clamped to 0.0-1.0 range and NaN values are converted to 0.0
  8. Error Handling: JSON::ParserError and StandardError are caught and return a zero-confidence Result with the error class name
Technical Highlight: Uses Ruby Struct for the Result object (support_page, confidence, answer, reason) with keyword_init for clean construction. The service supports both hardcoded constants and ENV variables for configuration, enabling easy development/production switching. Confidence clamping uses Ruby's min/max chaining: [[confidence, 0.0].max, 1.0].min.

SupportPageMatcher

Text Processing Scoring Algorithm Stopword Filter Confidence Engine

A self-contained keyword matching engine that provides intelligent support page routing without any external API dependency. Built with NLP fundamentals: tokenization, stopword removal, weighted field matching, and margin-based confidence scoring.

Algorithm Steps:

  1. Normalize & Tokenize: Downcase, strip, remove non-alphanumeric characters, split on whitespace, reject tokens < 2 chars, deduplicate
  2. Stopword Filtering: Remove 40+ English stopwords (the, a, an, help, please, etc.) from extracted keywords
  3. Score Each Page: Calculate overlap_ratio (hits / total keywords) for each field, multiply by field weight, sum all scores
  4. Exact Phrase Bonus: Add +0.5 if the joined keyword phrase appears in the page title or slug
  5. Rank Pages: Sort all pages by score descending; extract best and second-best scores
  6. Confidence Calculation: base = best_score / approx_max, plus margin_bonus (0.05 to 0.15 based on gap between best and second-best)
Technical Highlight: The margin-based confidence calculation ensures high confidence scores only when there is a clear winner — preventing ambiguous matches from returning misleading high confidence. Field weights are configurable via the WEIGHTS constant hash. The STOPWORDS set uses Ruby's Set class for O(1) lookup performance.

ConversationFinder

Session Management Permanent Cookies UUID Generation Privacy Hashing

Manages visitor sessions using Rails permanent cookies and SecureRandom UUIDs. Provides find-or-create semantics with activity tracking and privacy-compliant IP hashing.

Session Lifecycle:

  1. Cookie Check: Reads luvnote_support_visitor_id from cookies
  2. New Visitor: If no cookie exists, generates SecureRandom.uuid and stores in cookies.permanent
  3. Existing Visitor: Finds Conversation by visitor_id and calls touch! to update last_activity_at
  4. New Conversation: Creates Conversation with visitor_id, started_at, last_activity_at, user_agent, and ip_hash
  5. IP Privacy: ip_hash method uses Digest::SHA256.hexdigest to one-way hash the IP address before storage
Technical Highlight: Rails permanent cookies persist for 20 years by default, ensuring returning visitors maintain their conversation history. The COOKIE_KEY constant centralizes the cookie name. IP hashing is a GDPR-friendly approach that enables analytics (detecting unique visitors) without storing personally identifiable information. The touch! pattern uses update! (with bang) to raise on validation failures.

Skills Demonstrated

Ruby on Rails

  • Rails 8.1 with modern defaults
  • MVC architecture pattern
  • ActiveRecord ORM & associations
  • Service object pattern
  • Database migrations & schema management
  • Turbo Rails & Stimulus

AI & NLP

  • OpenAI GPT-4o-mini integration
  • Chat Completions API
  • Structured JSON prompt engineering
  • Confidence scoring & thresholds
  • Keyword tokenization & stopwords
  • Weighted scoring algorithms

Database Design

  • PostgreSQL relational database
  • Foreign key constraints
  • Indexed columns for performance
  • JSONB columns for flexible data
  • Soft delete patterns (cleared_at)

Web Development

  • ERB templating
  • CSS custom properties design system
  • Responsive design (3 breakpoints)
  • Cookie-based session management
  • RESTful routing

Security & Privacy

  • SHA-256 IP hashing for privacy
  • CSRF protection (Rails default)
  • Input sanitization (html_escape)
  • Secure cookie management
  • Environment variable configuration

Software Design

  • Service object pattern
  • Struct-based result objects
  • Graceful error handling
  • Configurable via ENV or constants
  • Clean separation of concerns

Key Achievements

AI Integration

OpenAI GPT-4o-mini with structured JSON responses and confidence scoring

Rails 8.1

Built on the latest Ruby on Rails framework with modern conventions

Dual Routing

AI-powered primary router with keyword-based fallback engine

Analytics Dashboard

Real-time admin analytics with conversation tracking and metrics

Privacy-First

SHA-256 IP hashing and cookie-based sessions without authentication

Velvet Theme

813 lines of custom CSS with design system and responsive layouts

Service Architecture

Clean service object pattern with Struct results and error handling

Production Ready

PostgreSQL database with migrations, indexes, and foreign keys

Technology Stack

Ruby Ruby on Rails 8.1 PostgreSQL OpenAI GPT-4o-mini ActiveRecord Turbo Rails Stimulus Importmap ERB Templates CSS Custom Properties Chat Completions API REST API Cookie Sessions SHA-256 Hashing JSONB Markdown

Experience the Live Support Portal

Try the AI-powered support chat and see intelligent question routing in action

Visit LuvNote Support Portal

Try These Questions

Ask the AI support assistant about LuvNote features:

"How do I find events in my calendar?"
"How do playlists work?"
"I can't connect my Spotify account"
← Back to LuvNote Portfolio