mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-09-07 00:17:17 +08:00
make prompt can be read from local file
This commit is contained in:
@@ -118,7 +118,8 @@ export const ensureStorageFileExist = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const readStorageFile = (fileName) => {
|
export const readStorageFile = (fileName, { isJson } = {}) => {
|
||||||
|
isJson = isJson ?? true
|
||||||
const joinedPath = path.join(storageFilePath, fileName)
|
const joinedPath = path.join(storageFilePath, fileName)
|
||||||
|
|
||||||
if (!fs.existsSync(
|
if (!fs.existsSync(
|
||||||
@@ -129,21 +130,36 @@ export const readStorageFile = (fileName) => {
|
|||||||
|
|
||||||
let o
|
let o
|
||||||
try {
|
try {
|
||||||
o = JSON.parse(
|
const content = fs.readFileSync(joinedPath)
|
||||||
fs.readFileSync(joinedPath)
|
if (isJson) {
|
||||||
)
|
o = JSON.parse(content)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
o = content.toString()
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
fs.existsSync(joinedPath) && fs.unlinkSync(joinedPath)
|
fs.existsSync(joinedPath) && fs.unlinkSync(joinedPath)
|
||||||
ensureStorageFileExist()
|
ensureStorageFileExist()
|
||||||
o = JSON.parse(defaultStorageFileContentMap[fileName])
|
if (isJson) {
|
||||||
|
o = JSON.parse(defaultStorageFileContentMap[fileName] ?? 'null')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
o = defaultStorageFileContentMap[fileName] ?? null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
export const writeStorageFile = async (fileName, content) => {
|
export const writeStorageFile = async (fileName, content, { isJson } = {}) => {
|
||||||
|
isJson = isJson ?? true
|
||||||
const filePath = path.join(storageFilePath, fileName)
|
const filePath = path.join(storageFilePath, fileName)
|
||||||
const fileContent = JSON.stringify(content)
|
let fileContent
|
||||||
|
if (isJson) {
|
||||||
|
fileContent = JSON.stringify(content)
|
||||||
|
} else {
|
||||||
|
fileContent = content
|
||||||
|
}
|
||||||
return fsPromise.writeFile(
|
return fsPromise.writeFile(
|
||||||
filePath,
|
filePath,
|
||||||
fileContent
|
fileContent
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ipcMain, shell, app } from 'electron'
|
import { ipcMain, shell, app } from 'electron'
|
||||||
|
import path from 'path'
|
||||||
import * as childProcess from 'node:child_process'
|
import * as childProcess from 'node:child_process'
|
||||||
import {
|
import {
|
||||||
ensureConfigFileExist,
|
ensureConfigFileExist,
|
||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
readConfigFile,
|
readConfigFile,
|
||||||
writeConfigFile,
|
writeConfigFile,
|
||||||
readStorageFile,
|
readStorageFile,
|
||||||
writeStorageFile
|
writeStorageFile,
|
||||||
|
storageFilePath
|
||||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||||
import { ChildProcess } from 'child_process'
|
import { ChildProcess } from 'child_process'
|
||||||
import * as JSONStream from 'JSONStream'
|
import * as JSONStream from 'JSONStream'
|
||||||
@@ -32,6 +33,11 @@ import { WriteStream } from 'node:fs'
|
|||||||
import { hasOwn } from '@vue/shared'
|
import { hasOwn } from '@vue/shared'
|
||||||
import { createLlmConfigWindow, llmConfigWindow } from '../../../window/llmConfigWindow'
|
import { createLlmConfigWindow, llmConfigWindow } from '../../../window/llmConfigWindow'
|
||||||
import { createResumeEditorWindow, resumeEditorWindow } from '../../../window/resumeEditorWindow'
|
import { createResumeEditorWindow, resumeEditorWindow } from '../../../window/resumeEditorWindow'
|
||||||
|
import { getValidTemplate } from '../../READ_NO_REPLY_AUTO_REMINDER/boss-operation'
|
||||||
|
import {
|
||||||
|
autoReminderPromptTemplateFileName,
|
||||||
|
writeDefaultAutoRemindPrompt
|
||||||
|
} from '../../READ_NO_REPLY_AUTO_REMINDER/boss-operation'
|
||||||
|
|
||||||
export default function initIpc() {
|
export default function initIpc() {
|
||||||
ipcMain.on('open-external-link', (_, link) => {
|
ipcMain.on('open-external-link', (_, link) => {
|
||||||
@@ -490,7 +496,21 @@ export default function initIpc() {
|
|||||||
})
|
})
|
||||||
return defer.promise
|
return defer.promise
|
||||||
})
|
})
|
||||||
|
ipcMain.on('no-reply-reminder-prompt-edit', async () => {
|
||||||
|
const template = await readStorageFile(autoReminderPromptTemplateFileName, { isJson: false })
|
||||||
|
if (!template) {
|
||||||
|
await writeDefaultAutoRemindPrompt()
|
||||||
|
}
|
||||||
|
const filePath = path.join(storageFilePath, autoReminderPromptTemplateFileName)
|
||||||
|
shell.openPath(filePath)
|
||||||
|
})
|
||||||
ipcMain.on('close-resume-editor', () => resumeEditorWindow?.close())
|
ipcMain.on('close-resume-editor', () => resumeEditorWindow?.close())
|
||||||
|
ipcMain.handle('check-if-auto-remind-prompt-valid', async () => {
|
||||||
|
await getValidTemplate()
|
||||||
|
})
|
||||||
|
ipcMain.handle('overwrite-auto-remind-prompt-with-default', async () => {
|
||||||
|
await writeDefaultAutoRemindPrompt()
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('exit-app-immediately', () => {
|
ipcMain.handle('exit-app-immediately', () => {
|
||||||
app.exit(0)
|
app.exit(0)
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { Page } from 'puppeteer'
|
import { Page } from 'puppeteer'
|
||||||
import { sleepWithRandomDelay, sleep } from '@geekgeekrun/utils/sleep.mjs'
|
import { sleepWithRandomDelay, sleep } from '@geekgeekrun/utils/sleep.mjs'
|
||||||
import { completes } from '@geekgeekrun/utils/gpt-request.mjs'
|
import { completes } from '@geekgeekrun/utils/gpt-request.mjs'
|
||||||
import { readConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
import {
|
||||||
|
readConfigFile,
|
||||||
|
readStorageFile,
|
||||||
|
writeStorageFile
|
||||||
|
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||||
import { formatResumeJsonToMarkdown } from '../../../common/utils/format-resume-json-to-markdown'
|
import { formatResumeJsonToMarkdown } from '../../../common/utils/format-resume-json-to-markdown'
|
||||||
|
|
||||||
export const sendLookForwardReplyEmotion = async (page: Page) => {
|
export const sendLookForwardReplyEmotion = async (page: Page) => {
|
||||||
@@ -38,14 +42,8 @@ const pickLlmConfigFromList = (llmConfigList) => {
|
|||||||
|
|
||||||
// let _index = 0
|
// let _index = 0
|
||||||
|
|
||||||
export const sendGptContent = async (page: Page, chatRecords) => {
|
const RESUME_PLACEHOLDER = `__REPLACE_REAL_RESUME_HERE__`
|
||||||
const resumeObject = (await readConfigFile('resumes.json'))?.[0]
|
const defaultPrompt = `**核心指令:**
|
||||||
const resumeContent = formatResumeJsonToMarkdown(resumeObject)
|
|
||||||
const chatList = [
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: `
|
|
||||||
**核心指令:**
|
|
||||||
你是一个智能求职助手,需要根据用户简历生成30字左右的提醒消息,满足以下要求:
|
你是一个智能求职助手,需要根据用户简历生成30字左右的提醒消息,满足以下要求:
|
||||||
1. 每次生成需满足:
|
1. 每次生成需满足:
|
||||||
- √ 包含1个核心技能 + 1个成果量化
|
- √ 包含1个核心技能 + 1个成果量化
|
||||||
@@ -56,7 +54,7 @@ export const sendGptContent = async (page: Page, chatRecords) => {
|
|||||||
- ✗ 严禁包含最近8条已经发过的内容(包括但不限于职位名称)
|
- ✗ 严禁包含最近8条已经发过的内容(包括但不限于职位名称)
|
||||||
|
|
||||||
**简历分析层:**
|
**简历分析层:**
|
||||||
请从以下简历内容中提取关键要素:\n\`\`\`markdown\n${resumeContent}\n\`\`\`\n
|
请从以下简历内容中提取关键要素:\n\`\`\`markdown\n${RESUME_PLACEHOLDER}\n\`\`\`\n
|
||||||
|
|
||||||
---
|
---
|
||||||
要求提取:
|
要求提取:
|
||||||
@@ -79,6 +77,34 @@ export const sendGptContent = async (page: Page, chatRecords) => {
|
|||||||
|
|
||||||
**输出格式:**
|
**输出格式:**
|
||||||
请确保仅回复一句话,以JSON响应,不要包含其他解释或内容;数据结构参考:\`{"response": "这里是将会发送给招聘者的内容"}\``
|
请确保仅回复一句话,以JSON响应,不要包含其他解释或内容;数据结构参考:\`{"response": "这里是将会发送给招聘者的内容"}\``
|
||||||
|
|
||||||
|
export const autoReminderPromptTemplateFileName = 'auto-reminder-resume-system-message-template.md'
|
||||||
|
export const getValidTemplate = async () => {
|
||||||
|
let template = await readStorageFile(autoReminderPromptTemplateFileName, { isJson: false })
|
||||||
|
if (!template) {
|
||||||
|
await writeDefaultAutoRemindPrompt()
|
||||||
|
template = defaultPrompt
|
||||||
|
}
|
||||||
|
if (!template.includes(RESUME_PLACEHOLDER)) {
|
||||||
|
const e = new Error(`简历内容占位符字符串不存在。占位字符串是 ${RESUME_PLACEHOLDER}`)
|
||||||
|
e.name = `RESUME_PLACEHOLDER_NOT_EXIST`
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
export const writeDefaultAutoRemindPrompt = async () => {
|
||||||
|
await writeStorageFile(autoReminderPromptTemplateFileName, defaultPrompt, { isJson: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendGptContent = async (page: Page, chatRecords) => {
|
||||||
|
const template = await getValidTemplate()
|
||||||
|
const resumeObject = (await readConfigFile('resumes.json'))?.[0]
|
||||||
|
const resumeContent = formatResumeJsonToMarkdown(resumeObject)
|
||||||
|
const chatList = [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: template.replace(RESUME_PLACEHOLDER, resumeContent)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
chatList.push({
|
chatList.push({
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="mb-0">
|
<el-form-item class="mb0">
|
||||||
<div>
|
<div>
|
||||||
<div>跟进话术 - 当发现已读不回的Boss时,将要向Boss发出:</div>
|
<div>跟进话术 - 当发现已读不回的Boss时,将要向Boss发出:</div>
|
||||||
<el-radio-group v-model="formContent.autoReminder.rechatContentSource">
|
<el-radio-group v-model="formContent.autoReminder.rechatContentSource">
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<el-form-item>
|
<el-form-item class="mb4px">
|
||||||
<div>
|
<div>
|
||||||
<el-button size="small" type="primary" @click="handleClickConfigLlm">
|
<el-button size="small" type="primary" @click="handleClickConfigLlm">
|
||||||
配置大语言模型
|
配置大语言模型
|
||||||
@@ -59,11 +59,20 @@
|
|||||||
style="background-color: #462ac4"
|
style="background-color: #462ac4"
|
||||||
>Qwen2.5</span
|
>Qwen2.5</span
|
||||||
>
|
>
|
||||||
模型;支持多个服务商提供的多个模型组合使用
|
模型,通过<a
|
||||||
|
class="font-size-12px pt0 pb0"
|
||||||
|
:style="{
|
||||||
|
color: 'var(--el-color-primary)',
|
||||||
|
}"
|
||||||
|
type="text"
|
||||||
|
href="javascript:void(0)"
|
||||||
|
@click.prevent="openIntroOfCompletion"
|
||||||
|
>对话补全</a
|
||||||
|
>接口实现消息生成;支持多个“服务商-模型”组合按权重搭配使用
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item class="mb4px">
|
||||||
<div>
|
<div>
|
||||||
<el-button size="small" type="primary" @click="handleClickEditResume">
|
<el-button size="small" type="primary" @click="handleClickEditResume">
|
||||||
编辑简历
|
编辑简历
|
||||||
@@ -73,6 +82,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item class="mb4px">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<el-button size="small" type="primary" @click="handleClickEditPrompt">
|
||||||
|
使用外部编辑器编辑 Prompt 模板
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="restoreDefaultTemplate">
|
||||||
|
还原默认 Prompt 模板
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="font-size-12px color-#666">
|
||||||
|
对生成效果不够满意?可在此查看、编辑 Prompt 模板。请在模板中需要插入简历的位置插入
|
||||||
|
__REPLACE_REAL_RESUME_HERE__
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item prop="recentMessageQuantityForLlm">
|
<el-form-item prop="recentMessageQuantityForLlm">
|
||||||
<div>
|
<div>
|
||||||
携带最近
|
携带最近
|
||||||
@@ -126,7 +151,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||||
import { dayjs, ElForm } from 'element-plus'
|
import { dayjs, ElForm, ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { RECHAT_CONTENT_SOURCE } from '../../../../common/enums/auto-start-chat'
|
import { RECHAT_CONTENT_SOURCE } from '../../../../common/enums/auto-start-chat'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -205,6 +230,34 @@ watch(
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await formRef.value!.validate()
|
await formRef.value!.validate()
|
||||||
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
|
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
|
||||||
|
try {
|
||||||
|
await electron.ipcRenderer.invoke('check-if-auto-remind-prompt-valid')
|
||||||
|
} catch (err) {
|
||||||
|
if (err?.message?.includes(`RESUME_PLACEHOLDER_NOT_EXIST`)) {
|
||||||
|
console.log(`Prompt 模板无效`, err)
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'Prompt 模板缺少简历内容占位符:<br /><b>__REPLACE_REAL_RESUME_HERE__</b><br /><br />您是否希望还原默认的 Prompt 模板?',
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
confirmButtonText: '是',
|
||||||
|
cancelButtonText: '否',
|
||||||
|
type: 'warning',
|
||||||
|
closeOnClickModal: false,
|
||||||
|
dangerouslyUseHTMLString: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(async () => {
|
||||||
|
await restoreDefaultTemplate()
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
} else {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: '用于生成自动提醒消息的 Prompt 检查未通过,请重试'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
router.replace({
|
router.replace({
|
||||||
path: '/geekAutoStartChatWithBoss/prepareRun',
|
path: '/geekAutoStartChatWithBoss/prepareRun',
|
||||||
query: { flow: 'read-no-reply-reminder' }
|
query: { flow: 'read-no-reply-reminder' }
|
||||||
@@ -219,6 +272,14 @@ function handleThrottleIntervalMinutesBlur() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const restoreDefaultTemplate = async () => {
|
||||||
|
await electron.ipcRenderer.invoke('overwrite-auto-remind-prompt-with-default')
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: '模板还原成功'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleClickLaunchLogin = () => {
|
const handleClickLaunchLogin = () => {
|
||||||
router.replace('/cookieAssistant')
|
router.replace('/cookieAssistant')
|
||||||
}
|
}
|
||||||
@@ -255,6 +316,17 @@ const handleClickEditResume = async () => {
|
|||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickEditPrompt = async () => {
|
||||||
|
await electron.ipcRenderer.send('no-reply-reminder-prompt-edit')
|
||||||
|
}
|
||||||
|
|
||||||
|
const openIntroOfCompletion = () => {
|
||||||
|
electron.ipcRenderer.send(
|
||||||
|
'open-external-link',
|
||||||
|
'https://api-docs.deepseek.com/zh-cn/api/create-chat-completion'
|
||||||
|
)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
Reference in New Issue
Block a user