From 84987129e669facdcd01e2a6c8a5107cc5288150 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Thu, 23 Nov 2023 12:45:46 +0100 Subject: [PATCH] feat: add recover ai route --- pb_migrations/1700739396_updated_invites.js | 16 ++++ pb_migrations/1700739818_updated_invites.js | 16 ++++ pb_migrations/1700739877_updated_invites.js | 31 +++++++ src/lib/helpers/pb.ts | 10 +++ src/routes/api/ai/image/[name]/+server.ts | 2 +- src/routes/api/ai/recover/[id]/+server.ts | 91 +++++++++++++++++++++ 6 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 pb_migrations/1700739396_updated_invites.js create mode 100644 pb_migrations/1700739818_updated_invites.js create mode 100644 pb_migrations/1700739877_updated_invites.js create mode 100644 src/routes/api/ai/recover/[id]/+server.ts diff --git a/pb_migrations/1700739396_updated_invites.js b/pb_migrations/1700739396_updated_invites.js new file mode 100644 index 0000000..9cb3d3f --- /dev/null +++ b/pb_migrations/1700739396_updated_invites.js @@ -0,0 +1,16 @@ +/// +migrate((db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k") + + collection.viewRule = "" + + return dao.saveCollection(collection) +}, (db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k") + + collection.viewRule = null + + return dao.saveCollection(collection) +}) diff --git a/pb_migrations/1700739818_updated_invites.js b/pb_migrations/1700739818_updated_invites.js new file mode 100644 index 0000000..4c33861 --- /dev/null +++ b/pb_migrations/1700739818_updated_invites.js @@ -0,0 +1,16 @@ +/// +migrate((db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k") + + collection.updateRule = "" + + return dao.saveCollection(collection) +}, (db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k") + + collection.updateRule = null + + return dao.saveCollection(collection) +}) diff --git a/pb_migrations/1700739877_updated_invites.js b/pb_migrations/1700739877_updated_invites.js new file mode 100644 index 0000000..9fca2d3 --- /dev/null +++ b/pb_migrations/1700739877_updated_invites.js @@ -0,0 +1,31 @@ +/// +migrate((db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k") + + // add + collection.schema.addField(new SchemaField({ + "system": false, + "id": "6j9gxbgv", + "name": "skin_color", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + })) + + return dao.saveCollection(collection) +}, (db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("zfbbb4gbdk9dh6k") + + // remove + collection.schema.removeField("6j9gxbgv") + + return dao.saveCollection(collection) +}) diff --git a/src/lib/helpers/pb.ts b/src/lib/helpers/pb.ts index 18ae576..d53e03f 100644 --- a/src/lib/helpers/pb.ts +++ b/src/lib/helpers/pb.ts @@ -9,6 +9,16 @@ export async function getPublicPortraits() { })).items } +export function getInvite(id: string) { + return pb.collection("invites").getOne(id) +} + +export function setInvitePortrait(id: string, url: string) { + return pb.collection("invites").update(id, { + portrait: url + }) +} + export function createPerson({ name, confidence, portrait, portrait_public, noble_name, hair_color, hair_type, hair_length, skin_color }: { name: string, portrait: string, portrait_public: boolean, hair_type: string, hair_length: string, hair_color: string, confidence: number, noble_name: string, skin_color: string }) { return pb.collection("invites").create({ name, diff --git a/src/routes/api/ai/image/[name]/+server.ts b/src/routes/api/ai/image/[name]/+server.ts index 59a6d4e..c06cef4 100644 --- a/src/routes/api/ai/image/[name]/+server.ts +++ b/src/routes/api/ai/image/[name]/+server.ts @@ -57,7 +57,7 @@ export const POST: RequestHandler = async ({ params, request }) => { } const prompt = `realistic sharp portrait oil painting of a masked ${inputName}, baroque, in the style of Charles Vess, masked ball attire, opulence, mystery, elegance, ${hairLength} ${hairType} ${hairColor} hair, ${skinColor} skin`; - const negativePrompt = "blurry, multiple persons, picture frame" + const negativePrompt = "blurry, multiple persons, picture frame, nsfw" const a = performance.now() const image = await generateImage(prompt, negativePrompt); diff --git a/src/routes/api/ai/recover/[id]/+server.ts b/src/routes/api/ai/recover/[id]/+server.ts new file mode 100644 index 0000000..6fad920 --- /dev/null +++ b/src/routes/api/ai/recover/[id]/+server.ts @@ -0,0 +1,91 @@ +import { json } from "@sveltejs/kit"; +import type { RequestHandler } from "./$types"; +import { putObject } from "$lib/helpers/minio"; +import { generateImage } from "$lib/helpers/stability"; +import sharp from "sharp"; +import * as pb from "$lib/helpers/pb" + +async function compressImage(imageName: string, imageBuffer: Buffer) { + + const ja = performance.now() + const jpgBuffer = await sharp(imageBuffer) + .jpeg({ quality: 70 }) + .withMetadata() + .toBuffer(); + + await putObject(imageName.replace(".png", ".jpg"), jpgBuffer, { "Content-Type": "image/jpeg" }); + + const jb = performance.now() - ja; + console.log(`[AI] JPG compression took ${jb}ms`) + + const wa = performance.now() + const webpBuffer = await sharp(imageBuffer) + .webp({ quality: 70 }) + .withMetadata() + .toBuffer() + + await putObject(imageName.replace(".png", ".webp"), webpBuffer, { "Content-Type": "image/webp" }); + const wb = performance.now() - wa; + console.log(`[AI] WebP compression took ${wb}ms`) + + const aa = performance.now() + const aviBuffer = await sharp(imageBuffer) + .avif({ quality: 70 }) + .withMetadata() + .toBuffer() + + await putObject(imageName.replace(".png", ".avif"), aviBuffer, { "Content-Type": "image/avif" }); + const ab = performance.now() - aa; + console.log(`[AI] AVIF compression took ${ab}ms`) +} + +export const GET: RequestHandler = async ({ params }) => { + + const inputId = params.id; + if (!inputId) { + throw new Error("Missing name"); + } + + const invite = await pb.getInvite(inputId); + if (!invite) { + throw new Error("Invite not found"); + } + + const { hair_color, hair_type, hair_length, noble_name, skin_color, name } = invite; + + console.log(`[AI] Generating image for ${name} ${JSON.stringify({ hair_type, hair_color, hair_length, skin_color, name })}`) + if (!hair_type || !hair_color || !hair_length || !noble_name || !skin_color) { + throw new Error("Missing hairType, hairColor or hairLength"); + } + + const prompt = `realistic sharp portrait oil painting of a masked ${noble_name}, baroque, in the style of Charles Vess, masked ball attire, opulence, mystery, elegance, ${hair_length} ${hair_type} ${hair_color} hair, ${skin_color} skin`; + const negativePrompt = "blurry, multiple persons, picture frame, nsfw" + + const a = performance.now() + const image = await generateImage(prompt, negativePrompt); + const d = performance.now() - a; + console.log(`[AI] Image generation took ${d}ms`) + + const imageName = `${Math.random().toString(16).substring(3, 10)}-${noble_name.toLowerCase().split(" ").slice(0, 5).join("-").slice(0, 25).split("-").filter(l => l.length).join("-")}.png` + + const imageBuffer = Buffer.from(image.base64, 'base64'); + + const a2 = performance.now() + const pngBuffer = await sharp(imageBuffer) + .png({ compressionLevel: 9, adaptiveFiltering: true, force: true }) + .withMetadata() + .toBuffer() + + await putObject(imageName, pngBuffer, { "Content-Type": "image/png" }); + const d2 = performance.now() - a2; + console.log(`[AI] PNG compression took ${d2}ms`) + + await compressImage(imageName, imageBuffer) + + await pb.setInvitePortrait(inputId, `https://s3.max-richter.dev/silvester23/${imageName}`); + + return json({ + url: `https://s3.max-richter.dev/silvester23/${imageName}` + }) +} +