Files
PaintPlus/frontend/src/utils/api.js
T
Claude d95b95e234 Wire Smart Select, Color Select, fix layer buttons, add zoom
Frontend changes:
- Wire Smart Select and Color Select to canvas click handlers
- Add externalSelection prop to ImageCanvas for displaying AI-generated selections
- Add zoom controls (mouse wheel + buttons) to ImageCanvas
- Fix layer buttons (New Layer, Delete, Duplicate) with proper handlers
- Lift advancedToolMode state to App.jsx for coordination between components
- Add tool mode indicator overlay on canvas

Backend changes:
- Update smart-select endpoint to return JSON with polygon and bbox data
- Update color-select endpoint to return JSON with polygon and bbox data
- Add _mask_to_polygon helper function using OpenCV contour detection
- Add cv2 and base64 imports to tools.py

API changes:
- smartSelect and colorSelect now return { polygon, bbox, mask_base64 }
2026-01-25 20:49:52 +00:00

242 lines
7.0 KiB
JavaScript

import axios from 'axios';
// Use relative URLs to go through nginx proxy, or use env variable for direct connection
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
export const projectsApi = {
// Create a new project
create: async (name) => {
const response = await api.post('/projects/', { name });
return response.data;
},
// List all projects
list: async () => {
const response = await api.get('/projects/');
return response.data;
},
// Get a specific project
get: async (projectId) => {
const response = await api.get(`/projects/${projectId}`);
return response.data;
},
// Delete a project
delete: async (projectId) => {
const response = await api.delete(`/projects/${projectId}`);
return response.data;
},
// Upload image to project
uploadImage: async (projectId, file) => {
const formData = new FormData();
formData.append('file', file);
const response = await api.post(`/projects/${projectId}/upload`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
},
// Get edits for a project
getEdits: async (projectId) => {
const response = await api.get(`/projects/${projectId}/edits`);
return response.data;
},
// Get image URLs
getOriginalImageUrl: (projectId) => `${API_BASE_URL}/projects/${projectId}/original`,
getCurrentImageUrl: (projectId) => `${API_BASE_URL}/projects/${projectId}/current`,
getEditResultUrl: (projectId, editId) => `${API_BASE_URL}/projects/${projectId}/history/${editId}/result`,
};
export const editsApi = {
// Create a new edit (Fix button)
create: async (projectId, editData) => {
const response = await api.post(`/edits/projects/${projectId}/fix`, editData);
return response.data;
},
// Get edit status
get: async (editId) => {
const response = await api.get(`/edits/${editId}`);
return response.data;
},
// Revert to a specific edit
revert: async (projectId, editId) => {
const response = await api.post(`/edits/projects/${projectId}/revert/${editId}`);
return response.data;
},
// Reset to original
reset: async (projectId) => {
const response = await api.post(`/edits/projects/${projectId}/reset`);
return response.data;
},
};
export const patchesApi = {
// List all patches (eyes)
list: async (category = null, tags = null) => {
const params = new URLSearchParams();
if (category) params.append('category', category);
if (tags) params.append('tags', tags);
const response = await api.get(`/patches/?${params.toString()}`);
return response.data;
},
// Get a specific patch
get: async (patchId) => {
const response = await api.get(`/patches/${patchId}`);
return response.data;
},
// Upload a new patch (eye)
create: async (name, file, category = 'eyes', tags = '') => {
const formData = new FormData();
formData.append('name', name);
formData.append('source_type', 'imported');
formData.append('category', category);
formData.append('tags', tags);
formData.append('file', file);
const response = await api.post('/patches/', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
},
// Apply a patch to the project
apply: async (projectId, patchId, bbox, featherPx = 5) => {
const formData = new FormData();
formData.append('project_id', projectId);
formData.append('patch_id', patchId);
formData.append('bbox', JSON.stringify(bbox));
formData.append('feather_px', featherPx);
const response = await api.post('/patches/apply', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
},
// Delete a patch
delete: async (patchId) => {
const response = await api.delete(`/patches/${patchId}`);
return response.data;
},
// Get patch image URL
getImageUrl: (patchId, thumbnail = false) =>
`${API_BASE_URL}/patches/${patchId}/image${thumbnail ? '?thumbnail=true' : ''}`,
};
export const toolsApi = {
// Remove background from project image
removeBackground: async (projectId) => {
const formData = new FormData();
formData.append('project_id', projectId);
const response = await api.post('/tools/remove-background', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
responseType: 'blob',
});
return response.data;
},
// Remove background and save as layer
removeBackgroundToLayer: async (projectId) => {
const formData = new FormData();
formData.append('project_id', projectId);
const response = await api.post('/tools/remove-background-to-layer', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return response.data;
},
// 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' },
});
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);
formData.append('color_r', r);
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' },
});
return response.data;
},
// Extract object with mask
extractObject: async (projectId, maskBlob) => {
const formData = new FormData();
formData.append('project_id', projectId);
formData.append('mask', maskBlob, 'mask.png');
const response = await api.post('/tools/extract-object', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
responseType: 'blob',
});
return response.data;
},
// List layers
listLayers: async (projectId) => {
const response = await api.get(`/tools/layers/${projectId}`);
return response.data;
},
// Flatten layers
flattenLayers: async (projectId, layerOrder) => {
const formData = new FormData();
formData.append('project_id', projectId);
formData.append('layer_order', JSON.stringify(layerOrder));
const response = await api.post('/tools/flatten-layers', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return response.data;
},
// Get layer image URL
getLayerImageUrl: (projectId, layerId) =>
`${API_BASE_URL}/projects/${projectId}/layers/layer_${layerId}.png`,
};
export default api;