From 02a9a2648375d1edf4731c0ee167e0d8737966d2 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 11 Jul 2024 11:44:50 -0700 Subject: [PATCH] Move node samples to top level, restore web samples (#206) --- README.md | 11 +- packages/main/README.md | 11 +- samples/{node => }/README.md | 4 +- samples/{node => }/cache.js | 0 samples/{node => }/chat.js | 0 samples/{node => }/code_execution.js | 0 samples/{node => }/controlled_generation.js | 0 samples/{node => }/count_tokens.js | 0 samples/{node => }/embed.js | 0 samples/{node => }/files.js | 0 samples/{node => }/function_calling.js | 0 samples/{node => }/media/Big_Buck_Bunny.mp4 | Bin samples/{node => }/media/a11.txt | 0 samples/{node => }/media/firefighter.jpg | Bin samples/{node => }/media/jetpack.jpg | Bin samples/{node => }/media/piranha.jpg | Bin samples/{node => }/media/samplesmall.mp3 | Bin samples/{node => }/model_configuration.js | 0 samples/{node => }/package.json | 0 samples/{node => }/safety_settings.js | 0 samples/{node => }/system_instruction.js | 0 samples/{node => }/text_generation.js | 0 samples/{node => }/utils/check-samples.js | 0 samples/web/README.md | 25 ++++ samples/web/chat.html | 96 +++++++++++++++ samples/web/favicon.svg | 1 + samples/web/http-server.js | 87 ++++++++++++++ samples/web/index.html | 91 +++++++++++++++ samples/web/package.json | 10 ++ samples/web/utils/main.css | 123 ++++++++++++++++++++ samples/web/utils/shared.js | 96 +++++++++++++++ 31 files changed, 546 insertions(+), 9 deletions(-) rename samples/{node => }/README.md (88%) rename samples/{node => }/cache.js (100%) rename samples/{node => }/chat.js (100%) rename samples/{node => }/code_execution.js (100%) rename samples/{node => }/controlled_generation.js (100%) rename samples/{node => }/count_tokens.js (100%) rename samples/{node => }/embed.js (100%) rename samples/{node => }/files.js (100%) rename samples/{node => }/function_calling.js (100%) rename samples/{node => }/media/Big_Buck_Bunny.mp4 (100%) rename samples/{node => }/media/a11.txt (100%) rename samples/{node => }/media/firefighter.jpg (100%) rename samples/{node => }/media/jetpack.jpg (100%) rename samples/{node => }/media/piranha.jpg (100%) rename samples/{node => }/media/samplesmall.mp3 (100%) rename samples/{node => }/model_configuration.js (100%) rename samples/{node => }/package.json (100%) rename samples/{node => }/safety_settings.js (100%) rename samples/{node => }/system_instruction.js (100%) rename samples/{node => }/text_generation.js (100%) rename samples/{node => }/utils/check-samples.js (100%) create mode 100644 samples/web/README.md create mode 100644 samples/web/chat.html create mode 100644 samples/web/favicon.svg create mode 100644 samples/web/http-server.js create mode 100644 samples/web/index.html create mode 100644 samples/web/package.json create mode 100644 samples/web/utils/main.css create mode 100644 samples/web/utils/shared.js diff --git a/README.md b/README.md index 96decf7b..49d0327e 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ const { GoogleGenerativeAI } = require("@google/generative-ai"); const genAI = new GoogleGenerativeAI(process.env.API_KEY); -const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash-latest" }); +const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); ``` 1. Run a prompt @@ -72,11 +72,14 @@ access and utilize the Gemini model for various use cases. 1. [Obtain an API key](https://makersuite.google.com/app/apikey) to use with the Google AI SDKs. -1. cd into the `samples/node` folder and run `npm install`. +2. cd into the `samples` folder and run `npm install`. -1. Assign your API key to an environment variable: `export API_KEY=MY_API_KEY`. +3. Assign your API key to an environment variable: `export API_KEY=MY_API_KEY`. -1. Run the sample file you're interested in. Example: `node simple-text.js`. +4. Open the sample file you're interested in. Example: `text_generation.js`. + In the `runAll()` function, comment out any samples you don't want to run. + +5. Run the sample file. Example: `node text_generation.js`. ## Documentation diff --git a/packages/main/README.md b/packages/main/README.md index a2e08141..906701af 100644 --- a/packages/main/README.md +++ b/packages/main/README.md @@ -41,7 +41,7 @@ const { GoogleGenerativeAI } = require("@google/generative-ai"); const genAI = new GoogleGenerativeAI(process.env.API_KEY); -const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash-latest" }); +const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); ``` 1. Run a prompt @@ -72,11 +72,14 @@ access and utilize the Gemini model for various use cases. 1. [Obtain an API key](https://makersuite.google.com/app/apikey) to use with the Google AI SDKs. -1. cd into the `samples/node` folder and run `npm install`. +2. cd into the `samples` folder and run `npm install`. -1. Assign your API key to an environment variable: `export API_KEY=MY_API_KEY`. +3. Assign your API key to an environment variable: `export API_KEY=MY_API_KEY`. -1. Run the sample file you're interested in. Example: `node simple-text.js`. +4. Open the sample file you're interested in. Example: `text_generation.js`. + In the `runAll()` function, comment out any samples you don't want to run. + +5. Run the sample file. Example: `node text_generation.js`. ## Documentation diff --git a/samples/node/README.md b/samples/README.md similarity index 88% rename from samples/node/README.md rename to samples/README.md index 3f51380d..5cf6bd02 100644 --- a/samples/node/README.md +++ b/samples/README.md @@ -1,10 +1,12 @@ -# Google Generative AI Sample for Node.js (Javascript) +# Google Generative AI Samples for JavaScript These samples demonstrate how to use state-of-the-art generative AI models (like Gemini) to build AI-powered features and applications. To try out these samples, you'll need Node.js v18+. +For some samples of how to adapt this to web, see the `web/` subdirectory. + ## Requirements Follow the instructions on Google AI Studio [setup page](https://makersuite.google.com/app/apikey) to obtain an API key. diff --git a/samples/node/cache.js b/samples/cache.js similarity index 100% rename from samples/node/cache.js rename to samples/cache.js diff --git a/samples/node/chat.js b/samples/chat.js similarity index 100% rename from samples/node/chat.js rename to samples/chat.js diff --git a/samples/node/code_execution.js b/samples/code_execution.js similarity index 100% rename from samples/node/code_execution.js rename to samples/code_execution.js diff --git a/samples/node/controlled_generation.js b/samples/controlled_generation.js similarity index 100% rename from samples/node/controlled_generation.js rename to samples/controlled_generation.js diff --git a/samples/node/count_tokens.js b/samples/count_tokens.js similarity index 100% rename from samples/node/count_tokens.js rename to samples/count_tokens.js diff --git a/samples/node/embed.js b/samples/embed.js similarity index 100% rename from samples/node/embed.js rename to samples/embed.js diff --git a/samples/node/files.js b/samples/files.js similarity index 100% rename from samples/node/files.js rename to samples/files.js diff --git a/samples/node/function_calling.js b/samples/function_calling.js similarity index 100% rename from samples/node/function_calling.js rename to samples/function_calling.js diff --git a/samples/node/media/Big_Buck_Bunny.mp4 b/samples/media/Big_Buck_Bunny.mp4 similarity index 100% rename from samples/node/media/Big_Buck_Bunny.mp4 rename to samples/media/Big_Buck_Bunny.mp4 diff --git a/samples/node/media/a11.txt b/samples/media/a11.txt similarity index 100% rename from samples/node/media/a11.txt rename to samples/media/a11.txt diff --git a/samples/node/media/firefighter.jpg b/samples/media/firefighter.jpg similarity index 100% rename from samples/node/media/firefighter.jpg rename to samples/media/firefighter.jpg diff --git a/samples/node/media/jetpack.jpg b/samples/media/jetpack.jpg similarity index 100% rename from samples/node/media/jetpack.jpg rename to samples/media/jetpack.jpg diff --git a/samples/node/media/piranha.jpg b/samples/media/piranha.jpg similarity index 100% rename from samples/node/media/piranha.jpg rename to samples/media/piranha.jpg diff --git a/samples/node/media/samplesmall.mp3 b/samples/media/samplesmall.mp3 similarity index 100% rename from samples/node/media/samplesmall.mp3 rename to samples/media/samplesmall.mp3 diff --git a/samples/node/model_configuration.js b/samples/model_configuration.js similarity index 100% rename from samples/node/model_configuration.js rename to samples/model_configuration.js diff --git a/samples/node/package.json b/samples/package.json similarity index 100% rename from samples/node/package.json rename to samples/package.json diff --git a/samples/node/safety_settings.js b/samples/safety_settings.js similarity index 100% rename from samples/node/safety_settings.js rename to samples/safety_settings.js diff --git a/samples/node/system_instruction.js b/samples/system_instruction.js similarity index 100% rename from samples/node/system_instruction.js rename to samples/system_instruction.js diff --git a/samples/node/text_generation.js b/samples/text_generation.js similarity index 100% rename from samples/node/text_generation.js rename to samples/text_generation.js diff --git a/samples/node/utils/check-samples.js b/samples/utils/check-samples.js similarity index 100% rename from samples/node/utils/check-samples.js rename to samples/utils/check-samples.js diff --git a/samples/web/README.md b/samples/web/README.md new file mode 100644 index 00000000..9149689a --- /dev/null +++ b/samples/web/README.md @@ -0,0 +1,25 @@ +# Google Generative AI Sample for Web (Javascript) + +This sample app demonstrates how to use state-of-the-art +generative AI models (like Gemini) to build AI-powered features and applications. + +To try out this sample app, you'll need a modern web browser and a local http server. + +## Requirements + +Follow the instructions on Google AI Studio [setup page](https://makersuite.google.com/app/apikey) to obtain an API key. + +It’s strongly recommended that you do not check an API key into your version control system. Instead, you should use a secrets store for your API key. + +The Node.js http server provided alonside this app (`http-server.js`) assumes that you're providing an `API_KEY` environment variable. + +## Features + +This sample showcases the following API capablilites: + +- `index.html` - demonstrates the Text and MultiModal feature from the SDK +- `chat.html` - demonstrates the Multi-turn Conversations feature from the SDK + +## Documentation + +- [Quickstart: Get started with the Gemini API in web apps](https://ai.google.dev/tutorials/web_quickstart) diff --git a/samples/web/chat.html b/samples/web/chat.html new file mode 100644 index 00000000..e6778e7d --- /dev/null +++ b/samples/web/chat.html @@ -0,0 +1,96 @@ + + + + + + + + + Generative AI - Chat + + + +
Generative AI - Chat
+
+
+
+
+
+ + +
+ +
+ + + + diff --git a/samples/web/favicon.svg b/samples/web/favicon.svg new file mode 100644 index 00000000..c2f0bd64 --- /dev/null +++ b/samples/web/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/web/http-server.js b/samples/web/http-server.js new file mode 100644 index 00000000..ae205e2c --- /dev/null +++ b/samples/web/http-server.js @@ -0,0 +1,87 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from "fs"; +import http from "http"; +import path from "path"; +import url from "url"; + +// Local port for http server to listen on +const PORT = 9000; + +// Get your API key from https://makersuite.google.com/app/apikey +// Access your API key as an environment variable +const API_KEY = process.env.API_KEY; + +if (!API_KEY) { + throw new Error("API_KEY environment variable not set"); +} + +// Maps file extention to MIME types +// Full list can be found here: https://www.freeformatter.com/mime-types-list.html +const mimeType = { + ".html": "text/html", + ".js": "text/javascript", + ".mjs": "text/javascript", + ".css": "text/css", +}; + +http + .createServer((req, res) => { + console.log(` ${req.method} ${req.url}`); + + // Parse URL + const parsedUrl = url.parse(req.url); + + // Extract URL path + // Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack + let sanitizedPath = path + .normalize(parsedUrl.pathname) + .replace(/^(\.\.[\/\\])+/, "") + .substring(1); + + if (sanitizedPath === "API_KEY") { + res.end(API_KEY); + return; + } + + if (sanitizedPath === "") { + sanitizedPath = "index.html"; + } + + // based on the URL path, extract the file extention. e.g. .js, .doc, ... + const ext = path.parse(sanitizedPath).ext; + + try { + const data = fs.readFileSync(sanitizedPath); + + // If the file is found, set Content-Type and send data + if (mimeType[ext]) { + res.setHeader("Content-Type", mimeType[ext]); + } + res.end(data); + } catch (err) { + // If the file is not found, return 404 + res.statusCode = 404; + res.end(); + } + }) + .listen(parseInt(PORT)); + +console.log( + `Server listening. Pages:\n - http://localhost:${PORT}\n - http://localhost:${PORT}/chat.html`, +); diff --git a/samples/web/index.html b/samples/web/index.html new file mode 100644 index 00000000..601c98e1 --- /dev/null +++ b/samples/web/index.html @@ -0,0 +1,91 @@ + + + + + + + + + Generative AI - Text and Image + + + +
Generative AI - Text and Image
+
+
+ + + +
+
+
+
+
+
+ + + + diff --git a/samples/web/package.json b/samples/web/package.json new file mode 100644 index 00000000..a264955f --- /dev/null +++ b/samples/web/package.json @@ -0,0 +1,10 @@ +{ + "type": "module", + "scripts": { + "start": "node http-server.js", + "http-server": "node http-server.js" + }, + "dependencies": { + "@google/generative-ai": "*" + } +} diff --git a/samples/web/utils/main.css b/samples/web/utils/main.css new file mode 100644 index 00000000..00daa568 --- /dev/null +++ b/samples/web/utils/main.css @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +* { + box-sizing: border-box; +} + +header { + border-bottom: 2px solid rgb(127, 127, 127, 0.5); + font-size: 24px; + padding: 16px; + text-align: center; +} + +body { + font-family: "Roboto", sans-serif; + margin: 0; +} + +.loading::after { + content: "Loading..."; + display: block; + font-size: 80%; + font-style: italic; + margin: 16px 0; +} + +.loading { + opacity: 0.5; +} + +.error { + color: red; +} + +.container, +header, +.form-container { + margin: 0 auto; + max-width: 700px; +} + +.form-container { + border-bottom: 2px solid rgb(127, 127, 127, 0.5); +} + +img.thumb { + border: 1px solid grey; + border-radius: 8px; + height: 100px; + margin: 0px 16px 16px 0; + padding: 2px; + width: 100px; +} + +#form { + flex-direction: column; +} + +#form > * { + margin: 10px 0; +} + +.history-item { + align-items: center; + display: flex; + justify-content: center; + padding: 16px 0; +} + +#file { + flex-grow: 0; +} + +#prompt { + margin: 4px; + padding: 2px; + width: 100%; +} + +button { + padding: 2px 16px; +} + +.name { + flex-shrink: 0; + font-size: 80%; + margin: 16px 16px 16px 0; + opacity: 0.5; + text-align: right; + width: 50px; +} + +blockquote { + margin: 0; +} + +.history-item { + padding: 0 8px 0 0; +} + +.history-item.model-role { + background: rgba(127, 127, 127, 0.1); +} + +.history-item > blockquote { + flex-grow: 1; + margin: 0; +} diff --git a/samples/web/utils/shared.js b/samples/web/utils/shared.js new file mode 100644 index 00000000..d420a876 --- /dev/null +++ b/samples/web/utils/shared.js @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GoogleGenerativeAI } from "https://esm.run/@google/generative-ai"; +import { marked } from "https://esm.run/marked"; + +/** + * Returns a model instance. + * + * @param {GoogleGenerativeAI.ModelParams} params + * @returns {GoogleGenerativeAI.GenerativeModel} + */ +export async function getGenerativeModel(params) { + // Fetch API key from server + // If you need a new API key, get it from https://makersuite.google.com/app/apikey + const API_KEY = await (await fetch("API_KEY")).text(); + + const genAI = new GoogleGenerativeAI(API_KEY); + + return genAI.getGenerativeModel(params); +} + +/** + * Converts a File object to a GoogleGenerativeAI.Part object. + * + * @param {Blob} file + * @returns {GoogleGenerativeAI.Part} + */ +export async function fileToGenerativePart(file) { + const base64EncodedDataPromise = new Promise((resolve) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result.split(",")[1]); + reader.readAsDataURL(file); + }); + return { + inlineData: { data: await base64EncodedDataPromise, mimeType: file.type }, + }; +} + +/** + * Scrolls the document all the way to the bottom. + */ +export function scrollToDocumentBottom() { + const scrollingElement = document.scrollingElement || document.body; + scrollingElement.scrollTop = scrollingElement.scrollHeight; +} + +/** + * Updates the `resultEl` with parsed markdown text returned by a `getResult()` call. + * + * @param {HTMLElement}} resultEl + * @param {() => Promise} getResult + * @param {boolean} streaming + */ +export async function updateUI(resultEl, getResult, streaming) { + resultEl.className = "loading"; + let text = ""; + try { + const result = await getResult(); + + if (streaming) { + resultEl.innerText = ""; + for await (const chunk of result.stream) { + // Get first candidate's current text chunk + const chunkText = chunk.text(); + text += chunkText; + resultEl.innerHTML = marked.parse(text); + scrollToDocumentBottom(); + } + } else { + const response = await result.response; + text = response.text(); + } + + resultEl.className = ""; // Remove .loading class + } catch (err) { + text += "\n\n> " + err; + resultEl.className = "error"; + } + resultEl.innerHTML = marked.parse(text); + scrollToDocumentBottom(); +}