A high-performance image generation server built with Swift, Vapor, and FluxSwift for local FLUX model inference. Generate stunning images using text-to-image and image-to-image capabilities with a RESTful API.
- 🎨 Text-to-Image Generation: Create images from text prompts using FLUX models
- 🖼️ Image-to-Image Editing: Transform existing images with text prompts
- 🔐 JWT Authentication: Secure API endpoints with token-based authentication
- 🚀 Local Inference: Run FLUX models locally on Apple Silicon
- 📊 Progress Tracking: Real-time generation progress updates
- 🗄️ SQLite Storage: Persistent storage for users, generations, and models
- 🎯 Multiple Models: Support for flux-schnell, flux-dev, and flux-kontext
- macOS 14.0+ (Apple Silicon recommended)
- Xcode 15.0+
- Swift 5.9+
- FLUX model weights (downloaded automatically on first use)
- Clone the repository:
git clone https://github.com/mzbac/ImageMatesCLI.git
cd ImageMatesCLI- Build the server using Xcode (required for MLX Metal support):
# Create dist directory
mkdir -p dist
# Build to dist folder (requires absolute path)
xcodebuild -scheme ImageMatesCLI -configuration Release -destination "platform=macOS" CONFIGURATION_BUILD_DIR="$(pwd)/dist" build- Start the server:
# From the dist directory
cd dist
./ImageMatesServerThe server will start on http://localhost:8080
- On first run, the server will:
- Create the SQLite database
- Seed test user:
fluxtest@example.com/password123 - Register available FLUX models
- Create necessary storage directories
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "fluxtest@example.com",
"password": "password123"
}'Response:
{
"success": true,
"data": {
"user": {
"id": "...",
"email": "fluxtest@example.com",
"name": "FluxSwift Test User"
},
"token": "eyJ0eXAiOiJKV1Q..."
}
}TOKEN="your-jwt-token"
curl -X POST http://localhost:8080/api/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A cute robot in a garden, digital art",
"model": "mzbac/flux1.schnell.4bit.mlx",
"width": 256,
"height": 256,
"steps": 4,
"guidance": 3.5,
"seed": 123
}'TOKEN="your-jwt-token"
curl -X POST http://localhost:8080/api/edit \
-H "Authorization: Bearer $TOKEN" \
-F "prompt=A beautiful ocean with waves at sunset" \
-F "image=@input_image.png" \
-F "model=mzbac/flux1.kontext.4bit.mlx" \
-F "strength=0.8" \
-F "width=256" \
-F "height=256" \
-F "steps=4" \
-F "guidance=3.5"curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/generations/{generationId}curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/modelscurl -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/galleryThe server provides Server-Sent Events for real-time generation progress updates:
- connected: Initial connection confirmation with session ID
- progress: Generation progress updates (0-100%)
- completed: Generation finished successfully
- error: Generation failed or error occurred
- heartbeat: Keep-alive signal every 30 seconds
// Since EventSource doesn't support custom headers natively,
// use a fetch-based approach for authentication
async function connectSSE(token) {
const response = await fetch('/api/events', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'text/event-stream'
}
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
// Process the stream
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Parse SSE events from chunk
parseSSEEvents(chunk);
}
}
function parseSSEEvents(chunk) {
const events = chunk.split('\n\n');
events.forEach(event => {
const lines = event.split('\n');
let eventType = null;
let eventData = null;
lines.forEach(line => {
if (line.startsWith('event: ')) {
eventType = line.slice(7);
} else if (line.startsWith('data: ')) {
eventData = JSON.parse(line.slice(6));
}
});
if (eventType && eventData) {
handleSSEEvent(eventType, eventData);
}
});
}Run image-to-image transformation:
# Get auth token
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "fluxtest@example.com", "password": "password123"}' \
| jq -r '.data.token')
# Submit image for transformation
RESPONSE=$(curl -s -X POST http://localhost:8080/api/edit \
-H "Authorization: Bearer $TOKEN" \
-F "prompt=A beautiful mountain landscape with snow" \
-F "image=@test_input.png" \
-F "model=flux-schnell" \
-F "strength=0.8" \
-F "steps=4")
# Extract generation ID
GEN_ID=$(echo $RESPONSE | jq -r '.data.generationId')
# Check status
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/generations/$GEN_ID | jq-
mzbac/flux1.schnell.4bit.mlx (Default)
- Fast generation (4 steps)
- Optimized for Apple Silicon
- ~3GB download
-
mzbac/flux1.schnell.8bit.mlx
- Higher quality Schnell
- Better than 4-bit
- ~6GB download
-
mzbac/flux1.dev.4bit.mlx
- Development model quality
- Good quality/speed balance
- ~6GB download
-
mzbac/flux1.dev.8bit.mlx
- Highest quality
- Slower generation
- ~12GB download
-
mzbac/flux1.kontext.4bit.mlx
- Image-to-image specialist
- Apple Silicon optimized
- ~6GB download
-
mzbac/flux1.kontext.8bit.mlx
- Best image-to-image quality
- Higher precision
- ~12GB download
-
black-forest-labs/FLUX.1-schnell
- Original fast model
- Requires authentication
-
black-forest-labs/FLUX.1-dev
- Original development model
- Requires authentication
-
black-forest-labs/FLUX.1-Kontext-dev
- Official image-to-image model
- Requires authentication
Models are automatically downloaded from Hugging Face on first use.
Models are stored in:
~/Documents/huggingface/models/black-forest-labs/
To use custom FLUX models, update the database:
sqlite3 db.sqlite
INSERT INTO models (id, model_id, name, type, source, path, supports_img2img)
VALUES (
'550e8400-e29b-41d4-a716-446655440000',
'my-custom-model',
'My Custom FLUX Model',
'universal',
'local',
'/path/to/model/weights',
true
);ImageMatesCLI/
├── db.sqlite # SQLite database
├── storage/
│ ├── images/ # Generated images
│ │ └── {generation-id}.png
│ ├── thumbnails/ # Image thumbnails
│ │ └── thumb_{id}.png
│ └── uploads/ # Uploaded input images
│ └── {upload-id}.png
PORT: Server port (default: 8080)DATABASE_PATH: SQLite database path (default: ./db.sqlite)JWT_SECRET: JWT signing secret (default: test-secret-key)LOG_LEVEL: Logging level (default: debug)
The server uses SQLite with the following main tables:
users: User accounts and settingsgenerations: Image generation recordsmodels: Available AI models_fluent_sessions: Session storage
If you see "Failed to load the default metallib", ensure you're building with xcodebuild:
xcodebuild -scheme ImageMatesCLI -configuration Release -destination "platform=macOS" CONFIGURATION_BUILD_DIR="$(pwd)/dist" buildIf model downloads fail:
- Check internet connection
- Ensure you have enough disk space (~15GB for all models)
- Models are cached after first download
If you get 401 errors:
- Ensure you're including the Bearer token in headers
- Token expires after 7 days
- Use the
/api/auth/refreshendpoint to get a new token
Common issues:
- Insufficient memory (8GB+ RAM recommended)
- Invalid image formats (use PNG, JPEG)
- Model not loaded (check
/api/modelsendpoint)
- Model Loading: First generation takes longer due to model loading
- Batch Size: Keep dimensions reasonable (256x256 to 1024x1024)
- Steps: Lower steps = faster generation (flux-schnell optimized for 4 steps)
- Concurrent Requests: Server queues generations to prevent memory issues
LOG_LEVEL=trace ./ImageMatesServersqlite3 db.sqlite
.tables
SELECT * FROM users;
SELECT * FROM generations ORDER BY created_at DESC LIMIT 5;This project is licensed under the GNU General Public License v3.0
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Built with Vapor web framework
- Powered by flux.swift for FLUX model inference
- Uses MLX for efficient computation on Apple Silicon