@reconcrap/boss-recommend-mcp 2.0.11 → 2.0.12
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/package.json +1 -1
- package/src/core/infinite-list/index.js +354 -1
- package/src/domains/chat/constants.js +11 -0
- package/src/domains/chat/run-service.js +16 -5
- package/src/domains/recommend/constants.js +11 -0
- package/src/domains/recommend/run-service.js +29 -8
- package/src/domains/recruit/constants.js +23 -0
- package/src/domains/recruit/run-service.js +16 -4
package/package.json
CHANGED
|
@@ -1,10 +1,68 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import {
|
|
3
3
|
getNodeBox,
|
|
4
|
+
getOuterHTML,
|
|
5
|
+
querySelectorAll,
|
|
4
6
|
scrollNodeIntoView,
|
|
5
7
|
sleep
|
|
6
8
|
} from "../browser/index.js";
|
|
7
9
|
|
|
10
|
+
export const DEFAULT_BOTTOM_HINT_KEYWORDS = Object.freeze([
|
|
11
|
+
"没有更多",
|
|
12
|
+
"已显示全部",
|
|
13
|
+
"已经到底",
|
|
14
|
+
"暂无更多",
|
|
15
|
+
"推荐完了",
|
|
16
|
+
"没有更多人选",
|
|
17
|
+
"没有更多了",
|
|
18
|
+
"已到底"
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_LOAD_MORE_HINT_KEYWORDS = Object.freeze([
|
|
22
|
+
"滚动加载更多",
|
|
23
|
+
"下滑加载更多",
|
|
24
|
+
"继续下滑",
|
|
25
|
+
"继续滑动",
|
|
26
|
+
"滑动加载",
|
|
27
|
+
"正在加载",
|
|
28
|
+
"加载中"
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
32
|
+
".finished-wrap",
|
|
33
|
+
".load-tips",
|
|
34
|
+
"div[role=\"tfoot\"] .load-tips",
|
|
35
|
+
".no-data-refresh",
|
|
36
|
+
".empty-tip",
|
|
37
|
+
".empty-text",
|
|
38
|
+
".no-data",
|
|
39
|
+
".tip-nodata",
|
|
40
|
+
"[class*=\"finished\"]",
|
|
41
|
+
"[class*=\"load-tips\"]",
|
|
42
|
+
"[class*=\"no-more\"]",
|
|
43
|
+
"[class*=\"no_more\"]"
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
export const DEFAULT_BOTTOM_TEXT_SCAN_SELECTORS = Object.freeze([
|
|
47
|
+
"div",
|
|
48
|
+
"span",
|
|
49
|
+
"p",
|
|
50
|
+
"li",
|
|
51
|
+
"button",
|
|
52
|
+
"a"
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
export const DEFAULT_BOTTOM_REFRESH_SELECTORS = Object.freeze([
|
|
56
|
+
".finished-wrap .btn-refresh",
|
|
57
|
+
".finished-wrap .btn",
|
|
58
|
+
".no-data-refresh .btn-refresh",
|
|
59
|
+
".no-data-refresh .btn",
|
|
60
|
+
"[class*=\"refresh\"]",
|
|
61
|
+
"[ka*=\"refresh\"]",
|
|
62
|
+
"button",
|
|
63
|
+
"a"
|
|
64
|
+
]);
|
|
65
|
+
|
|
8
66
|
function nowIso() {
|
|
9
67
|
return new Date().toISOString();
|
|
10
68
|
}
|
|
@@ -13,6 +71,31 @@ function normalizeText(value) {
|
|
|
13
71
|
return String(value || "").replace(/\s+/g, " ").trim();
|
|
14
72
|
}
|
|
15
73
|
|
|
74
|
+
function uniqueValues(values = []) {
|
|
75
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function decodeBasicHtmlEntities(value = "") {
|
|
79
|
+
return String(value || "")
|
|
80
|
+
.replace(/ | /gi, " ")
|
|
81
|
+
.replace(/&/gi, "&")
|
|
82
|
+
.replace(/</gi, "<")
|
|
83
|
+
.replace(/>/gi, ">")
|
|
84
|
+
.replace(/"/gi, "\"")
|
|
85
|
+
.replace(/'|'/gi, "'");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function plainTextFromHtml(html = "") {
|
|
89
|
+
return normalizeText(decodeBasicHtmlEntities(String(html || "")
|
|
90
|
+
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ")
|
|
91
|
+
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ")
|
|
92
|
+
.replace(/<[^>]+>/g, " ")));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isUsableBox(box) {
|
|
96
|
+
return Number(box?.rect?.width || 0) > 2 && Number(box?.rect?.height || 0) > 2;
|
|
97
|
+
}
|
|
98
|
+
|
|
16
99
|
function shortHash(value) {
|
|
17
100
|
return crypto.createHash("sha256").update(String(value || "")).digest("hex").slice(0, 16);
|
|
18
101
|
}
|
|
@@ -174,6 +257,220 @@ export function resetInfiniteListForRefreshRound(state, {
|
|
|
174
257
|
return compactInfiniteListState(state);
|
|
175
258
|
}
|
|
176
259
|
|
|
260
|
+
export function classifyInfiniteListBottomMarker({
|
|
261
|
+
text = "",
|
|
262
|
+
refreshButtonVisible = false,
|
|
263
|
+
bottomKeywords = DEFAULT_BOTTOM_HINT_KEYWORDS,
|
|
264
|
+
loadMoreKeywords = DEFAULT_LOAD_MORE_HINT_KEYWORDS
|
|
265
|
+
} = {}) {
|
|
266
|
+
const normalizedText = normalizeText(text);
|
|
267
|
+
const matchedBottomKeyword = bottomKeywords.find((keyword) => normalizedText.includes(keyword)) || null;
|
|
268
|
+
if (matchedBottomKeyword) {
|
|
269
|
+
return {
|
|
270
|
+
is_bottom: true,
|
|
271
|
+
reason: matchedBottomKeyword,
|
|
272
|
+
matched_bottom_keyword: matchedBottomKeyword,
|
|
273
|
+
matched_load_more_keyword: null
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const matchedLoadMoreKeyword = loadMoreKeywords.find((keyword) => normalizedText.includes(keyword)) || null;
|
|
278
|
+
if (matchedLoadMoreKeyword) {
|
|
279
|
+
return {
|
|
280
|
+
is_bottom: false,
|
|
281
|
+
reason: null,
|
|
282
|
+
matched_bottom_keyword: null,
|
|
283
|
+
matched_load_more_keyword: matchedLoadMoreKeyword
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (refreshButtonVisible) {
|
|
288
|
+
return {
|
|
289
|
+
is_bottom: true,
|
|
290
|
+
reason: "refresh_button_visible",
|
|
291
|
+
matched_bottom_keyword: null,
|
|
292
|
+
matched_load_more_keyword: null
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
is_bottom: false,
|
|
298
|
+
reason: null,
|
|
299
|
+
matched_bottom_keyword: null,
|
|
300
|
+
matched_load_more_keyword: null
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function safeQuerySelectorAll(client, rootNodeId, selector) {
|
|
305
|
+
try {
|
|
306
|
+
return await querySelectorAll(client, rootNodeId, selector);
|
|
307
|
+
} catch {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function readVisibleMarkerNode(client, nodeId) {
|
|
313
|
+
let box = null;
|
|
314
|
+
try {
|
|
315
|
+
box = await getNodeBox(client, nodeId);
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
if (!isUsableBox(box)) return null;
|
|
320
|
+
let outerHTML = "";
|
|
321
|
+
try {
|
|
322
|
+
outerHTML = await getOuterHTML(client, nodeId);
|
|
323
|
+
} catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
node_id: nodeId,
|
|
328
|
+
text: plainTextFromHtml(outerHTML),
|
|
329
|
+
box
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function looksLikeRefreshLabel(text = "") {
|
|
334
|
+
const normalized = normalizeText(text).replace(/\s+/g, "");
|
|
335
|
+
return Boolean(normalized) && normalized.length <= 80 && /刷新|refresh/i.test(normalized);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export async function detectInfiniteListBottomMarker(client, {
|
|
339
|
+
rootNodeId,
|
|
340
|
+
markerSelectors = DEFAULT_BOTTOM_MARKER_SELECTORS,
|
|
341
|
+
textScanSelectors = DEFAULT_BOTTOM_TEXT_SCAN_SELECTORS,
|
|
342
|
+
refreshSelectors = DEFAULT_BOTTOM_REFRESH_SELECTORS,
|
|
343
|
+
bottomKeywords = DEFAULT_BOTTOM_HINT_KEYWORDS,
|
|
344
|
+
loadMoreKeywords = DEFAULT_LOAD_MORE_HINT_KEYWORDS,
|
|
345
|
+
maxMarkerNodes = 300,
|
|
346
|
+
maxTextScanNodes = 800,
|
|
347
|
+
textMaxLength = 80
|
|
348
|
+
} = {}) {
|
|
349
|
+
if (!client || !rootNodeId) {
|
|
350
|
+
return {
|
|
351
|
+
found: false,
|
|
352
|
+
reason: "missing_client_or_root"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const selectorCounts = {};
|
|
357
|
+
const markerNodeIds = [];
|
|
358
|
+
for (const selector of markerSelectors || []) {
|
|
359
|
+
const nodeIds = await safeQuerySelectorAll(client, rootNodeId, selector);
|
|
360
|
+
selectorCounts[selector] = nodeIds.length;
|
|
361
|
+
markerNodeIds.push(...nodeIds);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const visibleMarkers = [];
|
|
365
|
+
const markerIds = uniqueValues(markerNodeIds).slice(0, Math.max(0, Number(maxMarkerNodes) || 0));
|
|
366
|
+
for (const nodeId of markerIds) {
|
|
367
|
+
const marker = await readVisibleMarkerNode(client, nodeId);
|
|
368
|
+
if (!marker?.text) continue;
|
|
369
|
+
const classified = classifyInfiniteListBottomMarker({
|
|
370
|
+
text: marker.text,
|
|
371
|
+
bottomKeywords,
|
|
372
|
+
loadMoreKeywords
|
|
373
|
+
});
|
|
374
|
+
const summary = {
|
|
375
|
+
node_id: marker.node_id,
|
|
376
|
+
text: marker.text.slice(0, 160),
|
|
377
|
+
y: marker.box?.rect?.y || null,
|
|
378
|
+
matched_bottom_keyword: classified.matched_bottom_keyword,
|
|
379
|
+
matched_load_more_keyword: classified.matched_load_more_keyword
|
|
380
|
+
};
|
|
381
|
+
visibleMarkers.push(summary);
|
|
382
|
+
if (classified.is_bottom) {
|
|
383
|
+
return {
|
|
384
|
+
found: true,
|
|
385
|
+
reason: classified.reason,
|
|
386
|
+
source: "marker_selector",
|
|
387
|
+
marker: summary,
|
|
388
|
+
selector_counts: selectorCounts,
|
|
389
|
+
visible_marker_count: visibleMarkers.length,
|
|
390
|
+
refresh_button_visible: false
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const hasLoadMoreMarker = visibleMarkers.some((marker) => marker.matched_load_more_keyword);
|
|
396
|
+
|
|
397
|
+
const refreshNodeIds = [];
|
|
398
|
+
for (const selector of refreshSelectors || []) {
|
|
399
|
+
const nodeIds = await safeQuerySelectorAll(client, rootNodeId, selector);
|
|
400
|
+
selectorCounts[selector] = (selectorCounts[selector] || 0) + nodeIds.length;
|
|
401
|
+
refreshNodeIds.push(...nodeIds);
|
|
402
|
+
}
|
|
403
|
+
const refreshButtons = [];
|
|
404
|
+
for (const nodeId of uniqueValues(refreshNodeIds).slice(0, 300)) {
|
|
405
|
+
const marker = await readVisibleMarkerNode(client, nodeId);
|
|
406
|
+
if (!marker?.text || !looksLikeRefreshLabel(marker.text)) continue;
|
|
407
|
+
refreshButtons.push({
|
|
408
|
+
node_id: marker.node_id,
|
|
409
|
+
text: marker.text.slice(0, 120),
|
|
410
|
+
y: marker.box?.rect?.y || null
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (refreshButtons.length && !hasLoadMoreMarker) {
|
|
414
|
+
return {
|
|
415
|
+
found: true,
|
|
416
|
+
reason: "refresh_button_visible",
|
|
417
|
+
source: "refresh_button",
|
|
418
|
+
marker: refreshButtons[0],
|
|
419
|
+
selector_counts: selectorCounts,
|
|
420
|
+
visible_marker_count: visibleMarkers.length,
|
|
421
|
+
refresh_button_visible: true,
|
|
422
|
+
refresh_button_count: refreshButtons.length
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const scanNodeIds = [];
|
|
427
|
+
for (const selector of textScanSelectors || []) {
|
|
428
|
+
const nodeIds = await safeQuerySelectorAll(client, rootNodeId, selector);
|
|
429
|
+
selectorCounts[selector] = (selectorCounts[selector] || 0) + nodeIds.length;
|
|
430
|
+
scanNodeIds.push(...nodeIds);
|
|
431
|
+
}
|
|
432
|
+
let checkedTextNodeCount = 0;
|
|
433
|
+
for (const nodeId of uniqueValues(scanNodeIds).slice(0, Math.max(0, Number(maxTextScanNodes) || 0))) {
|
|
434
|
+
const marker = await readVisibleMarkerNode(client, nodeId);
|
|
435
|
+
if (!marker?.text || marker.text.length > textMaxLength) continue;
|
|
436
|
+
checkedTextNodeCount += 1;
|
|
437
|
+
const classified = classifyInfiniteListBottomMarker({
|
|
438
|
+
text: marker.text,
|
|
439
|
+
bottomKeywords,
|
|
440
|
+
loadMoreKeywords
|
|
441
|
+
});
|
|
442
|
+
if (classified.is_bottom) {
|
|
443
|
+
return {
|
|
444
|
+
found: true,
|
|
445
|
+
reason: classified.reason,
|
|
446
|
+
source: "text_scan",
|
|
447
|
+
marker: {
|
|
448
|
+
node_id: marker.node_id,
|
|
449
|
+
text: marker.text.slice(0, 160),
|
|
450
|
+
y: marker.box?.rect?.y || null,
|
|
451
|
+
matched_bottom_keyword: classified.matched_bottom_keyword
|
|
452
|
+
},
|
|
453
|
+
selector_counts: selectorCounts,
|
|
454
|
+
visible_marker_count: visibleMarkers.length,
|
|
455
|
+
checked_text_node_count: checkedTextNodeCount,
|
|
456
|
+
refresh_button_visible: refreshButtons.length > 0,
|
|
457
|
+
refresh_button_count: refreshButtons.length
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
found: false,
|
|
464
|
+
reason: hasLoadMoreMarker ? "load_more_marker_visible" : "bottom_marker_not_found",
|
|
465
|
+
selector_counts: selectorCounts,
|
|
466
|
+
visible_markers: visibleMarkers.slice(0, 20),
|
|
467
|
+
visible_marker_count: visibleMarkers.length,
|
|
468
|
+
checked_text_node_count: checkedTextNodeCount,
|
|
469
|
+
refresh_button_visible: refreshButtons.length > 0,
|
|
470
|
+
refresh_button_count: refreshButtons.length
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
177
474
|
export async function readVisibleInfiniteListItems({
|
|
178
475
|
nodeIds = [],
|
|
179
476
|
readCandidate,
|
|
@@ -333,9 +630,11 @@ export async function getNextInfiniteListCandidate({
|
|
|
333
630
|
state,
|
|
334
631
|
findNodeIds,
|
|
335
632
|
readCandidate,
|
|
633
|
+
detectBottomMarker = null,
|
|
336
634
|
keyForCandidate = candidateKeyFromProfile,
|
|
337
635
|
maxScrolls = 20,
|
|
338
636
|
stableSignatureLimit = 2,
|
|
637
|
+
minScrollsBeforeEnd = 3,
|
|
339
638
|
wheelDeltaY = 850,
|
|
340
639
|
settleMs = 1200,
|
|
341
640
|
fallbackPoint = null
|
|
@@ -383,6 +682,54 @@ export async function getNextInfiniteListCandidate({
|
|
|
383
682
|
return result;
|
|
384
683
|
}
|
|
385
684
|
|
|
685
|
+
if (typeof detectBottomMarker === "function") {
|
|
686
|
+
let bottomMarker = null;
|
|
687
|
+
try {
|
|
688
|
+
bottomMarker = await detectBottomMarker({
|
|
689
|
+
scrollAttempt,
|
|
690
|
+
items,
|
|
691
|
+
signature,
|
|
692
|
+
state: compactInfiniteListState(state)
|
|
693
|
+
});
|
|
694
|
+
} catch (error) {
|
|
695
|
+
bottomMarker = {
|
|
696
|
+
found: false,
|
|
697
|
+
reason: "bottom_marker_probe_failed",
|
|
698
|
+
error: error?.message || String(error)
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
attempts[attempts.length - 1].bottom_marker = bottomMarker;
|
|
702
|
+
if (bottomMarker?.found) {
|
|
703
|
+
state.ledger?.push({
|
|
704
|
+
at: nowIso(),
|
|
705
|
+
event: "bottom_marker_detected",
|
|
706
|
+
reason: bottomMarker.reason || "bottom_marker",
|
|
707
|
+
source: bottomMarker.source || "",
|
|
708
|
+
marker: bottomMarker.marker || null
|
|
709
|
+
});
|
|
710
|
+
const result = {
|
|
711
|
+
ok: false,
|
|
712
|
+
end_reached: true,
|
|
713
|
+
reason: "bottom_marker",
|
|
714
|
+
bottom_marker: bottomMarker,
|
|
715
|
+
attempts,
|
|
716
|
+
state: compactInfiniteListState(state)
|
|
717
|
+
};
|
|
718
|
+
state.last_result = {
|
|
719
|
+
at: nowIso(),
|
|
720
|
+
ok: false,
|
|
721
|
+
end_reached: true,
|
|
722
|
+
reason: result.reason,
|
|
723
|
+
bottom_marker: {
|
|
724
|
+
reason: bottomMarker.reason || null,
|
|
725
|
+
source: bottomMarker.source || null,
|
|
726
|
+
marker: bottomMarker.marker || null
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
return result;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
386
733
|
if (!items.length) {
|
|
387
734
|
const result = {
|
|
388
735
|
ok: false,
|
|
@@ -400,7 +747,9 @@ export async function getNextInfiniteListCandidate({
|
|
|
400
747
|
return result;
|
|
401
748
|
}
|
|
402
749
|
|
|
403
|
-
|
|
750
|
+
const stableLimit = Math.max(1, Number(stableSignatureLimit) || 1);
|
|
751
|
+
const minStableScrolls = Math.max(0, Number(minScrollsBeforeEnd) || 0);
|
|
752
|
+
if (signature.stable_signature_count >= stableLimit && scrollAttempt >= minStableScrolls) {
|
|
404
753
|
const result = {
|
|
405
754
|
ok: false,
|
|
406
755
|
end_reached: true,
|
|
@@ -416,6 +765,10 @@ export async function getNextInfiniteListCandidate({
|
|
|
416
765
|
};
|
|
417
766
|
return result;
|
|
418
767
|
}
|
|
768
|
+
if (signature.stable_signature_count >= stableLimit) {
|
|
769
|
+
attempts[attempts.length - 1].stable_end_deferred = true;
|
|
770
|
+
attempts[attempts.length - 1].min_scrolls_before_end = minStableScrolls;
|
|
771
|
+
}
|
|
419
772
|
|
|
420
773
|
const scrollResult = await scrollInfiniteListByVisibleItems(client, items, {
|
|
421
774
|
wheelDeltaY,
|
|
@@ -8,6 +8,17 @@ export const CHAT_CARD_SELECTORS = Object.freeze([
|
|
|
8
8
|
"div[role=\"listitem\"]"
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
|
+
export const CHAT_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
12
|
+
"div[role=\"tfoot\"] .load-tips",
|
|
13
|
+
"p.load-tips",
|
|
14
|
+
".load-tips",
|
|
15
|
+
".empty-tip",
|
|
16
|
+
".empty-text",
|
|
17
|
+
".no-data",
|
|
18
|
+
"[class*=\"load-tips\"]",
|
|
19
|
+
"[class*=\"empty\"]"
|
|
20
|
+
]);
|
|
21
|
+
|
|
11
22
|
export const CHAT_JOB_LABEL_SELECTORS = Object.freeze([
|
|
12
23
|
".chat-job .chat-select-job",
|
|
13
24
|
".chat-job .dropmenu-label",
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
compactInfiniteListState,
|
|
21
21
|
createInfiniteListState,
|
|
22
|
+
detectInfiniteListBottomMarker,
|
|
22
23
|
getNextInfiniteListCandidate,
|
|
23
24
|
markInfiniteListCandidateProcessed
|
|
24
25
|
} from "../../core/infinite-list/index.js";
|
|
@@ -34,7 +35,10 @@ import {
|
|
|
34
35
|
normalizeText,
|
|
35
36
|
screenCandidate
|
|
36
37
|
} from "../../core/screening/index.js";
|
|
37
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
CHAT_BOTTOM_MARKER_SELECTORS,
|
|
40
|
+
CHAT_TARGET_URL
|
|
41
|
+
} from "./constants.js";
|
|
38
42
|
import {
|
|
39
43
|
chatCandidateKeyFromProfile,
|
|
40
44
|
findChatCandidateNodeIdById,
|
|
@@ -372,9 +376,9 @@ export async function runChatWorkflow({
|
|
|
372
376
|
llmImageDetail = "high",
|
|
373
377
|
screeningMode = "llm",
|
|
374
378
|
listMaxScrolls = 20,
|
|
375
|
-
listStableSignatureLimit =
|
|
379
|
+
listStableSignatureLimit = 5,
|
|
376
380
|
listWheelDeltaY = 850,
|
|
377
|
-
listSettleMs =
|
|
381
|
+
listSettleMs = 2200,
|
|
378
382
|
listFallbackPoint = null,
|
|
379
383
|
imageOutputDir = ""
|
|
380
384
|
} = {}, runControl) {
|
|
@@ -631,6 +635,13 @@ export async function runChatWorkflow({
|
|
|
631
635
|
run_candidate_index: results.length,
|
|
632
636
|
visible_index: visibleIndex
|
|
633
637
|
}
|
|
638
|
+
}),
|
|
639
|
+
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) => detectInfiniteListBottomMarker(client, {
|
|
640
|
+
rootNodeId: rootState?.rootNodes?.top,
|
|
641
|
+
markerSelectors: CHAT_BOTTOM_MARKER_SELECTORS,
|
|
642
|
+
refreshSelectors: [],
|
|
643
|
+
textScanSelectors: scrollAttempt > 0 || (signature?.stable_signature_count || 0) >= 2 ? undefined : [],
|
|
644
|
+
maxTextScanNodes: 500
|
|
634
645
|
})
|
|
635
646
|
}));
|
|
636
647
|
if (!nextCandidateResult.ok) {
|
|
@@ -1137,9 +1148,9 @@ export function createChatRunService({
|
|
|
1137
1148
|
llmImageDetail = "high",
|
|
1138
1149
|
screeningMode = "llm",
|
|
1139
1150
|
listMaxScrolls = 20,
|
|
1140
|
-
listStableSignatureLimit =
|
|
1151
|
+
listStableSignatureLimit = 5,
|
|
1141
1152
|
listWheelDeltaY = 850,
|
|
1142
|
-
listSettleMs =
|
|
1153
|
+
listSettleMs = 2200,
|
|
1143
1154
|
listFallbackPoint = null,
|
|
1144
1155
|
imageOutputDir = "",
|
|
1145
1156
|
name = "chat-domain-run"
|
|
@@ -67,6 +67,17 @@ export const RECOMMEND_END_REFRESH_SELECTOR = [
|
|
|
67
67
|
"a"
|
|
68
68
|
].join(", ");
|
|
69
69
|
|
|
70
|
+
export const RECOMMEND_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
71
|
+
".finished-wrap",
|
|
72
|
+
".no-data-refresh",
|
|
73
|
+
".load-tips",
|
|
74
|
+
".empty-tip",
|
|
75
|
+
".empty-text",
|
|
76
|
+
".no-data",
|
|
77
|
+
"[class*=\"finished\"]",
|
|
78
|
+
"[class*=\"load-tips\"]"
|
|
79
|
+
]);
|
|
80
|
+
|
|
70
81
|
export const DETAIL_POPUP_SELECTORS = Object.freeze([
|
|
71
82
|
".dialog-wrap.active",
|
|
72
83
|
".boss-popup__wrapper",
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
compactInfiniteListState,
|
|
23
23
|
createInfiniteListState,
|
|
24
|
+
detectInfiniteListBottomMarker,
|
|
24
25
|
getNextInfiniteListCandidate,
|
|
25
26
|
markInfiniteListCandidateProcessed,
|
|
26
27
|
resetInfiniteListForRefreshRound
|
|
@@ -54,6 +55,10 @@ import {
|
|
|
54
55
|
normalizeRecommendPageScope,
|
|
55
56
|
selectRecommendPageScope
|
|
56
57
|
} from "./scopes.js";
|
|
58
|
+
import {
|
|
59
|
+
RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
60
|
+
RECOMMEND_END_REFRESH_SELECTOR
|
|
61
|
+
} from "./constants.js";
|
|
57
62
|
import {
|
|
58
63
|
clickRecommendActionControl,
|
|
59
64
|
normalizeRecommendPostAction,
|
|
@@ -66,6 +71,15 @@ function normalizeLabels(labels = []) {
|
|
|
66
71
|
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
function isRefreshableListStall(reason = "") {
|
|
75
|
+
return new Set([
|
|
76
|
+
"stable_visible_signature",
|
|
77
|
+
"max_scrolls_exhausted",
|
|
78
|
+
"scroll_failed",
|
|
79
|
+
"scroll_anchor_unavailable"
|
|
80
|
+
]).has(String(reason || ""));
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
function normalizeFilter(filter = {}) {
|
|
70
84
|
const filterGroups = Array.isArray(filter.filterGroups)
|
|
71
85
|
? filter.filterGroups
|
|
@@ -364,9 +378,9 @@ export async function runRecommendWorkflow({
|
|
|
364
378
|
imageWheelDeltaY = 650,
|
|
365
379
|
cvAcquisitionMode = "unknown",
|
|
366
380
|
listMaxScrolls = 20,
|
|
367
|
-
listStableSignatureLimit =
|
|
381
|
+
listStableSignatureLimit = 5,
|
|
368
382
|
listWheelDeltaY = 850,
|
|
369
|
-
listSettleMs =
|
|
383
|
+
listSettleMs = 2200,
|
|
370
384
|
listFallbackPoint = null,
|
|
371
385
|
refreshOnEnd = true,
|
|
372
386
|
maxRefreshRounds = 2,
|
|
@@ -559,15 +573,22 @@ export async function runRecommendWorkflow({
|
|
|
559
573
|
run_candidate_index: results.length,
|
|
560
574
|
visible_index: visibleIndex
|
|
561
575
|
}
|
|
576
|
+
}),
|
|
577
|
+
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) => detectInfiniteListBottomMarker(client, {
|
|
578
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
579
|
+
markerSelectors: RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
580
|
+
refreshSelectors: [RECOMMEND_END_REFRESH_SELECTOR],
|
|
581
|
+
textScanSelectors: scrollAttempt > 0 || (signature?.stable_signature_count || 0) >= 2 ? undefined : [],
|
|
582
|
+
maxTextScanNodes: 500
|
|
562
583
|
})
|
|
563
584
|
}));
|
|
564
585
|
if (!nextCandidateResult.ok) {
|
|
565
586
|
listEndReason = nextCandidateResult.reason || "list_exhausted";
|
|
566
587
|
if (
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
588
|
+
(nextCandidateResult.end_reached || isRefreshableListStall(nextCandidateResult.reason))
|
|
589
|
+
&& refreshOnEnd
|
|
590
|
+
&& results.length < limit
|
|
591
|
+
&& refreshRounds < Math.max(0, Number(maxRefreshRounds) || 0)
|
|
571
592
|
) {
|
|
572
593
|
await runControl.waitIfPaused();
|
|
573
594
|
runControl.throwIfCanceled();
|
|
@@ -939,9 +960,9 @@ export function createRecommendRunService({
|
|
|
939
960
|
imageWheelDeltaY = 650,
|
|
940
961
|
cvAcquisitionMode = "unknown",
|
|
941
962
|
listMaxScrolls = 20,
|
|
942
|
-
listStableSignatureLimit =
|
|
963
|
+
listStableSignatureLimit = 5,
|
|
943
964
|
listWheelDeltaY = 850,
|
|
944
|
-
listSettleMs =
|
|
965
|
+
listSettleMs = 2200,
|
|
945
966
|
listFallbackPoint = null,
|
|
946
967
|
refreshOnEnd = true,
|
|
947
968
|
maxRefreshRounds = 2,
|
|
@@ -24,6 +24,29 @@ export const RECRUIT_NO_DATA_SELECTORS = Object.freeze([
|
|
|
24
24
|
'[class*="empty"]'
|
|
25
25
|
]);
|
|
26
26
|
|
|
27
|
+
export const RECRUIT_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
28
|
+
".finished-wrap",
|
|
29
|
+
".load-tips",
|
|
30
|
+
".tip-nodata",
|
|
31
|
+
".empty-tip",
|
|
32
|
+
".empty-text",
|
|
33
|
+
".no-data",
|
|
34
|
+
"[class*=\"finished\"]",
|
|
35
|
+
"[class*=\"load-tips\"]",
|
|
36
|
+
"[class*=\"empty\"]"
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
export const RECRUIT_BOTTOM_REFRESH_SELECTORS = Object.freeze([
|
|
40
|
+
".finished-wrap .btn-refresh",
|
|
41
|
+
".finished-wrap .btn",
|
|
42
|
+
".no-data-refresh .btn-refresh",
|
|
43
|
+
".no-data-refresh .btn",
|
|
44
|
+
"[class*=\"refresh\"]",
|
|
45
|
+
"[ka*=\"refresh\"]",
|
|
46
|
+
"button",
|
|
47
|
+
"a"
|
|
48
|
+
]);
|
|
49
|
+
|
|
27
50
|
export const RECRUIT_SEARCH_SELECTORS = Object.freeze({
|
|
28
51
|
keywordInput: [
|
|
29
52
|
"input.search-input",
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
compactInfiniteListState,
|
|
21
21
|
createInfiniteListState,
|
|
22
|
+
detectInfiniteListBottomMarker,
|
|
22
23
|
getNextInfiniteListCandidate,
|
|
23
24
|
markInfiniteListCandidateProcessed,
|
|
24
25
|
resetInfiniteListForRefreshRound
|
|
@@ -49,6 +50,10 @@ import {
|
|
|
49
50
|
} from "./search.js";
|
|
50
51
|
import { refreshRecruitSearchAtEnd } from "./refresh.js";
|
|
51
52
|
import { getRecruitRoots } from "./roots.js";
|
|
53
|
+
import {
|
|
54
|
+
RECRUIT_BOTTOM_MARKER_SELECTORS,
|
|
55
|
+
RECRUIT_BOTTOM_REFRESH_SELECTORS
|
|
56
|
+
} from "./constants.js";
|
|
52
57
|
|
|
53
58
|
function compactScreening(screening) {
|
|
54
59
|
return {
|
|
@@ -144,9 +149,9 @@ export async function runRecruitWorkflow({
|
|
|
144
149
|
imageWheelDeltaY = 650,
|
|
145
150
|
cvAcquisitionMode = "unknown",
|
|
146
151
|
listMaxScrolls = 20,
|
|
147
|
-
listStableSignatureLimit =
|
|
152
|
+
listStableSignatureLimit = 5,
|
|
148
153
|
listWheelDeltaY = 850,
|
|
149
|
-
listSettleMs =
|
|
154
|
+
listSettleMs = 2200,
|
|
150
155
|
listFallbackPoint = null,
|
|
151
156
|
refreshOnEnd = true,
|
|
152
157
|
maxRefreshRounds = 2,
|
|
@@ -298,6 +303,13 @@ export async function runRecruitWorkflow({
|
|
|
298
303
|
visible_index: visibleIndex,
|
|
299
304
|
search_params: normalizedSearchParams
|
|
300
305
|
}
|
|
306
|
+
}),
|
|
307
|
+
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) => detectInfiniteListBottomMarker(client, {
|
|
308
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
309
|
+
markerSelectors: RECRUIT_BOTTOM_MARKER_SELECTORS,
|
|
310
|
+
refreshSelectors: RECRUIT_BOTTOM_REFRESH_SELECTORS,
|
|
311
|
+
textScanSelectors: scrollAttempt > 0 || (signature?.stable_signature_count || 0) >= 2 ? undefined : [],
|
|
312
|
+
maxTextScanNodes: 500
|
|
301
313
|
})
|
|
302
314
|
}));
|
|
303
315
|
if (!nextCandidateResult.ok) {
|
|
@@ -625,9 +637,9 @@ export function createRecruitRunService({
|
|
|
625
637
|
imageWheelDeltaY = 650,
|
|
626
638
|
cvAcquisitionMode = "unknown",
|
|
627
639
|
listMaxScrolls = 20,
|
|
628
|
-
listStableSignatureLimit =
|
|
640
|
+
listStableSignatureLimit = 5,
|
|
629
641
|
listWheelDeltaY = 850,
|
|
630
|
-
listSettleMs =
|
|
642
|
+
listSettleMs = 2200,
|
|
631
643
|
listFallbackPoint = null,
|
|
632
644
|
refreshOnEnd = true,
|
|
633
645
|
maxRefreshRounds = 2,
|