feat: api for generating images

This commit is contained in:
Zitao Xiong
2023-12-12 14:34:01 +08:00
parent debc179d7d
commit 90f5fb50ef
9 changed files with 668 additions and 44 deletions

8
functions/.envrc Normal file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
PATH_add ./node_modules/.bin
if [ -f .envrc.override ]; then
source_env .envrc.override
fi

View File

@@ -7,3 +7,4 @@ typings/
# Node.js dependency directory
node_modules/
lib/

1
functions/.tool-versions Normal file
View File

@@ -0,0 +1 @@
nodejs 18.19.0

View File

@@ -14,13 +14,19 @@
},
"main": "lib/index.js",
"dependencies": {
"@google-cloud/functions-framework": "^3.3.0",
"@types/express": "^4.17.21",
"express": "^4.18.2",
"firebase-admin": "^11.11.1",
"firebase-functions": "^4.3.1",
"got-cjs": "^12.5.4",
"model": "../model",
"firebase-admin": "^11.8.0",
"firebase-functions": "^4.3.1"
"nanoid": "3",
"openai": "^4.20.1"
},
"devDependencies": {
"typescript": "^5.3.3",
"firebase-functions-test": "^3.1.0"
"firebase-functions-test": "^3.1.0",
"typescript": "^5.3.3"
},
"private": true
}

563
functions/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "storytime-web3",
"private_key_id": "c948532af7977cea6405e645825b2bdfd2c3e3d6",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDClnYzEADTVtoI\nKEt2gnpEpEKQLVOVWao7Z2QNalKtuEtZb3GqL5tFDWi5d0CzejoqADQTq30bIV/m\narYk5JnCDG8NlJ7prcJc2t5KfrKOX/ReVdy7Jn1pCy3wv7S5qChEYNOojzDufEmb\nBFclFUUGlFkZO5lgQHKQfXUr0j7khL0Z0d6QxhT8D4tEqLjCYz+ZRlPvHdEvP9kf\naBc3e/amQNXwwyhXidtOPwYsVkIBTmHBtUYXx4gcTJrOwumHwhJYQcRpgfjtt2yO\ncARYRlADtArJY+SXY6laSD9KNZfJ3CWKZtGcbtdMCOcRdMlD5hIOS7HH02Iy7wcv\nHuBckoW/AgMBAAECggEAGLTkUXZyNw3w2sFMV5dDMVej20zjWclhO8VUZv79fBuJ\ndv5snF21ZW6e7JGPha+la/N2U/BduaX4Aw3Ajm2iIjPUSTv6HhSBuFV9N6XfmQPm\nR1tijowEDLmD00IcDx337ZACaoVdLU1a8Sj7bEoc517K81EQbTAG+9aaG9QdnS5k\nD0HyHbcNneT6lQrjv+3iMh7zGwzmSWSxD81fKhrGalAB97OZwdiwZSQKp2icySRU\nsnhk0FFsggQ2KHaX26zrp0s9VSUGMvD82xDymSU6YWwrIlaC2P1lDy72bo2EaeRS\nh9qZKld7e1vFNUuKaZhiHPNF8CgEhq9ztVvOVgQW3QKBgQDmJqhDyoWSg7Qr1rba\nOs4UOigQnyRrd8vwarxyvm97tUTbzAeyaKvfxSNlxxRnGowh2Gm92sX7togJ+5w1\nnTBjxQjvvFLaTEsUdRQUPH7UcEbG71+YJpS82Izznc4UNYP3GLlsljQ20eQ+2PM/\nrHc4zm+qZb1MVoXiduCjKvU5SwKBgQDYcUh7J0TiKTDTWyyHagDVIImz0yfY3FXf\noqov8kJemApHgXSkH4NjSQV7bDpBWytt+kZEjIlLMXCx9/gUNRQIdAzX4n5qeOZr\nohRP6Fh7atycnlQQ+ifrjPcb+3OEuccxog+6nTqbTaJMig0Pa3yJqR1psIHlOxSF\nzeFzgSYw3QKBgQDRQmzT1pxEb0OuaHexTK57bbVoB4rRvAbN/f6CReyPim2le4m0\nCb/coh5hN/WxGU44p9DdMsG48GoYyZFqnhWVYV2SvKSIn73UR++NxsN63Os9jgBZ\nExtB6ZOfHeh2L4JhdTWDKb8n6QeirRfe2S09lVWqlP7dHf51vqjZMwHsqQKBgQC/\no9YETOm9wbDcgs6ze1UPYAbstJqEddqG73T2jO7c6Iu5clL/enOP9jinZlVSRtH2\nR3HuAe0SKc1ZlnAOHE0HixFQGNfLmA6U4GZRtiyZ4i1BcyKGAahU9HRbT1GiBAft\n10tL9SjOF3gLgvQ7YfVbweQsDz+D4sKyEm97IK/a9QKBgDNz3sj+qkbgMn0sW2Fw\n3o5h9oWvEk2Zvvpo8+g/w3xEgoS2RbboqsCAN+JNw/fKecFrlZA97xVgd6fQjZNb\nfnqPRx3uHFKQwaOGaEAc5yTA5JFDSBdeRO5qXrHnPTyqNbwk4hzIAz+LcSn0WJgW\neG0OLLjO1ZadmECODGEHlp8U\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-i9jvg@storytime-web3.iam.gserviceaccount.com",
"client_id": "112791080690462614850",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-i9jvg%40storytime-web3.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View File

@@ -7,15 +7,115 @@
* See a full list of supported triggers at https://firebase.google.com/docs/functions
*/
import { onCall } from "firebase-functions/v2/https";
import { onCall, onRequest, Request } from "firebase-functions/v2/https";
import * as logger from "firebase-functions/logger";
import { OpenAI } from "openai";
import * as express from "express";
import admin from "firebase-admin";
import { getStorage } from "firebase-admin/storage";
import got from "got-cjs";
import { nanoid } from "nanoid";
import adminKey from "./credentials/admin-key.json";
admin.initializeApp({
credential: admin.credential.cert(adminKey as any),
});
// Start writing functions
// https://firebase.google.com/docs/functions/typescript
export const execute = onCall((request) => {
export const execute = onCall({ region: "asia-east1" }, (request) => {
logger.info("Hello logs!", { structuredData: true });
return {
data: "Hello from Firebase!",
};
});
const openai = new OpenAI({
apiKey: "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",
});
const generateImage = async (req: Request, resp: express.Response) => {
const prompt = req.body.prompt;
if (prompt == null || prompt.length === 0) {
throw new Error("prompt is required");
}
const generateImageResponse = await openai.images.generate({
model: "dall-e-3",
prompt: prompt,
n: 1,
size: "1792x1024",
});
const imageUrl = generateImageResponse.data[0].url;
const revised_prompt = generateImageResponse.data[0].revised_prompt;
const bucket = getStorage().bucket("storytime-ai-images");
// response.send(generateImageResponse);
// Validate imageUrl
if (!imageUrl) {
resp.status(400).send("No image URL provided.");
return;
}
try {
const response = await fetch(imageUrl);
// Check if image fetch was successful
if (!response.ok)
throw new Error(`Unable to fetch image: ${response.statusText}`);
// Create a reference in Firebase Storage
const fileName = `images/${Date.now()}-${nanoid()}.png`;
const file = bucket.file(fileName);
const writeStream = file.createWriteStream();
got
.stream(imageUrl)
.on("error", (error) => {
resp.status(500).send(`Error downloading image: ${error.message}`);
})
.pipe(writeStream)
.on("finish", () => {
file
.getSignedUrl({
action: "read",
expires: "03-09-2491", // You can provide an expiry date for the URL
})
.then((signedUrls) => {
// signedUrls[0] contains the file's public URL
resp.status(200).send({ image_url: signedUrls[0], revised_prompt });
})
.catch((error) => {
resp.status(500).send(`Error getting signed URL: ${error.message}`);
});
})
.on("error", (error) => {
resp
.status(500)
.send(`Error uploading image to Firebase Storage: ${error.message}`);
});
} catch (error) {
resp.status(500).send(`Error downloading image: ${error}`);
}
};
export const request = onRequest(
{
memory: "4GiB",
region: "asia-east1",
invoker: "public",
minInstances: 1,
timeoutSeconds: 3600,
},
(request, response) => {
if (request.path === "/generate-image") {
generateImage(request, response);
} else {
response.status(200).send({
routes: ["/generate-image"],
});
}
}
);

View File

@@ -2,11 +2,13 @@
"compilerOptions": {
"module": "commonjs",
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"outDir": "lib",
"sourceMap": true,
"strict": true,
"target": "es2017"
"esModuleInterop": true,
"resolveJsonModule": true,
"target": "ES2022"
},
"compileOnSave": true,
"include": [

View File

@@ -37,7 +37,7 @@ current:
Generate image base on current scene while ensuring the options are in the scene. Ensure the character is accurate to the description and use the descriptions defined in the style, and setting section
`,
n: 1,
size: "1024x1024",
size: "1792x1024",
});
console.log(`response: ${JSON.stringify(response, null, 2)}`);