Skip to content

Commit

Permalink
Web sample app (#2)
Browse files Browse the repository at this point in the history
* Web sample app

* Add readme for node samples

* Cosmetic fixes
  • Loading branch information
alexastrum authored Dec 13, 2023
1 parent 93dbad5 commit c35a3d9
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 5 deletions.
35 changes: 35 additions & 0 deletions samples/node/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Google Generative AI Sample for Node.js (Javascript)

This sample 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, you'll need Node.js v18+.

## 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.

This sample assumes that you're providing an `API_KEY` environment variable.

## Features

### Simple examples

- `simple-text.js` - Text-only input, using the `gemini-pro` model
- `simple-text-and-images.js` - Text-and-images input (multimodal), using the `gemini-pro-vision` model
- `simple-chat.js` - Dialog language tasks, using `ChatSession` class
- `simple-config.js` - Configuring model parameters
- `simple-embedding.js` - Embeddings, using the `embedding-001` model

### More examples

- `advanced-text.js` - Using `countTokens`, `safetySettings` and streaming with a text-only input
- `advanced-text-and-images.js` - Using `countTokens`, `generationConfig` and streaming with multimodal input
- `advanced-chat.js` - Using `countTokens`, `generationConfig` and streaming with multi-turn conversations
- `advanced-embeddings.js` - Using `batchEmbedContents`

## Documentation

- [Quickstart: Get started with the Gemini API in Node.js applications](https://googledevai.google.com/tutorials/node_quickstart)
14 changes: 9 additions & 5 deletions samples/node/advanced-text-and-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,22 @@ async function run() {
const model = genAI.getGenerativeModel({
model: "gemini-pro-vision",
generationConfig: {
temperature: 0.9,
temperature: 0,
},
});

const prompt = " Give me the recipe for these, keto-friendly.";
const prompt =
"What do you see? Use lists. Start with a headline for each image.";

// Note: The only accepted mime types are some image types, image/*.
const imageParts = [fileToGenerativePart("./utils/scones.jpg", "image/jpeg")];
const imageParts = [
fileToGenerativePart("./utils/cat.jpg", "image/jpeg"),
fileToGenerativePart("./utils/scones.jpg", "image/jpeg"),
];

displayTokenCount(model, [...imageParts, prompt]);
displayTokenCount(model, [prompt, ...imageParts]);

const result = await model.generateContentStream([...imageParts, prompt]);
const result = await model.generateContentStream([prompt, ...imageParts]);

// Stream the first candidate text
await streamToStdout(result.stream);
Expand Down
25 changes: 25 additions & 0 deletions samples/web/README.md
Original file line number Diff line number Diff line change
@@ -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://googledevai.google.com/tutorials/web_quickstart)
96 changes: 96 additions & 0 deletions samples/web/chat.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!doctype html>
<!--
* @license
* Copyright 2023 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.
-->
<html>
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" type="image/svg+xml" href="favicon.svg" />
<link rel="stylesheet" href="utils/main.css" />
<link
href="https://fonts.googleapis.com/css?family=Roboto:400,700"
rel="stylesheet"
type="text/css"
/>
<title>Generative AI - Chat</title>
</head>

<body>
<header>Generative AI - Chat</header>
<div class="container">
<div id="chat-history"></div>
</div>
<div class="form-container">
<form id="form">
<input id="prompt" />
<button type="submit">Send</button>
</form>
<template id="thumb-template">
<img class="thumb" />
</template>
</div>

<script type="module">
import {
getGenerativeModel,
scrollToDocumentBottom,
updateUI,
} from "./utils/shared.js";

const promptInput = document.querySelector("#prompt");
const historyElement = document.querySelector("#chat-history");
let chat;

document
.querySelector("#form")
.addEventListener("submit", async (event) => {
event.preventDefault();

if (!chat) {
const model = await getGenerativeModel({ model: "gemini-pro" });
chat = model.startChat({
generationConfig: {
maxOutputTokens: 100,
},
});
}

const userMessage = promptInput.value;
promptInput.value = "";

// Create UI for the new user / assistant messages pair
historyElement.innerHTML += `<div class="history-item user-role">
<div class="name">User</div>
<blockquote>${userMessage}</blockquote>
</div>
<div class="history-item model-role">
<div class="name">Model</div>
<blockquote></blockquote>
</div>`;

scrollToDocumentBottom();
const resultEls = document.querySelectorAll(
".model-role > blockquote",
);
await updateUI(
resultEls[resultEls.length - 1],
() => chat.sendMessageStream(userMessage),
true,
);
});
</script>
</body>
</html>
1 change: 1 addition & 0 deletions samples/web/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions samples/web/http-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @license
* Copyright 2023 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`,
);
91 changes: 91 additions & 0 deletions samples/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!doctype html>
<!--
* @license
* Copyright 2023 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.
-->
<html>
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" type="image/svg+xml" href="favicon.svg" />
<link rel="stylesheet" href="utils/main.css" />
<link
href="https://fonts.googleapis.com/css?family=Roboto:400,700"
rel="stylesheet"
type="text/css"
/>
<title>Generative AI - Text and Image</title>
</head>

<body>
<header>Generative AI - Text and Image</header>
<div class="form-container">
<form id="form">
<input type="file" id="file'" multiple />
<input id="prompt" />
<button type="submit">Send</button>
</form>
<div id="thumbnails"></div>
</div>
<div class="container">
<blockquote id="result"></blockquote>
</div>

<script type="module">
import {
getGenerativeModel,
fileToGenerativePart,
updateUI,
} from "./utils/shared.js";

async function run(prompt, files) {
const imageParts = await Promise.all(
[...files].map(fileToGenerativePart),
);

const model = await getGenerativeModel({
model: imageParts.length ? "gemini-pro-vision" : "gemini-pro",
});

return model.generateContentStream([...imageParts, prompt]);
}

const fileInputEl = document.querySelector("input[type=file]");
const thumbnailsEl = document.querySelector("#thumbnails");

fileInputEl.addEventListener("input", () => {
thumbnailsEl.innerHTML = "";
for (const file of fileInputEl.files) {
const url = URL.createObjectURL(file);
thumbnailsEl.innerHTML += `<img class="thumb" src="${url}" onload="window.URL.revokeObjectURL(this.src)" />`;
}
});

document
.querySelector("#form")
.addEventListener("submit", async (event) => {
event.preventDefault();

const promptEl = document.querySelector("#prompt");
const resultEl = document.querySelector("#result");

updateUI(
resultEl,
() => run(promptEl.value, fileInputEl.files),
true,
);
});
</script>
</body>
</html>
10 changes: 10 additions & 0 deletions samples/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"type": "module",
"scripts": {
"start": "node http-server.js",
"http-server": "node http-server.js"
},
"dependencies": {
"@google/generative-ai": "*"
}
}
Loading

0 comments on commit c35a3d9

Please sign in to comment.