@youdie006/prodex 0.36.4 → 0.36.5
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/chatgpt-browser.js +46 -7
- package/dist/picker-interaction.js +40 -0
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -5,6 +5,7 @@ import net from "node:net";
|
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import os from "node:os";
|
|
8
|
+
import { readPowerSliderSelection } from "./picker-interaction.js";
|
|
8
9
|
export class ChatGptBrowserBlockerError extends Error {
|
|
9
10
|
blocker;
|
|
10
11
|
constructor(blocker) {
|
|
@@ -1300,6 +1301,14 @@ const CLICK_POINT_SNIPPET = `
|
|
|
1300
1301
|
el.scrollIntoView({ block: "center", inline: "nearest" });
|
|
1301
1302
|
const r = el.getBoundingClientRect();
|
|
1302
1303
|
if (r.width < 1 || r.height < 1) return { ok: false, reason: "target element has no visible area" };
|
|
1304
|
+
// An inert row cannot be clicked at ANY point, so say that instead of
|
|
1305
|
+
// blaming whatever hit-testing returns instead. Measured on the current
|
|
1306
|
+
// picker: the model rows carry pointer-events:none and sit outside the
|
|
1307
|
+
// menu's own box, so every coordinate on them resolves to the effort
|
|
1308
|
+
// control and the refusal read as "another element covers it".
|
|
1309
|
+
if (getComputedStyle(el).pointerEvents === "none") {
|
|
1310
|
+
return { ok: false, reason: "target element is not clickable (pointer-events: none)" };
|
|
1311
|
+
}
|
|
1303
1312
|
el.setAttribute('data-prodex-click', '1');
|
|
1304
1313
|
const x = Math.round(r.x + r.width / 2);
|
|
1305
1314
|
const y = Math.round(r.y + (yCap ? Math.min(r.height / 2, yCap) : r.height / 2));
|
|
@@ -1382,15 +1391,24 @@ export function powerSliderStateExpression() {
|
|
|
1382
1391
|
const slider = document.querySelector('[role="slider"]');
|
|
1383
1392
|
const menu = document.querySelector('[data-testid="composer-intelligence-picker-content"]');
|
|
1384
1393
|
const lines = menu ? (menu.innerText || "").split(String.fromCharCode(10)).map((l) => l.trim()).filter(Boolean) : [];
|
|
1385
|
-
const after = (label) => { const i = lines.indexOf(label); return i >= 0 ? lines[i + 1] : null; };
|
|
1386
1394
|
if (!slider) return { ok: false, reason: "power slider not found", lines };
|
|
1395
|
+
const readPowerSliderSelection = ${readPowerSliderSelection.toString()};
|
|
1396
|
+
const selection = readPowerSliderSelection({
|
|
1397
|
+
sliderValueText: slider.getAttribute("aria-valuetext"),
|
|
1398
|
+
items: menu ? [...menu.querySelectorAll('[role="menuitemradio"],[role="menuitem"]')].map((item) => ({
|
|
1399
|
+
role: item.getAttribute("role"),
|
|
1400
|
+
text: item.innerText || item.textContent || "",
|
|
1401
|
+
checked: item.getAttribute("aria-checked") === "true",
|
|
1402
|
+
containsSlider: item.contains(slider)
|
|
1403
|
+
})) : []
|
|
1404
|
+
});
|
|
1387
1405
|
return {
|
|
1388
1406
|
ok: true,
|
|
1389
1407
|
position: Number(slider.getAttribute("aria-valuenow")),
|
|
1390
1408
|
min: Number(slider.getAttribute("aria-valuemin")),
|
|
1391
1409
|
max: Number(slider.getAttribute("aria-valuemax")),
|
|
1392
|
-
model:
|
|
1393
|
-
effort:
|
|
1410
|
+
model: selection.model,
|
|
1411
|
+
effort: selection.effort,
|
|
1394
1412
|
lines
|
|
1395
1413
|
};
|
|
1396
1414
|
})()`;
|
|
@@ -1562,11 +1580,32 @@ const POWER_SLIDER_APPEAR_ATTEMPTS = 5;
|
|
|
1562
1580
|
// Click a model row. They are ordinary `menuitemradio` entries in the same
|
|
1563
1581
|
// menu as the effort slider, so the existing by-label lookup reaches them; what
|
|
1564
1582
|
// was missing is anything routing a model here instead of into the slider.
|
|
1565
|
-
|
|
1583
|
+
/**
|
|
1584
|
+
* Whether a model selection failure is the picker not offering it at all.
|
|
1585
|
+
*
|
|
1586
|
+
* Those two cases - the row is absent, or it is inert - are the UI declining to
|
|
1587
|
+
* provide the choice, and they must not kill a send that is otherwise fine. It
|
|
1588
|
+
* is the same answer prodex already gives for a Pro sub-mode it cannot apply.
|
|
1589
|
+
* Anything else (a menu that would not open, a click that would not land) is a
|
|
1590
|
+
* real failure and stays one, so a genuine break is not buried under a warning.
|
|
1591
|
+
*/
|
|
1592
|
+
export function modelSelectionUnavailableWarning(requested, reason, available) {
|
|
1593
|
+
if (!/menu item not found|not clickable/i.test(reason))
|
|
1594
|
+
return undefined;
|
|
1595
|
+
const offers = available?.length ? ` It offers: ${available.join(", ")}.` : "";
|
|
1596
|
+
return (`model_not_applied: this ChatGPT picker does not offer "${requested}" as a selectable model, ` +
|
|
1597
|
+
`so the send used whatever the composer already had.${offers} ` +
|
|
1598
|
+
'Pick an effort with --effort instead, or clear a saved default with `prodex setup --model ""`.');
|
|
1599
|
+
}
|
|
1600
|
+
async function selectPickerModel(cdp, requested, warnings = []) {
|
|
1566
1601
|
const hit = await cdp.evaluate(menuItemRectExpression(requested));
|
|
1567
1602
|
if (!hit.ok || hit.x === undefined || hit.y === undefined) {
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1603
|
+
const unavailable = modelSelectionUnavailableWarning(requested, hit.reason ?? "", hit.available);
|
|
1604
|
+
if (unavailable) {
|
|
1605
|
+
warnings.push(unavailable);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
throw new Error(hit.reason ?? `ChatGPT's model picker has no "${requested}" model.`);
|
|
1570
1609
|
}
|
|
1571
1610
|
await verifiedClickWithRetry(cdp, () => cdp.evaluate(menuItemRectExpression(requested)), requested);
|
|
1572
1611
|
}
|
|
@@ -1689,7 +1728,7 @@ async function selectModelReasoning(cdp, options, selectionWarnings = []) {
|
|
|
1689
1728
|
await selectPowerStep(cdp, plan.sliderLabel);
|
|
1690
1729
|
}
|
|
1691
1730
|
if (plan.modelLabel) {
|
|
1692
|
-
await selectPickerModel(cdp, plan.modelLabel);
|
|
1731
|
+
await selectPickerModel(cdp, plan.modelLabel, selectionWarnings);
|
|
1693
1732
|
}
|
|
1694
1733
|
// This branch cannot honour a sub-mode, and used to return without
|
|
1695
1734
|
// saying so - the caller got a clean receipt for a 확장 it never got.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Pure decision helpers for ChatGPT's composer picker, kept out of the page. */
|
|
2
|
+
/** Read model and effort from both the current mixed-control picker and its prior labeled-row form. */
|
|
3
|
+
export function readPowerSliderSelection(snapshot) {
|
|
4
|
+
const items = snapshot.items.map((item) => ({
|
|
5
|
+
...item,
|
|
6
|
+
lines: item.text
|
|
7
|
+
.split("\n")
|
|
8
|
+
.map((line) => line.trim())
|
|
9
|
+
.filter(Boolean)
|
|
10
|
+
}));
|
|
11
|
+
const powerLabels = new Set([
|
|
12
|
+
"instant",
|
|
13
|
+
"즉시",
|
|
14
|
+
"빠름",
|
|
15
|
+
"fast",
|
|
16
|
+
"medium",
|
|
17
|
+
"중간",
|
|
18
|
+
"보통",
|
|
19
|
+
"high",
|
|
20
|
+
"높음",
|
|
21
|
+
"extra high",
|
|
22
|
+
"extrahigh",
|
|
23
|
+
"very high",
|
|
24
|
+
"매우 높음",
|
|
25
|
+
"매우높음",
|
|
26
|
+
"pro",
|
|
27
|
+
"프로"
|
|
28
|
+
]);
|
|
29
|
+
const legacyModel = items.find((item) => item.lines[0] === "Model")?.lines[1] ?? null;
|
|
30
|
+
const legacyEffort = items.find((item) => item.lines[0] === "Effort")?.lines[1] ?? null;
|
|
31
|
+
const checkedModel = items.find((item) => item.role === "menuitemradio" && item.checked)?.lines[0] ?? null;
|
|
32
|
+
const sliderOwner = items.find((item) => item.role === "menuitem" && item.containsSlider)?.lines[0] ?? null;
|
|
33
|
+
const visiblePowerLabel = items.find((item) => item.role === "menuitem" && powerLabels.has((item.lines[0] ?? "").toLowerCase()))?.lines[0] ?? null;
|
|
34
|
+
const accessibleCandidate = snapshot.sliderValueText?.trim() || null;
|
|
35
|
+
const accessibleValue = accessibleCandidate && powerLabels.has(accessibleCandidate.toLowerCase()) ? accessibleCandidate : null;
|
|
36
|
+
return {
|
|
37
|
+
model: legacyModel ?? checkedModel,
|
|
38
|
+
effort: accessibleValue ?? legacyEffort ?? sliderOwner ?? visiblePowerLabel
|
|
39
|
+
};
|
|
40
|
+
}
|