// ==UserScript==
// @name BOSS直聘-AI招呼助手
// @namespace http://tampermonkey.net/
// @version 0.5.2
// @description BOSS直聘 - AI自动生成求职打招呼内容 - 智能简历助手!
// @author 道长@ASO黑科技 (by Trae AI)
// @license GPL-3.0 License
// @match https://www.zhipin.com/job_detail/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=zhipin.com
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @require https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js
// @require https://unpkg.com/[email protected]/lib/pdf.js
// ==/UserScript==
(function() {
'use strict';
// 初始化PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
// 样式定义
const styles = `
.resume-helper {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
width: 320px;
z-index: 9999;
transition: all 0.3s ease;
}
.select-group {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.api-select {
flex: 1;
padding: 8px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
background-color: white;
cursor: pointer;
transition: all 0.3s ease;
}
.api-select:hover {
border-color: #1677ff;
}
.api-select:focus {
outline: none;
border-color: #1677ff;
box-shadow: 0 0 0 2px rgba(22,119,255,0.1);
}
.resume-helper:hover {
transform: translateY(-50%) translateX(-5px);
box-shadow: 0 12px 36px rgba(0,0,0,0.16);
}
.resume-helper .title {
font-size: 18px;
font-weight: 600;
margin-bottom: 20px;
color: #1a1a1a;
display: flex;
align-items: center;
gap: 8px;
}
.resume-helper .title:before {
content: '📝';
font-size: 20px;
}
.resume-helper .upload-btn {
display: block;
width: 100%;
padding: 12px;
background: #1677ff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
margin-bottom: 16px;
font-size: 15px;
font-weight: 500;
transition: all 0.2s ease;
}
.resume-helper .upload-btn:hover {
background: #0958d9;
transform: translateY(-1px);
}
.resume-helper .status {
font-size: 14px;
color: #666;
margin: 12px 0;
padding: 8px 12px;
background: #f5f5f5;
border-radius: 6px;
transition: color 0.3s ease;
}
.resume-helper .result {
border: 1px solid #e6e6e6;
padding: 16px;
margin-top: 16px;
border-radius: 8px;
min-height: 120px;
max-height: 320px;
overflow-y: auto;
background: #fafafa;
font-size: 14px;
line-height: 1.6;
color: #262626;
}
.resume-helper .result::-webkit-scrollbar {
width: 6px;
}
.resume-helper .result::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 3px;
}
.resume-helper .result::-webkit-scrollbar-track {
background: #f5f5f5;
border-radius: 3px;
}
.resume-helper .copy-btn {
background: #52c41a;
color: white;
border: none;
padding: 8px 20px;
border-radius: 6px;
cursor: pointer;
margin-top: 12px;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 6px;
}
.resume-helper .copy-btn:before {
content: '📋';
font-size: 16px;
}
.resume-helper .copy-btn:hover {
background: #389e0d;
transform: translateY(-1px);
}
.resume-helper .settings {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
}
.resume-helper input[type="text"] {
width: calc(100% - 0px);
padding: 10px 12px;
margin-bottom: 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
transition: all 0.3s ease;
box-sizing: border-box;
}
.resume-helper input[type="text"]:focus {
border-color: #1677ff;
box-shadow: 0 0 0 2px rgba(22,119,255,0.1);
outline: none;
}
`;
// 注入样式
GM_addStyle(styles);
// 创建UI组件
function createUI() {
const container = document.createElement('div');
container.className = 'resume-helper';
container.innerHTML = `
<div class="title">简历助手 - AI生成打招呼内容</div>
<input type="file" id="resumeFile" accept=".txt,.docx,.md,.pdf" style="display:none">
<button class="upload-btn" id="uploadBtn">上传简历</button>
<div class="resume-list" id="resumeList"></div>
<div class="status" id="status">请上传简历</div>
<div class="info-panel" id="infoPanel">
<div class="info-title">当前岗位</div>
<div class="info-item" id="targetJob">
<span class="job-name"></span>
<button class="generate-btn" id="generateBtn">生成回复</button>
</div>
</div>
<div class="result" id="result"></div>
<button class="copy-btn" id="copyBtn" style="display:none">复制内容</button>
<div class="settings">
<div class="select-group">
<select id="apiUrl" class="api-select">
<option value="https://api.deepseek.com/v1/chat/completions">DeepSeek官方</option>
<option value="https://api.siliconflow.cn/v1/chat/completions">硅基流动</option>
<option value="https://api.302.ai/v1/chat/completions">302AI</option>
// <option value="https://ark.cn-beijing.volces.com/api/v3/chat/completions">火山引擎</option>
</select>
<select id="modelSelect" class="api-select">
<option value="DeepSeek-R1">DeepSeek-R1</option>
<option value="DeepSeek-V3">DeepSeek-V3</option>
</select>
</div>
<input type="text" id="apiKey" placeholder="请输入OpenAI API Key">
<div class="api-key-help">
免费获取APIKey:<a href="https://cloud.siliconflow.cn/i/KyzjYQTa" target="blank">硅基流动</a>,<a href="https://gpt302.saaslink.net/sBIfo8" target="blank">302AI</a>。
</div>
</div>
`;
// 添加新样式
GM_addStyle(`
.resume-list {
margin: 12px 0;
}
.resume-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: #f5f5f5;
border-radius: 6px;
margin-bottom: 8px;
font-size: 14px;
color: #333;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
z-index: 1;
}
.resume-item:hover {
background: #e6f4ff;
}
.resume-item .delete-btn {
background: none;
border: none;
color: #ff4d4f;
cursor: pointer;
padding: 4px;
font-size: 16px;
line-height: 1;
transition: all 0.2s ease;
}
.resume-item .delete-btn:hover {
transform: scale(1.1);
}
.generate-btn {
background: #1677ff;
color: white;
border: none;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
margin-left: auto;
transition: all 0.2s ease;
}
.generate-btn:hover {
background: #0958d9;
transform: translateY(-1px);
}
.info-title {
font-weight: 600;
margin-bottom: 12px;
color: #1677ff;
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: rgba(22,119,255,0.05);
border-radius: 8px;
transition: all 0.3s ease;
}
.info-title:before {
content: '📌';
font-size: 18px;
}
.info-item {
font-size: 14px;
margin: 12px 0;
color: #262626;
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #f0f0f0;
transition: all 0.3s ease;
position: relative;
z-index: 1;
box-shadow: 0 1px 2px rgba(0,0,0,0.03);
}
.job-name {
flex: 1;
font-weight: 500;
font-size: 15px;
line-height: 1.4;
color: #1f1f1f;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.info-item:hover {
background: #fff;
border-color: #1677ff;
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(22,119,255,0.1);
}
.info-item {
justify-content: space-between;
}
.job-name {
flex: 1;
}
.info-title {
font-weight: 600;
margin-bottom: 12px;
color: #1677ff;
font-size: 15px;
display: flex;
align-items: center;
gap: 6px;
}
.info-title:before {
content: '📌';
font-size: 16px;
}
.info-item {
font-size: 14px;
margin: 8px 0;
color: #595959;
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: rgba(0,0,0,0.02);
border-radius: 4px;
transition: all 0.2s ease;
position: relative;
z-index: 1;
}
.status {
position: relative;
z-index: 2;
}
.info-item:hover {
background: rgba(22,119,255,0.05);
transform: translateX(2px);
}
.result {
white-space: pre-line;
line-height: 1.6;
position: relative;
background: linear-gradient(to bottom right, #fafafa, #ffffff);
border: 1px solid #e8e8e8;
transition: all 0.3s ease;
}
.result:hover {
border-color: #1677ff;
box-shadow: 0 2px 8px rgba(22,119,255,0.1);
}
.api-key-help {
font-size: 12px;
color: #666;
margin-top: 4px;
}
.api-key-help a {
color: #1677ff;
text-decoration: none;
}
.api-key-help a:hover {
text-decoration: underline;
}
.select-group {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.api-select {
flex: 1;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
color: #333;
background-color: #fff;
cursor: pointer;
transition: all 0.3s ease;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8.825L1.175 4 2.238 2.938 6 6.7l3.763-3.762L10.825 4z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 32px;
}
.api-select:hover {
border-color: #1677ff;
}
.api-select:focus {
border-color: #1677ff;
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1);
outline: none;
}
`);
document.body.appendChild(container);
initEventListeners();
}
// 更新简历信息显示
function updateResumeInfo(resumeInfo, jobInfo) {
const infoPanel = document.getElementById('infoPanel');
document.getElementById('targetJob').querySelector('.job-name').textContent = jobInfo.jobName;
document.getElementById('generateBtn').style.display = 'block';
infoPanel.style.display = 'block';
}
// 更新状态显示
function updateStatus(message, isLoading = false) {
const status = document.getElementById('status');
const result = document.getElementById('result');
if (isLoading) {
result.textContent = message;
result.style.display = 'block';
status.style.display = 'none';
} else {
status.textContent = message;
status.style.color = '#666';
status.style.display = message === '生成完成' ? 'none' : 'block';
}
}
// 获取实际的模型ID
function getActualModelId(apiUrl, selectedModel) {
const models = nodeModelConfig[apiUrl] || {};
return models[selectedModel] || selectedModel;
}
// 添加节点与模型的映射配置
const nodeModelConfig = {
'https://api.deepseek.com/v1/chat/completions': {
'DeepSeek-V3': 'deepseek-chat',
'DeepSeek-R1': 'deepseek-reasoner'
},
'https://api.siliconflow.cn/v1/chat/completions': {
'DeepSeek-V3': 'deepseek-ai/DeepSeek-V3',
'DeepSeek-R1': 'deepseek-ai/DeepSeek-R1',
'DeepSeek-R1-70B': 'deepseek-ai/DeepSeek-R1-Distill-Llama-70B',
'DeepSeek-R1-7B': 'deepseek-ai/DeepSeek-R1-Distill-Qwen-7B'
},
'https://api.302.ai/v1/chat/completions': {
'DeepSeek-R1': 'deepseek-r1',
'DeepSeek-V3-hs': 'deepseek-v3-huoshan',
'DeepSeek-V3-bd': 'deepseek-v3-baidu'
},
'https://ark.cn-beijing.volces.com/api/v3/chat/completions': {
'DeepSeek-R1': 'ep-20250211094300-2zrlz',
'DeepSeek-V3': 'ep-20250211094327-lfn5l'
}
};
// 初始化事件监听
function initEventListeners() {
const uploadBtn = document.getElementById('uploadBtn');
const resumeFile = document.getElementById('resumeFile');
const copyBtn = document.getElementById('copyBtn');
const apiKeyInput = document.getElementById('apiKey');
const generateBtn = document.getElementById('generateBtn');
const apiUrlSelect = document.getElementById('apiUrl');
const modelSelect = document.getElementById('modelSelect');
// 从本地存储加载配置
const savedApiKey = GM_getValue('openai_api_key', '');
const savedResumes = GM_getValue('saved_resumes', []);
const savedApiUrl = GM_getValue('api_url', '');
const savedModel = GM_getValue('model', '');
// 恢复保存的配置
if (savedApiKey) {
apiKeyInput.value = savedApiKey;
}
if (savedApiUrl) {
apiUrlSelect.value = savedApiUrl;
}
if (savedModel) {
modelSelect.value = savedModel;
}
// 更新简历列表显示
updateResumeList(savedResumes);
// 如果有保存的简历,显示当前职位信息
if (savedResumes.length > 0) {
const jobInfo = extractJobInfo();
document.getElementById('targetJob').querySelector('.job-name').textContent = jobInfo.jobName;
document.getElementById('generateBtn').style.display = 'block';
document.getElementById('infoPanel').style.display = 'block';
}
uploadBtn.addEventListener('click', () => {
const savedResumes = GM_getValue('saved_resumes', []);
if (savedResumes.length > 0) {
// 如果已有简历,直接生成回复
processResume(savedResumes[0].content);
} else {
// 否则触发文件上传
resumeFile.click();
}
});
resumeFile.addEventListener('change', handleFileUpload);
copyBtn.addEventListener('click', () => {
const result = document.getElementById('result');
navigator.clipboard.writeText(result.textContent)
.then(() => alert('内容已复制到剪贴板'));
});
apiKeyInput.addEventListener('change', (e) => {
GM_setValue('openai_api_key', e.target.value);
});
// 保存API节点和模型选择
// 更新模型选择器选项
function updateModelOptions(apiUrl) {
const modelSelect = document.getElementById('modelSelect');
const models = nodeModelConfig[apiUrl] || {};
// 清空现有选项
modelSelect.innerHTML = '';
// 添加新选项
Object.entries(models).forEach(([label, value]) => {
const option = document.createElement('option');
option.value = value;
option.textContent = label;
modelSelect.appendChild(option);
});
// 如果有保存的模型设置且在当前节点的模型列表中,则选中它
const savedModel = GM_getValue('model', '');
if (savedModel && models[savedModel]) {
modelSelect.value = savedModel;
} else if (modelSelect.options.length > 0) {
// 否则选择第一个选项
modelSelect.value = modelSelect.options[0].value;
GM_setValue('model', modelSelect.value);
}
}
// 修改API节点选择器的change事件处理
apiUrlSelect.addEventListener('change', (e) => {
const apiUrl = e.target.value;
GM_setValue('api_url', apiUrl);
// 清空API Key
apiKeyInput.value = '';
GM_setValue('openai_api_key', '');
updateModelOptions(apiUrl);
});
// 初始化时更新模型选择器
const initialApiUrl = apiUrlSelect.value;
if (initialApiUrl) {
updateModelOptions(initialApiUrl);
}
modelSelect.addEventListener('change', (e) => {
GM_setValue('model', e.target.value);
});
// 生成回复按钮点击事件
generateBtn?.addEventListener('click', async () => {
const savedResumes = GM_getValue('saved_resumes', []);
if (savedResumes.length === 0) {
alert('请先上传简历');
return;
}
const lastResume = savedResumes[savedResumes.length - 1];
await processResume(lastResume.content);
});
// 监听简历列表的点击事件
document.getElementById('resumeList').addEventListener('click', (e) => {
const target = e.target;
if (target.classList.contains('delete-btn')) {
// 删除简历
const resumeItem = target.closest('.resume-item');
const fileName = resumeItem.getAttribute('data-filename');
deleteResume(fileName);
} else if (target.classList.contains('resume-item') || target.parentElement.classList.contains('resume-item')) {
// 预览简历
const resumeItem = target.classList.contains('resume-item') ? target : target.parentElement;
const fileName = resumeItem.getAttribute('data-filename');
const savedResumes = GM_getValue('saved_resumes', []);
const resume = savedResumes.find(r => r.fileName === fileName);
if (resume) {
// 创建一个新的 Blob 对象
const file = new File([resume.content], fileName, { type: 'text/plain' });
// 在新标签页中打开文件
const newWindow = window.open('', '_blank');
if (newWindow) {
newWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>预览简历: ${fileName}</title>
<style>
body {
margin: 0;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
margin: 0;
font-family: inherit;
font-size: 14px;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container">
<pre>${resume.content}</pre>
</div>
</body>
</html>
`);
newWindow.document.close();
}
}
}
});
}
// 更新简历列表显示
function updateResumeList(resumes) {
const resumeList = document.getElementById('resumeList');
const uploadBtn = document.getElementById('uploadBtn');
const status = document.getElementById('status');
// 根据简历列表状态更新按钮文本和状态显示
if (resumes.length > 0) {
uploadBtn.textContent = '生成回复';
status.style.display = 'none';
} else {
uploadBtn.textContent = '上传简历';
status.style.display = 'block';
}
resumeList.innerHTML = resumes.map(resume => `
<div class="resume-item" data-filename="${resume.fileName}">
<span>${resume.fileName}</span>
<button class="delete-btn">×</button>
</div>
`).join('');
}
// 删除简历
function deleteResume(fileName) {
const savedResumes = GM_getValue('saved_resumes', []);
const newResumes = savedResumes.filter(r => r.fileName !== fileName);
GM_setValue('saved_resumes', newResumes);
updateResumeList(newResumes);
}
// 文件上传处理
function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
const status = document.getElementById('status');
status.textContent = '正在解析简历...';
const reader = new FileReader();
reader.onload = async (e) => {
const content = e.target.result;
// 只保存最新的简历
const savedResumes = [{
fileName: file.name,
content: content
}];
GM_setValue('saved_resumes', savedResumes);
updateResumeList(savedResumes);
await processResume(content);
};
if (file.name.endsWith('.txt') || file.name.endsWith('.md')) {
reader.readAsText(file);
} else if (file.name.endsWith('.docx')) {
// TODO: 实现.docx文件解析
alert('DOCX格式支持开发中');
} else if (file.name.endsWith('.pdf')) {
const reader = new FileReader();
reader.onload = async function(e) {
const typedarray = new Uint8Array(e.target.result);
try {
console.log('PDF文件大小:', typedarray.length, '字节');
// 使用pdf-parse库解析PDF
const data = await window.pdfjsLib.getDocument({data: typedarray}).promise;
let fullText = '';
// 遍历所有页面并提取文本
for (let i = 1; i <= data.numPages; i++) {
const page = await data.getPage(i);
const content = await page.getTextContent();
const strings = content.items.map(item => item.str);
fullText += strings.join(' ') + '\n';
}
console.log('完整提取的文本:', fullText);
// 只保存最新的简历
const savedResumes = [{
fileName: file.name,
content: fullText
}];
GM_setValue('saved_resumes', savedResumes);
updateResumeList(savedResumes);
await processResume(fullText);
} catch (error) {
console.error('PDF解析失败:', error);
document.getElementById('status').textContent = 'PDF解析失败: ' + error.message;
}
};
reader.readAsArrayBuffer(file);
}
}
// 处理简历
async function processResume(content) {
try {
updateStatus('正在解析简历...', true);
// 提取职位信息
const jobInfo = extractJobInfo();
// 更新职位信息显示
document.getElementById('targetJob').textContent = jobInfo.jobName;
document.getElementById('infoPanel').style.display = 'block';
updateStatus('正在生成打招呼内容...', true);
// 生成打招呼内容
await generateGreeting(content, jobInfo);
} catch (error) {
console.error('处理失败:', error);
updateStatus('处理失败: ' + error.message);
}
}
// 提取职位信息
function extractJobInfo() {
// 使用XPath选择器获取职位信息
const getElementByXPath = (xpath) => {
return document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
};
const jobNameElement = getElementByXPath('//*[@id="main"]/div[1]/div/div/div[1]/div[2]/h1');
const jobDetailElement = getElementByXPath('//*[@id="main"]/div[3]/div/div[2]/div[1]/div[3]');
const companyNameElement = getElementByXPath('//*[@id="main"]/div[3]/div/div[2]/div[1]/div[4]/div[2]/text()[1]');
const jobName = jobNameElement?.textContent?.trim() || '';
const jobDetail = jobDetailElement?.textContent?.trim() || '';
const companyInfo = companyNameElement?.textContent?.trim() || document.querySelector('.company-info')?.textContent?.trim() || '';
console.log('提取的职位信息:', { jobName, jobDetail, companyInfo });
return {
jobName,
jobDetail,
companyInfo
};
}
// 解析简历内容
function parseResume(content) {
console.log('开始解析简历内容:', content);
// 提取基本信息
const nameMatch = content.match(/姓名[::](.*?)(?=\n|$)/s);
const ageMatch = content.match(/年龄[::](.*?)(?=\n|$)/s) || content.match(/(\d{2})岁/);
const educationMatch = content.match(/[学历|教育][::](.*?)(?=\n|$)/s) || content.match(/(?:本科|硕士|博士|大专|高中)(.*?)(?=\n|$)/);
const skillsMatch = content.match(/技能[::](.*?)(?=\n|$)/s);
const experienceMatch = content.match(/[工作]*经[验历][::](.*?)(?=\n|$)/s);
const resumeInfo = {
name: nameMatch?.[1]?.trim() || '未知',
age: ageMatch?.[1]?.trim() || ageMatch?.[1] || '未知',
education: educationMatch?.[1]?.trim() || educationMatch?.[0]?.trim() || '未知',
skills: skillsMatch?.[1]?.trim() || '',
experience: experienceMatch?.[1]?.trim() || ''
};
console.log('解析结果:', resumeInfo);
return resumeInfo;
}
// 生成打招呼内容
async function generateGreeting(resumeContent, jobInfo) {
const apiKey = GM_getValue('openai_api_key', '');
if (!apiKey) {
throw new Error('请先配置OpenAI API Key');
}
const prompt = `
请根据以下简历内容和职位要求,帮我生成专业的BOSS直聘打招呼内容(仅需要返回打招呼内容):
生成的打招呼内容要求:
1. 突出匹配度,分3点说明优势,并在每个点后换行(禁止用Markdown格式)
2. 包含可量化的成果
3. 保持口语化但专业
4. 限制在200字以内
【我的简历内容】
${resumeContent}
【投递岗位要求】
${jobInfo.jobDetail}
`;
try {
const response = await callOpenAI(apiKey, prompt);
displayResult(response);
} catch (error) {
throw new Error('AI生成失败: ' + error.message);
}
}
// 存储每个页面的生成结果
const pageResults = new Map();
// 调用OpenAI API
function callOpenAI(apiKey, prompt) {
return new Promise((resolve, reject) => {
const requestURL = window.location.href; // 添加requestURL变量定义
const result = document.getElementById('result');
result.textContent = '';
result.style.display = 'block';
document.getElementById('copyBtn').style.display = 'none';
updateStatus('正在生成内容...', true);
const apiUrl = document.getElementById('apiUrl').value;
const model = document.getElementById('modelSelect').value;
// 修改发送请求时的模型选择逻辑
GM_xmlhttpRequest({
method: 'POST',
url: apiUrl,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
data: JSON.stringify({
model: getActualModelId(apiUrl, model),
messages: [{
role: 'user',
content: prompt
}],
temperature: 0.7,
max_tokens: 512,
top_p: 0.7,
top_k: 50,
frequency_penalty: 0.5,
n: 1,
stream: false,
stop: ['null'],
response_format: {
type: 'text'
}
}),
onload: function(response) {
// 保存生成的内容到对应的页面URL
if (response.status === 200) {
const data = JSON.parse(response.responseText);
const messContent = data.choices[0].message.content;
var content;
if (messContent.includes('<think>')) {
const endThinkIndex = messContent.indexOf('</think>') + '</think>'.length;
content = messContent.substring(endThinkIndex).trim();
} else {
content = messContent;
}
// 保存内容到对应的URL
pageResults.set(requestURL, content);
// 只有当前页面URL匹配时才更新显示
if (window.location.href === requestURL) {
result.textContent = content;
updateStatus('生成完成');
document.getElementById('copyBtn').style.display = 'block';
} else {
console.log('页面已切换,内容已保存');
}
resolve(content);
return;
}
if (response.status === 200) {
const data = JSON.parse(response.responseText);
const messContent = data.choices[0].message.content;
// const content = data.choices[0].message.content;
var content; // 使用 var 声明,作用域是整个函数
if (messContent.includes('<think>')) {
// 找到 </think> 标签的位置,并提取后面的内容
const endThinkIndex = messContent.indexOf('</think>') + '</think>'.length;
content = messContent.substring(endThinkIndex).trim();
} else {
content = messContent;
}
result.textContent = content;
updateStatus('生成完成');
document.getElementById('copyBtn').style.display = 'block';
resolve(content);
} else {
reject(new Error(`API请求失败: ${response.status}`));
}
},
onerror: function(error) {
reject(new Error('网络请求失败'));
}
});
});
}
// 显示生成结果
function displayResult(content) {
const currentURL = window.location.href;
const result = document.getElementById('result');
const copyBtn = document.getElementById('copyBtn');
// 检查是否有当前页面的保存内容
const savedContent = pageResults.get(currentURL);
if (savedContent) {
content = savedContent;
// 使用后立即删除保存的内容
pageResults.delete(currentURL);
}
// 格式化内容
const formattedContent = content
.replace(/\n/g, '\n\n')
.replace(/•/g, '\n•')
.trim();
result.textContent = formattedContent;
updateStatus('生成完成');
copyBtn.style.display = 'block';
}
// 启动脚本
if (window.location.href.includes('zhipin.com/job_detail')) {
createUI();
// 立即提取并显示职位信息
const jobInfo = extractJobInfo();
document.getElementById('targetJob').textContent = jobInfo.jobName;
document.getElementById('infoPanel').style.display = 'block';
}
})();