Skip to content

Commit 93c4a2e

Browse files
authored
Merge branch 'main' into feat/documenting-custom-credential-supplier
2 parents 83548c0 + f6a44c5 commit 93c4a2e

18 files changed

Lines changed: 272 additions & 58 deletions
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
// [START googlegenaisdk_boundingbox_with_txt_img]
18+
const {GoogleGenAI} = require('@google/genai');
19+
20+
const {createCanvas, loadImage} = require('canvas');
21+
const fetch = require('node-fetch');
22+
const fs = require('fs');
23+
24+
const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT;
25+
const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || 'global';
26+
27+
async function fetchImageAsBase64(uri) {
28+
const response = await fetch(uri);
29+
const buffer = await response.buffer();
30+
return buffer.toString('base64');
31+
}
32+
33+
async function plotBoundingBoxes(imageUri, boundingBoxes) {
34+
console.log('Creating bounding boxes');
35+
const image = await loadImage(imageUri);
36+
const canvas = createCanvas(image.width, image.height);
37+
const ctx = canvas.getContext('2d');
38+
39+
ctx.drawImage(image, 0, 0);
40+
41+
const colors = ['red', 'blue', 'green', 'orange'];
42+
43+
boundingBoxes.forEach((bbox, i) => {
44+
const [yMin, xMin, yMax, xMax] = bbox.box_2d;
45+
46+
const absYMin = Math.floor((yMin / 1000) * image.height);
47+
const absXMin = Math.floor((xMin / 1000) * image.width);
48+
const absYMax = Math.floor((yMax / 1000) * image.height);
49+
const absXMax = Math.floor((xMax / 1000) * image.width);
50+
51+
ctx.strokeStyle = colors[i % colors.length];
52+
ctx.lineWidth = 4;
53+
ctx.strokeRect(absXMin, absYMin, absXMax - absXMin, absYMax - absYMin);
54+
55+
ctx.fillStyle = colors[i % colors.length];
56+
ctx.font = '20px Arial';
57+
ctx.fillText(bbox.label, absXMin + 8, absYMin + 20);
58+
});
59+
60+
fs.writeFileSync('output.png', canvas.toBuffer('image/png'));
61+
console.log('Saved output to file: output.png');
62+
}
63+
64+
async function createBoundingBox(
65+
projectId = GOOGLE_CLOUD_PROJECT,
66+
location = GOOGLE_CLOUD_LOCATION
67+
) {
68+
const client = new GoogleGenAI({
69+
vertexai: true,
70+
project: projectId,
71+
location: location,
72+
});
73+
74+
const systemInstruction = `
75+
Return bounding boxes as an array with labels.
76+
Never return masks. Limit to 25 objects.
77+
If an object is present multiple times, give each object a unique label
78+
according to its distinct characteristics (colors, size, position, etc).
79+
`;
80+
81+
const safetySettings = [
82+
{
83+
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
84+
threshold: 'BLOCK_ONLY_HIGH',
85+
},
86+
];
87+
88+
const imageUri =
89+
'https://storage.googleapis.com/generativeai-downloads/images/socks.jpg';
90+
const base64Image = await fetchImageAsBase64(imageUri);
91+
92+
const boundingBoxSchema = {
93+
type: 'ARRAY',
94+
description: 'List of bounding boxes for detected objects',
95+
items: {
96+
type: 'OBJECT',
97+
title: 'BoundingBox',
98+
description: 'Represents a bounding box with coordinates and label',
99+
properties: {
100+
box_2d: {
101+
type: 'ARRAY',
102+
description:
103+
'Bounding box coordinates in format [y_min, x_min, y_max, x_max]',
104+
items: {
105+
type: 'INTEGER',
106+
format: 'int32',
107+
},
108+
minItems: 4,
109+
maxItems: 4,
110+
},
111+
label: {
112+
type: 'STRING',
113+
description: 'Label describing the object within the bounding box',
114+
},
115+
},
116+
required: ['box_2d', 'label'],
117+
},
118+
};
119+
120+
const response = await client.models.generateContent({
121+
model: 'gemini-2.5-flash',
122+
contents: [
123+
{
124+
role: 'user',
125+
parts: [
126+
{
127+
text: 'Output the positions of the socks with a face. Label according to position in the image',
128+
},
129+
{
130+
inlineData: {
131+
data: base64Image,
132+
mimeType: 'image/jpeg',
133+
},
134+
},
135+
],
136+
},
137+
],
138+
config: {
139+
systemInstruction: systemInstruction,
140+
safetySettings: safetySettings,
141+
responseMimeType: 'application/json',
142+
temperature: 0.5,
143+
responseSchema: boundingBoxSchema,
144+
},
145+
});
146+
147+
const candidate = response.candidates[0].content.parts[0].text;
148+
const boundingBoxes = JSON.parse(candidate);
149+
150+
console.log('Bounding boxes:', boundingBoxes);
151+
152+
await plotBoundingBoxes(imageUri, boundingBoxes);
153+
return boundingBoxes;
154+
}
155+
// [END googlegenaisdk_boundingbox_with_txt_img]
156+
157+
module.exports = {
158+
createBoundingBox,
159+
};

genai/image-generation/imggen-mmflash-edit-img-with-txt-img.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,26 @@ async function generateImage(
3434
location: location,
3535
});
3636

37-
const image = fs.readFileSync(FILE_NAME);
37+
const imageBytes = fs.readFileSync(FILE_NAME);
3838

3939
const response = await client.models.generateContent({
4040
model: 'gemini-2.5-flash-image',
41-
contents: [image, 'Edit this image to make it look like a cartoon'],
41+
contents: [
42+
{
43+
role: 'user',
44+
parts: [
45+
{
46+
inlineData: {
47+
mimeType: 'image/png',
48+
data: imageBytes.toString('base64'),
49+
},
50+
},
51+
{
52+
text: 'Edit this image to make it look like a cartoon',
53+
},
54+
],
55+
},
56+
],
4257
config: {
4358
responseModalities: [Modality.TEXT, Modality.IMAGE],
4459
},

genai/output-folder/output.png

13.6 MB
Loading

genai/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@
1313
"test": "c8 mocha -p -j 2 --timeout 2400000 test/*.test.js test/**/*.test.js"
1414
},
1515
"dependencies": {
16-
"@google/genai": "1.20.0",
16+
"@google/genai": "1.30.0",
1717
"axios": "^1.6.2",
18+
"canvas": "^3.2.0",
1819
"google-auth-library": "^10.3.0",
1920
"luxon": "^3.7.1",
20-
"proxyquire": "^2.1.3",
21-
"node-fetch": "^3.3.2",
2221
"openai": "^5.19.1",
22+
"proxyquire": "^2.1.3",
2323
"supertest": "^7.0.0"
2424
},
2525
"devDependencies": {
2626
"c8": "^10.0.0",
2727
"chai": "^4.5.0",
2828
"mocha": "^10.0.0",
29-
"node-fetch": "^3.3.2",
29+
"node-fetch": "^2.7.0",
3030
"proxyquire": "^2.1.3",
3131
"sinon": "^18.0.0",
3232
"uuid": "^10.0.0"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const {assert} = require('chai');
18+
const {describe, it} = require('mocha');
19+
20+
const projectId = process.env.CAIP_PROJECT_ID;
21+
const sample = require('../bounding-box/boundingbox-with-txt-img');
22+
23+
describe('boundingbox-with-txt-img', async () => {
24+
it('should return the bounding box', async function () {
25+
this.timeout(100000);
26+
const output = await sample.createBoundingBox(projectId);
27+
assert(output.length > 0);
28+
});
29+
});

genai/test/textgen-with-txt-routing.test.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ const {describe, it} = require('mocha');
1919

2020
const projectId = process.env.CAIP_PROJECT_ID;
2121
const sample = require('../text-generation/textgen-with-txt-routing.js');
22+
const {delay} = require('./util');
2223

2324
describe('textgen-with-txt-routing', async () => {
24-
it('should generate text content from a text prompt and with routing configuration', async () => {
25+
it('should generate text content from a text prompt and with routing configuration', async function () {
26+
this.timeout(180000);
27+
this.retries(2);
28+
await delay(this.test);
2529
const output = await sample.generateContent(projectId);
26-
assert(output.length > 0 && output.includes('AI'));
30+
assert(output.length > 0);
2731
});
2832
});

genai/text-generation/textgen-with-txt-routing.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ async function generateContent(
3838
};
3939

4040
const response = await client.models.generateContent({
41-
model: 'model-optimizer-exp-04-09',
41+
model: 'gemini-2.5-flash',
4242
contents: 'How does AI work?',
4343
config: generateContentConfig,
4444
});

run/markdown-preview/editor/app.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,25 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
const express = require('express');
16-
const handlebars = require('handlebars');
17-
const {readFile} = require('fs').promises;
18-
const renderRequest = require('./render.js');
15+
import express from 'express';
16+
import handlebars from 'handlebars';
17+
import fs from 'fs';
18+
import renderRequest from './render.js';
1919

2020
const app = express();
2121
app.use(express.json());
2222

2323
let markdownDefault, compiledTemplate, renderedHtml;
2424

2525
// Load the template files and serve them with the Editor service.
26-
const buildRenderedHtml = async () => {
26+
export const buildRenderedHtml = async () => {
27+
const dirname = process.cwd();
2728
try {
28-
markdownDefault = await readFile(__dirname + '/templates/markdown.md');
29+
markdownDefault = await fs.promises.readFile(
30+
dirname + '/templates/markdown.md'
31+
);
2932
compiledTemplate = handlebars.compile(
30-
await readFile(__dirname + '/templates/index.html', 'utf8')
33+
await fs.promises.readFile(dirname + '/templates/index.html', 'utf8')
3134
);
3235
renderedHtml = compiledTemplate({default: markdownDefault});
3336
return renderedHtml;
@@ -62,7 +65,4 @@ app.post('/render', async (req, res) => {
6265
// [END cloudrun_secure_request_do]
6366

6467
// Exports for testing purposes.
65-
module.exports = {
66-
app,
67-
buildRenderedHtml,
68-
};
68+
export default app;

run/markdown-preview/editor/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
const {app} = require('./app');
16-
const pkg = require('./package.json');
17-
const PORT = parseInt(process.env.PORT) || 8080;
15+
import app from './app.js';
16+
import fs from 'fs';
1817

18+
const pkg = JSON.parse(fs.readFileSync('./package.json'));
19+
const PORT = parseInt(process.env.PORT) || 8080;
1920
app.listen(PORT, () => console.log(`${pkg.name} listening on port ${PORT}`));

run/markdown-preview/editor/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
"type": "git",
1010
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
1111
},
12+
"type": "module",
1213
"engines": {
13-
"node": ">=16.0.0"
14+
"node": ">=20.0.0"
1415
},
1516
"main": "main.js",
1617
"scripts": {
@@ -23,12 +24,12 @@
2324
"dependencies": {
2425
"express": "^4.17.1",
2526
"google-auth-library": "^9.0.0",
26-
"got": "^11.5.0",
27-
"handlebars": "^4.7.6"
27+
"got": "^14.6.5",
28+
"handlebars": "^4.7.8"
2829
},
2930
"devDependencies": {
3031
"c8": "^10.0.0",
31-
"mocha": "^10.0.0",
32+
"mocha": "^11.0.0",
3233
"supertest": "^7.0.0"
3334
}
3435
}

0 commit comments

Comments
 (0)