Add gemini nano sample (#1234)
Add gemini nano sample --------- Co-authored-by: Oliver Dunk <oliverdunk@google.com>
This commit is contained in:
Родитель
ad20939023
Коммит
e1ad9ce8f0
|
@ -0,0 +1,14 @@
|
||||||
|
# On-device AI with Gemini Nano
|
||||||
|
|
||||||
|
This sample demonstrates how to use the built-in Chrome Prompt API.
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
## Running this extension
|
||||||
|
|
||||||
|
1. Clone this repository.
|
||||||
|
2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
|
||||||
|
3. Click the extension icon.
|
||||||
|
4. Interact with the prompt API in the sidebar.
|
|
@ -0,0 +1,3 @@
|
||||||
|
chrome.sidePanel
|
||||||
|
.setPanelBehavior({ openPanelOnActionClick: true })
|
||||||
|
.catch((error) => console.error(error));
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 3.9 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 267 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 458 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 890 B |
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "Chrome Built-in AI Demo",
|
||||||
|
"version": "0.1",
|
||||||
|
"manifest_version": 3,
|
||||||
|
"description": "Try the built-in AI preview in Chrome.",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" type="text/css" href="index.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Chrome built-in AI</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"
|
||||||
|
/>
|
||||||
|
<label for="temperature"
|
||||||
|
>Temperature: <span id="label-temperature"></span
|
||||||
|
></label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="range" id="top-k" name="top-k" min="1" max="50" step="1" />
|
||||||
|
<label for="top-k">Top-k: <span id="label-top-k"></span></label>
|
||||||
|
</div>
|
||||||
|
<button id="button-prompt" class="primary" disabled>Run</button>
|
||||||
|
<button id="button-reset" class="secondary" disabled>Reset</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="index.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,138 @@
|
||||||
|
const inputPrompt = document.body.querySelector('#input-prompt');
|
||||||
|
const buttonPrompt = document.body.querySelector('#button-prompt');
|
||||||
|
const buttonReset = document.body.querySelector('#button-reset');
|
||||||
|
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 sliderTopK = document.body.querySelector('#top-k');
|
||||||
|
const labelTemperature = document.body.querySelector('#label-temperature');
|
||||||
|
const labelTopK = document.body.querySelector('#label-top-k');
|
||||||
|
|
||||||
|
let session;
|
||||||
|
|
||||||
|
async function runPrompt(prompt, params) {
|
||||||
|
try {
|
||||||
|
if (!session) {
|
||||||
|
// Start by checking if it's possible to create a session based on the availability of the model, and the characteristics of the device.
|
||||||
|
const canCreate = await self.ai.canCreateTextSession();
|
||||||
|
// canCreate will be one of the following:
|
||||||
|
// * "readily": the model is available on-device and so creating will happen quickly
|
||||||
|
// * "after-download": the model is not available on-device, but the device is capable,
|
||||||
|
// so creating the session will start the download process (which can take a while).
|
||||||
|
// * "no": the model is not available for this device.
|
||||||
|
if (canCreate === 'no') {
|
||||||
|
console.warn('Built-in prompt API not available.');
|
||||||
|
throw new Error(
|
||||||
|
'Built-in prompt API not available. Join the preview program to learn how to enable it.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log('Creating new text session');
|
||||||
|
session = await self.ai.createTextSession(params);
|
||||||
|
}
|
||||||
|
return session.prompt(prompt);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Prompt failed');
|
||||||
|
console.error(e);
|
||||||
|
console.log('Prompt:', prompt);
|
||||||
|
// Reset session
|
||||||
|
reset();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reset() {
|
||||||
|
if (session) {
|
||||||
|
session.destroy();
|
||||||
|
}
|
||||||
|
session = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initDefaults() {
|
||||||
|
const defaults = await window.ai.defaultTextSessionOptions();
|
||||||
|
console.log('Model default:', defaults);
|
||||||
|
sliderTemperature.value = defaults.temperature;
|
||||||
|
sliderTopK.value = defaults.topK;
|
||||||
|
labelTopK.textContent = defaults.topK;
|
||||||
|
labelTemperature.textContent = defaults.temperature;
|
||||||
|
labelTemperature.value = defaults.temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
initDefaults();
|
||||||
|
|
||||||
|
buttonReset.addEventListener('click', () => {
|
||||||
|
hide(elementLoading);
|
||||||
|
hide(elementError);
|
||||||
|
hide(elementResponse);
|
||||||
|
reset();
|
||||||
|
buttonReset.setAttribute('disabled', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
sliderTemperature.addEventListener('input', (event) => {
|
||||||
|
labelTemperature.textContent = event.target.value;
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
sliderTopK.addEventListener('input', (event) => {
|
||||||
|
labelTopK.textContent = event.target.value;
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
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 params = {
|
||||||
|
temperature: sliderTemperature.value,
|
||||||
|
topK: sliderTopK.value
|
||||||
|
};
|
||||||
|
const response = await runPrompt(prompt, params);
|
||||||
|
showResponse(response);
|
||||||
|
} catch (e) {
|
||||||
|
showError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
buttonReset.removeAttribute('disabled');
|
||||||
|
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 (const paragraph of paragraphs) {
|
||||||
|
if (paragraph) {
|
||||||
|
elementResponse.appendChild(document.createTextNode(paragraph));
|
||||||
|
}
|
||||||
|
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', '');
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче