diff --git a/.env.compose b/.env.compose new file mode 100644 index 0000000..24b7951 --- /dev/null +++ b/.env.compose @@ -0,0 +1,4 @@ +REACT_APP_NODE_ENV=development +REACT_APP_LOCAL=localhost +REACT_APP_MODEL_SERVICE=model +REACT_APP_SERVER_PORT=5002 diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..9ef90a3 --- /dev/null +++ b/.env.dev @@ -0,0 +1,4 @@ +REACT_APP_NODE_ENV=development +REACT_APP_LOCAL=localhost +REACT_APP_MODEL_SERVICE=localhost +REACT_APP_SERVER_PORT=5002 diff --git a/Dockerfile b/Dockerfile index 775c040..b7e9fbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,10 +10,6 @@ ARG NODE_VERSION=23.10.0 FROM node:${NODE_VERSION}-alpine -# Use production node environment by default. -ENV NODE_ENV production - - WORKDIR /usr/src/app # Download dependencies as a separate step to take advantage of Docker's caching. @@ -37,5 +33,8 @@ EXPOSE 3000 # To access server.js EXPOSE 5001 +# Expose dev port +EXPOSE 5002 + # Run the application. -CMD npm start +CMD npm run start:prod diff --git a/README.md b/README.md index aea7e99..d540baa 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,13 @@ It is a NodeJS app that uses the llama3.2 model to service prompt requests. It u ## How to Run ### Locally -- Update the `App.js` and `server.js` to use 0.0.0.0 as hosts for API requests instead of K8s service names. -- `docker run -p 11434:11434 ollama/ollama:0.6.2` +- Run an LLM container `docker run -p 11434:11434 --name model ollama/ollama:0.6.2` - Exec into the container and run `ollama pull llama3.2` -- `npm start` +- `dotenv -e .env.dev -- npm run start:dev` ### Locally with Docker Compose I used compose to develop this locally. -- Update the `App.js` and `server.js` to use 0.0.0.0 as hosts for API requests instead of K8s service names. - `docker compose up --build` - When done, `docker compose down` diff --git a/compose.yaml b/compose.yaml index dd5d675..c74228f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -9,53 +9,24 @@ # https://github.com/docker/awesome-compose services: server: + container_name: server + command: npm run start:dev depends_on: - model build: context: . - environment: - NODE_ENV: production + volumes: + - ./:/usr/src/app + - /usr/src/app/node_modules + env_file: + - .env.compose ports: - 3000:3000 + - 5002:5002 model: + container_name: model image: ollama/ollama:0.6.2 ports: - 11434:11434 - container_name: ollama post_start: - command: ollama pull llama3.2 - - -# The commented out section below is an example of how to define a PostgreSQL -# database that your application can use. `depends_on` tells Docker Compose to -# start the database before your application. The `db-data` volume persists the -# database data between container restarts. The `db-password` secret is used -# to set the database password. You must create `db/password.txt` and add -# a password of your choosing to it before running `docker-compose up`. -# depends_on: -# db: -# condition: service_healthy -# db: -# image: postgres -# restart: always -# user: postgres -# secrets: -# - db-password -# volumes: -# - db-data:/var/lib/postgresql/data -# environment: -# - POSTGRES_DB=example -# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password -# expose: -# - 5432 -# healthcheck: -# test: [ "CMD", "pg_isready" ] -# interval: 10s -# timeout: 5s -# retries: 5 -# volumes: -# db-data: -# secrets: -# db-password: -# file: db/password.txt - diff --git a/package-lock.json b/package-lock.json index 8b44fb2..d49941f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,9 @@ "concurrently": "^9.1.2", "cors": "^2.8.5", "dockerode": "^4.0.4", + "dotenv": "^16.4.7", "node-polyfill-webpack-plugin": "^4.1.0", + "os": "^0.1.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-scripts": "5.0.1", @@ -7553,12 +7555,15 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -13199,6 +13204,12 @@ "node": ">= 0.8.0" } }, + "node_modules/os": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os/-/os-0.1.2.tgz", + "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==", + "license": "MIT" + }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -15457,6 +15468,15 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 00f1f5e..cec6b06 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,17 @@ "concurrently": "^9.1.2", "cors": "^2.8.5", "dockerode": "^4.0.4", + "dotenv": "^16.4.7", "node-polyfill-webpack-plugin": "^4.1.0", + "os": "^0.1.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "scripts": { - "start": "concurrently \"node server.js\" \"react-scripts start\"", + "start:prod": "concurrently \"node server.js\" \"react-scripts start\"", + "start:dev": "concurrently \"node server.js\" \"react-scripts start\"", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/server.js b/server.js index 80f4c46..b1b9ab2 100644 --- a/server.js +++ b/server.js @@ -3,9 +3,6 @@ const cors = require("cors"); // Import CORS middleware const axios = require("axios") const app = express(); // Initialize Express -// Use env variable, or fallback to localhost -// const LLM_API_HOST = process.env.LLM_API_HOST || 'http://0.0.0.0:11434'; - app.use(cors()); app.use(express.json()); @@ -26,9 +23,8 @@ app.post("/execute", async (req, res) => { }); function getResponse(model, prompt) { - //console.log(`${LLM_API_HOST}`) - // Use K8s service nae, switch back to 0.0.0.0 for local testing (npm start) - return axios.post("http://model-published:11434/api/generate", { + var host = ("REACT_APP_MODEL_SERVICE" in process.env) ? process.env.REACT_APP_MODEL_SERVICE : "model-published"; + return axios.post(`http://${host}:11434/api/generate`, { model: model, prompt: prompt, stream: false @@ -43,8 +39,8 @@ function getResponse(model, prompt) { }); } -// Start the server and listen on port 5001 -const PORT = 5001; -app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); +// Start the server and listen on port specified in command line, ex. PORT=5001 node server.js +var port = ("REACT_APP_SERVER_PORT" in process.env) ? process.env.REACT_APP_SERVER_PORT : 5001; +app.listen(port, () => { + console.log(`Server running on port ${port}`); }); \ No newline at end of file diff --git a/src/App.js b/src/App.js index 0aa2cc7..fc827a0 100644 --- a/src/App.js +++ b/src/App.js @@ -53,8 +53,9 @@ function App() { const prompt = `Context: This is a cat named ${catName}. They have the following traits: ${catTraits}. Generate a response as the cat to the following message: ${newUserInput}`; // Execute command and wait for the result - // Switch K8s service name to 0.0.0.0 for local testing! - const result = await fetch("http://a4c423481a99842669d9088bba7450ad-1853516926.us-east-2.elb.amazonaws.com:5001/execute", { + var host = ("REACT_APP_LOCAL" in process.env) ? process.env.REACT_APP_LOCAL : "a4c423481a99842669d9088bba7450ad-1853516926.us-east-2.elb.amazonaws.com"; + var port = ("REACT_APP_SERVER_PORT" in process.env) ? process.env.REACT_APP_SERVER_PORT : 5001; + const result = await fetch(`http://${host}:${port}/execute`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({