feat: integrate uload and picture, unify package naming

- Add uload project with apps/web structure
  - Reorganize from flat to monorepo structure
  - Remove PocketBase binary and local data
  - Update to pnpm and @uload/web namespace

- Add picture project to monorepo
  - Remove embedded git repository

- Unify all package names to @{project}/{app} schema:
  - @maerchenzauber/* (was @storyteller/*)
  - @manacore/* (was manacore-*, manacore)
  - @manadeck/* (was web, backend, manadeck)
  - @memoro/* (was memoro-web, landing, memoro)
  - @picture/* (already unified)
  - @uload/web

- Add convenient dev scripts for all apps:
  - pnpm dev:{project}:web
  - pnpm dev:{project}:landing
  - pnpm dev:{project}:mobile
  - pnpm dev:{project}:backend

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-11-25 04:00:36 +01:00
parent c6c4c5a552
commit c712a2504a
1031 changed files with 189301 additions and 290 deletions

View file

@ -0,0 +1,202 @@
# Advanced Generation Settings
## Overview
The QuickGenerateBar now includes advanced settings that allow users to customize image generation parameters beyond just the prompt and model selection. These settings are accessible via a settings icon (⚙️) in the generation bar.
## Features
### 1. Image Count (Batch Generation)
- Generate 1-5 images at once with a single prompt
- Each image uses a different random seed for variety
- Progress indicator shows "Generiere Bild X/Y..." during batch generation
- Success toast shows total count: "X Bilder erfolgreich generiert!"
**Default**: 1 image
### 2. Aspect Ratio Selector
Choose from three preset aspect ratios:
- **Quadratisch** (Square): 1024×1024px
- **Hochformat** (Portrait): 832×1216px
- **Querformat** (Landscape): 1216×832px
Visual preview boxes show the relative dimensions.
**Default**: Square (1024×1024)
### 3. Steps Slider
Control the number of diffusion steps (quality vs. speed):
- **Range**: 20-150 steps (increments of 5)
- Lower values = faster generation, potentially lower quality
- Higher values = slower generation, higher quality/detail
- Visual labels: "Schnell" (20) → "Höchste Qualität" (150)
**Default**: 50 steps
### 4. Guidance Scale Slider
Control how closely the AI follows the prompt:
- **Range**: 1-20 (increments of 0.5)
- Lower values = more creative/artistic interpretation
- Higher values = stricter adherence to prompt
- Visual labels: "Kreativ" (1) → "Präzise" (20)
- Helper text: "Höhere Werte folgen dem Prompt genauer, niedrigere sind kreativer"
**Default**: 7.5
## UI/UX Details
### Settings Button
- **Location**: Between prompt input and Generate button
- **Icon**: Gear/settings icon (⚙️)
- **Badge**: Blue pulsing dot appears when any setting differs from defaults
- **State**: Disabled during generation
### Modal Layout
- **Style**: Glass-blur modal (white/95 with backdrop-blur-xl)
- **Size**: max-w-2xl centered
- **Close**: ESC key or close button (×)
- **Actions**: Cancel (gray) or Übernehmen (blue)
### Visual Feedback
The settings button shows a blue pulsing badge when:
- Image count > 1
- Aspect ratio ≠ Square
- Steps ≠ 50
- Guidance scale ≠ 7.5
## Implementation Details
### Files Modified
1. **`lib/components/generate/AdvancedSettingsModal.svelte`** (NEW)
- Self-contained modal component
- Local state management with $effect() for props sync
- Exports `AdvancedSettings` and `AspectRatio` types
2. **`lib/components/gallery/QuickGenerateBar.svelte`**
- Added `showAdvancedSettings` state
- Added `advancedSettings` state with defaults
- Added `hasCustomSettings` derived state for badge
- Updated `handleQuickGenerate()` to:
- Loop through imageCount
- Pass width, height, steps, guidance_scale to API
- Show progress per image
- Added settings button with badge indicator
- Integrated AdvancedSettingsModal component
3. **`lib/api/generate.ts`**
- Extended `GenerateImageParams` interface:
```typescript
export interface GenerateImageParams {
prompt: string;
model_id: string;
negative_prompt?: string;
width?: number; // NEW
height?: number; // NEW
steps?: number; // NEW
guidance_scale?: number; // NEW
}
```
### API Integration
The advanced settings are passed directly to the edge function:
```typescript
const result = await generateImage({
prompt: prompt.trim(),
model_id: selectedModelId,
width: advancedSettings.aspectRatio.width,
height: advancedSettings.aspectRatio.height,
steps: advancedSettings.steps,
guidance_scale: advancedSettings.guidanceScale
});
```
**Note**: The edge function (`supabase/functions/generate-image/index.ts`) must handle these parameters and pass them to the image generation API (Replicate, Stability AI, etc.).
### Batch Generation Flow
When `imageCount > 1`:
1. Loop from 0 to imageCount
2. For each iteration:
- Update progress: `Generiere Bild ${i + 1}/${totalImages}...`
- Call `generateImage()` with same params
- Poll for completion: `Verarbeite Bild ${i + 1}/${totalImages}...`
- Increment completedImages counter
3. Show success toast with total count
4. Trigger `onGenerated()` callback to refresh gallery
## Future Enhancements
Potential additions inspired by mobile app:
- [ ] Tag input for organizing generated images
- [ ] Negative prompt field
- [ ] Seed input for reproducible generations
- [ ] Style presets (e.g., "Photorealistic", "Artistic", "Anime")
- [ ] Rate limit indicator
- [ ] Batch progress tracker with individual image status
- [ ] Save/load preset configurations
- [ ] Advanced toggle (show/hide extra options)
## Testing
### Manual Test Cases
1. **Default Generation**
- Open generate bar
- Enter prompt
- Click Generate
- Verify: 1 square image at default quality
2. **Custom Aspect Ratio**
- Click settings icon
- Select Portrait
- Click Übernehmen
- Verify: Badge appears on settings button
- Generate image
- Verify: Image is 832×1216
3. **Batch Generation**
- Open settings
- Select 3 images
- Click Übernehmen
- Generate
- Verify: Progress shows "Bild 1/3", "Bild 2/3", "Bild 3/3"
- Verify: Toast shows "3 Bilder erfolgreich generiert!"
- Verify: 3 images appear in gallery
4. **Settings Persistence**
- Set custom values in modal
- Close modal
- Re-open modal
- Verify: Values are still set
5. **Settings Reset**
- Set custom values
- Generate image
- Verify: Badge still shows on settings button
- Open modal
- Change all back to defaults
- Verify: Badge disappears
6. **Keyboard Navigation**
- Open modal with click
- Press ESC
- Verify: Modal closes
## Design Inspiration
Based on mobile app's `QuickGenerateBar.tsx` which includes:
- ImageCountSelector with pill/counter/compact styles
- AspectRatioSelector with visual previews
- Slider controls for steps and guidance
- Tag input for organization
- Rate limiting display
- Batch progress tracking
The web implementation follows similar patterns while adapting to:
- Desktop-first layout with responsive mobile view
- Glass-blur aesthetic matching existing UI
- Svelte 5 runes instead of React hooks
- Simpler initial feature set (can expand later)

View file

@ -0,0 +1,120 @@
# Storage Bucket Setup für User Uploads
## Schritt 1: SQL Statement ausführen
### Option A: Erste Installation (Policies existieren noch nicht)
1. Öffne die **Supabase Dashboard**: https://supabase.com/dashboard
2. Wähle dein Projekt aus
3. Navigiere zu **SQL Editor**
4. Kopiere den Inhalt von `setup-storage-bucket.sql`
5. Führe das SQL-Script aus
### Option B: Update (Policies existieren bereits)
1. Öffne die **Supabase Dashboard**: https://supabase.com/dashboard
2. Wähle dein Projekt aus
3. Navigiere zu **SQL Editor**
4. Kopiere den Inhalt von `update-storage-policies.sql`
5. Führe das SQL-Script aus
**Falls Fehler "policy already exists"**: Verwende `update-storage-policies.sql` statt `setup-storage-bucket.sql`
## Schritt 2: Überprüfung
### Bucket überprüfen
Navigiere zu **Storage** im Supabase Dashboard:
- Du solltest einen Bucket namens `user-uploads` sehen
- Public: ✓ Enabled
- File Size Limit: 10 MB
- Allowed MIME types: image/jpeg, image/jpg, image/png, image/webp
### Policies überprüfen
Navigiere zu **Storage > Policies**:
- Du solltest 4 Policies für `user-uploads` sehen:
- ✓ Users can upload their own images (INSERT)
- ✓ Public images are publicly accessible (SELECT)
- ✓ Users can update their own images (UPDATE)
- ✓ Users can delete their own images (DELETE)
## Schritt 3: Testen
### Test 1: Upload über die Web-App
1. Öffne die Web-App: http://localhost:5173/app/upload
2. Wähle ein Bild aus oder nutze Drag & Drop
3. Klicke auf "Upload"
4. Das Bild sollte erfolgreich hochgeladen werden
5. Überprüfe in **Storage > user-uploads** im Supabase Dashboard
### Test 2: Zugriff auf öffentliche URL
1. Nachdem Upload erfolgreich war, kopiere die `public_url` aus der Konsole
2. Öffne die URL in einem neuen Browser-Tab
3. Das Bild sollte sichtbar sein (ohne Authentifizierung)
### Test 3: Galerie-Integration
1. Navigiere zur Galerie: http://localhost:5173/app/gallery
2. Die hochgeladenen Bilder sollten in der Galerie erscheinen
3. Klicke auf ein Bild, um die Detail-Ansicht zu öffnen
## Datei-Struktur im Bucket
```
user-uploads/
├── {user_id_1}/
│ ├── 1234567890-abc123.jpg
│ ├── 1234567891-def456.png
│ └── 1234567892-ghi789.webp
└── {user_id_2}/
├── 1234567893-jkl012.jpg
└── 1234567894-mno345.png
```
## Sicherheit
### ✅ Was ist geschützt:
- User können nur in ihren eigenen Ordner hochladen
- User können nur ihre eigenen Dateien bearbeiten/löschen
- Upload nur für authentifizierte User
- Datei-Größe ist auf 10MB begrenzt
- Nur erlaubte Bild-Formate (JPG, PNG, WebP)
### ⚠️ Was ist öffentlich:
- Alle hochgeladenen Bilder sind über ihre public_url zugänglich
- Jeder mit der URL kann das Bild sehen (auch ohne Account)
- Dies ist gewollt für die Galerie-Anzeige
### 🔒 Optionale Verbesserungen für später:
- Private Bilder: Separate Bucket für private Uploads
- Signed URLs: Temporäre URLs für sensible Inhalte
- CDN: CloudFlare oder AWS CloudFront vor Supabase Storage
## Troubleshooting
### Fehler: "Bucket bereits vorhanden"
- Kein Problem! Das Script verwendet `ON CONFLICT DO NOTHING`
- Die Policies werden trotzdem erstellt
### Fehler: "Permission denied"
1. Überprüfe ob du als authentifizierter User eingeloggt bist
2. Überprüfe die Policies im Supabase Dashboard
3. Führe das SQL-Script erneut aus
### Fehler: "File too large"
- Stelle sicher, dass die Datei kleiner als 10MB ist
- Die Validierung erfolgt sowohl im Frontend als auch im Backend
### Bilder werden nicht in der Galerie angezeigt
1. Überprüfe ob der Bucket `public` ist
2. Überprüfe ob die `public_url` korrekt generiert wird
3. Öffne die Browser-Konsole für Fehler-Logs
## Alternative: UI-basiertes Setup
Falls du das SQL-Script nicht ausführen möchtest, kannst du den Bucket auch manuell im UI erstellen:
1. **Storage > Create Bucket**
- Name: `user-uploads`
- Public: ✓ Enable
- File Size Limit: 10485760 (10MB)
- Allowed MIME types: image/jpeg, image/jpg, image/png, image/webp
2. **Storage > Policies > New Policy**
- Erstelle die 4 Policies manuell mit den gleichen Bedingungen wie im SQL-Script

View file

@ -0,0 +1,101 @@
-- ============================================
-- Storage Bucket Setup für User Uploads
-- ============================================
-- Dieses Script muss in der Supabase SQL-Konsole ausgeführt werden
-- 1. Erstelle Storage Bucket für User Uploads
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'user-uploads',
'user-uploads',
true, -- Public bucket, damit Bilder über public_url zugänglich sind
10485760, -- 10MB in Bytes (10 * 1024 * 1024)
ARRAY['image/jpeg', 'image/jpg', 'image/png', 'image/webp']::text[]
)
ON CONFLICT (id) DO NOTHING;
-- 2. Storage Policy: Benutzer können nur ihre eigenen Dateien hochladen
CREATE POLICY "Users can upload their own images"
ON storage.objects
FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- 3. Storage Policy: Jeder kann Bilder lesen (public bucket)
CREATE POLICY "Public images are publicly accessible"
ON storage.objects
FOR SELECT
TO public
USING (bucket_id = 'user-uploads');
-- 4. Storage Policy: Benutzer können nur ihre eigenen Dateien aktualisieren
CREATE POLICY "Users can update their own images"
ON storage.objects
FOR UPDATE
TO authenticated
USING (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
)
WITH CHECK (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- 5. Storage Policy: Benutzer können nur ihre eigenen Dateien löschen
CREATE POLICY "Users can delete their own images"
ON storage.objects
FOR DELETE
TO authenticated
USING (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- ============================================
-- Überprüfung der Bucket-Konfiguration
-- ============================================
-- Führe diese Queries aus, um die Konfiguration zu überprüfen:
-- Bucket-Details anzeigen
SELECT * FROM storage.buckets WHERE id = 'user-uploads';
-- Alle Policies für den Bucket anzeigen
SELECT
schemaname,
tablename,
policyname,
permissive,
roles,
cmd,
qual,
with_check
FROM pg_policies
WHERE tablename = 'objects'
AND policyname ILIKE '%user%'
ORDER BY policyname;
-- ============================================
-- Hinweise
-- ============================================
--
-- 1. Die Datei-Struktur im Bucket ist: user-uploads/{user_id}/{timestamp}-{random}.{ext}
-- Dies stellt sicher, dass jeder User nur auf seine eigenen Dateien zugreifen kann.
--
-- 2. Der Bucket ist PUBLIC, d.h. Bilder sind über die public_url ohne Auth zugänglich.
-- Dies ist notwendig, damit Bilder in der Galerie angezeigt werden können.
--
-- 3. Die Policies stellen sicher, dass:
-- - Nur authentifizierte User hochladen können
-- - User nur in ihren eigenen Ordner ({user_id}/) hochladen können
-- - Jeder User nur seine eigenen Dateien bearbeiten/löschen kann
-- - Alle Bilder öffentlich lesbar sind
--
-- 4. File Size Limit: 10MB pro Datei
-- Allowed Types: JPG, JPEG, PNG, WebP
--
-- 5. Falls der Bucket bereits existiert, wird er nicht neu erstellt (ON CONFLICT DO NOTHING)
--

View file

@ -0,0 +1,92 @@
-- ============================================
-- Storage Bucket Update - Policies aktualisieren
-- ============================================
-- Dieses Script aktualisiert die bestehenden Policies
-- 1. Lösche bestehende Policies (falls vorhanden)
DROP POLICY IF EXISTS "Users can upload their own images" ON storage.objects;
DROP POLICY IF EXISTS "Public images are publicly accessible" ON storage.objects;
DROP POLICY IF EXISTS "Users can update their own images" ON storage.objects;
DROP POLICY IF EXISTS "Users can delete their own images" ON storage.objects;
-- 2. Erstelle Bucket (falls nicht vorhanden)
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'user-uploads',
'user-uploads',
true,
10485760, -- 10MB
ARRAY['image/jpeg', 'image/jpg', 'image/png', 'image/webp']::text[]
)
ON CONFLICT (id)
DO UPDATE SET
public = EXCLUDED.public,
file_size_limit = EXCLUDED.file_size_limit,
allowed_mime_types = EXCLUDED.allowed_mime_types;
-- 3. Erstelle Policies neu
CREATE POLICY "Users can upload their own images"
ON storage.objects
FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
);
CREATE POLICY "Public images are publicly accessible"
ON storage.objects
FOR SELECT
TO public
USING (bucket_id = 'user-uploads');
CREATE POLICY "Users can update their own images"
ON storage.objects
FOR UPDATE
TO authenticated
USING (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
)
WITH CHECK (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
);
CREATE POLICY "Users can delete their own images"
ON storage.objects
FOR DELETE
TO authenticated
USING (
bucket_id = 'user-uploads' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- ============================================
-- Überprüfung
-- ============================================
-- Bucket-Details
SELECT
id,
name,
public,
file_size_limit,
allowed_mime_types
FROM storage.buckets
WHERE id = 'user-uploads';
-- Policies
SELECT
policyname,
cmd,
roles
FROM pg_policies
WHERE tablename = 'objects'
AND (
policyname ILIKE '%upload%' OR
policyname ILIKE '%public images%' OR
policyname ILIKE '%update%' OR
policyname ILIKE '%delete%'
)
ORDER BY policyname;