feat: enhance resume editor and formatResumeJsonToMarkdown; add the logic to send resume read from local to llm

This commit is contained in:
geekgeekrun
2025-04-11 23:36:55 +08:00
parent 0714b187bd
commit 901c3a51a0
3 changed files with 107 additions and 41 deletions

View File

@@ -0,0 +1,55 @@
export function formatResumeJsonToMarkdown(resume) {
const basicInfoText = [
['# 姓名', resume.content.name],
['# 工作年限', resume.content.workYearDesc],
['# 期望职位', resume.content.expectJob],
['# 个人优势', resume.content.userDescription]
]
.filter((it) => {
return Boolean(it[1]?.trim())
})
.map((it) => it.join('\n'))
.join('\n\n')
let formattedWorkExpText = resume.content.geekWorkExpList
.filter((it) => Boolean(it.company?.trim()))
.map((it) => {
const info = [
[`职务`, it.positionName],
[`任职时间`],
[`工作描述`, it.workDescription],
[`工作业绩`, it.performance]
].filter((it) => {
return Boolean(it[1]?.trim())
})
return [[`## ${it.company}`], ...info].map((it) => it.join('\n')).join('\n\n')
})
.join('\n')
if (formattedWorkExpText?.trim()) {
formattedWorkExpText = '# 工作经历\n' + formattedWorkExpText
}
let formattedProjWorkExpText = resume.content.geekProjExpList
.filter((it) => Boolean(it.name?.trim()))
.map((it) => {
const info = [
[`## ${it.name}`],
[`项目角色`, it.roleName],
[`项目时间`],
[`工作描述`, it.projectDescription],
[`工作业绩`, it.performance]
].filter((it) => {
return Boolean(it[1]?.trim())
})
return [[`## ${it.name}`], ...info].map((it) => it.join('\n')).join('\n\n')
})
.join('\n')
if (formattedProjWorkExpText?.trim()) {
formattedProjWorkExpText = '# 项目经历\n' + formattedProjWorkExpText
}
const result = `${basicInfoText}\n\n${formattedWorkExpText}\n\n${formattedProjWorkExpText}`
return result
}

View File

@@ -2,6 +2,7 @@ import { Page } from 'puppeteer'
import { sleepWithRandomDelay, sleep } from '@geekgeekrun/utils/sleep.mjs'
import { completes } from '@geekgeekrun/utils/gpt-request.mjs'
import { readConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import { formatResumeJsonToMarkdown } from '../../../common/utils/format-resume-json-to-markdown'
export const sendLookForwardReplyEmotion = async (page: Page) => {
const emotionEntryButtonProxy = await page.$('.chat-conversation .message-controls .btn-emotion')
@@ -18,10 +19,11 @@ export const sendLookForwardReplyEmotion = async (page: Page) => {
await lookForwardReplyEmojiProxy!.click()
}
const resumeContent = ``
// let _index = 0
export const sendGptContent = async (page: Page, chatRecords) => {
const resumeObject = (await readConfigFile('resumes.json'))?.[0]
const resumeContent = formatResumeJsonToMarkdown(resumeObject)
const chatList = [
{
role: 'system',
@@ -32,12 +34,12 @@ export const sendGptContent = async (page: Page, chatRecords) => {
- √ 包含1个核心技能 + 1个成果量化
- √ 使用不同句式模板至少准备5种
- √ 谦虚一些,头衔、工作年限等在历史记录信息中出现一次就好
- ✗ 禁与最近发送的几条相似或雷同
- ✗ 禁止使用专业术语堆砌
- ✗ 禁与最近发送的几条相似或雷同
- ✗ 严禁出现简历之外的词语
- ✗ 严禁包含最近8条已经发过的内容包括但不限于职位名称
**简历分析层:**
请从以下简历内容中提取关键要素:\n${resumeContent}\n
请从以下简历内容中提取关键要素:\n\`\`\`markdown\n${resumeContent}\n\`\`\`\n
---
要求提取:
@@ -53,7 +55,7 @@ export const sendGptContent = async (page: Page, chatRecords) => {
每次生成前执行:
1. 检查历史记录
2. 确保技能/成果组合未重复
3. 所生成的新消息,严禁包含最近8条已经发过的内容包括但不限于职位名称
3. 确保所生成的新消息包含最近8条已经发过的内容包括但不限于职位名称
4. 字数严格控制在10-40字
5. 避免感叹号等激进符号
6. 减少头衔“资深”、“高级”出现的频率严禁出现“专家”、“老兵”减少工作年限“x年”出现的频率

View File

@@ -5,6 +5,14 @@
<div class="mt1em mb1em flex flex-items-center flex-justify-between">
<span>简历编辑器</span>
</div>
<el-alert type="info" :closable="false" mb20px line-height-1.25em>
<ul pl16px>
<li>
此简历将作为提示词的一部分提交给语言大模型仅在匹配职位生成已读不回提醒消息时使用大部分信息非必填但在不填写的情况下可能会匹配到不准确的职位或生成预料之外的已读不回提醒消息
</li>
<li>期望薪资仅作匹配职位使用不会用作生成已读不回提醒消息</li>
</ul>
</el-alert>
<el-form
ref="formRef"
:model="formContent"
@@ -15,39 +23,33 @@
<div
:style="{
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
gridTemplateColumns: '1fr 1fr',
gap: '10px'
}"
>
<el-form-item prop="providerCompleteApiUrl" label="姓名">
<el-form-item label="姓名">
<el-input v-model="formContent.name" font-size-12px></el-input>
</el-form-item>
<el-form-item prop="providerCompleteApiUrl" label="性别">
<el-input v-model="formContent.gender" font-size-12px></el-input>
</el-form-item>
<el-form-item prop="providerCompleteApiUrl" label="年龄">
<el-input-number
v-model="formContent.age"
w-full
font-size-12px
:min="0"
:max="200"
:precision="0"
:step="1"
></el-input-number>
</el-form-item>
<el-form-item prop="providerCompleteApiUrl" label="学历">
<el-input v-model="formContent.degree" font-size-12px></el-input>
</el-form-item>
<el-form-item prop="providerCompleteApiUrl" label="工作年限">
<el-form-item label="工作年限">
<el-input v-model="formContent.workYearDesc" font-size-12px></el-input>
</el-form-item>
<el-form-item prop="providerCompleteApiUrl" label="期望职位">
<el-form-item label="期望职位">
<el-input v-model="formContent.expectJob" font-size-12px></el-input>
</el-form-item>
<el-form-item label="期望薪资k">
<div
:style="{
display: 'grid',
gridTemplateColumns: '1fr 1fr'
}"
>
<el-input v-model="formContent.expectSalary[0]" placeholder="下限" />
<el-input v-model="formContent.expectSalary[1]" placeholder="上限" />
</div>
</el-form-item>
</div>
<el-form-item prop="providerCompleteApiUrl" label="个人优势">
<el-form-item label="个人优势">
<el-input
v-model="formContent.userDescription"
type="textarea"
@@ -293,28 +295,27 @@
</el-form>
</main>
</div>
<footer flex pt10px pb10px pr20px flex-justify-between>
<div>
<!-- <el-button type="text" @click="handleTestAvailability">测试可用性</el-button> -->
</div>
<div>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
<footer pt10px pb10px flex flex-justify-center>
<div w768px flex flex-justify-between>
<div>
<!-- <el-button type="text" @click="handleTestAvailability">测试可用性</el-button> -->
</div>
<div>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</div>
</div>
</footer>
</div>
</template>
<script lang="ts" setup>
import { ElForm, ElButton } from 'element-plus'
import { ElForm, ElButton, ElAlert } from 'element-plus'
import { ref, onMounted } from 'vue'
import { ArrowUp, ArrowDown, Delete, Plus } from '@element-plus/icons-vue'
interface ResumeContent {
name: string
gender: string
age: string
degree: string
workYearDesc: string
expectJob: string
userDescription: string
@@ -334,19 +335,18 @@ interface ResumeContent {
projectDescription: string
performance: string
}>
expectSalary: [string, string]
}
const formRef = ref<InstanceType<typeof ElForm>>()
const getEmptyFormContent = () => {
const o: any = {
age: '',
degree: '',
expectJob: '',
gender: '',
name: '',
userDescription: '',
workYearDesc: '',
expectSalary: ['', ''],
geekWorkExpList: [],
geekProjExpList: []
}
@@ -375,6 +375,15 @@ onMounted(async () => {
for (const k of Object.keys(formContent.value)) {
formContent.value[k] = savedFileContent[k]
}
if (!formContent.value.expectSalary) {
formContent.value.expectSalary = ['', '']
}
if (!formContent.value.expectSalary?.[0] || /\D/.test(formContent.value.expectSalary?.[0])) {
formContent.value.expectSalary[0] = ''
}
if (!formContent.value.expectSalary?.[1] || /\D/.test(formContent.value.expectSalary?.[1])) {
formContent.value.expectSalary[1] = ''
}
if (!formContent.value.geekProjExpList?.length) {
formContent.value.geekProjExpList = [getNewProjExpItem()]
}