1 line
22 KiB
JSON
1 line
22 KiB
JSON
{"repo": "cahaaa/study-abroad-application-helper-llm", "files": {"main.py": {"3041dc0fb91758cd765f640cedaafcccf7b1be43": {"t": 1785674056.5971727, "n": 18955, "h": "36cc6331e2bbd813", "c": "import os\nimport sys\nimport datetime\nimport requests\nimport json\nfrom PyQt5.QtWidgets import (\n QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,\n QLineEdit, QTextEdit, QComboBox, QPushButton, QLabel, QFileDialog, QGroupBox,\n QProgressBar, QMessageBox\n)\nfrom PyQt5.QtGui import QPixmap, QIcon\nfrom PyQt5.QtCore import Qt, QThread, pyqtSignal\nfrom pylatex import Document, Section, Subsection, Command, Figure\nfrom pylatex.utils import NoEscape\n\n# 确保输出目录存在\nOUTPUT_DIR = \"output\"\nif not os.path.exists(OUTPUT_DIR):\n os.makedirs(OUTPUT_DIR)\n\n\nclass DeepSeekAPIWorker(QThread):\n \"\"\"后台线程处理DeepSeek API调用\"\"\"\n finished = pyqtSignal(str, bool) # (生成内容, 是否成功)\n progress = pyqtSignal(int) # 进度百分比\n\n def __init__(self, api_key, prompt, model=\"deepseek-chat\", parent=None):\n super().__init__(parent)\n self.api_key = api_key\n self.prompt = prompt\n self.model = model\n self.url = \"https://api.deepseek.com/v1/chat/completions\"\n\n def run(self):\n try:\n headers = {\n \"Authorization\": f\"Bearer {self.api_key}\",\n \"Content-Type\": \"application/json\"\n }\n\n data = {\n \"model\": self.model,\n \"messages\": [\n {\"role\": \"system\",\n \"content\": \"你是一位专业的留学求职文书助手,根据用户提供的信息生成高质量的个性化文书。\"},\n {\"role\": \"user\", \"content\": self.prompt}\n ],\n \"temperature\": 0.7,\n \"max_tokens\": 2000,\n \"stream\": False\n }\n\n self.progress.emit(30)\n response = requests.post(self.url, headers=headers, json=data, timeout=120)\n self.progress.emit(70)\n\n if response.status_code == 200:\n result = response.json()\n content = result['choices'][0]['message']['content']\n self.progress.emit(100)\n self.finished.emit(content, True)\n else:\n error_msg = f\"API错误: {response.status_code}\\n{response.text}\"\n self.finished.emit(error_msg, False)\n\n except Exception as e:\n self.finished.emit(f\"请求异常: {str(e)}\", False)\n\n\nclass ResumeGeneratorApp(QMainWindow):\n def __init__(self):\n super().__init__()\n self.setWindowTitle(\"留学求职文书助手 - DeepSeek集成版\")\n self.setGeometry(100, 100, 900, 700)\n\n # 主控件\n main_widget = QWidget()\n self.setCentralWidget(main_widget)\n main_layout = QVBoxLayout(main_widget)\n\n # 创建表单区域\n self.create_api_group(main_layout)\n self.create_personal_info_group(main_layout)\n self.create_academic_group(main_layout)\n self.create_target_group(main_layout)\n self.create_action_buttons(main_layout)\n self.create_progress_bar(main_layout)\n\n # 初始化数据\n self.api_key = \"\"\n self.photo_path = \"\"\n self.generated_content = \"\"\n self.generated_tex_path = \"\"\n\n def create_api_group(self, layout):\n group = QGroupBox(\"DeepSeek API 设置\")\n form = QFormLayout()\n\n # API 密钥输入\n self.api_input = QLineEdit()\n self.api_input.setPlaceholderText(\"输入DeepSeek API密钥 (sk-...)\")\n self.api_input.setEchoMode(QLineEdit.Password)\n form.addRow(QLabel(\"API密钥:\"), self.api_input)\n\n # 模型选择\n self.model_combo = QComboBox()\n self.model_combo.addItems([\"deepseek-chat (V3通用模型)\", \"deepseek-reasoner (R1推理模型)\"])\n form.addRow(QLabel(\"模型选择:\"), self.model_combo)\n\n group.setLayout(form)\n layout.addWidget(group)\n\n def create_personal_info_group(self, layout):\n group = QGroupBox(\"个人信息\")\n form = QFormLayout()\n\n self.name_input = QLineEdit()\n self.email_input = QLineEdit()\n self.phone_input = QLineEdit()\n self.photo_button = QPushButton(\"选择照片\")\n self.photo_preview = QLabel()\n self.photo_preview.setFixedSize(100, 100)\n self.photo_preview.setStyleSheet(\"border: 1px solid gray; background-color: #f0f0f0;\")\n self.photo_preview.setAlignment(Qt.AlignCenter)\n self.photo_preview.setText(\"无照片\")\n\n photo_layout = QHBoxLayout()\n photo_layout.addWidget(self.photo_button)\n photo_layout.addWidget(self.photo_preview)\n\n form.addRow(QLabel(\"姓名:\"), self.name_input)\n form.addRow(QLabel(\"邮箱:\"), self.email_input)\n form.addRow(QLabel(\"电话:\"), self.phone_input)\n form.addRow(QLabel(\"照片:\"), photo_layout)\n\n self.photo_button.clicked.connect(self.select_photo)\n\n group.setLayout(form)\n layout.addWidget(group)\n\n def create_academic_group(self, layout):\n group = QGroupBox(\"学术背景\")\n form = QFormLayout()\n\n self.university_input = QLineEdit()\n self.major_input = QLineEdit()\n self.gpa_input = QLineEdit()\n self.awards_input = QTextEdit()\n self.research_input = QTextEdit()\n self.competitions_input = QTextEdit()\n\n # 设置文本编辑框高度和样式\n for text_edit in [self.awards_input, self.research_input, self.competitions_input]:\n text_edit.setFixedHeight(80)\n text_edit.setStyleSheet(\"background-color: #f8f8f8; border: 1px solid #ddd;\")\n\n form.addRow(QLabel(\"院校:\"), self.university_input)\n form.addRow(QLabel(\"专业:\"), self.major_input)\n form.addRow(QLabel(\"GPA:\"), self.gpa_input)\n form.addRow(QLabel(\"获奖经历:\"), self.awards_input)\n form.addRow(QLabel(\"科研经历:\"), self.research_input)\n form.addRow(QLabel(\"竞赛经历:\"), self.competitions_input)\n\n group.setLayout(form)\n layout.addWidget(group)\n\n def create_target_group(self, layout):\n group = QGroupBox(\"申请目标\")\n form = QFormLayout()\n\n self.target_type = QComboBox()\n self.target_type.addItems([\"留学申请文书\", \"求职简历\"])\n\n self.target_input = QTextEdit()\n self.target_input.setPlaceholderText(\"输入目标院校/专业 或 公司/职位\")\n self.target_input.setFixedHeight(80)\n self.target_input.setStyleSheet(\"background-color: #f8f8f8; border: 1px solid #ddd;\")\n\n form.addRow(QLabel(\"文书类型:\"), self.target_type)\n form.addRow(QLabel(\"申请目标:\"), self.target_input)\n\n group.setLayout(form)\n layout.addWidget(group)\n\n def create_progress_bar(self, layout):\n self.progress_bar = QProgressBar()\n self.progress_bar.setRange(0, 100)\n self.progress_bar.setVisible(False)\n self.progress_bar.setStyleSheet(\"\"\"\n QProgressBar {\n border: 1px solid #3498db;\n border-radius: 5px;\n text-align: center;\n background: #f0f0f0;\n height: 20px;\n }\n QProgressBar::chunk {\n background-color: #3498db;\n width: 10px;\n }\n \"\"\")\n layout.addWidget(self.progress_bar)\n\n def create_action_buttons(self, layout):\n button_layout = QHBoxLayout()\n\n self.generate_btn = QPushButton(\"生成文书\")\n self.preview_btn = QPushButton(\"预览PDF\")\n self.export_btn = QPushButton(\"导出LaTeX\")\n\n # 设置按钮样式\n btn_style = \"\"\"\n QPushButton {\n padding: 10px 20px;\n font-weight: bold;\n border-radius: 5px;\n min-width: 120px;\n }\n QPushButton:hover {\n opacity: 0.9;\n }\n \"\"\"\n self.generate_btn.setStyleSheet(btn_style + \"background-color: #3498db; color: white;\")\n self.preview_btn.setStyleSheet(btn_style + \"background-color: #2ecc71; color: white;\")\n self.export_btn.setStyleSheet(btn_style + \"background-color: #9b59b6; color: white;\")\n\n self.generate_btn.clicked.connect(self.generate_document)\n self.preview_btn.clicked.connect(self.preview_pdf)\n self.export_btn.clicked.connect(self.export_latex)\n\n button_layout.addWidget(self.generate_btn)\n button_layout.addWidget(self.preview_btn)\n button_layout.addWidget(self.export_btn)\n\n layout.addLayout(button_layout)\n\n def select_photo(self):\n path, _ = QFileDialog.getOpenFileName(\n self, \"选择个人照片\", \"\", \"图片文件 (*.png *.jpg *.jpeg)\"\n )\n if path:\n self.photo_path = path\n pixmap = QPixmap(path).scaled(\n 100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation\n )\n self.photo_preview.setPixmap(pixmap)\n\n def generate_document(self):\n # 验证API密钥\n api_key = self.api_input.text().strip()\n if not api_key:\n QMessageBox.warning(self, \"API密钥缺失\", \"请输入有效的DeepSeek API密钥\")\n return\n\n # 收集用户数据\n user_data = self.collect_user_data()\n\n # 根据文书类型构建不同的提示词\n if \"留学\" in self.target_type.currentText():\n prompt = self.build_study_abroad_prompt(user_data)\n else:\n prompt = self.build_job_application_prompt(user_data)\n\n # 显示进度条\n self.progress_bar.setVisible(True)\n self.progress_bar.setValue(0)\n self.statusBar().showMessage(\"正在生成文书内容...\")\n\n # 确定模型\n model = \"deepseek-chat\"\n if \"R1\" in self.model_combo.currentText():\n model = \"deepseek-reasoner\"\n\n # 启动后台线程调用DeepSeek API\n self.worker = DeepSeekAPIWorker(api_key, prompt, model)\n self.worker.progress.connect(self.progress_bar.setValue)\n self.worker.finished.connect(self.handle_api_response)\n self.worker.start()\n\n def collect_user_data(self):\n \"\"\"收集所有用户输入数据\"\"\"\n return {\n \"name\": self.name_input.text(),\n \"email\": self.email_input.text(),\n \"phone\": self.phone_input.text(),\n \"university\": self.university_input.text(),\n \"major\": self.major_input.text(),\n \"gpa\": self.gpa_input.text(),\n \"awards\": self.awards_input.toPlainText(),\n \"research\": self.research_input.toPlainText(),\n \"competitions\": self.competitions_input.toPlainText(),\n \"target\": self.target_input.toPlainText(),\n \"doc_type\": self.target_type.currentText(),\n \"photo_path\": self.photo_path\n }\n\n def build_study_abroad_prompt(self, data):\n \"\"\"构建留学申请文书提示词\"\"\"\n return f\"\"\"\n 你是一位专业的留学申请文书写作专家,请根据以下信息为申请人{data['name']}撰写一份留学申请文书:要求返回latex格式\n 的代码,能够正常使用latex编译,注意,输出的文件内容应该符合留学申请文书个人陈述的要求,内容应该均为英文。\n 文书中不应该出现中文内容,非常重要。\n \n 文书格式应该符合院校的要求,美观。\n\n ## 申请目标\n {data['target']}\n\n ## 个人信息\n - 姓名:{data['name']}\n - 邮箱:{data['email']}\n - 电话:{data['phone']}\n\n ## 教育背景\n - 院校:{data['university']}\n - 专业:{data['major']}\n - GPA:{data['gpa']}\n\n ## 经历与成就\n - 获奖经历:{data['awards']}\n - 科研经历:{data['research']}\n - 竞赛经历:{data['competitions']}\n\n ## 写作要求\n 1. 文书应突出申请人的学术能力和研究潜力\n 2. 结合申请目标说明申请动机\n 3. 展现个人特质和独特优势\n 4. 结构清晰,语言专业流畅\n 5. 长度应该遵循申请院校的文书要求,请搜索你的知识库来确定,如果没法准确判断,则输出1000词左右。\n \"\"\"\n\n def build_job_application_prompt(self, data):\n \"\"\"构建求职简历提示词\"\"\"\n return f\"\"\"\n 你是一位专业的求职简历写作专家,请根据以下信息为申请人{data['name']}撰写一份求职简历的自我评价部分,要求返回latex格式\n 的代码,能够正常使用latex编译。简历应该美观,内容清晰。\n\n ## 申请目标\n {data['target']}\n\n ## 个人信息\n - 姓名:{data['name']}\n - 邮箱:{data['email']}\n - 电话:{data['phone']}\n\n ## 教育背景\n - 院校:{data['university']}\n - 专业:{data['major']}\n - GPA:{data['gpa']}\n\n ## 经历与成就\n - 获奖经历:{data['awards']}\n - 科研经历:{data['research']}\n - 竞赛经历:{data['competitions']}\n\n ## 写作要求\n 1. 突出与目标职位相关的技能和经验\n 2. 量化成就和贡献\n 3. 展现职业素养和团队合作能力\n 4. 语言精练专业,长度在300-500字\n 5. 格式使用Markdown,包含标题和项目符号\n \"\"\"\n\n def handle_api_response(self, content, success):\n \"\"\"处理API返回结果\"\"\"\n self.progress_bar.setVisible(False)\n\n if success:\n self.generated_content = content\n self.statusBar().showMessage(\"文书内容生成成功!\", 5000)\n\n # 生成LaTeX文档\n user_data = self.collect_user_data()\n doc = self.create_latex_document(user_data, content)\n\n # 生成唯一的文件名\n timestamp = datetime.datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n doc_type = \"study\" if \"留学\" in user_data['doc_type'] else \"job\"\n filename = f\"{doc_type}_application_{timestamp}.tex\"\n tex_path = os.path.join(OUTPUT_DIR, filename)\n\n # 保存文件到output目录\n doc.generate_tex(tex_path)\n self.generated_tex_path = tex_path\n\n # 显示预览\n preview_dialog = QMessageBox(self)\n preview_dialog.setWindowTitle(\"生成预览\")\n preview_dialog.setTextFormat(Qt.RichText)\n preview_dialog.setText(f\"<h3>文书内容预览</h3><p>{content[:1000]}...</p>\")\n preview_dialog.setDetailedText(f\"文件已保存至: {tex_path}\")\n preview_dialog.exec_()\n else:\n QMessageBox.critical(self, \"生成失败\", f\"文书生成失败:\\n{content}\")\n\n def create_latex_document(self, data, generated_content):\n \"\"\"创建包含生成内容的LaTeX文档\"\"\"\n doc = Document(documentclass=\"article\")\n\n # 添加LaTeX包\n doc.preamble.append(Command(\"usepackage\", \"graphicx\"))\n doc.preamble.append(Command(\"usepackage\", \"xcolor\"))\n doc.preamble.append(Command(\"usepackage\", \"fontspec\"))\n doc.preamble.append(Command(\"usepackage\", [\"geometry\", \"a4paper\", \"margin=1.5cm\"]))\n doc.preamble.append(Command(\"usepackage\", \"parskip\"))\n\n # 设置中文字体\n doc.preamble.append(NoEscape(r\"\\setmainfont{Noto Serif CJK SC}\"))\n doc.preamble.append(NoEscape(r\"\\setsansfont{Noto Sans CJK SC}\"))\n\n # 文档标题\n doc.preamble.append(Command(\"title\", f\"{data['doc_type']} - {data['name']}\"))\n doc.preamble.append(Command(\"author\", data['name']))\n doc.append(NoEscape(r\"\\maketitle\"))\n\n # 添加照片部分\n if data[\"photo_path\"]:\n # 复制照片到输出目录\n try:\n photo_filename = os.path.basename(data[\"photo_path\"])\n output_photo_path = os.path.join(OUTPUT_DIR, photo_filename)\n\n # 如果照片不在输出目录,则复制过去\n if not os.path.exists(output_photo_path):\n import shutil\n shutil.copy(data[\"photo_path\"], output_photo_path)\n\n with doc.create(Figure(position=\"h!\")) as photo_fig:\n photo_fig.add_image(photo_filename, width=NoEscape(r\"0.2\\textwidth\"))\n photo_fig.add_caption(\"个人照片\")\n except Exception as e:\n print(f\"照片处理错误: {str(e)}\")\n\n # 联系信息部分\n with doc.create(Section(\"联系方式\")):\n doc.append(f\"姓名: {data['name']}\\n\\n\")\n doc.append(f\"邮箱: {data['email']}\\n\\n\")\n doc.append(f\"电话: {data['phone']}\\n\\n\")\n\n # 教育背景部分\n with doc.create(Section(\"教育背景\")):\n doc.append(f\"{data['university']} | {data['major']}\\n\\n\")\n doc.append(f\"GPA: {data['gpa']}\\n\\n\")\n\n # 添加AI生成的内容\n if \"留学\" in data['doc_type']:\n with doc.create(Section(\"申请陈述\")):\n doc.append(NoEscape(generated_content.replace(\"\\n\\n\", \"\\n\\n\")))\n else:\n with doc.create(Section(\"求职意向\")):\n doc.append(f\"目标职位: {data['target']}\\n\\n\")\n with doc.create(Section(\"个人陈述\")):\n doc.append(NoEscape(generated_content.replace(\"\\n\\n\", \"\\n\\n\")))\n\n # 添加成就部分\n if data['awards']:\n with doc.create(Section(\"获奖经历\")):\n doc.append(NoEscape(data['awards'].replace(\"\\n\", \"\\n\\n\")))\n\n if data['research']:\n with doc.create(Section(\"科研经历\")):\n doc.append(NoEscape(data['research'].replace(\"\\n\", \"\\n\\n\")))\n\n if data['competitions']:\n with doc.create(Section(\"竞赛经历\")):\n doc.append(NoEscape(data['competitions'].replace(\"\\n\", \"\\n\\n\")))\n\n return doc\n\n def preview_pdf(self):\n if not hasattr(self, \"generated_tex_path\") or not self.generated_tex_path:\n self.statusBar().showMessage(\"请先生成文书\", 3000)\n return\n\n # 在实际应用中应调用pdflatex编译\n tex_file = self.generated_tex_path\n pdf_path = tex_file.replace(\".tex\", \".pdf\")\n\n # 检查系统是否安装了pdflatex\n if os.system(\"pdflatex --version\") == 0:\n # 在output目录中编译\n os.chdir(OUTPUT_DIR)\n os.system(f\"pdflatex {os.path.basename(tex_file)}\")\n\n if os.path.exists(pdf_path):\n # 跨平台打开PDF文件\n if sys.platform == \"win32\":\n os.startfile(pdf_path)\n elif sys.platform == \"darwin\": # macOS\n os.system(f\"open '{pdf_path}'\")\n else: # Linux\n os.system(f\"xdg-open '{pdf_path}'\")\n else:\n QMessageBox.warning(self, \"PDF生成失败\", \"无法生成PDF文件,请检查LaTeX安装\")\n else:\n QMessageBox.information(\n self,\n \"PDF预览\",\n \"PDF预览需要安装LaTeX环境(如MiKTeX或TeX Live)\\n\"\n f\"生成的LaTeX文件已保存至: {self.generated_tex_path}\"\n )\n\n def export_latex(self):\n if not hasattr(self, \"generated_tex_path\") or not self.generated_tex_path:\n self.statusBar().showMessage(\"请先生成文书\", 3000)\n return\n\n # 保存LaTeX文件\n default_filename = os.path.basename(self.generated_tex_path)\n file_path, _ = QFileDialog.getSaveFileName(\n self, \"保存LaTeX文件\", os.path.join(OUTPUT_DIR, default_filename), \"LaTeX文件 (*.tex)\"\n )\n\n if file_path:\n if not file_path.endswith(\".tex\"):\n file_path += \".tex\"\n\n try:\n with open(self.generated_tex_path, \"r\", encoding=\"utf-8\") as src:\n with open(file_path, \"w\", encoding=\"utf-8\") as dest:\n dest.write(src.read())\n self.statusBar().showMessage(f\"LaTeX文件已保存: {file_path}\", 5000)\n except Exception as e:\n QMessageBox.critical(self, \"保存失败\", f\"文件保存失败:\\n{str(e)}\")\n\n\nif __name__ == \"__main__\":\n app = QApplication(sys.argv)\n\n # 设置应用样式\n app.setStyle(\"Fusion\")\n app.setStyleSheet(\"\"\"\n QGroupBox {\n font-weight: bold;\n border: 1px solid #3498db;\n border-radius: 5px;\n margin-top: 1ex;\n padding-top: 10px;\n }\n QGroupBox::title {\n subcontrol-origin: margin;\n subcontrol-position: top center;\n padding: 0 5px;\n background-color: #3498db;\n color: white;\n border-radius: 3px;\n }\n QLabel {\n font-size: 12px;\n }\n QLineEdit, QTextEdit {\n border: 1px solid #ddd;\n border-radius: 3px;\n padding: 5px;\n }\n \"\"\")\n\n window = ResumeGeneratorApp()\n window.show()\n sys.exit(app.exec_())"}}}} |