fix: planning prompt (#448)

* feat: add more case for llm planning

* fix: ai e2e

* chore: use debug to print log

* chore: fix error in gpt mode
This commit is contained in:
yuyutaotao 2025-03-10 16:50:43 +08:00 committed by GitHub
parent 3218111c26
commit 212e4e3725
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 359 additions and 149 deletions

View File

@ -36,8 +36,13 @@ Some advanced configs are also supported. Usually you don't need to use them.
| `MIDSCENE_OPENAI_INIT_CONFIG_JSON` | Optional. Custom JSON config for OpenAI SDK initialization |
| `MIDSCENE_OPENAI_SOCKS_PROXY` | Optional. Proxy configuration (e.g. "socks5://127.0.0.1:1080") |
| `OPENAI_MAX_TOKENS` | Optional. Maximum tokens for model response |
| `MIDSCENE_DEBUG_AI_PROFILE` | Optional. Set to "1" to print the AI usage and response time |
| `MIDSCENE_DEBUG_AI_RESPONSE` | Optional. Set to "1" to print the AI response content |
There are some debug configs:
| Name | Description |
|------|-------------|
| `DEBUG=midscene:ai:profile` | Optional. Set this to print the AI usage and response time |
| `DEBUG=midscene:ai:response` | Optional. Set this to print the AI response content |
## Two ways to config environment variables

View File

@ -38,8 +38,13 @@ Midscene 默认集成了 OpenAI SDK 调用 AI 服务。使用这个 SDK 限定
| `MIDSCENE_OPENAI_INIT_CONFIG_JSON` | 可选。OpenAI SDK 的初始化配置 JSON |
| `MIDSCENE_OPENAI_SOCKS_PROXY` | 可选。代理配置 (如 "socks5://127.0.0.1:1080") |
| `OPENAI_MAX_TOKENS` | 可选。模型响应的 max_tokens 数 |
| `MIDSCENE_DEBUG_AI_PROFILE` | 可选。设置为 "1" 以打印 AI token 使用情况和响应时间 |
| `MIDSCENE_DEBUG_AI_RESPONSE` | 可选。设置为 "1" 以打印 AI 响应内容 |
Midscene 还支持一些调试配置:
| 名称 | 描述 |
|------|-------------|
| `DEBUG=midscene:ai:profile` | 可选。设置此项,可以打印 AI token 使用情况和响应时间 |
| `DEBUG=midscene:ai:response` | 可选。设置此项,可以打印 AI 响应内容 |
## 两种配置环境变量的方式

View File

@ -79,17 +79,18 @@ export async function plan(
usage,
};
assert(planFromAI, "can't get plans from AI");
if (getAIConfigInBoolean(MIDSCENE_USE_QWEN_VL)) {
actions.forEach((action) => {
if (action.locate) {
action.locate = fillLocateParam(action.locate);
}
});
// in Qwen-VL, error means error. In GPT-4o, error may mean more actions are needed.
assert(!planFromAI.error, `Failed to plan actions: ${planFromAI.error}`);
}
assert(planFromAI, "can't get plans from AI");
assert(!planFromAI.error, `Failed to plan actions: ${planFromAI.error}`);
if (
actions.length === 0 &&
returnValue.more_actions_needed_by_instruction &&

View File

@ -8,9 +8,9 @@ import type { ResponseFormatJSONSchema } from 'openai/resources';
import { samplePageDescription } from './util';
// Note: put the log field first to trigger the CoT
const commonOutputFields = `"log"?: string, // Log what this action(s) you just planned do. Use the same language as the user's instruction. Omit this field if there is an error and error message is provided.
"more_actions_needed_by_instruction": boolean, // Consider if all the actions described in the instruction have been covered by this action and logs. If so, set this field to false. Otherwise, you must have a clear reason what the remaining actions are.
"error"?: string, // Error messages about unexpected situations, if any. Only think it is an error when the situation is not expected according to the instruction. Use the same language as the user's instruction.`;
const commonOutputFields = `"log"?: string, // Log what this action(s) you just planned do. If no action should be done, log the reason. Use the same language as the user's instruction. Omit this field if there is an error and error message is provided.
"error"?: string, // Error messages about unexpected situations, if any. Only think it is an error when the situation is not expected according to the instruction. Use the same language as the user's instruction.
"more_actions_needed_by_instruction": boolean, // Consider if all the actions described in the instruction have been covered by this action and logs. If so, set this field to false. Otherwise, you must have a clear reason what the remaining actions are.`;
const qwenLocateParam =
'locate: {bbox_2d: [number, number, number, number], prompt: string }';

View File

@ -4,7 +4,7 @@ import {
DefaultAzureCredential,
getBearerTokenProvider,
} from '@azure/identity';
import { assert } from '@midscene/shared/utils';
import { assert, enableDebug, getDebug } from '@midscene/shared/utils';
import { ifInBrowser } from '@midscene/shared/utils';
import dJSON from 'dirty-json';
import OpenAI, { AzureOpenAI } from 'openai';
@ -50,6 +50,24 @@ export function checkAIConfig() {
return Boolean(getAIConfig(MIDSCENE_OPENAI_INIT_CONFIG_JSON));
}
const debugProfile = getDebug('ai:profile');
const debugResponse = getDebug('ai:response');
const shouldPrintTiming = getAIConfigInBoolean(MIDSCENE_DEBUG_AI_PROFILE);
if (shouldPrintTiming) {
console.warn(
'MIDSCENE_DEBUG_AI_PROFILE is deprecated, use DEBUG=midscene:ai:profile instead',
);
enableDebug('ai:profile');
}
const shouldPrintAIResponse = getAIConfigInBoolean(MIDSCENE_DEBUG_AI_RESPONSE);
if (shouldPrintAIResponse) {
console.warn(
'MIDSCENE_DEBUG_AI_RESPONSE is deprecated, use DEBUG=midscene:ai:response instead',
);
enableDebug('ai:response');
}
// default model
const defaultModel = 'gpt-4o';
export function getModelName() {
@ -192,10 +210,6 @@ export async function call(
const { completion, style } = await createChatClient({
AIActionTypeValue,
});
const shouldPrintTiming = getAIConfigInBoolean(MIDSCENE_DEBUG_AI_PROFILE);
const shouldPrintAIResponse = getAIConfigInBoolean(
MIDSCENE_DEBUG_AI_RESPONSE,
);
const maxTokens = getAIConfig(OPENAI_MAX_TOKENS);
@ -223,22 +237,22 @@ export async function call(
response_format: responseFormat,
...commonConfig,
} as any);
shouldPrintTiming &&
console.log(
'Midscene - AI call',
getAIConfig(MIDSCENE_USE_QWEN_VL) ? 'MIDSCENE_USE_QWEN_VL' : '',
model,
result.usage,
`${Date.now() - startTime}ms`,
result._request_id || '',
);
debugProfile(
model,
getAIConfig(MIDSCENE_USE_QWEN_VL) ? 'MIDSCENE_USE_QWEN_VL' : '',
result.usage,
`${Date.now() - startTime}ms`,
result._request_id || '',
);
assert(
result.choices,
`invalid response from LLM service: ${JSON.stringify(result)}`,
);
content = result.choices[0].message.content!;
shouldPrintAIResponse && console.log('Midscene - AI response', content);
debugResponse(content);
assert(content, 'empty content');
usage = result.usage;
// console.log('headers', result.headers);

View File

@ -234,9 +234,9 @@ The JSON format is as follows:
"actions": [
// ... some actions
],
"log"?: string, // Log what this action(s) you just planned do. Use the same language as the user's instruction. Omit this field if there is an error and error message is provided.
"more_actions_needed_by_instruction": boolean, // Consider if all the actions described in the instruction have been covered by this action and logs. If so, set this field to false. Otherwise, you must have a clear reason what the remaining actions are.
"log"?: string, // Log what this action(s) you just planned do. If no action should be done, log the reason. Use the same language as the user's instruction. Omit this field if there is an error and error message is provided.
"error"?: string, // Error messages about unexpected situations, if any. Only think it is an error when the situation is not expected according to the instruction. Use the same language as the user's instruction.
"more_actions_needed_by_instruction": boolean, // Consider if all the actions described in the instruction have been covered by this action and logs. If so, set this field to false. Otherwise, you must have a clear reason what the remaining actions are.
}
## Examples
@ -335,9 +335,9 @@ Field description:
Return in JSON format:
{
"log"?: string, // Log what this action(s) you just planned do. Use the same language as the user's instruction. Omit this field if there is an error and error message is provided.
"more_actions_needed_by_instruction": boolean, // Consider if all the actions described in the instruction have been covered by this action and logs. If so, set this field to false. Otherwise, you must have a clear reason what the remaining actions are.
"log"?: string, // Log what this action(s) you just planned do. If no action should be done, log the reason. Use the same language as the user's instruction. Omit this field if there is an error and error message is provided.
"error"?: string, // Error messages about unexpected situations, if any. Only think it is an error when the situation is not expected according to the instruction. Use the same language as the user's instruction.
"more_actions_needed_by_instruction": boolean, // Consider if all the actions described in the instruction have been covered by this action and logs. If so, set this field to false. Otherwise, you must have a clear reason what the remaining actions are.
"action":
{
// one of the supporting actions

View File

@ -4,37 +4,85 @@
{
"prompt": "在 Email 输入框中输入'test@test.com'",
"response_planning": {
"log": "在 Email 输入框中输入'test@test.com'",
"more_actions_needed_by_instruction": false,
"action": {
"type": "Input",
"locate": {
"prompt": "Email input field",
"bbox": [563, 214, 965, 240]
"prompt": "E-mail 输入框",
"bbox": [564, 213, 964, 240]
},
"param": {
"value": "test@test.com"
}
},
"log": "在 Email 输入框中输入'test@test.com'",
"more_actions_needed_by_instruction": false,
"actions": [
{
"type": "Input",
"locate": {
"prompt": "Email input field",
"bbox": [563, 214, 965, 240]
"prompt": "E-mail 输入框",
"bbox": [564, 213, 964, 240]
},
"param": {
"value": "test@test.com"
}
}
],
"rawResponse": "{\n \"action\": {\n \"type\": \"Input\",\n \"locate\": {\n \"bbox_2d\": [\n 563,\n 214,\n 965,\n 240\n ],\n \"prompt\": \"Email input field\"\n },\n \"param\": {\n \"value\": \"test@test.com\"\n }\n },\n \"log\": \"在 Email 输入框中输入'test@test.com'\",\n \"more_actions_needed_by_instruction\": false\n}",
"rawResponse": "{\n \"log\": \"在 Email 输入框中输入'test@test.com'\",\n \"more_actions_needed_by_instruction\": false,\n \"action\": {\n \"type\": \"Input\",\n \"locate\": {\n \"bbox_2d\": [\n 564,\n 213,\n 964,\n 240\n ],\n \"prompt\": \"E-mail 输入框\"\n },\n \"param\": {\n \"value\": \"test@test.com\"\n }\n }\n}",
"usage": {
"prompt_tokens": 2565,
"completion_tokens": 105,
"total_tokens": 2670
"prompt_tokens": 2612,
"completion_tokens": 107,
"total_tokens": 2719,
"completion_tokens_details": {
"text_tokens": 107
},
"prompt_tokens_details": {
"text_tokens": 660,
"image_tokens": 1952
}
}
}
},
"response_bbox": [564, 213, 964, 240],
"annotation_index_id": 1
},
{
"prompt": "界面已经打开,点击 Gender 下拉框",
"response_planning": {
"log": "点击 Gender 下拉框",
"more_actions_needed_by_instruction": false,
"action": {
"type": "Tap",
"locate": {
"prompt": "Gender 下拉框",
"bbox": [563, 749, 573, 759]
}
},
"sleep": 3000,
"actions": [
{
"type": "Tap",
"locate": {
"prompt": "Gender 下拉框",
"bbox": [563, 749, 573, 759]
}
}
],
"rawResponse": "{\n \"log\": \"点击 Gender 下拉框\",\n \"more_actions_needed_by_instruction\": false,\n \"action\": {\n \"type\": \"Tap\",\n \"locate\": {\n \"bbox_2d\": [\n 563,\n 749\n ],\n \"prompt\": \"Gender 下拉框\"\n }\n },\n \"sleep\": 3000\n}",
"usage": {
"prompt_tokens": 2611,
"completion_tokens": 86,
"total_tokens": 2697,
"completion_tokens_details": {
"text_tokens": 86
},
"prompt_tokens_details": {
"text_tokens": 659,
"image_tokens": 1952
}
}
},
"response_bbox": [563, 749, 573, 759],
"annotation_index_id": 2
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

View File

@ -0,0 +1,44 @@
{
"testDataPath": "todo",
"testCases": [
{
"prompt": "用户要求进入 tooltip 文档页,现在我们已经进入了,需要点击顶部的右上角的版本号下拉框",
"response_planning": {
"log": "点击顶部的右上角的版本号下拉框。",
"more_actions_needed_by_instruction": false,
"action": {
"type": "Tap",
"locate": {
"prompt": "版本号下拉框",
"bbox": [1680, 25, 1773, 42]
}
},
"sleep": 3000,
"actions": [
{
"type": "Tap",
"locate": {
"prompt": "版本号下拉框",
"bbox": [1680, 25, 1773, 42]
}
}
],
"rawResponse": "{\n \"log\": \"点击顶部的右上角的版本号下拉框。\",\n \"more_actions_needed_by_instruction\": false,\n \"action\": {\n \"type\": \"Tap\",\n \"locate\": {\n \"bbox_2d\": [\n 1680,\n 25,\n 1773,\n 42\n ],\n \"prompt\": \"版本号下拉框\"\n }\n },\n \"sleep\": 3000\n}",
"usage": {
"prompt_tokens": 3369,
"completion_tokens": 104,
"total_tokens": 3473,
"completion_tokens_details": {
"text_tokens": 104
},
"prompt_tokens_details": {
"text_tokens": 676,
"image_tokens": 2693
}
}
},
"response_bbox": [1680, 25, 1773, 42],
"annotation_index_id": 1
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

View File

@ -2,10 +2,10 @@
"testDataPath": "aweme-login",
"testCases": [
{
"prompt": "type 'user' in the username input box, type '123456' in the password input box",
"prompt": "type 'user' in the username input box, type '98' in the age input box",
"log": "type 'user' in the username input box",
"response_planning": {
"error": "Failed to plan actions: The current screen does not have a username or password input box. It is showing a verification code login interface."
"error": "Failed to plan actions: The current screenshot does not show a username input box or an age input box. The visible fields are for phone number and verification code, which do not match the instruction requirements."
}
}
]

View File

@ -162,6 +162,14 @@ ${errorMsg ? `Error: ${errorMsg}` : ''}
return resultData;
}
distanceOfTwoBbox(bbox1: number[], bbox2: number[]) {
const centerX1 = (bbox1[0] + bbox1[2]) / 2;
const centerY1 = (bbox1[1] + bbox1[3]) / 2;
const centerX2 = (bbox2[0] + bbox2[2]) / 2;
const centerY2 = (bbox2[1] + bbox2[3]) / 2;
return Math.sqrt((centerX1 - centerX2) ** 2 + (centerY1 - centerY2) ** 2);
}
private compareResult(
testCase: TestCase,
result: ActualResult | Error,
@ -177,26 +185,16 @@ ${errorMsg ? `Error: ${errorMsg}` : ''}
}
if (result instanceof Error) {
const msg = `got error: ${result}, but expected?.error is not set, the prompt is: ${testCase.prompt}`;
const msg = `got error: ${result}, but expected?.error is not set (i.e. this should not be an error), the prompt is: ${testCase.prompt}`;
return new Error(msg);
}
// compare coordinates
if ('rawResponse' in result && result.rawResponse.bbox) {
assert(testCase.response_bbox, 'testCase.response_bbox is required');
const centerX =
(result.rawResponse.bbox[0] + result.rawResponse.bbox[2]) / 2;
const centerY =
(result.rawResponse.bbox[1] + result.rawResponse.bbox[3]) / 2;
const expectedCenterX =
(testCase.response_bbox[0] + testCase.response_bbox[2]) / 2;
const expectedCenterY =
(testCase.response_bbox[1] + testCase.response_bbox[3]) / 2;
const distance = Math.floor(
Math.sqrt(
(centerX - expectedCenterX) ** 2 + (centerY - expectedCenterY) ** 2,
),
const distance = this.distanceOfTwoBbox(
result.rawResponse.bbox,
testCase.response_bbox,
);
if (distance > distanceThreshold) {
@ -252,6 +250,22 @@ ${errorMsg ? `Error: ${errorMsg}` : ''}
return new Error(msg);
}
const expectedBbox = expected?.action?.locate?.bbox;
const actualBbox = result.action?.locate?.bbox;
if (typeof expectedBbox !== typeof actualBbox) {
const msg = `expectedBbox: ${expectedBbox} is not equal to actualBbox: ${actualBbox}, the prompt is: ${testCase.prompt}`;
return new Error(msg);
}
if (expectedBbox && actualBbox) {
const distance = this.distanceOfTwoBbox(expectedBbox, actualBbox);
if (distance > distanceThreshold) {
const msg = `distance: ${distance} is greater than threshold: ${distanceThreshold}, the prompt is: ${testCase.prompt}`;
return new Error(msg);
}
}
return true;
}

View File

@ -5,17 +5,13 @@ import {
getAIConfig,
plan,
} from '@midscene/core';
import {
MATCH_BY_POSITION,
MIDSCENE_USE_QWEN_VL,
getAIConfigInBoolean,
} from '@midscene/core/env';
import { MIDSCENE_USE_QWEN_VL, getAIConfigInBoolean } from '@midscene/core/env';
import { sleep } from '@midscene/core/utils';
import { saveBase64Image } from '@midscene/shared/img';
import dotenv from 'dotenv';
import { describe, expect, test } from 'vitest';
import { TestResultCollector } from '../src/test-analyzer';
import { buildContext, getCases } from './util';
import { annotatePoints, buildContext, getCases } from './util';
dotenv.config({
debug: true,
override: true,
@ -79,7 +75,12 @@ describe.skipIf(vlMode)('ai planning - by element', () => {
});
});
const vlCases = ['todo-vl', 'aweme-login-vl', 'antd-form-vl'];
const vlCases = [
'todo-vl',
'aweme-login-vl',
'antd-form-vl',
'antd-tooltip-vl',
];
// const vlCases = ['aweme-login-vl'];
describe.skipIf(!vlMode)('ai planning - by coordinates', () => {
@ -97,7 +98,12 @@ describe.skipIf(!vlMode)('ai planning - by coordinates', () => {
getAIConfig(MIDSCENE_MODEL_NAME) || 'unspecified',
);
for (const [, testCase] of cases.testCases.entries()) {
const annotations: Array<{
indexId: number;
points: [number, number, number, number];
}> = [];
for (const [index, testCase] of cases.testCases.entries()) {
const context = await buildContext(source.replace('-vl', ''));
const prompt = testCase.prompt;
@ -120,10 +126,30 @@ describe.skipIf(!vlMode)('ai planning - by coordinates', () => {
} as any;
} else {
testCase.response_planning = res;
if (res.action?.locate?.bbox) {
const indexId = index + 1;
testCase.response_bbox = res.action.locate.bbox;
testCase.annotation_index_id = indexId;
annotations.push({
indexId,
points: res.action.locate.bbox,
});
}
}
writeFileSync(aiDataPath, JSON.stringify(cases, null, 2));
}
if (annotations.length > 0) {
const markedImage = await annotatePoints(
context.screenshotBase64,
annotations,
);
await saveBase64Image({
base64Data: markedImage,
outputPath: `${aiDataPath}-planning-coordinates-annotated.png`,
});
}
resultCollector.addResult(
aiDataPath.split('/').pop() || '',
testCase,

View File

@ -81,15 +81,16 @@
"test:ai": "AITEST=true npm run test"
},
"dependencies": {
"debug": "4.4.0",
"jimp": "0.22.12",
"js-sha256": "0.11.0"
},
"peerDependencies": {},
"devDependencies": {
"@modern-js/module-tools": "2.60.6",
"typescript": "~5.0.4",
"@types/debug": "4.1.12",
"@types/node": "^18.0.0",
"rimraf": "~3.0.2",
"typescript": "~5.0.4",
"vitest": "3.0.5"
},
"sideEffects": [],

View File

@ -1,5 +1,7 @@
import { sha256 } from 'js-sha256';
import debug from 'debug';
export const ifInBrowser = typeof window !== 'undefined';
export function uuid() {
@ -8,6 +10,15 @@ export function uuid() {
const hashMap: Record<string, string> = {}; // id - combined
const topicPrefix = 'midscene';
export function getDebug(topic: string) {
return debug(`${topicPrefix}:${topic}`);
}
export function enableDebug(topic: string) {
debug.enable(`${topicPrefix}:${topic}`);
}
export function generateHashId(rect: any, content = '') {
// Combine the input into a string
const combined = JSON.stringify({

View File

@ -9,8 +9,10 @@ import {
writeLogFile,
} from '@midscene/core/utils';
import { getRunningPkgInfo } from '@midscene/shared/fs';
import { ifInBrowser } from '@midscene/shared/utils';
import { type WebUIContext, generateCacheId } from './utils';
import { getDebug, ifInBrowser } from '@midscene/shared/utils';
import type { WebUIContext } from './utils';
const debug = getDebug('cache');
export type PlanTask = {
type: 'plan';
@ -189,14 +191,27 @@ export class TaskCache {
| LocateTask['response']
| UITarsPlanTask['response']
| false {
debug(
'will read cache, type: %s, prompt: %s, cacheGroupLength: %s',
type,
userPrompt,
cacheGroup.length,
);
if (cacheGroup.length > 0) {
const index = cacheGroup.findIndex((item) => item.prompt === userPrompt);
if (index === -1) {
debug('cannot find any cache matching prompt: %s', userPrompt);
return false;
}
const taskRes = cacheGroup.splice(index, 1)[0];
debug(
'found cache with same prompt, type: %s, prompt: %s, cached response is %j',
type,
userPrompt,
taskRes?.response,
);
// The corresponding element cannot be found in the new context
if (
@ -211,17 +226,28 @@ export class TaskCache {
return true;
})
) {
debug('cannot find element with same id in current page');
return false;
}
if (
taskRes &&
taskRes.type === type &&
taskRes.prompt === userPrompt &&
this.pageContextEqual(taskRes.pageContext, pageContext)
) {
if (taskRes && taskRes.type === type && taskRes.prompt === userPrompt) {
const contextEqual = this.pageContextEqual(
taskRes.pageContext,
pageContext,
);
if (!contextEqual) {
debug(
'cache almost hit, type: %s, prompt: %s, but context not equal, will not use cache',
type,
userPrompt,
);
return false;
}
debug('cache hit, type: %s, prompt: %s', type, userPrompt);
return taskRes.response;
}
}
debug('no cache hit, type: %s, prompt: %s', type, userPrompt);
return false;
}
@ -229,6 +255,13 @@ export class TaskCache {
taskPageContext: LocateTask['pageContext'],
pageContext: WebUIContext,
) {
debug(
'comparing page context size: %s x %s, %s x %s',
taskPageContext.size.width,
taskPageContext.size.height,
pageContext.size.width,
pageContext.size.height,
);
return (
taskPageContext.size.width === pageContext.size.width &&
taskPageContext.size.height === pageContext.size.height

View File

@ -1,12 +1,12 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
// import { fileURLToPath } from 'node:url';
import { defineConfig, devices } from '@playwright/test';
//@ts-ignore
import dotenv from 'dotenv';
// 添加这两行获取当前文件目录ES 模块兼容方式)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);
const MIDSCENE_REPORT = process.env.MIDSCENE_REPORT;

150
pnpm-lock.yaml generated
View File

@ -96,7 +96,7 @@ importers:
devDependencies:
'@modern-js/module-tools':
specifier: 2.60.6
version: 2.60.6(typescript@5.0.4)
version: 2.60.6(debug@4.4.0)(typescript@5.0.4)
'@types/js-yaml':
specifier: 4.0.9
version: 4.0.9
@ -175,7 +175,7 @@ importers:
devDependencies:
'@modern-js/module-tools':
specifier: 2.60.6
version: 2.60.6(typescript@5.0.4)
version: 2.60.6(debug@4.4.0)(typescript@5.0.4)
'@types/node':
specifier: ^18.0.0
version: 18.19.62
@ -219,16 +219,22 @@ importers:
packages/shared:
dependencies:
debug:
specifier: 4.4.0
version: 4.4.0(supports-color@5.5.0)
jimp:
specifier: 0.22.12
version: 0.22.12
version: 0.22.12(debug@4.4.0)
js-sha256:
specifier: 0.11.0
version: 0.11.0
devDependencies:
'@modern-js/module-tools':
specifier: 2.60.6
version: 2.60.6(typescript@5.0.4)
version: 2.60.6(debug@4.4.0)(typescript@5.0.4)
'@types/debug':
specifier: 4.1.12
version: 4.1.12
'@types/node':
specifier: ^18.0.0
version: 18.19.62
@ -262,7 +268,7 @@ importers:
version: link:../web-integration
'@modern-js/module-tools':
specifier: 2.60.6
version: 2.60.6(typescript@5.0.4)
version: 2.60.6(debug@4.4.0)(typescript@5.0.4)
'@modern-js/plugin-module-doc':
specifier: ^2.33.1
version: 2.33.1(@modern-js/core@2.60.6)(@modern-js/doc-tools@2.32.1(@types/express@4.17.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0))(@modern-js/module-tools@2.60.6(typescript@5.0.4))(@types/express@4.17.21)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-router-dom@6.29.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0)
@ -389,7 +395,7 @@ importers:
devDependencies:
'@modern-js/module-tools':
specifier: 2.60.6
version: 2.60.6(typescript@5.0.4)
version: 2.60.6(debug@4.4.0)(typescript@5.0.4)
'@playwright/test':
specifier: ^1.44.1
version: 1.44.1
@ -10677,7 +10683,7 @@ snapshots:
'@babel/traverse': 7.26.8
'@babel/types': 7.26.8
convert-source-map: 2.0.0
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@ -10744,7 +10750,7 @@ snapshots:
'@babel/core': 7.26.0
'@babel/helper-compilation-targets': 7.26.5
'@babel/helper-plugin-utils': 7.25.9
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@ -10755,7 +10761,7 @@ snapshots:
'@babel/core': 7.26.0
'@babel/helper-compilation-targets': 7.26.5
'@babel/helper-plugin-utils': 7.26.5
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.10
transitivePeerDependencies:
@ -11616,7 +11622,7 @@ snapshots:
'@babel/parser': 7.26.2
'@babel/template': 7.25.9
'@babel/types': 7.26.8
debug: 4.3.7(supports-color@5.5.0)
debug: 4.4.0(supports-color@5.5.0)
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@ -11628,7 +11634,7 @@ snapshots:
'@babel/parser': 7.26.8
'@babel/template': 7.26.8
'@babel/types': 7.26.8
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@ -12734,12 +12740,12 @@ snapshots:
'@jimp/custom': 0.22.12
'@jimp/utils': 0.22.12
'@jimp/plugin-print@0.22.12(@jimp/custom@0.22.12)(@jimp/plugin-blit@0.22.12(@jimp/custom@0.22.12))':
'@jimp/plugin-print@0.22.12(@jimp/custom@0.22.12)(@jimp/plugin-blit@0.22.12(@jimp/custom@0.22.12))(debug@4.4.0)':
dependencies:
'@jimp/custom': 0.22.12
'@jimp/plugin-blit': 0.22.12(@jimp/custom@0.22.12)
'@jimp/utils': 0.22.12
load-bmfont: 1.4.2
load-bmfont: 1.4.2(debug@4.4.0)
transitivePeerDependencies:
- debug
@ -12776,7 +12782,7 @@ snapshots:
'@jimp/plugin-resize': 0.22.12(@jimp/custom@0.22.12)
'@jimp/utils': 0.22.12
'@jimp/plugins@0.22.12(@jimp/custom@0.22.12)':
'@jimp/plugins@0.22.12(@jimp/custom@0.22.12)(debug@4.4.0)':
dependencies:
'@jimp/custom': 0.22.12
'@jimp/plugin-blit': 0.22.12(@jimp/custom@0.22.12)
@ -12794,7 +12800,7 @@ snapshots:
'@jimp/plugin-invert': 0.22.12(@jimp/custom@0.22.12)
'@jimp/plugin-mask': 0.22.12(@jimp/custom@0.22.12)
'@jimp/plugin-normalize': 0.22.12(@jimp/custom@0.22.12)
'@jimp/plugin-print': 0.22.12(@jimp/custom@0.22.12)(@jimp/plugin-blit@0.22.12(@jimp/custom@0.22.12))
'@jimp/plugin-print': 0.22.12(@jimp/custom@0.22.12)(@jimp/plugin-blit@0.22.12(@jimp/custom@0.22.12))(debug@4.4.0)
'@jimp/plugin-resize': 0.22.12(@jimp/custom@0.22.12)
'@jimp/plugin-rotate': 0.22.12(@jimp/custom@0.22.12)(@jimp/plugin-blit@0.22.12(@jimp/custom@0.22.12))(@jimp/plugin-crop@0.22.12(@jimp/custom@0.22.12))(@jimp/plugin-resize@0.22.12(@jimp/custom@0.22.12))
'@jimp/plugin-scale': 0.22.12(@jimp/custom@0.22.12)(@jimp/plugin-resize@0.22.12(@jimp/custom@0.22.12))
@ -13700,7 +13706,7 @@ snapshots:
'@modern-js/mdx-rs-binding-win32-arm64-msvc': 0.2.4
'@modern-js/mdx-rs-binding-win32-x64-msvc': 0.2.4
'@modern-js/module-tools@2.60.6(typescript@5.0.4)':
'@modern-js/module-tools@2.60.6(debug@4.4.0)(typescript@5.0.4)':
dependencies:
'@ampproject/remapping': 2.3.0
'@ast-grep/napi': 0.16.0
@ -13708,7 +13714,7 @@ snapshots:
'@babel/types': 7.26.8
'@modern-js/core': 2.60.6
'@modern-js/plugin': 2.60.6
'@modern-js/plugin-changeset': 2.60.6
'@modern-js/plugin-changeset': 2.60.6(debug@4.4.0)
'@modern-js/plugin-i18n': 2.60.6
'@modern-js/swc-plugins': 0.6.11(@swc/helpers@0.5.13)
'@modern-js/types': 2.60.6
@ -13767,7 +13773,7 @@ snapshots:
'@swc/helpers': 0.5.13
esbuild: 0.17.19
'@modern-js/plugin-changeset@2.60.6':
'@modern-js/plugin-changeset@2.60.6(debug@4.4.0)':
dependencies:
'@changesets/cli': 2.27.9
'@changesets/git': 2.0.0
@ -13775,7 +13781,7 @@ snapshots:
'@modern-js/plugin-i18n': 2.60.6
'@modern-js/utils': 2.60.6
'@swc/helpers': 0.5.13
axios: 1.7.9
axios: 1.7.9(debug@4.4.0)
resolve-from: 5.0.0
transitivePeerDependencies:
- debug
@ -13802,7 +13808,7 @@ snapshots:
'@modern-js/doc-core': 2.31.2(@modern-js/core@2.60.6)(@modern-js/doc-tools@2.32.1(@types/express@4.17.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0))(@types/express@4.17.21)(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0)
'@modern-js/doc-plugin-api-docgen': 2.31.2(@modern-js/core@2.60.6)(@modern-js/doc-tools@2.32.1(@types/express@4.17.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0))(@types/express@4.17.21)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-router-dom@6.29.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0)
'@modern-js/doc-plugin-preview': 2.31.2(@modern-js/core@2.60.6)(@modern-js/doc-tools@2.32.1(@types/express@4.17.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0))(@types/express@4.17.21)(react-dom@18.3.1(react@18.3.1))(react-router-dom@6.29.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@types/node@18.19.62)(typescript@5.0.4))(tsconfig-paths@4.2.0)(type-fest@3.13.1)(typescript@5.0.4)(webpack@5.95.0)
'@modern-js/module-tools': 2.60.6(typescript@5.0.4)
'@modern-js/module-tools': 2.60.6(debug@4.4.0)(typescript@5.0.4)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
transitivePeerDependencies:
@ -13860,7 +13866,7 @@ snapshots:
util: 0.12.5
vm-browserify: 1.1.2
optionalDependencies:
'@modern-js/module-tools': 2.60.6(typescript@5.0.4)
'@modern-js/module-tools': 2.60.6(debug@4.4.0)(typescript@5.0.4)
'@modern-js/plugin@2.31.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
@ -14043,7 +14049,7 @@ snapshots:
'@modern-js/types': 2.31.2
'@modern-js/utils': 2.31.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@swc/helpers': 0.5.1
axios: 1.7.9
axios: 1.7.9(debug@4.4.0)
connect-history-api-fallback: 2.0.0
http-compression: 1.0.6
minimatch: 3.1.2
@ -14070,7 +14076,7 @@ snapshots:
'@modern-js/types': 2.32.1
'@modern-js/utils': 2.32.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@swc/helpers': 0.5.1
axios: 1.7.9
axios: 1.7.9(debug@4.4.0)
connect-history-api-fallback: 2.0.0
http-compression: 1.0.6
minimatch: 3.1.2
@ -14323,7 +14329,7 @@ snapshots:
'@puppeteer/browsers@2.4.0':
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
extract-zip: 2.0.1
progress: 2.0.3
proxy-agent: 6.4.0
@ -14336,7 +14342,7 @@ snapshots:
'@puppeteer/browsers@2.7.1':
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
extract-zip: 2.0.1
progress: 2.0.3
proxy-agent: 6.5.0
@ -15525,7 +15531,7 @@ snapshots:
agent-base@6.0.2:
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@ -15780,15 +15786,15 @@ snapshots:
axios@1.7.7:
dependencies:
follow-redirects: 1.15.9
follow-redirects: 1.15.9(debug@4.4.0)
form-data: 4.0.1
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
axios@1.7.9:
axios@1.7.9(debug@4.4.0):
dependencies:
follow-redirects: 1.15.9
follow-redirects: 1.15.9(debug@4.4.0)
form-data: 4.0.1
proxy-from-env: 1.1.0
transitivePeerDependencies:
@ -16109,9 +16115,9 @@ snapshots:
ccount@2.0.1: {}
centra@2.7.0:
centra@2.7.0(debug@4.4.0):
dependencies:
follow-redirects: 1.15.9
follow-redirects: 1.15.9(debug@4.4.0)
transitivePeerDependencies:
- debug
@ -16753,16 +16759,16 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.3.7(supports-color@5.5.0):
debug@4.3.7:
dependencies:
ms: 2.1.3
debug@4.4.0(supports-color@5.5.0):
dependencies:
ms: 2.1.3
optionalDependencies:
supports-color: 5.5.0
debug@4.4.0:
dependencies:
ms: 2.1.3
decamelize-keys@1.1.1:
dependencies:
decamelize: 1.2.0
@ -17053,7 +17059,7 @@ snapshots:
engine.io-client@6.6.2:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7(supports-color@5.5.0)
debug: 4.3.7
engine.io-parser: 5.2.3
ws: 8.17.1
xmlhttprequest-ssl: 2.1.2
@ -17073,7 +17079,7 @@ snapshots:
base64id: 2.0.0
cookie: 0.7.2
cors: 2.8.5
debug: 4.3.7(supports-color@5.5.0)
debug: 4.3.7
engine.io-parser: 5.2.3
ws: 8.17.1
transitivePeerDependencies:
@ -17472,7 +17478,7 @@ snapshots:
extract-zip@2.0.1:
dependencies:
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
@ -17611,7 +17617,9 @@ snapshots:
flexsearch@0.7.43: {}
follow-redirects@1.15.9: {}
follow-redirects@1.15.9(debug@4.4.0):
optionalDependencies:
debug: 4.4.0(supports-color@5.5.0)
for-each@0.3.3:
dependencies:
@ -17786,7 +17794,7 @@ snapshots:
dependencies:
basic-ftp: 5.0.5
data-uri-to-buffer: 6.0.2
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
fs-extra: 11.2.0
transitivePeerDependencies:
- supports-color
@ -18245,14 +18253,14 @@ snapshots:
dependencies:
'@tootallnate/once': 2.0.0
agent-base: 6.0.2
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@ -18271,7 +18279,7 @@ snapshots:
http-proxy@1.18.1:
dependencies:
eventemitter3: 4.0.7
follow-redirects: 1.15.9
follow-redirects: 1.15.9(debug@4.4.0)
requires-port: 1.0.0
transitivePeerDependencies:
- debug
@ -18299,21 +18307,21 @@ snapshots:
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.5:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@ -18695,10 +18703,10 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
jimp@0.22.12:
jimp@0.22.12(debug@4.4.0):
dependencies:
'@jimp/custom': 0.22.12
'@jimp/plugins': 0.22.12(@jimp/custom@0.22.12)
'@jimp/plugins': 0.22.12(@jimp/custom@0.22.12)(debug@4.4.0)
'@jimp/types': 0.22.12(@jimp/custom@0.22.12)
regenerator-runtime: 0.13.11
transitivePeerDependencies:
@ -18933,14 +18941,14 @@ snapshots:
lines-and-columns@2.0.3: {}
load-bmfont@1.4.2:
load-bmfont@1.4.2(debug@4.4.0):
dependencies:
buffer-equal: 0.0.1
mime: 1.6.0
parse-bmfont-ascii: 1.0.6
parse-bmfont-binary: 1.0.6
parse-bmfont-xml: 1.1.6
phin: 3.7.1
phin: 3.7.1(debug@4.4.0)
xhr: 2.6.0
xtend: 4.0.2
transitivePeerDependencies:
@ -19605,7 +19613,7 @@ snapshots:
micromark@3.2.0:
dependencies:
'@types/debug': 4.1.12
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
decode-named-character-reference: 1.0.2
micromark-core-commonmark: 1.1.0
micromark-factory-space: 1.1.0
@ -19791,7 +19799,7 @@ snapshots:
nodemon@3.1.7:
dependencies:
chokidar: 3.6.0
debug: 4.3.7(supports-color@5.5.0)
debug: 4.4.0(supports-color@5.5.0)
ignore-by-default: 1.0.1
minimatch: 3.1.2
pstree.remy: 1.1.8
@ -20045,7 +20053,7 @@ snapshots:
dependencies:
'@tootallnate/quickjs-emscripten': 0.23.0
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
get-uri: 6.0.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
@ -20058,7 +20066,7 @@ snapshots:
dependencies:
'@tootallnate/quickjs-emscripten': 0.23.0
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
get-uri: 6.0.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
@ -20247,9 +20255,9 @@ snapshots:
estree-walker: 3.0.3
is-reference: 3.0.2
phin@3.7.1:
phin@3.7.1(debug@4.4.0):
dependencies:
centra: 2.7.0
centra: 2.7.0(debug@4.4.0)
transitivePeerDependencies:
- debug
@ -20614,7 +20622,7 @@ snapshots:
proxy-agent@6.4.0:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
lru-cache: 7.18.3
@ -20627,7 +20635,7 @@ snapshots:
proxy-agent@6.5.0:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
lru-cache: 7.18.3
@ -20669,7 +20677,7 @@ snapshots:
dependencies:
'@puppeteer/browsers': 2.7.1
chromium-bidi: 1.2.0(devtools-protocol@0.0.1402036)
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
devtools-protocol: 0.0.1402036
typed-query-selector: 2.12.0
ws: 8.18.0
@ -21890,7 +21898,7 @@ snapshots:
socket.io-adapter@2.5.5:
dependencies:
debug: 4.3.7(supports-color@5.5.0)
debug: 4.3.7
ws: 8.17.1
transitivePeerDependencies:
- bufferutil
@ -21900,7 +21908,7 @@ snapshots:
socket.io-client@4.8.1:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7(supports-color@5.5.0)
debug: 4.3.7
engine.io-client: 6.6.2
socket.io-parser: 4.2.4
transitivePeerDependencies:
@ -21911,7 +21919,7 @@ snapshots:
socket.io-parser@4.2.4:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7(supports-color@5.5.0)
debug: 4.3.7
transitivePeerDependencies:
- supports-color
@ -21920,7 +21928,7 @@ snapshots:
accepts: 1.3.8
base64id: 2.0.0
cors: 2.8.5
debug: 4.3.7(supports-color@5.5.0)
debug: 4.3.7
engine.io: 6.6.2
socket.io-adapter: 2.5.5
socket.io-parser: 4.2.4
@ -21932,7 +21940,7 @@ snapshots:
socks-proxy-agent@8.0.4:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@ -21940,7 +21948,7 @@ snapshots:
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@ -22795,7 +22803,7 @@ snapshots:
vite-node@3.0.5(@types/node@18.19.62)(sass-embedded@1.83.4)(terser@5.36.0):
dependencies:
cac: 6.7.14
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
es-module-lexer: 1.6.0
pathe: 2.0.3
vite: 5.4.10(@types/node@18.19.62)(sass-embedded@1.83.4)(terser@5.36.0)
@ -22813,7 +22821,7 @@ snapshots:
vite-node@3.0.5(@types/node@22.8.5)(sass-embedded@1.83.4)(terser@5.36.0):
dependencies:
cac: 6.7.14
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
es-module-lexer: 1.6.0
pathe: 2.0.3
vite: 5.4.10(@types/node@22.8.5)(sass-embedded@1.83.4)(terser@5.36.0)
@ -22860,7 +22868,7 @@ snapshots:
'@vitest/spy': 3.0.5
'@vitest/utils': 3.0.5
chai: 5.1.2
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
expect-type: 1.1.0
magic-string: 0.30.17
pathe: 2.0.3
@ -22897,7 +22905,7 @@ snapshots:
'@vitest/spy': 3.0.5
'@vitest/utils': 3.0.5
chai: 5.1.2
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
expect-type: 1.1.0
magic-string: 0.30.17
pathe: 2.0.3
@ -22947,7 +22955,7 @@ snapshots:
dependencies:
chalk: 4.1.2
commander: 9.5.0
debug: 4.4.0
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color