feat: allow web observer provider selection
This commit is contained in:
@@ -225,6 +225,7 @@ async function processCommand(command) {
|
||||
case "preflight": return preflightSummary();
|
||||
case "goto": return gotoTarget(command.path || command.url || targetPath);
|
||||
case "sendPrompt": return sendPrompt(String(command.text || ""));
|
||||
case "selectProvider": return selectProvider(String(command.provider || command.value || command.text || ""));
|
||||
case "clickSession": return clickSession(String(command.sessionId || command.value || ""));
|
||||
case "screenshot": return captureScreenshot(command.reason || "manual", command.imageType || "png");
|
||||
case "mark": return { mark: truncate(command.label || command.text || "mark", 200), currentUrl: currentPageUrl(), pageId };
|
||||
@@ -314,6 +315,73 @@ async function sendPrompt(text) {
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), textHash: sha256Text(text), textBytes: Buffer.byteLength(text), pageId };
|
||||
}
|
||||
|
||||
async function selectProvider(provider) {
|
||||
const target = String(provider || "").trim();
|
||||
if (!target) throw new Error("selectProvider requires provider name");
|
||||
const beforeUrl = currentPageUrl();
|
||||
const nativeSelect = await page.evaluate((name) => {
|
||||
const normalized = String(name).toLowerCase();
|
||||
const visible = (element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = window.getComputedStyle(element);
|
||||
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
||||
};
|
||||
for (const select of Array.from(document.querySelectorAll("select")).filter(visible)) {
|
||||
const options = Array.from(select.options || []);
|
||||
const option = options.find((item) => String(item.value || "").toLowerCase().includes(normalized) || String(item.textContent || "").toLowerCase().includes(normalized));
|
||||
if (!option) continue;
|
||||
select.value = option.value;
|
||||
select.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
select.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
return { kind: "native-select", value: option.value, text: option.textContent || "" };
|
||||
}
|
||||
return null;
|
||||
}, target).catch(() => null);
|
||||
if (nativeSelect) {
|
||||
await page.waitForTimeout(500);
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), provider: target, selected: nativeSelect, pageId };
|
||||
}
|
||||
const optionVisible = page.getByText(target, { exact: false }).last();
|
||||
if (await optionVisible.isVisible().catch(() => false)) {
|
||||
await optionVisible.click();
|
||||
await page.waitForTimeout(500);
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), provider: target, selected: { kind: "visible-text" }, pageId };
|
||||
}
|
||||
const control = page.locator([
|
||||
'[data-testid*="provider" i]',
|
||||
'[data-testid*="profile" i]',
|
||||
'[data-testid*="model" i]',
|
||||
'[aria-label*="provider" i]',
|
||||
'[aria-label*="profile" i]',
|
||||
'[aria-label*="model" i]',
|
||||
'[aria-label*="模型"]',
|
||||
'[aria-label*="提供"]',
|
||||
'[role="combobox"]',
|
||||
'[aria-haspopup="listbox"]',
|
||||
'button'
|
||||
].join(", "));
|
||||
const count = Math.min(await control.count().catch(() => 0), 60);
|
||||
const attempts = [];
|
||||
for (let index = count - 1; index >= 0; index -= 1) {
|
||||
const item = control.nth(index);
|
||||
const visible = await item.isVisible().catch(() => false);
|
||||
if (!visible) continue;
|
||||
const label = await item.evaluate((element) => String(element.getAttribute("aria-label") || element.getAttribute("data-testid") || element.textContent || "").slice(0, 200)).catch(() => "");
|
||||
if (!/provider|profile|model|模型|提供|codex|openai|moon|api/iu.test(label)) continue;
|
||||
attempts.push({ index, label });
|
||||
await item.click({ timeout: 3000 }).catch(() => null);
|
||||
await page.waitForTimeout(300);
|
||||
const option = page.getByText(target, { exact: false }).last();
|
||||
if (await option.isVisible().catch(() => false)) {
|
||||
await option.click();
|
||||
await page.waitForTimeout(700);
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), provider: target, selected: { kind: "opened-control", controlIndex: index, label }, attempts, pageId };
|
||||
}
|
||||
await page.keyboard.press("Escape").catch(() => null);
|
||||
}
|
||||
throw new Error("provider option not found: " + target + "; attempts=" + JSON.stringify(attempts.slice(-10)));
|
||||
}
|
||||
|
||||
async function clickSession(sessionId) {
|
||||
if (!sessionId) throw new Error("clickSession requires --session-id or --value");
|
||||
const beforeUrl = currentPageUrl();
|
||||
@@ -441,6 +509,7 @@ function commandInputSummary(command) {
|
||||
path: command.path || null,
|
||||
url: command.url ? safeUrl(command.url) : null,
|
||||
sessionId: command.sessionId || command.value || null,
|
||||
provider: command.provider || null,
|
||||
label: command.label ? truncate(command.label, 200) : null,
|
||||
textHash: text === null ? null : sha256Text(text),
|
||||
textBytes: text === null ? null : Buffer.byteLength(text),
|
||||
|
||||
Reference in New Issue
Block a user