mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-06-03 06:31:13 +08:00
Merge branch 'feature/ui'
This commit is contained in:
@@ -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 = [
|
||||
`您好,我对贵公司岗位很感兴趣,希望能有机会进一步沟通,期待您的回复`,
|
||||
`接下来的聊天,我将根据我简历里的内容,向您进行自我介绍`
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,6 +31,7 @@ export function createBrowserAssistantWindow(
|
||||
minWidth: 800,
|
||||
height: 400,
|
||||
resizable: true,
|
||||
frame: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
|
||||
@@ -16,6 +16,7 @@ export function createCommonJobConditionConfigWindow(
|
||||
resizable: false,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
|
||||
@@ -20,6 +20,7 @@ export function createCookieAssistantWindow(
|
||||
resizable: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
|
||||
@@ -8,11 +8,12 @@ export function createFirstLaunchNoticeWindow(
|
||||
// Create the browser window.
|
||||
firstLaunchNoticeWindow = new BrowserWindow({
|
||||
width: 960,
|
||||
height: 640,
|
||||
resizable: false,
|
||||
maxWidth: 960,
|
||||
minHeight: 320,
|
||||
resizable: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: false,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
|
||||
@@ -15,6 +15,7 @@ export function createLlmConfigWindow(
|
||||
resizable: false,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
|
||||
@@ -12,6 +12,7 @@ export function createMainWindow(): BrowserWindow {
|
||||
minWidth: 1280,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
...(process.platform === 'linux'
|
||||
? {
|
||||
/* icon */
|
||||
|
||||
@@ -17,6 +17,7 @@ export function createReadNoReplyReminderLlmMockWindow(
|
||||
resizable: false,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
|
||||
@@ -15,6 +15,7 @@ export function createResumeEditorWindow(
|
||||
resizable: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
|
||||
@@ -1,114 +1,118 @@
|
||||
<template>
|
||||
<div ref="componentRootEl" class="first-run-readme">
|
||||
<div class="first-run-readme__inner">
|
||||
<div class="readme-title">欢迎使用GeekGeekRun!祝您求职顺利~</div>
|
||||
<div class="readme-desc">
|
||||
如下是使用必读,请您逐条阅读;如果已经了解且接受,请在每一条前面打勾
|
||||
</div>
|
||||
<article class="readme-article">
|
||||
<ElCheckboxGroup
|
||||
v-model="readmeItemCheckStatusList"
|
||||
@change="handleReadmeItemCheckStatusListChange"
|
||||
>
|
||||
<ElCheckbox :label="0" :class="[unreadItemsAfterClickSubmit[0] ? 'unread' : '']">
|
||||
本程序从某种程度上说属于辅助工具,与<el-link
|
||||
color-blue
|
||||
@click.stop.prevent="
|
||||
() => {
|
||||
electron.ipcRenderer.send(
|
||||
'open-external-link',
|
||||
'https://about.zhipin.com/agreement/'
|
||||
)
|
||||
gtagRenderer('view_boss_agreement_clicked')
|
||||
}
|
||||
"
|
||||
>《BOSS直聘用户协议》</el-link
|
||||
>(2023年3月版)相关条款相违背,您在注册BOSS直聘时已签署过这一条款;根据该条款
|
||||
<i>七、用户的平台使用义务</i>、<i>八、违约责任</i>
|
||||
章节,如果一些非正常用户行为被风控监测到,您需要承受包括不仅限于<b class="color-red"
|
||||
>账号被强制退出登录、账号被限制使用、账号被封禁</b
|
||||
>等对您不利的风险;因此使用本程序即意味着<b class="color-red">您愿意接受以上风险</b
|
||||
>,且如果相关风险发生,<b class="color-red">您需要自行承担相关后果,本程序不负责</b>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="1" :class="[unreadItemsAfterClickSubmit[1] ? 'unread' : '']">
|
||||
本程序需要存储您的登录凭据,即Cookie,来模拟您在BOSS直聘上开聊BOSS的行为;本程序仅会把您的Cookie存储在本地,并在您访问BOSS直聘时将其传输到BOSS直聘,<b
|
||||
class="color-red"
|
||||
>不会泄露给第三方</b
|
||||
>,也不会进行除自动开聊BOSS以外的行为;<b class="color-red">请勿向他人泄漏您的Cookie</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="2" :class="[unreadItemsAfterClickSubmit[2] ? 'unread' : '']">
|
||||
本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时<b
|
||||
class="color-red"
|
||||
>注意节制</b
|
||||
>,建议当天开聊次数用尽后,隔几天再使用。建议您<b class="color-red"
|
||||
>注册一个本程序专用的新的BOSS直聘账号</b
|
||||
>进行求职。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="3" :class="[unreadItemsAfterClickSubmit[3] ? 'unread' : '']">
|
||||
本程序原理是模拟用户在BOSS直聘网页上,寻找关键元素并进行点击操作;BOSS直聘网站经常<b>发生改版</b>,且有可能<b>包含A/B实验</b>,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,<b
|
||||
class="color-red"
|
||||
>浏览器重复“闪退、重新启动”</b
|
||||
>)。如果您在使用过程中遇上程序未按照预期执行的情况,请点击程序左下角进行反馈。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="4" :class="[unreadItemsAfterClickSubmit[4] ? 'unread' : '']">
|
||||
您所在公司可能会采购上网行为监控工具或网关(例如奇安信、深信服、绿盟等厂商的产品),对您的计算机终端或网络进行<b
|
||||
color-red
|
||||
>监控</b
|
||||
>,从而<b color-red>审计、跟踪</b>您的行为;您的上级/IT/HR
|
||||
可能会获取到监控数据,从而了解团队成员离职倾向。如果您不希望您的上级/IT/HR
|
||||
了解到您正在求职,建议您<b color-red
|
||||
>不要在您所在公司提供的计算机终端或网络上使用本程序</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="5" :class="[unreadItemsAfterClickSubmit[5] ? 'unread' : '']">
|
||||
本程序尊重您的隐私,<b color-red
|
||||
>不会上报能够识别出您身份的信息、不会参与任何钓鱼活动、不会向您所在公司及您的上级/IT/HR报告您的求职行为、不会向猎头公司泄露您的信息</b
|
||||
>。但由于本程序开源,<b color-red
|
||||
>任何人均可更改本程序源码并重新发布,这一过程中其它开发者是可以加入恶意程序的,因此请从你信任的源下载本程序</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<div class="first-run-readme__inner-outer">
|
||||
<div class="first-run-readme__inner">
|
||||
<div class="readme-title">欢迎使用GeekGeekRun!祝您求职顺利~</div>
|
||||
<div class="readme-desc">
|
||||
如下是使用必读,请您逐条阅读;如果已经了解且接受,请在每一条前面打勾
|
||||
</div>
|
||||
<article class="readme-article">
|
||||
<ElCheckboxGroup
|
||||
v-model="readmeItemCheckStatusList"
|
||||
@change="handleReadmeItemCheckStatusListChange"
|
||||
>
|
||||
<ElCheckbox :label="0" :class="[unreadItemsAfterClickSubmit[0] ? 'unread' : '']">
|
||||
本程序从某种程度上说属于辅助工具,与<el-link
|
||||
color-blue
|
||||
@click.stop.prevent="
|
||||
() => {
|
||||
electron.ipcRenderer.send(
|
||||
'open-external-link',
|
||||
'https://about.zhipin.com/agreement/'
|
||||
)
|
||||
gtagRenderer('view_boss_agreement_clicked')
|
||||
}
|
||||
"
|
||||
>《BOSS直聘用户协议》</el-link
|
||||
>(2023年3月版)相关条款相违背,您在注册BOSS直聘时已签署过这一条款;根据该条款
|
||||
<i>七、用户的平台使用义务</i>、<i>八、违约责任</i>
|
||||
章节,如果一些非正常用户行为被风控监测到,您需要承受包括不仅限于<b class="color-red"
|
||||
>账号被强制退出登录、账号被限制使用、账号被封禁</b
|
||||
>等对您不利的风险;因此使用本程序即意味着<b class="color-red">您愿意接受以上风险</b
|
||||
>,且如果相关风险发生,<b class="color-red">您需要自行承担相关后果,本程序不负责</b>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="1" :class="[unreadItemsAfterClickSubmit[1] ? 'unread' : '']">
|
||||
本程序需要存储您的登录凭据,即Cookie,来模拟您在BOSS直聘上开聊BOSS的行为;本程序仅会把您的Cookie存储在本地,并在您访问BOSS直聘时将其传输到BOSS直聘,<b
|
||||
class="color-red"
|
||||
>不会泄露给第三方</b
|
||||
>,也不会进行除自动开聊BOSS以外的行为;<b class="color-red">请勿向他人泄漏您的Cookie</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="2" :class="[unreadItemsAfterClickSubmit[2] ? 'unread' : '']">
|
||||
本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时<b
|
||||
class="color-red"
|
||||
>注意节制</b
|
||||
>,建议当天开聊次数用尽后,隔几天再使用。建议您<b class="color-red"
|
||||
>注册一个本程序专用的新的BOSS直聘账号</b
|
||||
>进行求职。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="3" :class="[unreadItemsAfterClickSubmit[3] ? 'unread' : '']">
|
||||
本程序原理是模拟用户在BOSS直聘网页上,寻找关键元素并进行点击操作;BOSS直聘网站经常<b>发生改版</b>,且有可能<b>包含A/B实验</b>,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,<b
|
||||
class="color-red"
|
||||
>浏览器重复“闪退、重新启动”</b
|
||||
>)。如果您在使用过程中遇上程序未按照预期执行的情况,请点击程序左下角进行反馈。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="4" :class="[unreadItemsAfterClickSubmit[4] ? 'unread' : '']">
|
||||
您所在公司可能会采购上网行为监控工具或网关(例如奇安信、深信服、绿盟等厂商的产品),对您的计算机终端或网络进行<b
|
||||
color-red
|
||||
>监控</b
|
||||
>,从而<b color-red>审计、跟踪</b>您的行为;您的上级/IT/HR
|
||||
可能会获取到监控数据,从而了解团队成员离职倾向。如果您不希望您的上级/IT/HR
|
||||
了解到您正在求职,建议您<b color-red
|
||||
>不要在您所在公司提供的计算机终端或网络上使用本程序</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="5" :class="[unreadItemsAfterClickSubmit[5] ? 'unread' : '']">
|
||||
本程序尊重您的隐私,<b color-red
|
||||
>不会上报能够识别出您身份的信息、不会参与任何钓鱼活动、不会向您所在公司及您的上级/IT/HR报告您的求职行为、不会向猎头公司泄露您的信息</b
|
||||
>。但由于本程序开源,<b color-red
|
||||
>任何人均可更改本程序源码并重新发布,这一过程中其它开发者是可以加入恶意程序的,因此请从你信任的源下载本程序</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
|
||||
<ElCheckbox :label="6" :class="[unreadItemsAfterClickSubmit[6] ? 'unread' : '']">
|
||||
本程序<b class="color-red">没有内置任何付费功能</b>,<b class="color-red"
|
||||
>下载、使用是免费的</b
|
||||
>,任何人可以<b class="color-red">免费获得、免费使用</b>。<b class="color-red"
|
||||
>作者没有利用本程序赚到过任何收入</b
|
||||
>。如果您是从GitHub以外的地方付费后“购买”的本程序,或您被提示“必须付费后才能使用本程序”,<b
|
||||
class="color-red"
|
||||
>那您大概率被骗了</b
|
||||
>,或者<b class="color-red">您下载到了本程序修改版</b>。<b class="color-red"
|
||||
>本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="7" :class="[unreadItemsAfterClickSubmit[7] ? 'unread' : '']">
|
||||
本程序<b class="color-red">不对您的求职过程与结果负责</b
|
||||
>,为您开聊的职位均在BOSS直聘上发布,职位信息真实性由BOSS直聘负责;请<b
|
||||
class="color-red"
|
||||
>自行甄别为您开聊的公司、认真决定是否参加面试、慎重选择Offer</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="8" :class="[unreadItemsAfterClickSubmit[8] ? 'unread' : '']">
|
||||
请在BOSS直聘上自行<b class="color-red">屏蔽您不期望投递的公司</b
|
||||
>;如果您不希望您当前公司其它具有招聘账号的员工看到您在BOSS直聘上活跃,请<b
|
||||
class="color-red"
|
||||
>在BOSS直聘上屏蔽当前公司及与之关联的公司</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="9" :class="[unreadItemsAfterClickSubmit[9] ? 'unread' : '']">
|
||||
本程序经历过了多次测试,理论上来说大部分情况下可以正常运行,但可能也会出现测试用例覆盖不到位,导致程序不按预期运行的情况;如果您有顾虑,建议通过
|
||||
VMware Workstation / Fusion、Oracle VirtualBox、Microsoft Hyper-V
|
||||
等虚拟化技术运行本程序。如果您在使用过程中遇上程序未按照预期执行的情况,请点击程序左下角进行反馈。
|
||||
</ElCheckbox>
|
||||
</ElCheckboxGroup>
|
||||
</article>
|
||||
<footer flex mt20px pb20px flex-justify-end>
|
||||
<ElCheckbox :label="6" :class="[unreadItemsAfterClickSubmit[6] ? 'unread' : '']">
|
||||
本程序<b class="color-red">没有内置任何付费功能</b>,<b class="color-red"
|
||||
>下载、使用是免费的</b
|
||||
>,任何人可以<b class="color-red">免费获得、免费使用</b>。<b class="color-red"
|
||||
>作者没有利用本程序赚到过任何收入</b
|
||||
>。如果您是从GitHub以外的地方付费后“购买”的本程序,或您被提示“必须付费后才能使用本程序”,<b
|
||||
class="color-red"
|
||||
>那您大概率被骗了</b
|
||||
>,或者<b class="color-red">您下载到了本程序修改版</b>。<b class="color-red"
|
||||
>本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="7" :class="[unreadItemsAfterClickSubmit[7] ? 'unread' : '']">
|
||||
本程序<b class="color-red">不对您的求职过程与结果负责</b
|
||||
>,为您开聊的职位均在BOSS直聘上发布,职位信息真实性由BOSS直聘负责;请<b
|
||||
class="color-red"
|
||||
>自行甄别为您开聊的公司、认真决定是否参加面试、慎重选择Offer</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="8" :class="[unreadItemsAfterClickSubmit[8] ? 'unread' : '']">
|
||||
请在BOSS直聘上自行<b class="color-red">屏蔽您不期望投递的公司</b
|
||||
>;如果您不希望您当前公司其它具有招聘账号的员工看到您在BOSS直聘上活跃,请<b
|
||||
class="color-red"
|
||||
>在BOSS直聘上屏蔽当前公司及与之关联的公司</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="9" :class="[unreadItemsAfterClickSubmit[9] ? 'unread' : '']">
|
||||
本程序经历过了多次测试,理论上来说大部分情况下可以正常运行,但可能也会出现测试用例覆盖不到位,导致程序不按预期运行的情况;如果您有顾虑,建议通过
|
||||
VMware Workstation / Fusion、Oracle VirtualBox、Microsoft Hyper-V
|
||||
等虚拟化技术运行本程序。如果您在使用过程中遇上程序未按照预期执行的情况,请点击程序左下角进行反馈。
|
||||
</ElCheckbox>
|
||||
</ElCheckboxGroup>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<footer pt10px pb10px>
|
||||
<div flex flex-justify-end w-880px ml-auto mr-auto>
|
||||
<el-button type="text" @click="handleCancel">退出程序</el-button>
|
||||
<el-button type="primary" @click="handleSubmit"
|
||||
>我已经阅读,并接受上方所提及的相关风险,并决定继续使用本程序</el-button
|
||||
>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -163,30 +167,27 @@ const handleReadmeItemCheckStatusListChange = (value: number[]) => {
|
||||
})
|
||||
}
|
||||
const componentRootEl = ref<HTMLElement>()
|
||||
onMounted(() => {
|
||||
const ro = new ResizeObserver(() => {
|
||||
electron.ipcRenderer.send('update-window-size', {
|
||||
width: componentRootEl.value!.offsetWidth,
|
||||
height: componentRootEl.value!.offsetHeight
|
||||
})
|
||||
})
|
||||
ro.observe(componentRootEl.value!)
|
||||
onBeforeMount(() => {
|
||||
ro.disconnect()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.first-run-readme {
|
||||
box-sizing: border-box;
|
||||
width: 960px;
|
||||
height: fit-content;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
user-select: none;
|
||||
&__inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
.first-run-readme__inner-outer {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
.first-run-readme__inner {
|
||||
width: 880px;
|
||||
margin: 0 auto;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
.readme-title {
|
||||
}
|
||||
.readme-desc {
|
||||
@@ -222,5 +223,9 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
footer {
|
||||
flex: 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,7 +54,77 @@
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb0" label="跟进话术 - 当发现已读不回的BOSS时,将要向BOSS发出:">
|
||||
<el-form-item label="开场白话术">
|
||||
<el-radio-group v-model="formContent.autoReminder.openContentSource" w-full>
|
||||
<div w-full>
|
||||
<el-radio :label="OPEN_CONTENT_SOURCE.CONSTANT_CONTENT"> 固定文案 </el-radio>
|
||||
<el-input
|
||||
v-if="
|
||||
formContent.autoReminder.openContentSource ===
|
||||
OPEN_CONTENT_SOURCE.CONSTANT_CONTENT
|
||||
"
|
||||
v-model="formContent.autoReminder.constantOpenContent"
|
||||
w-full
|
||||
:autosize="{ minRows: 3 }"
|
||||
max-h-8lh
|
||||
type="textarea"
|
||||
:placeholder="defaultConstantOpenContent"
|
||||
class="pl-30px mb10px"
|
||||
:style="{
|
||||
boxSizing: 'border-box'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<el-radio :label="OPEN_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT">
|
||||
由大语言模型生成的内容
|
||||
</el-radio>
|
||||
<div
|
||||
v-if="
|
||||
formContent.autoReminder.openContentSource ===
|
||||
OPEN_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
||||
"
|
||||
ml30px
|
||||
>
|
||||
<el-form-item class="mb4px">
|
||||
<div>
|
||||
<div>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="
|
||||
handleClickEditPrompt({
|
||||
type: 'open'
|
||||
})
|
||||
"
|
||||
>
|
||||
使用外部编辑器编辑“开场白话术”提示词模板 (Markdown)
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="
|
||||
() => {
|
||||
restoreDefaultTemplate({
|
||||
type: 'open',
|
||||
gaEvName: 'reset_template_clicked_in_main_form'
|
||||
})
|
||||
}
|
||||
"
|
||||
>
|
||||
还原默认“开场白话术”提示词模板
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="font-size-12px color-#666">
|
||||
对生成效果不够满意?可在此查看、编辑“开场白话术”提示词模板。
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="跟进话术 - 当发现已读不回的BOSS时,将要向BOSS发出:">
|
||||
<el-radio-group v-model="formContent.autoReminder.rechatContentSource">
|
||||
<div>
|
||||
<el-tooltip
|
||||
@@ -71,91 +141,117 @@
|
||||
</el-radio>
|
||||
</el-tooltip>
|
||||
<br />
|
||||
<el-radio :label="RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT">
|
||||
由大语言模型(根据简历及当前聊天上下文)生成的内容
|
||||
</el-radio>
|
||||
<div>
|
||||
<el-radio :label="RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT">
|
||||
由大语言模型(根据简历及当前聊天上下文)生成的内容
|
||||
</el-radio>
|
||||
<div
|
||||
v-if="
|
||||
formContent.autoReminder.rechatContentSource ===
|
||||
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
||||
"
|
||||
ml30px
|
||||
>
|
||||
<el-form-item class="mb4px">
|
||||
<div>
|
||||
<div>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="
|
||||
handleClickEditPrompt({
|
||||
type: 'rechat'
|
||||
})
|
||||
"
|
||||
>
|
||||
使用外部编辑器编辑“跟进话术”提示词模板 (Markdown)
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="
|
||||
() => {
|
||||
restoreDefaultTemplate({
|
||||
type: 'rechat',
|
||||
gaEvName: 'reset_template_clicked_in_main_form'
|
||||
})
|
||||
}
|
||||
"
|
||||
>
|
||||
还原默认“跟进话术”提示词模板
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="font-size-12px color-#666">
|
||||
对生成效果不够满意?可在此查看、编辑“跟进话术”提示词模板。请在模板中需要插入简历的位置插入
|
||||
__REPLACE_REAL_RESUME_HERE__
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div class="ml-30px">
|
||||
<div mt10px>
|
||||
<el-form-item mb0 label="大语言模型公共设置及效果预览" />
|
||||
<template
|
||||
v-if="
|
||||
formContent.autoReminder.rechatContentSource ===
|
||||
RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT
|
||||
[
|
||||
formContent.autoReminder.rechatContentSource,
|
||||
formContent.autoReminder.openContentSource
|
||||
].includes(RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT)
|
||||
"
|
||||
>
|
||||
<el-form-item class="mb4px">
|
||||
<div>
|
||||
<el-button size="small" type="primary" @click="handleClickEditResume">
|
||||
编辑简历
|
||||
</el-button>
|
||||
<div class="font-size-12px color-#666">
|
||||
简历内容将提交给大语言模型,以用于生成已读不回提醒消息;提交内容及生成消息中不会包含期望薪资
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="mb4px">
|
||||
<div>
|
||||
<div ml-30px>
|
||||
<el-form-item class="mb4px">
|
||||
<div>
|
||||
<el-button size="small" type="primary" @click="handleClickEditPrompt">
|
||||
使用外部编辑器编辑提示词模板 (Markdown)
|
||||
<el-button size="small" type="primary" @click="handleClickEditResume">
|
||||
编辑简历
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="
|
||||
() => {
|
||||
gtagRenderer('reset_template_clicked_in_main_form')
|
||||
restoreDefaultTemplate()
|
||||
}
|
||||
"
|
||||
<div class="font-size-12px color-#666">
|
||||
简历内容将提交给大语言模型,以用于生成已读不回提醒消息;提交内容及生成消息中不会包含期望薪资
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="recentMessageQuantityForLlm">
|
||||
<div>
|
||||
携带最近
|
||||
<el-input-number
|
||||
v-model="formContent.autoReminder.recentMessageQuantityForLlm"
|
||||
class="w-120px"
|
||||
:min="8"
|
||||
:max="20"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
></el-input-number>
|
||||
次聊天内容作为上下文生成新消息
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="当配置的所有大模型均不可使用时">
|
||||
<div class="flex flex-items-center">
|
||||
<el-select
|
||||
v-model="formContent.autoReminder.rechatLlmFallback"
|
||||
class="w200px"
|
||||
label="name"
|
||||
>
|
||||
还原默认提示词模板
|
||||
</el-button>
|
||||
<el-option
|
||||
v-for="option in rechatLlmFallbackOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:label="option.name"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="font-size-12px color-#666">
|
||||
对生成效果不够满意?可在此查看、编辑提示词模板。请在模板中需要插入简历的位置插入
|
||||
__REPLACE_REAL_RESUME_HERE__
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="recentMessageQuantityForLlm">
|
||||
<div>
|
||||
携带最近
|
||||
<el-input-number
|
||||
v-model="formContent.autoReminder.recentMessageQuantityForLlm"
|
||||
class="w-120px"
|
||||
:min="8"
|
||||
:max="20"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
></el-input-number>
|
||||
次聊天内容作为上下文生成新消息
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary" @click="handleTestEffectClicked"
|
||||
>使用当前配置模拟已读不回自动复聊过程</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item prop="recentMessageQuantityForLlm">
|
||||
<div class="flex flex-items-center">
|
||||
<span class="whitespace-nowrap">当所有模型均不可使用时 </span>
|
||||
<el-select
|
||||
v-model="formContent.autoReminder.rechatLlmFallback"
|
||||
class="w200px"
|
||||
label="name"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in rechatLlmFallbackOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:label="option.name"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<el-form-item ml-30px>
|
||||
<el-button size="small" type="primary" @click="handleTestEffectClicked"
|
||||
>使用当前配置模拟已读不回自动复聊过程</el-button
|
||||
><span text-orange ml10px
|
||||
><- 正式运行前建议在这里先试一试大模型生成效果是否符合预期哦</span
|
||||
>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="跟进间隔(分钟)" prop="throttleIntervalMinutes">
|
||||
<el-input-number
|
||||
@@ -259,6 +355,8 @@ import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
import { dayjs, ElForm, ElMessage, ElMessageBox, ElSelect, ElOption } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
OPEN_CONTENT_SOURCE,
|
||||
// OPEN_LLM_FALLBACK,
|
||||
RECHAT_CONTENT_SOURCE,
|
||||
RECHAT_LLM_FALLBACK,
|
||||
RUNNING_STATUS_ENUM
|
||||
@@ -267,6 +365,7 @@ import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
||||
import mittBus from '../../utils/mitt'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import RunningOverlay from '@renderer/features/RunningOverlay/index.vue'
|
||||
import { DEFAULT_CONSTANT_OPEN_CONTENT_SEGS } from '../../../../common/constant'
|
||||
const gtagRenderer = (name, params?: object) => {
|
||||
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')
|
||||
@@ -504,14 +612,23 @@ const handleSubmit = async () => {
|
||||
throttle_interval_minutes: formContent.value.autoReminder.throttleIntervalMinutes,
|
||||
rechat_limit_day: formContent.value.autoReminder.rechatLimitDay,
|
||||
rechat_content_source: formContent.value.autoReminder.rechatContentSource,
|
||||
recent_message_quantity_for_llm: formContent.value.autoReminder.recentMessageQuantityForLlm
|
||||
recent_message_quantity_for_llm: formContent.value.autoReminder.recentMessageQuantityForLlm,
|
||||
only_remind_boss_with_expect_job_type:
|
||||
formContent.value.autoReminder.onlyRemindBossWithExpectJobType,
|
||||
only_remind_boss_without_block_company_name:
|
||||
formContent.value.autoReminder.onlyRemindBossWithoutBlockCompanyName,
|
||||
rechat_llm_fallback: formContent.value.autoReminder.rechatLlmFallback,
|
||||
open_content_source: formContent.value.autoReminder.openContentSource,
|
||||
constant_open_content_text_length: formContent.value.autoReminder.constantOpenContent.length ?? 0
|
||||
})
|
||||
await formRef.value!.validate()
|
||||
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
|
||||
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 +685,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 +721,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 +738,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 +782,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]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -22,39 +22,65 @@
|
||||
<div class="pb20px"></div>
|
||||
<div v-for="(item, index) in messageList" :key="index" flex flex-col flex-items-end>
|
||||
<div class="message-item-wrap flex flex-col">
|
||||
<div
|
||||
class="message-item"
|
||||
:class="{
|
||||
'will-enter-context': getIsEnterContent(index)
|
||||
}"
|
||||
>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<div
|
||||
:style="{
|
||||
width: 'fit-content',
|
||||
alignSelf: 'flex-end'
|
||||
}"
|
||||
font-size-10px
|
||||
>
|
||||
{{ item.usedLlmConfig.model }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item?.usedLlmConfig?.providerCompleteApiUrl?.trim()"
|
||||
:style="{
|
||||
width: 'fit-content',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
alignSelf: 'flex-end',
|
||||
color: '#bbb'
|
||||
}"
|
||||
font-size-10px
|
||||
w-fit-content
|
||||
max-w-20em
|
||||
>
|
||||
{{ item.usedLlmConfig.providerCompleteApiUrl }}
|
||||
</div>
|
||||
<template v-if="item.type === 'text'">
|
||||
<div
|
||||
class="message-item"
|
||||
:class="{
|
||||
'will-enter-context': getIsEnterContent(index)
|
||||
}"
|
||||
>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
class="message-item image-message-item"
|
||||
:class="{
|
||||
'will-enter-context': getIsEnterContent(index)
|
||||
}"
|
||||
>
|
||||
<img :src="item.imageUrl" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
<!-- eslint-disable-next-line prettier/prettier -->
|
||||
<template v-if="(typeof item.usedLlmConfig !== 'string')">
|
||||
<div
|
||||
:style="{
|
||||
width: 'fit-content',
|
||||
alignSelf: 'flex-end'
|
||||
}"
|
||||
font-size-10px
|
||||
>
|
||||
{{ item.usedLlmConfig.model }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item?.usedLlmConfig?.providerCompleteApiUrl?.trim()"
|
||||
:style="{
|
||||
width: 'fit-content',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
alignSelf: 'flex-end',
|
||||
color: '#bbb'
|
||||
}"
|
||||
font-size-10px
|
||||
w-fit-content
|
||||
max-w-20em
|
||||
>
|
||||
{{ item.usedLlmConfig.providerCompleteApiUrl }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
:style="{
|
||||
width: 'fit-content',
|
||||
alignSelf: 'flex-end'
|
||||
}"
|
||||
font-size-10px
|
||||
>
|
||||
{{ item.usedLlmConfig }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pb20px"></div>
|
||||
@@ -147,15 +173,27 @@ import { computed, ref, watch } from 'vue'
|
||||
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import {
|
||||
OPEN_CONTENT_SOURCE,
|
||||
RECHAT_CONTENT_SOURCE
|
||||
} from '../../../../common/enums/auto-start-chat'
|
||||
import { DEFAULT_CONSTANT_OPEN_CONTENT_SEGS } from '../../../../common/constant'
|
||||
import lookForwardReplyEmotion from '../MainLayout/resources/look-forward-reply-emotion.gif'
|
||||
|
||||
type MessageItem = {
|
||||
text: string
|
||||
usedLlmConfig: string
|
||||
type: 'text'
|
||||
// recordInfo: any
|
||||
}
|
||||
const messageList = ref<MessageItem[]>([])
|
||||
type ImageMessageItem = MessageItem & {
|
||||
type: 'image'
|
||||
imageUrl: string
|
||||
}
|
||||
const messageList = ref<(MessageItem | ImageMessageItem)[]>([])
|
||||
const searchParams = Object.fromEntries(new URL(location.href).searchParams)
|
||||
|
||||
const recentMessageQuantityForLlm =
|
||||
Number(new URL(location.href).searchParams.get('recentMessageQuantityForLlm')) || 8
|
||||
const recentMessageQuantityForLlm = Number(searchParams.recentMessageQuantityForLlm) || 8
|
||||
function getIsEnterContent(index) {
|
||||
return messageList.value.length - index - 1 < recentMessageQuantityForLlm
|
||||
}
|
||||
@@ -188,32 +226,93 @@ watch(
|
||||
|
||||
const scrollElRef = ref(null)
|
||||
const isLoading = ref(false)
|
||||
const openContentSource = Number(searchParams.openContentSource)
|
||||
const constantOpenContent = (() => {
|
||||
if (searchParams.constantOpenContent?.trim()) {
|
||||
return searchParams.constantOpenContent.trim()
|
||||
}
|
||||
if (Number(searchParams.rechatContentSource) === RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT) {
|
||||
return DEFAULT_CONSTANT_OPEN_CONTENT_SEGS.join(`;`)
|
||||
} else {
|
||||
return DEFAULT_CONSTANT_OPEN_CONTENT_SEGS[0]
|
||||
}
|
||||
})()
|
||||
const rechatContentSource = Number(searchParams.rechatContentSource)
|
||||
|
||||
async function sendLlmGeneratedContent() {
|
||||
gtagRenderer('click_mock_chat_send')
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await electron.ipcRenderer.invoke('request-llm-for-test', {
|
||||
messageList: JSON.parse(JSON.stringify((messageList.value ?? []).slice(-8))),
|
||||
llmConfigIdForPick: selectedLlmConfig.value ? [selectedLlmConfig.value] : null
|
||||
})
|
||||
console.log(response)
|
||||
messageList.value.push({
|
||||
text: response.responseText,
|
||||
usedLlmConfig: response.usedLlmConfig
|
||||
})
|
||||
await sleep(50)
|
||||
;(scrollElRef.value as any as HTMLDivElement)?.scrollTo({
|
||||
top: scrollElRef.value?.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
} catch (err) {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
grouping: true,
|
||||
message: `<div>本次测试所使用的模型不可用</div><div style="margin-top: 10px; white-space: nowrap;">建议在大语言模型配置中关闭相关模型</div>`
|
||||
})
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
if (!(messageList.value ?? []).length) {
|
||||
// send open content
|
||||
if (openContentSource === OPEN_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT) {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await electron.ipcRenderer.invoke('request-llm-for-test', {
|
||||
messageList: [],
|
||||
llmConfigIdForPick: selectedLlmConfig.value ? [selectedLlmConfig.value] : null
|
||||
})
|
||||
console.log(response)
|
||||
messageList.value.push({
|
||||
type: 'text',
|
||||
text: response.responseText,
|
||||
usedLlmConfig: response.usedLlmConfig
|
||||
})
|
||||
await sleep(50)
|
||||
;(scrollElRef.value as any as HTMLDivElement)?.scrollTo({
|
||||
top: scrollElRef.value?.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
} catch (err) {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
grouping: true,
|
||||
message: `<div>本次测试所使用的模型不可用</div><div style="margin-top: 10px; white-space: nowrap;">建议在大语言模型配置中关闭相关模型</div>`
|
||||
})
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
} else {
|
||||
messageList.value.push({
|
||||
type: 'text',
|
||||
text: constantOpenContent,
|
||||
usedLlmConfig: '未使用大模型'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (rechatContentSource === RECHAT_CONTENT_SOURCE.GEMINI_WITH_CHAT_CONTEXT) {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await electron.ipcRenderer.invoke('request-llm-for-test', {
|
||||
messageList: JSON.parse(JSON.stringify((messageList.value ?? []).slice(-8))),
|
||||
llmConfigIdForPick: selectedLlmConfig.value ? [selectedLlmConfig.value] : null
|
||||
})
|
||||
console.log(response)
|
||||
messageList.value.push({
|
||||
type: 'text',
|
||||
text: response.responseText,
|
||||
usedLlmConfig: response.usedLlmConfig
|
||||
})
|
||||
await sleep(50)
|
||||
;(scrollElRef.value as any as HTMLDivElement)?.scrollTo({
|
||||
top: scrollElRef.value?.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
} catch (err) {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
grouping: true,
|
||||
message: `<div>本次测试所使用的模型不可用</div><div style="margin-top: 10px; white-space: nowrap;">建议在大语言模型配置中关闭相关模型</div>`
|
||||
})
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
} else {
|
||||
messageList.value.push({
|
||||
type: 'image',
|
||||
text: `[盼回复] 表情`,
|
||||
imageUrl: lookForwardReplyEmotion,
|
||||
usedLlmConfig: '未使用大模型'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,5 +363,12 @@ gtagRenderer('enter_mock_chat_page')
|
||||
}
|
||||
}
|
||||
}
|
||||
.message-item.image-message-item {
|
||||
background-color: transparent;
|
||||
width: 128px;
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user