Skip to content
ParvSuhagiyaPublic
forked from codinggita/evofox

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Β 
Β 

Latest commit

Β 

History

50 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’¬ EchoMind

Local-First AI Texting-Style Cloner, Powered by Your Own WhatsApp History

React Vite Express.js NodeJS Ollama TailwindCSS

Runs 100% locally β€” no cloud LLM, no API keys, no database, no vector store.

Report Bug Β· Request Feature


🌍 Project Vision

"Everyone texts a little differently β€” the slang, the emoji habits, the message length, the punctuation (or total lack of it). EchoMind turns a real chat export into a working style profile, and lets a local LLM reply the way that person actually would β€” with nothing ever leaving your machine."

Paste an exported WhatsApp chat β†’ EchoMind parses it, learns one sender's texting style from real message pairs β†’ you type an incoming message β†’ a locally-running LLM (via Ollama) generates a reply in that person's voice β†’ you copy it out. No cloud fallback, no persistence, no data leaving the laptop it runs on.


πŸ“– Introduction

EchoMind is a decoupled full-stack app: a Vite + React frontend and an Express.js backend, connected to a local Ollama model as the only network dependency.

The backend is split into a pure-logic ai/ layer (parsing, cleaning, style profiling, retrieval, prompt building, and the Ollama call) sitting behind a thin controllers/routes HTTP layer β€” so the actual "AI" is unit-testable without spinning up Express at all. The frontend is a small, focused flow: paste β†’ pick a sender β†’ chat, with a style-profile card as the proof-of-learning centerpiece.

There is deliberately no database and no embeddings β€” retrieval is done with keyword-overlap scoring in plain JS, which is fast, dependency-free, and easy to reason about at chat-history scale.


πŸ› οΈ Tech Stack Architecture

πŸ’» Frontend (Client-Side)

Technology Category Purpose
React 19 + Vite Core UI Framework Fast dev server, instant HMR
React Router 7 Routing Landing β†’ Upload β†’ Select Sender β†’ Chat flow
Tailwind CSS 4 Styling Utility-first styling, light/dark theme support
lucide-react Icons Lightweight icon set for chat/nav UI
Context API State (theme) Persisted light/dark mode via ThemeContext

βš™οΈ Backend (Server-Side)

Technology Category Purpose
Node.js + Express Web Server Minimal, fast HTTP layer for 3 endpoints
Ollama (local) LLM Inference llama3.2:3b by default β€” the only network call in the whole backend
Plain JS keyword overlap Retrieval Few-shot example selection, zero dependencies
In-memory state Storage No DB β€” frontend passes parsed data back on each call
node:test Testing Native test runner, no extra dependencies

✨ System Features

  • 🧾 WhatsApp Export Parsing β€” handles 12h/24h clocks, both date orders, multi-line messages, and structurally tags system lines (joins/leaves, encryption banners) so they're never confused with real text.
  • 🧹 Noise Cleaning β€” strips media placeholders, missed calls, deleted messages β€” without false-positive matching on ordinary words like "left" or "added".
  • 🧠 Style Profiling β€” quantifies one sender's real voice: average word count, emoji usage (presence-ratio, not raw count), top emojis, common phrases, capitalization and punctuation style.
  • πŸ” Keyword-Overlap Retriever β€” pulls the most relevant real (incoming β†’ reply) examples as few-shot context, with a random-fill fallback so the model always has something to anchor on.
  • πŸ€– Local-Only Generation β€” one prompt, one call to a local Ollama instance, a 20-second hard timeout so a hung model never freezes the demo.
  • 🎨 Style Profile Card β€” a visual, at-a-glance proof that the app actually learned something, shown right inside the chat flow.

πŸ—οΈ System Architecture

graph TD
    A[React Frontend] -->|"POST /api/parse<br/>rawText"| B[Express Router]
    B --> C[parseController]
    C --> D["ai/parser + ai/cleaner"]
    D -->|"messages, senders"| A

    A -->|"POST /api/analyze<br/>messages, targetSender"| E[analyzeController]
    E --> F["ai/parser (buildReplyPairs)<br/>+ ai/personality"]
    F -->|"pairs, styleProfile"| A

    A -->|"POST /api/generate-reply<br/>newMessage, pairs, styleProfile"| G[generateController]
    G --> H[ai/retriever]
    H --> I[ai/prompts]
    I --> J["ai/response β†’ Ollama /api/generate"]
    J -->|reply| A
Loading

πŸ“‚ Repository Structure

evofox/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ ai/                          # Pure logic β€” no Express, no I/O except Ollama
β”‚   β”‚   β”œβ”€β”€ parser/
β”‚   β”‚   β”‚   β”œβ”€β”€ parseWhatsAppText.js   # Raw export text β†’ { timestamp, sender, text }[]
β”‚   β”‚   β”‚   └── buildReplyPairs.js     # Messages β†’ { incoming, reply }[] for one sender
β”‚   β”‚   β”œβ”€β”€ cleaner/
β”‚   β”‚   β”‚   └── cleanMessages.js       # Strips system lines, media/call placeholders, empties
β”‚   β”‚   β”œβ”€β”€ personality/
β”‚   β”‚   β”‚   └── buildStyleProfile.js   # Reply text β†’ StyleProfile
β”‚   β”‚   β”œβ”€β”€ retriever/
β”‚   β”‚   β”‚   └── findSimilarExamples.js # Keyword-overlap few-shot example selection
β”‚   β”‚   β”œβ”€β”€ prompts/
β”‚   β”‚   β”‚   └── buildPrompt.js         # Style profile + examples + new message β†’ LLM prompt
β”‚   β”‚   β”œβ”€β”€ response/
β”‚   β”‚   β”‚   └── callOllama.js          # The only network call in the backend
β”‚   β”‚   └── tests/
β”‚   β”‚       β”œβ”€β”€ parser.test.js
β”‚   β”‚       β”œβ”€β”€ cleaner.test.js
β”‚   β”‚       └── personality.test.js
β”‚   └── backend/
β”‚       β”œβ”€β”€ package.json
β”‚       └── src/
β”‚           β”œβ”€β”€ app.js                 # Express app, CORS, routes, error handler, listen
β”‚           β”œβ”€β”€ config/index.js        # PORT, OLLAMA_URL, OLLAMA_MODEL
β”‚           β”œβ”€β”€ controllers/
β”‚           β”‚   β”œβ”€β”€ parseController.js
β”‚           β”‚   β”œβ”€β”€ analyzeController.js
β”‚           β”‚   └── generateController.js
β”‚           β”œβ”€β”€ routes/
β”‚           β”‚   β”œβ”€β”€ parseRoutes.js     # POST /api/parse
β”‚           β”‚   β”œβ”€β”€ analyzeRoutes.js   # POST /api/analyze
β”‚           β”‚   └── generateRoutes.js  # POST /api/generate-reply
β”‚           β”œβ”€β”€ middlewares/errorHandler.js
β”‚           β”œβ”€β”€ services/              # scaffolded, currently empty
β”‚           └── utils/                 # scaffolded, currently empty
β”‚
└── frontend/
    β”œβ”€β”€ index.html
    β”œβ”€β”€ vite.config.js
    └── src/
        β”œβ”€β”€ main.jsx
        β”œβ”€β”€ App.jsx                    # Route table
        β”œβ”€β”€ context/ThemeContext.jsx   # Light/dark theme, persisted to localStorage
        β”œβ”€β”€ pages/
        β”‚   β”œβ”€β”€ Landing.jsx            # "/"
        β”‚   β”œβ”€β”€ Upload.jsx             # "/upload"        β€” paste + parse screen
        β”‚   β”œβ”€β”€ Dashboard.jsx          # "/select-sender" β€” pick target sender, run analyze
        β”‚   └── Chat.jsx               # "/chat"          β€” the live demo screen
        β”œβ”€β”€ components/
        β”‚   β”œβ”€β”€ common/{NavBar,Layout,Button}.jsx
        β”‚   β”œβ”€β”€ chat/MessageBubble.jsx
        β”‚   └── personality/PersonalityCard.jsx
        └── services/api.js            # fetch wrappers (⚠ see Build Scope & Status)

pages/Memory/, pages/Sources/, pages/Settings/, components/memory/, components/settings/, components/dashboard/, backend/models/, backend/vectorDB/, and ai/embeddings//ai/chunking/ exist only as empty .gitkeep placeholders β€” intentionally unbuilt. Persistence, embeddings, and multi-session features are out of scope for this build.


πŸ“‘ API Reference

Base URL: http://localhost:3000. All routes mounted under /api, plus a health check.

GET /health β€” liveness check

Response 200

{ "status": "ok" }
POST /api/parse β€” raw chat text β†’ structured messages

Body

{ "rawText": "12/07/2025, 14:30 - Alice: Hey there!\n12/07/2025, 14:31 - Bob: Hi!" }

Response 200

{
  "messages": [
    { "timestamp": "12/07/2025, 14:30", "sender": "Alice", "text": "Hey there!" },
    { "timestamp": "12/07/2025, 14:31", "sender": "Bob", "text": "Hi!" }
  ],
  "senders": ["Alice", "Bob"]
}

Errors 400 β€” missing/empty rawText; or zero parseable messages ("Couldn't parse any messages β€” check the format").

POST /api/analyze β€” build reply pairs + style profile for one sender

Body

{ "messages": [ /* from /api/parse */ ], "targetSender": "Alice" }

Response 200

{
  "pairs": [{ "incoming": "what time works for you", "reply": "6pm works for me!" }],
  "styleProfile": {
    "averageWordCount": 4.2,
    "emojiUsagePercent": 38.5,
    "topEmojis": ["πŸ˜‚", "πŸ™"],
    "commonPhrases": ["lol", "fr", "no worries"],
    "capitalizationStyle": "lowercase",
    "punctuationStyle": "minimal"
  }
}

Errors 400 β€” missing/invalid messages or targetSender; or no reply pairs found for that sender.

POST /api/generate-reply β€” generate a styled reply via local Ollama

Body

{
  "newMessage": "are we still on for tonight?",
  "pairs": [ /* from /api/analyze */ ],
  "styleProfile": { /* from /api/analyze */ }
}

Response 200

{ "reply": "yeah 100% see you at 6 πŸ˜‚" }

Errors 400 β€” missing/invalid newMessage, pairs, or styleProfile. Errors 5xx β€” Ollama unreachable or timed out (20s abort β€” no cloud fallback).


βš™οΈ Installation & Setup

1. Repository Setup

git clone https://github.com/Prathvikmehra/evofox.git
cd evofox

2. Pull and Warm Up the Model

ollama pull llama3.2:3b
ollama run llama3.2:3b   # confirm it responds, then leave Ollama running

3. Backend

cd backend
npm install
npm run dev        # node --watch backend/src/app.js

Runs on http://localhost:3000 by default:

Variable Default
PORT 3000
OLLAMA_URL http://localhost:11434
OLLAMA_MODEL llama3.2:3b

4. Frontend

cd frontend
npm install
npm run dev

Vite dev server on http://localhost:5173.


πŸ§ͺ Testing

cd backend
node --test ai/tests/

Covers the parser (both clocks, both date orders, continuation lines, system-line tagging), the cleaner (structural __SYSTEM__ filtering vs. text-based noise filtering), and the style profiler (e.g. emoji usage is a presence-ratio per message, not a count-ratio).


🚧 Build Scope & Status

  • Frontend is currently mocked, not wired to the backend. Upload.jsx, Dashboard.jsx, and Chat.jsx use in-component mock data instead of live calls. The real routes above are implemented and working standalone.
  • frontend/src/services/api.js targets placeholder endpoints (/api/upload, /api/chat/:id, etc.) from an earlier design, and isn't imported anywhere yet. Rewrite it against the routes documented above as the next integration step.
  • backend/src/services/ and backend/src/utils/ are scaffolded but empty β€” controllers call directly into ai/ for now.

βœ… Demo Checklist

  • Ollama pulled, model tested, and already running/warmed up β€” no cloud fallback, so a cold start or crash has no backup.
  • Rehearse on the actual laptop and network you'll present with.
  • Have 2–3 backup chat exports ready in case a live paste has a weird format.
  • Someone who didn't build a given feature tests it cold, like a judge would.
  • One-sentence pitch memorized: who'd use this and why they'd value it.
  • Only your own / consenting teammates' chat data β€” never wired to real unsuspecting contacts.

πŸ‘¨β€πŸ’» Author

Prathvik Mehra

πŸ’¬ Same words, same voice β€” just running on your own machine.

Back to Top

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages