@midscene/playground 1.10.6 → 1.10.7-beta-20260720030503.0

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.
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { stat } from "node:fs/promises";
4
- import { basename, dirname, join, resolve as external_node_path_resolve, sep } from "node:path";
4
+ import { basename, dirname, extname, join, resolve as external_node_path_resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { describeElementAtPoint, runConnectivityTest } from "@midscene/core";
7
7
  import { getModelRuntime } from "@midscene/core/ai-model";
@@ -10,7 +10,7 @@ import { getMidsceneRunSubDir } from "@midscene/shared/common";
10
10
  import { PLAYGROUND_SERVER_PORT } from "@midscene/shared/constants";
11
11
  import { ModelConfigManager, globalModelConfigManager, overrideAIConfig } from "@midscene/shared/env";
12
12
  import { generateElementByPoint } from "@midscene/shared/extractor";
13
- import { annotateRects, imageInfoOfBase64 } from "@midscene/shared/img";
13
+ import { annotateRects, imageInfoOfBase64, screenshotImageExtension, screenshotImageFormatFromExtension, screenshotImageFormatFromMimeType, screenshotImageMimeType } from "@midscene/shared/img";
14
14
  import { getDebug } from "@midscene/shared/logger";
15
15
  import { buildMidsceneRecorderActionSummary, buildMidsceneRecorderReplayInstruction } from "@midscene/shared/recorder";
16
16
  import { antiEscapeScriptTag, uuid as utils_uuid } from "@midscene/shared/utils";
@@ -232,7 +232,7 @@ function writeRecorderAiDescribeScreenshot(imageBase64, suffix) {
232
232
  const { base64, mimeType } = extractBase64Payload(imageBase64);
233
233
  const bytes = Buffer.from(base64, 'base64');
234
234
  const sha256 = createHash('sha256').update(bytes).digest('hex');
235
- const extension = mimeType?.includes('jpeg') ? 'jpg' : 'png';
235
+ const extension = recorderScreenshotAssetExtension(mimeType);
236
236
  const pathParts = formatRecorderAiDescribeScreenshotPathParts();
237
237
  const dumpRoot = external_node_path_resolve(getMidsceneRunSubDir('dump'), RECORDER_AI_DESCRIBE_SCREENSHOT_DUMP_DIR);
238
238
  const dumpDir = assertPathInsideDirectory(dumpRoot, join(dumpRoot, pathParts.datePart, pathParts.hourPart));
@@ -262,7 +262,9 @@ function assertRecorderScreenshotAssetId(assetId) {
262
262
  return assetId;
263
263
  }
264
264
  function recorderScreenshotAssetExtension(mimeType) {
265
- return mimeType?.includes('jpeg') ? 'jpg' : 'png';
265
+ const format = screenshotImageFormatFromMimeType(mimeType);
266
+ if (!format) return 'png';
267
+ return 'jpeg' === format ? 'jpg' : screenshotImageExtension(format);
266
268
  }
267
269
  function recorderScreenshotAssetPath(assetId, mimeType) {
268
270
  const root = getRecorderScreenshotAssetRoot();
@@ -288,7 +290,7 @@ function initializeRecorderScreenshotAssetUsage() {
288
290
  withFileTypes: true
289
291
  })){
290
292
  if (!entry.isFile()) continue;
291
- const match = entry.name.match(/^(.*)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.(?:png|jpg)$/i);
293
+ const match = entry.name.match(/^(.*)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.(?:png|jpg|webp)$/i);
292
294
  if (!match?.[1]) continue;
293
295
  const sessionId = match[1];
294
296
  const bytes = statSync(join(root, entry.name)).size;
@@ -341,7 +343,8 @@ function findRecorderScreenshotAssetPath(assetId) {
341
343
  const root = getRecorderScreenshotAssetRoot();
342
344
  for (const extension of [
343
345
  'png',
344
- 'jpg'
346
+ 'jpg',
347
+ 'webp'
345
348
  ]){
346
349
  const filePath = assertPathInsideDirectory(root, join(root, `${safeAssetId}.${extension}`));
347
350
  if (existsSync(filePath)) return filePath;
@@ -373,7 +376,7 @@ function pruneRecorderScreenshotAssetsForSession(sessionId, retainedAssetIds) {
373
376
  withFileTypes: true
374
377
  })){
375
378
  if (!entry.isFile() || !entry.name.startsWith(prefix)) continue;
376
- const assetId = entry.name.replace(/\.(?:png|jpg)$/i, '');
379
+ const assetId = entry.name.replace(/\.(?:png|jpg|webp)$/i, '');
377
380
  const filePath = join(root, entry.name);
378
381
  if (!retained.has(assetId)) {
379
382
  rmSync(filePath, {
@@ -1914,7 +1917,7 @@ class PlaygroundServer {
1914
1917
  if (!report) return res.status(404).json({
1915
1918
  error: 'Report not found or expired'
1916
1919
  });
1917
- const match = /^([A-Za-z0-9_-]+)\.(png|jpe?g)$/.exec(req.params.assetName);
1920
+ const match = /^([A-Za-z0-9_-]+)\.(png|jpe?g|webp)$/.exec(req.params.assetName);
1918
1921
  if (!match) return res.status(400).json({
1919
1922
  error: 'Invalid screenshot asset'
1920
1923
  });
@@ -1930,7 +1933,9 @@ class PlaygroundServer {
1930
1933
  });
1931
1934
  const commaIndex = dataUri.indexOf(',');
1932
1935
  if (-1 === commaIndex) throw new Error('Invalid screenshot data URI');
1933
- const mimeType = 'png' === extension ? 'image/png' : 'image/jpeg';
1936
+ const format = screenshotImageFormatFromExtension(extension);
1937
+ if (!format) throw new Error(`Unsupported screenshot extension: ${extension}`);
1938
+ const mimeType = screenshotImageMimeType(format);
1934
1939
  return res.type(mimeType).send(Buffer.from(dataUri.slice(commaIndex + 1), 'base64'));
1935
1940
  } catch (error) {
1936
1941
  debugReport('failed to stream report screenshot: %s', error);
@@ -2322,7 +2327,9 @@ class PlaygroundServer {
2322
2327
  if (!filePath) return res.status(404).json({
2323
2328
  error: 'Recorder screenshot not found'
2324
2329
  });
2325
- res.type(filePath.endsWith('.jpg') ? 'image/jpeg' : 'image/png');
2330
+ const format = screenshotImageFormatFromExtension(extname(filePath).slice(1));
2331
+ if (!format) throw new Error('Unsupported recorder screenshot format');
2332
+ res.type(screenshotImageMimeType(format));
2326
2333
  return res.send(readFileSync(filePath));
2327
2334
  } catch (error) {
2328
2335
  return res.status(400).json({