import { isAutoGLM } from "../ai-model/auto-glm/util.mjs";
import { AIResponseParseError, AiExtractElementInfo, AiLocateElement, callAIWithObjectResponse } from "../ai-model/index.mjs";
import { AiLocateSection } from "../ai-model/inspect.mjs";
import { elementDescriberInstruction } from "../ai-model/prompt/describe.mjs";
import { expandSearchArea } from "../common.mjs";
import { ServiceError } from "../types.mjs";
import { compositeElementInfoImg, cropByRect } from "@midscene/shared/img";
import { getDebug } from "@midscene/shared/logger";
import { assert } from "@midscene/shared/utils";
import { createServiceDump } from "./utils.mjs";
function _define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const debug = getDebug('ai:service');
class Service {
    async locate(query, opt, modelConfig, abortSignal) {
        const queryPrompt = 'string' == typeof query ? query : query.prompt;
        assert(queryPrompt, 'query is required for locate');
        assert('object' == typeof query, 'query should be an object for locate');
        let searchAreaPrompt;
        if (query.deepLocate) searchAreaPrompt = query.prompt;
        const { modelFamily } = modelConfig;
        if (searchAreaPrompt && !modelFamily) {
            console.warn('The "deepLocate" feature is not supported with multimodal LLM. Please config VL model for Midscene. https://midscenejs.com/model-config');
            searchAreaPrompt = void 0;
        }
        if (searchAreaPrompt && isAutoGLM(modelFamily)) {
            console.warn('The "deepLocate" feature is not supported with AutoGLM.');
            searchAreaPrompt = void 0;
        }
        const context = opt?.context || await this.contextRetrieverFn();
        let searchArea;
        let searchAreaRawResponse;
        let searchAreaUsage;
        let searchAreaResponse;
        if (searchAreaPrompt) {
            searchAreaResponse = await AiLocateSection({
                context,
                sectionDescription: searchAreaPrompt,
                modelConfig,
                abortSignal
            });
            assert(searchAreaResponse.rect, `cannot find search area for "${searchAreaPrompt}"${searchAreaResponse.error ? `: ${searchAreaResponse.error}` : ''}`);
            searchAreaRawResponse = searchAreaResponse.rawResponse;
            searchAreaUsage = searchAreaResponse.usage;
            searchArea = searchAreaResponse.rect;
        }
        const startTime = Date.now();
        const { parseResult, rect, rawResponse, usage, reasoning_content } = await AiLocateElement({
            context,
            targetElementDescription: queryPrompt,
            searchConfig: searchAreaResponse,
            modelConfig,
            abortSignal
        });
        const timeCost = Date.now() - startTime;
        const taskInfo = {
            ...this.taskInfo ? this.taskInfo : {},
            durationMs: timeCost,
            rawResponse: JSON.stringify(rawResponse),
            formatResponse: JSON.stringify(parseResult),
            usage,
            searchArea,
            searchAreaRawResponse,
            searchAreaUsage,
            reasoning_content
        };
        let errorLog;
        if (parseResult.errors?.length) errorLog = `failed to locate element: \n${parseResult.errors.join('\n')}`;
        const dumpData = {
            type: 'locate',
            userQuery: {
                element: queryPrompt
            },
            matchedElement: [],
            matchedRect: rect,
            data: null,
            taskInfo,
            deepLocate: !!searchArea,
            error: errorLog
        };
        const elements = parseResult.elements || [];
        const dump = createServiceDump({
            ...dumpData,
            matchedElement: elements
        });
        if (errorLog) throw new ServiceError(errorLog, dump);
        if (elements.length > 1) throw new ServiceError(`locate: multiple elements found, length = ${elements.length}`, dump);
        if (1 === elements.length) return {
            element: {
                center: elements[0].center,
                rect: elements[0].rect,
                description: elements[0].description
            },
            rect,
            dump
        };
        return {
            element: null,
            rect,
            dump
        };
    }
    async extract(dataDemand, modelConfig, opt, pageDescription, multimodalPrompt, context) {
        assert(context, 'context is required for extract');
        assert('object' == typeof dataDemand || 'string' == typeof dataDemand, `dataDemand should be object or string, but get ${typeof dataDemand}`);
        const startTime = Date.now();
        let parseResult;
        let rawResponse;
        let usage;
        let reasoning_content;
        try {
            const result = await AiExtractElementInfo({
                context,
                dataQuery: dataDemand,
                multimodalPrompt,
                extractOption: opt,
                modelConfig,
                pageDescription
            });
            parseResult = result.parseResult;
            rawResponse = result.rawResponse;
            usage = result.usage;
            reasoning_content = result.reasoning_content;
        } catch (error) {
            if (error instanceof AIResponseParseError) {
                const timeCost = Date.now() - startTime;
                const taskInfo = {
                    ...this.taskInfo ? this.taskInfo : {},
                    durationMs: timeCost,
                    rawResponse: error.rawResponse,
                    usage: error.usage
                };
                const dump = createServiceDump({
                    type: 'extract',
                    userQuery: {
                        dataDemand
                    },
                    matchedElement: [],
                    data: null,
                    taskInfo,
                    error: error.message
                });
                throw new ServiceError(error.message, dump);
            }
            throw error;
        }
        const timeCost = Date.now() - startTime;
        const taskInfo = {
            ...this.taskInfo ? this.taskInfo : {},
            durationMs: timeCost,
            rawResponse,
            formatResponse: JSON.stringify(parseResult),
            usage,
            reasoning_content
        };
        let errorLog;
        if (parseResult.errors?.length) errorLog = `AI response error: \n${parseResult.errors.join('\n')}`;
        const dumpData = {
            type: 'extract',
            userQuery: {
                dataDemand
            },
            matchedElement: [],
            data: null,
            taskInfo,
            error: errorLog
        };
        const { data, thought } = parseResult || {};
        const dump = createServiceDump({
            ...dumpData,
            data
        });
        if (errorLog && !data) throw new ServiceError(errorLog, dump);
        return {
            data,
            thought,
            usage,
            reasoning_content,
            dump
        };
    }
    async describe(target, modelConfig, opt) {
        assert(target, 'target is required for service.describe');
        const context = await this.contextRetrieverFn();
        const { shotSize } = context;
        const screenshotBase64 = context.screenshot.base64;
        assert(screenshotBase64, 'screenshot is required for service.describe');
        const { modelFamily } = modelConfig;
        const systemPrompt = elementDescriberInstruction();
        const defaultRectSize = 30;
        const targetRect = Array.isArray(target) ? {
            left: Math.floor(target[0] - defaultRectSize / 2),
            top: Math.floor(target[1] - defaultRectSize / 2),
            width: defaultRectSize,
            height: defaultRectSize
        } : target;
        let imagePayload = await compositeElementInfoImg({
            inputImgBase64: screenshotBase64,
            size: shotSize,
            elementsPositionInfo: [
                {
                    rect: targetRect
                }
            ],
            borderThickness: 3
        });
        if (opt?.deepLocate) {
            const searchArea = expandSearchArea(targetRect, shotSize);
            debug('describe: cropping to searchArea', searchArea);
            const croppedResult = await cropByRect(imagePayload, searchArea, 'qwen2.5-vl' === modelFamily);
            imagePayload = croppedResult.imageBase64;
        }
        const msgs = [
            {
                role: 'system',
                content: systemPrompt
            },
            {
                role: 'user',
                content: [
                    {
                        type: 'image_url',
                        image_url: {
                            url: imagePayload,
                            detail: 'high'
                        }
                    }
                ]
            }
        ];
        const res = await callAIWithObjectResponse(msgs, modelConfig);
        const { content } = res;
        assert(!content.error, `describe failed: ${content.error}`);
        assert(content.description, 'failed to describe the element');
        return content;
    }
    constructor(context, opt){
        _define_property(this, "contextRetrieverFn", void 0);
        _define_property(this, "taskInfo", void 0);
        assert(context, 'context is required for Service');
        if ('function' == typeof context) this.contextRetrieverFn = context;
        else this.contextRetrieverFn = ()=>Promise.resolve(context);
        if (void 0 !== opt?.taskInfo) this.taskInfo = opt.taskInfo;
    }
}
export { Service as default };

//# sourceMappingURL=index.mjs.map