Greasy Fork is available in English.

BOSS直聘-AI招呼助手

BOSS直聘 - AI自动生成求职打招呼内容 - 智能简历助手!

// ==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';
    }
})();