Click selection to move/resize/rotate
diff --git a/frontend/src/components/Layers.jsx b/frontend/src/components/Layers.jsx
index 22b50e2..f43770c 100644
--- a/frontend/src/components/Layers.jsx
+++ b/frontend/src/components/Layers.jsx
@@ -11,6 +11,7 @@ const Layers = ({
onLayerVisibilityChange,
onFlatten,
isProcessing,
+ onError,
}) => {
const [draggedLayer, setDraggedLayer] = useState(null);
@@ -69,6 +70,56 @@ const Layers = ({
loadLayers();
};
+ const handleNewLayer = async () => {
+ if (!projectId) return;
+ try {
+ // Create a new empty transparent layer
+ const newLayer = {
+ id: `layer-${Date.now()}`,
+ name: `Layer ${layers.length + 1}`,
+ visible: true,
+ thumbnail: null,
+ };
+ setLayers([...layers, newLayer]);
+ setActiveLayer(newLayer.id);
+ } catch (err) {
+ onError?.(`Failed to create layer: ${err.message}`);
+ }
+ };
+
+ const handleDeleteLayer = async () => {
+ if (!projectId || activeLayer === 'background') return;
+ try {
+ const updatedLayers = layers.filter((l) => l.id !== activeLayer);
+ setLayers(updatedLayers);
+ setActiveLayer(updatedLayers.length > 0 ? updatedLayers[updatedLayers.length - 1].id : 'background');
+ } catch (err) {
+ onError?.(`Failed to delete layer: ${err.message}`);
+ }
+ };
+
+ const handleDuplicateLayer = async () => {
+ if (!projectId || activeLayer === 'background') return;
+ try {
+ const layerToDuplicate = layers.find((l) => l.id === activeLayer);
+ if (!layerToDuplicate) return;
+
+ const newLayer = {
+ ...layerToDuplicate,
+ id: `layer-${Date.now()}`,
+ name: `${layerToDuplicate.name} copy`,
+ };
+
+ const activeIndex = layers.findIndex((l) => l.id === activeLayer);
+ const updatedLayers = [...layers];
+ updatedLayers.splice(activeIndex + 1, 0, newLayer);
+ setLayers(updatedLayers);
+ setActiveLayer(newLayer.id);
+ } catch (err) {
+ onError?.(`Failed to duplicate layer: ${err.message}`);
+ }
+ };
+
return (
@@ -142,6 +193,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || !projectId}
title="Add new empty layer"
+ onClick={handleNewLayer}
>
+ New Layer
@@ -149,6 +201,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Delete selected layer"
+ onClick={handleDeleteLayer}
>
Delete
@@ -156,6 +209,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Duplicate selected layer"
+ onClick={handleDuplicateLayer}
>
Duplicate
diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js
index cea392d..164910d 100644
--- a/frontend/src/utils/api.js
+++ b/frontend/src/utils/api.js
@@ -171,20 +171,22 @@ export const toolsApi = {
},
// Smart select object at point
+ // Returns { polygon, bbox, mask_base64 }
smartSelect: async (projectId, x, y) => {
const formData = new FormData();
formData.append('project_id', projectId);
formData.append('point_x', x);
formData.append('point_y', y);
+ formData.append('return_format', 'json');
const response = await api.post('/tools/smart-select', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
- responseType: 'blob',
});
return response.data;
},
// Select by color
+ // Returns { polygon, bbox, mask_base64, color, tolerance }
colorSelect: async (projectId, r, g, b, tolerance = 30) => {
const formData = new FormData();
formData.append('project_id', projectId);
@@ -192,10 +194,10 @@ export const toolsApi = {
formData.append('color_g', g);
formData.append('color_b', b);
formData.append('tolerance', tolerance);
+ formData.append('return_format', 'json');
const response = await api.post('/tools/color-select', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
- responseType: 'blob',
});
return response.data;
},
diff --git a/scripts/download_sample_eyes.py b/scripts/download_sample_eyes.py
index 6dacbf5..953aa42 100755
--- a/scripts/download_sample_eyes.py
+++ b/scripts/download_sample_eyes.py
@@ -106,17 +106,34 @@ def import_eye_to_database(db_path: Path, eye_data: dict, patch_path: str, thumb
return patch_id
def main():
- # Determine paths
- script_dir = Path(__file__).parent
- project_root = script_dir.parent
- data_dir = project_root / 'data'
- db_path = data_dir / 'photoedit.db'
+ # Determine paths - handle both Docker and local environments
+ # In Docker: script is at /scripts/, data is at /app/data/
+ # Locally: script is at ./scripts/, data is at ./data/
+ docker_data_dir = Path('/app/data')
+ local_data_dir = Path(__file__).parent.parent / 'data'
+
+ if docker_data_dir.exists():
+ data_dir = docker_data_dir
+ else:
+ data_dir = local_data_dir
+
+ db_path = data_dir / 'ai_photo_edit.db'
# Check if database exists
if not db_path.exists():
print(f"Database not found at {db_path}")
- print("Please start the backend first to initialize the database.")
- sys.exit(1)
+ print("Attempting to initialize database...")
+ # Try to import and initialize database
+ try:
+ sys.path.insert(0, str(Path('/app')))
+ sys.path.insert(0, str(Path(__file__).parent.parent / 'backend'))
+ from app.database import init_db
+ init_db()
+ print("Database initialized successfully.")
+ except Exception as e:
+ print(f"Could not initialize database: {e}")
+ print("Please start the backend first to initialize the database.")
+ sys.exit(1)
print(f"Using database: {db_path}")
print(f"Data directory: {data_dir}")
diff --git a/scripts/init_database.py b/scripts/init_database.py
new file mode 100644
index 0000000..aa99aef
--- /dev/null
+++ b/scripts/init_database.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+"""
+Initialize the database before other startup scripts run.
+
+This ensures the database exists and has all required tables
+before download_sample_eyes.py tries to use it.
+"""
+
+import os
+import sys
+from pathlib import Path
+
+# Add backend to path - handle both Docker and local environments
+# In Docker: backend is at /app/
+# Locally: backend is at ./backend/
+if Path('/app').exists():
+ sys.path.insert(0, '/app')
+else:
+ sys.path.insert(0, str(Path(__file__).parent.parent / 'backend'))
+
+def main():
+ # Import after path setup
+ from app.database import engine, Base, init_db
+ from app.models import project, user, patch
+
+ print("Initializing database...")
+
+ # Create all tables
+ init_db()
+
+ # Verify database was created - check both Docker and local paths
+ docker_db_path = Path('/app/data/ai_photo_edit.db')
+ local_db_path = Path('./data/ai_photo_edit.db')
+
+ if docker_db_path.exists():
+ print(f"✓ Database initialized at: {docker_db_path}")
+ elif local_db_path.exists():
+ print(f"✓ Database initialized at: {local_db_path}")
+ else:
+ print("⚠ Database file not found at expected locations, but tables may still be created")
+
+ print("Database initialization complete.")
+
+if __name__ == '__main__':
+ main()