feat: 8 AI providers, rich text editor, agent notifications, UI contrast & font settings
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m25s

- Add DeepSeek, OpenRouter, Mistral, Z.AI, LM Studio as AI providers
  with editable model names via Combobox in admin settings
- Fix OpenRouter broken by normalizeProvider bug in config.ts
- Convert agent-created notes from Markdown to HTML (TipTap rich text)
- Add Notification model + in-app notifications for agent results
- Agent notification click opens the created note directly
- Add note count display on notebook and inbox headers
- Fix checklist toggle in card view (persist state via localCheckItems)
- Add checklist creation option in tabs/list view (dropdown on + button)
- Fix image description ENOENT error with HTTP fallback
- Improve UI contrast across all themes (input, border, checkbox visibility)
- Add font family setting (Inter vs System Default) in Appearance settings
- Fix CSS font-sans variable conflict (removed dead Geist references)
- Update README with new features and 8 providers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Sepehr Ramezani
2026-05-01 16:14:07 +02:00
parent 1345403a31
commit dbd49d6fcb
64 changed files with 4124 additions and 1392 deletions

592
scripts/deploy-docker.sh Executable file
View File

@@ -0,0 +1,592 @@
#!/bin/bash
set -euo pipefail
# ============================================================
# Memento - Docker Deploy Script (macOS / Linux)
# ============================================================
# Usage:
# ./scripts/deploy-docker.sh # Full setup
# ./scripts/deploy-docker.sh --env-only # Generate .env.docker only
# ./scripts/deploy-docker.sh --build # Build + deploy (no env setup)
# ./scripts/deploy-docker.sh --stop # Stop all containers
# ./scripts/deploy-docker.sh --logs # Show logs
# ============================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
ENV_FILE="$PROJECT_DIR/.env.docker"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
step() { echo -e "\n${CYAN}${BOLD}==>${NC} ${BOLD}$*${NC}"; }
# -----------------------------------------------------------
# Check dependencies
# -----------------------------------------------------------
check_deps() {
step "Checking dependencies..."
if ! command -v docker &>/dev/null; then
error "Docker is not installed.
macOS: https://docs.docker.com/desktop/install/mac-install/
Linux: https://docs.docker.com/engine/install/"
fi
if ! docker info &>/dev/null 2>&1; then
error "Docker daemon is not running. Start Docker first."
fi
if docker compose version &>/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &>/dev/null; then
COMPOSE_CMD="docker-compose"
else
error "Docker Compose is not installed.
Install: https://docs.docker.com/compose/install/"
fi
if ! command -v openssl &>/dev/null; then
warn "openssl not found. Will use /dev/urandom for secrets."
fi
ok "All dependencies met"
}
# -----------------------------------------------------------
# Helper: generate random secret
# -----------------------------------------------------------
gen_secret() {
if command -v openssl &>/dev/null; then
openssl rand -base64 32 2>/dev/null
else
head -c 32 /dev/urandom | base64
fi
}
gen_password() {
if command -v openssl &>/dev/null; then
openssl rand -hex 16 2>/dev/null
else
head -c 16 /dev/urandom | hexdump -v -e '/1 "%02x"'
fi
}
# -----------------------------------------------------------
# Ask a question with default
# -----------------------------------------------------------
ask() {
local prompt="$1"
local default="${2:-}"
local var="$3"
local result
if [ -n "$default" ]; then
echo -ne " ${CYAN}?${NC} ${prompt} [${default}]: "
else
echo -ne " ${CYAN}?${NC} ${prompt}: "
fi
read -r result
result="${result:-$default}"
eval "$var=\"\$result\""
}
ask_required() {
local prompt="$1"
local var="$2"
local result
while true; do
echo -ne " ${CYAN}?${NC} ${prompt} (required): "
read -r result
if [ -n "$result" ]; then
eval "$var=\"\$result\""
return
fi
echo -e " ${RED}This field is required.${NC}"
done
}
ask_email() {
local prompt="$1"
local var="$2"
local result
while true; do
echo -ne " ${CYAN}?${NC} ${prompt} (required): "
read -r result
if [ -n "$result" ] && echo "$result" | grep -qE '^[^@]+@[^@]+\.[^@]+$'; then
eval "$var=\"\$result\""
return
fi
echo -e " ${RED}Please enter a valid email address.${NC}"
done
}
# -----------------------------------------------------------
# Generate .env.docker interactively
# -----------------------------------------------------------
generate_env() {
step "Configuring Memento for Docker deployment"
if [ -f "$ENV_FILE" ]; then
warn ".env.docker already exists."
echo -ne " ${CYAN}?${NC} Overwrite? [y/N]: "
read -r confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { info "Keeping existing .env.docker"; return 0; }
fi
echo ""
echo -e "${BOLD} This wizard will guide you through the configuration.${NC}"
echo -e "${BOLD} Press Enter to accept defaults in [brackets].${NC}"
echo ""
# ---- Core ----
step "Core configuration"
local url="http://localhost:3000"
ask "App URL (NEXTAUTH_URL)" "$url" url
local secret
secret=$(gen_secret)
info "Auto-generated NEXTAUTH_SECRET"
local admin_email
ask_email "Admin email (first user with this email becomes ADMIN)" admin_email
local allow_reg="true"
ask "Allow public registration" "$allow_reg" allow_reg
# Normalize
case "$allow_reg" in
[Yy]|[Yy]es|true|1) allow_reg="true" ;;
*) allow_reg="false" ;;
esac
# ---- PostgreSQL ----
step "PostgreSQL configuration"
local pg_port="5433"
local pg_db="memento"
local pg_user="memento"
local pg_pass
pg_pass=$(gen_password)
ask "PostgreSQL exposed port" "$pg_port" pg_port
ask "PostgreSQL database name" "$pg_db" pg_db
ask "PostgreSQL username" "$pg_user" pg_user
info "Auto-generated secure PostgreSQL password"
# ---- AI Provider ----
step "AI Provider configuration"
echo " Choose your AI provider:"
echo " 1) OpenAI"
echo " 2) Ollama (local, requires Ollama container)"
echo " 3) OpenRouter"
echo " 4) Custom OpenAI-compatible (Groq, Together, Mistral, etc.)"
echo " 5) Skip AI configuration"
echo ""
local ai_choice="5"
ask "Choice" "$ai_choice" ai_choice
local ai_tags_provider="" ai_tags_model=""
local ai_embed_provider="" ai_embed_model=""
local ai_chat_provider="" ai_chat_model=""
local openai_key="" custom_key="" custom_url="" ollama_url=""
case "$ai_choice" in
1)
ai_tags_provider="openai"; ai_tags_model="gpt-4o-mini"
ai_embed_provider="openai"; ai_embed_model="text-embedding-3-small"
ai_chat_provider="openai"; ai_chat_model="gpt-4o-mini"
ask_required "OpenAI API Key" openai_key
;;
2)
ai_tags_provider="ollama"; ai_tags_model="granite4:latest"
ai_embed_provider="ollama"; ai_embed_model="embeddinggemma:latest"
ai_chat_provider="ollama"; ai_chat_model="granite4:latest"
ollama_url="http://ollama:11434"
ask "Ollama base URL" "$ollama_url" ollama_url
;;
3)
ai_tags_provider="custom"; ai_tags_model="google/gemma-3-27b-it"
ai_embed_provider="custom"; ai_embed_model="text-embedding-3-small"
ai_chat_provider="custom"; ai_chat_model="google/gemma-3-27b-it"
custom_url="https://openrouter.ai/api/v1"
ask_required "OpenRouter API Key" custom_key
ask "OpenRouter base URL" "$custom_url" custom_url
;;
4)
ai_tags_provider="custom"
ai_embed_provider="custom"
ai_chat_provider="custom"
ask_required "Custom provider API Key" custom_key
ask_required "Custom provider base URL" custom_url
ask "Model for tags" "gpt-4o-mini" ai_tags_model
ask "Model for embeddings" "text-embedding-3-small" ai_embed_model
ask "Model for chat" "gpt-4o-mini" ai_chat_model
;;
5)
info "Skipping AI configuration. You can configure it later in the admin panel."
;;
*)
error "Invalid choice"
;;
esac
# ---- MCP Server ----
step "MCP Server configuration"
echo " The MCP server allows external tools (Claude, Cline, N8N) to interact with Memento."
echo ""
local mcp_enable="yes"
ask "Enable MCP server?" "$mcp_enable" mcp_enable
case "$mcp_enable" in
[Nn]|[Nn]o|false|0)
mcp_enable="false"
;;
*)
mcp_enable="true"
;;
esac
local mcp_port="3001"
local mcp_server_mode="sse"
local mcp_server_url=""
local mcp_api_key=""
if [ "$mcp_enable" = "true" ]; then
ask "MCP server port" "$mcp_port" mcp_port
ask "MCP server mode (sse or stdio)" "$mcp_server_mode" mcp_server_mode
mcp_server_url="${url%:*}:${mcp_port}"
local mcp_auth="yes"
ask "Require MCP authentication?" "$mcp_auth" mcp_auth
case "$mcp_auth" in
[Nn]|[Nn]o|false|0) ;;
*)
mcp_api_key=$(gen_password)
info "Auto-generated MCP API key"
;;
esac
fi
# ---- Email ----
step "Email configuration (optional, needed for password reset)"
echo " Choose an email provider:"
echo " 1) Resend"
echo " 2) SMTP"
echo " 3) Skip"
echo ""
local email_choice="3"
ask "Choice" "$email_choice" email_choice
local resend_key="" smtp_host="" smtp_port="" smtp_user="" smtp_pass="" smtp_from=""
case "$email_choice" in
1)
ask_required "Resend API Key" resend_key
;;
2)
ask_required "SMTP Host" smtp_host
ask "SMTP Port" "587" smtp_port
ask_required "SMTP Username" smtp_user
ask_required "SMTP Password" smtp_pass
ask_required "SMTP From email" smtp_from
;;
esac
# ---- Ollama container ----
local enable_ollama="no"
if [ "$ai_choice" = "2" ]; then
enable_ollama="yes"
else
step "Ollama container (optional)"
ask "Also start Ollama container?" "$enable_ollama" enable_ollama
case "$enable_ollama" in
[Yy]|[Yy]es|true|1) enable_ollama="yes" ;;
*) enable_ollama="no" ;;
esac
fi
# ---- Web Search (optional) ----
step "Web Search configuration (optional)"
echo " Choose a web search provider:"
echo " 1) SearXNG (self-hosted)"
echo " 2) Brave Search"
echo " 3) Skip"
echo ""
local search_choice="3"
ask "Choice" "$search_choice" search_choice
local searxng_url="" brave_key=""
case "$search_choice" in
1)
ask_required "SearXNG URL" searxng_url
;;
2)
ask_required "Brave Search API Key" brave_key
;;
esac
# ---- Write .env.docker ----
step "Writing .env.docker"
cat > "$ENV_FILE" << EOF
# =============================================================================
# Memento - Docker Environment (auto-generated by deploy-docker.sh)
# =============================================================================
# Generated on $(date '+%Y-%m-%d %H:%M:%S')
# =============================================================================
# Core
NEXTAUTH_URL="${url}"
NEXTAUTH_SECRET="${secret}"
ADMIN_EMAIL="${admin_email}"
ALLOW_REGISTRATION="${allow_reg}"
# PostgreSQL
POSTGRES_PORT=${pg_port}
POSTGRES_DB=${pg_db}
POSTGRES_USER=${pg_user}
POSTGRES_PASSWORD="${pg_pass}"
EOF
# AI config
if [ "$ai_choice" != "5" ]; then
cat >> "$ENV_FILE" << EOF
# AI - Tags
AI_PROVIDER_TAGS=${ai_tags_provider}
AI_MODEL_TAGS="${ai_tags_model}"
# AI - Embeddings
AI_PROVIDER_EMBEDDING=${ai_embed_provider}
AI_MODEL_EMBEDDING="${ai_embed_model}"
# AI - Chat
AI_PROVIDER_CHAT=${ai_chat_provider}
AI_MODEL_CHAT="${ai_chat_model}"
EOF
if [ -n "$openai_key" ]; then
echo "OPENAI_API_KEY=\"${openai_key}\"" >> "$ENV_FILE"
fi
if [ -n "$custom_key" ]; then
echo "CUSTOM_OPENAI_API_KEY=\"${custom_key}\"" >> "$ENV_FILE"
echo "CUSTOM_OPENAI_BASE_URL=\"${custom_url}\"" >> "$ENV_FILE"
fi
if [ -n "$ollama_url" ]; then
echo "OLLAMA_BASE_URL=\"${ollama_url}\"" >> "$ENV_FILE"
fi
fi
# MCP config
if [ "$mcp_enable" = "true" ]; then
cat >> "$ENV_FILE" << EOF
# MCP Server
MCP_MODE="${mcp_server_mode}"
MCP_PORT="${mcp_port}"
MCP_SERVER_MODE="${mcp_server_mode}"
MCP_SERVER_URL="${mcp_server_url}"
EOF
if [ -n "$mcp_api_key" ]; then
echo "MCP_API_KEY=\"${mcp_api_key}\"" >> "$ENV_FILE"
fi
else
cat >> "$ENV_FILE" << EOF
# MCP Server (disabled)
MCP_SERVER_MODE="disabled"
EOF
fi
# Email config
if [ -n "$resend_key" ]; then
cat >> "$ENV_FILE" << EOF
# Email - Resend
RESEND_API_KEY="${resend_key}"
EOF
fi
if [ -n "$smtp_host" ]; then
cat >> "$ENV_FILE" << EOF
# Email - SMTP
SMTP_HOST="${smtp_host}"
SMTP_PORT="${smtp_port}"
SMTP_USER="${smtp_user}"
SMTP_PASS="${smtp_pass}"
SMTP_FROM="${smtp_from}"
EOF
fi
# Web Search
if [ -n "$searxng_url" ]; then
cat >> "$ENV_FILE" << EOF
# Web Search - SearXNG
WEB_SEARCH_PROVIDER="searxng"
SEARXNG_URL="${searxng_url}"
EOF
fi
if [ -n "$brave_key" ]; then
cat >> "$ENV_FILE" << EOF
# Web Search - Brave
WEB_SEARCH_PROVIDER="brave"
BRAVE_SEARCH_API_KEY="${brave_key}"
EOF
fi
ok ".env.docker created at $ENV_FILE"
echo ""
echo -e " ${BOLD}Configuration summary:${NC}"
echo " URL: $url"
echo " Admin email: $admin_email"
echo " Registration: $allow_reg"
echo " PostgreSQL user: $pg_user / db: $pg_db"
echo " AI provider: $([ "$ai_choice" = "5" ] && echo "skipped" || echo "$ai_tags_provider")"
echo " MCP server: $([ "$mcp_enable" = "true" ] && echo "enabled ($mcp_server_mode)" || echo "disabled")"
echo " Email: $([ "$email_choice" = "3" ] && echo "skipped" || echo "configured")"
echo " Ollama container: $enable_ollama"
echo " (sensitive values are hidden)"
}
# -----------------------------------------------------------
# Build and deploy
# -----------------------------------------------------------
deploy() {
[ -f "$ENV_FILE" ] || error ".env.docker not found. Run: $0 --env-only"
cd "$PROJECT_DIR"
# Determine Ollama profile
local ollama_profile=""
if grep -q 'OLLAMA_BASE_URL="http://ollama' "$ENV_FILE" 2>/dev/null || \
grep -q 'AI_PROVIDER_TAGS=ollama' "$ENV_FILE" 2>/dev/null; then
ollama_profile="--profile ollama"
fi
step "Building Docker containers..."
$COMPOSE_CMD build --parallel 2>&1
step "Starting containers..."
$COMPOSE_CMD up -d $ollama_profile 2>&1
step "Waiting for services to be healthy..."
local retries=0
local max_retries=45
while [ $retries -lt $max_retries ]; do
local unhealthy
unhealthy=$($COMPOSE_CMD ps --format '{{.Status}}' 2>/dev/null | grep -c -v "healthy\|Up" || true)
if [ "$unhealthy" -eq 0 ]; then
break
fi
retries=$((retries + 1))
sleep 2
printf "."
done
echo ""
if [ $retries -ge $max_retries ]; then
warn "Some containers may still be starting. Check status with: $0 --logs"
fi
step "Waiting for database migrations (handled by entrypoint)..."
# The docker-entrypoint.sh runs prisma migrate deploy automatically on every start.
# It handles: fresh DB, existing DB with migrations, and P3005 baseline recovery.
# No manual migration step needed here.
echo ""
echo -e "${GREEN}${BOLD}============================================${NC}"
echo -e "${GREEN}${BOLD} Memento is running!${NC}"
echo -e "${GREEN}${BOLD}============================================${NC}"
echo ""
$COMPOSE_CMD ps
echo ""
local app_url
app_url=$(grep '^NEXTAUTH_URL=' "$ENV_FILE" | cut -d= -f2 | tr -d '"')
local admin_email
admin_email=$(grep '^ADMIN_EMAIL=' "$ENV_FILE" | cut -d= -f2 | tr -d '"')
echo -e " ${BOLD}App:${NC} $app_url"
echo -e " ${BOLD}Admin:${NC} Register with $admin_email to get admin access"
echo -e " ${BOLD}MCP:${NC} $([ -n "$(grep '^MCP_SERVER_MODE=sse' "$ENV_FILE" 2>/dev/null)" ] && echo "$app_url:$(grep '^MCP_PORT=' "$ENV_FILE" | cut -d= -f2 | tr -d '"')/mcp" || echo "disabled")"
echo ""
echo -e " ${CYAN}Useful commands:${NC}"
echo " $0 --logs View logs"
echo " $0 --stop Stop containers"
echo " $0 --env-only Reconfigure .env.docker"
}
# -----------------------------------------------------------
# Stop containers
# -----------------------------------------------------------
stop_containers() {
cd "$PROJECT_DIR"
step "Stopping containers..."
$COMPOSE_CMD down
ok "Containers stopped"
}
# -----------------------------------------------------------
# Show logs
# -----------------------------------------------------------
show_logs() {
cd "$PROJECT_DIR"
$COMPOSE_CMD logs -f --tail=100
}
# -----------------------------------------------------------
# Main
# -----------------------------------------------------------
check_deps
ACTION="${1:---full}"
case "$ACTION" in
--env-only)
generate_env
;;
--build)
deploy
;;
--full)
generate_env
echo ""
deploy
;;
--stop)
stop_containers
;;
--logs)
show_logs
;;
*)
echo "Usage: $0 [--env-only | --build | --full | --stop | --logs]"
echo ""
echo " --env-only Generate .env.docker interactively"
echo " --build Build and start containers (requires existing .env.docker)"
echo " --full Generate .env.docker + build + deploy (default)"
echo " --stop Stop all containers"
echo " --logs Show container logs"
exit 1
;;
esac