Skip to content

Commit 724d5fe

Browse files
committed
refactor: store screenshots as URLs
This makes it easy for custom report fetchers and uploaders to store screenshots in e.g. Firebase Storage.
1 parent 52679bb commit 724d5fe

File tree

9 files changed

+35
-22
lines changed

9 files changed

+35
-22
lines changed

report-app/src/app/pages/report-viewer/report-viewer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ export class ReportViewer {
236236

237237
protected getScreenshotUrl(result: AssessmentResult): string | null {
238238
for (let i = result.attemptDetails.length - 1; i > -1; i--) {
239-
const screenshot = result.attemptDetails[i].buildResult.screenshotBase64;
240-
241-
if (screenshot) {
242-
return `data:image/png;base64,${screenshot}`;
239+
const screenshotUrl =
240+
result.attemptDetails[i].buildResult.screenshotPngUrl;
241+
if (screenshotUrl) {
242+
return screenshotUrl;
243243
}
244244
}
245245
return null;

runner/builder/builder-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export interface BuildResult {
5656
status: BuildResultStatus;
5757
message: string;
5858
errorType?: BuildErrorType;
59-
screenshotBase64?: string; // Base64 encoded PNG screenshot
59+
screenshotPngUrl?: string;
6060
missingDependency?: string;
6161
runtimeErrors?: string;
6262
/** JSON report from the Safety Web runner, if available. */

runner/builder/worker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ process.on('message', async (message: BuildWorkerMessage) => {
156156
result = {
157157
status: BuildResultStatus.SUCCESS,
158158
message: 'Application built successfully!',
159-
screenshotBase64: screenshotBase64Data,
159+
screenshotPngUrl: `data:image/png;base64,${screenshotBase64Data}`,
160160
runtimeErrors: runtimeErrors.join('\n'),
161161
axeViolations,
162162
safetyWebReportJson,

runner/file-system-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export async function removeFolderWithSymlinks(dir: string) {
6565
/** Write a file and creates the necessary directory structure. */
6666
export async function safeWriteFile(
6767
filePath: string,
68-
content: string,
68+
content: string | Buffer,
6969
encoding?: BufferEncoding
7070
): Promise<void> {
7171
const directory = dirname(filePath);

runner/ratings/autoraters/rate-files.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export async function autoRateFiles(
2121
environment: Environment,
2222
files: LlmResponseFile[],
2323
appPrompt: string,
24-
screenshotBase64: string | null
24+
screenshotPngUrl: string | null
2525
): Promise<AutoraterRunInfo> {
2626
console.log(`Autorater is using '${model}' model. \n`);
2727

@@ -39,15 +39,15 @@ export async function autoRateFiles(
3939

4040
// Visual (screenshot) scoring...
4141
let visualRating = undefined;
42-
if (screenshotBase64) {
42+
if (screenshotPngUrl) {
4343
console.log('⏳ Awaiting visual scoring results...');
4444
visualRating = await autoRateAppearance(
4545
llm,
4646
abortSignal,
4747
model,
4848
environment,
4949
appPrompt,
50-
screenshotBase64,
50+
screenshotPngUrl,
5151
'command-line'
5252
);
5353
console.log(`${greenCheckmark()} Visual scoring is successful.`);

runner/ratings/autoraters/visuals-rater.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { GenkitRunner } from '../../codegen/genkit/genkit-runner.js';
99
import defaultVisualRaterPrompt from './visual-rating-prompt.js';
1010
import { Environment } from '../../configuration/environment.js';
11+
import { screenshotUrlToPngBuffer } from '../../utils/screenshots.js';
1112

1213
/**
1314
* Automatically rate the appearance of a screenshot using an LLM.
@@ -16,7 +17,7 @@ import { Environment } from '../../configuration/environment.js';
1617
* @param model Model to use for the rating.
1718
* @param environment Environment in which the rating is running.
1819
* @param appPrompt Prompt to be used for the rating.
19-
* @param screenshotBase64 Screenshot to be rated.
20+
* @param screenshotPngUrl Screenshot PNG URL to be rated.
2021
* @param label Label for the rating, used for logging.
2122
*/
2223
export async function autoRateAppearance(
@@ -25,7 +26,7 @@ export async function autoRateAppearance(
2526
model: string,
2627
environment: Environment,
2728
appPrompt: string,
28-
screenshotBase64: string,
29+
screenshotPngUrl: string,
2930
label: string
3031
): Promise<AutoRateResult> {
3132
const prompt = environment.renderPrompt(defaultVisualRaterPrompt, null, {
@@ -38,8 +39,10 @@ export async function autoRateAppearance(
3839
content: [
3940
{
4041
media: {
41-
base64PngImage: screenshotBase64,
42-
url: `data:image/png;base64,${screenshotBase64}`,
42+
base64PngImage: (
43+
await screenshotUrlToPngBuffer(screenshotPngUrl)
44+
).toString('base64'),
45+
url: screenshotPngUrl,
4346
},
4447
},
4548
],

runner/ratings/built-in-ratings/visual-appearance-rating.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const visualAppearanceRating: LLMBasedRating = {
2020
id: 'common-autorater-visuals',
2121
model: DEFAULT_AUTORATER_MODEL_NAME,
2222
rate: async (ctx) => {
23-
if (ctx.buildResult.screenshotBase64 === undefined) {
23+
if (ctx.buildResult.screenshotPngUrl === undefined) {
2424
return {
2525
state: RatingState.SKIPPED,
2626
message: 'No screenshot available',
@@ -36,7 +36,7 @@ export const visualAppearanceRating: LLMBasedRating = {
3636
ctx.model,
3737
ctx.environment,
3838
ctx.fullPromptText,
39-
ctx.buildResult.screenshotBase64,
39+
ctx.buildResult.screenshotPngUrl,
4040
ctx.currentPromptDef.name
4141
);
4242
} catch (e) {

runner/reporting/report-logging.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,16 @@ export async function writeReportToDisk(
105105

106106
// Write screenshot to fs first, since we'll remove this info
107107
// from JSON later in this function.
108-
if (attempt.buildResult.screenshotBase64) {
108+
if (attempt.buildResult.screenshotPngUrl) {
109109
const screenshotFilePath = join(attemptPath, 'screenshot.png');
110-
await safeWriteFile(
111-
screenshotFilePath,
112-
attempt.buildResult.screenshotBase64,
113-
'base64'
114-
);
110+
111+
// Note: In practice this is a base64 data URL, but `fetch` conveniently
112+
// allows us to extract the content for writing a PNG to disk.
113+
const screenshotContent = await (
114+
await fetch(attempt.buildResult.screenshotPngUrl)
115+
).arrayBuffer();
116+
117+
await safeWriteFile(screenshotFilePath, Buffer.from(screenshotContent));
115118
}
116119

117120
// Write the safety web report if it exists.

runner/utils/screenshots.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** Converts a screenshot PNG URL to a PNG buffer with the image contents. */
2+
export async function screenshotUrlToPngBuffer(screenshotPngUrl: string) {
3+
// Note: In practice this is a base64 data URL, but `fetch` conveniently
4+
// allows us to extract the content for writing a PNG to disk.
5+
const screenshotContent = await (await fetch(screenshotPngUrl)).arrayBuffer();
6+
return Buffer.from(screenshotContent);
7+
}

0 commit comments

Comments
 (0)