bruniai 0.1.4 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/compare-images.d.ts +16 -0
- package/dist/compare-images.d.ts.map +1 -0
- package/dist/compare-images.js +89 -0
- package/dist/compare-images.js.map +1 -0
- package/dist/compare-urls.d.ts +1 -1
- package/dist/compare-urls.d.ts.map +1 -1
- package/dist/compare-urls.js +25 -5
- package/dist/compare-urls.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/comparison/core.d.ts +59 -0
- package/dist/runtime/comparison/core.d.ts.map +1 -0
- package/dist/runtime/comparison/core.js +83 -0
- package/dist/runtime/comparison/core.js.map +1 -0
- package/dist/runtime/comparison/figma-core.d.ts +66 -0
- package/dist/runtime/comparison/figma-core.d.ts.map +1 -0
- package/dist/runtime/comparison/figma-core.js +116 -0
- package/dist/runtime/comparison/figma-core.js.map +1 -0
- package/dist/runtime/comparison/image-core.d.ts +33 -0
- package/dist/runtime/comparison/image-core.d.ts.map +1 -0
- package/dist/runtime/comparison/image-core.js +246 -0
- package/dist/runtime/comparison/image-core.js.map +1 -0
- package/dist/runtime/comparison/image-image-core.d.ts +30 -0
- package/dist/runtime/comparison/image-image-core.d.ts.map +1 -0
- package/dist/runtime/comparison/image-image-core.js +242 -0
- package/dist/runtime/comparison/image-image-core.js.map +1 -0
- package/dist/runtime/diff/diff.d.ts +18 -0
- package/dist/runtime/diff/diff.d.ts.map +1 -0
- package/dist/runtime/diff/diff.js +99 -0
- package/dist/runtime/diff/diff.js.map +1 -0
- package/dist/runtime/image/detector.d.ts +5 -0
- package/dist/runtime/image/detector.d.ts.map +1 -0
- package/dist/runtime/image/detector.js +33 -0
- package/dist/runtime/image/detector.js.map +1 -0
- package/dist/runtime/image/fetch.d.ts +8 -0
- package/dist/runtime/image/fetch.d.ts.map +1 -0
- package/dist/runtime/image/fetch.js +41 -0
- package/dist/runtime/image/fetch.js.map +1 -0
- package/dist/runtime/image/index.d.ts +6 -0
- package/dist/runtime/image/index.d.ts.map +1 -0
- package/dist/runtime/image/index.js +5 -0
- package/dist/runtime/image/index.js.map +1 -0
- package/dist/runtime/image/report.d.ts +11 -0
- package/dist/runtime/image/report.d.ts.map +1 -0
- package/dist/runtime/image/report.js +89 -0
- package/dist/runtime/image/report.js.map +1 -0
- package/dist/runtime/image/visual-sections.d.ts +105 -0
- package/dist/runtime/image/visual-sections.d.ts.map +1 -0
- package/dist/runtime/image/visual-sections.js +1012 -0
- package/dist/runtime/image/visual-sections.js.map +1 -0
- package/dist/runtime/reporter/image-compression.d.ts +15 -0
- package/dist/runtime/reporter/image-compression.d.ts.map +1 -0
- package/dist/runtime/reporter/image-compression.js +71 -0
- package/dist/runtime/reporter/image-compression.js.map +1 -0
- package/dist/runtime/reporter/index.d.ts +8 -0
- package/dist/runtime/reporter/index.d.ts.map +1 -0
- package/dist/runtime/reporter/index.js +7 -0
- package/dist/runtime/reporter/index.js.map +1 -0
- package/dist/runtime/reporter/report-generator.d.ts +33 -0
- package/dist/runtime/reporter/report-generator.d.ts.map +1 -0
- package/dist/runtime/reporter/report-generator.js +191 -0
- package/dist/runtime/reporter/report-generator.js.map +1 -0
- package/dist/runtime/reporter/reporter.d.ts +24 -0
- package/dist/runtime/reporter/reporter.d.ts.map +1 -0
- package/dist/runtime/reporter/reporter.js +99 -0
- package/dist/runtime/reporter/reporter.js.map +1 -0
- package/dist/runtime/reporter/types.d.ts +98 -0
- package/dist/runtime/reporter/types.d.ts.map +1 -0
- package/dist/runtime/reporter/types.js +5 -0
- package/dist/runtime/reporter/types.js.map +1 -0
- package/dist/runtime/sections/sectionDom.d.ts +19 -0
- package/dist/runtime/sections/sectionDom.d.ts.map +1 -0
- package/dist/runtime/sections/sectionDom.js +50 -0
- package/dist/runtime/sections/sectionDom.js.map +1 -0
- package/dist/runtime/sections/sectionExtraction.d.ts +50 -0
- package/dist/runtime/sections/sectionExtraction.d.ts.map +1 -0
- package/dist/runtime/sections/sectionExtraction.js +422 -0
- package/dist/runtime/sections/sectionExtraction.js.map +1 -0
- package/dist/runtime/sections/sections.d.ts +7 -0
- package/dist/runtime/sections/sections.d.ts.map +1 -0
- package/dist/runtime/sections/sections.js +128 -0
- package/dist/runtime/sections/sections.js.map +1 -0
- package/dist/runtime/utils/json.d.ts +2 -0
- package/dist/runtime/utils/json.d.ts.map +1 -0
- package/dist/runtime/utils/json.js +13 -0
- package/dist/runtime/utils/json.js.map +1 -0
- package/dist/runtime/utils/window.d.ts +35 -0
- package/dist/runtime/utils/window.d.ts.map +1 -0
- package/dist/runtime/utils/window.js +144 -0
- package/dist/runtime/utils/window.js.map +1 -0
- package/dist/runtime/vision/agents.d.ts +51 -0
- package/dist/runtime/vision/agents.d.ts.map +1 -0
- package/dist/runtime/vision/agents.js +409 -0
- package/dist/runtime/vision/agents.js.map +1 -0
- package/dist/runtime/vision/combiner.d.ts +18 -0
- package/dist/runtime/vision/combiner.d.ts.map +1 -0
- package/dist/runtime/vision/combiner.js +169 -0
- package/dist/runtime/vision/combiner.js.map +1 -0
- package/dist/runtime/vision/comparison-html.d.ts +16 -0
- package/dist/runtime/vision/comparison-html.d.ts.map +1 -0
- package/dist/runtime/vision/comparison-html.js +116 -0
- package/dist/runtime/vision/comparison-html.js.map +1 -0
- package/dist/runtime/vision/index.d.ts +5 -0
- package/dist/runtime/vision/index.d.ts.map +1 -0
- package/dist/runtime/vision/index.js +4 -0
- package/dist/runtime/vision/index.js.map +1 -0
- package/dist/runtime/vision/section-judge.d.ts +8 -0
- package/dist/runtime/vision/section-judge.d.ts.map +1 -0
- package/dist/runtime/vision/section-judge.js +87 -0
- package/dist/runtime/vision/section-judge.js.map +1 -0
- package/dist/runtime/vision/types.d.ts +592 -0
- package/dist/runtime/vision/types.d.ts.map +1 -0
- package/dist/runtime/vision/types.js +126 -0
- package/dist/runtime/vision/types.js.map +1 -0
- package/dist/runtime/vision/utils.d.ts +79 -0
- package/dist/runtime/vision/utils.d.ts.map +1 -0
- package/dist/runtime/vision/utils.js +415 -0
- package/dist/runtime/vision/utils.js.map +1 -0
- package/dist/runtime/vision/vision.d.ts +37 -0
- package/dist/runtime/vision/vision.d.ts.map +1 -0
- package/dist/runtime/vision/vision.js +173 -0
- package/dist/runtime/vision/vision.js.map +1 -0
- package/dist/types.d.ts +15 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install bruniai
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import { compareUrls } from "bruniai";
|
|
16
|
+
import { compareImages, compareUrls } from "bruniai";
|
|
17
17
|
|
|
18
18
|
const result = await compareUrls({
|
|
19
19
|
baseUrl: "https://example.com",
|
|
@@ -24,6 +24,13 @@ const result = await compareUrls({
|
|
|
24
24
|
console.log(result.status); // "pass" | "fail" | "warning"
|
|
25
25
|
console.log(result.visual_analysis);
|
|
26
26
|
console.log(result.images.base_screenshot);
|
|
27
|
+
|
|
28
|
+
const imageResult = await compareImages({
|
|
29
|
+
baseImage: "https://example.com/design.png",
|
|
30
|
+
previewImage: "data:image/png;base64,...",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log(imageResult.status);
|
|
27
34
|
```
|
|
28
35
|
|
|
29
36
|
## API
|
|
@@ -47,6 +54,22 @@ Performs a visual comparison between two URLs.
|
|
|
47
54
|
- `sections_analysis`: Formatted sections analysis text
|
|
48
55
|
- `images`: Object containing paths to generated screenshots and diff images
|
|
49
56
|
|
|
57
|
+
### `compareImages(input: CompareImagesInput): Promise<CompareImagesOutput>`
|
|
58
|
+
|
|
59
|
+
Performs a visual comparison between two images.
|
|
60
|
+
|
|
61
|
+
**Parameters:**
|
|
62
|
+
|
|
63
|
+
- `baseImage` (string): Base/reference image as an HTTP(S) URL or `data:image/...`
|
|
64
|
+
- `previewImage` (string): Preview/changed image as an HTTP(S) URL or `data:image/...`
|
|
65
|
+
|
|
66
|
+
**Returns:**
|
|
67
|
+
|
|
68
|
+
- `status`: Overall comparison status ("pass" | "fail" | "warning" | "none")
|
|
69
|
+
- `visual_analysis`: Detailed visual analysis result from AI
|
|
70
|
+
- `sections_analysis`: Formatted sections analysis text
|
|
71
|
+
- `images`: Object containing paths to normalized images and diff images
|
|
72
|
+
|
|
50
73
|
## Requirements
|
|
51
74
|
|
|
52
75
|
- Node.js 18+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CompareImagesInput, CompareImagesOutput } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compare two images visually and return analysis results.
|
|
4
|
+
*
|
|
5
|
+
* This function performs a complete image-native comparison workflow:
|
|
6
|
+
* - Creates a temporary directory for images
|
|
7
|
+
* - Normalizes both inputs into PNG images
|
|
8
|
+
* - Trims margins and generates a diff image
|
|
9
|
+
* - Matches sections deterministically
|
|
10
|
+
* - Produces structured analysis output
|
|
11
|
+
*
|
|
12
|
+
* @param input - Comparison input parameters
|
|
13
|
+
* @returns Complete analysis results with image paths
|
|
14
|
+
*/
|
|
15
|
+
export declare function compareImages(input: CompareImagesInput): Promise<CompareImagesOutput>;
|
|
16
|
+
//# sourceMappingURL=compare-images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-images.d.ts","sourceRoot":"","sources":["../src/compare-images.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAyDpB;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CA4C9B"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { mkdirSync, existsSync } from "fs";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
async function importRuntimeModule(relativePath) {
|
|
6
|
+
const modulePath = fileURLToPath(new URL(relativePath, import.meta.url));
|
|
7
|
+
return (await import(modulePath));
|
|
8
|
+
}
|
|
9
|
+
async function loadImageToImageComparisonModule() {
|
|
10
|
+
try {
|
|
11
|
+
return await importRuntimeModule("./runtime/comparison/image-image-core.js");
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
try {
|
|
15
|
+
return await importRuntimeModule("../../../dist/comparison/image-image-core.js");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return await importRuntimeModule("../../../src/comparison/image-image-core.js");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isSupportedImageInput(input) {
|
|
23
|
+
if (!input) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (input.startsWith("data:image/")) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(input);
|
|
31
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function assertSupportedImageInput(input, fieldName) {
|
|
38
|
+
if (!isSupportedImageInput(input)) {
|
|
39
|
+
throw new Error(`${fieldName} must be an HTTP(S) image URL or data:image/... string`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compare two images visually and return analysis results.
|
|
44
|
+
*
|
|
45
|
+
* This function performs a complete image-native comparison workflow:
|
|
46
|
+
* - Creates a temporary directory for images
|
|
47
|
+
* - Normalizes both inputs into PNG images
|
|
48
|
+
* - Trims margins and generates a diff image
|
|
49
|
+
* - Matches sections deterministically
|
|
50
|
+
* - Produces structured analysis output
|
|
51
|
+
*
|
|
52
|
+
* @param input - Comparison input parameters
|
|
53
|
+
* @returns Complete analysis results with image paths
|
|
54
|
+
*/
|
|
55
|
+
export async function compareImages(input) {
|
|
56
|
+
const { baseImage, previewImage } = input;
|
|
57
|
+
assertSupportedImageInput(baseImage, "baseImage");
|
|
58
|
+
assertSupportedImageInput(previewImage, "previewImage");
|
|
59
|
+
const { performImageToImageComparison } = await loadImageToImageComparisonModule();
|
|
60
|
+
const imagesDir = join(tmpdir(), `bruniai-${Date.now()}`);
|
|
61
|
+
if (!existsSync(imagesDir)) {
|
|
62
|
+
mkdirSync(imagesDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
const result = await performImageToImageComparison({
|
|
65
|
+
baseImageUrl: baseImage,
|
|
66
|
+
previewImageUrl: previewImage,
|
|
67
|
+
imagesDir,
|
|
68
|
+
});
|
|
69
|
+
const status = result.visual_analysis.status === "none"
|
|
70
|
+
? "pass"
|
|
71
|
+
: result.visual_analysis.status;
|
|
72
|
+
return {
|
|
73
|
+
status,
|
|
74
|
+
visual_analysis: result.visual_analysis,
|
|
75
|
+
sections_analysis: result.sections_analysis,
|
|
76
|
+
images: {
|
|
77
|
+
base_screenshot: result.base_screenshot,
|
|
78
|
+
preview_screenshot: result.preview_screenshot,
|
|
79
|
+
diff_image: result.diff_image,
|
|
80
|
+
section_screenshots: Object.keys(result.section_screenshots).length > 0
|
|
81
|
+
? Object.fromEntries(Object.entries(result.section_screenshots).map(([key, value]) => [
|
|
82
|
+
key,
|
|
83
|
+
{ base: value.base, preview: value.preview },
|
|
84
|
+
]))
|
|
85
|
+
: undefined,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=compare-images.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-images.js","sourceRoot":"","sources":["../src/compare-images.ts"],"names":[],"mappings":"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"}
|
package/dist/compare-urls.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type { CompareUrlsInput, CompareUrlsOutput } from "./types.js";
|
|
|
21
21
|
* previewUrl: "https://preview.example.com",
|
|
22
22
|
* page: "/contact"
|
|
23
23
|
* });
|
|
24
|
-
* console.log(result.status); // "pass" | "fail" | "warning"
|
|
24
|
+
* console.log(result.status); // "pass" | "fail" | "warning"
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export declare function compareUrls(input: CompareUrlsInput): Promise<CompareUrlsOutput>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare-urls.d.ts","sourceRoot":"","sources":["../src/compare-urls.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compare-urls.d.ts","sourceRoot":"","sources":["../src/compare-urls.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA+BtE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAiE5B"}
|
package/dist/compare-urls.js
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { Stagehand } from "@browserbasehq/stagehand";
|
|
2
|
-
import { performComparison } from "../../../dist/comparison/core.js";
|
|
3
2
|
import { join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
4
|
import { mkdirSync, existsSync } from "fs";
|
|
5
5
|
import { tmpdir } from "os";
|
|
6
|
+
async function importRuntimeModule(relativePath) {
|
|
7
|
+
const modulePath = fileURLToPath(new URL(relativePath, import.meta.url));
|
|
8
|
+
return (await import(modulePath));
|
|
9
|
+
}
|
|
10
|
+
async function loadComparisonCoreModule() {
|
|
11
|
+
try {
|
|
12
|
+
return await importRuntimeModule("./runtime/comparison/core.js");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
try {
|
|
16
|
+
return await importRuntimeModule("../../../dist/comparison/core.js");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return await importRuntimeModule("../../../src/comparison/core.js");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
24
|
* Compare two URLs visually and return analysis results.
|
|
8
25
|
*
|
|
@@ -25,11 +42,12 @@ import { tmpdir } from "os";
|
|
|
25
42
|
* previewUrl: "https://preview.example.com",
|
|
26
43
|
* page: "/contact"
|
|
27
44
|
* });
|
|
28
|
-
* console.log(result.status); // "pass" | "fail" | "warning"
|
|
45
|
+
* console.log(result.status); // "pass" | "fail" | "warning"
|
|
29
46
|
* ```
|
|
30
47
|
*/
|
|
31
48
|
export async function compareUrls(input) {
|
|
32
49
|
const { baseUrl, previewUrl, page = "/" } = input;
|
|
50
|
+
const { performComparison } = await loadComparisonCoreModule();
|
|
33
51
|
// Create temporary directory for images.
|
|
34
52
|
const imagesDir = join(tmpdir(), `bruniai-${Date.now()}`);
|
|
35
53
|
if (!existsSync(imagesDir)) {
|
|
@@ -53,12 +71,14 @@ export async function compareUrls(input) {
|
|
|
53
71
|
imagesDir,
|
|
54
72
|
prNumber: input.prNumber,
|
|
55
73
|
repository: input.repository,
|
|
56
|
-
prTitle: input.prTitle,
|
|
57
|
-
prDescription: input.prDescription,
|
|
58
74
|
});
|
|
59
75
|
// Build output structure.
|
|
76
|
+
// Convert status from VisualAnalysisResult (which can be "none") to ReportStatus.
|
|
77
|
+
const status = result.visual_analysis.status === "none"
|
|
78
|
+
? "pass"
|
|
79
|
+
: result.visual_analysis.status;
|
|
60
80
|
const output = {
|
|
61
|
-
status
|
|
81
|
+
status,
|
|
62
82
|
visual_analysis: result.visual_analysis,
|
|
63
83
|
sections_analysis: result.sections_analysis,
|
|
64
84
|
images: {
|
package/dist/compare-urls.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare-urls.js","sourceRoot":"","sources":["../src/compare-urls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"compare-urls.js","sourceRoot":"","sources":["../src/compare-urls.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
* Provides a simple API to compare two URLs and analyze visual differences.
|
|
6
6
|
*/
|
|
7
7
|
export { compareUrls } from "./compare-urls.js";
|
|
8
|
-
export
|
|
8
|
+
export { compareImages } from "./compare-images.js";
|
|
9
|
+
export type { CompareImagesInput, CompareImagesOutput, CompareUrlsInput, CompareUrlsOutput, ComparisonImages, } from "./types.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Stagehand } from "@browserbasehq/stagehand";
|
|
2
|
+
import type { VisualAnalysisResult } from "../vision/types.js";
|
|
3
|
+
import type { SectionVisualResult } from "../reporter/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Options for performing a visual comparison.
|
|
6
|
+
*/
|
|
7
|
+
export interface ComparisonOptions {
|
|
8
|
+
/** Stagehand instance to use for browser automation. */
|
|
9
|
+
stagehand: Stagehand;
|
|
10
|
+
/** Base/reference URL. */
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
/** Preview/changed URL. */
|
|
13
|
+
previewUrl: string;
|
|
14
|
+
/** Page path to compare (e.g., "/" or "/about"). */
|
|
15
|
+
page: string;
|
|
16
|
+
/** Directory where images should be saved. */
|
|
17
|
+
imagesDir: string;
|
|
18
|
+
/** Optional PR number for metadata. */
|
|
19
|
+
prNumber?: string;
|
|
20
|
+
/** Optional repository name for metadata. */
|
|
21
|
+
repository?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Result of a visual comparison.
|
|
25
|
+
*/
|
|
26
|
+
export interface ComparisonResult {
|
|
27
|
+
/** Visual analysis result from AI. */
|
|
28
|
+
visual_analysis: VisualAnalysisResult;
|
|
29
|
+
/** Formatted sections analysis text. */
|
|
30
|
+
sections_analysis: string;
|
|
31
|
+
/** Path to base screenshot. */
|
|
32
|
+
base_screenshot: string;
|
|
33
|
+
/** Path to preview screenshot. */
|
|
34
|
+
preview_screenshot: string;
|
|
35
|
+
/** Path to diff image. */
|
|
36
|
+
diff_image: string;
|
|
37
|
+
/** Section screenshots keyed by section ID. */
|
|
38
|
+
section_screenshots: Record<string, {
|
|
39
|
+
base: string;
|
|
40
|
+
preview: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** Optional deterministic section-level diff data. */
|
|
43
|
+
section_results?: SectionVisualResult[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Perform visual comparison between two URLs.
|
|
47
|
+
*
|
|
48
|
+
* This function performs the core comparison workflow:
|
|
49
|
+
* 1. Takes screenshots of base and preview URLs
|
|
50
|
+
* 2. Generates diff image
|
|
51
|
+
* 3. Analyzes sections structure
|
|
52
|
+
* 4. Performs visual analysis with AI
|
|
53
|
+
* 5. Captures section screenshots
|
|
54
|
+
*
|
|
55
|
+
* @param options - Comparison options
|
|
56
|
+
* @returns Complete comparison results with image paths
|
|
57
|
+
*/
|
|
58
|
+
export declare function performComparison(options: ComparisonOptions): Promise<ComparisonResult>;
|
|
59
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/comparison/core.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAS1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAIhE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,wDAAwD;IACxD,SAAS,EAAE,SAAS,CAAC;IACrB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,eAAe,EAAE,oBAAoB,CAAC;IACtC,wCAAwC;IACxC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,sDAAsD;IACtD,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAqI3B"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { generateDiffImage } from "../diff/diff.js";
|
|
2
|
+
import { analyzeSectionsSideBySide } from "../sections/sections.js";
|
|
3
|
+
import { extractSectionBoundingBoxes, takeSectionScreenshot, } from "../sections/sectionExtraction.js";
|
|
4
|
+
import { analyzeImagesWithVision } from "../vision/index.js";
|
|
5
|
+
import { ensureViewportSize, ensurePageFullyRendered } from "../utils/window.js";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { writeFileSync } from "fs";
|
|
8
|
+
/**
|
|
9
|
+
* Perform visual comparison between two URLs.
|
|
10
|
+
*
|
|
11
|
+
* This function performs the core comparison workflow:
|
|
12
|
+
* 1. Takes screenshots of base and preview URLs
|
|
13
|
+
* 2. Generates diff image
|
|
14
|
+
* 3. Analyzes sections structure
|
|
15
|
+
* 4. Performs visual analysis with AI
|
|
16
|
+
* 5. Captures section screenshots
|
|
17
|
+
*
|
|
18
|
+
* @param options - Comparison options
|
|
19
|
+
* @returns Complete comparison results with image paths
|
|
20
|
+
*/
|
|
21
|
+
export async function performComparison(options) {
|
|
22
|
+
const { stagehand, baseUrl, previewUrl, page, imagesDir, prNumber = "", repository = "", } = options;
|
|
23
|
+
// Construct full URLs for this page.
|
|
24
|
+
const fullBaseUrl = baseUrl.replace(/\/$/, "") + page;
|
|
25
|
+
const fullPreviewUrl = previewUrl.replace(/\/$/, "") + page;
|
|
26
|
+
// Generate page suffix for file naming.
|
|
27
|
+
let pageSuffix = page.replace(/\//g, "_");
|
|
28
|
+
pageSuffix = pageSuffix === "_" ? "home" : pageSuffix;
|
|
29
|
+
// Get the page and navigate to blank page first to ensure it's ready.
|
|
30
|
+
const initialPage = stagehand.context.pages()[0];
|
|
31
|
+
await ensureViewportSize(initialPage, "about:blank");
|
|
32
|
+
// Ensure viewport is set correctly before navigating and taking screenshot.
|
|
33
|
+
await ensureViewportSize(initialPage, fullBaseUrl);
|
|
34
|
+
// Scroll through the page to trigger lazy loading and wait for images.
|
|
35
|
+
await ensurePageFullyRendered(initialPage);
|
|
36
|
+
const baseScreenshot = await initialPage.screenshot({
|
|
37
|
+
fullPage: true,
|
|
38
|
+
});
|
|
39
|
+
const baseScreenshotPath = join(imagesDir, `base_screenshot_${pageSuffix}.png`);
|
|
40
|
+
writeFileSync(baseScreenshotPath, baseScreenshot);
|
|
41
|
+
// Ensure viewport is set correctly before navigating to preview URL and taking screenshot.
|
|
42
|
+
await ensureViewportSize(initialPage, fullPreviewUrl);
|
|
43
|
+
await ensurePageFullyRendered(initialPage);
|
|
44
|
+
const previewScreenshot = await initialPage.screenshot({
|
|
45
|
+
fullPage: true,
|
|
46
|
+
});
|
|
47
|
+
const previewScreenshotPath = join(imagesDir, `preview_screenshot_${pageSuffix}.png`);
|
|
48
|
+
writeFileSync(previewScreenshotPath, previewScreenshot);
|
|
49
|
+
const diffImagePath = join(imagesDir, `diff_${pageSuffix}.png`);
|
|
50
|
+
await generateDiffImage(baseScreenshotPath, previewScreenshotPath, diffImagePath);
|
|
51
|
+
// Analyze sections and get formatted analysis output.
|
|
52
|
+
const sectionsAnalysis = await analyzeSectionsSideBySide(stagehand, fullBaseUrl, fullPreviewUrl);
|
|
53
|
+
// Extract section bounding boxes with IDs from analysis.
|
|
54
|
+
const sectionsWithIds = await extractSectionBoundingBoxes(stagehand, fullBaseUrl, sectionsAnalysis);
|
|
55
|
+
// Capture section screenshots for both base and preview URLs.
|
|
56
|
+
const sectionScreenshots = {};
|
|
57
|
+
for (const section of sectionsWithIds) {
|
|
58
|
+
const sectionId = section.sectionId;
|
|
59
|
+
// Define section screenshot paths.
|
|
60
|
+
const baseSectionScreenshot = join(imagesDir, `base_screenshot_${pageSuffix}_section_${sectionId}.png`);
|
|
61
|
+
const previewSectionScreenshot = join(imagesDir, `preview_screenshot_${pageSuffix}_section_${sectionId}.png`);
|
|
62
|
+
// Take section screenshots using section IDs and analysis data.
|
|
63
|
+
const baseSectionSuccess = await takeSectionScreenshot(stagehand, fullBaseUrl, baseSectionScreenshot, sectionId, sectionsAnalysis);
|
|
64
|
+
const previewSectionSuccess = await takeSectionScreenshot(stagehand, fullPreviewUrl, previewSectionScreenshot, sectionId, sectionsAnalysis);
|
|
65
|
+
if (baseSectionSuccess && previewSectionSuccess) {
|
|
66
|
+
sectionScreenshots[sectionId] = {
|
|
67
|
+
base: baseSectionScreenshot,
|
|
68
|
+
preview: previewSectionScreenshot,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Perform visual analysis with the sections information.
|
|
73
|
+
const visualAnalysis = await analyzeImagesWithVision(baseScreenshotPath, previewScreenshotPath, diffImagePath, fullBaseUrl, fullPreviewUrl, prNumber, repository, sectionsAnalysis);
|
|
74
|
+
return {
|
|
75
|
+
visual_analysis: visualAnalysis,
|
|
76
|
+
sections_analysis: sectionsAnalysis,
|
|
77
|
+
base_screenshot: baseScreenshotPath,
|
|
78
|
+
preview_screenshot: previewScreenshotPath,
|
|
79
|
+
diff_image: diffImagePath,
|
|
80
|
+
section_screenshots: sectionScreenshots,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/comparison/core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma-to-URL comparison core functionality.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the workflow for comparing Figma prototype
|
|
5
|
+
* screenshots against live website URLs using visual AI-based section
|
|
6
|
+
* detection.
|
|
7
|
+
*/
|
|
8
|
+
import type { Stagehand } from "@browserbasehq/stagehand";
|
|
9
|
+
import type { VisualAnalysisResult } from "../vision/types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Options for performing a Figma-to-URL comparison.
|
|
12
|
+
*/
|
|
13
|
+
export interface FigmaComparisonOptions {
|
|
14
|
+
/** Stagehand instance to use for browser automation. */
|
|
15
|
+
stagehand: Stagehand;
|
|
16
|
+
/** Figma prototype URL (base/reference). */
|
|
17
|
+
figmaUrl: string;
|
|
18
|
+
/** Preview/live URL to compare against. */
|
|
19
|
+
previewUrl: string;
|
|
20
|
+
/** Page path for the comparison (used for file naming). */
|
|
21
|
+
page: string;
|
|
22
|
+
/** Directory where images should be saved. */
|
|
23
|
+
imagesDir: string;
|
|
24
|
+
/** Optional PR number for metadata. */
|
|
25
|
+
prNumber?: string;
|
|
26
|
+
/** Optional repository name for metadata. */
|
|
27
|
+
repository?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Result of a Figma-to-URL comparison.
|
|
31
|
+
*/
|
|
32
|
+
export interface FigmaComparisonResult {
|
|
33
|
+
/** Visual analysis result from AI. */
|
|
34
|
+
visual_analysis: VisualAnalysisResult;
|
|
35
|
+
/** Formatted sections analysis text (AI-detected). */
|
|
36
|
+
sections_analysis: string;
|
|
37
|
+
/** Path to Figma screenshot (base). */
|
|
38
|
+
base_screenshot: string;
|
|
39
|
+
/** Path to preview URL screenshot. */
|
|
40
|
+
preview_screenshot: string;
|
|
41
|
+
/** Path to diff image. */
|
|
42
|
+
diff_image: string;
|
|
43
|
+
/** Section screenshots keyed by section ID. */
|
|
44
|
+
section_screenshots: Record<string, {
|
|
45
|
+
base: string;
|
|
46
|
+
preview: string;
|
|
47
|
+
}>;
|
|
48
|
+
/** Comparison mode indicator. */
|
|
49
|
+
mode: "figma-to-url";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Perform visual comparison between a Figma prototype and a live URL.
|
|
53
|
+
*
|
|
54
|
+
* This function performs the Figma-to-URL comparison workflow:
|
|
55
|
+
* 1. Takes screenshot of Figma prototype (canvas only)
|
|
56
|
+
* 2. Takes screenshot of preview URL
|
|
57
|
+
* 3. Generates diff image
|
|
58
|
+
* 4. Extracts visual sections using AI
|
|
59
|
+
* 5. Captures section screenshots
|
|
60
|
+
* 6. Performs visual analysis with AI
|
|
61
|
+
*
|
|
62
|
+
* @param options - Figma comparison options.
|
|
63
|
+
* @returns Complete comparison results with image paths.
|
|
64
|
+
*/
|
|
65
|
+
export declare function performFigmaComparison(options: FigmaComparisonOptions): Promise<FigmaComparisonResult>;
|
|
66
|
+
//# sourceMappingURL=figma-core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma-core.d.ts","sourceRoot":"","sources":["../../src/comparison/figma-core.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAU1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAK/D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,SAAS,EAAE,SAAS,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,sCAAsC;IACtC,eAAe,EAAE,oBAAoB,CAAC;IACtC,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,iCAAiC;IACjC,IAAI,EAAE,cAAc,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAiKhC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma-to-URL comparison core functionality.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the workflow for comparing Figma prototype
|
|
5
|
+
* screenshots against live website URLs using visual AI-based section
|
|
6
|
+
* detection.
|
|
7
|
+
*/
|
|
8
|
+
import { generateDiffImage } from "../diff/diff.js";
|
|
9
|
+
import { screenshotFigmaPrototype, extractVisualSections, formatVisualSectionsAsAnalysis, takeSectionScreenshotsFromVisualBounds, } from "../figma/index.js";
|
|
10
|
+
import { analyzeImagesWithVisionFigmaMode } from "../vision/index.js";
|
|
11
|
+
import { ensureViewportSize } from "../utils/window.js";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { writeFileSync } from "fs";
|
|
14
|
+
import sharp from "sharp";
|
|
15
|
+
/**
|
|
16
|
+
* Perform visual comparison between a Figma prototype and a live URL.
|
|
17
|
+
*
|
|
18
|
+
* This function performs the Figma-to-URL comparison workflow:
|
|
19
|
+
* 1. Takes screenshot of Figma prototype (canvas only)
|
|
20
|
+
* 2. Takes screenshot of preview URL
|
|
21
|
+
* 3. Generates diff image
|
|
22
|
+
* 4. Extracts visual sections using AI
|
|
23
|
+
* 5. Captures section screenshots
|
|
24
|
+
* 6. Performs visual analysis with AI
|
|
25
|
+
*
|
|
26
|
+
* @param options - Figma comparison options.
|
|
27
|
+
* @returns Complete comparison results with image paths.
|
|
28
|
+
*/
|
|
29
|
+
export async function performFigmaComparison(options) {
|
|
30
|
+
const { stagehand, figmaUrl, previewUrl, page, imagesDir, prNumber = "", repository = "", } = options;
|
|
31
|
+
console.log(`\n${"=".repeat(50)}\n🎨 Starting Figma-to-URL Comparison\n${"=".repeat(50)}`);
|
|
32
|
+
console.log(`Figma URL: ${figmaUrl}`);
|
|
33
|
+
console.log(`Preview URL: ${previewUrl}`);
|
|
34
|
+
// Generate page suffix for file naming.
|
|
35
|
+
let pageSuffix = page.replace(/\//g, "_");
|
|
36
|
+
pageSuffix = pageSuffix === "_" ? "home" : pageSuffix;
|
|
37
|
+
// Step 1: Take screenshot of Figma prototype.
|
|
38
|
+
console.log("\n📸 Step 1: Capturing Figma prototype screenshot...");
|
|
39
|
+
const baseScreenshotPath = join(imagesDir, `base_screenshot_${pageSuffix}.png`);
|
|
40
|
+
const figmaResult = await screenshotFigmaPrototype(stagehand, figmaUrl, baseScreenshotPath);
|
|
41
|
+
if (!figmaResult.success) {
|
|
42
|
+
throw new Error(`Failed to capture Figma screenshot: ${figmaResult.error || "Unknown error"}`);
|
|
43
|
+
}
|
|
44
|
+
console.log(`Figma screenshot saved: ${baseScreenshotPath}`);
|
|
45
|
+
console.log(`Canvas bounds: ${JSON.stringify(figmaResult.canvasBounds)}`);
|
|
46
|
+
// Step 2: Take screenshot of preview URL.
|
|
47
|
+
console.log("\n📸 Step 2: Capturing preview URL screenshot...");
|
|
48
|
+
const initialPage = stagehand.context.pages()[0];
|
|
49
|
+
await ensureViewportSize(initialPage, previewUrl);
|
|
50
|
+
const previewScreenshot = await initialPage.screenshot({
|
|
51
|
+
fullPage: true,
|
|
52
|
+
});
|
|
53
|
+
const previewScreenshotPath = join(imagesDir, `preview_screenshot_${pageSuffix}.png`);
|
|
54
|
+
writeFileSync(previewScreenshotPath, previewScreenshot);
|
|
55
|
+
console.log(`Preview screenshot saved: ${previewScreenshotPath}`);
|
|
56
|
+
// Step 3: Generate diff image.
|
|
57
|
+
console.log("\n🔍 Step 3: Generating diff image...");
|
|
58
|
+
const diffImagePath = join(imagesDir, `diff_${pageSuffix}.png`);
|
|
59
|
+
await generateDiffImage(baseScreenshotPath, previewScreenshotPath, diffImagePath);
|
|
60
|
+
console.log(`Diff image saved: ${diffImagePath}`);
|
|
61
|
+
// Step 4: Extract visual sections from Figma screenshot using AI.
|
|
62
|
+
console.log("\n🤖 Step 4: Extracting visual sections using AI...");
|
|
63
|
+
const visualSectionsResult = await extractVisualSections(stagehand, baseScreenshotPath);
|
|
64
|
+
// Format sections analysis for compatibility with existing workflow.
|
|
65
|
+
const sectionsAnalysis = formatVisualSectionsAsAnalysis(visualSectionsResult);
|
|
66
|
+
console.log(`\n${"=".repeat(50)}\n🗺️ Visual Sections Analysis:\n${sectionsAnalysis}\n${"=".repeat(50)}`);
|
|
67
|
+
// Step 5: Capture section screenshots.
|
|
68
|
+
console.log("\n📷 Step 5: Capturing section screenshots...");
|
|
69
|
+
const sectionScreenshots = {};
|
|
70
|
+
// Take section screenshots from preview URL using visual bounds.
|
|
71
|
+
const previewSectionScreenshots = await takeSectionScreenshotsFromVisualBounds(stagehand, previewUrl, visualSectionsResult.sections, imagesDir, pageSuffix);
|
|
72
|
+
// For Figma sections, we use the visual bounds to clip from the base screenshot.
|
|
73
|
+
for (const section of visualSectionsResult.sections) {
|
|
74
|
+
const sectionId = section.sectionId;
|
|
75
|
+
const baseSectionPath = join(imagesDir, `base_screenshot_${pageSuffix}_section_${sectionId}.png`);
|
|
76
|
+
try {
|
|
77
|
+
// Crop directly from the stitched base screenshot.
|
|
78
|
+
// This works even when the section is below the first viewport.
|
|
79
|
+
const baseSectionScreenshot = await sharp(baseScreenshotPath)
|
|
80
|
+
.extract({
|
|
81
|
+
left: Math.max(0, Math.round(section.boundingBox.x)),
|
|
82
|
+
top: Math.max(0, Math.round(section.boundingBox.y)),
|
|
83
|
+
width: Math.max(1, Math.round(section.boundingBox.width)),
|
|
84
|
+
height: Math.max(1, Math.round(section.boundingBox.height)),
|
|
85
|
+
})
|
|
86
|
+
.png()
|
|
87
|
+
.toBuffer();
|
|
88
|
+
writeFileSync(baseSectionPath, baseSectionScreenshot);
|
|
89
|
+
// Only add to result if both screenshots exist.
|
|
90
|
+
if (previewSectionScreenshots[sectionId]) {
|
|
91
|
+
sectionScreenshots[sectionId] = {
|
|
92
|
+
base: baseSectionPath,
|
|
93
|
+
preview: previewSectionScreenshots[sectionId],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.warn(`Failed to capture Figma section ${sectionId}: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log(`Captured ${Object.keys(sectionScreenshots).length} section screenshot pairs`);
|
|
102
|
+
// Step 6: Perform visual analysis with AI (Figma mode - skips URL navigation).
|
|
103
|
+
console.log("\n🧠 Step 6: Performing visual analysis (Figma mode)...");
|
|
104
|
+
const visualAnalysis = await analyzeImagesWithVisionFigmaMode(baseScreenshotPath, previewScreenshotPath, diffImagePath, figmaUrl, previewUrl, prNumber, repository, sectionsAnalysis);
|
|
105
|
+
console.log(`Visual analysis completed: ${visualAnalysis.status}`);
|
|
106
|
+
return {
|
|
107
|
+
visual_analysis: visualAnalysis,
|
|
108
|
+
sections_analysis: sectionsAnalysis,
|
|
109
|
+
base_screenshot: baseScreenshotPath,
|
|
110
|
+
preview_screenshot: previewScreenshotPath,
|
|
111
|
+
diff_image: diffImagePath,
|
|
112
|
+
section_screenshots: sectionScreenshots,
|
|
113
|
+
mode: "figma-to-url",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=figma-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma-core.js","sourceRoot":"","sources":["../../src/comparison/figma-core.ts"],"names":[],"mappings":"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"}
|