@reconcrap/boss-recommend-mcp 1.3.31 → 1.3.33
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 +2716 -2157
- package/vendor/boss-chat-cli/src/app.js +1436 -1250
- package/vendor/boss-chat-cli/src/browser/chat-page.js +412 -242
- package/vendor/boss-chat-cli/src/cli.js +1580 -1580
- package/vendor/boss-chat-cli/src/services/chrome-client.js +103 -103
- package/vendor/boss-chat-cli/src/services/llm.js +1156 -662
- 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 -283
- 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,
|
|
@@ -2699,15 +2841,24 @@ export class BossChatPage {
|
|
|
2699
2841
|
async waitForConversationReady(options = {}) {
|
|
2700
2842
|
const maxAttempts = options.maxAttempts || 12;
|
|
2701
2843
|
const delayMs = options.delayMs || 260;
|
|
2844
|
+
const requirePanelsClosed = options.requirePanelsClosed === true;
|
|
2702
2845
|
|
|
2703
2846
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
2704
2847
|
const state = await this.chromeClient.callFunction(browserConversationReadyState);
|
|
2705
|
-
|
|
2848
|
+
const hasActionControls =
|
|
2849
|
+
Boolean(state?.hasOnlineResume) ||
|
|
2850
|
+
Boolean(state?.hasAskResume) ||
|
|
2851
|
+
Boolean(state?.hasAttachmentResume);
|
|
2852
|
+
const panelsReady = requirePanelsClosed ? Boolean(state?.panelsClosed) : true;
|
|
2853
|
+
const editorReady = requirePanelsClosed
|
|
2854
|
+
? Boolean(state?.editorVisible) && (Boolean(state?.activeSubmit) || Boolean(state?.hasAnySubmit))
|
|
2855
|
+
: true;
|
|
2856
|
+
if (hasActionControls && panelsReady && editorReady) {
|
|
2706
2857
|
return state;
|
|
2707
2858
|
}
|
|
2708
2859
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2709
2860
|
}
|
|
2710
|
-
throw new Error('CONVERSATION_PANEL_NOT_READY');
|
|
2861
|
+
throw new Error(requirePanelsClosed ? 'CONVERSATION_PANEL_NOT_READY_OR_BLOCKED' : 'CONVERSATION_PANEL_NOT_READY');
|
|
2711
2862
|
}
|
|
2712
2863
|
|
|
2713
2864
|
async waitForCandidateActivated(customer, options = {}) {
|
|
@@ -2877,6 +3028,10 @@ export class BossChatPage {
|
|
|
2877
3028
|
topPanelScore: Number(result?.topPanelScore || 0),
|
|
2878
3029
|
panelRect: result?.panelRect || null,
|
|
2879
3030
|
closeRect: result?.closeRect || null,
|
|
3031
|
+
overlayClass: String(result?.overlayClass || ''),
|
|
3032
|
+
overlayRect: result?.overlayRect || null,
|
|
3033
|
+
contentClass: String(result?.contentClass || ''),
|
|
3034
|
+
contentRect: result?.contentRect || null,
|
|
2880
3035
|
};
|
|
2881
3036
|
}
|
|
2882
3037
|
|
|
@@ -2919,6 +3074,34 @@ export class BossChatPage {
|
|
|
2919
3074
|
};
|
|
2920
3075
|
}
|
|
2921
3076
|
|
|
3077
|
+
async clickCandidateDetailOutside() {
|
|
3078
|
+
const result = await this.chromeClient.callFunction(browserFindCandidateDetailOutsideClickPoint);
|
|
3079
|
+
if (!result?.ok || !result?.point) {
|
|
3080
|
+
const finalState = await this.getCandidateDetailState();
|
|
3081
|
+
return {
|
|
3082
|
+
clicked: false,
|
|
3083
|
+
method: `outside-click-miss:${result?.error || 'unknown'}`,
|
|
3084
|
+
finalState,
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
const point = result.point;
|
|
3089
|
+
const rect = {
|
|
3090
|
+
left: Math.max(0, Number(point.x || 0) - 3),
|
|
3091
|
+
top: Math.max(0, Number(point.y || 0) - 3),
|
|
3092
|
+
width: 6,
|
|
3093
|
+
height: 6,
|
|
3094
|
+
};
|
|
3095
|
+
await this.clickRect(rect);
|
|
3096
|
+
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
3097
|
+
const finalState = await this.getCandidateDetailState();
|
|
3098
|
+
return {
|
|
3099
|
+
clicked: true,
|
|
3100
|
+
method: `outside-click:${result?.strategy || 'unknown'}`,
|
|
3101
|
+
finalState,
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
|
|
2922
3105
|
async closeCandidateDetail({ maxAttempts = 4, ensureDismiss = false } = {}) {
|
|
2923
3106
|
const drawerOpen = (state) =>
|
|
2924
3107
|
Boolean(state?.open) ||
|
|
@@ -2952,16 +3135,6 @@ export class BossChatPage {
|
|
|
2952
3135
|
};
|
|
2953
3136
|
}
|
|
2954
3137
|
|
|
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
3138
|
await this.chromeClient.pressEscape();
|
|
2966
3139
|
methods.push('escape');
|
|
2967
3140
|
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
@@ -2975,18 +3148,15 @@ export class BossChatPage {
|
|
|
2975
3148
|
};
|
|
2976
3149
|
}
|
|
2977
3150
|
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
finalState: midState,
|
|
2988
|
-
};
|
|
2989
|
-
}
|
|
3151
|
+
const outsideResult = await this.clickCandidateDetailOutside();
|
|
3152
|
+
methods.push(outsideResult.method || 'outside-click:unknown');
|
|
3153
|
+
midState = outsideResult.finalState || await this.getCandidateDetailState();
|
|
3154
|
+
if (!drawerOpen(midState)) {
|
|
3155
|
+
return {
|
|
3156
|
+
closed: true,
|
|
3157
|
+
method: methods.join('+'),
|
|
3158
|
+
finalState: midState,
|
|
3159
|
+
};
|
|
2990
3160
|
}
|
|
2991
3161
|
|
|
2992
3162
|
if (ensureDismiss && index >= 1) {
|