Version: 2.0.0 | Platform: Android (Flutter) | Architecture: On-Device AI + Optional Cloud Fallback
Utkarsh is a privacy-first, on-device AI behavioral wellness companion built for Indian students and working professionals. It uses a multi-layer AI inference pipeline to analyze emotion, intent, behavior, and stress in real time β entirely on the user's device β without sending any personal conversation data to external servers.
- Project Overview
- Core Philosophy
- Tech Stack
- System Architecture
- 9-Layer AI Pipeline
- All Models β Detailed
- Services Layer
- Data Layer & Database Schema
- Security & Privacy Architecture
- Features
- State Management
- Navigation & Routing
- Cloud Sync (Optional)
- Model Training Pipeline
- Project Structure
- Setup & Build
- Environment Configuration
Utkarsh is a mobile AI companion that:
- Detects emotion (happy, sad, anxious, stressed, neutral, angry) from natural language
- Classifies intent (stress help, task add, planning, knowledge query, casual) in every message
- Predicts burnout risk using the Maslach Burnout Inventory proxy model
- Computes a Composite Wellbeing Score (CWS) across 7 behavioral dimensions
- Manages tasks with stress-adjusted prioritization
- Runs a fine-tuned LLaMA 3.2-1B LLM on-device using
llama.cppfor empathetic responses - Falls back to Groq Cloud API (LLaMA 3.3-70B) when online
- Encrypts every message on device with AES-256-GCM, key derived from user PIN
- Optionally syncs encrypted data to Supabase for multi-device restore
| Principle | Implementation |
|---|---|
| Privacy by Default | All AI inference runs on-device; no chat data ever leaves the phone |
| Zero-Knowledge Design | PIN never leaves device; only a PBKDF2-derived key is used for encryption |
| Offline-First | Full functionality (emotion, intent, LLM chat) works with zero internet |
| Graceful Degradation | ONNX β LLM β keyword rule fallback at every inference step |
| Student-Centric | Fine-tuned on EmoSApp counseling dataset; understands Indian academic stress |
| Layer | Technology | Version |
|---|---|---|
| Mobile Framework | Flutter (Dart) | >=3.10.0 |
| Dart SDK | Dart | >=3.0.0 <4.0.0 |
| Target Platform | Android (arm64-v8a) | Min SDK 21 |
| Component | Technology | Details |
|---|---|---|
| On-Device LLM | llama_cpp_dart (git master) |
llama.cpp compiled as libllama.so via NDK 28.2 |
| ONNX Runtime | onnxruntime ^1.4.1 |
Runs emotion, intent, burnout, wearable-stress models |
| TFLite | Built-in (asset) | stress_predictor.tflite β wearable stress fallback |
| Speech-to-Text | speech_to_text (any) |
On-device STT via Android speech API |
| Text-to-Speech | flutter_tts ^4.0.2 |
On-device TTS output |
| Component | Technology |
|---|---|
| Cloud AI Fallback | Groq API (api.groq.com) β LLaMA 3.3-70B |
| Cloud Auth & Sync | Supabase (supabase_flutter ^2.12.2) |
| HTTP Client | http ^1.2.1 |
| Large File Download | dio ^5.4.0 (streaming, progress callbacks) |
| Connectivity | connectivity_plus ^6.0.3 |
| Component | Technology |
|---|---|
| Local Database | SQLite via sqflite ^2.3.2 (schema v12) |
| Secure Key Store | flutter_secure_storage β Android Keystore / iOS Keychain |
| Encryption | cryptography ^2.7.0 β AES-256-GCM |
| KDF | PBKDF2-SHA256, 100,000 iterations |
| Hashing | crypto ^3.0.3 |
| Component | Technology |
|---|---|
| State Management | flutter_riverpod ^2.5.1 |
| Navigation | go_router ^13.2.0 + named routes |
| Animations | lottie ^3.1.0 (avatar) |
| Charts | fl_chart ^0.68.0 (wellbeing trends) |
| Icons | lucide_icons ^0.257.0 |
| Fonts | google_fonts ^8.0.2 |
| Notifications | flutter_local_notifications ^17.2.1 |
| PIN Input | pinput ^4.0.0 |
| Environment | flutter_dotenv ^5.1.0 |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β UTKARSH AI β v2.0 β
β Flutter Mobile App (Android) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PRESENTATION LAYER β
β ββββββββββββ ββββββββββ ββββββββββββ ββββββββββ βββββββββββββββ β
β β Chat β β Dash- β β Tasks β β Growth β β Assessment β β
β β Screen β β board β β Screen β β Screen β β Screen β β
β ββββββ¬ββββββ βββββ¬βββββ ββββββ¬ββββββ βββββ¬βββββ ββββββββ¬βββββββ β
β βββββββββββββ΄ββββββββββββ΄ββββββββββββ΄βββββββββββββ β
β STATE (Riverpod) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 9-LAYER AI PIPELINE β
β β
β L1: Input Capture β L2: Emotion β L3: Intent β L4: Behavior β
β β β β β β
β L5: Task Engine β L6: CWS Engine β L7: Decision Engine β
β β β
β L9: Response (LLM/Groq/Template) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ON-DEVICE AI MODELS β
β ββββββββββββββββ ββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β β utkarsh_llm β βemotion.onnxβ β intent.onnx β βburnout.onnx β β
β β .gguf β β(RoBERTa- β β(NLI zero- β β(go_emotions β β
β β(LLaMA 3.2-1B β β sentiment) β β shot NLI) β β RoBERTa-28) β β
β β Q4_K_M 770MB)β β 120MB β β 64MB β β 476MB β β
β ββββββββββββββββ ββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β βββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββ β
β β wearable_stress β β Whisper base (encoder + decoder) β β
β β .onnx (257KB) β β int8 ONNX β 160MB total β β
β βββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SERVICES LAYER β
β Auth β Encryption β Database β CWS β Burnout β Personalization β
β Cloud Sync β Notifications β XP/Gamification β Context Builder β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β STORAGE LAYER β
β SQLite (utkarsh_v2.db v12) β All messages encrypted AES-256-GCM β
β Android Keystore (PIN hash, KDF salt) β Supabase (optional sync) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
User Message
β
βΌ
βββββββββββββββ online AI disabled? ββββββββββββββββββββ
β Decision β ββββββββββββββββββββββββββΊβ Offline LLM β
β Engine β β (llama.cpp) β
β (Layer 7) β ββββββββββββββββββββ
β β has internet? ββββββββββββββββββββ
β β ββββββββββββββββββββββββββΊβ Groq Cloud API β
β β (yes, knowledge query) β LLaMA-3.3-70B β
β β ββββββββββββββββββββ
β β has internet + local ββββββββββββββββββββ
β β model loaded? ββββΊβ On-Device LLM β
β β β (preferred) β
β β no internet + LLM? ββββΊβ On-Device LLM β
β β ββββββββββββββββββββ
β β no internet + no LLM ββββββββββββββββββββ
β β ββββββββββββββββββββββββββΊβ Template Fallbackβ
βββββββββββββββ ββββββββββββββββββββ
The pipeline processes every user message through 9 sequential layers. Each layer is a dedicated Dart service class with ONNX/LLM/rule fallbacks.
What it does: Captures raw user input in multiple modalities.
- Text input: Direct keyboard entry via
TextEditingController - Voice input: Converted via
SpeechService(Android on-device STT β text string) - Normalization: Strips excess whitespace, validates non-empty payload
- Output:
String userMessageβ passed to Layer 2
What it does: Classifies the emotional sentiment of the user's message with a 3-tier cascade.
Tier 1 β ONNX Inference (Primary):
- Model:
emotion.onnxβ a quantizedtwitter-roberta-base-sentiment-latestmodel (120 MB) - Input: Tokenized message β
[CLS] tokens [SEP]βinput_ids+attention_masktensors, shape[1, seq_len] - Output: 3 logits β softmax β probabilities
[negative, neutral, positive] - Stress computation:
stress = (neg_prob Γ 80) + (neutral_prob Γ 20)β clamped 0β100 - Emotion mapping: positiveβ
happy, neutralβneutral, stress>75βstressed, stress>50βanxious, elseβsad
Tier 2 β LLM Fallback (if ONNX fails):
- Sends prompt to on-device
LLMService.generate() - Parses JSON response:
{"sentiment":"...", "stressLevel": 0-100}
Tier 3 β Keyword Fallback (always available):
- Hardcoded negative/positive keyword lists
- Returns
EmotionResultwithisFallback=true, confidence 0.45β0.55
Output: EmotionResult { Emotion sentiment, double stressLevel, double confidence, bool isFallback }
What it does: Classifies the purpose of the user's message into one of 6 intent classes.
Intent Classes:
| Class | Description |
|---|---|
stressHelp |
User needs emotional support / is distressed |
taskAdd |
User wants to add a task / reminder |
taskUpdate |
User wants to modify/complete a task |
planning |
User wants to plan/schedule/organize |
knowledgeQuery |
User is asking an informational question |
casual |
Small talk / greetings |
3-Tier Classification Cascade:
Tier 1 β Rule Engine (β₯0.80 confidence β short-circuits):
- Keyword phrase matching with multi-hit scoring
- Example:
['add task', 'remind me', 'to-do']βtaskAddat confidence 0.80+
Tier 2 β Zero-Shot NLI ONNX:
- Model:
intent.onnxβ a RoBERTa-NLI model (64 MB) - Method: For each of the 6 intent classes, forms a premise+hypothesis pair:
[CLS] user_text [SEP] "This text is about adding a new task" [SEP]
- Runs 6 inference passes β NLI logits β entailment probability for each class
- Selects class with highest entailment score
- Auto-detects if model accepts
token_type_ids(RoBERTa-style models do not)
Tier 3 β LLM Fallback:
- Prompt: classify intent among the 6 classes, return JSON
Output: IntentResult { IntentClass intent, double confidence, String source }
What it does: Tracks behavioral events over time and surfaces patterns.
- Logs discrete behavioral events (task completion, mood check-in, engagement patterns)
- Events stored in
behavior_eventsSQLite table withevent_type,intensity,metadata BehaviorScore(0β100) is fed into CWS Layer 6- Detects flags:
overload,procrastination,social_withdrawal
What it does: Manages the full task lifecycle and computes task completion score.
- Task Extraction from Chat: If intent is
taskAdd, the AI extracts task title from message - Stress-Adjusted Prioritization: If
stressLevel > 70, taskpriority_scoreis lowered andstress_adjusted = true - Smart Scheduling: Tasks have
estimated_minutes,scheduled_for,snoozed_until,deferred_to - Subtask decomposition: Large tasks split into
List<String> subtasks - Scoring:
taskScore = (completedToday / totalPending) Γ (1 - stressLevel/100) Γ 100
What it does: Computes a single holistic wellbeing index from 7 weighted dimensions.
CWS Formula:
CWS = (emotionScore Γ 0.25)
+ ((100 - stressLevel) Γ 0.15)
+ (taskScore Γ 0.15)
+ (activityScore Γ 0.10)
+ (routineScore Γ 0.10)
+ (behaviorScore Γ 0.15)
+ (growthScore Γ 0.10)
7-Day Exponential Smoothing:
smoothedCWS = (0.7 Γ todayCWS) + (0.3 Γ 7dayHistoryAverage)
Risk Levels:
| CWS Score | Risk | Color |
|---|---|---|
| 75β100 | π’ Green β Healthy | Green |
| 50β74 | π‘ Yellow β Mild Stress | Yellow |
| 30β49 | π Orange β Moderate Burnout Risk | Orange |
| 0β29 | π΄ Red β Severe Burnout | Red |
Context-Aware Weights: PersonalizationEngine adjusts weights:
- Exam period β tasks weight: 0.20, stress weight: 0.20
- High stress triggers (β₯3) β emotion weight: 0.30
LLM Insight Generation: If LLM is in memory, generates a 1-sentence longitudinal insight based on CWS trend (improving/stable/declining).
What it does: The routing intelligence that decides which AI model responds.
Decision Paths:
- Path A β Forced Offline (
onlineAiEnabled=false): Routes to on-device LLM; if model not downloaded β__MODEL_NOT_LOADED__message - Path B β Online Mode with Internet:
knowledgeQueryintent β always Groq Cloud (needs up-to-date knowledge)- Local LLM in memory β prefer on-device LLM (privacy, speed)
- No local LLM β Groq Cloud
- Groq fails β fallback to on-device LLM β fallback to template
- Path C β Online Mode, No Internet:
- Local LLM available β on-device LLM
- No LLM β template fallback
Context Injection: Injects stressLevel, emotion, and ContextCapsule data into Groq system prompt for personalized responses.
What it does: Executes actual text generation from the on-device LLM.
Model Initialization:
- Loads
libllama.so(NDK 28.2, GGUF v3 compatible) - Priority:
utkarsh_llm.gguf(770MB) βutkarsh_llm_tiny.gguf(469MB) - Two adaptive load profiles (mmap + no-mmap fallback)
- Automatic extraction from Flutter assets to app documents directory
Inference Parameters:
| Parameter | Value |
|---|---|
| Max tokens | 128 |
| Context size | 512 (low RAM optimization) |
| Threads | 2 |
| Temperature | 0.72 |
| Top-P | 0.9 |
| Repeat penalty | 1.1 |
| Max history turns | 5 |
Prompt Format (LLaMA 3.2 Instruct):
<|begin_of_text|>
<|start_header_id|>system<|end_header_id|>
[PersonalizationEngine system prompt]
<|eot_id|>
<|start_header_id|>user<|end_header_id|>
[user message]
<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
[streamed response tokens...]
Streaming: Tokens streamed via onToken callback β real-time display in chat bubble
| Property | Value |
|---|---|
| Base Model | meta-llama/Llama-3.2-1B-Instruct |
| Fine-Tuning Method | LoRA (rank=16, alpha=32, dropout=0.05) |
| Fine-Tuning Dataset | EmoSApp mental health counseling dataset (seeker/supporter multi-turn) |
| Training Framework | HuggingFace TRL SFTTrainer on Google Colab T4 GPU |
| Quantization | Q4_K_M via llama.cpp convert_hf_to_gguf.py |
| File Size | ~770 MB (full) / ~469 MB (tiny variant) |
| Runtime | llama_cpp_dart β libllama.so (NDK 28.2) |
| Context Window | 512 tokens (app-side) |
| Target Layers | q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj |
| Training Duration | ~2-3 hours on T4 GPU, 3 epochs |
| System Persona | "Utkarsh, warm and caring mental health companion for Indian students" |
| Crisis Reference | iCALL helpline: 9152987821 |
| Property | Value |
|---|---|
| Base Model | twitter-roberta-base-sentiment-latest |
| Architecture | RoBERTa (transformer encoder) |
| Output | 3-class: [negative, neutral, positive] |
| Quantization | ONNX (fp32 export) |
| File Size | ~120 MB |
| Input Tensors | input_ids [1, seq_len], attention_mask [1, seq_len] |
| Output | Logits [1, 3] β softmax β probabilities |
| Stress Score | neg_prob Γ 80 + neutral_prob Γ 20 |
| Runtime | ONNX Runtime (onnxruntime ^1.4.1) |
| Property | Value |
|---|---|
| Base Model | RoBERTa-based NLI model |
| Method | Zero-shot Natural Language Inference (NLI) |
| Output | Entailment probability for each of 6 intent hypotheses |
| File Size | ~64 MB |
| Input | Premise (user text) + Hypothesis (intent description) encoded as NLI pair |
| NLI Labels | [contradiction=0, neutral=1, entailment=2] |
| Runtime | ONNX Runtime |
| Property | Value |
|---|---|
| Base Model | go_emotions RoBERTa (Google's 28-class emotion model) |
| Output | 28 emotion probabilities mapped to 5 Maslach Burnout Inventory dimensions |
| File Size | ~476 MB |
| Input | Tokenized text (last 5β10 messages concatenated) |
| Output Dimensions | Exhaustion, Cynicism, Low Efficacy, Anxiety, Sadness |
| Burnout Index | Weighted sum: exhaustionΓ1.5 + cynicism + lowEfficacy + anxietyΓ0.75 + sadnessΓ0.75 |
| Risk Labels | Healthy / Mild stress / Moderate burnout risk / High burnout risk / Severe burnout |
Label-to-Dimension Mapping:
| Dimension | go_emotions Labels (index) |
|---|---|
| Exhaustion | grief(16), sadness(25), disappointment(9) |
| Cynicism | annoyance(3), disapproval(10), disgust(11) |
| Low Efficacy | admiration(0), gratitude(15), pride(21) β INVERTED |
| Anxiety | fear(14), confusion(6), remorse(24) |
| Sadness | sadness(25), grief(16), disappointment(9) |
| Property | Value |
|---|---|
| Purpose | Predicts stress from wearable sensor inputs (HRV, steps, sleep) |
| File Size | ~257 KB (lightweight) |
| Companion | stress_scaler.json β feature normalization parameters |
| Runtime | ONNX Runtime |
| Property | Value |
|---|---|
| Purpose | Alternative lightweight stress prediction (TensorFlow Lite) |
| File Size | ~350 KB |
| Companion | stress_scaler.json |
| Property | Value |
|---|---|
| Base Model | OpenAI Whisper Base |
| Format | INT8 ONNX (quantized for mobile) |
| Components | base-encoder.int8.onnx (28MB) + base-decoder.int8.onnx (124MB) |
| Vocabulary | base-tokens.txt (~800KB) |
| Tokenizer | emosapp/tokenizer.bin (2.1MB) |
| Purpose | Speech-to-text for voice input |
Manages Supabase authentication + PIN-based encryption key setup.
- Registration: Creates Supabase account β generates KDF salt β derives AES key from PIN β stores verification blob in Supabase
user_metatable β migrates local data to new user ID - Login: Supabase auth β fetches KDF salt β re-derives AES key from PIN locally β verifies against encrypted blob β auto-restores cloud backup if local DB is empty
- Auto-unlock: On cold start, reads cached PIN from Android Keystore β re-derives key without prompting
- Manual unlock: Used from PIN lock screen
AES-256-GCM end-to-end encryption for all stored data.
- Key Derivation: PBKDF2-SHA256 (100,000 iterations) from
PIN + saltβ 256-bit key - Key Storage: Only in memory during session; PIN hash in Android Keystore
- Format:
nonce_base64:ciphertext_base64:mac_base64 - Scope: All
messages.contentfields encrypted before SQLite insert; decrypted on read - Key Lock: On app background β
lockKey()clears in-memory key
SQLite data access layer (v12 schema, singleton pattern).
- Tables:
messages,wellbeing_records,tasks,xp_events,assessments,behavior_events,user_profile,context_capsules - Encryption: All message content encrypted via
EncryptionServicebefore storage - Migration:
migrateLocalData()moves alllocal_userrecords to authenticated user ID on first login - Multi-user safe: All queries scoped by
user_id
3-tier emotion analysis (ONNX β LLM β keyword fallback). See Layer 2.
3-tier zero-shot NLI intent classification. See Layer 3.
go_emotions-based burnout dimension analysis. See Model #4.
Composite Wellbeing Score computation + 7-day smoothing + LLM insight. See Layer 6.
AI routing logic. See Layer 7.
On-device LLM inference via llama.cpp. See Layer 9.
Cloud AI fallback using Groq REST API.
- Endpoint:
https://api.groq.com/openai/v1/chat/completions - Model:
llama-3.3-70b-versatile - Parameters: temperature 0.7, max_tokens 1024
- Context injection:
PersonalizationEngine.buildSystemPrompt()+ContextCapsuleblock - Sends last 8 messages of history
Builds dynamic system prompts tailored to user profile.
- Injects: name, age, profession, stream/year (college), stress triggers, social preference
- Communication style: Warm/Direct/Analytical based on
ResponseStyle - Language: English / Hindi / Hinglish
- Greeting: Time-of-day + academic context (exam/placement season)
- CWS weight adjustment: exam period, high trigger count
Builds ContextCapsule β a 7-day behavioral summary injected into AI prompts.
- Aggregates: dominant emotion, average stress, primary intent, consecutive negative days, session themes
- Behavioral flags:
overload,procrastination,social_withdrawal - Session themes (12 types):
academicPressure,sleepIssues,burnout,positiveProgress, etc.
Gamification layer β XP rewards and level progression.
XP Table:
| Action | XP |
|---|---|
TASK_COMPLETED |
10 |
STRESS_REDUCED |
15 |
WEEKLY_STREAK |
20 |
ASSESSMENT_COMPLETE |
8 |
MOOD_IMPROVED |
10 |
REFLECTION_ADDED |
5 |
DAILY_CHECKIN |
3 |
Level Progression:
| Level | XP Range | Emoji |
|---|---|---|
| Awareness | 0β100 | π± |
| Stabilizing | 100β300 | πΏ |
| Improving | 300β700 | π³ |
| Resilient | 700β1500 | π |
| Flourishing | 1500+ | β¨ |
Wraps Android speech_to_text plugin for voice input. Singleton instance with initialize/listen/stop lifecycle.
Push notifications via flutter_local_notifications. Schedules evening wellbeing check-in.
Optional Supabase sync for multi-device support. Encrypts all data before upload; restores on fresh install.
Database File: utkarsh_v2.db (SQLite, schema version 12)
CREATE TABLE messages (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL DEFAULT 'local_user',
role TEXT NOT NULL CHECK(role IN ('user','assistant','system')),
content TEXT NOT NULL, -- AES-256-GCM encrypted
timestamp INTEGER NOT NULL,
emotion_label TEXT, -- 'happy','sad','anxious','stressed','neutral','angry'
stress_level REAL, -- 0.0-100.0
intent_class TEXT, -- 'stressHelp','taskAdd',...
session_id TEXT NOT NULL
)CREATE TABLE wellbeing_records (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL DEFAULT 'local_user',
date TEXT NOT NULL UNIQUE, -- YYYY-MM-DD
cws_score REAL NOT NULL, -- 0-100 (smoothed)
emotion_score REAL,
stress_score REAL,
task_score REAL,
activity_score REAL,
routine_score REAL,
behavior_score REAL,
growth_score REAL,
risk_level TEXT, -- 'green','yellow','orange','red'
created_at INTEGER NOT NULL
)CREATE TABLE tasks (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL DEFAULT 'local_user',
title TEXT NOT NULL,
description TEXT,
deadline INTEGER, -- ms timestamp
priority INTEGER DEFAULT 2, -- 1=low,2=med,3=high,4=critical
status TEXT DEFAULT 'pending',
category TEXT DEFAULT 'academic', -- academic|personal|social|professional|...
complexity TEXT DEFAULT 'medium', -- micro|small|medium|large|project
extracted_from_chat INTEGER DEFAULT 0,
pre_mood INTEGER,
post_mood INTEGER,
created_at INTEGER NOT NULL,
completed_at INTEGER,
snoozed_until INTEGER,
deferred_to INTEGER,
subtasks TEXT, -- pipe-separated micro-steps
estimated_minutes INTEGER DEFAULT 30,
scheduled_for INTEGER,
priority_score REAL DEFAULT 50.0, -- stress-adjusted computed score
stress_adjusted INTEGER DEFAULT 0
)Stores complete UserProfile β demographics, academic context, lifestyle, AI preferences, notification prefs, gamification state, emergency contacts.
Stores structured wellbeing assessment results (GAD-7, PHQ-9, daily mood/stress).
Weekly behavioral summary: dominant emotion, average stress, behavioral flags, session themes, CWS trend.
Individual XP award events for gamification history.
Discrete behavioral signals with intensity scores.
User PIN (6 digits)
β
βΌ
PBKDF2-SHA256 (100,000 iterations)
β
βΌ
256-bit AES-GCM Key βββ (IN MEMORY ONLY) ββββΊ Encrypts all SQLite messages
β
βββββ Cleared on app background
β
PIN Hash (checksum) βββ Android Keystore βββΊ Used for PIN verification only
KDF Salt ββββββββββββ Android Keystore + Supabase user_meta βββΊ Salt restoration
- β Chat messages β encrypted on device before SQLite write β never sent to Supabase
- β Emotion/Intent analysis β runs 100% on-device (ONNX)
- β LLM response β runs 100% on-device (llama.cpp)
- β‘ Groq Cloud β only used when: user enables online AI AND has internet AND (knowledge query or no local LLM)
- β
Supabase β only stores:
kdf_salt,kdf_iters,pin_verify_blob,schema_verβ NO chat content
| Spec | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key Derivation | PBKDF2-SHA256 |
| Iterations | 100,000 |
| Salt Length | 256-bit random |
| Nonce | 96-bit per-message random |
| MAC | 128-bit GCM authentication tag |
| Wire Format | nonce_b64:ciphertext_b64:mac_b64 |
- Real-time streaming response from on-device LLM or Groq
- Automatic emotion + intent analysis per message
- Avatar animation (Lottie) reactive to emotional state
- Voice input (STT) + voice output (TTS)
- Task extraction from natural language
- Context-aware responses using 7-day
ContextCapsule - AI mode indicator (Offline/Online/Template)
- Composite Wellbeing Score (CWS) donut chart
- 7-day CWS trend line (fl_chart)
- Risk level indicator with color coding
- Daily check-in prompt
- Streak counter + XP progress bar
- Quick task overview
- Full task CRUD with categories and complexity levels
- Stress-adjusted priority scoring
- Snooze and defer functionality
- Subtask decomposition for large tasks
- Pre/post mood tracking per task
- Chat-extracted task detection badge
- XP history and level visualization
- Weekly streak tracking
- Wellbeing trend analysis
- Achievement indicators
- Daily mood & stress check-ins
- Structured wellbeing assessments (GAD-7 / PHQ-9 proxies)
- Score history and severity labels
- XP award on completion
- Email/password registration with Supabase
- 6-digit PIN setup for on-device key derivation
- Auto-unlock on app start
- Multi-device restore from Supabase
- Toggle online/offline AI mode
- Clear cached LLM models
- Emergency contacts management
- Notification preferences
- Profile edit
- Debug panel for AI service status
- Profession selection (college student, school student, professional, job seeker, researcher)
- Academic context (university, stream, year, upcoming events)
- Lifestyle preferences (wake/sleep time, stress triggers, activity level)
- AI preference setup (response style, language, voice)
- Model download/setup screen
Uses Riverpod v2 (flutter_riverpod ^2.5.1) via ProviderScope at app root.
AppState (lib/state/app_state.dart):
enum AiMode { groq, llm, offline }Tracks current AI mode across the app for UI indicators.
All services use singleton pattern (ServiceName.instance or serviceNameSingleton) rather than Riverpod providers for heavy ML services to avoid re-initialization.
Uses go_router v13 + named MaterialApp.routes for legacy compatibility.
Route Map:
| Route | Screen | Condition |
|---|---|---|
/ (home) |
AuthWrapper β decides initial screen |
Always |
WelcomeScreen |
Welcome/Login choice | Not logged in |
PinUnlockScreen |
PIN entry screen | Logged in but locked |
OnboardingFlow |
Multi-step onboarding | First time user |
ModelSetupScreen |
Model download / setup | After onboarding, before first use |
AppShell |
Main tab navigation | Fully authenticated |
/profile |
Profile view | Authenticated |
AuthWrapper boot sequence:
- PIN auto-unlock attempt
- LLM disk presence check (non-blocking)
- Profile check (onboarding gate)
- Bundled offline pack auto-detection
Supabase is used only as a secure backup β no AI processing occurs in the cloud.
Supabase Tables:
| Table | Columns | Purpose |
|---|---|---|
user_meta |
user_id, kdf_salt, kdf_iters, schema_ver, pin_verify_blob |
KDF salt + PIN verification blob |
Cloud Sync flow:
CloudSyncService.restoreAll()β called on login to a new device- Fetches
kdf_saltβ re-derives local AES key from PIN - Downloads and decrypts context capsules / profile from Supabase
- Restores local SQLite state
The primary LLM (utkarsh_llm.gguf) is trained using scripts/finetune_emosapp.py:
Step 1: Load EmoSApp dataset (seeker/supporter multi-turn JSON)
βββ Format as LLaMA 3.2 Instruct chat template
Step 2: Load base model: meta-llama/Llama-3.2-1B-Instruct
βββ 4-bit NF4 quantization (BitsAndBytes) for T4 GPU
Step 3: Apply LoRA (rank=16, alpha=32)
βββ Target: q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj
Step 4: SFT Training (3 epochs, cosine LR schedule, paged_adamw_8bit)
βββ ~2-3 hours on Google Colab T4 GPU
Step 5: Merge LoRA adapter into base model weights (fp16)
Step 6: Convert merged model β GGUF Q4_K_M via llama.cpp
βββ Output: utkarsh_llm.gguf (~770MB)
Step 7: Place in assets/models/ β flutter build apk
libllama.so Build (scripts/build_libllama.ps1):
- llama.cpp commit
0e4ebeb(GGUF v3 compatible) - NDK 28.2 + CMake 3.22.1
- Target:
arm64-v8a - Output:
android/app/src/main/jniLibs/arm64-v8a/libllama.so
utkarsh_ai/
βββ lib/
β βββ main.dart # Entry point β sequential boot, background init
β βββ app.dart # UtkarshApp widget + AuthWrapper
β βββ models/
β β βββ user_profile.dart # UserProfile + nested profiles (College/School/Professional)
β β βββ emotion.dart # Emotion enum (happy|sad|anxious|stressed|neutral|angry)
β β βββ intent.dart # IntentClass enum (6 classes)
β β βββ task.dart # Task model (status/category/complexity/subtasks)
β β βββ context_capsule.dart # 7-day behavioral summary model
β βββ pipeline/
β β βββ layer1_input/ # Input capture (placeholder, logic in services)
β β βββ layer2_emotion/ # (Implemented in services/emotion/)
β β βββ layer3_intent/ # (Implemented in services/intent/)
β β βββ layer4_behavior/ # (Implemented in services/behavior/)
β β βββ layer5_tasks/ # (Implemented in services/tasks/)
β β βββ layer6_cws/ # (Implemented in services/cws/)
β β βββ layer7_decision/ # (Implemented in services/decision/)
β β βββ layer9_response/
β β βββ llm_service.dart # On-device LLM (llama.cpp wrapper, streaming)
β βββ services/
β β βββ auth/
β β β βββ auth_service.dart # Supabase auth + PIN KDF setup
β β β βββ key_derivation_service.dart # PBKDF2 implementation
β β βββ storage/
β β β βββ database_service.dart # SQLite DAO (v12 schema, 8 tables)
β β β βββ encryption_service.dart # AES-256-GCM (in-memory key)
β β βββ emotion/
β β β βββ emotion_service.dart # ONNX β LLM β keyword cascade
β β βββ intent/
β β β βββ intent_service.dart # Rule β NLI ONNX β LLM cascade
β β βββ burnout/
β β β βββ burnout_service.dart # go_emotions ONNX β Maslach mapping
β β βββ cws/
β β β βββ cws_engine.dart # CWS formula + smoothing + risk levels
β β βββ decision/
β β β βββ decision_engine.dart # AI routing (offline/online/template)
β β β βββ groq_client.dart # Groq REST API client
β β βββ personalization/
β β β βββ personalization_engine.dart # System prompt builder
β β βββ context/
β β β βββ context_builder_service.dart # ContextCapsule builder
β β βββ growth/
β β β βββ xp_service.dart # XP awards + level progression
β β βββ cloud/
β β β βββ cloud_sync_service.dart # Supabase encrypted backup/restore
β β βββ input/
β β β βββ speech_service.dart # STT via speech_to_text
β β β βββ input_capture_service.dart
β β βββ avatar/
β β β βββ avatar_service.dart # Lottie avatar emotion state
β β βββ notifications/
β β β βββ notification_service.dart # Local push notifications
β β βββ behavior/ # Behavioral event logging
β βββ features/
β β βββ auth/ # Login, Welcome, PIN unlock screens
β β βββ onboarding/ # Multi-step onboarding + model setup
β β βββ chat/ # AI chat interface (main screen)
β β βββ dashboard/ # CWS dashboard + trends
β β βββ tasks/ # Task management screens
β β βββ growth/ # XP, levels, streak screens
β β βββ assessment/ # Mood + stress assessment screens
β β βββ settings/ # App settings + profile screens
β β βββ setup/ # Model download/setup UI
β βββ core/
β β βββ theme/ # AppTheme (dark theme, color scheme)
β β βββ constants/ # App-wide constants
β β βββ utils/ # Helpers (todayString, etc.)
β β βββ widgets/ # Shared UI components
β βββ navigation/
β β βββ app_router.dart # go_router configuration
β βββ state/
β βββ app_state.dart # Global AiMode enum + Riverpod providers
βββ assets/
β βββ logo.png
β βββ lottie/ # Avatar Lottie animation JSON files
β βββ models/
β βββ utkarsh_llm_tiny.gguf # ~469MB (bundled tiny LLM)
β βββ emotion.onnx # ~120MB (RoBERTa sentiment)
β βββ intent.onnx # ~64MB (NLI zero-shot)
β βββ burnout.onnx # ~476MB (go_emotions)
β βββ wearable_stress.onnx # ~257KB
β βββ stress_scaler.json # Feature scaler params
β βββ stress_predictor.tflite # ~350KB
β βββ whisper/
β β βββ base-encoder.int8.onnx # ~28MB
β β βββ base-decoder.int8.onnx # ~124MB
β β βββ base-tokens.txt # Whisper vocabulary
β βββ emosapp/
β βββ tokenizer.bin # EmosApp tokenizer (~2.1MB)
βββ models_devonly/
β βββ utkarsh_llm.gguf # Full 770MB LLM (dev only, not committed)
β βββ utkarsh_llm_tiny.gguf
β βββ burnout.onnx
β βββ tts.onnx # TTS model (dev exploration)
βββ scripts/
β βββ finetune_emosapp.py # LoRA fine-tuning pipeline (Colab)
β βββ convert_emosapp.py # Model conversion utilities
β βββ colab_setup.py # Colab environment setup
β βββ build_libllama.ps1 # libllama.so native build script (Windows)
βββ android/ # Android native project
β βββ app/src/main/jniLibs/
β βββ arm64-v8a/
β βββ libllama.so # Pre-built llama.cpp native lib
βββ pubspec.yaml # Flutter dependencies
βββ .env # API keys (not committed)
βββ analysis_options.yaml
- Flutter SDK
>=3.10.0 - Android NDK 28.2 (for native llama.cpp build)
- Google Colab T4 (for LLM fine-tuning)
git clone <repo-url>
cd utkarsh_ai
flutter pub getcp .env.example .env
# Fill in GROQ_API_KEY, SUPABASE_URL, SUPABASE_ANON_KEYThe app handles model extraction automatically on first launch from bundled assets. For development, place models in assets/models/.
# Debug
flutter run
# Release APK
flutter build apk --release
# Generate icons
flutter pub run flutter_launcher_icons# From project root
.\scripts\build_libllama.ps1.env file (not committed to git):
# Groq Cloud API (optional β used for online AI fallback)
GROQ_API_KEY=gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Supabase (optional β used for auth and encrypted backup)
SUPABASE_URL=https://xxxx.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...App behavior without keys:
- No
GROQ_API_KEYβ Online AI disabled; on-device LLM only - No Supabase keys β Auth disabled; app runs in fully local mode (
local_userprofile) - All core AI features work with zero API keys (emotion, intent, burnout, LLM chat)
| Metric | Value |
|---|---|
| Total bundled AI models | 7 models, ~1.65 GB |
| Primary LLM size (full) | ~770 MB (Q4_K_M GGUF) |
| Primary LLM size (tiny) | ~469 MB |
| SQLite schema version | 12 |
| Pipeline layers | 9 |
| Intent classes | 6 |
| Emotion classes | 6 |
| Burnout dimensions | 5 (Maslach Inventory proxies) |
| CWS dimensions | 7 |
| Gamification levels | 5 (Awareness β Flourishing) |
| Min Android SDK | API 21 (Android 5.0) |
| Target architecture | arm64-v8a |
Built with β€οΈ for Indian students facing academic stress β privacy first, always.