fix(ui): clean up settings, automate page, fullscreen logo, and README

- README: simplify to match Stirling-PDF style, add dashboard screenshot
- Settings: remove unsupported languages from dropdown, remove unused
  experimental tools toggle
- Automate: remove hardcoded template pipelines from sidebar
- Fullscreen: add GemLogo icon to header
This commit is contained in:
Siddharth Kumar Sah
2026-03-27 13:50:04 +08:00
parent e6dc7b0a18
commit e3a8558134
5 changed files with 36 additions and 240 deletions
+23 -97
View File
@@ -1,8 +1,11 @@
<p align="center">
<h1 align="center">Stirling Image</h1>
<p align="center">The Open-Source Image Processing Platform</p>
<img src="apps/web/public/logo-192.png" width="80" alt="Stirling Image logo">
</p>
<h1 align="center">Stirling Image - The Open-Source Image Processing Platform</h1>
Stirling Image is a powerful, open-source image processing platform. Self-host it in a single Docker container with a private API. Resize, compress, convert, remove backgrounds, upscale, run OCR, and more — without sending images to external services.
<p align="center">
<a href="https://github.com/siddharthksah/Stirling-Image/pkgs/container/stirling-image"><img src="https://img.shields.io/badge/Docker-ghcr.io-blue?logo=docker" alt="Docker"></a>
<a href="https://github.com/siddharthksah/Stirling-Image/actions"><img src="https://img.shields.io/github/actions/workflow/status/siddharthksah/Stirling-Image/ci.yml?label=CI" alt="CI"></a>
@@ -10,114 +13,37 @@
<a href="https://github.com/siddharthksah/Stirling-Image/stargazers"><img src="https://img.shields.io/github/stars/siddharthksah/Stirling-Image?style=social" alt="Stars"></a>
</p>
---
![Stirling Image - Dashboard](images/dashboard.png)
Self-hosted image processing with 33+ tools in a single Docker container. Resize, compress, convert, watermark, remove backgrounds, run OCR, and more. Nothing leaves your server.
## Key Capabilities
Inspired by [Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF), built for images.
- **33+ image tools** — Resize, crop, compress, convert, watermark, OCR, and more.
- **AI-powered** — Background removal, upscaling, object erasing, face blurring — all running locally.
- **Automation & workflows** — Chain tools into reusable pipelines. Batch process up to 200 images at once.
- **Developer platform** — REST API for every tool. Swagger docs included.
- **Your data stays yours** — No telemetry, no tracking, no cloud. Single Docker container on any architecture.
<!-- TODO: Add screenshot here -->
<!-- ![Dashboard](docs/screenshot-dashboard.png) -->
For a full feature list, see the docs: **https://siddharthksah.github.io/Stirling-Image/**
## Quick start
## Quick Start
```bash
docker run -d -p 1349:1349 -v ./data:/data ghcr.io/siddharthksah/stirling-image:latest
docker run -d -p 1349:1349 -v stirling-data:/data ghcr.io/siddharthksah/stirling-image:latest
```
Open [http://localhost:1349](http://localhost:1349). Default login is `admin` / `admin`.
Then open: http://localhost:1349. Default login: `admin` / `admin`.
## What it does
For full installation options, see the [Getting Started Guide](https://siddharthksah.github.io/Stirling-Image/guide/getting-started).
- **33+ image tools** in one place — resize, crop, rotate, compress, convert, watermark, color adjustments, and the rest.
## Resources
- **AI tools that run locally** — background removal (rembg), upscaling (Real-ESRGAN), OCR (PaddleOCR), face blurring (MediaPipe), object erasing (LaMa). No external API calls.
- [**Documentation**](https://siddharthksah.github.io/Stirling-Image/)
- [**API Docs**](https://siddharthksah.github.io/Stirling-Image/api/rest)
- [**Configuration**](https://siddharthksah.github.io/Stirling-Image/guide/configuration)
- **Your hardware, your data** — no telemetry, no tracking, no cloud. Files stay on your machine.
## Support
- **Batch processing** — drop up to 200 images, apply any tool, get a ZIP back. Configurable concurrency.
- **Pipelines** — chain tools into reusable workflows (resize, then compress, then convert to WebP, then strip metadata). Save them and rerun later.
- **REST API** — every tool is exposed at `/api/v1/tools/:toolId`. Swagger docs at `/api/docs`.
- **Persistent file storage** — save processed images server-side with version tracking. Pick up where you left off.
- **Teams and admin settings** — manage users, toggle tool visibility, configure cleanup, upload a custom logo.
- **Single container** — runs on Intel, AMD, and Apple Silicon (`linux/amd64` + `linux/arm64`).
## Tools
| Category | Tools |
|----------|-------|
| **Essentials** | Resize, Crop, Rotate & Flip, Convert, Compress |
| **Optimization** | Strip Metadata, Bulk Rename, Image to PDF, Favicon Generator |
| **Adjustments** | Brightness/Contrast, Saturation, Color Channels, Color Effects, Replace Color |
| **AI Tools** | Background Removal, Upscaling, Object Eraser, OCR, Face Blur, Smart Crop |
| **Watermark** | Text Watermark, Image Watermark, Text Overlay, Image Composition |
| **Utilities** | Image Info, Compare, Find Duplicates, Color Palette, QR Generator, Barcode Reader |
| **Layout** | Collage/Grid, Image Splitting, Border & Frame |
| **Format** | SVG to Raster, Image to SVG, GIF Tools |
| **Automation** | Pipeline Builder, Batch Processing |
## Supported formats
**In:** JPG, PNG, WebP, AVIF, TIFF, BMP, GIF (animated), SVG, HEIC/HEIF, JPEG XL, ICO, RAW (CR2, NEF, ARW, DNG)
**Out:** JPG, PNG, WebP, AVIF, TIFF, GIF, JPEG XL, SVG, ICO, PDF
## Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `1349` | Server port |
| `AUTH_ENABLED` | `true` | Require login (`admin` / `admin` by default) |
| `MAX_UPLOAD_SIZE_MB` | `100` | Max file upload size |
| `MAX_BATCH_SIZE` | `200` | Max files per batch |
| `CONCURRENT_JOBS` | `3` | Parallel processing limit |
| `FILE_MAX_AGE_HOURS` | `24` | Auto-delete temp files after this many hours |
| `FILES_STORAGE_PATH` | `./data/files` | Where persistent user files are stored |
| `STORAGE_MODE` | `local` | Storage backend (`local` or `s3`) |
See [`.env.example`](.env.example) for the full list.
## Docker Compose example
```yaml
services:
stirling-image:
image: ghcr.io/siddharthksah/stirling-image:latest
container_name: stirling-image
ports:
- "1349:1349"
volumes:
- stirling-data:/data
restart: unless-stopped
volumes:
stirling-data:
```
## Development
```bash
git clone https://github.com/siddharthksah/Stirling-Image.git
cd Stirling-Image
pnpm install
pnpm dev
# UI: http://localhost:1349
```
Requires Node.js 22+ and pnpm 9+.
## Tech stack
React 19 + Vite frontend, Fastify + Sharp backend, SQLite via Drizzle ORM, Python sidecar for AI/ML models. Monorepo with pnpm workspaces. Multi-arch Docker builds.
## Support this project
If you find this useful, consider supporting development:
- **Bug Reports**: [GitHub Issues](https://github.com/siddharthksah/Stirling-Image/issues)
<p align="center">
<a href="https://github.com/sponsors/siddharthksah"><img src="https://img.shields.io/badge/Sponsor-GitHub-ea4aaa?logo=github-sponsors" alt="GitHub Sponsors"></a>
@@ -397,12 +397,7 @@ function SystemSection() {
onChange={(e) => updateSetting("defaultLocale", e.target.value)}
className="px-3 py-1.5 rounded-lg border border-border bg-background text-sm text-foreground"
>
<option value="en">English (en)</option>
<option value="es">Spanish (es)</option>
<option value="fr">French (fr)</option>
<option value="de">German (de)</option>
<option value="zh">Chinese (zh)</option>
<option value="ja">Japanese (ja)</option>
<option value="en">English</option>
</select>
</SettingRow>
@@ -420,32 +415,6 @@ function SystemSection() {
/>
</SettingRow>
<SettingRow
label="Enable Experimental Tools"
description="Show tools that are still in development. These may be unstable."
>
<button
type="button"
onClick={() =>
updateSetting(
"enableExperimentalTools",
settings.enableExperimentalTools === "true" ? "false" : "true",
)
}
className={cn(
"w-11 h-6 rounded-full transition-colors relative",
settings.enableExperimentalTools === "true" ? "bg-primary" : "bg-muted-foreground/30",
)}
>
<span
className={cn(
"block w-4 h-4 rounded-full bg-white absolute top-1 transition-transform",
settings.enableExperimentalTools === "true" ? "translate-x-6" : "translate-x-1",
)}
/>
</button>
</SettingRow>
<div className="pt-4 border-t border-border">
<h4 className="text-sm font-semibold text-foreground mb-3">File Management</h4>
</div>
+6 -110
View File
@@ -1,74 +1,7 @@
import { Globe, Play, ShieldOff, Stamp, Trash2, User, Workflow, Zap } from "lucide-react";
import { Play, Trash2, Workflow } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { AppLayout } from "@/components/layout/app-layout";
import { PipelineBuilder, type PipelineStep } from "@/components/tools/pipeline-builder";
import { cn } from "@/lib/utils";
/** Pipeline template definition. */
interface PipelineTemplate {
name: string;
description: string;
icon: React.ComponentType<{ className?: string }>;
color: string;
steps: Array<{ toolId: string; settings: Record<string, unknown> }>;
}
const TEMPLATES: PipelineTemplate[] = [
{
name: "Social Media Ready",
description: "Resize 1080x1080, compress 200KB, strip metadata, convert to WebP",
icon: Globe,
color: "bg-blue-500/10 text-blue-500",
steps: [
{ toolId: "resize", settings: { width: 1080, height: 1080, fit: "cover" } },
{ toolId: "compress", settings: { quality: 80 } },
{ toolId: "strip-metadata", settings: {} },
{ toolId: "convert", settings: { format: "webp" } },
],
},
{
name: "Privacy Clean",
description: "Strip all metadata and convert to JPG",
icon: ShieldOff,
color: "bg-green-500/10 text-green-500",
steps: [
{ toolId: "strip-metadata", settings: {} },
{ toolId: "convert", settings: { format: "jpg" } },
],
},
{
name: "Web Optimization",
description: "Resize to 1920px, convert to WebP, compress to 80% quality",
icon: Zap,
color: "bg-yellow-500/10 text-yellow-500",
steps: [
{ toolId: "resize", settings: { width: 1920, fit: "inside" } },
{ toolId: "convert", settings: { format: "webp" } },
{ toolId: "compress", settings: { quality: 80 } },
],
},
{
name: "Profile Picture",
description: "Resize to 400x400 and compress",
icon: User,
color: "bg-purple-500/10 text-purple-500",
steps: [
{ toolId: "resize", settings: { width: 400, height: 400, fit: "cover" } },
{ toolId: "compress", settings: { quality: 85 } },
],
},
{
name: "Watermark Batch",
description: "Add text watermark, strip metadata, and compress",
icon: Stamp,
color: "bg-red-500/10 text-red-500",
steps: [
{ toolId: "watermark-text", settings: { text: "SAMPLE", opacity: 0.3 } },
{ toolId: "strip-metadata", settings: {} },
{ toolId: "compress", settings: { quality: 85 } },
],
},
];
interface SavedPipeline {
id: string;
@@ -200,17 +133,6 @@ export function AutomatePage() {
[steps],
);
// Load template into builder
const loadTemplate = useCallback((template: PipelineTemplate) => {
const newSteps: PipelineStep[] = template.steps.map((s) => ({
id: crypto.randomUUID(),
toolId: s.toolId,
settings: { ...s.settings },
}));
setSteps(newSteps);
setExecutionResult(null);
}, []);
// Load saved pipeline into builder
const loadSaved = useCallback((pipeline: SavedPipeline) => {
const newSteps: PipelineStep[] = pipeline.steps.map((s) => ({
@@ -225,35 +147,9 @@ export function AutomatePage() {
return (
<AppLayout showToolPanel={false}>
<div className="flex h-full w-full overflow-hidden">
{/* Left sidebar: templates + saved */}
<div className="w-72 border-r border-border overflow-y-auto p-4 space-y-6 shrink-0 hidden md:block">
{/* Templates */}
<div>
<h3 className="text-xs font-semibold uppercase text-muted-foreground tracking-wider mb-3">
Templates
</h3>
<div className="space-y-2">
{TEMPLATES.map((tpl) => (
<button
key={tpl.name}
type="button"
onClick={() => loadTemplate(tpl)}
className="w-full text-left p-3 rounded-lg border border-border hover:bg-muted/50 transition-colors"
>
<div className="flex items-center gap-2 mb-1">
<div className={cn("p-1.5 rounded-md", tpl.color)}>
<tpl.icon className="h-3.5 w-3.5" />
</div>
<span className="text-sm font-medium text-foreground">{tpl.name}</span>
</div>
<p className="text-xs text-muted-foreground line-clamp-2">{tpl.description}</p>
</button>
))}
</div>
</div>
{/* Saved automations */}
{savedPipelines.length > 0 && (
{/* Left sidebar: saved automations */}
{savedPipelines.length > 0 && (
<div className="w-72 border-r border-border overflow-y-auto p-4 space-y-6 shrink-0 hidden md:block">
<div>
<h3 className="text-xs font-semibold uppercase text-muted-foreground tracking-wider mb-3">
Saved Automations
@@ -293,8 +189,8 @@ export function AutomatePage() {
))}
</div>
</div>
)}
</div>
</div>
)}
{/* Main builder area */}
<div className="flex-1 overflow-y-auto p-6">
+6 -1
View File
@@ -4,6 +4,7 @@ import * as icons from "lucide-react";
import { Eye, EyeOff, FileImage, LayoutGrid, List, Search } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { GemLogo } from "@/components/common/gem-logo";
import { apiGet } from "@/lib/api";
import { cn } from "@/lib/utils";
@@ -63,7 +64,11 @@ export function FullscreenGridPage() {
{/* Top bar */}
<header className="sticky top-0 z-30 bg-background/95 backdrop-blur-sm border-b border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-3 flex items-center gap-4">
<Link to="/" className="text-lg font-bold text-foreground shrink-0">
<Link
to="/"
className="flex items-center gap-2 text-lg font-bold text-foreground shrink-0"
>
<GemLogo className="h-6 w-6 text-primary" />
Stirling <span className="text-primary">Image</span>
</Link>
Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB