mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Improve docs search UX, dash cleanup, and colored modality tool icons (#338)
* feat(docs): improve search UX - Hide the 'Search by Pagefind' branding in the search dialog footer - Replace the 'No results found.' message shown before any query with a friendly hint (detected via empty-input :placeholder-shown state) - Tune placeholder, empty-state, and results-heading copy; show a few sub-section matches per result (pageResultCount) * docs: replace double-dash em-dash substitute with single dash Swept prose ' -- ' to ' - ' and numeric ranges (e.g. 2--20 to 2-20) across the documentation. CLI flags and code blocks left untouched. * fix(landing): show colored category icons on modality tool pages The /tools/<modality>/ pages rendered bare name+description cards with no icon or color. Port the colored, category-tinted icon card from /tools/ so modality pages match the main catalog (icon, tint, 'Learn more').
This commit is contained in:
@@ -49,7 +49,14 @@ export default defineConfig({
|
|||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [
|
plugins: [
|
||||||
pagefindPlugin(),
|
pagefindPlugin({
|
||||||
|
btnPlaceholder: "Search",
|
||||||
|
placeholder: "Search tools, guides, and the API…",
|
||||||
|
emptyText: "No matches found. Try a different term or check the spelling.",
|
||||||
|
heading: "{{searchResult}} results",
|
||||||
|
// Show a few sub-section matches per page so deep-linked headings surface.
|
||||||
|
pageResultCount: 3,
|
||||||
|
}),
|
||||||
llmstxt({
|
llmstxt({
|
||||||
domain: "https://docs.snapotter.com",
|
domain: "https://docs.snapotter.com",
|
||||||
customLLMsTxtTemplate: `# {title}
|
customLLMsTxtTemplate: `# {title}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Layout from "./Layout.vue";
|
|||||||
import "./vars.css";
|
import "./vars.css";
|
||||||
import "./fonts.css";
|
import "./fonts.css";
|
||||||
import "./github-stars.css";
|
import "./github-stars.css";
|
||||||
|
import "./search.css";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: DefaultTheme,
|
extends: DefaultTheme,
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Customizations for the Pagefind-powered search dialog
|
||||||
|
* (vitepress-plugin-pagefind, rendered via vue-command-palette `algolia` theme).
|
||||||
|
*
|
||||||
|
* Two goals:
|
||||||
|
* 1. Hide the "Search by Pagefind" branding in the dialog footer.
|
||||||
|
* 2. Replace the misleading "No results found." text shown the instant the
|
||||||
|
* dialog opens (before the user has typed anything) with a friendly hint.
|
||||||
|
* We can't change the component's markup, so we detect an empty query via
|
||||||
|
* the input's `:placeholder-shown` state and restyle the empty slot.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 1. Drop the Pagefind logo/attribution in the footer.
|
||||||
|
The keyboard-shortcut hints (to select / navigate / close) stay. */
|
||||||
|
.algolia .command-palette-logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2a. Before anything is typed (input still shows its placeholder), turn the
|
||||||
|
empty slot into a centered hint with an icon instead of "No results found." */
|
||||||
|
.algolia
|
||||||
|
[command-dialog-wrapper]:has([command-input]:placeholder-shown)
|
||||||
|
[command-empty=""] {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
height: auto;
|
||||||
|
min-height: 160px;
|
||||||
|
/* Hide the component's default empty text without removing it from the DOM. */
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.algolia
|
||||||
|
[command-dialog-wrapper]:has([command-input]:placeholder-shown)
|
||||||
|
[command-empty=""]::before {
|
||||||
|
content: "";
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
background-color: var(--vp-c-brand-1);
|
||||||
|
/* magnifying-glass mask so the icon picks up the brand (Otter Orange) */
|
||||||
|
-webkit-mask: var(--search-hint-icon) center / contain no-repeat;
|
||||||
|
mask: var(--search-hint-icon) center / contain no-repeat;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.algolia
|
||||||
|
[command-dialog-wrapper]:has([command-input]:placeholder-shown)
|
||||||
|
[command-empty=""]::after {
|
||||||
|
content: "Search 157 tools, guides, and the API reference";
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
max-width: 320px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--search-hint-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z' stroke='black' fill='none' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
+2
-2
@@ -6,7 +6,7 @@ description: AI engine reference with all local ML tools. Background removal, up
|
|||||||
|
|
||||||
The `@snapotter/ai` package bridges Node.js to a **persistent Python sidecar** for all ML operations. The dispatcher process stays alive between requests for fast warm-start performance. GPU is auto-detected at startup and used when available.
|
The `@snapotter/ai` package bridges Node.js to a **persistent Python sidecar** for all ML operations. The dispatcher process stays alive between requests for fast warm-start performance. GPU is auto-detected at startup and used when available.
|
||||||
|
|
||||||
19 Python sidecar AI tools across four modalities (image, audio, video, document), plus 2 tools with optional AI capabilities. All models run locally -- no internet required after initial model download.
|
19 Python sidecar AI tools across four modalities (image, audio, video, document), plus 2 tools with optional AI capabilities. All models run locally - no internet required after initial model download.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -406,7 +406,7 @@ An additional analysis endpoint is available at `POST /api/v1/tools/image/image-
|
|||||||
### Content-Aware Resize (Seam Carving)
|
### Content-Aware Resize (Seam Carving)
|
||||||
|
|
||||||
**Tool route:** `content-aware-resize`
|
**Tool route:** `content-aware-resize`
|
||||||
**Engine:** Go `caire` binary (not Python -- no GPU benefit)
|
**Engine:** Go `caire` binary (not Python - no GPU benefit)
|
||||||
|
|
||||||
Intelligently resizes images by removing low-energy seams, preserving important content.
|
Intelligently resizes images by removing low-energy seams, preserving important content.
|
||||||
|
|
||||||
|
|||||||
+23
-23
@@ -65,11 +65,11 @@ Live demo, per-tool landing pages, and a batch of polish fixes.
|
|||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
- **Live demo** -- [demo.snapotter.com](https://demo.snapotter.com) lets people try SnapOtter without installing anything.
|
- **Live demo** - [demo.snapotter.com](https://demo.snapotter.com) lets people try SnapOtter without installing anything.
|
||||||
- **Tools index page** -- Browse all 50+ tools at `/tools` with search and category filters.
|
- **Tools index page** - Browse all 50+ tools at `/tools` with search and category filters.
|
||||||
- **50+ SEO landing pages** -- Every tool now has a dedicated landing page with FAQs, use cases, and comparison tables.
|
- **50+ SEO landing pages** - Every tool now has a dedicated landing page with FAQs, use cases, and comparison tables.
|
||||||
- **Background preview** -- Before-after slider shows a checkered background behind transparent images.
|
- **Background preview** - Before-after slider shows a checkered background behind transparent images.
|
||||||
- **Strong password generator** -- One-click button in the Add Members form.
|
- **Strong password generator** - One-click button in the Add Members form.
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
@@ -110,28 +110,28 @@ Five new tools, a full image editor, SSO login, 20 languages. Probably should ha
|
|||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
- **Image editor** -- Layers, brushes, shapes, adjustments, filters, curves, keyboard shortcuts. Runs in your browser, processes on your hardware.
|
- **Image editor** - Layers, brushes, shapes, adjustments, filters, curves, keyboard shortcuts. Runs in your browser, processes on your hardware.
|
||||||
- **OIDC / SSO authentication** -- Login with Google, GitHub, Okta, or any OpenID Connect provider. Set a few env vars and your team uses their existing accounts.
|
- **OIDC / SSO authentication** - Login with Google, GitHub, Okta, or any OpenID Connect provider. Set a few env vars and your team uses their existing accounts.
|
||||||
- **Meme generator** -- 100 built-in templates with text rendering via opentype.js. Or upload your own image.
|
- **Meme generator** - 100 built-in templates with text rendering via opentype.js. Or upload your own image.
|
||||||
- **Beautify** -- Drop a screenshot in, get a polished image out. Device frames (macOS, Windows, browser), shadows, gradients, social media presets.
|
- **Beautify** - Drop a screenshot in, get a polished image out. Device frames (macOS, Windows, browser), shadows, gradients, social media presets.
|
||||||
- **Color blindness simulation** -- Preview how images look with protanopia, deuteranopia, tritanopia, and other color vision deficiencies.
|
- **Color blindness simulation** - Preview how images look with protanopia, deuteranopia, tritanopia, and other color vision deficiencies.
|
||||||
- **PNG transparency fixer** -- Detects fake-transparent PNGs and fixes them with BiRefNet HR-matting. Optional watermark removal via LaMa inpainting.
|
- **PNG transparency fixer** - Detects fake-transparent PNGs and fixes them with BiRefNet HR-matting. Optional watermark removal via LaMa inpainting.
|
||||||
- **AI canvas expand** -- Extend image boundaries with AI fill. Three quality tiers (fast, balanced, quality) depending on how much GPU time you want to trade.
|
- **AI canvas expand** - Extend image boundaries with AI fill. Three quality tiers (fast, balanced, quality) depending on how much GPU time you want to trade.
|
||||||
- **20 languages** -- Arabic, Chinese (Simplified/Traditional), Czech, Dutch, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Thai, Turkish, Ukrainian, Vietnamese. RTL works for Arabic.
|
- **20 languages** - Arabic, Chinese (Simplified/Traditional), Czech, Dutch, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Thai, Turkish, Ukrainian, Vietnamese. RTL works for Arabic.
|
||||||
- **URL import** -- Paste URLs into the dropzone or bulk-import from a list. Server-side fetch with SSRF protection.
|
- **URL import** - Paste URLs into the dropzone or bulk-import from a list. Server-side fetch with SSRF protection.
|
||||||
- **Multi-file eraser** -- Draw erase masks across multiple images, process them all with one click. Strokes persist per-image.
|
- **Multi-file eraser** - Draw erase masks across multiple images, process them all with one click. Strokes persist per-image.
|
||||||
- **Pipeline import/export** -- Save tool chains as JSON, share them with others.
|
- **Pipeline import/export** - Save tool chains as JSON, share them with others.
|
||||||
- **17 new camera RAW formats** via exiftool, plus QOI, JP2, EPS, DDS, CUR, DPX, FITS, PPM/PGM/PBM, SVGZ, and APNG input. New output codecs for BMP, ICO, JP2, QOI. AVIF, TIFF, GIF, JXL, and PSD export recovered from a previously lost branch.
|
- **17 new camera RAW formats** via exiftool, plus QOI, JP2, EPS, DDS, CUR, DPX, FITS, PPM/PGM/PBM, SVGZ, and APNG input. New output codecs for BMP, ICO, JP2, QOI. AVIF, TIFF, GIF, JXL, and PSD export recovered from a previously lost branch.
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
- **Image enhancement** -- Replaced the old pipeline with CLAHE + normalise + gamma. New Deep Enhance toggle uses the AI model for more aggressive results.
|
- **Image enhancement** - Replaced the old pipeline with CLAHE + normalise + gamma. New Deep Enhance toggle uses the AI model for more aggressive results.
|
||||||
- **Restore photo** -- Scratch detection rewritten with 8-angle Otsu filtering. LaMa inpainting now runs at native resolution.
|
- **Restore photo** - Scratch detection rewritten with 8-angle Otsu filtering. LaMa inpainting now runs at native resolution.
|
||||||
- **Exotic formats everywhere** -- OCR, image-to-PDF, favicon generator, composition, stitch, and vectorize all decode HEIC, RAW, PSD now.
|
- **Exotic formats everywhere** - OCR, image-to-PDF, favicon generator, composition, stitch, and vectorize all decode HEIC, RAW, PSD now.
|
||||||
- **Compress** -- Target-size tolerance tightened from 5% to 1%. Target size is the default mode. Added stepper buttons and KB/MB unit selector.
|
- **Compress** - Target-size tolerance tightened from 5% to 1%. Target size is the default mode. Added stepper buttons and KB/MB unit selector.
|
||||||
- **Sentry cleanup** -- 644 non-actionable events filtered. Real errors now handled properly.
|
- **Sentry cleanup** - 644 non-actionable events filtered. Real errors now handled properly.
|
||||||
- **GPU detection** -- Better diagnostics for containers where CUDA is present but nvidia-smi is not.
|
- **GPU detection** - Better diagnostics for containers where CUDA is present but nvidia-smi is not.
|
||||||
- **Auth-disabled mode** -- Anonymous user is seeded in the DB with admin role. API keys, pipelines, and user files no longer break on FK constraints.
|
- **Auth-disabled mode** - Anonymous user is seeded in the DB with admin role. API keys, pipelines, and user files no longer break on FK constraints.
|
||||||
- **2,705+ new tests** across unit, integration, and E2E.
|
- **2,705+ new tests** across unit, integration, and E2E.
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|||||||
@@ -51,14 +51,14 @@ Shared TypeScript types, constants (like `APP_VERSION` and tool definitions), an
|
|||||||
|
|
||||||
A Fastify v5 server exposing 157 tool routes across five modalities (image, video, audio, document, data) that handles:
|
A Fastify v5 server exposing 157 tool routes across five modalities (image, video, audio, document, data) that handles:
|
||||||
- File uploads, temporary workspace management, and persistent file storage
|
- File uploads, temporary workspace management, and persistent file storage
|
||||||
- User file library with version chains (`user_files` table) -- each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page
|
- User file library with version chains (`user_files` table) - each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page
|
||||||
- Tool execution (routes each tool request to the image engine or AI bridge)
|
- Tool execution (routes each tool request to the image engine or AI bridge)
|
||||||
- Pipeline orchestration (chaining multiple tools sequentially)
|
- Pipeline orchestration (chaining multiple tools sequentially)
|
||||||
- Batch processing with concurrency control via BullMQ job queues (pools: image, media, ai, docs, system)
|
- Batch processing with concurrency control via BullMQ job queues (pools: image, media, ai, docs, system)
|
||||||
- User authentication, RBAC (admin/user roles with a full permission set), API key management, and rate limiting
|
- User authentication, RBAC (admin/user roles with a full permission set), API key management, and rate limiting
|
||||||
- Teams management -- admin-only CRUD; users are assigned to a team via the `team` field on their profile
|
- Teams management - admin-only CRUD; users are assigned to a team via the `team` field on their profile
|
||||||
- Runtime settings -- a key-value store in the `settings` table that controls `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, and other operational knobs without redeploying
|
- Runtime settings - a key-value store in the `settings` table that controls `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, and other operational knobs without redeploying
|
||||||
- Custom branding -- logo upload endpoint; the uploaded image is stored at `data/branding/logo.png` and served to the frontend
|
- Custom branding - logo upload endpoint; the uploaded image is stored at `data/branding/logo.png` and served to the frontend
|
||||||
- Swagger/OpenAPI documentation at `/api/docs`
|
- Swagger/OpenAPI documentation at `/api/docs`
|
||||||
- Serving the built frontend as a SPA in production
|
- Serving the built frontend as a SPA in production
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ volumes:
|
|||||||
|
|
||||||
The Docker Compose stack uses four volumes:
|
The Docker Compose stack uses four volumes:
|
||||||
|
|
||||||
- `/data` (app) -- AI models, Python venv, and user files. Mount this to keep uploaded files and installed AI bundles across restarts.
|
- `/data` (app) - AI models, Python venv, and user files. Mount this to keep uploaded files and installed AI bundles across restarts.
|
||||||
- `/tmp/workspace` (app) -- Temporary storage for files being processed. This can be ephemeral, but mounting it avoids filling up the container's writable layer.
|
- `/tmp/workspace` (app) - Temporary storage for files being processed. This can be ephemeral, but mounting it avoids filling up the container's writable layer.
|
||||||
- `SnapOtter-pgdata` (postgres) -- PostgreSQL data directory. This holds all relational data (users, settings, pipelines, jobs, audit log). Back up via `pg_dump` or volume snapshot.
|
- `SnapOtter-pgdata` (postgres) - PostgreSQL data directory. This holds all relational data (users, settings, pipelines, jobs, audit log). Back up via `pg_dump` or volume snapshot.
|
||||||
- `SnapOtter-redisdata` (redis) -- Redis append-only file for durable job queues.
|
- `SnapOtter-redisdata` (redis) - Redis append-only file for durable job queues.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ See [Docker Image](./docker-tags) for GPU setup, Docker Compose examples, and ve
|
|||||||
## Quick Start (CPU)
|
## Quick Start (CPU)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# docker-compose.yml -- Copy this file and run: docker compose up -d
|
# docker-compose.yml - Copy this file and run: docker compose up -d
|
||||||
services:
|
services:
|
||||||
SnapOtter:
|
SnapOtter:
|
||||||
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
|
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
|
||||||
@@ -94,7 +94,7 @@ services:
|
|||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
SnapOtter-data: # Named volume -- Docker manages permissions automatically
|
SnapOtter-data: # Named volume - Docker manages permissions automatically
|
||||||
SnapOtter-workspace:
|
SnapOtter-workspace:
|
||||||
SnapOtter-pgdata:
|
SnapOtter-pgdata:
|
||||||
SnapOtter-redisdata:
|
SnapOtter-redisdata:
|
||||||
@@ -113,7 +113,7 @@ The app is then available at `http://localhost:1349`.
|
|||||||
For NVIDIA GPU acceleration on AI tools (background removal, upscaling, face enhancement, OCR):
|
For NVIDIA GPU acceleration on AI tools (background removal, upscaling, face enhancement, OCR):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# docker-compose-gpu.yml -- Requires: NVIDIA GPU + nvidia-container-toolkit
|
# docker-compose-gpu.yml - Requires: NVIDIA GPU + nvidia-container-toolkit
|
||||||
# Install toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
# Install toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
||||||
services:
|
services:
|
||||||
SnapOtter:
|
SnapOtter:
|
||||||
@@ -511,9 +511,9 @@ Note: Cloudflare has a 100 MB upload limit on free plans. Set `MAX_UPLOAD_SIZE_M
|
|||||||
|
|
||||||
The GitHub repository has three workflows:
|
The GitHub repository has three workflows:
|
||||||
|
|
||||||
- **ci.yml** -- Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing).
|
- **ci.yml** - Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing).
|
||||||
- **release.yml** -- Triggered manually via `workflow_dispatch`. Runs semantic-release to create a version tag and GitHub release, then builds a multi-arch Docker image (amd64 + arm64) and pushes to Docker Hub (`snapotter/snapotter`) and GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter`).
|
- **release.yml** - Triggered manually via `workflow_dispatch`. Runs semantic-release to create a version tag and GitHub release, then builds a multi-arch Docker image (amd64 + arm64) and pushes to Docker Hub (`snapotter/snapotter`) and GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter`).
|
||||||
- **deploy-docs.yml** -- Builds this documentation site and deploys it to Cloudflare Pages on push to `main`.
|
- **deploy-docs.yml** - Builds this documentation site and deploys it to Cloudflare Pages on push to `main`.
|
||||||
|
|
||||||
To create a release, go to **Actions > Release > Run workflow** in the GitHub UI, or run:
|
To create a release, go to **Actions > Release > Run workflow** in the GitHub UI, or run:
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ pnpm test:coverage # tests with coverage report
|
|||||||
|
|
||||||
## Database
|
## Database
|
||||||
|
|
||||||
PostgreSQL 17 via Drizzle ORM (pg-core). Local dev requires Postgres and Redis running -- start them with:
|
PostgreSQL 17 via Drizzle ORM (pg-core). Local dev requires Postgres and Redis running - start them with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose -f docker-compose.dev.yml up -d
|
docker compose -f docker-compose.dev.yml up -d
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Install SnapOtter with Docker in one command. Includes Docker Compo
|
|||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
::: tip Try before installing
|
::: tip Try before installing
|
||||||
Explore the full UI at [demo.snapotter.com](https://demo.snapotter.com) -- no signup or install required.
|
Explore the full UI at [demo.snapotter.com](https://demo.snapotter.com) - no signup or install required.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ The response contains the token. Save it immediately; it cannot be retrieved aga
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"token": "a1b2c3d4e5f6...",
|
"token": "a1b2c3d4e5f6...",
|
||||||
"message": "Save this token -- it cannot be retrieved again"
|
"message": "Save this token - it cannot be retrieved again"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ services:
|
|||||||
|
|
||||||
# --- Resource limits ---
|
# --- Resource limits ---
|
||||||
mem_limit: 6g # Prevents runaway memory from crashing the host
|
mem_limit: 6g # Prevents runaway memory from crashing the host
|
||||||
memswap_limit: 6g # No swap -- fail fast instead of degrading the host
|
memswap_limit: 6g # No swap - fail fast instead of degrading the host
|
||||||
cpus: 4 # Cap CPU usage to 4 cores
|
cpus: 4 # Cap CPU usage to 4 cores
|
||||||
pids_limit: 512 # Prevents fork bombs
|
pids_limit: 512 # Prevents fork bombs
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ The only exception is **AI model downloads**: when a user installs an AI feature
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Air-gapped (no AI) | Block all outbound traffic from the container |
|
| Air-gapped (no AI) | Block all outbound traffic from the container |
|
||||||
| AI bundles needed | Allow HTTPS to `github.com`, `objects.githubusercontent.com`, `pypi.org`, `files.pythonhosted.org` during install, then block |
|
| AI bundles needed | Allow HTTPS to `github.com`, `objects.githubusercontent.com`, `pypi.org`, `files.pythonhosted.org` during install, then block |
|
||||||
| After AI install | Block all outbound traffic -- models are cached locally |
|
| After AI install | Block all outbound traffic - models are cached locally |
|
||||||
|
|
||||||
For reverse proxy configuration (Nginx, Traefik, Caddy, Cloudflare Tunnels), see the [Deployment guide](/guide/deployment#reverse-proxy).
|
For reverse proxy configuration (Nginx, Traefik, Caddy, Cloudflare Tunnels), see the [Deployment guide](/guide/deployment#reverse-proxy).
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ SnapOtter ships with 21 languages out of the box. The i18n system uses a lightwe
|
|||||||
|
|
||||||
SnapOtter uses a three-tier resolution order:
|
SnapOtter uses a three-tier resolution order:
|
||||||
|
|
||||||
1. **User preference** -- stored in `localStorage("snapotter-locale")` and synced to user settings when authenticated
|
1. **User preference** - stored in `localStorage("snapotter-locale")` and synced to user settings when authenticated
|
||||||
2. **Browser auto-detect** -- walks the `navigator.languages` array with BCP 47 prefix matching
|
2. **Browser auto-detect** - walks the `navigator.languages` array with BCP 47 prefix matching
|
||||||
3. **Instance default** -- the admin's `DEFAULT_LOCALE` env var (fetched from `GET /api/v1/config/locale`)
|
3. **Instance default** - the admin's `DEFAULT_LOCALE` env var (fetched from `GET /api/v1/config/locale`)
|
||||||
4. **English fallback** -- always available
|
4. **English fallback** - always available
|
||||||
|
|
||||||
Users can change language from:
|
Users can change language from:
|
||||||
- The **footer Globe selector** (desktop, always visible)
|
- The **footer Globe selector** (desktop, always visible)
|
||||||
@@ -152,7 +152,7 @@ Open a PR against `main` with a title like `feat(i18n): add Swedish translation`
|
|||||||
When adding a new feature that needs new UI strings:
|
When adding a new feature that needs new UI strings:
|
||||||
|
|
||||||
1. Add the new keys to `en.ts` first (the reference file)
|
1. Add the new keys to `en.ts` first (the reference file)
|
||||||
2. Run `pnpm typecheck` -- every locale file will fail if missing the new key
|
2. Run `pnpm typecheck` - every locale file will fail if missing the new key
|
||||||
3. Add the new key to all locale files (use English as a temporary fallback)
|
3. Add the new key to all locale files (use English as a temporary fallback)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ The default admin is required to change their password on first login.
|
|||||||
|
|
||||||
Users can authenticate through several methods:
|
Users can authenticate through several methods:
|
||||||
|
|
||||||
- **Local** -- username and password stored in the SnapOtter database
|
- **Local** - username and password stored in the SnapOtter database
|
||||||
- **OIDC** -- any OpenID Connect provider (see [OIDC / SSO](/guide/oidc))
|
- **OIDC** - any OpenID Connect provider (see [OIDC / SSO](/guide/oidc))
|
||||||
- **SAML** -- SAML 2.0 identity providers (see [SAML SSO](/guide/saml))
|
- **SAML** - SAML 2.0 identity providers (see [SAML SSO](/guide/saml))
|
||||||
- **SCIM** -- automated provisioning from an identity provider (see [SCIM Provisioning](/guide/scim))
|
- **SCIM** - automated provisioning from an identity provider (see [SCIM Provisioning](/guide/scim))
|
||||||
|
|
||||||
### Disabling authentication
|
### Disabling authentication
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ Accepts multipart form data with a CSV or JSON file and a JSON `settings` field.
|
|||||||
|-----------|------|----------|---------|-------------|
|
|-----------|------|----------|---------|-------------|
|
||||||
| kind | string | No | `"bar"` | Chart type: `bar`, `line`, `pie` |
|
| kind | string | No | `"bar"` | Chart type: `bar`, `line`, `pie` |
|
||||||
| title | string | No | - | Chart title (max 120 characters) |
|
| title | string | No | - | Chart title (max 120 characters) |
|
||||||
| width | integer | No | `960` | Chart width in pixels (320--2048) |
|
| width | integer | No | `960` | Chart width in pixels (320-2048) |
|
||||||
| height | integer | No | `540` | Chart height in pixels (240--1536) |
|
| height | integer | No | `540` | Chart height in pixels (240-1536) |
|
||||||
|
|
||||||
## Example Request
|
## Example Request
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Accepts multipart form data with two or more files. No settings field is require
|
|||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
This tool has no configurable parameters. Upload 2--50 files of any type to bundle.
|
This tool has no configurable parameters. Upload 2-50 files of any type to bundle.
|
||||||
|
|
||||||
## Example Request
|
## Example Request
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Accepts multipart form data with two or more CSV files. No settings field is req
|
|||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
This tool has no configurable parameters. Upload 2--20 CSV or TSV files with matching column headers.
|
This tool has no configurable parameters. Upload 2-20 CSV or TSV files with matching column headers.
|
||||||
|
|
||||||
## Example Request
|
## Example Request
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Accepts multipart form data with a CSV file and a JSON `settings` field.
|
|||||||
|
|
||||||
| Parameter | Type | Required | Default | Description |
|
| Parameter | Type | Required | Default | Description |
|
||||||
|-----------|------|----------|---------|-------------|
|
|-----------|------|----------|---------|-------------|
|
||||||
| rowsPerFile | integer | No | `1000` | Number of data rows per output file (1--1,000,000) |
|
| rowsPerFile | integer | No | `1000` | Number of data rows per output file (1-1,000,000) |
|
||||||
| keepHeader | boolean | No | `true` | Repeat the header row in each output file |
|
| keepHeader | boolean | No | `true` | Repeat the header row in each output file |
|
||||||
|
|
||||||
## Example Request
|
## Example Request
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Smart subject-aware, face-aware, or trim-based cropping. Uses Sharp's attention/
|
|||||||
|
|
||||||
**Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE)
|
**Processing:** Asynchronous (returns 202, poll `/api/v1/jobs/{jobId}/progress` for status via SSE)
|
||||||
|
|
||||||
**Model bundle:** `face-detection` (200-300 MB) -- required only for `face` mode
|
**Model bundle:** `face-detection` (200-300 MB) - required only for `face` mode
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { SECTIONS, TOOLS, toolSection } from "@snapotter/shared";
|
import { CATEGORIES, SECTIONS, TOOLS, toolSection } from "@snapotter/shared";
|
||||||
|
import * as lucideIcons from "lucide";
|
||||||
import Footer from "@/components/Footer.astro";
|
import Footer from "@/components/Footer.astro";
|
||||||
import Navbar from "@/components/Navbar.astro";
|
import Navbar from "@/components/Navbar.astro";
|
||||||
import Base from "@/layouts/Base.astro";
|
import Base from "@/layouts/Base.astro";
|
||||||
@@ -11,7 +12,22 @@ export function getStaticPaths() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderIcon(iconName: string): string {
|
||||||
|
const iconData = (lucideIcons as Record<string, unknown>)[iconName];
|
||||||
|
if (!iconData || !Array.isArray(iconData))
|
||||||
|
return '<rect width="14" height="14" x="5" y="5" rx="2" />';
|
||||||
|
return iconData
|
||||||
|
.map(([tag, attrs]: [string, Record<string, string>]) => {
|
||||||
|
const attrStr = Object.entries(attrs)
|
||||||
|
.map(([k, v]) => `${k}="${v}"`)
|
||||||
|
.join(" ");
|
||||||
|
return `<${tag} ${attrStr}/>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
const { section, tools } = Astro.props;
|
const { section, tools } = Astro.props;
|
||||||
|
const categoryMap = new Map(CATEGORIES.map((c) => [c.id, c]));
|
||||||
const canonical = `https://snapotter.com/tools/${section.id}/`;
|
const canonical = `https://snapotter.com/tools/${section.id}/`;
|
||||||
const title = `${section.name} Tools | SnapOtter`;
|
const title = `${section.name} Tools | SnapOtter`;
|
||||||
---
|
---
|
||||||
@@ -41,18 +57,45 @@ const title = `${section.name} Tools | SnapOtter`;
|
|||||||
<section class="mx-auto max-w-6xl px-6 pt-8 pb-16">
|
<section class="mx-auto max-w-6xl px-6 pt-8 pb-16">
|
||||||
<div class="reveal">
|
<div class="reveal">
|
||||||
<h1 class="font-display text-3xl font-bold">{section.name} Tools</h1>
|
<h1 class="font-display text-3xl font-bold">{section.name} Tools</h1>
|
||||||
<ul class="mt-8 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
<ul class="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{tools.map((tool) => (
|
{tools.map((tool) => {
|
||||||
<li>
|
const cat = categoryMap.get(tool.category);
|
||||||
<a
|
const color = cat?.color ?? "#E07832";
|
||||||
class="card-hover block rounded-lg border border-border p-4"
|
return (
|
||||||
href={`/tools/${section.id}/${tool.id}/`}
|
<li>
|
||||||
>
|
<a
|
||||||
<span class="font-medium">{tool.name}</span>
|
class="tool-item group flex items-start gap-4 rounded-xl border border-border bg-surface p-5 transition-all hover:border-primary hover:shadow-md"
|
||||||
<span class="mt-1 block text-sm text-muted">{tool.description}</span>
|
href={`/tools/${section.id}/${tool.id}/`}
|
||||||
</a>
|
>
|
||||||
</li>
|
<div
|
||||||
))}
|
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg"
|
||||||
|
style={`background-color: ${color}14`}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-[18px] w-[18px]"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
set:html={renderIcon(tool.icon)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<span class="text-sm font-bold">{tool.name}</span>
|
||||||
|
<p class="mt-1 line-clamp-2 text-xs leading-relaxed text-muted">{tool.description}</p>
|
||||||
|
<span class="mt-2 inline-flex items-center gap-1 text-xs font-medium text-primary transition-all group-hover:gap-2">
|
||||||
|
Learn more
|
||||||
|
<svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M5 12h14" /><path d="m12 5 7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user