add Gemini API sample (#1240)

* add Gemini API sample

* Simplify configuration and fix response formatting

* Update wording for gemini nano sample
This commit is contained in:
Sebastian Benz
2024-07-25 10:08:13 +00:00
committed by GitHub
parent 2dd50d5850
commit 005f7caa65
15 changed files with 595 additions and 2 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ node_modules
# Temporary directory for debugging extension samples
_debug
_metadata
*.swp # vim temp files

View File

@@ -0,0 +1 @@
dist

View File

@@ -0,0 +1,23 @@
# On-device AI with Gemini Nano
This sample demonstrates how to use the Gemini Cloud API in a Chrome Extension.
## Overview
The extension provides a chat interface for the Gemini API. To learn more about the API head over to [https://ai.google.dev/](https://ai.google.dev/).
## Running this extension
1. Clone this repository.
2. Download the Gemini API client by running:
```sh
npm install
```
3. [Retrieve an API key](https://ai.google.dev/gemini-api/docs/api-key) and update [functional-samples/ai.gemini-in-the-cloud/sidepanel/index.js](functional-samples/ai.gemini-in-the-cloud/sidepanel/index.js) (only for testing).
4. Compile the JS bundle for the sidepanel implementation by running:
```sh
npm run build
```
5. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
6. Click the extension icon.
7. Interact with the prompt API in the sidebar.

View File

@@ -0,0 +1,3 @@
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.catch((error) => console.error(error));

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

View File

@@ -0,0 +1,22 @@
{
"name": "Google Gemini Demo",
"version": "0.1",
"manifest_version": 3,
"description": "Try the Gemini Models.",
"background": {
"service_worker": "background.js"
},
"permissions": ["sidePanel"],
"side_panel": {
"default_path": "sidepanel/index.html"
},
"action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"default_title": "Open Chat Interface"
}
}

View File

@@ -0,0 +1,288 @@
{
"name": "Chrome Extensions Gemini Demo",
"version": "1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Chrome Extensions Gemini Demo",
"version": "1.0",
"devDependencies": {
"@google/generative-ai": "0.15.0",
"rollup": "4.19.0"
}
},
"node_modules/@google/generative-ai": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.15.0.tgz",
"integrity": "sha512-zs37judcTYFJf1U7tnuqnh7gdzF6dcWj9pNRxjA5JTONRoiQ0htrRdbefRFiewOIfXwhun5t9hbd2ray7812eQ==",
"dev": true,
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz",
"integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz",
"integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz",
"integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz",
"integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz",
"integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz",
"integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz",
"integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz",
"integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz",
"integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz",
"integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz",
"integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz",
"integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz",
"integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz",
"integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz",
"integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz",
"integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/rollup": {
"version": "4.19.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.0.tgz",
"integrity": "sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.19.0",
"@rollup/rollup-android-arm64": "4.19.0",
"@rollup/rollup-darwin-arm64": "4.19.0",
"@rollup/rollup-darwin-x64": "4.19.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.19.0",
"@rollup/rollup-linux-arm-musleabihf": "4.19.0",
"@rollup/rollup-linux-arm64-gnu": "4.19.0",
"@rollup/rollup-linux-arm64-musl": "4.19.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.19.0",
"@rollup/rollup-linux-riscv64-gnu": "4.19.0",
"@rollup/rollup-linux-s390x-gnu": "4.19.0",
"@rollup/rollup-linux-x64-gnu": "4.19.0",
"@rollup/rollup-linux-x64-musl": "4.19.0",
"@rollup/rollup-win32-arm64-msvc": "4.19.0",
"@rollup/rollup-win32-ia32-msvc": "4.19.0",
"@rollup/rollup-win32-x64-msvc": "4.19.0",
"fsevents": "~2.3.2"
}
}
}
}

View File

@@ -0,0 +1,12 @@
{
"name": "Chrome Extensions Gemini Demo",
"version": "1.0",
"scripts": {
"build": "rollup sidepanel/index.js --file dist/sidepanel.bundle.js --format iife"
},
"private": true,
"devDependencies": {
"@google/generative-ai": "0.15.0",
"rollup": "4.19.0"
}
}

View File

@@ -0,0 +1,82 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
color: #1f1f1f;
background-color: #f2f2f2;
font-size: 16px;
padding: 8px;
}
input,
button,
textarea,
select {
font-family: inherit;
font-size: inherit;
}
button {
background: #333;
color: white;
border-radius: 8px;
border: none;
min-width: 100px;
padding: 8px;
margin: 16px 0;
cursor: pointer;
}
button.primary {
background: #333;
color: white;
}
button.secondary {
background: #ccc;
color: black;
}
button[disabled] {
background: #ddd;
color: #aaa;
}
input[type='range'] {
margin-top: 16px;
accent-color: black;
}
textarea {
--padding: 32px;
width: calc(100% - var(--padding));
max-width: calc(100% - var(--padding));
}
.text,
textarea {
background-color: white;
padding: 16px;
border-radius: 16px;
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px, rgb(51, 51, 51) 0px 0px 0px 3px;
outline: none;
}
.blink {
animation: 1s ease-in-out 1s infinite reverse both running blink;
}
@keyframes blink {
25% {
opacity: 0.5;
}
50% {
opacity: 0;
}
75% {
opacity: 0.5;
}
}
[hidden] {
display: none;
}

View File

@@ -0,0 +1,34 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="index.css" />
</head>
<body>
<h1>Google Gemini</h1>
<textarea
id="input-prompt"
placeholder='Type something, e.g. "Write a haiku about Chrome Extensions"'
cols="30"
rows="5"
></textarea>
<div>
<input
type="range"
id="temperature"
name="temperature"
min="0"
max="2"
step="0.01"
value="1"
/>
<label for="temperature"
>Temperature: <span id="label-temperature">1</span></label
>
</div>
<button id="button-prompt" class="primary" disabled>Run</button>
<div id="response" class="text" hidden></div>
<div id="loading" class="text" hidden><span class="blink">...</span></div>
<div id="error" class="text" hidden></div>
<script src="../dist/sidepanel.bundle.js" type="module"></script>
</body>
</html>

View File

@@ -0,0 +1,127 @@
import {
GoogleGenerativeAI,
HarmBlockThreshold,
HarmCategory
} from '../node_modules/@google/generative-ai/dist/index.mjs';
// Important! Do not expose your API in your extension code. You have to
// options:
//
// 1. Let users provide their own API key.
// 2. Manage API keys in your own server and proxy all calls to the Gemini
// API through your own server, where you can implement additional security
// measures such as authentification.
//
// It is only OK to put your API key into this file if you're the only
// user of your extension or for testing.
const apiKey = '...';
let genAI = null;
let model = null;
let generationConfig = {
temperature: 1
};
const inputPrompt = document.body.querySelector('#input-prompt');
const buttonPrompt = document.body.querySelector('#button-prompt');
const elementResponse = document.body.querySelector('#response');
const elementLoading = document.body.querySelector('#loading');
const elementError = document.body.querySelector('#error');
const sliderTemperature = document.body.querySelector('#temperature');
const labelTemperature = document.body.querySelector('#label-temperature');
function initModel(generationConfig) {
const safetySettings = [
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_NONE
}
];
genAI = new GoogleGenerativeAI(apiKey);
model = genAI.getGenerativeModel({
model: 'gemini-1.5-flash',
safetySettings,
generationConfig
});
return model;
}
async function runPrompt(prompt) {
try {
const result = await model.generateContent(prompt);
const response = await result.response;
return response.text();
} catch (e) {
console.log('Prompt failed');
console.error(e);
console.log('Prompt:', prompt);
throw e;
}
}
sliderTemperature.addEventListener('input', (event) => {
labelTemperature.textContent = event.target.value;
generationConfig.temperature = event.target.value;
});
inputPrompt.addEventListener('input', () => {
if (inputPrompt.value.trim()) {
buttonPrompt.removeAttribute('disabled');
} else {
buttonPrompt.setAttribute('disabled', '');
}
});
buttonPrompt.addEventListener('click', async () => {
const prompt = inputPrompt.value.trim();
showLoading();
try {
const generationConfig = {
temperature: sliderTemperature.value
};
initModel(generationConfig);
const response = await runPrompt(prompt, generationConfig);
showResponse(response);
} catch (e) {
showError(e);
}
});
function showLoading() {
hide(elementResponse);
hide(elementError);
show(elementLoading);
}
function showResponse(response) {
hide(elementLoading);
show(elementResponse);
// Make sure to preserve line breaks in the response
elementResponse.textContent = '';
const paragraphs = response.split(/\r?\n/);
for (let i = 0; i < paragraphs.length; i++) {
const paragraph = paragraphs[i];
if (paragraph) {
elementResponse.appendChild(document.createTextNode(paragraph));
}
// Don't add a new line after the final paragraph
if (i < paragraphs.length - 1) {
elementResponse.appendChild(document.createElement('BR'));
}
}
}
function showError(error) {
show(elementError);
hide(elementResponse);
hide(elementLoading);
elementError.textContent = error;
}
function show(element) {
element.removeAttribute('hidden');
}
function hide(element) {
element.setAttribute('hidden', '');
}

View File

@@ -1,10 +1,10 @@
# On-device AI with Gemini Nano
This sample demonstrates how to use the built-in Chrome Prompt API.
This sample demonstrates how to use the experimental Gemini Nano API in Chrome. To learn more about the API and how to sign-up for the preview, head over to [Built-in AI on developer.chrome.com](https://developer.chrome.com/docs/ai/built-in).
## Overview
The extension provides a chat interface for the built-in Chrome prompt API. To learn more about the API and how to sign-up for the preview, head over to [Built-in AI on developer.chrome.com](https://developer.chrome.com/docs/ai/built-in).
The extension provides a chat interface using the prompt API with Chrome's built-in Gemini Nano model.
## Running this extension