- I'm a big fan of Jennie. If there are any copyright issues, I will remove the content.
This service was born from a simple question: instead of extracting information from AI and manually moving it into separate documents, could we document the AI responses themselves to manage knowledge permanently? And could we automatically link related documents by considering the relationships between the current document and others, making information easier to navigate?
A question asked to an LLM can become a subtitle. For example, the message "Tell me about the modern history of our country" can be turned into a title like "Modern History of Our Country." Based on the generated response, hashtags are automatically created by AI, and documents can be linked (Related Documents) based on matching hashtags from other documents. Of course, hashtags can have homonyms, so the more hashtags two documents share, the closer the relationship. When the LLM responds using web search, the referenced documents (Reference Documents) are visualized, numbered, and cited within the response to show which sources were used. Based on this, a loose implementation of the Zettelkasten method has been applied.
There are two modes: Thread and Chat. Writing in Thread mode automatically generates hashtags and links documents together. If you want to ask casual questions lightly, use Chat mode. However, Chat mode uses the same base engine for AI responses but does not support related document linking via hashtags or editing of AI-generated messages.
- Reference Documents
- When the AI performs a web search to answer a question, the referenced web pages are numbered, and images or other content from the search appear above them. Key images or YouTube videos related to the document can be pinned to the screen. The content editor also allows you to delete unnecessary images and reposition them.
- User message → Subtitle conversion
- Clicking on a user message allows you to edit it into a subtitle/title.
- Right-side menu
- Reorder and move sections up or down by subtitle
- Delete a document set (question + answer) by subtitle
- Manage AI-generated hashtags (add/delete)
- Related Documents — browse documents linked by shared hashtags
Here are some ways I personally use this service:
- Paste a YouTube link and request a summary, play it in PiP mode, and ask follow-up questions mid-video to study.
- When reading a book, web-search for a summary of the book, then document questions that come up while reading.
- Provide a web link on a specific topic and have the content organized.
Tested primarily with the gemma4 and oss-20b models served via Ollama / LM Studio.
The API Endpoint must end with /v1!
| Type | Purpose |
|---|---|
| Local LLM | OpenAI-compatible endpoint URL |
| Tavily API Key | For web search |
Built against the OpenAI GPT-compatible spec. Below is the list of models that have been tested.
- Note: every AI Endpoint must end with
/v1!
| Type | Model | Purpose |
|---|---|---|
| Ollama | Gemma4:26b | Reasoning Model |
| Ollama | Gemma4:26b | Vision Model |
| OpenAI | gpt-4o | Reasoning Model |
| OpenAI | gpt-4o | Vision Model |
| Anthropic | opus | Reasoning Model |
- You only need an admin account + AI endpoint + Tavily key.
.env
TH_ADMIN_EMAIL_ID=admin@example.com # admin login email
TH_ADMIN_PASSWORD=changeme1234 # admin login password
# Postgres
POSTGRES_DB=stella
POSTGRES_USER=stella
POSTGRES_PASSWORD=stella_pass@docker-compose.yml
services:
postgres:
image: pgvector/pgvector:pg18
environment:
POSTGRES_DB: ${POSTGRES_DB:-stella}
POSTGRES_USER: ${POSTGRES_USER:-stella}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-stella}
volumes:
- pgdata:/var/lib/postgresql/18/docker
app:
image: charles1031/stella-th:latest
restart: unless-stopped
ports:
- "3100:3100" # web (frontend)
- "4100:4100" # API (backend)
environment:
TH_ADMIN_EMAIL_ID: ${TH_ADMIN_EMAIL_ID}
TH_ADMIN_PASSWORD: ${TH_ADMIN_PASSWORD}
DATABASE_URL: postgres://${POSTGRES_USER:-stella}:${POSTGRES_PASSWORD:-stella}@postgres:5432/${POSTGRES_DB:-stella}
depends_on:
- postgres
volumes:
pgdata:docker compose up -dOpen http://localhost:3100.
Need custom ports, host volumes, healthchecks, or a reverse proxy? See the full setup below.
# ── Required ──────────────────────────────────────────
# Admin account created on first boot
TH_ADMIN_EMAIL_ID=admin@example.com
TH_ADMIN_PASSWORD=changeme1234
# Web (frontend) host port behind your reverse proxy. Change on port conflict.
TH_PORT=3100
# AI — configured per group: Reasoning (text answers) and Vision (image analysis).
# Each group has its own OpenAI-compatible endpoint (include /v1), API key, model, and max output tokens.
# These seed the DB on first boot; afterwards values set in Settings → AI take priority.
# OpenAI cloud: https://api.openai.com/v1 (e.g. model gpt-4o)
# Anthropic: https://api.anthropic.com/v1 (e.g. model claude-...)
# Local runtime (Ollama/vLLM/LM Studio …): http://host.docker.internal:11434/v1
AI_REASONING_ENDPOINT=https://api.openai.com/v1
AI_REASONING_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxx # leave empty for local servers that need no auth
AI_REASONING_MODEL=gpt-4o
AI_REASONING_MAX_TOKENS=16384
AI_VISION_ENDPOINT=https://api.openai.com/v1 # can point to a different provider/model
AI_VISION_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxx
AI_VISION_MODEL=gpt-4o # vision-capable model
AI_VISION_MAX_TOKENS=8192
# ── Optional ──────────────────────────────────────────
# Web search feature (disabled if not provided)
TAVILY_API_KEY=tvly-xxxxxxxxxxxxxxxxxxxxx
# DB credentials (can be omitted to use defaults)
# POSTGRES_USER=stella
# POSTGRES_PASSWORD=stella_dev_pass
# POSTGRES_DB=stellaservices:
postgres:
image: pgvector/pgvector:pg18 # PostgreSQL 18 + pgvector (required for the vector extension)
container_name: stella-th-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-stella}
POSTGRES_USER: ${POSTGRES_USER:-stella}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-stella_dev_pass}
volumes:
- postgres-data:/var/lib/postgresql/18/docker
networks:
- stella-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-stella} -d ${POSTGRES_DB:-stella}"]
interval: 5s
timeout: 5s
retries: 20
start_period: 10s
app:
image: charles1031/stella-th:1.0.1 # Docker Hub — replace with the latest tag
container_name: stella-th-app
restart: unless-stopped
ports:
- "4100:4100" # backend (NestJS)
- "${TH_PORT:-3100}:3100" # web (frontend) — change TH_PORT in .env on conflict
env_file:
- .env
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-stella}:${POSTGRES_PASSWORD:-stella_dev_pass}@postgres:5432/${POSTGRES_DB:-stella}
UPLOAD_DIR: /app/backend/uploads
LOG_DIR: /app/backend/logs
volumes:
- attachments-data:/app/backend/uploads
- logs-data:/app/backend/logs
depends_on:
postgres:
condition: service_healthy
networks:
- stella-net
networks:
stella-net:
driver: bridge
volumes:
postgres-data:
attachments-data:
logs-data:docker compose up -d

