mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-05-22 08:46:56 +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)
|
||||
|
||||
if (!fs.existsSync(
|
||||
@@ -129,21 +130,36 @@ export const readStorageFile = (fileName) => {
|
||||
|
||||
let o
|
||||
try {
|
||||
o = JSON.parse(
|
||||
fs.readFileSync(joinedPath)
|
||||
)
|
||||
const content = fs.readFileSync(joinedPath)
|
||||
if (isJson) {
|
||||
o = JSON.parse(content)
|
||||
}
|
||||
else {
|
||||
o = content.toString()
|
||||
}
|
||||
} catch {
|
||||
fs.existsSync(joinedPath) && fs.unlinkSync(joinedPath)
|
||||
ensureStorageFileExist()
|
||||
o = JSON.parse(defaultStorageFileContentMap[fileName])
|
||||
if (isJson) {
|
||||
o = JSON.parse(defaultStorageFileContentMap[fileName] ?? 'null')
|
||||
}
|
||||
else {
|
||||
o = defaultStorageFileContentMap[fileName] ?? null
|
||||
}
|
||||
}
|
||||
|
||||
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 fileContent = JSON.stringify(content)
|
||||
let fileContent
|
||||
if (isJson) {
|
||||
fileContent = JSON.stringify(content)
|
||||
} else {
|
||||
fileContent = content
|
||||
}
|
||||
return fsPromise.writeFile(
|
||||
filePath,
|
||||
fileContent
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ipcMain, shell, app } from 'electron'
|
||||
|
||||
import path from 'path'
|
||||
import * as childProcess from 'node:child_process'
|
||||
import {
|
||||
ensureConfigFileExist,
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
readConfigFile,
|
||||
writeConfigFile,
|
||||
readStorageFile,
|
||||
writeStorageFile
|
||||
writeStorageFile,
|
||||
storageFilePath
|
||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||
import { ChildProcess } from 'child_process'
|
||||
import * as JSONStream from 'JSONStream'
|
||||
@@ -32,6 +33,11 @@ import { WriteStream } from 'node:fs'
|
||||
import { hasOwn } from '@vue/shared'
|
||||
import { createLlmConfigWindow, llmConfigWindow } from '../../../window/llmConfigWindow'
|
||||
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() {
|
||||
ipcMain.on('open-external-link', (_, link) => {
|
||||
@@ -490,7 +496,21 @@ export default function initIpc() {
|
||||
})
|
||||
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.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', () => {
|
||||
app.exit(0)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
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 {
|
||||
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'
|
||||
|
||||
export const sendLookForwardReplyEmotion = async (page: Page) => {
|
||||
@@ -38,14 +42,8 @@ const pickLlmConfigFromList = (llmConfigList) => {
|
||||
|
||||
// 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',
|
||||
content: `
|
||||
**核心指令:**
|
||||
const RESUME_PLACEHOLDER = `__REPLACE_REAL_RESUME_HERE__`
|
||||
const defaultPrompt = `**核心指令:**
|
||||
你是一个智能求职助手,需要根据用户简历生成30字左右的提醒消息,满足以下要求:
|
||||
1. 每次生成需满足:
|
||||
- √ 包含1个核心技能 + 1个成果量化
|
||||
@@ -56,7 +54,7 @@ export const sendGptContent = async (page: Page, chatRecords) => {
|
||||
- ✗ 严禁包含最近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": "这里是将会发送给招聘者的内容"}\``
|
||||
|
||||
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({
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb-0">
|
||||
<el-form-item class="mb0">
|
||||
<div>
|
||||
<div>跟进话术 - 当发现已读不回的Boss时,将要向Boss发出:</div>
|
||||
<el-radio-group v-model="formContent.autoReminder.rechatContentSource">
|
||||
@@ -37,7 +37,7 @@
|
||||
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
||||
"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-form-item class="mb4px">
|
||||
<div>
|
||||
<el-button size="small" type="primary" @click="handleClickConfigLlm">
|
||||
配置大语言模型
|
||||
@@ -59,11 +59,20 @@
|
||||
style="background-color: #462ac4"
|
||||
>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>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item class="mb4px">
|
||||
<div>
|
||||
<el-button size="small" type="primary" @click="handleClickEditResume">
|
||||
编辑简历
|
||||
@@ -73,6 +82,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<div>
|
||||
携带最近
|
||||
@@ -126,7 +151,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
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 { RECHAT_CONTENT_SOURCE } from '../../../../common/enums/auto-start-chat'
|
||||
const router = useRouter()
|
||||
@@ -205,6 +230,34 @@ watch(
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value!.validate()
|
||||
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({
|
||||
path: '/geekAutoStartChatWithBoss/prepareRun',
|
||||
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 = () => {
|
||||
router.replace('/cookieAssistant')
|
||||
}
|
||||
@@ -255,6 +316,17 @@ const handleClickEditResume = async () => {
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user