diff --git a/packages/ui/src/common/constant.ts b/packages/ui/src/common/constant.ts index 6e8d81e..9be1eac 100644 --- a/packages/ui/src/common/constant.ts +++ b/packages/ui/src/common/constant.ts @@ -1,2 +1,7 @@ export const SINGLE_ITEM_DEFAULT_SERVE_WEIGHT = 1 export const EXPECT_CHROMIUM_BUILD_ID = '139.0.7258.154' + +export const DEFAULT_CONSTANT_OPEN_CONTENT_SEGS = [ + `您好,我对贵公司岗位很感兴趣,希望能有机会进一步沟通,期待您的回复`, + `接下来的聊天,我将根据我简历里的内容,向您进行自我介绍` +] diff --git a/packages/ui/src/common/enums/auto-start-chat.ts b/packages/ui/src/common/enums/auto-start-chat.ts index 2be778a..9482af7 100644 --- a/packages/ui/src/common/enums/auto-start-chat.ts +++ b/packages/ui/src/common/enums/auto-start-chat.ts @@ -18,6 +18,16 @@ export enum RECHAT_LLM_FALLBACK { EXIT_REMINDER_PROGRAM = 2 } +export enum OPEN_CONTENT_SOURCE { + CONSTANT_CONTENT = 1, + GEMINI_WITH_CHAT_CONTEXT = 2 +} + +// export enum OPEN_LLM_FALLBACK { +// SEND_CONSTANT_CONTENT = 1, +// EXIT_REMINDER_PROGRAM = 2 +// } + export enum RUNNING_STATUS_ENUM { RUNNING = 0, NORMAL_EXITED = 1, diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts index 3937ecb..9309523 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/ipc/index.ts @@ -32,7 +32,7 @@ import { requestNewMessageContent } from '../../READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation' import { - autoReminderPromptTemplateFileName, + defaultPromptMap, writeDefaultAutoRemindPrompt } from '../../READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation' import { @@ -466,17 +466,19 @@ export default function initIpc() { const res = (await readConfigFile('resumes.json'))?.[0] return res?.content ?? null }) - ipcMain.on('no-reply-reminder-prompt-edit', async () => { - const template = await readStorageFile(autoReminderPromptTemplateFileName, { isJson: false }) + ipcMain.on('no-reply-reminder-prompt-edit', async (_, { type }) => { + const template = await readStorageFile(defaultPromptMap[type].fileName, { + isJson: false + }) if (!template) { - await writeDefaultAutoRemindPrompt() + await writeDefaultAutoRemindPrompt({ type }) } - const filePath = path.join(storageFilePath, autoReminderPromptTemplateFileName) + const filePath = path.join(storageFilePath, defaultPromptMap[type].fileName) shell.openPath(filePath) }) ipcMain.on('close-resume-editor', () => resumeEditorWindow?.close()) - ipcMain.handle('check-if-auto-remind-prompt-valid', async () => { - await getValidTemplate() + ipcMain.handle('check-if-auto-remind-prompt-valid', async (_, { type }) => { + await getValidTemplate({ type }) }) ipcMain.handle('check-is-resume-content-valid', async () => { const res = (await readConfigFile('resumes.json'))?.[0] @@ -486,8 +488,8 @@ export default function initIpc() { const res = (await readConfigFile('resumes.json'))?.[0] return resumeContentEnoughDetect(res) }) - ipcMain.handle('overwrite-auto-remind-prompt-with-default', async () => { - await writeDefaultAutoRemindPrompt() + ipcMain.handle('overwrite-auto-remind-prompt-with-default', async (_, { type }) => { + await writeDefaultAutoRemindPrompt({ type }) }) ipcMain.handle('check-if-llm-config-list-valid', async () => { const llmConfigList = await readConfigFile('llm.json') diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation.ts index 5b2227c..7ec0c4b 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/boss-operation.ts @@ -60,7 +60,10 @@ const pickLlmConfigFromList = (llmConfigList, blockModelSet) => { // let _index = 0 const RESUME_PLACEHOLDER = `__REPLACE_REAL_RESUME_HERE__` -const defaultPrompt = `**核心指令:** +export const defaultPromptMap = { + rechat: { + fileName: 'auto-reminder-resume-system-message-template.md', + content: `**核心指令:** 你是一个智能求职助手,需要根据用户简历生成30字左右的提醒消息,满足以下要求: 1. 每次生成需满足: - √ 包含1个核心技能 + 1个成果量化 @@ -94,15 +97,21 @@ const defaultPrompt = `**核心指令:** **输出格式:** 请确保仅回复一句话,以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 + }, + open: { + fileName: 'auto-reminder-open-message-template.md', + content: + '请根据我的简历,帮我写一句谦逊有礼貌的开场白。开头包含“您好”等类似敬语、结尾包含“期待回复”等类似话术。不必包含简历中的具体内容,但需要表达出应聘意向。请确保仅响应一句话,以JSON响应;数据结构参考:`{"response": "这里是将会发送给招聘者的内容"}`' } - if (!template.includes(RESUME_PLACEHOLDER)) { +} + +export const getValidTemplate = async ({ type }) => { + let template = await readStorageFile(defaultPromptMap[type].fileName, { isJson: false }) + if (!template) { + await writeDefaultAutoRemindPrompt({ type }) + template = defaultPromptMap[type].content + } + if (type === 'rechat' && !template.includes(RESUME_PLACEHOLDER)) { const e = new Error(`简历内容占位符字符串不存在。占位字符串是 ${RESUME_PLACEHOLDER}`) e.name = `RESUME_PLACEHOLDER_NOT_EXIST` throw e @@ -110,8 +119,19 @@ export const getValidTemplate = async () => { return template } -export const writeDefaultAutoRemindPrompt = async () => { - await writeStorageFile(autoReminderPromptTemplateFileName, defaultPrompt, { isJson: false }) +export const writeDefaultAutoRemindPrompt = async ({ type }) => { + switch (type) { + case 'rechat': + await writeStorageFile(defaultPromptMap[type].fileName, defaultPromptMap[type].content, { + isJson: false + }) + break + case 'open': + await writeStorageFile(defaultPromptMap[type].fileName, defaultPromptMap[type].content, { + isJson: false + }) + break + } } export const requestNewMessageContent = async ( @@ -119,24 +139,26 @@ export const requestNewMessageContent = async ( { requestScene, llmConfigIdForPick - }: { requestScene?: RequestSceneEnum; llmConfigIdForPick?: string[] } = {} + }: { + requestScene?: RequestSceneEnum + llmConfigIdForPick?: string[] + } = {} ) => { - const template = await getValidTemplate() + const systemMessageTemplate = await getValidTemplate({ type: 'rechat' }) const resumeObject = (await readConfigFile('resumes.json'))?.[0] const resumeContent = formatResumeJsonToMarkdown(resumeObject) const chatList = [ { role: 'system', - content: template.replace(RESUME_PLACEHOLDER, resumeContent) + content: systemMessageTemplate.replace(RESUME_PLACEHOLDER, resumeContent) } ] + const openMessageTemplate = await getValidTemplate({ type: 'open' }) chatList.push({ role: 'user', - content: - '请根据我的简历,帮我写一句谦逊有礼貌的开场白。开头包含“您好”等类似敬语、结尾包含“期待回复”等类似话术。不必包含简历中的具体内容,但需要表达出应聘意向。请确保仅响应一句话,以JSON响应;数据结构参考:`{"response": "这里是将会发送给招聘者的内容"}`' + content: openMessageTemplate }) // chatRecords = chatRecords.slice(chatRecords.length - _index) - // debugger for (const record of chatRecords) { const assistantJsonContent = JSON.stringify({ response: record.text @@ -246,14 +268,18 @@ export const requestNewMessageContent = async ( } } -export async function sendGptContent(page: Page, chatRecords) { +export async function getGptContent(chatRecords) { const textToSend = ( await requestNewMessageContent(chatRecords, { requestScene: RequestSceneEnum.readNoReplyAutoReminder }) ).responseText + return textToSend +} + +export async function sendMessage(page: Page, textToSend: string) { const chatInputSelector = `.chat-conversation .message-controls .chat-input` - const chatInputHandle = await page.$(chatInputSelector) + const chatInputHandle = (await page.$(chatInputSelector))! await chatInputHandle.click() await sleep(500) await chatInputHandle.click() diff --git a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts index 7d03628..0718b4f 100644 --- a/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts +++ b/packages/ui/src/main/flow/READ_NO_REPLY_AUTO_REMINDER_MAIN/index.ts @@ -1,7 +1,7 @@ import { bootstrap, launchBoss } from './bootstrap' import { MsgStatus, type ChatListItem } from './types' import { Browser, Page } from 'puppeteer' -import { sendGptContent, sendLookForwardReplyEmotion } from './boss-operation' +import { getGptContent, sendLookForwardReplyEmotion, sendMessage } from './boss-operation' import { sleep, sleepWithRandomDelay } from '@geekgeekrun/utils/sleep.mjs' import { waitForPage } from '@geekgeekrun/utils/puppeteer/wait.mjs' import { app, dialog } from 'electron' @@ -24,6 +24,7 @@ import { BossInfo } from '@geekgeekrun/sqlite-plugin/dist/entity/BossInfo' import { messageForSaveFilter } from '../../../common/utils/chat-list' import { AUTO_CHAT_ERROR_EXIT_CODE, + OPEN_CONTENT_SOURCE, RECHAT_CONTENT_SOURCE, RECHAT_LLM_FALLBACK } from '../../../common/enums/auto-start-chat' @@ -40,6 +41,7 @@ import { loginWithCookieAssistant } from '../../features/login-with-cookie-assis import initPublicIpc from '../../utils/initPublicIpc' import { getLastUsedAndAvailableBrowser } from '../DOWNLOAD_DEPENDENCIES/utils/browser-history' import { configWithBrowserAssistant } from '../../features/config-with-browser-assistant' +import { DEFAULT_CONSTANT_OPEN_CONTENT_SEGS } from '../../../common/constant' process.on('SIGTERM', () => { console.log('收到SIGTERM信号,正在退出') @@ -87,6 +89,21 @@ const onlyRemindBossWithoutBlockCompanyName = readConfigFile('boss.json').autoReminder?.onlyRemindBossWithoutBlockCompanyName ?? !!blockCompanyNameRegExp +const openContentSource = readConfigFile('boss.json').autoReminder?.openContentSource ?? OPEN_CONTENT_SOURCE.CONSTANT_CONTENT +const constantOpenContent = (() => { + let constantOpenContent = readConfigFile('boss.json').autoReminder?.constantOpenContent ?? '' + if (constantOpenContent?.trim?.()) { + return constantOpenContent + } else { + if (rechatContentSource === RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT) { + constantOpenContent = DEFAULT_CONSTANT_OPEN_CONTENT_SEGS.join(`;`) + } else { + constantOpenContent = DEFAULT_CONSTANT_OPEN_CONTENT_SEGS[0] + } + } + return constantOpenContent +})() + const dbInitPromise = initDb(getPublicDbFilePath()) export const pageMapByName: { @@ -582,30 +599,50 @@ const mainLoop = async () => { (throttleIntervalMinutes + 4 * Math.random()) * 60 * 1000 ) { await sleepWithRandomDelay(3250) - if (rechatContentSource === RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT) { - try { - const messageListForGpt = historyMessageList - .filter((it) => it.bizType !== 101 && it.isSelf) - .slice(-recentMessageQuantityForLlm) - await sendGptContent(pageMapByName.boss!, messageListForGpt) + const messageList = historyMessageList + .filter((it) => it.bizType !== 101 && it.isSelf) + .slice(-recentMessageQuantityForLlm) + if (!messageList?.length) { + if (openContentSource === OPEN_CONTENT_SOURCE.CONSTANT_CONTENT) { + await sendMessage(pageMapByName.boss!, constantOpenContent) gtag('rnrr_llm_content_sent') - } catch (err) { - console.log(err) - if (rechatLlmFallback === RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION) { - await sendLookForwardReplyEmotion(pageMapByName.boss!) + } else { + try { + const textToSend = await getGptContent(messageList) + await sendMessage(pageMapByName.boss!, textToSend) + gtag('rnrr_llm_content_sent') + } catch (err) { + console.log(err) + await sendMessage(pageMapByName.boss!, constantOpenContent) gtag('rnrr_look_forward_reply_emotion_sent', { fallback: true }) - } else { - gtag('rnrr_encounter_error', { - error: err - }) - throw err } } } else { - await sendLookForwardReplyEmotion(pageMapByName.boss!) - gtag('rnrr_look_forward_reply_emotion_sent') + if (rechatContentSource === RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT) { + try { + const textToSend = await getGptContent(messageList) + await sendMessage(pageMapByName.boss!, textToSend) + gtag('rnrr_llm_content_sent') + } catch (err) { + console.log(err) + if (rechatLlmFallback === RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION) { + await sendLookForwardReplyEmotion(pageMapByName.boss!) + gtag('rnrr_look_forward_reply_emotion_sent', { + fallback: true + }) + } else { + gtag('rnrr_encounter_error', { + error: err + }) + throw err + } + } + } else { + await sendLookForwardReplyEmotion(pageMapByName.boss!) + gtag('rnrr_look_forward_reply_emotion_sent') + } } } else { cursorToContinueFind += 1 diff --git a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue index cf48a23..beb5240 100644 --- a/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue +++ b/packages/ui/src/renderer/src/page/MainLayout/ReadNoReplyReminder.vue @@ -54,7 +54,77 @@ - + + +
+ 固定文案 + +
+
+ + 由大语言模型生成的内容 + +
+ +
+
+ + 使用外部编辑器编辑“开场白话术”提示词模板 (Markdown) + + + 还原默认“开场白话术”提示词模板 + +
+
+ 对生成效果不够满意?可在此查看、编辑“开场白话术”提示词模板。 +
+
+
+
+
+
+
+

- - 由大语言模型(根据简历及当前聊天上下文)生成的内容 - +
+ + 由大语言模型(根据简历及当前聊天上下文)生成的内容 + +
+ +
+
+ + 使用外部编辑器编辑“跟进话术”提示词模板 (Markdown) + + + 还原默认“跟进话术”提示词模板 + +
+
+ 对生成效果不够满意?可在此查看、编辑“跟进话术”提示词模板。请在模板中需要插入简历的位置插入 + __REPLACE_REAL_RESUME_HERE__ +
+
+
+
+
-
+
+ + + 使用当前配置模拟已读不回自动复聊过程<- 正式运行前建议在这里先试一试大模型生成效果是否符合预期哦 +
{ return baseGtagRenderer(name, { scene: 'rnrr-config', @@ -282,7 +381,10 @@ const formContent = ref({ recentMessageQuantityForLlm: 8, rechatLlmFallback: RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION, onlyRemindBossWithExpectJobType: true, - onlyRemindBossWithoutBlockCompanyName: true + onlyRemindBossWithoutBlockCompanyName: true, + openContentSource: OPEN_CONTENT_SOURCE.CONSTANT_CONTENT, + // openLlmFallback: OPEN_LLM_FALLBACK.SEND_CONSTANT_CONTENT, + constantOpenContent: '' } }) @@ -315,7 +417,10 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => { : parseInt(conf.recentMessageQuantityForLlm) : 8 conf.onlyRemindBossWithExpectJobType = conf.onlyRemindBossWithExpectJobType ?? true + conf.onlyRemindBossWithoutBlockCompanyName = conf.onlyRemindBossWithoutBlockCompanyName ?? true conf.rechatLlmFallback = conf.rechatLlmFallback ?? RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION + conf.openContentSource = conf.openContentSource ?? OPEN_CONTENT_SOURCE.CONSTANT_CONTENT + conf.constantOpenContent = conf.constantOpenContent ?? '' formContent.value.autoReminder = conf }) @@ -462,8 +567,9 @@ async function checkIsCanRun() { return false } try { - await electron.ipcRenderer.invoke('check-if-auto-remind-prompt-valid') + await electron.ipcRenderer.invoke('check-if-auto-remind-prompt-valid', { type: 'rechat' }) } catch (err) { + console.log(err) if (err?.message?.includes(`RESUME_PLACEHOLDER_NOT_EXIST`)) { gtagRenderer('cannot_launch_for_no_resume_placehold') console.log(`提示词模板无效`, err) @@ -479,8 +585,10 @@ async function checkIsCanRun() { } ) .then(async () => { - gtagRenderer('confirm_invalid_rt_tip_dialog') - await restoreDefaultTemplate() + await restoreDefaultTemplate({ + type: 'rechat', + gaEvName: 'confirm_invalid_rt_tip_dialog' + }) }) .catch(() => { gtagRenderer('close_invalid_rt_tip_dialog') @@ -511,7 +619,9 @@ const handleSubmit = async () => { gtagRenderer('config_saved') if ( formContent.value.autoReminder?.rechatContentSource === - RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT + RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT || + formContent.value.autoReminder?.openContentSource === + OPEN_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT ) { if (!(await checkIsCanRun())) { return @@ -568,8 +678,9 @@ function handleThrottleIntervalMinutesBlur() { ) } -const restoreDefaultTemplate = async () => { - await electron.ipcRenderer.invoke('overwrite-auto-remind-prompt-with-default') +const restoreDefaultTemplate = async ({ type, gaEvName }) => { + gtagRenderer(gaEvName) + await electron.ipcRenderer.invoke('overwrite-auto-remind-prompt-with-default', { type }) ElMessage({ type: 'success', message: '模板还原成功' @@ -603,11 +714,12 @@ const handleClickEditResume = async () => { } } -const handleClickEditPrompt = async () => { - gtagRenderer('edit_prompt_clicked') - await electron.ipcRenderer.send('no-reply-reminder-prompt-edit') +const handleClickEditPrompt = async ({ type }) => { + gtagRenderer('edit_prompt_clicked', { type }) + await electron.ipcRenderer.send('no-reply-reminder-prompt-edit', { type }) } +// for 跟进话术 const rechatLlmFallbackOptions = [ { name: '发送“[盼回复]”表情', @@ -619,6 +731,18 @@ const rechatLlmFallbackOptions = [ } ] +// // for 开场白话术 +// const openLlmFallbackOptions = [ +// { +// name: '发送固定文案', +// value: OPEN_LLM_FALLBACK.SEND_CONSTANT_CONTENT +// }, +// { +// name: '退出已读不回自动复聊', +// value: OPEN_LLM_FALLBACK.EXIT_REMINDER_PROGRAM +// } +// ] + async function handleTestEffectClicked() { gtagRenderer('goto_mock_chat_clicked') if (!(await checkIsCanRun())) { @@ -651,6 +775,16 @@ const handleStopButtonClick = async () => { isStopButtonLoading.value = false } } + +const defaultConstantOpenContent = computed(() => { + if ( + formContent.value.autoReminder.rechatContentSource === + RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT + ) { + return DEFAULT_CONSTANT_OPEN_CONTENT_SEGS.join(';') + } + return DEFAULT_CONSTANT_OPEN_CONTENT_SEGS[0] +})