Skip to content

JordanJamesJA/context-assistant

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Context Assistant

Context Assistant is a conversation tracking application that uses AI to automatically extract and categorize important information about people. It helps users remember key details from conversations by organizing them into structured, searchable categories.

Built with React + TypeScript (frontend) and Node.js + Express (backend), it demonstrates production-ready patterns including centralized state management, type-safe component architecture, and AI-powered data extraction.


Core Features

1. AI-Powered Extraction

  • Automatically categorizes conversation text into four types:
    • Interests: Hobbies, preferences, likes/dislikes
    • Important Dates: Birthdays, anniversaries, deadlines
    • Places: Locations visited, lived, or mentioned
    • Notes: General information that doesn't fit other categories
  • Uses Ollama (deepseek-r1:1.5b) for natural language processing
  • Pattern-based validation ensures accurate categorization

2. Person Management

  • Track multiple people with individual context profiles
  • Add/remove people dynamically
  • Switch between people to view their specific information
  • Visual item count per person for quick reference

3. Inline Editing

  • Click any item to edit directly
  • Keyboard shortcuts: Enter to save, Escape to cancel
  • Delete items with hover-triggered controls
  • All changes update immediately (optimistic UI)

4. Source Tracking

  • Every extracted item links back to its original conversation
  • Speaker attribution: See whether "You" or "Them" said something
  • Message timestamps for historical context
  • Hover over items to view source text

5. Responsive Design

  • Desktop: Sidebar always visible, spacious layout
  • Mobile: Swipe-to-open sidebar, hamburger menu
  • Touch-optimized UI with gesture support
  • Tailwind CSS v4 for consistent design system

High-Level Architecture

Technology Stack

Frontend:

  • React 19.2.0 + TypeScript
  • Vite for build tooling and dev server
  • Tailwind CSS v4 for styling
  • Lucide React for icons

Backend:

  • Node.js + Express 5.2.1
  • Ollama for AI extraction
  • TypeScript (ES modules)
  • CORS enabled for frontend communication

System Diagram

┌─────────────────────────────────────────────────────────────┐
│                      User Interface                          │
│  ┌────────────┐  ┌──────────────────┐  ┌─────────────────┐ │
│  │  Sidebar   │  │  PersonDetails   │  │ Conversation    │ │
│  │            │  │                  │  │ Input           │ │
│  │ • People   │  │ • Interests      │  │ • Speaker       │ │
│  │ • Add/Del  │  │ • Dates          │  │ • Text          │ │
│  │ • Select   │  │ • Places         │  │ • Submit        │ │
│  │            │  │ • Notes          │  │                 │ │
│  └────────────┘  └──────────────────┘  └─────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
                            │
                            ▼
                    ┌───────────────┐
                    │    App.tsx    │  ◄── Root Component
                    │               │      (State Owner)
                    │ State:        │
                    │ • people[]    │
                    │ • messages[]  │
                    │ • selectedId  │
                    └───────┬───────┘
                            │
                            ▼
                    HTTP POST /extract
                            │
                            ▼
┌───────────────────────────────────────────────────────────────┐
│                     Backend API                               │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │  POST /extract                                           │ │
│  │  1. Receive conversation text                           │ │
│  │  2. Send to Ollama AI                                   │ │
│  │  3. Validate & correct fact types                       │ │
│  │  4. Transform to frontend format                        │ │
│  │  5. Return categorized facts                            │ │
│  └─────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
                            │
                            ▼
                    ┌───────────────┐
                    │  Ollama AI    │
                    │ (deepseek-r1) │
                    └───────────────┘

State Management

Centralized State Pattern

All application state lives in the root component (App.tsx):

const [people, setPeople] = useState<Person[]>([]);
const [messages, setMessages] = useState<Message[]>([]);
const [selectedPersonId, setSelectedPersonId] = useState<string | null>(null);

Why this pattern:

  1. Single Source of Truth: All data lives in one place, making state predictable and debuggable
  2. Prop Drilling: State flows down via props, events flow up via callbacks
  3. No External Libraries: Demonstrates that React's built-in useState is sufficient for moderate complexity
  4. TypeScript Safety: Strongly typed props ensure compile-time error detection

Immutability

All state updates use immutable patterns (spread operator, .map(), .filter()):

// ✅ Correct: Creates new array
setPeople(prev => [...prev, newPerson]);

// ✅ Correct: Creates new person object with updated array
setPeople(prevPeople =>
  prevPeople.map(person =>
    person.id === selectedPersonId
      ? { ...person, interests: [...person.interests, newInterest] }
      : person
  )
);

// ❌ Wrong: Mutates existing state
people.push(newPerson);  // Never do this!

Why immutability:

  • React's reconciliation algorithm relies on reference equality checks
  • Immutable updates ensure React detects changes correctly
  • Prevents subtle bugs from shared object references
  • Makes debugging easier (state history is preserved)

Data Flow

Unidirectional Data Flow

Data flows down via props, events flow up via callbacks:

User Input (ConversationInput)
    │
    ▼
Event: onSubmit(text, speaker)
    │
    ▼
App.tsx: handleConversationSubmit()
    │
    ├─ 1. Create Message object
    ├─ 2. POST to /extract endpoint
    ├─ 3. Receive categorized facts
    ├─ 4. Transform to InfoItems
    └─ 5. Update people state
         │
         ▼
    setPeople() triggers re-render
         │
         ▼
PersonDetails receives updated person prop
         │
         ▼
EditableList components display new items

Example: Adding a Conversation

  1. User types: "They love hiking and their birthday is April 5"
  2. ConversationInput: Calls onSubmit(text, speaker) callback
  3. App.tsx: Creates Message object, stores in state
  4. App.tsx: Sends POST request to backend
  5. Backend: AI extracts facts, returns:
    {
      "interests": [{ "value": "loves hiking" }],
      "importantDates": [{ "value": "birthday is April 5" }],
      "places": [],
      "notes": []
    }
  6. App.tsx: Transforms facts into InfoItem[] with IDs and messageId
  7. App.tsx: Updates people state (immutably)
  8. React: Re-renders PersonDetails with new data
  9. User sees: New items appear in Interests and Important Dates sections

TypeScript Usage

Why TypeScript?

  1. Compile-Time Safety: Catch bugs before runtime
  2. Self-Documenting: Types serve as inline documentation
  3. Refactoring Confidence: IDE support for safe renames and refactors
  4. Component Contracts: Props interfaces define clear component APIs

Key Type Definitions

// Core domain types
export type Person = {
  id: string;
  name: string;
  interests: InfoItem[];
  importantDates: InfoItem[];
  places: InfoItem[];
  notes: InfoItem[];
};

export type InfoItem = {
  id: string;           // Unique ID for React keys
  value: string;        // The extracted fact
  messageId: string;    // Links back to source conversation
};

export type Message = {
  id: string;
  personId: string;     // Which person this is about
  text: string;         // Original conversation text
  speaker: Speaker;     // Who said it (USER or PERSON)
  timestamp: number;    // When it was said
};

export const Speaker = {
  USER: 'user',    // The logged-in user
  PERSON: 'person' // The person being tracked
} as const;

Component Props Pattern

type PersonDetailsProps = {
  person: Person | null;
  onUpdateInterest: (itemId: string, newValue: string) => void;
  onDeleteInterest: (itemId: string) => void;
  // ... other category handlers
  getMessageById: (messageId: string) => Message | undefined;
};

This pattern ensures:

  • Components can't receive invalid props (TypeScript error)
  • IDE autocomplete shows available props
  • Refactoring is safe (rename propagates everywhere)

Project Structure

context-assistant/
│
├── frontend/
│   ├── src/
│   │   ├── main.tsx                  # React app entry point
│   │   ├── App.tsx                   # Root component (state owner)
│   │   ├── index.css                 # Global styles + Tailwind
│   │   │
│   │   ├── components/
│   │   │   ├── Sidebar.tsx           # People list + add/remove
│   │   │   ├── PersonDetails.tsx     # Main content area
│   │   │   ├── EditableList.tsx      # Reusable collapsible list
│   │   │   └── ConversationInput.tsx # Message input + extraction
│   │   │
│   │   └── types/
│   │       └── person.ts             # TypeScript type definitions
│   │
│   ├── public/
│   ├── package.json
│   ├── vite.config.ts
│   ├── tailwind.config.js
│   └── tsconfig.json
│
├── backend/
│   ├── src/
│   │   ├── index.ts                  # Express server
│   │   │
│   │   ├── services/
│   │   │   └── ai.service.ts         # AI extraction logic
│   │   │
│   │   └── types/
│   │       └── ai.types.ts           # Backend type definitions
│   │
│   ├── package.json
│   ├── tsconfig.json
│   └── nodemon.json
│
└── README.md

Component Hierarchy

App (Root)
├── Sidebar
│   └── Person list items
│       └── Add person form (inline)
│
└── Main Content
    ├── PersonDetails
    │   ├── EditableList (Interests)
    │   ├── EditableList (Important Dates)
    │   ├── EditableList (Places)
    │   └── EditableList (Notes)
    │
    └── ConversationInput
        ├── Speaker toggle
        ├── Textarea (auto-resize)
        └── Submit button

Getting Started

Prerequisites

  • Node.js 18+ (for both frontend and backend)
  • Ollama installed and running (ollama.ai)
  • deepseek-r1:1.5b model pulled: ollama pull deepseek-r1:1.5b

Installation

1. Clone the repository

git clone <repository-url>
cd context-assistant

2. Install backend dependencies

cd backend
npm install

3. Install frontend dependencies

cd ../frontend
npm install

Running the Application

1. Start Ollama (if not already running)

ollama serve

2. Start the backend (runs on http://localhost:5000)

cd backend
npm run dev

3. Start the frontend (runs on http://localhost:5173)

cd frontend
npm run dev

4. Open your browser

http://localhost:5173

Usage Flow

  1. Add a person: Click the + button in the sidebar
  2. Select the person: Click on their name
  3. Add a conversation: Type what was said (e.g., "They love hiking and their birthday is April 5")
  4. Toggle speaker: Click the speaker badge to indicate who said it
  5. Submit: Click "Extract" or press Enter
  6. View results: Facts appear categorized in the main content area
  7. Edit: Click any item to edit it inline
  8. Delete: Hover and click the trash icon

Architecture Decisions

Why Prop Drilling Over Context API?

Prop drilling was chosen over Context API because:

  • Explicit data flow: Easy to trace where props come from
  • Better performance: No unnecessary re-renders from context changes
  • Simpler debugging: Chrome DevTools shows full prop chain
  • Small component tree: Only 3 levels deep (App → PersonDetails → EditableList)

When to use Context: If the app grows to 5+ levels or requires deeply nested shared state (theme, auth), Context API would be appropriate.

Why useState Over Redux/Zustand?

useState is sufficient because:

  • Moderate complexity: 3 state variables, predictable updates
  • No async middleware needed: Backend calls are simple fetch requests
  • Fast development: No boilerplate, no extra dependencies
  • TypeScript coverage: Full type safety without redux-toolkit complexity

When to use Redux: If the app grows to require:

  • Time-travel debugging
  • Complex async orchestration (sagas, thunks)
  • State persistence across sessions
  • Shared state across disconnected components

Why TypeScript?

TypeScript provides:

  • Compile-time safety: Catch undefined is not a function before deployment
  • Refactoring confidence: Rename a prop, see all usage sites update
  • Self-documentation: Props interfaces explain component APIs
  • Developer experience: IDE autocomplete, inline errors

Trade-off: Slightly slower development initially, but faster debugging and maintenance long-term.

Why Tailwind CSS?

Tailwind CSS v4 was chosen for:

  • Consistency: Design tokens enforced via utility classes
  • Speed: No context switching between files
  • Maintainability: Changes are localized (no cascading CSS bugs)
  • Small bundle: PurgeCSS removes unused styles automatically

How This Could Scale

Short-Term Scalability (100-1,000 users)

Current bottlenecks:

  • All data is in-memory (resets on page refresh)
  • No user authentication
  • Single Ollama instance (sequential processing)

Incremental improvements:

  1. Add persistence: Use localStorage or IndexedDB for client-side storage

    • Benefit: Data survives page refresh
    • Implementation: Sync state to storage on every update
  2. Add authentication: Integrate with Auth0, Clerk, or Firebase Auth

    • Benefit: Multiple users can track different people
    • Implementation: Add auth provider, protect routes
  3. Optimize AI calls: Debounce extraction, batch multiple messages

    • Benefit: Reduce API calls, faster response
    • Implementation: Add 500ms debounce, queue system

Medium-Term Scalability (1,000-10,000 users)

Architecture changes needed:

  1. Backend database: PostgreSQL or MongoDB

    • Store people, messages, items in database
    • Add user accounts with foreign keys
    • Implement server-side pagination
  2. State management migration: Move to Zustand or Redux

    • Keep frequently-accessed data in global state
    • Lazy-load historical messages
    • Add optimistic updates with rollback
  3. AI infrastructure: Deploy dedicated AI server

    • Separate Ollama instance on GPU server
    • Add request queue (Redis, RabbitMQ)
    • Implement caching for common extractions
  4. API layer: Add GraphQL or tRPC

    • Reduce over-fetching (only send needed fields)
    • Batch related queries
    • Type-safe API contracts

Long-Term Scalability (10,000+ users)

Full production architecture:

┌────────────────────────────────────────────────────────┐
│                   Load Balancer (Nginx)                │
└──────────────────────┬─────────────────────────────────┘
                       │
        ┌──────────────┴──────────────┐
        │                             │
┌───────▼────────┐          ┌─────────▼────────┐
│  Web Server 1  │          │  Web Server 2    │
│  (Next.js SSR) │          │  (Next.js SSR)   │
└───────┬────────┘          └─────────┬────────┘
        │                             │
        └──────────────┬──────────────┘
                       │
           ┌───────────▼───────────┐
           │   API Gateway (Kong)  │
           └───────────┬───────────┘
                       │
        ┌──────────────┴──────────────┐
        │                             │
┌───────▼────────┐          ┌─────────▼────────┐
│  API Server 1  │          │  API Server 2    │
│  (Express)     │          │  (Express)       │
└───────┬────────┘          └─────────┬────────┘
        │                             │
        └──────────────┬──────────────┘
                       │
        ┌──────────────┴──────────────┐
        │                             │
┌───────▼────────┐          ┌─────────▼────────┐
│  PostgreSQL    │          │  Redis Cache     │
│  (Primary)     │          │  (Sessions)      │
└────────────────┘          └──────────────────┘
        │
┌───────▼────────┐
│  PostgreSQL    │
│  (Replica)     │
└────────────────┘

        AI Processing Layer
        ┌──────────────┐
        │ Queue (Redis)│
        └──────┬───────┘
               │
    ┌──────────┴──────────┐
    │                     │
┌───▼─────┐       ┌───────▼────┐
│ Worker 1│       │  Worker 2  │
│ (Ollama)│       │  (Ollama)  │
└─────────┘       └────────────┘

Key additions:

  1. Horizontal scaling: Multiple API servers behind load balancer
  2. Database replication: Read replicas for query performance
  3. Caching layer: Redis for sessions, frequently-accessed data
  4. Queue system: Asynchronous AI processing with workers
  5. CDN: CloudFlare for static assets, global edge caching
  6. Monitoring: Datadog/Sentry for error tracking, performance metrics
  7. Search: Elasticsearch for full-text search across all conversations

Feature Scalability

Potential feature additions:

  • Export/Import: Download person data as JSON/CSV
  • Advanced search: Full-text search across all messages
  • Analytics: Visualize conversation trends over time
  • Collaboration: Share person profiles with team members
  • Mobile app: React Native version with offline-first sync
  • Voice input: Speech-to-text for hands-free conversation logging
  • Smart reminders: Notify before important dates
  • AI insights: Summarize person's profile, suggest conversation topics

Development Commands

Frontend

npm run dev      # Start development server (http://localhost:5173)
npm run build    # Production build (outputs to dist/)
npm run preview  # Preview production build locally
npm run lint     # Run ESLint for code quality

Backend

npm run dev      # Start with nodemon (auto-restart on changes)
npm run build    # Compile TypeScript to JavaScript (outputs to dist/)
npm start        # Run production build (node dist/index.js)

Design Philosophy

This project demonstrates production-ready practices without over-engineering:

Type safety via TypeScript (catch bugs at compile time) ✅ Immutable updates for predictable state management ✅ Component reusability (EditableList used for all 4 categories) ✅ Separation of concerns (UI components vs. business logic) ✅ Defensive programming (validate API responses, handle edge cases) ✅ Accessibility (keyboard shortcuts, semantic HTML) ✅ Responsive design (mobile-first Tailwind utilities)

No premature optimization (no memoization until needed) ❌ No unnecessary abstraction (simple is better than clever) ❌ No external state libraries (useState is sufficient) ❌ No backend framework (Express is lightweight and flexible)


Contributing

This is a demonstration project. For educational purposes, feel free to:

  • Experiment with different AI models (llama3, gpt-4, etc.)
  • Add new extraction categories (skills, preferences, habits)
  • Implement data persistence (localStorage, IndexedDB, database)
  • Improve UI/UX (animations, drag-and-drop, themes)

License

This project is provided as-is for educational and demonstration purposes.


Questions?

Common questions:

Q: Why not use a database? A: This is a frontend-focused demo. Adding a database would shift focus to backend CRUD operations. The in-memory approach demonstrates React state management clearly.

Q: Why not use Context API? A: Prop drilling is explicit and performs well for small component trees. Context adds indirection that makes debugging harder.

Q: Is this production-ready? A: The code quality is production-ready (types, patterns, documentation). The architecture would need persistence, auth, and hosting for actual production use.

Q: How accurate is the AI extraction? A: The deepseek-r1:1.5b model is ~85% accurate. Pattern-based validation catches most misclassifications. For production, consider GPT-4 or Claude for higher accuracy.


Built with attention to detail. Ready for code review.

About

Context Assistant is a React + TypeScript web app that helps users track conversations with multiple people by intelligently extracting and organizing key information such as interests, important dates, places, and notes.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors