From 62bc8fc7da909efad6ead514e03a9bc8f9ebe27f Mon Sep 17 00:00:00 2001
From: TJ Maynes
Date: Mon, 15 Jan 2024 11:23:29 -0600
Subject: [PATCH] chore: remove cloudflare kv requirement for project, single
read-only file works for me
---
Makefile | 10 +--
README.md | 2 +-
next.config.js | 8 --
package.json | 2 +-
script/cloudflare/add-cloudflare-kv-value.sh | 40 ----------
.../cloudflare/ensure-cloudflare-kv-exists.sh | 34 ---------
script/{ => data}/image-classes.json | 0
script/{ => data}/seed.js | 75 ++++---------------
src/app/_components/BackgroundInfo.tsx | 15 ++--
src/app/api/v1/image/description/route.ts | 23 ++----
{data => src/data}/seed.json | 0
src/env.d.ts | 12 ---
src/lib/cache-client.ts | 41 ----------
src/lib/openai.ts | 28 -------
14 files changed, 35 insertions(+), 255 deletions(-)
delete mode 100644 script/cloudflare/add-cloudflare-kv-value.sh
delete mode 100755 script/cloudflare/ensure-cloudflare-kv-exists.sh
rename script/{ => data}/image-classes.json (100%)
rename script/{ => data}/seed.js (72%)
rename {data => src/data}/seed.json (100%)
delete mode 100644 src/env.d.ts
delete mode 100644 src/lib/cache-client.ts
delete mode 100644 src/lib/openai.ts
diff --git a/Makefile b/Makefile
index 9dac044..c74f509 100644
--- a/Makefile
+++ b/Makefile
@@ -24,10 +24,6 @@ performance:
test: performance
-ensure_cloudflare_kv_exists:
- chmod +x ./script/cloudflare/ensure-cloudflare-kv-exists.sh
- ./script/cloudflare/ensure-cloudflare-kv-exists.sh "IMAGE_ANALYZER_KV"
-
ensure_cloudflare_page_exists:
chmod +x ./script/cloudflare/ensure-cloudflare-pages-exists.sh
./script/cloudflare/ensure-cloudflare-pages-exists.sh "image-analyzer-app"
@@ -35,7 +31,7 @@ ensure_cloudflare_page_exists:
ensure_cloudflare_secrets_exist:
@$(call add_cloudflare_secret,"image-analyzer-app","NODE_VERSION",$(shell cat .node-version))
-ensure_cloudflare_infra_exists: ensure_cloudflare_page_exists ensure_cloudflare_kv_exists ensure_cloudflare_secrets_exist
+ensure_cloudflare_infra_exists: ensure_cloudflare_page_exists ensure_cloudflare_secrets_exist
deploy_cloudflare_page:
npm run pages:deploy
@@ -43,9 +39,7 @@ deploy_cloudflare_page:
deploy: install build ensure_cloudflare_infra_exists deploy_cloudflare_page
seed:
- node ./script/seed.js \
- --cloudflare-kv-binding-id "5df82e748f494385a2aeaf2912cbb359" \
- --seed-file "./data/seed.json"
+ node ./script/data/seed.js --seed-file "./src/data/seed.json"
clean:
rm -rf .next/ .vercel/ build/
diff --git a/README.md b/README.md
index d30ae4e..afe3516 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# image-analyzer-app
-> a NextJS app that allows users to analyze images using MobileNet (via TensorflowJS), ChatGPT, and Cloudflare Pages
+> a NextJS app that allows users to analyze images using MobileNet (via TensorflowJS), image descriptions from ChatGPT, and hosted on Cloudflare Pages
## Requirements
diff --git a/next.config.js b/next.config.js
index 4846ddc..767719f 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,12 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
-const {
- setupDevBindings,
-} = require('@cloudflare/next-on-pages/__experimental__next-dev')
-
-setupDevBindings({
- kvNamespaces: ['IMAGE_ANALYZER_KV'],
-})
-
module.exports = nextConfig
diff --git a/package.json b/package.json
index aeb425d..bc737ae 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
"pages:build": "npx @cloudflare/next-on-pages",
"pages:deploy": "wrangler pages deploy .vercel/output/static --project-name='image-analyzer-app'",
"pages:watch": "next-on-pages --watch",
- "pages:dev": "npm run pages:build && wrangler pages dev .vercel/output/static --compatibility-date=2023-10-30 --compatibility-flag=nodejs_compat --kv IMAGE_ANALYZER_KV"
+ "pages:dev": "npm run pages:build && wrangler pages dev .vercel/output/static --compatibility-date=2023-10-30 --compatibility-flag=nodejs_compat"
},
"dependencies": {
"@tensorflow-models/mobilenet": "^2.1.1",
diff --git a/script/cloudflare/add-cloudflare-kv-value.sh b/script/cloudflare/add-cloudflare-kv-value.sh
deleted file mode 100644
index e31ead2..0000000
--- a/script/cloudflare/add-cloudflare-kv-value.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-CLOUDFLARE_KV_BINDING_ID=$1
-CLOUDFLARE_KV_KEY=$2
-CLOUDFLARE_KV_VALUE=$3
-
-function check_requirements() {
- if [[ -z "$(command -v node)" ]]; then
- echo "Please install 'node' program before running this script"
- exit 1
- elif [[ -z "$CLOUDFLARE_ACCOUNT_ID" ]]; then
- echo "Please ensure environment variable 'CLOUDFLARE_ACCOUNT_ID' exists before running this script"
- exit 1
- elif [[ -z "$CLOUDFLARE_API_TOKEN" ]]; then
- echo "Please ensure environment variable 'CLOUDFLARE_API_TOKEN' exists before running this script"
- exit 1
- elif [[ -z "$CLOUDFLARE_KV_BINDING_ID" ]]; then
- echo "Arg 1 for script 'CLOUDFLARE_KV_BINDING_ID' was not given..."
- exit 1
- elif [[ -z "$CLOUDFLARE_KV_KEY" ]]; then
- echo "Arg 2 for script 'CLOUDFLARE_KV_KEY' was not given..."
- exit 1
- elif [[ -z "$CLOUDFLARE_KV_VALUE" ]]; then
- echo "Arg 3 for script 'CLOUDFLARE_KV_VALUE' was not given..."
- exit 1
- fi
-}
-
-function main() {
- check_requirements
-
- if ! ./node_modules/.bin/wrangler kv:key get "$CLOUDFLARE_KV_KEY" --namespace-id="$CLOUDFLARE_KV_BINDING_ID" > /dev/null 2>&1; then
- echo "Adding Cloudflare KV key '$CLOUDFLARE_KV_KEY' and value '$CLOUDFLARE_KV_VALUE' to KV '$CLOUDFLARE_KV_NAME'..."
- ./node_modules/.bin/wrangler kv:key put "$CLOUDFLARE_KV_KEY" "$CLOUDFLARE_KV_VALUE" --namespace-id="$CLOUDFLARE_KV_BINDING_ID"
- fi
-}
-
-main
diff --git a/script/cloudflare/ensure-cloudflare-kv-exists.sh b/script/cloudflare/ensure-cloudflare-kv-exists.sh
deleted file mode 100755
index ef37997..0000000
--- a/script/cloudflare/ensure-cloudflare-kv-exists.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-CLOUDFLARE_KV_NAME=$1
-
-function check_requirements() {
- if [[ -z "$(command -v node)" ]]; then
- echo "Please install 'node' program before running this script"
- exit 1
- elif [[ -z "$CLOUDFLARE_ACCOUNT_ID" ]]; then
- echo "Please ensure environment variable 'CLOUDFLARE_ACCOUNT_ID' exists before running this script"
- exit 1
- elif [[ -z "$CLOUDFLARE_API_TOKEN" ]]; then
- echo "Please ensure environment variable 'CLOUDFLARE_API_TOKEN' exists before running this script"
- exit 1
- elif [[ -z "$CLOUDFLARE_KV_NAME" ]]; then
- echo "Arg 1 for script 'CLOUDFLARE_KV_NAME' was not given..."
- exit 1
- fi
-}
-
-function main() {
- check_requirements
-
- if ./node_modules/.bin/wrangler kv:namespace list | grep -q "$CLOUDFLARE_KV_NAME"; then
- echo -e "\nCloudflare KV '$CLOUDFLARE_KV_NAME' already exists...\n"
- else
- echo "Cloudflare KV '$CLOUDFLARE_KV_NAME' does not exist..."
- ./node_modules/.bin/wrangler kv:namespace create "$CLOUDFLARE_KV_NAME"
- fi
-}
-
-main
diff --git a/script/image-classes.json b/script/data/image-classes.json
similarity index 100%
rename from script/image-classes.json
rename to script/data/image-classes.json
diff --git a/script/seed.js b/script/data/seed.js
similarity index 72%
rename from script/seed.js
rename to script/data/seed.js
index b1bb505..66e3444 100644
--- a/script/seed.js
+++ b/script/data/seed.js
@@ -1,5 +1,4 @@
const fs = require('fs/promises')
-const { execSync } = require('child_process')
const OpenAI = require('openai')
const imageClasses = require('./image-classes.json')
@@ -10,30 +9,23 @@ const throwIfEnvVarDoesNotExist = (envVarName) => {
}
}
-const cleanDescription = (description) =>
- description.replace(/\"/g, "'").replace(/(\r\n|\n|\r)/gm, ' ')
+const getFlagValue = (flagName) => {
+ const flagIndex = process.argv.indexOf(`--${flagName}`)
+ const flagValue = flagIndex > -1 ? process.argv[flagIndex + 1] : ''
-const addKeyValueToCloudflareKV = (cloudflareKVBindingId, key, value) => {
- console.log(
- `Attempting to add key "${key}" to Cloudflare KV: '${cloudflareKVBindingId}'`
- )
+ if (!flagValue) {
+ console.error(
+ `Expected required flag '--${flagName}' to be passed to script!`
+ )
+ process.exit(1)
+ }
- execSync(
- `bash ./script/cloudflare/add-cloudflare-kv-value.sh "${cloudflareKVBindingId}" "${key}" "${value}"`,
- (err, output) => {
- if (err) {
- console.error('could not execute command: ', err)
- return
- }
- console.log('Output: \n', output)
- }
- )
+ return flagValue
}
const batchSeedProcess = async (
currentSeedFileContent,
seedFile,
- cloudflareKVBindingId,
limitBatchSize = 5
) => {
const currentClassifications = imageClasses.slice(
@@ -103,14 +95,6 @@ const batchSeedProcess = async (
flag: 'w',
})
- newSeedData.forEach(({ name, description }) => {
- addKeyValueToCloudflareKV(
- cloudflareKVBindingId,
- name,
- cleanDescription(description)
- )
- })
-
latestSeedFileContent = newSeedFileContent
}
@@ -139,20 +123,6 @@ const ensureSeedFileExists = (seedFile, onSeedFileConfirmation) =>
}
})
-const getFlag = (flagName) => {
- const flagIndex = process.argv.indexOf(`--${flagName}`)
- const flagValue = flagIndex > -1 ? process.argv[flagIndex + 1] : ''
-
- if (!flagValue) {
- console.error(
- `Expected required flag '--${flagName}' to be passed to script!`
- )
- process.exit(1)
- }
-
- return flagValue
-}
-
const main = async () => {
;['CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_API_TOKEN', 'OPENAI_API_KEY'].forEach(
(envVarName) => throwIfEnvVarDoesNotExist(envVarName)
@@ -163,26 +133,13 @@ const main = async () => {
process.exit(1)
}
- const [seedFile, cloudflareKVBindingId] = [
- getFlag('seed-file'),
- getFlag('cloudflare-kv-binding-id'),
- ]
-
- const updatedSeedData = await ensureSeedFileExists(
- seedFile,
- (currentSeedData) =>
- currentSeedData.data.length !== imageClasses.length
- ? batchSeedProcess(currentSeedData, seedFile, cloudflareKVBindingId, 5)
- : currentSeedData
- )
+ const [seedFile] = [getFlagValue('seed-file')]
- updatedSeedData.data.forEach(({ name, description }) => {
- addKeyValueToCloudflareKV(
- cloudflareKVBindingId,
- name,
- cleanDescription(description)
- )
- })
+ return await ensureSeedFileExists(seedFile, (currentSeedData) =>
+ currentSeedData.data.length !== imageClasses.length
+ ? batchSeedProcess(currentSeedData, seedFile, 5)
+ : currentSeedData
+ )
}
main().then(() => console.log('Done!'))
diff --git a/src/app/_components/BackgroundInfo.tsx b/src/app/_components/BackgroundInfo.tsx
index 4a1f044..4f77d53 100644
--- a/src/app/_components/BackgroundInfo.tsx
+++ b/src/app/_components/BackgroundInfo.tsx
@@ -10,15 +10,16 @@ const BackgroundInfo = () => (
ChatGPT
{' '}
- (for generating image descriptions), and{' '}
+ (for generating image descriptions), and hosted on{' '}
-
- Cloudflare KV
-
+ Cloudflare Pages
{' '}
- (read-only database pairing "image name" to "image
- description"). I built this web application to learn how to run maching
- learning models in web browsers. Please contact me on{' '}
+ (via{' '}
+
+ next-on-pages
+
+ ). I built this web application to learn how to run maching learning models
+ in web browsers. Please contact me on{' '}
LinkedIn for any feedback or
concerns. Enjoy! 😀
diff --git a/src/app/api/v1/image/description/route.ts b/src/app/api/v1/image/description/route.ts
index 623175b..29cd341 100644
--- a/src/app/api/v1/image/description/route.ts
+++ b/src/app/api/v1/image/description/route.ts
@@ -1,23 +1,14 @@
import 'server-only'
import { NextRequest, NextResponse } from 'next/server'
-import {
- CacheClient,
- InMemoryCacheClient,
- KVCacheClient,
-} from '@/lib/cache-client'
+import seedData from '@/data/seed.json'
-export const runtime = 'edge'
-
-const createCacheClient = (): CacheClient => {
- if (process.env.NODE_ENV === 'development') return new InMemoryCacheClient()
-
- const IMAGE_ANALYZER_KV = process.env.IMAGE_ANALYZER_KV
+const imageDescriptionData = seedData.data.reduce>(
+ (accum, curr) => ({ ...accum, ...{ [curr.name]: curr.description } }),
+ {}
+)
- return new KVCacheClient(IMAGE_ANALYZER_KV)
-}
-
-const cacheClient = createCacheClient()
+export const runtime = 'edge'
export async function GET(request: NextRequest) {
const thing = request.nextUrl.searchParams.get('thing')
@@ -28,7 +19,7 @@ export async function GET(request: NextRequest) {
status: 422,
})
- const response = await cacheClient.get(thing)
+ const response = imageDescriptionData[thing]
return response
? NextResponse.json({ message: response, status: 200 })
diff --git a/data/seed.json b/src/data/seed.json
similarity index 100%
rename from data/seed.json
rename to src/data/seed.json
diff --git a/src/env.d.ts b/src/env.d.ts
deleted file mode 100644
index 422cb57..0000000
--- a/src/env.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { KVNamespace } from '@cloudflare/workers-types'
-
-declare global {
- namespace NodeJS {
- interface ProcessEnv {
- [key: string]: string | undefined
- IMAGE_ANALYZER_KV: KVNamespace
- }
- }
-}
-
-export {}
diff --git a/src/lib/cache-client.ts b/src/lib/cache-client.ts
deleted file mode 100644
index 3b9a38e..0000000
--- a/src/lib/cache-client.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import 'server-only'
-
-import { KVNamespace } from '@cloudflare/workers-types'
-
-export interface CacheClient {
- get(key: string): Promise
- put(key: string, value: string): Promise
-}
-
-export class KVCacheClient implements CacheClient {
- private readonly store: KVNamespace
-
- constructor(store: KVNamespace) {
- this.store = store
- }
-
- get(key: string): Promise {
- return this.store.get(key)
- }
-
- put(key: string, value: string): Promise {
- return this.store.put(key, value)
- }
-}
-
-export class InMemoryCacheClient implements CacheClient {
- private readonly store: Record
-
- constructor(store: any = {}) {
- this.store = store
- }
-
- get(key: string): Promise {
- return Promise.resolve(this.store[key] ?? null)
- }
-
- put(key: string, value: string): Promise {
- this.store[key] = value
- return Promise.resolve()
- }
-}
diff --git a/src/lib/openai.ts b/src/lib/openai.ts
deleted file mode 100644
index 8dcfc17..0000000
--- a/src/lib/openai.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import 'server-only'
-
-import OpenAI from 'openai'
-
-const openai = new OpenAI({
- apiKey: process.env.OPENAI_API_KEY,
-})
-
-export interface IOpenAIWrapper {
- chat(prompt: string): Promise<{ response: string }>
-}
-
-export default class OpenAIWrapper implements IOpenAIWrapper {
- async chat(prompt: string): Promise<{ response: string }> {
- try {
- const response = await openai.chat.completions.create({
- model: 'gpt-3.5-turbo',
- messages: [{ role: 'user', content: prompt }],
- })
-
- const openaiResponse = response.choices.join(', ')
-
- return Promise.resolve({ response: openaiResponse })
- } catch (e) {
- return Promise.reject(e)
- }
- }
-}