445 lines
14 KiB
Markdown
445 lines
14 KiB
Markdown
# TotalRecall - AI-Powered Daily Journal
|
|
|
|
> Your day, analyzed. A journal that writes itself.
|
|
|
|
---
|
|
|
|
## Concept
|
|
|
|
TotalRecall is a privacy-first, self-hostable daily journal application that captures life through multiple input methods (text, photos, voice, data) and uses AI to generate thoughtful, reflective journal entries at the end of each day.
|
|
|
|
The core philosophy: **your data is yours**. Whether you host it yourself or use a hosted service, you own everything. The same codebase runs everywhere.
|
|
|
|
---
|
|
|
|
## The Problem
|
|
|
|
- Most journaling apps are siloed, paid, or data-mining platforms
|
|
- Manual journaling is time-consuming and inconsistent
|
|
- People capture moments (photos, voice memos, scattered notes) but never reflect on them
|
|
- AI tools exist but require manual input of context
|
|
|
|
## The Solution
|
|
|
|
TotalRecall aggregates all your daily inputs throughout the day, then at your command (or scheduled), sends everything to an AI to synthesize your day into a coherent, reflective journal entry.
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
### Input Capture
|
|
|
|
| Type | Description | Storage |
|
|
|------|-------------|---------|
|
|
| **Text Notes** | Quick thoughts, feelings, observations | Markdown |
|
|
| **Photos** | Camera or gallery uploads | JPEG/PNG in media folder |
|
|
| **Voice Memos** | Audio recordings | WebM/Opus |
|
|
| **Location** | Optional GPS tagging of entries | Lat/long metadata |
|
|
| **Health Data** | Manual entry (steps, mood, sleep) | JSON metadata |
|
|
|
|
### AI Journal Generation
|
|
|
|
- Aggregates all entries for a day
|
|
- Sends to configurable AI provider
|
|
- Generates reflective, narrative journal entry
|
|
- Stores generated journal alongside raw entries
|
|
|
|
### AI Provider Support (Pluggable)
|
|
|
|
- **OpenAI** - GPT-4, GPT-4-Turbo, GPT-3.5
|
|
- **Anthropic** - Claude 3 Opus, Sonnet, Haiku
|
|
- **Ollama** - Local LLM (Llama, Mistral, etc.)
|
|
- **LM Studio** - Local LLM with OpenAI-compatible API
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ End User │
|
|
│ │
|
|
│ ┌────────────────────┐ ┌────────────────────┐ │
|
|
│ │ Web App │ │ Mobile App │ │
|
|
│ │ (React/PWA) │ │ (Future: iOS) │ │
|
|
│ └─────────┬──────────┘ └─────────┬──────────┘ │
|
|
│ │ │ │
|
|
│ └──────────┬─────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────┐ │
|
|
│ │ REST API │ │
|
|
│ │ /api/v1/* │ │
|
|
│ │ │ │
|
|
│ │ - Authentication │ │
|
|
│ │ - Entries CRUD │ │
|
|
│ │ - Journal Gen │ │
|
|
│ │ - Settings │ │
|
|
│ └──────────┬───────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────┐ │
|
|
│ │ SQLite Database │ │
|
|
│ │ + Media Storage │ │
|
|
│ └──────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Tech Stack
|
|
|
|
| Layer | Technology | Reason |
|
|
|-------|------------|--------|
|
|
| Backend Runtime | **Bun** | Fast startup, native TypeScript, small Docker image |
|
|
| API Framework | **Hono** | Lightweight, fast, works everywhere |
|
|
| Database | **SQLite / MySQL / PostgreSQL** (via Prisma ORM) | Switch databases with one line, zero-config for SQLite |
|
|
| Frontend | **React + Vite** | Fast dev, PWA capable |
|
|
| Styling | **Tailwind CSS** | Rapid UI development |
|
|
| Container | **Docker + Docker Compose** | One-command deployment |
|
|
|
|
---
|
|
|
|
## Deployment Modes
|
|
|
|
TotalRecall runs the same codebase whether self-hosted or on a SaaS platform.
|
|
|
|
### Single-User (Self-Hosted)
|
|
|
|
```yaml
|
|
services:
|
|
totalrecall:
|
|
image: ghcr.io/totalrecall/totalrecall:latest
|
|
ports:
|
|
- "3000:3000"
|
|
volumes:
|
|
- ./data:/data
|
|
```
|
|
|
|
- Users create accounts locally
|
|
- No external dependencies
|
|
- One Docker command to run
|
|
|
|
### Multi-Tenant (SaaS)
|
|
|
|
```yaml
|
|
services:
|
|
totalrecall:
|
|
image: ghcr.io/totalrecall/totalrecall:latest
|
|
ports:
|
|
- "3000:3000"
|
|
environment:
|
|
- DEPLOYMENT_MODE=multi-tenant
|
|
- JWT_SECRET=${JWT_SECRET}
|
|
- DATABASE_URL=file:/data/totalrecall.db
|
|
volumes:
|
|
- ./data:/data
|
|
```
|
|
|
|
- Multiple users with accounts
|
|
- API key authentication
|
|
- Same features as self-hosted
|
|
|
|
---
|
|
|
|
## API Design
|
|
|
|
### Authentication
|
|
|
|
```
|
|
Authorization: Bearer {api_key}
|
|
```
|
|
|
|
All requests (except auth endpoints) require a valid API key.
|
|
|
|
### Endpoints
|
|
|
|
```
|
|
POST /api/v1/auth/register # Create account
|
|
POST /api/v1/auth/login # Get API key
|
|
POST /api/v1/auth/logout # Invalidate session
|
|
|
|
GET /api/v1/days # List days with entries
|
|
GET /api/v1/days/:date # Get day's data (entries + journal)
|
|
DELETE /api/v1/days/:date # Delete day and all entries
|
|
|
|
POST /api/v1/entries # Create entry
|
|
GET /api/v1/entries/:id # Get entry
|
|
PUT /api/v1/entries/:id # Update entry
|
|
DELETE /api/v1/entries/:id # Delete entry
|
|
|
|
POST /api/v1/entries/:id/photo # Upload photo to entry
|
|
POST /api/v1/entries/:id/voice # Upload voice to entry
|
|
|
|
POST /api/v1/journal/generate/:date # Generate AI journal for date
|
|
GET /api/v1/journal/:date # Get generated journal
|
|
|
|
GET /api/v1/settings # Get user settings
|
|
PUT /api/v1/settings # Update settings (AI provider, etc.)
|
|
|
|
GET /api/v1/health # Health check (no auth)
|
|
```
|
|
|
|
### Response Format
|
|
|
|
```typescript
|
|
// Success
|
|
{ "data": { ... }, "error": null }
|
|
|
|
// Error
|
|
{ "data": null, "error": { "code": "NOT_FOUND", "message": "Entry not found" } }
|
|
```
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
### User
|
|
|
|
```typescript
|
|
interface User {
|
|
id: string; // UUID
|
|
email: string; // Unique
|
|
passwordHash: string; // bcrypt
|
|
createdAt: string; // ISO timestamp
|
|
}
|
|
```
|
|
|
|
### API Key
|
|
|
|
```typescript
|
|
interface ApiKey {
|
|
id: string;
|
|
userId: string;
|
|
keyHash: string; // SHA-256 hash, not stored plaintext
|
|
name: string; // "iPhone", "Web", etc.
|
|
lastUsedAt: string;
|
|
createdAt: string;
|
|
}
|
|
```
|
|
|
|
### Entry
|
|
|
|
```typescript
|
|
type EntryType = 'text' | 'voice' | 'photo' | 'health' | 'location';
|
|
|
|
interface Entry {
|
|
id: string;
|
|
userId: string;
|
|
date: string; // YYYY-MM-DD
|
|
type: EntryType;
|
|
content: string; // Text or metadata JSON
|
|
mediaPath?: string; // Path to uploaded file
|
|
metadata?: {
|
|
source?: 'manual' | 'health' | 'calendar';
|
|
location?: { lat: number; lng: number };
|
|
duration?: number; // For voice entries
|
|
[key: string]: unknown;
|
|
};
|
|
createdAt: string;
|
|
}
|
|
```
|
|
|
|
### Journal
|
|
|
|
```typescript
|
|
interface Journal {
|
|
id: string;
|
|
userId: string;
|
|
date: string; // YYYY-MM-DD
|
|
content: string; // Markdown
|
|
entryCount: number; // How many entries were used
|
|
generatedAt: string;
|
|
}
|
|
```
|
|
|
|
### Settings
|
|
|
|
```typescript
|
|
interface Settings {
|
|
userId: string;
|
|
aiProvider: 'openai' | 'anthropic' | 'ollama' | 'lmstudio';
|
|
aiConfig: {
|
|
// Provider-specific config (API keys stored encrypted)
|
|
model?: string;
|
|
baseUrl?: string; // For Ollama/LM Studio
|
|
};
|
|
journalPrompt?: string;
|
|
language: string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## File Storage
|
|
|
|
```
|
|
/data/
|
|
├── totalrecall.db # SQLite database
|
|
└── media/
|
|
└── {user_id}/
|
|
└── {date}/
|
|
├── entry-{uuid}.webm # Voice recordings
|
|
└── entry-{uuid}.jpg # Photos
|
|
```
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
totalrecall/
|
|
├── backend/
|
|
│ ├── src/
|
|
│ │ ├── index.ts # Entry point
|
|
│ │ ├── app.ts # Hono app setup
|
|
│ │ ├── routes/
|
|
│ │ │ ├── auth.ts # Registration, login
|
|
│ │ │ ├── days.ts # Day operations
|
|
│ │ │ ├── entries.ts # Entry CRUD
|
|
│ │ │ ├── journal.ts # Journal generation
|
|
│ │ │ └── settings.ts # User settings
|
|
│ │ ├── services/
|
|
│ │ │ ├── db.ts # Database connection
|
|
│ │ │ ├── storage.ts # File storage operations
|
|
│ │ │ └── ai/
|
|
│ │ │ ├── mod.ts # Provider interface
|
|
│ │ │ ├── openai.ts
|
|
│ │ │ ├── anthropic.ts
|
|
│ │ │ └── ollama.ts
|
|
│ │ ├── middleware/
|
|
│ │ │ └── auth.ts # API key validation
|
|
│ │ └── types.ts
|
|
│ ├── prisma/
|
|
│ │ └── schema.prisma # Database schema
|
|
│ ├── Dockerfile
|
|
│ └── package.json
|
|
│
|
|
├── frontend/
|
|
│ ├── src/
|
|
│ │ ├── main.tsx
|
|
│ │ ├── App.tsx
|
|
│ │ ├── pages/
|
|
│ │ │ ├── Home.tsx # Today's view
|
|
│ │ │ ├── History.tsx # Browse past days
|
|
│ │ │ ├── Journal.tsx # View generated journals
|
|
│ │ │ └── Settings.tsx # Configuration
|
|
│ │ ├── components/
|
|
│ │ │ ├── EntryInput.tsx
|
|
│ │ │ ├── PhotoCapture.tsx
|
|
│ │ │ ├── VoiceRecorder.tsx
|
|
│ │ │ ├── EntryList.tsx
|
|
│ │ │ └── JournalView.tsx
|
|
│ │ ├── lib/
|
|
│ │ │ └── api.ts # API client
|
|
│ │ └── hooks/
|
|
│ ├── Dockerfile
|
|
│ └── package.json
|
|
│
|
|
├── docker-compose.yml # Full stack
|
|
├── docker-compose.prod.yml # Production overrides
|
|
├── Dockerfile # Multi-stage build
|
|
└── README.md
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1: Backend Foundation ✅
|
|
- [x] Bun + Hono setup
|
|
- [x] Prisma ORM with SQLite (dev) / PostgreSQL (prod)
|
|
- [x] User registration & login (JWT)
|
|
- [x] API key authentication
|
|
- [x] Basic CRUD routes
|
|
|
|
### Phase 2: Data Management ✅
|
|
- [x] Entry CRUD with media support
|
|
- [x] File upload handling (photos, voice)
|
|
- [x] Day aggregation queries
|
|
- [x] Media file storage
|
|
|
|
### Phase 3: AI Integration ✅
|
|
- [x] AI provider interface
|
|
- [x] OpenAI implementation
|
|
- [x] Anthropic implementation
|
|
- [x] Ollama implementation
|
|
- [x] Journal generation endpoint
|
|
|
|
### Phase 4: Frontend Core ✅
|
|
- [x] React + Vite setup
|
|
- [x] API client library
|
|
- [x] Authentication flow
|
|
- [x] Home page (today's entries)
|
|
- [x] Entry creation (text input)
|
|
|
|
### Phase 5: Media Inputs ✅
|
|
- [x] Photo capture/upload
|
|
- [x] Voice recording
|
|
- [x] Entry list with media preview
|
|
|
|
### Phase 6: Journal & Settings ✅
|
|
- [x] Journal viewing
|
|
- [x] Settings page
|
|
- [x] AI provider configuration
|
|
- [x] Prompt customization
|
|
|
|
### Phase 7: Deployment ✅
|
|
- [x] Docker multi-stage build
|
|
- [x] Docker Compose setup
|
|
- [x] Health checks
|
|
- [ ] PWA manifest
|
|
|
|
### Phase 8: Future
|
|
- [ ] iOS app (SwiftUI)
|
|
- [ ] Android app (Kotlin)
|
|
- [ ] Calendar integration
|
|
- [ ] Health data sync
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
- API keys hashed with SHA-256 (never stored plaintext)
|
|
- Passwords hashed with bcrypt
|
|
- CORS configurable for domain restriction
|
|
- Rate limiting on auth endpoints
|
|
- File upload validation (type, size)
|
|
- SQL injection prevention via ORM
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `DATABASE_URL` | `file:./data/totalrecall.db` | SQLite/PostgreSQL/MySQL connection |
|
|
| `MEDIA_DIR` | `./data/media` | Directory for uploaded files |
|
|
| `JWT_SECRET` | (required) | Secret for JWT signing |
|
|
| `PORT` | `3000` | Server port |
|
|
| `CORS_ORIGIN` | `*` | Allowed origins |
|
|
| `RATE_LIMIT` | `100/hour` | Auth endpoint rate limit |
|
|
|
|
### Database Connection Examples
|
|
|
|
```bash
|
|
# SQLite (development, default)
|
|
DATABASE_URL="file:./data/totalrecall.db"
|
|
|
|
# PostgreSQL (production)
|
|
DATABASE_URL="postgresql://user:password@localhost:5432/totalrecall"
|
|
|
|
# MySQL
|
|
DATABASE_URL="mysql://user:password@localhost:3306/totalrecall"
|
|
```
|
|
|
|
---
|
|
|
|
## Licensing
|
|
|
|
This project is open source under [MIT License](LICENSE). You are free to:
|
|
- Use it for yourself
|
|
- Host it for others
|
|
- Modify and redistribute
|
|
|
|
No attribution required (but appreciated).
|