feat(heal): add server-side AI inpainting endpoint via ONNX Runtime

This commit is contained in:
2026-10-16 20:23:24 +07:00
parent a5031c76d5
commit 8aa59bcfb2
5 changed files with 321 additions and 2 deletions
+38
View File
@@ -1,3 +1,4 @@
import { runServerInpaint } from './inpaintService';
import { recipeFile } from './recipeFile';
import { sendVerificationMail } from './mailer';
import { placeName } from './place';
@@ -425,6 +426,43 @@ async function lookupGeo(ip: string): Promise<GeoPlace | null> {
// ---- routes ----
app.get('/api/health', async () => ({ ok: true }));
// AI Inpainting endpoint (Hybrid Option 2)
app.post('/api/inpaint', { bodyLimit: 10 * 1024 * 1024 }, async (req, reply) => {
const b = bodyOf(req) as {
src?: string;
mask?: string;
w?: number;
h?: number;
};
if (!b || !b.src || !b.mask || typeof b.w !== 'number' || typeof b.h !== 'number') {
return reply.status(400).send({ ok: false, error: 'Missing src, mask, w, or h' });
}
const { w, h } = b;
const srcBuf = Buffer.from(b.src, 'base64');
const maskBuf = Buffer.from(b.mask, 'base64');
if (srcBuf.length !== 3 * w * h || maskBuf.length !== w * h) {
return reply.status(400).send({ ok: false, error: 'Dimension mismatch' });
}
try {
const patch = await runServerInpaint(new Uint8Array(srcBuf), new Uint8Array(maskBuf), w, h);
if (!patch) {
return reply.status(503).send({ ok: false, error: 'Inpaint model unavailable' });
}
return reply.send({
ok: true,
patch: Buffer.from(patch).toString('base64'),
w,
h,
});
} catch (err: any) {
req.log.error(err, 'Inpaint inference error');
return reply.status(500).send({ ok: false, error: String(err?.message || err) });
}
});
// Native file manager launcher (Open in window explorer)
app.post('/api/open-explorer', async (req, reply) => {
const b = bodyOf(req);