feat: replace Starlight docs with simple HTML product website

- Removed docs/ (Starlight)
- Added www/ with pure HTML/CSS/JS landing page
- Product website with features, how it works, get started
- Basic HTML documentation pages (installation, quick start, AI providers)
- Dockerfile.docs now serves static files from www/
- Updated README and AGENTS.md
This commit is contained in:
lotherk
2026-03-27 10:42:22 +00:00
parent f414161fd8
commit 36474db276
40 changed files with 1193 additions and 9682 deletions

View File

@@ -1,17 +1,6 @@
FROM node:20-alpine AS builder
WORKDIR /app/docs
COPY docs/package*.json ./
RUN npm install
COPY docs ./
RUN npm run build
FROM nginx:alpine FROM nginx:alpine
COPY --from=builder /app/docs/dist /usr/share/nginx/html COPY www/ /usr/share/nginx/html/
COPY docs/nginx.conf /etc/nginx/http.d/default.conf
EXPOSE 80 EXPOSE 80

View File

@@ -3,7 +3,6 @@
Self-hosted AI-powered daily journaling application. Capture events throughout the day and let AI generate thoughtful diary pages from your entries. Self-hosted AI-powered daily journaling application. Capture events throughout the day and let AI generate thoughtful diary pages from your entries.
[![Deploy to Docker](https://img.shields.io/badge/Deploy-Docker-blue)](https://github.com/lotherk/deardiary) [![Deploy to Docker](https://img.shields.io/badge/Deploy-Docker-blue)](https://github.com/lotherk/deardiary)
[![Documentation](https://img.shields.io/badge/Documentation-Live-brightgreen)](https://lotherk.github.io/deardiary)
## Features ## Features
@@ -33,21 +32,19 @@ cd deardiary
docker compose up -d docker compose up -d
``` ```
Access the app at `http://localhost:8080` Access the app at `http://localhost:5173`
Default credentials: `admin@localhost` / `changeme123` Default credentials: `admin@localhost` / `changeme123`
## Documentation ## Documentation
Full documentation is available at [https://lotherk.github.io/deardiary](https://lotherk.github.io/deardiary) Documentation is included in the `www/` directory. Run the website container:
Topics covered: ```bash
- [Installation](https://lotherk.github.io/deardiary/getting-started/installation/) docker compose up -d docs
- [Quick Start](https://lotherk.github.io/deardiary/getting-started/quick-start/) ```
- [Configuration](https://lotherk.github.io/deardiary/getting-started/configuration/)
- [Features](https://lotherk.github.io/deardiary/features/events/) Access at `http://localhost:4000`
- [API Reference](https://lotherk.github.io/deardiary/api/authentication/)
- [Deployment](https://lotherk.github.io/deardiary/deployment/docker/)
## Configuration ## Configuration
@@ -81,14 +78,6 @@ npm install
npm run dev npm run dev
``` ```
### Build Docs Locally
```bash
cd docs
npm install
npm run dev
```
### Docker Build ### Docker Build
```bash ```bash
@@ -106,7 +95,9 @@ docker compose build && docker compose up -d
│ ├── pages/ # Page components │ ├── pages/ # Page components
│ ├── components/ │ ├── components/
│ └── lib/ # API client, geolocation │ └── lib/ # API client, geolocation
├── docs/ # Starlight documentation site ├── www/ # Product website with docs
├── Dockerfile # App container
├── Dockerfile.docs # Website container
└── docker-compose.yml └── docker-compose.yml
``` ```

View File

@@ -1,643 +0,0 @@
{
"$ref": "#/definitions/docs",
"definitions": {
"docs": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"editUrl": {
"anyOf": [
{
"type": "string",
"format": "uri"
},
{
"type": "boolean"
}
],
"default": true
},
"head": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tag": {
"type": "string",
"enum": [
"title",
"base",
"link",
"style",
"meta",
"script",
"noscript",
"template"
]
},
"attrs": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "boolean"
},
{
"not": {}
}
]
}
},
"content": {
"type": "string"
}
},
"required": [
"tag"
],
"additionalProperties": false
},
"default": []
},
"tableOfContents": {
"anyOf": [
{
"type": "object",
"properties": {
"minHeadingLevel": {
"type": "integer",
"minimum": 1,
"maximum": 6,
"default": 2
},
"maxHeadingLevel": {
"type": "integer",
"minimum": 1,
"maximum": 6,
"default": 3
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
],
"default": {
"minHeadingLevel": 2,
"maxHeadingLevel": 3
}
},
"template": {
"type": "string",
"enum": [
"doc",
"splash"
],
"default": "doc"
},
"hero": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"tagline": {
"type": "string"
},
"image": {
"anyOf": [
{
"type": "object",
"properties": {
"alt": {
"type": "string",
"default": ""
},
"file": {
"type": "string"
}
},
"required": [
"file"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"alt": {
"type": "string",
"default": ""
},
"dark": {
"type": "string"
},
"light": {
"type": "string"
}
},
"required": [
"dark",
"light"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"html": {
"type": "string"
}
},
"required": [
"html"
],
"additionalProperties": false
}
]
},
"actions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"text": {
"type": "string"
},
"link": {
"type": "string"
},
"variant": {
"type": "string",
"enum": [
"primary",
"secondary",
"minimal"
],
"default": "primary"
},
"icon": {
"anyOf": [
{
"type": "string",
"enum": [
"up-caret",
"down-caret",
"right-caret",
"left-caret",
"up-arrow",
"down-arrow",
"right-arrow",
"left-arrow",
"bars",
"translate",
"pencil",
"pen",
"document",
"add-document",
"setting",
"external",
"download",
"cloud-download",
"moon",
"sun",
"laptop",
"open-book",
"information",
"magnifier",
"forward-slash",
"close",
"error",
"warning",
"approve-check-circle",
"approve-check",
"rocket",
"star",
"puzzle",
"list-format",
"random",
"comment",
"comment-alt",
"heart",
"github",
"gitlab",
"bitbucket",
"codePen",
"farcaster",
"discord",
"gitter",
"twitter",
"x.com",
"mastodon",
"codeberg",
"youtube",
"threads",
"linkedin",
"twitch",
"azureDevOps",
"microsoftTeams",
"instagram",
"stackOverflow",
"telegram",
"rss",
"facebook",
"email",
"phone",
"reddit",
"patreon",
"signal",
"slack",
"matrix",
"hackerOne",
"openCollective",
"blueSky",
"discourse",
"zulip",
"pinterest",
"tiktok",
"astro",
"alpine",
"pnpm",
"biome",
"bun",
"mdx",
"apple",
"linux",
"homebrew",
"nix",
"starlight",
"pkl",
"node",
"cloudflare",
"vercel",
"netlify",
"deno",
"jsr",
"nostr",
"backstage",
"confluence",
"jira",
"storybook",
"vscode",
"jetbrains",
"zed",
"vim",
"figma",
"sketch",
"npm",
"sourcehut",
"substack",
"seti:folder",
"seti:bsl",
"seti:mdo",
"seti:salesforce",
"seti:asm",
"seti:bicep",
"seti:bazel",
"seti:c",
"seti:c-sharp",
"seti:html",
"seti:cpp",
"seti:clojure",
"seti:coldfusion",
"seti:config",
"seti:crystal",
"seti:crystal_embedded",
"seti:json",
"seti:css",
"seti:csv",
"seti:xls",
"seti:cu",
"seti:cake",
"seti:cake_php",
"seti:d",
"seti:word",
"seti:elixir",
"seti:elixir_script",
"seti:hex",
"seti:elm",
"seti:favicon",
"seti:f-sharp",
"seti:git",
"seti:go",
"seti:godot",
"seti:gradle",
"seti:grails",
"seti:graphql",
"seti:hacklang",
"seti:haml",
"seti:mustache",
"seti:haskell",
"seti:haxe",
"seti:jade",
"seti:java",
"seti:javascript",
"seti:jinja",
"seti:julia",
"seti:karma",
"seti:kotlin",
"seti:dart",
"seti:liquid",
"seti:livescript",
"seti:lua",
"seti:markdown",
"seti:argdown",
"seti:info",
"seti:clock",
"seti:maven",
"seti:nim",
"seti:github",
"seti:notebook",
"seti:nunjucks",
"seti:npm",
"seti:ocaml",
"seti:odata",
"seti:perl",
"seti:php",
"seti:pipeline",
"seti:pddl",
"seti:plan",
"seti:happenings",
"seti:powershell",
"seti:prisma",
"seti:pug",
"seti:puppet",
"seti:purescript",
"seti:python",
"seti:react",
"seti:rescript",
"seti:R",
"seti:ruby",
"seti:rust",
"seti:sass",
"seti:spring",
"seti:slim",
"seti:smarty",
"seti:sbt",
"seti:scala",
"seti:ethereum",
"seti:stylus",
"seti:svelte",
"seti:swift",
"seti:db",
"seti:terraform",
"seti:tex",
"seti:default",
"seti:twig",
"seti:typescript",
"seti:tsconfig",
"seti:vala",
"seti:vite",
"seti:vue",
"seti:wasm",
"seti:wat",
"seti:xml",
"seti:yml",
"seti:prolog",
"seti:zig",
"seti:zip",
"seti:wgt",
"seti:illustrator",
"seti:photoshop",
"seti:pdf",
"seti:font",
"seti:image",
"seti:svg",
"seti:sublime",
"seti:code-search",
"seti:shell",
"seti:video",
"seti:audio",
"seti:windows",
"seti:jenkins",
"seti:babel",
"seti:bower",
"seti:docker",
"seti:code-climate",
"seti:eslint",
"seti:firebase",
"seti:firefox",
"seti:gitlab",
"seti:grunt",
"seti:gulp",
"seti:ionic",
"seti:platformio",
"seti:rollup",
"seti:stylelint",
"seti:yarn",
"seti:webpack",
"seti:lock",
"seti:license",
"seti:makefile",
"seti:heroku",
"seti:todo",
"seti:ignored"
]
},
{
"type": "string",
"pattern": "^\\<svg"
}
]
},
"attrs": {
"type": "object",
"additionalProperties": {
"type": [
"string",
"number",
"boolean"
]
}
}
},
"required": [
"text",
"link"
],
"additionalProperties": false
},
"default": []
}
},
"additionalProperties": false
},
"lastUpdated": {
"anyOf": [
{
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "string",
"format": "date"
},
{
"type": "integer",
"format": "unix-time"
}
]
},
{
"type": "boolean"
}
]
},
"prev": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
},
{
"type": "object",
"properties": {
"link": {
"type": "string"
},
"label": {
"type": "string"
}
},
"additionalProperties": false
}
]
},
"next": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
},
{
"type": "object",
"properties": {
"link": {
"type": "string"
},
"label": {
"type": "string"
}
},
"additionalProperties": false
}
]
},
"sidebar": {
"type": "object",
"properties": {
"order": {
"type": "number"
},
"label": {
"type": "string"
},
"hidden": {
"type": "boolean",
"default": false
},
"badge": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"variant": {
"type": "string",
"enum": [
"note",
"danger",
"success",
"caution",
"tip",
"default"
],
"default": "default"
},
"class": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"text"
],
"additionalProperties": false
}
]
},
"attrs": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"not": {}
}
]
},
"default": {}
}
},
"additionalProperties": false,
"default": {}
},
"banner": {
"type": "object",
"properties": {
"content": {
"type": "string"
}
},
"required": [
"content"
],
"additionalProperties": false
},
"pagefind": {
"type": "boolean",
"default": true
},
"draft": {
"type": "boolean",
"default": false
},
"$schema": {
"type": "string"
}
},
"required": [
"title"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@@ -1 +0,0 @@
export default new Map();

View File

@@ -1,20 +0,0 @@
export default new Map([
["src/content/docs/index.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Findex.mdx&astroContentModuleFlag=true")],
["src/content/docs/api/authentication.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fapi%2Fauthentication.mdx&astroContentModuleFlag=true")],
["src/content/docs/api/events.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fapi%2Fevents.mdx&astroContentModuleFlag=true")],
["src/content/docs/api/journals.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fapi%2Fjournals.mdx&astroContentModuleFlag=true")],
["src/content/docs/api/settings.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fapi%2Fsettings.mdx&astroContentModuleFlag=true")],
["src/content/docs/deployment/docker.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdeployment%2Fdocker.mdx&astroContentModuleFlag=true")],
["src/content/docs/deployment/environment.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdeployment%2Fenvironment.mdx&astroContentModuleFlag=true")],
["src/content/docs/features/ai-providers.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ffeatures%2Fai-providers.mdx&astroContentModuleFlag=true")],
["src/content/docs/features/calendar.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ffeatures%2Fcalendar.mdx&astroContentModuleFlag=true")],
["src/content/docs/features/diary-pages.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ffeatures%2Fdiary-pages.mdx&astroContentModuleFlag=true")],
["src/content/docs/features/export-import.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ffeatures%2Fexport-import.mdx&astroContentModuleFlag=true")],
["src/content/docs/features/media.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ffeatures%2Fmedia.mdx&astroContentModuleFlag=true")],
["src/content/docs/features/search.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ffeatures%2Fsearch.mdx&astroContentModuleFlag=true")],
["src/content/docs/getting-started/configuration.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fgetting-started%2Fconfiguration.mdx&astroContentModuleFlag=true")],
["src/content/docs/getting-started/installation.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fgetting-started%2Finstallation.mdx&astroContentModuleFlag=true")],
["src/content/docs/getting-started/quick-start.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fgetting-started%2Fquick-start.mdx&astroContentModuleFlag=true")],
["src/content/docs/features/events.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ffeatures%2Fevents.mdx&astroContentModuleFlag=true")]]);

View File

@@ -1,218 +0,0 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MDXContent;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
components: import('astro').MDXInstance<{}>['components'];
}>;
}
}
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export type ReferenceDataEntry<
C extends CollectionKey,
E extends keyof DataEntryMap[C] = string,
> = {
collection: C;
id: E;
};
export type ReferenceContentEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}) = string,
> = {
collection: C;
slug: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter?: LiveLoaderCollectionFilterType<C>,
): Promise<
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
entry: ReferenceContentEntry<C, E>,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
entry: ReferenceDataEntry<C, E>,
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter: string | LiveLoaderEntryFilterType<C>,
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
collection: C,
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type DataEntryMap = {
"docs": Record<string, {
id: string;
body?: string;
collection: "docs";
data: any;
rendered?: RenderedContent;
filePath?: string;
}>;
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
infer TCollectionFilter,
infer TError
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
LiveContentConfig['collections'][C]['schema'] extends undefined
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
: import('astro/zod').infer<
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
>;
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
LiveContentConfig['collections'][C]['loader']
>;
export type ContentConfig = typeof import("../src/content.config.mjs");
export type LiveContentConfig = never;
}

View File

@@ -1,2 +0,0 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View File

@@ -1,254 +0,0 @@
# API Reference
Base URL: `/api/v1`
Authentication: All requests require an API key in the `Authorization` header:
```
Authorization: Bearer <api_key>
```
## Authentication
### Register User
```bash
curl -X POST http://localhost:3000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password123"}'
```
### Login
```bash
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password123"}'
```
Response:
```json
{
"data": {
"token": "jwt_token",
"userId": "user_id"
},
"error": null
}
```
### Create API Key
```bash
curl -X POST http://localhost:3000/api/v1/auth/api-key \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"name": "my-app"}'
```
## Events
### Create Event
```bash
curl -X POST http://localhost:3000/api/v1/events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
-d '{
"date": "2026-03-27",
"type": "event",
"content": "Had a great meeting about the new project"
}'
```
Valid types: `event`, `text`, `photo`, `voice`, `health`
### Get Event
```bash
curl http://localhost:3000/api/v1/events/<event_id> \
-H "Authorization: Bearer <api_key>"
```
### Update Event
```bash
curl -X PUT http://localhost:3000/api/v1/events/<event_id> \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
-d '{"content": "Updated content"}'
```
Note: Events cannot be updated if a diary page has been generated for that date.
### Delete Event
```bash
curl -X DELETE http://localhost:3000/api/v1/events/<event_id> \
-H "Authorization: Bearer <api_key>"
```
Note: Events cannot be deleted if a diary page has been generated for that date.
## Days
### List Days
```bash
curl http://localhost:3000/api/v1/days \
-H "Authorization: Bearer <api_key>"
```
Response:
```json
{
"data": [
{
"date": "2026-03-27",
"eventCount": 5,
"hasJournal": true
}
],
"error": null
}
```
### Get Day Details
```bash
curl http://localhost:3000/api/v1/days/2026-03-27 \
-H "Authorization: Bearer <api_key>"
```
Response:
```json
{
"data": {
"date": "2026-03-27",
"events": [...],
"journal": {...}
},
"error": null
}
```
### Delete Day (and journal)
```bash
curl -X DELETE http://localhost:3000/api/v1/days/2026-03-27 \
-H "Authorization: Bearer <api_key>"
```
## Journal
### Generate Diary Page
```bash
curl -X POST http://localhost:3000/api/v1/journal/generate/2026-03-27 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
-d '{"instructions": "Focus more on the technical aspects"}'
```
The `instructions` field is optional. If provided, it will be appended to the prompt for regeneration.
Response:
```json
{
"data": {
"journal": {
"id": "...",
"date": "2026-03-27",
"content": "Generated diary content...",
"eventCount": 5,
"generatedAt": "2026-03-27T10:30:00Z"
},
"task": {
"id": "...",
"type": "journal_generate",
"status": "completed",
"provider": "groq",
"model": "llama-3.3-70b-versatile"
}
},
"error": null
}
```
### Get Journal
```bash
curl http://localhost:3000/api/v1/journal/2026-03-27 \
-H "Authorization: Bearer <api_key>"
```
### Delete Journal
```bash
curl -X DELETE http://localhost:3000/api/v1/journal/2026-03-27 \
-H "Authorization: Bearer <api_key>"
```
Deleting the journal unlocks events for editing.
### Get Journal Tasks
```bash
curl http://localhost:3000/api/v1/journal/2026-03-27/tasks \
-H "Authorization: Bearer <api_key>"
```
## Tasks
### Get Task
```bash
curl http://localhost:3000/api/v1/tasks/<task_id> \
-H "Authorization: Bearer <api_key>"
```
Response includes full request/response JSON for debugging.
## Settings
### Get Settings
```bash
curl http://localhost:3000/api/v1/settings \
-H "Authorization: Bearer <api_key>"
```
### Update Settings
```bash
curl -X PUT http://localhost:3000/api/v1/settings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
-d '{
"aiProvider": "groq",
"providerSettings": {
"groq": {
"apiKey": "your-api-key",
"model": "llama-3.3-70b-versatile"
}
}
}'
```
## AI Providers
### Test Connection
```bash
curl -X POST http://localhost:3000/api/v1/ai/test \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api_key>" \
-d '{
"provider": "groq",
"apiKey": "your-api-key",
"model": "llama-3.3-70b-versatile"
}'
```
Supported providers: `groq`, `openai`, `anthropic`, `ollama`, `lmstudio`
## Error Responses
All endpoints return errors in this format:
```json
{
"data": null,
"error": {
"code": "ERROR_CODE",
"message": "Human readable message"
}
}
```
Common error codes:
- `UNAUTHORIZED` - Invalid or missing API key
- `NOT_FOUND` - Resource not found
- `NO_EVENTS` - No events for diary generation
- `NO_AI_CONFIG` - AI provider not configured
- `EVENT_IMMUTABLE` - Cannot modify event (diary exists)

View File

@@ -1,41 +0,0 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
export default defineConfig({
integrations: [
starlight({
title: 'DearDiary',
description: 'AI-Powered Daily Journal - Self-hosted journaling app where users capture events throughout the day and AI generates diary pages.',
logo: {
light: './src/assets/logo-light.svg',
dark: './src/assets/logo-dark.svg',
replacesTitle: true,
},
social: [
{ icon: 'github', label: 'GitHub', href: 'https://github.com/lotherk/deardiary' },
],
editLink: {
baseUrl: 'https://github.com/lotherk/deardiary/edit/main/',
},
sidebar: [
{
label: 'Getting Started',
autogenerate: { directory: 'getting-started' },
},
{
label: 'Features',
autogenerate: { directory: 'features' },
},
{
label: 'API Reference',
autogenerate: { directory: 'api' },
},
{
label: 'Deployment',
autogenerate: { directory: 'deployment' },
},
],
customCss: ['./src/styles/custom.css'],
}),
],
});

View File

@@ -1,28 +0,0 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Starlight static site
location / {
try_files $uri $uri/ $uri.html =404;
}
# SPA fallback for client-side routing
location ~ ^/[^.]+$ {
try_files $uri $uri.html =404;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}

7311
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +0,0 @@
{
"name": "deardiary-docs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/starlight": "^0.34.2",
"astro": "^5.6.1",
"sharp": "^0.33.5",
"zod": "^3.23.8"
},
"overrides": {
"zod": "^3.23.8"
}
}

View File

@@ -1,15 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#a78bfa;stop-opacity:1" />
<stop offset="100%" style="stop-color:#818cf8;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="100" height="100" rx="20" fill="url(#grad)"/>
<path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3" stroke-linecap="round"/>
<path d="M35 40 L65 40" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M35 50 L65 50" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M35 60 L55 60" stroke="white" stroke-width="2" stroke-linecap="round"/>
<circle cx="70" cy="60" r="8" fill="white" opacity="0.3"/>
<path d="M70 55 L70 65 M65 60 L75 60" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 897 B

View File

@@ -1,15 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#8b5cf6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#6366f1;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="100" height="100" rx="20" fill="url(#grad)"/>
<path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3" stroke-linecap="round"/>
<path d="M35 40 L65 40" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M35 50 L65 50" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M35 60 L55 60" stroke="white" stroke-width="2" stroke-linecap="round"/>
<circle cx="70" cy="60" r="8" fill="white" opacity="0.3"/>
<path d="M70 55 L70 65 M65 60 L75 60" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 897 B

View File

@@ -1,47 +0,0 @@
---
title: Authentication
description: API authentication methods
sidebar:
order: 1
---
## API Key Authentication
All API requests require authentication using a Bearer token:
```http
Authorization: Bearer your-api-key-here
```
## Getting an API Key
### Via Login
1. POST to `/api/v1/auth/login` with email/password
2. Receive JWT token
3. Create API key via POST to `/api/v1/auth/api-key`
### Via Registration
1. POST to `/api/v1/auth/register` with email/password
2. Receive API key directly
## Response Format
All endpoints return:
```json
{
"data": { ... } | null,
"error": { "code": "ERROR_CODE", "message": "Error description" } | null
}
```
## Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| `UNAUTHORIZED` | 401 | Invalid or missing API key |
| `NOT_FOUND` | 404 | Resource not found |
| `VALIDATION_ERROR` | 400 | Invalid request data |
| `EVENT_IMMUTABLE` | 400 | Cannot modify locked events |

View File

@@ -1,70 +0,0 @@
---
title: Events API
description: Events endpoints reference
sidebar:
order: 2
---
## Create Event
```http
POST /api/v1/events
```
### Request Body
```json
{
"date": "2024-01-15",
"type": "text",
"content": "Had coffee with Sarah",
"latitude": 40.7128,
"longitude": -74.0060,
"placeName": "New York, NY"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `date` | string | Yes | Date in YYYY-MM-DD format |
| `type` | string | Yes | One of: event, text, photo, voice, health |
| `content` | string | Yes | Event content |
| `latitude` | number | No | GPS latitude |
| `longitude` | number | No | GPS longitude |
| `placeName` | string | No | Reverse-geocoded place name |
| `metadata` | object | No | Additional metadata |
### Response
```json
{
"data": {
"id": "uuid",
"date": "2024-01-15",
"type": "text",
"content": "Had coffee with Sarah",
"latitude": 40.7128,
"longitude": -74.0060,
"placeName": "New York, NY",
"createdAt": "2024-01-15T10:30:00Z"
}
}
```
## Get Day
```http
GET /api/v1/days/:date
```
Returns all events and journal for a specific date.
## Delete Event
```http
DELETE /api/v1/events/:id
```
:::caution
Cannot delete events from days with generated journals.
:::

View File

@@ -1,80 +0,0 @@
---
title: Journals API
description: Journals endpoints reference
sidebar:
order: 3
---
## Generate Diary
```http
POST /api/v1/journal/generate/:date
```
### Request Body
```json
{
"instructions": "Make it more detailed and poetic"
}
```
### Response
```json
{
"data": {
"journal": {
"id": "uuid",
"date": "2024-01-15",
"title": "A Productive Tuesday",
"content": "Full diary content...",
"eventCount": 8,
"generatedAt": "2024-01-15T22:00:00Z"
},
"task": {
"id": "uuid",
"status": "completed",
"provider": "groq",
"model": "llama-3.3-70b-versatile"
}
}
}
```
## Get Journal
```http
GET /api/v1/journal/:date
```
Returns the diary page for a specific date.
## List Journals
```http
GET /api/v1/journals?page=1&limit=10
```
### Query Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `page` | 1 | Page number |
| `limit` | 10 | Items per page (10, 50, or 100) |
## Get Generation Tasks
```http
GET /api/v1/journal/:date/tasks
```
Returns all generation attempts for a journal with request/response JSON.
## Delete Journal
```http
DELETE /api/v1/journal/:date
```
Deleting a journal **unlocks** all events for that day, allowing edits and new entries.

View File

@@ -1,80 +0,0 @@
---
title: Settings API
description: Settings endpoints reference
sidebar:
order: 4
---
## Get Settings
```http
GET /api/v1/settings
```
### Response
```json
{
"data": {
"aiProvider": "groq",
"aiApiKey": null,
"aiModel": "llama-3.3-70b-versatile",
"journalPrompt": "Custom instructions...",
"language": "en",
"timezone": "UTC",
"journalContextDays": 10,
"providerSettings": {
"groq": { "apiKey": "...", "model": "..." }
}
}
}
```
## Update Settings
```http
PUT /api/v1/settings
```
### Request Body
```json
{
"aiProvider": "openai",
"aiApiKey": "sk-...",
"journalPrompt": "Write in a reflective tone"
}
```
### Fields
| Field | Type | Description |
|-------|------|-------------|
| `aiProvider` | string | groq, openai, anthropic, ollama, lmstudio |
| `aiApiKey` | string | API key for selected provider |
| `aiModel` | string | Model identifier |
| `journalPrompt` | string | Custom instructions (null to clear) |
| `journalContextDays` | number | Days of previous journals to include (0-30) |
## Change Password
```http
POST /api/v1/account/password
```
```json
{
"currentPassword": "old-password",
"newPassword": "new-secure-password"
}
```
## Delete Account
```http
DELETE /api/v1/account
```
:::danger
This permanently deletes your account and all data. This cannot be undone.
:::

View File

@@ -1,99 +0,0 @@
---
title: Docker Deployment
description: Deploy DearDiary with Docker
sidebar:
order: 1
---
## Docker Compose
The recommended way to run DearDiary:
```yaml
services:
app:
build: .
ports:
- "8080:8080"
volumes:
- ./data:/data
env_file:
- backend/.env
restart: unless-stopped
```
## Running
### Start
```bash
docker compose up -d
```
### Stop
```bash
docker compose down
```
### Rebuild
```bash
docker compose build && docker compose up -d
```
## Volumes
Data persists in `./data/`:
| Directory | Contents |
|-----------|----------|
| `data/db/` | SQLite database |
| `data/media/` | Uploaded files |
## Health Check
The app exposes a health endpoint:
```http
GET http://localhost:8080/health
```
Returns:
```json
{
"status": "ok",
"timestamp": "2024-01-15T10:30:00Z"
}
```
## Production Considerations
### Reverse Proxy
For production, use a reverse proxy (nginx, Caddy, Traefik) with:
- HTTPS/TLS termination
- Security headers
- Rate limiting
### Database
Default SQLite is fine for single-user or small deployments.
For multi-user or high traffic, consider PostgreSQL:
```env
DATABASE_URL="postgresql://user:pass@host:5432/deardiary"
```
### Backup
Regularly backup `./data/` directory.
## Documentation Site
The documentation site is included in the stack and starts automatically with `docker compose up`.
Access at `http://localhost:4000`

View File

@@ -1,66 +0,0 @@
---
title: Environment Variables
description: Complete environment variable reference
sidebar:
order: 2
---
## Configuration File
Copy `.env.example` to `.env`:
```bash
cp backend/.env.example backend/.env
```
## Variables
### Application
| Variable | Default | Description |
|----------|---------|-------------|
| `APP_NAME` | DearDiary | App name displayed in UI |
| `VERSION` | 0.1.0 | App version |
| `PORT` | 3000 | Internal API port |
### Database
| Variable | Default | Description |
|----------|---------|-------------|
| `DATABASE_URL` | file:./data/deardiary.db | SQLite by default |
| `DATABASE_URL` | postgresql://... | PostgreSQL connection |
| `DATABASE_URL` | mysql://... | MySQL connection |
### Security
| Variable | Default | Description |
|----------|---------|-------------|
| `JWT_SECRET` | development-secret... | **Required in production!** |
| `CORS_ORIGIN` | * | CORS allowed origins |
### User Management
| Variable | Default | Description |
|----------|---------|-------------|
| `REGISTRATION_ENABLED` | false | Enable/disable registration |
| `DEFAULT_USER_EMAIL` | admin@localhost | Default admin email |
| `DEFAULT_USER_PASSWORD` | changeme123 | Default admin password |
### Storage
| Variable | Default | Description |
|----------|---------|-------------|
| `MEDIA_DIR` | ./data/media | Media files directory |
## Production Checklist
:::caution
Before going to production:
1. Set a strong `JWT_SECRET`
2. Set `CORS_ORIGIN` to your domain
3. Change default admin credentials
4. Set `REGISTRATION_ENABLED=false` if not needed
5. Use HTTPS/TLS
6. Set up regular backups
:::

View File

@@ -1,62 +0,0 @@
---
title: AI Providers
description: Configure AI providers for diary generation
sidebar:
order: 3
---
## Supported Providers
DearDiary supports multiple AI providers:
| Provider | Default Model | Type |
|----------|---------------|------|
| **Groq** | llama-3.3-70b-versatile | Cloud |
| OpenAI | gpt-4o | Cloud |
| Anthropic | claude-3.5-sonnet | Cloud |
| Ollama | varies | Local |
| LM Studio | varies | Local |
## Groq (Recommended)
Free tier available. Fast inference.
1. Get an API key from [console.groq.com](https://console.groq.com)
2. Enter the API key in Settings
3. Select Groq as your provider
## OpenAI
1. Get an API key from [platform.openai.com](https://platform.openai.com)
2. Optionally select a specific model:
- `gpt-4o` - Most capable
- `gpt-4o-mini` - Faster, cheaper
## Anthropic
1. Get an API key from [console.anthropic.com](https://console.anthropic.com)
2. Select Claude model:
- `claude-3-5-sonnet-latest` - Recommended
- `claude-3-opus-latest` - Most capable
## Ollama (Local)
Run models locally on your machine.
1. Install [Ollama](https://ollama.ai)
2. Pull a model: `ollama pull llama3.2`
3. Set base URL: `http://localhost:11434/v1`
4. Select model name
## LM Studio (Local)
Alternative local option with GUI.
1. Download [LM Studio](https://lmstudio.ai)
2. Download a model
3. Start local server (click "Start Server")
4. Set base URL: `http://localhost:1234/v1`
## Testing Connection
Use the **Test Connection** button in Settings to verify your AI provider is working before generating diaries.

View File

@@ -1,44 +0,0 @@
---
title: Calendar
description: Visual calendar view of your journals
sidebar:
order: 5
---
## Overview
The Calendar view provides a monthly overview of your journal activity.
Access it via the navigation menu: **Calendar**
## Calendar Indicators
Each day shows:
| Indicator | Meaning |
|-----------|---------|
| **Number** | Day of month |
| **Filled circle** | Has events |
| **Purple border** | Has generated diary page |
| **Today** | Highlighted border |
## Navigation
- **Previous/Next Month** - Arrow buttons
- **Month/Year Picker** - Click month name
- **Today Button** - Jump to current date
## Clicking a Day
Click any day to view:
- All events for that day
- Diary page if generated
- Option to add new events (if unlocked)
## Use Cases
- Quickly see activity patterns
- Identify gaps in journaling
- Navigate to specific past dates
- Plan when to catch up on entries

View File

@@ -1,60 +0,0 @@
---
title: Diary Pages
description: AI-generated diary entries
sidebar:
order: 2
---
## What is a Diary Page?
A **Diary Page** is an AI-generated narrative summary of your day's events. Unlike events, diary pages:
- Can be regenerated (rewritten)
- Include a generated title
- Are narrative, not just data points
## Generating a Diary
1. Navigate to **Today** (`/today`)
2. Ensure you have at least one event
3. Click **Generate Diary Page**
4. Wait for AI processing
## Viewing Diary Pages
Access diary pages via:
- **Dashboard** - Recent diary excerpts
- **Diary** - Paginated list of all diaries
- **Calendar** - Visual overview with indicators
- **Day View** - Click any date to see its diary
## Rewriting
Click **Rewrite** to regenerate with additional instructions:
### Default Rewrite
Regenerates with the same events and default prompt.
### Custom Instructions
Add specific guidance:
```
Make it more poetic and reflective.
Focus on the conversations I had.
```
## Title Generation
Each diary page includes an AI-generated title (max 50 characters) that summarizes the day.
## Task History
Every generation attempt is logged as a **Task**, including:
- Request/response JSON for debugging
- Provider and model used
- Success/failure status
- Processing time

View File

@@ -1,60 +0,0 @@
---
title: Events
description: Capturing and managing events
sidebar:
order: 1
---
## What is an Event?
An **Event** is a single log entry in your journal. Events can be:
- **Text** - Simple text notes
- **Photo** - Captured moments with images
- **Voice** - Voice memos
- **Health** - Health-related observations
- **Event** - General activity logs
## Creating Events
### Via Today Page
1. Navigate to **Today** (`/today`)
2. Type your event in the input field
3. Select the event type
4. Press Enter or click Add
### Via Quick Add
Press `Ctrl + J` from anywhere to open the quick add widget.
## Location
Events automatically capture:
- GPS coordinates (if browser permits)
- Reverse-geocoded place names
This helps you remember where things happened.
## Immutability
Once a **Diary Page** is generated for a day, all events for that day become **locked**:
- Cannot be deleted
- Cannot be edited
- Cannot have new events added
To unlock, you must delete the diary page.
## Media
### Photos
Upload photos directly or attach them to existing events.
### Voice Memos
Record voice memos using your browser's microphone.
Media files are stored in `./data/media/{userId}/{date}/`

View File

@@ -1,72 +0,0 @@
---
title: Export & Import
description: Backup and restore your data
sidebar:
order: 6
---
## Why Export?
Regular exports ensure you never lose your data:
- Backup to external storage
- Migrate to new server
- Keep offline archive
## Export Format
Exports are JSON files containing:
```json
{
"version": "0.0.6",
"exportedAt": "2024-01-15T10:30:00Z",
"settings": { ... },
"events": [ ... ],
"journals": [ ... ],
"tasks": [ ... ]
}
```
### Included Data
- **Settings** - AI provider, model, custom prompts
- **Events** - All event data including location
- **Journals** - All generated diary pages
- **Tasks** - Generation history for debugging
## How to Export
1. Go to **Settings**
2. Scroll to **Data Management**
3. Click **Export Data**
4. Save the JSON file
## Import
### Version Compatibility
- Minimum supported: 0.0.3
- Current version: 0.0.6
Older exports may lose some data or fail to import.
### Import Steps
1. Go to **Settings** → **Data Management**
2. Click **Import Data**
3. Select your JSON export file
4. Review the preview (imported/skipped counts)
5. Click **Confirm Import**
### Duplicate Handling
- **Events**: Skipped if identical (date + content + timestamp)
- **Journals**: Skipped by date
- **Tasks**: Linked to existing journals
## Danger Zone
:::caution
The **Reset Account** option in Danger Zone deletes ALL your data except settings. Use with extreme caution!
:::

View File

@@ -1,66 +0,0 @@
---
title: Media Uploads
description: Photos and voice memos
sidebar:
order: 7
---
## Supported Media Types
### Photos
- Upload photos to accompany events
- JPEG, PNG, GIF, WebP supported
- Displayed inline in event list
### Voice Memos
- Record voice notes
- WebM audio format
- Playback controls in event list
## How to Add Media
### Photo Type Events
1. Select **Photo** type when creating event
2. Add description in text field
3. Upload photo
### Voice Type Events
1. Select **Voice** type
2. Click microphone button
3. Start recording
4. Stop when finished
5. Add optional text note
## Storage
Media files are stored at:
```
./data/media/{userId}/{date}/
```
For example:
```
./data/media/user123abc/2024-01-15/photo.jpg
./data/media/user123abc/2024-01-15/voice.webm
```
## Serving
Media is served via nginx at `/media/` route:
```
/media/{userId}/{date}/{filename}
```
## Privacy
Media files are:
- Stored locally only
- Associated with your user account
- Not shared between users
- Deleted when you delete events

View File

@@ -1,43 +0,0 @@
---
title: Search
description: Finding events and diary pages
sidebar:
order: 4
---
## Using Search
Press `Ctrl + K` from anywhere to open the search modal.
## What You Can Search
### Diary Pages
Search across all generated diary pages:
- Titles
- Full content
- Dates
### Events
Search across all events:
- Event content
- Event type
- Date
## How It Works
DearDiary uses **SQLite FTS5** (Full-Text Search) for fast, indexed searching:
1. Automatic indexing on content creation
2. Porter stemming for better matches
3. Fallback to LIKE queries if FTS fails
## Tips
- Use specific keywords for better results
- Search works across all dates
- Results are grouped by type (diaries vs events)
- Click any result to navigate directly to that item

View File

@@ -1,54 +0,0 @@
---
title: Configuration
description: Configure DearDiary for your needs
sidebar:
order: 3
---
## Settings Page
Access settings via the navigation menu. Settings are organized into sections:
### AI Provider
Choose your AI provider for diary generation:
| Provider | Model | Notes |
|----------|-------|-------|
| **Groq** (default) | llama-3.3-70b-versatile | Fast, free tier available |
| OpenAI | gpt-4o, gpt-4o-mini | Requires API key |
| Anthropic | claude-3.5-sonnet | Requires API key |
| Ollama | Various | Local models |
| LM Studio | Various | Local models |
### Custom Instructions
Add custom instructions that are prepended to the default AI prompt:
```
Write in a poetic style.
Focus on health and wellness aspects.
```
### Journal Context
Set how many previous days of diaries the AI considers when generating new entries (0-30 days).
## Environment Variables
See [Environment Variables](/deployment/environment) for server-side configuration.
## Multi-User Setup
Enable user registration:
```env
REGISTRATION_ENABLED=true
```
Or create a default admin user:
```env
DEFAULT_USER_EMAIL=admin@example.com
DEFAULT_USER_PASSWORD=your-secure-password
```

View File

@@ -1,67 +0,0 @@
---
title: Installation
description: How to install and set up DearDiary
sidebar:
order: 1
---
## Prerequisites
- [Docker](https://docs.docker.com/get-docker/) and Docker Compose
- Git
## Quick Install
1. Clone the repository:
```bash
git clone https://github.com/lotherk/deardiary.git
cd deardiary
```
2. Copy the environment file:
```bash
cp backend/.env.example backend/.env
```
3. Start with Docker:
```bash
docker compose up -d
```
4. Access the app at `http://localhost:8080`
## Default Credentials
On first start, a default user is created if configured:
- **Email**: `admin@localhost` (configurable via `DEFAULT_USER_EMAIL`)
- **Password**: `changeme123` (configurable via `DEFAULT_USER_PASSWORD`)
:::tip
Change these credentials immediately after first login!
:::
## Ports
| Service | Port | Description |
|---------|------|-------------|
| App | 8080 | Main application (nginx) |
| API | 3000 | Backend API (internal) |
| Docs | 4000 | Documentation site (optional, run with `--profile docs`) |
## Documentation Site
The documentation site is included and starts automatically:
```bash
docker compose up -d
```
Access at `http://localhost:4000`
## Data Storage
All data is stored in Docker volumes:
- `./data/db/` - SQLite database
- `./data/media/` - Uploaded photos and voice memos

View File

@@ -1,58 +0,0 @@
---
title: Quick Start
description: Get started with DearDiary in 5 minutes
sidebar:
order: 2
---
## First Steps
### 1. Log an Event
Navigate to **Today** and start logging events:
- Type your event and press Enter
- Use the type buttons to change event category (Event, Health, Photo, Voice)
- Events are timestamped automatically
### 2. Add Your Location
When you log an event, DearDiary automatically captures:
- **Latitude/Longitude** from your browser
- **Place Name** via reverse geocoding (OpenStreetMap)
:::note
Location access requires browser permission. Grant it when prompted for automatic geolocation.
:::
### 3. Generate Your Diary
When you're ready to lock in your day:
1. Click **Generate Diary Page**
2. AI analyzes all your events
3. Creates a narrative diary entry with title
Once generated, events become **locked** (immutable).
### 4. Rewrite if Needed
Click **Rewrite** to regenerate with additional instructions:
- Add context like "Make it more detailed"
- Include specific topics to emphasize
- Adjust the tone or style
## Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `Ctrl + J` | Quick add event |
| `Ctrl + K` | Search |
## Tips
- Log events throughout the day for richer diaries
- Use the calendar view to browse past days
- Export your data regularly for backup

View File

@@ -1,34 +0,0 @@
---
title: DearDiary
description: AI-Powered Daily Journal
template: splash
hero:
title: DearDiary
tagline: Your AI-powered daily journal. Capture events throughout the day and let AI transform them into beautiful diary pages.
image:
file: ../../assets/logo-light.svg
actions:
- theme: brand
text: Get Started
link: /getting-started/installation/
- theme: alt
text: View on GitHub
link: https://github.com/lotherk/deardiary
---
import { Card, CardGrid } from '@astrojs/starlight/components';
<CardGrid stagger>
<Card title="Capture Events">
Log events throughout your day - text, photos, voice memos, and health data.
</Card>
<Card title="AI-Generated Diary">
Let AI transform your events into beautiful, narrative diary pages.
</Card>
<Card title="Privacy First">
Self-hosted. Your data stays on your server.
</Card>
<Card title="Geolocation">
Events are automatically tagged with your location.
</Card>
</CardGrid>

View File

@@ -1,21 +0,0 @@
/* Custom DearDiary styling */
:root[data-theme='light'] {
--sl-color-accent-low: #e9e3ff;
--sl-color-accent: #7c3aed;
--sl-color-accent-high: #4c1d95;
--sl-color-white: #1e1b4b;
--sl-color-gray-1: #3730a3;
--sl-color-gray-2: #4338ca;
--sl-color-gray-3: #4f46e5;
--sl-color-gray-4: #6366f1;
--sl-color-gray-5: #818cf8;
--sl-color-gray-6: #a5b4fc;
--sl-color-gray-7: #c7d2fe;
--sl-color-black: #e0e7ff;
}
:root[data-theme='dark'] {
--sl-color-accent-low: #4c1d95;
--sl-color-accent: #a78bfa;
--sl-color-accent-high: #c4b5fd;
}

178
www/css/docs.css Normal file
View File

@@ -0,0 +1,178 @@
.docs-layout {
display: flex;
padding-top: 72px;
min-height: 100vh;
}
.docs-sidebar {
width: 260px;
flex-shrink: 0;
background: var(--bg-secondary);
border-right: 1px solid var(--border);
padding: 32px 24px;
position: fixed;
top: 72px;
left: 0;
bottom: 0;
overflow-y: auto;
}
.sidebar-section {
margin-bottom: 32px;
}
.sidebar-section h3 {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
margin-bottom: 12px;
}
.sidebar-section a {
display: block;
padding: 8px 12px;
color: var(--text-secondary);
border-radius: var(--radius-sm);
transition: all 0.2s;
font-size: 0.95rem;
}
.sidebar-section a:hover {
color: var(--text-primary);
background: rgba(109, 40, 217, 0.1);
}
.sidebar-section a.active {
color: var(--accent-light);
background: rgba(109, 40, 217, 0.15);
}
.docs-content {
flex: 1;
max-width: 800px;
margin: 0 auto;
padding: 48px 48px 100px;
margin-left: 260px;
}
.docs-content h1 {
font-size: 2.5rem;
margin-bottom: 32px;
}
.docs-content h2 {
font-size: 1.5rem;
margin-top: 48px;
margin-bottom: 16px;
color: var(--text-primary);
}
.docs-content h3 {
font-size: 1.1rem;
margin-top: 32px;
margin-bottom: 12px;
color: var(--text-primary);
}
.docs-content p {
color: var(--text-secondary);
margin-bottom: 16px;
line-height: 1.8;
}
.docs-content ul,
.docs-content ol {
color: var(--text-secondary);
margin-bottom: 16px;
padding-left: 24px;
}
.docs-content li {
margin-bottom: 8px;
}
.docs-content pre {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 16px 20px;
margin-bottom: 24px;
overflow-x: auto;
}
.docs-content code {
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 0.9rem;
color: var(--accent-light);
}
.docs-content table {
width: 100%;
border-collapse: collapse;
margin-bottom: 24px;
}
.docs-content th,
.docs-content td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--border);
}
.docs-content th {
color: var(--text-muted);
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.docs-content td {
color: var(--text-secondary);
}
.docs-content .tip {
background: rgba(109, 40, 217, 0.15);
border: 1px solid var(--accent);
border-radius: var(--radius-sm);
padding: 16px 20px;
margin-bottom: 24px;
}
.docs-content .tip strong {
color: var(--accent-light);
}
.docs-content .next-steps {
margin-top: 48px;
padding-top: 32px;
border-top: 1px solid var(--border);
}
.docs-content .btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
color: white;
border-radius: var(--radius-sm);
font-weight: 600;
transition: all 0.2s;
}
.docs-content .btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 20px rgba(109, 40, 217, 0.3);
}
@media (max-width: 900px) {
.docs-sidebar {
display: none;
}
.docs-content {
margin-left: 0;
padding: 32px 24px;
}
}

367
www/css/styles.css Normal file
View File

@@ -0,0 +1,367 @@
:root {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-card: #1e293b;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--accent: #6d28d9;
--accent-light: #a78bfa;
--accent-dark: #4c1d95;
--border: #334155;
--radius: 12px;
--radius-sm: 8px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
}
a {
color: inherit;
text-decoration: none;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(15, 23, 42, 0.9);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
z-index: 100;
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 1.25rem;
font-weight: 700;
}
.nav-links {
display: flex;
align-items: center;
gap: 32px;
}
.nav-links a:not(.btn) {
color: var(--text-secondary);
transition: color 0.2s;
}
.nav-links a:not(.btn):hover {
color: var(--text-primary);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
border-radius: var(--radius-sm);
font-weight: 600;
font-size: 0.95rem;
transition: all 0.2s;
cursor: pointer;
border: none;
}
.btn-primary {
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, var(--accent-light), var(--accent));
transform: translateY(-1px);
}
.btn-secondary {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border);
}
.btn-secondary:hover {
border-color: var(--accent);
color: var(--accent-light);
}
.btn-large {
padding: 14px 28px;
font-size: 1rem;
}
.hero {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 120px 24px 80px;
background: radial-gradient(ellipse at top, rgba(109, 40, 217, 0.15) 0%, transparent 60%);
}
.hero-content {
max-width: 800px;
}
.hero h1 {
font-size: clamp(2.5rem, 6vw, 4.5rem);
font-weight: 800;
line-height: 1.1;
margin-bottom: 24px;
}
.gradient-text {
background: linear-gradient(135deg, var(--accent-light), #818cf8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 1.25rem;
color: var(--text-secondary);
max-width: 600px;
margin: 0 auto 40px;
}
.hero-actions {
display: flex;
gap: 16px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 32px;
}
.hero-meta {
display: flex;
gap: 12px;
justify-content: center;
color: var(--text-muted);
font-size: 0.9rem;
}
.features {
padding: 100px 0;
}
.features h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 60px;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 24px;
}
.feature-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 32px;
transition: all 0.3s;
}
.feature-card:hover {
border-color: var(--accent);
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(109, 40, 217, 0.15);
}
.feature-icon {
font-size: 2.5rem;
margin-bottom: 16px;
}
.feature-card h3 {
font-size: 1.25rem;
margin-bottom: 12px;
}
.feature-card p {
color: var(--text-secondary);
font-size: 0.95rem;
}
.how {
padding: 100px 0;
background: var(--bg-secondary);
}
.how h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 60px;
}
.steps {
max-width: 700px;
margin: 0 auto;
}
.step {
display: flex;
gap: 24px;
margin-bottom: 48px;
}
.step:last-child {
margin-bottom: 0;
}
.step-number {
flex-shrink: 0;
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1.25rem;
}
.step-content h3 {
font-size: 1.25rem;
margin-bottom: 8px;
}
.step-content p {
color: var(--text-secondary);
}
.try {
padding: 100px 0;
}
.try-content {
text-align: center;
max-width: 700px;
margin: 0 auto;
}
.try-content h2 {
font-size: 2.5rem;
margin-bottom: 16px;
}
.try-content > p {
color: var(--text-secondary);
margin-bottom: 32px;
}
.try-code {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px 24px;
margin-bottom: 16px;
overflow-x: auto;
}
.try-code code {
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 0.9rem;
color: var(--accent-light);
}
.try-hint {
color: var(--text-muted);
font-size: 0.9rem;
}
.footer {
padding: 60px 0 40px;
border-top: 1px solid var(--border);
}
.footer-links {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 40px;
margin-bottom: 40px;
}
.footer-col h4 {
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
margin-bottom: 16px;
}
.footer-col a {
display: block;
color: var(--text-secondary);
padding: 6px 0;
transition: color 0.2s;
}
.footer-col a:hover {
color: var(--accent-light);
}
.footer-bottom {
text-align: center;
padding-top: 40px;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 0.9rem;
}
@media (max-width: 768px) {
.nav-links a:not(.btn) {
display: none;
}
.hero h1 {
font-size: 2.5rem;
}
.feature-grid {
grid-template-columns: 1fr;
}
.step {
flex-direction: column;
align-items: center;
text-align: center;
}
}

138
www/docs/ai-providers.html Normal file
View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Providers - DearDiary</title>
<link rel="stylesheet" href="../css/styles.css">
<link rel="stylesheet" href="../css/docs.css">
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="../" class="logo">
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/></svg>
DearDiary
</a>
<div class="nav-links">
<a href="../">Home</a>
<a href="../docs/">Docs</a>
<a href="https://github.com/lotherk/deardiary" target="_blank">GitHub</a>
</div>
</div>
</nav>
<div class="docs-layout">
<aside class="docs-sidebar">
<div class="sidebar-section">
<h3>Getting Started</h3>
<a href="installation.html">Installation</a>
<a href="quick-start.html">Quick Start</a>
<a href="configuration.html">Configuration</a>
</div>
<div class="sidebar-section">
<h3>Features</h3>
<a href="events.html">Events</a>
<a href="diary-pages.html">Diary Pages</a>
<a href="ai-providers.html" class="active">AI Providers</a>
<a href="search.html">Search</a>
<a href="export-import.html">Export & Import</a>
</div>
</aside>
<main class="docs-content">
<h1>AI Providers</h1>
<p>DearDiary supports multiple AI providers for diary generation. Choose the one that best fits your needs.</p>
<h2>Supported Providers</h2>
<table>
<tr>
<th>Provider</th>
<th>Default Model</th>
<th>Type</th>
</tr>
<tr>
<td><strong>Groq</strong> (Recommended)</td>
<td>llama-3.3-70b-versatile</td>
<td>Cloud</td>
</tr>
<tr>
<td>OpenAI</td>
<td>gpt-4o</td>
<td>Cloud</td>
</tr>
<tr>
<td>Anthropic</td>
<td>claude-3-5-sonnet-latest</td>
<td>Cloud</td>
</tr>
<tr>
<td>Ollama</td>
<td>varies</td>
<td>Local</td>
</tr>
<tr>
<td>LM Studio</td>
<td>varies</td>
<td>Local</td>
</tr>
</table>
<h2>Groq (Recommended)</h2>
<p>Fast inference with a free tier available.</p>
<ol>
<li>Get an API key from <a href="https://console.groq.com" target="_blank">console.groq.com</a></li>
<li>Enter the API key in Settings</li>
<li>Select Groq as your provider</li>
<li>Test connection and save</li>
</ol>
<h2>OpenAI</h2>
<ol>
<li>Get an API key from <a href="https://platform.openai.com" target="_blank">platform.openai.com</a></li>
<li>Optionally select a specific model:
<ul>
<li><code>gpt-4o</code> - Most capable</li>
<li><code>gpt-4o-mini</code> - Faster, cheaper</li>
</ul>
</li>
</ol>
<h2>Anthropic</h2>
<ol>
<li>Get an API key from <a href="https://console.anthropic.com" target="_blank">console.anthropic.com</a></li>
<li>Select Claude model:
<ul>
<li><code>claude-3-5-sonnet-latest</code> - Recommended</li>
<li><code>claude-3-opus-latest</code> - Most capable</li>
</ul>
</li>
</ol>
<h2>Local Models (Ollama)</h2>
<p>Run models locally on your machine for complete privacy.</p>
<ol>
<li>Install <a href="https://ollama.ai" target="_blank">Ollama</a></li>
<li>Pull a model: <code>ollama pull llama3.2</code></li>
<li>Set base URL: <code>http://localhost:11434/v1</code></li>
<li>Enter model name (e.g., <code>llama3.2</code>)</li>
</ol>
<h2>Local Models (LM Studio)</h2>
<ol>
<li>Download <a href="https://lmstudio.ai" target="_blank">LM Studio</a></li>
<li>Download a model</li>
<li>Start local server (click "Start Server")</li>
<li>Set base URL: <code>http://localhost:1234/v1</code></li>
</ol>
<div class="next-steps">
<a href="diary-pages.html" class="btn">← Diary Pages</a>
<a href="events.html" class="btn">Events →</a>
</div>
</main>
</div>
</body>
</html>

84
www/docs/index.html Normal file
View File

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documentation - DearDiary</title>
<link rel="stylesheet" href="../css/styles.css">
<link rel="stylesheet" href="../css/docs.css">
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="../" class="logo">
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/></svg>
DearDiary
</a>
<div class="nav-links">
<a href="../">Home</a>
<a href="../docs/" class="active">Docs</a>
<a href="https://github.com/lotherk/deardiary" target="_blank">GitHub</a>
</div>
</div>
</nav>
<div class="docs-layout">
<aside class="docs-sidebar">
<div class="sidebar-section">
<h3>Getting Started</h3>
<a href="installation.html">Installation</a>
<a href="quick-start.html">Quick Start</a>
<a href="configuration.html">Configuration</a>
</div>
<div class="sidebar-section">
<h3>Features</h3>
<a href="events.html">Events</a>
<a href="diary-pages.html">Diary Pages</a>
<a href="ai-providers.html">AI Providers</a>
<a href="search.html">Search</a>
<a href="export-import.html">Export & Import</a>
</div>
</aside>
<main class="docs-content">
<h1>Documentation</h1>
<p>Welcome to the DearDiary documentation. Here you'll find everything you need to get started and make the most of your AI-powered journal.</p>
<h2>Quick Links</h2>
<div class="feature-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="feature-card">
<h3><a href="installation.html">Installation</a></h3>
<p>Get DearDiary up and running with Docker in minutes.</p>
</div>
<div class="feature-card">
<h3><a href="quick-start.html">Quick Start</a></h3>
<p>Learn the basics and create your first diary entry.</p>
</div>
<div class="feature-card">
<h3><a href="ai-providers.html">AI Providers</a></h3>
<p>Configure Groq, OpenAI, Anthropic, or local models.</p>
</div>
<div class="feature-card">
<h3><a href="events.html">Events</a></h3>
<p>Learn about capturing and managing events.</p>
</div>
</div>
<h2>Need Help?</h2>
<p>If you encounter issues:</p>
<ul>
<li>Check the <a href="https://github.com/lotherk/deardiary/issues" target="_blank">GitHub Issues</a></li>
<li>Review the configuration settings</li>
<li>Ensure your AI provider API key is valid</li>
</ul>
<div class="next-steps">
<a href="installation.html" class="btn btn-primary">Get Started →</a>
</div>
</main>
</div>
</body>
</html>

121
www/docs/installation.html Normal file
View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installation - DearDiary</title>
<link rel="stylesheet" href="../css/styles.css">
<link rel="stylesheet" href="../css/docs.css">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><linearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'><stop offset='0%25' style='stop-color:%236d28d9'/><stop offset='100%25' style='stop-color:%234c1d95'/></linearGradient></defs><rect width='100' height='100' rx='20' fill='url(%23g)'/><path d='M25 25 L75 25 L75 80 L25 80 Z' fill='none' stroke='white' stroke-width='3'/></svg>">
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="../" class="logo">
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/></svg>
DearDiary
</a>
<div class="nav-links">
<a href="../">Home</a>
<a href="../docs/">Docs</a>
<a href="https://github.com/lotherk/deardiary" target="_blank">GitHub</a>
</div>
</div>
</nav>
<div class="docs-layout">
<aside class="docs-sidebar">
<div class="sidebar-section">
<h3>Getting Started</h3>
<a href="installation.html" class="active">Installation</a>
<a href="quick-start.html">Quick Start</a>
<a href="configuration.html">Configuration</a>
</div>
<div class="sidebar-section">
<h3>Features</h3>
<a href="events.html">Events</a>
<a href="diary-pages.html">Diary Pages</a>
<a href="ai-providers.html">AI Providers</a>
<a href="search.html">Search</a>
<a href="export-import.html">Export & Import</a>
</div>
</aside>
<main class="docs-content">
<h1>Installation</h1>
<h2>Prerequisites</h2>
<ul>
<li><a href="https://docs.docker.com/get-docker/" target="_blank">Docker</a> and Docker Compose</li>
<li>Git</li>
</ul>
<h2>Quick Install</h2>
<h3>1. Clone the repository</h3>
<pre><code>git clone https://github.com/lotherk/deardiary.git
cd deardiary</code></pre>
<h3>2. Copy the environment file</h3>
<pre><code>cp backend/.env.example backend/.env</code></pre>
<h3>3. Start with Docker</h3>
<pre><code>docker compose up -d</code></pre>
<h3>4. Access the app</h3>
<p>Open <code>http://localhost:5173</code> in your browser.</p>
<h2>Default Credentials</h2>
<table>
<tr>
<td><strong>Email</strong></td>
<td><code>admin@localhost</code></td>
</tr>
<tr>
<td><strong>Password</strong></td>
<td><code>changeme123</code></td>
</tr>
</table>
<div class="tip">
<strong>Important:</strong> Change these credentials immediately after first login!
</div>
<h2>Ports</h2>
<table>
<tr>
<th>Service</th>
<th>Port</th>
<th>Description</th>
</tr>
<tr>
<td>Frontend</td>
<td>5173</td>
<td>Main application (nginx)</td>
</tr>
<tr>
<td>API</td>
<td>3000</td>
<td>Backend API (internal)</td>
</tr>
<tr>
<td>Website</td>
<td>8080</td>
<td>This website (if enabled)</td>
</tr>
</table>
<h2>Data Storage</h2>
<p>All data is stored in Docker volumes:</p>
<ul>
<li><code>./data/db/</code> - SQLite database</li>
<li><code>./data/media/</code> - Uploaded photos and voice memos</li>
</ul>
<div class="next-steps">
<a href="quick-start.html" class="btn btn-primary">Next: Quick Start →</a>
</div>
</main>
</div>
</body>
</html>

118
www/docs/quick-start.html Normal file
View File

@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quick Start - DearDiary</title>
<link rel="stylesheet" href="../css/styles.css">
<link rel="stylesheet" href="../css/docs.css">
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="../" class="logo">
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/></svg>
DearDiary
</a>
<div class="nav-links">
<a href="../">Home</a>
<a href="../docs/">Docs</a>
<a href="https://github.com/lotherk/deardiary" target="_blank">GitHub</a>
</div>
</div>
</nav>
<div class="docs-layout">
<aside class="docs-sidebar">
<div class="sidebar-section">
<h3>Getting Started</h3>
<a href="installation.html">Installation</a>
<a href="quick-start.html" class="active">Quick Start</a>
<a href="configuration.html">Configuration</a>
</div>
<div class="sidebar-section">
<h3>Features</h3>
<a href="events.html">Events</a>
<a href="diary-pages.html">Diary Pages</a>
<a href="ai-providers.html">AI Providers</a>
<a href="search.html">Search</a>
<a href="export-import.html">Export & Import</a>
</div>
</aside>
<main class="docs-content">
<h1>Quick Start</h1>
<h2>First Steps</h2>
<h3>1. Configure AI Provider</h3>
<p>Before generating diaries, you need to set up an AI provider:</p>
<ol>
<li>Go to <strong>Settings</strong></li>
<li>Select your AI provider (Groq is recommended for free, fast inference)</li>
<li>Enter your API key</li>
<li>Click "Test Connection" to verify</li>
<li>Save settings</li>
</ol>
<h3>2. Log Your First Event</h3>
<p>Navigate to <strong>Today</strong> and start logging events:</p>
<ul>
<li>Type your event and press Enter</li>
<li>Use the type buttons to change event category</li>
<li>Events are timestamped automatically</li>
<li>Location is captured if you allow browser permission</li>
</ul>
<h3>3. Generate Your Diary</h3>
<p>When you're ready to lock in your day:</p>
<ol>
<li>Click <strong>Generate Diary Page</strong></li>
<li>Wait for AI processing</li>
<li>Your diary page appears with a generated title</li>
</ol>
<div class="tip">
Once generated, events become <strong>locked</strong> (immutable). To unlock, delete the diary page.
</div>
<h3>4. Rewrite if Needed</h3>
<p>Click <strong>Rewrite</strong> to regenerate with additional instructions:</p>
<ul>
<li>Add context like "Make it more detailed"</li>
<li>Focus on specific topics</li>
<li>Adjust the tone or style</li>
</ul>
<h2>Keyboard Shortcuts</h2>
<table>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
<tr>
<td><code>Ctrl + J</code></td>
<td>Quick add event</td>
</tr>
<tr>
<td><code>Ctrl + K</code></td>
<td>Search</td>
</tr>
</table>
<h2>Tips</h2>
<ul>
<li>Log events throughout the day for richer diaries</li>
<li>Use the calendar view to browse past days</li>
<li>Export your data regularly for backup</li>
<li>Try different AI providers for varied writing styles</li>
</ul>
<div class="next-steps">
<a href="installation.html" class="btn">← Previous</a>
<a href="configuration.html" class="btn">Next: Configuration →</a>
</div>
</main>
</div>
</body>
</html>

155
www/index.html Normal file
View File

@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DearDiary - AI-Powered Daily Journal</title>
<meta name="description" content="Self-hosted AI-powered daily journaling. Capture events throughout the day and let AI generate beautiful diary pages.">
<link rel="stylesheet" href="css/styles.css">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><linearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'><stop offset='0%25' style='stop-color:%236d28d9'/><stop offset='100%25' style='stop-color:%234c1d95'/></linearGradient></defs><rect width='100' height='100' rx='20' fill='url(%23g)'/><path d='M25 25 L75 25 L75 80 L25 80 Z' fill='none' stroke='white' stroke-width='3'/><path d='M35 40 L65 40 M35 50 L65 50 M35 60 L55 60' stroke='white' stroke-width='2'/></svg>">
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<a href="/" class="logo">
<svg width="32" height="32" viewBox="0 0 100 100"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#6d28d9"/><stop offset="100%" style="stop-color:#4c1d95"/></linearGradient></defs><rect width="100" height="100" rx="20" fill="url(#g)"/><path d="M25 25 L75 25 L75 80 L25 80 Z" fill="none" stroke="white" stroke-width="3"/><path d="M35 40 L65 40 M35 50 L65 50 M35 60 L55 60" stroke="white" stroke-width="2"/></svg>
DearDiary
</a>
<div class="nav-links">
<a href="#features">Features</a>
<a href="docs/">Docs</a>
<a href="https://github.com/lotherk/deardiary" target="_blank">GitHub</a>
<a href="#try" class="btn btn-primary">Get Started</a>
</div>
</div>
</nav>
<header class="hero">
<div class="hero-content">
<h1>Your AI-Powered<br><span class="gradient-text">Daily Journal</span></h1>
<p class="hero-subtitle">Capture events throughout the day and let AI transform them into beautiful, thoughtful diary pages. Self-hosted. Private. Yours.</p>
<div class="hero-actions">
<a href="#try" class="btn btn-primary btn-large">Start Free</a>
<a href="docs/" class="btn btn-secondary btn-large">Read Docs</a>
</div>
<div class="hero-meta">
<span>v0.1.0</span>
<span></span>
<span>Docker</span>
<span></span>
<span>MIT License</span>
</div>
</div>
</header>
<section id="features" class="features">
<div class="container">
<h2>Everything you need for journaling</h2>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">📝</div>
<h3>Quick Capture</h3>
<p>Log events instantly with Ctrl+J. Multiple types: text, photos, voice memos, health notes.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🤖</div>
<h3>AI Generation</h3>
<p>Let AI craft thoughtful diary pages from your events. Works with Groq, OpenAI, Anthropic, and local models.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📍</div>
<h3>Auto Geolocation</h3>
<p>Events are automatically tagged with your location. Remember where life happened.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔒</div>
<h3>Immutable Memories</h3>
<p>Once a diary is generated, events lock. Your past stays authentic, untouched by today's edits.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔍</div>
<h3>Full-Text Search</h3>
<p>Find any event or diary entry instantly across all your data.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📤</div>
<h3>Export & Import</h3>
<p>Your data, your control. Export everything as JSON. Migrate anytime.</p>
</div>
</div>
</div>
</section>
<section id="how" class="how">
<div class="container">
<h2>How it works</h2>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h3>Capture Throughout the Day</h3>
<p>Log events as they happen. Type, snap a photo, record a voice note. Add health data. Each entry is timestamped and geolocated.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h3>Generate Your Diary</h3>
<p>When you're ready, click Generate. AI reads all your events and writes a thoughtful, narrative diary page just for you.</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h3>Rewrite if Needed</h3>
<p>Not happy with the result? Add instructions and regenerate. Focus on what matters to you.</p>
</div>
</div>
</div>
</div>
</section>
<section id="try" class="try">
<div class="container">
<div class="try-content">
<h2>Ready to start journaling?</h2>
<p>Self-host in minutes with Docker. Your data stays on your server.</p>
<div class="try-code">
<code>git clone https://github.com/lotherk/deardiary.git && cd deardiary && docker compose up -d</code>
</div>
<p class="try-hint">Default login: admin@localhost / changeme123</p>
</div>
</div>
</section>
<footer class="footer">
<div class="container">
<div class="footer-links">
<div class="footer-col">
<h4>Product</h4>
<a href="#features">Features</a>
<a href="docs/">Documentation</a>
<a href="#how">How it Works</a>
</div>
<div class="footer-col">
<h4>Resources</h4>
<a href="docs/getting-started/installation.html">Installation</a>
<a href="docs/getting-started/quick-start.html">Quick Start</a>
<a href="docs/features/ai-providers.html">AI Providers</a>
</div>
<div class="footer-col">
<h4>Project</h4>
<a href="https://github.com/lotherk/deardiary" target="_blank">GitHub</a>
<a href="https://github.com/lotherk/deardiary/issues" target="_blank">Issues</a>
<a href="https://github.com/lotherk/deardiary/releases" target="_blank">Releases</a>
</div>
</div>
<div class="footer-bottom">
<p>MIT License • Copyright 2026 Konrad Lother</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

21
www/js/main.js Normal file
View File

@@ -0,0 +1,21 @@
document.addEventListener('DOMContentLoaded', () => {
const navbar = document.querySelector('.navbar');
window.addEventListener('scroll', () => {
if (window.scrollY > 50) {
navbar.style.background = 'rgba(15, 23, 42, 0.98)';
} else {
navbar.style.background = 'rgba(15, 23, 42, 0.9)';
}
});
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
});