@reconcrap/boss-recommend-mcp 1.3.32 → 1.3.34
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/config/screening-config.example.json +11 -11
- package/package.json +64 -64
- package/src/boss-chat.js +769 -769
- package/src/test-adapters-runtime.js +628 -628
- package/src/test-boss-chat.js +2732 -2217
- package/vendor/boss-chat-cli/src/app.js +1435 -1268
- package/vendor/boss-chat-cli/src/browser/chat-page.js +441 -242
- package/vendor/boss-chat-cli/src/cli.js +1580 -1580
- package/vendor/boss-chat-cli/src/services/chrome-client.js +104 -100
- package/vendor/boss-chat-cli/src/services/llm.js +1146 -810
- package/vendor/boss-chat-cli/src/services/llm.test.js +326 -0
- package/vendor/boss-chat-cli/src/services/profile-store.js +168 -168
- package/vendor/boss-chat-cli/src/services/report-store.js +317 -317
- package/vendor/boss-chat-cli/src/services/resume-capture.js +469 -469
- package/vendor/boss-chat-cli/src/services/resume-network.js +727 -727
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +6660 -6272
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +429 -31
|
@@ -921,6 +921,21 @@ function browserConversationReadyState() {
|
|
|
921
921
|
const askResume = Array.from(document.querySelectorAll('span.operate-btn, button, a, span')).find(
|
|
922
922
|
(el) => isVisible(el) && isAskResumeText(el.textContent || ''),
|
|
923
923
|
);
|
|
924
|
+
const editor = document.querySelector(
|
|
925
|
+
'#boss-chat-editor-input, .conversation-editor #boss-chat-editor-input, .conversation-editor .boss-chat-editor-input',
|
|
926
|
+
);
|
|
927
|
+
const activeSubmit = Array.from(
|
|
928
|
+
document.querySelectorAll(
|
|
929
|
+
'.conversation-editor .submit.active, .conversation-editor .submit-content .submit.active, .submit.active',
|
|
930
|
+
),
|
|
931
|
+
).find((node) => isVisible(node));
|
|
932
|
+
const anySubmit = Array.from(
|
|
933
|
+
document.querySelectorAll(
|
|
934
|
+
'.conversation-editor .submit-content .submit, .conversation-editor .submit, .submit-content .submit, .submit',
|
|
935
|
+
),
|
|
936
|
+
).find((node) => isVisible(node) && normalize(node.textContent || '').includes('发送'));
|
|
937
|
+
const resumeState = browserIsResumeModalOpen();
|
|
938
|
+
const detailState = browserCollectCandidateDetailSnapshot();
|
|
924
939
|
const attachmentResumeEnabled = Boolean(attachmentResume) && !isDisabledDeep(attachmentResume);
|
|
925
940
|
return {
|
|
926
941
|
hasOnlineResume: Boolean(onlineResume),
|
|
@@ -929,6 +944,345 @@ function browserConversationReadyState() {
|
|
|
929
944
|
hasAttachmentResume: Boolean(attachmentResume),
|
|
930
945
|
attachmentResumeEnabled,
|
|
931
946
|
attachmentResumeClass: String(attachmentResume?.className || ''),
|
|
947
|
+
resumeModalOpen:
|
|
948
|
+
Boolean(resumeState?.open) ||
|
|
949
|
+
Number(resumeState?.iframeCount || 0) > 0 ||
|
|
950
|
+
Number(resumeState?.scopeCount || 0) > 0,
|
|
951
|
+
candidateDetailOpen:
|
|
952
|
+
Boolean(detailState?.open) ||
|
|
953
|
+
Number(detailState?.panelCount || 0) > 0 ||
|
|
954
|
+
Number(detailState?.closeCount || 0) > 0,
|
|
955
|
+
panelsClosed:
|
|
956
|
+
!(
|
|
957
|
+
Boolean(resumeState?.open) ||
|
|
958
|
+
Number(resumeState?.iframeCount || 0) > 0 ||
|
|
959
|
+
Number(resumeState?.scopeCount || 0) > 0 ||
|
|
960
|
+
Boolean(detailState?.open) ||
|
|
961
|
+
Number(detailState?.panelCount || 0) > 0 ||
|
|
962
|
+
Number(detailState?.closeCount || 0) > 0
|
|
963
|
+
),
|
|
964
|
+
editorVisible: editor instanceof HTMLElement && isVisible(editor),
|
|
965
|
+
activeSubmit: Boolean(activeSubmit),
|
|
966
|
+
hasAnySubmit: Boolean(anySubmit),
|
|
967
|
+
messageInputReady:
|
|
968
|
+
editor instanceof HTMLElement &&
|
|
969
|
+
isVisible(editor) &&
|
|
970
|
+
(Boolean(activeSubmit) || Boolean(anySubmit)),
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function browserNormalizeVisibleText(value) {
|
|
975
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function browserIsVisibleElement(el, minimum = 2) {
|
|
979
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
980
|
+
const style = getComputedStyle(el);
|
|
981
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.01) {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
const rect = el.getBoundingClientRect();
|
|
985
|
+
return rect.width > minimum && rect.height > minimum;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function browserRectToJson(rect) {
|
|
989
|
+
return {
|
|
990
|
+
left: rect.left,
|
|
991
|
+
top: rect.top,
|
|
992
|
+
width: rect.width,
|
|
993
|
+
height: rect.height,
|
|
994
|
+
right: rect.right,
|
|
995
|
+
bottom: rect.bottom,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function browserCollectCandidateDetailSnapshot() {
|
|
1000
|
+
const normalize = browserNormalizeVisibleText;
|
|
1001
|
+
const isVisible = browserIsVisibleElement;
|
|
1002
|
+
const rectToJson = browserRectToJson;
|
|
1003
|
+
const viewportWidth = Math.max(document.documentElement?.clientWidth || 0, window.innerWidth || 0);
|
|
1004
|
+
const viewportHeight = Math.max(document.documentElement?.clientHeight || 0, window.innerHeight || 0);
|
|
1005
|
+
const panelSelectors = [
|
|
1006
|
+
'.base-info-single-top-detail',
|
|
1007
|
+
'.resume-detail-wrap',
|
|
1008
|
+
'.geek-card-detail',
|
|
1009
|
+
'.candidate-detail-wrap',
|
|
1010
|
+
'.chat-detail-wrap',
|
|
1011
|
+
'.new-resume-online-main-ui',
|
|
1012
|
+
'.resume-recommend.resume-common-wrap',
|
|
1013
|
+
'.resume-recommend',
|
|
1014
|
+
'.boss-dialog__body',
|
|
1015
|
+
];
|
|
1016
|
+
const overlaySelectors = [
|
|
1017
|
+
'.dialog-wrap.active',
|
|
1018
|
+
'.dialog-wrap',
|
|
1019
|
+
'.boss-dialog.active',
|
|
1020
|
+
'.boss-dialog',
|
|
1021
|
+
'.boss-popup__wrapper',
|
|
1022
|
+
'.v-modal',
|
|
1023
|
+
'.modal-mask',
|
|
1024
|
+
'.geek-detail-modal',
|
|
1025
|
+
'.modal',
|
|
1026
|
+
];
|
|
1027
|
+
const closeButtons = Array.from(document.querySelectorAll('.close-btn')).filter((el) => isVisible(el));
|
|
1028
|
+
const contentEntries = [];
|
|
1029
|
+
const overlayEntries = [];
|
|
1030
|
+
const contentSeen = new Set();
|
|
1031
|
+
const overlaySeen = new Set();
|
|
1032
|
+
|
|
1033
|
+
const pushEntry = (entries, seen, node, source, { minWidth = 240, minHeight = 160 } = {}) => {
|
|
1034
|
+
if (!(node instanceof HTMLElement) || !isVisible(node)) return;
|
|
1035
|
+
const rect = node.getBoundingClientRect();
|
|
1036
|
+
if (rect.width < minWidth || rect.height < minHeight) return;
|
|
1037
|
+
const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}:${normalize(node.className || '')}`;
|
|
1038
|
+
if (seen.has(key)) return;
|
|
1039
|
+
seen.add(key);
|
|
1040
|
+
entries.push({ node, rect, source });
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
const hasContentHint = (node, rect, source = '') => {
|
|
1044
|
+
const classText = normalize(node.className || '').toLowerCase();
|
|
1045
|
+
const text = normalize(node.textContent || '').slice(0, 240).toLowerCase();
|
|
1046
|
+
const containsClose = closeButtons.some((button) => node.contains(button));
|
|
1047
|
+
const anchoredRight =
|
|
1048
|
+
rect.left >= viewportWidth * 0.35 ||
|
|
1049
|
+
rect.right >= viewportWidth * 0.68;
|
|
1050
|
+
const hasKnownContentClass =
|
|
1051
|
+
classText.includes('base-info-single-top-detail') ||
|
|
1052
|
+
classText.includes('resume-detail-wrap') ||
|
|
1053
|
+
classText.includes('candidate-detail') ||
|
|
1054
|
+
classText.includes('chat-detail') ||
|
|
1055
|
+
classText.includes('geek-card-detail') ||
|
|
1056
|
+
classText.includes('new-resume-online-main-ui') ||
|
|
1057
|
+
classText.includes('resume-common-wrap') ||
|
|
1058
|
+
classText.includes('boss-dialog__body');
|
|
1059
|
+
const hasDetailHint =
|
|
1060
|
+
text.includes('在线简历') ||
|
|
1061
|
+
text.includes('附件简历') ||
|
|
1062
|
+
text.includes('牛人分析器') ||
|
|
1063
|
+
text.includes('活跃');
|
|
1064
|
+
const notFullScreen =
|
|
1065
|
+
rect.width < viewportWidth * 0.96 ||
|
|
1066
|
+
rect.height < viewportHeight * 0.96;
|
|
1067
|
+
return (
|
|
1068
|
+
containsClose ||
|
|
1069
|
+
hasKnownContentClass ||
|
|
1070
|
+
hasDetailHint ||
|
|
1071
|
+
(source === 'close-ancestor' && (anchoredRight || notFullScreen))
|
|
1072
|
+
);
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
const hasOverlayHint = (node, rect) => {
|
|
1076
|
+
const classText = normalize(node.className || '').toLowerCase();
|
|
1077
|
+
const hasOverlayClass =
|
|
1078
|
+
classText.includes('dialog-wrap') ||
|
|
1079
|
+
classText.includes('boss-dialog') ||
|
|
1080
|
+
classText.includes('popup') ||
|
|
1081
|
+
classText.includes('modal') ||
|
|
1082
|
+
classText.includes('mask') ||
|
|
1083
|
+
classText.includes('overlay');
|
|
1084
|
+
const coversViewport =
|
|
1085
|
+
rect.left <= 8 &&
|
|
1086
|
+
rect.top <= 8 &&
|
|
1087
|
+
rect.width >= viewportWidth * 0.85 &&
|
|
1088
|
+
rect.height >= viewportHeight * 0.85;
|
|
1089
|
+
return hasOverlayClass || coversViewport;
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
for (const selector of panelSelectors) {
|
|
1093
|
+
for (const node of Array.from(document.querySelectorAll(selector))) {
|
|
1094
|
+
pushEntry(contentEntries, contentSeen, node, `selector:${selector}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
for (const selector of overlaySelectors) {
|
|
1099
|
+
for (const node of Array.from(document.querySelectorAll(selector))) {
|
|
1100
|
+
pushEntry(overlayEntries, overlaySeen, node, `selector:${selector}`, {
|
|
1101
|
+
minWidth: 180,
|
|
1102
|
+
minHeight: 120,
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
for (const closeButton of closeButtons) {
|
|
1108
|
+
let current = closeButton.parentElement;
|
|
1109
|
+
let depth = 0;
|
|
1110
|
+
while (current instanceof HTMLElement && depth < 12) {
|
|
1111
|
+
const rect = current.getBoundingClientRect();
|
|
1112
|
+
if (hasContentHint(current, rect, 'close-ancestor')) {
|
|
1113
|
+
pushEntry(contentEntries, contentSeen, current, 'close-ancestor');
|
|
1114
|
+
}
|
|
1115
|
+
if (hasOverlayHint(current, rect)) {
|
|
1116
|
+
pushEntry(overlayEntries, overlaySeen, current, 'close-ancestor', {
|
|
1117
|
+
minWidth: 180,
|
|
1118
|
+
minHeight: 120,
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
current = current.parentElement;
|
|
1122
|
+
depth += 1;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const scoredContent = contentEntries
|
|
1127
|
+
.map((entry) => {
|
|
1128
|
+
const classText = normalize(entry.node.className || '').toLowerCase();
|
|
1129
|
+
const text = normalize(entry.node.textContent || '').slice(0, 240).toLowerCase();
|
|
1130
|
+
const containsClose = closeButtons.some((button) => entry.node.contains(button));
|
|
1131
|
+
const anchoredRight =
|
|
1132
|
+
entry.rect.left >= viewportWidth * 0.35 ||
|
|
1133
|
+
entry.rect.right >= viewportWidth * 0.68;
|
|
1134
|
+
const hasKnownContentClass =
|
|
1135
|
+
classText.includes('base-info-single-top-detail') ||
|
|
1136
|
+
classText.includes('resume-detail-wrap') ||
|
|
1137
|
+
classText.includes('candidate-detail') ||
|
|
1138
|
+
classText.includes('chat-detail') ||
|
|
1139
|
+
classText.includes('geek-card-detail') ||
|
|
1140
|
+
classText.includes('new-resume-online-main-ui') ||
|
|
1141
|
+
classText.includes('resume-common-wrap') ||
|
|
1142
|
+
classText.includes('boss-dialog__body');
|
|
1143
|
+
const hasDetailHint =
|
|
1144
|
+
text.includes('在线简历') ||
|
|
1145
|
+
text.includes('附件简历') ||
|
|
1146
|
+
text.includes('牛人分析器') ||
|
|
1147
|
+
text.includes('活跃');
|
|
1148
|
+
const resemblesFullscreen =
|
|
1149
|
+
entry.rect.left <= 8 &&
|
|
1150
|
+
entry.rect.top <= 8 &&
|
|
1151
|
+
entry.rect.width >= viewportWidth * 0.92 &&
|
|
1152
|
+
entry.rect.height >= viewportHeight * 0.92;
|
|
1153
|
+
|
|
1154
|
+
let score = 0;
|
|
1155
|
+
if (containsClose) score += 220;
|
|
1156
|
+
if (anchoredRight) score += 150;
|
|
1157
|
+
if (hasKnownContentClass) score += 200;
|
|
1158
|
+
if (hasDetailHint) score += 80;
|
|
1159
|
+
if (entry.source === 'close-ancestor') score += 40;
|
|
1160
|
+
if (entry.rect.width <= viewportWidth * 0.82) score += 70;
|
|
1161
|
+
if (entry.rect.height <= viewportHeight * 0.98) score += 20;
|
|
1162
|
+
if (resemblesFullscreen) score -= 220;
|
|
1163
|
+
score += Math.min(140, Math.floor((entry.rect.width * entry.rect.height) / 18000));
|
|
1164
|
+
|
|
1165
|
+
return {
|
|
1166
|
+
...entry,
|
|
1167
|
+
score,
|
|
1168
|
+
};
|
|
1169
|
+
})
|
|
1170
|
+
.filter((entry) => entry.score > 0)
|
|
1171
|
+
.sort((a, b) => b.score - a.score);
|
|
1172
|
+
|
|
1173
|
+
const scoredOverlay = overlayEntries
|
|
1174
|
+
.map((entry) => {
|
|
1175
|
+
const classText = normalize(entry.node.className || '').toLowerCase();
|
|
1176
|
+
const coversViewport =
|
|
1177
|
+
entry.rect.left <= 8 &&
|
|
1178
|
+
entry.rect.top <= 8 &&
|
|
1179
|
+
entry.rect.width >= viewportWidth * 0.85 &&
|
|
1180
|
+
entry.rect.height >= viewportHeight * 0.85;
|
|
1181
|
+
let score = 0;
|
|
1182
|
+
if (classText.includes('dialog-wrap')) score += 160;
|
|
1183
|
+
if (classText.includes('boss-dialog')) score += 120;
|
|
1184
|
+
if (classText.includes('modal') || classText.includes('overlay') || classText.includes('mask')) {
|
|
1185
|
+
score += 80;
|
|
1186
|
+
}
|
|
1187
|
+
if (coversViewport) score += 180;
|
|
1188
|
+
if (entry.source === 'close-ancestor') score += 20;
|
|
1189
|
+
return {
|
|
1190
|
+
...entry,
|
|
1191
|
+
score,
|
|
1192
|
+
};
|
|
1193
|
+
})
|
|
1194
|
+
.filter((entry) => entry.score > 0)
|
|
1195
|
+
.sort((a, b) => b.score - a.score);
|
|
1196
|
+
|
|
1197
|
+
const topContent = scoredContent[0] || null;
|
|
1198
|
+
const topOverlay = scoredOverlay[0] || null;
|
|
1199
|
+
const topContentNode = topContent?.node || null;
|
|
1200
|
+
const topOverlayNode = topOverlay?.node || null;
|
|
1201
|
+
const closeButton =
|
|
1202
|
+
closeButtons.find((button) => topContentNode instanceof HTMLElement && topContentNode.contains(button)) ||
|
|
1203
|
+
closeButtons[0] ||
|
|
1204
|
+
null;
|
|
1205
|
+
const topPanel = topContent || topOverlay;
|
|
1206
|
+
const topPanelNode = topPanel?.node || null;
|
|
1207
|
+
|
|
1208
|
+
return {
|
|
1209
|
+
open: Boolean(topContent || topOverlay || closeButton),
|
|
1210
|
+
panelCount: scoredContent.length,
|
|
1211
|
+
closeCount: closeButtons.length,
|
|
1212
|
+
topPanelClass: normalize(topPanelNode?.className || ''),
|
|
1213
|
+
topPanelScore: Number(topPanel?.score || 0),
|
|
1214
|
+
panelRect: topContent ? rectToJson(topContent.rect) : topOverlay ? rectToJson(topOverlay.rect) : null,
|
|
1215
|
+
closeRect: closeButton ? rectToJson(closeButton.getBoundingClientRect()) : null,
|
|
1216
|
+
overlayClass: normalize(topOverlayNode?.className || ''),
|
|
1217
|
+
overlayRect: topOverlay ? rectToJson(topOverlay.rect) : null,
|
|
1218
|
+
contentClass: normalize(topContentNode?.className || ''),
|
|
1219
|
+
contentRect: topContent ? rectToJson(topContent.rect) : null,
|
|
1220
|
+
closeButton,
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function browserFindCandidateDetailOutsideClickPoint() {
|
|
1225
|
+
const state = browserCollectCandidateDetailSnapshot();
|
|
1226
|
+
const viewportWidth = Math.max(document.documentElement?.clientWidth || 0, window.innerWidth || 0);
|
|
1227
|
+
const viewportHeight = Math.max(document.documentElement?.clientHeight || 0, window.innerHeight || 0);
|
|
1228
|
+
const contentRect = state?.contentRect;
|
|
1229
|
+
if (!state?.open || !contentRect) {
|
|
1230
|
+
return {
|
|
1231
|
+
ok: false,
|
|
1232
|
+
error: 'CANDIDATE_DETAIL_OUTSIDE_POINT_NOT_FOUND',
|
|
1233
|
+
state,
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
1238
|
+
const candidates = [];
|
|
1239
|
+
const safeMidY = clamp(contentRect.top + contentRect.height / 2, 18, viewportHeight - 18);
|
|
1240
|
+
const safeMidX = clamp(contentRect.left + contentRect.width / 2, 18, viewportWidth - 18);
|
|
1241
|
+
|
|
1242
|
+
if (contentRect.left >= 32) {
|
|
1243
|
+
candidates.push({
|
|
1244
|
+
strategy: 'left-gap',
|
|
1245
|
+
x: clamp(Math.min(contentRect.left - 18, contentRect.left / 2), 18, viewportWidth - 18),
|
|
1246
|
+
y: safeMidY,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
if (viewportWidth - contentRect.right >= 32) {
|
|
1250
|
+
candidates.push({
|
|
1251
|
+
strategy: 'right-gap',
|
|
1252
|
+
x: clamp(Math.max(contentRect.right + 18, contentRect.right + (viewportWidth - contentRect.right) / 2), 18, viewportWidth - 18),
|
|
1253
|
+
y: safeMidY,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
if (contentRect.top >= 32) {
|
|
1257
|
+
candidates.push({
|
|
1258
|
+
strategy: 'top-gap',
|
|
1259
|
+
x: safeMidX,
|
|
1260
|
+
y: clamp(Math.min(contentRect.top - 18, contentRect.top / 2), 18, viewportHeight - 18),
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const point = candidates.find((candidate) => {
|
|
1265
|
+
const outsideHorizontally = candidate.x < contentRect.left - 6 || candidate.x > contentRect.right + 6;
|
|
1266
|
+
const outsideVertically = candidate.y < contentRect.top - 6 || candidate.y > contentRect.bottom + 6;
|
|
1267
|
+
return outsideHorizontally || outsideVertically;
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
if (!point) {
|
|
1271
|
+
return {
|
|
1272
|
+
ok: false,
|
|
1273
|
+
error: 'CANDIDATE_DETAIL_OUTSIDE_POINT_NOT_FOUND',
|
|
1274
|
+
state,
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
return {
|
|
1279
|
+
ok: true,
|
|
1280
|
+
strategy: point.strategy,
|
|
1281
|
+
point: {
|
|
1282
|
+
x: Math.round(point.x),
|
|
1283
|
+
y: Math.round(point.y),
|
|
1284
|
+
},
|
|
1285
|
+
state,
|
|
932
1286
|
};
|
|
933
1287
|
}
|
|
934
1288
|
|
|
@@ -1012,226 +1366,10 @@ function browserOpenOnlineResume(options = {}) {
|
|
|
1012
1366
|
}
|
|
1013
1367
|
|
|
1014
1368
|
function browserIsCandidateDetailOpen() {
|
|
1015
|
-
|
|
1016
|
-
const normalize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
|
|
1017
|
-
const isVisible = (el) => {
|
|
1018
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
1019
|
-
const style = getComputedStyle(el);
|
|
1020
|
-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.01) {
|
|
1021
|
-
return false;
|
|
1022
|
-
}
|
|
1023
|
-
const rect = el.getBoundingClientRect();
|
|
1024
|
-
return rect.width > 2 && rect.height > 2;
|
|
1025
|
-
};
|
|
1026
|
-
const rectToJson = (rect) => ({
|
|
1027
|
-
left: rect.left,
|
|
1028
|
-
top: rect.top,
|
|
1029
|
-
width: rect.width,
|
|
1030
|
-
height: rect.height,
|
|
1031
|
-
right: rect.right,
|
|
1032
|
-
bottom: rect.bottom,
|
|
1033
|
-
});
|
|
1034
|
-
const panelSelectors = [
|
|
1035
|
-
'.base-info-single-top-detail',
|
|
1036
|
-
'.resume-detail-wrap',
|
|
1037
|
-
'.geek-card-detail',
|
|
1038
|
-
'.candidate-detail-wrap',
|
|
1039
|
-
'.chat-detail-wrap',
|
|
1040
|
-
];
|
|
1041
|
-
const closeButtons = Array.from(document.querySelectorAll('.close-btn')).filter(isVisible);
|
|
1042
|
-
const panelEntries = [];
|
|
1043
|
-
const seen = new Set();
|
|
1044
|
-
const pushPanel = (node, source) => {
|
|
1045
|
-
if (!(node instanceof HTMLElement) || !isVisible(node)) return;
|
|
1046
|
-
const rect = node.getBoundingClientRect();
|
|
1047
|
-
if (rect.width < 240 || rect.height < 160) return;
|
|
1048
|
-
const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}:${normalize(node.className || '')}`;
|
|
1049
|
-
if (seen.has(key)) return;
|
|
1050
|
-
seen.add(key);
|
|
1051
|
-
panelEntries.push({ node, rect, source });
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
|
-
for (const selector of panelSelectors) {
|
|
1055
|
-
for (const node of Array.from(document.querySelectorAll(selector))) {
|
|
1056
|
-
pushPanel(node, `selector:${selector}`);
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
for (const closeButton of closeButtons) {
|
|
1061
|
-
let current = closeButton.parentElement;
|
|
1062
|
-
let depth = 0;
|
|
1063
|
-
while (current instanceof HTMLElement && depth < 10) {
|
|
1064
|
-
pushPanel(current, 'close-ancestor');
|
|
1065
|
-
current = current.parentElement;
|
|
1066
|
-
depth += 1;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
const scoredPanels = panelEntries
|
|
1071
|
-
.map((entry) => {
|
|
1072
|
-
const classText = normalize(entry.node.className || '').toLowerCase();
|
|
1073
|
-
const text = normalize(entry.node.textContent || '').slice(0, 240).toLowerCase();
|
|
1074
|
-
const containsClose = closeButtons.some((button) => entry.node.contains(button));
|
|
1075
|
-
const anchoredRight =
|
|
1076
|
-
entry.rect.left >= window.innerWidth * 0.4 ||
|
|
1077
|
-
entry.rect.right >= window.innerWidth * 0.72;
|
|
1078
|
-
const hasKnownDetailClass =
|
|
1079
|
-
classText.includes('base-info-single-top-detail') ||
|
|
1080
|
-
classText.includes('resume-detail-wrap') ||
|
|
1081
|
-
classText.includes('candidate-detail') ||
|
|
1082
|
-
classText.includes('chat-detail') ||
|
|
1083
|
-
classText.includes('geek-card-detail');
|
|
1084
|
-
const hasDetailHint =
|
|
1085
|
-
text.includes('在线简历') ||
|
|
1086
|
-
text.includes('附件简历') ||
|
|
1087
|
-
text.includes('牛人分析器') ||
|
|
1088
|
-
text.includes('活跃');
|
|
1089
|
-
|
|
1090
|
-
let score = 0;
|
|
1091
|
-
if (containsClose) score += 220;
|
|
1092
|
-
if (anchoredRight) score += 140;
|
|
1093
|
-
if (hasKnownDetailClass) score += 160;
|
|
1094
|
-
if (hasDetailHint) score += 80;
|
|
1095
|
-
if (entry.source === 'close-ancestor') score += 40;
|
|
1096
|
-
score += Math.min(180, Math.floor((entry.rect.width * entry.rect.height) / 12000));
|
|
1097
|
-
|
|
1098
|
-
return {
|
|
1099
|
-
...entry,
|
|
1100
|
-
score,
|
|
1101
|
-
};
|
|
1102
|
-
})
|
|
1103
|
-
.sort((a, b) => b.score - a.score);
|
|
1104
|
-
|
|
1105
|
-
const topPanel = scoredPanels[0] || null;
|
|
1106
|
-
const topPanelNode = topPanel?.node || null;
|
|
1107
|
-
const closeButton =
|
|
1108
|
-
closeButtons.find((button) => topPanelNode instanceof HTMLElement && topPanelNode.contains(button)) ||
|
|
1109
|
-
closeButtons[0] ||
|
|
1110
|
-
null;
|
|
1111
|
-
|
|
1112
|
-
return {
|
|
1113
|
-
open: Boolean(topPanel || closeButton),
|
|
1114
|
-
panelCount: scoredPanels.length,
|
|
1115
|
-
closeCount: closeButtons.length,
|
|
1116
|
-
topPanelClass: normalize(topPanelNode?.className || ''),
|
|
1117
|
-
topPanelScore: Number(topPanel?.score || 0),
|
|
1118
|
-
panelRect: topPanel ? rectToJson(topPanel.rect) : null,
|
|
1119
|
-
closeRect: closeButton ? rectToJson(closeButton.getBoundingClientRect()) : null,
|
|
1120
|
-
};
|
|
1121
|
-
};
|
|
1122
|
-
|
|
1123
|
-
return collectSnapshot();
|
|
1369
|
+
return browserCollectCandidateDetailSnapshot();
|
|
1124
1370
|
}
|
|
1125
1371
|
|
|
1126
1372
|
function browserCloseCandidateDetailDomOnce() {
|
|
1127
|
-
const collectSnapshot = () => {
|
|
1128
|
-
const normalize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
|
|
1129
|
-
const isVisible = (el) => {
|
|
1130
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
1131
|
-
const style = getComputedStyle(el);
|
|
1132
|
-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.01) {
|
|
1133
|
-
return false;
|
|
1134
|
-
}
|
|
1135
|
-
const rect = el.getBoundingClientRect();
|
|
1136
|
-
return rect.width > 2 && rect.height > 2;
|
|
1137
|
-
};
|
|
1138
|
-
const rectToJson = (rect) => ({
|
|
1139
|
-
left: rect.left,
|
|
1140
|
-
top: rect.top,
|
|
1141
|
-
width: rect.width,
|
|
1142
|
-
height: rect.height,
|
|
1143
|
-
right: rect.right,
|
|
1144
|
-
bottom: rect.bottom,
|
|
1145
|
-
});
|
|
1146
|
-
const panelSelectors = [
|
|
1147
|
-
'.base-info-single-top-detail',
|
|
1148
|
-
'.resume-detail-wrap',
|
|
1149
|
-
'.geek-card-detail',
|
|
1150
|
-
'.candidate-detail-wrap',
|
|
1151
|
-
'.chat-detail-wrap',
|
|
1152
|
-
];
|
|
1153
|
-
const closeButtons = Array.from(document.querySelectorAll('.close-btn')).filter(isVisible);
|
|
1154
|
-
const panelEntries = [];
|
|
1155
|
-
const seen = new Set();
|
|
1156
|
-
const pushPanel = (node, source) => {
|
|
1157
|
-
if (!(node instanceof HTMLElement) || !isVisible(node)) return;
|
|
1158
|
-
const rect = node.getBoundingClientRect();
|
|
1159
|
-
if (rect.width < 240 || rect.height < 160) return;
|
|
1160
|
-
const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}:${normalize(node.className || '')}`;
|
|
1161
|
-
if (seen.has(key)) return;
|
|
1162
|
-
seen.add(key);
|
|
1163
|
-
panelEntries.push({ node, rect, source });
|
|
1164
|
-
};
|
|
1165
|
-
|
|
1166
|
-
for (const selector of panelSelectors) {
|
|
1167
|
-
for (const node of Array.from(document.querySelectorAll(selector))) {
|
|
1168
|
-
pushPanel(node, `selector:${selector}`);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
for (const closeButton of closeButtons) {
|
|
1173
|
-
let current = closeButton.parentElement;
|
|
1174
|
-
let depth = 0;
|
|
1175
|
-
while (current instanceof HTMLElement && depth < 10) {
|
|
1176
|
-
pushPanel(current, 'close-ancestor');
|
|
1177
|
-
current = current.parentElement;
|
|
1178
|
-
depth += 1;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
const scoredPanels = panelEntries
|
|
1183
|
-
.map((entry) => {
|
|
1184
|
-
const classText = normalize(entry.node.className || '').toLowerCase();
|
|
1185
|
-
const text = normalize(entry.node.textContent || '').slice(0, 240).toLowerCase();
|
|
1186
|
-
const containsClose = closeButtons.some((button) => entry.node.contains(button));
|
|
1187
|
-
const anchoredRight =
|
|
1188
|
-
entry.rect.left >= window.innerWidth * 0.4 ||
|
|
1189
|
-
entry.rect.right >= window.innerWidth * 0.72;
|
|
1190
|
-
const hasKnownDetailClass =
|
|
1191
|
-
classText.includes('base-info-single-top-detail') ||
|
|
1192
|
-
classText.includes('resume-detail-wrap') ||
|
|
1193
|
-
classText.includes('candidate-detail') ||
|
|
1194
|
-
classText.includes('chat-detail') ||
|
|
1195
|
-
classText.includes('geek-card-detail');
|
|
1196
|
-
const hasDetailHint =
|
|
1197
|
-
text.includes('在线简历') ||
|
|
1198
|
-
text.includes('附件简历') ||
|
|
1199
|
-
text.includes('牛人分析器') ||
|
|
1200
|
-
text.includes('活跃');
|
|
1201
|
-
|
|
1202
|
-
let score = 0;
|
|
1203
|
-
if (containsClose) score += 220;
|
|
1204
|
-
if (anchoredRight) score += 140;
|
|
1205
|
-
if (hasKnownDetailClass) score += 160;
|
|
1206
|
-
if (hasDetailHint) score += 80;
|
|
1207
|
-
if (entry.source === 'close-ancestor') score += 40;
|
|
1208
|
-
score += Math.min(180, Math.floor((entry.rect.width * entry.rect.height) / 12000));
|
|
1209
|
-
|
|
1210
|
-
return {
|
|
1211
|
-
...entry,
|
|
1212
|
-
score,
|
|
1213
|
-
};
|
|
1214
|
-
})
|
|
1215
|
-
.sort((a, b) => b.score - a.score);
|
|
1216
|
-
|
|
1217
|
-
const topPanel = scoredPanels[0] || null;
|
|
1218
|
-
const topPanelNode = topPanel?.node || null;
|
|
1219
|
-
const closeButton =
|
|
1220
|
-
closeButtons.find((button) => topPanelNode instanceof HTMLElement && topPanelNode.contains(button)) ||
|
|
1221
|
-
closeButtons[0] ||
|
|
1222
|
-
null;
|
|
1223
|
-
|
|
1224
|
-
return {
|
|
1225
|
-
open: Boolean(topPanel || closeButton),
|
|
1226
|
-
panelCount: scoredPanels.length,
|
|
1227
|
-
closeCount: closeButtons.length,
|
|
1228
|
-
topPanelClass: normalize(topPanelNode?.className || ''),
|
|
1229
|
-
topPanelScore: Number(topPanel?.score || 0),
|
|
1230
|
-
panelRect: topPanel ? rectToJson(topPanel.rect) : null,
|
|
1231
|
-
closeRect: closeButton ? rectToJson(closeButton.getBoundingClientRect()) : null,
|
|
1232
|
-
closeButton,
|
|
1233
|
-
};
|
|
1234
|
-
};
|
|
1235
1373
|
const serializeSnapshot = (snapshot = {}) => ({
|
|
1236
1374
|
open: Boolean(snapshot?.open),
|
|
1237
1375
|
panelCount: Number(snapshot?.panelCount || 0),
|
|
@@ -1240,9 +1378,13 @@ function browserCloseCandidateDetailDomOnce() {
|
|
|
1240
1378
|
topPanelScore: Number(snapshot?.topPanelScore || 0),
|
|
1241
1379
|
panelRect: snapshot?.panelRect || null,
|
|
1242
1380
|
closeRect: snapshot?.closeRect || null,
|
|
1381
|
+
overlayClass: String(snapshot?.overlayClass || ''),
|
|
1382
|
+
overlayRect: snapshot?.overlayRect || null,
|
|
1383
|
+
contentClass: String(snapshot?.contentClass || ''),
|
|
1384
|
+
contentRect: snapshot?.contentRect || null,
|
|
1243
1385
|
});
|
|
1244
1386
|
|
|
1245
|
-
const snapshot =
|
|
1387
|
+
const snapshot = browserCollectCandidateDetailSnapshot();
|
|
1246
1388
|
if (!snapshot?.open || !(snapshot.closeButton instanceof HTMLElement)) {
|
|
1247
1389
|
return {
|
|
1248
1390
|
ok: false,
|
|
@@ -1776,6 +1918,35 @@ function browserAnyPopupVisible() {
|
|
|
1776
1918
|
return { visible: wrappers.length > 0, count: wrappers.length };
|
|
1777
1919
|
}
|
|
1778
1920
|
|
|
1921
|
+
browserConversationReadyState.helpers = [
|
|
1922
|
+
browserIsResumeModalOpen,
|
|
1923
|
+
browserNormalizeVisibleText,
|
|
1924
|
+
browserIsVisibleElement,
|
|
1925
|
+
browserRectToJson,
|
|
1926
|
+
browserCollectCandidateDetailSnapshot,
|
|
1927
|
+
];
|
|
1928
|
+
|
|
1929
|
+
browserFindCandidateDetailOutsideClickPoint.helpers = [
|
|
1930
|
+
browserNormalizeVisibleText,
|
|
1931
|
+
browserIsVisibleElement,
|
|
1932
|
+
browserRectToJson,
|
|
1933
|
+
browserCollectCandidateDetailSnapshot,
|
|
1934
|
+
];
|
|
1935
|
+
|
|
1936
|
+
browserIsCandidateDetailOpen.helpers = [
|
|
1937
|
+
browserNormalizeVisibleText,
|
|
1938
|
+
browserIsVisibleElement,
|
|
1939
|
+
browserRectToJson,
|
|
1940
|
+
browserCollectCandidateDetailSnapshot,
|
|
1941
|
+
];
|
|
1942
|
+
|
|
1943
|
+
browserCloseCandidateDetailDomOnce.helpers = [
|
|
1944
|
+
browserNormalizeVisibleText,
|
|
1945
|
+
browserIsVisibleElement,
|
|
1946
|
+
browserRectToJson,
|
|
1947
|
+
browserCollectCandidateDetailSnapshot,
|
|
1948
|
+
];
|
|
1949
|
+
|
|
1779
1950
|
function browserSetEditorMessage(message) {
|
|
1780
1951
|
const normalize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
|
|
1781
1952
|
const text = String(message || '').trim();
|
|
@@ -2699,15 +2870,24 @@ export class BossChatPage {
|
|
|
2699
2870
|
async waitForConversationReady(options = {}) {
|
|
2700
2871
|
const maxAttempts = options.maxAttempts || 12;
|
|
2701
2872
|
const delayMs = options.delayMs || 260;
|
|
2873
|
+
const requirePanelsClosed = options.requirePanelsClosed === true;
|
|
2702
2874
|
|
|
2703
2875
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
2704
2876
|
const state = await this.chromeClient.callFunction(browserConversationReadyState);
|
|
2705
|
-
|
|
2877
|
+
const hasActionControls =
|
|
2878
|
+
Boolean(state?.hasOnlineResume) ||
|
|
2879
|
+
Boolean(state?.hasAskResume) ||
|
|
2880
|
+
Boolean(state?.hasAttachmentResume);
|
|
2881
|
+
const panelsReady = requirePanelsClosed ? Boolean(state?.panelsClosed) : true;
|
|
2882
|
+
const editorReady = requirePanelsClosed
|
|
2883
|
+
? Boolean(state?.editorVisible) && (Boolean(state?.activeSubmit) || Boolean(state?.hasAnySubmit))
|
|
2884
|
+
: true;
|
|
2885
|
+
if (hasActionControls && panelsReady && editorReady) {
|
|
2706
2886
|
return state;
|
|
2707
2887
|
}
|
|
2708
2888
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2709
2889
|
}
|
|
2710
|
-
throw new Error('CONVERSATION_PANEL_NOT_READY');
|
|
2890
|
+
throw new Error(requirePanelsClosed ? 'CONVERSATION_PANEL_NOT_READY_OR_BLOCKED' : 'CONVERSATION_PANEL_NOT_READY');
|
|
2711
2891
|
}
|
|
2712
2892
|
|
|
2713
2893
|
async waitForCandidateActivated(customer, options = {}) {
|
|
@@ -2877,6 +3057,10 @@ export class BossChatPage {
|
|
|
2877
3057
|
topPanelScore: Number(result?.topPanelScore || 0),
|
|
2878
3058
|
panelRect: result?.panelRect || null,
|
|
2879
3059
|
closeRect: result?.closeRect || null,
|
|
3060
|
+
overlayClass: String(result?.overlayClass || ''),
|
|
3061
|
+
overlayRect: result?.overlayRect || null,
|
|
3062
|
+
contentClass: String(result?.contentClass || ''),
|
|
3063
|
+
contentRect: result?.contentRect || null,
|
|
2880
3064
|
};
|
|
2881
3065
|
}
|
|
2882
3066
|
|
|
@@ -2919,6 +3103,34 @@ export class BossChatPage {
|
|
|
2919
3103
|
};
|
|
2920
3104
|
}
|
|
2921
3105
|
|
|
3106
|
+
async clickCandidateDetailOutside() {
|
|
3107
|
+
const result = await this.chromeClient.callFunction(browserFindCandidateDetailOutsideClickPoint);
|
|
3108
|
+
if (!result?.ok || !result?.point) {
|
|
3109
|
+
const finalState = await this.getCandidateDetailState();
|
|
3110
|
+
return {
|
|
3111
|
+
clicked: false,
|
|
3112
|
+
method: `outside-click-miss:${result?.error || 'unknown'}`,
|
|
3113
|
+
finalState,
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
const point = result.point;
|
|
3118
|
+
const rect = {
|
|
3119
|
+
left: Math.max(0, Number(point.x || 0) - 3),
|
|
3120
|
+
top: Math.max(0, Number(point.y || 0) - 3),
|
|
3121
|
+
width: 6,
|
|
3122
|
+
height: 6,
|
|
3123
|
+
};
|
|
3124
|
+
await this.clickRect(rect);
|
|
3125
|
+
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
3126
|
+
const finalState = await this.getCandidateDetailState();
|
|
3127
|
+
return {
|
|
3128
|
+
clicked: true,
|
|
3129
|
+
method: `outside-click:${result?.strategy || 'unknown'}`,
|
|
3130
|
+
finalState,
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
|
|
2922
3134
|
async closeCandidateDetail({ maxAttempts = 4, ensureDismiss = false } = {}) {
|
|
2923
3135
|
const drawerOpen = (state) =>
|
|
2924
3136
|
Boolean(state?.open) ||
|
|
@@ -2952,16 +3164,6 @@ export class BossChatPage {
|
|
|
2952
3164
|
};
|
|
2953
3165
|
}
|
|
2954
3166
|
|
|
2955
|
-
if (midState?.panelRect) {
|
|
2956
|
-
await this.clickRect(midState.panelRect);
|
|
2957
|
-
methods.push('focus-panel');
|
|
2958
|
-
await new Promise((resolve) => setTimeout(resolve, 160));
|
|
2959
|
-
} else if (midState?.closeRect) {
|
|
2960
|
-
await this.clickRect(midState.closeRect);
|
|
2961
|
-
methods.push('focus-close');
|
|
2962
|
-
await new Promise((resolve) => setTimeout(resolve, 160));
|
|
2963
|
-
}
|
|
2964
|
-
|
|
2965
3167
|
await this.chromeClient.pressEscape();
|
|
2966
3168
|
methods.push('escape');
|
|
2967
3169
|
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
@@ -2975,18 +3177,15 @@ export class BossChatPage {
|
|
|
2975
3177
|
};
|
|
2976
3178
|
}
|
|
2977
3179
|
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
finalState: midState,
|
|
2988
|
-
};
|
|
2989
|
-
}
|
|
3180
|
+
const outsideResult = await this.clickCandidateDetailOutside();
|
|
3181
|
+
methods.push(outsideResult.method || 'outside-click:unknown');
|
|
3182
|
+
midState = outsideResult.finalState || await this.getCandidateDetailState();
|
|
3183
|
+
if (!drawerOpen(midState)) {
|
|
3184
|
+
return {
|
|
3185
|
+
closed: true,
|
|
3186
|
+
method: methods.join('+'),
|
|
3187
|
+
finalState: midState,
|
|
3188
|
+
};
|
|
2990
3189
|
}
|
|
2991
3190
|
|
|
2992
3191
|
if (ensureDismiss && index >= 1) {
|