You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
3.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type { Locator } from "playwright";
import { RPA_STREET_INPUT_CLICK_MS } from "@/lib/constants/rpa";
import { RpaError } from "@/modules/rpa/errors";
import { dismissAddressSuggestionDropdown } from "@/workers/rpa/address-commit";
import { AddressCommitStateMachine } from "@/workers/rpa/address-adapter/commit-state-machine";
import {
createEmptyDiagnostics,
logAddressSelectDiagnostics,
} from "@/workers/rpa/address-adapter/diagnostics";
import type {
AddressAdapter,
AddressCommitResult,
AddressSelectDiagnostics,
SelectAddressParams,
} from "@/workers/rpa/address-adapter/types";
const MOCK_STREET_FILL_MS = Math.min(RPA_STREET_INPUT_CLICK_MS, 5_000);
/** 链路探通:仅 fill 街道文本,不等待 MotherShip 联想/place API */
async function mockTypeStreetInput(
street: Locator,
text: string,
): Promise<void> {
const value = text.replace(/\s+/g, " ").trim().slice(0, 160);
await street.click({ timeout: MOCK_STREET_FILL_MS }).catch(() => undefined);
await street.fill("", { timeout: MOCK_STREET_FILL_MS }).catch(() => undefined);
await street
.fill(value, { timeout: MOCK_STREET_FILL_MS })
.catch(() => undefined);
await street.press("Escape").catch(() => undefined);
await street.press("Tab").catch(() => undefined);
}
/**
* mock 模式:快速填入地址文本并合成 commit不触发 MotherShip place 闸门。
* 下游 cargo / submit / quote parser 仍真实执行。
*/
export class MockAddressAdapter implements AddressAdapter {
private readonly stateMachine = new AddressCommitStateMachine();
private result: AddressCommitResult | null = null;
private diagnostics: AddressSelectDiagnostics = createEmptyDiagnostics("");
getCommitState() {
return this.stateMachine.getState();
}
getCommittedAddress(): AddressCommitResult | null {
return this.result;
}
getDiagnostics(): AddressSelectDiagnostics {
return {
...this.diagnostics,
commitState: this.stateMachine.getState(),
};
}
async selectAddress(params: SelectAddressParams): Promise<AddressCommitResult> {
const { ctx, side, address, street } = params;
const page = ctx.page;
const label =
address.mothershipDisplayLabel?.trim() ||
address.formattedAddress?.trim() ||
address.street;
const streetText =
address.formattedAddress?.trim() ||
`${address.street}, ${address.city ?? ""}, ${address.state ?? ""} ${address.zip ?? ""}`.trim();
this.diagnostics = createEmptyDiagnostics(label, "EMPTY");
this.stateMachine.transition("TYPING");
this.diagnostics.method = "mock-fast-fill";
this.diagnostics.events.push("mock-mode-start");
await mockTypeStreetInput(street, streetText);
await dismissAddressSuggestionDropdown(page, street);
this.diagnostics.events.push("street-filled");
this.stateMachine.transition("COMMITTED");
const placeId =
address.placeId?.trim() || `mock-place-${side}-${Date.now()}`;
const formattedAddress = streetText || label;
this.diagnostics.address = label;
this.diagnostics.placeId = placeId;
this.diagnostics.status = "ok";
this.diagnostics.placeRequestObserved = false;
this.diagnostics.events.push("mock-commit-synthesized");
this.result = {
success: true,
placeId,
formattedAddress,
committed: true,
};
logAddressSelectDiagnostics(side, this.getDiagnostics());
console.log(
`[rpa] address-mock:${side} 已合成 commit placeId=${placeId}(跳过联想/place API`,
);
return this.result;
}
async waitForCommit(): Promise<AddressCommitResult> {
if (!this.result?.committed) {
throw new RpaError(
"ADDRESS_NOT_CONFIRMED",
"mock waitForCommitselectAddress 未完成",
{ retryable: false },
);
}
return this.result;
}
}