@midscene/shared 1.10.5-beta-20260716114658.0 → 1.10.5
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/dist/es/env/parse-model-config.mjs +1 -1
- package/dist/es/img/index.mjs +2 -2
- package/dist/es/img/transform.mjs +18 -1
- package/dist/es/logger.mjs +2 -23
- package/dist/lib/env/parse-model-config.js +1 -1
- package/dist/lib/img/index.js +4 -1
- package/dist/lib/img/transform.js +23 -3
- package/dist/lib/logger.js +2 -23
- package/dist/types/img/index.d.ts +1 -1
- package/dist/types/img/transform.d.ts +2 -0
- package/dist/types/recorder.d.ts +0 -14
- package/package.json +1 -1
- package/src/img/index.ts +1 -0
- package/src/img/transform.ts +25 -0
- package/src/logger.ts +3 -38
- package/src/recorder.ts +0 -15
|
@@ -5,7 +5,7 @@ import { assert } from "../utils.mjs";
|
|
|
5
5
|
import { maskConfig, parseJson } from "./helper.mjs";
|
|
6
6
|
import { initDebugConfig } from "./init-debug.mjs";
|
|
7
7
|
const MODEL_CONFIG_DOC_URL = 'https://midscenejs.com/model-common-config.html';
|
|
8
|
-
const getCurrentVersion = ()=>"1.10.5
|
|
8
|
+
const getCurrentVersion = ()=>"1.10.5";
|
|
9
9
|
const getInvalidModelFamilyMessage = (modelFamily)=>`Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}. Current version v${getCurrentVersion()} accepts the following model families: ${MODEL_FAMILY_VALUES.join(', ')}. You can also visit ${MODEL_CONFIG_DOC_URL} for the latest configuration information.`;
|
|
10
10
|
const KEYS_MAP = {
|
|
11
11
|
insight: INSIGHT_MODEL_CONFIG_KEYS,
|
package/dist/es/img/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { imageInfoOfBase64, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, validateScreenshotBuffer } from "./info.mjs";
|
|
2
|
-
import { createImgBase64ByFormat, cropByRect, httpImg2Base64, inferBase64ImageFormat, localImg2Base64, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlockByBase64, parseBase64, preProcessImageUrl, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, zoomForGPT4o } from "./transform.mjs";
|
|
2
|
+
import { convertImgBufferToJpeg, createImgBase64ByFormat, cropByRect, httpImg2Base64, inferBase64ImageFormat, localImg2Base64, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlockByBase64, parseBase64, preProcessImageUrl, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, zoomForGPT4o } from "./transform.mjs";
|
|
3
3
|
import { annotateRects, compositeElementInfoImg, compositePointMarkerImg, processImageElementInfo } from "./box-select.mjs";
|
|
4
|
-
export { annotateRects, compositeElementInfoImg, compositePointMarkerImg, createImgBase64ByFormat, cropByRect, httpImg2Base64, imageInfoOfBase64, inferBase64ImageFormat, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, localImg2Base64, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlockByBase64, parseBase64, preProcessImageUrl, processImageElementInfo, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, validateScreenshotBuffer, zoomForGPT4o };
|
|
4
|
+
export { annotateRects, compositeElementInfoImg, compositePointMarkerImg, convertImgBufferToJpeg, createImgBase64ByFormat, cropByRect, httpImg2Base64, imageInfoOfBase64, inferBase64ImageFormat, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, localImg2Base64, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlockByBase64, parseBase64, preProcessImageUrl, processImageElementInfo, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, validateScreenshotBuffer, zoomForGPT4o };
|
|
@@ -70,6 +70,23 @@ async function resizeAndConvertImgBuffer(inputFormat, inputData, newSize) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
const normalizeBase64Body = (body)=>body.replace(/\s/g, '');
|
|
73
|
+
async function convertImgBufferToJpeg(inputData, quality = 90) {
|
|
74
|
+
if (ifInNode) try {
|
|
75
|
+
const Sharp = await get_sharp();
|
|
76
|
+
return await Sharp(inputData).jpeg({
|
|
77
|
+
quality
|
|
78
|
+
}).toBuffer();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
imgDebug('Sharp failed, falling back to Photon:', error);
|
|
81
|
+
}
|
|
82
|
+
const mimeType = detectImageMimeTypeFromBuffer(inputData) ?? 'image/png';
|
|
83
|
+
const photonImage = await photonFromBase64(`data:${mimeType};base64,${inputData.toString('base64')}`);
|
|
84
|
+
try {
|
|
85
|
+
return Buffer.from(photonImage.get_bytes_jpeg(quality));
|
|
86
|
+
} finally{
|
|
87
|
+
photonImage.free();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
73
90
|
const base64ImageDataUrlPattern = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i;
|
|
74
91
|
const supportedScreenshotDataUriPattern = /^data:image\/(png|jpe?g);base64,([\s\S]*)$/i;
|
|
75
92
|
const rawBase64BodyPattern = /^[A-Za-z0-9+/=\s]+$/;
|
|
@@ -310,4 +327,4 @@ async function scaleImage(imageBase64, scale) {
|
|
|
310
327
|
imageBase64: base64
|
|
311
328
|
};
|
|
312
329
|
}
|
|
313
|
-
export { createImgBase64ByFormat, cropByRect, httpImg2Base64, inferBase64ImageFormat, localImg2Base64, normalizeBase64Body, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlock, paddingToMatchBlockByBase64, parseBase64, photonFromBase64, photonToBase64, preProcessImageUrl, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, zoomForGPT4o };
|
|
330
|
+
export { convertImgBufferToJpeg, createImgBase64ByFormat, cropByRect, httpImg2Base64, inferBase64ImageFormat, localImg2Base64, normalizeBase64Body, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlock, paddingToMatchBlockByBase64, parseBase64, photonFromBase64, photonToBase64, preProcessImageUrl, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, zoomForGPT4o };
|
package/dist/es/logger.mjs
CHANGED
|
@@ -6,32 +6,21 @@ import { getMidsceneRunSubDir } from "./common.mjs";
|
|
|
6
6
|
import { ifInNode } from "./utils.mjs";
|
|
7
7
|
const topicPrefix = 'midscene';
|
|
8
8
|
const logStreams = new Map();
|
|
9
|
-
const backpressuredLogStreams = new Set();
|
|
10
|
-
const unavailableLogStreams = new Set();
|
|
11
9
|
const debugInstances = new Map();
|
|
12
10
|
function getLogStream(topic) {
|
|
13
11
|
const topicFileName = topic.replace(/:/g, '-');
|
|
14
|
-
if (unavailableLogStreams.has(topicFileName)) return null;
|
|
15
12
|
if (!logStreams.has(topicFileName)) {
|
|
16
13
|
const logFile = node_path.join(getMidsceneRunSubDir('log'), `${topicFileName}.log`);
|
|
17
14
|
const stream = node_fs.createWriteStream(logFile, {
|
|
18
15
|
flags: 'a'
|
|
19
16
|
});
|
|
20
|
-
stream.on('error', ()=>{
|
|
21
|
-
unavailableLogStreams.add(topicFileName);
|
|
22
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
23
|
-
if (logStreams.get(topicFileName) === stream) logStreams.delete(topicFileName);
|
|
24
|
-
});
|
|
25
17
|
logStreams.set(topicFileName, stream);
|
|
26
18
|
}
|
|
27
|
-
return logStreams.get(topicFileName)
|
|
19
|
+
return logStreams.get(topicFileName);
|
|
28
20
|
}
|
|
29
21
|
function writeLogToFile(topic, message) {
|
|
30
22
|
if (!ifInNode) return;
|
|
31
|
-
const topicFileName = topic.replace(/:/g, '-');
|
|
32
|
-
if (backpressuredLogStreams.has(topicFileName)) return;
|
|
33
23
|
const stream = getLogStream(topic);
|
|
34
|
-
if (!stream) return;
|
|
35
24
|
const now = new Date();
|
|
36
25
|
const isoDate = now.toLocaleDateString('sv-SE');
|
|
37
26
|
const isoTime = now.toLocaleTimeString('sv-SE');
|
|
@@ -42,17 +31,7 @@ function writeLogToFile(topic, message) {
|
|
|
42
31
|
const minutes = (Math.abs(timezoneOffsetMinutes) % 60).toString().padStart(2, '0');
|
|
43
32
|
const timezoneString = `${sign}${hours}:${minutes}`;
|
|
44
33
|
const localISOTime = `${isoDate}T${isoTime}.${milliseconds}${timezoneString}`;
|
|
45
|
-
|
|
46
|
-
if (!stream.write(`[${localISOTime}] ${message}\n`)) {
|
|
47
|
-
backpressuredLogStreams.add(topicFileName);
|
|
48
|
-
stream.once('drain', ()=>{
|
|
49
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
} catch {
|
|
53
|
-
unavailableLogStreams.add(topicFileName);
|
|
54
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
55
|
-
}
|
|
34
|
+
stream.write(`[${localISOTime}] ${message}\n`);
|
|
56
35
|
}
|
|
57
36
|
function getDebug(topic, options) {
|
|
58
37
|
const fullTopic = `${topicPrefix}:${topic}`;
|
|
@@ -37,7 +37,7 @@ const external_utils_js_namespaceObject = require("../utils.js");
|
|
|
37
37
|
const external_helper_js_namespaceObject = require("./helper.js");
|
|
38
38
|
const external_init_debug_js_namespaceObject = require("./init-debug.js");
|
|
39
39
|
const MODEL_CONFIG_DOC_URL = 'https://midscenejs.com/model-common-config.html';
|
|
40
|
-
const getCurrentVersion = ()=>"1.10.5
|
|
40
|
+
const getCurrentVersion = ()=>"1.10.5";
|
|
41
41
|
const getInvalidModelFamilyMessage = (modelFamily)=>`Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}. Current version v${getCurrentVersion()} accepts the following model families: ${external_types_js_namespaceObject.MODEL_FAMILY_VALUES.join(', ')}. You can also visit ${MODEL_CONFIG_DOC_URL} for the latest configuration information.`;
|
|
42
42
|
const KEYS_MAP = {
|
|
43
43
|
insight: external_constants_js_namespaceObject.INSIGHT_MODEL_CONFIG_KEYS,
|
package/dist/lib/img/index.js
CHANGED
|
@@ -24,9 +24,10 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
-
|
|
27
|
+
convertImgBufferToJpeg: ()=>external_transform_js_namespaceObject.convertImgBufferToJpeg,
|
|
28
28
|
inferBase64ImageFormat: ()=>external_transform_js_namespaceObject.inferBase64ImageFormat,
|
|
29
29
|
localImg2Base64: ()=>external_transform_js_namespaceObject.localImg2Base64,
|
|
30
|
+
paddingToMatchBlockByBase64: ()=>external_transform_js_namespaceObject.paddingToMatchBlockByBase64,
|
|
30
31
|
parseBase64: ()=>external_transform_js_namespaceObject.parseBase64,
|
|
31
32
|
resizeAndConvertImgBuffer: ()=>external_transform_js_namespaceObject.resizeAndConvertImgBuffer,
|
|
32
33
|
resizeImgBase64: ()=>external_transform_js_namespaceObject.resizeImgBase64,
|
|
@@ -55,6 +56,7 @@ const external_box_select_js_namespaceObject = require("./box-select.js");
|
|
|
55
56
|
exports.annotateRects = __webpack_exports__.annotateRects;
|
|
56
57
|
exports.compositeElementInfoImg = __webpack_exports__.compositeElementInfoImg;
|
|
57
58
|
exports.compositePointMarkerImg = __webpack_exports__.compositePointMarkerImg;
|
|
59
|
+
exports.convertImgBufferToJpeg = __webpack_exports__.convertImgBufferToJpeg;
|
|
58
60
|
exports.createImgBase64ByFormat = __webpack_exports__.createImgBase64ByFormat;
|
|
59
61
|
exports.cropByRect = __webpack_exports__.cropByRect;
|
|
60
62
|
exports.httpImg2Base64 = __webpack_exports__.httpImg2Base64;
|
|
@@ -80,6 +82,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
|
80
82
|
"annotateRects",
|
|
81
83
|
"compositeElementInfoImg",
|
|
82
84
|
"compositePointMarkerImg",
|
|
85
|
+
"convertImgBufferToJpeg",
|
|
83
86
|
"createImgBase64ByFormat",
|
|
84
87
|
"cropByRect",
|
|
85
88
|
"httpImg2Base64",
|
|
@@ -33,12 +33,13 @@ var __webpack_require__ = {};
|
|
|
33
33
|
var __webpack_exports__ = {};
|
|
34
34
|
__webpack_require__.r(__webpack_exports__);
|
|
35
35
|
__webpack_require__.d(__webpack_exports__, {
|
|
36
|
-
|
|
36
|
+
convertImgBufferToJpeg: ()=>convertImgBufferToJpeg,
|
|
37
37
|
inferBase64ImageFormat: ()=>inferBase64ImageFormat,
|
|
38
38
|
localImg2Base64: ()=>localImg2Base64,
|
|
39
|
+
paddingToMatchBlockByBase64: ()=>paddingToMatchBlockByBase64,
|
|
39
40
|
photonFromBase64: ()=>photonFromBase64,
|
|
40
|
-
photonToBase64: ()=>photonToBase64,
|
|
41
41
|
parseBase64: ()=>parseBase64,
|
|
42
|
+
httpImg2Base64: ()=>httpImg2Base64,
|
|
42
43
|
resizeAndConvertImgBuffer: ()=>resizeAndConvertImgBuffer,
|
|
43
44
|
resizeImgBase64: ()=>resizeImgBase64,
|
|
44
45
|
saveBase64Image: ()=>saveBase64Image,
|
|
@@ -51,7 +52,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
51
52
|
zoomForGPT4o: ()=>zoomForGPT4o,
|
|
52
53
|
createImgBase64ByFormat: ()=>createImgBase64ByFormat,
|
|
53
54
|
paddingToMatchBlock: ()=>paddingToMatchBlock,
|
|
54
|
-
|
|
55
|
+
photonToBase64: ()=>photonToBase64
|
|
55
56
|
});
|
|
56
57
|
const external_node_assert_namespaceObject = require("node:assert");
|
|
57
58
|
var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
|
|
@@ -129,6 +130,23 @@ async function resizeAndConvertImgBuffer(inputFormat, inputData, newSize) {
|
|
|
129
130
|
};
|
|
130
131
|
}
|
|
131
132
|
const normalizeBase64Body = (body)=>body.replace(/\s/g, '');
|
|
133
|
+
async function convertImgBufferToJpeg(inputData, quality = 90) {
|
|
134
|
+
if (external_utils_js_namespaceObject.ifInNode) try {
|
|
135
|
+
const Sharp = await external_get_sharp_js_default()();
|
|
136
|
+
return await Sharp(inputData).jpeg({
|
|
137
|
+
quality
|
|
138
|
+
}).toBuffer();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
imgDebug('Sharp failed, falling back to Photon:', error);
|
|
141
|
+
}
|
|
142
|
+
const mimeType = detectImageMimeTypeFromBuffer(inputData) ?? 'image/png';
|
|
143
|
+
const photonImage = await photonFromBase64(`data:${mimeType};base64,${inputData.toString('base64')}`);
|
|
144
|
+
try {
|
|
145
|
+
return external_node_buffer_namespaceObject.Buffer.from(photonImage.get_bytes_jpeg(quality));
|
|
146
|
+
} finally{
|
|
147
|
+
photonImage.free();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
132
150
|
const base64ImageDataUrlPattern = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i;
|
|
133
151
|
const supportedScreenshotDataUriPattern = /^data:image\/(png|jpe?g);base64,([\s\S]*)$/i;
|
|
134
152
|
const rawBase64BodyPattern = /^[A-Za-z0-9+/=\s]+$/;
|
|
@@ -369,6 +387,7 @@ async function scaleImage(imageBase64, scale) {
|
|
|
369
387
|
imageBase64: base64
|
|
370
388
|
};
|
|
371
389
|
}
|
|
390
|
+
exports.convertImgBufferToJpeg = __webpack_exports__.convertImgBufferToJpeg;
|
|
372
391
|
exports.createImgBase64ByFormat = __webpack_exports__.createImgBase64ByFormat;
|
|
373
392
|
exports.cropByRect = __webpack_exports__.cropByRect;
|
|
374
393
|
exports.httpImg2Base64 = __webpack_exports__.httpImg2Base64;
|
|
@@ -389,6 +408,7 @@ exports.saveBase64Image = __webpack_exports__.saveBase64Image;
|
|
|
389
408
|
exports.scaleImage = __webpack_exports__.scaleImage;
|
|
390
409
|
exports.zoomForGPT4o = __webpack_exports__.zoomForGPT4o;
|
|
391
410
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
411
|
+
"convertImgBufferToJpeg",
|
|
392
412
|
"createImgBase64ByFormat",
|
|
393
413
|
"cropByRect",
|
|
394
414
|
"httpImg2Base64",
|
package/dist/lib/logger.js
CHANGED
|
@@ -48,32 +48,21 @@ const external_common_js_namespaceObject = require("./common.js");
|
|
|
48
48
|
const external_utils_js_namespaceObject = require("./utils.js");
|
|
49
49
|
const topicPrefix = 'midscene';
|
|
50
50
|
const logStreams = new Map();
|
|
51
|
-
const backpressuredLogStreams = new Set();
|
|
52
|
-
const unavailableLogStreams = new Set();
|
|
53
51
|
const debugInstances = new Map();
|
|
54
52
|
function getLogStream(topic) {
|
|
55
53
|
const topicFileName = topic.replace(/:/g, '-');
|
|
56
|
-
if (unavailableLogStreams.has(topicFileName)) return null;
|
|
57
54
|
if (!logStreams.has(topicFileName)) {
|
|
58
55
|
const logFile = external_node_path_default().join((0, external_common_js_namespaceObject.getMidsceneRunSubDir)('log'), `${topicFileName}.log`);
|
|
59
56
|
const stream = external_node_fs_default().createWriteStream(logFile, {
|
|
60
57
|
flags: 'a'
|
|
61
58
|
});
|
|
62
|
-
stream.on('error', ()=>{
|
|
63
|
-
unavailableLogStreams.add(topicFileName);
|
|
64
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
65
|
-
if (logStreams.get(topicFileName) === stream) logStreams.delete(topicFileName);
|
|
66
|
-
});
|
|
67
59
|
logStreams.set(topicFileName, stream);
|
|
68
60
|
}
|
|
69
|
-
return logStreams.get(topicFileName)
|
|
61
|
+
return logStreams.get(topicFileName);
|
|
70
62
|
}
|
|
71
63
|
function writeLogToFile(topic, message) {
|
|
72
64
|
if (!external_utils_js_namespaceObject.ifInNode) return;
|
|
73
|
-
const topicFileName = topic.replace(/:/g, '-');
|
|
74
|
-
if (backpressuredLogStreams.has(topicFileName)) return;
|
|
75
65
|
const stream = getLogStream(topic);
|
|
76
|
-
if (!stream) return;
|
|
77
66
|
const now = new Date();
|
|
78
67
|
const isoDate = now.toLocaleDateString('sv-SE');
|
|
79
68
|
const isoTime = now.toLocaleTimeString('sv-SE');
|
|
@@ -84,17 +73,7 @@ function writeLogToFile(topic, message) {
|
|
|
84
73
|
const minutes = (Math.abs(timezoneOffsetMinutes) % 60).toString().padStart(2, '0');
|
|
85
74
|
const timezoneString = `${sign}${hours}:${minutes}`;
|
|
86
75
|
const localISOTime = `${isoDate}T${isoTime}.${milliseconds}${timezoneString}`;
|
|
87
|
-
|
|
88
|
-
if (!stream.write(`[${localISOTime}] ${message}\n`)) {
|
|
89
|
-
backpressuredLogStreams.add(topicFileName);
|
|
90
|
-
stream.once('drain', ()=>{
|
|
91
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
} catch {
|
|
95
|
-
unavailableLogStreams.add(topicFileName);
|
|
96
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
97
|
-
}
|
|
76
|
+
stream.write(`[${localISOTime}] ${message}\n`);
|
|
98
77
|
}
|
|
99
78
|
function getDebug(topic, options) {
|
|
100
79
|
const fullTopic = `${topicPrefix}:${topic}`;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidImageBuffer, validateScreenshotBuffer, type ValidateScreenshotBufferOptions, } from './info';
|
|
2
|
-
export { resizeAndConvertImgBuffer, resizeImgBase64, zoomForGPT4o, saveBase64Image, paddingToMatchBlockByBase64, cropByRect, scaleImage, localImg2Base64, httpImg2Base64, preProcessImageUrl, parseBase64, createImgBase64ByFormat, inferBase64ImageFormat, normalizeBase64Image, normalizeScreenshotBase64, type NormalizeScreenshotBase64Options, } from './transform';
|
|
2
|
+
export { resizeAndConvertImgBuffer, convertImgBufferToJpeg, resizeImgBase64, zoomForGPT4o, saveBase64Image, paddingToMatchBlockByBase64, cropByRect, scaleImage, localImg2Base64, httpImg2Base64, preProcessImageUrl, parseBase64, createImgBase64ByFormat, inferBase64ImageFormat, normalizeBase64Image, normalizeScreenshotBase64, type NormalizeScreenshotBase64Options, } from './transform';
|
|
3
3
|
export { processImageElementInfo, compositeElementInfoImg, compositePointMarkerImg, annotateRects, } from './box-select';
|
|
@@ -27,6 +27,8 @@ export declare function resizeAndConvertImgBuffer(inputFormat: string, inputData
|
|
|
27
27
|
format: string;
|
|
28
28
|
}>;
|
|
29
29
|
export declare const normalizeBase64Body: (body: string) => string;
|
|
30
|
+
/** Convert an image buffer to JPEG without changing its dimensions. */
|
|
31
|
+
export declare function convertImgBufferToJpeg(inputData: Buffer, quality?: number): Promise<Buffer>;
|
|
30
32
|
export declare const inferBase64ImageFormat: (base64Body: string) => "jpeg" | "png";
|
|
31
33
|
export declare const createImgBase64ByFormat: (format: string, body: string) => string;
|
|
32
34
|
export interface NormalizeScreenshotBase64Options {
|
package/dist/types/recorder.d.ts
CHANGED
|
@@ -13,18 +13,6 @@ export interface MidsceneRecorderPageInfo {
|
|
|
13
13
|
width: number;
|
|
14
14
|
height: number;
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
* A screenshot stored outside the recording event payload.
|
|
18
|
-
*
|
|
19
|
-
* Recorder events are persisted in the Studio renderer. Keeping full data
|
|
20
|
-
* URLs there makes long recordings retain every screenshot in the renderer
|
|
21
|
-
* heap, so screenshot bytes live in the Playground run directory instead.
|
|
22
|
-
*/
|
|
23
|
-
export interface MidsceneRecorderScreenshotAssetRef {
|
|
24
|
-
id: string;
|
|
25
|
-
mimeType: string;
|
|
26
|
-
bytes: number;
|
|
27
|
-
}
|
|
28
16
|
export interface MidsceneRecorderEvent {
|
|
29
17
|
type: MidsceneRecorderEventType;
|
|
30
18
|
source?: MidsceneRecorderSourceKind;
|
|
@@ -37,8 +25,6 @@ export interface MidsceneRecorderEvent {
|
|
|
37
25
|
pageInfo: MidsceneRecorderPageInfo;
|
|
38
26
|
screenshotBefore?: string;
|
|
39
27
|
screenshotAfter?: string;
|
|
40
|
-
/** The single screenshot retained for AI description and Markdown export. */
|
|
41
|
-
screenshotAsset?: MidsceneRecorderScreenshotAssetRef;
|
|
42
28
|
semantic?: MidsceneRecorderSemantic;
|
|
43
29
|
elementDescription?: string;
|
|
44
30
|
descriptionLoading?: boolean;
|
package/package.json
CHANGED
package/src/img/index.ts
CHANGED
package/src/img/transform.ts
CHANGED
|
@@ -156,6 +156,31 @@ export async function resizeAndConvertImgBuffer(
|
|
|
156
156
|
|
|
157
157
|
export const normalizeBase64Body = (body: string) => body.replace(/\s/g, '');
|
|
158
158
|
|
|
159
|
+
/** Convert an image buffer to JPEG without changing its dimensions. */
|
|
160
|
+
export async function convertImgBufferToJpeg(
|
|
161
|
+
inputData: Buffer,
|
|
162
|
+
quality = 90,
|
|
163
|
+
): Promise<Buffer> {
|
|
164
|
+
if (ifInNode) {
|
|
165
|
+
try {
|
|
166
|
+
const Sharp = await getSharp();
|
|
167
|
+
return await Sharp(inputData).jpeg({ quality }).toBuffer();
|
|
168
|
+
} catch (error) {
|
|
169
|
+
imgDebug('Sharp failed, falling back to Photon:', error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const mimeType = detectImageMimeTypeFromBuffer(inputData) ?? 'image/png';
|
|
174
|
+
const photonImage = await photonFromBase64(
|
|
175
|
+
`data:${mimeType};base64,${inputData.toString('base64')}`,
|
|
176
|
+
);
|
|
177
|
+
try {
|
|
178
|
+
return Buffer.from(photonImage.get_bytes_jpeg(quality));
|
|
179
|
+
} finally {
|
|
180
|
+
photonImage.free();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
159
184
|
const base64ImageDataUrlPattern = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i;
|
|
160
185
|
const supportedScreenshotDataUriPattern =
|
|
161
186
|
/^data:image\/(png|jpe?g);base64,([\s\S]*)$/i;
|
package/src/logger.ts
CHANGED
|
@@ -8,53 +8,28 @@ import { ifInNode } from './utils';
|
|
|
8
8
|
const topicPrefix = 'midscene';
|
|
9
9
|
// Map to store file streams
|
|
10
10
|
const logStreams = new Map<string, fs.WriteStream>();
|
|
11
|
-
// A WriteStream queues every write made after it reports backpressure. The
|
|
12
|
-
// main process can keep running while macOS has deprioritized a backgrounded
|
|
13
|
-
// app, so retaining diagnostic logs here can otherwise grow without bound.
|
|
14
|
-
// Drop best-effort logs until the stream drains instead.
|
|
15
|
-
const backpressuredLogStreams = new Set<string>();
|
|
16
|
-
const unavailableLogStreams = new Set<string>();
|
|
17
11
|
// Map to store debug instances
|
|
18
12
|
const debugInstances = new Map<string, DebugFunction>();
|
|
19
13
|
|
|
20
14
|
// Function to get or create a log stream
|
|
21
|
-
function getLogStream(topic: string): fs.WriteStream
|
|
15
|
+
function getLogStream(topic: string): fs.WriteStream {
|
|
22
16
|
const topicFileName = topic.replace(/:/g, '-');
|
|
23
|
-
if (unavailableLogStreams.has(topicFileName)) {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
17
|
if (!logStreams.has(topicFileName)) {
|
|
27
18
|
const logFile = path.join(
|
|
28
19
|
getMidsceneRunSubDir('log'),
|
|
29
20
|
`${topicFileName}.log`,
|
|
30
21
|
);
|
|
31
22
|
const stream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
32
|
-
// A stream error without a listener terminates the Electron main process.
|
|
33
|
-
// Logging must remain best-effort, so disable this topic after a file error
|
|
34
|
-
// rather than repeatedly queuing writes to a broken stream.
|
|
35
|
-
stream.on('error', () => {
|
|
36
|
-
unavailableLogStreams.add(topicFileName);
|
|
37
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
38
|
-
if (logStreams.get(topicFileName) === stream) {
|
|
39
|
-
logStreams.delete(topicFileName);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
23
|
logStreams.set(topicFileName, stream);
|
|
43
24
|
}
|
|
44
|
-
return logStreams.get(topicFileName)
|
|
25
|
+
return logStreams.get(topicFileName)!;
|
|
45
26
|
}
|
|
46
27
|
|
|
47
28
|
// Function to write log to file
|
|
48
29
|
function writeLogToFile(topic: string, message: string): void {
|
|
49
30
|
if (!ifInNode) return;
|
|
50
31
|
|
|
51
|
-
const topicFileName = topic.replace(/:/g, '-');
|
|
52
|
-
if (backpressuredLogStreams.has(topicFileName)) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
32
|
const stream = getLogStream(topic);
|
|
57
|
-
if (!stream) return;
|
|
58
33
|
// Generate ISO format timestamp with local timezone
|
|
59
34
|
const now = new Date();
|
|
60
35
|
// Use sv-SE locale to get ISO-like format (YYYY-MM-DD HH:mm:ss)
|
|
@@ -72,17 +47,7 @@ function writeLogToFile(topic: string, message: string): void {
|
|
|
72
47
|
.padStart(2, '0');
|
|
73
48
|
const timezoneString = `${sign}${hours}:${minutes}`;
|
|
74
49
|
const localISOTime = `${isoDate}T${isoTime}.${milliseconds}${timezoneString}`;
|
|
75
|
-
|
|
76
|
-
if (!stream.write(`[${localISOTime}] ${message}\n`)) {
|
|
77
|
-
backpressuredLogStreams.add(topicFileName);
|
|
78
|
-
stream.once('drain', () => {
|
|
79
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
unavailableLogStreams.add(topicFileName);
|
|
84
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
85
|
-
}
|
|
50
|
+
stream.write(`[${localISOTime}] ${message}\n`);
|
|
86
51
|
}
|
|
87
52
|
|
|
88
53
|
export type DebugFunction = (...args: unknown[]) => void;
|
package/src/recorder.ts
CHANGED
|
@@ -34,19 +34,6 @@ export interface MidsceneRecorderPageInfo {
|
|
|
34
34
|
height: number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* A screenshot stored outside the recording event payload.
|
|
39
|
-
*
|
|
40
|
-
* Recorder events are persisted in the Studio renderer. Keeping full data
|
|
41
|
-
* URLs there makes long recordings retain every screenshot in the renderer
|
|
42
|
-
* heap, so screenshot bytes live in the Playground run directory instead.
|
|
43
|
-
*/
|
|
44
|
-
export interface MidsceneRecorderScreenshotAssetRef {
|
|
45
|
-
id: string;
|
|
46
|
-
mimeType: string;
|
|
47
|
-
bytes: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
37
|
export interface MidsceneRecorderEvent {
|
|
51
38
|
type: MidsceneRecorderEventType;
|
|
52
39
|
source?: MidsceneRecorderSourceKind;
|
|
@@ -59,8 +46,6 @@ export interface MidsceneRecorderEvent {
|
|
|
59
46
|
pageInfo: MidsceneRecorderPageInfo;
|
|
60
47
|
screenshotBefore?: string;
|
|
61
48
|
screenshotAfter?: string;
|
|
62
|
-
/** The single screenshot retained for AI description and Markdown export. */
|
|
63
|
-
screenshotAsset?: MidsceneRecorderScreenshotAssetRef;
|
|
64
49
|
semantic?: MidsceneRecorderSemantic;
|
|
65
50
|
elementDescription?: string;
|
|
66
51
|
descriptionLoading?: boolean;
|