diff --git a/lib/db.js b/lib/db.js index cbdabae..d7903fb 100644 --- a/lib/db.js +++ b/lib/db.js @@ -1,8 +1,17 @@ import prisma from "./prisma"; +function enabled() { + return process.env.DATABASE_URL ? true : false; +} + export async function upsertPrediction(predictionData) { console.log("🤔 upsert prediction? ", predictionData.id); + if (!enabled()) { + console.log("skiping upsert, database not enabled"); + return; + } + if (predictionData?.status !== "succeeded") { console.log("🙈 skiping incomplete or unsuccesful prediction"); return; diff --git a/package-lock.json b/package-lock.json index 3e8d3a4..b5ef3ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-dom": "18.2.0", "react-sketch-canvas": "^6.2.0", "react-spinners": "^0.13.8", + "replicate": "github:replicate/replicate-js", "upload-js-full": "^1.22.0" }, "devDependencies": { @@ -935,6 +936,17 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -944,6 +956,45 @@ "node": ">= 6" } }, + "node_modules/camelcase-keys": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-8.0.2.tgz", + "integrity": "sha512-qMKdlOfsjlezMqxkUGGMaWWs17i2HoL15tM+wtx8ld4nLrUwU58TFdvyGOz/piNP842KeO8yXvggVQSdQ828NA==", + "dependencies": { + "camelcase": "^7.0.0", + "map-obj": "^4.3.0", + "quick-lru": "^6.1.1", + "type-fest": "^2.13.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz", + "integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -2517,6 +2568,17 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3240,6 +3302,14 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/replicate": { + "version": "0.4.0", + "resolved": "git+ssh://git@github.com/replicate/replicate-js.git#d4474a4c0d4a29537152aee59e2816ed19e1c969", + "license": "Apache-2.0", + "dependencies": { + "camelcase-keys": "^8.0.2" + } + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -4397,12 +4467,40 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==" + }, "camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "dev": true }, + "camelcase-keys": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-8.0.2.tgz", + "integrity": "sha512-qMKdlOfsjlezMqxkUGGMaWWs17i2HoL15tM+wtx8ld4nLrUwU58TFdvyGOz/piNP842KeO8yXvggVQSdQ828NA==", + "requires": { + "camelcase": "^7.0.0", + "map-obj": "^4.3.0", + "quick-lru": "^6.1.1", + "type-fest": "^2.13.0" + }, + "dependencies": { + "quick-lru": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz", + "integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==" + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + } + } + }, "camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -5576,6 +5674,11 @@ "integrity": "sha512-DiLZ0uqqt4Qpe7bc+RvJMMI7z3gWlMOnst+TtNFUH6XfQ12APMQDx+/fHsmMlIkyCs/pPo3UKZpYbZ5i1iMOuA==", "requires": {} }, + "map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==" + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6042,6 +6145,13 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "replicate": { + "version": "git+ssh://git@github.com/replicate/replicate-js.git#d4474a4c0d4a29537152aee59e2816ed19e1c969", + "from": "replicate@replicate/replicate-js", + "requires": { + "camelcase-keys": "^8.0.2" + } + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", diff --git a/package.json b/package.json index e313e2f..f564e64 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-dom": "18.2.0", "react-sketch-canvas": "^6.2.0", "react-spinners": "^0.13.8", + "replicate": "github:replicate/replicate-js", "upload-js-full": "^1.22.0" }, "devDependencies": { @@ -41,4 +42,4 @@ "tailwindcss": "^3.1.8", "tailwindcss-animate": "^1.0.5" } -} \ No newline at end of file +} diff --git a/pages/api/predictions/[id].js b/pages/api/predictions/[id].js index 7029fbb..42ac611 100644 --- a/pages/api/predictions/[id].js +++ b/pages/api/predictions/[id].js @@ -1,19 +1,13 @@ -const API_HOST = process.env.REPLICATE_API_HOST || "https://api.replicate.com"; +import replicate from "replicate"; export default async function handler(req, res) { - const response = await fetch(`${API_HOST}/v1/predictions/${req.query.id}`, { - headers: { - Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`, - "Content-Type": "application/json", - }, - }); - if (response.status !== 200) { - let error = await response.json(); + const prediction = await replicate.prediction(req.query.id).load(); + + if (prediction.error) { res.statusCode = 500; - res.end(JSON.stringify({ detail: error.detail })); + res.end(JSON.stringify({ detail: prediction.error })); return; } - const prediction = await response.json(); res.end(JSON.stringify(prediction)); } diff --git a/pages/api/predictions/index.js b/pages/api/predictions/index.js index 6c8f449..3a47f77 100644 --- a/pages/api/predictions/index.js +++ b/pages/api/predictions/index.js @@ -1,6 +1,4 @@ -const REPLICATE_API_HOST = "https://api.replicate.com"; - -import packageData from "../../../package.json"; +import replicate from "replicate"; const WEBHOOK_HOST = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` @@ -13,34 +11,25 @@ export default async function handler(req, res) { ); } - const body = JSON.stringify({ - // https://replicate.com/jagilley/controlnet-scribble/versions - version: "435061a1b5a4c1e26740464bf786efdfa9cb3a3ac488595a2de23e143fdb0117", - input: req.body, - webhook: `${WEBHOOK_HOST}/api/replicate-webhook`, - webhook_events_filter: ["start", "completed"], - }); - - const headers = { - Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`, - "Content-Type": "application/json", - "User-Agent": `${packageData.name}/${packageData.version}`, - }; - - const response = await fetch(`${REPLICATE_API_HOST}/v1/predictions`, { - method: "POST", - headers, - body, - }); + const input = req.body; + const prediction = await replicate + .model( + "jagilley/controlnet-scribble:435061a1b5a4c1e26740464bf786efdfa9cb3a3ac488595a2de23e143fdb0117" + ) + .createPrediction( + { input }, + { + webhook: `${WEBHOOK_HOST}/api/replicate-webhook`, + webhookEventsFilter: ["completed"], + } + ); - if (response.status !== 201) { - let error = await response.json(); + if (prediction.error) { res.statusCode = 500; - res.end(JSON.stringify({ detail: error.detail })); + res.end(JSON.stringify({ detail: prediction.error })); return; } - const prediction = await response.json(); res.statusCode = 201; res.end(JSON.stringify(prediction)); } diff --git a/pages/api/replicate-webhook.js b/pages/api/replicate-webhook.js index bee901f..b536e67 100644 --- a/pages/api/replicate-webhook.js +++ b/pages/api/replicate-webhook.js @@ -4,7 +4,7 @@ import { upsertPrediction } from "../../lib/db"; export default async function handler(req, res) { - console.log("received webhook for prediction: ", req.body.id); + console.log("🪝 received webhook for prediction: ", req.body.id); await upsertPrediction(req.body); res.end();