pediatric-ai-scribe-v3/src/routes/generatedImages.js

67 lines
4.2 KiB
JavaScript

const router = require('express').Router();
const { authMiddleware, moderatorMiddleware, adminMiddleware } = require('../middleware/auth');
const images = require('../utils/generatedImages');
const db = require('../db/database');
router.use(authMiddleware);
function fail(res, e) { res.status(e.statusCode || 503).json({ error: e.statusCode ? e.message : 'Image service unavailable' }); }
async function sendAsset(req, res, download) {
const image = await images.service().asset(req.params.id, req.user);
res.setHeader('Content-Type', image.mime);
res.setHeader('Content-Length', image.bytes.length);
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Cache-Control', 'private, no-store');
res.setHeader('X-Image-SHA256', image.checksum);
res.setHeader('X-Image-Owner', String(req.user.id)); // requesting account ticket, not original author
res.setHeader('Content-Disposition', (download ? 'attachment' : 'inline') + '; filename="generated-image.' + ({ 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp' })[image.mime] + '"');
res.send(image.bytes);
}
router.get('/generated-images/:id', async (req, res) => {
try { await sendAsset(req, res, req.query.download === '1'); } catch (e) { fail(res, e); }
});
router.get('/image-jobs/:workflow', async (req, res) => {
try {
if (!['clinical_assistant', 'learning_hub'].includes(req.params.workflow)) throw images.failure(404, 'Workflow not found');
const result = await db.query('SELECT id,stage,model,error_code,context_included,context_total,prompt_units,budget FROM generated_image_jobs WHERE owner_id=$1 AND workflow=$2 ORDER BY created_at DESC LIMIT 100', [req.user.id, req.params.workflow]);
res.json({ success: true, jobs: result.rows.map(images.publicJob) });
} catch (e) { fail(res, e); }
});
// Mounted narrowly before the server's blanket /api/admin guards.
const learningRouter = require('express').Router();
learningRouter.use(authMiddleware, moderatorMiddleware);
learningRouter.post('/jobs', async (req, res) => {
try { res.json(await images.service().enqueue(req.user.id, 'learning_hub', { prompt: req.body.prompt, ...(req.body.layout === undefined ? {} : { layout: req.body.layout }) }, images.requestKey(req.body), false, req.body.content === undefined ? undefined : images.imageContext(req.body.prompt, [{ role: 'user', content: req.body.content }]))); }
catch (e) { fail(res, e); }
});
learningRouter.get('/jobs/:id', async (req, res) => {
try { res.json(await images.service().get(req.params.id, req.user.id, 'learning_hub')); } catch (e) { fail(res, e); }
});
router.get('/admin/image-settings', adminMiddleware, async (req, res) => {
try {
const workflows = {};
for (const workflow of ['clinical_assistant', 'learning_hub']) workflows[workflow] = {
model: await db.getSetting(workflow + '.image_model') || '',
budget: images.budgetLimit(await db.getSetting(workflow + '.image_budget')), unit: 'UTF-16 code units'
};
res.json({ success: true, workflows });
} catch (e) { fail(res, e); }
});
router.put('/admin/image-settings/:workflow', adminMiddleware, async (req, res) => {
try {
const workflow = req.params.workflow;
if (!['clinical_assistant', 'learning_hub'].includes(workflow)) throw images.failure(404, 'Workflow not found');
const budget = images.budgetLimit(req.body.budget);
if (workflow === 'learning_hub' && (typeof req.body.model !== 'string' || !/^[a-zA-Z0-9_.:/-]{1,200}$/.test(req.body.model))) throw images.failure(400, 'Enter an image model ID enabled at the configured image gateway');
const client = await db.pool.connect();
try {
await client.query('BEGIN');
for (const [key, value] of Object.entries(workflow === 'learning_hub' ? { image_model: req.body.model, image_budget: budget } : { image_budget: budget })) {
await client.query('INSERT INTO app_settings(key,value) VALUES($1,$2) ON CONFLICT(key) DO UPDATE SET value=$2,updated_at=NOW()', [workflow + '.' + key, String(value)]);
}
await client.query('COMMIT');
} catch (e) { await client.query('ROLLBACK').catch(() => {}); throw e; } finally { client.release(); }
res.json({ success: true });
} catch (e) { fail(res, e); }
});
module.exports = router;
module.exports.sendAsset = sendAsset;
module.exports.learningRouter = learningRouter;