mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-05-31 21:21:15 +08:00
visualize prerequisite check steps
This commit is contained in:
@@ -149,10 +149,10 @@ function handleMessage(socket, message) {
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'worker-message': {
|
||||
case 'worker-to-gui-message': {
|
||||
// 转发工具进程消息到GUI客户端
|
||||
broadcastToGUI({
|
||||
type: 'worker-message',
|
||||
type: 'worker-to-gui-message',
|
||||
workerId: workerId,
|
||||
data: message.data || message,
|
||||
timestamp: Date.now()
|
||||
|
||||
17
packages/ui/src/common/prerequisite-step-by-step-check.ts
Normal file
17
packages/ui/src/common/prerequisite-step-by-step-check.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const getAutoStartChatSteps = () => [{
|
||||
id: 'worker-launch',
|
||||
describe: '启动子进程',
|
||||
},
|
||||
// {
|
||||
// id: 'basic-cookie-check',
|
||||
// describe: 'Cookie 格式检查',
|
||||
// },
|
||||
{
|
||||
id: 'puppeteer-executable-check',
|
||||
describe: 'Puppeteer 可执行程序检查',
|
||||
},
|
||||
{
|
||||
id: 'login-status-check',
|
||||
describe: '登录状态检查',
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
import { sendToDaemon } from "../flow/OPEN_SETTING_WINDOW/connect-to-daemon"
|
||||
import minimist from 'minimist'
|
||||
|
||||
const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null
|
||||
export function pushUserInfoValidStatus (userInfoResponse) {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: userInfoResponse.code === 0 ? 'fulfilled' : 'rejected'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export class UserResponseInfoPlugin {
|
||||
apply(hooks) {
|
||||
hooks.userInfoResponse.tapPromise(
|
||||
"UserResponseInfoPlugin",
|
||||
(userInfoResponse) => {
|
||||
pushUserInfoValidStatus(userInfoResponse)
|
||||
return Promise.resolve()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,33 +3,37 @@ import { sendToDaemon } from "../flow/OPEN_SETTING_WINDOW/connect-to-daemon"
|
||||
import { saveAndGetCurrentRunRecord } from "../flow/OPEN_SETTING_WINDOW/utils/db"
|
||||
|
||||
export async function runCommon ({ mode }) {
|
||||
const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE: [
|
||||
AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE,
|
||||
AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID,
|
||||
AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE
|
||||
].join(',')
|
||||
const currentRunRecord = (await saveAndGetCurrentRunRecord())?.data
|
||||
const subProcessEnv = {
|
||||
...process.env,
|
||||
GEEKGEEKRUND_NO_AUTO_RESTART_EXIT_CODE: [
|
||||
AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE,
|
||||
AUTO_CHAT_ERROR_EXIT_CODE.LOGIN_STATUS_INVALID,
|
||||
AUTO_CHAT_ERROR_EXIT_CODE.LLM_UNAVAILABLE
|
||||
].join(',')
|
||||
}
|
||||
const args = process.env.NODE_ENV === 'development' ? [
|
||||
process.argv[1],
|
||||
`--mode=${mode}`,
|
||||
`--run-record-id=${currentRunRecord?.id || 0}`
|
||||
] : [
|
||||
`--mode=${mode}`,
|
||||
`--run-record-id=${currentRunRecord?.id || 0}`
|
||||
]
|
||||
await sendToDaemon(
|
||||
{
|
||||
type: 'start-worker',
|
||||
workerId: mode,
|
||||
command: process.argv[0],
|
||||
args,
|
||||
env: subProcessEnv
|
||||
},
|
||||
{
|
||||
needCallback: true
|
||||
}
|
||||
const args = process.env.NODE_ENV === 'development' ? [
|
||||
process.argv[1],
|
||||
`--mode=${mode}`,
|
||||
`--run-record-id=${currentRunRecord?.id || 0}`
|
||||
] : [
|
||||
`--mode=${mode}`,
|
||||
`--run-record-id=${currentRunRecord?.id || 0}`
|
||||
]
|
||||
await sendToDaemon(
|
||||
{
|
||||
type: 'start-worker',
|
||||
workerId: mode,
|
||||
command: process.argv[0],
|
||||
args,
|
||||
env: subProcessEnv
|
||||
},
|
||||
{
|
||||
needCallback: true
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
runRecordId: currentRunRecord?.id
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,14 @@ import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPEND
|
||||
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
|
||||
import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../../common/enums/auto-start-chat'
|
||||
import attachListenerForKillSelfOnParentExited from '../../utils/attachListenerForKillSelfOnParentExited'
|
||||
|
||||
import minimist from 'minimist'
|
||||
import SqlitePluginModule from '@geekgeekrun/sqlite-plugin'
|
||||
import gtag from '../../utils/gtag'
|
||||
import GtagPlugin from '../../utils/gtag/GtagPlugin'
|
||||
import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to-daemon'
|
||||
import { PeriodPushCurrentPageScreenshotPlugin } from '../../utils/screenshot'
|
||||
import { checkShouldExit } from '../../utils/worker'
|
||||
import { UserResponseInfoPlugin } from '../../features/boss-user-info-response-plugin'
|
||||
const { default: SqlitePlugin } = SqlitePluginModule
|
||||
|
||||
const rerunInterval = (() => {
|
||||
@@ -35,15 +36,39 @@ const initPlugins = (hooks) => {
|
||||
new SqlitePlugin(getPublicDbFilePath()).apply(hooks)
|
||||
new GtagPlugin().apply(hooks)
|
||||
new PeriodPushCurrentPageScreenshotPlugin().apply(hooks)
|
||||
new UserResponseInfoPlugin().apply(hooks)
|
||||
}
|
||||
|
||||
const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null
|
||||
const runAutoChat = async () => {
|
||||
app.dock?.hide()
|
||||
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
|
||||
if (!puppeteerExecutable) {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'rejected'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
app.exit(AUTO_CHAT_ERROR_EXIT_CODE.PUPPETEER_IS_NOT_EXECUTABLE)
|
||||
return
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'fulfilled'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
process.env.PUPPETEER_EXECUTABLE_PATH = puppeteerExecutable.executablePath
|
||||
const { initPuppeteer, mainLoop, closeBrowserWindow, autoStartChatEventBus } = await import(
|
||||
'@geekgeekrun/geek-auto-start-chat-with-boss/index.mjs'
|
||||
@@ -131,6 +156,17 @@ export const waitForProcessHandShakeAndRunAutoChat = async () => {
|
||||
needCallback: true
|
||||
}
|
||||
)
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'worker-launch',
|
||||
status: 'fulfilled'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
runAutoChat()
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ export default function initIpc() {
|
||||
|
||||
ipcMain.handle('run-geek-auto-start-chat-with-boss', async (ev) => {
|
||||
const mode = 'geekAutoStartWithBossMain'
|
||||
await runCommon({ mode })
|
||||
const { runRecordId } = await runCommon({ mode })
|
||||
daemonEE.on('message', function handler (message) {
|
||||
if (message.workerId !== mode) {
|
||||
return
|
||||
@@ -226,11 +226,12 @@ export default function initIpc() {
|
||||
}
|
||||
}
|
||||
})
|
||||
return { runRecordId }
|
||||
})
|
||||
|
||||
ipcMain.handle('run-read-no-reply-auto-reminder', async () => {
|
||||
const mode = 'readNoReplyAutoReminderMain'
|
||||
await runCommon({ mode })
|
||||
const { runRecordId } = await runCommon({ mode })
|
||||
daemonEE.on('message', function handler (message) {
|
||||
if (message.workerId !== mode) {
|
||||
return
|
||||
@@ -254,6 +255,7 @@ export default function initIpc() {
|
||||
}
|
||||
}
|
||||
})
|
||||
return { runRecordId }
|
||||
})
|
||||
|
||||
ipcMain.handle('check-dependencies', async () => {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { connectToDaemon, sendToDaemon } from '../OPEN_SETTING_WINDOW/connect-to
|
||||
import { pushCurrentPageScreenshot, SCREENSHOT_INTERVAL_MS } from '../../utils/screenshot'
|
||||
import { checkShouldExit } from '../../utils/worker'
|
||||
import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
|
||||
import minimist from 'minimist'
|
||||
|
||||
const throttleIntervalMinutes =
|
||||
readConfigFile('boss.json').autoReminder?.throttleIntervalMinutes ?? 10
|
||||
@@ -249,12 +250,34 @@ const mainLoop = async () => {
|
||||
// #region
|
||||
if (currentPageUrl.startsWith('https://www.zhipin.com/web/user/')) {
|
||||
writeStorageFile('boss-cookies.json', [])
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'rejected'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
throw new Error('LOGIN_STATUS_INVALID')
|
||||
}
|
||||
if (
|
||||
currentPageUrl.startsWith('https://www.zhipin.com/web/common/403.html') ||
|
||||
currentPageUrl.startsWith('https://www.zhipin.com/web/common/error.html')
|
||||
) {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'rejected'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
throw new Error('ACCESS_IS_DENIED')
|
||||
}
|
||||
if (currentPageUrl.startsWith('https://www.zhipin.com/web/user/safe/verify-slider')) {
|
||||
@@ -277,9 +300,31 @@ const mainLoop = async () => {
|
||||
})
|
||||
if (validateRes.code === 0) {
|
||||
await storeStorage(pageMapByName.boss)
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'rejected'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
throw new Error('CAPTCHA_PASSED_AND_NEED_RESTART')
|
||||
}
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'login-status-check',
|
||||
status: 'fulfilled'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
// #endregion
|
||||
// check set security question tip modal
|
||||
let setSecurityQuestionTipModelProxy = await pageMapByName.boss!.$(
|
||||
@@ -478,19 +523,53 @@ const rerunInterval = (() => {
|
||||
return v
|
||||
})()
|
||||
|
||||
const runRecordId = minimist(process.argv.slice(2))['run-record-id'] ?? null
|
||||
export async function runEntry() {
|
||||
app.dock?.hide()
|
||||
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
|
||||
if (!puppeteerExecutable) {
|
||||
throw new Error(`PUPPETEER_IS_NOT_EXECUTABLE`)
|
||||
}
|
||||
process.env.PUPPETEER_EXECUTABLE_PATH = puppeteerExecutable.executablePath
|
||||
await connectToDaemon()
|
||||
await sendToDaemon({
|
||||
type: 'ping'
|
||||
}, {
|
||||
needCallback: true
|
||||
})
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'worker-launch',
|
||||
status: 'fulfilled'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
|
||||
if (!puppeteerExecutable) {
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'rejected'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
throw new Error(`PUPPETEER_IS_NOT_EXECUTABLE`)
|
||||
}
|
||||
sendToDaemon({
|
||||
type: 'worker-to-gui-message',
|
||||
data: {
|
||||
type: 'prerequisite-step-by-step-checkstep-by-step-check',
|
||||
step: {
|
||||
id: 'puppeteer-executable-check',
|
||||
status: 'fulfilled'
|
||||
},
|
||||
runRecordId
|
||||
}
|
||||
})
|
||||
process.env.PUPPETEER_EXECUTABLE_PATH = puppeteerExecutable.executablePath
|
||||
while (true) {
|
||||
try {
|
||||
await mainLoop()
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from 'path'
|
||||
import { openDevTools } from '../commands'
|
||||
import { createFirstLaunchNoticeWindow } from './firstLaunchNoticeWindow'
|
||||
import { isFirstLaunchNoticeApproveFlagExist } from '../features/first-launch-notice-window'
|
||||
import { daemonEE } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon'
|
||||
export let mainWindow: BrowserWindow | null = null
|
||||
|
||||
export function createMainWindow(): BrowserWindow {
|
||||
@@ -58,5 +59,10 @@ export function createMainWindow(): BrowserWindow {
|
||||
mainWindow!.once('closed', () => {
|
||||
mainWindow = null
|
||||
})
|
||||
daemonEE.on('message', (message) => {
|
||||
if (message.type === 'worker-to-gui-message') {
|
||||
mainWindow?.webContents?.send('worker-to-gui-message', message)
|
||||
}
|
||||
})
|
||||
return mainWindow!
|
||||
}
|
||||
|
||||
@@ -6,8 +6,22 @@
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<!-- v-if="stepsForRender.some(it => ['todo', 'pending', 'rejected'].includes(it.status))" -->
|
||||
<div>
|
||||
<ul m0 pl0>
|
||||
<li list-style-none v-for="item in stepsForRender" 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>任务运行中!</div>
|
||||
<div>任务运行中</div>
|
||||
<div>
|
||||
<slot name="op-buttons" />
|
||||
</div>
|
||||
@@ -17,20 +31,68 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTaskManagerStore } from '@renderer/store'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { getAutoStartChatSteps } from '../../../../common/prerequisite-step-by-step-check'
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
const props = defineProps({
|
||||
workerId: {
|
||||
type: String
|
||||
},
|
||||
runRecordId: {
|
||||
type: Number
|
||||
}
|
||||
})
|
||||
|
||||
const taskManagerStore = useTaskManagerStore()
|
||||
const runingTaskInfo = 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)
|
||||
)
|
||||
|
||||
if (clonedSteps.some(it => it.status === 'rejected')) {
|
||||
return clonedSteps
|
||||
}
|
||||
const lastFulfilledIndex = clonedSteps.findLastIndex(it => it.status === 'fulfilled')
|
||||
if (lastFulfilledIndex + 1 < clonedSteps.length) {
|
||||
clonedSteps[lastFulfilledIndex + 1].status = 'pending'
|
||||
}
|
||||
return clonedSteps
|
||||
})
|
||||
function fillEmptySteps () {
|
||||
const arr = getAutoStartChatSteps()
|
||||
arr.forEach(it => it.status = 'todo')
|
||||
steps.value = arr
|
||||
}
|
||||
watch(
|
||||
() => props.runRecordId,
|
||||
fillEmptySteps,
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
const { ipcRenderer } = electron
|
||||
|
||||
function messageHandler (ev, { data }) {
|
||||
if (
|
||||
data.type !== 'prerequisite-step-by-step-checkstep-by-step-check' ||
|
||||
data.runRecordId !== props.runRecordId
|
||||
) {
|
||||
return
|
||||
}
|
||||
const { id: stepId, status: stepStatus } = data.step
|
||||
const targetStep = steps.value.find(it => it.id === stepId)
|
||||
if (!targetStep) {
|
||||
return
|
||||
}
|
||||
targetStep.status = stepStatus
|
||||
}
|
||||
const unListenMessage = ipcRenderer.on('worker-to-gui-message', messageHandler)
|
||||
onUnmounted(unListenMessage)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1062,7 +1062,7 @@
|
||||
pointerEvents: 'none'
|
||||
}"
|
||||
>
|
||||
<RuningOverlay worker-id="geekAutoStartWithBossMain">
|
||||
<RuningOverlay worker-id="geekAutoStartWithBossMain" :run-record-id="runRecordId">
|
||||
<template #op-buttons>
|
||||
<el-button @click="handleStopButtonClick">结束任务</el-button>
|
||||
</template>
|
||||
@@ -1431,6 +1431,7 @@ const formRules = {
|
||||
}
|
||||
|
||||
const formRef = ref<InstanceType<typeof ElForm>>()
|
||||
const runRecordId = ref(null)
|
||||
const handleSubmit = async () => {
|
||||
gtagRenderer('save_config_and_launch_clicked', {
|
||||
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken,
|
||||
@@ -1469,7 +1470,8 @@ const handleSubmit = async () => {
|
||||
})
|
||||
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('run-geek-auto-start-chat-with-boss')
|
||||
const { runRecordId: rrId } = await electron.ipcRenderer.invoke('run-geek-auto-start-chat-with-boss')
|
||||
runRecordId.value = rrId
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
|
||||
gtagRenderer('gascwb_cannot_run_for_corrupt')
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
pointerEvents: 'none'
|
||||
}"
|
||||
>
|
||||
<RuningOverlay worker-id="readNoReplyAutoReminderMain">
|
||||
<RuningOverlay worker-id="readNoReplyAutoReminderMain" :run-record-id="runRecordId">
|
||||
<template #op-buttons>
|
||||
<el-button @click="handleStopButtonClick">结束任务</el-button>
|
||||
</template>
|
||||
@@ -449,7 +449,7 @@ async function checkIsCanRun() {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const runRecordId = ref(null)
|
||||
const handleSubmit = async () => {
|
||||
gtagRenderer('run_read_no_reply_reminder_clicked', {
|
||||
throttle_interval_minutes: formContent.value.autoReminder.throttleIntervalMinutes,
|
||||
@@ -488,7 +488,8 @@ const handleSubmit = async () => {
|
||||
gtagRenderer('run_read_no_reply_reminder_launched')
|
||||
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('run-read-no-reply-auto-reminder')
|
||||
const { runRecordId: rrId } = await electron.ipcRenderer.invoke('run-read-no-reply-auto-reminder')
|
||||
runRecordId.value = rrId
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
|
||||
gtagRenderer('rnrr_cannot_run_for_corrupt')
|
||||
|
||||
Reference in New Issue
Block a user