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

582
scripts/deploy-local.sh Executable file
View File

@@ -0,0 +1,582 @@
#!/bin/bash
set -euo pipefail
# ============================================================
# Memento - Local Deploy Script (macOS / Linux)
# ============================================================
# Usage:
# ./scripts/deploy-local.sh # Full setup
# ./scripts/deploy-local.sh --env-only # Generate .env only
# ./scripts/deploy-local.sh --start # Start the app (dev or prod)
# ./scripts/deploy-local.sh --stop # Stop the app
# ./scripts/deploy-local.sh --migrate # Run database migrations
# ============================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$PROJECT_DIR/memento-note"
ENV_FILE="$APP_DIR/.env"
MCP_DIR="$PROJECT_DIR/mcp-server"
MCP_ENV_FILE="$MCP_DIR/.env"
# 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}"; }
# -----------------------------------------------------------
# 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
}
# -----------------------------------------------------------
# Ask helpers
# -----------------------------------------------------------
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
}
# -----------------------------------------------------------
# Check dependencies
# -----------------------------------------------------------
check_deps() {
step "Checking dependencies..."
local missing=()
# Node.js
if command -v node &>/dev/null; then
local node_version
node_version=$(node -v 2>/dev/null)
ok "Node.js $node_version"
else
missing+=("node")
fi
# npm
if command -v npm &>/dev/null; then
ok "npm $(npm -v 2>/dev/null)"
else
missing+=("npm")
fi
# PostgreSQL
if command -v psql &>/dev/null; then
ok "PostgreSQL client $(psql --version 2>/dev/null | head -1)"
else
missing+=("psql")
fi
# Check if PostgreSQL server is running
if command -v pg_isready &>/dev/null; then
if pg_isready &>/dev/null 2>&1; then
ok "PostgreSQL server is running"
else
warn "PostgreSQL server does not seem to be running."
echo " Try: brew services start postgresql (macOS) or sudo systemctl start postgresql (Linux)"
fi
fi
if [ ${#missing[@]} -gt 0 ]; then
echo ""
warn "Missing dependencies: ${missing[*]}"
echo ""
echo -e " ${BOLD}Installation instructions:${NC}"
echo ""
for dep in "${missing[@]}"; do
case "$dep" in
node|npm)
echo " Node.js + npm:"
echo " macOS: brew install node"
echo " Linux: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs"
echo " Or: https://nodejs.org/"
echo ""
;;
psql)
echo " PostgreSQL:"
echo " macOS: brew install postgresql@16 && brew services start postgresql@16"
echo " Linux: sudo apt install -y postgresql postgresql-contrib"
echo " Or: https://www.postgresql.org/download/"
echo ""
;;
esac
done
echo -ne " ${CYAN}?${NC} Continue anyway? [y/N]: "
read -r cont
[[ "$cont" != "y" && "$cont" != "Y" ]] && exit 1
fi
}
# -----------------------------------------------------------
# Generate .env interactively
# -----------------------------------------------------------
generate_env() {
step "Configuring Memento for local development"
if [ -f "$ENV_FILE" ]; then
warn ".env already exists at $ENV_FILE"
echo -ne " ${CYAN}?${NC} Overwrite? [y/N]: "
read -r confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { info "Keeping existing .env"; 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 ""
# ---- Database ----
step "Database configuration"
local db_host="localhost"
local db_port="5432"
local db_name="memento"
local db_user="memento"
local db_pass="memento"
ask "PostgreSQL host" "$db_host" db_host
ask "PostgreSQL port" "$db_port" db_port
ask "PostgreSQL database name" "$db_name" db_name
ask "PostgreSQL username" "$db_user" db_user
ask "PostgreSQL password" "$db_pass" db_pass
local db_url="postgresql://${db_user}:${db_pass}@${db_host}:${db_port}/${db_name}"
# Check DB connectivity
if command -v pg_isready &>/dev/null; then
step "Testing database connection..."
if PGPASSWORD="$db_pass" psql -h "$db_host" -p "$db_port" -U "$db_user" -d "$db_name" -c "SELECT 1" &>/dev/null 2>&1; then
ok "Database connection successful"
else
warn "Could not connect to database. It may not exist yet."
echo ""
echo " Create it with:"
echo " createdb $db_name"
echo " psql -c \"CREATE USER $db_user WITH PASSWORD '$db_pass';\""
echo " psql -c \"GRANT ALL PRIVILEGES ON DATABASE $db_name TO $db_user;\""
echo ""
echo -ne " ${CYAN}?${NC} Continue anyway? [Y/n]: "
read -r cont
[[ "$cont" == "n" || "$cont" == "N" ]] && exit 1
fi
fi
# ---- 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
case "$allow_reg" in
[Yy]|[Yy]es|true|1) allow_reg="true" ;;
*) allow_reg="false" ;;
esac
# ---- AI Provider ----
step "AI Provider configuration"
echo " Choose your AI provider:"
echo " 1) OpenAI"
echo " 2) Ollama (local)"
echo " 3) Custom OpenAI-compatible (OpenRouter, Groq, Together, etc.)"
echo " 4) Skip AI configuration"
echo ""
local ai_choice="4"
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://localhost:11434"
ask "Ollama base URL" "$ollama_url" ollama_url
;;
3)
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
;;
4)
info "Skipping AI configuration. You can configure it later in the admin panel."
;;
*)
error "Invalid choice"
;;
esac
# ---- MCP ----
step "MCP Server configuration (optional)"
local mcp_enable="no"
ask "Configure MCP server?" "$mcp_enable" mcp_enable
case "$mcp_enable" in
[Yy]|[Yy]es|true|1) mcp_enable="yes" ;;
*) mcp_enable="no" ;;
esac
local mcp_mode="sse" mcp_port="3001" mcp_server_url=""
if [ "$mcp_enable" = "yes" ]; then
ask "MCP mode (sse or stdio)" "$mcp_mode" mcp_mode
ask "MCP port" "$mcp_port" mcp_port
mcp_server_url="http://localhost:${mcp_port}"
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
# ---- Write .env ----
step "Writing .env"
cat > "$ENV_FILE" << EOF
# =============================================================================
# Memento - Local Environment (auto-generated by deploy-local.sh)
# =============================================================================
# Generated on $(date '+%Y-%m-%d %H:%M:%S')
# =============================================================================
# Core
DATABASE_URL="${db_url}"
NEXTAUTH_SECRET="${secret}"
NEXTAUTH_URL="${url}"
ADMIN_EMAIL="${admin_email}"
ALLOW_REGISTRATION="${allow_reg}"
EOF
# AI config
if [ "$ai_choice" != "4" ]; 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" = "yes" ]; then
cat >> "$ENV_FILE" << EOF
# MCP Server
MCP_SERVER_MODE="${mcp_mode}"
MCP_SERVER_URL="${mcp_server_url}"
EOF
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
ok ".env created at $ENV_FILE"
# Also generate MCP server .env if MCP enabled
if [ "$mcp_enable" = "yes" ]; then
cat > "$MCP_ENV_FILE" << EOF
# =============================================================================
# MCP Server - Local Environment (auto-generated by deploy-local.sh)
# =============================================================================
DATABASE_URL="${db_url}"
MCP_MODE="${mcp_mode}"
PORT="${mcp_port}"
APP_BASE_URL="${url}"
EOF
ok ".env created at $MCP_ENV_FILE"
fi
echo ""
echo -e " ${BOLD}Configuration summary:${NC}"
echo " URL: $url"
echo " Admin email: $admin_email"
echo " Database: $db_host:$db_port/$db_name"
echo " AI provider: $([ "$ai_choice" = "4" ] && echo "skipped" || echo "$ai_tags_provider")"
echo " MCP server: $([ "$mcp_enable" = "yes" ] && echo "enabled ($mcp_mode)" || echo "disabled")"
echo " Email: $([ "$email_choice" = "3" ] && echo "skipped" || echo "configured")"
}
# -----------------------------------------------------------
# Install dependencies
# -----------------------------------------------------------
install_deps() {
step "Installing dependencies..."
cd "$APP_DIR"
if [ ! -d "node_modules" ]; then
info "Running npm install..."
npm install
ok "Dependencies installed"
else
info "node_modules exists, checking for updates..."
npm install
fi
}
# -----------------------------------------------------------
# Run migrations
# -----------------------------------------------------------
run_migrations() {
[ -f "$ENV_FILE" ] || error ".env not found. Run: $0 --env-only"
step "Running database migrations..."
cd "$APP_DIR"
npx prisma migrate deploy 2>&1 || {
warn "migrate deploy failed, trying db push..."
npx prisma db push --skip-generate 2>&1 || error "Database migration failed"
}
ok "Database migrations complete"
}
# -----------------------------------------------------------
# Start the app
# -----------------------------------------------------------
start_app() {
[ -f "$ENV_FILE" ] || error ".env not found. Run: $0 --env-only"
cd "$APP_DIR"
echo ""
echo " Choose mode:"
echo " 1) Development (npm run dev, with hot reload)"
echo " 2) Production (npm run build + npm start)"
echo ""
local mode="1"
ask "Choice" "$mode" mode
case "$mode" in
2)
step "Building for production..."
npm run build
step "Starting in production mode..."
info "Starting server on http://localhost:3000"
info "Press Ctrl+C to stop"
echo ""
npm start
;;
*)
step "Starting in development mode..."
info "Starting dev server on http://localhost:3000"
info "Press Ctrl+C to stop"
echo ""
npm run dev
;;
esac
}
# -----------------------------------------------------------
# Full setup
# -----------------------------------------------------------
full_setup() {
check_deps
generate_env
install_deps
run_migrations
echo ""
echo -e "${GREEN}${BOLD}============================================${NC}"
echo -e "${GREEN}${BOLD} Setup complete!${NC}"
echo -e "${GREEN}${BOLD}============================================${NC}"
echo ""
local admin_email
admin_email=$(grep '^ADMIN_EMAIL=' "$ENV_FILE" | cut -d= -f2 | tr -d '"')
echo -e " ${BOLD}Next steps:${NC}"
echo " 1. Start the app: $0 --start"
echo " 2. Open: $(grep '^NEXTAUTH_URL=' "$ENV_FILE" | cut -d= -f2 | tr -d '"')"
echo -e " 3. Register with: ${BOLD}${admin_email}${NC}"
echo " 4. That account will automatically get ADMIN role"
echo ""
}
# -----------------------------------------------------------
# Main
# -----------------------------------------------------------
ACTION="${1:---full}"
case "$ACTION" in
--env-only)
check_deps
generate_env
;;
--start)
start_app
;;
--migrate)
run_migrations
;;
--install)
install_deps
;;
--stop)
warn "For local deployment, stop the server with Ctrl+C"
;;
--full)
full_setup
;;
*)
echo "Usage: $0 [--env-only | --start | --migrate | --install | --full | --stop]"
echo ""
echo " --env-only Generate .env interactively"
echo " --start Start the app (dev or prod mode)"
echo " --migrate Run database migrations"
echo " --install Install npm dependencies"
echo " --full Full setup: env + install + migrate (default)"
echo " --stop Reminder to use Ctrl+C"
exit 1
;;
esac