mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-07-11 23:41:51 +08:00
Compare commits
87 Commits
feature/bl
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4ebdb0882 | ||
|
|
f0f01ae317 | ||
|
|
9d3408d1f3 | ||
|
|
432ac9c298 | ||
|
|
eb90a825a0 | ||
|
|
345d75ff98 | ||
|
|
60ac324603 | ||
|
|
dd76a856e8 | ||
|
|
15e566b343 | ||
|
|
e09e7ee936 | ||
|
|
6c865252b2 | ||
|
|
3db4905cf6 | ||
|
|
a7e57c08f6 | ||
|
|
bf28af126b | ||
|
|
09a1b4b918 | ||
|
|
9400d2bd6f | ||
|
|
6c80147be2 | ||
|
|
b5b4cbe259 | ||
|
|
45c860ddbd | ||
|
|
7a5c199889 | ||
|
|
1447299354 | ||
|
|
775b3467e5 | ||
|
|
882645771d | ||
|
|
675f77a408 | ||
|
|
435fb170a2 | ||
|
|
4048e3b323 | ||
|
|
e6489db6c4 | ||
|
|
3f08481657 | ||
|
|
97c0f3dfaf | ||
|
|
80158682b0 | ||
|
|
bf1d9023a6 | ||
|
|
b7ffe30ee0 | ||
|
|
f82987bd76 | ||
|
|
57bab1900c | ||
|
|
1c7d3d992a | ||
|
|
fff8ca7dff | ||
|
|
98ffc3a143 | ||
|
|
70d6453e61 | ||
|
|
679090c9af | ||
|
|
fb7b545d10 | ||
|
|
88c3d63cb6 | ||
|
|
40328a24ef | ||
|
|
b1282c8169 | ||
|
|
b3c7b287ab | ||
|
|
0ffab64653 | ||
|
|
b94520e00a | ||
|
|
cac32b934a | ||
|
|
0eb64a1c69 | ||
|
|
ce651c8f7e | ||
|
|
013d76726b | ||
|
|
4847e8e8d1 | ||
|
|
2980aebf8d | ||
|
|
31236acda4 | ||
|
|
8506921ef2 | ||
|
|
7561412d18 | ||
|
|
a8a6bd9174 | ||
|
|
64d10876c3 | ||
|
|
d0101cfdce | ||
|
|
ba7c61f22b | ||
|
|
18b9868ff0 | ||
|
|
6743649f04 | ||
|
|
81f990084d | ||
|
|
7aa2cc1485 | ||
|
|
60928b618c | ||
|
|
01ce8c996d | ||
|
|
b8f6042f36 | ||
|
|
1dad234dbc | ||
|
|
3155017e73 | ||
|
|
b3dfd827ea | ||
|
|
960d937100 | ||
|
|
69e5516898 | ||
|
|
333b9a4558 | ||
|
|
7e55641d86 | ||
|
|
58b836deb3 | ||
|
|
56fc7e8aef | ||
|
|
51920e54eb | ||
|
|
38296e5b5b | ||
|
|
e52015ccb9 | ||
|
|
8831ae4b17 | ||
|
|
19a80cf18d | ||
|
|
26a3beaced | ||
|
|
10075826dd | ||
|
|
efcb0f0783 | ||
|
|
b4ca041592 | ||
|
|
35f7e36ab7 | ||
|
|
55c1032ad6 | ||
|
|
eac79a9661 |
35
README.md
35
README.md
@@ -1,7 +1,5 @@
|
||||
# 牛人快跑 - GeekGeekRun
|
||||
|
||||
**->维护者正在疯狂求职中<-**
|
||||
|
||||
一款可以帮助你在BOSS直聘上**自动批量开聊BOSS**的脚本,基于Puppeteer。
|
||||
|
||||
与每一位牛人站在一起
|
||||
@@ -11,8 +9,33 @@
|
||||
|
||||
各行各业,无论你是小白还是大佬,都能通过几步简单的配置,快速开始求职!
|
||||
|
||||
## TO 有求职之外其他目的朋友
|
||||
本程序的目标,是帮助求职者尽快寻得一个能让他为之**挥洒汗水**、**努力拼搏**的**事业**。如果有其它目的,请勿下载使用。
|
||||
|
||||
## For 有求职之外其他用途的朋友
|
||||
本程序的目标,是帮助求职者调研求职市场行情或寻找工作。如果有其它用途,请勿下载使用。
|
||||
|
||||
## 近期面试吐槽
|
||||
|
||||
### 面评
|
||||
To 写下这段话的面试官 - 我从事前端开发近7年(十万级~亿级 toC 产品),就因为我写了几个月爬虫相关项目,就得出这种结论,你不觉得你的面评很搞笑吗?
|
||||
|
||||
我们面试中有关爬虫的内容一点儿没聊,你一定要用这种低级的方式证明我不适合这个职位吗?
|
||||
|
||||
<img width="320" alt="image" src="https://github.com/user-attachments/assets/96a20b03-fce4-46c2-a8ac-e9c032418944" />
|
||||
|
||||
### 面试方式
|
||||
To 一些需要现场面试的公司 - 我1小时地铁吭呲吭呲赶到贵司,和贵司面试官聊天,20分钟面试结束把我打发走,我又1小时吭呲吭呲回家。你邀约我面试的目是什么?完成到面率KPI?非要浪费大家的时间吗?
|
||||
|
||||
### 流程推进
|
||||
To 我所遇到的大大小小的公司 - 能不能少一些横向对比?一个流程20天没反馈,HR 经常不接电话玩 Ghosting;请问,这到底是在干什么?就不能有个痛快话吗?
|
||||
|
||||
### 面试官的刻板印象
|
||||
#### “你既然写出了这个项目,那你是不是很好找工作啊?”
|
||||
对不起,不是;既然你说我好找工作,那给我Offer啊
|
||||
|
||||
#### “你写出了这个项目,同时我也看到你几份工作时间不长;那你是不是不稳定啊?难道你来了以后天天就想着跑?”
|
||||
近期离职原因:部门解散、业务HC缩减
|
||||
|
||||
你以为我真的想不稳定吗?难道我入职就是为了离职而不是在劳动岗位上创造价值?
|
||||
|
||||
## 程序有哪些功能?运行逻辑是什么?怎样使用?
|
||||
|
||||
@@ -152,8 +175,8 @@ BOSS不明原因已读不回?简历就是投不出去?
|
||||
- 本程序需要存储您的登录凭据,即Cookie,来模拟您在BOSS直聘上开聊BOSS的行为;本程序仅会把您的Cookie存储在本地,并在您访问BOSS直聘时将其传输到BOSS直聘,**不会泄露给第三方**,也不会进行除自动开聊BOSS以外的行为;**请勿向他人泄漏您的Cookie**。
|
||||
- 本程序会通过尽可能模仿用户行为来规避相关风险,但并不能保证可以完全规避。建议您使用本程序时**注意节制**,建议当天开聊次数用尽后,隔几天再使用。建议您**注册一个本程序专用的新的BOSS直聘账号**进行求职。
|
||||
- 本程序原理是模拟用户在BOSS直聘网页上,寻找关键元素并进行点击操作;BOSS直聘网站经常**发生改版**,且有可能**包含A/B实验**,这将导致本程序相关脚本失效(典型表现为本程序运行到某一步骤后,浏览器重复“闪退、重新启动”)。如果您在使用过程中遇上程序未按照预期执行的情况,请[点击这里](https://github.com/geekgeekrun/geekgeekrun/issues/new)进行反馈。
|
||||
- 您所在公司可能会对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;上级/HRBP团队可能会从 IT 团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级/HRBP团队了解到您正在求职,建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。
|
||||
- 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及上级/HRBP报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。
|
||||
- 您所在公司可能会采购上网行为监控工具或网关(例如奇安信、深信服、绿盟等厂商的产品),对您的计算机终端或网络进行**监控**,从而**审计**、**跟踪**您的行为;您的上级/IT/HR 可能会获取到监控数据,从而了解团队成员离职倾向。如果您不希望您的上级/IT/HR 了解到您正在求职,建议您**不要在您所在公司提供的计算机终端或网络上使用本程序**。
|
||||
- 本程序尊重您的隐私,**不会参与任何钓鱼活动**、**不会上报能够识别出您身份的信息**、**不会向您所在公司及您的上级/IT/HR 报告您的求职行为**、**不会向任何猎头公司泄露您的个人信息**。但由于本程序开源,任何人均可更改本程序源码并重新发布,这一过程中其它开发者是可以加入恶意程序的,因此请从你信任的源下载本程序。
|
||||
- 本程序**没有内置任何付费功能**,**下载**、**使用**是**免费**的,任何人可以**免费获得**、**免费使用**。**作者没有利用本程序赚到过任何收入**。如果您是从GitHub以外的地方付费后“购买”的本程序,或您被提示“必须付费后才能使用本程序”,那**您大概率被骗了**,或者**您下载到了本程序修改版**。**本程序对此概不负责,请勿找作者商讨退款、售后事宜,相关事宜请咨询卖方**。
|
||||
- 本程序**不对您的求职过程与结果负责**,为您开聊的职位均在BOSS直聘上发布,职位信息真实性由BOSS直聘负责;请**自行甄别为您开聊的公司**、**认真决定是否参加面试**、**慎重选择Offer**。
|
||||
- 请在BOSS直聘上自行**屏蔽您不期望投递的公司**;如果您不希望您当前公司其它具有招聘账号的员工看到您在BOSS直聘上活跃,请**在BOSS直聘上屏蔽当前公司及与之关联的公司**。
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"staticCombineRecommendJobFilterConditions": [],
|
||||
"isSkipEmptyConditionForCombineRecommendJobFilter": false,
|
||||
"expectJobRegExpStr": "",
|
||||
"expectJobNameRegExpStr": "",
|
||||
"expectJobTypeRegExpStr": "",
|
||||
"expectJobDescRegExpStr": "",
|
||||
"jobNotMatchStrategy": 1,
|
||||
"jobNotActiveStrategy": 1,
|
||||
"markAsNotActiveSelectedTimeRange": 7,
|
||||
@@ -36,5 +39,6 @@
|
||||
"isSageTimeEnabled": true,
|
||||
"sageTimeOpTimes": 100,
|
||||
"sageTimePauseMinute": 15,
|
||||
"blockCompanyNameRegExpStr": ""
|
||||
"blockCompanyNameRegExpStr": "",
|
||||
"fieldsForUseCommonConfig": {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"expectCityList": [],
|
||||
"expectJobNameRegExpStr": "",
|
||||
"expectJobTypeRegExpStr": "",
|
||||
"expectJobDescRegExpStr": "",
|
||||
"expectCompanies": [],
|
||||
"blockCompanyNameRegExpStr": "",
|
||||
"jobDetailRegExpMatchLogic": 1,
|
||||
"expectSalaryCalculateWay": 1,
|
||||
"expectSalaryLow": null,
|
||||
"expectSalaryHigh": null
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
import { parseSalary } from "@geekgeekrun/sqlite-plugin/dist/utils/parser"
|
||||
import { waitForSageTimeOrJustContinue } from './sage-time.mjs'
|
||||
import cityGroupData from './cityGroup.mjs'
|
||||
import { hasIntersection } from '@geekgeekrun/utils/number.mjs';
|
||||
const flattedCityList = []
|
||||
;(cityGroupData?.zpData?.cityGroup ?? []).forEach(it => {
|
||||
const firstChar = it.firstChar
|
||||
@@ -112,7 +113,15 @@ export async function initPuppeteer () {
|
||||
}
|
||||
}
|
||||
|
||||
const targetCompanyList = readConfigFile('target-company-list.json').filter(it => !!it.trim());
|
||||
const commonJobConditionConfig = readConfigFile('common-job-condition-config.json')
|
||||
const fieldsForUseCommonConfig = readConfigFile('boss.json').fieldsForUseCommonConfig ?? {}
|
||||
|
||||
const targetCompanyList = (
|
||||
!fieldsForUseCommonConfig.expectCompanies ?
|
||||
readConfigFile('target-company-list.json')
|
||||
:
|
||||
commonJobConditionConfig.expectCompanies
|
||||
).filter(it => !!it.trim());
|
||||
const combineRecommendJobFilterType = readConfigFile('boss.json').combineRecommendJobFilterType ?? CombineRecommendJobFilterType.ANY_COMBINE
|
||||
|
||||
const anyCombineRecommendJobFilter = readConfigFile('boss.json').anyCombineRecommendJobFilter
|
||||
@@ -125,14 +134,34 @@ const expectJobRegExpStr = readConfigFile('boss.json').expectJobRegExpStr
|
||||
const jobNotMatchStrategy = readConfigFile('boss.json').jobNotMatchStrategy ?? MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
|
||||
|
||||
const expectCityNotMatchStrategy = readConfigFile('boss.json').expectCityNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||
const expectCityList = readConfigFile('boss.json').expectCityList ?? []
|
||||
const expectCityList = (
|
||||
!fieldsForUseCommonConfig.city ?
|
||||
readConfigFile('boss.json').expectCityList
|
||||
:
|
||||
commonJobConditionConfig.expectCityList
|
||||
) ?? []
|
||||
|
||||
const strategyScopeOptionWhenMarkJobCityNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobCityNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
|
||||
// salary
|
||||
const expectSalaryLow = parseFloat(readConfigFile('boss.json').expectSalaryLow) || null
|
||||
const expectSalaryHigh = parseFloat(readConfigFile('boss.json').expectSalaryHigh) || null
|
||||
const expectSalaryCalculateWay = readConfigFile('boss.json').expectSalaryCalculateWay ?? SalaryCalculateWay.MONTH_SALARY
|
||||
const expectSalaryLow = parseFloat(
|
||||
!fieldsForUseCommonConfig.salary ?
|
||||
readConfigFile('boss.json').expectSalaryLow
|
||||
:
|
||||
commonJobConditionConfig.expectSalaryLow
|
||||
) || null
|
||||
const expectSalaryHigh = parseFloat(
|
||||
!fieldsForUseCommonConfig.salary ?
|
||||
readConfigFile('boss.json').expectSalaryHigh
|
||||
:
|
||||
commonJobConditionConfig.expectSalaryHigh
|
||||
) || null
|
||||
const expectSalaryCalculateWay = (
|
||||
!fieldsForUseCommonConfig.salary ?
|
||||
readConfigFile('boss.json').expectSalaryCalculateWay
|
||||
:
|
||||
commonJobConditionConfig.expectSalaryCalculateWay
|
||||
) ?? SalaryCalculateWay.MONTH_SALARY
|
||||
const expectSalaryNotMatchStrategy = readConfigFile('boss.json').expectSalaryNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||
const isSalaryFilterEnabled = expectSalaryLow || expectSalaryHigh
|
||||
const strategyScopeOptionWhenMarkSalaryNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkSalaryNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
@@ -153,7 +182,12 @@ expectWorkExpList = Array.from(expectWorkExpListSet)
|
||||
const expectWorkExpNotMatchStrategy = readConfigFile('boss.json').expectWorkExpNotMatchStrategy ?? MarkAsNotSuitOp.NO_OP
|
||||
const strategyScopeOptionWhenMarkJobWorkExpNotMatch = readConfigFile('boss.json').strategyScopeOptionWhenMarkJobWorkExpNotMatch ?? StrategyScopeOptionWhenMarkJobNotMatch.ONLY_COMPANY_MATCHED_JOB
|
||||
|
||||
let jobDetailRegExpMatchLogic = readConfigFile('boss.json').jobDetailRegExpMatchLogic ?? JobDetailRegExpMatchLogic.EVERY
|
||||
let jobDetailRegExpMatchLogic = (
|
||||
!fieldsForUseCommonConfig.jobDetail ?
|
||||
readConfigFile('boss.json').jobDetailRegExpMatchLogic
|
||||
:
|
||||
commonJobConditionConfig.jobDetailRegExpMatchLogic
|
||||
) ?? JobDetailRegExpMatchLogic.EVERY
|
||||
|
||||
const markAsNotActiveSelectedTimeRange = (() => {
|
||||
let n = readConfigFile('boss.json').markAsNotActiveSelectedTimeRange
|
||||
@@ -176,8 +210,9 @@ let {
|
||||
expectJobNameRegExpStr,
|
||||
expectJobTypeRegExpStr,
|
||||
expectJobDescRegExpStr,
|
||||
} = readConfigFile('boss.json')
|
||||
} = !fieldsForUseCommonConfig.jobDetail ? readConfigFile('boss.json') : commonJobConditionConfig
|
||||
if (
|
||||
!fieldsForUseCommonConfig.jobDetail &&
|
||||
expectJobRegExpStr &&
|
||||
!expectJobNameRegExpStr &&
|
||||
!expectJobTypeRegExpStr &&
|
||||
@@ -247,7 +282,12 @@ const recommendJobPageUrl = `https://www.zhipin.com/web/geek/jobs`
|
||||
|
||||
const expectCompanySet = new Set(targetCompanyList)
|
||||
const enableCompanyAllowList = Boolean(expectCompanySet.size)
|
||||
const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? ''
|
||||
const blockCompanyNameRegExpStr = (
|
||||
!fieldsForUseCommonConfig.blockCompanyNameRegExpStr ?
|
||||
readConfigFile('boss.json').blockCompanyNameRegExpStr
|
||||
:
|
||||
commonJobConditionConfig.blockCompanyNameRegExpStr
|
||||
) ?? ''
|
||||
const blockCompanyNameRegExp = (() => {
|
||||
if (!blockCompanyNameRegExpStr?.trim()) {
|
||||
return null
|
||||
@@ -659,7 +699,7 @@ async function toRecommendPage (hooks) {
|
||||
hooks.pageLoaded?.call()
|
||||
|
||||
let userInfoResponse = await userInfoPromise
|
||||
await hooks.userInfoResponse?.promise(userInfoResponse)
|
||||
await hooks.userInfoResponse?.promise({ userInfoResponse, browser })
|
||||
if (userInfoResponse?.code !== 0) {
|
||||
autoStartChatEventBus.emit('LOGIN_STATUS_INVALID', {
|
||||
userInfoResponse
|
||||
@@ -947,20 +987,24 @@ async function toRecommendPage (hooks) {
|
||||
function checkIfSalarySuit(salaryDesc) {
|
||||
const salaryData = parseSalary(salaryDesc)
|
||||
if (expectSalaryCalculateWay === SalaryCalculateWay.MONTH_SALARY) {
|
||||
if (expectSalaryHigh && salaryData.high > expectSalaryHigh) {
|
||||
return false
|
||||
let ourSalaryInterval = [expectSalaryLow ?? null, expectSalaryHigh ?? null]
|
||||
if (ourSalaryInterval.every(it => !isNaN(parseFloat(it)))) {
|
||||
ourSalaryInterval = ourSalaryInterval.sort((a, b) => a - b)
|
||||
}
|
||||
if (expectSalaryLow && salaryData.high < expectSalaryLow) {
|
||||
return false
|
||||
}
|
||||
} else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
||||
const theirSalaryInterval = [salaryData.low ?? null, salaryData.high ?? null]
|
||||
return hasIntersection(theirSalaryInterval, ourSalaryInterval)
|
||||
}
|
||||
else if (expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
||||
const salaryDataMonth = salaryData.month || 12
|
||||
if (expectSalaryHigh && (salaryData.high * salaryDataMonth) / 10 > expectSalaryHigh) {
|
||||
return false
|
||||
}
|
||||
if (expectSalaryLow && (salaryData.high * salaryDataMonth) / 10 < expectSalaryLow) {
|
||||
return false
|
||||
let ourSalaryInterval = [expectSalaryLow ?? null, expectSalaryHigh ?? null]
|
||||
if (ourSalaryInterval.every(it => !isNaN(parseFloat(it)))) {
|
||||
ourSalaryInterval = ourSalaryInterval.sort((a, b) => a - b)
|
||||
}
|
||||
const theirSalaryInterval = [salaryData.low ?? null, salaryData.high ?? null].map(
|
||||
it =>
|
||||
it === null ? null : (it * salaryDataMonth / 10)
|
||||
)
|
||||
return hasIntersection(theirSalaryInterval, ourSalaryInterval)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1373,7 +1417,12 @@ async function toRecommendPage (hooks) {
|
||||
// 刚刚活跃 // 今日活跃 // 昨日活跃 // 3日内活跃 // 本周活跃 // 2周内活跃
|
||||
// 本月活跃 // 2月内活跃 // 3月内活跃 // 4月内活跃 // 5月内活跃 // 近半年活跃 // 半年前活跃
|
||||
//#endregion
|
||||
const indexOfActiveText = activeDescList.indexOf(targetJobData.bossInfo.activeTimeDesc)
|
||||
let activeTimeDescForCompare = targetJobData.bossInfo.activeTimeDesc
|
||||
// handle empty string case
|
||||
if (activeTimeDescForCompare === '') {
|
||||
activeTimeDescForCompare = '半年前活跃'
|
||||
}
|
||||
const indexOfActiveText = activeDescList.indexOf(activeTimeDescForCompare)
|
||||
if (
|
||||
markAsNotActiveSelectedTimeRange > 0 &&
|
||||
indexOfActiveText > 0 && indexOfActiveText <= markAsNotActiveSelectedTimeRange
|
||||
@@ -1488,10 +1537,23 @@ async function toRecommendPage (hooks) {
|
||||
return false
|
||||
}
|
||||
);
|
||||
const res = await addFriendResponse.json()
|
||||
return res
|
||||
await sleepWithRandomDelay(3000)
|
||||
let res
|
||||
try {
|
||||
res = await addFriendResponse.json()
|
||||
return res
|
||||
}
|
||||
catch(err) {
|
||||
await sleep(2000)
|
||||
if (page.url().startsWith('https://www.zhipin.com/web/geek/chat')) {
|
||||
throw new Error('PAGE_JUMPED_TO_CHAT_PAGE')
|
||||
}
|
||||
else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
const waitAndHandleChatSuccess = async () => {
|
||||
const waitAndHandleChatSuccess = async ({ hasGoToChatPage = false } = {}) => {
|
||||
await hooks.newChatStartup?.promise(
|
||||
targetJobData,
|
||||
{
|
||||
@@ -1503,13 +1565,22 @@ async function toRecommendPage (hooks) {
|
||||
|
||||
await storeStorage(page).catch(() => void 0)
|
||||
await sleepWithRandomDelay(1500)
|
||||
if (hasGoToChatPage) {
|
||||
await page.goBack()
|
||||
await page.waitForFunction(() => {
|
||||
return location.href.startsWith(`https://www.zhipin.com/web/geek/jobs`) && document.readyState === 'complete'
|
||||
})
|
||||
await sleepWithRandomDelay(2000)
|
||||
}
|
||||
const closeDialogButtonProxy = await page.$('.greet-boss-dialog .greet-boss-footer .cancel-btn')
|
||||
await closeDialogButtonProxy.click()
|
||||
await sleepWithRandomDelay(2000)
|
||||
if (closeDialogButtonProxy) {
|
||||
await closeDialogButtonProxy.click()
|
||||
await sleepWithRandomDelay(2000)
|
||||
}
|
||||
}
|
||||
const handleAddFriendResponse = async (res) => {
|
||||
const handleAddFriendResponse = async (res, { hasGoToChatPage = false } = {}) => {
|
||||
if (res.code === 0) {
|
||||
await waitAndHandleChatSuccess()
|
||||
await waitAndHandleChatSuccess({ hasGoToChatPage })
|
||||
}
|
||||
else if (
|
||||
res.zpData.bizCode === 1 &&
|
||||
@@ -1557,9 +1628,21 @@ async function toRecommendPage (hooks) {
|
||||
throw new Error('STARTUP_CHAT_ERROR_WITH_UNKNOWN_ERROR')
|
||||
}
|
||||
}
|
||||
const res = await waitAddFriendResponse()
|
||||
await handleAddFriendResponse(res)
|
||||
// #endregion
|
||||
let res
|
||||
try {
|
||||
res = await waitAddFriendResponse()
|
||||
await handleAddFriendResponse(res)
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof Error && err.message === 'PAGE_JUMPED_TO_CHAT_PAGE') {
|
||||
await handleAddFriendResponse({
|
||||
code: 0
|
||||
}, { hasGoToChatPage: true })
|
||||
}
|
||||
else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
switch (err.message) {
|
||||
@@ -1630,8 +1713,11 @@ export async function mainLoop (hooks) {
|
||||
//set cookies
|
||||
const bossCookies = readStorageFile('boss-cookies.json')
|
||||
const bossLocalStorage = readStorageFile('boss-local-storage.json')
|
||||
await hooks.cookieWillSet?.promise(bossCookies)
|
||||
await hooks.cookieWillSet?.promise({ cookies: bossCookies, browser })
|
||||
for(let i = 0; i < bossCookies.length; i++){
|
||||
if (Object.hasOwn(bossCookies[i], 'sameSite')) {
|
||||
bossCookies[i].sameSite = 'unspecified'
|
||||
}
|
||||
await page.setCookie(bossCookies[i]);
|
||||
}
|
||||
await setDomainLocalStorage(browser, localStoragePageUrl, bossLocalStorage)
|
||||
|
||||
@@ -11,16 +11,114 @@ import defaultLlmConf from './default-config-file/llm.json' assert { type: 'json
|
||||
import defaultBossCookieStorage from './default-storage-file/boss-cookies.json' assert { type: 'json' }
|
||||
import defaultBossLocalStorageStorage from './default-storage-file/boss-local-storage.json' assert { type: 'json' }
|
||||
import defaultJobNotSuitReasonCodeToTextCacheStorage from './default-storage-file/job-not-suit-reason-code-to-text-cache.json' assert { type: 'json' }
|
||||
export const configFileNameList = ['boss.json', 'dingtalk.json', 'target-company-list.json', 'llm.json']
|
||||
import defaultCommonJobConditionConfig from './default-config-file/common-job-condition-config.json' assert { type: 'json' }
|
||||
export const configFileNameList = ['boss.json', 'dingtalk.json', 'target-company-list.json', 'llm.json', 'common-job-condition-config.json']
|
||||
|
||||
const defaultConfigFileContentMap = {
|
||||
'boss.json': JSON.stringify(defaultBossConf),
|
||||
'dingtalk.json': JSON.stringify(defaultDingtalkConf),
|
||||
'target-company-list.json': JSON.stringify(defaultTargetCompanyListConf),
|
||||
'llm.json': JSON.stringify(defaultLlmConf)
|
||||
'llm.json': JSON.stringify(defaultLlmConf),
|
||||
'common-job-condition-config.json': JSON.stringify(defaultCommonJobConditionConfig)
|
||||
}
|
||||
const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
|
||||
export const configFolderPath = path.join(
|
||||
runtimeFolderPath,
|
||||
'config'
|
||||
)
|
||||
export const writeConfigFile = async (fileName, content, { isSync } = {}) => {
|
||||
const filePath = path.join(configFolderPath, fileName)
|
||||
const fileContent = JSON.stringify(content)
|
||||
if (isSync) {
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
fileContent
|
||||
)
|
||||
}
|
||||
else {
|
||||
return fsPromise.writeFile(
|
||||
filePath,
|
||||
fileContent
|
||||
)
|
||||
}
|
||||
}
|
||||
if (
|
||||
!fs.existsSync(
|
||||
path.join(configFolderPath, 'common-job-condition-config.json')
|
||||
)
|
||||
) {
|
||||
let bossConfig = null
|
||||
if (
|
||||
fs.existsSync(
|
||||
path.join(configFolderPath, 'boss.json')
|
||||
)
|
||||
) {
|
||||
fs.existsSync(
|
||||
path.join(configFolderPath, 'boss.json')
|
||||
)
|
||||
try {
|
||||
bossConfig = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(configFolderPath, 'boss.json')
|
||||
)
|
||||
)
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
if (bossConfig) {
|
||||
Object.keys(defaultCommonJobConditionConfig).forEach(
|
||||
key => {
|
||||
if (Object.hasOwn(bossConfig, key)) {
|
||||
defaultCommonJobConditionConfig[key] = bossConfig[key]
|
||||
}
|
||||
}
|
||||
)
|
||||
let {
|
||||
expectJobRegExpStr,
|
||||
expectJobNameRegExpStr,
|
||||
expectJobTypeRegExpStr,
|
||||
expectJobDescRegExpStr,
|
||||
} = bossConfig
|
||||
if (
|
||||
expectJobRegExpStr &&
|
||||
!expectJobNameRegExpStr &&
|
||||
!expectJobTypeRegExpStr &&
|
||||
!expectJobDescRegExpStr
|
||||
) {
|
||||
expectJobNameRegExpStr = expectJobRegExpStr
|
||||
expectJobTypeRegExpStr = expectJobRegExpStr
|
||||
expectJobDescRegExpStr = expectJobRegExpStr
|
||||
}
|
||||
Object.assign(defaultCommonJobConditionConfig, {
|
||||
expectJobNameRegExpStr,
|
||||
expectJobTypeRegExpStr,
|
||||
expectJobDescRegExpStr
|
||||
})
|
||||
}
|
||||
let targetCompanyList = null
|
||||
if (
|
||||
fs.existsSync(
|
||||
path.join(configFolderPath, 'target-company-list.json')
|
||||
)
|
||||
) {
|
||||
targetCompanyList = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(configFolderPath, 'target-company-list.json')
|
||||
)
|
||||
)
|
||||
}
|
||||
if (targetCompanyList) {
|
||||
defaultCommonJobConditionConfig.expectCompanies = targetCompanyList ?? []
|
||||
}
|
||||
writeConfigFile('common-job-condition-config.json', defaultCommonJobConditionConfig, { isSync: true })
|
||||
if (bossConfig) {
|
||||
if (!bossConfig.fieldsForUseCommonConfig) {
|
||||
bossConfig.fieldsForUseCommonConfig = {}
|
||||
}
|
||||
writeConfigFile('boss.json', bossConfig, { isSync: true })
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
|
||||
const ensureRuntimeFolderPathExist = () => {
|
||||
if (!fs.existsSync(runtimeFolderPath)) {
|
||||
fs.mkdirSync(runtimeFolderPath)
|
||||
@@ -35,11 +133,6 @@ const ensureRuntimeFolderPathExist = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const configFolderPath = path.join(
|
||||
runtimeFolderPath,
|
||||
'config'
|
||||
)
|
||||
export const ensureConfigFileExist = () => {
|
||||
ensureRuntimeFolderPathExist()
|
||||
;configFileNameList.forEach(
|
||||
@@ -82,15 +175,6 @@ export const readConfigFile = (fileName) => {
|
||||
return o
|
||||
}
|
||||
|
||||
export const writeConfigFile = async (fileName, content) => {
|
||||
const filePath = path.join(configFolderPath, fileName)
|
||||
const fileContent = JSON.stringify(content)
|
||||
return fsPromise.writeFile(
|
||||
filePath,
|
||||
fileContent
|
||||
)
|
||||
}
|
||||
|
||||
export const storageFilePath = path.join(
|
||||
runtimeFolderPath,
|
||||
'storage'
|
||||
|
||||
@@ -1,125 +1,134 @@
|
||||
"use strict";
|
||||
|
||||
const { PuppeteerExtraPlugin } = require("puppeteer-extra-plugin");
|
||||
async function handle(p) {
|
||||
await p.evaluateOnNewDocument(() => {
|
||||
(() => {
|
||||
"use strict";
|
||||
/* -------------------------------------------------------
|
||||
* 1. 保存原生 Function.prototype.toString
|
||||
* ----------------------------------------------------- */
|
||||
const nativeFunctionToString = Function.prototype.toString;
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 2. WeakMap:函数 → 伪原生源码
|
||||
* ----------------------------------------------------- */
|
||||
const nativeSourceMap = new WeakMap();
|
||||
// 定义脚本内容,避免重复定义
|
||||
const stealthScript = () => {
|
||||
"use strict";
|
||||
/* -------------------------------------------------------
|
||||
* 1. 保存原生 Function.prototype.toString
|
||||
* ----------------------------------------------------- */
|
||||
const nativeFunctionToString = Function.prototype.toString;
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 3. 注册伪原生源码
|
||||
* ----------------------------------------------------- */
|
||||
const registerNativeSource = (fn, source) => {
|
||||
try {
|
||||
nativeSourceMap.set(fn, source);
|
||||
} catch (_) {}
|
||||
};
|
||||
/* -------------------------------------------------------
|
||||
* 2. WeakMap:函数 → 伪原生源码
|
||||
* ----------------------------------------------------- */
|
||||
const nativeSourceMap = new WeakMap();
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 4. 劫持 Function.prototype.toString
|
||||
* ----------------------------------------------------- */
|
||||
Object.defineProperty(Function.prototype, "toString", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function toString() {
|
||||
if (nativeSourceMap.has(this)) {
|
||||
return nativeSourceMap.get(this);
|
||||
}
|
||||
return nativeFunctionToString.call(this);
|
||||
},
|
||||
});
|
||||
/* -------------------------------------------------------
|
||||
* 3. 注册伪原生源码
|
||||
* ----------------------------------------------------- */
|
||||
const registerNativeSource = (fn, source) => {
|
||||
try {
|
||||
nativeSourceMap.set(fn, source);
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 5. 伪装 Function.prototype.toString 自身
|
||||
* ----------------------------------------------------- */
|
||||
registerNativeSource(
|
||||
Function.prototype.toString,
|
||||
nativeFunctionToString.toString(),
|
||||
);
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 6. stealthify:包装函数但保持“原生外观”
|
||||
* ----------------------------------------------------- */
|
||||
const stealthify = (obj, prop, handler) => {
|
||||
const original = obj[prop];
|
||||
if (typeof original !== "function") return;
|
||||
|
||||
const wrapped = function (...args) {
|
||||
return handler.call(this, original, args);
|
||||
};
|
||||
const namePropertyDescriptor = Object.getOwnPropertyDescriptor(
|
||||
wrapped,
|
||||
"name",
|
||||
);
|
||||
// 处理函数 name 属性
|
||||
Object.defineProperty(wrapped, "name", {
|
||||
...namePropertyDescriptor,
|
||||
value: prop,
|
||||
});
|
||||
// 保留 prototype(某些函数有)
|
||||
try {
|
||||
Object.setPrototypeOf(wrapped, Object.getPrototypeOf(original));
|
||||
} catch (_) {}
|
||||
|
||||
// 注册伪原生源码(直接复用原函数的 native 表现)
|
||||
registerNativeSource(wrapped, nativeFunctionToString.call(original));
|
||||
|
||||
// 用 defineProperty 保持 descriptor 接近原生
|
||||
const desc = Object.getOwnPropertyDescriptor(obj, prop);
|
||||
Object.defineProperty(obj, prop, {
|
||||
...desc,
|
||||
value: wrapped,
|
||||
});
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 7. 示例:stealth console.log / debug / info
|
||||
* ----------------------------------------------------- */
|
||||
const filterConsoleArgs = (args) =>
|
||||
args.map((arg) => {
|
||||
if (arg && typeof arg === "object") {
|
||||
// 防止 getter / Proxy / 大对象触发
|
||||
return {};
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
|
||||
[
|
||||
"log",
|
||||
"debug",
|
||||
"info",
|
||||
"warn",
|
||||
"error",
|
||||
"dir",
|
||||
"table",
|
||||
"debug",
|
||||
].forEach((name) => {
|
||||
stealthify(console, name, (original, args) => {
|
||||
// ❗不传递原始对象,避免 DevTools / CDP 展开
|
||||
return original.apply(console, filterConsoleArgs(args));
|
||||
});
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 8. 防御性补丁(可选但强烈建议)
|
||||
* ----------------------------------------------------- */
|
||||
|
||||
// 防止检测 toString 被替换
|
||||
registerNativeSource(
|
||||
registerNativeSource,
|
||||
"function registerNativeSource() { [native code] }",
|
||||
);
|
||||
})();
|
||||
/* -------------------------------------------------------
|
||||
* 4. 劫持 Function.prototype.toString
|
||||
* ----------------------------------------------------- */
|
||||
Object.defineProperty(Function.prototype, "toString", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function toString() {
|
||||
if (nativeSourceMap.has(this)) {
|
||||
return nativeSourceMap.get(this);
|
||||
}
|
||||
return nativeFunctionToString.call(this);
|
||||
},
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 5. 伪装 Function.prototype.toString 自身
|
||||
* ----------------------------------------------------- */
|
||||
registerNativeSource(
|
||||
Function.prototype.toString,
|
||||
nativeFunctionToString.toString(),
|
||||
);
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 6. stealthify:包装函数但保持"原生外观"
|
||||
* ----------------------------------------------------- */
|
||||
const stealthify = (obj, prop, handler) => {
|
||||
const original = obj[prop];
|
||||
if (typeof original !== "function") return;
|
||||
|
||||
const wrapped = function (...args) {
|
||||
return handler.call(this, original, args);
|
||||
};
|
||||
const namePropertyDescriptor = Object.getOwnPropertyDescriptor(
|
||||
wrapped,
|
||||
"name",
|
||||
);
|
||||
// 处理函数 name 属性
|
||||
Object.defineProperty(wrapped, "name", {
|
||||
...namePropertyDescriptor,
|
||||
value: prop,
|
||||
});
|
||||
// 保留 prototype(某些函数有)
|
||||
try {
|
||||
Object.setPrototypeOf(wrapped, Object.getPrototypeOf(original));
|
||||
} catch (_) {}
|
||||
|
||||
// 注册伪原生源码(直接复用原函数的 native 表现)
|
||||
registerNativeSource(wrapped, nativeFunctionToString.call(original));
|
||||
|
||||
// 用 defineProperty 保持 descriptor 接近原生
|
||||
const desc = Object.getOwnPropertyDescriptor(obj, prop);
|
||||
Object.defineProperty(obj, prop, {
|
||||
...desc,
|
||||
value: wrapped,
|
||||
});
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 7. 示例:stealth console.log / debug / info
|
||||
* ----------------------------------------------------- */
|
||||
const filterConsoleArgs = (args) =>
|
||||
args.map((arg) => {
|
||||
if (arg && typeof arg === "object") {
|
||||
// 防止 getter / Proxy / 大对象触发
|
||||
return {};
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
|
||||
[
|
||||
"log",
|
||||
"debug",
|
||||
"info",
|
||||
"warn",
|
||||
"error",
|
||||
"dir",
|
||||
"table",
|
||||
"debug",
|
||||
].forEach((name) => {
|
||||
stealthify(console, name, (original, args) => {
|
||||
// ❗不传递原始对象,避免 DevTools / CDP 展开
|
||||
return original.apply(console, filterConsoleArgs(args));
|
||||
});
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* 8. 防御性补丁(可选但强烈建议)
|
||||
* ----------------------------------------------------- */
|
||||
|
||||
// 防止检测 toString 被替换
|
||||
registerNativeSource(
|
||||
registerNativeSource,
|
||||
"function registerNativeSource() { [native code] }",
|
||||
);
|
||||
};
|
||||
|
||||
async function handle(p) {
|
||||
// 1. 立即执行一次(针对当前已有的文档,防止错过)
|
||||
try {
|
||||
await p.evaluate(stealthScript);
|
||||
} catch (e) {
|
||||
// 如果当前页面还没准备好,忽略错误
|
||||
}
|
||||
// 2. 注册为新文档脚本(针对未来的导航)
|
||||
await p.evaluateOnNewDocument(stealthScript);
|
||||
}
|
||||
|
||||
class Plugin extends PuppeteerExtraPlugin {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@geekgeekrun/puppeteer-extra-plugin-laodeng",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "laodeng",
|
||||
"main": "./index.js",
|
||||
"author": "geekgeekrun",
|
||||
|
||||
@@ -47,7 +47,13 @@ export async function ensureEditThisCookie () {
|
||||
if (geekgeekrunEditVersion < APP_GEEKGEEKRUN_EDIT_VERSION) {
|
||||
isNeedExtractEditThisCookie = true
|
||||
}
|
||||
|
||||
const isExtractDoneFlagFilePath = path.join(editThisCookieExtensionPath, 'EXTRACT_DONE')
|
||||
if (
|
||||
!isNeedExtractEditThisCookie &&
|
||||
!fs.existsSync(isExtractDoneFlagFilePath)
|
||||
) {
|
||||
isNeedExtractEditThisCookie = true
|
||||
}
|
||||
if (isNeedExtractEditThisCookie) {
|
||||
if (
|
||||
fs.existsSync(
|
||||
@@ -68,5 +74,9 @@ export async function ensureEditThisCookie () {
|
||||
dir: extensionDir
|
||||
}
|
||||
)
|
||||
await fs.promises.writeFile(
|
||||
isExtractDoneFlagFilePath,
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ const server = net.createServer((socket) => {
|
||||
|
||||
// 处理消息
|
||||
function handleMessage(socket, message) {
|
||||
console.log('收到消息:', message);
|
||||
message.type !== 'get-status' && console.log('收到消息:', message);
|
||||
const _callbackUuid = message._callbackUuid
|
||||
if (message.type === 'ping') {
|
||||
sendResponse(socket, _callbackUuid, {
|
||||
@@ -143,6 +143,23 @@ function handleMessage(socket, message) {
|
||||
return
|
||||
}
|
||||
case 'worker-to-gui-message': {
|
||||
// 将 prerequisite step 状态写入 worker 的 runtimeStorage
|
||||
if (
|
||||
workerInfo &&
|
||||
message.data?.type === 'prerequisite-step-by-step-check' &&
|
||||
message.data?.step?.id
|
||||
) {
|
||||
workerInfo.runtimeStorage = workerInfo.runtimeStorage || {}
|
||||
workerInfo.runtimeStorage.stepStatusMapByStepId =
|
||||
workerInfo.runtimeStorage.stepStatusMapByStepId || {}
|
||||
workerInfo.runtimeStorage.stepStatusMapByStepId[
|
||||
message.data.step.id
|
||||
] = {
|
||||
step: message.data.step,
|
||||
runRecordId: message.data.runRecordId
|
||||
}
|
||||
}
|
||||
|
||||
// 转发工具进程消息到GUI客户端
|
||||
broadcastToGUI({
|
||||
type: 'worker-to-gui-message',
|
||||
@@ -329,6 +346,9 @@ function startWorker({ workerId, command, args, env }, restartCount = 0) {
|
||||
status: 'running',
|
||||
startTime: Date.now(),
|
||||
restartCount, // 使用传入的重启次数
|
||||
runtimeStorage: {
|
||||
stepStatusMapByStepId: {}
|
||||
},
|
||||
// socket: null, // 工具进程的TCP连接,稍后由工具进程注册
|
||||
// lastHeartbeat: null,
|
||||
command,
|
||||
@@ -390,6 +410,7 @@ function getWorkersStatus() {
|
||||
status: workerInfo.status,
|
||||
uptime: Date.now() - workerInfo.startTime,
|
||||
restartCount: workerInfo.restartCount || 0,
|
||||
runtimeStorage: workerInfo.runtimeStorage || {},
|
||||
// connected: workerInfo.socket !== null && !workerInfo.socket.destroyed,
|
||||
// lastHeartbeat: workerInfo.lastHeartbeat,
|
||||
command: workerInfo.command,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AUTO_CHAT_ERROR_EXIT_CODE } from './enums.mjs'
|
||||
const rerunInterval = (() => {
|
||||
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
|
||||
if (isNaN(v)) {
|
||||
v = 3000
|
||||
v = 5000
|
||||
}
|
||||
|
||||
return v
|
||||
|
||||
@@ -22,7 +22,7 @@ const {
|
||||
const rerunInterval = (() => {
|
||||
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
|
||||
if (isNaN(v)) {
|
||||
v = 3000
|
||||
v = 5000
|
||||
}
|
||||
|
||||
return v
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.6.2",
|
||||
"cli-highlight": "^2.1.11",
|
||||
"lodash": "^4.17.21",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"better-sqlite3": "12.6.2",
|
||||
"typeorm": "0.3.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.17.23",
|
||||
"node-gyp": "^12.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
|
||||
@@ -116,7 +116,7 @@ export default class SqlitePlugin {
|
||||
)
|
||||
hooks.userInfoResponse.tapPromise(
|
||||
"SqlitePlugin",
|
||||
async (userInfoResponse) => {
|
||||
async ({ userInfoResponse } = {}) => {
|
||||
if (!userInfoResponse || userInfoResponse.code !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,43 +7,72 @@ import transformerDirective from '@unocss/transformer-directives'
|
||||
import Replace from 'unplugin-replace/vite'
|
||||
|
||||
process.env = { ...process.env, ...loadEnv(process.env.NODE_ENV!, process.cwd()) }
|
||||
const mainPlugins = [
|
||||
externalizeDepsPlugin({
|
||||
exclude: [
|
||||
'@geekgeekrun/utils',
|
||||
'find-chrome-bin',
|
||||
'@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension'
|
||||
]
|
||||
}),
|
||||
Replace({
|
||||
delimiters: ['', ''],
|
||||
sourcemap: true,
|
||||
include: ['**/src/main/utils/gtag/Analytics.ts'],
|
||||
values: [
|
||||
{
|
||||
find: /<measurement_id>/g,
|
||||
replacement: process.env.VITE_APP_GTAG_MEASUREMENT_ID as string
|
||||
},
|
||||
{
|
||||
find: /<api_secret>/g,
|
||||
replacement: process.env.VITE_APP_GTAG_API_SECRET as string
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
const preloadPlugins = [externalizeDepsPlugin()]
|
||||
const rendererPlugins = [
|
||||
vue(),
|
||||
UnoCSS({
|
||||
presets: [presetUno(), presetAttributify(), presetIcons()],
|
||||
transformers: [transformerDirective()]
|
||||
})
|
||||
]
|
||||
if (process.env.NODE_ENV) {
|
||||
;[mainPlugins, preloadPlugins, rendererPlugins].forEach((pluginList) => {
|
||||
pluginList.push(
|
||||
Replace({
|
||||
delimiters: ['', ''],
|
||||
sourcemap: true,
|
||||
include: ['**'],
|
||||
values: [
|
||||
{
|
||||
find: /process.env.NODE_ENV/g,
|
||||
replacement: `'${process.env.NODE_ENV}'` as string
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: []
|
||||
},
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser',
|
||||
watch: process.env.NODE_ENV === 'development' ? {} : undefined
|
||||
},
|
||||
plugins: [
|
||||
externalizeDepsPlugin({
|
||||
exclude: [
|
||||
'@geekgeekrun/utils',
|
||||
'find-chrome-bin',
|
||||
'@geekgeekrun/launch-bosszhipin-login-page-with-preload-extension'
|
||||
]
|
||||
}),
|
||||
Replace({
|
||||
delimiters: ['', ''],
|
||||
sourcemap: true,
|
||||
include: ['**/src/main/utils/gtag/Analytics.ts'],
|
||||
values: [
|
||||
{
|
||||
find: /<measurement_id>/g,
|
||||
replacement: process.env.VITE_APP_GTAG_MEASUREMENT_ID as string
|
||||
},
|
||||
{
|
||||
find: /<api_secret>/g,
|
||||
replacement: process.env.VITE_APP_GTAG_API_SECRET as string
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
plugins: mainPlugins
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
plugins: preloadPlugins,
|
||||
build: {
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser',
|
||||
watch: process.env.NODE_ENV === 'development' ? {} : undefined
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
@@ -52,13 +81,7 @@ export default defineConfig({
|
||||
'@renderer': resolve('src/renderer/src')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
UnoCSS({
|
||||
presets: [presetUno(), presetAttributify(), presetIcons()],
|
||||
transformers: [transformerDirective()]
|
||||
})
|
||||
],
|
||||
plugins: rendererPlugins,
|
||||
build: {
|
||||
minify: process.env.NODE_ENV === 'development' ? undefined : 'terser'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "geekgeekrun-ui",
|
||||
"version": "0.14.2",
|
||||
"version": "0.17.4",
|
||||
"description": "BOSS 炸弹 - 自动开聊BOSS,助力每位打工人求职!",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "geekgeekrun",
|
||||
@@ -87,4 +87,4 @@
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-tsc": "^1.8.27"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "0.14.2",
|
||||
"buildVersion": 32,
|
||||
"buildTime": 1770299394728,
|
||||
"buildHash": "1d5fdb9d54c569cb53035dfef0f6157c899ccb06",
|
||||
"version": "0.17.4",
|
||||
"buildVersion": 41,
|
||||
"buildTime": 1777732024939,
|
||||
"buildHash": "9d3408d1f3fd5bba0e44736233408e2be2e48536",
|
||||
"name": "geekgeekrun-ui"
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
28
packages/ui/src/main/features/common-job-condition.ts
Normal file
28
packages/ui/src/main/features/common-job-condition.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { createCommonJobConditionConfigWindow } from '../window/commonJobConditionConfigWindow'
|
||||
import { mainWindow } from '../window/mainWindow'
|
||||
|
||||
let commonJobConditionConfigWindow = null
|
||||
export async function waitForCommonJobConditionDone() {
|
||||
return new Promise((resolve, reject) => {
|
||||
commonJobConditionConfigWindow = createCommonJobConditionConfigWindow({
|
||||
parent: mainWindow!,
|
||||
modal: true,
|
||||
show: true
|
||||
})
|
||||
let processDone = false
|
||||
function handler() {
|
||||
processDone = true
|
||||
commonJobConditionConfigWindow.close()
|
||||
}
|
||||
ipcMain.once('common-job-condition-config-done', handler)
|
||||
commonJobConditionConfigWindow.on('closed', async () => {
|
||||
ipcMain.off('common-job-condition-config-done', handler)
|
||||
if (processDone) {
|
||||
resolve(true)
|
||||
} else {
|
||||
reject(new Error('USER_CANCELLED'))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -3,17 +3,37 @@ import minimist from 'minimist'
|
||||
import { loginWithCookieAssistant } from './login-with-cookie-assistant'
|
||||
import { checkCookieListFormat } from '../../common/utils/cookie'
|
||||
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
|
||||
import { readStorageFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||
import {
|
||||
readStorageFile,
|
||||
writeStorageFile
|
||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||
|
||||
const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null
|
||||
export class CookieInvalidHandlePlugin {
|
||||
apply(hooks) {
|
||||
hooks.cookieWillSet.tapPromise('CookieInvalidHandlePlugin', async (cookies) => {
|
||||
hooks.cookieWillSet.tapPromise('CookieInvalidHandlePlugin', async ({ cookies, browser } = {}) => {
|
||||
let isValid = checkCookieListFormat(cookies)
|
||||
while (!isValid) {
|
||||
try {
|
||||
browser && (await browser.close())
|
||||
} catch (err) {
|
||||
console.log(`close browser failed`, err)
|
||||
}
|
||||
try {
|
||||
// popup login dialog, then update login status
|
||||
await loginWithCookieAssistant()
|
||||
let app
|
||||
try {
|
||||
app = (await import('electron')).app
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
// popup login dialog, then update login status
|
||||
try {
|
||||
await app?.dock?.show()
|
||||
await loginWithCookieAssistant()
|
||||
} finally {
|
||||
await app?.dock?.hide()
|
||||
}
|
||||
await sleep(2000)
|
||||
const newCookies = readStorageFile('boss-cookies.json')
|
||||
isValid = checkCookieListFormat(newCookies)
|
||||
@@ -27,8 +47,9 @@ export class CookieInvalidHandlePlugin {
|
||||
if (e?.message === 'USER_CANCELLED_LOGIN') {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'basic-cookie-check',
|
||||
status: 'rejected'
|
||||
@@ -42,8 +63,9 @@ export class CookieInvalidHandlePlugin {
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'basic-cookie-check',
|
||||
status: 'fulfilled'
|
||||
@@ -52,12 +74,13 @@ export class CookieInvalidHandlePlugin {
|
||||
}
|
||||
})
|
||||
})
|
||||
hooks.userInfoResponse.tapPromise('CookieInvalidHandlePlugin', async (userInfoResponse) => {
|
||||
hooks.userInfoResponse.tapPromise('CookieInvalidHandlePlugin', async ({ userInfoResponse, browser } = {}) => {
|
||||
if (userInfoResponse.code === 0) {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'fulfilled'
|
||||
@@ -67,15 +90,34 @@ export class CookieInvalidHandlePlugin {
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
browser && (await browser.close())
|
||||
} catch (err) {
|
||||
console.log(`close browser failed`, err)
|
||||
}
|
||||
await writeStorageFile('boss-cookies.json', [])
|
||||
try {
|
||||
// popup login dialog, then update login status
|
||||
await loginWithCookieAssistant()
|
||||
let app
|
||||
try {
|
||||
app = (await import('electron')).app
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
// popup login dialog, then update login status
|
||||
try {
|
||||
await app?.dock?.show()
|
||||
await loginWithCookieAssistant()
|
||||
} finally {
|
||||
await app?.dock?.hide()
|
||||
}
|
||||
} catch (e) {
|
||||
if (e?.message === 'USER_CANCELLED_LOGIN') {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'rejected'
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface BrowserInfo {
|
||||
executablePath: string
|
||||
}
|
||||
|
||||
const CONFIG_VSERION = 2
|
||||
const CONFIG_VERSION = 2
|
||||
|
||||
const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
|
||||
export const lastUsedBrowserRecordFilePath = path.join(
|
||||
@@ -35,7 +35,7 @@ export const getLastUsedAndAvailableBrowser = async (): Promise<BrowserInfo | nu
|
||||
!path ||
|
||||
!fs.existsSync(path) ||
|
||||
!Number(configVersion) ||
|
||||
Number(configVersion) < CONFIG_VSERION
|
||||
Number(configVersion) < CONFIG_VERSION
|
||||
) {
|
||||
await removeLastUsedAndAvailableBrowserPath()
|
||||
return null
|
||||
@@ -57,7 +57,7 @@ export const saveLastUsedAndAvailableBrowserInfo = async (browserInfo: BrowserIn
|
||||
}
|
||||
await fsPromise.writeFile(
|
||||
lastUsedBrowserRecordFilePath,
|
||||
[browserInfo.executablePath, browserInfo.browser, CONFIG_VSERION].join('\n')
|
||||
[browserInfo.executablePath, browserInfo.browser, CONFIG_VERSION].join('\n')
|
||||
)
|
||||
} catch {
|
||||
console.warn('lastUsedBrowserRecordFile write error')
|
||||
|
||||
@@ -83,14 +83,14 @@ export const checkAndDownloadPuppeteerExecutable = async (
|
||||
})
|
||||
).find((it) => it.buildId === EXPECT_CHROMIUM_BUILD_ID)!
|
||||
}
|
||||
await saveLastUsedAndAvailableBrowserInfo({
|
||||
executablePath: installedBrowser.executablePath,
|
||||
browser:
|
||||
installedBrowser.browser[0].toUpperCase() +
|
||||
installedBrowser.browser.slice(1) +
|
||||
' ' +
|
||||
EXPECT_CHROMIUM_BUILD_ID
|
||||
})
|
||||
// await saveLastUsedAndAvailableBrowserInfo({
|
||||
// executablePath: installedBrowser.executablePath,
|
||||
// browser:
|
||||
// installedBrowser.browser[0].toUpperCase() +
|
||||
// installedBrowser.browser.slice(1) +
|
||||
// ' ' +
|
||||
// EXPECT_CHROMIUM_BUILD_ID
|
||||
// })
|
||||
|
||||
return installedBrowser
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ process.on('SIGTERM', () => {
|
||||
const rerunInterval = (() => {
|
||||
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
|
||||
if (isNaN(v)) {
|
||||
v = 3000
|
||||
v = 5000
|
||||
}
|
||||
|
||||
return v
|
||||
@@ -66,8 +66,9 @@ const runAutoChat = async () => {
|
||||
})
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'rejected'
|
||||
@@ -80,8 +81,9 @@ const runAutoChat = async () => {
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'fulfilled'
|
||||
@@ -183,8 +185,9 @@ export const waitForProcessHandShakeAndRunAutoChat = async () => {
|
||||
)
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'worker-launch',
|
||||
status: 'fulfilled'
|
||||
|
||||
@@ -49,64 +49,62 @@ const attachRequestsListener = async (target: Target) => {
|
||||
return
|
||||
}
|
||||
|
||||
function handleJobDetailPage({ encryptJobId } = { encryptJobId: null }) {
|
||||
Promise.resolve()
|
||||
.then(async () => {
|
||||
if (!encryptJobId) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
({ encryptJobId }) => {
|
||||
return (
|
||||
location.href.startsWith(`https://www.zhipin.com/job_detail/${encryptJobId}`) &&
|
||||
document.readyState === 'complete'
|
||||
)
|
||||
},
|
||||
undefined,
|
||||
{ encryptJobId }
|
||||
// FIXME: might not work
|
||||
async function handleJobDetailPage({ encryptJobId } = { encryptJobId: null }) {
|
||||
if (!encryptJobId) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
({ encryptJobId }) => {
|
||||
return (
|
||||
location.href.startsWith(`https://www.zhipin.com/job_detail/${encryptJobId}`) &&
|
||||
(!!document.querySelector('#main .job-banner') ||
|
||||
!!document.documentElement.innerText?.includes(`您访问的页面不存在`))
|
||||
)
|
||||
const htmlContent = await page.content()
|
||||
if (htmlContent) {
|
||||
const $ = cheerio.load(htmlContent)
|
||||
const [jobBannerEl] = $('#main .job-banner') ?? []
|
||||
if (!jobBannerEl) {
|
||||
console.log(`access might be blocked`)
|
||||
if (
|
||||
htmlContent.includes(`您访问的页面不存在`) ||
|
||||
location.href === `https://www.zhipin.com/`
|
||||
) {
|
||||
await saveJobHireStatusRecord(await dbInitPromise, {
|
||||
encryptJobId,
|
||||
hireStatus: JobHireStatus.DELETED,
|
||||
lastSeenDate: new Date()
|
||||
})
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
{ encryptJobId }
|
||||
)
|
||||
const htmlContent = await page.content()
|
||||
if (htmlContent) {
|
||||
const $ = cheerio.load(htmlContent)
|
||||
const [jobBannerEl] = $('#main .job-banner') ?? []
|
||||
if (!jobBannerEl) {
|
||||
console.log(`access might be blocked`)
|
||||
if (
|
||||
htmlContent.includes(`您访问的页面不存在`) ||
|
||||
location.href === `https://www.zhipin.com/`
|
||||
) {
|
||||
await saveJobHireStatusRecord(await dbInitPromise, {
|
||||
encryptJobId,
|
||||
hireStatus: JobHireStatus.DELETED,
|
||||
lastSeenDate: new Date()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const [jobStatusTextEl] = $('#main .job-banner .job-status') ?? []
|
||||
if (jobStatusTextEl) {
|
||||
const jobStatusText = $(jobStatusTextEl).text()?.trim() ?? ''
|
||||
if ([`职位已关闭`].includes(jobStatusText)) {
|
||||
await saveJobHireStatusRecord(await dbInitPromise, {
|
||||
encryptJobId,
|
||||
hireStatus: JobHireStatus.CLOSED,
|
||||
lastSeenDate: new Date()
|
||||
})
|
||||
} else {
|
||||
const [jobStatusTextEl] = $('#main .job-banner .job-status') ?? []
|
||||
if (jobStatusTextEl) {
|
||||
const jobStatusText = $(jobStatusTextEl).text()?.trim() ?? ''
|
||||
if ([`职位已关闭`].includes(jobStatusText)) {
|
||||
await saveJobHireStatusRecord(await dbInitPromise, {
|
||||
encryptJobId,
|
||||
hireStatus: JobHireStatus.CLOSED,
|
||||
lastSeenDate: new Date()
|
||||
})
|
||||
} else {
|
||||
await saveJobHireStatusRecord(await dbInitPromise, {
|
||||
encryptJobId,
|
||||
hireStatus: JobHireStatus.HIRING,
|
||||
lastSeenDate: new Date()
|
||||
})
|
||||
}
|
||||
}
|
||||
await saveJobHireStatusRecord(await dbInitPromise, {
|
||||
encryptJobId,
|
||||
hireStatus: JobHireStatus.HIRING,
|
||||
lastSeenDate: new Date()
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
})
|
||||
.catch(() => void 0)
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if (page.url().match(/^https:\/\/www.zhipin.com\/job_detail\/(.+)\.html/)) {
|
||||
@@ -200,7 +198,7 @@ const attachRequestsListener = async (target: Target) => {
|
||||
)
|
||||
const requestBody = new URLSearchParams(response.request().postData())
|
||||
|
||||
const securityIdInRequest = requestBody.get("securityId")
|
||||
const securityIdInRequest = requestBody.get('securityId')
|
||||
const currentJobSecurityId = currentJobData?.securityId
|
||||
|
||||
if (securityIdInRequest !== currentJobSecurityId) {
|
||||
@@ -376,6 +374,9 @@ export async function launchBossSite() {
|
||||
})
|
||||
let [page] = await browser.pages()
|
||||
for (let i = 0; i < bossCookies.length; i++) {
|
||||
if (Object.hasOwn(bossCookies[i], 'sameSite')) {
|
||||
bossCookies[i].sameSite = 'unspecified'
|
||||
}
|
||||
await page.setCookie(bossCookies[i])
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ import { ipcMain, shell, app, dialog, BrowserWindow } from 'electron'
|
||||
import path from 'path'
|
||||
import * as childProcess from 'node:child_process'
|
||||
import {
|
||||
ensureConfigFileExist,
|
||||
configFileNameList,
|
||||
readConfigFile,
|
||||
writeConfigFile,
|
||||
readStorageFile,
|
||||
@@ -34,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 {
|
||||
@@ -58,23 +56,10 @@ import {
|
||||
waitForUserApproveAgreement
|
||||
} from '../../../features/first-launch-notice-window'
|
||||
import { getLastUsedAndAvailableBrowser } from '../../DOWNLOAD_DEPENDENCIES/utils/browser-history'
|
||||
import { waitForCommonJobConditionDone } from '../../../features/common-job-condition'
|
||||
import { ensureConfigFileExist } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||
|
||||
export default function initIpc() {
|
||||
ipcMain.handle('fetch-config-file-content', async () => {
|
||||
const configFileContentList = configFileNameList.map((fileName) => {
|
||||
return readConfigFile(fileName)
|
||||
})
|
||||
const result = {
|
||||
config: {}
|
||||
}
|
||||
|
||||
configFileNameList.forEach((fileName, index) => {
|
||||
result.config[fileName] = configFileContentList[index]
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
ipcMain.handle('save-config-file-from-ui', async (ev, payload) => {
|
||||
payload = JSON.parse(payload)
|
||||
ensureConfigFileExist()
|
||||
@@ -187,6 +172,9 @@ export default function initIpc() {
|
||||
if (hasOwn(payload, 'blockCompanyNameRegMatchStrategy')) {
|
||||
bossConfig.blockCompanyNameRegMatchStrategy = payload.blockCompanyNameRegMatchStrategy
|
||||
}
|
||||
if (hasOwn(payload, 'fieldsForUseCommonConfig')) {
|
||||
bossConfig.fieldsForUseCommonConfig = payload.fieldsForUseCommonConfig
|
||||
}
|
||||
|
||||
promiseArr.push(writeConfigFile('boss.json', bossConfig))
|
||||
|
||||
@@ -373,7 +361,9 @@ export default function initIpc() {
|
||||
}
|
||||
subProcessOfOpenBossSite = childProcess.spawn(
|
||||
process.argv[0],
|
||||
[process.argv[1], `--mode=launchBossSite`],
|
||||
process.env.NODE_ENV === 'development'
|
||||
? [process.argv[1], `--mode=launchBossSite`]
|
||||
: [`--mode=launchBossSite`],
|
||||
{
|
||||
env: subProcessEnv,
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe']
|
||||
@@ -476,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]
|
||||
@@ -496,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')
|
||||
@@ -605,6 +597,12 @@ export default function initIpc() {
|
||||
}
|
||||
}
|
||||
})
|
||||
ipcMain.handle('common-job-condition-config', async () => {
|
||||
await waitForCommonJobConditionDone()
|
||||
mainWindow?.webContents.send('common-job-condition-config-updated', {
|
||||
config: await readConfigFile('common-job-condition-config.json')
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('exit-app-immediately', () => {
|
||||
app.exit(0)
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { connectToDaemon } from './connect-to-daemon'
|
||||
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
export async function ensureIpcPipeName({ isReset } = {}) {
|
||||
if (isReset) {
|
||||
await writeStorageFile('ipc-pipe-name', '', { isJson: false })
|
||||
@@ -29,7 +28,9 @@ export async function launchDaemon() {
|
||||
// 添加参数使守护进程在后台运行,不显示 UI
|
||||
daemonProcess = spawn(
|
||||
process.argv[0],
|
||||
isUiDev ? [process.argv[1], `--mode=launchDaemon`] : [`--mode=launchDaemon`],
|
||||
process.env.NODE_ENV === 'development'
|
||||
? [process.argv[1], `--mode=launchDaemon`]
|
||||
: [`--mode=launchDaemon`],
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'pipe'],
|
||||
detached: true,
|
||||
|
||||
@@ -30,6 +30,9 @@ export async function launchBoss(browser: Browser) {
|
||||
const bossLocalStorage = readStorageFile('boss-local-storage.json')
|
||||
//set cookies
|
||||
for (let i = 0; i < bossCookies.length; i++) {
|
||||
if (Object.hasOwn(bossCookies[i], 'sameSite')) {
|
||||
bossCookies[i].sameSite = 'unspecified'
|
||||
}
|
||||
await page.setCookie(bossCookies[i])
|
||||
}
|
||||
await setDomainLocalStorage(browser, localStoragePageUrl, bossLocalStorage)
|
||||
|
||||
@@ -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信号,正在退出')
|
||||
@@ -58,12 +60,21 @@ const rechatLlmFallback =
|
||||
readConfigFile('boss.json').autoReminder?.rechatLlmFallback ??
|
||||
RECHAT_LLM_FALLBACK.SEND_LOOK_FORWARD_EMOTION
|
||||
|
||||
const expectJobTypeRegExpStr = readConfigFile('boss.json').expectJobTypeRegExpStr
|
||||
const fieldsForUseCommonConfig = readConfigFile('boss.json').fieldsForUseCommonConfig ?? {}
|
||||
const commonJobConditionConfig = readConfigFile('common-job-condition-config.json') ?? {}
|
||||
const expectJobTypeRegExpStr =
|
||||
(!fieldsForUseCommonConfig.jobDetail ? readConfigFile('boss.json') : commonJobConditionConfig)
|
||||
?.expectJobTypeRegExpStr ?? ''
|
||||
const onlyRemindBossWithExpectJobType =
|
||||
readConfigFile('boss.json').autoReminder?.onlyRemindBossWithExpectJobType ??
|
||||
!!expectJobTypeRegExpStr
|
||||
|
||||
const blockCompanyNameRegExpStr = readConfigFile('boss.json').blockCompanyNameRegExpStr ?? ''
|
||||
const blockCompanyNameRegExpStr =
|
||||
(!fieldsForUseCommonConfig.blockCompanyNameRegExpStr
|
||||
? readConfigFile('boss.json')
|
||||
: commonJobConditionConfig
|
||||
)?.blockCompanyNameRegExpStr ?? ''
|
||||
|
||||
const blockCompanyNameRegExp = (() => {
|
||||
if (!blockCompanyNameRegExpStr?.trim()) {
|
||||
return null
|
||||
@@ -78,6 +89,23 @@ 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: {
|
||||
@@ -277,7 +305,17 @@ const mainLoop = async () => {
|
||||
let cookieCheckResult = checkCookieListFormat(bossCookies)
|
||||
while (!cookieCheckResult) {
|
||||
try {
|
||||
await loginWithCookieAssistant()
|
||||
browser && (await browser.close())
|
||||
} catch (err) {
|
||||
console.log(`close browser failed`, err)
|
||||
}
|
||||
try {
|
||||
try {
|
||||
await app.dock?.show()
|
||||
await loginWithCookieAssistant()
|
||||
} finally {
|
||||
await app.dock?.hide()
|
||||
}
|
||||
bossCookies = readStorageFile('boss-cookies.json')
|
||||
cookieCheckResult = checkCookieListFormat(bossCookies)
|
||||
} catch (err) {
|
||||
@@ -288,8 +326,9 @@ const mainLoop = async () => {
|
||||
})
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'basic-cookie-check',
|
||||
status: 'rejected'
|
||||
@@ -302,8 +341,9 @@ const mainLoop = async () => {
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'basic-cookie-check',
|
||||
status: 'fulfilled'
|
||||
@@ -330,8 +370,17 @@ const mainLoop = async () => {
|
||||
if (currentPageUrl.startsWith('https://www.zhipin.com/web/user/')) {
|
||||
writeStorageFile('boss-cookies.json', [])
|
||||
try {
|
||||
// popup login dialog, then update login status
|
||||
await loginWithCookieAssistant()
|
||||
browser && (await browser.close())
|
||||
} catch (err) {
|
||||
console.log(`close browser failed`, err)
|
||||
}
|
||||
try {
|
||||
try {
|
||||
await app.dock?.show()
|
||||
await loginWithCookieAssistant()
|
||||
} finally {
|
||||
await app.dock?.hide()
|
||||
}
|
||||
} catch (err) {
|
||||
await dialog.showMessageBox({
|
||||
type: `error`,
|
||||
@@ -340,8 +389,9 @@ const mainLoop = async () => {
|
||||
})
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'rejected'
|
||||
@@ -359,8 +409,9 @@ const mainLoop = async () => {
|
||||
) {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'rejected'
|
||||
@@ -392,8 +443,9 @@ const mainLoop = async () => {
|
||||
await storeStorage(pageMapByName.boss)
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'rejected'
|
||||
@@ -406,8 +458,9 @@ const mainLoop = async () => {
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'fulfilled'
|
||||
@@ -573,30 +626,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
|
||||
@@ -610,7 +683,7 @@ const mainLoop = async () => {
|
||||
const rerunInterval = (() => {
|
||||
let v = Number(process.env.MAIN_BOSSGEEKGO_RERUN_INTERVAL)
|
||||
if (isNaN(v)) {
|
||||
v = 3000
|
||||
v = 5000
|
||||
}
|
||||
|
||||
return v
|
||||
@@ -635,8 +708,9 @@ export async function runEntry() {
|
||||
)
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'worker-launch',
|
||||
status: 'fulfilled'
|
||||
@@ -661,8 +735,9 @@ export async function runEntry() {
|
||||
})
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'rejected'
|
||||
@@ -674,8 +749,9 @@ export async function runEntry() {
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: process.env.GEEKGEEKRUND_WORKER_ID,
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
type: 'prerequisite-step-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'fulfilled'
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import overrideConsole from './utils/overrideConsole'
|
||||
import minimist from 'minimist'
|
||||
import { runCommon } from './features/run-common'
|
||||
import { launchDaemon } from './flow/OPEN_SETTING_WINDOW/launch-daemon'
|
||||
import { app } from 'electron'
|
||||
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
const enableLogToFile = process.env.GEEKGEEKRUN_ENABLE_LOG_TO_FILE === String(1)
|
||||
if (isUiDev || enableLogToFile) {
|
||||
overrideConsole()
|
||||
}
|
||||
console.log('NODE_ENV:', process.env.NODE_ENV)
|
||||
|
||||
// 捕获未处理的 EPIPE 错误
|
||||
process.on('uncaughtException', (err) => {
|
||||
if (err?.code === 'EPIPE' || err?.code === 'ERR_STREAM_DESTROYED') {
|
||||
@@ -11,9 +19,9 @@ process.on('uncaughtException', (err) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
const isUiDev = process.env.NODE_ENV === 'development'
|
||||
console.log('argv:', process.argv)
|
||||
const commandlineArgs = minimist(isUiDev ? process.argv.slice(2) : process.argv.slice(1))
|
||||
console.log(commandlineArgs)
|
||||
console.log('parsed commandline args:', commandlineArgs)
|
||||
|
||||
const runMode = commandlineArgs['mode']
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import os from 'node:os'
|
||||
import fs from 'node:fs'
|
||||
import {
|
||||
ensureStorageFileExist,
|
||||
readStorageFile,
|
||||
writeStorageFile
|
||||
writeStorageFile,
|
||||
configFileNameList,
|
||||
readConfigFile,
|
||||
readStorageFile
|
||||
} from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||
|
||||
export default function initPublicIpc() {
|
||||
@@ -110,4 +112,19 @@ export default function initPublicIpc() {
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
ipcMain.handle('fetch-config-file-content', async () => {
|
||||
const configFileContentList = configFileNameList.map((fileName) => {
|
||||
return readConfigFile(fileName)
|
||||
})
|
||||
const result = {
|
||||
config: {}
|
||||
}
|
||||
|
||||
configFileNameList.forEach((fileName, index) => {
|
||||
result.config[fileName] = configFileContentList[index]
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
75
packages/ui/src/main/utils/overrideConsole.ts
Normal file
75
packages/ui/src/main/utils/overrideConsole.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import fs from 'node:fs'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export default function overrideConsole() {
|
||||
const originConsoleLog = console.log.bind(console)
|
||||
const originConsoleWarn = console.warn.bind(console)
|
||||
const originConsoleError = console.error.bind(console)
|
||||
|
||||
const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
|
||||
const logDirPath = path.join(runtimeFolderPath, 'log')
|
||||
if (!fs.existsSync(logDirPath)) {
|
||||
fs.mkdirSync(logDirPath, { recursive: true })
|
||||
}
|
||||
|
||||
const logFileStream = fs.createWriteStream(path.join(logDirPath, `log.log`), {
|
||||
flags: 'a' // 追加模式
|
||||
})
|
||||
const warnFileStream = fs.createWriteStream(path.join(logDirPath, `warn.log`), {
|
||||
flags: 'a' // 追加模式
|
||||
})
|
||||
const errorFileStream = fs.createWriteStream(path.join(logDirPath, `error.log`), {
|
||||
flags: 'a' // 追加模式
|
||||
})
|
||||
|
||||
console.log = (...args: any[]) => {
|
||||
const lineHead = `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} [log][PID=${process.pid}]`
|
||||
originConsoleLog(lineHead, ...args)
|
||||
logFileStream.write(
|
||||
[
|
||||
lineHead,
|
||||
args.map((arg) => {
|
||||
try {
|
||||
return JSON.stringify(arg)
|
||||
} catch (err) {
|
||||
return `[[${JSON.stringify(err?.toString())}]]`
|
||||
}
|
||||
})
|
||||
].join(' ') + '\n'
|
||||
)
|
||||
}
|
||||
console.warn = (...args: any[]) => {
|
||||
const lineHead = `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} [warn][PID=${process.pid}]`
|
||||
originConsoleWarn(lineHead, ...args)
|
||||
warnFileStream.write(
|
||||
[
|
||||
lineHead,
|
||||
args.map((arg) => {
|
||||
try {
|
||||
return JSON.stringify(arg)
|
||||
} catch (err) {
|
||||
return `[[${JSON.stringify(err?.toString())}]]`
|
||||
}
|
||||
})
|
||||
].join(' ') + '\n'
|
||||
)
|
||||
}
|
||||
console.error = (...args: any[]) => {
|
||||
const lineHead = `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} [warn][PID=${process.pid}]`
|
||||
originConsoleError(lineHead, ...args)
|
||||
errorFileStream.write(
|
||||
[
|
||||
lineHead,
|
||||
args.map((arg) => {
|
||||
try {
|
||||
return JSON.stringify(arg)
|
||||
} catch (err) {
|
||||
return `[[${JSON.stringify(err?.toString())}]]`
|
||||
}
|
||||
})
|
||||
].join(' ') + '\n'
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export function createBrowserAssistantWindow(
|
||||
minWidth: 800,
|
||||
height: 400,
|
||||
resizable: true,
|
||||
frame: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
@@ -39,7 +40,9 @@ export function createBrowserAssistantWindow(
|
||||
},
|
||||
...opt
|
||||
})
|
||||
|
||||
browserAssistantWindow!.setAlwaysOnTop(true, 'normal')
|
||||
browserAssistantWindow!.focus()
|
||||
browserAssistantWindow!.setAlwaysOnTop(false)
|
||||
browserAssistantWindow.on('ready-to-show', () => {
|
||||
browserAssistantWindow!.show()
|
||||
})
|
||||
|
||||
@@ -62,7 +62,9 @@ export function createBrowserDownloadProgressWindow(
|
||||
}
|
||||
subProcessOfCheckAndDownloadDependencies = childProcess.spawn(
|
||||
process.argv[0],
|
||||
[process.argv[1], `--mode=downloadDependenciesForInit`],
|
||||
process.env.NODE_ENV === 'development'
|
||||
? [process.argv[1], `--mode=downloadDependenciesForInit`]
|
||||
: [`--mode=downloadDependenciesForInit`],
|
||||
{
|
||||
stdio: [null, null, null, 'pipe', 'ipc']
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import path from 'path'
|
||||
import { writeConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
|
||||
|
||||
export let commonJobConditionConfigWindow: BrowserWindow | null = null
|
||||
export function createCommonJobConditionConfigWindow(
|
||||
opt?: Electron.BrowserWindowConstructorOptions
|
||||
): BrowserWindow {
|
||||
// Create the browser window.
|
||||
if (commonJobConditionConfigWindow) {
|
||||
commonJobConditionConfigWindow!.show()
|
||||
}
|
||||
commonJobConditionConfigWindow = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 768,
|
||||
resizable: false,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
},
|
||||
...opt
|
||||
})
|
||||
|
||||
commonJobConditionConfigWindow.on('ready-to-show', () => {
|
||||
commonJobConditionConfigWindow!.show()
|
||||
})
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) {
|
||||
commonJobConditionConfigWindow.loadURL(
|
||||
process.env['ELECTRON_RENDERER_URL'] + '#/commonJobConditionConfig'
|
||||
)
|
||||
} else {
|
||||
commonJobConditionConfigWindow.loadURL(
|
||||
'file://' + path.join(__dirname, '../renderer/index.html') + '#/commonJobConditionConfig'
|
||||
)
|
||||
}
|
||||
|
||||
commonJobConditionConfigWindow!.once('closed', () => {
|
||||
commonJobConditionConfigWindow = null
|
||||
})
|
||||
|
||||
ipcMain.handle('save-common-job-condition-config', async (_ev, payload) => {
|
||||
await writeConfigFile('common-job-condition-config.json', payload)
|
||||
commonJobConditionConfigWindow!.close()
|
||||
})
|
||||
commonJobConditionConfigWindow!.once('closed', () => {
|
||||
ipcMain.removeHandler('save-common-job-condition-config')
|
||||
})
|
||||
|
||||
return commonJobConditionConfigWindow!
|
||||
}
|
||||
@@ -20,13 +20,16 @@ export function createCookieAssistantWindow(
|
||||
resizable: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
},
|
||||
...opt
|
||||
})
|
||||
|
||||
cookieAssistantWindow!.setAlwaysOnTop(true, 'normal')
|
||||
cookieAssistantWindow!.focus()
|
||||
cookieAssistantWindow!.setAlwaysOnTop(false)
|
||||
cookieAssistantWindow.on('ready-to-show', () => {
|
||||
cookieAssistantWindow!.show()
|
||||
})
|
||||
@@ -83,7 +86,9 @@ export function createCookieAssistantWindow(
|
||||
}
|
||||
subProcessOfBossZhipinLoginPageWithPreloadExtension = childProcess.spawn(
|
||||
process.argv[0],
|
||||
[process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`],
|
||||
process.env.NODE_ENV === 'development'
|
||||
? [process.argv[1], `--mode=launchBossZhipinLoginPageWithPreloadExtension`]
|
||||
: [`--mode=launchBossZhipinLoginPageWithPreloadExtension`],
|
||||
{
|
||||
env: subProcessEnv,
|
||||
stdio: [null, null, null, 'pipe', 'ipc']
|
||||
|
||||
@@ -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,52 +1,68 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
modal-class="runing-overlay__modal"
|
||||
modal-class="running-overlay__modal"
|
||||
:model-value="isDialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
width="400px"
|
||||
@closed="fillEmptySteps"
|
||||
>
|
||||
<!-- v-if="stepsForRender.some(it => ['todo', 'pending', 'rejected'].includes(it.status))" -->
|
||||
<div>
|
||||
<ul m0 pl0>
|
||||
<li
|
||||
v-for="(item, index) in stepsForRender"
|
||||
:key="index"
|
||||
list-style-none
|
||||
flex
|
||||
justify-start
|
||||
pt4px
|
||||
pb4px
|
||||
>
|
||||
<div>
|
||||
<span v-if="item.status === 'todo'">🕐</span>
|
||||
<span v-if="item.status === 'pending'">👉</span>
|
||||
<span v-if="item.status === 'fulfilled'">✅</span>
|
||||
<span v-if="item.status === 'rejected'">⛔️</span>
|
||||
</div>
|
||||
<span ml8px>{{ item.describe }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div flex justify-between items-center w-full>
|
||||
<div>
|
||||
{{ runningStatusTextMapByCode[currentRunningStatus] }}
|
||||
<div flex flex-col flex-items-center>
|
||||
<div class="dialog-header" w-full>
|
||||
<div
|
||||
h160px
|
||||
w-full
|
||||
:style="{
|
||||
backgroundImage: 'linear-gradient(#666, #666)'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="op-buttons" :current-running-status="currentRunningStatus" />
|
||||
<div class="dialog-main" w-full mt--20px>
|
||||
<!-- v-if="stepsForRender.some(it => ['todo', 'pending', 'rejected'].includes(it.status))" -->
|
||||
<div>
|
||||
<ul m0 pl0>
|
||||
<li
|
||||
v-for="(item, index) in stepsForRender"
|
||||
:key="index"
|
||||
list-style-none
|
||||
flex
|
||||
justify-start
|
||||
pt4px
|
||||
pb4px
|
||||
>
|
||||
<div>
|
||||
<span v-if="item.status === 'todo'">🕐</span>
|
||||
<span v-if="item.status === 'pending'">👉</span>
|
||||
<span v-if="item.status === 'fulfilled'">✅</span>
|
||||
<span v-if="item.status === 'rejected'">⛔️</span>
|
||||
</div>
|
||||
<span ml8px>{{ item.describe }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div flex justify-between items-center w-full>
|
||||
<div>
|
||||
{{ runningStatusTextMapByCode[currentRunningStatus] }}
|
||||
</div>
|
||||
<div>
|
||||
<slot name="op-buttons" :current-running-status="currentRunningStatus" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// import { useTaskManagerStore } from '@renderer/store'
|
||||
import { useTaskManagerStore } from '@renderer/store'
|
||||
import { getAutoStartChatSteps } from '../../../../common/prerequisite-step-by-step-check'
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
import {
|
||||
AUTO_CHAT_ERROR_EXIT_CODE,
|
||||
RUNNING_STATUS_ENUM
|
||||
} from '../../../../common/enums/auto-start-chat'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
const props = defineProps({
|
||||
workerId: {
|
||||
type: String
|
||||
@@ -55,12 +71,12 @@ const props = defineProps({
|
||||
type: Number
|
||||
}
|
||||
})
|
||||
// const taskManagerStore = useTaskManagerStore()
|
||||
// const runingTaskInfo = computed(() => {
|
||||
// return taskManagerStore.runningTasks?.find((it) => {
|
||||
// return it.workerId === props.workerId
|
||||
// })
|
||||
// })
|
||||
const taskManagerStore = useTaskManagerStore()
|
||||
const runningTaskInfo = computed(() => {
|
||||
return taskManagerStore.runningTasks?.find((it) => {
|
||||
return it.workerId === props.workerId
|
||||
})
|
||||
})
|
||||
const steps = ref([])
|
||||
const stepsForRender = computed(() => {
|
||||
const clonedSteps = JSON.parse(JSON.stringify(steps.value))
|
||||
@@ -85,16 +101,61 @@ function fillEmptySteps() {
|
||||
steps.value = arr
|
||||
currentRunningStatus.value = RUNNING_STATUS_ENUM.RUNNING
|
||||
}
|
||||
watch(() => props.runRecordId, fillEmptySteps, {
|
||||
immediate: true
|
||||
})
|
||||
|
||||
function applyRuntimeStepStatus() {
|
||||
const task = runningTaskInfo.value
|
||||
if (!task?.runtimeStorage?.stepStatusMapByStepId) {
|
||||
return
|
||||
}
|
||||
const stepMap = task.runtimeStorage.stepStatusMapByStepId
|
||||
steps.value = getAutoStartChatSteps().map((step) => {
|
||||
const saved = stepMap[step.id]
|
||||
if (!saved || !saved.step) {
|
||||
return step
|
||||
}
|
||||
return {
|
||||
...step,
|
||||
status: saved.step.status
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.runRecordId,
|
||||
() => {
|
||||
fillEmptySteps()
|
||||
applyRuntimeStepStatus()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => taskManagerStore.runningTasks,
|
||||
() => {
|
||||
applyRuntimeStepStatus()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
watch(
|
||||
() => stepsForRender.value,
|
||||
(v) => {
|
||||
const rejectedItems = v?.filter((it) => it.status === 'rejected')
|
||||
if (!rejectedItems.length) {
|
||||
return
|
||||
}
|
||||
gtagRenderer('running_overlay_rejected', {
|
||||
stepId: rejectedItems.map((it) => it.id).join(','),
|
||||
workerId: props.workerId
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const { ipcRenderer } = electron
|
||||
function messageHandler(ev, { data }) {
|
||||
if (
|
||||
data.type !== 'prerequisite-step-by-step-checkstep-by-step-check' ||
|
||||
data.runRecordId !== props.runRecordId
|
||||
) {
|
||||
if (data.type !== 'prerequisite-step-by-step-check' || data.runRecordId !== props.runRecordId) {
|
||||
return
|
||||
}
|
||||
const { id: stepId, status: stepStatus } = data.step
|
||||
@@ -114,6 +175,16 @@ const show = () => {
|
||||
const hide = () => {
|
||||
isDialogVisible.value = false
|
||||
}
|
||||
watch(
|
||||
() => isDialogVisible.value,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
gtagRenderer('running_overlay_shown')
|
||||
} else {
|
||||
gtagRenderer('running_overlay_hidden')
|
||||
}
|
||||
}
|
||||
)
|
||||
defineExpose({
|
||||
show,
|
||||
hide
|
||||
@@ -128,14 +199,22 @@ ipcRenderer.on('worker-exited', (ev, payload) => {
|
||||
}
|
||||
if (code !== AUTO_CHAT_ERROR_EXIT_CODE.NORMAL) {
|
||||
currentRunningStatus.value = RUNNING_STATUS_ENUM.ERROR_EXITED
|
||||
gtagRenderer('running_overlay_error_exited', {
|
||||
exitCode: code,
|
||||
workerId: props.workerId
|
||||
})
|
||||
} else {
|
||||
currentRunningStatus.value = RUNNING_STATUS_ENUM.NORMAL_EXITED
|
||||
gtagRenderer('running_overlay_normal_exited', {
|
||||
exitCode: code,
|
||||
workerId: props.workerId
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-overlay.runing-overlay__modal {
|
||||
.el-overlay.running-overlay__modal {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -159,7 +238,26 @@ ipcRenderer.on('worker-exited', (ev, payload) => {
|
||||
display: none;
|
||||
}
|
||||
.el-dialog__body {
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
}
|
||||
.dialog-header {
|
||||
display: none;
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// border-radius: 20px 20px 0 0;
|
||||
// overflow: hidden;
|
||||
}
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
.dialog-main {
|
||||
box-sizing: border-box;
|
||||
background: var(--el-dialog-bg-color);
|
||||
box-shadow: var(--el-dialog-box-shadow);
|
||||
padding: var(--el-dialog-padding-primary);
|
||||
//border-radius: 0 0 20px 20px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
<div flex-1 of-auto font-size-14px line-height-1.5em>
|
||||
<div mt10px ml-auto mr-auto class="w-90%">
|
||||
<div>常见问题</div>
|
||||
<div class="faq-main">
|
||||
<details class="faq-item">
|
||||
<div ref="faqMainRef" class="faq-main">
|
||||
<details class="faq-item" data-faq-id="cannot-auto-find-executable">
|
||||
<summary>“自动检测”点击后提示“未检测到可用浏览器的可执行文件”?</summary>
|
||||
<div class="faq-answer">
|
||||
请尝试如下方案之一来处理:
|
||||
@@ -72,7 +72,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<details class="faq-item" data-faq-id="manual-select-browser-prerequisite">
|
||||
<summary>
|
||||
如果要手动选择浏览器,可选择的浏览器有哪些?对于浏览器有什么要求?
|
||||
</summary>
|
||||
@@ -145,7 +145,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<details
|
||||
class="faq-item"
|
||||
data-faq-id="what-will-happen-when-select-unsupported-browser"
|
||||
>
|
||||
<summary>如果我选择了一个不支持的浏览器,会发生什么?</summary>
|
||||
<div class="faq-answer">
|
||||
<div>可能会发生的情况:</div>
|
||||
@@ -156,12 +159,12 @@
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<details class="faq-item" data-faq-id="what-will-happen-when-select-other-file">
|
||||
<summary>如果我选择了一个不是浏览器的可执行文件,会发生什么?</summary>
|
||||
<div class="faq-answer">
|
||||
<div>可能会发生的情况:</div>
|
||||
<ul>
|
||||
<li>可执行文件将会启动,但会开启一个空白页面,之后可执行文件不会做任何事</li>
|
||||
<li>可执行文件将会启动,但不会受到本程序控制</li>
|
||||
<li>可执行文件闪退并报错,闪退后会自动重启,继续报错</li>
|
||||
<li>可执行文件会不断运行多个实例</li>
|
||||
</ul>
|
||||
@@ -199,7 +202,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
||||
@@ -212,7 +215,7 @@ useRouter()
|
||||
|
||||
const gtagRenderer = (name, params?: object) => {
|
||||
return baseGtagRenderer(name, {
|
||||
scene: 'cookie-assistant',
|
||||
scene: 'browser-assistant',
|
||||
...params
|
||||
})
|
||||
}
|
||||
@@ -250,6 +253,7 @@ const rules = {
|
||||
|
||||
const isAutoDetectLoading = ref(false)
|
||||
async function autoDetectPuppeteerExecutable() {
|
||||
gtagRenderer('auto_detect_pptr_exe_clicked')
|
||||
isAutoDetectLoading.value = true
|
||||
await sleep(50)
|
||||
try {
|
||||
@@ -258,6 +262,7 @@ async function autoDetectPuppeteerExecutable() {
|
||||
noSave: true
|
||||
})
|
||||
if (!result) {
|
||||
gtagRenderer('auto_detect_pptr_exe_not_found')
|
||||
ElMessage({
|
||||
message: '未检测到可用浏览器的可执行文件',
|
||||
type: 'warning',
|
||||
@@ -265,6 +270,12 @@ async function autoDetectPuppeteerExecutable() {
|
||||
})
|
||||
return
|
||||
}
|
||||
gtagRenderer('auto_detect_pptr_exe_done', {
|
||||
isUseCached: !!(
|
||||
result.executablePath?.includes(`cache`) && result.executablePath?.includes(`.geekgeekrun`)
|
||||
),
|
||||
executableName: result.executablePath?.split(/\/|\\/).pop() ?? ''
|
||||
})
|
||||
formData.value.browserPath = result.executablePath
|
||||
ElMessage({
|
||||
message: '已找到可用浏览器,可执行文件路径已填入输入框',
|
||||
@@ -279,6 +290,7 @@ async function autoDetectPuppeteerExecutable() {
|
||||
}
|
||||
|
||||
async function chooseExecutableFile() {
|
||||
gtagRenderer('choose_pptr_exe_clicked')
|
||||
const chooseResult = await ipcRenderer.invoke('choose-file', {
|
||||
fileChooserConfig: {
|
||||
properties: ['openFile', 'treatPackageAsDirectory'],
|
||||
@@ -292,9 +304,13 @@ async function chooseExecutableFile() {
|
||||
}
|
||||
})
|
||||
if (chooseResult.canceled || !chooseResult.filePaths?.length) {
|
||||
gtagRenderer('choose_pptr_exe_cancelled')
|
||||
return
|
||||
}
|
||||
formData.value.browserPath = chooseResult.filePaths[0]
|
||||
gtagRenderer('choose_pptr_exe_done', {
|
||||
executableName: chooseResult.filePaths[0]?.split(/\/|\\/).pop() ?? ''
|
||||
})
|
||||
await nextTick()
|
||||
await formRef.value.validateField()
|
||||
}
|
||||
@@ -308,12 +324,28 @@ function handleCancel() {
|
||||
}
|
||||
const formRef = ref()
|
||||
async function handleSave() {
|
||||
await formRef.value.validate()
|
||||
await ipcRenderer.invoke('save-last-used-and-available-browser-info', {
|
||||
gtagRenderer('save_clicked', {
|
||||
executablePath: formData.value.browserPath,
|
||||
browser: ''
|
||||
executableName: formData.value.browserPath?.split(/\/|\\/).pop() ?? ''
|
||||
})
|
||||
await ipcRenderer.send('browser-config-saved')
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
await ipcRenderer.invoke('save-last-used-and-available-browser-info', {
|
||||
executablePath: formData.value.browserPath,
|
||||
browser: ''
|
||||
})
|
||||
await ipcRenderer.send('browser-config-saved')
|
||||
gtagRenderer('save_done', {
|
||||
executablePath: formData.value.browserPath,
|
||||
executableName: formData.value.browserPath?.split(/\/|\\/).pop() ?? ''
|
||||
})
|
||||
} catch (err) {
|
||||
gtagRenderer('save_validate_failed', {
|
||||
error: err?.message ?? '',
|
||||
executablePath: formData.value.browserPath,
|
||||
executableName: formData.value.browserPath?.split(/\/|\\/).pop() ?? ''
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleFeedbackClick = () => {
|
||||
gtagRenderer('goto_feedback_for_ba_clicked')
|
||||
@@ -321,18 +353,44 @@ const handleFeedbackClick = () => {
|
||||
}
|
||||
const handleClickLaunchBrowserDownloader = async () => {
|
||||
gtagRenderer('launch_browser_downloader_clicked')
|
||||
const downloadedBrowserPath = await electron.ipcRenderer.invoke(
|
||||
'download-browser-with-downloader'
|
||||
)
|
||||
if (downloadedBrowserPath) {
|
||||
formData.value.browserPath = downloadedBrowserPath
|
||||
ElMessage({
|
||||
message: '浏览器下载成功,可执行文件路径已填入输入框',
|
||||
type: 'success',
|
||||
grouping: true
|
||||
})
|
||||
let downloadedBrowserPath
|
||||
try {
|
||||
downloadedBrowserPath = await electron.ipcRenderer.invoke('download-browser-with-downloader')
|
||||
if (downloadedBrowserPath) {
|
||||
formData.value.browserPath = downloadedBrowserPath
|
||||
ElMessage({
|
||||
message: '浏览器下载成功,可执行文件路径已填入输入框',
|
||||
type: 'success',
|
||||
grouping: true
|
||||
})
|
||||
gtagRenderer('browser_downloader_done_with_path')
|
||||
} else {
|
||||
ElMessage({
|
||||
message:
|
||||
'浏览器下载成功,但未返回可执行文件路径。请点击自动检测,或手动选择~/.geekgeekrun/cache/chrome文件夹下的文件,或重新下载',
|
||||
type: 'success',
|
||||
grouping: true
|
||||
})
|
||||
gtagRenderer('browser_downloader_done_without_path')
|
||||
}
|
||||
} catch (err) {
|
||||
gtagRenderer('browser_downloader_cancelled')
|
||||
}
|
||||
}
|
||||
|
||||
const faqMainRef = ref()
|
||||
onMounted(() => {
|
||||
const faqItemEls = faqMainRef?.value?.querySelectorAll(`details`) ?? []
|
||||
for (const el of faqItemEls) {
|
||||
el.addEventListener('toggle', () => {
|
||||
const isOpen = el.open
|
||||
gtagRenderer('faq_item_toggled', {
|
||||
faqId: el.dataset.faqId,
|
||||
isOpen
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -18,11 +18,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onUnmounted, h } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
||||
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
|
||||
import FailMessage from './FailMessage.vue'
|
||||
import { EXPECT_CHROMIUM_BUILD_ID } from '../../../../common/constant'
|
||||
|
||||
const gtagRenderer = (name, params?: object) => {
|
||||
return baseGtagRenderer(name, {
|
||||
scene: 'browser-download-progress',
|
||||
...params
|
||||
})
|
||||
}
|
||||
|
||||
const browserDownloadPercentage = ref(0)
|
||||
const handleBrowserDownloadProgress = (ev, { downloadedBytes, totalBytes }) => {
|
||||
browserDownloadPercentage.value = (downloadedBytes / totalBytes) * 100
|
||||
@@ -62,6 +69,7 @@ const processTasks = async () => {
|
||||
try {
|
||||
await processDownloadBrowser()
|
||||
electron.ipcRenderer.send('browser-download-done', executablePath)
|
||||
gtagRenderer('download_deps_done')
|
||||
} catch (err) {
|
||||
gtagRenderer('encounter_error_when_download_deps')
|
||||
await ElMessageBox.confirm(h(FailMessage), {
|
||||
|
||||
@@ -0,0 +1,733 @@
|
||||
<template>
|
||||
<div class="common-job-condition-config" flex flex-col h-full>
|
||||
<div flex-1 of-auto>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formContent"
|
||||
:rules="formRules"
|
||||
inline-message
|
||||
w-800px
|
||||
pt-30px
|
||||
pb-30px
|
||||
ml-auto
|
||||
mr-auto
|
||||
>
|
||||
<div mb20px>公共职位筛选条件</div>
|
||||
<el-form-item prop="expectCompanies" mb0>
|
||||
<div
|
||||
font-size-14px
|
||||
flex
|
||||
mb6px
|
||||
:style="{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
width: '100%',
|
||||
lineHeight: '1.25em'
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
期望投递公司
|
||||
<br /><span font-size-12px
|
||||
><b color-orange>逗号分隔</b>,不区分大小写;输入框留空表示不筛选</span
|
||||
>
|
||||
</div>
|
||||
<el-dropdown @command="handleExpectCompanyTemplateClicked">
|
||||
<el-button size="small"
|
||||
>公司列表模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||
></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in expectCompanyTemplateList"
|
||||
:key="item.name"
|
||||
:command="item"
|
||||
>{{ item.name }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="formContent.expectCompanies"
|
||||
:autosize="{ minRows: 4 }"
|
||||
max-h-8lh
|
||||
type="textarea"
|
||||
placeholder="置空表示“不限公司,任意公司都可以投递”"
|
||||
@blur="
|
||||
formContent.expectCompanies = normalizeCommaSplittedStr(formContent.expectCompanies)
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="h-1px bg-#f0f0f0" mt16px mb8px />
|
||||
<div
|
||||
ref="blockCompanyNameRegExpSectionEl"
|
||||
font-size-14px
|
||||
flex
|
||||
:style="{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
width: '100%',
|
||||
lineHeight: '1.25em'
|
||||
}"
|
||||
>
|
||||
<div mb6px>
|
||||
不期望投递公司<b color-orange>正则</b><br /><span font-size-12px
|
||||
><b color-orange>正则表达式</b>,不区分大小写;输入框留空表示不筛选;<span
|
||||
color-orange
|
||||
>优先级高于上方“期望投递公司”</span
|
||||
><br />请<b color-red>小心验证</b
|
||||
>你编写的正则,填写太过于宽泛的正则(例如`.*`)将导致任何职位都不会开聊</span
|
||||
>
|
||||
</div>
|
||||
<el-dropdown @command="handleBlockCompanyNameRegExpTemplateClicked">
|
||||
<el-button size="small"
|
||||
>公司列表模板 <el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||
></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in blockCompanyNameRegExpTemplateList"
|
||||
:key="item.name"
|
||||
:command="item"
|
||||
>{{ item.name }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div
|
||||
class="block-company-filter-wrap"
|
||||
:style="{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '10px'
|
||||
}"
|
||||
>
|
||||
<el-form-item prop="blockCompanyNameRegExpStr" mb0 w-full>
|
||||
<el-input
|
||||
v-model="formContent.blockCompanyNameRegExpStr"
|
||||
:autosize="{ minRows: 4 }"
|
||||
max-h-8lh
|
||||
type="textarea"
|
||||
placeholder="置空表示“不限公司,任意公司都不会被标记为不合适”"
|
||||
@blur="
|
||||
formContent.blockCompanyNameRegExpStr =
|
||||
formContent.blockCompanyNameRegExpStr?.trim() ?? ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="h-1px bg-#f0f0f0" mt16px mb16px />
|
||||
<div mt16px>
|
||||
<div font-size-14px mb8px>工作地</div>
|
||||
<div
|
||||
:style="{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '10px'
|
||||
}"
|
||||
>
|
||||
<el-form-item prop="expectCityList" mb0>
|
||||
<div
|
||||
font-size-12px
|
||||
:style="{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
}"
|
||||
>
|
||||
<city-chooser v-model="formContent.expectCityList">
|
||||
<template #default="{ modelValue, showDialog, clearValue }">
|
||||
<div v-if="modelValue?.length">
|
||||
<div>当前已选择城市:</div>
|
||||
<div flex flex-wrap gap-10px>
|
||||
<el-tag v-for="it in modelValue" :key="it">
|
||||
{{ it }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>当前未选择任何期望城市,将不会按照城市进行筛选</div>
|
||||
</div>
|
||||
<div
|
||||
line-height-1
|
||||
:style="{
|
||||
marginTop: modelValue?.length ? '10px' : ''
|
||||
}"
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="
|
||||
() => {
|
||||
// isDialogVisible = true
|
||||
showDialog()
|
||||
gtagRenderer('choose_city_entry_button_clicked')
|
||||
}
|
||||
"
|
||||
>选择城市</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="modelValue?.length"
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="clearValue"
|
||||
>清空已选择的所有城市</el-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</city-chooser>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-1px bg-#f0f0f0" mt16px mb16px />
|
||||
<div mt16px>
|
||||
<div font-size-14px mb8px>
|
||||
薪资(仅支持按月计算薪资的职位;非按月计算薪资职位(例如兼职职位、实习职位)将直接跳过)
|
||||
</div>
|
||||
<div
|
||||
:style="{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '10px'
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<el-form-item prop="expectSalaryLow" mb10px>
|
||||
<div w-full>
|
||||
<div font-size-12px>薪资筛选方式</div>
|
||||
<el-select
|
||||
v-model="formContent.expectSalaryCalculateWay"
|
||||
@change="handleExpectSalaryCalculateWayChanged"
|
||||
>
|
||||
<el-option
|
||||
v-for="op in expectSalaryCalculateWayOption"
|
||||
:key="op.value"
|
||||
:label="op.name"
|
||||
:value="op.value"
|
||||
>{{ op.name }}</el-option
|
||||
>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="expectSalaryLow" mb10px>
|
||||
<div>
|
||||
<div font-size-12px>期望薪资范围</div>
|
||||
<div>
|
||||
<el-input-number
|
||||
v-model="formContent.expectSalaryLow"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:step="0.25"
|
||||
placeholder="不设置"
|
||||
@change="
|
||||
() => {
|
||||
gtagRenderer('expect_salary_low_changed')
|
||||
ensureSalaryRangeCorrect({ formContent })
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #prefix>下限</template>
|
||||
<template #suffix>
|
||||
<template v-if="formContent.expectSalaryLow">
|
||||
<template
|
||||
v-if="
|
||||
formContent.expectSalaryCalculateWay ===
|
||||
SalaryCalculateWay.MONTH_SALARY
|
||||
"
|
||||
>k</template
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
formContent.expectSalaryCalculateWay ===
|
||||
SalaryCalculateWay.ANNUAL_PACKAGE
|
||||
"
|
||||
>W</template
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
</el-input-number>
|
||||
-
|
||||
<el-input-number
|
||||
v-model="formContent.expectSalaryHigh"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:step="0.25"
|
||||
placeholder="不设置"
|
||||
@change="
|
||||
() => {
|
||||
gtagRenderer('expect_salary_high_changed')
|
||||
ensureSalaryRangeCorrect({ formContent })
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #prefix>上限</template>
|
||||
<template #suffix>
|
||||
<template v-if="formContent.expectSalaryHigh">
|
||||
<template
|
||||
v-if="
|
||||
formContent.expectSalaryCalculateWay ===
|
||||
SalaryCalculateWay.MONTH_SALARY
|
||||
"
|
||||
>k</template
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
formContent.expectSalaryCalculateWay ===
|
||||
SalaryCalculateWay.ANNUAL_PACKAGE
|
||||
"
|
||||
>W</template
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
formContent.expectSalaryCalculateWay === SalaryCalculateWay.ANNUAL_PACKAGE &&
|
||||
(formContent.expectSalaryLow || formContent.expectSalaryHigh)
|
||||
"
|
||||
mb10px
|
||||
>
|
||||
<div>
|
||||
<div font-size-12px>薪资范围满足以下条件的职位将会被匹配</div>
|
||||
<div>
|
||||
<div flex flex-nowrap flex-items-start>
|
||||
<template
|
||||
v-for="(mGroup, index) in [
|
||||
[12, 13, 14, 15, 16, 17, 18],
|
||||
[19, 20, 21, 22, 23, 24]
|
||||
]"
|
||||
:key="index"
|
||||
>
|
||||
<table
|
||||
:style="{
|
||||
lineHeight: '1.25em'
|
||||
}"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(text, i) in ['月薪下限', '月薪上限', '']"
|
||||
:key="i"
|
||||
:style="{
|
||||
borderBottom: '2px solid #f0f0f0'
|
||||
}"
|
||||
>
|
||||
{{ text }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr v-for="m in mGroup" :key="m">
|
||||
<td>
|
||||
{{
|
||||
formContent.expectSalaryLow
|
||||
? ((formContent.expectSalaryLow / m) * 10).toFixed(2)
|
||||
: '无下限'
|
||||
}}<small v-if="formContent.expectSalaryLow" class="color-#999 ml-2px"
|
||||
>k</small
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
{{
|
||||
formContent.expectSalaryHigh
|
||||
? ((formContent.expectSalaryHigh / m) * 10).toFixed(2)
|
||||
: '无上限'
|
||||
}}<small v-if="formContent.expectSalaryHigh" class="color-#999 ml-2px"
|
||||
>k</small
|
||||
>
|
||||
</td>
|
||||
<td>{{ m }}薪</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div v-if="index !== 1" class="bg-#f0f0f0 w-2px flex-self-stretch"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-1px bg-#f0f0f0" mt16px mb16px />
|
||||
<div>
|
||||
<div
|
||||
flex
|
||||
:style="{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}"
|
||||
>
|
||||
<div font-size-14px>期望职位信息</div>
|
||||
<div>
|
||||
<el-dropdown ml20px @command="handleExpectJobFilterTemplateClicked">
|
||||
<el-button size="small"
|
||||
>职位详情筛选模板(按职类区分)
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon
|
||||
></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu
|
||||
:style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr 1fr'
|
||||
}"
|
||||
>
|
||||
<el-dropdown-item
|
||||
v-for="item in expectJobFilterTemplateList"
|
||||
:key="item.name"
|
||||
:command="item"
|
||||
>{{ item.name }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:style="{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
gap: '10px'
|
||||
}"
|
||||
>
|
||||
<div flex-1>
|
||||
<el-form-item mb0 prop="expectJobNameRegExpStr">
|
||||
<div font-size-12px>职位名称/类型/描述 正则匹配筛选逻辑</div>
|
||||
<el-select
|
||||
v-model="formContent.jobDetailRegExpMatchLogic"
|
||||
@change="(value) => gtagRenderer('job_detail_re_ml_change', { value })"
|
||||
>
|
||||
<el-option
|
||||
v-for="op in jobDetailRegExpMatchLogicOptions"
|
||||
:key="op.value"
|
||||
:label="op.name"
|
||||
:value="op.value"
|
||||
>{{ op.name }}</el-option
|
||||
>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div
|
||||
:style="{
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
backgroundColor: '#f0f0f0',
|
||||
marginTop: '0.5lh'
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
:style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '2em 1fr',
|
||||
gap: '5px',
|
||||
flex: 1,
|
||||
alignItems: 'end'
|
||||
}"
|
||||
class="job-detail-filter-wrap"
|
||||
>
|
||||
<div></div>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="bottom"
|
||||
@show="gtagRenderer('tooltip_show_about_how_to_fill_df')"
|
||||
>
|
||||
<template #content>
|
||||
<div w-800px>
|
||||
<div style="margin-top: 4px; margin-bottom: 4px">
|
||||
上方“职位名称/类型/描述
|
||||
正则匹配筛选逻辑”配置,你可以自行决定如下三个正则“所有正则匹配时才认为职位匹配”还是“任一正则匹配时即认为职位匹配”。
|
||||
</div>
|
||||
<ul m0>
|
||||
<li>
|
||||
当选择“所有正则匹配时才认为职位匹配”规则时,如果你留空某个输入框,表示任何职位一定匹配这个条件。
|
||||
</li>
|
||||
<li>
|
||||
当选择“任一正则匹配时即认为职位匹配”规则时,如果你留空某个输入框,表示任何职位一定不匹配这个条件。
|
||||
</li>
|
||||
</ul>
|
||||
<b>请注意</b>,如果<span color-orange>三个输入框均留空</span
|
||||
>,无论上方“职位名称/类型/描述 正则匹配筛选逻辑”配置是什么,都表示<span
|
||||
color-orange
|
||||
>列表中出现的任意职位都将认为同时匹配这三个条件(即不根据职位名称/类型/描述进行筛选)</span
|
||||
>。<br />
|
||||
因此,可以按照如下场景填写你对于期望职位的筛选条件:
|
||||
<ul style="margin-top: 4px; margin-bottom: 4px">
|
||||
<li>
|
||||
如果你只考虑工作类型,请填写“职位类型正则”输入框,其余两个输入框清空。这可以确保求职方向基本正确。
|
||||
</li>
|
||||
<li>
|
||||
如果你着重关注职位描述,请填写“职位描述正则”,其余两个输入框酌情填写。
|
||||
</li>
|
||||
<li>
|
||||
如果你想开聊列表里的推荐的任意职位,不根据职位名称/类型/描述进行筛选,请清空这三个输入框。
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
你可以在右侧"职位详情筛选模板"选择一个模板,并在选中模板基础上尝试修改
|
||||
</div>
|
||||
<div>
|
||||
<b>“职位类型正则”填写过程中请注意</b
|
||||
>,“职位类型”是由BOSS直聘预定义好的一系列职位分类,因此<b>请按照这个分类来编写正则</b>。<br />
|
||||
<!-- 这个分类可以在此找到:<br />
|
||||
<img w-400px src="../resources/job-type-source-entry.png" /> -->
|
||||
</div>
|
||||
<div>
|
||||
关于误伤/误投的排查,<a
|
||||
href="javascript:;"
|
||||
style="color: var(--el-color-primary)"
|
||||
@click.prevent="handleHowToFillDetailFilterClick"
|
||||
>请参阅这个链接</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-button
|
||||
type="text"
|
||||
font-size-12px
|
||||
:style="{
|
||||
width: 'fit-content',
|
||||
padding: '0',
|
||||
height: 'auto',
|
||||
position: 'relative',
|
||||
top: '6px'
|
||||
}"
|
||||
><span><QuestionFilled w-1em h-1em mr2px /></span
|
||||
>如下三个输入框工作机制是怎样的?怎样填写?误伤/误投如何排查?</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
<div></div>
|
||||
<el-form-item mb0 prop="expectJobNameRegExpStr">
|
||||
<div font-size-12px>职位名称正则(不区分大小写)</div>
|
||||
<el-input
|
||||
v-model="formContent.expectJobNameRegExpStr"
|
||||
type="textarea"
|
||||
:placeholder="
|
||||
getJobDetailRegExpMatchLogicConfig({ formContent }).inputPlaceholderText
|
||||
"
|
||||
:autosize="{ minRows: 2 }"
|
||||
max-h-6lh
|
||||
@blur="
|
||||
formContent.expectJobNameRegExpStr =
|
||||
formContent.expectJobNameRegExpStr?.trim() ?? ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div
|
||||
mb0px
|
||||
font-size-12px
|
||||
flex
|
||||
flex-justify-center
|
||||
fw-800
|
||||
flex-self-start
|
||||
position-relative
|
||||
style="top: 42px"
|
||||
>
|
||||
{{ getJobDetailRegExpMatchLogicConfig({ formContent }).logicText }}
|
||||
</div>
|
||||
<el-form-item mb0 prop="expectJobTypeRegExpStr">
|
||||
<div ref="jobDetailRegExpSectionEl" font-size-12px>
|
||||
职位类型正则(推荐填写,不区分大小写)
|
||||
</div>
|
||||
<el-input
|
||||
v-model="formContent.expectJobTypeRegExpStr"
|
||||
type="textarea"
|
||||
:placeholder="
|
||||
getJobDetailRegExpMatchLogicConfig({ formContent }).inputPlaceholderText
|
||||
"
|
||||
:autosize="{ minRows: 2 }"
|
||||
max-h-6lh
|
||||
@blur="
|
||||
formContent.expectJobTypeRegExpStr =
|
||||
formContent.expectJobTypeRegExpStr?.trim() ?? ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div
|
||||
mb0px
|
||||
font-size-12px
|
||||
flex
|
||||
flex-justify-center
|
||||
fw-800
|
||||
flex-self-start
|
||||
position-relative
|
||||
style="top: 42px"
|
||||
>
|
||||
{{ getJobDetailRegExpMatchLogicConfig({ formContent }).logicText }}
|
||||
</div>
|
||||
<el-form-item mb0 prop="expectJobDescRegExpStr">
|
||||
<div font-size-12px>职位描述正则(不区分大小写)</div>
|
||||
<el-input
|
||||
v-model="formContent.expectJobDescRegExpStr"
|
||||
type="textarea"
|
||||
:placeholder="
|
||||
getJobDetailRegExpMatchLogicConfig({ formContent }).inputPlaceholderText
|
||||
"
|
||||
:autosize="{ minRows: 2 }"
|
||||
max-h-6lh
|
||||
@blur="
|
||||
formContent.expectJobDescRegExpStr =
|
||||
formContent.expectJobDescRegExpStr?.trim() ?? ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="bg-#f8f8f8 pb10px pt10px">
|
||||
<div
|
||||
:style="{
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
maxWidth: '800px',
|
||||
margin: '0 auto',
|
||||
paddingLeft: '20px',
|
||||
paddingRight: 'calc(20px + 16px)'
|
||||
}"
|
||||
>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
||||
import { JobDetailRegExpMatchLogic, SalaryCalculateWay } from '@geekgeekrun/sqlite-plugin/src/enums'
|
||||
import CityChooser from '../MainLayout/GeekAutoStartChatWithBoss/components/CityChooser.vue'
|
||||
import { QuestionFilled, ArrowDown } from '@element-plus/icons-vue'
|
||||
|
||||
import {
|
||||
getJobDetailRegExpMatchLogicConfig,
|
||||
expectSalaryCalculateWayOption,
|
||||
ensureSalaryRangeCorrect,
|
||||
expectCompanyTemplateList,
|
||||
blockCompanyNameRegExpTemplateList,
|
||||
getHandlerForBlockCompanyNameRegExpTemplateClicked,
|
||||
getHandlerForExpectCompanyTemplateClicked,
|
||||
getHandlerForExpectJobFilterTemplateClicked,
|
||||
getRuleOfExpectJobNameRegExpStr,
|
||||
getRuleOfExpectJobDescRegExpStr,
|
||||
getRuleOfExpectJobTypeRegExpStr,
|
||||
getRuleOfBlockCompanyNameRegExpStr,
|
||||
jobDetailRegExpMatchLogicOptions,
|
||||
getHandlerForExpectSalaryCalculateWayChanged,
|
||||
normalizeCommaSplittedStr
|
||||
} from '../MainLayout/GeekAutoStartChatWithBoss/common'
|
||||
import { computed, ref } from 'vue'
|
||||
import expectJobFilterTemplateList from '../MainLayout/GeekAutoStartChatWithBoss/expectJobFilterTemplateList'
|
||||
const { ipcRenderer } = window.electron
|
||||
const gtagRenderer = (name, params?: object) => {
|
||||
return baseGtagRenderer(name, {
|
||||
scene: 'cjc_config',
|
||||
...params
|
||||
})
|
||||
}
|
||||
|
||||
const formContent = ref({
|
||||
expectCompanies: '',
|
||||
expectCityList: [],
|
||||
jobDetailRegExpMatchLogic: JobDetailRegExpMatchLogic.EVERY,
|
||||
expectJobNameRegExpStr: '',
|
||||
expectJobTypeRegExpStr: '',
|
||||
expectJobDescRegExpStr: '',
|
||||
expectSalaryCalculateWay: SalaryCalculateWay.ANNUAL_PACKAGE,
|
||||
expectSalaryHigh: null,
|
||||
expectSalaryLow: null,
|
||||
blockCompanyNameRegExpStr: ''
|
||||
})
|
||||
|
||||
const jobDetailRegExpSectionEl = ref<HTMLDivElement>()
|
||||
const blockCompanyNameRegExpSectionEl = ref<HTMLDivElement>()
|
||||
const formRules = computed(() => ({
|
||||
expectJobNameRegExpStr: {
|
||||
trigger: 'blur',
|
||||
validator: getRuleOfExpectJobNameRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
|
||||
},
|
||||
expectJobTypeRegExpStr: {
|
||||
trigger: 'blur',
|
||||
validator: getRuleOfExpectJobTypeRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
|
||||
},
|
||||
expectJobDescRegExpStr: {
|
||||
trigger: 'blur',
|
||||
validator: getRuleOfExpectJobDescRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl })
|
||||
},
|
||||
blockCompanyNameRegExpStr: {
|
||||
trigger: 'blur',
|
||||
validator: getRuleOfBlockCompanyNameRegExpStr({ gtagRenderer, blockCompanyNameRegExpSectionEl })
|
||||
}
|
||||
}))
|
||||
|
||||
const handleBlockCompanyNameRegExpTemplateClicked =
|
||||
getHandlerForBlockCompanyNameRegExpTemplateClicked({
|
||||
gtagRenderer,
|
||||
formContent
|
||||
})
|
||||
|
||||
const handleExpectCompanyTemplateClicked = getHandlerForExpectCompanyTemplateClicked({
|
||||
gtagRenderer,
|
||||
formContent
|
||||
})
|
||||
|
||||
const handleExpectJobFilterTemplateClicked = getHandlerForExpectJobFilterTemplateClicked({
|
||||
gtagRenderer,
|
||||
formContent
|
||||
})
|
||||
|
||||
function handleHowToFillDetailFilterClick() {
|
||||
gtagRenderer('click_linux_do_how_to_fill_df')
|
||||
electron.ipcRenderer.send(
|
||||
'open-external-link',
|
||||
'https://linux.do/t/topic/640626/74?u=geekgeekrun'
|
||||
)
|
||||
}
|
||||
|
||||
const handleExpectSalaryCalculateWayChanged = getHandlerForExpectSalaryCalculateWayChanged({
|
||||
gtagRenderer,
|
||||
formContent
|
||||
})
|
||||
|
||||
function handleCancel() {
|
||||
//
|
||||
window.close()
|
||||
}
|
||||
|
||||
const formRef = ref()
|
||||
async function handleSave() {
|
||||
await formRef.value?.validate()
|
||||
await ipcRenderer.invoke(
|
||||
'save-common-job-condition-config',
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
...formContent.value,
|
||||
expectCompanies: formContent.value.expectCompanies
|
||||
? formContent.value.expectCompanies.split(',').map((s) => s.trim())
|
||||
: []
|
||||
})
|
||||
)
|
||||
)
|
||||
ipcRenderer.send('common-job-condition-config-done')
|
||||
}
|
||||
|
||||
ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||
const commonJobConditionConfig = res.config?.['common-job-condition-config.json'] ?? {}
|
||||
Object.keys(formContent.value).forEach((key) => {
|
||||
if (key in commonJobConditionConfig) {
|
||||
if (key === 'expectCompanies') {
|
||||
formContent.value[key] = (commonJobConditionConfig[key] ?? []).join(',')
|
||||
} else {
|
||||
formContent.value[key] = commonJobConditionConfig[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.common-job-condition-config .el-form-item__error.el-form-item__error--inline {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,112 +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
|
||||
>您的行为;上级 / HRBP 团队可能会从 IT
|
||||
团队处获取到监控数据,从而了解团队成员离职倾向。如果您不希望上级 / HRBP
|
||||
团队了解到您正在求职,建议您<b color-red
|
||||
>不要在您所在公司提供的计算机终端或网络上使用本程序</b
|
||||
>。
|
||||
</ElCheckbox>
|
||||
<ElCheckbox :label="5" :class="[unreadItemsAfterClickSubmit[5] ? 'unread' : '']">
|
||||
本程序尊重您的隐私,<b color-red
|
||||
>不会上报能够识别出您身份的信息、不会参与任何钓鱼活动、不会向您所在公司及上级 / HRBP
|
||||
团队报告您的求职行为、不会向猎头公司泄露您的信息</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>
|
||||
|
||||
@@ -161,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;
|
||||
width: 100vw;
|
||||
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 {
|
||||
@@ -220,5 +223,9 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
footer {
|
||||
flex: 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
import { SalaryCalculateWay, JobDetailRegExpMatchLogic } from '@geekgeekrun/sqlite-plugin/src/enums'
|
||||
import sampleCompanyList from '@geekgeekrun/geek-auto-start-chat-with-boss/default-config-file/sample-company-list.json'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
export function isJobDetailRegExpEmpty({ formContent }) {
|
||||
return [
|
||||
formContent.expectJobDescRegExpStr,
|
||||
formContent.expectJobNameRegExpStr,
|
||||
formContent.expectJobTypeRegExpStr
|
||||
]
|
||||
.map((it) => Boolean(it?.trim()))
|
||||
.every((it) => it === false)
|
||||
}
|
||||
|
||||
export function getJobDetailRegExpMatchLogicConfig({ formContent }) {
|
||||
const result = {
|
||||
logicText: '-',
|
||||
inputPlaceholderText: '-'
|
||||
}
|
||||
if (formContent.jobDetailRegExpMatchLogic === JobDetailRegExpMatchLogic.EVERY) {
|
||||
Object.assign(result, {
|
||||
logicText: '且',
|
||||
inputPlaceholderText: 'true'
|
||||
})
|
||||
}
|
||||
if (formContent.jobDetailRegExpMatchLogic === JobDetailRegExpMatchLogic.SOME) {
|
||||
Object.assign(result, {
|
||||
logicText: '或',
|
||||
inputPlaceholderText: 'false'
|
||||
})
|
||||
}
|
||||
|
||||
if (isJobDetailRegExpEmpty({ formContent })) {
|
||||
result.inputPlaceholderText = 'true'
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const expectSalaryCalculateWayOption = [
|
||||
{
|
||||
name: '月薪(单位为 千元 - 即“k”)',
|
||||
value: SalaryCalculateWay.MONTH_SALARY
|
||||
},
|
||||
{
|
||||
name: '总包(单位为 万元 - 即“W”)',
|
||||
value: SalaryCalculateWay.ANNUAL_PACKAGE
|
||||
}
|
||||
]
|
||||
|
||||
export function ensureSalaryRangeCorrect({ formContent }) {
|
||||
if (!formContent.expectSalaryHigh || isNaN(parseFloat(formContent.expectSalaryHigh))) {
|
||||
formContent.expectSalaryHigh = null
|
||||
} else {
|
||||
formContent.expectSalaryHigh = parseFloat(formContent.expectSalaryHigh.toFixed(2))
|
||||
}
|
||||
if (!formContent.expectSalaryLow || isNaN(parseFloat(formContent.expectSalaryLow))) {
|
||||
formContent.expectSalaryLow = null
|
||||
} else {
|
||||
formContent.expectSalaryLow = parseFloat(formContent.expectSalaryLow.toFixed(2))
|
||||
}
|
||||
|
||||
if (
|
||||
formContent.expectSalaryLow &&
|
||||
formContent.expectSalaryHigh &&
|
||||
formContent.expectSalaryLow > formContent.expectSalaryHigh
|
||||
) {
|
||||
formContent.expectSalaryHigh = formContent.expectSalaryLow
|
||||
}
|
||||
}
|
||||
|
||||
export function getRuleOfExpectJobNameRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl }) {
|
||||
return (_, value, cb) => {
|
||||
if (!value) {
|
||||
cb()
|
||||
gtagRenderer('empty_reg_exp_for_expect_job_name')
|
||||
return
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_name', { v: value })
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_name', { v: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRuleOfExpectJobTypeRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl }) {
|
||||
return (_, value, cb) => {
|
||||
if (!value) {
|
||||
cb()
|
||||
gtagRenderer('empty_reg_exp_for_expect_job_type')
|
||||
return
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_type', { v: value })
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_type', { v: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRuleOfExpectJobDescRegExpStr({ gtagRenderer, jobDetailRegExpSectionEl }) {
|
||||
return (_, value, cb) => {
|
||||
if (!value) {
|
||||
cb()
|
||||
gtagRenderer('empty_reg_exp_for_expect_job_desc')
|
||||
return
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_expect_job_desc', { v: value })
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
jobDetailRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||
gtagRenderer('invalid_reg_exp_for_expect_job_desc', { v: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRuleOfBlockCompanyNameRegExpStr({
|
||||
gtagRenderer,
|
||||
blockCompanyNameRegExpSectionEl
|
||||
}) {
|
||||
return (_, value, cb) => {
|
||||
if (!value) {
|
||||
cb()
|
||||
gtagRenderer('empty_reg_exp_for_bcn')
|
||||
return
|
||||
}
|
||||
try {
|
||||
new RegExp(value, 'ig')
|
||||
gtagRenderer('valid_reg_exp_for_bcn', { v: value })
|
||||
cb()
|
||||
} catch (err) {
|
||||
cb(new Error(`正则无效:${err?.message}`))
|
||||
blockCompanyNameRegExpSectionEl.value?.scrollIntoViewIfNeeded()
|
||||
gtagRenderer('invalid_reg_exp_for_bcn', { v: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const expectCompanyTemplateList = [
|
||||
{
|
||||
name: '不限公司(随便投)',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
name: '示例公司',
|
||||
value: sampleCompanyList.join(',')
|
||||
},
|
||||
{
|
||||
name: '大厂及关联企业',
|
||||
value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,滴滴,嘀嘀,巨量引擎,小桔,网易,有道,腾讯,酷狗,酷我,阅文,搜狗,小鹅通,富途,京东,沃东天骏,达达,达冠,京邦达,百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,智图,长地万方,瑞图万方,道道通,小熊博望,理想,蔚来,顺丰,丰巢,中通,圆通,申通,跨越,讯飞,同程,艺龙,马蜂窝,贝壳,自如,链家,我爱我家,相寓,多点,金山,小米,猎豹,新浪,微博,阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,高德,LAZADA,来赞达,飞猪,菜鸟,哈啰,钉钉,乌鸫,饿了么,美团,三快,猫眼,快手,映客,小红书,行吟,奇虎,360,三六零,鸿盈,奇富,奇元,亚信,启明星辰,奇安信,深信服,长亭,绿盟,天融信,商汤,SenseTime,大华,海康威视,hikvision,汽车之家,车好多,瓜子,易车,昆仑万维,昆仑天工,闲徕,趣加,FunPlus,完美,马上消费,轻松,水滴,白龙马,58,更赢,车欢欢,五八,红布林,致美,快狗,天鹅到家,转转,美餐,知乎,智者四海,易点云,搜狐,用友,畅捷通,猿辅导,小猿,猿力,好未来,学而思,希望学,新东方,东方甄选,东方优选,作业帮,高途,跟谁学,学科网,天学网,一起教育,一起作业,美术宝,火花思维,粉笔,51talk,爱学习,高思,老虎国际,一心向上,向上一意,联想,拉勾,乐视,欢聚,竞技世界,拼多多,寻梦,从鲸,TEMU,得物,有赞,Moka,希瑞亚斯,北森,OPPO,欧珀,vivo,维沃,小天才,步步高,读书郎,货拉拉,陌陌,探探,Shopee,虾皮,首汽租车,GoFun,神州租车,天眼查,旷视,小冰,美图,智谱华章,MiniMax,石头科技,迅雷,TP,锐捷,Tenda,腾达,斐讯,希音,SHEIN,稀宇,深言,百川智能,与爱为舞,牵手,Grab,爱回收,洋钱罐,瓴岳,得到,思维造物,地平线,咪咕,翼支付,电信,天翼,联通,蓝湖,墨刀,海尔,美的,米哈游,传音,同花顺,国美,TCL`
|
||||
},
|
||||
{
|
||||
name: '阿里系',
|
||||
value: `阿里,淘宝,淘麦郎,天猫,盒马,口碑,优视,夸克,UC,蚂蚁,飞猪,乌鸫,饿了么,LAZADA,来赞达,菜鸟,哈啰,钉钉,高德,白龙马,新浪,微博`
|
||||
},
|
||||
{
|
||||
name: '字节(头条/抖音)系',
|
||||
value: `抖音,字节,字跳,有竹居,脸萌,头条,懂车帝,巨量引擎`
|
||||
},
|
||||
{
|
||||
name: '百度系',
|
||||
value: `百度,昆仑芯,小度,度小满,爱奇艺,携程,趣拿,去哪儿,集度,作业帮,智图,长地万方,瑞图万方,道道通,小熊博望`
|
||||
},
|
||||
{
|
||||
name: '腾讯系',
|
||||
value: `腾讯,酷狗,酷我,阅文,搜狗,小鹅通,富途,京东,沃东天骏,达达,达冠,京邦达,美团,三快,猫眼,快手,拼多多,寻梦,从鲸,TEMU,Shopee,虾皮,滴滴,嘀嘀,小桔,转转`
|
||||
},
|
||||
{
|
||||
name: '外包、劳务派遣企业',
|
||||
value: `青钱,软通动力,南天,睿服,中电金信,佰钧成,云链,博彦,汉克时代,柯莱特,拓保,亿达信息,纬创,微创,微澜,诚迈科技,法本,兆尹,诚迈,联合永道,新致软件,宇信科技,华为,德科,FESCO,科锐,科之锐`
|
||||
}
|
||||
]
|
||||
|
||||
export const blockCompanyNameRegExpTemplateList = [
|
||||
{
|
||||
name: '不限公司(不按照公司名称来标注不合适)',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
name: '外包、劳务派遣企业',
|
||||
value: `青钱|软通动力|南天|睿服|中电金信|佰钧成|云链|博彦|汉克时代|柯莱特|拓保|亿达信息|纬创|微创|微澜|诚迈科技|法本|兆尹|诚迈|联合永道|新致软件|宇信科技|华为|德科|FESCO|科锐|科之锐`
|
||||
},
|
||||
{
|
||||
name: '京东及相关公司',
|
||||
value: '京东|沃东天骏|达达|达冠|京邦达'
|
||||
}
|
||||
]
|
||||
|
||||
export function getHandlerForExpectCompanyTemplateClicked({ gtagRenderer, formContent }) {
|
||||
return function handleExpectCompanyTemplateClicked(item) {
|
||||
gtagRenderer('expect_company_tpl_clicked', {
|
||||
name: item.name
|
||||
})
|
||||
formContent.value.expectCompanies = item.value
|
||||
}
|
||||
}
|
||||
|
||||
export function getHandlerForExpectJobFilterTemplateClicked({ gtagRenderer, formContent }) {
|
||||
return function handleExpectJobFilterTemplateClicked(item) {
|
||||
gtagRenderer('expect_job_filter_tpl_clicked', {
|
||||
name: item.name
|
||||
})
|
||||
Object.assign(formContent.value, {
|
||||
...item.config
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function getHandlerForBlockCompanyNameRegExpTemplateClicked({ gtagRenderer, formContent }) {
|
||||
return function handleBlockCompanyNameRegExpTemplateClicked(item) {
|
||||
gtagRenderer('bcn_reg_exp_tpl_clicked', {
|
||||
name: item.name
|
||||
})
|
||||
formContent.value.blockCompanyNameRegExpStr = item.value
|
||||
}
|
||||
}
|
||||
|
||||
export const jobDetailRegExpMatchLogicOptions = [
|
||||
{
|
||||
name: '“且”模式 - 所有正则匹配时才认为职位匹配',
|
||||
value: JobDetailRegExpMatchLogic.EVERY
|
||||
},
|
||||
{
|
||||
name: '“或”模式 - 任一正则匹配时即认为职位匹配',
|
||||
value: JobDetailRegExpMatchLogic.SOME
|
||||
}
|
||||
]
|
||||
|
||||
export function getHandlerForExpectSalaryCalculateWayChanged({ gtagRenderer, formContent }) {
|
||||
return async function handleExpectSalaryCalculateWayChanged(value) {
|
||||
gtagRenderer('expect_salary_calculate_way_changed', { value })
|
||||
|
||||
await nextTick()
|
||||
// convert annual package to month salary as 12-month
|
||||
if (value === SalaryCalculateWay.MONTH_SALARY) {
|
||||
if (formContent.value.expectSalaryHigh) {
|
||||
formContent.value.expectSalaryHigh = Number(
|
||||
((formContent.value.expectSalaryHigh * 10) / 12).toFixed(2)
|
||||
)
|
||||
}
|
||||
if (formContent.value.expectSalaryLow) {
|
||||
formContent.value.expectSalaryLow = Number(
|
||||
((formContent.value.expectSalaryLow * 10) / 12).toFixed(2)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
// convert month salary to annual package as 12-month
|
||||
else if (value === SalaryCalculateWay.ANNUAL_PACKAGE) {
|
||||
if (formContent.value.expectSalaryHigh) {
|
||||
formContent.value.expectSalaryHigh = Number(
|
||||
((formContent.value.expectSalaryHigh / 10) * 12).toFixed(2)
|
||||
)
|
||||
}
|
||||
if (formContent.value.expectSalaryLow) {
|
||||
formContent.value.expectSalaryLow = Number(
|
||||
((formContent.value.expectSalaryLow / 10) * 12).toFixed(2)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const normalizeCommaSplittedStr = (str) => {
|
||||
return str
|
||||
.split(/,|,/)
|
||||
.map((it) => it.trim())
|
||||
.filter(Boolean)
|
||||
.join(',')
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="group-item">
|
||||
<div class="group-title">逛BOSS</div>
|
||||
<div flex flex-col class="link-list">
|
||||
<RouterLink to="./GeekAutoStartChatWithBoss">
|
||||
自动开聊
|
||||
<el-tooltip
|
||||
placement="right"
|
||||
:enterable="false"
|
||||
@show="gtagRenderer('tooltip_show_for_nav_boss_b_entry')"
|
||||
>
|
||||
<template #content>
|
||||
<div w-480px>
|
||||
<div>扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的BOSS。</div>
|
||||
<br />
|
||||
<div>匹配步骤</div>
|
||||
<ol m0 pl2em>
|
||||
<li>
|
||||
按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
|
||||
</li>
|
||||
<li>
|
||||
检查BOSS活跃度
|
||||
<ul pl2em>
|
||||
<li>
|
||||
如果BOSS活跃度为本月活跃或更往前的时间,则会把职位标记为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
对职位名称、职位类型、职位描述进行匹配
|
||||
<ul pl2em>
|
||||
<li>如果匹配则自动点击开聊按钮</li>
|
||||
<li>
|
||||
不匹配则标记这个职位为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<br />
|
||||
<div>异常情况</div>
|
||||
<ol m0 pl2em>
|
||||
<li>
|
||||
当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
|
||||
</li>
|
||||
<li>
|
||||
如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</template>
|
||||
<QuestionFilled w-1em h-1em mr10px />
|
||||
</el-tooltip>
|
||||
</RouterLink>
|
||||
<RouterLink to="./ReadNoReplyReminder">
|
||||
已读不回自动复聊
|
||||
<el-tooltip
|
||||
placement="right"
|
||||
:enterable="false"
|
||||
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
||||
>
|
||||
<template #content>
|
||||
<div w-480px>
|
||||
<div>
|
||||
BOSS不明原因已读不回?简历就是投不出去?<br />
|
||||
已读不回自动复聊,提醒一下已读不回的 BOSS,助力把握每次机会
|
||||
</div>
|
||||
<br />
|
||||
<div>匹配逻辑</div>
|
||||
<div>在聊天列表中查找对你消息已读不回的BOSS,再发一条消息,多次复聊;同时:</div>
|
||||
<ul m0 pl2em>
|
||||
<li>如果设置了“跟进时限”,那么在这个时间之前活跃的聊天将不会被检查</li>
|
||||
<li>
|
||||
如果设置了“跟进间隔”,且再次检查时发现BOSS已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<div>发送内容</div>
|
||||
<ul m0 pl2em>
|
||||
<li>“[盼回复]”表情</li>
|
||||
<li>由大语言模型(根据简历及当前聊天上下文)生成的内容</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<QuestionFilled w-1em h-1em mr10px />
|
||||
</el-tooltip>
|
||||
</RouterLink>
|
||||
<a href="javascript:void(0)" @click="handleClickLaunchBossLogin">
|
||||
编辑登录凭据<TopRight w-1em h-1em mr10px />
|
||||
</a>
|
||||
<a href="javascript:void(0)" @click="handleLaunchBossSite">
|
||||
手动逛<TopRight w-1em h-1em mr10px />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import { debounce } from 'lodash'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { TopRight, QuestionFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const handleClickLaunchBossLogin = async () => {
|
||||
gtagRenderer('launch_login_clicked')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('login-with-cookie-assistant')
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '登录凭据保存成功'
|
||||
})
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const handleLaunchBossSite = debounce(
|
||||
async () => {
|
||||
gtagRenderer('launch_boss_site_clicked')
|
||||
return await electron.ipcRenderer.invoke('open-site-with-boss-cookie', {
|
||||
url: `https://www.zhipin.com/`
|
||||
})
|
||||
},
|
||||
1000,
|
||||
{ leading: true, trailing: false }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss" src="./style.scss"></style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="group-item">
|
||||
<div class="group-title">全局设置</div>
|
||||
<div flex flex-col class="link-list">
|
||||
<a href="javascript:void(0)" @click="handleClickConfigCommonJobCondition">
|
||||
公共职位筛选条件
|
||||
</a>
|
||||
<a href="javascript:void(0)" @click="handleClickBrowserSetting">
|
||||
编辑浏览器偏好<TopRight w-1em h-1em mr10px />
|
||||
</a>
|
||||
<a href="javascript:void(0)" @click="handleClickConfigLlm">
|
||||
配置大语言模型
|
||||
<div>
|
||||
<el-tooltip
|
||||
placement="right"
|
||||
:enterable="false"
|
||||
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
||||
>
|
||||
<template #content>
|
||||
<div class="font-size-12px">
|
||||
支持
|
||||
<span
|
||||
class="pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
||||
style="background-color: #3c4efd"
|
||||
>DeepSeek-V3</span
|
||||
>
|
||||
<span
|
||||
class="ml4px pl6px pr6px pt4px pb2px color-black border-rd-full font-size-0.8em"
|
||||
style="background-color: #fff"
|
||||
>GPT-4o mini</span
|
||||
>
|
||||
<span
|
||||
class="ml4px pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
||||
style="background-color: #462ac4"
|
||||
>Qwen2.5</span
|
||||
>
|
||||
模型<br />支持多个“服务商-模型”组合按权重搭配使用
|
||||
</div>
|
||||
</template>
|
||||
<QuestionFilled w-1em h-1em mr10px />
|
||||
</el-tooltip>
|
||||
<TopRight w-1em h-1em mr10px />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { TopRight, QuestionFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const handleClickBrowserSetting = async () => {
|
||||
gtagRenderer('browser_setting_clicked')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('config-with-browser-assistant')
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '浏览器偏好保存成功'
|
||||
})
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickConfigLlm = async () => {
|
||||
gtagRenderer('config_llm_clicked')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('llm-config')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickConfigCommonJobCondition = async () => {
|
||||
gtagRenderer('config_cjc_clicked', { entry: 'left-nav' })
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('common-job-condition-config')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss" src="./style.scss"></style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div class="group-item">
|
||||
<div class="group-title">运行数据</div>
|
||||
<div flex flex-col class="link-list">
|
||||
<RouterLink to="./StartChatRecord">开聊记录</RouterLink>
|
||||
<RouterLink to="./MarkAsNotSuitRecord">标记不合适记录</RouterLink>
|
||||
<RouterLink to="./JobLibrary">职位库</RouterLink>
|
||||
<RouterLink to="./BossLibrary">BOSS库</RouterLink>
|
||||
<RouterLink to="./CompanyLibrary">公司库</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss" src="./style.scss"></style>
|
||||
@@ -0,0 +1,26 @@
|
||||
.group-item {
|
||||
.group-title {
|
||||
color: #849492;
|
||||
font-size: 12px;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
.link-list {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 2em;
|
||||
box-sizing: border-box;
|
||||
padding-left: 1em;
|
||||
font-size: 14px;
|
||||
&.router-link-active {
|
||||
background-color: #fff;
|
||||
font-weight: 700;
|
||||
color: #2faa9e;
|
||||
border-radius: 9999px 0 0 9999px;
|
||||
position: relative;
|
||||
box-shadow: 0px 0px 10px rgba(50, 114, 108, 0.187);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,11 @@
|
||||
>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<el-checkbox v-if="!expectJobTypeRegExpStr?.trim()" :model-value="false" disabled>
|
||||
<el-checkbox
|
||||
v-if="!expectJobTypeRegExpStrForRender?.trim()"
|
||||
:model-value="false"
|
||||
disabled
|
||||
>
|
||||
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
|
||||
</el-checkbox>
|
||||
<template v-else>
|
||||
@@ -17,7 +21,7 @@
|
||||
发送提醒消息前,先按照“自动开聊-职位类型正则”校验正在与BOSS沟通的岗位是否满足期望,校验通过后再提醒
|
||||
</el-checkbox>
|
||||
<div ml1.5em color-gray>
|
||||
<div>当前职位类型正则:{{ expectJobTypeRegExpStr?.trim() }}</div>
|
||||
<div>当前职位类型正则:{{ expectJobTypeRegExpStrForRender?.trim() }}</div>
|
||||
<template
|
||||
v-if="
|
||||
formContent.autoReminder.rechatContentSource ===
|
||||
@@ -33,7 +37,11 @@
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<el-checkbox v-if="!blockCompanyNameRegExpStr?.trim()" :model-value="false" disabled>
|
||||
<el-checkbox
|
||||
v-if="!blockCompanyNameRegExpStrForRender?.trim()"
|
||||
:model-value="false"
|
||||
disabled
|
||||
>
|
||||
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
|
||||
</el-checkbox>
|
||||
<template v-else>
|
||||
@@ -41,12 +49,82 @@
|
||||
发送提醒消息前,先按照“自动开聊-不期望投递公司正则”校验正在与BOSS沟通的岗位是否归属于不期望投递的公司,如果是,则不提醒
|
||||
</el-checkbox>
|
||||
<div ml1.5em color-gray>
|
||||
<div>当前不期望投递公司正则:{{ blockCompanyNameRegExpStr?.trim() }}</div>
|
||||
<div>当前不期望投递公司正则:{{ blockCompanyNameRegExpStrForRender?.trim() }}</div>
|
||||
</div>
|
||||
</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
|
||||
@@ -63,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
|
||||
@@ -207,14 +311,14 @@
|
||||
</el-form>
|
||||
</div>
|
||||
<div
|
||||
class="runing-overlay__wrap"
|
||||
class="running-overlay__wrap"
|
||||
:style="{
|
||||
pointerEvents: 'none'
|
||||
}"
|
||||
>
|
||||
<RuningOverlay
|
||||
<RunningOverlay
|
||||
ref="runningOverlayRef"
|
||||
worker-id="readNoReplyAutoReminderMain"
|
||||
:worker-id="CURRENT_WORKER_ID"
|
||||
:run-record-id="runRecordId"
|
||||
>
|
||||
<template #op-buttons="{ currentRunningStatus }">
|
||||
@@ -241,16 +345,18 @@
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</RuningOverlay>
|
||||
</RunningOverlay>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, 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
|
||||
@@ -258,7 +364,9 @@ import {
|
||||
import { gtagRenderer as baseGtagRenderer } from '@renderer/utils/gtag'
|
||||
import mittBus from '../../utils/mitt'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import RuningOverlay from '@renderer/features/RunningOverlay/index.vue'
|
||||
import RunningOverlay from '@renderer/features/RunningOverlay/index.vue'
|
||||
import { DEFAULT_CONSTANT_OPEN_CONTENT_SEGS } from '../../../../common/constant'
|
||||
import { useTaskManagerStore } from '@renderer/store'
|
||||
const gtagRenderer = (name, params?: object) => {
|
||||
return baseGtagRenderer(name, {
|
||||
scene: 'rnrr-config',
|
||||
@@ -274,7 +382,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: ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -307,16 +418,22 @@ 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
|
||||
})
|
||||
|
||||
const expectJobTypeRegExpStr = ref('')
|
||||
const blockCompanyNameRegExpStr = ref('')
|
||||
const fieldsForUseCommonConfig = ref({})
|
||||
async function fetchAutoStartChatConfig() {
|
||||
await electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
|
||||
expectJobTypeRegExpStr.value = res.config['boss.json']?.expectJobTypeRegExpStr
|
||||
blockCompanyNameRegExpStr.value = res.config['boss.json']?.blockCompanyNameRegExpStr
|
||||
fieldsForUseCommonConfig.value = res.config['boss.json']?.fieldsForUseCommonConfig ?? {}
|
||||
commonJobConditionConfig.value = res.config['common-job-condition-config.json']
|
||||
})
|
||||
}
|
||||
fetchAutoStartChatConfig()
|
||||
@@ -324,6 +441,30 @@ mittBus.on('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig)
|
||||
onUnmounted(() => {
|
||||
mittBus.off('auto-start-chat-with-boss-config-saved', fetchAutoStartChatConfig)
|
||||
})
|
||||
const commonJobConditionConfig = ref({})
|
||||
const unListenCommonJobConditionConfig = electron.ipcRenderer.on(
|
||||
'common-job-condition-config-updated',
|
||||
(_, { config }) => {
|
||||
commonJobConditionConfig.value = {
|
||||
...config,
|
||||
expectCompanies: config?.expectCompanies?.map((it) => it.trim())?.join(',') ?? ''
|
||||
}
|
||||
}
|
||||
)
|
||||
onUnmounted(() => {
|
||||
unListenCommonJobConditionConfig()
|
||||
})
|
||||
|
||||
const expectJobTypeRegExpStrForRender = computed(() => {
|
||||
return !fieldsForUseCommonConfig.value.jobDetail
|
||||
? expectJobTypeRegExpStr.value
|
||||
: commonJobConditionConfig.value.expectJobTypeRegExpStr
|
||||
})
|
||||
const blockCompanyNameRegExpStrForRender = computed(() => {
|
||||
return !fieldsForUseCommonConfig.value.blockCompanyNameRegExpStr
|
||||
? blockCompanyNameRegExpStr.value
|
||||
: commonJobConditionConfig.value.blockCompanyNameRegExpStr
|
||||
})
|
||||
|
||||
const resumeContent = ref(null)
|
||||
async function fetchResumeContent() {
|
||||
@@ -427,8 +568,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)
|
||||
@@ -444,8 +586,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')
|
||||
@@ -464,19 +608,46 @@ async function checkIsCanRun() {
|
||||
}
|
||||
const runRecordId = ref(null)
|
||||
const runningOverlayRef = ref(null)
|
||||
const taskManagerStore = useTaskManagerStore()
|
||||
const CURRENT_WORKER_ID = 'readNoReplyAutoReminderMain'
|
||||
|
||||
onMounted(async () => {
|
||||
await taskManagerStore.getRunningTasks()
|
||||
const existingWorker = taskManagerStore.runningTasks?.find(
|
||||
(it) => it.workerId === CURRENT_WORKER_ID
|
||||
)
|
||||
if (existingWorker) {
|
||||
runRecordId.value = existingWorker.runtimeStorage?.stepStatusMapByStepId
|
||||
? Object.values(existingWorker.runtimeStorage.stepStatusMapByStepId)[0]?.runRecordId
|
||||
: null
|
||||
if (runRecordId.value) {
|
||||
runningOverlayRef.value?.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
gtagRenderer('run_read_no_reply_reminder_clicked', {
|
||||
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
|
||||
@@ -533,8 +704,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: '模板还原成功'
|
||||
@@ -568,11 +740,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: '发送“[盼回复]”表情',
|
||||
@@ -584,6 +757,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())) {
|
||||
@@ -616,6 +801,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">
|
||||
@@ -638,7 +833,7 @@ const handleStopButtonClick = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.runing-overlay__wrap {
|
||||
.running-overlay__wrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
@@ -3,153 +3,11 @@
|
||||
<div class="flex flex-col min-w200px w200px pt30px pl30px aside-nav of-hidden">
|
||||
<div class="nav-list flex-1 of-auto pl20px ml--20px">
|
||||
<RouterLink v-show="false" to="./TaskManager">任务管理</RouterLink>
|
||||
<div class="group-item">
|
||||
<div class="group-title">BOSS直聘</div>
|
||||
<div flex flex-col class="link-list">
|
||||
<RouterLink to="./GeekAutoStartChatWithBoss">
|
||||
自动开聊
|
||||
<el-tooltip
|
||||
placement="right"
|
||||
:enterable="false"
|
||||
@show="gtagRenderer('tooltip_show_for_nav_boss_b_entry')"
|
||||
>
|
||||
<template #content>
|
||||
<div w-480px>
|
||||
<div>扩列神器!按照你所设置的求职偏好,自动开聊推荐职位列表中的匹配的BOSS。</div>
|
||||
<br />
|
||||
<div>匹配步骤</div>
|
||||
<ol m0 pl2em>
|
||||
<li>
|
||||
按照公司名称查找职位,查找到目标职位后,自动点击这个职位,右侧将会展示职位详情
|
||||
</li>
|
||||
<li>
|
||||
检查BOSS活跃度
|
||||
<ul pl2em>
|
||||
<li>
|
||||
如果BOSS活跃度为本月活跃或更往前的时间,则会把职位标记为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
对职位名称、职位类型、职位描述进行匹配
|
||||
<ul pl2em>
|
||||
<li>如果匹配则自动点击开聊按钮</li>
|
||||
<li>
|
||||
不匹配则标记这个职位为不合适,一段时间内你将不会在BOSS上看到这个职位,且将会推荐新职位置换这个职位
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<br />
|
||||
<div>异常情况</div>
|
||||
<ol m0 pl2em>
|
||||
<li>
|
||||
当前页面筛选条件下,如果没有更多职位,则自动切换备选筛选条件,以获取更多新职位
|
||||
</li>
|
||||
<li>
|
||||
如当天开聊次数用完,本程序会暂停运行60分钟,之后尝试继续重新运行;如重新运行时间已在第二天,则将会继续开聊
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</template>
|
||||
<QuestionFilled w-1em h-1em mr10px />
|
||||
</el-tooltip>
|
||||
</RouterLink>
|
||||
<RouterLink to="./ReadNoReplyReminder">
|
||||
已读不回自动复聊
|
||||
<el-tooltip
|
||||
placement="right"
|
||||
:enterable="false"
|
||||
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
||||
>
|
||||
<template #content>
|
||||
<div w-480px>
|
||||
<div>
|
||||
BOSS不明原因已读不回?简历就是投不出去?<br />
|
||||
已读不回自动复聊,提醒一下已读不回的 BOSS,助力把握每次机会
|
||||
</div>
|
||||
<br />
|
||||
<div>匹配逻辑</div>
|
||||
<div>在聊天列表中查找对你消息已读不回的BOSS,再发一条消息,多次复聊;同时:</div>
|
||||
<ul m0 pl2em>
|
||||
<li>如果设置了“跟进时限”,那么在这个时间之前活跃的聊天将不会被检查</li>
|
||||
<li>
|
||||
如果设置了“跟进间隔”,且再次检查时发现BOSS已读不回,且距离上次提醒时间间隔小于这个时间,那么聊天将暂时不会跟进,直到下次检查时距离上次提醒时间间隔大于这个时间
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<div>发送内容</div>
|
||||
<ul m0 pl2em>
|
||||
<li>“[盼回复]”表情</li>
|
||||
<li>由大语言模型(根据简历及当前聊天上下文)生成的内容</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<QuestionFilled w-1em h-1em mr10px />
|
||||
</el-tooltip>
|
||||
</RouterLink>
|
||||
<a href="javascript:void(0)" @click="handleClickLaunchBossLogin">
|
||||
编辑登录凭据<TopRight w-1em h-1em mr10px />
|
||||
</a>
|
||||
<a href="javascript:void(0)" @click="handleLaunchBossSite">
|
||||
手动逛<TopRight w-1em h-1em mr10px />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<BossPart />
|
||||
<hr class="group-divider" />
|
||||
<div class="group-item">
|
||||
<div class="group-title">全局设置</div>
|
||||
<div flex flex-col class="link-list">
|
||||
<a href="javascript:void(0)" @click="handleClickBrowserSetting">
|
||||
编辑浏览器偏好<TopRight w-1em h-1em mr10px />
|
||||
</a>
|
||||
<a href="javascript:void(0)" @click="handleClickConfigLlm">
|
||||
配置大语言模型
|
||||
<div>
|
||||
<el-tooltip
|
||||
placement="right"
|
||||
:enterable="false"
|
||||
@show="gtagRenderer('tooltip_show_for_rnrr_entry')"
|
||||
>
|
||||
<template #content>
|
||||
<div class="font-size-12px">
|
||||
支持
|
||||
<span
|
||||
class="pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
||||
style="background-color: #3c4efd"
|
||||
>DeepSeek-V3</span
|
||||
>
|
||||
<span
|
||||
class="ml4px pl6px pr6px pt4px pb2px color-black border-rd-full font-size-0.8em"
|
||||
style="background-color: #fff"
|
||||
>GPT-4o mini</span
|
||||
>
|
||||
<span
|
||||
class="ml4px pl6px pr6px pt4px pb2px color-white border-rd-full font-size-0.8em"
|
||||
style="background-color: #462ac4"
|
||||
>Qwen2.5</span
|
||||
>
|
||||
模型<br />支持多个“服务商-模型”组合按权重搭配使用
|
||||
</div>
|
||||
</template>
|
||||
<QuestionFilled w-1em h-1em mr10px />
|
||||
</el-tooltip>
|
||||
<TopRight w-1em h-1em mr10px />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<GlobalConfigPart />
|
||||
<hr class="group-divider" />
|
||||
<div class="group-item">
|
||||
<div class="group-title">运行数据</div>
|
||||
<div flex flex-col class="link-list">
|
||||
<RouterLink to="./StartChatRecord">开聊记录</RouterLink>
|
||||
<RouterLink to="./MarkAsNotSuitRecord">标记不合适记录</RouterLink>
|
||||
<RouterLink to="./JobLibrary">职位库</RouterLink>
|
||||
<RouterLink to="./BossLibrary">BOSS库</RouterLink>
|
||||
<RouterLink to="./CompanyLibrary">公司库</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
<RunDataRecordPart />
|
||||
</div>
|
||||
<div class="pt-16px pb-16px flex-0 font-size-12px">
|
||||
<div v-if="updateStore.availableNewRelease" mb16px>
|
||||
@@ -204,12 +62,12 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { TopRight, QuestionFilled } from '@element-plus/icons-vue'
|
||||
import useBuildInfo from '@renderer/hooks/useBuildInfo'
|
||||
import { debounce } from 'lodash'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import { useUpdateStore, useTaskManagerStore } from '../../store/index'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import BossPart from './LeftNavBar/BossPart.vue'
|
||||
import GlobalConfigPart from './LeftNavBar/GlabalConfigPart.vue'
|
||||
import RunDataRecordPart from './LeftNavBar/RunDataRecordPart.vue'
|
||||
|
||||
useRouter()
|
||||
|
||||
@@ -223,17 +81,6 @@ const handleGotoProjectPageClick = () => {
|
||||
electron.ipcRenderer.send('open-external-link', 'https://github.com/geekgeekrun/geekgeekrun')
|
||||
}
|
||||
|
||||
const handleLaunchBossSite = debounce(
|
||||
async () => {
|
||||
gtagRenderer('launch_boss_site_clicked')
|
||||
return await electron.ipcRenderer.invoke('open-site-with-boss-cookie', {
|
||||
url: `https://www.zhipin.com/`
|
||||
})
|
||||
},
|
||||
1000,
|
||||
{ leading: true, trailing: false }
|
||||
)
|
||||
|
||||
const updateStore = useUpdateStore()
|
||||
function handleDownloadNewReleaseClick() {
|
||||
gtagRenderer('click_download_release_form_nav')
|
||||
@@ -246,41 +93,6 @@ function handleViewNewReleaseClick() {
|
||||
|
||||
const taskManagerStore = useTaskManagerStore()
|
||||
void taskManagerStore
|
||||
|
||||
const handleClickLaunchBossLogin = async () => {
|
||||
gtagRenderer('launch_login_clicked')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('login-with-cookie-assistant')
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '登录凭据保存成功'
|
||||
})
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickBrowserSetting = async () => {
|
||||
gtagRenderer('browser_setting_clicked')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('config-with-browser-assistant')
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '浏览器偏好保存成功'
|
||||
})
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickConfigLlm = async () => {
|
||||
gtagRenderer('config_llm_clicked')
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('llm-config')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -296,32 +108,6 @@ const handleClickConfigLlm = async () => {
|
||||
margin-bottom: 4px;
|
||||
margin-right: 0;
|
||||
}
|
||||
.group-item {
|
||||
.group-title {
|
||||
color: #849492;
|
||||
font-size: 12px;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
.link-list {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 2em;
|
||||
box-sizing: border-box;
|
||||
padding-left: 1em;
|
||||
font-size: 14px;
|
||||
&.router-link-active {
|
||||
background-color: #fff;
|
||||
font-weight: 700;
|
||||
color: #2faa9e;
|
||||
border-radius: 9999px 0 0 9999px;
|
||||
position: relative;
|
||||
box-shadow: 0px 0px 10px rgba(50, 114, 108, 0.187);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.feedback-button-area,
|
||||
.update-button-area {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -59,6 +59,13 @@ const routes: Array<RouteRecordRaw> = [
|
||||
title: '已读不回自动复聊 大语言模型测试'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/commonJobConditionConfig',
|
||||
component: () => import('@renderer/page/CommonJobConditionConfig/index.vue'),
|
||||
meta: {
|
||||
title: '公共职位筛选条件'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/main-layout',
|
||||
component: () => import('@renderer/page/MainLayout/index.vue'),
|
||||
@@ -156,7 +163,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '/',
|
||||
component: BootstrapSplash,
|
||||
meta: {
|
||||
title: '薪想事成'
|
||||
title: '你的职场大机密'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -22,13 +22,13 @@ export const useUpdateStore = defineStore('update', () => {
|
||||
|
||||
export const useTaskManagerStore = defineStore('taskManager', () => {
|
||||
const runningTasks = ref<unknown[]>([])
|
||||
function getRunningTasks() {
|
||||
async function getRunningTasks() {
|
||||
const { ipcRenderer } = electron
|
||||
ipcRenderer.invoke('get-task-manager-list').then(res => {
|
||||
runningTasks.value = res.workers ?? []
|
||||
})
|
||||
const res = await ipcRenderer.invoke('get-task-manager-list')
|
||||
runningTasks.value = res.workers ?? []
|
||||
}
|
||||
const throttledGetRunningTasks = throttle(getRunningTasks, 2000)
|
||||
setInterval(throttledGetRunningTasks, 2 * 1000)
|
||||
getRunningTasks()
|
||||
return { runningTasks, getRunningTasks: throttledGetRunningTasks }
|
||||
})
|
||||
|
||||
16
packages/utils/number.mjs
Normal file
16
packages/utils/number.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
export function hasIntersection(interval1, interval2) {
|
||||
// 通用函数:将区间标准化,null转换为对应的无穷大
|
||||
const normalizeInterval = (interval) => {
|
||||
const [start, end] = interval;
|
||||
return [
|
||||
[null, undefined].includes(start) ? -Infinity : start,
|
||||
[null, undefined].includes(end) ? Infinity : end
|
||||
];
|
||||
};
|
||||
|
||||
const [norm1Start, norm1End] = normalizeInterval(interval1);
|
||||
const [norm2Start, norm2End] = normalizeInterval(interval2);
|
||||
|
||||
// 判断交集
|
||||
return Math.max(norm1Start, norm2Start) <= Math.min(norm1End, norm2End);
|
||||
}
|
||||
359
pnpm-lock.yaml
generated
359
pnpm-lock.yaml
generated
@@ -141,6 +141,9 @@ importers:
|
||||
'@types/lodash':
|
||||
specifier: ^4.17.23
|
||||
version: 4.17.23
|
||||
node-gyp:
|
||||
specifier: ^12.2.0
|
||||
version: 12.2.0
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@18.19.15)(typescript@5.3.3)
|
||||
@@ -1119,7 +1122,7 @@ packages:
|
||||
'@antfu/install-pkg': 0.1.1
|
||||
'@antfu/utils': 0.7.7
|
||||
'@iconify/types': 2.0.0
|
||||
debug: 4.3.4
|
||||
debug: 4.4.3
|
||||
kolorist: 1.8.0
|
||||
local-pkg: 0.5.0
|
||||
mlly: 1.5.0
|
||||
@@ -1299,6 +1302,18 @@ packages:
|
||||
'@types/node': 18.19.9
|
||||
dev: true
|
||||
|
||||
/@isaacs/balanced-match@4.0.1:
|
||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
dev: true
|
||||
|
||||
/@isaacs/brace-expansion@5.0.1:
|
||||
resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
dependencies:
|
||||
'@isaacs/balanced-match': 4.0.1
|
||||
dev: true
|
||||
|
||||
/@isaacs/cliui@8.0.2:
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1310,6 +1325,13 @@ packages:
|
||||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: /wrap-ansi@7.0.0
|
||||
|
||||
/@isaacs/fs-minipass@4.0.1:
|
||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
/@jridgewell/gen-mapping@0.3.3:
|
||||
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -1411,6 +1433,26 @@ packages:
|
||||
fastq: 1.17.1
|
||||
dev: true
|
||||
|
||||
/@npmcli/agent@4.0.0:
|
||||
resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
lru-cache: 11.2.5
|
||||
socks-proxy-agent: 8.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@npmcli/fs@5.0.0:
|
||||
resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
semver: 7.7.3
|
||||
dev: true
|
||||
|
||||
/@pkgjs/parseargs@0.11.0:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1773,7 +1815,7 @@ packages:
|
||||
graphemer: 1.4.0
|
||||
ignore: 5.3.1
|
||||
natural-compare: 1.4.0
|
||||
semver: 7.6.0
|
||||
semver: 7.7.3
|
||||
ts-api-utils: 1.2.1(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
@@ -2258,7 +2300,7 @@ packages:
|
||||
'@vue/compiler-dom': 3.4.18
|
||||
'@vue/shared': 3.4.18
|
||||
computeds: 0.0.1
|
||||
minimatch: 9.0.3
|
||||
minimatch: 9.0.5
|
||||
muggle-string: 0.3.1
|
||||
path-browserify: 1.0.1
|
||||
typescript: 5.3.3
|
||||
@@ -2338,6 +2380,11 @@ packages:
|
||||
through: 2.3.8
|
||||
dev: false
|
||||
|
||||
/abbrev@4.0.0:
|
||||
resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dev: true
|
||||
|
||||
/abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
@@ -2491,7 +2538,7 @@ packages:
|
||||
minimatch: 5.1.6
|
||||
read-config-file: 6.3.2
|
||||
sanitize-filename: 1.6.3
|
||||
semver: 7.6.0
|
||||
semver: 7.7.3
|
||||
tar: 6.2.0
|
||||
temp-file: 3.4.0
|
||||
transitivePeerDependencies:
|
||||
@@ -2791,6 +2838,23 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/cacache@20.0.3:
|
||||
resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
'@npmcli/fs': 5.0.0
|
||||
fs-minipass: 3.0.3
|
||||
glob: 13.0.1
|
||||
lru-cache: 11.2.5
|
||||
minipass: 7.1.2
|
||||
minipass-collect: 2.0.1
|
||||
minipass-flush: 1.0.5
|
||||
minipass-pipeline: 1.2.4
|
||||
p-map: 7.0.4
|
||||
ssri: 13.0.0
|
||||
unique-filename: 5.0.0
|
||||
dev: true
|
||||
|
||||
/cacheable-lookup@5.0.4:
|
||||
resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
|
||||
engines: {node: '>=10.6.0'}
|
||||
@@ -2908,6 +2972,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/chownr@3.0.0:
|
||||
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||
engines: {node: '>=18'}
|
||||
dev: true
|
||||
|
||||
/chromium-bidi@8.0.0(devtools-protocol@0.0.1495869):
|
||||
resolution: {integrity: sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==}
|
||||
peerDependencies:
|
||||
@@ -2995,6 +3064,7 @@ packages:
|
||||
/color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
@@ -3003,6 +3073,7 @@ packages:
|
||||
|
||||
/color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
requiresBuild: true
|
||||
|
||||
/colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
@@ -3038,7 +3109,7 @@ packages:
|
||||
/config-file-ts@0.2.6:
|
||||
resolution: {integrity: sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==}
|
||||
dependencies:
|
||||
glob: 10.3.10
|
||||
glob: 10.5.0
|
||||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
@@ -3538,10 +3609,19 @@ packages:
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
requiresBuild: true
|
||||
|
||||
/emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
/encoding@0.1.13:
|
||||
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/end-of-stream@1.4.4:
|
||||
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||
dependencies:
|
||||
@@ -3842,6 +3922,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/exponential-backoff@3.1.3:
|
||||
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
|
||||
dev: true
|
||||
|
||||
/external-editor@3.1.0:
|
||||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3912,6 +3996,18 @@ packages:
|
||||
dependencies:
|
||||
pend: 1.2.0
|
||||
|
||||
/fdir@6.5.0(picomatch@4.0.3):
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
dependencies:
|
||||
picomatch: 4.0.3
|
||||
dev: true
|
||||
|
||||
/fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
@@ -4072,6 +4168,13 @@ packages:
|
||||
minipass: 3.3.6
|
||||
dev: true
|
||||
|
||||
/fs-minipass@3.0.3:
|
||||
resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
@@ -4170,18 +4273,6 @@ packages:
|
||||
is-glob: 4.0.3
|
||||
dev: true
|
||||
|
||||
/glob@10.3.10:
|
||||
resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
foreground-child: 3.1.1
|
||||
jackspeak: 2.3.6
|
||||
minimatch: 9.0.3
|
||||
minipass: 7.0.4
|
||||
path-scurry: 1.10.1
|
||||
dev: true
|
||||
|
||||
/glob@10.5.0:
|
||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||
hasBin: true
|
||||
@@ -4192,7 +4283,15 @@ packages:
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
dev: false
|
||||
|
||||
/glob@13.0.1:
|
||||
resolution: {integrity: sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==}
|
||||
engines: {node: 20 || >=22}
|
||||
dependencies:
|
||||
minimatch: 10.1.2
|
||||
minipass: 7.1.2
|
||||
path-scurry: 2.0.1
|
||||
dev: true
|
||||
|
||||
/glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
@@ -4434,7 +4533,7 @@ packages:
|
||||
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
ms: 2.1.3
|
||||
dev: false
|
||||
|
||||
/iconv-corefoundation@1.1.7:
|
||||
@@ -4550,6 +4649,7 @@ packages:
|
||||
/is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
requiresBuild: true
|
||||
|
||||
/is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
@@ -4609,27 +4709,22 @@ packages:
|
||||
/isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
/isexe@3.1.4:
|
||||
resolution: {integrity: sha512-jCErc4h4RnTPjFq53G4whhjAMbUAqinGrCrTT4dmMNyi4zTthK+wphqbRLJtL4BN/Mq7Zzltr0m/b1X0m7PGFQ==}
|
||||
engines: {node: '>=20'}
|
||||
dev: true
|
||||
|
||||
/isobject@3.0.1:
|
||||
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/jackspeak@2.3.6:
|
||||
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
|
||||
engines: {node: '>=14'}
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
dev: true
|
||||
|
||||
/jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
dev: false
|
||||
|
||||
/jake@10.8.7:
|
||||
resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==}
|
||||
@@ -4815,6 +4910,11 @@ packages:
|
||||
resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
|
||||
/lru-cache@11.2.5:
|
||||
resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==}
|
||||
engines: {node: 20 || >=22}
|
||||
dev: true
|
||||
|
||||
/lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
dependencies:
|
||||
@@ -4846,6 +4946,25 @@ packages:
|
||||
/make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
/make-fetch-happen@15.0.3:
|
||||
resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
'@npmcli/agent': 4.0.0
|
||||
cacache: 20.0.3
|
||||
http-cache-semantics: 4.1.1
|
||||
minipass: 7.1.2
|
||||
minipass-fetch: 5.0.1
|
||||
minipass-flush: 1.0.5
|
||||
minipass-pipeline: 1.2.4
|
||||
negotiator: 1.0.0
|
||||
proc-log: 6.1.0
|
||||
promise-retry: 2.0.1
|
||||
ssri: 13.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/matcher@3.0.0:
|
||||
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4922,6 +5041,13 @@ packages:
|
||||
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/minimatch@10.1.2:
|
||||
resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==}
|
||||
engines: {node: 20 || >=22}
|
||||
dependencies:
|
||||
'@isaacs/brace-expansion': 5.0.1
|
||||
dev: true
|
||||
|
||||
/minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
@@ -4946,11 +5072,49 @@ packages:
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
dev: false
|
||||
|
||||
/minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
/minipass-collect@2.0.1:
|
||||
resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
/minipass-fetch@5.0.1:
|
||||
resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
minipass-sized: 2.0.0
|
||||
minizlib: 3.1.0
|
||||
optionalDependencies:
|
||||
encoding: 0.1.13
|
||||
dev: true
|
||||
|
||||
/minipass-flush@1.0.5:
|
||||
resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
dev: true
|
||||
|
||||
/minipass-pipeline@1.2.4:
|
||||
resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
dev: true
|
||||
|
||||
/minipass-sized@2.0.0:
|
||||
resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
/minipass@3.3.6:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4963,15 +5127,9 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/minipass@7.0.4:
|
||||
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dev: true
|
||||
|
||||
/minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dev: false
|
||||
|
||||
/minizlib@2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
@@ -4981,6 +5139,13 @@ packages:
|
||||
yallist: 4.0.0
|
||||
dev: true
|
||||
|
||||
/minizlib@3.1.0:
|
||||
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
|
||||
engines: {node: '>= 18'}
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
/mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
dev: false
|
||||
@@ -5053,6 +5218,11 @@ packages:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
||||
/negotiator@1.0.0:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/netmask@2.0.2:
|
||||
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
@@ -5100,6 +5270,25 @@ packages:
|
||||
formdata-polyfill: 4.0.10
|
||||
dev: false
|
||||
|
||||
/node-gyp@12.2.0:
|
||||
resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
exponential-backoff: 3.1.3
|
||||
graceful-fs: 4.2.11
|
||||
make-fetch-happen: 15.0.3
|
||||
nopt: 9.0.0
|
||||
proc-log: 6.1.0
|
||||
semver: 7.7.3
|
||||
tar: 7.5.7
|
||||
tinyglobby: 0.2.15
|
||||
which: 6.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/node-machine-id@1.1.12:
|
||||
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
|
||||
dev: false
|
||||
@@ -5108,6 +5297,14 @@ packages:
|
||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||
dev: true
|
||||
|
||||
/nopt@9.0.0:
|
||||
resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
abbrev: 4.0.0
|
||||
dev: true
|
||||
|
||||
/normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -5226,6 +5423,11 @@ packages:
|
||||
p-limit: 3.1.0
|
||||
dev: true
|
||||
|
||||
/p-map@7.0.4:
|
||||
resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==}
|
||||
engines: {node: '>=18'}
|
||||
dev: true
|
||||
|
||||
/pac-proxy-agent@7.2.0:
|
||||
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -5250,7 +5452,6 @@ packages:
|
||||
|
||||
/package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
dev: false
|
||||
|
||||
/parent-module@1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
@@ -5316,21 +5517,20 @@ packages:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
|
||||
/path-scurry@1.10.1:
|
||||
resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
lru-cache: 10.2.0
|
||||
minipass: 7.0.4
|
||||
dev: true
|
||||
|
||||
/path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
dependencies:
|
||||
lru-cache: 10.2.0
|
||||
minipass: 7.1.2
|
||||
dev: false
|
||||
|
||||
/path-scurry@2.0.1:
|
||||
resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
|
||||
engines: {node: 20 || >=22}
|
||||
dependencies:
|
||||
lru-cache: 11.2.5
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
/path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
@@ -5355,6 +5555,11 @@ packages:
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/pinia@3.0.2(typescript@5.3.3)(vue@3.4.15):
|
||||
resolution: {integrity: sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==}
|
||||
peerDependencies:
|
||||
@@ -5461,6 +5666,11 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/proc-log@6.1.0:
|
||||
resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dev: true
|
||||
|
||||
/progress@2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -5937,7 +6147,7 @@ packages:
|
||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
semver: 7.6.0
|
||||
semver: 7.7.3
|
||||
dev: true
|
||||
|
||||
/sirv@2.0.4:
|
||||
@@ -6030,6 +6240,13 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
dev: false
|
||||
|
||||
/ssri@13.0.0:
|
||||
resolution: {integrity: sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
dev: true
|
||||
|
||||
/stat-mode@1.0.0:
|
||||
resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -6204,6 +6421,17 @@ packages:
|
||||
yallist: 4.0.0
|
||||
dev: true
|
||||
|
||||
/tar@7.5.7:
|
||||
resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
chownr: 3.0.0
|
||||
minipass: 7.1.2
|
||||
minizlib: 3.1.0
|
||||
yallist: 5.0.0
|
||||
dev: true
|
||||
|
||||
/temp-file@3.4.0:
|
||||
resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==}
|
||||
dependencies:
|
||||
@@ -6254,6 +6482,14 @@ packages:
|
||||
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
|
||||
dev: false
|
||||
|
||||
/tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
dev: true
|
||||
|
||||
/tmp-promise@3.0.3:
|
||||
resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
|
||||
dependencies:
|
||||
@@ -6518,6 +6754,20 @@ packages:
|
||||
- rollup
|
||||
dev: true
|
||||
|
||||
/unique-filename@5.0.0:
|
||||
resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
unique-slug: 6.0.0
|
||||
dev: true
|
||||
|
||||
/unique-slug@6.0.0:
|
||||
resolution: {integrity: sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
dependencies:
|
||||
imurmurhash: 0.1.4
|
||||
dev: true
|
||||
|
||||
/universalify@0.1.2:
|
||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
@@ -6761,7 +7011,7 @@ packages:
|
||||
espree: 9.6.1
|
||||
esquery: 1.5.0
|
||||
lodash: 4.17.21
|
||||
semver: 7.6.0
|
||||
semver: 7.7.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -6872,6 +7122,14 @@ packages:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
/which@6.0.0:
|
||||
resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
isexe: 3.1.4
|
||||
dev: true
|
||||
|
||||
/wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6935,6 +7193,11 @@ packages:
|
||||
/yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
||||
/yallist@5.0.0:
|
||||
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||
engines: {node: '>=18'}
|
||||
dev: true
|
||||
|
||||
/yargs-parser@20.2.9:
|
||||
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
Reference in New Issue
Block a user