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}`
+ })
+}
+