@midscene/core 0.18.1-beta-20250611082446.0 → 0.18.1
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/ai-model.d.ts +52 -4
- package/dist/es/ai-model.js +9 -1
- package/dist/es/{chunk-D4ZWVRRG.js → chunk-A7OTPIZJ.js} +19 -6
- package/dist/es/chunk-A7OTPIZJ.js.map +1 -0
- package/dist/es/{chunk-X2UALJ3B.js → chunk-EYIL4AHP.js} +353 -3
- package/dist/es/chunk-EYIL4AHP.js.map +1 -0
- package/dist/es/index.d.ts +4 -4
- package/dist/es/index.js +19 -26
- package/dist/es/index.js.map +1 -1
- package/dist/es/{llm-planning-453ffce5.d.ts → llm-planning-a951deb9.d.ts} +2 -2
- package/dist/es/{types-00c9086f.d.ts → types-dce56c26.d.ts} +6 -6
- package/dist/es/utils.d.ts +1 -1
- package/dist/es/utils.js +1 -1
- package/dist/lib/ai-model.d.ts +52 -4
- package/dist/lib/ai-model.js +10 -2
- package/dist/lib/{chunk-D4ZWVRRG.js → chunk-A7OTPIZJ.js} +18 -5
- package/dist/lib/chunk-A7OTPIZJ.js.map +1 -0
- package/dist/lib/{chunk-X2UALJ3B.js → chunk-EYIL4AHP.js} +371 -21
- package/dist/lib/chunk-EYIL4AHP.js.map +1 -0
- package/dist/lib/index.d.ts +4 -4
- package/dist/lib/index.js +37 -44
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/{llm-planning-453ffce5.d.ts → llm-planning-a951deb9.d.ts} +2 -2
- package/dist/{types/types-00c9086f.d.ts → lib/types-dce56c26.d.ts} +6 -6
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/types/ai-model.d.ts +52 -4
- package/dist/types/index.d.ts +4 -4
- package/dist/types/{llm-planning-453ffce5.d.ts → llm-planning-a951deb9.d.ts} +2 -2
- package/dist/{lib/types-00c9086f.d.ts → types/types-dce56c26.d.ts} +6 -6
- package/dist/types/utils.d.ts +1 -1
- package/package.json +4 -3
- package/dist/es/chunk-D4ZWVRRG.js.map +0 -1
- package/dist/es/chunk-X2UALJ3B.js.map +0 -1
- package/dist/lib/chunk-D4ZWVRRG.js.map +0 -1
- package/dist/lib/chunk-X2UALJ3B.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../src/utils.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport * as path from 'node:path';\nimport { dirname } from 'node:path';\nimport {\n defaultRunDirName,\n getMidsceneRunSubDir,\n} from '@midscene/shared/common';\nimport {\n MIDSCENE_DEBUG_MODE,\n MIDSCENE_OPENAI_INIT_CONFIG_JSON,\n getAIConfig,\n getAIConfigInJson,\n} from '@midscene/shared/env';\nimport { getRunningPkgInfo } from '@midscene/shared/fs';\nimport { assert, logMsg } from '@midscene/shared/utils';\nimport { escapeHtml, ifInBrowser, uuid } from '@midscene/shared/utils';\nimport xss from 'xss';\nimport type { Rect, ReportDumpWithAttributes } from './types';\n\nlet logEnvReady = false;\n\nconst xssOptions = {\n escapeHtml,\n};\nexport const groupedActionDumpFileExt = 'web-dump.json';\n\nconst reportTpl = 'REPLACE_ME_WITH_REPORT_HTML';\n\nfunction getReportTpl() {\n return reportTpl;\n}\n\nexport function replaceStringWithFirstAppearance(\n str: string,\n target: string,\n replacement: string,\n) {\n const index = str.indexOf(target);\n return str.slice(0, index) + replacement + str.slice(index + target.length);\n}\n\nexport function reportHTMLContent(\n dumpData: string | ReportDumpWithAttributes[],\n reportPath?: string,\n): string {\n const tpl = getReportTpl();\n if (!tpl) {\n console.warn('reportTpl is not set, will not write report');\n return '';\n }\n\n const dumpPlaceholder = '{{dump}}';\n\n // verify the template contains the placeholder\n if (!tpl.includes(dumpPlaceholder)) {\n console.warn(\n 'Failed to get the Midscene report template due to the lack of the {{dump}} placeholder. If you are building Midscene.js by yourself, please refer to the contribution guide for more information: https://github.com/web-infra-dev/midscene/blob/main/CONTRIBUTING.md#FAQ',\n );\n return '';\n }\n\n // find the first placeholder position\n const placeholderIndex = tpl.indexOf(dumpPlaceholder);\n\n // split the template into two parts before and after the placeholder\n const firstPart = tpl.substring(0, placeholderIndex);\n const secondPart = tpl.substring(placeholderIndex + dumpPlaceholder.length);\n\n // if reportPath is set, it means we are in write to file mode\n const writeToFile = reportPath && !ifInBrowser;\n let resultContent = '';\n\n // helper function: decide to write to file or append to resultContent\n const appendOrWrite = (content: string): void => {\n if (writeToFile) {\n writeFileSync(reportPath!, `${content}\\n`, {\n flag: 'a',\n });\n } else {\n resultContent += `${content}\\n`;\n }\n };\n\n // if writeToFile is true, write the first part to file, otherwise set the first part to the initial value of resultContent\n if (writeToFile) {\n writeFileSync(reportPath!, firstPart, { flag: 'w' }); // use 'w' flag to overwrite the existing file\n } else {\n resultContent = firstPart;\n }\n\n // generate dump content\n // handle empty data or undefined\n if (\n (Array.isArray(dumpData) && dumpData.length === 0) ||\n typeof dumpData === 'undefined'\n ) {\n const dumpContent =\n '<script type=\"midscene_web_dump\" type=\"application/json\"></script>';\n appendOrWrite(dumpContent);\n }\n // handle string type dumpData\n else if (typeof dumpData === 'string') {\n const dumpContent =\n // biome-ignore lint/style/useTemplate: <explanation> do not use template string here, will cause bundle error\n '<script type=\"midscene_web_dump\" type=\"application/json\">\\n' +\n xss(dumpData, xssOptions) +\n '\\n</script>';\n appendOrWrite(dumpContent);\n }\n // handle array type dumpData\n else {\n // for array, handle each item\n for (let i = 0; i < dumpData.length; i++) {\n const { dumpString, attributes } = dumpData[i];\n const attributesArr = Object.keys(attributes || {}).map((key) => {\n return `${key}=\"${encodeURIComponent(attributes![key])}\"`;\n });\n\n const dumpContent =\n // biome-ignore lint/style/useTemplate: <explanation> do not use template string here, will cause bundle error\n '<script type=\"midscene_web_dump\" type=\"application/json\" ' +\n attributesArr.join(' ') +\n '>\\n' +\n xss(dumpString, xssOptions) +\n '\\n</script>';\n appendOrWrite(dumpContent);\n }\n }\n\n // add the second part\n if (writeToFile) {\n writeFileSync(reportPath!, secondPart, { flag: 'a' });\n return reportPath!;\n }\n\n resultContent += secondPart;\n return resultContent;\n}\n\nexport function writeDumpReport(\n fileName: string,\n dumpData: string | ReportDumpWithAttributes[],\n): string | null {\n if (ifInBrowser) {\n console.log('will not write report in browser');\n return null;\n }\n\n const __dirname = dirname(__filename);\n const midscenePkgInfo = getRunningPkgInfo(__dirname);\n if (!midscenePkgInfo) {\n console.warn('midscenePkgInfo not found, will not write report');\n return null;\n }\n\n const reportPath = path.join(\n getMidsceneRunSubDir('report'),\n `${fileName}.html`,\n );\n\n reportHTMLContent(dumpData, reportPath);\n\n if (process.env.MIDSCENE_DEBUG_LOG_JSON) {\n const jsonPath = `${reportPath}.json`;\n let data = dumpData as ReportDumpWithAttributes[];\n\n if (typeof dumpData === 'string') {\n data = JSON.parse(dumpData) as ReportDumpWithAttributes[];\n }\n\n writeFileSync(\n jsonPath,\n JSON.stringify(\n data.map((item) => ({\n dumpString: JSON.parse(item.dumpString),\n attributes: item.attributes,\n })),\n null,\n 2,\n ),\n );\n\n logMsg(`Midscene - dump file written: ${jsonPath}`);\n }\n\n return reportPath;\n}\n\nexport function writeLogFile(opts: {\n fileName: string;\n fileExt: string;\n fileContent: string;\n type: 'dump' | 'cache' | 'report' | 'tmp';\n generateReport?: boolean;\n}) {\n if (ifInBrowser) {\n return '/mock/report.html';\n }\n const { fileName, fileExt, fileContent, type = 'dump' } = opts;\n const targetDir = getMidsceneRunSubDir(type);\n // Ensure directory exists\n if (!logEnvReady) {\n assert(targetDir, 'logDir should be set before writing dump file');\n\n // gitIgnore in the parent directory\n const gitIgnorePath = path.join(targetDir, '../../.gitignore');\n const gitPath = path.join(targetDir, '../../.git');\n let gitIgnoreContent = '';\n\n if (existsSync(gitPath)) {\n // if the git path exists, we need to add the log folder to the git ignore file\n if (existsSync(gitIgnorePath)) {\n gitIgnoreContent = readFileSync(gitIgnorePath, 'utf-8');\n }\n\n // ignore the log folder\n if (!gitIgnoreContent.includes(`${defaultRunDirName}/`)) {\n writeFileSync(\n gitIgnorePath,\n `${gitIgnoreContent}\\n# Midscene.js dump files\\n${defaultRunDirName}/dump\\n${defaultRunDirName}/report\\n${defaultRunDirName}/tmp\\n${defaultRunDirName}/log\\n`,\n 'utf-8',\n );\n }\n }\n\n logEnvReady = true;\n }\n\n const filePath = path.join(targetDir, `${fileName}.${fileExt}`);\n\n if (type !== 'dump') {\n // do not write dump file any more\n writeFileSync(filePath, fileContent);\n }\n\n if (opts?.generateReport) {\n return writeDumpReport(fileName, fileContent);\n }\n\n return filePath;\n}\n\nexport function getTmpDir(): string | null {\n try {\n const runningPkgInfo = getRunningPkgInfo();\n if (!runningPkgInfo) {\n return null;\n }\n const { name } = runningPkgInfo;\n const tmpPath = path.join(tmpdir(), name);\n mkdirSync(tmpPath, { recursive: true });\n return tmpPath;\n } catch (e) {\n return null;\n }\n}\n\nexport function getTmpFile(fileExtWithoutDot: string): string | null {\n if (ifInBrowser) {\n return null;\n }\n const tmpDir = getTmpDir();\n const filename = `${uuid()}.${fileExtWithoutDot}`;\n return path.join(tmpDir!, filename);\n}\n\nexport function overlapped(container: Rect, target: Rect) {\n // container and the target have some part overlapped\n return (\n container.left < target.left + target.width &&\n container.left + container.width > target.left &&\n container.top < target.top + target.height &&\n container.top + container.height > target.top\n );\n}\n\nexport async function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function replacerForPageObject(key: string, value: any) {\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n return value;\n}\n\nexport function stringifyDumpData(data: any, indents?: number) {\n return JSON.stringify(data, replacerForPageObject, indents);\n}\n\ndeclare const __VERSION__: string;\n\nexport function getVersion() {\n return __VERSION__;\n}\n\nfunction debugLog(...message: any[]) {\n const debugMode = getAIConfig(MIDSCENE_DEBUG_MODE);\n if (debugMode) {\n console.log('[Midscene]', ...message);\n }\n}\n\nlet lastReportedRepoUrl = '';\nexport function uploadTestInfoToServer({ testUrl }: { testUrl: string }) {\n let repoUrl = '';\n let userEmail = '';\n\n const extraConfig = getAIConfigInJson(MIDSCENE_OPENAI_INIT_CONFIG_JSON);\n const serverUrl = extraConfig?.REPORT_SERVER_URL;\n\n try {\n repoUrl = execSync('git config --get remote.origin.url').toString().trim();\n userEmail = execSync('git config --get user.email').toString().trim();\n } catch (error) {\n debugLog('Failed to get git info:', error);\n }\n\n // Only upload test info if:\n // 1. Server URL is configured AND\n // 2. Either:\n // - We have a repo URL that's different from last reported one (to avoid duplicate reports)\n // - OR we don't have a repo URL but have a test URL (for non-git environments)\n if (\n serverUrl &&\n ((repoUrl && repoUrl !== lastReportedRepoUrl) || (!repoUrl && testUrl))\n ) {\n debugLog('Uploading test info to server', {\n serverUrl,\n repoUrl,\n testUrl,\n userEmail,\n });\n\n fetch(serverUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n repo_url: repoUrl,\n test_url: testUrl,\n user_email: userEmail,\n }),\n })\n .then((response) => response.json())\n .then((data) => {\n debugLog('Successfully uploaded test info to server:', data);\n })\n .catch((error) =>\n debugLog('Failed to upload test info to server:', error),\n );\n lastReportedRepoUrl = repoUrl;\n }\n}\n"]}
|
|
@@ -47,6 +47,14 @@ var _constants = require('@midscene/shared/constants');
|
|
|
47
47
|
var _extractor = require('@midscene/shared/extractor');
|
|
48
48
|
var _img = require('@midscene/shared/img');
|
|
49
49
|
|
|
50
|
+
var AIActionType = /* @__PURE__ */ ((AIActionType2) => {
|
|
51
|
+
AIActionType2[AIActionType2["ASSERT"] = 0] = "ASSERT";
|
|
52
|
+
AIActionType2[AIActionType2["INSPECT_ELEMENT"] = 1] = "INSPECT_ELEMENT";
|
|
53
|
+
AIActionType2[AIActionType2["EXTRACT_DATA"] = 2] = "EXTRACT_DATA";
|
|
54
|
+
AIActionType2[AIActionType2["PLAN"] = 3] = "PLAN";
|
|
55
|
+
AIActionType2[AIActionType2["DESCRIBE_ELEMENT"] = 4] = "DESCRIBE_ELEMENT";
|
|
56
|
+
return AIActionType2;
|
|
57
|
+
})(AIActionType || {});
|
|
50
58
|
async function callAiFn(msgs, AIActionTypeValue) {
|
|
51
59
|
_utils.assert.call(void 0,
|
|
52
60
|
checkAIConfig(),
|
|
@@ -694,6 +702,11 @@ async function describeUserPage(context, opt) {
|
|
|
694
702
|
const treeRoot = context.tree;
|
|
695
703
|
const idElementMap = {};
|
|
696
704
|
const flatElements = _extractor.treeToList.call(void 0, treeRoot);
|
|
705
|
+
if (_optionalChain([opt, 'optionalAccess', _15 => _15.domIncluded]) === true && flatElements.length >= 5e3) {
|
|
706
|
+
console.warn(
|
|
707
|
+
'The number of elements is too large, it may cause the prompt to be too long, please use domIncluded: "visible-only" to reduce the number of elements'
|
|
708
|
+
);
|
|
709
|
+
}
|
|
697
710
|
flatElements.forEach((element) => {
|
|
698
711
|
idElementMap[element.id] = element;
|
|
699
712
|
if (typeof element.indexId !== "undefined") {
|
|
@@ -701,12 +714,13 @@ async function describeUserPage(context, opt) {
|
|
|
701
714
|
}
|
|
702
715
|
});
|
|
703
716
|
let pageDescription = "";
|
|
704
|
-
|
|
717
|
+
const visibleOnly = _nullishCoalesce(_optionalChain([opt, 'optionalAccess', _16 => _16.visibleOnly]), () => ( _optionalChain([opt, 'optionalAccess', _17 => _17.domIncluded]) === "visible-only"));
|
|
718
|
+
if (_optionalChain([opt, 'optionalAccess', _18 => _18.domIncluded])) {
|
|
705
719
|
const contentTree = await _extractor.descriptionOfTree.call(void 0,
|
|
706
720
|
treeRoot,
|
|
707
|
-
_optionalChain([opt, 'optionalAccess',
|
|
708
|
-
_optionalChain([opt, 'optionalAccess',
|
|
709
|
-
|
|
721
|
+
_optionalChain([opt, 'optionalAccess', _19 => _19.truncateTextLength]),
|
|
722
|
+
_optionalChain([opt, 'optionalAccess', _20 => _20.filterNonTextContent]),
|
|
723
|
+
visibleOnly
|
|
710
724
|
);
|
|
711
725
|
const sizeDescription = describeSize({ width, height });
|
|
712
726
|
pageDescription = `The size of the page: ${sizeDescription}
|
|
@@ -1239,7 +1253,7 @@ Please check your config.`
|
|
|
1239
1253
|
httpAgent: proxyAgent,
|
|
1240
1254
|
...extraConfig,
|
|
1241
1255
|
defaultHeaders: {
|
|
1242
|
-
..._optionalChain([extraConfig, 'optionalAccess',
|
|
1256
|
+
..._optionalChain([extraConfig, 'optionalAccess', _21 => _21.defaultHeaders]) || {},
|
|
1243
1257
|
[_env.MIDSCENE_API_TYPE]: AIActionTypeValue.toString()
|
|
1244
1258
|
},
|
|
1245
1259
|
dangerouslyAllowBrowser: true
|
|
@@ -1288,6 +1302,7 @@ async function call(messages, AIActionTypeValue, responseFormat) {
|
|
|
1288
1302
|
const model = getModelName();
|
|
1289
1303
|
let content;
|
|
1290
1304
|
let usage;
|
|
1305
|
+
let timeCost;
|
|
1291
1306
|
const commonConfig = {
|
|
1292
1307
|
temperature: _env.vlLocateMode.call(void 0, ) === "vlm-ui-tars" ? 0 : 0.1,
|
|
1293
1308
|
stream: false,
|
|
@@ -1300,12 +1315,14 @@ async function call(messages, AIActionTypeValue, responseFormat) {
|
|
|
1300
1315
|
debugCall(`sending request to ${model}`);
|
|
1301
1316
|
let result;
|
|
1302
1317
|
try {
|
|
1318
|
+
const startTime2 = Date.now();
|
|
1303
1319
|
result = await completion.create({
|
|
1304
1320
|
model,
|
|
1305
1321
|
messages,
|
|
1306
1322
|
response_format: responseFormat,
|
|
1307
1323
|
...commonConfig
|
|
1308
1324
|
});
|
|
1325
|
+
timeCost = Date.now() - startTime2;
|
|
1309
1326
|
} catch (e) {
|
|
1310
1327
|
const newError = new Error(
|
|
1311
1328
|
`failed to call AI model service: ${e.message}. Trouble shooting: https://midscenejs.com/model-provider.html`,
|
|
@@ -1316,7 +1333,7 @@ async function call(messages, AIActionTypeValue, responseFormat) {
|
|
|
1316
1333
|
throw newError;
|
|
1317
1334
|
}
|
|
1318
1335
|
debugProfileStats(
|
|
1319
|
-
`model, ${model}, mode, ${_env.vlLocateMode.call(void 0, ) || "default"}, ui-tars-version, ${_env.uiTarsModelVersion.call(void 0, )}, prompt-tokens, ${_optionalChain([result, 'access',
|
|
1336
|
+
`model, ${model}, mode, ${_env.vlLocateMode.call(void 0, ) || "default"}, ui-tars-version, ${_env.uiTarsModelVersion.call(void 0, )}, prompt-tokens, ${_optionalChain([result, 'access', _22 => _22.usage, 'optionalAccess', _23 => _23.prompt_tokens]) || ""}, completion-tokens, ${_optionalChain([result, 'access', _24 => _24.usage, 'optionalAccess', _25 => _25.completion_tokens]) || ""}, total-tokens, ${_optionalChain([result, 'access', _26 => _26.usage, 'optionalAccess', _27 => _27.total_tokens]) || ""}, cost-ms, ${Date.now() - startTime}, requestId, ${result._request_id || ""}`
|
|
1320
1337
|
);
|
|
1321
1338
|
debugProfileDetail(`model usage detail: ${JSON.stringify(result.usage)}`);
|
|
1322
1339
|
_utils.assert.call(void 0,
|
|
@@ -1343,6 +1360,7 @@ async function call(messages, AIActionTypeValue, responseFormat) {
|
|
|
1343
1360
|
}
|
|
1344
1361
|
return content2;
|
|
1345
1362
|
};
|
|
1363
|
+
const startTime2 = Date.now();
|
|
1346
1364
|
const result = await completion.create({
|
|
1347
1365
|
model,
|
|
1348
1366
|
system: "You are a versatile professional in software UI automation",
|
|
@@ -1353,11 +1371,20 @@ async function call(messages, AIActionTypeValue, responseFormat) {
|
|
|
1353
1371
|
response_format: responseFormat,
|
|
1354
1372
|
...commonConfig
|
|
1355
1373
|
});
|
|
1374
|
+
timeCost = Date.now() - startTime2;
|
|
1356
1375
|
content = result.content[0].text;
|
|
1357
1376
|
_utils.assert.call(void 0, content, "empty content");
|
|
1358
1377
|
usage = result.usage;
|
|
1359
1378
|
}
|
|
1360
|
-
return {
|
|
1379
|
+
return {
|
|
1380
|
+
content: content || "",
|
|
1381
|
+
usage: {
|
|
1382
|
+
prompt_tokens: _nullishCoalesce(_optionalChain([usage, 'optionalAccess', _28 => _28.prompt_tokens]), () => ( 0)),
|
|
1383
|
+
completion_tokens: _nullishCoalesce(_optionalChain([usage, 'optionalAccess', _29 => _29.completion_tokens]), () => ( 0)),
|
|
1384
|
+
total_tokens: _nullishCoalesce(_optionalChain([usage, 'optionalAccess', _30 => _30.total_tokens]), () => ( 0)),
|
|
1385
|
+
time_cost: _nullishCoalesce(timeCost, () => ( 0))
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1361
1388
|
}
|
|
1362
1389
|
async function callToGetJSONObject(messages, AIActionTypeValue) {
|
|
1363
1390
|
let responseFormat;
|
|
@@ -1417,8 +1444,8 @@ function preprocessDoubaoBboxJson(input) {
|
|
|
1417
1444
|
}
|
|
1418
1445
|
function safeParseJson(input) {
|
|
1419
1446
|
const cleanJsonString = extractJSONFromCodeBlock(input);
|
|
1420
|
-
if (_optionalChain([cleanJsonString, 'optionalAccess',
|
|
1421
|
-
return _optionalChain([cleanJsonString, 'access',
|
|
1447
|
+
if (_optionalChain([cleanJsonString, 'optionalAccess', _31 => _31.match, 'call', _32 => _32(/\((\d+),(\d+)\)/)])) {
|
|
1448
|
+
return _optionalChain([cleanJsonString, 'access', _33 => _33.match, 'call', _34 => _34(/\((\d+),(\d+)\)/), 'optionalAccess', _35 => _35.slice, 'call', _36 => _36(1), 'access', _37 => _37.map, 'call', _38 => _38(Number)]);
|
|
1422
1449
|
}
|
|
1423
1450
|
try {
|
|
1424
1451
|
return JSON.parse(cleanJsonString);
|
|
@@ -1435,6 +1462,325 @@ function safeParseJson(input) {
|
|
|
1435
1462
|
throw Error(`failed to parse json response: ${input}`);
|
|
1436
1463
|
}
|
|
1437
1464
|
|
|
1465
|
+
// src/ai-model/prompt/playwright-generator.ts
|
|
1466
|
+
|
|
1467
|
+
var getScreenshotsForLLM = (events, maxScreenshots = 1) => {
|
|
1468
|
+
const eventsWithScreenshots = events.filter(
|
|
1469
|
+
(event) => event.screenshotBefore || event.screenshotAfter || event.screenshotWithBox
|
|
1470
|
+
);
|
|
1471
|
+
const sortedEvents = [...eventsWithScreenshots].sort((a, b) => {
|
|
1472
|
+
if (a.type === "navigation" && b.type !== "navigation")
|
|
1473
|
+
return -1;
|
|
1474
|
+
if (a.type !== "navigation" && b.type === "navigation")
|
|
1475
|
+
return 1;
|
|
1476
|
+
if (a.type === "click" && b.type !== "click")
|
|
1477
|
+
return -1;
|
|
1478
|
+
if (a.type !== "click" && b.type === "click")
|
|
1479
|
+
return 1;
|
|
1480
|
+
return 0;
|
|
1481
|
+
});
|
|
1482
|
+
const screenshots = [];
|
|
1483
|
+
for (const event of sortedEvents) {
|
|
1484
|
+
const screenshot = event.screenshotWithBox || event.screenshotAfter || event.screenshotBefore;
|
|
1485
|
+
if (screenshot && !screenshots.includes(screenshot)) {
|
|
1486
|
+
screenshots.push(screenshot);
|
|
1487
|
+
if (screenshots.length >= maxScreenshots)
|
|
1488
|
+
break;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return screenshots;
|
|
1492
|
+
};
|
|
1493
|
+
var filterEventsByType = (events) => {
|
|
1494
|
+
return {
|
|
1495
|
+
navigationEvents: events.filter((event) => event.type === "navigation"),
|
|
1496
|
+
clickEvents: events.filter((event) => event.type === "click"),
|
|
1497
|
+
inputEvents: events.filter((event) => event.type === "input"),
|
|
1498
|
+
scrollEvents: events.filter((event) => event.type === "scroll")
|
|
1499
|
+
};
|
|
1500
|
+
};
|
|
1501
|
+
var createEventCounts = (filteredEvents, totalEvents) => {
|
|
1502
|
+
return {
|
|
1503
|
+
navigation: filteredEvents.navigationEvents.length,
|
|
1504
|
+
click: filteredEvents.clickEvents.length,
|
|
1505
|
+
input: filteredEvents.inputEvents.length,
|
|
1506
|
+
scroll: filteredEvents.scrollEvents.length,
|
|
1507
|
+
total: totalEvents
|
|
1508
|
+
};
|
|
1509
|
+
};
|
|
1510
|
+
var extractInputDescriptions = (inputEvents) => {
|
|
1511
|
+
return inputEvents.map((event) => ({
|
|
1512
|
+
description: event.elementDescription || "",
|
|
1513
|
+
value: event.value || ""
|
|
1514
|
+
})).filter((item) => item.description && item.value);
|
|
1515
|
+
};
|
|
1516
|
+
var processEventsForLLM = (events) => {
|
|
1517
|
+
return events.map((event) => ({
|
|
1518
|
+
type: event.type,
|
|
1519
|
+
timestamp: event.timestamp,
|
|
1520
|
+
url: event.url,
|
|
1521
|
+
title: event.title,
|
|
1522
|
+
elementDescription: event.elementDescription,
|
|
1523
|
+
value: event.value,
|
|
1524
|
+
pageInfo: event.pageInfo,
|
|
1525
|
+
elementRect: event.elementRect
|
|
1526
|
+
}));
|
|
1527
|
+
};
|
|
1528
|
+
var prepareEventSummary = (events, options = {}) => {
|
|
1529
|
+
const filteredEvents = filterEventsByType(events);
|
|
1530
|
+
const eventCounts = createEventCounts(filteredEvents, events.length);
|
|
1531
|
+
const startUrl = filteredEvents.navigationEvents.length > 0 ? filteredEvents.navigationEvents[0].url || "" : "";
|
|
1532
|
+
const pageTitles = filteredEvents.navigationEvents.map((event) => event.title).filter((title) => Boolean(title)).slice(0, 5);
|
|
1533
|
+
const clickDescriptions = filteredEvents.clickEvents.map((event) => event.elementDescription).filter((desc) => Boolean(desc)).slice(0, 10);
|
|
1534
|
+
const inputDescriptions = extractInputDescriptions(
|
|
1535
|
+
filteredEvents.inputEvents
|
|
1536
|
+
).slice(0, 10);
|
|
1537
|
+
const urls = filteredEvents.navigationEvents.map((e) => e.url).filter((url) => Boolean(url)).slice(0, 5);
|
|
1538
|
+
const processedEvents = processEventsForLLM(events);
|
|
1539
|
+
return {
|
|
1540
|
+
testName: options.testName || "Automated test from recorded events",
|
|
1541
|
+
startUrl,
|
|
1542
|
+
eventCounts,
|
|
1543
|
+
pageTitles,
|
|
1544
|
+
urls,
|
|
1545
|
+
clickDescriptions,
|
|
1546
|
+
inputDescriptions,
|
|
1547
|
+
events: processedEvents
|
|
1548
|
+
};
|
|
1549
|
+
};
|
|
1550
|
+
var createMessageContent = (promptText, screenshots = [], includeScreenshots = true) => {
|
|
1551
|
+
const messageContent = [
|
|
1552
|
+
{
|
|
1553
|
+
type: "text",
|
|
1554
|
+
text: promptText
|
|
1555
|
+
}
|
|
1556
|
+
];
|
|
1557
|
+
if (includeScreenshots && screenshots.length > 0) {
|
|
1558
|
+
messageContent.unshift({
|
|
1559
|
+
type: "text",
|
|
1560
|
+
text: "Here are screenshots from the recording session to help you understand the context:"
|
|
1561
|
+
});
|
|
1562
|
+
screenshots.forEach((screenshot) => {
|
|
1563
|
+
messageContent.push({
|
|
1564
|
+
type: "image_url",
|
|
1565
|
+
image_url: {
|
|
1566
|
+
url: screenshot
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
return messageContent;
|
|
1572
|
+
};
|
|
1573
|
+
var validateEvents = (events) => {
|
|
1574
|
+
if (!events.length) {
|
|
1575
|
+
throw new Error("No events provided for test generation");
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
var generatePlaywrightTest = async (events, options = {}) => {
|
|
1579
|
+
validateEvents(events);
|
|
1580
|
+
const summary = prepareEventSummary(events, {
|
|
1581
|
+
testName: options.testName,
|
|
1582
|
+
maxScreenshots: options.maxScreenshots || 3
|
|
1583
|
+
});
|
|
1584
|
+
const playwrightSummary = {
|
|
1585
|
+
...summary,
|
|
1586
|
+
waitForNetworkIdle: options.waitForNetworkIdle !== false,
|
|
1587
|
+
waitForNetworkIdleTimeout: options.waitForNetworkIdleTimeout || 2e3,
|
|
1588
|
+
viewportSize: options.viewportSize || { width: 1280, height: 800 }
|
|
1589
|
+
};
|
|
1590
|
+
const screenshots = getScreenshotsForLLM(events, options.maxScreenshots || 3);
|
|
1591
|
+
const promptText = `Generate a Playwright test using @midscene/web/playwright that reproduces this recorded browser session. The test should be based on the following events and follow the structure of the example provided. Make the test descriptive with appropriate assertions and validations.
|
|
1592
|
+
|
|
1593
|
+
Event Summary:
|
|
1594
|
+
${JSON.stringify(playwrightSummary, null, 2)}
|
|
1595
|
+
|
|
1596
|
+
Generated code should:
|
|
1597
|
+
1. Import required dependencies
|
|
1598
|
+
2. Set up the test with proper configuration
|
|
1599
|
+
3. Include a beforeEach hook to navigate to the starting URL
|
|
1600
|
+
4. Implement a test that uses Midscene AI methods (aiTap, aiInput, aiAssert, etc.)
|
|
1601
|
+
5. Include appropriate assertions and validations
|
|
1602
|
+
6. Follow best practices for Playwright tests
|
|
1603
|
+
7. Be ready to execute without further modification
|
|
1604
|
+
|
|
1605
|
+
Respond ONLY with the complete Playwright test code, no explanations.`;
|
|
1606
|
+
const messageContent = createMessageContent(
|
|
1607
|
+
promptText,
|
|
1608
|
+
screenshots,
|
|
1609
|
+
options.includeScreenshots !== false
|
|
1610
|
+
);
|
|
1611
|
+
const systemPrompt = `You are an expert test automation engineer specializing in Playwright and Midscene.
|
|
1612
|
+
Your task is to generate a complete, executable Playwright test using @midscene/web/playwright that reproduces a recorded browser session.
|
|
1613
|
+
|
|
1614
|
+
${_constants.PLAYWRIGHT_EXAMPLE_CODE}`;
|
|
1615
|
+
const prompt = [
|
|
1616
|
+
{
|
|
1617
|
+
role: "system",
|
|
1618
|
+
content: systemPrompt
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
role: "user",
|
|
1622
|
+
content: messageContent
|
|
1623
|
+
}
|
|
1624
|
+
];
|
|
1625
|
+
const response = await call(prompt, 2 /* EXTRACT_DATA */);
|
|
1626
|
+
if (_optionalChain([response, 'optionalAccess', _39 => _39.content]) && typeof response.content === "string") {
|
|
1627
|
+
return response.content;
|
|
1628
|
+
}
|
|
1629
|
+
throw new Error("Failed to generate Playwright test code");
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
// src/ai-model/prompt/yaml-generator.ts
|
|
1633
|
+
|
|
1634
|
+
var getScreenshotsForLLM2 = (events, maxScreenshots = 1) => {
|
|
1635
|
+
const eventsWithScreenshots = events.filter(
|
|
1636
|
+
(event) => event.screenshotBefore || event.screenshotAfter || event.screenshotWithBox
|
|
1637
|
+
);
|
|
1638
|
+
const sortedEvents = [...eventsWithScreenshots].sort((a, b) => {
|
|
1639
|
+
if (a.type === "navigation" && b.type !== "navigation")
|
|
1640
|
+
return -1;
|
|
1641
|
+
if (a.type !== "navigation" && b.type === "navigation")
|
|
1642
|
+
return 1;
|
|
1643
|
+
if (a.type === "click" && b.type !== "click")
|
|
1644
|
+
return -1;
|
|
1645
|
+
if (a.type !== "click" && b.type === "click")
|
|
1646
|
+
return 1;
|
|
1647
|
+
return 0;
|
|
1648
|
+
});
|
|
1649
|
+
const screenshots = [];
|
|
1650
|
+
for (const event of sortedEvents) {
|
|
1651
|
+
const screenshot = event.screenshotWithBox || event.screenshotAfter || event.screenshotBefore;
|
|
1652
|
+
if (screenshot && !screenshots.includes(screenshot)) {
|
|
1653
|
+
screenshots.push(screenshot);
|
|
1654
|
+
if (screenshots.length >= maxScreenshots)
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return screenshots;
|
|
1659
|
+
};
|
|
1660
|
+
var filterEventsByType2 = (events) => {
|
|
1661
|
+
return {
|
|
1662
|
+
navigationEvents: events.filter((event) => event.type === "navigation"),
|
|
1663
|
+
clickEvents: events.filter((event) => event.type === "click"),
|
|
1664
|
+
inputEvents: events.filter((event) => event.type === "input"),
|
|
1665
|
+
scrollEvents: events.filter((event) => event.type === "scroll")
|
|
1666
|
+
};
|
|
1667
|
+
};
|
|
1668
|
+
var createEventCounts2 = (filteredEvents, totalEvents) => {
|
|
1669
|
+
return {
|
|
1670
|
+
navigation: filteredEvents.navigationEvents.length,
|
|
1671
|
+
click: filteredEvents.clickEvents.length,
|
|
1672
|
+
input: filteredEvents.inputEvents.length,
|
|
1673
|
+
scroll: filteredEvents.scrollEvents.length,
|
|
1674
|
+
total: totalEvents
|
|
1675
|
+
};
|
|
1676
|
+
};
|
|
1677
|
+
var extractInputDescriptions2 = (inputEvents) => {
|
|
1678
|
+
return inputEvents.map((event) => ({
|
|
1679
|
+
description: event.elementDescription || "",
|
|
1680
|
+
value: event.value || ""
|
|
1681
|
+
})).filter((item) => item.description && item.value);
|
|
1682
|
+
};
|
|
1683
|
+
var processEventsForLLM2 = (events) => {
|
|
1684
|
+
return events.map((event) => ({
|
|
1685
|
+
type: event.type,
|
|
1686
|
+
timestamp: event.timestamp,
|
|
1687
|
+
url: event.url,
|
|
1688
|
+
title: event.title,
|
|
1689
|
+
elementDescription: event.elementDescription,
|
|
1690
|
+
value: event.value,
|
|
1691
|
+
pageInfo: event.pageInfo,
|
|
1692
|
+
elementRect: event.elementRect
|
|
1693
|
+
}));
|
|
1694
|
+
};
|
|
1695
|
+
var prepareEventSummary2 = (events, options = {}) => {
|
|
1696
|
+
const filteredEvents = filterEventsByType2(events);
|
|
1697
|
+
const eventCounts = createEventCounts2(filteredEvents, events.length);
|
|
1698
|
+
const startUrl = filteredEvents.navigationEvents.length > 0 ? filteredEvents.navigationEvents[0].url || "" : "";
|
|
1699
|
+
const pageTitles = filteredEvents.navigationEvents.map((event) => event.title).filter((title) => Boolean(title)).slice(0, 5);
|
|
1700
|
+
const clickDescriptions = filteredEvents.clickEvents.map((event) => event.elementDescription).filter((desc) => Boolean(desc)).slice(0, 10);
|
|
1701
|
+
const inputDescriptions = extractInputDescriptions2(
|
|
1702
|
+
filteredEvents.inputEvents
|
|
1703
|
+
).slice(0, 10);
|
|
1704
|
+
const urls = filteredEvents.navigationEvents.map((e) => e.url).filter((url) => Boolean(url)).slice(0, 5);
|
|
1705
|
+
const processedEvents = processEventsForLLM2(events);
|
|
1706
|
+
return {
|
|
1707
|
+
testName: options.testName || "Automated test from recorded events",
|
|
1708
|
+
startUrl,
|
|
1709
|
+
eventCounts,
|
|
1710
|
+
pageTitles,
|
|
1711
|
+
urls,
|
|
1712
|
+
clickDescriptions,
|
|
1713
|
+
inputDescriptions,
|
|
1714
|
+
events: processedEvents
|
|
1715
|
+
};
|
|
1716
|
+
};
|
|
1717
|
+
var validateEvents2 = (events) => {
|
|
1718
|
+
if (!events.length) {
|
|
1719
|
+
throw new Error("No events provided for test generation");
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
var generateYamlTest = async (events, options = {}) => {
|
|
1723
|
+
try {
|
|
1724
|
+
validateEvents2(events);
|
|
1725
|
+
const summary = prepareEventSummary2(events, {
|
|
1726
|
+
testName: options.testName,
|
|
1727
|
+
maxScreenshots: options.maxScreenshots || 3
|
|
1728
|
+
});
|
|
1729
|
+
const yamlSummary = {
|
|
1730
|
+
...summary,
|
|
1731
|
+
includeTimestamps: options.includeTimestamps || false
|
|
1732
|
+
};
|
|
1733
|
+
const screenshots = getScreenshotsForLLM2(
|
|
1734
|
+
events,
|
|
1735
|
+
options.maxScreenshots || 3
|
|
1736
|
+
);
|
|
1737
|
+
const prompt = [
|
|
1738
|
+
{
|
|
1739
|
+
role: "system",
|
|
1740
|
+
content: `You are an expert in Midscene.js YAML test generation. Generate clean, accurate YAML following these rules: ${_constants.YAML_EXAMPLE_CODE}`
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
role: "user",
|
|
1744
|
+
content: `Generate YAML test for Midscene.js automation from recorded browser events.
|
|
1745
|
+
|
|
1746
|
+
Event Summary:
|
|
1747
|
+
${JSON.stringify(yamlSummary, null, 2)}
|
|
1748
|
+
|
|
1749
|
+
Convert events:
|
|
1750
|
+
- navigation → target.url
|
|
1751
|
+
- click → aiTap with element description
|
|
1752
|
+
- input → aiInput with value and locate
|
|
1753
|
+
- scroll → aiScroll with appropriate direction
|
|
1754
|
+
- Add aiAssert for important state changes
|
|
1755
|
+
|
|
1756
|
+
Respond with YAML only, no explanations.`
|
|
1757
|
+
}
|
|
1758
|
+
];
|
|
1759
|
+
if (screenshots.length > 0) {
|
|
1760
|
+
prompt.push({
|
|
1761
|
+
role: "user",
|
|
1762
|
+
content: "Here are screenshots from the recording session to help you understand the context:"
|
|
1763
|
+
});
|
|
1764
|
+
prompt.push({
|
|
1765
|
+
role: "user",
|
|
1766
|
+
content: screenshots.map((screenshot) => ({
|
|
1767
|
+
type: "image_url",
|
|
1768
|
+
image_url: {
|
|
1769
|
+
url: screenshot
|
|
1770
|
+
}
|
|
1771
|
+
}))
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
const response = await call(prompt, 2 /* EXTRACT_DATA */);
|
|
1775
|
+
if (_optionalChain([response, 'optionalAccess', _40 => _40.content]) && typeof response.content === "string") {
|
|
1776
|
+
return response.content;
|
|
1777
|
+
}
|
|
1778
|
+
throw new Error("Failed to generate YAML test configuration");
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
throw new Error(`Failed to generate YAML test: ${error}`);
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1438
1784
|
// src/ai-model/inspect.ts
|
|
1439
1785
|
|
|
1440
1786
|
|
|
@@ -1630,7 +1976,7 @@ async function AiLocateElement(options) {
|
|
|
1630
1976
|
);
|
|
1631
1977
|
}
|
|
1632
1978
|
let referenceImagePayload;
|
|
1633
|
-
if (_optionalChain([options, 'access',
|
|
1979
|
+
if (_optionalChain([options, 'access', _41 => _41.referenceImage, 'optionalAccess', _42 => _42.rect]) && options.referenceImage.base64) {
|
|
1634
1980
|
referenceImagePayload = await _img.cropByRect.call(void 0,
|
|
1635
1981
|
options.referenceImage.base64,
|
|
1636
1982
|
options.referenceImage.rect,
|
|
@@ -1666,10 +2012,10 @@ async function AiLocateElement(options) {
|
|
|
1666
2012
|
if ("bbox" in res.content && Array.isArray(res.content.bbox)) {
|
|
1667
2013
|
resRect = adaptBboxToRect(
|
|
1668
2014
|
res.content.bbox,
|
|
1669
|
-
_optionalChain([options, 'access',
|
|
1670
|
-
_optionalChain([options, 'access',
|
|
1671
|
-
_optionalChain([options, 'access',
|
|
1672
|
-
_optionalChain([options, 'access',
|
|
2015
|
+
_optionalChain([options, 'access', _43 => _43.searchConfig, 'optionalAccess', _44 => _44.rect, 'optionalAccess', _45 => _45.width]) || context.size.width,
|
|
2016
|
+
_optionalChain([options, 'access', _46 => _46.searchConfig, 'optionalAccess', _47 => _47.rect, 'optionalAccess', _48 => _48.height]) || context.size.height,
|
|
2017
|
+
_optionalChain([options, 'access', _49 => _49.searchConfig, 'optionalAccess', _50 => _50.rect, 'optionalAccess', _51 => _51.left]),
|
|
2018
|
+
_optionalChain([options, 'access', _52 => _52.searchConfig, 'optionalAccess', _53 => _53.rect, 'optionalAccess', _54 => _54.top])
|
|
1673
2019
|
);
|
|
1674
2020
|
debugInspect("resRect", resRect);
|
|
1675
2021
|
const rectCenter = {
|
|
@@ -1688,7 +2034,7 @@ async function AiLocateElement(options) {
|
|
|
1688
2034
|
}
|
|
1689
2035
|
} catch (e) {
|
|
1690
2036
|
const msg = e instanceof Error ? `Failed to parse bbox: ${e.message}` : "unknown error in locate";
|
|
1691
|
-
if (!errors || _optionalChain([errors, 'optionalAccess',
|
|
2037
|
+
if (!errors || _optionalChain([errors, 'optionalAccess', _55 => _55.length]) === 0) {
|
|
1692
2038
|
errors = [msg];
|
|
1693
2039
|
} else {
|
|
1694
2040
|
errors.push(`(${msg})`);
|
|
@@ -1779,14 +2125,14 @@ async function AiExtractElementInfo(options) {
|
|
|
1779
2125
|
truncateTextLength: 200,
|
|
1780
2126
|
filterNonTextContent: false,
|
|
1781
2127
|
visibleOnly: false,
|
|
1782
|
-
domIncluded: _optionalChain([extractOption, 'optionalAccess',
|
|
2128
|
+
domIncluded: _optionalChain([extractOption, 'optionalAccess', _56 => _56.domIncluded])
|
|
1783
2129
|
});
|
|
1784
2130
|
const extractDataPromptText = await extractDataQueryPrompt(
|
|
1785
2131
|
description,
|
|
1786
2132
|
dataQuery
|
|
1787
2133
|
);
|
|
1788
2134
|
const userContent = [];
|
|
1789
|
-
if (_optionalChain([extractOption, 'optionalAccess',
|
|
2135
|
+
if (_optionalChain([extractOption, 'optionalAccess', _57 => _57.screenshotIncluded]) !== false) {
|
|
1790
2136
|
userContent.push({
|
|
1791
2137
|
type: "image_url",
|
|
1792
2138
|
image_url: {
|
|
@@ -1914,7 +2260,7 @@ async function plan(userInstruction, opts) {
|
|
|
1914
2260
|
const { content, usage } = await call2(msgs, 3 /* PLAN */);
|
|
1915
2261
|
const rawResponse = JSON.stringify(content, void 0, 2);
|
|
1916
2262
|
const planFromAI = content;
|
|
1917
|
-
const actions = (_optionalChain([planFromAI, 'access',
|
|
2263
|
+
const actions = (_optionalChain([planFromAI, 'access', _58 => _58.action, 'optionalAccess', _59 => _59.type]) ? [planFromAI.action] : planFromAI.actions) || [];
|
|
1918
2264
|
const returnValue = {
|
|
1919
2265
|
...planFromAI,
|
|
1920
2266
|
actions,
|
|
@@ -1941,7 +2287,7 @@ async function plan(userInstruction, opts) {
|
|
|
1941
2287
|
_utils.assert.call(void 0, !planFromAI.error, `Failed to plan actions: ${planFromAI.error}`);
|
|
1942
2288
|
} else {
|
|
1943
2289
|
actions.forEach((action) => {
|
|
1944
|
-
if (_optionalChain([action, 'access',
|
|
2290
|
+
if (_optionalChain([action, 'access', _60 => _60.locate, 'optionalAccess', _61 => _61.id])) {
|
|
1945
2291
|
const element = elementById(action.locate.id);
|
|
1946
2292
|
if (element) {
|
|
1947
2293
|
action.locate.id = element.id;
|
|
@@ -2226,6 +2572,10 @@ async function resizeImageForUiTars(imageBase64, size) {
|
|
|
2226
2572
|
|
|
2227
2573
|
|
|
2228
2574
|
|
|
2229
|
-
exports.systemPromptToLocateElement = systemPromptToLocateElement; exports.elementByPositionWithElementInfo = elementByPositionWithElementInfo; exports.describeUserPage = describeUserPage; exports.callToGetJSONObject = callToGetJSONObject; exports.callAiFn = callAiFn; exports.adaptBboxToRect = adaptBboxToRect; exports.expandSearchArea = expandSearchArea; exports.AiLocateElement = AiLocateElement; exports.AiLocateSection = AiLocateSection; exports.AiExtractElementInfo = AiExtractElementInfo; exports.AiAssert = AiAssert; exports.plan = plan; exports.vlmPlanning = vlmPlanning; exports.resizeImageForUiTars = resizeImageForUiTars;
|
|
2230
2575
|
|
|
2231
|
-
|
|
2576
|
+
|
|
2577
|
+
|
|
2578
|
+
|
|
2579
|
+
exports.systemPromptToLocateElement = systemPromptToLocateElement; exports.elementByPositionWithElementInfo = elementByPositionWithElementInfo; exports.describeUserPage = describeUserPage; exports.call = call; exports.callToGetJSONObject = callToGetJSONObject; exports.AIActionType = AIActionType; exports.callAiFn = callAiFn; exports.adaptBboxToRect = adaptBboxToRect; exports.expandSearchArea = expandSearchArea; exports.generatePlaywrightTest = generatePlaywrightTest; exports.generateYamlTest = generateYamlTest; exports.AiLocateElement = AiLocateElement; exports.AiLocateSection = AiLocateSection; exports.AiExtractElementInfo = AiExtractElementInfo; exports.AiAssert = AiAssert; exports.plan = plan; exports.vlmPlanning = vlmPlanning; exports.resizeImageForUiTars = resizeImageForUiTars;
|
|
2580
|
+
|
|
2581
|
+
//# sourceMappingURL=chunk-EYIL4AHP.js.map
|