HomePilot / frontend /src /ui /edit /editApi.js
HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
921d377
Raw
History Blame Contribute Delete
8.48 kB
/**
* API client for the edit-session sidecar service.
*
* This module provides functions to interact with the edit session endpoints,
* enabling natural language image editing workflows.
*/
/**
* Generate authentication headers if API key is provided.
*/
function authHeaders(apiKey) {
return apiKey ? { 'X-API-Key': apiKey } : {};
}
/**
* Upload an image to start or continue an edit session.
*
* @param params - Upload parameters
* @returns Session state after upload
*/
export async function uploadToEditSession(params) {
const { backendUrl, apiKey, conversationId, file, instruction } = params;
const base = backendUrl.replace(/\/+$/, '');
const url = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}/image`;
const fd = new FormData();
fd.append('file', file);
if (instruction?.trim()) {
fd.append('instruction', instruction.trim());
}
const res = await fetch(url, {
method: 'POST',
headers: { ...authHeaders(apiKey) },
body: fd,
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Send a natural language edit message to modify the active image.
*
* @param params - Message parameters
* @returns Edit result with generated images
*/
export async function sendEditMessage(params) {
const { backendUrl, apiKey, conversationId, ...body } = params;
const base = backendUrl.replace(/\/+$/, '');
const url = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}/message`;
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders(apiKey),
},
body: JSON.stringify(body),
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Select a generated image as the new active base for further edits.
*
* @param params - Selection parameters
* @returns Updated session state
*/
export async function selectActiveImage(params) {
const { backendUrl, apiKey, conversationId, image_url } = params;
const base = backendUrl.replace(/\/+$/, '');
const url = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}/select`;
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders(apiKey),
},
body: JSON.stringify({ image_url }),
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Get the current state of an edit session.
*
* @param params - Session parameters
* @returns Current session state
*/
export async function getEditSession(params) {
const { backendUrl, apiKey, conversationId } = params;
const base = backendUrl.replace(/\/+$/, '');
const url = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}`;
const res = await fetch(url, {
method: 'GET',
headers: { ...authHeaders(apiKey) },
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Clear all data for an edit session.
*
* @param params - Session parameters
* @returns Success indicator
*/
export async function clearEditSession(params) {
const { backendUrl, apiKey, conversationId } = params;
const base = backendUrl.replace(/\/+$/, '');
const url = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}`;
const res = await fetch(url, {
method: 'DELETE',
headers: { ...authHeaders(apiKey) },
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Delete a single version from an edit session.
*
* @param params - Delete parameters
* @returns Updated session state
*/
export async function deleteVersion(params) {
const { backendUrl, apiKey, conversationId, imageUrl } = params;
const base = backendUrl.replace(/\/+$/, '');
const url = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}/version?image_url=${encodeURIComponent(imageUrl)}`;
const res = await fetch(url, {
method: 'DELETE',
headers: { ...authHeaders(apiKey) },
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Revert to a previous image from history.
*
* @param params - Revert parameters
* @returns Updated session state
*/
export async function revertToHistory(params) {
const { backendUrl, apiKey, conversationId, index } = params;
const base = backendUrl.replace(/\/+$/, '');
const url = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}/revert?index=${index}`;
const res = await fetch(url, {
method: 'POST',
headers: { ...authHeaders(apiKey) },
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Extract image URLs from a HomePilot response.
*
* Handles various response shapes:
* - { media: { images: [...] } }
* - { images: [...] }
* - { data: { media: { images: [...] } } }
*
* @param raw - Raw response object
* @returns Array of unique image URLs
*/
export function extractImages(raw) {
const out = [];
const add = (u) => {
if (typeof u === 'string' && (u.startsWith('http://') || u.startsWith('https://'))) {
out.push(u);
}
};
// Try media.images
const media = raw?.media;
if (media?.images && Array.isArray(media.images)) {
media.images.forEach(add);
}
// Try top-level images
if (raw?.images && Array.isArray(raw.images)) {
raw.images.forEach(add);
}
// Try data.media.images
const data = raw?.data;
if (data?.media) {
const dataMedia = data.media;
if (dataMedia?.images && Array.isArray(dataMedia.images)) {
dataMedia.images.forEach(add);
}
}
// De-duplicate while preserving order
return Array.from(new Set(out));
}
/**
* Import an existing image by URL — server-side copy, no browser round-trip.
*
* This avoids the client download → re-upload pattern that fails on WSL
* mounted filesystems and is wasteful for images already on the server.
*
* Accepts /files/{asset_id}, /comfy/view/..., or ComfyUI absolute URLs.
*
* @param params - Import parameters
* @returns New asset info with url and optional asset_id
*/
export async function importImageByUrl(params) {
const { backendUrl, apiKey, url } = params;
const base = backendUrl.replace(/\/+$/, '');
const endpoint = `${base}/v1/import-image`;
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders(apiKey),
},
body: JSON.stringify({ url }),
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}
/**
* Upload an image to an edit session by URL — entirely server-side.
*
* The backend resolves the image URL (/files/..., /comfy/view/...) to a file
* on disk and forwards it to the edit session sidecar as a multipart upload.
* No browser download/reupload needed.
*
* @param params - Upload parameters
* @returns Session state after upload
*/
export async function uploadToEditSessionByUrl(params) {
const { backendUrl, apiKey, conversationId, imageUrl } = params;
const base = backendUrl.replace(/\/+$/, '');
const endpoint = `${base}/v1/edit-sessions/${encodeURIComponent(conversationId)}/image`;
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders(apiKey),
},
body: JSON.stringify({ image_url: imageUrl }),
});
if (!res.ok) {
const text = await res.text().catch(() => `HTTP ${res.status}`);
throw new Error(text);
}
return res.json();
}