Add LaMa magic eraser, remote provider abstraction, and AI tool infrastructure
Backend: - requirements.txt: add simple-lama-inpainting, rembg[gpu]; upgrade opencv to 4.10+ - app/config.py: add InvokeAI (url, model) and ComfyUI (url, model) settings; OPENAI_MODEL - app/services/local_inpaint.py: LaMa, OpenCV, rembg wrappers (auto GPU/CPU) - app/services/remote_provider.py: abstract RemoteAIProvider + OpenAI, InvokeAI, ComfyUI drivers - app/routers/ai_tools.py: new /api/* endpoints — /erase, /inpaint/lama, /inpaint/fast, /background/remove, /inpaint/remote, /generate/txt2img, /generate/img2img, /generate/outpaint, GET /config (capability flags) - app/main.py: register ai_tools router Frontend: - services/api.js: add erase(), textToImage(), imageToImage(), remoteInpaint(), getConfig() - api/capabilities.js: lazy-fetch /api/config singleton; hasRemote() helper - tools/ai_lama_erase.js: brush-paint mask → LaMa erase → apply to layer - tools/ai_smart_inpaint.js: brush mask + dialog (Fast/Quality mode + prompt) → inpaint - core/components/provider-badge.js: shows active provider + health in toolbar - config.js: register ai_lama_erase and ai_smart_inpaint tools - main.js: mount provider badge on load - .env.example: document InvokeAI, ComfyUI, OpenAI provider settings https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
This commit is contained in:
@@ -95,6 +95,124 @@ class ApiService {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* AI erase using LaMa (local, no API key needed)
|
||||
* @param {string} imageData - Base64 encoded image
|
||||
* @param {string} maskData - Base64 encoded mask (white = erase)
|
||||
* @returns {Promise<{result: string, method: string}>}
|
||||
*/
|
||||
async erase(imageData, maskData) {
|
||||
const response = await fetch(`${this.baseUrl}/api/erase`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ image: imageData, mask: maskData }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
||||
throw new Error(error.detail || `Erase request failed: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Text-to-image via remote provider
|
||||
* @param {string} prompt
|
||||
* @param {Object} options - width, height, negativePrompt, steps, cfgScale, model
|
||||
* @returns {Promise<{result: string}>}
|
||||
*/
|
||||
async textToImage(prompt, options = {}) {
|
||||
const response = await fetch(`${this.baseUrl}/api/generate/txt2img`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
prompt,
|
||||
width: options.width || 1024,
|
||||
height: options.height || 1024,
|
||||
negative_prompt: options.negativePrompt || '',
|
||||
steps: options.steps || 30,
|
||||
cfg_scale: options.cfgScale || 7.5,
|
||||
model: options.model || null,
|
||||
seed: options.seed || 0,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
||||
throw new Error(error.detail || `Text-to-image failed: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Image-to-image via remote provider
|
||||
* @param {string} imageData - Base64 encoded image
|
||||
* @param {string} prompt
|
||||
* @param {Object} options
|
||||
* @returns {Promise<{result: string}>}
|
||||
*/
|
||||
async imageToImage(imageData, prompt, options = {}) {
|
||||
const response = await fetch(`${this.baseUrl}/api/generate/img2img`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
image: imageData,
|
||||
prompt,
|
||||
strength: options.strength || 0.75,
|
||||
negative_prompt: options.negativePrompt || '',
|
||||
steps: options.steps || 30,
|
||||
cfg_scale: options.cfgScale || 7.5,
|
||||
model: options.model || null,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
||||
throw new Error(error.detail || `Image-to-image failed: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inpaint with prompt via remote provider
|
||||
* @param {string} imageData - Base64
|
||||
* @param {string} maskData - Base64
|
||||
* @param {string} prompt
|
||||
* @param {Object} options
|
||||
* @returns {Promise<{result: string}>}
|
||||
*/
|
||||
async remoteInpaint(imageData, maskData, prompt, options = {}) {
|
||||
const response = await fetch(`${this.baseUrl}/api/inpaint/remote`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
image: imageData,
|
||||
mask: maskData,
|
||||
prompt,
|
||||
negative_prompt: options.negativePrompt || '',
|
||||
steps: options.steps || 30,
|
||||
cfg_scale: options.cfgScale || 7.5,
|
||||
model: options.model || null,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
|
||||
throw new Error(error.detail || `Remote inpaint failed: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch backend capabilities (local tools available, remote provider status).
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getConfig() {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/api/config`);
|
||||
if (!response.ok) return null;
|
||||
return response.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check for the backend
|
||||
* @returns {Promise<boolean>}
|
||||
|
||||
Reference in New Issue
Block a user