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', '');
|
||||
}
|
Загрузка…
Ссылка в новой задаче