From a4e71322440d024d3a6e8c06c98e11ff1e32ede0 Mon Sep 17 00:00:00 2001 From: lotherk Date: Thu, 26 Mar 2026 20:03:52 +0000 Subject: [PATCH] feat: add theme system, branding, and task logging - Add light/dark/system theme toggle in settings - Add DearDiary.io branding in navbar - Add task logging for journal generation with request/response - Rename project from TotalRecall to DearDiary - Update Docker configuration --- CHANGELOG.md | 47 +++++++++++ Dockerfile | 46 ++++++++++ README.md | 2 + android/app/build.gradle.kts | 4 +- android/app/src/main/AndroidManifest.xml | 4 +- .../MainActivity.kt | 6 +- .../api/ApiClient.kt | 2 +- .../repository/Repository.kt | 4 +- .../ui/Navigation.kt | 14 ++-- .../{totalrecall => deardiary}/ui/Theme.kt | 2 +- .../ui/auth/AuthScreen.kt | 4 +- .../ui/history/HistoryScreen.kt | 4 +- .../ui/home/HomeScreen.kt | 6 +- .../ui/journal/JournalScreen.kt | 4 +- .../ui/settings/SettingsScreen.kt | 4 +- .../viewmodel/MainViewModel.kt | 8 +- android/app/src/main/res/values/strings.xml | 2 +- android/app/src/main/res/values/themes.xml | 2 +- docker-compose.yml | 24 ++++++ frontend/src/App.tsx | 15 +++- frontend/src/index.css | 72 +++++++++++++++- frontend/src/lib/ThemeContext.tsx | 83 +++++++++++++++++++ frontend/src/lib/api.ts | 25 +++++- frontend/src/main.tsx | 9 +- frontend/src/pages/Journal.tsx | 76 ++++++++++++++++- frontend/src/pages/Settings.tsx | 42 ++++++++++ frontend/tsconfig.tsbuildinfo | 2 +- nginx.conf | 21 +++++ 28 files changed, 487 insertions(+), 47 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 Dockerfile rename android/app/src/main/java/com/{totalrecall => deardiary}/MainActivity.kt (88%) rename android/app/src/main/java/com/{totalrecall => deardiary}/api/ApiClient.kt (99%) rename android/app/src/main/java/com/{totalrecall => deardiary}/repository/Repository.kt (98%) rename android/app/src/main/java/com/{totalrecall => deardiary}/ui/Navigation.kt (94%) rename android/app/src/main/java/com/{totalrecall => deardiary}/ui/Theme.kt (96%) rename android/app/src/main/java/com/{totalrecall => deardiary}/ui/auth/AuthScreen.kt (98%) rename android/app/src/main/java/com/{totalrecall => deardiary}/ui/history/HistoryScreen.kt (98%) rename android/app/src/main/java/com/{totalrecall => deardiary}/ui/home/HomeScreen.kt (98%) rename android/app/src/main/java/com/{totalrecall => deardiary}/ui/journal/JournalScreen.kt (98%) rename android/app/src/main/java/com/{totalrecall => deardiary}/ui/settings/SettingsScreen.kt (99%) rename android/app/src/main/java/com/{totalrecall => deardiary}/viewmodel/MainViewModel.kt (96%) create mode 100644 docker-compose.yml create mode 100644 frontend/src/lib/ThemeContext.tsx create mode 100644 nginx.conf diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9257fae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog + +All notable changes to DearDiary will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [Unreleased] + +### Added +- **Task System**: AI journal generation now creates tasks that track: + - Request sent to AI provider (full prompt + config) + - Response received from AI + - Status: pending, completed, failed + - Error messages if failed +- `Task` model in database for logging +- `GET /api/v1/journal/:date/tasks` endpoint +- `GET /api/v1/tasks/:id` endpoint +- **Theme System**: Light/Dark/System theme toggle +- **Branding**: "DearDiary.io" logo in navbar + +### Changed +- **Renamed project from "TotalRecall" to "DearDiary"** +- Journal generation now returns `{ journal, task }` on success +- Auth redirect now works properly (PrivateRoute component) +- Android app package: `com.totalrecall` → `com.deardiary` + +### Fixed +- Ollama support: properly routes to configured baseUrl +- Anthropic API integration + +## [0.1.0] - 2026-03-26 + +### Added +- User authentication (register/login) +- API key authentication for app access +- Entry CRUD (text, voice, health types) +- Day aggregation and history +- Journal generation with multiple AI providers: + - OpenAI (GPT-4) + - Anthropic (Claude) + - Ollama (local) + - LM Studio (local) +- Settings page for AI configuration +- React frontend with dark theme +- Native Android app (Kotlin/Compose) +- Docker deployment +- Prisma ORM with SQLite (extensible to PostgreSQL/MySQL) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..06b039a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# Multi-stage build: Backend + Frontend +FROM oven/bun:1.1-alpine AS backend-builder + +WORKDIR /app/backend +COPY backend/package*.json ./ +RUN bun install +COPY backend/prisma ./prisma +RUN bunx prisma generate +COPY backend/src ./src +RUN bun build src/index.ts --outdir ./dist --target bun + +FROM node:20-alpine AS frontend-builder + +WORKDIR /app/frontend +COPY frontend/package*.json ./ +RUN npm install +COPY frontend ./ +RUN npm run build + +FROM oven/bun:1.1-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs || true + +# Install nginx for serving frontend +RUN apk add --no-cache nginx + +# Copy backend +COPY --from=backend-builder /app/backend/dist ./dist +COPY --from=backend-builder /app/backend/node_modules ./node_modules +COPY backend/package.json . +COPY backend/prisma ./prisma + +# Copy frontend build +COPY --from=frontend-builder /app/frontend/dist ./public + +# Setup nginx +COPY nginx.conf /etc/nginx/http.d/default.conf + +RUN mkdir -p /data /run /app/nginx_tmp /var/lib/nginx/logs && chmod 777 /var/lib/nginx/logs /var/lib/nginx/tmp && chown -R bun:nodejs /data /app + +# Start everything as root +CMD sh -c "nginx -g 'daemon off;' & bunx prisma db push --accept-data-loss & bun ./dist/index.js" diff --git a/README.md b/README.md index d35f8ec..df2b1ae 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ AI-powered daily journal that captures life through multiple input methods and generates thoughtful, reflective journal entries. +See [CHANGELOG.md](./CHANGELOG.md) for detailed version history. + ## Features - **Multiple Input Types**: Text notes, photos, voice memos, health data diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 4affa6d..3696b85 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -4,11 +4,11 @@ plugins { } android { - namespace = "com.totalrecall" + namespace = "com.deardiary" compileSdk = 34 defaultConfig { - applicationId = "com.totalrecall" + applicationId = "com.deardiary" minSdk = 26 targetSdk = 34 versionCode = 1 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 08c4799..1a86e61 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -18,12 +18,12 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.TotalRecall" + android:theme="@style/Theme.DearDiary" android:usesCleartextTraffic="true"> + android:theme="@style/Theme.DearDiary"> diff --git a/android/app/src/main/java/com/totalrecall/MainActivity.kt b/android/app/src/main/java/com/deardiary/MainActivity.kt similarity index 88% rename from android/app/src/main/java/com/totalrecall/MainActivity.kt rename to android/app/src/main/java/com/deardiary/MainActivity.kt index fe03ac0..2d82b36 100644 --- a/android/app/src/main/java/com/totalrecall/MainActivity.kt +++ b/android/app/src/main/java/com/deardiary/MainActivity.kt @@ -1,4 +1,4 @@ -package com.totalrecall +package com.deardiary import android.os.Bundle import androidx.activity.ComponentActivity @@ -8,8 +8,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier -import com.totalrecall.ui.AppNavigation -import com.totalrecall.ui.TotalRecallTheme +import com.deardiary.ui.AppNavigation +import com.deardiary.ui.TotalRecallTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/android/app/src/main/java/com/totalrecall/api/ApiClient.kt b/android/app/src/main/java/com/deardiary/api/ApiClient.kt similarity index 99% rename from android/app/src/main/java/com/totalrecall/api/ApiClient.kt rename to android/app/src/main/java/com/deardiary/api/ApiClient.kt index 768236e..6e86e74 100644 --- a/android/app/src/main/java/com/totalrecall/api/ApiClient.kt +++ b/android/app/src/main/java/com/deardiary/api/ApiClient.kt @@ -1,4 +1,4 @@ -package com.totalrecall.api +package com.deardiary.api import com.google.gson.Gson import com.google.gson.annotations.SerializedName diff --git a/android/app/src/main/java/com/totalrecall/repository/Repository.kt b/android/app/src/main/java/com/deardiary/repository/Repository.kt similarity index 98% rename from android/app/src/main/java/com/totalrecall/repository/Repository.kt rename to android/app/src/main/java/com/deardiary/repository/Repository.kt index 5f7b9f7..9990e2c 100644 --- a/android/app/src/main/java/com/totalrecall/repository/Repository.kt +++ b/android/app/src/main/java/com/deardiary/repository/Repository.kt @@ -1,4 +1,4 @@ -package com.totalrecall.repository +package com.deardiary.repository import android.content.Context import androidx.datastore.core.DataStore @@ -6,7 +6,7 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import com.totalrecall.api.* +import com.deardiary.api.* private val Context.dataStore: DataStore by preferencesDataStore(name = "totalrecall") diff --git a/android/app/src/main/java/com/totalrecall/ui/Navigation.kt b/android/app/src/main/java/com/deardiary/ui/Navigation.kt similarity index 94% rename from android/app/src/main/java/com/totalrecall/ui/Navigation.kt rename to android/app/src/main/java/com/deardiary/ui/Navigation.kt index d6983b6..8023e5e 100644 --- a/android/app/src/main/java/com/totalrecall/ui/Navigation.kt +++ b/android/app/src/main/java/com/deardiary/ui/Navigation.kt @@ -1,4 +1,4 @@ -package com.totalrecall.ui +package com.deardiary.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -10,12 +10,12 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument -import com.totalrecall.ui.auth.AuthScreen -import com.totalrecall.ui.history.HistoryScreen -import com.totalrecall.ui.home.HomeScreen -import com.totalrecall.ui.journal.JournalScreen -import com.totalrecall.ui.settings.SettingsScreen -import com.totalrecall.viewmodel.MainViewModel +import com.deardiary.ui.auth.AuthScreen +import com.deardiary.ui.history.HistoryScreen +import com.deardiary.ui.home.HomeScreen +import com.deardiary.ui.journal.JournalScreen +import com.deardiary.ui.settings.SettingsScreen +import com.deardiary.viewmodel.MainViewModel import kotlinx.coroutines.launch sealed class Screen(val route: String) { diff --git a/android/app/src/main/java/com/totalrecall/ui/Theme.kt b/android/app/src/main/java/com/deardiary/ui/Theme.kt similarity index 96% rename from android/app/src/main/java/com/totalrecall/ui/Theme.kt rename to android/app/src/main/java/com/deardiary/ui/Theme.kt index e42d4f5..2581083 100644 --- a/android/app/src/main/java/com/totalrecall/ui/Theme.kt +++ b/android/app/src/main/java/com/deardiary/ui/Theme.kt @@ -1,4 +1,4 @@ -package com.totalrecall.ui +package com.deardiary.ui import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.* diff --git a/android/app/src/main/java/com/totalrecall/ui/auth/AuthScreen.kt b/android/app/src/main/java/com/deardiary/ui/auth/AuthScreen.kt similarity index 98% rename from android/app/src/main/java/com/totalrecall/ui/auth/AuthScreen.kt rename to android/app/src/main/java/com/deardiary/ui/auth/AuthScreen.kt index cac0044..80e5897 100644 --- a/android/app/src/main/java/com/totalrecall/ui/auth/AuthScreen.kt +++ b/android/app/src/main/java/com/deardiary/ui/auth/AuthScreen.kt @@ -1,4 +1,4 @@ -package com.totalrecall.ui.auth +package com.deardiary.ui.auth import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions @@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp -import com.totalrecall.viewmodel.UiState +import com.deardiary.viewmodel.UiState @Composable fun AuthScreen( diff --git a/android/app/src/main/java/com/totalrecall/ui/history/HistoryScreen.kt b/android/app/src/main/java/com/deardiary/ui/history/HistoryScreen.kt similarity index 98% rename from android/app/src/main/java/com/totalrecall/ui/history/HistoryScreen.kt rename to android/app/src/main/java/com/deardiary/ui/history/HistoryScreen.kt index be69958..e4dac55 100644 --- a/android/app/src/main/java/com/totalrecall/ui/history/HistoryScreen.kt +++ b/android/app/src/main/java/com/deardiary/ui/history/HistoryScreen.kt @@ -1,4 +1,4 @@ -package com.totalrecall.ui.history +package com.deardiary.ui.history import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -12,7 +12,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.totalrecall.api.DayInfo +import com.deardiary.api.DayInfo import java.text.SimpleDateFormat import java.util.* diff --git a/android/app/src/main/java/com/totalrecall/ui/home/HomeScreen.kt b/android/app/src/main/java/com/deardiary/ui/home/HomeScreen.kt similarity index 98% rename from android/app/src/main/java/com/totalrecall/ui/home/HomeScreen.kt rename to android/app/src/main/java/com/deardiary/ui/home/HomeScreen.kt index bd48467..c39b61b 100644 --- a/android/app/src/main/java/com/totalrecall/ui/home/HomeScreen.kt +++ b/android/app/src/main/java/com/deardiary/ui/home/HomeScreen.kt @@ -1,4 +1,4 @@ -package com.totalrecall.ui.home +package com.deardiary.ui.home import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -11,8 +11,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.totalrecall.api.DayResponse -import com.totalrecall.api.Entry +import com.deardiary.api.DayResponse +import com.deardiary.api.Entry import java.text.SimpleDateFormat import java.util.* diff --git a/android/app/src/main/java/com/totalrecall/ui/journal/JournalScreen.kt b/android/app/src/main/java/com/deardiary/ui/journal/JournalScreen.kt similarity index 98% rename from android/app/src/main/java/com/totalrecall/ui/journal/JournalScreen.kt rename to android/app/src/main/java/com/deardiary/ui/journal/JournalScreen.kt index d034c5c..ac38ce3 100644 --- a/android/app/src/main/java/com/totalrecall/ui/journal/JournalScreen.kt +++ b/android/app/src/main/java/com/deardiary/ui/journal/JournalScreen.kt @@ -1,4 +1,4 @@ -package com.totalrecall.ui.journal +package com.deardiary.ui.journal import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -12,7 +12,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.totalrecall.api.Journal +import com.deardiary.api.Journal import java.text.SimpleDateFormat import java.util.* diff --git a/android/app/src/main/java/com/totalrecall/ui/settings/SettingsScreen.kt b/android/app/src/main/java/com/deardiary/ui/settings/SettingsScreen.kt similarity index 99% rename from android/app/src/main/java/com/totalrecall/ui/settings/SettingsScreen.kt rename to android/app/src/main/java/com/deardiary/ui/settings/SettingsScreen.kt index a010326..f4835b8 100644 --- a/android/app/src/main/java/com/totalrecall/ui/settings/SettingsScreen.kt +++ b/android/app/src/main/java/com/deardiary/ui/settings/SettingsScreen.kt @@ -1,4 +1,4 @@ -package com.totalrecall.ui.settings +package com.deardiary.ui.settings import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp -import com.totalrecall.api.Settings +import com.deardiary.api.Settings @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/android/app/src/main/java/com/totalrecall/viewmodel/MainViewModel.kt b/android/app/src/main/java/com/deardiary/viewmodel/MainViewModel.kt similarity index 96% rename from android/app/src/main/java/com/totalrecall/viewmodel/MainViewModel.kt rename to android/app/src/main/java/com/deardiary/viewmodel/MainViewModel.kt index 2a024e7..dafb245 100644 --- a/android/app/src/main/java/com/totalrecall/viewmodel/MainViewModel.kt +++ b/android/app/src/main/java/com/deardiary/viewmodel/MainViewModel.kt @@ -1,11 +1,11 @@ -package com.totalrecall.viewmodel +package com.deardiary.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.totalrecall.BuildConfig -import com.totalrecall.api.* -import com.totalrecall.repository.Repository +import com.deardiary.BuildConfig +import com.deardiary.api.* +import com.deardiary.repository.Repository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index bd5ef91..8e6da9f 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - TotalRecall + DearDiary diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml index 02564f1..fa98e08 100644 --- a/android/app/src/main/res/values/themes.xml +++ b/android/app/src/main/res/values/themes.xml @@ -1,6 +1,6 @@ - diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..dab1c65 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + - "5173:80" + environment: + - DATABASE_URL=file:/data/deardiary.db + - MEDIA_DIR=/data/media + - JWT_SECRET=${JWT_SECRET:-change-me-in-production} + - PORT=3000 + - CORS_ORIGIN=${CORS_ORIGIN:-*} + volumes: + - ./data:/data + restart: unless-stopped + extra_hosts: + - "host.docker.internal:host-gateway" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ccc0888..5b5cfd1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -58,10 +58,17 @@ function App() {
{isAuthenticated && ( )} diff --git a/frontend/src/index.css b/frontend/src/index.css index b205a63..1b2f379 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,6 +2,74 @@ @tailwind components; @tailwind utilities; -body { - @apply bg-slate-950 text-slate-100; +:root { + --bg-primary: #020917; + --bg-secondary: #0f172a; + --bg-tertiary: #1e293b; + --text-primary: #e2e8f0; + --text-secondary: #94a3b8; + --border-color: #334155; +} + +.light { + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --bg-tertiary: #f1f5f9; + --text-primary: #1e293b; + --text-secondary: #64748b; + --border-color: #e2e8f0; +} + +body { + background-color: var(--bg-primary); + color: var(--text-primary); + transition: background-color 0.2s, color 0.2s; +} + +.bg-slate-950 { background-color: var(--bg-primary); } +.bg-slate-900 { background-color: var(--bg-secondary); } +.bg-slate-800 { background-color: var(--bg-tertiary); } +.bg-slate-700 { background-color: #475569; } + +.text-slate-100 { color: var(--text-primary); } +.text-slate-200 { color: var(--text-primary); } +.text-slate-300 { color: var(--text-primary); } +.text-slate-400 { color: var(--text-secondary); } +.text-slate-500 { color: var(--text-secondary); } + +.border-slate-800 { border-color: var(--border-color); } +.border-slate-700 { border-color: var(--border-color); } + +.light .bg-slate-900 { background-color: #f1f5f9; } +.light .bg-slate-800 { background-color: #e2e8f0; } +.light .bg-slate-700 { background-color: #cbd5e1; } + +.light .text-slate-300 { color: #475569; } +.light .text-slate-400 { color: #64748b; } + +.light .border-slate-800 { border-color: #e2e8f0; } + +.bg-white { background-color: var(--bg-primary); } +.bg-black { background-color: var(--bg-secondary); } + +.ring-offset-slate-950 { --tw-ring-offset-color: var(--bg-primary); } +.dark .ring-offset-slate-950 { --tw-ring-offset-color: #020917; } +.light .ring-offset-slate-950 { --tw-ring-offset-color: #ffffff; } + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); } diff --git a/frontend/src/lib/ThemeContext.tsx b/frontend/src/lib/ThemeContext.tsx new file mode 100644 index 0000000..6c86f7e --- /dev/null +++ b/frontend/src/lib/ThemeContext.tsx @@ -0,0 +1,83 @@ +import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +type Theme = 'light' | 'dark' | 'system'; +type ResolvedTheme = 'light' | 'dark'; + +interface ThemeContextType { + theme: Theme; + resolvedTheme: ResolvedTheme; + setTheme: (theme: Theme) => void; +} + +const ThemeContext = createContext({ + theme: 'system', + resolvedTheme: 'dark', + setTheme: () => {}, +}); + +export function useTheme() { + return useContext(ThemeContext); +} + +function getSystemTheme(): ResolvedTheme { + if (typeof window !== 'undefined' && window.matchMedia) { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + return 'dark'; +} + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setThemeState] = useState(() => { + if (typeof window !== 'undefined') { + return (localStorage.getItem('theme') as Theme) || 'system'; + } + return 'system'; + }); + + const [resolvedTheme, setResolvedTheme] = useState(() => { + if (theme === 'system') { + return getSystemTheme(); + } + return theme; + }); + + useEffect(() => { + const root = document.documentElement; + + if (theme === 'system') { + const systemTheme = getSystemTheme(); + setResolvedTheme(systemTheme); + root.classList.remove('light', 'dark'); + root.classList.add(systemTheme); + } else { + setResolvedTheme(theme); + root.classList.remove('light', 'dark'); + root.classList.add(theme); + } + }, [theme]); + + useEffect(() => { + if (theme === 'system') { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = (e: MediaQueryListEvent) => { + const newTheme = e.matches ? 'dark' : 'light'; + setResolvedTheme(newTheme); + document.documentElement.classList.remove('light', 'dark'); + document.documentElement.classList.add(newTheme); + }; + mediaQuery.addEventListener('change', handler); + return () => mediaQuery.removeEventListener('change', handler); + } + }, [theme]); + + const setTheme = (newTheme: Theme) => { + setThemeState(newTheme); + localStorage.setItem('theme', newTheme); + }; + + return ( + + {children} + + ); +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index f083fb2..2e28bff 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -126,13 +126,21 @@ class ApiClient { } async generateJournal(date: string) { - return this.request('POST', `/journal/generate/${date}`); + return this.request<{ journal: Journal; task: Task }>('POST', `/journal/generate/${date}`); } async getJournal(date: string) { return this.request('GET', `/journal/${date}`); } + async getJournalTasks(date: string) { + return this.request('GET', `/journal/${date}/tasks`); + } + + async getTask(taskId: string) { + return this.request('GET', `/tasks/${taskId}`); + } + async getSettings() { return this.request('GET', '/settings'); } @@ -160,6 +168,21 @@ export interface Journal { generatedAt: string; } +export interface Task { + id: string; + journalId: string; + type: string; + status: 'pending' | 'completed' | 'failed'; + provider: string; + model?: string; + prompt?: string; + request?: string; + response?: string; + error?: string; + createdAt: string; + completedAt?: string; +} + export interface Settings { aiProvider: 'openai' | 'anthropic' | 'ollama' | 'lmstudio'; aiApiKey?: string; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 9aa52ff..94d1881 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,17 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; +import { ThemeProvider } from './lib/ThemeContext'; import './index.css'; +const savedTheme = localStorage.getItem('theme') || 'system'; +const isDark = savedTheme === 'dark' || (savedTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); +document.documentElement.classList.add(isDark ? 'dark' : 'light'); + ReactDOM.createRoot(document.getElementById('root')!).render( - + + + , ); diff --git a/frontend/src/pages/Journal.tsx b/frontend/src/pages/Journal.tsx index 1d52182..46bfefc 100644 --- a/frontend/src/pages/Journal.tsx +++ b/frontend/src/pages/Journal.tsx @@ -1,15 +1,19 @@ import { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { api, Journal } from '../lib/api'; +import { api, Journal, Task } from '../lib/api'; export default function JournalPage() { const { date } = useParams<{ date: string }>(); const [journal, setJournal] = useState(null); + const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); const [generating, setGenerating] = useState(false); useEffect(() => { - if (date) loadJournal(); + if (date) { + loadJournal(); + loadTasks(); + } }, [date]); const loadJournal = async () => { @@ -22,12 +26,21 @@ export default function JournalPage() { setLoading(false); }; + const loadTasks = async () => { + if (!date) return; + const res = await api.getJournalTasks(date); + if (res.data) { + setTasks(res.data); + } + }; + const handleGenerate = async () => { if (!date) return; setGenerating(true); const res = await api.generateJournal(date); if (res.data) { - setJournal(res.data); + setJournal(res.data.journal); + setTasks(prev => [res.data!.task, ...prev]); } setGenerating(false); }; @@ -81,6 +94,63 @@ export default function JournalPage() {
)} + + {tasks.length > 0 && ( +
+

Generation History

+
+ {tasks.map(task => ( +
+ +
+ + + {task.provider.charAt(0).toUpperCase() + task.provider.slice(1)} + {task.model && ` - ${task.model}`} + + + {new Date(task.createdAt).toLocaleString()} + +
+ + {task.status} + +
+
+ {task.error && ( +
+

Error:

+
{task.error}
+
+ )} + {task.request && ( +
+

Request:

+
+                        {JSON.stringify(JSON.parse(task.request), null, 2)}
+                      
+
+ )} + {task.response && ( +
+

Response:

+
+                        {JSON.stringify(JSON.parse(task.response), null, 2)}
+                      
+
+ )} +
+
+ ))} +
+
+ )} ); } diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 8eca8d9..db65abe 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -1,11 +1,13 @@ import { useState, useEffect } from 'react'; import { api, Settings } from '../lib/api'; +import { useTheme } from '../lib/ThemeContext'; export default function SettingsPage() { const [settings, setSettings] = useState>({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); + const { theme, setTheme } = useTheme(); useEffect(() => { loadSettings(); @@ -47,6 +49,46 @@ export default function SettingsPage() {

Settings

+
+

Appearance

+ +
+ +
+ + + +
+
+
+

AI Provider

diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index 60ad2ae..b77ef49 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/EntryInput.tsx","./src/components/EntryList.tsx","./src/lib/api.ts","./src/pages/Auth.tsx","./src/pages/Day.tsx","./src/pages/History.tsx","./src/pages/Home.tsx","./src/pages/Journal.tsx","./src/pages/Settings.tsx"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/EntryInput.tsx","./src/components/EntryList.tsx","./src/lib/ThemeContext.tsx","./src/lib/api.ts","./src/pages/Auth.tsx","./src/pages/Day.tsx","./src/pages/History.tsx","./src/pages/Home.tsx","./src/pages/Journal.tsx","./src/pages/Settings.tsx"],"version":"5.9.3"} \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..13eb93c --- /dev/null +++ b/nginx.conf @@ -0,0 +1,21 @@ +server { + listen 80; + root /app/public; + index index.html; + + client_body_temp_path /app/nginx_tmp/client_body; + proxy_temp_path /app/nginx_tmp/proxy; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +}